/*
 * Public article reading layout — phase 2 of Interactive Articles (SEO/GEO).
 *
 * Self-contained: served by the Node server at /assets/articles/article.css,
 * no Tailwind build. Colors mirror the friendly design system's brand values
 * (cobalt primary rgb(30 107 235), gold accent rgb(245 158 11)); look = pill
 * buttons, soft cards, single-column measure ~68ch.
 *
 * Brand fonts are SELF-HOSTED, same-origin (latin-subset woff2 under
 * /assets/articles/fonts/) so the router CSP already carries `font-src 'self'`
 * — no CSP widening and no third-party render-blocking request on this
 * LCP-sensitive surface (D5). Nunito (wordmark/display) + Hanken Grotesk
 * (chrome UI) mirror the homepage; `font-display: swap` keeps text visible
 * during the swap. The article BODY prose stays Charter (see --a-serif).
 */

/* Nunito 800 — the wordmark + display headings. */
@font-face {
  font-family: "Nunito";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("/assets/articles/fonts/nunito-800.woff2") format("woff2");
}

/* Hanken Grotesk 400/600/700 — chrome UI / widget sans. */
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/articles/fonts/hanken-400.woff2") format("woff2");
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/articles/fonts/hanken-600.woff2") format("woff2");
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/articles/fonts/hanken-700.woff2") format("woff2");
}

:root {
  --a-brand: rgb(30 107 235);
  --a-brand-hover: rgb(26 88 199);
  --a-brand-subtle: rgb(240 245 255);
  --a-accent: rgb(245 158 11);
  --a-accent-subtle: rgb(254 243 199);
  --a-text: rgb(33 37 41);
  --a-text-muted: rgb(108 117 125);
  --a-surface: #ffffff;
  --a-surface-sunken: rgb(248 249 250);
  --a-border: rgb(233 236 239);
  --a-success: rgb(22 163 74);
  --a-success-subtle: rgb(240 253 244);
  --a-error: rgb(220 38 38);
  --a-error-subtle: rgb(254 242 242);
  --a-gray: rgb(73 80 87);
  --a-gray-subtle: rgb(248 249 250);

  /* Serif for reading prose (Medium-style); sans for chrome/UI/widget;
     display sans for the title, section headings, and avatar. */
  --a-serif: Charter, "Bitstream Charter", "Iowan Old Style",
    "Palatino Linotype", Georgia, Cambria, "Times New Roman", serif;
  --a-sans: "Hanken Grotesk", Inter, system-ui, -apple-system, "Segoe UI",
    Roboto, sans-serif;
  --a-display: Nunito, Inter, system-ui, sans-serif;
  --a-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Reading-prose ink — a touch softer than heading ink, like Medium. */
  --a-prose: rgb(41 41 41);

  /* Homepage chrome palette (light-mode Tailwind values; this surface is
     light-only and self-contained). The sticky nav bar + the dark marketing
     footer mirror LandingNav/LandingFooter. */
  --a-neutral-100: rgb(241 243 245);
  --a-neutral-300: rgb(222 226 230);
  --a-neutral-400: rgb(206 212 218);
  --a-neutral-500: rgb(173 181 189);
  --a-neutral-800: rgb(52 58 64);
  --a-neutral-900: rgb(33 37 41);
  --a-primary-400: rgb(84 144 240);
  --a-primary-600: rgb(30 107 235);
  --a-primary-700: rgb(26 88 199);
}

::selection { background: var(--a-brand-subtle); }

* { box-sizing: border-box; }

/*
 * The widget's Check/Try-again state machine toggles the `hidden` attribute.
 * Author-origin display values (e.g. .article-btn's inline-flex) beat the UA
 * stylesheet's `[hidden] { display: none }` regardless of specificity, so
 * hidden elements must be forced off here.
 */
[hidden] { display: none !important; }

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

body {
  margin: 0;
  background: var(--a-surface);
  color: var(--a-text);
  font-family: var(--a-sans);
  font-size: 1.0625rem;
  line-height: 1.7;
}

/* ---------------------------------------------------------------- chrome */

/* Homepage nav bar (LandingNav.tsx:85-88 reference): translucent white with a
   backdrop blur and a hairline bottom border. ONE DELIBERATE DIVERGENCE (D7):
   the homepage uses `position: fixed` because the landing page owns its whole
   scroll; here it is `position: sticky; top: 0` so a long article's prose is
   never underlapped by a fixed bar and no body top-padding hack is needed. */
.article-site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.8);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  border-bottom: 1px solid var(--a-neutral-100);
}

/* Inner max-width wrapper (homepage `max-w-7xl` = 80rem) with responsive
   gutters (px-4 / sm:px-6 / lg:px-8) and the fixed bar height (h-16, xl:h-20). */
.article-site-header-inner {
  max-width: 80rem;
  margin: 0 auto;
  padding: 0 1rem;
  height: 4rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

@media (min-width: 640px) {
  .article-site-header-inner { padding: 0 1.5rem; }
}

@media (min-width: 1024px) {
  .article-site-header-inner { padding: 0 2rem; }
}

@media (min-width: 1280px) {
  .article-site-header-inner { height: 5rem; }
}

.article-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  text-decoration: none;
}

/* Portrait glyph mark — 3.5rem tall (homepage h-14), width auto so it never
   distorts and deliberately overflows the 4rem bar like the homepage. The
   <img> also carries width/height attributes; aspect-ratio keeps the reserved
   box correct once `height` overrides the attribute height, so the wordmark
   beside it never shifts as the asset loads. */
.article-brand-logo {
  height: 3.5rem;
  width: auto;
  aspect-ratio: 579.4 / 874.5;
  display: block;
}

/* Two-tone Nunito wordmark (BrandWordmark reference): 'Edu' in brand, 'Park'
   in neutral-900. The two-tone spans are aria-hidden; an unbroken 'EduPark' is
   emitted visually-hidden alongside (D10) so a screen reader never hears
   "Edu Park". Never white-labeled — this is EduPark's apex marketing surface. */
.article-brand-name {
  font-family: var(--a-display);
  font-weight: 800;
  font-size: 1.5rem;
  color: var(--a-neutral-900);
  line-height: 1;
}

.article-brand-name .article-brand-edu { color: var(--a-brand); }

/* Visually-hidden but readable by assistive tech (mirrors Tailwind sr-only). */
.article-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.article-site-nav {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.article-nav-link {
  color: var(--a-text);
  font-family: var(--a-sans);
  font-weight: 600;
  font-size: 0.9375rem;
  text-decoration: none;
  /* ≥44px tap target for the text links */
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
}

.article-nav-link:hover { color: var(--a-brand); }

/* The 'Start Free' CTA matches the HOMEPAGE, not the friendly pill (D8):
   linear-gradient(primary-600 → primary-700) + border-radius 1rem (rounded-2xl)
   per LandingNav.tsx:66-67. article.css is outside the lint:buttons ratchet
   (it is not client/src), documented in design-system.md, so nothing is
   suppressed.
   SCOPED to the nav (0-2-0) on purpose: a bare `.article-nav-cta` ties 0-1-0
   with `.article-btn`, declared LATER, so it lost on source order — the
   declarations here were silently inert. min-height stays ≥ the 2.75rem
   (≥44px) tap target inherited from .article-btn. */
.article-site-nav .article-nav-cta {
  padding: 0.5rem 1.25rem;
  font-size: 0.9375rem;
  border-radius: 1rem;
  background: linear-gradient(
    to right,
    var(--a-primary-600),
    var(--a-primary-700)
  );
  color: #fff;
}

.article-site-nav .article-nav-cta:hover {
  background: linear-gradient(
    to right,
    var(--a-primary-700),
    var(--a-primary-700)
  );
}

/* ≤480px: tighten the nav so the secondary links (Articles / Sign In) still FIT
   beside the 'Start Free' CTA rather than being hidden (D9). They used to
   `display: none` here, which left a phone reader — the majority of organic
   article traffic — with no path to the index and NO WAY TO LOG IN, since the
   footer carries Articles but not Sign In. The header already wraps, so the row
   reflows to a second line on the narrowest screens instead of disappearing. */
@media (max-width: 480px) {
  .article-site-header-inner {
    padding: 0.75rem 1rem;
    height: auto;
    gap: 0.5rem;
  }

  .article-site-nav {
    gap: 0.5rem;
  }

  .article-nav-link {
    font-size: 0.875rem;
  }

  .article-site-nav .article-nav-cta {
    padding: 0.5rem 0.875rem;
    font-size: 0.875rem;
  }
}

/* Dark marketing footer (LandingFooter.tsx:55-129 reference): neutral-900
   background, a 5-column grid where the brand block spans 2, then the
   PRODUCT / COMPANY / HELP & LEGAL columns, then a copyright row. */
.article-site-footer {
  margin-top: 4rem;
  background: var(--a-neutral-900);
  color: var(--a-neutral-400);
  font-family: var(--a-sans);
}

.article-footer-inner {
  max-width: 80rem;
  margin: 0 auto;
  padding: 3rem 1rem;
}

@media (min-width: 640px) {
  .article-footer-inner { padding-left: 1.5rem; padding-right: 1.5rem; }
}

@media (min-width: 1024px) {
  .article-footer-inner { padding: 4rem 2rem; }
}

.article-footer-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
}

@media (min-width: 640px) {
  .article-footer-grid { grid-template-columns: 1fr 1fr; }
}

@media (min-width: 1024px) {
  .article-footer-grid {
    grid-template-columns: repeat(5, 1fr);
    gap: 3rem;
  }
}

/* Brand block spans 2 of the 5 columns on lg. */
.article-footer-brand { grid-column: auto; }

@media (min-width: 1024px) {
  .article-footer-brand { grid-column: span 2 / span 2; }
}

.article-footer-brand-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

/* Footer wordmark: white 'Park', primary-400 'Edu' accent on the dark bg. */
.article-footer-brand-name {
  font-family: var(--a-display);
  font-weight: 800;
  font-size: 1.25rem;
  color: #fff;
  line-height: 1;
}

.article-footer-brand-name .article-brand-edu { color: var(--a-primary-400); }

.article-footer-desc {
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--a-neutral-400);
  max-width: 24rem;
  margin: 0;
}

.article-footer-col-title {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
  color: var(--a-neutral-300);
  margin: 0 0 1rem;
}

.article-footer-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.article-footer-col a {
  color: var(--a-neutral-400);
  text-decoration: none;
  font-size: 0.875rem;
  /* ≥44px tap target on touch, matching the header/nav and pagination links. */
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
}

.article-footer-col a:hover { color: #fff; }

.article-footer-bottom {
  margin-top: 3rem;
  padding-top: 2rem;
  border-top: 1px solid var(--a-neutral-800);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  font-size: 0.75rem;
  color: var(--a-neutral-500);
}

@media (min-width: 640px) {
  .article-footer-bottom {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

.article-preview-banner {
  background: var(--a-accent-subtle);
  color: rgb(146 64 14);
  text-align: center;
  padding: 0.625rem 1.25rem;
  font-weight: 600;
}

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

/*
 * The reader is a WIDE container (56rem) holding a NARROW reading column: prose
 * stays at the ~42rem Medium measure while the hero and interactive problem
 * widgets break out to the full width, so visuals use the side space the text
 * shouldn't. The index (.article-index) overrides this width for its card grid.
 */
.article-page {
  max-width: 56rem;
  margin: 0 auto;
  padding: 2.5rem 1.25rem 3rem;
}

/* Text-level content holds the reading measure, centered in the wide container.
   Scoped away from the index (whose header spans the full grid width). The high
   specificity is deliberate: it must beat the per-element `.article-body p {
   margin: 0 0 1.5rem }` rules below, which would otherwise zero the auto side
   margins and left-align the column. `margin-inline` centers without disturbing
   the vertical rhythm those rules own. */
.article-page:not(.article-index) .article-header > h1,
.article-page:not(.article-index) .article-header > .article-dek,
.article-page:not(.article-index) .article-header > .article-byline-top,
.article-page:not(.article-index) .article-header > .article-tags,
.article-page:not(.article-index) .article-body > *,
.article-page:not(.article-index) .article-cta,
.article-page:not(.article-index) .article-byline-card,
.article-page:not(.article-index) .article-read-next {
  max-width: 42rem;
  margin-inline: auto;
}

/* Visuals + interactive widgets break out to the full container width. (The hero
   already fills its container; the uncapped .article-header lets it span too.) */
.article-page:not(.article-index) .article-body > .article-problem,
.article-page:not(.article-index) .article-body > .article-problem-fallback,
.article-page:not(.article-index) .article-body > figure,
.article-page:not(.article-index) .article-body > img,
.article-page:not(.article-index) .article-body > table {
  max-width: 100%;
}

.article-header h1 {
  font-family: var(--a-display);
  font-size: clamp(2rem, 5vw, 2.75rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 800;
  margin: 0 0 0.5rem;
  overflow-wrap: break-word;
}

/* Breadcrumb link above the title (phase 3 — back to the index). */
.article-breadcrumb {
  display: inline-block;
  margin: 0 0 0.75rem;
  color: var(--a-text-muted);
  font-family: var(--a-sans);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
}

.article-breadcrumb:hover { color: var(--a-brand); }

.article-breadcrumb span { margin-right: 0.25rem; }

/* Subtitle / deck under the title (Medium-style). */
.article-dek {
  font-family: var(--a-serif);
  font-size: 1.375rem;
  line-height: 1.4;
  color: var(--a-text-muted);
  margin: 0 0 1.25rem;
}

.article-meta {
  color: var(--a-text-muted);
  font-size: 0.9375rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

/* Top author row: initials avatar + name + date · read time. */
.article-byline-top {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.5rem 0 0;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--a-border);
}

.article-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 9999px;
  background: var(--a-brand-subtle);
  color: var(--a-brand);
  font-family: var(--a-display);
  font-weight: 800;
  font-size: 0.9375rem;
}

.article-byline-top-text {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.article-byline-top-name {
  font-family: var(--a-display);
  font-weight: 700;
  font-size: 0.9375rem;
  color: var(--a-text);
}

.article-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.875rem 0;
}

.article-tag {
  display: inline-flex;
  align-items: center;
  min-height: 2rem;
  padding: 0.125rem 0.875rem;
  border-radius: 9999px;
  background: var(--a-brand-subtle);
  color: var(--a-brand);
  font-size: 0.875rem;
  font-weight: 600;
  text-decoration: none;
}

.article-tag-active {
  background: var(--a-brand);
  color: #fff;
}

/* The category chip bar sits directly above the tag bar; tighten the gap so the
   two filter rows read as one grouped control. */
.article-index-categories {
  margin-bottom: 0.25rem;
}

.article-hero {
  width: 100%;
  max-width: 100%;
  border-radius: 0.75rem;
  margin: 1.75rem 0 0;
}

/* ----------------------------------------------------------------- body */

/* The reading prose — serif, larger, softer ink (Medium-style). The problem
   widget and code below reset back to sans/mono so only prose is serif. */
.article-body {
  margin-top: 2rem;
  font-family: var(--a-serif);
  font-size: 1.3125rem;
  line-height: 1.7;
  color: var(--a-prose);
}

.article-body p { margin: 0 0 1.5rem; }
.article-body ul, .article-body ol { margin: 0 0 1.5rem; padding-left: 1.5rem; }
.article-body li { margin: 0.375rem 0; }
.article-body li::marker { color: var(--a-text-muted); }

/* Drop cap — only when the article opens with prose (not a heading), so a
   mid-article paragraph never gets one. */
.article-body > p:first-child::first-letter {
  float: left;
  font-family: var(--a-display);
  font-weight: 800;
  font-size: 3.5rem;
  line-height: 0.78;
  padding: 0.28rem 0.55rem 0 0;
  color: var(--a-text);
}

.article-body h2, .article-body h3 {
  font-family: var(--a-display);
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin: 2.5rem 0 0.75rem;
}

.article-body h2 { font-size: 1.65rem; }
.article-body h3 { font-size: 1.3rem; }

.article-body img { max-width: 100%; height: auto; border-radius: 0.75rem; }

/* Standalone images are wrapped as <figure> by the renderer (see
   articleRenderService's standalone_image_figure rule) so the breakout rule
   above can widen them to the full container; own the vertical rhythm and
   center the image within the figure. */
.article-body figure { margin: 1.75rem 0; text-align: center; }
.article-body figcaption {
  margin-top: 0.5rem;
  font-size: 0.9375rem;
  color: var(--a-text-muted);
}

/*
 * Prose links only — button-styled anchors (.article-btn, e.g. the in-body
 * ::cta and post-answer nudge) are excluded: without :not() this rule's
 * specificity (0-1-1) beats .article-btn-primary's (0-1-0) and paints the
 * button label brand-on-brand (invisible).
 */
.article-body a:not(.article-btn) {
  color: var(--a-brand);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

/* Pull quote — large italic serif with a left rule (no box). */
.article-body blockquote {
  margin: 2rem 0;
  padding: 0.25rem 0 0.25rem 1.5rem;
  border-left: 3px solid var(--a-brand);
  font-style: italic;
  font-size: 1.5rem;
  line-height: 1.5;
  color: var(--a-text-muted);
}

.article-body blockquote p { margin: 0; }

.article-body pre {
  background: var(--a-surface-sunken);
  border: 1px solid var(--a-border);
  border-radius: 0.75rem;
  padding: 1rem;
  overflow-x: auto;
}

.article-body code {
  font-family: var(--a-mono);
  background: var(--a-surface-sunken);
  border-radius: 0.375rem;
  padding: 0.125rem 0.375rem;
  font-size: 0.875em;
}

.article-body pre code { font-size: 0.9375rem; }

.article-body pre code { background: none; padding: 0; }

/* Wide content must scroll inside its own container, never the page. */
.article-body table {
  border-collapse: collapse;
  display: block;
  max-width: 100%;
  width: fit-content;
  margin-left: auto;
  margin-right: auto;
  overflow-x: auto;
}

.article-body th, .article-body td {
  border: 1px solid var(--a-border);
  padding: 0.5rem 0.875rem;
  text-align: left;
}

.article-body th { background: var(--a-surface-sunken); }

/* ::compare block (server/src/services/articleRenderService.ts) — a
   comparison table whose data-column tint/icon is declared per article via
   ::compare{tones="..."}, not hardcoded to "left=bad, right=good".
   `.article-body th` (class+element, specificity 0,1,1) outranks a bare
   `.article-compare-header` (0,1,0) — the extra `.article-body` qualifier
   below matches it so the center-align actually wins the cascade. */
.article-body .article-compare-header { text-align: center; }

.article-compare-cell--red { background: var(--a-error-subtle); }
.article-compare-cell--green { background: var(--a-success-subtle); }
.article-compare-cell--amber { background: var(--a-accent-subtle); }
.article-compare-cell--blue { background: var(--a-brand-subtle); }
.article-compare-cell--gray { background: var(--a-gray-subtle); }

.article-compare-cell--red::before,
.article-compare-cell--green::before {
  display: inline-block;
  margin-right: 0.5em;
  font-weight: 700;
}
.article-compare-cell--red::before { content: "\2715"; color: var(--a-error); }
.article-compare-cell--green::before { content: "\2713"; color: var(--a-success); }

/* texmath display-math wrapper */
.article-body section { overflow-x: auto; }
eq { display: inline; }
eqn { display: block; }

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

.article-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem; /* ≥44px tap target */
  padding: 0.5rem 1.5rem;
  border-radius: 9999px; /* pill */
  border: 1px solid transparent;
  font: inherit;
  font-weight: 700;
  font-size: 1rem;
  text-decoration: none;
  cursor: pointer;
}

.article-btn-primary {
  background: var(--a-brand);
  color: #fff;
}

.article-btn-primary:hover { background: var(--a-brand-hover); }

.article-btn-ghost {
  background: transparent;
  color: var(--a-brand);
  border-color: var(--a-border);
}

.article-btn-ghost:hover { background: var(--a-brand-subtle); }

/* --------------------------------------------------------- problem card */

.article-problem,
.article-problem-fallback {
  /* Reset the interactive widget back to sans — only the reading prose is
     serif. (KaTeX sets its own font-family, so math is unaffected.) */
  font-family: var(--a-sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--a-text);
  border: 1px solid var(--a-border);
  border-radius: 1rem;
  padding: 1.25rem;
  margin: 1.75rem 0;
  background: var(--a-surface);
  box-shadow: 0 4px 16px rgb(30 107 235 / 0.06);
}

.article-problem-fallback { color: var(--a-text-muted); }

/* text-align centers the SVG (a replaced inline element, same technique as
   .article-body figure above) — without it the raw <svg> keeps its own
   intrinsic width and sits flush-left in a wider reading column, while its
   sibling .article-problem-svg-caption below renders its text centered
   across that same full width, visibly misaligning the figure/caption pair. */
.article-problem-svg { text-align: center; }
.article-problem-svg svg { display: inline-block; max-width: 100%; height: auto; }

/* Student-visible muted caption under a problem's SVG figure (svg_caption).
   Distinct from the figure's own aria-label (svg_description), which is
   never rendered as visible text. */
.article-problem-svg-caption {
  margin-top: 0.25rem;
  font-size: 0.875rem;
  color: var(--a-text-muted);
  text-align: center;
}

.article-problem-image { max-width: 100%; height: auto; }

.article-choice {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  min-height: 2.75rem; /* ≥44px tap target */
  padding: 0.625rem 0.875rem;
  margin: 0.5rem 0;
  border: 1px solid var(--a-border);
  border-radius: 0.75rem;
  cursor: pointer;
}

.article-choice:hover { border-color: var(--a-brand); }

.article-choice input { margin-top: 0.3rem; accent-color: var(--a-brand); }

.article-choice-letter {
  font-weight: 700;
  color: var(--a-brand);
}

.article-choice.is-correct {
  border-color: var(--a-success);
  background: var(--a-success-subtle);
}

.article-choice.is-incorrect {
  border-color: var(--a-error);
  background: var(--a-error-subtle);
}

.article-problem-actions {
  display: flex;
  gap: 0.75rem;
  margin-top: 0.875rem;
  flex-wrap: wrap;
}

.article-problem-result { font-weight: 700; }

.article-problem-result.is-correct { color: var(--a-success); }

.article-problem-result.is-incorrect { color: var(--a-error); }

.article-explanation {
  margin-top: 1rem;
  border-top: 1px solid var(--a-border);
  padding-top: 0.875rem;
}

/*
 * Problem text and explanations are BLOCK-rendered markdown (intern-bank
 * explanations use '## Observation' / '#### Step 1' headings); scale their
 * headings down to card-local sub-structure, not article-section size.
 */
.article-problem h1, .article-problem h2, .article-problem h3 {
  font-size: 1.125rem;
  margin: 1.25rem 0 0.5rem;
}

.article-problem h4, .article-problem h5, .article-problem h6 {
  font-size: 1rem;
  margin: 1rem 0 0.375rem;
}

.article-explanation summary {
  cursor: pointer;
  font-weight: 700;
  color: var(--a-brand);
  min-height: 2.75rem;
  display: flex;
  align-items: center;
}

/* ------------------------------------------------- VE walkthrough (stepper) */

/*
 * The stepwise visual-explanation walkthrough rendered inside the <details>
 * reveal (visualExplanationArticleRenderer.ts). With JS OFF every step is
 * visible and STACKED (crawl-first, S2); widget.js adds `.is-enhanced` to the
 * container + the UA `hidden` attribute to non-current steps to page through
 * them one at a time — so the one-at-a-time layout applies ONLY under
 * `.article-ve.is-enhanced`. Hiding rides on the global
 * `[hidden]{display:none!important}` rule above (author CSS can't override it).
 */
.article-ve { margin-top: 0.25rem; }

.article-ve-steps {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Stacked (no-JS / crawler) view: each step is a discrete block with a divider
   so the flat list still reads as separate steps. */
.article-ve-step {
  margin: 0 0 1.25rem;
  padding: 0 0 1.25rem;
  border-bottom: 1px solid var(--a-border);
}

.article-ve-step:last-child {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: 0;
}

/* Enhanced view: the single visible step stands alone — no divider/spacing. */
.article-ve.is-enhanced .article-ve-step {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: 0;
}

.article-ve-step-title {
  font-family: var(--a-display);
  font-weight: 800;
  font-size: 1.0625rem;
  margin: 0 0 0.5rem;
  color: var(--a-text);
}

.article-ve-step-instruction { margin: 0 0 0.75rem; }

/* Bare-LaTeX work lines / equation transforms are KaTeX displayMode blocks;
   give each its own rhythm and let wide math scroll inside its own line so it
   never forces horizontal PAGE overflow. */
.article-ve-math { margin: 0.75rem 0; }

.article-ve-math-line,
.article-ve-transform-before,
.article-ve-transform-after {
  margin: 0.375rem 0;
  overflow-x: auto;
}

.article-ve-transform {
  margin: 0.75rem 0;
  padding-left: 0.875rem;
  border-left: 3px solid var(--a-brand-subtle);
}

.article-ve-transform-op {
  margin: 0.25rem 0;
  color: var(--a-text-muted);
  font-size: 0.9375rem;
}

.article-ve-concept { margin: 0.75rem 0 0; color: var(--a-text-muted); }

.article-ve-concept-term { color: var(--a-text); }

/* Keep KaTeX display blocks inside the card. */
.article-ve .katex-display {
  margin: 0.375rem 0;
  overflow-x: auto;
  overflow-y: hidden;
}

/* ------ answer analysis (correct + trap cards; revealed on the last step) --- */

.article-ve-analysis {
  margin-top: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
}

.article-ve-correct,
.article-ve-trap {
  border: 1px solid var(--a-border);
  border-radius: 0.75rem;
  padding: 0.875rem 1rem;
}

.article-ve-correct {
  border-color: var(--a-success);
  background: var(--a-success-subtle);
}

.article-ve-trap {
  border-color: var(--a-error);
  background: var(--a-error-subtle);
}

.article-ve-trap-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.article-ve-analysis-heading {
  font-family: var(--a-display);
  font-weight: 800;
  font-size: 1rem;
  margin: 0 0 0.625rem;
}

.article-ve-answer-label { font-weight: 700; margin: 0 0 0.25rem; }

.article-ve-key-insight { margin-top: 0.5rem; font-weight: 600; }

.article-ve-trap-misconception {
  margin-top: 0.375rem;
  color: var(--a-text-muted);
  font-size: 0.9375rem;
}

/* ---------------------------------- expert tips ---------------------------- */

.article-ve-tips {
  margin-top: 1.25rem;
  background: var(--a-accent-subtle);
  border-radius: 0.75rem;
  padding: 0.875rem 1rem;
}

.article-ve-tips-heading {
  font-family: var(--a-display);
  font-weight: 800;
  font-size: 1rem;
  margin: 0 0 0.5rem;
}

.article-ve-tip-list { margin: 0; padding-left: 1.25rem; }

.article-ve-tip { margin: 0.25rem 0; }

/* ------------------------------ Back / Next controls ----------------------- */

/* Injected by widget.js — they only exist under JS. Wrap on narrow screens and
   inherit the ≥44px tap target from .article-btn. */
.article-ve-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  margin-top: 1rem;
}

/* Disabled Back/Next (first/last step) reads as inert. */
.article-ve-controls .article-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* --------------------------- progress bar (client-injected, top) ----------- */

/*
 * widget.js injects the progress bar at the TOP of the enhanced stepper (D4) —
 * it only ever exists under JS, so every rule is scoped to .is-enhanced. Mirrors
 * the app's StepProgressBar: a brand fill over a track, with display-only step
 * dots overlaid (D2 — never buttons). Dots are capped at ≤10 steps in widget.js
 * (D7); above that the fill + label carry progress alone.
 */
.article-ve.is-enhanced .article-ve-progress {
  margin-top: 0.25rem;
  margin-bottom: 1rem;
}

.article-ve.is-enhanced .article-ve-progress-label {
  font-weight: 700;
  font-size: 0.9375rem;
  color: var(--a-text-muted);
  margin-bottom: 0.5rem;
}

/* The progress bar is a relatively-positioned box holding TWO stacked layers:
   the clipped track (fill) and an UNCLIPPED dots overlay above it. Mirrors the
   app's StepProgressBar. Stays within the embed width — never forces horizontal
   PAGE overflow. */
.article-ve.is-enhanced .article-ve-progress-bar {
  position: relative;
  height: 0.5rem;
}

/* The track holds ONLY the fill; overflow:hidden clips the fill's ends to the
   pill radius — NOT the dots, which live in the sibling overlay above and would
   otherwise be clipped into lozenges (they are taller than the 8px track). */
.article-ve.is-enhanced .article-ve-progress-track {
  position: absolute;
  inset: 0;
  background: var(--a-border);
  border-radius: 9999px;
  overflow: hidden;
}

.article-ve.is-enhanced .article-ve-progress-fill {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  background: var(--a-brand);
  transition: width 300ms ease-in-out;
}

/* Display-only dot overlay — sits ABOVE the clipped track (not inside it) so the
   dots render as full circles, the is-current scale included. z-index promotes it
   over the positioned fill so the current dot's leading edge is never covered by
   the fill's advancing edge (fill is position:absolute; equal-z siblings would
   otherwise fall back to tree order). */
.article-ve.is-enhanced .article-ve-progress-dots {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 0.25rem;
}

/* Upcoming dot — NOTE there is no --a-surface-raised token on this surface, so
   use --a-surface (D2 note in the plan). */
.article-ve.is-enhanced .article-ve-progress-dot {
  flex: 0 0 auto;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 9999px;
  background: var(--a-surface);
  border: 1px solid var(--a-border);
  transition: transform 200ms, background 200ms;
}

.article-ve.is-enhanced .article-ve-progress-dot.is-current {
  background: var(--a-brand);
  transform: scale(1.25);
}

.article-ve.is-enhanced .article-ve-progress-dot.is-done {
  background: var(--a-brand);
}

/* --------------------------- incoming-step + analysis motion --------------- */

/*
 * Pure-CSS motion keyed off the EXISTING [hidden] display toggle — ZERO JS.
 * render() flips each step's (and the analysis block's) `hidden`
 * (display:none↔block); a display change auto-replays a CSS `animation`, so
 * :not([hidden]) fades each newly-shown step/analysis in. Opacity/transform
 * only — no layout shift.
 */
@keyframes articleVeStepIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

.article-ve.is-enhanced .article-ve-step:not([hidden]) {
  animation: articleVeStepIn 180ms ease-out;
}

/* Mirrors the app's WrongAnswerCard slideDown; animates the single
   .article-ve-analysis CONTAINER (not each card) so reduced-motion has ONE
   selector to pin. */
@keyframes articleVeSlideDown {
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
}

.article-ve.is-enhanced .article-ve-analysis:not([hidden]) {
  animation: articleVeSlideDown 200ms ease-out;
}

/* ------------------------ reduced motion (D3, first motion gate) ----------- */

/*
 * The FIRST @media(prefers-reduced-motion) in this file, placed AFTER the VE
 * motion above so it wins. Selectors EXACTLY match the transitions/keyframes
 * above, so reduced-motion readers get the final state instantly (bar jumps,
 * dots snap, step + cards appear) with no animation.
 */
@media (prefers-reduced-motion: reduce) {
  .article-ve.is-enhanced .article-ve-progress-fill { transition: none; }
  .article-ve.is-enhanced .article-ve-progress-dot { transition: none; }
  .article-ve.is-enhanced .article-ve-step:not([hidden]) { animation: none; }
  .article-ve.is-enhanced .article-ve-analysis:not([hidden]) { animation: none; }
}

/* --------------------------------------------------------- CTA surfaces */

.article-cta,
.article-nudge {
  background: var(--a-brand-subtle);
  border: 1px solid var(--a-border);
  border-radius: 1rem;
  padding: 1.25rem;
  margin: 1.75rem 0 0;
  text-align: center;
}

.article-nudge { margin-top: 1rem; }

.article-subtle-link { margin: 1rem 0 0; }

.article-subtle-link a { color: var(--a-brand); font-weight: 600; }

.article-byline-card {
  border-top: 1px solid var(--a-border);
  margin-top: 2.5rem;
  padding-top: 1.25rem;
}

.article-byline-name { font-weight: 700; margin: 0; }

.article-byline-bio { color: var(--a-text-muted); margin: 0.25rem 0 0; }

/* --------------------------------------------------------- read next (D20) */

/* Related-articles row after the byline card — a top border sets it off from
   the article, then the REUSED .article-card-grid renders the cards. */
.article-read-next {
  border-top: 1px solid var(--a-border);
  margin-top: 2.5rem;
  padding-top: 1.75rem;
}

.article-read-next-heading {
  font-family: var(--a-display);
  font-size: 1.375rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  margin: 0;
}

/* The reused grid is stacked on phones and 2-col from 640px (see .article-card-
   grid below). At ≥1024px the read-next row goes 3-across so its up-to-3 cards
   sit on ONE balanced line instead of a lopsided 2+1 wrap. Scoped to read-next
   (0-2-0) so it wins over the base grid's 2-col rule without touching the index;
   stays inside the reading column (capped to the 42rem measure with the byline
   card above it), so no horizontal overflow at any width. */
@media (min-width: 1024px) {
  .article-read-next .article-card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ---------------------------------------------------------------- index */

.article-index-subtitle { color: var(--a-text-muted); font-size: 1.0625rem; }

.article-card-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  margin-top: 1.5rem;
}

@media (min-width: 640px) {
  .article-index { max-width: 52rem; }
  .article-card-grid { grid-template-columns: 1fr 1fr; }
}

/* Desktop: widen to the home content width (max-w-6xl) and go 3-across so the
   card grid fills the side space like the landing page. */
@media (min-width: 1024px) {
  .article-index { max-width: 72rem; }
  .article-card-grid { grid-template-columns: repeat(3, 1fr); }
}

.article-card {
  display: block;
  border: 1px solid var(--a-border);
  border-radius: 1rem;
  padding: 1.25rem;
  text-decoration: none;
  color: inherit;
  box-shadow: 0 4px 16px rgb(30 107 235 / 0.06);
}

.article-card:hover { border-color: var(--a-brand); }

.article-card h2,
.article-card h3 {
  font-family: var(--a-display);
  font-size: 1.25rem;
  margin: 0 0 0.375rem;
  /* Clamp long titles to 2 lines (like .article-card-desc) so a wordy headline
     never wraps to 3+ lines and desyncs card heights across the grid — shared
     by both the index cards (h2) and the read-next row (h3, one articleCardHtml). */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.article-card-desc {
  color: var(--a-text-muted);
  margin: 0 0 0.625rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.article-card-meta {
  color: var(--a-text-muted);
  font-size: 0.875rem;
  margin: 0;
}

/* Category badge — a small brand-subtle eyebrow above the card title, shared by
   the index grid and the read-next row (ONE articleCardHtml renderer). Reuses
   the semantic brand-subtle look of the chip pills for visual consistency. */
.article-card-category {
  display: inline-block;
  margin: 0 0 0.5rem;
  padding: 0.125rem 0.625rem;
  border-radius: 9999px;
  background: var(--a-brand-subtle);
  color: var(--a-brand);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.article-empty {
  text-align: center;
  color: var(--a-text-muted);
  padding: 3rem 0;
}

/* -------------------------------------------------- index search + paging */

.article-search {
  display: flex;
  gap: 0.5rem;
  margin: 1.25rem 0 0.5rem;
}

.article-search-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.5rem 0.875rem;
  border: 1px solid var(--a-border);
  border-radius: 9999px;
  background: var(--a-surface);
  color: var(--a-text);
  font: inherit;
}

.article-search-input:focus-visible {
  outline: 2px solid var(--a-brand);
  outline-offset: 1px;
}

.article-search-btn { flex: 0 0 auto; }

.article-results-count {
  color: var(--a-text-muted);
  font-size: 0.9375rem;
  margin: 1rem 0 0;
}

.article-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  margin-top: 2rem;
}

.article-page-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.25rem;
  min-height: 2.25rem;
  padding: 0.25rem 0.625rem;
  border: 1px solid var(--a-border);
  border-radius: 0.5rem;
  color: var(--a-brand);
  font-weight: 600;
  text-decoration: none;
}

.article-page-link:hover { border-color: var(--a-brand); }

.article-page-current {
  background: var(--a-brand);
  border-color: var(--a-brand);
  color: #fff;
}

.article-page-ellipsis {
  padding: 0 0.25rem;
  color: var(--a-text-muted);
}

/* Error pages hold the reading measure, not the 56rem article width — their
   short, centered copy shouldn't stretch across the wide container. */
.article-error-page { max-width: 42rem; text-align: center; padding-top: 4rem; }

.article-error-actions {
  display: flex;
  gap: 0.75rem;
  justify-content: center;
  flex-wrap: wrap;
}
