/* Shared by every page. Design values are custom properties so the site can be
   retuned in one place; a page that needs more than this baseline should load
   its own assets in its <head> rather than growing this file.

   System fonts are deliberate: they render every writing system the reader's
   OS supports, which is what makes browser translation legible. */

:root {
  --fg: #1a1a1a;
  --fg-muted: #6b6b68;
  --bg: #fdfdfc;

  /* Desaturated brown, picked to sit at the same lightness as --fg-muted so
     that adding it changes the hue of the page and not its rhythm. 5.37:1 on
     --bg, so it clears AA at the small size the section headings use. */
  --accent: #7c6553;
  --accent-wash: rgba(124, 101, 83, 0.07);
  --rule: rgba(26, 26, 26, 0.13);

  /* The OS interface face on each platform, ending in the generic `sans-serif`.
     That last entry is what carries the multilingual strategy: when a reader
     translates the page, the browser resolves `sans-serif` to the OS default
     for whatever script it just produced. Naming a Japanese or Arabic face
     here would privilege one language over the rest — the generic keeps them
     equal. */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
          Arial, "Noto Sans", "Liberation Sans", sans-serif;
  --text: 1.0625rem;
  --leading: 1.75;

  --measure: 36rem;   /* text column */
  --wide: 60rem;      /* upper bound for media that breaks out of it */
  --space-para: 1.25rem;
  --space-section: 4.5rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --fg: #e8e8e5;
    --fg-muted: #96968f;
    --bg: #141413;
    --accent: #b49780;
    --accent-wash: rgba(180, 151, 128, 0.10);
    --rule: rgba(232, 232, 229, 0.16);
  }

  /* Light text on a dark ground optically gains weight, so subpixel rendering
     reads as too heavy here. This is the one place thinning it helps; in light
     mode the browser default is left alone deliberately. */
  body { -webkit-font-smoothing: antialiased; }
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  padding: 5rem 1.5rem 7rem;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font);
  font-size: var(--text);
  line-height: var(--leading);
}

main {
  max-width: var(--measure);
  margin: 0 auto;
}

h1 {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: -0.01em;   /* large type needs less tracking, not more */
  line-height: 1.3;
  text-wrap: balance;
}

/* The line under the heading, in both of its jobs: the theme of the site on
   the index, the credit on a work page. Same treatment, since both are the
   quiet line that qualifies the heading above them. */
.tagline,
.dateline {
  margin: 0.35rem 0 0;
  color: var(--fg-muted);
}

/* Work pages are signed under the title, and the signature runs over two
   lines: who made it, then when. The name is lifted out of the muted line
   because it says whose work this is, which the date does not — the date is
   metadata and stays below on its own. The name also carries the way home from
   the top of the page, which is what lets these pages go on without the header
   the site has deliberately done without. Only the name is the link. */
.byline { color: var(--fg); }

section { margin-top: var(--space-section); }

h2 {
  margin: 0 0 1rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Keeps a lone word off the last line. Ignored where unsupported. */
p { margin: 0 0 var(--space-para); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

/* The index is set ragged-right and the work pages justified — that rule lives
   in works/<slug>/page.css, since that is the file only a work page loads.
   The index is four short paragraphs and a list; a flush right edge earns
   little there, and a ragged one keeps the front of the site lighter.

   The exception is below, and it is about what justification costs, not what
   it gains. In a space-separated script the slack of a flush edge is paid in
   the gaps between words, and the design philosophy is the most prominent
   text on the site — at this measure the gaps open wide enough to line up
   down the page. On the work pages hyphenation absorbs most of that; the
   index has no hyphenation rule, so the cost stays visible and the paragraphs
   are left ragged.

   Japanese and Chinese do not pay it. The line can break between almost any
   two characters, so the slack is distributed across the whole line in
   amounts too small to see, and a flush edge is what the scripts are
   ordinarily set with. There the flush edge is free, so the index takes it.

   `:lang(zh)` and not the two script subtags: :lang() matches on hyphen
   prefixes, so this already covers zh-Hans and zh-Hant, and would cover a
   regional variant if one is ever added. Korean sits between the two cases —
   it is spaced like English but breaks like Chinese — and is not here to
   decide; add it deliberately if it arrives, rather than by widening this
   selector.

   The selector reaches the work pages too, where it only restates what
   page.css already sets. Note that it outranks page.css if the two ever
   disagree: the language pseudo-class makes it the more specific rule, no
   matter which file loads second. A per-language exception on a work page has
   to be written to beat this, the way the Indonesian one there is. */
:lang(ja) section p,
:lang(zh) section p { text-align: justify; }

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

a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: var(--fg-muted);
}

a:hover { text-decoration-color: currentColor; }

a:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  border-radius: 2px;
}

/* Hit areas. The links that carry a reader between pages — the profiles, the
   name that leads home from a work, the way back at the foot of it, the
   language switcher — take the padded box the work cards on the index already
   have, lit with the same wash on hover and with no transition, so the area
   answers the cursor at once.

   An underline says a word is a link. It does not say how far the click
   reaches, and the reader who wants to know finds out by aiming and missing.
   The box is that answer, and it is drawn only at the moment it is asked for.

   The padding is pulled straight back out by an equal negative margin, so the
   text keeps the alignment it had and only the box is new. Both are in `rem`
   rather than `em`: these links are set at three sizes across the site, and
   the area a finger needs is the same at all three.

   Prose links inside a work are deliberately not here. They are words in a
   justified paragraph, and a box lighting up around three of them would break
   the line it sits in — while an underline mid-sentence is already unambiguous
   about what is being offered. The rule that separates the two is structural
   rather than a list: a link is chrome if it sits in a `nav`, signs the page,
   or switches the language, and prose otherwise. */
   `inline-block` is load-bearing, not tidiness. On an inline box the underline
   is drawn across the whole box, padding included, so padding alone left every
   one of these links underlined a quarter-space past its own last letter. A
   block container draws the decoration on its line box instead, which is the
   text and nothing else, and the padding goes back to being only a target. */
nav a,
.byline,
.langs a {
  display: inline-block;
  padding-block: 0.2rem;
  padding-inline: 0.4rem;
  margin-block: -0.2rem;
  margin-inline: -0.4rem;
  border-radius: 3px;
}

nav a:hover,
.byline:hover,
.langs a:hover { background-color: var(--accent-wash); }

/* Decorative marker for outbound links — a symbol, not prose, so it stays in
   CSS without putting text out of reach of translation.

   `margin-inline-start`, not `margin-left`: the marker follows the text, and
   in a right-to-left language the text ends on the left. Logical properties
   are used throughout this file for the same reason — nothing here means
   "left", it means "where the line begins". */
.ext::after {
  content: "\2197";
  margin-inline-start: 0.15em;
  font-size: 0.85em;
  color: var(--fg-muted);
}

/* Works listing on the index. The whole card is the link target, so the
   underline is moved onto the title alone rather than every line of text. */
.works { margin: 0; padding: 0; list-style: none; }
.works li { margin-bottom: 3rem; }
.works li:last-child { margin-bottom: 0; }

/* Padded, then pulled back out by the same amount, so the hover wash extends
   past the text without shifting the text off the page's alignment. */
.work {
  display: grid;
  grid-template-columns: minmax(0, 11rem) 1fr;
  gap: 1.25rem;
  align-items: start;
  color: inherit;
  text-decoration: none;
  padding: 0.85rem;
  margin: -0.85rem;
  border-radius: 3px;
}

.work:hover { background-color: var(--accent-wash); }

.work img { width: 100%; }

.work-title {
  display: block;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: var(--fg-muted);
}

.work-desc {
  display: block;
  margin-top: 0.2rem;
  color: var(--fg-muted);
}

.work:hover .work-title { text-decoration-color: currentColor; }

@media (max-width: 32rem) {
  .work { grid-template-columns: 1fr; gap: 0.85rem; }
  /* Stacked, the thumbnail would stretch past its own source width. */
  .work img { max-width: 20rem; }
}

nav { margin-top: var(--space-section); }

nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 1.75rem;
}

/* The name and the links to the profiles that stand behind it are one thing,
   so they share one line. The name says who this is; the links are where a
   reader goes to check that, which is the question that follows the name and
   precedes everything else on the page. Below them the heading's own line says
   what the page is about — identify, corroborate, then declare.

   Wrapping is the fallback, not the layout: where the measure runs out the
   links drop to a line of their own and the reading order is unchanged.

   Baselines, not centres. The two are set at different sizes on the index, and
   what should line up between type of two sizes is the line it sits on.

   Nothing here names a side. In Arabic the row runs from the right and the
   name still comes first, because `flex` follows the writing direction. */
.identity {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 2.5rem;
}

/* The row's own gap does the spacing now, so the children give up their
   margins — including the section-sized top margin every nav carries by
   default, which the class beats without needing an override. */
.identity > * { margin-top: 0; }

/* Tighter between the two links than between them and the name, so they read
   as a pair attached to it rather than three items in a row. */
.identity nav ul { gap: 1.25rem; }

/* Sharing a line is not being equal on it. The name is the claim; LinkedIn and
   GitHub are two places to go and check it, and they carry someone else's name
   besides. A step down in size and out of the full text colour says which is
   which without a separator having to — and a separator would say the opposite
   anyway, since `A · B` is how a list of peers is written.

   On the index the heading already outweighs them and this only confirms it.
   On a work page it is the whole of the effect: the byline is set at body size,
   so left alone the two read as equals on the line.

   `--fg-muted` is 5.25:1 on the light ground and 6.19:1 on the dark, so it
   clears AA at this size; it is the same colour the tagline and the date are
   already set in. */
.identity nav { font-size: 0.875rem; }
.identity nav a { color: var(--fg-muted); }

/* Hover has to move the colour here, not just the underline. Everywhere else
   on the site an underline goes from muted to `currentColor` on hover, which
   is a visible change because the text is at full strength — but these links
   are muted, so `currentColor` is what the underline already was, and hover
   would do nothing at all. Lifting the whole link to `--fg` restores the
   response and takes the underline with it. */
.identity nav a:hover { color: var(--fg); }

/* A work page puts the title first and the row beneath it, at the same remove
   the tagline keeps on the index. On the index the row opens with the title
   itself and has nothing to clear. */
h1 + .identity { margin-top: 0.35rem; }

/* The rule is the width of the text column, since the footer is inside the
   same measure as every paragraph. */
footer {
  margin-top: var(--space-section);
  padding-top: 1.75rem;
  border-top: 1px solid var(--rule);
  font-size: 0.8125rem;
  color: var(--fg-muted);
}

/* Where a nav already ended the page, the footer closes it rather than
   starting a new block. */
nav + footer { margin-top: 3rem; }

footer p { margin: 0; }

/* Media. Unused on the home page, present so that work pages can carry images
   without reworking the layout. `.bleed` widens a figure past the text column
   while keeping it centred on the same axis. */

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

figure { margin: var(--space-section) 0; }

figcaption {
  margin-top: 0.75rem;
  font-size: 0.875rem;
  color: var(--fg-muted);
}

.bleed {
  --bleed: min(92vw, var(--wide));
  width: var(--bleed);
  margin-inline-start: calc(50% - var(--bleed) / 2);
}

/* Per-script adjustments. Only where a value tuned for Latin text actively
   misreads elsewhere — not a place to re-tune the design per language. */

/* Arabic joins its letters into words, so tracking does not open the script
   up, it pulls the joins apart. Both the negative tracking on the name and
   the wide tracking on the section headings come off. `text-transform:
   uppercase` is inert in Arabic and can stay where it is. */
:lang(ar) h1,
:lang(ar) h2 { letter-spacing: normal; }

/* The language switcher sits in the footer, above the copyright line. Each
   name is written in its own language, so each anchor carries its own `lang`
   and is marked `translate="no"` — otherwise page translation turns 日本語
   into "Japanese" and the reader looking for it cannot find it. The current
   language is plain text rather than a link to itself. */
.langs {
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  /* The row gap carries the hit areas. Vertical padding on an inline box does
     not push the line apart, so at the old 0.4rem the boxes on the two rows
     this list wraps to would have met along an edge. */
  gap: 0.75rem 1.1rem;
}

.langs [aria-current] { color: var(--fg); }
