/* Spline Sans Mono — Sorkin Type, OFL 1.1. See fonts/OFL.txt.
   Subset build, wght axis clipped to the 400-600 the page actually uses. */
@font-face {
  font-family: "Spline Sans Mono";
  src: url("/fonts/SplineSansMono-VF.woff2") format("woff2");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

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

:root {
  color-scheme: light;

  /* type — one size on the page, everything else inherits */
  --mono:    "Spline Sans Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --s-body:  0.90rem;

  /* color */
  --bg:     #ffffff;
  --ink:    #111111;
  --muted:  #6b6b6b;  /* 5.33:1 on white — do not lighten */
  --link:   #1a3fd4;
  --shade:  rgba(17, 17, 17, .07);   /* hover ground, no border */

  /* space */
  --measure:   56ch;   /* rail + gutter + ~43ch of content */
  --label-col: 11ch;   /* 'Portfolio' at 9ch plus breathing room */
  --gutter:    2ch;
  --rhythm:    clamp(0.9rem, 1.6vh + 0.4rem, 1.6rem);
}

/* Inversion, not a separate palette. --muted must lift: #6b6b6b is 3.65:1 here. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg:     #0d0d0d;
    --ink:    #e8e8e8;
    --muted:  #8f8f8f;
    --link:   #8ab4ff;
    --shade:  rgba(232, 232, 232, .10);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg:     #0d0d0d;
  --ink:    #e8e8e8;
  --muted:  #8f8f8f;
  --link:   #8ab4ff;
  --shade:  rgba(232, 232, 232, .10);
}

:root[data-theme="light"] {
  color-scheme: light;
  --bg:     #ffffff;
  --ink:    #111111;
  --muted:  #6b6b6b;
  --link:   #1a3fd4;
  --shade:  rgba(17, 17, 17, .07);
}

html {
  background: var(--bg);
}

body {
  background: var(--bg);
  color: var(--ink);
  font: 400 var(--s-body)/1.5 var(--mono);
  min-height: 100vh;
  min-height: 100svh; /* mobile Safari's collapsing toolbar overshoots with vh */
}

/* content-box so --measure is the text column itself.
   Under the global border-box, padding-inline would eat the measure. */
.wrap {
  box-sizing: content-box;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 6vw, 5.5rem);
  padding-block-start: clamp(2rem, 6vw, 4.5rem);
  padding-block-end: calc(var(--rhythm) * 3);
  display: flex;
  flex-direction: column;
}

/* The toggle right-aligns to the measure, which the header states rather
   than inherits — .wrap is a flex column, so children stretch by default. */
header {
  max-width: var(--measure);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

/* Wordmark plus descriptor, one unit. The tagline is set tight under the
   wordmark rather than floating, so the head is the only thing at x: 0 and
   the record starts clean. */
.mark {
  font-size: inherit;
  font-weight: 600;
  letter-spacing: .14em;
  color: var(--ink);
}

.tagline {
  color: var(--ink);
  font-weight: 400;
  margin-top: calc(var(--rhythm) * 0.4);
}

header + main {
  margin-top: calc(var(--rhythm) * 2.8);
}

/* The record. Label in the rail, value in the content column; label and
   first value share a grid row, so they sit on one baseline. */
.row {
  display: grid;
  grid-template-columns: var(--label-col) 1fr;
  gap: 0 var(--gutter);
  align-items: start;
}

.row + .row {
  margin-top: calc(var(--rhythm) * 1.6);
}

/* A row's margin hangs off the last value's box, so a multi-line row's extra
   mass makes the identical gap read tighter after it. .tall marks the rows
   that stack, and the row following one gets the compensation. */
.row.tall + .row {
  margin-top: calc(var(--rhythm) * 1.9);
}

.row dt {
  grid-column: 1;
  font-weight: 600;
  color: var(--muted);
}

/* Balanced so a two-line value breaks after a phrase instead of orphaning
   its last word. Degrades to normal wrapping where unsupported. */
.row dd {
  grid-column: 2;
  text-wrap: balance;
}

.row dd + dd {
  margin-top: calc(var(--rhythm) * 0.25);
}

/* Color carries the affordance; underline on hover only. */
a {
  color: var(--link);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
  text-underline-offset: .2em;
  text-decoration-thickness: 1px;
}

/* Stealth is the only non-link in Portfolio and would otherwise be the
   loudest thing on the page. Scoped: Focus, Hours and the curl are ink. */
#portfolio dd:not(:has(a)) {
  color: var(--muted);
}

/* Second-tier content inside a value: roles, the fallback under curl. */
.aside {
  color: var(--muted);
}

/* Copy-to-clipboard, undeclared until you reach it: no border, no link
   color, body ink. The hover ground is the whole affordance. Padding is
   cancelled by an equal negative margin so the text stays on the rail's
   grid, and min-width holds the block steady while the label swaps. */
.copy {
  appearance: none;
  border: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  display: inline-block;
  min-width: 18ch;
  padding: .15em .5em;
  margin: -.15em -.5em;
  border-radius: 2px;
  transition: background-color .12s linear;
}

.copy:hover,
.copy:focus-visible,
.copy[data-state="done"] {
  background: var(--shade);
}

/* Utility chrome, not content. Inset one character cell from the measure:
   no line of text ever fills the column, so flush to the measure left the
   toggle hanging past the visible edge of the text block. */
.util {
  margin-inline-end: 1ch;
  appearance: none;
  border: 0;
  padding: 0;
  background: transparent;
  font: inherit;
  color: var(--muted);
  cursor: pointer;
}

.util:hover {
  color: var(--ink);
}

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

/* User-triggered only. */
body {
  transition: background-color .15s linear, color .15s linear;
}

/* 11ch of a phone-width column is too much to give away. */
@media (max-width: 34rem) {
  .row {
    grid-template-columns: 1fr;
  }

  .row dt,
  .row dd {
    grid-column: 1;
  }

  .row dt {
    margin-bottom: calc(var(--rhythm) * 0.4);
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
