:root {
  color-scheme: light; /* light only, so the browser leaves the whites alone */
  --bg: #ffffff;
  --text: #17171a;
  --muted: #3d3d38; /* 10.9:1 on the card, which is what the links sit on now */
  /* Master dial for the drawing. collatz.js already grades every strand by
     depth, so this multiplies the whole ramp rather than setting a single
     level: turn it down to push the artwork back. */
  --coral: 1;
  --ink: #28303a; /* near-black with a cool cast */
}

* { box-sizing: border-box; }

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

body {
  display: flex;
  min-height: 100vh;
  min-height: 100svh;
  margin: 0;
  padding: 3rem 1.5rem;
  background: var(--bg);
  color: var(--text);
  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 15.5px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Collatz coral, grown in the browser by collatz.js. Its own layer so opacity
   can differ per theme; the grey stroke reads correctly against both. */
#coral {
  position: fixed;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  transform: scaleX(-1); /* strands sweep up and to the right, away from the text */
  pointer-events: none;
}

#coral .strands { opacity: var(--coral); }

/* Width and alpha are presentation attributes, not properties: collatz.js sets
   one of each per depth band, and a rule here would outrank both.

   Butt caps, deliberately. Two pieces meet at every branch and at every band
   boundary, and a round cap puts a half-disc of ink at each end of each -- so
   with alpha now on the stroke rather than on the layer, the two discs composite
   and bead the strand with a dark dot at every joint. Butt caps leave nothing at
   the ends to double up. What they cost is a notch on the outside of the turn,
   which at 8 degrees and 2.4px is a third of a pixel, and square tips out at
   0.55px wide, which is nothing at all. */
#coral path {
  fill: none;
  stroke: var(--ink);
  stroke-linecap: butt;
  stroke-linejoin: round;
}

/* The growth wave. collatz.js sets --len (the longest chain in this depth
   bucket, in px) plus a delay and duration proportional to depth, so every
   bucket draws at one shared speed and the tree opens outward from the root.
   Dashing restarts at each subpath, so one <path> grows all of its chains.

   The huge gap keeps the pattern's *second* dash away from the far end of a
   full-length chain, and starting one pixel past --len keeps the first dash's
   trailing edge off position 0. Butt caps paint nothing at a zero-length dash,
   so neither is load-bearing any more, but both are free. */
@keyframes coral-grow {
  from { stroke-dashoffset: calc(var(--len) + 1px); }
}

#coral path[style] {
  stroke-dasharray: var(--len) 100000px;
  stroke-dashoffset: 0;
  animation-name: coral-grow;
  animation-timing-function: linear;
  animation-fill-mode: both;
}

/* The trajectory pulse: one number's route back to 1, drawn by collatz.js as a
   tip-to-root curve with a short dash sliding down it. The dash pattern
   starts entirely before the path (offset --pulse-len) and finishes entirely
   past it (offset -(--path-len)), so the pulse grows in at the tip and drains
   out at the far end without opacity keyframes; the extra pixels keep the round
   caps off both boundaries. The gap outruns path + pulse, so the pattern's
   second dash never enters. This rule ties with path[style] above on
   specificity and must stay after it -- the pulse carries an inline style, so
   the earlier rule would otherwise hand it the growth animation. Round caps are
   safe here: nothing abuts a pulse, so caps cannot bead. */
@keyframes coral-pulse {
  from { stroke-dashoffset: calc(var(--pulse-len) + 1px); }
  to   { stroke-dashoffset: calc(-1px - var(--path-len)); }
}

#coral path.pulse {
  stroke-dasharray: var(--pulse-len) calc(var(--path-len) + var(--pulse-len) + 2px);
  stroke-linecap: round;
  animation-name: coral-pulse;
  animation-timing-function: linear;
  animation-fill-mode: both;
}

/* `margin: auto` centers on both axes without clipping overflow
   the way `align-items: center` would on short viewports. */
/* Portrait beside the text on wide screens, stacked below 720px. The card is
   wide enough that the text column still reads at ~36rem next to the photo.

   An opaque white card, so the coral runs behind the page rather than through
   it: the strands stay legible out at the margins while the text
   sits on plain white. The padding is part of the block, so max-width carries
   it on top of the 56rem the content itself wants. */
main {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 2.75rem;
  width: 100%;
  max-width: 61rem;
  margin: auto;
  padding: 2.5rem;
  background: var(--bg);
  border-radius: 16px;
  /* A hairline to hold the edge where a pale strand passes under it, over a
     soft drop shadow that lifts the block off the drawing. */
  box-shadow: 0 0 0 1px rgba(23, 23, 26, 0.05),
              0 18px 40px -18px rgba(23, 23, 26, 0.28);
}

.intro {
  flex: 1 1 24rem;
  min-width: 0;
}

/* Uncropped: nathan.jpg is the full-height original trimmed only on the sides,
   widened to the right so the head sits centred. Nothing is masked away here. */
.portrait {
  display: block;
  flex: 0 0 auto;
  width: clamp(180px, 45vw, 260px);
  height: auto;
  aspect-ratio: 1000 / 1228;
  margin: 0;
  border-radius: 8px;
  object-fit: contain;
}

h1 {
  margin: 0;
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.bio {
  margin: 1rem 0 0;
  color: var(--text);
}

/* Each sentence is its own beat, so the gap between them carries the rhythm. */
.bio p {
  margin: 0 0 0.9rem;
  text-wrap: pretty;
}

/* In-prose links: keep the body text color but mark them with a quiet
   underline, since the global anchor style hides underlines for icons. */
.bio a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--muted);
  text-underline-offset: 3px;
}

.bio a:hover,
.bio a:focus-visible {
  text-decoration-color: currentColor;
}

.bio p:last-child {
  margin-bottom: 0;
}

/* The gallery link. Fixed to the top-right of the viewport, clear of the card,
   so it sits where a site's navigation is looked for. It has to read as a link
   on its own -- colour alone would not -- so it takes a hairline underline set
   clear of the descenders, which fills in on hover rather than appearing from
   nothing. */
.gallery-link {
  position: fixed;
  top: 1.5rem;
  right: 1.75rem;
  z-index: 2;
  padding: 0.25rem;
  color: var(--muted);
  font-size: 0.94rem;
  letter-spacing: -0.005em;
  text-decoration: underline;
  text-decoration-color: rgba(23, 23, 26, 0.28);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}

.gallery-link:hover,
.gallery-link:focus-visible {
  color: var(--text);
  text-decoration-color: currentColor;
}

.links {
  display: flex;
  flex-wrap: wrap;
  gap: 1.125rem;
  margin-top: 1.5rem;
}

/* Line-art icons: no fills, so one stroke colour suits both themes and the
   hover transition on `a` carries the icon along with it. */
.links a {
  display: inline-flex;
  padding: 0.25rem; /* keeps the tap target comfortable without visual weight */
}

.links svg {
  width: 21px;
  height: 21px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

a {
  color: var(--muted);
  text-decoration: none;
  transition: color 0.15s ease;
}

a:hover,
a:focus-visible {
  color: var(--text);
}

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

@media (prefers-reduced-motion: reduce) {
  a, .gallery-link { transition: none; }
}

@media (max-width: 720px) {
  main {
    gap: 2rem;
    padding: 2rem;
  }
}

@media (max-width: 480px) {
  h1 { font-size: 1.5rem; }

  /* Extra headroom so the pinned link never lands on the card's corner. */
  body { padding: 3rem 1rem 1.75rem; }

  main { padding: 1.5rem; }

  .gallery-link {
    top: 0.9rem;
    right: 0.9rem;
  }
}
