@font-face{font-family:"Figtree";src:url("/assets/assets/fonts/web/Figtree_400Regular.b00b86325604c1026e0323ecf6a6c5f0.woff2") format("woff2");font-weight:400;font-display:block}
@font-face{font-family:"Figtree";src:url("/assets/assets/fonts/web/Figtree_600SemiBold.f52d7ceb6e86c1eb5c9501b063a1177c.woff2") format("woff2");font-weight:600;font-display:block}
@font-face{font-family:"Figtree";src:url("/assets/assets/fonts/web/Figtree_500Medium.b3d70a9d15541a421d93233623c9f494.woff2") format("woff2");font-weight:500;font-display:block}
@font-face{font-family:"Newsreader_500Display";src:url("/assets/assets/fonts/web/Newsreader_500Display.5370d67f76efe24cdaaf917da4201a38.woff2") format("woff2");font-display:block}
@font-face{font-family:"Newsreader_500Heading";src:url("/assets/assets/fonts/web/Newsreader_500Heading.034fc11bbf8c7ee8de6496defc293310.woff2") format("woff2");font-display:block}
@font-face{font-family:"Figtree Arial";src:local("Arial"),local("ArialMT"),local("Liberation Sans"),local("LiberationSans");font-weight:400;size-adjust:100.76%;ascent-override:94.28%;descent-override:24.81%;line-gap-override:0%}
@font-face{font-family:"Figtree Roboto";src:local("Roboto"),local("Roboto-Regular");font-weight:400;size-adjust:101.33%;ascent-override:93.75%;descent-override:24.67%;line-gap-override:0%}
@font-face{font-family:"Figtree Arial";src:local("Arial Bold"),local("Arial-BoldMT"),local("Liberation Sans Bold"),local("LiberationSans-Bold");font-weight:600;size-adjust:95.52%;ascent-override:99.45%;descent-override:26.17%;line-gap-override:0%}
@font-face{font-family:"Figtree Roboto";src:local("Roboto Bold"),local("Roboto-Bold");font-weight:600;size-adjust:104.11%;ascent-override:91.25%;descent-override:24.02%;line-gap-override:0%}
@font-face{font-family:"Figtree Arial";src:local("Arial Bold"),local("Arial-BoldMT"),local("Liberation Sans Bold"),local("LiberationSans-Bold");font-weight:500;size-adjust:93.71%;ascent-override:101.38%;descent-override:26.68%;line-gap-override:0%}
@font-face{font-family:"Figtree Roboto";src:local("Roboto"),local("Roboto-Regular");font-weight:500;size-adjust:101.61%;ascent-override:93.49%;descent-override:24.6%;line-gap-override:0%}
@font-face{font-family:Newsreader_500Display_Arial;src:local("Times New Roman"),local("TimesNewRomanPSMT"),local("Liberation Serif"),local("LiberationSerif");size-adjust:114.44%;ascent-override:64.66%;descent-override:23.59%;line-gap-override:0%}
@font-face{font-family:Newsreader_500Display_Roboto;src:local("Noto Serif"),local("NotoSerif"),local("NotoSerif-Regular");size-adjust:96.43%;ascent-override:76.74%;descent-override:28%;line-gap-override:0%}
@font-face{font-family:Newsreader_500Heading_Arial;src:local("Times New Roman"),local("TimesNewRomanPSMT"),local("Liberation Serif"),local("LiberationSerif");size-adjust:105.55%;ascent-override:70.11%;descent-override:25.58%;line-gap-override:0%}
@font-face{font-family:Newsreader_500Heading_Roboto;src:local("Noto Serif"),local("NotoSerif"),local("NotoSerif-Regular");size-adjust:89%;ascent-override:83.15%;descent-override:30.34%;line-gap-override:0%}
:root{--bg:#121712;--bg-veil:rgba(18,23,18,0.72);--surface:#1A211B;--field:#232C24;--hero-hi:#1F2820;--line:#263027;--line-strong:#34413A;--text:#ECE8DD;--text-sec:#B4BCAE;--text-mut:#929C8C;--gold:#E9A23B;--gold-text:#EDB458;--gold-tint:#263A2B;--on-gold:#1E1A10;--coral:#E9A27A;--mint:#5CCFC2;--rose:#F08A80;--sky:#92C3E8;--lav:#A9AEEA;--teal:#92C3E8;--slate:#929C8C;--heat:#DCA0D0;--info:#929C8C;--ok-text:#5CCFC2;--on-ok:#0F140F;--alert-text:#F08A80;--on-alert:#2A0F0C;--forest:#A9C9A4;--forest-wash:#263A2B;--forest-line:#3E5A44;--hero-head:#A9C9A4;--hero-sec:#C3CDBB;--hero-line:#6F8F72;--hero-btn-line:#6F8F72;--band:#1F3123;--band-text:#ECE8DD;--band-sec:#B4BCAE;--act:#A9C9A4;--act-hover:#ECE8DD;--class-lap:#E2A2D3;--class-companion:#95C4FF;--class-family:#45B3AD;--class-guardian:#9F8FD3;--class-working:#FFCDB2;--class-sport:#B0E8FF;--class-field:#AAC47C;--cat-out:#B0E8FF;--cat-care:#E7A0B4;--cat-rest:#A9AEEA;--cat-play:#E9A27A;--cat-food:#DDBB6E;--cat-health:#92C3E8;--cat-heat:#DCA0D0;--celebrate:#222A23;--celebrate-ring:rgba(235,164,58,0.38);--field-ring:#ECE8DD;--glass:rgba(26,33,27,0.84);--scrim:rgba(6,9,6,0.58);--shadow-card:none;--shadow-float:0 16px 40px rgba(0,0,0,0.45);--breed-face-ink:#B4BCAE;--breed-face-ring:#B4BCAE;--coral-text:#E9A27A;--hero-paper:#354B38;--tint-ink:#ECE8DD;}
html[data-theme="light"]{--bg:#F4EEE3;--bg-veil:rgba(244,238,227,0.7);--surface:#FFFBF3;--field:#EAE2D3;--hero-hi:#FFFBF3;--line:#E3D9C7;--line-strong:#CDBFA6;--text:#1E2A20;--text-sec:#4A5547;--text-mut:#5B6656;--gold:#E39A2A;--gold-text:#835300;--gold-tint:#E3EADF;--on-gold:#1E1A10;--coral:#9E5426;--mint:#00756F;--rose:#B03E3B;--sky:#2E6C99;--lav:#5A5FA6;--teal:#2E6C99;--slate:#5B6656;--heat:#94508A;--info:#5B6656;--ok-text:#00756F;--on-ok:#FFFFFF;--alert-text:#B03E3B;--on-alert:#FFFFFF;--forest:#2E4733;--forest-wash:#E3EADF;--forest-line:#B9C8B4;--hero-head:#2E4733;--hero-sec:#4A5547;--hero-line:#C5D0BF;--hero-btn-line:#ADBFAA;--band:#2E4733;--band-text:#F3EEE2;--band-sec:#C9D3C2;--act:#2E4733;--act-hover:#1E2A20;--class-lap:#8E4880;--class-companion:#3268AA;--class-family:#00615D;--class-guardian:#503883;--class-working:#632A00;--class-sport:#004458;--class-field:#496100;--cat-out:#004458;--cat-care:#A64B66;--cat-rest:#5A5FA6;--cat-play:#9E5426;--cat-food:#85611A;--cat-health:#2E6C99;--cat-heat:#94508A;--celebrate:#FBEBD0;--celebrate-ring:rgba(227,154,42,0.55);--field-ring:#1E2A20;--glass:rgba(255,251,243,0.82);--scrim:rgba(30,42,32,0.42);--shadow-card:0 1px 2px rgba(30,42,32,0.04), 0 2px 8px rgba(30,42,32,0.035);--shadow-float:0 12px 32px rgba(30,42,32,0.12), 0 1px 3px rgba(30,42,32,0.06);--coral-text:#9E5426;--hero-paper:#DCE1D4;--tint-ink:#1E2A20;--breed-face-ink:#4A5547;--breed-face-ring:#4A5547;}
:root{--type-display:normal 32px/36px var(--serif-display);--type-display-family:var(--serif-display);--type-display-weight:normal;--type-display-size:32px;--type-display-line:36px;--type-heading:normal 22px/27px var(--serif-heading);--type-heading-family:var(--serif-heading);--type-heading-weight:normal;--type-heading-size:22px;--type-heading-line:27px;--type-title:600 16px/21px var(--font-body);--type-title-family:var(--font-body);--type-title-weight:600;--type-title-size:16px;--type-title-line:21px;--type-body:400 16px/24px var(--font-body);--type-body-family:var(--font-body);--type-body-weight:400;--type-body-size:16px;--type-body-line:24px;--type-label:600 13.5px/18px var(--font-body);--type-label-family:var(--font-body);--type-label-weight:600;--type-label-size:13.5px;--type-label-line:18px;--type-figure:600 40px/44px var(--font-body);--type-figure-family:var(--font-body);--type-figure-weight:600;--type-figure-size:40px;--type-figure-line:44px;--type-wordmark:normal 20px/24px var(--serif-display);--type-wordmark-family:var(--serif-display);--type-wordmark-weight:normal;--type-wordmark-size:20px;--type-wordmark-line:24px;--type-strong-weight:600;--type-monogram-family:var(--font-body);--type-monogram-weight:600;--type-monogram-ratio:0.42;--type-monogram-line:1.2;}
@media (min-width: 820px){:root{--type-display:normal 46px/50px var(--serif-display);--type-display-size:46px;--type-display-line:50px;--type-heading:normal 30px/35px var(--serif-heading);--type-heading-size:30px;--type-heading-line:35px;}}
/* The standalone tool pages' one stylesheet (ruling 2026-09-26). The colour
   tokens above this file are GENERATED from constants/Colors.ts by
   scripts/build-web-tools.mjs, so the pages cannot drift from the app's
   palette. Presentation 2026: one tonal hero card (surface, radius 28, no
   border or shadow), flat sections, forest (`--act`, scheme C) for links and selections,
   gold only on the one CTA, 44px targets, sentence case. Dark is the default
   and the no-JS state, as on the landing; `html[data-theme="light"]` is the
   visitor's choice. */

/* The app's faces (constants/fonts.ts). The @font-face rules are generated
   (build-web-tools.mjs SITE_FONTS, SITE_SERIF): the Expo export's own woff2
   files, so the tools, the landing and the breed pages share one cached face.
   Each stack names the face's metric-matched fallbacks right after it, so a
   layout drawn before a face lands is the one it keeps (tools-fonts.test.ts).
   Figtree is the UI face; Newsreader speaks only in the H1 and the page's
   section heads, as on a breed page. Every size, leading, family and weight
   below is a `--type-*` variable, generated from constants/type.ts above this
   file (build-web-tools.mjs typeTokens; public-type.test.ts). */
:root {
  --font-display: "Figtree", "Figtree Arial", "Figtree Roboto", system-ui, sans-serif;
  --font-body: "Figtree", "Figtree Arial", "Figtree Roboto", system-ui, sans-serif;
  --serif-display:
    "Newsreader_500Display", Newsreader_500Display_Arial, Newsreader_500Display_Roboto, Georgia,
    serif;
  --serif-heading:
    "Newsreader_500Heading", Newsreader_500Heading_Arial, Newsreader_500Heading_Roboto, Georgia,
    serif;
  /* The shape family: components/ui/theme.ts `radii`, one radius per role
     (web-shape-tokens.test.ts holds the two equal). Buttons are controls, not
     pills; 999 is only for what is round by nature (tracks, meters). */
  --r-hero: 28px;
  --r-tile: 20px;
  --r-control: 14px;
  --r-input: 14px;
  --r-chip: 12px;
  --r-row: 8px;
  --r-badge: 6px;
  --r-print: 2px;
  --r-pill: 999px;
  --target: 44px;
  color-scheme: dark;
}
html[data-theme="light"] {
  color-scheme: light;
}

* {
  box-sizing: border-box;
}
/* The site bar stays at the top (sitenav.css, 61px): an in-page jump (a
   contents link, the matcher's scroll to a card) lands below it. */
html {
  scroll-padding-top: 69px;
}
html,
body {
  margin: 0;
  padding: 0;
}
body {
  background: var(--bg);
  color: var(--text);
  font: var(--type-body);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}
a {
  color: var(--act);
  font-weight: var(--type-strong-weight);
  text-underline-offset: 3px;
}
a:hover {
  text-decoration-thickness: 2px;
}
/* Plain text links only: a classed <a> (the gold CTA, nav, footer, ghost
   buttons) keeps its own colour on hover. */
a:not([class]):hover {
  color: var(--act-hover);
}
:focus-visible {
  outline: 2px solid var(--field-ring);
  outline-offset: 2px;
}

/* The section's one container: every public page's, the landing's (Dash,
   2026-10-19: "all the public pages should be full width, really"). Its
   content is the site bar's column, `--site-column` (1152: the landing's 1216
   measure less its gutters; sitenav.css), gutter 18 under 820px and 32 above,
   as app/breeds/* pad their scroll view and the landing pads its .wrap. Long
   running text inside it keeps `--prose-measure` (PROSE_MEASURE,
   components/web/column.ts). public-wide.test.ts holds them in step. */
:root {
  --prose-measure: 540px;
}
.page {
  box-sizing: content-box;
  max-width: var(--site-column, 1152px);
  margin: 0 auto;
  padding: 28px 18px 40px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
@media (min-width: 820px) {
  .page {
    padding-left: 32px;
    padding-right: 32px;
  }
}

/* The site bar, the page gutter and the cross-document fade are
   shared/sitenav.css, the one spec the landing carries too. */

/* Type: the app's categories (design-system/type.md). The H1 is Display and
   a section's H2 Heading, each a Newsreader cut at weight normal, stepping to
   46/50 and 30/35 at 820px; an H2 inside the tool's card heads that card, the
   app's card title, so it is Title; a sub-head H3 is Title. */
h1,
h2,
h3 {
  color: var(--text);
  margin: 0;
}
h1 {
  font: var(--type-display);
}
h2 {
  font: var(--type-heading);
}
h3,
.card h2 {
  font: var(--type-title);
}
p {
  margin: 0;
}
/* The browser's bold is 700, a weight the pages do not load (it would be
   synthesised over the 600): inline emphasis is Figtree 600. */
strong,
b,
th {
  font-weight: var(--type-strong-weight);
}
.prose p,
.intro {
  color: var(--text-sec);
  font: var(--type-body);
}
/* The measure: running text, never the page, is capped. */
.prose p,
.prose li,
.intro,
.faq-item p,
.cta p,
.disclaimer {
  max-width: var(--prose-measure);
}
.kicker {
  font: var(--type-label);
  color: var(--forest);
}
.hero {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 6px;
}
section {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Fewer boxes: prose, FAQ and the rest sit on the page, a hairline between
   sections rather than a rule beside each heading. */
.section-head {
  margin-top: 28px;
  padding-top: 28px;
  border-top: 1px solid var(--line);
}

/* The ONE hero card */
.card {
  background: var(--surface);
  border-radius: var(--r-hero);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
@media (max-width: 380px) {
  .card {
    padding: 18px;
  }
}

/* What a tool's result will hold: from 1024 only, beside the inputs (below);
   under it the one line says enough, and the page is unchanged. */
.tool-preview {
  display: none;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.tool-preview li {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font: var(--type-label);
  color: var(--text-mut);
  max-width: var(--prose-measure);
}
.tool-preview li::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--line-strong);
  transform: translateY(-2px);
}
/* A tool's inputs (.tool-in) and its result (.tool-out): one stack in the
   card under 1024, side by side from it (ruling
   2026-10-19-public-pages-take-the-landing-container), the result taking the
   wider share. The fields keep the card's 16px rhythm inside their block. */
.tool-in {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
@media (min-width: 1024px) {
  .card:has(> .tool-in) {
    flex-direction: row;
    align-items: flex-start;
    gap: 40px;
  }
  .card > .tool-in {
    flex: 5 1 0;
    min-width: 0;
  }
  .card > .tool-out {
    flex: 7 1 0;
    min-width: 0;
  }
  /* Before there is a result (shared/page.ts emptyResult) the result's half
     is its place, not a blank: the result card's outline, as tall as the
     inputs, holding the one line and what the result will show (Dash,
     2026-10-19). It is the prerender's, so nothing moves on load; the result
     replacing it follows the visitor's own input. */
  .card:has(> .tool-out > .placeholder) {
    align-items: stretch;
  }
  .card > .tool-out:has(> .placeholder) {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 12px;
    padding: 24px 28px;
    border-radius: var(--r-tile);
    border: 1px solid var(--line);
  }
  .tool-out > .placeholder {
    color: var(--text-sec);
    text-align: left;
  }
  .tool-preview {
    display: flex;
  }

  /* Below the card the page is running text at the measure, so a section's
     rule ends where its text does rather than crossing the empty half of the
     container (Dash, 2026-10-19); the container itself stays full width.
     Drawn as a 1px fill the measure wide, the 1px border it replaces added
     to the padding, so no line moves. A tool page only (its card holds
     .tool-in); the finder keeps its own. */
  .page-main:has(> #tool > .tool-in) :is(.section-head, .faq-item + .faq-item, .cta) {
    border-top: 0;
    background:
      linear-gradient(var(--line), var(--line)) left top / min(100%, var(--prose-measure)) 1px
      no-repeat;
  }
  .page-main:has(> #tool > .tool-in) .section-head {
    padding-top: 29px;
  }
  .page-main:has(> #tool > .tool-in) .faq-item + .faq-item {
    padding-top: 17px;
  }
  .page-main:has(> #tool > .tool-in) .cta {
    padding-top: 25px;
  }
}

/* Form */
.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.field > label,
.label {
  font: var(--type-label);
  color: var(--text);
}
input[type="text"],
input[type="date"],
input[type="number"],
select,
textarea {
  font: var(--type-body);
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-input);
  padding: 11px 14px;
  min-height: var(--target);
  width: 100%;
}
input::placeholder,
textarea::placeholder {
  color: var(--text-mut);
}
.hint {
  font: var(--type-label);
  color: var(--text-mut);
}
.hint-ok {
  font: var(--type-label);
  color: var(--ok-text);
}
.row2 {
  display: flex;
  gap: 12px;
}
.row2 > * {
  flex: 1;
}
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.chip {
  min-height: var(--target);
  min-width: var(--target);
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-chip);
  background: transparent;
  color: var(--text-sec);
  font: var(--type-label);
  cursor: pointer;
}
.chip[aria-checked="true"],
.chip[aria-pressed="true"],
.chip[aria-checked="true"]:hover,
.chip[aria-pressed="true"]:hover {
  /* A selection is forest (scheme C, ruling 2026-10-17). */
  background: var(--forest-wash);
  border-color: var(--forest-line);
  color: var(--forest);
}

/* Result */
.placeholder {
  font: var(--type-body);
  color: var(--text-mut);
  text-align: center;
}
.result {
  /* A card, not a gold wash: gold is the page's one action (scheme C). Not
     `field`: the on-track verdict's okText is 4.3:1 there in light. */
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-tile);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  text-align: center;
}
.result hr {
  align-self: stretch;
  border: 0;
  border-top: 1px solid var(--line);
  margin: 8px 0;
}
.result-label {
  font: var(--type-label);
  color: var(--text-mut);
}
.result-big {
  font: var(--type-figure);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.result-med {
  font: var(--type-title);
  color: var(--text);
}
.verdict {
  font: var(--type-label);
  margin-top: 4px;
  color: var(--text-sec);
}
.verdict.ok {
  color: var(--ok-text);
}
table.grid {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
}
table.grid th,
table.grid td {
  text-align: left;
  padding: 10px 14px;
  border-top: 1px solid var(--line);
  font: var(--type-body);
}
table.grid th {
  font-weight: var(--type-strong-weight);
}
table.grid thead th {
  background: var(--bg);
  border-top: 0;
  font: var(--type-label);
  color: var(--text-mut);
}
table.grid td {
  color: var(--text);
}
.sources {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-top: 12px;
}
.sources p {
  font: var(--type-label);
  color: var(--text-mut);
}

/* Links down into the breed index */
.link-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
}
.link-row a,
.more {
  display: inline-flex;
  align-items: center;
  min-height: var(--target);
}

/* FAQ */
.faq {
  display: flex;
  flex-direction: column;
}
.faq-item {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px 0;
  border-top: 1px solid var(--line);
}
.faq-item:first-child {
  border-top: 0;
}
.faq-item p {
  color: var(--text-sec);
  font: var(--type-body);
}

/* The closing CTA: a flat section with the page's one gold button */
.cta {
  border-top: 1px solid var(--line);
  margin-top: 24px;
  padding-top: 24px;
  gap: 10px;
}
.cta p {
  color: var(--text-sec);
  font: var(--type-body);
}
.btn-gold {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  height: 52px;
  padding: 0 24px;
  margin-top: 6px;
  border-radius: var(--r-control);
  background: var(--gold);
  color: var(--on-gold);
  font: var(--type-title);
  text-decoration: none;
}
.btn-gold:hover {
  opacity: 0.88;
  text-decoration: none;
}

/* The page's own disclaimer sits ABOVE the site footer, in the column. */
.disclaimer {
  margin: 8px 0 0;
  font: var(--type-label);
  color: var(--text-mut);
}

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

/* ── The breed tools' shared pieces (web-tools/shared/breed.ts) ── */
.btn {
  min-height: var(--target);
  padding: 0 18px;
  border-radius: var(--r-control);
  border: 1px solid var(--line-strong);
  background: transparent;
  color: var(--text);
  font: var(--type-title);
  cursor: pointer;
}
.btn:hover {
  background: var(--field);
}
.btn.primary {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--on-gold);
}
.btn.primary:hover {
  opacity: 0.9;
  background: var(--gold);
}
.btn[disabled] {
  opacity: 0.5;
  cursor: default;
}
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 4px;
}
.nav .spacer {
  flex: 1;
}
.pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.pill {
  min-height: var(--target);
  max-width: 100%;
  padding: 0 16px;
  border-radius: var(--r-chip);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text-sec);
  font: var(--type-title);
  cursor: pointer;
  text-align: left;
}
.pill:hover {
  background: var(--field);
}
.pill[aria-checked="true"],
.pill[aria-checked="true"]:hover {
  background: var(--forest-wash);
  border-color: var(--forest-line);
  color: var(--forest);
}
.disc {
  --size: 48px;
  --tone: var(--slate);
  width: var(--size);
  height: var(--size);
  flex: none;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid color-mix(in srgb, var(--tone) 30%, transparent);
  background: color-mix(in srgb, var(--tone) 12%, transparent);
  color: var(--tone);
  /* A monogram: Title's face, sized by its disc (type.ts monogram). */
  font-family: var(--type-monogram-family);
  font-weight: var(--type-monogram-weight);
  font-size: calc(var(--size) * var(--type-monogram-ratio));
  line-height: var(--type-monogram-line);
}
/* The breed's face disc (shared/breed.ts disc()): BreedFaceDisc's drawing, a
   flat disc of the face ring's ink at its 0.15 tint (FACE_DISC_ALPHA; scheme C:
   gold is the action, never an identity wash), the ring and the WebP mask
   re-inked from the palette's breed-face tokens. */
.face-disc {
  --size: 48px;
  position: relative;
  width: var(--size);
  height: var(--size);
  flex: none;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--breed-face-ring) 15%, transparent);
}
.face-ring {
  position: absolute;
  width: var(--ring);
  height: var(--ring);
  box-sizing: border-box;
  border-radius: 50%;
  border: var(--ring-w) solid var(--breed-face-ring);
}
.face-mask {
  position: relative;
  width: var(--face);
  height: var(--face);
  background: var(--breed-face-ink);
  -webkit-mask: var(--src) center / contain no-repeat;
  mask: var(--src) center / contain no-repeat;
}
/* A decision caveat (Caveat board): Body in a field panel, no accent bar;
   it stays where the decision is made. */
.caveat {
  border-radius: 14px;
  padding: 16px 20px;
  margin: 0;
  background: var(--field);
  color: var(--text-sec);
  font: var(--type-body);
}
.caveat strong {
  color: var(--text);
}
/* A safety note (Caveat board): a soft wash, no outline; it stays visible. */
.kids {
  background: color-mix(in srgb, var(--heat) 12%, transparent);
  border-radius: 14px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font: var(--type-body);
  color: var(--text-sec);
}
.kids-estimate {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.kids-label {
  font: var(--type-label);
  color: var(--text);
}
.body {
  color: var(--text-sec);
  font: var(--type-body);
}
.small {
  font: var(--type-body);
  color: var(--text-sec);
}
.muted {
  font: var(--type-label);
  color: var(--text-mut);
}
.ai {
  border: 1px solid color-mix(in srgb, var(--lav) 33%, transparent);
  background: color-mix(in srgb, var(--lav) 7%, transparent);
  border-radius: 14px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ai-label {
  font: var(--type-label);
  color: var(--text);
}
.note {
  font: var(--type-label);
  color: var(--text-mut);
}
.lines {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  color: var(--text-sec);
  font: var(--type-body);
}
/* Colour only where it identifies something: a plain marker for a reason, coral
   for the one line that ranked a breed lower. */
.lines li::marker {
  color: var(--text-mut);
}
.card.bare {
  background: none;
  padding: 0;
  border-radius: 0;
}
.card-inner {
  background: var(--surface);
  border-radius: var(--r-hero);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* The public site's bar, the ONE copy every static page draws it from: the
   tools pages read this file (scripts/build-web-tools.mjs stylesheet()) and
   the landing carries it verbatim between its sitenav-css markers
   (scripts/build-landing.mjs). The React bar on the breed pages
   (components/web/SiteNav.tsx) takes the same numbers from SITE_NAV_BOX in
   components/web/site-nav-vars.ts; sitenav-spec.test.ts holds all three to
   them (Dash's owner walk, 2026-10-11: the header shifted between pages).
   Tokens only: each page generates the palette above it. */

/* The page gutter. The static pages scroll the document, so a classic
   scrollbar sits beside the bar; the breed pages scroll inside the app shell
   under it, and their bar steps in by the same width (app/+html.tsx --kc-sb).
   Reserved even on a page too short to scroll, and the same thin bar as the
   app shell's (+html.tsx), so the two widths are one width. */
html {
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

/* A full-document navigation between any two public pages cross-fades with
   the bar held in place (app/+html.tsx adds the same rule on a breed page). */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

/* The bar: 8px above and below the 44px row and a 1px hairline (61px). Its
   ground spans the window; its content lines up with the LANDING's column on
   every public page (Dash, 2026-10-19: "the landing page has the correct
   header, so it's just copying that implementation around"): the side padding
   is the gutter (32px, 18px under 820px) or the room either side of
   `--site-column` (1152px: the landing's 1216px measure less its gutters),
   whichever is larger. It stays at the top as the page scrolls, as the
   landing's does. */
.sitenav {
  view-transition-name: sitenav;
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px max(32px, calc((100% - var(--site-column, 1152px)) / 2));
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.sn-side,
.sn-wide {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
/* The way into the app keeps its width; the brand side gives way first. */
.sitenav > .sn-side:last-child {
  flex-shrink: 0;
  margin-right: -8px;
}
.sitenav a {
  text-decoration: none;
}
/* 8px of hit padding around the brand and every item;
   the 32px mark sits 10px from the wordmark. */
.sn-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 8px;
  margin-right: 2px;
  color: var(--text);
  font: var(--type-wordmark);
}
.sn-brand img {
  border-radius: 50%;
}
.sn-item {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 8px;
  border-radius: 8px;
  color: var(--text-sec);
  font: var(--type-label);
  cursor: pointer;
}
.sn-item:hover {
  background: var(--hero-hi);
}
.sn-item[aria-current="page"],
.sn-item[data-current] {
  /* The current item is forest: structure, never gold (scheme C, ruling
     2026-10-17). */
  color: var(--forest);
}
/* Under 360 the mark alone is the brand (its link says "Kintail home"), so Menu
   and Open the app still fit on one row. */
@media (max-width: 359px) {
  .sn-word {
    display: none;
  }
}
/* "Open the app" is an item, as Sign in is (Dash, 2026-10-12: "open the app
   can be given the same treatment as signin across the free pages"): no gold
   pill, so a page's own gold button is its one amber light. `.sn-cta` stays
   only as the hook web-tools/shared/nav.ts points at /. */
.sn-cta {
  white-space: nowrap;
}
.sitenav :is(a, button, summary):focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
}
.sn-menu {
  display: none;
}
.sn-menu,
.sn-tools {
  position: relative;
}
.sitenav summary {
  list-style: none;
}
.sitenav summary::-webkit-details-marker {
  display: none;
}
/* The app's chevD (build-web-tools.mjs CHEV), as WebTopBar's More: 8px from
   its label, textMut closed and text open, no flip. */
.sn-chev {
  flex: none;
  margin-left: 8px;
  color: var(--text-mut);
}
.sitenav details[open] > summary > .sn-chev {
  color: var(--text);
}
/* The Menu fold and the Free tools list: the app's tile radius (20). */
.sn-panel,
.sn-tools > .sn-drop {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 40;
  padding: 4px 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 20px;
  /* The bar stays on top, so its panel moves with it: on a short window the
     last rows scroll inside the panel rather than off the screen. */
  max-height: calc(100vh - 69px);
  overflow-y: auto;
}
.sn-panel {
  min-width: 210px;
}
.sn-tools > .sn-drop {
  min-width: 260px;
}
.sn-panel .sn-item,
.sn-drop .sn-item {
  padding: 0 16px;
  color: var(--text);
  white-space: nowrap;
}
/* The Menu fold keeps the lit section (ruling 2026-10-04): as specific as the
   rule above and after it. */
.sn-panel .sn-item[aria-current="page"],
.sn-panel .sn-item[data-current],
.sn-drop .sn-item[aria-current="page"] {
  color: var(--forest);
}
/* In the Menu the tools are a tap-to-open group, indented under it. */
.sn-group > .sn-drop .sn-item {
  padding-left: 28px;
}
/* The brand's and the last item's 8px hit padding hang outside the column,
   so the mark and "Open the app" sit on the page's own edges (the last
   side's margin is with its flex-shrink, above). */
.sitenav > .sn-side:first-child {
  margin-left: -8px;
}
@media (max-width: 819px) {
  .sitenav {
    padding: 8px max(18px, calc((100% - var(--site-column, 1152px)) / 2));
  }
  .sn-wide {
    display: none;
  }
  .sn-menu {
    display: block;
  }
}

/* The public site's footer, the ONE copy every static page draws it from:
   the tools, matcher, legal and about pages read this file
   (scripts/build-web-tools.mjs stylesheet()) and the landing carries it
   verbatim between its sitefooter-css markers (scripts/build-landing.mjs;
   Dash, 2026-10-18: "make the footer consistent with other pages on the new
   landing"). The breed pages' React footer (components/web/SiteFooter.tsx)
   draws the same box: the public container, a hairline, 44 px targets. The
   container is the site bar's `--site-column` (sitenav.css), the landing's
   1152 px, on every public page (Dash, 2026-10-19: "all the public pages
   should be full width, really").
   Literal sizes, not base.css's --target/--r-row: the landing has no base.css.
   lib/__tests__/guardrails/site-footer-parity.test.ts holds all of it. */
.site-footer {
  box-sizing: content-box;
  max-width: var(--site-column, 1152px);
  margin: 0 auto;
  padding: 0 18px 28px;
}
@media (min-width: 820px) {
  .site-footer {
    padding-left: 32px;
    padding-right: 32px;
  }
}
.sf-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 4px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
.sf-item {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 8px;
  border-radius: 8px;
  color: var(--text-sec);
  font: var(--type-label);
  text-decoration: none;
  cursor: pointer;
}
.sf-item:hover {
  background: var(--hero-hi);
}
.sf-copy {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 8px;
  color: var(--text-mut);
  font: var(--type-label);
}
.site-footer a:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
}

/* The Breed matcher (web-tools/dog-breed-finder/main.ts) */
.results {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* A regular result is a neutral tile, not the page's hero. Only the ruled
   rare status gets a soft gold wash below (Dash, 2026-10-14: option F);
   solid gold fills still belong to actions. */
.result {
  box-shadow: var(--shadow-card);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-tile);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* Summary and detail use the same Body/Label grammar as the breed pages. */
.result .why {
  font: var(--type-body);
  color: var(--text);
}
.result-details {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.result-head {
  display: flex;
  align-items: center;
  gap: 14px;
}
.result-name {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.result-name a {
  font: var(--type-title);
  color: var(--text);
  text-decoration: none;
  min-height: var(--target);
  display: inline-flex;
  align-items: center;
}
.result-name a:hover {
  text-decoration: underline;
}
/* The breed in one line, under its name (the ResultCard board). */
.descriptor {
  font: var(--type-body);
  color: var(--text-sec);
  margin: 0;
}
.meta {
  font: var(--type-label);
  color: var(--text-mut);
}
.lines.warn li::marker {
  color: var(--coral);
}
.coat {
  font: var(--type-body);
  color: var(--text-sec);
}
.coat strong {
  color: var(--text);
}
.status {
  font: var(--type-label);
  color: var(--text-sec);
}
.ref {
  font: var(--type-label);
  text-align: right;
  color: var(--text-mut);
}
.results textarea {
  resize: vertical;
}
.results textarea[readonly] {
  opacity: 0.5;
}
a.btn {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}
/* The progress row (UX 3): the part and the count within it, then the three
   parts as segments sized by their screens, filled in forest as one bar:
   done ones full, the current one to its point (scheme C). */
.progress-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
}
.progress {
  font: var(--type-label);
  color: var(--text-mut);
  margin: 0;
}
.progress-part {
  color: var(--forest);
}
.parts {
  /* Low enough that the longest part's label ("The dog you'd love · 1 of 8")
     keeps the track on its line at 390, so the row is one height on every
     screen and the question under it never steps down (Dash, 2026-10-03). */
  flex: 1 1 110px;
  display: flex;
  gap: 4px;
}
.track {
  flex: 1 1 0;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--line);
  overflow: hidden;
}
.track-fill {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--forest);
}
/* Under the answers (Dash, 2026-10-03: "all the info text on the quiz should
   show up below the answers, so there's no layout shift during the quiz"):
   the line that opens parts two and three. */
.quiz-info {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.part-start {
  margin: 0;
  font: var(--type-label);
  color: var(--forest);
}
/* Each set's number one so far (best.ts leadersRow): two quiet cells under
   the answers, a face and its set over the name, side by side at every width
   (at 320 each is ~130px). The row is held at its full height from the first
   screen and each name is one line, cut, so a leader arriving or changing
   moves nothing. */
.leaders {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
  min-height: 36px;
}
.leader {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  min-height: 36px;
}
.leader.waiting {
  visibility: hidden;
}
.leader-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.leader-set {
  font: var(--type-label);
  color: var(--text-sec);
}
.leader-name {
  font: var(--type-title);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* A new leader fades in where it stands; nothing moves under reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .leader.fresh > * {
    animation: leader-in 0.32s ease-out both;
  }
  @keyframes leader-in {
    from {
      opacity: 0;
    }
  }
}
/* A question's hint, under its answers. */
.q-hint {
  margin: 0;
}
/* A question a pick enables ("Is that size a must?" waits for a size) shows
   greyed out until then, in its place, so the answers under it never jump
   (Dash, 2026-10-03). Its "Pick a size first." line stays, unseen, once it is
   enabled, for the same reason. */
.group.waiting h3 {
  color: var(--text-mut);
}
.answer:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.answer:disabled:hover {
  border-color: var(--line);
  background: var(--bg);
}
.group:not(.waiting) .wait-hint {
  visibility: hidden;
}
.answers {
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* The checkbox mark's two shapes, drawn by .answer[role="checkbox"]. */
  --box: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='1' y='1' width='18' height='18' rx='5' fill='none' stroke='%23000' stroke-width='2'/%3E%3C/svg%3E");
  --box-ticked: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='1' y='1' width='18' height='18' rx='5' fill='none' stroke='%23000' stroke-width='2'/%3E%3Cpath d='M5.5 10.2l3 3 6-6.4' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* A single-choice screen's answers (UX 1, "presentation is poor and dull"):
   each a tile with a drawn radio mark, so the choice reads as a choice and the
   chosen one is plain at a glance. */
.answer {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 56px;
  padding: 12px 16px;
  border-radius: var(--r-tile);
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--text);
  font: var(--type-title);
  text-align: left;
  cursor: pointer;
  transition:
    border-color 0.15s,
    background-color 0.15s;
}
.answer::before {
  content: "";
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid var(--line-strong);
  box-sizing: border-box;
}
.answer:hover {
  border-color: var(--line-strong);
  background: var(--field);
}
.answer[aria-checked="true"],
.answer[aria-checked="true"]:hover {
  /* A picked answer is a forest selection (scheme C, ruling 2026-10-17). */
  background: var(--forest-wash);
  border-color: var(--forest-line);
  color: var(--text);
}
.answer[aria-checked="true"]::before {
  border-color: var(--forest);
  background: radial-gradient(circle, var(--forest) 0 5px, transparent 5.5px);
}
/* A multi-choice screen's answers are the same tiles (Dash, 2026-10-02: "make
   the multi-answer quiz questions present more similarly to the single answer
   questions"), with a drawn checkbox in place of the radio: the same stroke and
   colours, a square, and a tick when chosen. The mark is a mask over the theme
   colour, so the tick follows --forest in both themes. */
.answer[role="checkbox"]::before {
  border: 0;
  border-radius: 0;
  background: var(--line-strong);
  -webkit-mask: var(--box) center / 20px 20px no-repeat;
  mask: var(--box) center / 20px 20px no-repeat;
}
.answer[role="checkbox"][aria-checked="true"]::before {
  background: var(--forest);
  -webkit-mask-image: var(--box-ticked);
  mask-image: var(--box-ticked);
}
/* A question asked with others on one screen (Dash, 2026-10-03: those screens
   were still pills): the same tile, marks and states, compact so a
   three-question screen stays about a phone tall. Two to a row when every
   answer fits half the width (compactColumns, questions.ts), else one; two to
   a row for all of them once the card is wide. The 44px floor is the tap
   target (ruling, queue row 238). */
.answers.compact {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
}
.answers.compact.cols-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.answers.compact .answer {
  gap: 10px;
  min-height: 44px;
  padding: 8px 12px;
}
@media (min-width: 600px) {
  .answers.compact {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
/* Each new screen settles in (draw() marks #tool "entering" on a screen
   change only), unless the reader asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
  .entering .card-inner > h2,
  .entering .card-inner > .answers,
  .entering .card-inner > .group {
    animation: settle 0.22s ease-out both;
  }
  @keyframes settle {
    from {
      opacity: 0;
      transform: translateY(6px);
    }
  }
}
.group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.gdot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--tone);
  margin-right: 6px;
  vertical-align: 1px;
}
/* The browse bar (row 331·yxis): the answers as one-tap rows above the six,
   set off by hairlines like the highlight. */
.toggles {
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* sectionGap.card each side of both rules, with .results' 14 gap. */
  padding: 14px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.toggles h3 {
  margin: 0;
}
.toggles .small {
  margin: 0;
}
/* The panel's hint: over the groups when there is room, inside an opened
   group on a phone, so the closed groups sit right under the heading. */
.ag-hint {
  display: none;
}
@media (max-width: 600px) {
  .ag-hint-wide {
    display: none;
  }
  .ag-hint {
    display: block;
  }
}
/* Two groups, each a <details>: a no-JS disclosure with its answers in one
   line when closed. */
.answer-group {
  border-top: 1px solid var(--line);
  padding-top: 6px;
}
.answer-group > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  min-height: var(--target);
  padding: 6px 0;
  cursor: pointer;
  list-style: none;
}
.answer-group > summary::-webkit-details-marker {
  display: none;
}
.answer-group > summary::before {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--text-mut);
  border-bottom: 2px solid var(--text-mut);
  transform: rotate(-45deg);
  margin-right: 2px;
}
.answer-group[open] > summary::before {
  transform: rotate(45deg);
}
.ag-title {
  font: var(--type-title);
  color: var(--text);
}
.ag-sum {
  font: var(--type-label);
  color: var(--text-sec);
}
.answer-group[open] .ag-sum {
  display: none;
}
.ag-rows {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 4px 0 10px;
}
.toggle-row .dealbreaker {
  grid-column: 2;
  justify-self: start;
}
.toggle-row .dealbreaker .pill {
  white-space: nowrap;
}
.toggle-row {
  display: grid;
  grid-template-columns: 132px 1fr;
  align-items: center;
  gap: 12px;
}
.toggle-label {
  color: var(--text-sec);
  font: var(--type-label);
}
/* On a phone each row is its label over its answers, wrapped: every answer
   in sight, none cut off at the edge (a sideways scroller hid them mid-word
   with no cue). The groups start closed here, so an open one is the reader's
   choice to spend the height. Pills tighten a little; targets stay 44px. */
@media (max-width: 600px) {
  .toggle-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
  }
  .toggle-row .dealbreaker {
    grid-column: 1;
  }
  .toggle-row .pills {
    gap: 6px;
  }
  .toggle-row .pill {
    padding: 0 12px;
  }
}
.ratings-label {
  font: var(--type-label);
  color: var(--text-mut);
}
/* Breed personality (Dash, 2026-09-27: "show both sets"; 2026-10-08: renamed;
   2026-10-10: "should always be full screen"): Button ghost geometry
   (design-system/components.md Actions), with a disclosure chevron.
   It opens a full-screen view at every width. */
.ratings {
  display: contents;
}
.ratings-open {
  font: var(--type-title);
  text-align: left;
  background: none;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--r-control);
  padding: 0 20px;
  cursor: pointer;
  min-height: 46px;
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--text);
}
.ratings-open:hover {
  background: var(--hero-hi);
}
.ratings-open::after {
  content: "›";
  margin-left: auto;
  font: var(--type-title);
}
.ratings-open > .ratings-label {
  margin: 0;
  font: inherit;
  color: inherit;
}
dialog.personality {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--bg);
  color: var(--text);
  overflow-y: auto;
  overscroll-behavior: contain;
}
dialog.personality::backdrop {
  background: var(--bg);
}
html:has(dialog.personality[open]) {
  overflow: hidden;
}
.sheet-bar {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  padding: 8px 16px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.sheet-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding-left: 8px;
}
.sheet-back svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sheet-body {
  max-width: 640px;
  margin: 0 auto;
  padding: 20px 16px 40px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.sheet-body > .ratings-label {
  margin: 0;
}
.sheet-name {
  font: var(--type-title);
  color: var(--text);
  margin: 0;
}
.roles {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font: var(--type-label);
}
/* Further roles: plain words after the tag, never a chip (the Tags board). */
.roles-also {
  color: var(--text-sec);
}
/* The reasons and the shedding line sit left with their bullets, also inside
   the #1 card, whose column centres its children. */
.result .lines,
.result .coat {
  align-self: stretch;
  text-align: start;
}
/* A breed's class: its main role, one identity tag with its text label (Dash,
   2026-09-27), never a control (the Tags board, 2026-10-14): Label, the row
   radius, the hue on the wash (constants/Colors.ts `tint.tag`, 0x24, 14%) and
   the page's ink on top, as the app's tintInk. */
.class-tag {
  border-radius: var(--r-row);
  padding: 8px 12px;
  font: var(--type-label);
  color: var(--text);
  background: color-mix(in srgb, var(--class) 14%, var(--surface));
}
/* The two sets (Dash, 2026-09-27): "You're most like", then "Suits you best",
   each a landmark under its own heading. */
.set {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}
/* Each set's heading: the small kicker the old "Your match" band wore
   (base.css .kicker's size, weight and token; Dash, 2026-10-10: "clear
   sections with the smaller heading"), forest since scheme C (2026-10-17). */
.set-title {
  font: var(--type-label);
  color: var(--forest);
  margin: 0;
}
/* The second set starts a new section, not another card. */
.set + .set {
  border-top: 1px solid var(--line);
  padding-top: 14px;
}
/* The Kintail+ live match (Dash, 2026-09-27): the tap and its credits after
   the two sets; once matched, its two sets as short lists under their own
   heading, set off like a third section. */
.live-match {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 10px;
  border-top: 1px solid var(--line);
  padding-top: 18px;
}
.live-match .actions {
  align-items: center;
}
.live-match .muted,
.live-match .note {
  margin: 0;
  /* --text-mut is under 4.5:1 at this size in both themes. */
  color: var(--text-sec);
}
.live-match p:empty {
  display: none;
}
/* Matched: the live sets in the AI box, an add-on under the two sets. */
.live-match.matched {
  padding: 14px;
  border-radius: var(--r-tile);
  border: 1px solid color-mix(in srgb, var(--lav) 33%, transparent);
  background: color-mix(in srgb, var(--lav) 7%, transparent);
}
.live-match h4 {
  margin: 4px 0 0;
  font: var(--type-title);
  color: var(--text);
}
.live-match .lines {
  margin: 0;
  padding-left: 22px;
  text-align: start;
}
.live-match .lines li {
  margin: 4px 0;
  font: var(--type-body);
}
.live-match .lines a {
  font-weight: var(--type-strong-weight);
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
/* The matcher's cards read left to right: the shared .result centring is for
   the other tools (design review, #1500). */
.set .result {
  align-items: stretch;
  text-align: left;
}
.breed-name {
  margin: 0;
  font: inherit;
}
/* The name is the card's one link to its breed page, so it wears the tools'
   link colour (mint links, ruling 2026-09-26); its 44px target stays. */
.set .breed-name a {
  color: var(--act);
}
.set-title + .small {
  margin: -8px 0 0;
}
/* Each card's first line: why this breed, in the reader's words. */
.why {
  margin: 0;
  font: var(--type-title);
  color: var(--text);
  overflow-wrap: anywhere;
}
/* Every hidden trait (#1509), at the foot of the Breed personality fold,
   which keeps it closed by default: a heading of its own, not a second fold. */
.all-traits {
  border-top: 1px solid var(--line);
  padding-top: 10px;
}
.all-traits-label {
  margin: 0 0 6px;
  font: var(--type-label);
  color: var(--text-mut);
}

/* "Good match?" on every result (Dash, 2026-10-02): small, so mint, not gold
   (ruling 2026-07-05: prominent interactives are gold; small ones stay mint).
   The row's height is the thumbs' 44px target, and the thanks sits at its end,
   so a vote moves nothing. */
.good-match {
  display: flex;
  align-items: center;
  gap: 2px;
  min-height: var(--target);
}
.gm-label {
  margin-right: 6px;
  color: var(--text-sec);
  font: var(--type-label);
}
.gm-thumb {
  width: var(--target);
  height: var(--target);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-mut);
  cursor: pointer;
}
.gm-thumb svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  /* The family stroke at 20 px (ruling 2026-10-18). */
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.gm-thumb.down svg {
  transform: rotate(180deg);
}
.gm-thumb:hover {
  background: var(--field);
}
.gm-thumb[aria-pressed="true"] {
  color: var(--act);
}
.gm-thumb[aria-pressed="true"] svg {
  fill: color-mix(in srgb, var(--act) 22%, transparent);
}
.gm-thanks {
  margin-left: 6px;
  color: var(--text-mut);
  font: var(--type-label);
}

/* The results' ways on, under the list. */
.doors {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
/* In the actions row: from 600 at its far end, the way on rather than a
   fourth action; on a phone it wraps under them. */
@media (min-width: 600px) {
  .actions > .doors {
    margin-left: auto;
  }
}

/* Dash's 2026-10-14 preview ruling: gold rarity marks, not a primary action.
   gold-text also keeps the 1px stroke above 3:1 on the light card surface;
   the fill gold does not. The estimate wording stays visible. */
.rare-head {
  margin: 0;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font: var(--type-label);
  color: var(--gold-text);
}
.result.rare {
  background: color-mix(in srgb, var(--gold) 8%, var(--surface));
  border-color: transparent;
}
.rare-line {
  margin: 0;
  font: var(--type-label);
  color: var(--gold-text);
}

/* The estimates panel (UX 6): one shape on every card, the class on its own
   line under the fold's summary. On a phone each rating is one row led by its three-step meter,
   in a fixed column at the panel's edge, so one trait reads down the cards by
   the meter alone; the label runs straight into its plain words, which wrap
   under it only when long. Wider, a grid of cells, two then three across. */
.ratings-grid {
  margin: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px 18px;
}
/* The DOM keeps label, meter, words (a screen reader hears them in that
   order); only the meter is lifted into the row's lead column. */
.stat {
  position: relative;
  min-width: 0;
  padding-left: 58px;
  font: var(--type-label);
  font-weight: var(--type-body-weight);
}
.stat dt {
  display: inline;
  font-weight: var(--type-strong-weight);
  color: var(--text);
}
.stat dd {
  margin: 0;
}
.stat-meter {
  position: absolute;
  left: 0;
  top: 6px;
  line-height: 0;
}
/* The space between the label and its words is a real one in the DOM
   (main.ts): `.stat dd { margin: 0 }` would outrank a margin here. */
.stat-words {
  display: inline;
  color: var(--text-sec);
}
@media (min-width: 560px) {
  .ratings-grid {
    grid-template-columns: 1fr 1fr;
    gap: 10px 18px;
  }
  .stat {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 2px 10px;
    padding-left: 0;
  }
  .stat-meter {
    position: static;
    line-height: inherit;
  }
  .stat-words {
    grid-column: 1 / -1;
  }
}
@media (min-width: 720px) {
  .ratings-grid {
    grid-template-columns: 1fr 1fr 1fr;
  }
}
.meter {
  display: inline-flex;
  gap: 3px;
}
.meter span {
  width: 14px;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--line-strong);
}
.meter span.on {
  background: var(--text-sec);
}
.all-traits dl {
  margin: 0;
  display: grid;
  gap: 4px 20px;
  font: var(--type-label);
  font-weight: var(--type-body-weight);
}
.all-traits dl div {
  display: grid;
  grid-template-columns: 8.6em 1fr;
  gap: 8px;
}
.all-traits dt {
  font-weight: var(--type-strong-weight);
  color: var(--text);
}
.all-traits dd {
  margin: 0;
  color: var(--text-sec);
}
/* "Compare side by side" (Dash, 2026-10-02, #1597: "b"): each set's cards'
   estimates side by side, one fold after both sets at the results' full
   width (round 2), in the estimates' dashed box. One row per trait, one column per breed; each cell is the meter alone,
   so a trait reads across the set by eye (its words are in the meter's
   accessible name and on the card). Fixed columns, so the meters line up.
   Sized by its own width (a container), not the window. */
.compare {
  /* Not on .set: a container's layout containment turns its subgrid off. */
  container: compare / inline-size;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-tile);
  padding: 0 14px;
  font-weight: var(--type-body-weight);
}
.compare summary {
  font: var(--type-label);
  color: var(--act);
  cursor: pointer;
  min-height: var(--target);
  display: flex;
  align-items: center;
}
.compare > .ratings-label {
  margin: 0 0 6px;
}
/* Each set's table under its own title, one under the other, at the
   results' full width (round 2). */
.compare-set + .compare-set {
  border-top: 1px solid var(--line);
  padding-top: 10px;
}
.compare-title {
  margin: 0 0 2px;
  font: var(--type-label);
  color: var(--text-sec);
}
/* On a phone each trait's label sits over its row of meters, so every breed
   keeps a column a name fits (a label column would leave ~57px each for four);
   from a 520 column the label takes a column of its own. */
.compare-grid {
  margin: 0 0 12px;
  font: var(--type-label);
}
.cg-row {
  display: grid;
  grid-template-columns: repeat(var(--n), minmax(0, 1fr));
  align-items: center;
  column-gap: 4px;
  padding: 7px 0 0;
}
.cg-row > .cg-label {
  grid-column: 1 / -1;
  font-weight: var(--type-strong-weight);
  color: var(--text);
}
.cg-head > .cg-label {
  display: none;
}
.cg-head {
  align-items: end;
  padding-bottom: 6px;
}
.cg-cell {
  text-align: center;
  line-height: 0;
  color: var(--text-mut);
}
.cg-head > .cg-cell,
.cg-class > .cg-cell,
.cg-cell > [aria-hidden] {
  line-height: var(--type-label-line);
}
.cg-head > .cg-cell {
  color: var(--text);
  hyphens: auto;
  overflow-wrap: break-word;
}
@container compare (min-width: 520px) {
  .cg-row {
    grid-template-columns: 7.4em repeat(var(--n), minmax(0, 1fr));
  }
  .cg-row > .cg-label {
    grid-column: auto;
  }
  .cg-head > .cg-label {
    display: block;
  }
}
.compare-kicker {
  display: block;
  font: var(--type-label);
  color: var(--text-mut);
}
.compare-role {
  font: var(--type-label);
  color: var(--class);
  overflow-wrap: anywhere;
}
/* Over the results the intro is hidden (main.ts), so the page's h1 would sit
   right on "Your matches": sectionGap.card between them (#1598 round 1). */
.hero:has(> .intro[hidden]) {
  padding-bottom: 14px;
}

/* The quiz's first screen is drawn by the script, so the empty card took no
   room and everything under it moved when it arrived (CLS 0.21 at 390 on v7,
   #1622). Until the script first draws (draw() adds .bare, quiz or results
   alike) the card holds the first screen's measured height. Scoped to the
   finder: every tool page shares this stylesheet, and only this one draws
   .bare. A bank whose first
   screen changes re-measures these: v8's (energy, visitors, nerves), with the
   held leaders row (2026-10-10), is 605 at
   390 and 513 wide. */
#tool[data-tool="finder"]:not(.bare) {
  min-height: 513px;
}
@media (max-width: 719px) {
  #tool[data-tool="finder"]:not(.bare) {
    min-height: 605px;
  }
}

/* Each breed's photo (Dash, 2026-10-10: "the breed photo should be there for
   each breed"): the breed page's reviewed photo, whole in a fixed 3:2 box, so
   its arrival moves nothing; a breed with none shows its face in the same box.
   Beside the name on every card, the leading card's larger. */
.photo-box {
  position: relative;
  flex: none;
  aspect-ratio: 3 / 2;
  border-radius: var(--r-tile);
  overflow: hidden;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
}
.result-head > .photo-box {
  width: 112px;
}
/* Beside a photo the name has less room than beside the old disc: a long one
   breaks rather than running off the card at 320. */
.result-head > .result-name {
  flex: 1;
}
.result-head .breed-name a {
  overflow-wrap: break-word;
  hyphens: auto;
}
@media (max-width: 359px) {
  .result-head > .photo-box {
    width: 84px;
  }
}
.photo-box img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
/* Two lines held for the credit on a phone, so its words arriving with the
   credits file move nothing under it. */
.photo-credit {
  margin: 0;
  min-height: 36px;
  font: var(--type-label);
  color: var(--text-mut);
  overflow-wrap: anywhere;
}
/* One compact link (ruling 2026-10-14-compact-per-photo-credits), a 44px target. */
.photo-credit a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: inherit;
}
/* From 600 a card is half the results (.sets), 353 to 483 wide: the photo
   takes up to 38% of it, so the name keeps ~60%. */
@media (min-width: 600px) {
  .result-head > .photo-box {
    width: min(168px, 38%);
  }
}

/* Each set's leading card, its number one (Dash, 2026-10-10: "two columns for
   the photo/header bit and one column for the rest"): every card's grammar,
   larger. The head row is the photo beside the name and meta, about as tall as
   each other at every width; the why and everything under it run the card's
   full width, so no column of the card is left empty. */
/* Sized by the card, not the window: a lead is a phone's width, or half the
   results from 600 (275 to 483). Its name is every card's Title. */
.result.lead {
  container: lead / inline-size;
}
.result.lead .result-head > .photo-box {
  width: 38%;
  max-width: 240px;
}
@container lead (min-width: 300px) {
  .result.lead .result-head > .photo-box {
    width: 48%;
  }
}
.fits ul {
  margin: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.fits-lead {
  margin: 0 0 6px;
}

/* Every pick at a glance (Dash, 2026-10-10: "show all 8 suggested picks with
   their avatars as a quick preview"): the two sets side by side from 390, each
   a list of face and name, every name a link to its card. Under 390 a column
   would be ~120px, so the sets stack. */
.preview {
  background: var(--surface);
  border-radius: var(--r-tile);
  padding: 14px 16px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px 18px;
}
@media (min-width: 390px) {
  .preview:not(.single) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}
/* A phone's two columns: a little less padding, gap and type, so each name's
   longest word fits its column whole. */
@media (min-width: 390px) and (max-width: 599px) {
  .preview:not(.single) {
    padding: 14px;
    column-gap: 12px;
  }
  .preview:not(.single) .pv-set a {
    gap: 6px;
  }
}
.pv-set {
  min-width: 0;
}
.pv-title {
  margin: 0 0 4px;
  font: var(--type-label);
  color: var(--text-sec);
}
.pv-set ol {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}
.pv-set a {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--target);
  padding: 4px 0;
  color: var(--act);
  font: var(--type-label);
  text-decoration: none;
}
.pv-set a:hover .pv-name {
  text-decoration: underline;
}
.pv-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* A name breaks between words only (post-loop, #1843: "Hamiltonstov-are"
   split mid-word at 390). The longest one-word name, Hamiltonstovare, is
   115 px at 15 px bold: two columns start at 390, where a phone's text column
   (~107 px at 14 px) holds it whole. */
.pv-name {
  overflow-wrap: normal;
  hyphens: manual;
}
.pv-rare {
  font: var(--type-label);
  color: var(--gold-text);
}
@media (min-width: 900px) {
  .pv-set ol {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 14px;
  }
}
/* A card the preview jumped to: focus is shown, not the default ring on a
   whole tile. */
.result:focus {
  outline: none;
}
.result:focus-visible {
  outline: 2px solid var(--act);
  outline-offset: 2px;
}

/* The two sets (Dash, 2026-10-10: "if possible, the two top picks would sit
   side-by-side"): stacked on a phone; from 600 one grid, a column per set,
   each set's title, cards and rare pick on rows shared with the other
   set's (main.ts sets --row), so its pairs line up. The sections stay boxes
   (subgrid), so each is still a landmark under its heading. */
.sets {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
@media (min-width: 600px) {
  .sets:not(.single) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: repeat(6, auto);
    column-gap: 14px;
    row-gap: 14px;
  }
  .sets:not(.single) > .set {
    grid-row: 1 / span 6;
    display: grid;
    grid-template-rows: subgrid;
    gap: inherit;
  }
  .sets:not(.single) > .set > * {
    grid-row: var(--row);
  }
  /* Side by side, the second set is a column, not a section under a rule. */
  .sets:not(.single) > .set + .set {
    border-top: 0;
    padding-top: 0;
  }
  .sets:not(.single) .set-title {
    align-self: end;
  }
  .sets:not(.single) .rare-head {
    align-self: end;
  }
  /* A row's two cards stretch to the taller one; each card's foot is pinned
     to its bottom (.card-foot), so the pair ends level. */
  .sets:not(.single) .result {
    align-self: stretch;
  }
}

/* "Meet the dog", after the tool where "How the matcher works" was (Dash,
   2026-10-10; build-web-tools.mjs meetTheDog): the caveat box, then the
   breeder line in the same box. */
.meet .caveat p {
  margin: 0;
  max-width: var(--prose-measure);
}
.meet .caveat p + p {
  margin-top: 8px;
}

/* A card's foot (main.ts card): Breed personality and "Good match?" on one
   row, then the photo credit, pushed to the card's bottom so cards side by
   side end level. */
.card-foot {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.foot-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
}
.foot-row .ratings-open {
  flex: 1 1 auto;
}

/* Share lives in the heading row, never over the breed names or in its own
   row. Grid reserves its width; an error alone may use the next row. */
.results-heading {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px 12px;
}
.results-heading h2 {
  margin: 0;
  min-width: 0;
}
.pv-share {
  display: contents;
}
.pv-share .share-status {
  grid-column: 1 / -1;
  margin: 0;
}
.pv-share-btn {
  border-radius: var(--r-control);
  min-height: var(--target);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0 16px;
}
.pv-share-btn svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.share-sheet {
  position: fixed;
  inset: auto 0 0;
  margin: 0;
  padding: 0;
  width: 100%;
  max-width: none;
  max-height: 90dvh;
  overflow: hidden;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--r-hero) var(--r-hero) 0 0;
}
/* Derived from the mounted dialog: a results redraw cannot strand a scroll lock. */
html:has(.share-sheet[open]) {
  overflow: hidden;
}
.share-sheet[open] {
  display: flex;
  flex-direction: column;
}
.share-sheet-head,
.share-sheet-foot {
  flex-shrink: 0;
}
.share-sheet[hidden] {
  display: none;
}
.share-sheet::backdrop {
  background: var(--scrim);
}
.share-sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 20px 8px;
}
.share-sheet-head h2 {
  font: var(--type-heading);
  margin: 0;
}
.share-close {
  width: var(--target);
  height: var(--target);
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  color: var(--text-sec);
  font: var(--type-heading);
  cursor: pointer;
}
.share-sheet-body {
  overflow-y: auto;
  min-height: 0;
  padding: 8px 20px 16px;
}
.share-preview {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1200 / 630;
  object-fit: contain;
  border-radius: var(--r-control);
  background: var(--field);
}
.share-status:empty {
  display: none;
}
.share-sheet-foot {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 20px max(16px, env(safe-area-inset-bottom));
}
.share-copy {
  width: 100%;
  border-radius: var(--r-control);
}
@media (min-width: 760px) {
  .share-sheet {
    inset: 0;
    margin: auto;
    width: 452px;
    max-height: 90dvh;
    border-radius: var(--r-hero);
  }
}

/* The standalone shared result: approved result parts, then a real matcher
   link. No separator/spacing for the removed quiz below it. */
.shared-result {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.shared-result h1 {
  margin: 0;
}
.shared-result .kicker {
  margin: 0 0 -8px;
}
/* Two rows only (title, lead card): the results' six-row grid would leave
   four empty gaps under the cards. */
@media (min-width: 600px) {
  .shared-result .sets:not(.single) {
    grid-template-rows: repeat(2, auto);
  }
  .shared-result .sets:not(.single) > .set {
    grid-row: 1 / span 2;
  }
}
.shared-cta {
  margin: 0;
}
/* #1945: matcher entry is outlined, gold belongs to conversion. Keep the
   house control radius and tap floor, not the tools' legacy pill. */
.shared-cta .btn {
  width: 100%;
  justify-content: center;
  border-radius: var(--r-control);
}

/* /tools/dog-heat-cycle-calculator: the four phases, each on the heat hue. */
.phases {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.phases li {
  border-left: 3px solid var(--heat);
  padding-left: 12px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.phase-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.phase-when {
  font: var(--type-label);
  color: var(--heat);
  text-align: right;
}
.prose .phases p {
  font: var(--type-body);
}
.result-sub {
  font: var(--type-label);
  color: var(--text-sec);
}

/* /tools/puppy-vaccination-schedule: the timeline, one hue per kind of item. */
.timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.timeline li {
  display: flex;
  gap: 12px;
}
.timeline .rail {
  flex: none;
  width: 4px;
  border-radius: 2px;
  background: var(--kind);
}
.timeline .item-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.timeline .item-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.timeline .item-date,
.timeline .item-title {
  font: var(--type-title);
  color: var(--text);
}
.timeline .item-title {
  margin-top: 2px;
}
.timeline .kind-tag {
  font: var(--type-label);
  /* The tag's words take the kind's own hue, AA as 13px type on the card in
     both themes since scheme C (teal is 4.4:1 on `field`: keep tags on the card). */
  color: var(--kind-ink, var(--kind));
  border: 1px solid var(--kind);
  border-radius: var(--r-chip);
  padding: 2px 8px;
}
.timeline .item-age {
  font: var(--type-label);
  color: var(--text-mut);
}
.timeline .item-detail {
  font: var(--type-body);
  color: var(--text-sec);
}
.timeline .k-vaccine {
  --kind: var(--lav);
}
.timeline .k-parasite {
  --kind: var(--teal);
}
.timeline .k-milestone {
  /* A planned milestone is not a celebration: forest, not gold (scheme C). */
  --kind: var(--forest);
}
.timeline .k-vet {
  --kind: var(--slate);
}

/* The legal pages (site-sections fix 7): /privacy, /terms and /delete-account,
   in the public container with its tokens, the prose at the reading measure. Their prose is the old
   pages' <main>, byte for byte (web-tools/legal/*.html), so these rules style
   its existing classes: sentence case, no tracked uppercase, mint links, no
   bordered cards, the one gold piece the delete page's primary button. */
.legal h1 {
  margin-bottom: 4px;
}
.legal .updated {
  color: var(--text-mut);
  font: var(--type-label);
}
.legal .lede {
  color: var(--text-sec);
  font: var(--type-body);
}
.legal h2 {
  margin: 24px 0 0;
}
.legal h2 .n {
  color: var(--text-mut);
  margin-right: 8px;
  font-variant-numeric: tabular-nums;
}
.legal h3.sub {
  font: var(--type-title);
  color: var(--text);
  margin-top: 4px;
}
.legal p,
.legal li {
  color: var(--text-sec);
  font: var(--type-body);
}
.legal p,
.legal li,
.legal .lede {
  max-width: var(--prose-measure);
}
.legal .lede,
.legal p.sec {
  margin: 0;
}
.legal ul,
.legal ol {
  margin: 0;
  padding-left: 22px;
}
.legal li {
  margin: 0 0 8px;
}
.legal strong {
  color: var(--text);
  font-weight: var(--type-strong-weight);
}
/* The contents: a tonal block, a sentence-case label, each entry a 44px target. */
.legal .toc {
  background: var(--surface);
  border-radius: var(--r-tile);
  padding: 14px 20px;
  margin: 8px 0;
}
.legal .toc h3 {
  font: var(--type-label);
  color: var(--text-sec);
  margin: 0 0 2px;
}
.legal .toc ol {
  columns: 2;
  column-gap: 32px;
}
.legal .toc li {
  margin: 0;
  break-inside: avoid;
}
.legal .toc a {
  display: inline-flex;
  align-items: center;
  min-height: var(--target);
}
@media (max-width: 560px) {
  .legal .toc ol {
    columns: 1;
  }
}
/* From 1024 a page with contents puts them in a rail beside the measured text
   (ruling 2026-10-19-public-pages-take-the-landing-container), held in view as
   the text scrolls; the prose is byte-pinned, so this is CSS alone. The rail
   spans every row of the one-column grid (more than any page's block count:
   privacy has 48); the grid's row gap is 0, so the spare rows take no room
   and the blocks keep their own margins. */
@media (min-width: 1024px) {
  .legal:has(.toc) .page-main {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 280px;
    column-gap: 64px;
    align-items: start;
  }
  .legal:has(.toc) .page-main > * {
    grid-column: 1;
  }
  .legal .page-main > .toc {
    grid-column: 2;
    grid-row: 1 / span 120;
    position: sticky;
    top: 85px;
    max-height: calc(100vh - 101px);
    overflow-y: auto;
    margin: 0;
  }
  .legal .toc ol {
    columns: 1;
  }
}
.legal .callout {
  background: var(--surface);
  border-radius: var(--r-tile);
  padding: 16px 18px;
}
.legal .callout p {
  margin: 0;
}
/* Tables: sentence-case headers, hairline rows. */
.legal .table-scroll {
  overflow-x: auto;
}
.legal table {
  width: 100%;
  border-collapse: collapse;
  font: var(--type-body);
}
.legal th,
.legal td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
  color: var(--text-sec);
}
.legal th {
  color: var(--text-mut);
  font: var(--type-label);
}
/* The delete page's two request paths, side by side. */
.legal .ways {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin: 8px 0;
}
.legal .way {
  background: var(--surface);
  border-radius: var(--r-tile);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.legal .way .tag {
  font: var(--type-label);
  color: var(--text-mut);
  margin: 0;
}
.legal .way p,
.legal .way li {
  margin: 0;
}
/* base.css's .cta is the tools' closing section, rule and all; here it is
   only the button's slot at the foot of the card. */
.legal .way .cta {
  border-top: 0;
  margin-top: auto;
  padding-top: 4px;
}
.legal .btn {
  display: inline-flex;
  align-items: center;
  min-height: var(--target);
  padding: 0 20px;
  border-radius: var(--r-control);
  border-color: transparent;
  background: var(--gold);
  color: var(--on-gold);
  font: var(--type-title);
  text-decoration: none;
}
.legal .btn-quiet {
  background: none;
  color: var(--text);
  border-color: var(--line);
}
@media (max-width: 640px) {
  .legal .ways {
    grid-template-columns: 1fr;
  }
  /* A three-column table of prose is one word per line on a phone: each row
     stacks into a labelled block, the header cells its data-label prefixes. */
  .legal table,
  .legal tbody,
  .legal tr,
  .legal td {
    display: block;
    width: 100%;
  }
  .legal thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .legal tr {
    border-bottom: 1px solid var(--line);
    padding: 14px 0 4px;
  }
  .legal tr:last-child {
    border-bottom: 0;
  }
  .legal td {
    border-bottom: 0;
    padding: 0 0 8px;
  }
  .legal td[data-label]::before {
    content: attr(data-label);
    display: block;
    color: var(--text-mut);
    font: var(--type-label);
    margin-bottom: 2px;
  }
  .legal td[data-label="What"]::before {
    display: none;
  }
}
