/* Galahad Journal — long-form article styling.
   Pure static CSS (no Tailwind, no JS). Consumes the shared design tokens from
   tokens.css and adds an editorial typographic system: Space Grotesk headings
   (the site's display face), Geist for everything else, the stone palette.
   Loaded only by the generated /journal/ pages. The Journal stays light on
   purpose, so it keeps the palette it has; only the display stack is shared.
   That stack is written out literally here, six times, because theme-dark.css
   never loads on these pages and --font-display would resolve to nothing. Keep
   it identical to theme-dark.css's --font-display and to tailwind.config.js's
   `display` family: one face across both surfaces, three places to edit.

   TWO FACES ON THESE PAGES, NOT THREE. Geist Mono used to dress eleven things:
   the eyebrow, the byline row, the tags, figure captions, table headers, the
   code-fence badge, the index entry meta, the footer copyright and the glass
   chips. A monospace face means "this is code" and none of those are, so the
   Journal read as a terminal log. Mono is now reserved for `<code>` and inside
   `<pre>`, where the fixed advance carries meaning. Every other label is Geist,
   separated from body copy by SIZE, WEIGHT, TRACKING and INK TIER, never by a
   typeface switch. Most of them simply inherit the body face and declare no
   family at all; the two that cannot are the chip (a `font:` shorthand needs a
   family) and the fence badge (it sits inside <pre>, so it inherits the UA's
   monospace unless told otherwise). */

@import "./tokens.css";

/* ---------- base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body { margin: 0; min-height: 100vh; display: flex; flex-direction: column; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }
::selection { background: rgba(15,15,15,0.10); }

/* Film grain — the site's signature texture, kept for continuity */
body::after {
  content: '';
  position: fixed; inset: 0;
  pointer-events: none;
  z-index: 200;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.9 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  background-size: 220px 220px;
}

/* The PNG is white on transparent. Inverted to black on the light page,
   left alone inside anything marked dark, which is index.html's own pair. */
.brand-mark { filter: invert(1); }
.dark .brand-mark { filter: none; }

/* The fallback ring, for anything no component claims. The two components that
   draw their own are excluded. .cta-glass is not optional: :where() contributes
   nothing, so this selector is (0,1,0), exactly .cta-glass's own border-radius,
   and tokens.css loads first, so left in it squared every glass control off the
   moment it took focus. .nav-link would simply have been ringed twice. */
:where(a:not(.cta-glass, .nav-link)):focus-visible,
:where(button:not(.cta-glass, .nav-link)):focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--fg);
  border-radius: 2px;
}

/* ---------- the glass chip ----------
   The Journal's controls are the SPA's control: same primitive from
   tokens.css, same 44px hit box, same 18px shell. Only the scale changes: the
   same Geist the header CTA uses, three points down and a shade quieter, so a
   back link reads as the smaller sibling of Book a call rather than as a
   different species. It was 11px uppercase mono at 0.13em, which is what made
   "Back to the Journal" read as a log line. Sentence case at 13px is a button
   label. Ink is the muted tier at rest and full ink on hover, a LIGHTNESS
   move: hue never signals clickability here. Colour joins the primitive's
   three transitioned properties by list, so the durations and the curve are
   not restated. */
.cta-glass--chip {
  --glass-px: 18px;
  --glass-font: 500 13px/1 var(--font-sans, 'Geist', ui-sans-serif, system-ui, -apple-system, sans-serif);
  --glass-track: -0.005em;
  --glass-ink: var(--fg-muted);
  gap: 9px;
  transition-property: background, box-shadow, transform, color;
}
.cta-glass--chip:hover { --glass-ink: var(--fg); }

/* ---------- site header ----------
   The SPA's bar, not a lookalike: same 80px height on the same 1280px
   measure, same 24/40px gutters, same 44px mark, the same four section links at
   28px, and the same pairing of the EN / FR switch with the Journal glass
   control. The markup carries `dark`, so the whole subtree takes
   tokens.css's dark ladder (--glass goes to rgba(0,0,0,0.72), the hairline to
   white-on-black, the mark stops inverting) and .cta-glass inside it takes the
   dark glass tuning, which is the landing page's control down to the number.
   A dark bar over a white article body is the resolution of the two things
   asked for at once: the header the same as the site, the page full white. */
.site-header {
  position: sticky; top: 0; z-index: 40;
  /* 0.86, not the 0.72 the dark token carries, and the reason is the backdrop.
     On the SPA that veil sits over a #101012 floor and renders near-black; over
     a white article body it renders #474747, and the nav ink (#a3a3a3) measured
     3.68:1 on it, under the 4.5:1 AA asks of 15px text. 0.86 renders #242424,
     which puts the nav at 6.15:1 and reads as the same bar the landing page
     has. It still transmits: 14% of whatever scrolls under it comes through. */
  --glass: rgba(0,0,0,0.86);
  background: var(--glass);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  /* The glass CTA casts 40px of shadow at -12px spread. On the SPA that lands
     on a dark page and is invisible; here it hung below the bar as a smudge on
     white. A surface does not cast onto the page it floats over, so it clips. */
  overflow: hidden;
  /* Safari drops a compositing frame on a scrolling backdrop-filter unless it
     owns its layer. index.html's .glass carries the same pair. */
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
  border-bottom: 1px solid var(--border-subtle);
}
.site-header__inner {
  /* 1280 and 24/40, matching the SPA's max-w-[1280px] with px-6 sm:px-10. It ran
     1180/24 here, which put the Journal brand 50px inboard of the landing page's
     on the same viewport, visible the moment you navigate between them. */
  max-width: 1280px; margin-inline: auto;
  height: 80px; padding-inline: 24px;
  display: flex; align-items: center; justify-content: space-between;
}
.site-header__brand { display: inline-flex; align-items: center; }
.site-header__brand img { height: 44px; width: auto; }
.site-header__nav { display: flex; align-items: center; gap: 28px; }
.site-header__links { display: none; align-items: center; gap: 28px; }
/* The glass control and the language switch, at 12px rather than the row's 28px,
   so the switch reads as a setting attached to the control. Same grouping as the
   SPA's .cta-header-desktop. */
.site-header__control { display: flex; align-items: center; gap: 12px; }

/* .nav-link, mirroring theme-dark.css value for value. It cannot be shared
   the way .cta-glass now is: that took promoting the whole component into
   tokens.css, and theme-dark.css is outside this task's remit beyond what the
   glass move required. If these two ever drift, promote this one the same way
   rather than patching both. Tokens are written with fallbacks because
   theme-dark.css never loads here: --ink-muted #a3a3a3 and --ink #fafafa are
   its values, and they are what the landing page's nav actually paints. */
.nav-link {
  position: relative; display: inline-flex; align-items: center;
  /* 44px, where theme-dark.css says 40. The only deliberate divergence, and
     it moves nothing: the bar is 80px and both boxes centre their text in it,
     so the label sits at the same y and the underline is pulled back by the
     same 4px below. What changes is the pointer target, which reaches 44 and
     clears the bar every other control on these pages already clears. */
  min-height: 44px; padding: 0 2px;
  font: 500 var(--fs-control, 15px)/1 var(--font-sans, 'Geist', ui-sans-serif, system-ui, -apple-system, sans-serif);
  color: var(--ink-muted, #a3a3a3); background: none; border: 0;
  cursor: pointer; user-select: none; text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-state, 150ms) var(--ease, cubic-bezier(0.16,1,0.3,1));
}
.nav-link::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 10px; height: 1px;
  background: currentColor; transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-state, 150ms) var(--ease, cubic-bezier(0.16,1,0.3,1));
}
.nav-link:hover, .nav-link[aria-current="page"] { color: var(--ink, #fafafa); }
.nav-link:hover::after, .nav-link[aria-current="page"]::after { transform: scaleX(1); }
.nav-link:active { color: var(--ink-muted, #a3a3a3); }
.nav-link:focus-visible {
  outline: var(--focus-w, 2px) solid var(--focus-color, var(--ink, #fafafa));
  outline-offset: var(--focus-off, 2px);
  border-radius: var(--r-sm, 5px);
}
/* The header CTA was a 32px black rectangle, a different control from the one
   the landing page ships. It is .cta-glass now, straight from tokens.css, at
   the primitive's own size: nothing is declared here. */


@media (min-width: 640px) {
  /* sm: on the SPA, where px-6 becomes px-10. */
  .site-header__inner { padding-inline: 40px; }
}
@media (min-width: 768px) {
  .site-header__links { display: flex; }
}

/* ---------- page shell ---------- */
.article-main { flex: 1 0 auto; }
.wrap {
  max-width: 680px;
  margin-inline: auto;
  padding-inline: 24px;
}

/* ---------- back control ----------
   Rendered at the top of the reading column on every generated page, and
   again under the article, where it is the only control on screen and has to
   read as clickable at rest rather than on hover. It IS .cta-glass now; the
   only thing left here is the arrow and its travel. */
.back-link__arrow {
  font-size: 13px; line-height: 1;
  transition: transform .25s cubic-bezier(0.16,1,0.3,1);
}
.back-link:hover .back-link__arrow { transform: translateX(-3px); }

/* ---------- article header ---------- */
.article-header { padding-top: 56px; padding-bottom: 8px; }
.article-header .back-link { margin-bottom: 30px; }
/* Geist, inherited from body: no family declared. Uppercase at 500 with wide
   tracking is what makes an eyebrow an eyebrow; 11px because Geist sets a
   smaller uppercase than Geist Mono did at the same nominal size. */
.eyebrow {
  margin: 0 0 28px;
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--fg-faint);
  display: flex; align-items: center; gap: 10px;
}

/* The byline is now the only thing in the eyebrow row (the visible date is
   gone), and it stays one ink tier above the row it sits in: the author is
   content, the row is metadata. Existing tokens, no new tier. */
.byline { color: var(--fg-muted); }

.article-header h1 {
  margin: 0;
  font-family: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  font-optical-sizing: auto;
  font-weight: 500;
  font-size: clamp(2.1rem, 5.5vw, 3rem);
  line-height: 1.07;
  letter-spacing: -0.02em;
  color: var(--fg);
}
.dek {
  margin: 22px 0 0;
  font-size: 1.28rem;
  line-height: 1.5;
  color: var(--fg-muted);
  font-weight: 400;
}
.article-meta {
  margin-top: 30px;
  display: flex; align-items: center; flex-wrap: wrap; gap: 16px;
  justify-content: space-between;
}
.tags { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.tags li {
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--fg-muted);
  padding: 3px 10px;
  border: 1px solid var(--border-subtle);
  border-radius: 9999px;
}

/* ---------- language toggle (rendered only when a translation is linked) ----
   Was a segmented pill whose selected half was `color: var(--bg); background:
   var(--fg)`, pure black on white, which is the flat treatment the founder
   called out by name. Two glass chips instead, so each half carries a real
   44px hit box and the same material as every other control.

   Selected is not a fill and not a hue: the chip SINKS. Its shell loses the
   top bevel and the lift, gains an inner shade, and its label goes to full
   ink while the sibling stays muted. Elevation and lightness, both readable
   without colour. */
.lang-toggle { display: inline-flex; align-items: center; gap: 6px; }
.lang-toggle .is-active {
  cursor: default;
  --glass-ink: var(--fg);
  background: var(--glass-tint-active);
  box-shadow:
    inset 0 1px 2px 0 rgba(15,15,15,0.16),
    inset 0 0 0 1px rgba(255,255,255,0.35),
    0 0 0 1px rgba(15,15,15,0.32);
}
/* It is a <span> and not a control, so it must not answer a press. The shell
   and the ink above already outrank the primitive's hover by source order. */
.lang-toggle .is-active:active { transform: none; }

.cover {
  margin: 44px 0 0;
}
.cover img {
  width: 100%;
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
}
.cover figcaption { margin-top: 12px; }

/* ---------- article body — vertical rhythm ---------- */
.article-body {
  margin-top: 52px;
  padding-bottom: 24px;
  font-size: 1.1875rem;   /* ~19px */
  line-height: 1.72;
  color: var(--fg);
}
.article-body > * { margin-top: 0; margin-bottom: 0; }
.article-body > * + * { margin-top: 1.45em; }

.article-body p { text-wrap: pretty; }

/* headings */
.article-body h2, .article-body h3, .article-body h4 {
  font-family: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  font-optical-sizing: auto;
  color: var(--fg);
  letter-spacing: -0.015em;
  scroll-margin-top: 96px;
}
.article-body h2 {
  font-weight: 500; font-size: 1.7rem; line-height: 1.2;
  margin-top: 2.5em;
}
.article-body h3 {
  font-weight: 500; font-size: 1.32rem; line-height: 1.25;
  margin-top: 2em;
}
.article-body h4 {
  font-weight: 500; font-size: 1.08rem; line-height: 1.3;
  margin-top: 1.8em;
}
.article-body h2 + *, .article-body h3 + *, .article-body h4 + * { margin-top: 0.9em; }

/* links */
.article-body a {
  color: var(--fg);
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: text-decoration-color .15s ease;
}
.article-body a:hover { text-decoration-color: var(--fg); }

/* lists */
.article-body ul, .article-body ol { padding-left: 1.35em; }
.article-body li + li { margin-top: 0.4em; }
.article-body li::marker { color: var(--fg-faint); }
.article-body ul { list-style: none; padding-left: 1.4em; }
.article-body ul > li { position: relative; }
.article-body ul > li::before {
  content: ''; position: absolute; left: -1.15em; top: 0.72em;
  width: 5px; height: 1px; background: var(--fg-faint);
}

/* strong / em */
.article-body strong { font-weight: 600; }
.article-body em { font-style: italic; }

/* blockquote */
.article-body blockquote {
  border-left: 2px solid var(--border-strong);
  padding-left: 1.25em;
  color: var(--fg-muted);
  font-size: 1.05em;
}
.article-body blockquote p { margin: 0; }
.article-body blockquote p + p { margin-top: 0.8em; }

/* ---------- enhanced blocks ---------- */

/* ::: lead — in-body standfirst / intro */
.article-body .lead {
  font-size: 1.32rem;
  line-height: 1.5;
  color: var(--fg-muted);
}
.article-body .lead p { margin: 0; }
.article-body .lead p + p { margin-top: 0.6em; }

/* ::: callout — aside / info box */
.article-body .callout {
  background: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-left: 2px solid var(--fg-faint);
  border-radius: 6px;
  padding: 20px 22px;
  font-size: 1.0125rem;
  line-height: 1.62;
  color: var(--fg-muted);
}
.article-body .callout > :first-child { margin-top: 0; }
.article-body .callout > * + * { margin-top: 0.8em; }
.article-body .callout strong { color: var(--fg); }

/* ::: pullquote — display-size pulled quotation */
.article-body .pullquote {
  margin-top: 2.6em; margin-bottom: 2.6em;
  padding-top: 1.1em;
  border-top: 1px solid var(--border-default);
  font-family: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: 1.6rem;
  line-height: 1.32;
  letter-spacing: -0.01em;
  color: var(--fg);
}
.article-body .pullquote p { margin: 0; }

/* ---------- figures ---------- */
.article-body figure { margin-top: 2.2em; margin-bottom: 2.2em; }
.article-body figure img {
  width: 100%;
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
}
/* A caption is prose, not a label: it is read as a sentence. It was 10.5px
   uppercase tracked mono, which is a stamp. Geist at 13px, sentence case, sits
   under the figure as the smallest readable tier of the article's own voice. */
.article-body figcaption,
.cover figcaption {
  margin-top: 12px;
  font-size: 13px; line-height: 1.5;
  color: var(--fg-faint);
  text-align: center;
}

/* ---------- code ---------- */
.article-body :not(pre) > code {
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 0.86em;
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: 4px;
  padding: 0.12em 0.4em;
}
.article-body pre {
  position: relative;
  background: #0d0d0c;
  color: #e7e5e4;
  border: 1px solid var(--border-default);
  border-radius: 8px;
  padding: 20px 22px;
  overflow-x: auto;
  font-size: 0.85rem;
  line-height: 1.65;
}
.article-body pre code {
  font-family: 'Geist Mono', ui-monospace, monospace;
  background: none; border: 0; padding: 0; color: inherit;
}
/* The fence badge names the language, it is not code, so it is the only label
   on these pages that has to declare the sans family explicitly: it inherits
   from <pre>, whose UA rule is monospace. */
.article-body pre[data-lang]::before {
  content: attr(data-lang);
  position: absolute; top: 10px; right: 14px;
  font-family: var(--font-sans, 'Geist', ui-sans-serif, system-ui, -apple-system, sans-serif);
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: rgba(231,229,228,0.42);
}

/* ---------- tables ---------- */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.article-body table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}
.article-body th {
  text-align: left;
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--fg-muted);
  font-weight: 500;
  padding: 8px 14px;
  border-bottom: 1px solid var(--border-default);
  white-space: nowrap;
}
.article-body td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: top;
}
.article-body tbody tr:last-child td { border-bottom: 0; }

/* ---------- horizontal rule → asterism ---------- */
.article-body hr {
  border: 0;
  margin: 3em 0;
  text-align: center;
}
.article-body hr::before {
  content: '* * *';
  font-family: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  letter-spacing: 0.5em;
  color: var(--fg-faint);
}

/* ---------- footnotes ---------- */
.article-body .footnote-ref a {
  text-decoration: none;
  font-size: 0.8em;
  color: var(--fg-muted);
  padding: 0 1px;
}
.article-body .footnotes {
  margin-top: 3.5em;
  border-top: 1px solid var(--border-subtle);
  padding-top: 1.6em;
  font-size: 0.95rem;
  color: var(--fg-muted);
}
.article-body .footnotes-sep { display: none; }
.article-body .footnotes ol { padding-left: 1.2em; }
.article-body .footnotes li { margin-top: 0.5em; }
.article-body .footnote-backref { text-decoration: none; margin-left: 4px; }

/* ---------- article footer nav ----------
   The control the founder found at the bottom of a long article and could not
   tell was a link. It was bare mono text whose only state was a hover, and
   nothing else within a screen of it to signal affordance. It is the same
   glass chip as the one at the top of the page now, arrow included. */
.article-foot {
  margin-top: 64px;
  padding-top: 28px;
  border-top: 1px solid var(--border-subtle);
}

/* ---------- journal index ---------- */
.index-head { padding-top: 64px; padding-bottom: 8px; }
.index-head .back-link { margin-bottom: 34px; }
.index-head h1 {
  margin: 18px 0 0;
  font-family: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  font-optical-sizing: auto;
  font-weight: 500;
  font-size: clamp(2.4rem, 6vw, 3.4rem);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--fg);
}
.index-head .dek { max-width: 560px; }

.index-list { margin: 56px 0 0; list-style: none; padding: 0; }
.index-list > li { border-top: 1px solid var(--border-subtle); }
.index-list > li:last-child { border-bottom: 1px solid var(--border-subtle); }
.entry {
  display: block; text-decoration: none; color: inherit;
  padding: 30px 4px;
  /* same curve and duration as the title underline: one gesture, not two */
  transition: padding-left .45s cubic-bezier(.2,.8,.2,1);
}
.entry:hover { padding-left: 12px; }
/* .entry__meta is gone with the visible date it existed to carry. The title is
   the entry's first line now, so it takes no top margin: the 30px on .entry is
   the whole gap. */
.entry__title {
  margin: 0;
  font-family: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  font-optical-sizing: auto;
  font-weight: 500; font-size: 1.6rem; line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--fg);
  /* fit-content so the ::after bar tracks the text, not the whole column */
  width: fit-content;
  position: relative;
}
/* Underline wipes in from the left, matching the SPA's link treatment */
.entry__title::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: -4px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .45s cubic-bezier(.2,.8,.2,1);
}
.entry:hover .entry__title::after { transform: scaleX(1); }
.entry__desc {
  margin: 10px 0 0;
  font-size: 1.05rem; line-height: 1.55;
  color: var(--fg-muted);
  max-width: 62ch;
}
.entry__tags { margin: 16px 0 0; display: flex; flex-wrap: wrap; gap: 8px; }
.entry__tags span {
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--fg-muted);
  padding: 3px 10px;
  border: 1px solid var(--border-subtle);
  border-radius: 9999px;
}
.index-empty { margin-top: 48px; color: var(--fg-muted); }

/* ---------- site footer (matches the SPA chrome) ---------- */
.site-footer {
  flex-shrink: 0;
  padding: 40px 24px;
  border-top: 1px solid var(--border-subtle);
  margin-top: 96px;
}
.site-footer__inner {
  max-width: 1180px; margin-inline: auto;
  display: flex; flex-direction: column; gap: 16px;
}
.site-footer__inner img { height: 22px; width: auto; }
.site-footer__nav {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 10px 24px;
  font-size: 12.5px; color: var(--fg-muted);
}
.site-footer__nav a { color: var(--fg-muted); text-decoration: none; transition: color .15s ease; }
.site-footer__nav a:hover { color: var(--fg); }
/* The copyright sits in the same row as the four footer links and now reads as
   one line with them: same face, same size, one ink tier down. */
.site-footer__copy {
  font-size: 12.5px;
  color: var(--fg-faint);
}
@media (min-width: 640px) {
  .site-footer__inner { flex-direction: row; align-items: center; justify-content: space-between; }
}

@media (prefers-reduced-motion: reduce) {
  * { scroll-behavior: auto !important; transition: none !important; }
  .back-link:hover .back-link__arrow { transform: none; }
}
