/* -- Typography component --------------------------------------- */

/* -- Base text ---------------------------------------------- */
/* Sets the default font size for the entire page.            */
/* All typography classes are sized relative to this baseline. */
@layer base {
  body {
    font-size: 0.875rem;
    line-height: 1.5;
  }
}

@layer components {
  /* -- Headings ---------------------------------------------- */
  .h1 {
    scroll-margin-top: 5rem;
    font-size: 2.25rem;
    font-weight: 800;
    letter-spacing: -0.025em;
    line-height: 1.15;
    text-wrap: balance;
    color: var(--foreground);
  }

  .h2 {
    scroll-margin-top: 5rem;
    font-size: 1.875rem;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.25;
    text-wrap: balance;
    color: var(--foreground);
    border-block-end: 1px solid var(--border);
    padding-block-end: 0.5rem;

    &:first-child { margin-block-start: 0; }
  }

  .h3 {
    scroll-margin-top: 5rem;
    font-size: 1.5rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.3;
    text-wrap: balance;
    color: var(--foreground);
  }

  .h4 {
    scroll-margin-top: 5rem;
    font-size: 1.25rem;
    font-weight: 600;
    line-height: 1.4;
    text-wrap: balance;
    color: var(--foreground);
  }

  /* -- Paragraph --------------------------------------------- */
  .p {
    font-size: 0.875rem;
    line-height: 1.75;
    text-wrap: pretty;
    color: var(--foreground);

    &:not(:first-child) { margin-block-start: 1.5rem; }
  }

  /* -- Lead --------------------------------------------------- */
  .lead {
    font-size: 1.25rem;
    line-height: 1.6;
    color: var(--muted-foreground);
    text-wrap: pretty;
  }

  /* -- Large -------------------------------------------------- */
  .large {
    font-size: 1.125rem;
    font-weight: 600;
    color: var(--foreground);
  }

  /* -- Small -------------------------------------------------- */
  .small {
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1;
    color: var(--foreground);
  }

  /* -- Muted -------------------------------------------------- */
  .muted {
    font-size: 0.875rem;
    color: var(--muted-foreground);
  }

  /* -- Blockquote --------------------------------------------- */
  .blockquote {
    margin-block-start: 1.5rem;
    border-inline-start: 2px solid var(--border);
    padding-inline-start: 1.5rem;
    font-style: italic;
    hanging-punctuation: first last;
    color: var(--foreground);

    & p { margin: 0; }
  }

  /* -- Inline code -------------------------------------------- */
  .inline-code {
    position: relative;
    border-radius: var(--radius-sm);
    background: var(--muted);
    padding: 0.2rem 0.3rem;
    font-family: var(--font-mono);
    font-size: 0.875em;
    font-weight: 600;
    color: var(--foreground);
  }

  /* -- CJK: Chinese, Japanese, Korean ------------------------------
     Keyed on the lang attribute, so one set of classes serves every script.
     Latin tuning is undone (negative tracking, faux italics); paragraphs
     justify between characters with strict line breaking (kinsoku: no 。
     or ， at a line start), and sentence-end punctuation may hang past the
     edge (Safari: hanging-punctuation) or trims to half width
     (Chromium: text-spacing-trim) so the justified edge stays straight.
     text-autospace adds the thin gap between CJK and Latin /
     digits that authors used to type by hand. */
  :is(.h1, .h2, .h3, .h4, .lead, .large):is(:lang(zh), :lang(ja), :lang(ko)),
  :is(:lang(zh), :lang(ja), :lang(ko)) :is(.h1, .h2, .h3, .h4, .lead, .large) {
    letter-spacing: 0;
  }
  /* Japanese headings break between phrases (bunsetsu), not mid-word */
  :is(.h1, .h2, .h3, .h4, .lead):lang(ja),
  :lang(ja) :is(.h1, .h2, .h3, .h4, .lead) {
    word-break: auto-phrase;
  }
  :is(.p, .lead, .blockquote):is(:lang(zh), :lang(ja), :lang(ko)),
  :is(:lang(zh), :lang(ja), :lang(ko)) :is(.p, .lead, .blockquote) {
    line-height: 1.9;
    line-break: strict;
    text-autospace: normal;
  }
  :is(.p, .blockquote):is(:lang(zh), :lang(ja)),
  :is(:lang(zh), :lang(ja)) :is(.p, .blockquote) {
    text-align: justify;
    text-justify: inter-character;
    /* the line-end 。，: Safari hangs it past the edge; Chromium trims the
       fullwidth glyph's blank half (text-spacing-trim) - either way the
       justified edge stays straight */
    hanging-punctuation: allow-end;
    text-spacing-trim: normal;
  }
  /* CJK has no italics - a slanted Han character is a synthetic oblique */
  .blockquote:is(:lang(zh), :lang(ja), :lang(ko)),
  :is(:lang(zh), :lang(ja), :lang(ko)) .blockquote {
    font-style: normal;
  }

  /* -- Vertical text (tategaki / 直排) ----------------------------------
     .text-vertical sets top-to-bottom lines flowing right to left. Latin
     runs lie sideways (text-orientation: mixed); .text-upright stands a
     short run (a two-digit number) upright in one cell - tate-chu-yoko.
     The spacing above uses logical properties, so headings, paragraphs and
     the blockquote rule turn with the text. Give the block a height. */
  .text-vertical {
    writing-mode: vertical-rl;
    text-orientation: mixed;
  }
  .text-upright {
    text-combine-upright: all;
  }

  /* -- Emphasis marks (圈点 / 傍点) ---------------------------------------
     Dots beside each character instead of italics: over the text in
     Japanese (right of it when vertical), under it in Chinese. */
  .text-emphasis {
    font-style: normal;
    text-emphasis: filled dot;
    text-emphasis-position: over right;
  }
  .text-emphasis:lang(ja) { text-emphasis-style: filled sesame; }
  .text-emphasis:lang(zh) { text-emphasis-position: under right; }

  /* ruby annotations (furigana, pinyin, zhuyin) read as small, muted type */
  :is(.p, .lead, .blockquote, .h1, .h2, .h3, .h4) rt {
    font-size: 0.5em;
    color: var(--muted-foreground);
    letter-spacing: 0;
  }

  /* -- Typesetting (Satz): book and journal setting ----------------------
     .typeset holds continuous text set like a book or a journal page. Each
     classic rule is an opt-in data attribute, tuned by a --typeset-* knob -
     component properties, not theme tokens (the theme set stays the
     tweakcn shape):
       data-columns="2|3"        columns never narrower than
                                 --typeset-column-width: a phone gets one
       data-rule                 a hairline between the columns
       data-align="justify"      Blocksatz: justified and hyphenated (set
                                 lang=""), the last line flush start
       data-indent               Einzug: paragraphs follow without a gap and
                                 indent their first line; the first one after
                                 a heading starts flush (stumpf)
       data-initial="drop|raised"  Initiale over --typeset-initial-lines lines
       data-lead="smallcaps|caps"  the opening line in small capitals or in
                                 letterspaced capitals (Versalien)
       data-ligatures="classic"  discretionary and historical ligatures,
                                 old-style figures - where the font has them
       data-leading="tight|loose"  compact or airy lines, the tracking matched
     .typeset-span spans every column (a heading, a figure). Figures, tables,
     quotes, groups and Separator breaks keep --typeset-gap from the text -
     the Separator component draws the break, this block only spaces it.
     VERIFIED: (typography.e2e, the typeset checks) every rule as a computed
     style, the gaps, the captions' counters, 44rem two columns vs 20rem one. */
  .typeset {
    --typeset-measure: 68ch;
    --typeset-column-width: 18rem;
    --typeset-column-gap: 2.5em;
    --typeset-indent: 1em;
    --typeset-leading: 1.65;
    --typeset-tracking: 0em;
    --typeset-gap: 1.5em;
    --typeset-initial-lines: 3;
    --typeset-initial-gap: 0.3em;
    --typeset-initial-font: var(--font-serif);
    --typeset-initial-color: var(--primary);

    max-inline-size: var(--typeset-measure);
    line-height: var(--typeset-leading);
    letter-spacing: var(--typeset-tracking);
    text-wrap: pretty;
    hanging-punctuation: first allow-end last;
    font-kerning: normal;
    orphans: 2;
    widows: 2;

    & > p { margin-block: 0 1em; }
    /* every block keeps the same distance from the text around it */
    & > :is(figure, table, blockquote, pre, hr, .separator, .typeset-group, .chart, .diagram, .mockup-code, .table-container) {
      margin-block: var(--typeset-gap);
    }
    /* a figure spans the measure (the browser indents it 40px by default) */
    & > :is(figure, blockquote) { margin-inline: 0; }
    & > :is(h1, h2, h3, h4, .h1, .h2, .h3, .h4) { margin-block: var(--typeset-gap) 0.5em; }
    & > :first-child { margin-block-start: 0; }
    & :is(figure, blockquote, table, pre, .typeset-group) { break-inside: avoid; }
    & :is(h1, h2, h3, h4, .h1, .h2, .h3, .h4) { break-after: avoid; }
    & .typeset-span { column-span: all; }
    /* the columns below a spanning heading or figure start level: the first
       block keeps no top margin - across the column break it cannot collapse
       into the spanner's, so a subheading sat lower than the text beside it */
    &[data-columns] > .typeset-span + :not(.typeset-span) { margin-block-start: 0; }

    &[data-columns] { max-inline-size: none; column-gap: var(--typeset-column-gap); }
    &[data-columns="2"] { columns: 2 var(--typeset-column-width); }
    &[data-columns="3"] { columns: 3 var(--typeset-column-width); }
    &[data-rule] { column-rule: 1px solid var(--border); }

    &[data-align="justify"] {
      text-align: justify;
      text-align-last: start;
      hyphens: auto;
      hyphenate-limit-chars: 6 3 2;

      /* text-align-last inherits: a centered caption inside a justified setting
         kept its last line flush left (a two-line figure caption looked
         off-center) - what centers itself takes its last line from its own alignment */
      & :where(figcaption, caption, .typeset-caption) { text-align-last: auto; }
    }

    /* leading and tracking move together: tight lines close up a little,
       airy lines open up a little, so the grey of the column stays even */
    &[data-leading="tight"] { --typeset-leading: 1.45; --typeset-tracking: -0.01em; }
    &[data-leading="loose"] { --typeset-leading: 1.9; --typeset-tracking: 0.01em; }

    &[data-indent] > p { margin-block: 0; }
    &[data-indent] > p + p { text-indent: var(--typeset-indent); }
    /* text-indent inherits into an inline-block, which then indents ITS first line: a badge,
       a key cap or an epistemic tag at the start of an indented paragraph grew a blank
       inside its left edge */
    &[data-indent] > p :where(.badge, kbd, .paper-tag) { text-indent: 0; }

    /* the gap is a margin: Chromium ignores logical padding on ::first-letter
       (VERIFIED: probe 2026-10-07 - padding-inline-end computed 0, margin-inline-end 2.52px) */
    &[data-initial] > p:first-of-type::first-letter {
      margin-inline-end: var(--typeset-initial-gap);
      font-family: var(--typeset-initial-font);
      font-weight: 600;
      color: var(--typeset-initial-color);
    }
    &[data-initial="drop"] > p:first-of-type::first-letter {
      -webkit-initial-letter: var(--typeset-initial-lines);
      initial-letter: var(--typeset-initial-lines);
    }
    &[data-initial="raised"] > p:first-of-type::first-letter {
      -webkit-initial-letter: var(--typeset-initial-lines) 1;
      initial-letter: var(--typeset-initial-lines) 1;
    }

    &[data-lead="smallcaps"] > p:first-of-type::first-line {
      font-variant-caps: all-small-caps;
      letter-spacing: 0.04em;
    }
    /* letterspaced capitals - and the small-caps lead beside an initial:
       a first line in small capitals moves the initial down a line
       (Chromium), capitals keep its top level with the line's */
    &:is([data-lead="caps"], [data-initial][data-lead="smallcaps"]) > p:first-of-type::first-line {
      font-variant-caps: normal;
      text-transform: uppercase;
      letter-spacing: 0.06em;
    }

    &[data-ligatures="classic"] {
      font-variant-ligatures: common-ligatures discretionary-ligatures historical-ligatures contextual;
      font-variant-numeric: oldstyle-nums proportional-nums;
    }
  }
  /* a heading is never justified: a wrapping heading stretched to the measure
     opened wide gaps between its words. Flush start and unhyphenated - zero
     specificity, so an alignment the heading sets itself still wins */
  :where(.typeset[data-align="justify"]) :where(h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4) {
    text-align: start;
    text-align-last: auto;
    hyphens: manual;
  }
  /* no initial-letter (Firefox): a floated letter as tall as the lines */
  @supports not ((initial-letter: 3) or (-webkit-initial-letter: 3)) {
    .typeset[data-initial] > p:first-of-type::first-letter {
      float: inline-start;
      font-size: calc(var(--typeset-initial-lines) * var(--typeset-leading) * 1em * 0.82);
      line-height: 0.8;
      padding-block-start: 0.08em;
    }
  }

  /* -- Captions (Bildunterschriften), numbered in order ------------------
     .typeset-caption on a figcaption (a picture, a video, a chart) or a
     table's caption. Inside .typeset-numbered, figures and tables count on
     their own: "Figure 1.", "Table 1." - the words are --typeset-figure-label
     and --typeset-table-label ("" leaves the bare number: "1."). A caption
     is flush start; data-align="justify" or "center" sets it otherwise. */
  .typeset-numbered {
    --typeset-figure-label: "Figure ";
    --typeset-table-label: "Table ";

    counter-reset: typeset-figure typeset-table;
  }
  .typeset-caption {
    margin-block-start: 0.6em;
    color: var(--muted-foreground);
    font-size: 0.875em;
    line-height: 1.5;
    text-align: start;
    text-align-last: auto;
    text-wrap: pretty;
    caption-side: top;

    &[data-align="justify"] { text-align: justify; text-align-last: start; hyphens: auto; }
    &[data-align="center"] { text-align: center; text-wrap: balance; }
    table > & { margin-block: 0 0.6em; }
  }
  .typeset-numbered :is(figcaption.typeset-caption)::before {
    counter-increment: typeset-figure;
    content: var(--typeset-figure-label) counter(typeset-figure) ". ";
    color: var(--foreground);
    font-weight: 600;
  }
  .typeset-numbered :is(caption.typeset-caption, figure:has(> table) > figcaption.typeset-caption, figure:has(> .table-container) > figcaption.typeset-caption)::before {
    counter-increment: typeset-table;
    content: var(--typeset-table-label) counter(typeset-table) ". ";
  }

  /* -- Figure groups: several figures in one box ---------------------------
     .typeset-group sets two or three figures (charts, pictures, tables) side
     by side; each figure's caption is lettered "(a)", "(b)" (.typeset-subcaption),
     the group's own figcaption numbers the whole. Every figure spans two
     subgrid rows - media, then caption - so the captions of a row sit at one
     height whatever the media's height. data-align="middle" centres the media
     in their row; data-divided draws hairlines between the figures - the
     1px grid gap over a border-coloured background, so the lines run between
     columns and, once the figures stack, between rows, with no container
     query (a size container would scope the caption counters to the group);
     data-variant="cards" gives each figure its own card instead of one box.
     Narrow pages stack them.
     VERIFIED: (typography.e2e "figure groups") captions level, the 1px lines
     between columns and between stacked rows, the cards' borders. */
  .typeset-group {
    --typeset-group-min: 12rem;
    --typeset-group-gap: 1.25rem;

    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--typeset-group-min)), 1fr));
    gap: var(--typeset-group-gap);
    margin-inline: 0;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--card);
    counter-reset: typeset-sub;

    & > figure {
      display: grid;
      grid-row: span 2;
      grid-template-rows: subgrid;
      row-gap: 0.4em;
      align-content: start;
      margin: 0;
      min-inline-size: 0;
    }
    & > figcaption { grid-column: 1 / -1; margin-block-start: 0.25em; }
    & img, & video { display: block; inline-size: 100%; block-size: auto; border-radius: var(--radius-md); }

    &[data-align="middle"] > figure > :first-child { align-self: center; }

    &[data-variant="cards"] {
      padding: 0;
      border: 0;
      background: none;

      & > figure {
        padding: 0.875rem;
        border: 1px solid var(--border);
        border-radius: var(--radius-lg);
        background: var(--card);
      }
    }

    &[data-divided] {
      gap: 1px;
      padding: 0;
      overflow: hidden;
      background: var(--border);

      & > figure { padding: 1rem; background: var(--card); }
      & > figcaption { margin: 0; padding: 0.75rem 1rem 1rem; background: var(--card); }
    }
  }
  .typeset-subcaption {
    margin: 0;
    color: var(--muted-foreground);
    font-size: 0.8125em;
    line-height: 1.45;

    &::before {
      counter-increment: typeset-sub;
      content: "(" counter(typeset-sub, lower-alpha) ") ";
      color: var(--foreground);
      font-weight: 600;
    }
  }

  /* -- Accessibility ------------------------------------------ */

  @media (prefers-contrast: more) {
    .h1, .h2, .h3, .h4 {
      letter-spacing: 0;
    }

    .typeset {
      --typeset-initial-color: var(--foreground);
    }

    .blockquote {
      border-inline-start-width: 3px;
    }

    .inline-code {
      outline: 1px solid var(--border);
    }

    .muted {
      color: var(--foreground);
    }
  }

  @media (forced-colors: active) {
    .blockquote {
      border-inline-start-color: CanvasText;
    }

    .typeset {
      --typeset-initial-color: CanvasText;
    }

    .typeset[data-rule] {
      column-rule-color: CanvasText;
    }

    .inline-code {
      background: Canvas;
      outline: 1px solid CanvasText;
      color: CanvasText;
    }
  }
}
