/* ===========================================================================
   SECTIONS
   Each section uses a different layout family. In page order:
     hero          layered overlap
     about         split portrait and text
     skills        grouped tile grid
     projects      pinned horizontal pan
     github        instrument panel
     journey       scroll drawn timeline
     services      asymmetric bento
     testimonials  quote strip
     notes         editorial list
     contact       full width terminal form
   =========================================================================== */

/* --------------------------------------------------------------- reveals */
/* Set by JS only when GSAP is confirmed alive, so a failed script load can
   never leave content invisible. */

.js-motion [data-reveal="rise"] {
  opacity: 0;
  transform: translate3d(0, 28px, 0);
}

.js-motion [data-reveal="fade"] {
  opacity: 0;
}

.js-motion [data-reveal="mask"] > .line-inner {
  transform: translate3d(0, 105%, 0);
}

.line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}

.line-inner {
  display: block;
  will-change: transform;
}

/* ------------------------------------------------------------------- nav */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: var(--z-nav);
  height: 68px;
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--d-fast) var(--e-transition),
    background-color var(--d-fast) var(--e-transition);
}

.nav[data-scrolled="true"] {
  border-bottom-color: var(--rule);
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  width: 100%;
}

.nav__brand {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

.nav__brand span {
  color: var(--blue);
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 1.8vw, 2rem);
}

.nav__link {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--ink-soft);
  text-decoration: none;
  white-space: nowrap;
  position: relative;
  padding-block: 0.35rem;
}

.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 100%;
  background: var(--blue);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--d-fast) var(--e-transition);
}

.nav__link:hover,
.nav__link[aria-current="true"] {
  color: var(--ink);
}

.nav__link:hover::after,
.nav__link[aria-current="true"]::after {
  transform: scaleX(1);
  transform-origin: left;
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.icon-btn {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  color: var(--ink-soft);
  cursor: pointer;
  font-size: 1.05rem;
  transition: color var(--d-fast) var(--e-transition),
    border-color var(--d-fast) var(--e-transition),
    transform var(--d-fast) var(--e-spring);
}

.icon-btn:hover {
  color: var(--blue);
  border-color: var(--blue);
  transform: translateY(-1px);
}

.nav__toggle {
  display: none;
}

@media (max-width: 860px) {
  .nav__links {
    position: fixed;
    inset: 68px 0 auto 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    background: var(--paper);
    border-bottom: 1px solid var(--rule);
    padding: 0.5rem var(--gutter) 1.5rem;
    transform: translate3d(0, -12px, 0);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--d-fast) var(--e-transition),
      transform var(--d-fast) var(--e-entrance), visibility var(--d-fast);
  }

  .nav[data-open="true"] .nav__links {
    opacity: 1;
    visibility: visible;
    transform: none;
  }

  .nav__link {
    width: 100%;
    padding-block: 0.85rem;
    border-bottom: 1px solid var(--rule);
    font-size: 1rem;
  }

  .nav__link::after {
    display: none;
  }

  .nav__toggle {
    display: grid;
  }
}

/* ------------------------------------------------------------------ hero */
/* Layered composition: type, terminal and canvas occupy overlapping planes
   on the same grid rather than stacking in a centred column. */

.hero {
  position: relative;
  min-height: 100dvh;
  /* Grid, not flex. A flex item shrink-to-fits, which collapsed the 12 column
     track list inside .shell and squeezed the headline into a narrow column. */
  display: grid;
  align-content: center;
  /* Capped so the hero content never floats halfway down the viewport. */
  padding-top: clamp(6rem, 7vw, 7rem);
  padding-bottom: clamp(2.5rem, 4vw, 3.5rem);
  overflow: hidden;
}

.hero__canvas {
  position: absolute;
  inset: 0;
  z-index: var(--z-canvas);
  pointer-events: none;
  /* The structure emerges from behind the terminal on the right and fades out
     before it reaches the headline, so the planes overlap without the mesh
     ever competing with the type for the same pixels. */
  mask-image: linear-gradient(to right, transparent 22%, #000 58%),
    linear-gradient(to bottom, #000 58%, transparent 86%);
  mask-composite: intersect;
  -webkit-mask-image: linear-gradient(to right, transparent 22%, #000 58%),
    linear-gradient(to bottom, #000 58%, transparent 86%);
  -webkit-mask-composite: source-in;
  opacity: 0.9;
}

.hero__canvas canvas,
.hero__canvas svg {
  width: 100%;
  height: 100%;
  display: block;
}

.hero__grid {
  position: relative;
  z-index: var(--z-content);
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem) clamp(1rem, 2.5vw, 2rem);
  align-items: end;
  width: 100%;
}

/* Explicit rows are required. These two items share columns 7 and 8, and
   without a declared row auto-placement refuses to overlap them: it drops the
   terminal into a second row and the hero grows past the viewport. Declaring
   the row is what makes the overlapping-planes composition actually overlap. */
.hero__copy {
  grid-column: 1 / span 7;
  grid-row: 1;
}

.hero__title {
  font-size: var(--t-display);
  font-weight: 600;
  letter-spacing: -0.045em;
  line-height: 1.02;
  /* The column already constrains the measure. A tighter cap here pushed the
     headline to a third line, which is a font-size error, not a copy problem. */
  max-width: 20ch;
}

.hero__name {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  margin-bottom: clamp(1rem, 2vw, 1.5rem);
}

.hero__name b {
  color: var(--ink);
  font-weight: 500;
}

.hero__sub {
  margin-top: clamp(1rem, 1.8vw, 1.4rem);
  max-width: 46ch;
  color: var(--ink-soft);
  font-size: clamp(1.02rem, 1.25vw, 1.2rem);
  line-height: 1.55;
}

.hero__cta {
  margin-top: clamp(1.35rem, 2.2vw, 1.9rem);
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* The terminal overlaps the type column by a fraction, which is what makes
   the composition read as designed instead of as two stacked boxes. */
.hero__terminal {
  grid-column: 7 / span 6;
  grid-row: 1;
  align-self: center;
  /* It sits on the upper plane where the two overlap. */
  position: relative;
  z-index: var(--z-content);
}

.hero__status {
  grid-column: 1 / -1;
  grid-row: 2;
  margin-top: clamp(1.5rem, 2.6vw, 2.25rem);
  border-top: 1px solid var(--rule);
  padding-top: 1.1rem;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem clamp(1rem, 3vw, 3rem);
}

.stat__label {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
  letter-spacing: 0.02em;
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.stat__value {
  font-family: var(--font-mono);
  font-size: clamp(1.1rem, 1.9vw, 1.6rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  margin-top: 0.3rem;
  font-variant-numeric: tabular-nums;
}

.stat__value--live {
  color: var(--green);
}

.stat__value--muted {
  font-size: clamp(0.9rem, 1.2vw, 1.05rem);
  color: var(--ink-soft);
}

@media (max-width: 1080px) {
  /* Stacked, so the planes separate and each takes its own row. */
  .hero__copy { grid-column: 1 / -1; grid-row: 1; }
  .hero__terminal { grid-column: 1 / -1; grid-row: 2; }
  .hero__status { grid-row: 3; grid-template-columns: repeat(2, 1fr); }
  .hero__title { max-width: 24ch; }
  .hero { min-height: 0; }
}

@media (max-width: 560px) {
  .hero { min-height: auto; padding-block: 7rem 4rem; }
  .hero__status { grid-template-columns: repeat(2, 1fr); }
}

/* -------------------------------------------------------- section header */

/* ch on the wrapper resolved against the inherited body size, not the 56px
   headline inside it, which squeezed section titles to four lines. The measure
   belongs on the title, in its own em-relative units. */
.sec-head {
  max-width: 46rem;
  margin-bottom: clamp(2.5rem, 5vw, 4.5rem);
}

.sec-head__title {
  font-size: var(--t-h2);
  letter-spacing: -0.04em;
  max-width: 20ch;
}

.sec-head__body {
  margin-top: 1rem;
  color: var(--ink-soft);
  max-width: 52ch;
  font-size: 1.02rem;
}

/* ----------------------------------------------------------------- about */

.about__grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}

.about__portrait {
  position: relative;
  border-radius: var(--r);
  overflow: hidden;
  background: var(--paper-sunk);
  aspect-ratio: 4 / 5;
  max-width: 100%;
}

.about__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Resolves from blue duotone to full colour as the section scrolls past. */
  filter: grayscale(1) contrast(1.05);
  will-change: filter;
}

.about__portrait::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--blue);
  mix-blend-mode: color;
  opacity: var(--duotone, 0.85);
  pointer-events: none;
}

/* Monogram stand-in, shown only until a real portrait is uploaded. Built
   from the page's own blueprint language so it reads as a designed panel
   rather than a missing image. */
.about__portrait--empty {
  display: grid;
  place-items: center;
  background:
    linear-gradient(var(--blue-line) 1px, transparent 1px) 0 0 / 100% 2.5rem,
    linear-gradient(90deg, var(--blue-line) 1px, transparent 1px) 0 0 / 2.5rem 100%,
    var(--paper-sunk);
  border: 1px solid var(--rule);
}

.about__monogram {
  font-family: var(--font-display);
  font-size: clamp(4rem, 9vw, 7rem);
  font-weight: 600;
  letter-spacing: -0.06em;
  color: var(--blue);
  opacity: 0.9;
}

.about__meta {
  margin-top: 1rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

/* Docstring treatment: the about copy is presented the way the man who wrote
   it would document a module. The gutter rail is a real structural element. */
.about__doc {
  border-left: 1px solid var(--rule);
  padding-left: clamp(1.25rem, 3vw, 2.5rem);
}

.about__doc-open {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--green);
  margin-bottom: 1.25rem;
}

.about__doc h2 {
  font-size: var(--t-h2);
  letter-spacing: -0.04em;
  margin-bottom: 1.5rem;
}

.about__doc p {
  color: var(--ink-soft);
  max-width: var(--maxw-text);
  margin-bottom: 1.15rem;
  font-size: 1.05rem;
  line-height: 1.65;
}

.about__doc p strong {
  color: var(--ink);
  font-weight: 500;
}

.about__doc-close {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--ink-faint);
  margin-top: 1.5rem;
}

@media (max-width: 900px) {
  .about__grid { grid-template-columns: 1fr; }
  .about__portrait { max-width: 380px; }
  .about__doc { border-left: 0; padding-left: 0; }
}

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

.skills__group + .skills__group {
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.skills__group-head {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding-bottom: 0.85rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--rule);
}

.skills__group-name {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--ink);
}

.skills__group-blurb {
  font-size: var(--t-small);
  color: var(--ink-faint);
}

.skills__grid {
  display: grid;
  /* auto-fit, not auto-fill: empty tracks collapse so the row never ends in
     blank cells when a category has fewer entries than the row can hold. */
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  overflow: hidden;
}

.tile {
  position: relative;
  background: var(--paper);
  padding: 1.25rem 1.15rem;
  min-height: 124px;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  isolation: isolate;
  transition: background-color var(--d-fast) var(--e-transition);
}

.tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--blue-wash);
  opacity: 0;
  transition: opacity var(--d-fast) var(--e-transition);
}

.tile:hover::before {
  opacity: 1;
}

.tile__logo {
  width: 34px;
  height: 34px;
  position: relative;
}

.tile__logo img {
  position: absolute;
  inset: 0;
  width: 34px;
  height: 34px;
  object-fit: contain;
  transition: opacity var(--d-fast) var(--e-transition);
}

.tile__logo img.is-color {
  opacity: 0;
}

.tile:hover .tile__logo img.is-mono {
  opacity: 0;
}

.tile:hover .tile__logo img.is-color {
  opacity: 1;
}

.tile__name {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: -0.02em;
}

/* Proficiency as evidence, never as a percentage bar. */
.tile__context {
  font-size: var(--t-micro);
  color: var(--ink-faint);
  line-height: 1.45;
  margin-top: auto;
  opacity: 0;
  transform: translate3d(0, 6px, 0);
  transition: opacity var(--d-fast) var(--e-transition),
    transform var(--d-fast) var(--e-entrance);
}

.tile:hover .tile__context,
.tile:focus-within .tile__context {
  opacity: 1;
  transform: none;
}

@media (hover: none) {
  .tile__context { opacity: 1; transform: none; }
}

.tile--core .tile__name {
  color: var(--blue);
}

@media (max-width: 560px) {
  .skills__grid { grid-template-columns: repeat(2, 1fr); }
  .tile { min-height: 132px; padding: 1.1rem 0.9rem; }
}

/* -------------------------------------------------------------- projects */

.projects {
  position: relative;
  background: var(--paper-sunk);
}

.projects__head {
  padding-block: clamp(4rem, 8vw, 7rem) clamp(2rem, 4vw, 3rem);
}

.pan {
  position: relative;
  overflow: hidden;
}

.pan__track {
  display: flex;
  gap: clamp(1.25rem, 2.5vw, 2rem);
  padding-inline: var(--gutter);
  align-items: stretch;
  will-change: transform;
}

@media (min-width: 1025px) {
  /* While the section is pinned the track occupies the viewport, so the cards
     sit centred in it rather than clinging to the top with dead space below. */
  .pan__track {
    min-height: 100dvh;
    align-items: center;
  }

  .pcard {
    max-height: 84dvh;
  }
}

.pcard {
  flex: 0 0 min(78vw, 620px);
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: border-color var(--d-base) var(--e-transition),
    box-shadow var(--d-base) var(--e-transition);
}

.pcard:hover {
  border-color: var(--blue);
  box-shadow: var(--shadow-3);
  color: inherit;
}

.pcard__shot {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--paper-sunk);
  border-bottom: 1px solid var(--rule);
}

.pcard__shot img {
  width: 100%;
  height: 114%;
  object-fit: cover;
  will-change: transform;
}

.pcard__body {
  padding: clamp(1.25rem, 2.2vw, 1.9rem);
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  flex: 1;
}

.pcard__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.pcard__title {
  font-size: clamp(1.4rem, 2.1vw, 1.9rem);
  letter-spacing: -0.035em;
}

.pcard__year {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

.pcard__tagline {
  color: var(--blue);
  font-family: var(--font-mono);
  font-size: var(--t-small);
}

.pcard__problem {
  color: var(--ink-soft);
  font-size: 0.97rem;
  line-height: 1.6;
}

.pcard__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: auto;
  padding-top: 0.6rem;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.55rem;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--ink-soft);
  background: var(--paper);
}

.chip img {
  width: 13px;
  height: 13px;
}

.pcard__stats {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
  flex-wrap: wrap;
}

.pcard__stats i {
  font-size: 0.9rem;
  vertical-align: -1px;
}

.lang-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
  margin-right: 0.35rem;
  vertical-align: -1px;
}

.pcard__go {
  color: var(--blue);
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.pan__hint {
  display: none;
}

@media (max-width: 1024px) {
  /* No scroll hijack on touch. The gallery becomes an ordinary scroll-snap
     rail the visitor drives with their thumb. */
  .pan__track {
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: 1.5rem;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    min-height: 0;
    align-items: stretch;
  }

  .pcard {
    flex-basis: min(86vw, 460px);
    scroll-snap-align: start;
  }

  .pan__hint {
    display: block;
    padding-inline: var(--gutter);
    padding-bottom: 3rem;
    font-family: var(--font-mono);
    font-size: var(--t-micro);
    color: var(--ink-faint);
  }
}

/* ---------------------------------------------------------------- github */

.gh__grid {
  display: grid;
  /* minmax(0, ...) rather than a bare fr: the default automatic minimum is
     min-content, and the heatmap's 27 columns were pushing the whole page
     wider than the phone viewport through this track. */
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(1.5rem, 3vw, 3rem);
  align-items: start;
}

.gh__grid > *,
.gh__stack > *,
.panel {
  min-width: 0;
}

.panel {
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--paper-raised);
  padding: clamp(1.25rem, 2.2vw, 1.75rem);
}

.panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.panel__title {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 500;
}

.panel__note {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

/* Contribution grid */
.heat {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(7, 1fr);
  gap: 3px;
  /* The grid is wider than a phone. It scrolls inside its own panel rather
     than widening the page. */
  overflow-x: auto;
  max-width: 100%;
  padding-bottom: 0.25rem;
  scrollbar-width: thin;
}

.heat__cell {
  width: 11px;
  height: 11px;
  border-radius: 1px;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
}

.heat__cell[data-level="1"] { background: #c8f0d8; border-color: #b3e8c8; }
.heat__cell[data-level="2"] { background: #7fdca6; border-color: #6bd398; }
.heat__cell[data-level="3"] { background: #34c176; border-color: #2bb26a; }
.heat__cell[data-level="4"] { background: var(--green); border-color: var(--green); }

[data-theme="dark"] .heat__cell[data-level="1"] { background: #10361f; border-color: #16482a; }
[data-theme="dark"] .heat__cell[data-level="2"] { background: #1a6438; border-color: #1f7642; }
[data-theme="dark"] .heat__cell[data-level="3"] { background: #26a05a; border-color: #2bb266; }
[data-theme="dark"] .heat__cell[data-level="4"] { background: #35d47a; border-color: #35d47a; }

.heat__legend {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.9rem;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

/* Language ring */
.ring-wrap {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2rem);
  flex-wrap: wrap;
}

.ring {
  flex: none;
  width: 148px;
  height: 148px;
  transform: rotate(-90deg);
}

.ring circle {
  fill: none;
  stroke-width: 14;
  stroke-linecap: butt;
}

.ring__legend {
  display: grid;
  gap: 0.5rem;
  flex: 1 1 150px;
  min-width: 0;
}

.ring__row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
}

.ring__row span:last-child {
  margin-left: auto;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* Commit feed */
.gh__stack {
  display: grid;
  gap: clamp(1.5rem, 3vw, 3rem);
  min-width: 0;
}

.commit {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.75rem;
  padding-block: 0.6rem;
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  align-items: baseline;
}

.commit:first-child {
  border-top: 0;
}

.commit__sha {
  color: var(--blue);
}

.commit__msg {
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.commit__meta {
  grid-column: 2;
  color: var(--ink-faint);
}

/* Skeleton state, shown until the data resolves so the section has a real
   loading feel rather than popping into place. */
.skeleton {
  background: linear-gradient(
    90deg,
    var(--paper-sunk) 0%,
    var(--rule) 50%,
    var(--paper-sunk) 100%
  );
  background-size: 200% 100%;
  animation: sweep 1.4s var(--e-transition) infinite;
  border-radius: var(--r);
  color: transparent;
}

@keyframes sweep {
  to { background-position: -200% 0; }
}

.gh__repo {
  text-decoration: none;
  color: inherit;
  transition: background-color var(--d-fast) var(--e-transition);
}

.gh__repo:hover {
  background: var(--blue-wash);
  color: inherit;
}

.gh__repo .commit__msg {
  color: var(--blue);
}

.gh__quiet {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
  line-height: 1.7;
  max-width: 52ch;
  padding-block: 0.5rem 0.25rem;
}

.gh__empty {
  border: 1px dashed var(--rule-strong);
  border-radius: var(--r);
  padding: 2rem;
  text-align: center;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--t-small);
}

@media (max-width: 900px) {
  .gh__grid { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------- journey */

.tl {
  position: relative;
  padding-left: clamp(2rem, 5vw, 4rem);
  /* clip, not hidden: it contains the cards' entrance offset without turning
     the timeline into a scroll container. */
  overflow-x: clip;
}

/* Pre-animation state lives here rather than in a style attribute, so it only
   applies when the motion system is actually alive. */
.js-motion [data-tl-card] {
  opacity: 0;
  transform: translate3d(18px, 0, 0);
}

.tl__rail {
  position: absolute;
  left: clamp(0.4rem, 1.4vw, 1rem);
  top: 6px;
  bottom: 6px;
  width: 1px;
  background: var(--rule);
}

.tl__progress {
  position: absolute;
  inset: 0 auto 0 0;
  width: 1px;
  background: var(--blue);
  transform: scaleY(0);
  transform-origin: top;
  will-change: transform;
}

.tl__item {
  position: relative;
  padding-bottom: clamp(2.5rem, 5vw, 4rem);
}

.tl__item:last-child {
  padding-bottom: 0;
}

.tl__node {
  position: absolute;
  left: calc(clamp(2rem, 5vw, 4rem) * -1 + clamp(0.4rem, 1.4vw, 1rem) - 4px);
  top: 8px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  transition: background-color var(--d-base) var(--e-transition),
    border-color var(--d-base) var(--e-transition),
    box-shadow var(--d-base) var(--e-transition);
}

/* Node ignites when the drawn rail reaches it. Green because it reports a
   real state change: this milestone is now reached. */
.tl__item.is-lit .tl__node {
  background: var(--green-signal);
  border-color: var(--green-signal);
  box-shadow: 0 0 0 4px var(--green-wash);
}

.tl__period {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

.tl__title {
  font-size: var(--t-h3);
  letter-spacing: -0.03em;
  margin-top: 0.35rem;
}

.tl__org {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--blue);
  margin-top: 0.3rem;
}

.tl__desc {
  color: var(--ink-soft);
  margin-top: 0.7rem;
  max-width: 58ch;
  font-size: 0.98rem;
}

.tl__now {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--green);
  margin-top: 0.6rem;
}

/* -------------------------------------------------------------- services */
/* Bento with auto-fit tracks, so the cell count always equals the item count
   no matter how many services the control panel holds. The lead cell carries
   a real image so this is not a row of white text cards on white ground. */

.bento {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  overflow: hidden;
}

.bento__cell {
  background: var(--paper);
  padding: clamp(1.4rem, 2.4vw, 2.2rem);
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  min-height: 210px;
  transition: background-color var(--d-fast) var(--e-transition);
}

.bento__cell:hover {
  background: var(--paper-raised);
}

.bento__cell--feature {
  grid-column: 1 / -1;
  padding: 0;
  min-height: clamp(240px, 34vw, 340px);
  position: relative;
  overflow: hidden;
}

.bento__cell--feature img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.bento__feature-copy {
  position: relative;
  z-index: 1;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: clamp(1.5rem, 3vw, 2.5rem);
  /* Two axis scrim. The single bottom-up gradient left the body copy sitting
     on bright sky and failing contrast; this guarantees white text clears AA
     wherever the photograph happens to be light. */
  background:
    linear-gradient(to right, rgba(8, 12, 20, 0.9) 0%, rgba(8, 12, 20, 0.62) 48%, rgba(8, 12, 20, 0.12) 100%),
    linear-gradient(to top, rgba(8, 12, 20, 0.92) 0%, rgba(8, 12, 20, 0.62) 45%, rgba(8, 12, 20, 0.18) 100%);
  color: #fff;
}

.bento__feature-copy h3 {
  font-size: clamp(1.5rem, 3vw, 2.25rem);
  letter-spacing: -0.035em;
  color: #fff;
}

.bento__feature-copy p {
  margin-top: 0.6rem;
  max-width: 42ch;
  color: #fff;
  font-size: 1rem;
}

.bento__icon {
  font-size: 1.5rem;
  color: var(--blue);
  line-height: 1;
}

.bento__cell h3 {
  font-size: var(--t-h3);
  letter-spacing: -0.03em;
}

.bento__cell p {
  color: var(--ink-soft);
  font-size: 0.97rem;
  line-height: 1.6;
}

.bento__deliverable {
  margin-top: auto;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

@media (max-width: 560px) {
  /* Explicit single column collapse rather than relying on auto-fit alone. */
  .bento { grid-template-columns: 1fr; }
  .bento__cell { min-height: 0; }
}

/* ---------------------------------------------------------- testimonials */
/* Layout family: editorial feature plus a hairline rail. Deliberately not a
   row of cards, because three equal boxes is the generic default and elevation
   would imply a hierarchy between quotes that does not exist. */

.tm-lead {
  margin: 0 0 clamp(3rem, 6vw, 5rem);
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: end;
  padding-bottom: clamp(2rem, 4vw, 3rem);
  border-bottom: 1px solid var(--rule);
}

/* The quote mark is set in the display face at a size where it reads as a
   typographic element rather than an icon, and sits in the margin so the
   quote itself stays on the grid. */
.tm-lead__mark {
  position: absolute;
  left: -0.14em;
  top: -0.42em;
  font-family: var(--font-display);
  font-size: clamp(6rem, 13vw, 11rem);
  line-height: 1;
  color: var(--blue);
  opacity: 0.12;
  pointer-events: none;
  user-select: none;
}

.tm-lead__quote {
  position: relative;
  margin: 0;
  /* Display type with descenders inside an overflow-hidden line mask clips
     without this reserve. */
  padding-bottom: 0.08em;
  font-size: clamp(1.5rem, 3.2vw, 2.6rem);
  line-height: 1.24;
  letter-spacing: -0.035em;
  font-weight: 500;
  /* Wide enough that a featured quote lands in three lines. A narrower
     measure pushed it to five, and a landing page quote is a snippet. */
  max-width: 34ch;
  text-wrap: balance;
}

.tm-lead__who {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  min-width: 0;
}

@media (min-width: 761px) {
  /* Kept on one line beside the quote at desktop only. On a phone the lead
     stacks and a long role string would push the page wider than the screen. */
  .tm-lead__who {
    white-space: nowrap;
  }
}

.tm-lead__meta {
  display: grid;
  gap: 0.15rem;
}

.tm-lead__name {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--ink);
}

.tm-lead__role {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
  overflow-wrap: anywhere;
}

/* Supporting rail. Hairlines between columns rather than around them. */
.tm-rail {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: clamp(1.75rem, 3.5vw, 3rem);
}

.tm-item {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  padding-left: clamp(1.25rem, 2.5vw, 2rem);
  border-left: 1px solid var(--rule);
}

.tm-item:first-child {
  padding-left: 0;
  border-left: 0;
}

.tm-item__quote {
  margin: 0;
  font-size: 1.02rem;
  line-height: 1.55;
  color: var(--ink);
  letter-spacing: -0.012em;
  max-width: 34ch;
}

.tm-item__who {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-top: auto;
  padding-top: 0.6rem;
}

.tm-item__name {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink);
}

.tm-item__role {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
  overflow-wrap: anywhere;
}

/* Monogram stands in for a photograph. Quoting someone is one thing; issuing
   them a stock face is another, so initials are the honest default. */
.tm-avatar {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  background: var(--paper-sunk);
}

.tm-avatar--sm {
  width: 36px;
  height: 36px;
}

.tm-avatar--mono {
  display: grid;
  place-items: center;
  background: var(--blue-wash);
  border: 1px solid var(--blue-line);
  color: var(--blue);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}

.tm-avatar--sm.tm-avatar--mono {
  font-size: 0.7rem;
}

@media (max-width: 760px) {
  .tm-lead {
    grid-template-columns: 1fr;
    align-items: start;
    gap: 1.75rem;
  }

  .tm-lead__quote {
    max-width: none;
  }

  .tm-lead__mark {
    font-size: 5rem;
    top: -0.3em;
  }

  .tm-item {
    padding-left: 0;
    border-left: 0;
    border-top: 1px solid var(--rule);
    padding-top: 1.5rem;
  }

  .tm-item:first-child {
    border-top: 0;
    padding-top: 0;
  }
}

/* ----------------------------------------------------------------- notes */

.notes__list {
  border-top: 1px solid var(--rule);
}

.note {
  display: grid;
  grid-template-columns: 8rem minmax(0, 1fr) auto;
  gap: clamp(1rem, 3vw, 2.5rem);
  align-items: baseline;
  padding-block: clamp(1.25rem, 2.4vw, 1.9rem);
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
  transition: padding-left var(--d-base) var(--e-entrance);
}

.note:hover {
  padding-left: 0.9rem;
  color: inherit;
}

.note__date {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

.note__title {
  font-size: clamp(1.1rem, 1.8vw, 1.45rem);
  letter-spacing: -0.03em;
}

.note__excerpt {
  color: var(--ink-soft);
  font-size: 0.95rem;
  margin-top: 0.4rem;
  max-width: 62ch;
}

.note__read {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--blue);
  white-space: nowrap;
}

@media (max-width: 700px) {
  .note { grid-template-columns: 1fr; gap: 0.4rem; }
  .note__read { display: none; }
}

/* --------------------------------------------------------------- contact */

.contact {
  background: var(--paper-sunk);
}

.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}

.contact__aside h2 {
  font-size: var(--t-h2);
  letter-spacing: -0.04em;
}

.contact__aside p {
  margin-top: 1rem;
  color: var(--ink-soft);
  max-width: 40ch;
}

.contact__links {
  margin-top: 2rem;
  display: grid;
  gap: 0.1rem;
}

.contact__link {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding-block: 0.7rem;
  border-bottom: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--t-small);
  text-decoration: none;
  color: var(--ink);
}

.contact__link i {
  color: var(--blue);
  font-size: 1.05rem;
}

.contact__link:hover {
  color: var(--blue);
}

@media (max-width: 900px) {
  .contact__grid { grid-template-columns: 1fr; }
}

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

.footer {
  border-top: 1px solid var(--rule);
  padding-block: clamp(2.5rem, 5vw, 4rem);
}

.footer__inner {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 2rem;
  flex-wrap: wrap;
}

.footer__note {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
  max-width: 40ch;
  line-height: 1.6;
}

.footer__links {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.footer__links a {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-soft);
  text-decoration: none;
}

.footer__links a:hover {
  color: var(--blue);
}

/* ----------------------------------------------------- project detail page */

.pd-hero {
  padding-top: clamp(8rem, 12vw, 11rem);
  padding-bottom: clamp(2.5rem, 5vw, 4rem);
}

.pd-hero__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}

.pd-hero__title {
  font-size: clamp(2.5rem, 6vw, 4.75rem);
  letter-spacing: -0.045em;
  margin-top: 1rem;
}

.pd-hero__tagline {
  font-family: var(--font-mono);
  color: var(--blue);
  margin-top: 0.9rem;
  font-size: var(--t-small);
}

.pd-shot {
  border: 1px solid var(--rule);
  border-radius: var(--r);
  overflow: hidden;
  background: var(--paper-sunk);
  aspect-ratio: 16 / 9;
}

.pd-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pd-grid {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}

.pd-body h2 {
  font-size: var(--t-h3);
  letter-spacing: -0.03em;
  margin-bottom: 1rem;
}

.pd-body h2:not(:first-child) {
  margin-top: clamp(2.5rem, 4vw, 3.5rem);
}

.pd-body p {
  color: var(--ink-soft);
  max-width: var(--maxw-text);
  margin-bottom: 1.1rem;
  line-height: 1.65;
}

.pd-challenge {
  border-left: 2px solid var(--blue);
  padding-left: 1.25rem;
  margin-bottom: 1.5rem;
}

.pd-challenge h3 {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 500;
}

.pd-challenge p {
  margin-top: 0.4rem;
  margin-bottom: 0;
  font-size: 0.97rem;
}

.pd-facts {
  position: sticky;
  top: 92px;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  padding: 1.5rem;
  background: var(--paper-raised);
}

.pd-fact {
  padding-block: 0.7rem;
  border-bottom: 1px solid var(--rule);
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
}

.pd-fact:last-of-type {
  border-bottom: 0;
}

.pd-fact span:first-child {
  color: var(--ink-faint);
}

.pd-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 1.25rem;
}

.pd-gallery figure {
  margin: 0;
}

.pd-gallery img {
  width: 100%;
  border: 1px solid var(--rule);
  border-radius: var(--r);
}

.pd-gallery figcaption {
  margin-top: 0.6rem;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

@media (max-width: 900px) {
  .pd-grid { grid-template-columns: 1fr; }
  .pd-facts { position: static; }
}

/* ------------------------------------------------------------ error page */

.errpage {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  text-align: center;
  padding: var(--gutter);
}

.errpage__code {
  font-family: var(--font-mono);
  font-size: clamp(3rem, 10vw, 6rem);
  color: var(--blue);
  letter-spacing: -0.05em;
}
