/* ═══════════════════════════════════════════════════════════════════════
 * michaelpantano.com
 *
 * One page. Zero JavaScript, as an invariant rather than a preference:
 * every effect here is CSS, which makes Cloudflare's Rocket Loader and
 * Email Obfuscation structurally unable to break anything.
 *
 * The signature is the trace. A hairline runs from the rule under the name,
 * down the gutter, through a via at each section, and stops at the contact
 * block. It marks the sequence the page actually is. Traces turn at 45
 * degrees on a real board, so the terminus does too.
 * ═══════════════════════════════════════════════════════════════════════ */


/* ─────────────────────────────────────────────────────────── 1. fonts */

/* block, not swap: the name is set at up to 144px, where a Georgia-to-serif
   swap is a visible reflow and a measurable layout shift. Both files are
   preloaded in the head, so the block period effectively never elapses. */
@font-face {
  font-family: "Instrument Serif";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("/assets/fonts/InstrumentSerif-400.woff2") format("woff2");
}
@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: block;
  src: url("/assets/fonts/InstrumentSerif-400-italic.woff2") format("woff2");
}
/* swap is right for the text faces: small type, and a fallback flash costs
   less than blocking the paint. */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/PlexMono-400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/PlexSans-400.woff2") format("woff2");
}


/* ────────────────────────────────────────────────────────── 2. tokens */

:root {
  /* Ratios are measured against --bg, not estimated. */
  --bg:      #14120F;                 /* warm near-black */
  --bg-lift: #1C1915;                 /* fills only, never a hairline */
  --hair:    rgba(240, 235, 224, .09);/* separators; survives a bg change */
  --hair-2:  rgba(240, 235, 224, .16);

  --ink:   #F0EBE0;                   /* 15.7:1  display name, headings */
  --ink-b: #DDD5C6;                   /* 12.8:1  the italic second line */
  --ink-2: #C9BFAE;                   /* 10.3:1  body prose */
  --ink-3: #948977;                   /*  5.4:1  mono microlabels, dates */

  /* One accent. #C2913F was the draft, and it is the alias brand's current
     working gold; #B0682B was the other candidate and measures 4.3:1, which
     fails AA on the smallest and most letterspaced text on the page. This
     value clears both problems at 7.8:1 and separates cleanly from skin tone,
     so it still reads as a signal next to the portrait. */
  --accent: #D89B4A;

  --serif: "Instrument Serif", Georgia, "Times New Roman", serif;
  --sans:  "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --mono:  "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --measure: 60ch;
  /* max() keeps the gutter clear of the notch in landscape without shrinking
     it in portrait, where the inset is 0. */
  --gutter: max(1.5rem, env(safe-area-inset-left));
  /* How far LEFT of the content column the trace runs. It lives in the margin,
     not in the column: at gutter + 4px it collided with the row dates, which
     start at exactly the gutter, and the line ran straight through
     "SUMMER 2026". Only the section labels had padding to clear it. */
  --trace-inset: 1.05rem;

  --dur: 620ms;
  --ease: cubic-bezier(.22, 1, .36, 1);
}


/* ──────────────────────────────────────────────────── 3. reset + base */
/* Adapted from projects/personal_website/styles.css:186-260, deliberately
   WITHOUT its `-webkit-font-smoothing: antialiased`. On macOS that renders
   type thinner, and applied to a display serif on a near-black ground it
   makes a perfectly solid face look anemic. */

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

html, body { margin: 0; padding: 0; }

html {
  color-scheme: dark;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  touch-action: manipulation;          /* kills the 300ms tap delay */
  -webkit-tap-highlight-color: rgba(216, 155, 74, .18);
  scroll-behavior: smooth;
}

body {
  background: var(--bg);
  color: var(--ink-2);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.6;
  min-height: 100svh;
  overflow-x: hidden;
}

h1, h2, h3, p, ul, ol, figure { margin: 0; }
ul, ol { list-style: none; padding: 0; }
img { display: block; max-width: 100%; height: auto; }

/* A long course title or a pasted URL must never push the page sideways. */
p, li, h1, h2, h3 { overflow-wrap: break-word; }
/* Except the name. Breaking "Pantano" across lines as "Pa / nta / no" is worse
   than any overflow this could cause, and the rule above will happily do it
   once the column gets narrow enough. */
.nameplate, .nameplate span { overflow-wrap: normal; word-break: keep-all; }
h1, h2, h3 { text-wrap: balance; }
p { text-wrap: pretty; }

/* The in-page nav links land on these. Without it a section label sits flush
   against the top edge of the viewport. */
section[id] { scroll-margin-top: 2rem; }

::selection { background: var(--accent); color: var(--bg); }

a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 180ms var(--ease), border-color 180ms var(--ease);
}

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

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: -200px;
  z-index: 100;
  padding: .6rem 1rem;
  background: var(--accent);
  color: var(--bg);
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  transition: top 180ms var(--ease);
}
.skip-link:focus { top: var(--gutter); }

.dot { color: var(--ink-3); padding: 0 .15em; }
.nowrap { white-space: nowrap; }


/* ───────────────────────────────────────────── 4. shared micro-styles */

.band-label,
.row-date,
.col-label,
.spec dt,
.wordmark,
.code,
.foot p {
  font-family: var(--mono);
  font-size: .6875rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
}


/* ────────────────────────────────────────────────────────── 5. topbar */

.topbar {
  position: relative;
  z-index: 3;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.4rem var(--gutter);
}

/* Mono, not serif. A serif wordmark with an italic surname is the reference
   site's mark; this one belongs to the page's microlabel system instead.
   Hidden on phones, where the name is set at 60px directly beneath it and the
   wordmark both says nothing new and collides with the nav. */
.wordmark {
  display: none;
  color: var(--ink-3);
  white-space: nowrap;
}
.wordmark:hover { color: var(--ink); }

.topnav { display: flex; gap: 1.1rem; }
.topnav a {
  font-family: var(--mono);
  font-size: .625rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.topnav a:hover { color: var(--ink); }


/* ──────────────────────────────────────────────────────────── 6. hero */

.hero {
  position: relative;
  min-height: calc(100svh - 5rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2.75rem;
  padding: 2.5rem var(--gutter) 3rem;
}

/* Both lines roman, both in --ink, size-stepped so the surname carries the
   weight. The reference site sets its second line in dimmed italic; that is
   its single most recognisable move and this page does not borrow it. Stepping
   by size instead also puts the emphasis on the name people actually search. */
.nameplate {
  font-family: var(--serif);
  font-weight: 400;
  line-height: .9;
  letter-spacing: -.015em;
  color: var(--ink);
}
.nameplate span { display: block; }
.given {
  font-size: clamp(2.4rem, 13vw, 4.6rem);
  color: var(--ink-b);
}
.family {
  font-size: clamp(3.6rem, 20vw, 7.2rem);
  margin-top: -.04em;
}

/* The trace begins here. */
.trace-origin {
  width: 4.5rem;
  height: 1px;
  background: var(--accent);
  margin: 1.7rem 0 1.4rem;
}

.hero-main .lede {
  margin-bottom: .9rem;
  max-width: 30ch;
}
.hero-note {
  font-size: .9375rem;
  color: var(--ink-2);
  max-width: 46ch;
}

/* ── the spec card ─────────────────────────────────────────────────────
   Same label/value grid as the coursework table and the work rows, so the
   page has one data language rather than a prose hero sitting above tabular
   sections. */

/* Left-aligned in the stacked layout, where the card is full width and the
   reading order runs down the left edge. It gets right-aligned in the split
   layout only; see the 1060px block for why. */
.portrait { display: flex; margin-bottom: 1.6rem; }
.portrait img {
  width: 112px;
  height: 112px;
  border-radius: 50%;
  /* No accent ring. Decorating the avatar spends the accent on the least
     important element and undercuts the premise that the face confirms the
     name rather than competing with it. A hairline is enough to give the
     disc an edge against the ground. */
  box-shadow: inset 0 0 0 1px var(--hair-2);
}

.spec { margin: 0; }
.spec-row {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: .25rem 1rem;
  padding: .6rem 0;
  border-top: 1px solid var(--hair);
  align-items: baseline;
}
.spec-row:first-child { border-top: 0; padding-top: 0; }
.spec dt { color: var(--ink-3); margin: 0; }
.spec dd {
  margin: 0;
  font-size: .9375rem;
  color: var(--ink-2);
}


/* ─────────────────────────────────────────────────────────── 7. bands */

.band {
  position: relative;
  padding: 2.75rem var(--gutter);
  border-top: 1px solid var(--hair);
}

.band-label {
  position: relative;
  font-weight: 400;
  color: var(--ink-3);
  margin-bottom: 1.5rem;
}

/* A via: an annular ring with the board showing through the middle, which is
   what a via looks like from the top. Sits on the trace, out in the margin, so
   the label text stays flush with everything else in the column. */
.via {
  position: absolute;
  left: calc(-1 * var(--trace-inset) - 4px);
  top: .18em;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid var(--accent);
  background: var(--bg);
}
.via::after {
  content: "";
  position: absolute;
  inset: 2px;
  border-radius: 50%;
  background: var(--accent);
  opacity: .28;
}

/* The trace itself: the segment of hairline that connects this section's via
   to the next one down. The last band terminates instead of continuing. */
.band::before {
  content: "";
  position: absolute;
  left: calc(var(--gutter) - var(--trace-inset));
  top: calc(2.75rem + .18em + 9px);
  bottom: 0;
  width: 1px;
  background: var(--accent);
  opacity: .3;
}
.band-last::before { bottom: auto; height: 1.6rem; }

/* The 45 degree terminus. A trace does not stop dead and it does not turn a
   right angle; it breaks out at 45 and ends on a pad. */
.band-last::after {
  content: "";
  position: absolute;
  left: calc(var(--gutter) - var(--trace-inset));
  top: calc(2.75rem + .18em + 9px + 1.6rem);
  width: 12px;
  height: 12px;
  border-left: 1px solid var(--accent);
  border-bottom: 1px solid var(--accent);
  opacity: .3;
  transform: skewX(-45deg);
  transform-origin: top left;
}

/* One width for every band. Deliberately NOT keyed to section ids.
 *
 * This used to be `.band-body { max-width: var(--measure) }` with an
 * `#work .band-body, #courses .band-body { max-width: 78rem }` override. That
 * coupled the layout to the section id across two files that cache
 * independently: index.html revalidates on every load, styles.css was cached
 * for hours. Renaming the section to #experience shipped both changes at once,
 * but any browser holding the older stylesheet matched neither selector, fell
 * back to the 60ch measure, and collapsed the three-column row until each note
 * wrapped one character per line.
 *
 * The 60ch measure was vestigial anyway: the About band moved into the hero,
 * so no band contains prose. Every band now holds a table, a grid, or a link
 * list, and all of them want the same generous width. No override, no id
 * dependency, no way for a stale stylesheet to break the layout. */
.band-body { max-width: 78rem; }

.lede {
  font-family: var(--serif);
  font-size: clamp(1.4rem, 4.6vw, 1.85rem);
  line-height: 1.32;
  color: var(--ink);
  margin-bottom: 1rem;
}
.band-body p + p { margin-top: .9rem; }


/* ─────────────────────────────────────────────────────── 8. work rows */

.rows { max-width: none; }

.row {
  padding: 1.4rem 0;
  border-top: 1px solid var(--hair);
}
.row:first-child { border-top: 0; padding-top: 0; }

.row-date {
  color: var(--ink-3);
  margin-bottom: .5rem;
  transition: color 180ms var(--ease);
}
.row:hover .row-date { color: var(--accent); }

.row-title {
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 400;
  line-height: 1.2;
  color: var(--ink);
}

.row-role {
  font-size: .875rem;
  color: var(--ink-3);
  margin-top: .1rem;
}

.row-note {
  margin-top: .6rem;
  max-width: var(--measure);
  font-size: .9375rem;
}


/* ──────────────────────────────────────────── 9. courses + interests */

.col-label {
  color: var(--accent);
  margin-bottom: .85rem;
}
.col-label-2 { margin-top: 2rem; }

.courses li {
  display: grid;
  grid-template-columns: 6.5rem 1fr;
  gap: .5rem;
  padding: .42rem 0;
  border-top: 1px solid var(--hair);
  align-items: baseline;
}
.courses li:first-child { border-top: 0; }
.code { color: var(--ink-3); }
.course { font-size: .9375rem; color: var(--ink-2); }

.interests li {
  padding: .42rem 0;
  border-top: 1px solid var(--hair);
  font-size: .9375rem;
}
.interests li:first-child { border-top: 0; }

.split { max-width: none; }
/* Stacked on phones, so the second column needs its own separation. The grid
   gap takes over at 760px and this is reset there. */
.split .col + .col { margin-top: 2.75rem; }


/* ──────────────────────────────────────────────────────── 10. contact */

.links { display: flex; flex-direction: column; gap: .1rem; }
.links a {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .45rem 0;
  font-size: 1.0625rem;
  color: var(--ink-2);
}
.links a:hover { color: var(--ink); border-bottom-color: var(--accent); }
.mail { font-family: var(--mono); font-size: .9375rem; }

/* Inline, so it inherits currentColor and cannot fall back to a system font.
   U+2197 is not in the latin subset these faces ship with. */
.ext { width: .7em; height: .7em; flex: none; opacity: .65; }


/* ───────────────────────────────────────────────────────── 11. footer */

.foot {
  display: flex;
  gap: .4rem;
  padding: 2rem var(--gutter) 2.5rem;
  border-top: 1px solid var(--hair);
  color: var(--ink-3);
}


/* ───────────────────────────────────────────────────────── 12. motion */

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
.reveal {
  opacity: 0;
  animation: rise var(--dur) var(--ease) forwards;
  animation-delay: calc(var(--i) * 85ms);
}


/* ───────────────────────────────────────────────────── 13. wide (760+) */

@media (min-width: 760px) {
  :root { --gutter: 4rem; }

  .wordmark { display: block; }
  .topbar { padding: 2rem var(--gutter); }
  .topnav { gap: 2rem; }
  .topnav a { font-size: .6875rem; }

  .hero { padding: 3rem var(--gutter); }
  .spec-row { grid-template-columns: 6rem 1fr; }

  .band { padding: 4rem var(--gutter); }
  .band-label { margin-bottom: 2rem; }
  .band::before { top: calc(4rem + .18em + 9px); }
  .band-last::after { top: calc(4rem + .18em + 9px + 1.6rem); }

  /* Two columns here: date, then everything else stacked. The three-column
     version needs ~1020px and is in the 1060px block. Running it from 760px
     left the note track about 32px wide, which wrapped every note to one word
     per line and made the page 5,977px tall at 768. */
  .row {
    display: grid;
    grid-template-columns: 9rem minmax(0, 1fr);
    gap: 0 2rem;
    padding: 1.6rem 0;
    align-items: start;
  }
  .row-date { margin-bottom: 0; padding-top: .45rem; }

  .split {
    display: grid;
    grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
    gap: 4rem;
  }
  .split .col + .col { margin-top: 0; }

  .courses li { grid-template-columns: 7rem 1fr; }
}

/* ───────────────────────────────── 13b. the split hero (1060px and up)

   The hero needs its own, higher breakpoint. The split wants roughly
   36rem + 19rem + a 3rem gap + two 4rem gutters = ~1056px. Running it from
   760px like the rest of the layout squeezed the left track to about 150px
   and the browser broke the name mid-word into "Mich / ael / Pa / nta / no".
   Below this width the hero simply stays stacked, which is what a tablet
   should get anyway. */

@media (min-width: 1060px) {
  .hero {
    display: grid;
    /* Fixed-ish tracks with space-between rather than fr ratios. With fr the
       left track came out ~770px while its text capped at ~410px, which put a
       hole in the middle of the composition. Sizing the track to roughly the
       width of the nameplate and letting the free space fall between the two
       columns fixes it: the column IS the measure, so nothing floats. */
    grid-template-columns: minmax(0, 36rem) minmax(19rem, 30rem);
    justify-content: space-between;
    gap: 3rem;
    /* align-content centres the single row track inside the tall hero;
       align-items alone parks everything at the bottom and leaves the top
       half of the viewport empty. */
    align-content: center;
    align-items: start;
  }

  .nameplate { letter-spacing: -.025em; }
  .hero-main .lede,
  .hero-note { max-width: none; }

  /* Optically align the card's first rule with the top of the nameplate. */
  .hero-card { padding-top: .5rem; }
  /* Right-aligned so the disc's right edge lands on the end of the spec table's
     rules. Left-aligned it only met the label column, which is a weak
     alignment, so the circle read as floating beside the card rather than
     belonging to it. It also balances diagonally against the nameplate. */
  .portrait { justify-content: flex-end; margin-bottom: 1.5rem; }
  .portrait img { width: 152px; height: 152px; }

  /* Three columns: date, title+role, note. Needs ~1020px, hence this block
     rather than the 760px one. */
  .row {
    grid-template-columns: 11rem minmax(0, 20rem) minmax(0, 1fr);
    /* The note spans both rows. Without an explicit track sizing, grid splits
       any extra height the note needs across BOTH rows, which pushes the role
       away from its title by a distance that varies with note length. Sizing
       row 1 to content and giving row 2 the slack keeps the title and role
       locked together no matter how long the note runs. */
    grid-template-rows: auto 1fr;
    gap: 0 2.5rem;
  }
  .row-date { padding-top: .5rem; }
  .row-main { display: contents; }
  .row-title { grid-column: 2; }
  .row-role  { grid-column: 2; }
  .row-note  { grid-column: 3; grid-row: 1 / span 2; margin-top: .4rem; }
}

@media (min-width: 1180px) {
  :root { --gutter: 6rem; }
  .hero { padding-bottom: 4rem; }
}


/* ───────────────────────────────────────────── 14. reduced motion */

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


/* ─────────────────────────────────────────────────────────── 15. print */

@media print {
  :root { --bg: #fff; --ink: #000; --ink-2: #222; --ink-3: #555; --accent: #555; }
  body { background: #fff; color: #000; }
  .topnav, .skip-link, .portrait, .via, .trace-origin { display: none; }
  .band, .row, .courses li, .interests li { border-color: #ccc; }
  .links a::after { content: " (" attr(href) ")"; font-size: .75rem; color: #555; }
  .reveal { opacity: 1; animation: none; }
}
