/* Geist by Vercel, OFL 1.1 — see fonts/OFL.txt.
   Sans and Mono are variable: one file each, every weight 100–900.
   A font here is only downloaded if something on the page actually
   uses it, so the unused Pixel styles cost visitors nothing. */

@font-face {
  font-family: "Geist";
  src: url("../fonts/geist-variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Geist Mono";
  src: url("../fonts/geist-mono-variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Pixel has no weight axis — five fixed styles, each its own family.
   Display sizes only; it's unreadable as body text. */
@font-face {
  font-family: "Geist Pixel Square";
  src: url("../fonts/geist-pixel-square.woff2") format("woff2");
  font-display: swap;
}
@font-face {
  font-family: "Geist Pixel Circle";
  src: url("../fonts/geist-pixel-circle.woff2") format("woff2");
  font-display: swap;
}
@font-face {
  font-family: "Geist Pixel Grid";
  src: url("../fonts/geist-pixel-grid.woff2") format("woff2");
  font-display: swap;
}
@font-face {
  font-family: "Geist Pixel Line";
  src: url("../fonts/geist-pixel-line.woff2") format("woff2");
  font-display: swap;
}
@font-face {
  font-family: "Geist Pixel Triangle";
  src: url("../fonts/geist-pixel-triangle.woff2") format("woff2");
  font-display: swap;
}

/* ---- Everything you'd want to change is in this block ----

   OLED black + amber highlights. Amber tokens are yours, lifted from
   r_projects/dwelling_job_access/quarto/css/styles.css, so this site
   matches the thesis and the Quarto reports:
       --amber #ffb000   --amber-hi #ffc940

   Prose stays neutral and amber does the highlighting — the same split
   your NERV Amber RStudio theme uses (#F8F8F2 text, amber accents).
   Colours are named by role, like an editor theme: --keyword is links,
   --number is dates, --string is tags.

   Contrast floors are noted per line. Amber is only legible on black —
   light mode deepens it to #8f5600, since #ffb000 on white is 1.7:1.  */

:root {
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --font-pixel: "Geist Pixel Circle", var(--font-mono);
  /* EXPERIMENT: pixel throughout. Swap these two back to var(--font-mono)
     to undo. */
  --font-body: var(--font-pixel);
  --font-heading: var(--font-pixel);

  /* Each colour is light-dark(light, dark) — one line, both themes.
     Dark is the real one: true OLED black with your amber.
     Light deepens amber to #8f5600 because #ffb000 on white is 1.7:1.

     Dark is the default for everyone, regardless of their OS setting;
     only the toggle changes it. To follow the OS instead, set this back
     to `light dark` — and flip DEFAULT_THEME in js/main.js to match.   */
  color-scheme: dark;

  --bg:         light-dark(#faf8f4, #000000);
  --surface:    light-dark(#f2efe8, #0a0a08);
  --text:       light-dark(#232320, #f8f8f2);   /* dark: your NERV fg, 19.7:1 */
  /* Brighter than the NERV grey it was (#929ba1, 7.4:1). Pixel Circle's
     strokes are dotted, so it puts far less ink on the page than Geist Mono
     at the same size and reads fainter than its contrast ratio implies —
     the number doesn't capture coverage. 10.9:1 / 8.4:1 now. */
  --text-muted: light-dark(#4c4a42, #b4bcc4);
  --keyword:    light-dark(#8f5600, #ffc940);   /* links — dark: --amber-hi */
  --number:     light-dark(#9a5f00, #ffb000);   /* dates — dark: --amber   */
  --string:     light-dark(#6d5a2e, rgba(255, 176, 0, 0.72));  /* tags */
  --rule:       light-dark(#e3ddd0, rgba(255, 176, 0, 0.18));
  /* the blinking cursor — dark mode is your --amber-dim verbatim.
     Alpha rather than an opacity property: the blink animates opacity, and
     the two would fight. */
  --cursor:     light-dark(rgba(154, 95, 0, 0.5), rgba(255, 176, 0, 0.5));

  --measure: 40rem;   /* how wide the text column gets */
  --sidebar: 24rem;   /* identity column; don't go below 22rem, see below */
  --gap: 2.5rem;      /* space between sections */
}

/* The toggle sets data-theme on <html>; that beats the OS preference.
   With no attribute, color-scheme: light dark follows the OS.          */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

/* Fluid root size — everything else is in rem, so this scales the lot.

   On a 4K screen with no OS scaling, a CSS pixel is a physical pixel and
   14px text is genuinely tiny. This grows the base with viewport width:
   16px at 1920 (1080p is unchanged), ~21px at 3840 — near enough to the
   133% zoom that reads correctly, without touching the browser.

   The rem term is what keeps this accessible: it responds to the visitor's
   default font size, so a vw-only version would ignore that setting.

   Rough guide:  1280 -> 16px | 1920 -> 16px | 2560 -> 18px | 3840 -> 21px  */
html {
  font-size: clamp(1rem, 0.67rem + 0.28vw, 1.33rem);
}

/* ---------------------------------------------------------- */

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

body {
  margin: 0;
  padding: 4rem 1.5rem;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  /* 0.9rem suited Geist Mono. Pixel Circle is lighter and narrower, so it
     needs the size back to stay readable — its dots don't resolve at all
     below ~20px, and at body size it just reads as a thin face. */
  font-size: 1.05rem;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

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


h1, h2 {
  font-family: var(--font-heading);
  line-height: 1.2;
  margin: 0;
}

/* The name, as a banner. It sits outside <header> in the markup so the
   grid can span it across both columns.

   Geist Pixel Circle has no weight axis (no fvar, usWeightClass 400), so
   font-weight does nothing here — asking for a bold only makes the browser
   synthesise one, which thickens the dots and fills in the pixel forms.
   Pinned to 400 deliberately.

   The size is derived, not chosen: the name is meant to fill its column.
   Measured, "~/ Paul Spasojevic" plus the cursor runs 9.65px wide per 1px
   of font-size at this letter-spacing. Here the container is a single
   column — min(40rem, viewport) — so 4rem fills a 40rem measure, and 8.2vw
   fills a narrow screen. The two-column size, where the container is the
   69rem of grid tracks, is set in the layout block at the end of this file.

   Re-measure the ratio if the cursor or letter-spacing change — both feed
   it, and a stale ratio wraps the name onto two lines. */
h1 {
  font-family: "Geist Pixel Circle", var(--font-mono);
  font-size: clamp(1.6rem, 8.2vw, 4rem);
  font-weight: 400;
  /* Opens the pixel dots up. It also widens the name ~8% (ratio 9.10 -> 9.85),
     which is why the fill sizes are lower than they'd otherwise need to be. */
  letter-spacing: 0.05em;
  margin-bottom: 0.4rem;
}

/* Inherits Pixel Circle from h1. 0.6em rather than 0.45 because the dots
   stop resolving into shapes below ~40px. */
h1::before {
  content: "~/ ";
  color: var(--number);
  font-size: 0.6em;
  vertical-align: 0.22em;
}

/* A blinking bar cursor. It was a 0.5em block, which is right at terminal
   text sizes but reads as a slab next to a 108px name. Sized in em so it
   tracks the name at every width. */
h1::after {
  content: "";
  display: inline-block;
  width: 0.09em;
  height: 0.8em;
  margin-left: 0.28em;
  vertical-align: -0.04em;
  background: var(--cursor);
  animation: blink 1.2s steps(1) infinite;
}

@keyframes blink {
  50% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  h1::after { animation: none; opacity: 0.5; }
}

h2 {
  font-size: 1.4rem;
  letter-spacing: 0;
  color: var(--text);
  font-weight: 800;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 1.5rem;
}

h2::before {
  content: "// ";
  color: var(--keyword);
}

a {
  color: var(--keyword);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

a:hover {
  border-bottom-color: currentColor;
}

#theme-toggle {
  font: inherit;
  color: var(--number);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

#theme-toggle:hover {
  color: var(--keyword);
}

#theme-toggle:focus-visible {
  outline: 1px solid var(--keyword);
  outline-offset: 2px;
}

header .intro {
  margin: 0.75rem 0 0;
  color: var(--text-muted);
}

.links {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  padding: 0;
  margin: 1rem 0 0;
  font-size: 0.85rem;
}

/* slash-separated, the way a path is */
.links li:not(:last-child)::after {
  content: " / ";
  color: var(--text-muted);
  white-space: pre;
}

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

.entries {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.entry-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}

.entry-title {
  font-size: 1.2rem;
  font-weight: 600;
}

/* Amber alone doesn't say "link" — on this page amber is also dates and
   tags. So linked entries get an underline plus a bracketed arrow: the
   underline is the affordance, [↗] says it leaves the site, and brackets
   already mean "interactive" here because of the [light] toggle.
   Entries with no url in the JSON get neither, automatically. */
.entry-title a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  border-bottom: 0;
}

.entry-title a::after {
  content: "[↗]";
  /* inline-block keeps the parent's underline from running under it */
  display: inline-block;
  margin-left: 0.45em;
  font-size: 0.75em;
  color: var(--text-muted);
}

.entry-title a:hover::after {
  color: currentColor;
}

.entry-meta {
  color: var(--number);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.entry-note {
  margin: 0.35rem 0 0;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.entry-tags {
  margin-top: 0.5rem;
  font-size: 0.78rem;
  color: var(--string);
}

@media (max-width: 30rem) {
  body { padding: 2.5rem 1.25rem; }
  .entry-head { flex-direction: column; gap: 0.1rem; }
}

/* ---------------------------------------------------------------
   Layout overrides live at the END of the file on purpose. Media
   queries add no specificity, so a base rule appearing later would
   beat them — which is exactly what happened when this block sat
   higher up: `section { margin-top: var(--gap) }` further down the
   file silently overrode the `margin-top: 0` here, and the first
   section carried a 40px margin the sidebar never wanted.
   --------------------------------------------------------------- */
/* Once there's room:

       ~/ Paul Spasojevic ▐       <- h1 spans both columns
       ──────────────┬──────────────
       intro         │ // Projects
       links         │ // Work
       (sticky)      │ // Education

   Below the breakpoint it's one column, which is what phones get.

   Source order is h1, header, sections — the same order it reads in — so
   reading order and screen-reader order agree.

   The 22rem sidebar floor is now about the intro's measure rather than the
   name; the name spans the full width and no longer has to fit the column.
   Breakpoint is 22 + 5 gap + 40 content + margin. */
@media (min-width: 68rem) {
  main {
    max-width: 82rem;
    display: grid;
    grid-template-columns: minmax(22rem, var(--sidebar)) minmax(0, var(--measure));
    /* Row 1 is the banner. The rest are content rows; any past the last
       section are empty and collapse to 0 — headroom for more sections. */
    grid-template-rows: auto repeat(10, auto);
    column-gap: 5rem;
    align-items: start;
  }

  h1 {
    grid-column: 1 / -1;
    grid-row: 1;
    margin-bottom: 2.5rem;
    /* The container is the grid TRACKS, not main's 82rem box: the columns
       are capped (24rem sidebar + 5rem gap + 40rem measure = 69rem ~ 1104px)
       and don't stretch to fill it. Sizing to 82rem overshoots by ~200px and
       wraps the name onto two lines.
         69rem * 0.97 / 9.65 = 6.9rem.
       9.4vw covers the range below ~1184px where the tracks are still
       viewport-bound. Both terms are rem/vw, so this holds at 4K too. */
    font-size: clamp(4rem, 9.4vw, 6.9rem);
  }

  header {
    grid-column: 1;
    /* spans the content rows, or sticky has nothing to travel in */
    grid-row: 2 / -1;
    position: sticky;
    top: 4rem;
  }

  header .intro {
    margin-top: 0;
  }

  section {
    grid-column: 2;
    margin-top: 0;
  }

  section + section {
    margin-top: var(--gap);
  }
}
