
/* ── Post-page layout: article centered, sidebar is decoration ───────────
 *
 * The old rule here (grid-template-columns: 100px var(--width-article);
 * width: fit-content) put the sidebar in a grid column, so the article
 * sat flush against it on the LEFT of the viewport — the optical center
 * landed well left of screen center, and the sidebar read as a "second
 * column" rather than a decorative mark.
 *
 * Below removes the sidebar from the layout calculation entirely: the
 * page becomes a single full-width column with symmetric left/right
 * padding, the article centers inside it with margin: 0 auto (article
 * center = page center), and the sidebar becomes position: absolute,
 * positioned with calc(50% - article-width/2 - gap) so it hangs just
 * outside the article's left edge WITHOUT being part of the box the
 * article centers within. (position: absolute, not fixed — .page-layout
 * has animation: fadeUp using transform: translateY(...) in layout.css,
 * and any ancestor with a transform becomes the containing block for
 * fixed descendants, so position: fixed would anchor to .page-layout's
 * full height instead of the viewport and fall off-screen.)
 * ─────────────────────────────────────────────────────────────────────── */

.page-layout {
  grid-template-columns: 1fr;
  width: 100%;
  max-width: none;
  margin: 0;
  padding: var(--space-32) clamp(24px, 6vw, 72px); /* symmetric */
  position: relative;
}

.sidebar {
  min-height: 0;
  align-items: center;
  transition: opacity var(--duration-fast) var(--ease-out);
  position: absolute;
  top: var(--space-32);
  /* left: measured from .page-layout's own box (it's the positioned
     ancestor and spans full page width) — same coordinate space
     .article-wrapper's margin: 0 auto centers within below, so this
     reliably lands just left of the article's rendered edge as long as
     the article is at its intrinsic --width-article width (true above
     the 640px breakpoint, where max-width: 100% isn't clamping it down
     and the sidebar switches to a normal grid column instead). */
  left: max(24px, calc(50% - (var(--width-article) / 2) - clamp(56px, 6vw, 96px)));
}

.article-wrapper {
  min-width: 0;
  width: var(--width-article);
  max-width: 100%;
  margin: 0 auto; /* centers within the symmetric padding box above */
}

.brand-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  color: var(--color-text-heading);
  transition: color var(--duration-fast);
}
.brand-block:hover { color: var(--color-text-primary); }

.brand-name-vertical {
  writing-mode: vertical-rl;
  font-family: var(--font-display);
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  line-height: 1;
  color: inherit;
}

.article-container { width: 100%; }

.article-header {
  margin-top: -0.18em;
  margin-bottom: var(--space-12);
}

.article-title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, var(--text-4xl));
  font-weight: 700;
  color: var(--color-text-heading);
  line-height: var(--leading-tight);
  margin-bottom: var(--space-6);
  letter-spacing: var(--tracking-tight);
}

.article-meta {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
  font-variant-numeric: tabular-nums;
}

.article-meta time { letter-spacing: 0.08em; }
.article-meta > *:not(:last-child)::after {
  content: "·";
  margin-left: var(--space-2);
  color: var(--color-border-strong);
}

/* ── Mixed CJK/Latin typesetting ──────────────────────────────────────────
 * Not font-specific — this is CJK/Latin mixed-typesetting behavior from
 * 中文文案排版指北 (sparanoid/chinese-copywriting-guidelines, the
 * commonly-cited community spec for this) and stays correct regardless of
 * which CJK typeface --font-body resolves to on a given platform:
 *
 *   - text-autospace (CSS Text L4) inserts a ¼em gap around Latin letters
 *     and digits embedded in CJK runs. word-spacing is the legacy
 *     fallback that produces a similar (less precise) effect on browsers
 *     that don't support text-autospace yet.
 *   - font-variant-numeric: tabular-nums keeps digit columns aligned
 *     across line wraps; oldstyle-nums gives Latin copy a classical look
 *     (3/5/7/9 dip below the baseline instead of flat lining-nums).
 *   - font-feature-settings enables OpenType features: kern (pair
 *     spacing), liga (Latin ligatures), calt (contextual alternates),
 *     palt (proportional alternates — tightens CJK punctuation spacing).
 *   - hyphens: manual (was auto) — auto hyphenation occasionally splits
 *     English words mid-line and serves no purpose for CJK text.
 *   - line-break: strict turns on the stricter 避头尾 (kinsoku shori)
 *     line-breaking rules — punctuation like 」。，、 never starts a line.
 *     Most current browsers already default CJK to this, but it's not
 *     universal, so it's declared explicitly rather than relied on as a
 *     default.
 * ─────────────────────────────────────────────────────────────────────── */
.article-content {
  font-size: var(--text-md);
  hyphens: manual;
  widows: 2;
  orphans: 2;
  overflow-wrap: break-word;
  word-break: break-word;
  text-autospace: ideograph-alpha ideograph-numeric;
  word-spacing: 0.05em;
  font-variant-numeric: tabular-nums oldstyle-nums;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1, "palt" 1;
  line-break: strict;
}

.article-content p {
  /* line-height 1.75 sits in the commonly-cited 1.6–1.8 range for CJK
     body copy at reading size — a touch looser than the 1.6 this used to
     inherit from body, which read tight for Chinese serif at 18px. */
  line-height: 1.75;
  margin-bottom: var(--space-3);
  text-align: start;
}

/* font-weight: inherit keeps CJK <strong> from faux-bolding; the
 * underline carries the emphasis instead, in the accent red reserved for
 * this and blockquote's rule — em/u/del/mark stay monochrome, told apart
 * by line shape instead. :not(.pull-quote-label) excludes the pullquote
 * shortcode's own <strong> label (uppercase, muted gray, styled below). */
.article-content strong:not(.pull-quote-label) {
  font-weight: inherit;
  color: inherit;
  text-decoration-line: underline;
  text-decoration-color: var(--color-accent);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 0.25em;
  text-decoration-skip-ink: none;
}

.article-content h2 {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--color-text-heading);
  margin-top: var(--space-16);
  margin-bottom: var(--space-6);
  line-height: var(--leading-snug);
}

.article-content h3 {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--color-text-heading);
  margin-top: var(--space-12);
  margin-bottom: var(--space-4);
  line-height: var(--leading-snug);
}

/* h1 in body content is discouraged (the page title is the sole <h1>), but a
 * stray `# ` in markdown shouldn't fall back to UA styles — keep it a touch
 * above h2 and move on. */
.article-content h1 {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 700;
  color: var(--color-text-heading);
  margin-top: var(--space-16);
  margin-bottom: var(--space-6);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

.article-content h4 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--color-text-heading);
  margin-top: var(--space-10);
  margin-bottom: var(--space-3);
  line-height: var(--leading-snug);
}

.article-content h5 {
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--color-text-heading);
  margin-top: var(--space-8);
  margin-bottom: var(--space-2);
  line-height: var(--leading-snug);
}

/* h6 reads as a small caps-tracked label rather than another serif heading. */
.article-content h6 {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-functional);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  margin-top: var(--space-8);
  margin-bottom: var(--space-2);
}

.article-content blockquote {
  border-left: 2px solid var(--color-accent);
  background: var(--color-bg-subtle);
  padding: var(--space-4) var(--space-6);
  margin: var(--space-8) 0;
  color: var(--color-text-secondary);
  line-height: var(--leading-relaxed);
}
.article-content blockquote p:last-child { margin-bottom: 0; }

/* Nested quotes recede instead of stacking the same accent + tint twice:
   neutral border, no fill of their own (the ancestor's tint already reads
   as the quoted block), tighter top margin to sit close to the line above
   it inside the same quote. */
.article-content blockquote blockquote {
  border-left-color: var(--color-border-strong);
  background: transparent;
  margin: var(--space-4) 0 0;
  padding: var(--space-1) var(--space-5);
}

.article-content ul,
.article-content ol {
  margin-bottom: var(--space-5);
}
.article-content li { margin-bottom: var(--space-2); }

/* ul reuses .brand-mark's accent square (sidebar.css) instead of a browser
 * disc; nested ul drops to a hollow gray square. ol counters use the mono/
 * tabular-nums family from .article-meta, zero-padded ("01."); nested ol
 * drops to lower-alpha. Doesn't honor a list's `start` value — an accepted
 * gap, not worth counter(attr())'s patchy browser support. */
.article-content ul {
  list-style: none;
  padding-left: 0;
}
.article-content ul > li {
  position: relative;
  padding-left: 1.4em;
}
.article-content ul > li::before {
  content: "";
  position: absolute;
  left: 0;
  /* Vertically centered against the line box, not eyeballed: matches
     where the browser's own `vertical-align: middle` would place a same-
     size inline box against this text (verified against li font-size,
     independent of the marker's own size). */
  top: 0.769em;
  width: 6px;
  height: 6px;
  background-color: var(--color-accent);
}
.article-content ul ul > li::before {
  background-color: transparent;
  border: 1px solid var(--color-text-muted);
}
.article-content ul ul,
.article-content ul ol {
  margin-top: var(--space-2);
}

.article-content ol {
  list-style: none;
  counter-reset: article-ol;
  padding-left: 0;
}
.article-content ol > li {
  counter-increment: article-ol;
  position: relative;
  padding-left: 2.2em;
}
.article-content ol > li::before {
  content: counter(article-ol, decimal-leading-zero) ".";
  position: absolute;
  left: 0;
  /* line-height: 1 makes this pseudo's own box exactly font-size tall, so
     top can be solved for a precise center instead of guessed: verified
     against the browser's own `vertical-align: middle` placement for this
     li, then re-solved for this box's own (smaller, tighter) height. Holds
     across font-size contexts (base list, nested list, the smaller
     .footnotes list) since everything here is in em. */
  top: 0.59em;
  line-height: 1;
  font-family: var(--font-mono);
  font-size: 0.85em;
  color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums;
}
.article-content ol ol {
  counter-reset: article-ol-nested;
}
.article-content ol ol > li {
  counter-increment: article-ol-nested;
}
.article-content ol ol > li::before {
  content: counter(article-ol-nested, lower-alpha) ")";
  color: var(--color-text-muted);
}
.article-content ol ol,
.article-content ol ul {
  margin-top: var(--space-2);
}

/* Task list items: the checkbox itself is the marker, so the ::before dot
   is dropped and a custom-drawn box sits in the same gutter the dot used
   to occupy — same left/top positioning system as the ul dot above, not
   the native OS checkbox widget, so it lines up with the rest of the list
   family instead of varying by browser/platform. Falls back to dot+native
   checkbox together on browsers without :has() support — a soft
   degradation, not a broken layout. */
.article-content li:has(> input[type="checkbox"])::before {
  content: none;
}
.article-content input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  /* Form controls don't inherit font-size from their container by default
     (browser UA stylesheet gives <input> its own base size) — without
     this, `top` below resolves against that unrelated size instead of the
     li's, throwing the vertical centering off. */
  font-size: inherit;
  position: absolute;
  left: 0;
  top: 0.575em;
  width: 13px;
  height: 13px;
  margin: 0;
  border: 1.5px solid var(--color-border-strong);
  border-radius: 3px;
  background: var(--color-bg-pure);
}
.article-content input[type="checkbox"]:checked {
  border-color: var(--color-accent);
  background: var(--color-accent);
}
.article-content input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 3.5px;
  top: 0.5px;
  width: 4px;
  height: 7px;
  border: solid var(--color-bg-pure);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}

/* Definition lists (dl/dt/dd) */
.article-content dl {
  margin: var(--space-6) 0;
}
.article-content dt {
  font-family: var(--font-ui);
  font-weight: 600;
  color: var(--color-text-heading);
}
.article-content dd {
  margin: var(--space-1) 0 var(--space-4) 0;
  padding-left: var(--space-4);
  border-left: 1px solid var(--color-border-subtle);
  color: var(--color-text-secondary);
}

.article-content code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--color-bg-subtle);
  padding: 2px 6px;
  border-radius: 3px;
}

.article-content pre {
  background: var(--color-bg-subtle);
  border-left: 3px solid var(--color-border-strong);
  padding: var(--space-4);
  overflow-x: auto;
  margin: var(--space-8) 0;
}
.article-content pre code {
  background: none;
  padding: 0;
  border-radius: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

/* Chroma syntax highlighting moved to assets/css/syntax.css. */

.article-content img {
  width: 100%;
  height: auto;
  display: block;
  margin: var(--space-8) 0;
}
.article-content figure { margin: var(--space-8) 0; }
.article-content figcaption {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-align: center;
  margin-top: var(--space-3);
}

.article-content hr {
  border: none;
  text-align: center;
  margin: var(--space-16) 0;
}
.article-content hr::after {
  content: "***";
  font-family: var(--font-display);
  font-size: var(--text-xl);
  color: var(--color-border-strong);
  letter-spacing: 0.5em;
}

/* WCAG 1.4.1: links must be distinguishable by more than color alone */
.article-content a {
  color: var(--color-text-primary);
  text-decoration: underline;
  text-decoration-color: var(--color-border-strong);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: color var(--duration-fast), text-decoration-color var(--duration-fast);
}
.article-content a:hover {
  color: var(--color-accent);
  text-decoration-color: var(--color-accent);
}

/* ── Footnotes ── */
.article-content .footnote-ref {
  color: var(--color-accent);
  text-decoration: none;
}
.article-content .footnote-ref:hover { text-decoration: underline; }

.article-content .footnotes {
  margin-top: var(--space-16);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}
.article-content .footnotes ol { margin-bottom: 0; }
.article-content .footnote-backref {
  margin-left: var(--space-1);
  color: var(--color-text-muted);
  text-decoration: none;
}
.article-content .footnote-backref:hover { color: var(--color-accent); }

/* Anchor targets (heading anchors, footnotes) land with a little headroom
   instead of flush against the viewport edge, and briefly highlight so a
   jump — in either direction — is visually easy to follow. */
.article-content [id] {
  scroll-margin-top: var(--space-8);
}
.article-content .footnotes li:target {
  animation: footnoteTargetFade 2s ease-out;
}
@keyframes footnoteTargetFade {
  from { background-color: var(--color-accent-subtle); }
  to   { background-color: transparent; }
}

.pull-quote {
  margin: var(--space-24) 0;
  border-top: 1px solid var(--color-border-subtle);
  border-bottom: 1px solid var(--color-border-subtle);
  padding: var(--space-12) 0;
}
.pull-quote-label {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  margin-bottom: var(--space-6);
  display: block;
}
.pull-quote-text {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-style: italic;
  font-weight: 600;
  color: var(--color-text-heading);
  line-height: var(--leading-snug);
  margin-bottom: var(--space-8);
}
.pull-quote-attribution {
  border-top: 1px solid var(--color-border-strong);
  padding-top: var(--space-4);
  width: 40px;
}
.pull-quote-author {
  font-family: var(--font-body);
  font-weight: 700;
  color: var(--color-text-heading);
  white-space: nowrap;
  font-size: var(--text-base);
}

.article-footer {
  margin-top: var(--space-24);
  padding-top: var(--space-12);
  border-top: 1px solid var(--color-border-subtle);
}

.tags {
  list-style: none;
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-12);
}
.tag {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
  border: 1px solid var(--color-border-subtle);
  padding: 4px var(--space-3);
  border-radius: 2px;
  letter-spacing: var(--tracking-wide);
  transition: color var(--duration-fast), border-color var(--duration-fast);
}
.tag:hover {
  color: var(--color-accent);
  border-color: var(--color-accent);
}
.tag:active {
  color: var(--color-accent);
  border-color: var(--color-accent);
  opacity: 0.7;
}

.post-nav {
  display: flex;
  justify-content: space-between;
  gap: var(--space-8);
  font-family: var(--font-ui);
  font-size: var(--text-sm);
}
.post-nav-label {
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
  display: block;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
}
.post-nav-title {
  color: var(--color-text-primary);
  font-family: var(--font-body);
  font-size: var(--text-md);
  font-weight: 600;
  transition: color var(--duration-fast);
}
.post-nav a:hover .post-nav-title { color: var(--color-accent); }
.post-nav a:active .post-nav-title { color: var(--color-accent); opacity: 0.7; }
.post-nav-next { text-align: right; }

/* ── Tablet & mobile: NOT a scaled-down copy of the desktop rule ─────────
 *
 * The desktop treatment above (absolute-positioned sidebar hung beside a
 * centered article via calc()) is a wide-viewport trick: it only works
 * because there's slack space on both sides of the article to hang the
 * brand mark in. Reusing it at tablet widths caused the sidebar's
 * calc()-based position and the article's own max-width: 100% cap to
 * disagree once the viewport got tight, and the brand mark collided with
 * the title. Tablet and mobile get their own treatment instead:
 *
 *   - Tablet (640–1024px): the brand mark goes back to being a normal
 *     grid column (sticky, not absolutely positioned) and the article
 *     fills the remaining 1fr fluidly instead of being held to the
 *     desktop-tuned --width-article clamp. No calc()-guessing, so no
 *     collision is possible — the grid track math is exact.
 *   - Mobile (<640px): the brand mark is a home-page link, which a phone
 *     reader doesn't need dedicated chrome for — it's dropped entirely
 *     rather than reflowed, giving the article the full width instead of
 *     spending vertical space on a horizontal brand bar.
 * ─────────────────────────────────────────────────────────────────────── */

@media (max-width: 1024px) and (min-width: 640px) {
  .page-layout {
    grid-template-columns: clamp(56px, 8vw, 96px) 1fr;
    gap: var(--space-10);
    padding: var(--space-24) clamp(20px, 4vw, 56px);
  }
  .sidebar {
    position: sticky;
    top: var(--space-24);
    left: auto;
    min-height: 0;
  }
  .article-wrapper {
    width: 100%;
    max-width: none;
    margin: 0;
  }
  .brand-name-vertical { font-size: 1.75rem; }
}

@media (max-width: 639px) {
  .page-layout {
    grid-template-columns: 1fr;
    padding: var(--space-12) clamp(20px, 5vw, 48px);
  }
  .sidebar { display: none; }
  .article-wrapper {
    width: 100%;
    max-width: none;
    margin: 0;
  }

  .article-header { margin-bottom: var(--space-8); }
  .article-title { font-size: clamp(1.5rem, 5vw, 2rem); }

  .article-content h2 { margin-top: var(--space-10); }
  .article-content h3 { margin-top: var(--space-8); }

  .pull-quote-text { font-size: var(--text-2xl); }
  .post-nav { flex-direction: column; gap: var(--space-6); }
  .post-nav-next { text-align: left; }
}

@media print {
  .page-layout { display: block; width: 100%; }
  .article-title { font-size: 24pt; }
  .pull-quote { break-inside: avoid; }
  pre { white-space: pre-wrap; word-wrap: break-word; }
  .article-content a[href]::after { content: " (" attr(href) ")"; font-size: 0.8em; color: #666; }
}

/* No italic cut for Songti/Noto Serif SC — italic fakes an oblique skew on
 * CJK glyphs. Wavy underline instead. */
.article-content em {
  font-style: normal;
  text-decoration-line: underline;
  text-decoration-style: wavy;
  text-decoration-color: var(--color-text-secondary);
  text-decoration-thickness: 1.25px;
  text-underline-offset: 0.2em;
}

.article-content del {
  text-decoration-line: line-through;
  text-decoration-color: var(--color-border-strong);
  text-decoration-thickness: 1.25px;
  color: var(--color-text-muted);
}

.article-content u {
  text-decoration-style: dotted;
  text-decoration-color: var(--color-text-secondary);
  text-decoration-thickness: 1.4px;
  text-underline-offset: 0.2em;
}

/* Gradient clipped to the lower two-thirds of the glyph, not a full box —
 * reads as a highlighter stroke, not a UI "tag". */
.article-content mark {
  background-color: transparent;
  background-image: linear-gradient(var(--color-accent-subtle), var(--color-accent-subtle));
  background-repeat: no-repeat;
  background-size: 100% 0.6em;
  background-position: 0 82%;
  border-radius: 2px;
  padding: 0 3px;
  color: inherit;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

.article-content sup,
.article-content sub {
  font-size: 0.75em;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

.article-content sup { top: -0.5em; }
.article-content sub { bottom: -0.25em; }

.article-content abbr {
  text-decoration: underline dotted;
  text-decoration-color: var(--color-text-muted);
  cursor: help;
}

.article-content table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--space-6) 0 var(--space-8);
  font-size: var(--text-sm);
  border: 1px solid var(--color-border-subtle);
}

.article-content thead {
  border-bottom: 2px solid var(--color-border-strong);
}

.article-content th,
.article-content td {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-subtle);
  text-align: left;
  vertical-align: top;
}

.article-content th {
  font-family: var(--font-ui);
  font-weight: 600;
  color: var(--color-text-heading);
  background-color: var(--color-bg-subtle);
}

.article-content td {
  color: var(--color-text-primary);
}

.article-content tbody tr:nth-child(even) {
  background-color: var(--color-bg-subtle);
}

.article-content kbd {
  font-family: var(--font-mono);
  font-size: 0.85em;
  background-color: var(--color-bg-subtle);
  border: 1px solid var(--color-border-strong);
  border-radius: 3px;
  padding: 2px 6px;
  box-shadow: 0 1px 0 var(--color-border-strong);
}

.article-content var {
  font-family: var(--font-body);
  font-style: italic;
  color: var(--color-text-secondary);
}

.article-content details {
  margin: var(--space-6) 0;
  padding: var(--space-4);
  background-color: var(--color-bg-subtle);
  border-radius: 4px;
}

.article-content summary {
  font-family: var(--font-ui);
  font-weight: 500;
  color: var(--color-text-heading);
  cursor: pointer;
  user-select: none;
}

.article-content summary:hover {
  color: var(--color-accent);
}

.article-content details[open] summary {
  margin-bottom: var(--space-4);
}

.article-content details > *:not(summary) {
  margin-left: var(--space-2);
}

/* ── Heading anchor links ── */
.article-content h2,
.article-content h3,
.article-content h4 {
  position: relative;
}
.heading-anchor {
  position: absolute;
  left: -1.4em;
  color: var(--color-text-muted);
  font-weight: 400;
  font-size: 0.9em;
  line-height: inherit;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out);
  text-decoration: none;
}
.article-content h2:hover .heading-anchor,
.article-content h3:hover .heading-anchor,
.article-content h4:hover .heading-anchor {
  opacity: 1;
}
.heading-anchor:hover { color: var(--color-accent); }
@media (max-width: 639px) { .heading-anchor { display: none; } }

/* ── Callout blocks ── */
.callout {
  margin: var(--space-8) 0;
  padding: var(--space-5) var(--space-6);
  border-left: 3px solid var(--color-border-strong);
  background-color: var(--color-bg-subtle);
}
.callout-note  { border-left-color: var(--color-border-strong); }
.callout-tip   { border-left-color: var(--color-text-muted); }
.callout-warning { border-left-color: var(--color-accent); background-color: var(--color-accent-subtle); }
.callout-content > *:first-child { margin-top: 0; }
.callout-content > *:last-child  { margin-bottom: 0; }

/* ── Responsive images ── */
/* 8px, not 2px — soft corner without a "card" look. */
.article-content img {
  max-width: 100%;
  height: auto;
  display: block;
  border-radius: 8px;
}
.article-figure {
  margin: var(--space-8) 0;
}
.article-figure img { width: 100%; }
.article-figure figcaption {
  margin-top: var(--space-3);
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-align: center;
}
