/* ---------------------------------------------------------------------------
   Natanael Alves Gabriel — portfolio
   Palette: cool paper / pine signal. Sand is reserved for the prototypes band.
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   Self-hosted @font-face declarations
   ---------------------------------------------------------------------------
   PATHS: these rules are written to be pasted into assets/style.css.
   A relative url() in CSS resolves against the stylesheet's own URL, so from
   assets/style.css the path fonts/x.woff2 -> assets/fonts/x.woff2. Correct.
   (They will NOT resolve from this file's own location; this file is a
   staging buffer for merging, not a stylesheet to link directly.)

   All three are VARIABLE fonts, one file per family, taken from the
   google/fonts upstream repo and subset to latin + latin-ext, plus the two
   extra symbols the page uses (U+2192 RIGHTWARDS ARROW, U+25B6 TRIANGLE).

   Fraunces keeps all four axes -- opsz, wght, SOFT, WONK -- so the
   `font-variation-settings: 'SOFT' 0, 'WONK' 1` rule on h1,h2,h3 is now
   actually backed by a font file that exposes those axes.

   font-weight is declared as the font's true variable range so the browser
   maps font-weight correctly. This matters for Fraunces in particular: its
   wght axis DEFAULTS to 900, so without a declared range a heading could
   render far heavier than intended.
--------------------------------------------------------------------------- */

@font-face {
  font-family: 'Fraunces';
  src: url('fonts/fraunces-var.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-02FF, U+0304, U+0308,
    U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2000-206F, U+2074,
    U+20A0-20C0, U+2113, U+2122, U+2191-2193, U+2212, U+2215, U+25B6,
    U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Public Sans';
  src: url('fonts/public-sans-var.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-02FF, U+0304, U+0308,
    U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2000-206F, U+2074,
    U+20A0-20C0, U+2113, U+2122, U+2191-2193, U+2212, U+2215, U+25B6,
    U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url('fonts/jetbrains-mono-var.woff2') format('woff2');
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-02FF, U+0304, U+0308,
    U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2000-206F, U+2074,
    U+20A0-20C0, U+2113, U+2122, U+2191-2193, U+2212, U+2215, U+25B6,
    U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
}


:root {
  --paper: #E8EAE6;
  --surface: #F3F4F1;
  --sunk: #DCDFDA;
  --ink: #0E1417;
  --ink-soft: #333F44;
  --muted: #5A666B;
  --rule: #C9CFC9;
  --rule-soft: #D8DCD6;
  --signal: #0B6156;
  --signal-bg: #D6E3DF;
  --sand: #8A5F26;

  --band-ink: #0B0F12;
  --band-paper: #E6E4DE;
  --band-muted: #8E9599;
  --band-rule: #222A2F;
  --band-sand: #C9974E;

  --display: 'Fraunces', Georgia, serif;
  --body: 'Public Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --measure: 68ch;
  --gutter: clamp(20px, 5vw, 40px);
  --bar: 46px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #0D1114;
    --surface: #141A1E;
    --sunk: #080B0D;
    --ink: #E2E6E3;
    --ink-soft: #BAC3C1;
    --muted: #8A9490;
    --rule: #232C30;
    --rule-soft: #1A2226;
    --signal: #57C3AE;
    --signal-bg: #10241F;
    --sand: #C9974E;
  }
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

.shell { max-width: 1000px; margin: 0 auto; padding: 0 var(--gutter); }
.col { max-width: var(--measure); }

a { color: var(--signal); text-underline-offset: 3px; text-decoration-thickness: 1px; }
a:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; border-radius: 2px; }

h1, h2, h3 { font-family: var(--display); font-weight: 400; font-variation-settings: 'SOFT' 0, 'WONK' 1; text-wrap: balance; }

/* ---------- section bar ---------- */

/* Sticky rather than fixed: it takes part in the flow, so no section has to be
   padded out from under it, and it still pins the moment the page scrolls.
   Opaque rather than translucent because it crosses the dark prototypes band. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
}

.bar { display: flex; align-items: stretch; justify-content: space-between; gap: 14px; height: var(--bar); }

/* The bar has two children, so the CTA and the language toggle travel together
   on the right rather than being appended into the scrolling tab strip, where
   they would scroll out of sight on a phone. */
.bar-end { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }

.cta {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 5px 10px;
  white-space: nowrap;
  transition: color .15s ease, border-color .15s ease;
}
.cta:hover { border-color: var(--signal); color: var(--signal); }

@media (prefers-reduced-motion: reduce) { .cta { transition: none; } }

/* A keyboard user should not have to tab the whole bar to reach the content. */
.skip {
  position: absolute;
  left: -9999px;
  z-index: 30;
  font-family: var(--mono);
  font-size: 12px;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--signal);
  padding: 10px 14px;
}
.skip:focus { left: var(--gutter); top: 8px; }

/* Six labels do not fit a phone, so the strip scrolls inside itself. That keeps
   the document's own scrollWidth honest — a bar that widened the page would put
   a horizontal scrollbar under every section on the site, not just this one. */
.sections {
  display: flex;
  gap: 2px;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.sections::-webkit-scrollbar { display: none; }

/* The scrollbar is hidden, so a fade is the only thing left saying there are
   more tabs past the edge — and it has to be on the side that actually has
   them, or it is decoration. A zero-width stop is no fade at all, so the two
   ends are just a variable each and the script sets which one is live. */
.sections.scrolls {
  --fade-l: 0px;
  --fade-r: 0px;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent);
  mask-image: linear-gradient(to right, transparent, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent);
}
.sections.scrolls.more-left { --fade-l: 26px; }
.sections.scrolls.more-right { --fade-r: 26px; }

/* Six labels cannot be made to fit 265px of phone — 40 characters of 11.5px
   JetBrains Mono is ~276px of glyph advance before any padding. So the strip
   scrolls, and this says so: a fade alone reads as a tab that got cut off,
   which looks like a bug rather than like an affordance. */
.sections.scrolls.more-right::after {
  content: "›";
  position: sticky;
  right: 0;
  align-self: center;
  flex: 0 0 auto;
  padding-left: 6px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1;
}

.sections a {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 1;
  padding: 0 10px;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  transition: color .15s ease, border-color .15s ease;
}

.sections a:hover { color: var(--ink); }
.sections a:focus-visible { outline: 2px solid var(--signal); outline-offset: -3px; border-radius: 2px; }

/* Where you are, not just where you can go. A bar that never answers the first
   question is a menu rather than a position. */
.sections a[aria-current] { color: var(--signal); border-bottom-color: var(--signal); }

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

/* Anchors land under a bar that is 46px tall unless every target says so. */
section[id], .dive[id] { scroll-margin-top: calc(var(--bar) + 12px); }

/* ---------- language switch ---------- */

/* Rides in the bar, so it stays reachable from the bottom of a long page
   without being a second thing floating over the top of one. One button, not
   two: it shows the language you would be switching TO, which is the only
   thing a reader needs from it, and it gives 36px back to a strip that is
   already narrower than its own tabs on a phone. */
.lang-toggle {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .1em;
  line-height: 1;
  padding: 6px 9px;
  border: 1px solid transparent;
  border-radius: 2px;
  background: var(--signal-bg);
  color: var(--signal);
  cursor: pointer;
  transition: color .15s ease, background .15s ease;
}

.lang-toggle:hover { border-color: var(--signal); }
.lang-toggle:focus-visible { outline: 2px solid var(--signal); outline-offset: 1px; }

@media (prefers-reduced-motion: reduce) { .lang-toggle { transition: none; } }

.eyebrow {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}

/* ---------- masthead ---------- */

.masthead { padding: clamp(60px, 12vh, 120px) 0 clamp(48px, 9vh, 88px); }

.masthead h1 {
  font-size: clamp(40px, 6.4vw, 74px);
  line-height: 1.02;
  letter-spacing: -.025em;
  margin: 18px 0 26px;
}

.masthead .lede {
  font-size: clamp(19px, 2.3vw, 23px);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 34ch;
  margin: 0 0 34px;
}

.masthead .lede b { color: var(--ink); font-weight: 600; }

/* The right half of the first screen was empty at any desktop width. It now
   carries the summary layer the page had no version of — so a reader who will
   not scroll 23 screens still learns what is on it, and can reach either deep
   dive in one click. The h1 must span both tracks or it squeezes into the
   first one and rewraps. */
/* Two columns below the headline: the pitch on the left, the page's own index
   on the right. Each column is one grid item — the left one wraps its three
   parts — so neither can be stretched to match the other's height. */
.masthead .shell { display: grid; gap: 0 48px; align-items: start; }

@media (min-width: 900px) {
  .masthead .shell { grid-template-columns: 1fr 1fr; }
  .masthead .eyebrow, .masthead h1 { grid-column: 1 / -1; }
  .index { margin: 6px 0 0; }
}

.index { list-style: none; padding: 0; margin: 0 0 30px; display: grid; gap: 14px; align-content: start; }
.index li { display: grid; }
.index a {
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  justify-self: start;
}
.index a:hover { border-bottom-color: var(--signal); }
.index span { font-size: 14px; color: var(--muted); line-height: 1.45; }

.contact { display: flex; flex-wrap: wrap; gap: 10px 22px; font-family: var(--mono); font-size: 13.5px; }
.contact a { text-decoration: none; border-bottom: 1px solid var(--rule); padding-bottom: 2px; }
.contact a:hover { border-bottom-color: var(--signal); }

.avail { color: var(--muted); font-size: 15px; max-width: 46ch; margin: 18px 0 0; }

/* The end of Selected work is where a reader has just finished an argument. */
.ask { margin: 44px 0 0; color: var(--muted); max-width: 74ch; }

/* ---------- section frame ---------- */

section { padding: clamp(56px, 9vh, 96px) 0; border-top: 1px solid var(--rule); }

.section-title { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; }
.section-title h2 { font-size: clamp(27px, 3.4vw, 36px); letter-spacing: -.02em; margin: 0; }
.section-intro { color: var(--muted); max-width: 60ch; margin: 0 0 44px; }

/* ---------- work entries ---------- */

.work { display: flex; flex-direction: column; gap: 52px; }

.entry { display: grid; grid-template-columns: 130px 1fr; gap: 0 32px; }
/* A grid track's automatic minimum is its content's min-content width, which a
   row of screenshots pushes past the viewport. Let the column shrink instead. */
.entry > div { min-width: 0; }

.rail { font-family: var(--mono); font-size: 11.5px; line-height: 1.9; color: var(--muted); }
/* No opacity here. Composited, --muted at .75 came to 3.03:1 — and these are the
   words (ROLE, AUTHORSHIP, TESTS) that turn a number into a claim, so they were
   the faintest text on the page and the most load-bearing. Caps, tracking, the
   mono face and 11px against a 17px body already say "subordinate". */
.rail dt { letter-spacing: .1em; text-transform: uppercase; font-size: 11px; color: var(--muted); }
.rail dd { margin: 0 0 12px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

.entry h3 { font-size: 26px; letter-spacing: -.015em; margin: 0 0 4px; }

/* Subheads were 14px against a 17px body — smaller than the paragraphs they
   head. The scan structure is bought with a hairline rather than with size,
   because .pull is already display-face at 19px and matching it would make a
   section heading typographically identical to its own epigraph. */
.entry h4, .dive h4 {
  font-family: var(--body);
  font-size: 15.5px;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--ink);
  margin: 34px 0 12px;
  padding-top: 16px;
  border-top: 1px solid var(--rule-soft);
}
.entry h4:first-of-type, .dive h4:first-of-type {
  border-top: 0;
  padding-top: 0;
  margin-top: 26px;
}

/* This line says what the project IS. It was set at 12px mono in the accent
   colour, i.e. styled exactly like the metadata labels above it, so it read as
   a tag rather than as the sentence it is. */
.entry .role {
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.5;
  color: var(--ink-soft);
  letter-spacing: 0;
  margin: 0 0 18px;
  max-width: 52ch;
}
.entry p { margin: 0 0 15px; max-width: var(--measure); }
.entry p:last-of-type { margin-bottom: 0; }

/* ---------- application screenshots ---------- */

/* These are product UI, not artwork: shown at a size you can read a label at,
   captioned, and never stretched to fill a box they were not shot for. */
.shots { margin: 26px 0 0; padding: 0; }

.shots .plate {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-start;
}

/* 420px is chosen, not arbitrary: two of them plus the gap exceed the content
   column, so a pair of desktop shots wraps to one per row and each gets the
   full width — enough to read a column heading. A desktop shot beside the
   200px phone shot still fits on one line, which is the layout that pair wants. */
/* Each shot is wrapped in an <a> to its 2x export. display:contents keeps the
   anchor out of the flex layout entirely, so the img still lays out as if the
   wrapper were not there. */
.shots .plate > a { display: contents; }

.shots img {
  flex: 1 1 420px;
  min-width: 0;
  max-width: 100%;
  width: auto;
  height: auto;
  display: block;
  /* --rule against --paper is 1.31:1, so a white-backgrounded app screenshot
     had effectively no edge and bled into the page. */
  border: 1px solid var(--muted);
  box-shadow: 0 1px 3px rgb(14 20 23 / .10);
  background: var(--sunk);
  border-radius: 2px;
  cursor: zoom-in;
}

.shots .plate > a:focus-visible img { outline: 2px solid var(--signal); outline-offset: 3px; }

/* A phone shot let into the same flex row would be stretched to a desktop
   width and end up the tallest thing on the page. Pin it to its own size. */
.shots img.portrait { flex: 0 0 auto; width: 200px; }
/* Same reasoning for a near-square capsule: at column width it would be the
   tallest thing on the page and outweigh the screenshot that earns its place. */
.shots img.square { flex: 0 0 auto; width: 230px; }

/* Caption above the plate. These images run up to 1,480px tall, so a caption
   underneath landed a full screen below the thing it was describing — and
   several of them tell you which pixel to look at. */
.shots figcaption {
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--muted);
  margin: 0 0 12px;
}

.pull {
  font-family: var(--display);
  font-size: 19px;
  line-height: 1.45;
  color: var(--ink);
  border-left: 2px solid var(--signal);
  padding-left: 18px;
  margin: 0 0 18px;
  max-width: 52ch;
}

.stack { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 18px; }
/* --muted on --sunk was 4.40:1, a fail — on the one row a recruiter's eye
   scans for keywords before reading a word of prose. --ink-soft is 8.06:1. */
.stack li {
  list-style: none;
  font-family: var(--mono);
  font-size: 11.5px;
  padding: 3px 9px;
  background: var(--sunk);
  color: var(--ink-soft);
  border-radius: 2px;
}
.stack { padding: 0; }

/* Not held to the prose measure. `ch` is relative to the element's own font, so
   68ch of mono is ~530px — narrow enough to break a row of three short links
   onto three lines and leave the entry looking like it ends in a list. */
.links { display: flex; flex-wrap: wrap; gap: 10px 22px; margin-top: 20px; font-family: var(--mono); font-size: 13px; max-width: none; }

.tag-private {
  display: inline-block;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--rule);
  padding: 2px 7px;
  vertical-align: middle;
  margin-left: 10px;
}

/* ---------- skills ---------- */

/* The same 130px rail as .entry, so a group label sits in the column every meta
   block above uses. This section is an index of those entries, not a new kind
   of thing, and lining the two up is what says so. */
.skills { margin: 0; }
.skills > div { display: grid; grid-template-columns: 130px 1fr; gap: 0 32px; }
.skills > div + div { margin-top: 22px; padding-top: 22px; border-top: 1px solid var(--rule-soft); }

.skills dt {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 5px;
}

.skills dd { margin: 0; min-width: 0; }
.skills .stack { margin-top: 0; }

.skills .where {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--muted);
  margin: 12px 0 0;
  max-width: 62ch;
}

/* ---------- prototypes band ---------- */

.band { background: var(--band-ink); color: var(--band-paper); border-top: 0; }
.band .section-title h2 { color: var(--band-paper); }
.band .eyebrow, .band .section-intro { color: var(--band-muted); }
.band .section-intro { max-width: 62ch; }

.slices { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 28px; }

.slice { display: flex; flex-direction: column; }

.frame {
  position: relative;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--band-rule);
  background: #05080A;
  overflow: hidden;
}

.frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.frame iframe { width: 100%; height: 100%; border: 0; display: block; }

.play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  background: linear-gradient(to top, rgba(5, 8, 10, .82), rgba(5, 8, 10, .15));
  border: 0;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--band-paper);
  transition: background .25s ease;
}
.play:hover, .play:focus-visible { background: linear-gradient(to top, rgba(5, 8, 10, .7), rgba(5, 8, 10, .05)); }
.play span { border: 1px solid var(--band-sand); color: var(--band-sand); padding: 7px 15px; }
.play[hidden] { display: none; }

/* The way back out of a game that is entitled to swallow every key it gets.
   It sits outside .frame so the iframe cannot cover it. */
.stop {
  align-self: flex-start;
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  background: transparent;
  border: 1px solid var(--band-sand);
  color: var(--band-sand);
  border-radius: 2px;
  padding: 6px 12px;
  cursor: pointer;
}
.stop:hover { background: var(--band-sand); color: var(--band-ink); }
.stop:focus-visible { outline: 2px solid var(--band-sand); outline-offset: 2px; }
.stop[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) { .play { transition: none; } }

.slice h3 { font-size: 20px; margin: 18px 0 3px; color: var(--band-paper); }
.slice .kicker { font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--band-sand); margin: 0 0 10px; }
.slice p { margin: 0; color: var(--band-muted); font-size: 15px; line-height: 1.6; }
.slice .links { color: var(--band-muted); }
.slice .links a { color: var(--band-sand); }

/* ---------- deep dives ---------- */

.dive { max-width: var(--measure); }
.dive + .dive { margin-top: 64px; padding-top: 56px; border-top: 1px solid var(--rule-soft); }
.dive h3 { font-size: 25px; letter-spacing: -.015em; margin: 0 0 6px; }
.dive .kicker { font-family: var(--mono); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin: 0 0 20px; }
.dive p { margin: 0 0 16px; }
.dive h4 { font-family: var(--body); font-size: 15px; font-weight: 600; margin: 28px 0 10px; letter-spacing: .005em; }

pre {
  background: var(--sunk);
  border: 1px solid var(--rule-soft);
  border-left: 2px solid var(--signal);
  padding: 16px 18px;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.65;
  margin: 0 0 18px;
}

code { font-family: var(--mono); font-size: .88em; }
p code, li code { background: var(--sunk); padding: 1.5px 5px; border-radius: 2px; }

.dive ol, .dive ul { padding-left: 22px; margin: 0 0 18px; }
.dive li { margin-bottom: 10px; }
.dive li::marker { color: var(--signal); }

.aside {
  background: var(--surface);
  border: 1px solid var(--rule-soft);
  padding: 18px 20px;
  font-size: 15.5px;
  color: var(--ink-soft);
  margin: 0 0 18px;
}
.aside p:last-child { margin-bottom: 0; }

/* ---------- footer ---------- */

footer { border-top: 1px solid var(--rule); padding: 44px 0 72px; font-family: var(--mono); font-size: 12.5px; color: var(--muted); }
footer p { margin: 0 0 8px; }

/* ---------- responsive ---------- */

@media (max-width: 720px) {
  body { font-size: 16.5px; }

  /* The rail used to collapse to a label-less run of values that also landed
     ABOVE the project name — so a phone reader's first line was
     "Lead developer · Jan – Jun 2026 · 331 of 406 commits", uncaptioned, with
     nothing yet saying what it described. "305, all green" without its TESTS
     label means nothing at all. Order it after the prose and keep the labels:
     each pair is a <div>, so the two columns break cleanly. */
  .entry { display: flex; flex-direction: column; }
  .rail {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 12px 20px;
    margin: 26px 0 0;
    padding-top: 20px;
    border-top: 1px solid var(--rule-soft);
    order: 2;
  }
  .rail dt { display: block; }
  .rail dd { margin: 0; }

  .skills > div { grid-template-columns: 1fr; }
  .skills dt { padding-top: 0; margin-bottom: 9px; }

  /* Stacked under the pitch rather than beside it, so it needs a gap of its own
     — otherwise the first project title sits straight onto the availability line. */
  .index { margin: 34px 0 26px; padding-top: 26px; border-top: 1px solid var(--rule-soft); }
}

/* Two desktop shots side by side at 390px are 350px each and unreadable. One
   of them, bled to the full viewport width, is 390px and legible. */
@media (max-width: 480px) {
  .shots .plate > a:nth-of-type(n+2) { display: none; }
  .shots .plate { margin-inline: calc(var(--gutter) * -1); }
  .shots img { border-left: 0; border-right: 0; border-radius: 0; }
}
