/* ==========================================================================
   dwbakker.com
   Two faces, two jobs.
     Newsreader (serif) — voice:  the name (roman), section headings (italic).
     Archivo (sans)     — record: everything factual.
   Nothing else gets a typeface. If a new element needs one, it belongs to
   one of those two jobs; decide which before styling it.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts

   font-optical-sizing: auto lets the browser drive the opsz axis from
   font-size, which is what an optical size axis is for. Do not override it
   with font-variation-settings — that also silently kills font-weight, and
   the dark-mode weight correction below depends on font-weight working.

   font-display: swap means correct type always arrives, at the cost of a
   small layout shift when it does. The alternative is `optional`: zero shift
   guaranteed, but first-time visitors on a slow connection get the fallback
   for that pageload. Swap is the right call for a site whose whole point is
   the typography. Revisit only with metric-matched fallbacks in place.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Archivo";
  src: url("/fonts/archivo.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

/* Not optional and not unused. Anything italic in the sans layer — the <cite>
   inside a quotes.html figcaption, any future <em> — will otherwise be
   synthesised by shearing the roman, which reads as obviously wrong beside a
   properly drawn italic. Nothing on the index uses it, so it is not preloaded
   there and is only fetched on the pages that need it. */
@font-face {
  font-family: "Archivo";
  src: url("/fonts/archivo-italic.woff2") format("woff2");
  font-weight: 300 700;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Newsreader";
  src: url("/fonts/newsreader-roman.woff2") format("woff2");
  font-weight: 300 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Newsreader";
  src: url("/fonts/newsreader-italic.woff2") format("woff2");
  font-weight: 300 600;
  font-style: italic;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   2. Tokens

   Type scale: a perfect fourth off a 16px base, rounded to whole pixels.
   Space: --u is 4px. EVERY vertical value is calc(n * var(--u)).

   Palette: "Ink" — warm paper, warm black, blue-black for links. The greys are
   tinted the same direction as the black, so the whole ramp reads as one ink
   rather than three unrelated colours. Keep that property if you change
   anything: a grey that drifts cool while the black stays warm is the single
   fastest way to make this palette look assembled instead of designed.

   Contrast is measured, not eyeballed. Against --bg:
     light   fg 17.17   fg-2 7.00   fg-3 3.06   accent 10.93
     dark    fg 14.54   fg-2 5.88   fg-3 3.12   accent  8.82

   --fg-3 is BELOW the 4.5:1 AA threshold, deliberately. Do not "fix" it.
   It colours only the date column and the footer line — supplementary content
   that a reader can lose without losing the record. Raising it to 4.87:1 puts
   it within touching distance of --fg-2 at 7:1, which collapses three tiers of
   grey into two and destroys the hierarchy the hanging date column exists to
   create. This was measured, weighed, and chosen. Everything that carries
   actual content — body text, meta, and every link — clears AA comfortably,
   and links must stay above 8:1 since they are the only interactive elements
   on the page.
   -------------------------------------------------------------------------- */

:root {
  --s-1: 0.8125rem;
  /* 13 — dates, footer */
  --s0: 0.9375rem;
  /* 15 — record body, affiliation */
  --s1: 1.0625rem;
  /* 17 — lede, personal line */
  --s-h: 1.625rem;
  /* 26 — section headings */

  /* The rem term is not decoration: a clamp whose middle value is pure vw
     ignores the user's default font-size setting, which fails WCAG 1.4.4. */
  --s-n: clamp(2.5rem, 1.7rem + 3.4vw, 3.5rem);
  /* 40–56 — name (enlarged to
                                                     hold its own beside the portrait) */

  --u: 0.25rem;
  --measure: 41rem;

  --bg: #FCFAF6;
  /* warm paper */
  --fg: #191713;
  /* warm black — ink, not screen grey */
  --fg-2: #5C5648;
  --fg-3: #96907F;
  --rule: #E6E1D5;
  --accent: #1F3A63;
  /* blue-black ink */

  /* Light-on-dark blooms optically: stems fatten, hairlines thin. The fix is
     to drop weight in dark mode, which is only possible with variable fonts.
     If anyone proposes static weights, this correction goes with them. */
  --w-body: 400;
  --w-strong: 550;
  --w-serif: 400;

  --ease: cubic-bezier(.32, .72, 0, 1);
  --theme-fade: .4s;
}

[data-theme="dark"] {
  --bg: #161512;
  /* warm near-black — a dim page, not a dark screen */
  --fg: #EAE5DA;
  --fg-2: #9A9184;
  --fg-3: #6B6459;
  --rule: #2E2B24;
  --accent: #9BB6DE;

  --w-body: 350;
  --w-strong: 500;
  --w-serif: 340;
}

[data-theme="light"] {
  color-scheme: light;
}

[data-theme="dark"] {
  color-scheme: dark;
}

/* --------------------------------------------------------------------------
   3. Base

   Deliberately absent, having been tried and removed:
     text-rendering: optimizeLegibility — forces extra layout work and has a
       long history of rendering bugs; browsers kern OpenType by default.
     -webkit-font-smoothing: antialiased — thins glyphs on macOS, which fights
       the dark-mode weight correction above and lowers effective contrast.
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-weight: var(--w-body);
  font-optical-sizing: auto;
  hanging-punctuation: first;
  /* Safari only; harmless elsewhere */
}

h1,
h2,
p {
  margin: 0;
}

p {
  text-wrap: pretty;
}

/* kills orphans and bad rags */
h1,
h2 {
  text-wrap: balance;
}

.items {
  list-style: none;
  margin: 0;
  padding: 0;
}

a {
  color: inherit;
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
  text-decoration-color: color-mix(in srgb, currentColor 32%, transparent);
  transition: text-decoration-color .18s ease;
}

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

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

::selection {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* Custom properties do not transition, so a rule on <body> alone fades the
   background while every element carrying --fg-2 or --fg-3 snaps. These are
   the elements that hold an explicit colour; keep the list in step with them. */
body,
.role,
.meta,
.item-status,
.item-date,
.block-label,
.portrait,
.site-footer,
.theme-toggle {
  transition:
    background-color var(--theme-fade) var(--ease),
    color var(--theme-fade) var(--ease),
    border-color var(--theme-fade) var(--ease);
}

/* --------------------------------------------------------------------------
   4. Layout
   -------------------------------------------------------------------------- */

.site {
  max-width: var(--measure);
  margin: 0 auto;
  padding: calc(18 * var(--u)) 1.5rem calc(16 * var(--u));
}

/* --------------------------------------------------------------------------
   5. The serif layer — voice
   -------------------------------------------------------------------------- */

.name {
  font-family: "Newsreader", Georgia, serif;
  font-weight: var(--w-serif);
  font-size: var(--s-n);
  line-height: 1.02;
  letter-spacing: -.012em;
  /* tracking tightens as size grows */
}

/* Name and portrait. Row on desktop: the name is flush-left and the photo is
   pushed right so its right edge lands on the content-column boundary — the
   same right edge the role, lede and item text below are measured to (there is
   no narrower measure on the text, so that boundary is the .site box edge). The
   photo is square (record register, not the softer circle) and 84px — a
   credential beside the name, deliberately not a hero. Reversing to a column
   below 30rem drops the photo above the name; column-reverse keeps the name
   first in the DOM while putting the photo on top visually. */
.namebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.portrait {
  flex: none;
  width: calc(21 * var(--u));
  /* 84 */
  height: calc(21 * var(--u));
  border-radius: 3px;
  object-fit: cover;
  border: 1px solid var(--rule);
}

@media (max-width: 30rem) {
  .namebar {
    flex-direction: column-reverse;
    align-items: flex-start;
    gap: calc(4 * var(--u));
  }
}

.block-label {
  font-family: "Newsreader", Georgia, serif;
  font-weight: var(--w-serif);
  font-style: italic;
  font-size: var(--s-h);
  line-height: 1.2;
  margin-bottom: calc(5 * var(--u));
  padding-bottom: calc(3 * var(--u));
  border-bottom: 1px solid var(--rule);
}

/* --------------------------------------------------------------------------
   6. The sans layer — record
   -------------------------------------------------------------------------- */

.role {
  margin-top: calc(3 * var(--u));
  font-size: var(--s0);
  line-height: 1.55;
  color: var(--fg-2);
}

.contact {
  margin-top: calc(3 * var(--u));
  font-size: var(--s0);
  line-height: 1.55;
}

/* .lede and .personal are deliberately identical apart from the space above
   them. Lower contrast reads optically as lower weight, so if you ever mute
   .personal, raise its font-weight by ~20 to compensate. */
.lede {
  margin-top: calc(7 * var(--u));
  font-size: var(--s1);
  line-height: 1.7;
}

.personal {
  margin-top: calc(5 * var(--u));
  font-size: var(--s1);
  line-height: 1.7;
}

.block {
  margin-top: calc(14 * var(--u));
}

.item {
  display: grid;
  grid-template-columns: 4.75rem 1fr;
  column-gap: 1.5rem;
  row-gap: 0;   /* the status line is a second grid row; a row gap here would
                   stack on its margin and float it away from the entry */
}

.item+.item {
  margin-top: calc(5 * var(--u));
}

/* Entries with no date keep the text column rather than carrying an empty
   element as a grid spacer. */
.item--nodate .item-body {
  grid-column: 2;
}

.item-date {
  font-size: var(--s-1);
  line-height: 1.78;
  /* aligns to the first baseline of .item-body */
  color: var(--fg-3);
  font-variant-numeric: tabular-nums;
}

.item-body {
  font-size: var(--s0);
  line-height: 1.75;
}

.item-title {
  font-weight: var(--w-strong);
}

.meta {
  color: var(--fg-2);
}

/* Genre/status line beneath an entry — the CV convention where the venue sits
   under the title. grid-column: 2 keeps it in the body column (a bare second
   child would auto-place into the empty date cell of the next row). No new type
   tokens: it reuses --s-1 (the date size) and the --fg-2 tier, so nothing new
   enters the two-face system or the grey scale. It is deliberately NOT --fg-3:
   that tier is sub-AA and reserved for content a reader can lose (dates,
   footer), whereas "Under review" or "Master's thesis" is load-bearing record
   information and must clear AA. The smaller size, not a lighter grey, is what
   separates it from the .meta description above it. */
.item-status {
  grid-column: 2;
  margin-top: calc(2 * var(--u));
  font-size: var(--s-1);
  line-height: 1.5;
  color: var(--fg-2);
}

/* --------------------------------------------------------------------------
   7. Footer
   -------------------------------------------------------------------------- */

.site-footer {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 1.5rem calc(12 * var(--u));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--s-1);
  color: var(--fg-3);
}

/* The glyph is drawn from the theme attribute so it is right on first paint.
   Safe to put in ::before here only because the button carries an explicit
   aria-label, which is what assistive technology reads instead.

   The box is fixed rather than min-sized on purpose. ☾ and ☀ are different
   characters with different metrics, rendered from whichever fallback font
   has them — neither Archivo nor Newsreader does. With an auto-height button
   the taller glyph grows the footer row and shifts the whole page on toggle,
   visibly in one direction only. Fixed width and height, glyph centred, and
   layout can no longer see which character is in there. */
.theme-toggle {
  width: 2.25rem;
  height: 2.25rem;
  display: inline-grid;
  place-items: center;
  padding: 0;
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  background: transparent;
  color: var(--fg-3);
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
}

.theme-toggle:hover {
  color: var(--fg);
  border-color: var(--rule);
}

[data-theme="light"] .theme-toggle::before {
  content: "\263E";
}

/* ☾ */
[data-theme="dark"] .theme-toggle::before {
  content: "\2600";
}

/* ☀ */

/* --------------------------------------------------------------------------
   8. Narrow viewports
   Below 34rem the date column costs more than it gives: dates stack above
   their entries instead.
   -------------------------------------------------------------------------- */

@media (max-width: 34rem) {
  .site {
    padding-top: calc(12 * var(--u));
  }

  .item {
    grid-template-columns: 1fr;
    gap: .15rem;
  }

  .item--nodate .item-body {
    grid-column: 1;
  }

  .item-date {
    line-height: 1.5;
  }
}

/* --------------------------------------------------------------------------
   9. Reduced motion and print
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {

  *,
  *::before,
  *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
  }
}

@media print {
  :root {
    --bg: #fff;
    --fg: #000;
    --fg-2: #333;
    --fg-3: #555;
    --accent: #000;
  }

  .site-footer {
    display: none;
  }

  a {
    text-decoration: none;
  }

  .site {
    max-width: none;
    padding: 0;
  }
}

/* --------------------------------------------------------------------------
   10. Utility
   Visually hidden but readable by assistive technology. Not display:none,
   which removes it from the accessibility tree entirely.
   -------------------------------------------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}