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

/* ---------- Design tokens ---------- */
/* One radius scale, one border colour, one lift. Surfaces are defined by a
   hairline border rather than a drop shadow -- a shadow at this contrast
   would read as grubby against the white ground, and a 1px border keeps its
   structure at any zoom. Shadow is reserved for the hover lift only. */
:root {
  --radius-card: 14px;   /* list rows, award cards, image frames */
  --radius-sm: 10px;     /* nested surfaces inside a card */
  --radius-chip: 999px;  /* pills: tags, meta, back link */

  --hairline: #e9e9e9;
  --hairline-strong: #d4d4d4;

  --surface: #ffffff;
  --surface-sunken: #fafafa;  /* static (non-clickable) cards */

  --ink: #1a1a1a;
  --ink-soft: #565656;

  --lift: 0 1px 2px rgba(0, 0, 0, 0.04), 0 10px 26px rgba(0, 0, 0, 0.06);
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
}

html, body {
  min-height: 100%;
  background: #ffffff;
  color: #1a1a1a;
}

/* The layout is centred, so a scrollbar that appears on a long page and not
   on a short one shifts the whole column sideways -- the site visibly changes
   width as you move between tabs. Hiding it keeps the column in one place on
   every page. Scrolling is unaffected: wheel, trackpad, keyboard and touch
   all still work, only the bar itself is not painted. */
html {
  scrollbar-width: none;     /* firefox */
  -ms-overflow-style: none;  /* legacy edge */
  /* Anchor targets stop below the sticky nav rather than under it. This also
     covers keyboard focus moving behind the nav when tabbing backwards, which
     scroll-margin-top does not reliably do. */
  scroll-padding-top: 4.5rem;
}
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
html::-webkit-scrollbar,
body::-webkit-scrollbar {    /* chrome, safari, edge */
  width: 0;
  height: 0;
}

body {
  font-family: 'Space Mono', monospace;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
}

/* ---------- Nav ---------- */
/* The nav stays with you down the page now that the site is one scroll. It
   sits on a translucent blurred ground so content passing underneath stays
   faintly visible instead of hard-clipping against a solid bar, and it only
   grows its bottom hairline once you have scrolled off the top. */
nav.top-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  padding: 1.05rem 1.25rem;
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: saturate(180%) blur(10px);
  -webkit-backdrop-filter: saturate(180%) blur(10px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s var(--ease-out), background-color 0.2s var(--ease-out);
  display: flex;
  align-items: center;
  /* Four tabs rather than three: wrap to a second centred line on narrow
     screens instead of overflowing. */
  flex-wrap: wrap;
  justify-content: center;
  width: 100%;
  gap: 0.7rem 0.9rem;
  font-size: 0.85rem;
  letter-spacing: 0.15em;
  text-transform: lowercase;
}
nav.top-nav.is-stuck {
  border-bottom-color: var(--hairline);
  background: rgba(255, 255, 255, 0.85);
}
/* Without backdrop-filter the translucency would just look washed out. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  nav.top-nav { background: #ffffff; }
}

nav.top-nav a {
  color: #1a1a1a;
  text-decoration: none;
  padding-bottom: 3px;
  border-bottom: 1px solid transparent;
  transition: color 0.25s ease, border-color 0.25s ease;
}
nav.top-nav a:hover { color: #1a1a1a; }
nav.top-nav a.active {
  color: #1a1a1a;
  font-weight: 700;
  border-bottom-color: #1a1a1a;
}
nav.top-nav .dot { color: #1a1a1a; }

/* ---------- Page content ---------- */
main {
  flex: 1;
  width: 100%;
  max-width: 560px;
  padding: 5rem 2rem 4rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 1.75rem;
}

main .eyebrow {
  font-size: 0.75rem;
  letter-spacing: 0.25em;
  text-transform: lowercase;
  color: #1a1a1a;
  font-weight: 700;
}

main p {
  font-size: 1.05rem;
  line-height: 1.85;
  color: #333;
  text-wrap: pretty;
}

main p.muted { color: #1a1a1a; font-size: 0.9rem; }

a.link {
  color: #1a1a1a;
  text-decoration: none;
  border-bottom: 1px solid #1a1a1a;
  padding-bottom: 1px;
}
a.link:hover { color: #1a1a1a; border-bottom-width: 2px; }

/* ---------- Projects list ---------- */
.proj-list {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}
/* Rows were hairline-separated entries in one continuous list. They are now
   discrete cards: each gets its own border and radius, and the list gains a
   gap so the rounded corners actually read. */
.proj-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.3rem 1.45rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--surface);
  text-align: left;
}
.proj-item .meta { display: flex; flex-direction: column; gap: 0.35rem; }
.proj-item .title { font-size: 1rem; color: #1a1a1a; font-weight: 700; }
.proj-item .desc { font-size: 0.85rem; color: var(--ink-soft); line-height: 1.6; }
.proj-item .yr { font-size: 0.8rem; color: var(--ink-soft); white-space: nowrap; }

.section-gap { margin-top: 1rem; }
.connect-list {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  margin-top: 0.5rem;
}
.connect-list a {
  font-size: 1rem;
  color: #1a1a1a;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.connect-list a:hover { border-bottom-color: #1a1a1a; }

footer.page-footer {
  padding-bottom: 2.5rem;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: lowercase;
  color: #1a1a1a;
}

/* ---------- Project list as links ---------- */
.proj-item.link-item {
  text-decoration: none;
  color: inherit;
  transition: padding-left 0.24s var(--ease), border-color 0.24s ease,
              transform 0.24s var(--ease), box-shadow 0.24s ease;
}
/* Hover marks the row with a rounded vertical bar on the left rather than
   dimming it. Fading reads as "disabled", the opposite of the affordance
   wanted here. The bar lives in main's left padding so nothing reflows;
   it grows from the centre and has fully round ends. */
.proj-item.link-item { position: relative; }
.proj-item.link-item::before {
  content: '';
  position: absolute;
  left: 0.6rem;
  top: 1.1rem;
  bottom: 1.1rem;
  width: 3px;
  border-radius: 999px;
  background: #1a1a1a;
  transform: scaleY(0);
  transform-origin: center;
  opacity: 0;
  transition: transform 0.24s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.18s ease;
}
.proj-item.link-item:hover::before,
.proj-item.link-item:focus-visible::before {
  transform: scaleY(1);
  opacity: 1;
}
/* Hover lifts the card and darkens its edge, and the content slides right to
   clear the accent bar. The bar now lives inside the card (the old position
   sat in main's padding, which a rounded, bordered card no longer has). */
.proj-item.link-item:hover,
.proj-item.link-item:focus-visible {
  padding-left: 1.95rem;
  border-color: var(--hairline-strong);
  transform: translateY(-2px);
  box-shadow: var(--lift);
}
.proj-item.link-item:focus-visible { outline: none; }

/* ---------- Social row (connect page) ---------- */
.social-row {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  margin-top: 0.5rem;
}
.social-row a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid #e2e2e2;
  border-radius: 50%;
  color: #1a1a1a;
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}
.social-row a svg { width: 19px; height: 19px; fill: currentColor; }
.social-row a:hover { border-color: #1a1a1a; background: #fafafa; transform: translateY(-2px); }
.email-line {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  align-items: center;
}

/* ---------- Project detail page ---------- */
/* The back link is a pill so the page's one navigational control matches the
   rounded language of the cards below it. */
.back-link {
  align-self: flex-start;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  color: var(--ink);
  text-decoration: none;
  margin-bottom: 0.5rem;
  padding: 0.45rem 0.95rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-chip);
  transition: border-color 0.2s ease, background 0.2s ease;
}
.back-link:hover {
  color: var(--ink);
  border-color: var(--hairline-strong);
  background: var(--surface-sunken);
}

.detail-images {
  display: flex;
  align-items: flex-start;
  gap: 1.75rem;
  width: 100%;
}
.detail-images .frame {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  overflow: hidden;
}
.detail-images .frame:nth-child(1) {
  width: 52%;
  aspect-ratio: 4/3;
}
.detail-images .frame:nth-child(2) {
  width: 34%;
  aspect-ratio: 1/1;
  margin-top: 3rem;
}
.detail-images.single {
  justify-content: flex-start;
}
.detail-images.single .frame {
  width: 44%;
  aspect-ratio: 4/3;
}

.detail-desc {
  font-family: 'Courier Prime', 'Space Mono', monospace;
  font-size: 1.15rem;
  line-height: 2;
  letter-spacing: 0.01em;
  color: #262626;
  text-align: left;
  text-wrap: pretty;
}
/* Footer metadata reads as pills rather than dot-separated text, matching the
   tag pills used in the awards list. */
.detail-meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
  text-transform: lowercase;
}
.detail-meta-row span {
  padding: 0.35rem 0.8rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-chip);
  background: var(--surface-sunken);
}

/* ---------- Empty image slots ---------- */
/* image-slot sets [data-filled] once an image is present. Until then the
   frame would render as a bordered "drop an image" box, which reads as
   broken to a visitor — so unfilled frames (and a row with no filled
   frames at all) collapse. Adding images later reverses this with no
   markup change. */
.detail-images .frame:not(:has(image-slot[data-filled])) { display: none; }
.detail-images:not(:has(image-slot[data-filled])) { display: none; }


/* ---------- One-page sections ---------- */
/* The four tabs are sections of a single scroll now. main itself stops being
   the centred text column -- each section is -- so it drops its own width,
   padding and centring and just stacks them. */
main.page {
  max-width: none;
  padding: 0;
  justify-content: flex-start;
  gap: 0;
}
.section {
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  padding: 6rem 2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 1.75rem;
  /* Belt and braces with scroll-padding-top for browsers that honour one and
     not the other. */
  scroll-margin-top: 4.5rem;
}
.section:focus { outline: none; }

/* The intro holds the first screen, so it is taller and reads larger. */
.section-intro {
  max-width: 680px;
  min-height: clamp(420px, 72svh, 720px);
  padding-top: 3rem;
  gap: 1.25rem;
}
.section-intro p {
  font-size: 1.6rem;
  line-height: 1.7;
  color: #1a1a1a;
}
.section-intro p.muted {
  font-size: 1rem;
  color: #1a1a1a;
}
@media (max-width: 600px) {
  .section { padding: 4.5rem 1.5rem; }
  .section-intro { padding-top: 2rem; }
  .section-intro p { font-size: 1.25rem; }
}


/* The nav label is longer now ("projs & experience"); tighten tracking on
   narrow screens so the row still fits on one line. */
@media (max-width: 520px) {
  nav.top-nav {
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    gap: 0.55rem 1.1rem;
    padding-left: 1.6rem;
    padding-right: 1.6rem;
  }
  /* With four tabs the row wraps here, which leaves a dot dangling at the end
     of the first line. Drop the separators and let the gap do that work. */
  nav.top-nav .dot { display: none; }
}


/* ---------- Awards ---------- */
/* These cards are deliberately NOT links. Two things signal that: a sunken
   surface instead of the white of a clickable row, and no hover lift at all.
   A card that moves under the cursor but cannot be clicked is the worst of
   both, so these stay put. */
.award-list {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}
.award-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 1.3rem 1.45rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--surface-sunken);
  text-align: left;
}

.award-title {
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.45;
  text-wrap: pretty;
}
/* Sits above the title as a small label rather than opposite it, so a long
   title never has to share its line. */
.award-yr {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  color: var(--ink-soft);
  white-space: nowrap;
}

/* The fly-in programs card holds five entries. They are one award, so they
   are pills inside a single card rather than five cards of their own. */
.award-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  list-style: none;
  padding: 0;
  margin: 0;
}
.award-tags li {
  font-size: 0.78rem;
  color: var(--ink);
  padding: 0.45rem 0.85rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-chip);
  background: var(--surface);
  white-space: nowrap;
}

@media (max-width: 520px) {
  .proj-item {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }
  .award-card { padding: 1.15rem 1.2rem; gap: 0.6rem; }
  .award-tags { gap: 0.4rem; }
  .award-tags li { white-space: normal; font-size: 0.75rem; padding: 0.4rem 0.75rem; }
}

/* The hover lift is motion; skip it when motion is unwelcome. */
@media (prefers-reduced-motion: reduce) {
  .proj-item.link-item,
  .proj-item.link-item::before { transition: none; }
  .proj-item.link-item:hover,
  .proj-item.link-item:focus-visible { transform: none; }
}


/* ---------- Scroll reveal ---------- */
/* Elements lift in as they enter the viewport. The distance is deliberately
   small: a long travel reads as a slideshow, a short one reads as the page
   settling. scroll.js adds .is-in; if it never runs, the unstyled state is
   the visible one, so the page still reads with JS off. */
/* Starting at 0.001 rather than 0 keeps the element painted, so the first
   animated frame has nothing to warm up and cannot flash. */
.reveal {
  opacity: 0.001;
  transform: translateY(12px);
  transition: opacity 0.4s var(--ease-out), transform 0.4s var(--ease-out);
  transition-delay: var(--reveal-delay, 0s);
  will-change: opacity, transform;
}
.reveal.is-in {
  opacity: 1;
  transform: none;
  will-change: auto;
}
/* No JS, no observer support: show everything rather than a blank page. */
.no-js .reveal,
html:not(.js) .reveal {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ---------- Section separators ---------- */
/* A hairline between sections gives the scroll a sense of chapters without
   boxing anything in. The first section has nothing above it to divide. */
.section + .section {
  border-top: 1px solid var(--hairline);
}

/* ---------- Skip link ---------- */
/* One-page anchor nav means keyboard users would otherwise tab the whole nav
   before reaching content. Visually hidden until focused. */
.skip-link {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translate(-50%, -120%);
  z-index: 30;
  padding: 0.6rem 1.1rem;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-chip);
  text-decoration: none;
  transition: transform 0.2s var(--ease);
}
.skip-link:focus {
  transform: translate(-50%, 0.5rem);
}


/* ---------- Visually hidden ---------- */
/* Present for screen readers and search results, absent from the layout. The
   one-page site has a single h1 that the visual design has no room for. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
