/* ============================================================
   Aiden Bodily — the darkroom
   One roll of 35mm, developing. Near-black room, purple
   safelight, real grain. Content enters as a latent image
   and develops as you scroll.
   Serif voiceover, mono frame numbers.
   Spacing locked to an 8px grid.
   ============================================================ */

:root {
  --space-1: 0.5rem;   /* 8  */
  --space-2: 1rem;     /* 16 */
  --space-3: 1.5rem;   /* 24 */
  --space-4: 2rem;     /* 32 */
  --space-5: 2.5rem;   /* 40 */
  --space-6: 3rem;     /* 48 */
  --space-7: 3.5rem;   /* 56 */
  --space-9: 4.5rem;   /* 72 */
  --space-12: 6rem;    /* 96 */

  --bg: #060503;
  --bg-vignette: radial-gradient(130% 100% at 50% 0%, #0a0805 0%, #060503 55%, #010100 100%);
  --ink: #f0e8d8;
  --muted: #b3a996;
  --faint: #7d7463;
  --line: #2b251c;
  --wash: #0d0b08;
  --accent: #7920f5;
  --accent-glow: rgba(121, 32, 245, 0.5);
  --header-bg: rgba(6, 5, 3, 0.85);

  /* Palatino for the voice, Courier Prime for the darkroom notations —
     a typewriter face fits a roll of film better than a terminal face */
  --font-serif: Palatino, "Palatino Linotype", "Book Antiqua", Georgia, serif;
  --font-mono: "Courier Prime", ui-monospace, "SF Mono", Menlo, monospace;
}

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

html { scroll-behavior: smooth; }

/* nothing on this page should ever scroll sideways; clip is the
   guarantee, hidden the fallback (clip preserves position:sticky) */
html, body { overflow-x: hidden; overflow-x: clip; }

body {
  margin: 0;
  position: relative; /* anchors the end-of-roll burn to the page bottom */
  font-family: var(--font-serif);
  font-size: 1.06rem;
  line-height: 1.75;
  color: var(--ink);
  background-color: var(--bg);
  background-image: var(--bg-vignette);
  background-attachment: fixed;
  transition: background-color 0.3s ease, color 0.3s ease;
}

/* fine synthetic grain over everything, unblended — raw speckle,
   the way the page looked before the scan arrived. Overlay math
   on a near-black page would mute this to nothing, so no blend. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 200;
  pointer-events: none;
  opacity: 0.11;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 240px 240px;
  /* the scan never sits quite still: the tile jumps a few times a
     second, hard cuts only */
  animation: grain-settle 0.9s steps(1) infinite;
}
@keyframes grain-settle {
  0%   { background-position: 0 0; }
  25%  { background-position: 90px 140px; }
  50%  { background-position: -70px 60px; }
  75%  { background-position: 130px -90px; }
  100% { background-position: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  body::after { animation: none; }
}

/* the big organic clumps: a real scan of Kodak Tri-X 400 (CC0,
   Wikimedia Commons), overlay-blended so the mid-gray base drops
   out and only the grain shows, strongest over lighter content */
.grain-scan {
  position: fixed;
  inset: 0;
  z-index: 198;
  pointer-events: none;
  opacity: 0.6;
  mix-blend-mode: overlay;
  background-image: url("images/grain.jpg");
}

h1, h2, h3 { line-height: 1.25; margin: 0; font-weight: 700; }
p { margin: 0; }
ul { margin: 0; }
a { color: var(--accent); text-decoration-color: rgba(121, 32, 245, 0.5); text-underline-offset: 3px; }
a:hover { color: var(--accent); }

.mono {
  font-family: var(--font-mono);
  font-size: 0.8em;
  letter-spacing: 0.01em;
}

/* outline icons, 1.8px stroke, used for the contact links */
.svg-icon {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -2px;
  margin-right: 6px;
}

/* degree subtitle inside an entry header */
.entry-title h4 {
  font-size: 0.92rem;
  font-weight: 400;
  font-style: italic;
  color: var(--muted);
  margin-top: 2px;
}

.container { max-width: 1080px; margin: 0 auto; padding: 0 var(--space-3); }

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--accent);
  color: #fff;
  padding: var(--space-1) var(--space-2);
  z-index: 300;
}
.skip-link:focus { left: 0; }

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

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

/* ============ Developing ============
   Content enters as a latent image — faint, milky — and
   resolves as it scrolls into the tray. */
.develop {
  opacity: 0.1;
  filter: blur(2px) brightness(1.6);
  transition: opacity 0.45s ease, filter 0.45s ease;
  will-change: opacity, filter;
}
.develop.developed {
  opacity: 1;
  filter: none;
}

/* unexposed frames never develop — they haven't happened yet */
.uframe {
  opacity: 0.35;
  filter: blur(1px);
}
.uframe.planned {
  opacity: 0.7;
  filter: none;
}

/* ============ Header / nav ============ */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--header-bg);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
  transition: box-shadow 0.2s ease;
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 64px;
  gap: var(--space-4);
}

.nav-logo {
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  font-size: 1.1rem;
  white-space: nowrap;
}
.nav-logo-dot { color: var(--accent); margin: 0 -0.03em; }
/* the name dots are drawn by CSS so the page text (and crawlers) read
   "Bodily"; the "/ ''" gives screen readers no extra character */
.nav-logo-dot::before,
.hero-name-dot::before {
  content: ".";
  content: "." / "";
}

.nav-links {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav-links a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.85rem;
}
.nav-links a:hover { color: var(--ink); }

.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 6px;
  background: none;
  border: none;
  padding: var(--space-1);
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--ink);
}

/* ============ Hero — frame 1 ============ */
.hero {
  padding: var(--space-12) 0 var(--space-9);
  border-bottom: 1px solid var(--line);
}

.hero-name {
  font-size: clamp(3.2rem, 9vw, 6rem);
  letter-spacing: -0.03em;
  margin-bottom: var(--space-4);
}

.hero-name-dot {
  color: var(--accent);
  text-shadow: 0 0 24px var(--accent-glow);
  /* periods carry sentence-spacing sidebearings — pull the
     neighbors in so the dot reads as part of the word */
  margin: 0 -0.02em;
}

.tagline {
  font-size: clamp(1.15rem, 2.4vw, 1.4rem);
  color: var(--muted);
  max-width: 64ch;
}
.hero-note {
  color: var(--muted);
  font-style: italic;
  font-size: 1.05rem;
  max-width: 56ch;
  margin: var(--space-2) 0 var(--space-6);
}

.hero-eyebrow {
  color: var(--faint);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  margin-bottom: var(--space-3);
}
/* the key facts, stacked: status first and loudest, then how to reach
   me, then where I am */
.hero-meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: 0.82rem;
  margin-bottom: var(--space-5);
}
.hero-meta p {
  margin: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.45em;
}
.hero-meta .meta-status {
  font-size: 0.95rem;
  color: var(--ink);
  /* keep the dot beside the first line when the text wraps on phones */
  flex-wrap: nowrap;
  align-items: flex-start;
}
.meta-status .status-dot { margin-top: 0.6em; }
.meta-soft { color: var(--muted); }
.hero-meta .meta-links { column-gap: 1.5em; }
.meta-links a {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  text-shadow: 0 0 18px var(--accent-glow);
}
.meta-links a:hover { border-bottom-color: var(--accent); }
.meta-loc { color: var(--muted); }
.meta-sep { color: var(--faint); }
.nowrap { white-space: nowrap; }

/* the safelight: a purple lamp that's always on */
.status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
  animation: safelight 2.6s ease-in-out infinite;
}
@keyframes safelight {
  0%, 100% { box-shadow: 0 0 0 0 var(--accent-glow); }
  50% { box-shadow: 0 0 10px 3px var(--accent-glow); }
}

/* ============ Hero stats line (count-up numbers) ============ */
.stat-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.hero-stats {
  color: var(--faint);
  font-size: 1.02rem;
}
.hero-stats .stat-num {
  font-weight: 700;
  color: var(--ink);
}

/* ============ Sections ============ */
.section {
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--line);
}
.section-grid {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: var(--space-12);
}
/* grid children default to min-width:auto, which lets any wide
   descendant stretch the track past the viewport on phones */
.section-grid > * { min-width: 0; }
.section-label {
  font-size: 1.5rem;
  letter-spacing: -0.01em;
  color: var(--ink);
  padding-top: var(--space-1);
  position: sticky;
  top: 96px;
  align-self: start;
  font-weight: 700;
}
.fr {
  display: block;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--accent);
  text-shadow: 0 0 14px var(--accent-glow);
  margin-bottom: var(--space-1);
}
.section-body > p { max-width: 62ch; color: var(--muted); }
.section-body > p + p { margin-top: var(--space-4); }
/* specificity beats ".section-body > p" so the lede stays ink, not muted */
.section-body .lede {
  font-size: 1.25rem;
  color: var(--ink);
  line-height: 1.55;
  margin-bottom: var(--space-5);
}

/* ============ Skills ============ */
.skills-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5) var(--space-12);
}
.skill-group h3 {
  font-size: 1rem;
  margin-bottom: var(--space-1);
  font-style: italic;
  font-weight: 400;
  color: var(--ink);
}
.skill-group p {
  color: var(--muted);
  font-size: 0.98rem;
}

/* ============ Entries (education & experience) ============ */
.entry-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.entry-head h3 { font-size: 1.2rem; letter-spacing: -0.01em; }
.entry-meta { color: var(--faint); white-space: nowrap; }
.entry-sub { color: var(--muted); font-style: italic; font-size: 0.92rem; margin: var(--space-1) 0 var(--space-2); }
.entry p { color: var(--muted); font-size: 0.98rem; max-width: 62ch; }

.timeline {
  padding-left: var(--space-5);
  position: relative;
}
.timeline-item + .timeline-item { margin-top: var(--space-5); }
.timeline::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 8px;
  width: 1px;
  background: var(--line);
}
.timeline-item { position: relative; }
.timeline-item::before {
  content: "";
  position: absolute;
  left: calc(-1 * var(--space-5) - 4px);
  top: 10px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent-glow);
}

.bullets {
  padding-left: var(--space-5);
  color: var(--muted);
  font-size: 0.98rem;
}
.bullets li { margin-bottom: var(--space-1); }
.bullets li:last-child { margin-bottom: 0; }

/* ============ Certifications ============ */
.cert-list { border-top: 1px solid var(--line); }
.cert-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-1);
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: var(--ink);
  transition: background 0.15s ease;
}
.cert-row:hover { background: var(--wash); }
.cert-row-main h3 { font-size: 1.08rem; }
.cert-icon { color: var(--faint); }
.cert-row:hover .cert-icon { color: var(--accent); }
.cert-row-soon { cursor: default; }
.cert-row-soon:hover { background: transparent; }
.cert-row-soon:hover .cert-icon { color: var(--faint); }
/* in-progress certs get the same treatment as in-development
   projects: title sinks to faint, no hover flirtation */
.cert-row-soon h3,
.cert-row-soon .entry-sub { color: var(--faint); }
.cert-row-main .entry-sub { margin-bottom: 0; }
.cert-row-meta { color: var(--faint); white-space: nowrap; }
.cert-row:hover .cert-row-meta { color: var(--accent); }
.cert-row-soon:hover .cert-row-meta { color: var(--faint); }

/* ============ Projects (selected frames) ============ */
.project-disclaimer {
  text-align: center;
  max-width: 52ch;
  margin: 0 auto var(--space-5);
  color: var(--faint);
  font-size: 0.8rem;
}
.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 0;
  border-top: 1px solid var(--line);
  border-left: 1px solid var(--line);
}
.project {
  padding: var(--space-6) var(--space-5);
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  transition: background 0.2s ease;
}
.project:hover { background: var(--wash); }
/* an odd card out spans the row instead of leaving an empty cell */
.project:last-child:nth-child(odd) { grid-column: 1 / -1; }
.project-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: var(--faint);
  margin-bottom: var(--space-5);
}
.project-top a {
  color: var(--accent);
  text-decoration: none;
}
.project-top a:hover { text-decoration: underline; }
.project h3 {
  font-size: 1.35rem;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-2);
}
.project p {
  color: var(--muted);
  font-size: 0.95rem;
  margin-bottom: var(--space-5);
}
.project-stack {
  margin-top: auto;
  color: var(--faint);
  font-size: 0.75rem;
}

/* ============ See more: unexposed frames & writeups ============ */
.hero-stats a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted var(--faint);
  transition: color 0.2s ease, border-color 0.2s ease;
}
.hero-stats a:hover { color: var(--accent); border-bottom-color: var(--accent); }

.more {
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  border-left: 1px solid var(--line);
}
.more-toggle {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  padding: var(--space-5);
  background: none;
  border: 0;
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}
.more-toggle:hover,
.more-toggle[aria-expanded="true"] { background: var(--wash); }
.more-toggle-hint { color: var(--faint); }
.more-icon { color: var(--accent); font-size: 1.1rem; line-height: 1; }
.more-panel {
  border-top: 1px solid var(--line);
  padding: var(--space-6) var(--space-5);
}
.more-frames {
  margin-top: 0;
  margin-bottom: var(--space-5);
  max-width: 420px;
}
.is-wip h3 { color: var(--faint); }

/* finished projects open a writeup dialog. The title button stretches
   over the whole card; the source link sits above it. */
.project.is-clickable { position: relative; cursor: pointer; }
.project-open {
  background: none;
  border: 0;
  padding: 0;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-align: left;
  cursor: pointer;
}
.project-open::after { content: ""; position: absolute; inset: 0; }
.project-open:focus-visible { outline: none; }
.project.is-clickable:has(.project-open:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.project-top a { position: relative; z-index: 1; }
.project-hint {
  margin-top: auto;
  color: var(--faint);
  font-size: 0.8rem;
  transition: color 0.2s ease;
}
.project-stack + .project-hint { margin-top: var(--space-3); }
.project.is-clickable:hover h3,
.project.is-clickable:hover .project-hint { color: var(--accent); }

/* the "see more" cards reuse the project grid; the panel's own borders
   stand in for the grid's top and left edges */
.more-panel:has(.more-grid) { padding: 0; }
.more-grid {
  border-top: 0;
  border-left: 0;
  margin: 0 -1px -1px 0;
}
/* CTF writeups: a full-width footer row under the project grid */
.ctf-open.grid-foot {
  border-top: 0;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.ctf-open.grid-foot:hover { background: var(--wash); border-color: var(--line); }

.ctf-open {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 1px solid var(--line);
  padding: var(--space-5);
  color: inherit;
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.2s ease;
}
.ctf-open:hover { border-color: var(--accent); }
.ctf-open + .ctf-open { margin-top: var(--space-4); }
.ctf-label {
  display: block;
  color: var(--accent);
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: var(--space-2);
}
.ctf-open-text {
  display: block;
  color: var(--muted);
  font-size: 0.95rem;
  max-width: 48ch;
  margin-bottom: var(--space-4);
}
.ctf-open-link {
  color: var(--faint);
  font-size: 0.8rem;
  transition: color 0.2s ease;
}
.ctf-open:hover .ctf-open-link { color: var(--accent); }

/* writeups dialog — a loupe over the contact sheet */
.ctf-dialog {
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--ink);
  max-width: 640px;
  width: calc(100vw - var(--space-6) * 2);
  max-height: 80vh;
  padding: var(--space-6);
  overflow-y: auto;
}
.ctf-dialog::backdrop { background: rgba(5, 4, 2, 0.72); }
.ctf-dialog-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-5);
}
.ctf-dialog-head .ctf-label { margin-bottom: 0; }
.ctf-close {
  background: none;
  border: 0;
  color: var(--faint);
  font-size: 1rem;
  cursor: pointer;
  padding: 0.25rem 0.5rem;
}
.ctf-close:hover { color: var(--accent); }
.ctf-entries { font-size: 0.95rem; }
.ctf-entries > * + * { margin-top: var(--space-4); }
/* writeup picker */
.ctf-menu {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-bottom: var(--space-5);
}
.ctf-menu-item {
  background: none;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  padding: 0.25em 0.8em;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.ctf-menu-item:hover { border-color: var(--accent); color: var(--ink); }
.ctf-menu-item.is-active { border-color: var(--accent); color: var(--accent); }
.writeup { display: none; }
.writeup.is-active { display: block; }
.writeup-title {
  font-size: 1.3rem;
  letter-spacing: -0.01em;
}
.writeup-meta {
  color: var(--faint);
  font-size: 0.75rem;
  margin: var(--space-1) 0 var(--space-5);
}
.writeup-h {
  color: var(--accent);
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin: var(--space-6) 0 var(--space-3);
}
.writeup p { color: var(--muted); margin-bottom: var(--space-4); }
.writeup strong { color: var(--ink); }
.writeup ul {
  color: var(--muted);
  margin: 0 0 var(--space-4) var(--space-3);
}
.writeup li { margin-bottom: var(--space-1); }
.writeup code {
  font-family: var(--font-mono);
  font-size: 0.85em;
  color: var(--ink);
  background: var(--wash);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.1em 0.4em;
}
.writeup .flag {
  border-left: 2px solid var(--accent);
  padding: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-4);
  color: var(--faint);
  font-style: italic;
}

/* goals top: moulton pano beside the lede + planned frames, both
   vertically centered so neither column leaves a void under it */
.goals-top {
  display: flex;
  gap: var(--space-5);
  align-items: center;
}
.goals-top .interlude {
  float: none;
  width: 46%;
  margin: 0;
  flex: none;
}
.goals-top-text { min-width: 0; }
/* no top nudge — the flex row centers the columns already; keep the
   gap between lede and planned frames matched to the strip below */
.goals-top-text .lede { margin-bottom: var(--space-3); }

.roll-now {
  display: grid;
  /* minmax(0, …): a long caption can't widen its column past the screen */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-1);
}

/* optically center the school text against its photo */
#education .entry { margin-top: var(--space-5); }

/* ============ Unexposed frames ============
   Empties stay faint. Planned frames get enough room for their
   caption to breathe; a done frame looks like one that already
   came out of the tank. */
.unexposed {
  display: grid;
  /* minmax, not bare 88px: fixed auto-fill tracks inflate the
     min-content size of the whole section and force sideways
     scrolling on phones */
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: var(--space-2);
  margin-top: var(--space-6);
}
.uframe {
  position: relative;
  aspect-ratio: 3 / 2;
  border: 1px solid var(--line);
  padding: 6px 8px;
}

/* the tail of the roll: five fluid columns so frames 19–33 pack into
   three full rows — no orphan frame, no ragged right edge */
.roll-rest {
  clear: both;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  margin-top: var(--space-3);
}
.uframe .mono {
  color: var(--faint);
  font-size: 0.66rem;
}
.uframe.planned {
  aspect-ratio: auto;
  border-color: var(--faint);
}
/* wider cells in the 88px auto-fill grids only; the goals .roll-now
   grid lays planned frames out two per row, so no span there */
.roll-rest .uframe.planned,
.more-frames .uframe.planned { grid-column: span 2; }
.uframe.planned em {
  display: block;
  margin-top: 4px;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--muted);
}
.uframe.planned .mono { color: var(--accent); }

/* ============ Contact ============ */
.section-contact {
  padding: var(--space-5) 0;
  border-bottom: none;
}
/* easter egg: the portfolio, mid-development */
.egg {
  color: var(--faint);
  text-decoration: none;
  border-bottom: 1px dotted var(--faint);
}
.egg:hover { color: var(--accent); border-bottom-color: var(--accent); }
.contact-heading {
  font-size: clamp(2.4rem, 6vw, 4rem);
  letter-spacing: -0.02em;
  margin-bottom: var(--space-4);
}
.contact-lede {
  color: var(--muted);
  max-width: 50ch;
  margin-bottom: var(--space-5);
}
.contact-email {
  display: inline-block;
  font-size: clamp(1rem, 2.4vw, 1.25rem);
  font-weight: 700;
  margin-bottom: var(--space-5);
  text-shadow: 0 0 18px var(--accent-glow);
}
.contact-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
}
.contact-links a {
  color: var(--muted);
  text-decoration: none;
}
.contact-links a:hover { color: var(--accent); }

/* ============ Footer ============ */
.site-footer {
  border-top: 1px solid var(--line);
  padding: var(--space-6) 0 var(--space-5);
}
.colophon {
  color: var(--faint);
  font-style: italic;
  font-size: 0.9rem;
  max-width: 58ch;
}
/* easter egg gets its own line under the colophon */
.egg-line { margin-top: var(--space-2); }
/* TEMP: preview note, remove at go-live */
.preview-note {
  color: var(--faint);
  font-size: 0.78rem;
  margin-top: var(--space-4);
  max-width: 46ch;
}
.preview-note a { color: var(--muted); }
.preview-note a:hover { color: var(--accent); }

/* ============ Responsive ============ */
@media (max-width: 820px) {
  .section-grid { grid-template-columns: 1fr; gap: var(--space-3); }
  .section-label { position: static; padding-top: 0; }
}

@media (max-width: 900px) {
  .nav-toggle { display: flex; }
  .nav-links {
    display: none;
    position: absolute;
    top: 64px;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
    padding: var(--space-1) var(--space-3) var(--space-4);
  }
  .nav-links.open { display: flex; }
  .nav-links li { width: 100%; }
  .nav-links a { display: block; padding: var(--space-2) 0; }
}

@media (max-width: 720px) {
  .skills-grid { grid-template-columns: 1fr; gap: var(--space-5); }
  .cert-row { flex-direction: column; align-items: flex-start; gap: var(--space-1); }
  .hero { padding: var(--space-9) 0 var(--space-7); }
  .section { padding: var(--space-7) 0; }
  .section-contact { padding: var(--space-9) 0; }
  .more-toggle { padding: var(--space-4); }
  .more-panel { padding: var(--space-5) var(--space-4); }
}

/* ============ Reduced motion ============ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* everything arrives already developed */
  .develop {
    opacity: 1 !important;
    filter: none !important;
    transition: none !important;
  }
}

/* ============ Film photos — negatives the text wraps around ============
   Each photo is a strip of negative: dark film base, sprocket rails
   punched along both long edges, caption in mono underneath. The
   floats sit to one side and the text wraps tight, sliding underneath
   once it clears the print. The barn pano is too wide to float — it
   gets a full-width band above its section instead. */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: var(--space-4);
  align-items: start;
}
/* tagline, meta, and stats run full width beneath the name-and-photo
   row, so the text can extend under the pano */
.hero-rest {
  grid-column: 1 / -1;
  margin-top: var(--space-4);
}
.hero-grid .interlude {
  float: none;
  width: auto;
  max-width: none;
  margin: 0;
}
.interlude {
  float: right;
  width: min(46%, 360px);
  margin: 0.35rem 0 var(--space-3) var(--space-4);
}
.interlude.float-l {
  float: left;
  margin: 0.35rem var(--space-4) var(--space-3) 0;
}
.interlude.slim-v {
  width: min(30%, 230px);
}
.site-footer { clear: both; }
.negative {
  display: block;
  position: relative;
  background: #0a0805;
  padding: 22px 8px;   /* room for the sprocket rails, top and bottom */
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35);
  transform: rotate(0.6deg);
  transition: transform 0.25s ease;
}
.negative.tilt-l { transform: rotate(-0.6deg); }
.negative img {
  display: block;
  width: 100%;
  height: auto;
}
/* sprocket rails: punched holes along both long edges, like real 35mm */
.negative::before,
.negative::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  height: 8px;
  background-image: repeating-linear-gradient(
    90deg,
    rgba(228, 218, 198, 0.7) 0 9px,
    transparent 9px 21px
  );
}
.negative::before { top: 7px; }
.negative::after { bottom: 7px; }
/* vertical negatives run the rails up both sides instead */
.negative.vert {
  padding: 8px 22px;
}
.negative.vert::before,
.negative.vert::after {
  top: 10px;
  bottom: 10px;
  width: 8px;
  height: auto;
  background-image: repeating-linear-gradient(
    180deg,
    rgba(228, 218, 198, 0.7) 0 9px,
    transparent 9px 21px
  );
}
.negative.vert::before { left: 7px; right: auto; }
.negative.vert::after { right: 7px; left: auto; }
.interlude:hover .negative { transform: rotate(0) translateY(-3px); }
.interlude figcaption {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--faint);
  margin-top: var(--space-2);
}

/* ============ Page imperfections ============
   The flaws live on the scan, not the prints: warm light leaking in
   from the corners and the left edge of the room. Viewport-fixed,
   so the burns hang in the air while the frames scroll past. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 199;   /* just under the grain, over everything else */
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0.65;   /* global dial for every leak below */
  background:
    radial-gradient(55% 42% at 106% -6%, rgba(255, 180, 80, 0.36) 0%, rgba(255, 110, 35, 0.18) 32%, rgba(225, 70, 22, 0.08) 55%, transparent 75%),
    radial-gradient(38% 30% at -8% 105%, rgba(255, 150, 55, 0.16) 0%, rgba(230, 80, 25, 0.07) 45%, transparent 72%),
    radial-gradient(74% 56% at 106% 106%, rgba(238, 58, 28, 0.2) 0%, rgba(200, 45, 18, 0.09) 45%, transparent 72%),
    linear-gradient(95deg, rgba(255, 130, 45, 0.1) 0%, rgba(230, 70, 20, 0.04) 6%, transparent 10%);
}

/* the tail burn: the end of the roll burning out behind the footer.
   A full-width bottom-edge gradient whose contour is warped by
   fractal noise, blooming like real emulsion damage. The SVG fades
   to black on its own (black = invisible under screen blend), so no
   mask, no photo, no attribution needed.
   Tunables:
     height  — how far into the page the glow reaches
     opacity — how loud the red is
     seed=11 / scale=60 in the SVG — edge shape */
.roll-end {
  position: relative;
  z-index: 197;   /* under the gradient leaks and both grain layers */
  pointer-events: none;
  mix-blend-mode: screen;
  background:
    /* shifted 70px down so the SVG's frayed bottom edge is clipped off
       below the element — only smooth glow remains visible */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1600' height='320' viewBox='0 0 1600 320'%3E%3Cdefs%3E%3ClinearGradient id='g' gradientUnits='userSpaceOnUse' x1='0' y1='320' x2='0' y2='0'%3E%3Cstop offset='0' stop-color='%23ff7a34'/%3E%3Cstop offset='0.28' stop-color='%23ee3a1c' stop-opacity='0.7'/%3E%3Cstop offset='0.6' stop-color='%23b01708' stop-opacity='0.3'/%3E%3Cstop offset='1' stop-color='%23000000' stop-opacity='0'/%3E%3C/linearGradient%3E%3Cfilter id='w' x='-10%25' y='-30%25' width='120%25' height='160%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.004 0.012' numOctaves='4' seed='11' result='n'/%3E%3CfeDisplacementMap in='SourceGraphic' in2='n' scale='60' xChannelSelector='R' yChannelSelector='G'/%3E%3C/filter%3E%3C/defs%3E%3Crect x='-80' y='-80' width='1760' height='480' fill='url(%23g)' filter='url(%23w)'/%3E%3C/svg%3E") center calc(100% + 70px) / 100% 100% no-repeat,
    /* backstop: guarantees the glow reaches the very last pixel of the page */
    linear-gradient(to top, rgba(255, 122, 52, 0.5) 0%, rgba(238, 58, 28, 0.22) 25%, transparent 60%);
  /* sits in normal flow as the last element, pulled up over the footer by
     its own height — its bottom edge IS the page bottom by construction,
     so it can't drift away from it on mobile the way absolute did */
  height: clamp(220px, 30vw, 380px);
  margin-top: calc(-1 * clamp(220px, 30vw, 380px));
  opacity: 0.26;
}

/* a little louder on desktop, where there's more dark footer to fill */
@media (min-width: 721px) {
  .roll-end { opacity: 0.36; }
}

@media (max-width: 720px) {
  .hero-grid { grid-template-columns: 1fr; }
  .interlude,
  .interlude.float-l {
    float: none;
    width: 100%;
    max-width: 440px;
    margin: var(--space-2) 0 var(--space-4);
  }
  /* vertical frames get a deliberate card size — the desktop 30% width
     rule would otherwise shrink them to postage stamps here */
  .interlude.slim-v {
    width: 100%;
    max-width: 240px;
    margin-left: auto;
    margin-right: auto;
  }
  /* photos land after the section's text on phones, not before it */
  .section-body { display: flex; flex-direction: column; }
  .section-body > .interlude { order: 2; }
  .goals-top { flex-direction: column; align-items: stretch; }
  .goals-top .interlude { width: 100%; max-width: 440px; order: 2; }
}
