/* slovary.vip — design system.
 *
 * Layer 1 is raw values (the ramp), layer 2 gives them roles. Components only
 * ever speak to layer 2, so a re-theme touches one block.
 *
 * The page is set in a book serif because the product is a dictionary: type IS
 * the subject here. The stack resolves to fonts already installed on the
 * reader's machine (Iowan Old Style / Charter on Apple, Sitka / Cambria on
 * Windows), so it costs zero bytes and never flashes.
 */

/* ---------- Layer 1: ramp ---------- */
:root {
  /* Hues: one warm neutral ground, one ink, one accent. Nothing else. */
  --c-paper:      oklch(0.988 0.004 85);
  --c-paper-sunk: oklch(0.962 0.006 85);
  --c-paper-edge: oklch(0.905 0.008 85);
  --c-ink:        oklch(0.245 0.012 75);
  --c-ink-soft:   oklch(0.435 0.012 75);
  /* Not lighter than this in light mode: --c-ink-faint carries real content
     (licence blurbs, attribution, the spec's dependency note), and at
     oklch(0.585) it measured 4.06:1 on paper and 3.76:1 on cards — under AA. */
  --c-ink-faint:  oklch(0.515 0.010 75);
  --c-accent:     oklch(0.470 0.088 172);
  --c-accent-lift:oklch(0.395 0.092 172);

  /* Type: display + prose share the serif; UI and data have their own roles. */
  --font-prose: "Iowan Old Style", "Charter", "Bitstream Charter", "Sitka Text",
                Cambria, Georgia, "Times New Roman", serif;
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
             "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;

  /* Fluid ramp, ratio ~1.28 — editorial but not showy. */
  --step--1: clamp(0.83rem, 0.81rem + 0.10vw, 0.89rem);
  --step-0:  clamp(1.03rem, 1.00rem + 0.16vw, 1.13rem);
  --step-1:  clamp(1.19rem, 1.13rem + 0.28vw, 1.36rem);
  --step-2:  clamp(1.42rem, 1.31rem + 0.52vw, 1.74rem);
  --step-3:  clamp(1.85rem, 1.55rem + 1.42vw, 2.85rem);

  /* Two optical regimes, hard break. Interpolating between them is the tell. */
  --track-display: -0.022em;
  --lh-display: 1.05;
  --track-ui: -0.008em;
  --lh-prose: 1.62;

  /* One space ladder, 4px base. */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px;
  --s-5: 24px; --s-6: 32px; --s-7: 48px; --s-8: 72px;

  --r1: 4px; --r2: 7px; --r3: 10px; --r4: 14px; --r-pill: 999px;

  --hairline: 1px;

  --dur-tap: 120ms; --dur-ui: 180ms;
  --ease-ui: cubic-bezier(0.4, 0, 0.2, 1);

  --measure: 34rem;
}

@media (min-resolution: 192dpi) {
  :root { --hairline: 0.5px; }
}

@media (color-gamut: p3) {
  :root {
    --c-accent:      color(display-p3 0.153 0.451 0.400);
    --c-accent-lift: color(display-p3 0.098 0.365 0.325);
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --c-paper:      oklch(0.188 0.008 75);
    --c-paper-sunk: oklch(0.232 0.009 75);
    --c-paper-edge: oklch(0.310 0.010 75);
    --c-ink:        oklch(0.925 0.008 85);
    --c-ink-soft:   oklch(0.760 0.010 85);
    --c-ink-faint:  oklch(0.645 0.010 85);
    --c-accent:     oklch(0.760 0.098 172);
    --c-accent-lift:oklch(0.840 0.090 172);
  }
}

/* ---------- Layer 2: roles ---------- */
:root {
  --bg: var(--c-paper);
  --bg-sunk: var(--c-paper-sunk);
  --line: var(--c-paper-edge);
  --text: var(--c-ink);
  --text-soft: var(--c-ink-soft);
  --text-faint: var(--c-ink-faint);
  --link: var(--c-accent);
  --link-hover: var(--c-accent-lift);
  --focus: var(--c-accent);
  color-scheme: light dark;
}

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

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

body {
  margin: 0;
  padding: 0 var(--s-5) var(--s-8);
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-prose);
  font-size: var(--step-0);
  line-height: var(--lh-prose);
  letter-spacing: var(--track-ui);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* The prose column is narrow for reading; tables and code get to be wider —
   they are scanned, not read line by line. The cap applies at ANY depth: a
   direct-child selector let card and section copy escape to the full 46rem,
   which produced ~83ch lines of SMALL type under ~62ch lines of body type. */
main, nav, footer { max-width: 46rem; margin-inline: auto; }
main :is(p, ul, ol) { max-width: var(--measure); }
main :is(.table-wrap, pre, table) { max-width: none; }

::selection { background: color-mix(in oklab, var(--link) 22%, transparent); }

a {
  color: var(--link);
  text-decoration-thickness: from-font;
  text-underline-offset: 0.14em;
  transition: color var(--dur-tap) var(--ease-ui);
}
a:hover { color: var(--link-hover); }

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

h1, h2, h3, h4 {
  letter-spacing: var(--track-display);
  line-height: var(--lh-display);
  text-wrap: balance;
  font-weight: 600;
}
h1 { font-size: var(--step-3); margin: 0 0 var(--s-3); }
h2 {
  font-size: var(--step-2);
  margin: var(--s-8) 0 var(--s-3);
  max-width: var(--measure);
}
h3 { font-size: var(--step-1); margin: var(--s-6) 0 var(--s-2); }
h4 {
  font-size: var(--step-0);
  margin: var(--s-5) 0 var(--s-2);
  font-weight: 600;
}
p { margin: 0 0 var(--s-4); text-wrap: pretty; }
ul, ol { margin: 0 0 var(--s-4); padding-left: 1.15em; }
li { margin: var(--s-1) 0; }
li::marker { color: var(--text-faint); }
strong { font-weight: 600; }
hr {
  border: 0;
  border-top: var(--hairline) solid var(--line);
  margin: var(--s-7) 0;
}

/* ---------- Navigation ---------- */
nav {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2) var(--s-5);
  padding: var(--s-5) 0 var(--s-4);
  margin-bottom: var(--s-7);
  border-bottom: var(--hairline) solid var(--line);
  font-family: var(--font-ui);
  font-size: var(--step--1);
}
nav a {
  text-decoration: none;
  color: var(--text-soft);
  /* A 22px-tall link is not a tap target. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
nav a:hover { color: var(--text); }
nav .brand {
  margin-right: auto;
  font-family: var(--font-prose);
  font-size: var(--step-0);
  font-weight: 600;
  color: var(--text);
  letter-spacing: var(--track-display);
}
/* On a phone the row broke after "Privacy", orphaning "Support" onto a second
   line under the brand — where the active-page underline then read as a
   mistake. Give the brand its own row and let the four links form one. */
@media (max-width: 30rem) {
  nav { gap: 0 var(--s-4); padding-top: var(--s-4); }
  nav .brand { flex-basis: 100%; margin-right: 0; }
}
/* "Where am I" — the current page is stated, not left to be inferred. */
nav a[aria-current="page"] {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--link);
  text-underline-offset: 0.35em;
  text-decoration-thickness: 2px;
}

/* ---------- The hero, set as a dictionary entry ----------
   The product is a dictionary; the page opens by being one. Headword,
   pronunciation, part of speech, numbered senses — the structure carries real
   meaning here rather than decorating the page. */
.entry { margin: var(--s-6) 0 var(--s-8); }
.entry-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}
.entry-head h1 { margin: 0; }
/* The icon is not decoration: a page that claims to be about an app and shows
   no app is the placeholder signal a reviewer reads first. */
.app-icon {
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 22.5%;
  align-self: center;
  flex-basis: 100%;
  margin-bottom: var(--s-2);
}
@media (min-width: 34rem) {
  .app-icon { flex-basis: auto; margin-bottom: 0; margin-right: var(--s-1); }
}
.entry-pron {
  font-family: var(--font-ui);
  font-size: var(--step-0);
  color: var(--text-faint);
}
.entry-pos {
  font-family: var(--font-prose);
  font-style: italic;
  font-size: var(--step-0);
  color: var(--link);
}
.entry-sense {
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  gap: var(--s-3);
  max-width: calc(var(--measure) + 1.5rem);
  margin-bottom: var(--s-4);
}
.entry-sense > .num {
  font-family: var(--font-ui);
  font-size: var(--step--1);
  color: var(--text-faint);
  padding-top: 0.35em;
  font-variant-numeric: tabular-nums;
}
.entry-sense > p { margin: 0; }
.entry-note {
  max-width: var(--measure);
  margin-top: var(--s-5);
  padding-left: var(--s-4);
  border-left: 2px solid var(--line);
  color: var(--text-soft);
  font-size: var(--step--1);
  font-family: var(--font-ui);
  line-height: 1.55;
}

/* ---------- Surfaces ----------
   Separation ladder: space first, then a tonal shift. A border only where an
   edge must be unambiguous (code blocks against long prose). */
/* Cards overhang the prose column by one deliberate step. Letting them run to
   the full 46rem put a second, unexplained right edge on the page. */
.card, .dict-list {
  max-width: calc(var(--measure) + 6rem);
}
.card {
  background: var(--bg-sunk);
  border-radius: var(--r4);
  padding: var(--s-5);
  margin: var(--s-5) 0;
}
.card > :first-child { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }

/* ---------- Dictionary list ----------
   Cards, not table rows: each dictionary is a decision (is it the language I
   need, is the licence one I accept, how big is the file), and a row forces
   that decision to be read horizontally across five columns on a phone. */
.dict-list { display: grid; gap: var(--s-4); margin: var(--s-6) 0; }
.dict {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-2) var(--s-5);
  align-items: start;
  padding: var(--s-5);
  background: var(--bg-sunk);
  border-radius: var(--r4);
}
.dict-name {
  grid-column: 1;
  font-size: var(--step-1);
  font-weight: 600;
  letter-spacing: var(--track-display);
  line-height: 1.25;
}
.dict-meta {
  grid-column: 1;
  font-family: var(--font-ui);
  font-size: var(--step--1);
  color: var(--text-soft);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-4);
  font-variant-numeric: tabular-nums;
}
/* Column 1, not full width: the actions stack beside it is two pills tall, so a
   note that ducked underneath would stretch the rows above it and tear the name
   away from its own card. */
.dict-note {
  grid-column: 1;
  font-family: var(--font-ui);
  font-size: var(--step--1);
  color: var(--text-faint);
  margin: 0;
}
/* Two actions per card. The download is the one that cannot fail and the one
   carrying the size, so it keeps the accent fill; "Open in app" hands the link
   to the installed app and is outlined above it — a custom scheme does nothing
   at all where the app is missing, and a button that can fail silently must not
   look like the only way through. */
.dict-actions {
  grid-column: 2;
  /* Spans name + meta + note. `1 / -1` would be the honest way to say "all of
     them", but negative line numbers count back through the EXPLICIT grid, and
     this grid has no explicit rows — there it silently collapses to one row. */
  grid-row: 1 / span 3;
  justify-self: end;
  align-self: center;
  /* Side by side, not stacked: a two-pill column is taller than the name, the
     metadata and the note together, and the surplus is paid for by the rows it
     spans — the card grows and its own text drifts apart. */
  display: flex;
  align-items: stretch;
  gap: var(--s-2);
}
.dict-open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  min-height: 44px;
  border: var(--hairline) solid var(--line);
  border-radius: var(--r-pill);
  background: var(--bg);
  color: var(--link);
  font-family: var(--font-ui);
  font-size: var(--step--1);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: border-color var(--dur-tap) var(--ease-ui),
              color var(--dur-tap) var(--ease-ui);
}
.dict-open:hover { border-color: var(--link); color: var(--link-hover); }
.dict-open::after {
  content: "";
  width: 6px;
  height: 6px;
  border: solid currentColor;
  border-width: 1.5px 1.5px 0 0;
  transform: rotate(45deg);
}
/* The collection card inverts the emphasis, and the reason is arithmetic rather
   than decoration: through the app you fetch only the dictionaries you tick,
   while Download hands you 424 MB including everything you did not want. */
.dict--all .dict-open {
  background: var(--link);
  border-color: var(--link);
  color: var(--bg);
}
.dict--all .dict-open:hover {
  background: var(--link-hover);
  border-color: var(--link-hover);
  color: var(--bg);
}
.dict--all .dict-get {
  background: var(--bg);
  border: var(--hairline) solid var(--line);
  color: var(--link);
}
.dict--all .dict-get:hover {
  background: var(--bg);
  border-color: var(--link);
  color: var(--link-hover);
}
.dict--all .dict-get:focus-visible { outline-color: var(--focus); box-shadow: none; }

/* Only appears when the tap above provably went nowhere — see the script on
   the dictionaries page. */
.dict-fallback {
  grid-column: 1;
  margin: var(--s-2) 0 0;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r3);
  background: var(--bg);
  font-family: var(--font-ui);
  font-size: var(--step--1);
  color: var(--text-soft);
}
.dict-copy {
  font: inherit;
  font-weight: 600;
  color: var(--link);
  background: none;
  border: 0;
  border-bottom: var(--hairline) solid currentColor;
  padding: 0;
  cursor: pointer;
}
.dict-url {
  font-family: var(--font-mono);
  font-size: 0.92em;
  overflow-wrap: anywhere;
  user-select: all;
}
.dict-copy.is-copied {
  color: var(--text-faint);
  border-bottom-color: transparent;
  cursor: default;
}
.dict-get {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--s-2) var(--s-4);
  min-height: 44px;
  justify-content: center;
  border-radius: var(--r-pill);
  background: var(--link);
  color: var(--bg);
  font-family: var(--font-ui);
  font-size: var(--step--1);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--dur-tap) var(--ease-ui);
}
.dict-get:hover { background: var(--link-hover); color: var(--bg); }
/* The default ring is the accent, and so is this button — accent on accent is
   invisible where it matters. Ring in ink, separated from the fill by the page
   ground. */
.dict-get:focus-visible {
  outline-color: var(--text);
  box-shadow: 0 0 0 2px var(--bg);
}
.dict-get .size {
  font-size: 0.78em;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 30rem) {
  .dict { grid-template-columns: 1fr; }
  .dict-actions {
    grid-column: 1;
    grid-row: auto;
    justify-self: stretch;
    flex-direction: column;
  }
}

/* ---------- Tables (mostly inside the generated spec) ---------- */
.table-wrap { overflow-x: auto; margin: var(--s-4) 0; }
table {
  border-collapse: collapse;
  /* `width: 100%` defeats the wrapper's overflow: the table squeezes instead
     of scrolling, and on a phone a `yes`/`no` column ends up as wide as the
     description while the description becomes a seven-line ribbon. */
  min-width: 32rem;
  width: auto;
  font-family: var(--font-ui);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
}
th, td {
  text-align: left;
  padding: var(--s-2) var(--s-3);
  border-bottom: var(--hairline) solid var(--line);
  vertical-align: top;
}
th {
  font-weight: 600;
  color: var(--text-soft);
  border-bottom-color: var(--text-faint);
}
tr:last-child td { border-bottom: 0; }

/* ---------- Code ---------- */
code, kbd, samp { font-family: var(--font-mono); }
code {
  font-size: 0.88em;
  background: var(--bg-sunk);
  padding: 0.1em 0.34em;
  border-radius: var(--r1);
  /* Without this a wrapped identifier renders as two separate chips and reads
     as corrupted — bad anywhere, worse in a spec people copy from. */
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
pre {
  background: var(--bg-sunk);
  border: var(--hairline) solid var(--line);
  border-radius: var(--r3);
  padding: var(--s-4);
  overflow-x: auto;
  font-size: var(--step--1);
  line-height: 1.55;
  margin: var(--s-4) 0;
}
pre code { background: none; padding: 0; font-size: 1em; }

/* ---------- Table of contents (generated spec) ---------- */
.toc {
  max-width: calc(var(--measure) + 6rem);
  margin: var(--s-6) 0;
  padding: var(--s-4) var(--s-5);
  background: var(--bg-sunk);
  border-radius: var(--r4);
  font-family: var(--font-ui);
  font-size: var(--step--1);
}
.toc ol {
  margin: 0;
  padding: 0;
  list-style: none;
  columns: 2;
  column-gap: var(--s-6);
}
.toc li { margin: 0; break-inside: avoid; }
.toc a {
  display: block;
  padding: var(--s-2) 0;
  text-decoration: none;
  color: var(--text-soft);
}
.toc a:hover { color: var(--link); }
@media (max-width: 32rem) { .toc ol { columns: 1; } }

/* Anchored jumps must not land under nothing; give headings breathing room
   when they become the scroll target. */
:target { scroll-margin-top: var(--s-5); }

/* ---------- Notes in the generated spec (markdown blockquotes) ---------- */
blockquote {
  margin: var(--s-4) 0;
  padding-left: var(--s-4);
  border-left: 2px solid var(--line);
  color: var(--text-soft);
  max-width: var(--measure);
}
blockquote p:last-child { margin-bottom: 0; }

/* ---------- Small print ---------- */
.lede {
  font-size: var(--step-1);
  line-height: 1.45;
  color: var(--text-soft);
  max-width: var(--measure);
  margin-bottom: var(--s-6);
}
.muted {
  color: var(--text-soft);
  font-family: var(--font-ui);
  font-size: var(--step--1);
  line-height: 1.6;
}

footer {
  margin-top: var(--s-8);
  padding-top: var(--s-4);
  border-top: var(--hairline) solid var(--line);
  color: var(--text-faint);
  font-family: var(--font-ui);
  font-size: var(--step--1);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
}
footer a {
  color: var(--text-soft);
  /* Undecorated links the colour of muted prose read as a caption, not as
     something you can click. */
  text-decoration: underline;
  text-decoration-color: color-mix(in oklab, var(--link) 45%, transparent);
  text-underline-offset: 0.25em;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
footer a:hover { color: var(--text); text-decoration-color: var(--link); }
footer .operator {
  flex-basis: 100%;
  color: var(--text-faint);
}

@media (prefers-reduced-motion: reduce) {
  :root { --dur-tap: 1ms; --dur-ui: 1ms; }
  * { animation-duration: 1ms !important; transition-duration: 1ms !important; }
}
