/* ==========================================================================
   ToolQuilt: "Studio" theme
   Warm paper ground, graphite ink, one deep teal accent. Light by default;
   dark is opt-in via the toggle, never forced on the visitor.
   ========================================================================== */

/* Self-hosted so the site still makes no third-party request.
   Latin subset only, variable weight axes, woff2. */

@font-face {
  font-family: 'Fraunces';
  src: url("../fonts/fraunces-latin.069cb90e11a3.woff2") format('woff2-variations'),
       url("../fonts/fraunces-latin.069cb90e11a3.woff2") format('woff2');
  font-weight: 500 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  src: url("../fonts/inter-latin.260c81a4759b.woff2") format('woff2-variations'),
       url("../fonts/inter-latin.260c81a4759b.woff2") format('woff2');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url("../fonts/jetbrainsmono-latin.b636a65da4f0.woff2") format('woff2-variations'),
       url("../fonts/jetbrainsmono-latin.b636a65da4f0.woff2") format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------- tokens */

:root {
  color-scheme: light;

  --paper:       #fbfaf8;
  --paper-2:     #f4f1ec;
  --surface:     #ffffff;
  --surface-2:   #f7f5f1;
  --surface-3:   #ece7de;

  --ink:         #16191d;
  --ink-2:       #3a424d;
  /* Both greys clear 4.5:1 on every surface above; --faint is simply the one
     reserved for the smallest, least important text. */
  --muted:       #646d78;
  --faint:       #676c75;

  /* --line is decorative separation. --line-2 draws control edges -- input
     borders, button outlines -- so it has to clear 3:1 per WCAG 1.4.11. */
  --line:        #e4dfd6;
  --line-2:      #90887c;

  --accent:      #2f6f68;
  --accent-2:    #245751;
  --accent-3:    #1b423e;
  --accent-soft: #e6f0ee;
  --accent-line: #b9d5d0;

  --clay:        #a15a26;
  --clay-soft:   #f6ece2;
  --ok:          #2c6e49;
  --ok-soft:     #e5f1e9;
  --warn:        #8a5a12;
  --warn-soft:   #f7efdf;
  --danger:      #a4362c;
  --danger-soft: #f8e9e7;

  /* Self-hosted, so the typographic voice is the same for every visitor
     instead of whatever serif the operating system happens to ship. The
     stacks behind them only cover the swap window and outright failure. */
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  --font-display: "Fraunces", "Iowan Old Style", "Palatino Linotype", Georgia,
                  "Times New Roman", serif;
  --font-serif: var(--font-display);
  --font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", "Cascadia Mono",
               Consolas, "Liberation Mono", monospace;

  /* Type scale. 48 ad-hoc sizes had collapsed to values a quarter of a pixel
     apart -- invisible individually, but it meant no two labels ever shared a
     baseline. Whole pixels at 16px root, so nothing lands on a half. */
  --text-3xs:  .625rem;    /* 10 */
  --text-2xs:  .6875rem;   /* 11 */
  --text-xs:   .75rem;     /* 12 */
  --text-sm:   .8125rem;   /* 13 */
  --text-md:   .875rem;    /* 14 */
  --text-base: .9375rem;   /* 15 */
  --text-lg:   1rem;       /* 16 */
  --text-xl:   1.125rem;   /* 18 */
  --text-2xl:  1.25rem;    /* 20 */
  --text-3xl:  1.375rem;   /* 22 */
  --text-4xl:  1.5rem;     /* 24 */
  /* h1-h3 stay fluid: display sizes want to respond to the viewport, UI text
     does not. */

  /* Spacing scale, 4px based. Was 47 distinct values. */
  --space-0:  .125rem;     /*  2 */
  --space-1:  .25rem;      /*  4 */
  --space-2:  .5rem;       /*  8 */
  --space-3:  .75rem;      /* 12 */
  --space-4:  1rem;        /* 16 */
  --space-5:  1.25rem;     /* 20 */
  --space-6:  1.5rem;      /* 24 */
  --space-7:  2rem;        /* 32 */
  --space-8:  2.5rem;      /* 40 */
  --space-9:  3rem;        /* 48 */
  --space-10: 4rem;        /* 64 */
  --space-11: 5rem;        /* 80 */

  --mark-1: #2f6f68;
  --mark-2: #5f9ea0;
  --mark-3: #e4dfd6;
  --mark-4: #f2b134;
  --mark-5: #c05746;
  --mark-fault: #a4362c;

  --radius-sm: 6px;
  --radius:    10px;
  --radius-lg: 16px;
  --radius-xl: 22px;

  --shadow-xs: 0 1px 2px rgba(22, 25, 29, .05);
  --shadow-sm: 0 1px 3px rgba(22, 25, 29, .07), 0 1px 2px rgba(22, 25, 29, .04);
  --shadow:    0 4px 14px rgba(22, 25, 29, .07), 0 1px 3px rgba(22, 25, 29, .05);
  --shadow-lg: 0 18px 44px rgba(22, 25, 29, .11), 0 4px 12px rgba(22, 25, 29, .05);

  --wrap: 1180px;
  --gutter: clamp(1rem, 4vw, 2.5rem);
  --header-h: 68px;
}

[data-theme="dark"] {
  color-scheme: dark;

  --mark-1: #3f8f86;
  --mark-2: #74b3b5;
  --mark-3: #39414b;
  --mark-4: #d9a344;
  --mark-5: #d4705f;
  --mark-fault: #c8564a;

  --paper:       #14171b;
  --paper-2:     #191d22;
  --surface:     #1c2126;
  --surface-2:   #222830;
  --surface-3:   #2c333c;

  --ink:         #f0eeea;
  --ink-2:       #cbd1d8;
  --muted:       #98a1ac;
  --faint:       #86909d;

  --line:        #2e353d;
  --line-2:      #69737e;

  --accent:      #6fb7ac;
  --accent-2:    #8ecdc3;
  --accent-3:    #a9ded5;
  --accent-soft: #1d2f2e;
  --accent-line: #37544f;

  --clay:        #d99a63;
  --clay-soft:   #33261c;
  --ok:          #6cbb8b;
  --ok-soft:     #1c2c23;
  --warn:        #d3ab63;
  --warn-soft:   #2e2718;
  --danger:      #e08a80;
  --danger-soft: #331f1d;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .4);
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, .45);
  --shadow:    0 4px 14px rgba(0, 0, 0, .45);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, .55);
}

/* ---------------------------------------------------------------- reset */

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

body {
  margin: 0;
  font-optical-sizing: auto;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-wrap: break-word;
}

img, svg, video { max-width: 100%; height: auto; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }
[hidden] { display: none !important; }

h1, h2, h3, h4, h5 {
  margin: 0 0 .5em;
  font-family: var(--font-display);
  line-height: 1.16;
  letter-spacing: -0.014em;
  font-weight: 600;
  color: var(--ink);
  text-wrap: balance;
}
h1 { font-weight: 620; }
h1 { font-size: clamp(2rem, 1.35rem + 2.6vw, 3.1rem); letter-spacing: -0.03em; }
h2 { font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2.05rem); letter-spacing: -0.026em; }
h3 { font-size: clamp(1.16rem, 1.05rem + .5vw, 1.4rem); }
h4 { font-size: var(--text-lg); }
p  { margin: 0 0 1em; }

a { color: var(--accent-2); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent-3); }

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

code, kbd, pre, samp { font-family: var(--font-mono); font-size: .9em; }

hr { border: 0; border-top: 1px solid var(--line); margin: var(--space-8) 0; }

::selection { background: var(--accent-soft); color: var(--accent-3); }

/* -------------------------------------------------------------- layout */

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); }
.wrap--narrow { max-width: 760px; }
.wrap--wide { max-width: 1320px; }

.section { padding-block: clamp(2.5rem, 5vw, 4.5rem); }
.section--tight { padding-block: clamp(1.75rem, 3vw, 2.75rem); }
.section--paper { background: var(--paper-2); border-block: 1px solid var(--line); }

.stack > * + * { margin-top: var(--stack-gap, var(--space-4)); }
.cluster { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

.skip-link {
  position: absolute; left: 50%; top: -100px; transform: translateX(-50%);
  z-index: 200; background: var(--accent); color: #fff; padding: var(--space-3) var(--space-5);
  border-radius: 0 0 var(--radius) var(--radius); font-weight: 600;
  transition: top .15s ease;
}
.skip-link:focus { top: 0; color: #fff; }

/* -------------------------------------------------------------- header */

.site-header {
  position: sticky; top: 0; z-index: 60;
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--line);
}
.site-header__inner {
  display: flex; align-items: center; gap: var(--space-7);
  min-height: var(--header-h);
}

/* -- brand -------------------------------------------------------------- */

.brand {
  display: inline-flex; align-items: center; gap: var(--space-2);
  color: var(--ink); text-decoration: none; flex-shrink: 0;
}
.brand:hover { color: var(--ink); }
.brand__mark {
  width: 24px; height: 24px; border-radius: 7px; overflow: hidden;
  flex-shrink: 0; display: block;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
}
.brand__mark svg { width: 100%; height: 100%; display: block; }
.brand__word {
  font-family: var(--font-serif); font-size: var(--text-2xl); font-weight: 600;
  letter-spacing: -0.015em; line-height: 1;
}
.brand__craft { color: var(--accent); }

/* The mark was four literal hexes in the markup, repeated in the header and
   the footer, so it alone never followed the theme. */
.brand__q1 { fill: var(--mark-1); }
.brand__q2 { fill: var(--mark-2); }
.brand__q3 { fill: var(--mark-4); }
.brand__q4 { fill: var(--mark-5); }

/* Running stitch along the two seams, so the four squares read as a quilt
   block. Same thread color in both themes: it sits on the fabric, not the page. */
.brand__seam {
  fill: none; stroke: #fff8ec; stroke-opacity: .92;
  stroke-width: 1; stroke-dasharray: 1.7 1.3; stroke-linecap: butt;
}

/* -- primary nav -------------------------------------------------------- */

/* Every tool is a direct link. Nothing important sits behind a menu, so the
   bar is one scan rather than a click-then-scan. */
.nav {
  display: flex; align-items: center; justify-content: space-between;
  flex: 1 1 auto; min-width: 0; gap: var(--space-4);
}
.nav__group { display: flex; align-items: center; gap: var(--space-1); }

.nav__link {
  position: relative;
  display: inline-flex; align-items: center;
  padding: var(--space-2) var(--space-2); border: 0; background: none; border-radius: var(--radius-sm);
  color: var(--ink-2); text-decoration: none;
  font-size: var(--text-base); font-weight: 500; font-family: inherit;
  white-space: nowrap; cursor: pointer;
  transition: color .14s ease, background-color .14s ease;
}
.nav__link:hover { color: var(--ink); background: var(--surface-2); }

/* The current page gets a rule under the label. A filled pill here would read
   as a button sitting among the links. */
.nav__link[aria-current] { color: var(--ink); font-weight: 640; }
.nav__link[aria-current]::after {
  content: ""; position: absolute; left: .6rem; right: .6rem; bottom: .16rem;
  height: 2px; border-radius: 2px; background: var(--accent);
}

/* -- tool swatches (shared by the sidebar) ------------------------------ */

.tool-swatch {
  width: 26px; height: 26px; border-radius: 7px; flex-shrink: 0; margin-top: 1px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .1);
}
.tool-swatch[data-tool="palette"] {
  background: conic-gradient(from 140deg, #2f6f68, #5f9ea0, #f2b134, #c05746, #2f6f68);
}
.tool-swatch[data-tool="contrast"] {
  background: linear-gradient(105deg, #16191d 0 50%, #f7f5f1 50% 100%);
}
.tool-swatch[data-tool="convert"] {
  background: linear-gradient(135deg, #3d5a80 0 33%, #5f9ea0 33% 66%, #c8dedb 66% 100%);
}
.tool-swatch[data-tool="gradient"] {
  background: linear-gradient(135deg, #2f6f68, #f2b134);
}

/* -- actions ------------------------------------------------------------ */

.header-actions { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }

.icon-btn {
  display: inline-grid; place-items: center; width: 36px; height: 36px;
  border: 1px solid var(--line); background: transparent;
  border-radius: var(--radius-sm); cursor: pointer; color: var(--ink-2);
  transition: background-color .14s ease, color .14s ease, border-color .14s ease;
}
.icon-btn:hover { background: var(--surface-2); color: var(--ink); border-color: var(--line-2); }
.icon-btn__sun { display: none; }
[data-theme="dark"] .icon-btn__moon { display: none; }
[data-theme="dark"] .icon-btn__sun { display: block; }

.nav-toggle { display: none; }

/* Six links plus the brand and actions need roughly 1000px. Below that the
   whole nav moves into the sidebar rather than wrapping or truncating. */
@media (max-width: 1000px) {
  .nav { display: none; }
  .nav-toggle { display: inline-grid; }
  .site-header__inner { gap: var(--space-4); }
  .brand { margin-right: auto; }
}

@media (max-width: 360px) {
  .brand__word { font-size: var(--text-lg); }
  .brand__mark { width: 21px; height: 21px; border-radius: 6px; }
}

/* -- right-hand sidebar ------------------------------------------------- */

.sidebar-backdrop {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(16, 18, 22, .42);
  animation: cc-fade .2s ease;
}
@keyframes cc-fade { from { opacity: 0; } to { opacity: 1; } }

.sidebar {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 72;
  width: min(348px, 88vw);
  display: flex; flex-direction: column;
  background: var(--surface); border-left: 1px solid var(--line);
  box-shadow: -18px 0 44px rgba(16, 18, 22, .16);
  transform: translateX(100%); visibility: hidden;
  transition: transform .26s cubic-bezier(.32, .72, 0, 1), visibility .26s;
}
.sidebar[data-open="true"] { transform: translateX(0); visibility: visible; }

@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
}

.sidebar__head {
  display: flex; align-items: center; justify-content: flex-end;
  padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

.sidebar__body {
  padding: var(--space-5) var(--space-5) var(--space-7);
  overflow-y: auto; flex: 1;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

.sidebar__label {
  margin: 0 0 var(--space-2); font-size: var(--text-2xs); font-weight: 680;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}
.sidebar__label + .sidebar__row { margin-top: 0; }
.sidebar__row + .sidebar__label { margin-top: var(--space-6); }

/*
  One row shape for every destination. The tools carried an icon and a two-line
  description while the links below were bare text, which read as two menus
  stacked rather than one. The descriptions are gone: in a drawer you read them
  once and then scroll past them on every visit, and the names say it anyway.
*/
.sidebar__row {
  display: flex; align-items: center; gap: var(--space-3);
  min-height: 44px; padding: var(--space-2);
  margin-inline: calc(var(--space-2) * -1);
  border-radius: var(--radius); text-decoration: none;
  color: var(--ink); font-weight: 550; font-size: var(--text-lg);
}
.sidebar__row:hover, .sidebar__row:active { background: var(--surface-2); color: var(--ink); }
.sidebar__row[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent-3); font-weight: 650;
}

.sidebar__icon {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 26px; height: 26px; color: var(--muted);
}
.sidebar__icon svg { width: 20px; height: 20px; }
/* The swatch nudges down 1px to meet the first line of a two-line block. The
   rows are one line and centred now, so that nudge only breaks alignment
   against the stroke icons beside it. */
.sidebar__row .tool-swatch { margin-top: 0; }
.sidebar__row:hover .sidebar__icon { color: var(--ink-2); }
.sidebar__row[aria-current="page"] .sidebar__icon { color: var(--accent-2); }

/* -------------------------------------------------------------- footer */

.site-footer {
  margin-top: var(--space-11); background: var(--paper-2); border-top: 1px solid var(--line);
}

.site-footer .wrap { padding-block: var(--space-9) var(--space-6); }

/* The link columns hug their content and sit right; the brand block takes what
   is left. That stays composed at any width, where three equal stretched
   columns look sparse on a wide screen. */
/* No `fr` track: the brand block is capped so it cannot swallow the slack,
   and space-between then spreads the leftover evenly between the columns
   rather than leaving one wide void in the middle. */
.footer-grid {
  display: grid; gap: var(--space-8) clamp(1.75rem, 3vw, 3rem);
  grid-template-columns: minmax(240px, 24rem) repeat(3, minmax(8rem, auto));
  justify-content: space-between;
}

.footer-about { max-width: 34ch; }
.brand--footer { margin-bottom: var(--space-4); }
.footer-about__text {
  color: var(--muted); font-size: var(--text-md); line-height: 1.6; margin: 0 0 var(--space-4);
}

.footer-badges { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; }
.footer-badge {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: var(--space-1) var(--space-2); border-radius: 100px;
  border: 1px solid var(--line-2); color: var(--muted);
  font-size: var(--text-xs); font-weight: 560; white-space: nowrap;
}
.footer-badge::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
}

.footer-col h2 {
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .11em;
  color: var(--ink-2); font-weight: 700; margin: 0 0 var(--space-4);
}
.footer-col ul { list-style: none; margin: 0; padding: 0; }
.footer-col li { margin-bottom: var(--space-2); }
.footer-col a {
  color: var(--muted); text-decoration: none; font-size: var(--text-base);
  transition: color .14s ease;
}
.footer-col a:hover { color: var(--accent-2); text-decoration: underline; }

.footer-base {
  margin-top: var(--space-9); padding-top: var(--space-6); border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-6); justify-content: space-between;
  color: var(--muted); font-size: var(--text-sm);
}
.footer-base p { margin: 0; }

@media (max-width: 900px) {
  .footer-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    justify-content: stretch;
  }
  .footer-about { grid-column: 1 / -1; max-width: 52ch; }
}

@media (max-width: 720px) {
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-7) var(--space-6); }
}

@media (max-width: 560px) {
  .site-footer { margin-top: var(--space-9); }
  .footer-base { margin-top: var(--space-7); }
}

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

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4); min-height: 42px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  font-size: var(--text-base); font-weight: 600; line-height: 1.2;
  text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: background-color .14s ease, border-color .14s ease, color .14s ease;
}
.btn--primary { background: var(--accent); color: #fff; }
.btn--primary:hover { background: var(--accent-2); color: #fff; }
[data-theme="dark"] .btn--primary { color: #10201e; }
[data-theme="dark"] .btn--primary:hover { color: #10201e; }
.btn--ghost { background: var(--surface); border-color: var(--line-2); color: var(--ink); }
.btn--ghost:hover { background: var(--surface-2); color: var(--ink); border-color: var(--muted); }
.btn--quiet { background: transparent; color: var(--ink-2); padding-inline: var(--space-2); }
.btn--quiet:hover { background: var(--surface-2); color: var(--ink); }
.btn--sm { min-height: 34px; padding: var(--space-1) var(--space-3); font-size: var(--text-sm); }
.btn--block { width: 100%; }

.field { display: block; margin-bottom: var(--space-4); }
.field__label {
  display: block; font-size: var(--text-sm); font-weight: 620; letter-spacing: .01em;
  color: var(--ink-2); margin-bottom: var(--space-1);
}
.field__hint { display: block; font-size: var(--text-sm); color: var(--muted); margin-top: var(--space-1); line-height: 1.45; }

.input, .select, textarea.input {
  width: 100%; padding: var(--space-2) var(--space-3); min-height: 42px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  transition: border-color .14s ease, box-shadow .14s ease;
}
.input:focus, .select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
  outline: none;
}
.input--mono { font-family: var(--font-mono); font-size: var(--text-md); letter-spacing: .01em; }

.select {
  appearance: none; padding-right: var(--space-7); cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%236b7480' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .75rem center;
}

/* Colour input: a native swatch picker glued to a text field. */
.color-field { display: flex; gap: var(--space-2); align-items: stretch; }
.color-field .input { flex: 1 1 auto; min-width: 0; }
.color-chip {
  flex: 0 0 auto; width: 46px; padding: 0; border: 1px solid var(--line-2);
  border-radius: var(--radius-sm); cursor: pointer; background: none;
  overflow: hidden;
}
.color-chip::-webkit-color-swatch-wrapper { padding: 3px; }
.color-chip::-webkit-color-swatch { border: 0; border-radius: 4px; }
.color-chip::-moz-color-swatch { border: 0; border-radius: 4px; }

.range {
  width: 100%; appearance: none; background: transparent; height: 28px; cursor: pointer;
}
.range::-webkit-slider-runnable-track {
  height: 5px; border-radius: 3px; background: var(--surface-3);
}
.range::-moz-range-track { height: 5px; border-radius: 3px; background: var(--surface-3); }
.range::-webkit-slider-thumb {
  appearance: none; width: 18px; height: 18px; margin-top: -6.5px;
  border-radius: 50%; background: var(--accent); border: 2px solid var(--surface);
  box-shadow: var(--shadow-sm);
}
.range::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%; background: var(--accent);
  border: 2px solid var(--surface); box-shadow: var(--shadow-sm);
}

/* Segmented radio group */
.segmented { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.segmented input { position: absolute; opacity: 0; width: 0; height: 0; }
.segmented label {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: var(--space-2) var(--space-3); min-height: 36px;
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink-2);
  font-size: var(--text-md); font-weight: 550; cursor: pointer;
}
.segmented label:hover { border-color: var(--muted); color: var(--ink); }
.segmented input:checked + label {
  background: var(--accent-soft); border-color: var(--accent);
  color: var(--accent-3); font-weight: 640;
}
.segmented input:focus-visible + label { outline: 2.5px solid var(--accent); outline-offset: 2px; }

/* --------------------------------------------------------------- cards */

.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-xs);
}
.card__body { padding: clamp(1.1rem, 2.4vw, 1.6rem); }
.card__head {
  padding: var(--space-4) clamp(1.1rem, 2.4vw, 1.6rem);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  flex-wrap: wrap;
}
.card__head h2, .card__head h3 { margin: 0; font-size: var(--text-lg); }

.panel {
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--space-4);
}

.eyebrow {
  display: inline-block; font-size: var(--text-xs); font-weight: 680;
  text-transform: uppercase; letter-spacing: .11em; color: var(--accent-2);
  margin-bottom: var(--space-3);
}

.lede { font-size: clamp(1.02rem, .98rem + .3vw, 1.2rem); color: var(--ink-2); line-height: 1.65; }

.badge {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: var(--space-0) var(--space-2); border-radius: 100px;
  font-size: var(--text-xs); font-weight: 650; letter-spacing: .02em;
  background: var(--surface-3); color: var(--ink-2); white-space: nowrap;
}
.badge--pass { background: var(--ok-soft); color: var(--ok); }
.badge--fail { background: var(--danger-soft); color: var(--danger); }
.badge--warn { background: var(--warn-soft); color: var(--warn); }
.badge--accent { background: var(--accent-soft); color: var(--accent-3); }

.tag-pill {
  display: inline-block; padding: var(--space-1) var(--space-2); border-radius: 100px;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--ink-2); font-size: var(--text-sm); text-decoration: none;
}
.tag-pill:hover { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-3); }

/* ---------------------------------------------------------- breadcrumbs */

.breadcrumbs { padding-block: var(--space-4) var(--space-1); font-size: var(--text-md); color: var(--muted); }
.breadcrumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; padding: 0; }
.breadcrumbs li::after { content: "/"; margin-left: var(--space-2); color: var(--faint); }
.breadcrumbs li:last-child::after { content: none; }
.breadcrumbs a { color: var(--muted); text-decoration: none; }
.breadcrumbs a:hover { color: var(--accent-2); text-decoration: underline; }
.breadcrumbs [aria-current="page"] { color: var(--ink-2); }

/* ---------------------------------------------------------- page header */

.page-head { padding-block: clamp(1.5rem, 3vw, 2.75rem) clamp(1.25rem, 2.5vw, 2rem); }
.page-head h1 { margin-bottom: var(--space-2); }
.page-head .lede { max-width: 62ch; }

/* ------------------------------------------------------------ tool grid */

.tool-layout {
  display: grid; gap: clamp(1.25rem, 2.5vw, 2rem);
  grid-template-columns: minmax(280px, 340px) minmax(0, 1fr);
  align-items: start;
}
@media (max-width: 940px) { .tool-layout { grid-template-columns: minmax(0, 1fr); } }

.controls { position: sticky; top: calc(var(--header-h) + 1rem); }
@media (max-width: 940px) { .controls { position: static; } }

.results { min-width: 0; }
.results > * + * { margin-top: var(--space-6); }

/* --------------------------------------------------------------- swatch */

.swatch-row {
  display: grid; gap: var(--space-2); border-radius: var(--radius-lg); overflow: hidden;
  grid-template-columns: repeat(auto-fit, minmax(126px, 1fr));
}
.swatch {
  position: relative; display: flex; flex-direction: column; justify-content: flex-end;
  min-height: 170px; padding: var(--space-3); border-radius: var(--radius);
  border: 1px solid rgba(0, 0, 0, .09);
  text-align: left; transition: transform .16s ease, box-shadow .16s ease;
}
.swatch:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.swatch__hex {
  font-family: var(--font-mono); font-size: var(--text-base); font-weight: 600;
  letter-spacing: .02em; margin: 0;
}
.swatch__meta { font-size: var(--text-xs); opacity: .8; margin: var(--space-0) 0 0; line-height: 1.35; }
.swatch__tools { position: absolute; inset: .55rem .55rem auto auto; display: flex; gap: var(--space-1); }
.swatch__btn {
  display: inline-grid; place-items: center; width: 30px; height: 30px;
  border-radius: var(--radius-sm); border: 1px solid currentColor;
  background: transparent; cursor: pointer; padding: 0;
  /* These are controls over an arbitrary user colour, so they need to stay
     visible at rest, not only on hover. */
  opacity: .72;
}
.swatch__btn:hover, .swatch__btn[aria-pressed="true"] { opacity: 1; }
.swatch__btn[aria-pressed="true"] { background: currentColor; }

/* The edit control is a label with the real <input type="color"> stretched
   invisibly across it: the input is what gets clicked and focused, the label
   is what gets seen. Focus is drawn on the label, since the input is
   transparent. */
.swatch__btn--edit { position: relative; overflow: hidden; }
.swatch__btn--edit input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer; border: 0; padding: 0; margin: 0;
}
.swatch__btn--edit:focus-within { outline: 2.5px solid var(--accent); outline-offset: 2px; opacity: 1; }

/* A locked swatch fills the button with the swatch's ink colour, so the icon
   has to be the swatch's own background to stay legible. Using a theme colour
   here breaks in both directions: a white icon on a white button for a black
   swatch in light mode, and a near-black icon on a black button for a white
   swatch in dark mode. --swatch-bg is set inline on each swatch. */
.swatch__btn[aria-pressed="true"] svg { color: var(--swatch-bg, #fff); }

.swatch-strip { display: flex; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); }
.swatch-strip > * { flex: 1 1 0; min-height: 54px; }

.mini-swatch {
  display: flex; align-items: flex-end; justify-content: center;
  min-height: 66px; padding: var(--space-1); border-radius: var(--radius-sm);
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 600;
  border: 1px solid rgba(0, 0, 0, .08); cursor: pointer;
  min-width: 0; overflow: hidden;
}

/* Every fixed-count swatch grid on the site uses minmax(0, 1fr), never a bare
   1fr. A 1fr track will not shrink below its content, so a row of hex labels
   sets a floor on the whole column -- and inside a card with overflow hidden
   the last swatches are simply cut off, with no scrollbar to say so. */
.scale-row { display: grid; grid-template-columns: repeat(11, minmax(0, 1fr)); gap: var(--space-1); }
@media (max-width: 720px) { .scale-row { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
@media (max-width: 420px) { .scale-row { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.scale-step { text-align: center; min-width: 0; }
.scale-step__chip {
  height: 62px; border-radius: var(--radius-sm); border: 1px solid rgba(0, 0, 0, .09);
  cursor: pointer; width: 100%; display: grid; place-items: center; overflow: hidden;
  font-family: var(--font-mono); font-size: var(--text-3xs); font-weight: 700;
}
.scale-step__chip[data-current="true"] { box-shadow: 0 0 0 2.5px var(--accent); }
.scale-step__label {
  display: block; font-family: var(--font-mono); font-size: var(--text-2xs);
  color: var(--muted); margin-top: var(--space-1);
}

/* ---------------------------------------------------------------- table */

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.data {
  width: 100%; border-collapse: collapse; font-size: var(--text-md); min-width: 420px;
}
table.data th, table.data td {
  padding: var(--space-2) var(--space-3); text-align: left; border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
table.data tr.is-current th, table.data tr.is-current td { background: var(--accent-soft); }

table.data th {
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted); font-weight: 650; white-space: nowrap;
}
table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: var(--surface-2); }
table.data td.mono { font-family: var(--font-mono); font-size: var(--text-md); }

.copy-row {
  display: flex; align-items: center; gap: var(--space-2); justify-content: space-between;
  width: 100%; background: none; border: 0; padding: 0; cursor: pointer;
  font-family: var(--font-mono); font-size: var(--text-md); color: var(--ink); text-align: left;
}
.copy-row:hover { color: var(--accent-2); }
.copy-row__icon { opacity: 0; color: var(--muted); flex-shrink: 0; }
.copy-row:hover .copy-row__icon, .copy-row:focus-visible .copy-row__icon { opacity: 1; }

/* -------------------------------------------------------------- metrics */

.metric-grid {
  display: grid; gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}
.metric {
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--space-3) var(--space-4);
}
.metric__label {
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); font-weight: 650; margin: 0 0 var(--space-1);
}
.metric__value {
  font-size: var(--text-3xl); font-weight: 660; letter-spacing: -0.02em;
  font-family: var(--font-mono); margin: 0; color: var(--ink);
}
.metric__note { font-size: var(--text-xs); color: var(--muted); margin: var(--space-1) 0 0; line-height: 1.4; }

.meter { height: 7px; border-radius: 4px; background: var(--surface-3); overflow: hidden; margin-top: var(--space-2); }
.meter__fill { height: 100%; border-radius: 4px; background: var(--accent); }

/* -------------------------------------------------------- code / export */

.code-block {
  position: relative; background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.code-block pre {
  margin: 0; padding: var(--space-4) var(--space-4); overflow-x: auto;
  font-size: var(--text-sm); line-height: 1.6; color: var(--ink-2);
  max-height: 320px;
}
.code-block__bar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4); border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.code-block__bar span {
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); font-weight: 650;
}

.tabs { display: flex; gap: var(--space-1); flex-wrap: wrap; border-bottom: 1px solid var(--line); }
.tabs button {
  padding: var(--space-2) var(--space-3); border: 0; background: none; cursor: pointer;
  font-size: var(--text-md); font-weight: 560; color: var(--muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs button:hover { color: var(--ink); }
.tabs button[aria-selected="true"] { color: var(--accent-2); border-bottom-color: var(--accent); font-weight: 650; }

/* ------------------------------------------------------------- preview  */

.checker {
  background-image:
    linear-gradient(45deg, var(--surface-3) 25%, transparent 25%),
    linear-gradient(-45deg, var(--surface-3) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--surface-3) 75%),
    linear-gradient(-45deg, transparent 75%, var(--surface-3) 75%);
  background-size: 16px 16px;
  background-position: 0 0, 0 8px, 8px -8px, -8px 0;
}

/* --------------------------------------------------------- contrast grid */

.contrast-grid { border-collapse: separate; border-spacing: 3px; font-size: var(--text-xs); }
.contrast-grid th {
  font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--muted);
  font-weight: 600; padding: var(--space-1); white-space: nowrap;
}
.contrast-grid td {
  width: 62px; height: 40px; border-radius: var(--radius-sm); text-align: center;
  font-family: var(--font-mono); font-weight: 650; padding: 0;
}

/* ------------------------------------------------------------- post card */

.post-grid {
  display: grid; gap: clamp(1rem, 2.5vw, 1.75rem);
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.post-card {
  display: flex; flex-direction: column; height: 100%;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); overflow: hidden;
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.post-card:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--line-2); }
.post-card__art { display: flex; height: 88px; }
.post-card__art > span { flex: 1 1 0; }
.post-card__art img { width: 100%; height: 100%; object-fit: cover; }
.post-card__body { padding: var(--space-4) var(--space-4) var(--space-4); display: flex; flex-direction: column; flex: 1; }

/* Title and excerpt are clamped to a fixed number of lines and reserve that
   space whether they fill it or not, so every card in the grid is exactly the
   same height and the bottoms line up. Left to size themselves they ranged
   from 349 to 372px and the rows looked ragged. */
.post-card__title {
  font-size: var(--text-lg); margin: var(--space-2) 0 var(--space-2); letter-spacing: -0.022em;
  line-height: 1.3; min-height: calc(2 * 1.3em);
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
}
.post-card__title a { color: var(--ink); text-decoration: none; }
.post-card__title a:hover { color: var(--accent-2); }
.post-card__excerpt {
  color: var(--muted); font-size: var(--text-md); margin: 0 0 var(--space-3);
  line-height: 1.5; min-height: calc(2 * 1.5em);
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
}
.post-card__foot {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  font-size: var(--text-xs); color: var(--faint); margin-top: auto;
}
.cat-label {
  display: inline-flex; align-items: center; gap: var(--space-1);
  font-size: var(--text-xs); font-weight: 660; letter-spacing: .04em; text-transform: uppercase;
  text-decoration: none;
}
.cat-label::before {
  content: ""; width: 8px; height: 8px; border-radius: 2px;
  background: var(--cat-accent, var(--accent));
}

.featured-post {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 0; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-xl); overflow: hidden; box-shadow: var(--shadow-sm);
}
@media (max-width: 780px) {
  .featured-post { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, the art sits above the text and a 170px band pushes the whole
     card off the first screen. */
  .featured-post__art { min-height: 104px; }
}
.featured-post__body { padding: clamp(1.25rem, 2.4vw, 1.9rem); align-self: center; }
.featured-post__art { display: flex; min-height: 170px; }

/* Now that the featured card sits in the results column its excerpt wraps to
   five or six lines, which made it the tallest block on the page. Three is
   enough to sell the article. */
.featured-post__body .muted {
  line-height: 1.55; margin-bottom: var(--space-4);
  display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3;
  -webkit-box-orient: vertical; overflow: hidden;
}
.featured-post__body h2 { font-size: clamp(1.3rem, 1.1rem + .8vw, 1.6rem); }
.featured-post__art > span { flex: 1 1 0; }
.featured-post__art img { width: 100%; height: 100%; object-fit: cover; }

/* --------------------------------------------------------------- article */

.article-layout {
  display: grid; gap: clamp(1.5rem, 4vw, 3.5rem);
  grid-template-columns: minmax(0, 1fr) 260px;
  align-items: start;
}
@media (max-width: 1000px) { .article-layout { grid-template-columns: minmax(0, 1fr); } }
/* A grid item's minimum width defaults to its content's, so a 420px table
   inside .prose held the whole column at 424px on a 375px phone -- every
   paragraph on the cookie policy laid out past the right edge, and
   .table-scroll never got the chance to scroll because its parent had
   already grown. The bare `1fr` above used to be the culprit: it means
   minmax(auto, 1fr). Stated here as well so the fix does not depend on
   anyone remembering the minmax() when the tracks are next edited. */
.article-layout > * { min-width: 0; }

.prose { font-family: var(--font-serif); font-size: var(--text-xl); line-height: 1.75; color: var(--ink-2); max-width: 70ch; }
.prose > * + * { margin-top: 1.15em; }
.prose h2 {
  font-family: var(--font-sans); font-size: var(--text-4xl); margin-top: 2.2em; margin-bottom: .1em;
  color: var(--ink); scroll-margin-top: calc(var(--header-h) + var(--space-4));
}
.prose h3 {
  font-family: var(--font-sans); font-size: var(--text-2xl); margin-top: 1.8em; margin-bottom: 0;
  color: var(--ink); scroll-margin-top: calc(var(--header-h) + var(--space-4));
}
.prose ul, .prose ol { padding-left: 1.4em; }
.prose li + li { margin-top: .4em; }
.prose blockquote {
  margin-inline: 0; padding: .3em 0 .3em 1.3em;
  border-left: 3px solid var(--accent); color: var(--ink); font-style: italic;
}
.prose code {
  background: var(--surface-2); border: 1px solid var(--line);
  padding: .1em .35em; border-radius: 4px; font-size: .87em; font-family: var(--font-mono);
}
.prose pre {
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--space-4); overflow-x: auto; font-size: var(--text-md);
  line-height: 1.6;
}
.prose pre code { background: none; border: 0; padding: 0; font-size: inherit; }
.prose img { border-radius: var(--radius); margin-block: 1.6em; }
.prose table { width: 100%; border-collapse: collapse; font-family: var(--font-sans); font-size: var(--text-base); }
.prose th, .prose td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--line); text-align: left; }
.prose th { font-weight: 650; color: var(--ink); font-size: var(--text-sm); text-transform: uppercase; letter-spacing: .06em; }
.prose .admonition {
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  border-radius: var(--radius); padding: var(--space-4) var(--space-5); font-family: var(--font-sans);
  font-size: var(--text-base);
}
.prose .admonition-title { font-weight: 680; margin-bottom: var(--space-1); color: var(--accent-3); }
.prose hr { margin-block: 2.4em; }

.toc { position: sticky; top: calc(var(--header-h) + 1.5rem); font-size: var(--text-md); }
.toc h2 {
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .1em;
  color: var(--muted); margin-bottom: var(--space-3); font-weight: 660;
}
.toc ul { list-style: none; margin: 0; padding: 0; border-left: 2px solid var(--line); }
.toc ul ul { border: 0; padding-left: var(--space-3); }
.toc li { margin: 0; }
.toc a {
  display: block; padding: var(--space-1) 0 var(--space-1) var(--space-3); color: var(--muted);
  text-decoration: none; margin-left: -2px; border-left: 2px solid transparent;
}
.toc a:hover { color: var(--accent-2); border-left-color: var(--accent-line); }
@media (max-width: 1000px) { .toc { position: static; } }

.article-head {
  padding-block: var(--space-4) var(--space-7); border-bottom: 1px solid var(--line);
  margin-bottom: var(--space-7); max-width: 72ch;
}
.article-meta { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); color: var(--muted); font-size: var(--text-md); align-items: center; }
/* The hero runs the full width, the way the tools' answer bands do. Boxing it
   inside the text column was the one place the article stopped agreeing with
   the rest of the site. */
.article-hero {
  display: flex; width: 100%; overflow: hidden;
  min-height: clamp(180px, 22vw, 300px);
  border-block: 1px solid var(--line);
}
.article-hero > span { flex: 1 1 0; }

/* -------------------------------------------------------------- utility */

.muted { color: var(--muted); }
.small { font-size: var(--text-md); }
.mono { font-family: var(--font-mono); }
.center { text-align: center; }
 .visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.feature-grid {
  display: grid; gap: clamp(1rem, 2.5vw, 1.5rem);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.feature {
  display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-6);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); text-decoration: none; color: inherit;
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
a.feature:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--accent-line); }
.feature__icon {
  width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-2); flex-shrink: 0;
}
.feature h3 { margin: 0; font-size: var(--text-lg); }
.feature p { margin: 0; color: var(--muted); font-size: var(--text-md); }

.faq-item { border-bottom: 1px solid var(--line); }
.faq-item summary {
  cursor: pointer; padding: var(--space-4) 0; font-weight: 620; font-size: var(--text-lg);
  list-style: none; display: flex; justify-content: space-between; gap: var(--space-4);
  align-items: flex-start; color: var(--ink);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: "+"; font-size: var(--text-3xl); line-height: 1; color: var(--accent);
  flex-shrink: 0; font-weight: 400;
}
.faq-item[open] summary::after { content: "\2212"; }
.faq-item__body { padding-bottom: var(--space-4); color: var(--ink-2); max-width: 72ch; }
.faq-item__body p:last-child { margin-bottom: 0; }

.steps { list-style: none; counter-reset: step; margin: 0; padding: 0; }
.steps li {
  counter-increment: step; position: relative; padding-left: var(--space-8);
  padding-bottom: var(--space-5); border-left: 1px solid var(--line); margin-left: var(--space-3);
}
.steps li:last-child { border-left-color: transparent; padding-bottom: 0; }
.steps li::before {
  content: counter(step); position: absolute; left: -.85rem; top: -.1rem;
  width: 1.7rem; height: 1.7rem; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent-3);
  display: grid; place-items: center; font-size: var(--text-xs); font-weight: 700;
  border: 1px solid var(--accent-line);
}
.steps strong { display: block; color: var(--ink); margin-bottom: var(--space-0); }
.steps span { color: var(--muted); font-size: var(--text-base); }

/* ------------------------------------------------------- layout helpers */
/*
  These replace 113 static inline declarations that had accumulated in the
  templates. They are named for the job, not the property, so a change of mind
  about spacing happens here rather than in fifteen files.
*/

/* A trailing note under a block of content. */
.note-after { margin: var(--space-4) 0 0; }
.note-after--lg { margin: var(--space-5) 0 0; }

/* A column of labelled blocks inside a card body. */
.block-stack { display: grid; gap: var(--space-4); }
.block-stack--lg { gap: var(--space-5); }
.block-stack--sm { gap: var(--space-3); }

/* Readable measures, so prose never runs the full width of a card. */
.measure { max-width: 62ch; }
.measure--sm { max-width: 60ch; }
.measure--lg { max-width: 64ch; }

/* Rhythm between a section and what precedes it. */

/* Fieldsets carry a legend, never a border here. */
.fieldset { border: 0; padding: 0; margin: 0 0 var(--space-5); }

.field__label--tight { margin-bottom: var(--space-1); }
.field__label--spaced { margin-top: var(--space-6); }

.panel--danger { border-color: var(--danger); background: var(--danger-soft); }
.panel--accent { border-color: var(--accent-line); background: var(--accent-soft); }

/* -- converter: channel meters ------------------------------------------ */
.channel-list { display: grid; gap: var(--space-1); }
.channel-row {
  display: grid; grid-template-columns: 1.5rem minmax(0, 1fr) 4rem;
  gap: var(--space-2); align-items: center;
}
.channel-row .meter { margin: 0; }
.channel-row__value { text-align: right; }

/* -- swatch row variants ------------------------------------------------- */
.ramp-chip { border: 0; cursor: pointer; }
.swatch-row--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
/* Five #RRGGBB labels across a 320px phone: about 50px a cell once the gap
   comes in, and the label at 10px mono needs 42px plus 2px padding a side. */
@media (max-width: 360px) {
  .swatch-row--5 { gap: var(--space-1); }
  .swatch-row--5 .mini-swatch { font-size: var(--text-3xs); padding: 2px; }
}
.swatch-row--wide { grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); }
.mini-swatch--stacked {
  flex-direction: column; justify-content: center; gap: var(--space-1);
}
.mini-swatch--static { cursor: default; }
.mini-swatch__label { font-size: var(--text-3xs); opacity: .85; }

.cell--value { min-width: 15rem; }

/* -- gradient: stop editor ----------------------------------------------- */
.stop-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-3); align-items: center;
}
.stop-row__chip { width: 44px; height: 40px; }
.stop-row__head {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: var(--space-0);
}
.stop-row__hex {
  background: none; border: 0; padding: 0; cursor: pointer; font-weight: 600;
}
.stop-row__spacer { width: 38px; }

/* -- gradient: applied examples ------------------------------------------ */
.applied-grid {
  display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.applied-card {
  border-radius: var(--radius); padding: var(--space-6) var(--space-5);
  border: 1px solid var(--line);
}
.applied-card--hero {
  border: 0; min-height: 150px;
  display: flex; flex-direction: column; justify-content: flex-end;
}
.applied-card--parts { display: flex; flex-direction: column;
  gap: var(--space-3); justify-content: center; }
.applied-card--dot { display: grid; place-items: center; padding: var(--space-5); }
.applied-card__title { margin: 0; font-weight: 700; font-size: var(--text-xl); }
.applied-card__note { margin: var(--space-1) 0 0; font-size: var(--text-sm); opacity: .85; }
.applied-dot { width: 96px; height: 96px; border-radius: 50%; display: block; }
.applied-button {
  display: inline-block; padding: var(--space-2) var(--space-5);
  border-radius: 8px; font-weight: 650; text-align: center; font-size: var(--text-base);
}
.applied-bar { height: 8px; border-radius: 5px; }
.applied-rule { display: block; height: 3px; border-radius: 2px; }

/* -- contrast: suggestions ----------------------------------------------- */
.suggestion__title { margin: 0 0 var(--space-1); font-weight: 700; font-size: var(--text-base); }
.suggestion__note { margin: 0 0 var(--space-2); font-size: var(--text-sm); opacity: .85; }
.suggestion__value { margin: 0; font-family: var(--font-mono);
  font-size: var(--text-sm); opacity: .9; }
.suggestion__swap {
  background: none; border: 0; cursor: pointer; font-weight: 700; padding: 0;
}
.was { text-decoration: line-through; }
.self-start { align-self: flex-start; }

.cluster--center { justify-content: center; }
.cluster--between { justify-content: space-between; }
.block-stack--xs { gap: var(--space-1); }

/* Two equal columns that collapse to one; the site's default prose pairing. */
.two-up {
  display: grid; gap: var(--space-8);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
/* Claim beside proof: the argument column stays readable while the worked
   example takes whatever is left. */
.two-up--proof { align-items: center; }
@media (min-width: 900px) { .two-up--proof { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); } }

/* A heading inside a panel or a narrow card head, where the h2 default is
   far too large for the role it plays. */
.panel__title { font-size: var(--text-lg); margin-bottom: var(--space-2); }
.feature__title { font-size: var(--text-xl); margin: 0; }
.feature__cta {
  font-size: var(--text-sm); color: var(--accent-2);
  font-weight: 600; margin-top: auto;
}
.btn--start { justify-content: flex-start; gap: var(--space-3); }
.preset-chip {
  width: 2.4rem; height: 1.1rem; border-radius: 4px;
  flex: 0 0 auto; border: 1px solid var(--line-2);
}

/* Blog sidebar navigation. The active state used to be three inline ternaries
   per link, which is also why these never picked up the dark theme. */
.side-nav { display: grid; gap: var(--space-2); }
.side-nav__link {
  text-decoration: none; font-weight: 500; color: var(--ink-2);
  font-size: var(--text-base);
}
.side-nav__link:hover { color: var(--accent-2); }
.side-nav__link[aria-current="page"] { font-weight: 680; color: var(--accent-2); }
/* A category link colours itself from its own accent when it is the active one. */
.side-nav__link--cat[aria-current="page"] { color: var(--cat-accent, var(--accent-2)); }
.side-nav__count { font-weight: 400; }

.post-feature__title { margin-bottom: var(--space-2); }
.post-feature__title a { color: inherit; text-decoration: none; }
.post-feature__cat { display: block; margin-bottom: var(--space-2); }
.search-card__body {
  display: grid; gap: var(--space-3); grid-template-columns: minmax(0, 1fr) auto;
}

/* ---------------------------------------------------------------- demos */
/*
  A worked example sitting inside prose: the claim above it, executed by the
  same code the tools run on. Set in the UI face rather than the reading face
  so it reads as apparatus, not argument.
*/
.prose--ui { font-family: var(--font-sans); font-size: var(--text-lg); }

.demo {
  /* figure carries a 40px UA margin-inline, which indented every example off
     the text column it belongs to. */
  margin: 0; font-family: var(--font-sans); font-size: var(--text-base); line-height: 1.5;
  color: var(--ink); border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface); overflow: hidden;
}
.prose > .demo { margin-top: 2em; }
.prose > .demo + * { margin-top: 2em; }

.demo__head {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  align-items: baseline; justify-content: space-between;
  padding: var(--space-3) var(--space-5);
  background: var(--surface-2); border-bottom: 1px solid var(--line);
}
.demo__title {
  margin: 0; font-family: var(--font-sans); font-size: var(--text-xs);
  text-transform: uppercase; letter-spacing: .09em; font-weight: 680; color: var(--muted);
}
.demo__verdict { font-size: var(--text-sm); color: var(--ink-2); }
.demo__body { padding: var(--space-5); }
.demo__foot {
  padding: var(--space-3) var(--space-5); border-top: 1px solid var(--line);
  background: var(--surface-2); font-size: var(--text-sm); color: var(--ink-2);
}
.demo__foot strong { color: var(--ink); }

/* -- contrast pairs ------------------------------------------------------ */
.demo-pairs {
  display: grid; gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
}
.demo-pair {
  border-radius: var(--radius); border: 1px solid var(--line); overflow: hidden;
}
.demo-pair__sample { padding: var(--space-4); font-weight: 600; }
.demo-pair__data {
  padding: var(--space-2) var(--space-4) var(--space-3);
  display: grid; gap: var(--space-0);
}
.demo-pair__ratio { font-family: var(--font-mono); font-weight: 700; font-size: var(--text-lg); }
.demo-pair__apca, .demo-pair__role { font-size: var(--text-xs); color: var(--muted); }

/* -- lightness ramps ----------------------------------------------------- */
.demo-ramp + .demo-ramp { margin-top: var(--space-5); }
.demo-ramp__label {
  display: flex; justify-content: space-between; gap: var(--space-3);
  font-size: var(--text-xs); color: var(--muted); margin-bottom: var(--space-2);
}
.demo-ramp__label b { color: var(--ink); font-weight: 680; }
.demo-ramp__row { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
.demo-ramp__row > :first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.demo-ramp__row > :last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.demo-chip {
  display: grid; place-items: center; min-height: 64px;
  font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 650;
}

/* -- round trip ---------------------------------------------------------- */
.demo-trip { display: grid; gap: var(--space-3); }
.demo-trip__row {
  display: grid; gap: var(--space-2) var(--space-3); align-items: center;
  grid-template-columns: 2.25rem minmax(0, 1fr) auto;
}
.demo-trip__chip { width: 2.25rem; height: 2.25rem; border-radius: var(--radius-sm); }
.demo-trip__path {
  font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink-2);
  overflow-x: auto; white-space: nowrap;
}
.demo-trip__ok { font-size: var(--text-xs); font-weight: 650; color: var(--ok); }

/* -- colour vision ------------------------------------------------------- */
.demo-cvd { display: grid; gap: var(--space-3); }
.demo-cvd__row { display: grid; gap: var(--space-1); }
.demo-cvd__label { font-size: var(--text-xs); color: var(--muted); }
.demo-cvd__label b { color: var(--ink); font-weight: 680; }
.demo-cvd__strip { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
.demo-cvd__strip > * { min-height: 34px; }
.demo-cvd__strip > :first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.demo-cvd__strip > :last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.demo-converge {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  font-size: var(--text-sm);
}
.demo-converge__pair { display: flex; }
.demo-converge__pair span { width: 1.6rem; height: 1.6rem; }
.demo-converge__pair :first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.demo-converge__pair :last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }

@media (max-width: 560px) {
  .demo-chip { min-height: 52px; font-size: var(--text-3xs); }
  .demo__body { padding: var(--space-4); }
  .demo__head, .demo__foot { padding-inline: var(--space-4); }
  /* The conversion path is the evidence. Letting it scroll off the right of a
     phone hides the half that proves the point, so it wraps instead. */
  .demo-trip__row { grid-template-columns: 2.25rem minmax(0, 1fr); }
  .demo-trip__path { white-space: normal; overflow-x: visible; overflow-wrap: anywhere; }
  .demo-trip__ok { grid-column: 2; }
}

/* --------------------------------------------------------- tool previews */
/*
  Each card on the toolkit index shows what its tool produces. The index used
  to be four descriptions and a thin decorative strip, on a site whose whole
  subject is colour.
*/
.feature--preview { padding-top: var(--space-4); }
.tool-preview {
  display: grid; gap: var(--space-2); margin-bottom: var(--space-3);
}
.tool-preview__caption {
  font-size: var(--text-xs); color: var(--muted); letter-spacing: .01em;
  min-height: 1.4em;
}

/* One fixed height for all four, so the cards line up across the grid. With
   min-height each preview sized to its own content and the row went ragged by
   7px. */
.tool-preview__strip,
.tool-preview__pair,
.tool-preview__convert,
.tool-preview__gradient {
  border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line);
  height: 84px;
}
.tool-preview__strip { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }

.tool-preview__pair {
  display: grid; align-content: center; gap: var(--space-0);
  padding: var(--space-3) var(--space-4);
}
.tool-preview__ratio {
  font-family: var(--font-mono); font-weight: 700; font-size: var(--text-2xl);
}
.tool-preview__sample { font-size: var(--text-sm); opacity: .8; }

.tool-preview__convert {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3); align-items: center; padding: var(--space-3);
  background: var(--surface-2);
}
.tool-preview__chip { width: 52px; height: 52px; border-radius: var(--radius-sm); }
.tool-preview__rows { display: grid; gap: var(--space-0); min-width: 0; }
.tool-preview__rows span {
  font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tool-preview__rows b {
  display: inline-block; width: 3.4rem; color: var(--muted); font-weight: 650;
}

/* ------------------------------------------------------ curated palettes */

.pal-grid {
  display: grid; gap: var(--space-5);
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}

/* A fixed count for the home strip. auto-fill gave three columns for four
   cards at some widths, which left one card stranded on a row of its own. */
.pal-grid--four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 900px) { .pal-grid--four { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .pal-grid--four { grid-template-columns: minmax(0, 1fr); } }

.pal-card {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface); overflow: hidden;
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.pal-card:hover { border-color: var(--line-2); box-shadow: var(--shadow-sm); }
.pal-card:has(:focus-visible) { border-color: var(--accent); }

.pal-card__link { display: block; text-decoration: none; color: inherit; }
.pal-card__strip {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; height: 108px;
}
.pal-card__body { display: block; padding: var(--space-3) var(--space-4) var(--space-4); }
.pal-card__name {
  display: block; font-family: var(--font-display); font-size: var(--text-xl);
  font-weight: 600; color: var(--ink); letter-spacing: -0.01em;
}
.pal-card__meta {
  display: block; margin-top: var(--space-1);
  font-size: var(--text-sm); color: var(--muted);
}

.pal-search { display: flex; gap: var(--space-2); max-width: 24rem; }
.pal-search .input { min-width: 0; }
.pal-moods { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.tag-pill--on {
  background: var(--accent-soft); border-color: var(--accent-line);
  color: var(--accent-3); font-weight: 650;
}

@media (max-width: 560px) {
  .pal-card__strip { height: 84px; }
  .pal-grid { gap: var(--space-4); }
}

/* ------------------------------------------------------- copy feedback */
/*
  The toast says what was copied; this says where from. It matters most on the
  palette bands, where a dozen swatches look alike and the corner toast cannot
  tell you which one you hit.

  An overlay ring, not a change to the swatch. Fading or tinting the swatch
  itself would show -- for a couple of hundred milliseconds -- a colour that is
  not the answer, sitting next to a hex code saying otherwise. On a colour tool
  that is a correctness bug, not a style choice. The ring is a separate layer:
  the colour underneath never moves.

  position: relative on a static element establishes a containing block and
  changes no layout. transform and opacity only, so nothing reflows, and the
  global prefers-reduced-motion rule collapses the duration for anyone who
  asked for that.
*/
[data-copied] { position: relative; }
[data-copied]::after {
  content: ""; position: absolute; inset: 0;
  border: 2px solid currentColor; border-radius: inherit;
  pointer-events: none;
  animation: cc-copied .5s ease-out forwards;
}
@keyframes cc-copied {
  from { transform: scale(1); opacity: .9; }
  to { transform: scale(1.12); opacity: 0; }
}

/* -------------------------------------------------------------- toast */

.toast {
  position: fixed; left: 50%; bottom: 1.5rem; transform: translate(-50%, 1rem);
  background: var(--ink); color: var(--paper); padding: var(--space-2) var(--space-4);
  border-radius: 100px; font-size: var(--text-md); font-weight: 560;
  box-shadow: var(--shadow-lg); opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease; z-index: 200;
}
.toast[data-show="true"] { opacity: 1; transform: translate(-50%, 0); }

/* --------------------------------------------------------------- htmx */

.htmx-indicator { opacity: 0; transition: opacity .2s ease; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }
.results.htmx-request { opacity: .58; transition: opacity .12s ease; }

/* ------------------------------------------------------- contrast result */
/*
  These carry a colour pair as an inline style, so only the shape lives here.
  The values match what the retired inline styles set, snapped to the scale:
  1.1rem padding to space-4, 12px radius to --radius, and .95/.85/.8rem to the
  nearest text steps.

  They went missing once already: the edit that introduced them anchored on a
  comment header whose dash count did not match, and str.replace fails silently
  on no match. The cards rendered with no padding, no border and 16px body text
  for several rounds before anyone looked.
*/

/* A sample of real text painted in a pair being tested. */
.pair-sample {
  padding: var(--space-3) var(--space-4); border-radius: 8px; font-weight: 600;
}

/* One simulation of the pair, with its own ratio underneath. */
.cvd-card {
  padding: var(--space-4); border-radius: var(--radius); border: 1px solid var(--line);
}
.cvd-card__title { margin: 0 0 var(--space-1); font-weight: 700; font-size: var(--text-base); }
.cvd-card__note { margin: 0 0 var(--space-2); font-size: var(--text-sm); opacity: .85; }
.cvd-card__ratio {
  margin: 0; font-family: var(--font-mono); font-size: var(--text-sm); opacity: .9;
}

/* Polarity is a sentence, not a number, so it does not take the metric size. */
.metric__value--text { font-size: var(--text-lg); line-height: 1.4; }

/* --------------------------------------------------------- pagination */

.pagination { display: flex; gap: var(--space-1); align-items: center; justify-content: center; flex-wrap: wrap; margin-top: var(--space-8); }
.pagination a, .pagination span {
  min-width: 40px; height: 40px; display: inline-grid; place-items: center;
  padding-inline: var(--space-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink-2); text-decoration: none; font-size: var(--text-md);
}
.pagination a:hover { border-color: var(--accent); color: var(--accent-2); }
.pagination [aria-current="page"] { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 650; }
[data-theme="dark"] .pagination [aria-current="page"] { color: #10201e; }
.pagination .is-disabled { opacity: .4; pointer-events: none; }

/* ----------------------------------------------------------------- hero */

.hero { padding-block: clamp(2.5rem, 6vw, 5rem) clamp(2rem, 4vw, 3.5rem); }
.hero__grid {
  display: grid; gap: clamp(2rem, 5vw, 3.5rem);
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); align-items: center;
}
@media (max-width: 900px) { .hero__grid { grid-template-columns: minmax(0, 1fr); } }
.hero h1 { margin-bottom: var(--space-4); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-7); }

.hero__demo {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-xl); padding: var(--space-4); box-shadow: var(--shadow-lg);
}
/* minmax(0, 1fr), not 1fr: a bare 1fr track cannot shrink below its content,
   and five swatches each as wide as their #RRGGBB label came to 290px -- more
   than a 320px phone has left once the card and page padding are taken off,
   so the hero pushed the whole page sideways. The swatch rules that shrink
   the label live with .hero__swatch below. */
.hero__swatches { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--space-2); }

.stat-row { display: flex; flex-wrap: wrap; gap: var(--space-6); margin-top: var(--space-7); }
.stat-row div strong { display: block; font-size: var(--text-4xl); letter-spacing: -0.02em; font-weight: 660; }
.stat-row div span { font-size: var(--text-sm); color: var(--muted); }

/* ------------------------------------------------------------ empty/404 */

.empty-state { text-align: center; padding: clamp(2.5rem, 6vw, 4.5rem) var(--space-4); }
.empty-state__mark {
  display: inline-flex; gap: var(--space-1); margin-bottom: var(--space-6);
}
.empty-state__mark span { width: 22px; height: 40px; border-radius: 5px; }

.empty-state__mark span:nth-child(1) { background: var(--mark-1); }
.empty-state__mark span:nth-child(2) { background: var(--mark-2); }
.empty-state__mark span:nth-child(3) { background: var(--mark-3); }
.empty-state__mark span:nth-child(4) { background: var(--mark-4); }
.empty-state__mark span:nth-child(5) { background: var(--mark-5); }
.empty-state__mark--fault span:nth-child(1) { background: var(--mark-fault); }
.empty-state__mark--fault span:nth-child(2) { background: var(--mark-5); }
.empty-state__mark--fault span:nth-child(3) { background: var(--mark-3); }

/* -------------------------------------------------- gradient controls */

/* Both the angle slider and the radial shape picker are always in the DOM.
   :has() shows the one that matters the moment the type radio changes, with
   no round trip and no JavaScript. */
.gradient-controls:has(#type-radial:checked) [data-when="angle"] { display: none; }
.gradient-controls:not(:has(#type-radial:checked)) [data-when="shape"] { display: none; }

/* Fallback for engines without :has(): show both rather than hide the wrong one. */
@supports not selector(:has(*)) {
  .gradient-controls [data-when] { display: block; }
}

/* ------------------------------------------------------- legal & contact */

.legal-updated {
  font-size: var(--text-md); color: var(--muted); margin: 0;
}

.prose--legal {
  font-family: var(--font-sans); font-size: var(--text-lg); max-width: 68ch;
}
.prose--legal h2 { font-size: var(--text-3xl); margin-top: 2.4em; }
.prose--legal h3 { font-size: var(--text-lg); margin-top: 1.9em; }
.prose--legal .lede { color: var(--ink-2); }

.legal-warning {
  border: 1px solid var(--warn); background: var(--warn-soft); color: var(--warn);
  border-radius: var(--radius); padding: var(--space-4) var(--space-4); margin-bottom: var(--space-7);
  font-size: var(--text-md); line-height: 1.55;
}
.legal-warning strong { display: block; color: inherit; }
.legal-warning code { background: rgba(0, 0, 0, .06); padding: .05em .3em; border-radius: 3px; }

.notice {
  border: 1px solid var(--line-2); background: var(--surface-2);
  border-radius: var(--radius); padding: var(--space-3) var(--space-4); margin-bottom: var(--space-6);
  font-size: var(--text-base);
}
.notice--success { border-color: var(--ok); background: var(--ok-soft); color: var(--ok); }
.notice--error { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }

.field__error {
  display: block; font-size: var(--text-sm); color: var(--danger);
  margin-top: var(--space-1); font-weight: 550;
}

/* Never shown to a person; only a bot fills it in. */
.honeypot {
  position: absolute !important; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

.contact-layout {
  display: grid; gap: clamp(1.25rem, 3vw, 2rem);
  grid-template-columns: minmax(0, 1.35fr) minmax(240px, 1fr);
  align-items: start;
}
@media (max-width: 780px) { .contact-layout { grid-template-columns: minmax(0, 1fr); } }

.contact-aside { display: grid; gap: var(--space-3); }
.contact-aside h2 { color: var(--ink); }

/* Two-across cards, so a card can carry a real description instead of the
   description needing a second list of its own further down the page. */
.feature-grid--roomy {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
}
.feature-grid--roomy .feature { gap: var(--space-3); padding: var(--space-6); }
.feature__lead { color: var(--ink-2) !important; font-weight: 550; }
.feature-grid--roomy .tool-swatch { width: 32px; height: 32px; border-radius: 9px; }

/* A disabled control still has to be legible, so dim the whole button rather
   than let the label fall below the contrast floor on its own. */
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
  pointer-events: none;
}

/* Stack the export panels in one grid cell so the card is always as tall as
   its tallest tab. Switching from JSON to SCSS used to collapse it by ~186px
   and shunt the rest of the page upwards. Inactive panels keep their space
   but are inert, so they stay out of the tab order and the accessibility tree. */
.tab-panels { display: grid; margin-top: var(--space-4); }
.tab-panels > [role="tabpanel"] { grid-area: 1 / 1; min-width: 0; }
.tab-panels > [data-inactive] { visibility: hidden; }

/* A channel that runs through zero (CIELAB a and b) needs its bar measured
   from the centre. Measuring from zero drew an empty bar for every negative
   value, so -108 was indistinguishable from 0. */
.meter--signed { position: relative; }
.meter--signed::before {
  content: ""; position: absolute; left: 50%; top: 0; bottom: 0;
  width: 1px; background: var(--line-2); opacity: .8;
}
.meter--signed .meter__fill { position: relative; }

/* The number has to be legible in every cell, so it is drawn in ink chosen
   for the cell's background. The bar underneath carries the text colour of
   the pair, which is what the number is about. */
.contrast-grid__cell {
  display: inline-flex; flex-direction: column; align-items: center; gap: 3px;
  line-height: 1;
}
.contrast-grid__ink {
  display: block; width: 22px; height: 3px; border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(127, 127, 127, .35);
}

/* ------------------------------------------------------- swap containers */

/*
 * Scroll anchoring has to be off wherever HTMX replaces the whole subtree.
 *
 * The browser keeps the page visually still by anchoring to a node near the
 * top of the viewport and correcting the scroll offset when content above it
 * resizes. Replacing a container's entire innerHTML destroys that anchor, so
 * the browser picks a new one and "corrects" against it -- measured at 1606px
 * on the gradient builder, which threw the reader from the stop they were
 * dragging down past the bottom of the results.
 *
 * These containers are wholly re-rendered on every interaction, so there is
 * nothing here worth anchoring to. Content outside them still anchors.
 */
#results,
#post-list {
  overflow-anchor: none;
}

/* ------------------------------------------------------ blog list header */

/* The featured card used to run straight into the article count with no
   margin at all, so the grid read as a continuation of it rather than a new
   section. The rule and heading only appear when there is a card above to
   break away from. */
.list-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-6);
}
.list-head__title { margin: 0; font-size: var(--text-2xl); letter-spacing: -0.022em; }
.list-head__count { margin-left: auto; }
.list-head--divided {
  margin-top: var(--space-9); padding-top: var(--space-6); border-top: 1px solid var(--line);
}
.featured-post + .list-head--divided { margin-top: var(--space-8); }

/* The tag footer belongs to the article, so it takes the article's measure.
   Left to the full page width its rule ran 1085px under a 610px column. */
.article-tags {
  max-width: 72ch; margin-top: var(--space-9); padding-top: var(--space-6);
  border-top: 1px solid var(--line);
}
.article-tags .field__label { margin-bottom: var(--space-3); }

/* A .wrap only constrains width; unlike .section and .page-head it carries no
   vertical rhythm. A content block built on a bare wrap therefore ends exactly
   where its last child ends, so whatever follows starts flush against it --
   which put the "Filed under" tags hard on the edge of the next section's
   coloured band. This gives those blocks the closing space a section has. */
.page-body { padding-bottom: clamp(2.5rem, 5vw, 4rem); }

/* Each tool's own mark, at card size. The generic circle-and-cross icon that
   used to sit here said nothing and repeated on every card. */
.tool-swatch--lg { width: 38px; height: 38px; border-radius: 10px; margin-top: 0; }

/* ------------------------------------------------------ result hierarchy */

/*
 * Tool results used to be a single stack of identically weighted cards, so
 * nothing read as primary and the palette page ran to 4,400px before you had
 * seen the palette. Three levels now:
 *
 *   primary    the answer you came for -- on the palette page it breaks the
 *              container entirely and runs edge to edge
 *   secondary  the payoff you act on, a normal card
 *   tertiary   diagnostics, folded away behind a summary that states the
 *              finding, so the page is scannable but nothing is lost
 */

.card--fold { padding: 0; }
.card--fold > summary {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-4) clamp(1.1rem, 2.4vw, 1.6rem);
  cursor: pointer; list-style: none; border-radius: var(--radius-lg);
}
.card--fold > summary::-webkit-details-marker { display: none; }
.card--fold[open] > summary {
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.card--fold > summary:hover { background: var(--surface-2); }
.card--fold > summary h2 { margin: 0; font-size: var(--text-lg); }

.card--fold__chevron {
  margin-left: auto; flex-shrink: 0; color: var(--muted);
  transition: transform .18s ease;
}
.card--fold[open] .card--fold__chevron { transform: rotate(180deg); }

/* The summary carries the headline finding, so the section is useful closed. */
.card--fold__note {
  color: var(--muted); font-size: var(--text-md); margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 560px) { .card--fold__note { display: none; } }

/* ---------------------------------------------------------- hero palette */

.hero__swatch {
  aspect-ratio: 3 / 4; border-radius: var(--radius-sm);
  border: 1px solid rgba(0, 0, 0, .08); cursor: pointer;
  display: flex; align-items: flex-end; padding: var(--space-2);
  font-family: var(--font-mono); font-size: var(--text-3xs); font-weight: 600;
  transition: transform .16s ease;
  /* Let the cell shrink past its label; if a label still cannot fit it is
     cropped inside the swatch rather than widening the page. */
  min-width: 0; overflow: hidden;
}
.hero__swatch:hover { transform: translateY(-3px); }
@media (max-width: 420px) { .hero__swatch { padding: var(--space-1); } }
/* At 320px each cell is about 47px; #RRGGBB in the mono face at 10px needs
   42px, so the padding and gap come in far enough for it to fit. */
@media (max-width: 360px) {
  .hero__swatches { gap: var(--space-1); }
  .hero__swatch { padding: 2px 3px; }
}

.hero__controls {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin-top: var(--space-4);
}
.hero__controls .color-chip { width: 40px; height: 34px; }
.hero__note { margin: var(--space-3) var(--space-1) 0; }
.hero__note strong { color: var(--ink); font-weight: 650; font-variant-numeric: tabular-nums; }
.hero__note a { color: inherit; text-decoration: underline; text-underline-offset: .15em; }
.hero__note a:hover { color: var(--ink); }
.hero__hint { margin: var(--space-1) var(--space-1) 0; font-size: var(--text-xs); }

/* Shuffle crossfades rather than cuts. htmx asks for a view transition on
   the swap; naming the two elements that change keeps the rest of the page
   out of it. Browsers without the API get the old instant swap, and the
   global prefers-reduced-motion rule above cannot reach these pseudo-elements,
   so they are switched off here explicitly. */
.hero__swatches { view-transition-name: hero-swatches; }
#hero-note { view-transition-name: hero-note; }
::view-transition-old(hero-swatches), ::view-transition-new(hero-swatches),
::view-transition-old(hero-note), ::view-transition-new(hero-note) {
  animation-duration: .32s; animation-timing-function: ease;
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
    animation: none !important;
  }
}

/* --------------------------------------------------------- palette stage */

/*
 * The palette breaks the 1180px container and runs to both edges of the
 * viewport. Everything else on the site stays on the grid, so the colour
 * reads as architecture rather than as a picture sitting inside a card.
 * The heading stays aligned to the wrap; only the colour escapes.
 */
/* The shared shape: a heading that stays on the grid, and a canvas that does
   not. Used by every tool whose answer is itself a colour. */
.stage { margin-bottom: clamp(1.75rem, 3vw, 2.5rem); }

.stage__head {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-2) var(--space-4);
  justify-content: space-between; margin-bottom: var(--space-4);
}
.stage__head h2 { margin: 0 0 var(--space-0); font-size: var(--text-2xl); }

.stage__canvas { border-block: 1px solid var(--line); }

/* The badge and the share button travel together at the end of the head. */
.stage__actions {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3);
}
.share-link { flex: 0 0 auto; }

/* -- the palette: one column per swatch ---------------------------------- */
.palette-stage__strip {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  min-height: clamp(190px, 24vw, 290px);
}

/* -- the gradient: the blend itself, over a chequer so alpha reads --------- */
/* Grid rather than height:100%: the child has no resolvable percentage basis
   against an auto-height parent, so it fell 2px short and let the chequer
   show along the bottom edge. */
.gradient-stage__canvas { display: grid; min-height: clamp(200px, 26vw, 320px); }
.gradient-stage__canvas > div { grid-area: 1 / 1; }

/* -- the contrast pair: sized like a real page, not a sample chip --------- */
.contrast-stage__canvas { padding-block: clamp(1.75rem, 4vw, 3rem); }
.contrast-stage__canvas .wrap > p { margin: 0 0 var(--space-2); }

/* Cap the measure on the lines, never on the .wrap itself: overriding the
   wrap's own max-width leaves margin-inline:auto to centre the whole block,
   which put the samples 229px to the right of the heading above them. */
.contrast-stage__sheet p { max-width: 62ch; }
/* The line the verdict is about. A rule in the pair's own text colour, so it
   is visible on any background without introducing a third colour. */
.contrast-stage__sheet .is-current {
  border-left: 3px solid currentColor; padding-left: var(--space-3);
  margin-left: calc(-1 * (var(--space-3) + 3px));
}
.contrast-stage__sheet p { color: inherit; margin: 0 0 var(--space-3); line-height: 1.35; }
.contrast-stage__chip { margin: var(--space-5) 0 0 !important; }
.contrast-stage__chip span {
  display: inline-block; padding: var(--space-2) var(--space-4); border-radius: 6px;
  border: 2px solid currentColor; font-weight: 600; font-size: var(--text-md);
}

/* -- the converted colour: the colour, at the size of a colour ------------ */
.converter-stage__canvas { padding-block: clamp(1.75rem, 4vw, 3rem); }
.converter-stage__hex {
  display: inline-flex; align-items: center; gap: var(--space-3);
  background: none; border: 0; padding: 0; cursor: pointer; color: inherit;
  font-family: var(--font-mono); font-weight: 650; letter-spacing: -0.02em;
  font-size: clamp(2.1rem, 7vw, 3.6rem); line-height: 1.05;
}
.converter-stage__hex svg { opacity: .6; flex: 0 0 auto; }
.converter-stage__hex:hover svg { opacity: 1; }
.converter-stage__hex:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; }
.converter-stage__values {
  font-family: var(--font-mono); font-size: var(--text-md); opacity: .85;
  margin: var(--space-2) 0 0; color: inherit;
}

.bleed-swatch {
  position: relative; display: flex; flex-direction: column;
  justify-content: flex-end; padding: var(--space-4);
  transition: flex .2s ease;
}
.bleed-swatch__tools {
  position: absolute; inset: .7rem .7rem auto auto; display: flex; gap: var(--space-1);
}
.bleed-swatch__hex {
  font-family: var(--font-mono); font-size: var(--text-base); font-weight: 600;
  letter-spacing: .02em; margin: 0 0 var(--space-0);
}
.bleed-swatch__note { font-size: var(--text-xs); opacity: .82; margin: 0; line-height: 1.4; }

/* Five columns on a phone leaves 75px each, too narrow for a hex code, so the
   band becomes stacked rows instead. */
/* Five columns on a phone leaves 75px each, too narrow for a hex code, so the
   band becomes stacked rows. They are laid out as a three-column grid rather
   than a flex row: with flex, each row sized to its own text and the hex codes
   landed at five different x positions down the list. */
@media (max-width: 720px) {
  .palette-stage__strip { grid-auto-flow: row; min-height: 0; }

  .bleed-swatch {
    display: grid;
    grid-template-columns: 4.9rem minmax(0, 1fr) auto;
    align-items: center; column-gap: var(--space-3);
    min-height: 62px; padding: var(--space-2) var(--space-4);
  }
  /* Lets the hex and the description become grid cells in their own right. */
  .bleed-swatch__meta { display: contents; }
  /* The controls are first in the markup but belong at the end of the row. */
  .bleed-swatch__tools { order: 9; position: static; inset: auto; margin: 0; }

  .bleed-swatch__hex { margin: 0; font-size: var(--text-md); }
  .bleed-swatch__note {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .bleed-swatch__note:last-child { display: none; }
}

/* ---------------------------------------------------------- spacing utils */
/*
  Last in the file on purpose. These sat among the component rules, so
  `.page-body` -- declared later at the same specificity -- silently beat
  `.pb-0` and the article hero inherited 91px of dead space above it. A
  utility that a component can outrank is not a utility.
*/
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }
.mb-2 { margin-bottom: var(--space-1); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-5 { margin-bottom: var(--space-5); }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-6 { margin-top: var(--space-6); }
.mt-7 { margin-top: var(--space-7); }
.mb-6 { margin-bottom: var(--space-6); }
.mb-7 { margin-bottom: var(--space-7); }
.mt-9 { margin-top: var(--space-9); }
.mb-9 { margin-bottom: var(--space-9); }
.pt-0 { padding-top: 0; }
.pb-0 { padding-bottom: 0; }
.pt-6 { padding-top: var(--space-6); }

/* ---------------------------------------------------------------- print */
/*
  A contrast report gets printed or saved as a PDF and kept as evidence that a
  pair was checked. Before this there were no print rules at all, so that
  printout carried the navbar, the footer and the drawer, and -- worst -- the
  folded diagnostics printed closed, leaving the conformance table off the
  page entirely.

  Two things matter more here than on a normal site. Backgrounds must survive,
  because on a colour tool the colour IS the result and browsers strip
  backgrounds by default. And the folds must open.
*/
@media print {
  /* Sizing everything from one root value works because the whole type and
     spacing system is rem-based: set the root in points and the scale follows. */
  html { font-size: 10.5pt; }

  @page { margin: 14mm; }

  *, *::before, *::after {
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
    box-shadow: none !important;
    text-shadow: none !important;
    transition: none !important;
    animation: none !important;
  }

  body { background: #fff; color: #000; }

  /* Chrome, controls and the supplementary reading below a result. */
  .site-header, .sidebar, .sidebar-backdrop, .skip-link, .toast,
  .breadcrumbs, .toc, .pagination, .controls, .site-footer,
  .share-link, .icon-btn, .swatch__btn, .bleed-swatch__tools,
  .copy-row__icon, .btn, .segmented, .range, noscript,
  [data-print="omit"], [data-shuffle], [data-nav-toggle], [data-theme-toggle] {
    display: none !important;
  }

  /* A button whose label is the value -- a hex, a css string -- has to keep
     printing that value, so strip the control chrome rather than the element. */
  button:not(.btn) {
    background: none !important; border: 0 !important;
    color: inherit !important; padding: 0 !important;
  }

  /*
    Folded diagnostics are the substance of a printed report, not an aside.

    Chrome hides a closed details with `content-visibility: hidden` on the
    ::details-content pseudo-element, so no rule targeting the children can
    reveal it -- `display: revert` on them changed nothing. This targets the
    real mechanism, and app.js opens the folds on beforeprint for engines that
    do not support the pseudo-element yet.
  */
  details > summary { list-style: none; }
  details > summary::-webkit-details-marker { display: none; }
  .card--fold__chevron { display: none; }
  details::details-content { content-visibility: visible !important; }

  /* One column, nothing fixed, nothing scrolling off the sheet.
     .wrap is deliberately not given `display: block !important` here: it is
     already block, and the declaration would come after the hide list above
     and beat it, so `.breadcrumbs.wrap` printed the nav trail. */
  .tool-layout, .article-layout, .two-up {
    display: block !important;
  }
  .tool-layout, .article-layout, .two-up, .wrap {
    max-width: none !important; width: auto !important; padding-inline: 0 !important;
  }
  .table-scroll, .demo-trip__path, .code-block pre { overflow: visible !important; }

  /*
    Export formats are tabbed, and the panels share one grid cell sized to the
    tallest -- so the card printed 464px to show 155px of CSS, the rest held
    open by an invisible 97-line JSON panel. Print the format the reader chose
    and let the card size to it. Five syntaxes of the same six hex values is
    another page and a half of paper for no extra information, and the swatch
    band above already carries every value.
  */
  .tab-panels { display: block !important; }
  .tab-panels > [data-inactive] { display: none !important; }
  .tabs { display: none !important; }
  .section { padding-block: var(--space-4) !important; }

  /* Keep a result and its label on the same sheet. */
  .card, .demo, .metric, .stage, .applied-card, .cvd-card,
  .demo-pair, tr, .post-card { break-inside: avoid; }
  h1, h2, h3 { break-after: avoid; }

  /* The colour bands are the report. Let them size to the page, not the screen. */
  .stage__canvas { border: 1px solid #999; }
  .palette-stage__strip { min-height: 34mm; }
  .gradient-stage__canvas { min-height: 34mm; }
  .article-hero { min-height: 28mm; }

  /* Ink that survives a laser printer. */
  .muted, .small, .field__hint, .card--fold__note { color: #333 !important; }
  a { text-decoration: underline; }

  /* In prose the destination matters on paper; in a UI chip it is noise. */
  .prose a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .85em; word-break: break-all; }
}
