/* ==========================================================================
   Design tokens, reset and animations. Phase 3.8 and 3.9.
   White-first, light default, dark via prefers-color-scheme.
   ========================================================================== */

:root {
  /* surface */
  --bg: #ffffff;
  --bg-subtle: #faf7ff;
  --bg-raised: #ffffff;
  --border: #e9e2f8;
  --border-strong: #cdbdf0;

  /* text */
  --text: #17122b;
  --text-muted: #574a75;
  /* 5.0:1 on --bg. Anything lighter fails WCAG AA for the 14px counter. */
  --text-faint: #6b5f8a;

  /* accent: vivid violet, 5.7:1 on white so it carries small text and
     white-on-accent buttons at AA in both directions */
  --accent: #7c3aed;
  --accent-hover: #6425d0;
  --accent-soft: #f3ecff;
  --accent-text: #ffffff;
  --pill-on: #ffffff;

  /* the vibrant family. These carry fills, borders and large text.
     Each has a -deep variant for anything that must hold small text at AA. */
  --c-violet: #7c3aed;   --c-violet-deep: #5b21b6;
  --c-pink: #ec4899;     --c-pink-deep: #a3155f;
  --c-cyan: #06b6d4;     --c-cyan-deep: #0e6f85;
  --c-amber: #f59e0b;    --c-amber-deep: #92600a;
  --c-emerald: #10b981;  --c-emerald-deep: #05704b;
  --c-rose: #f43f5e;     --c-rose-deep: #9f1239;

  /* signature gradients */
  --grad-brand: linear-gradient(135deg, #7c3aed 0%, #c026d3 50%, #ec4899 100%);

  /* For surfaces that carry white text. The display gradient ends in pink,
     where white drops to 3.53:1 and fails AA, so anything with a label on top
     uses this shorter ramp instead: both endpoints clear 4.5:1 against white.
     --brand-solid sits underneath as a measurable background-color, since a
     gradient reports no computed backgroundColor for a contrast checker. */
  --grad-brand-strong: linear-gradient(135deg, #7c3aed 0%, #a21caf 100%);
  --brand-solid: #6d28d9;
  --on-brand: #ffffff;
  --grad-soft: linear-gradient(135deg, #ede4ff 0%, #fbe4f8 45%, #ffe4ee 75%, #e4f7fd 100%);
  --grad-cool: linear-gradient(135deg, #06b6d4 0%, #7c3aed 100%);

  /* state */
  --success: #05704b;
  --success-soft: #e8fbf3;
  --warn: #9a4a06;
  --warn-soft: #fff6e8;

  /* radius */
  --r-card: 0.75rem;
  --r-input: 0.5rem;
  --r-pill: 999px;

  /* spacing scale */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;

  /* type */
  --font-ui: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  /* Result text needs the widest possible Unicode coverage. Without these
     fallbacks, styled output renders as tofu boxes on Windows and Android. */
  --font-glyph: 'Segoe UI Symbol', 'Noto Sans Symbols 2', 'Noto Sans Math', 'Apple Symbols',
    'Segoe UI Historic', 'Arial Unicode MS', system-ui, sans-serif;

  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05);
  --shadow-md: 0 4px 12px rgba(16, 24, 40, 0.08);

  --wrap: 68rem;
  --wrap-narrow: 46rem;

  /* motion */
  --dur-fast: 120ms;
  --dur-base: 200ms;
  --dur-slow: 320ms;
  --ease: cubic-bezier(0.2, 0, 0.2, 1);

  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #120e1f;
    --bg-subtle: #191330;
    --bg-raised: #1d1738;
    --border: #322a52;
    --border-strong: #4a3d75;

    --text: #f2eefc;
    --text-muted: #b8aede;
    --text-faint: #9b90c4;

    --accent: #a78bfa;
    --accent-hover: #c4b5fd;
    --accent-soft: #291f4d;
    --accent-text: #120e1f;
    --pill-on: #120e1f;

    /* The -deep suffix means "safe for small text on this background", so in
       dark mode it resolves lighter rather than darker. Components reference
       the same token name in both themes and stay at AA either way. */
    --c-violet: #a78bfa;   --c-violet-deep: #c4b5fd;
    --c-pink: #f472b6;     --c-pink-deep: #f9a8d4;
    --c-cyan: #22d3ee;     --c-cyan-deep: #67e8f9;
    --c-amber: #fbbf24;    --c-amber-deep: #fcd34d;
    --c-emerald: #34d399;  --c-emerald-deep: #6ee7b7;
    --c-rose: #fb7185;     --c-rose-deep: #fda4af;

    --grad-brand: linear-gradient(135deg, #a78bfa 0%, #e879f9 50%, #f472b6 100%);
    /* In dark mode the brand ramp is light, so the label flips to the page
       ground rather than white. */
    --grad-brand-strong: linear-gradient(135deg, #a78bfa 0%, #e879f9 100%);
    --brand-solid: #a78bfa;
    --on-brand: #120e1f;
    --grad-soft: linear-gradient(135deg, #221a42 0%, #2c1b3d 55%, #331a33 100%);
    --grad-cool: linear-gradient(135deg, #22d3ee 0%, #a78bfa 100%);

    --success: #6ee7b7;
    --success-soft: #0e3527;
    --warn: #fcd34d;
    --warn-soft: #3a2a08;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
    --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.6);
  }
}

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

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

/* The browser's own [hidden] rule is a user-agent style, so any author rule
   that sets display wins over it. Components here set display: grid and
   display: inline-flex, which silently un-hid every element carrying the
   hidden attribute: filtered-out cards and collapsed batches stayed on screen.
   This restores the attribute's meaning for the whole document. */
[hidden] { display: none !important; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

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

h1, h2, h3, h4 { line-height: 1.25; margin: 0 0 var(--s-3); font-weight: 650; letter-spacing: -0.01em; }
/* Anchors from the contents list land with breathing room rather than flush
   against the viewport edge. */
:target, [id] { scroll-margin-top: var(--s-5); }
h1 { font-size: clamp(1.75rem, 1.2rem + 2.2vw, 2.5rem); }
h2 { font-size: clamp(1.35rem, 1.1rem + 1.1vw, 1.75rem); margin-top: var(--s-7); }
h3 { font-size: 1.15rem; margin-top: var(--s-5); }

p { margin: 0 0 var(--s-4); }
p, li { max-width: 68ch; }

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

button { font: inherit; cursor: pointer; }

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

/* Wide content scrolls inside its own container. The page body never does. */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { border-collapse: collapse; width: 100%; }

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

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--s-4); }
.wrap-narrow { max-width: var(--wrap-narrow); }

.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--s-2);
  background: var(--accent);
  color: var(--accent-text);
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-input);
  z-index: 100;
}
.skip-link:focus { left: var(--s-2); }

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

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  border: 1px solid var(--border-strong);
  background: var(--bg-raised);
  color: var(--text);
  border-radius: var(--r-input);
  padding: var(--s-2) var(--s-4);
  min-height: 2.75rem;
  font-weight: 550;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
/* Not applied to primary buttons. `background` is a shorthand, so it resets
   background-image to none: on a .btn-primary it wiped the gradient and left
   --on-brand white text on the near-white --bg-subtle fill, unreadable for as
   long as the pointer was over the button. .btn-primary:hover has the same
   specificity and only sets background-position, so it could not win it back. */
.btn:not(.btn-primary):hover { background: var(--bg-subtle); }
.btn:active { transform: translateY(1px); }

.btn-primary {
  background-color: var(--brand-solid);
  background-image: var(--grad-brand-strong);
  border-color: transparent;
  color: var(--on-brand);
  background-size: 160% 160%;
  background-position: 0% 50%;
  transition: background-position var(--dur-slow) var(--ease), box-shadow var(--dur-base) var(--ease);
  box-shadow: 0 2px 10px rgba(124, 58, 237, 0.28);
}
.btn-primary:hover {
  background-position: 100% 50%;
  box-shadow: 0 4px 18px rgba(124, 58, 237, 0.4);
}

.btn[data-copied='true'] {
  background: var(--success-soft);
  border-color: var(--success);
  color: var(--success);
}

/* Headline treatment. The gradient is painted through the text, with a solid
   colour underneath so the heading is still legible if background-clip fails. */
.gradient-text {
  color: var(--accent);
  background: var(--grad-brand);
  -webkit-background-clip: text;
  background-clip: text;
}
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .gradient-text { -webkit-text-fill-color: transparent; }
}

/* Pills carry their hue at rest, not only on hover, so the filter row reads as
   a colour key for the results below it. Pressed state fills instead of tints. */
.pill {
  border-radius: var(--r-pill);
  padding: var(--s-1) var(--s-3);
  border: 1.5px solid var(--pill-color, var(--accent));
  background: var(--pill-soft, var(--accent-soft));
  color: var(--pill-color, var(--accent));
  font-size: 0.875rem;
  font-weight: 560;
  min-height: 2.25rem;
  transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-base) var(--ease);
}
.pill:hover {
  transform: translateY(-2px);
  box-shadow: 0 3px 10px color-mix(in srgb, var(--pill-color, var(--accent)) 30%, transparent);
}
.pill[aria-pressed='true'] {
  background: var(--pill-color, var(--accent));
  border-color: var(--pill-color, var(--accent));
  /* In light mode the hue resolves dark and takes white text. In dark mode it
     resolves light, where white would drop to about 1.8:1, so the label flips
     to the page background colour instead. */
  color: var(--pill-on);
  font-weight: 650;
}

/* Each filter category owns a hue, so the row reads as a palette rather than
   as six identical chips. -deep variants keep the label at AA in both themes. */
.pill[data-cat=''],           .pill[data-cat='math']       { --pill-color: var(--c-violet-deep);  --pill-soft: var(--accent-soft); }
.pill[data-cat='width']       { --pill-color: var(--c-cyan-deep);    --pill-soft: color-mix(in srgb, var(--c-cyan) 12%, transparent); }
.pill[data-cat='enclosed']    { --pill-color: var(--c-pink-deep);    --pill-soft: color-mix(in srgb, var(--c-pink) 12%, transparent); }
.pill[data-cat='phonetic']    { --pill-color: var(--c-emerald-deep); --pill-soft: color-mix(in srgb, var(--c-emerald) 14%, transparent); }
.pill[data-cat='positional']  { --pill-color: var(--c-amber-deep);   --pill-soft: color-mix(in srgb, var(--c-amber) 16%, transparent); }
.pill[data-cat='combining']   { --pill-color: var(--c-rose-deep);    --pill-soft: color-mix(in srgb, var(--c-rose) 12%, transparent); }

input[type='text'],
input[type='search'],
textarea {
  width: 100%;
  font: inherit;
  color: var(--text);
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-input);
  padding: var(--s-3);
}
textarea { resize: vertical; min-height: 6.5rem; }

/* ---------------------------------------------------------- animations -- */

@keyframes fade-up {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.04); }
  100% { transform: scale(1); }
}
@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.anim-reveal { animation: fade-up var(--dur-base) var(--ease) both; }
.anim-pop { animation: pop var(--dur-base) var(--ease); }
.anim-fade { animation: fade-in var(--dur-base) var(--ease) both; }

/* Staggered reveal, capped so a 30-item list does not crawl in. */
.stagger > * { animation: fade-up var(--dur-base) var(--ease) both; }
.stagger > *:nth-child(1)  { animation-delay: 0ms; }
.stagger > *:nth-child(2)  { animation-delay: 20ms; }
.stagger > *:nth-child(3)  { animation-delay: 40ms; }
.stagger > *:nth-child(4)  { animation-delay: 60ms; }
.stagger > *:nth-child(5)  { animation-delay: 80ms; }
.stagger > *:nth-child(n + 6) { animation-delay: 100ms; }

/* ------------------------------------------------------- scroll reveal -- */

/* Guarded behind html.js, which an inline head script sets. Without JavaScript
   the rule never applies and every section is visible from the start, so a
   crawler or a reader with scripts off never sees a blank page. */
html.js .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity var(--dur-slow) var(--ease),
    transform var(--dur-slow) var(--ease);
  will-change: opacity, transform;
}
html.js .reveal.is-visible {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* Children stagger in behind their parent for lists and grids. */
html.js .reveal[data-stagger] > * {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity var(--dur-base) var(--ease),
    transform var(--dur-base) var(--ease);
}
html.js .reveal[data-stagger].is-visible > * { opacity: 1; transform: none; }
html.js .reveal[data-stagger].is-visible > *:nth-child(1) { transition-delay: 0ms; }
html.js .reveal[data-stagger].is-visible > *:nth-child(2) { transition-delay: 60ms; }
html.js .reveal[data-stagger].is-visible > *:nth-child(3) { transition-delay: 120ms; }
html.js .reveal[data-stagger].is-visible > *:nth-child(4) { transition-delay: 180ms; }
html.js .reveal[data-stagger].is-visible > *:nth-child(n + 5) { transition-delay: 240ms; }

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  /* Reveal is disabled outright rather than merely sped up, so nothing ever
     depends on a transition firing to become visible. */
  html.js .reveal,
  html.js .reveal[data-stagger] > * {
    opacity: 1 !important;
    transform: none !important;
  }
}
