/* Deliberately plain. One column, system fonts, no framework.
   The point of this pass is to make the content readable so it can be
   written; visual interest comes later. */

:root {
  /* Tells the browser the page has both palettes, so the canvas it paints
     outside the document — the overscroll area past the footer, and the
     scrollbars — follows the dark theme instead of defaulting to white. That
     gap is only visible on pages shorter than the viewport, which is why the
     blog index showed it and the project pages did not. */
  color-scheme: light dark;

  --accent: #c2410c;
  --fg: #1a1a1a;
  --muted: #666;
  --bg: #fdfdfc;
  --line: #e2e2df;
  --measure: 42rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --fg: #e8e8e6;
    --muted: #a0a09c;
    --bg: #16161a;
    --line: #2e2e33;
  }
}

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

/* Painted explicitly rather than relying on body's background propagating to
   the canvas: propagation is easy to lose the moment anything sets a
   background on html, and the failure is invisible until a page is short. */
html {
  background: var(--bg);
  min-height: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  padding: 0 1.25rem;
  background: var(--bg);
  color: var(--fg);
  font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

main, .site-header, .site-footer {
  max-width: var(--measure);
  margin: 0 auto;
}
main { min-width: 0; }
/* Nothing in the main column may outgrow the rule under the header. */
main :where(img, video, iframe, table, pre) { max-width: 100%; }

a { color: var(--accent); }
a:hover { text-decoration: none; }

h1, h2, h3 { line-height: 1.25; margin: 2rem 0 0.5rem; }
h1 { font-size: 1.75rem; }
h2 { font-size: 1.25rem; margin-top: 2.5rem; }
h3 { font-size: 1.05rem; }
p { margin: 0 0 1rem; }

img, video { max-width: 100%; height: auto; display: block; }

/* header + footer ------------------------------------------------------- */

.site-header {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  align-items: baseline;
  justify-content: space-between;
  padding: 1.5rem 0;
  border-bottom: 1px solid var(--line);
}
.site-name { font-weight: 600; color: var(--fg); text-decoration: none; }
.site-header nav { display: flex; gap: 1rem; flex-wrap: wrap; }
.site-header nav a[aria-current="page"] { color: var(--fg); font-weight: 600; }

.site-footer {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  align-items: center;
  justify-content: space-between;
  margin-top: 4rem;
  padding: 1.5rem 0 3rem;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.9rem;
}
.site-footer .footer-name { color: var(--fg); font-weight: 600; text-decoration: none; }
.site-footer .footer-name:hover { color: var(--accent); }
@media (max-width: 30rem) {
  .site-footer { justify-content: center; text-align: center; }
  .site-footer .socials { justify-content: center; }
}

/* shared bits ----------------------------------------------------------- */

.meta { color: var(--muted); font-size: 0.9rem; }
.headline { font-size: 1.1rem; color: var(--muted); }
.summary { font-size: 1.05rem; }

.note {
  border-left: 3px solid var(--line);
  padding-left: 0.75rem;
  color: var(--muted);
  font-size: 0.9rem;
}

.available {
  display: inline-block;
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 0.4rem 0.7rem;
  color: var(--accent);
  font-size: 0.9rem;
}

.tags { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.5rem 0; }
.tags span {
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.1rem 0.45rem;
  color: var(--muted);
  font-size: 0.8rem;
}

.links { display: flex; flex-wrap: wrap; gap: 1rem; }

.facts { display: grid; grid-template-columns: max-content 1fr; gap: 0.25rem 1rem; margin: 1rem 0; }
.facts dt { color: var(--muted); font-size: 0.9rem; }
.facts dd { margin: 0; }

/* project cards --------------------------------------------------------- */

.cards {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2rem;
}
.cards > li { min-width: 0; }
.card-image { display: block; }
.card-image img,
.card-image video { width: 100%; }
.card-image img { border: 1px solid var(--line); border-radius: 4px; background: #000; }
.card h3 { margin: 0.6rem 0 0.2rem; }
.card h3 a { color: var(--fg); text-decoration: none; }
.card h3 a:hover { color: var(--accent); }
.card p { margin: 0 0 0.4rem; }

/* posts ----------------------------------------------------------------- */

.posts { list-style: none; padding: 0; }
.posts li { margin-bottom: 1.5rem; }
.posts li > a { font-weight: 600; }
.posts li p { margin: 0.25rem 0 0; }

/* project + post bodies ------------------------------------------------- */

figure { margin: 1.5rem 0; }
/* Portrait phone video is far narrower than the text column. Centre anything
   that does not fill the width rather than leaving it pinned left against a
   band of empty space. Media wider than the column is capped at 100% and the
   auto margins collapse, so this is a no-op there. */
figure img, figure video { margin-inline: auto; }
figure img, figure video { border: 1px solid var(--line); border-radius: 4px; background: #000; }
/* --media-w is the media's own pixel width, set on the figure. Holding the
   caption to it keeps the two together instead of letting a two-line caption
   sprawl across the column beside a 400px video. */
figcaption {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0.4rem auto 0;
  max-width: min(100%, var(--media-w, 100%));
}

.documents { list-style: none; padding: 0; }
.documents li { margin-bottom: 0.75rem; }



.prose > :first-child { margin-top: 0; }
.prose img, .prose video { margin-inline: auto; }
/* Diagrams are drawn on the light palette, so give them a light panel that
   still reads as one in dark mode. */
.prose img[src$=".svg"] { background: #fdfdfc; padding: 0.75rem; border-radius: 6px; }
.prose pre {
  overflow-x: auto;
  padding: 0.75rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  font-size: 0.875rem;
}
.prose code { font-size: 0.9em; }
.prose blockquote {
  margin: 1rem 0;
  padding-left: 1rem;
  border-left: 3px solid var(--line);
  color: var(--muted);
}
.prose table { width: 100%; border-collapse: collapse; font-size: 0.925rem; }
.prose th, .prose td { border: 1px solid var(--line); padding: 0.4rem 0.6rem; text-align: left; }

.socials { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 0.4rem 1.1rem; }
.socials a { display: inline-flex; align-items: center; gap: 0.35rem; text-decoration: none; }
.socials a:hover span { text-decoration: underline; }
.socials svg { flex: none; }

/* landing page ----------------------------------------------------------- */

.intro {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  gap: 0 2rem;
  align-items: start;
  margin: 2.5rem 0 1.5rem;
}
.intro-aside { display: flex; flex-direction: column; gap: 1rem; align-items: flex-start; width: 190px; }
.intro-body > :first-child { margin-top: 0; }
.intro-body > :last-child { margin-bottom: 0; }

/* one link per line, icons in a column so the labels align */
/* Centred flex, not a fixed grid: the number of links differs between
   configurations — configuration_b has no Resume — and a two-column grid
   strands an odd count on a row of its own. Flex centres whatever is there,
   however many there are. */
.socials-stacked {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem 1rem;
  width: 100%;
  align-self: center;
}
.socials-stacked li { width: auto; min-width: 0; }
.intro h1 { margin: 0 0 0.2rem; font-size: 1.75rem; line-height: 1.2; }
.intro .headline { margin: 0 0 0.15rem; font-size: 1rem; }
.intro-location { margin: 0 0 1rem; color: var(--muted); font-size: 0.9rem; }

.portrait {
  width: 190px;
  height: 190px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--line);
  /* Optical alignment: a heading's cap-height sits below its box top, so the
     circle needs a nudge to share a line with it rather than a bounding box. */
  margin-top: 0.3rem;
}
@media (max-width: 34rem) {
  .intro { grid-template-columns: 1fr; gap: 1rem; }
  .portrait { width: 110px; height: 110px; }
}

.button {
  display: inline-block;
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 0.5rem 0.9rem;
  color: var(--accent);
  text-decoration: none;
}
.button:hover { background: var(--accent); color: #fff; }

/* carousel: a scroll-snap strip. Works without JS; the arrows are an
   enhancement and stay hidden if the script does not run. */
.carousel { position: relative; margin: 1.5rem 0 1.25rem; }
.carousel-track {
  display: flex;
  gap: 1rem;
  list-style: none;
  margin: 0;
  padding: 0 0 0.5rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}
.carousel-track > li {
  flex: 0 0 100%;
  scroll-snap-align: center;
  margin: 0;
}
.carousel-track a { display: block; }
.carousel-track img,
.carousel-track video {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: #000;
}
.carousel-track img { object-fit: cover; }
/* `cover` would crop the controls bar off a video, so it is contained. */
.carousel-track video { object-fit: contain; }
.carousel-caption { color: var(--muted); font-size: 0.9rem; margin: 0.4rem 0 0; }
.carousel-caption a { color: var(--accent); text-decoration: underline; }
.carousel-caption a:hover { text-decoration: none; }

.carousel-nav {
  position: absolute;
  top: calc(50% - 1.5rem);
  transform: translateY(-50%);
  z-index: 1;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--bg);
  color: var(--fg);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}
.carousel-nav:hover { border-color: var(--accent); color: var(--accent); }
.carousel-nav.prev { left: -0.5rem; }
.carousel-nav.next { right: -0.5rem; }

@media (min-width: 40rem) {
  .carousel-track > li { flex-basis: 60%; }
}


hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 2rem 0 0;
}

.context { color: var(--muted); font-size: 0.95rem; margin-bottom: 0.5rem; }

/* a card whose media is a horizontal strip rather than one hero */
.card-wide .strip {
  display: flex;
  gap: 0.75rem;
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0 0 0.5rem;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}
.card-wide .strip > li { flex: 0 0 15rem; scroll-snap-align: start; margin: 0; }
.card-wide .strip img { object-fit: cover; }
.card-wide .strip video { object-fit: contain; }
.card-wide .strip img,
.card-wide .strip video {
  width: 100%;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: #000;
}
.strip-caption a { color: var(--accent); text-decoration: underline; }
.strip-caption a:hover { text-decoration: none; }
.strip-caption {
  color: var(--muted);
  font-size: 0.8rem;
  margin: 0.3rem 0 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* the 8-bit fallback swapped in when media fails to load */
.is-placeholder {
  image-rendering: pixelated;
  object-fit: contain;
  background: #1a1a1e;
}


/* résumé ----------------------------------------------------------------- */

.lede p { font-size: 1.05rem; }

/* Each section gets a rule, so the page reads as distinct blocks rather than
   one long column of text. */
.resume-section { margin-top: 3rem; }
.resume-section > h2 {
  margin: 0 0 0.25rem;
  padding-bottom: 0.5rem;
  border-bottom: 2px solid var(--line);
  font-size: 1.1rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Entries are separated by a hairline rather than by whitespace alone. */
.entry { padding: 1.4rem 0; border-bottom: 1px solid var(--line); }
.entry:last-child { border-bottom: 0; padding-bottom: 0.5rem; }

.entry-head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.5rem;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 0.6rem;
}
.entry-title { flex: 1 1 20rem; min-width: 0; }
.entry h3 { margin: 0; font-size: 1.05rem; }
.entry-org { margin: 0.1rem 0 0; color: var(--muted); }
.entry-when {
  flex: 0 0 auto;
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.entry-when span { display: block; }

.entry > p { margin: 0 0 0.6rem; }
.entry ul { margin: 0 0 0.75rem; padding-left: 1.1rem; }
.entry li { margin-bottom: 0.35rem; }
.entry .tags { margin-bottom: 0; }
.tag-note { border-color: var(--accent) !important; color: var(--accent) !important; }

/* Coursework is long and secondary, so it folds away by default. */
.coursework summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 0.9rem;
  margin-bottom: 0.5rem;
}
.coursework summary:hover { color: var(--accent); }

.skills { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 0.6rem 1.5rem; margin: 1.25rem 0 0; }
.skills dt { color: var(--fg); font-weight: 600; font-size: 0.9rem; padding-top: 0.15rem; }
.skills dd { margin: 0; min-width: 0; }

/* Contact: platform on the left, the address on the right. */
.contact { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 0.4rem 1.5rem; margin: 1rem 0 0; }
.contact dt { color: var(--muted); }
.contact dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }

@media (max-width: 34rem) {
  .entry-when { text-align: left; }
  .entry-when span { display: inline; }
  .entry-when span::before { content: " · "; }
  .skills { grid-template-columns: 1fr; gap: 0.2rem; }
  .skills dd { margin-bottom: 0.75rem; }
}

/* media loading state ----------------------------------------------------
   A spinner on the containing element while media is in flight. The 8-bit
   placeholder is reserved for actual failure — it should never mean "wait". */
.media-loading { position: relative; min-height: 3rem; }
.media-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 28px;
  height: 28px;
  border: 3px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: media-spin 0.8s linear infinite;
  pointer-events: none;
}
@keyframes media-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .media-loading::after { animation-duration: 2.4s; }
}


/* A call-to-action closing a section needs air above it, or it reads as the
   last row of the content rather than a separate action. */
.section-action { margin-top: 2.25rem; }

/* The cards block follows a rule with no heading of its own, so it needs the
   top spacing a heading would otherwise have given it. */
section > .cards:first-child { margin-top: 2.5rem; }
