/* joshuagalvan.com home page. Editorial: clean sans, a hand-written accent in orange. */

@font-face {
  font-family: "JaneAust";
  src: url("/assets/fonts/JaneAust.ttf") format("truetype");
  font-display: swap;
}

:root {
  --bg: #ffffff;
  --paper: #f2f2f0;
  --paper-deep: #d9d9d6;
  --ink: #111111;
  --grey: #6e6e6e;
  --grey-soft: #b9b7b2;
  --line: #d4d4d4;
  --orange: #ff5e00;
  --orange-deep: #f75708;
  --night: #151515;

  --sans: "Inter Tight", ui-sans-serif, system-ui, sans-serif;
  --accent: "JaneAust", cursive;

  --gutter: 5vw;
  --ease-out: cubic-bezier(.2, .7, .2, 1);
  --ease-draw: cubic-bezier(.6, 0, .2, 1);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; } /* the menu locks scroll without the page shifting */
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 16px/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}
img { display: block; max-width: 100%; }
a { color: inherit; }
h1, h2, h3, p, ul, ol, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }
button { font: inherit; color: inherit; }

.jg-sr {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.wrap { max-width: 1100px; margin: 0 auto; }
[tabindex="-1"]:focus { outline: none; } /* sections take focus after a menu jump */
.wrap-wide { max-width: 1440px; margin: 0 auto; }

/* ---------- Hand-written accent (JaneAust, always orange) ---------- */
.accent {
  display: inline-block;
  font-family: var(--accent);
  font-weight: 400;
  font-style: normal;
  line-height: 1;
  letter-spacing: 0;
  color: var(--orange);
  white-space: nowrap;
  rotate: -6deg;
}
/* Writes itself on, left to right (signature.js adds .is-written). JaneAust
   swashes reach well past the box (descenders ~40% below, the first letter
   ~10% to the left), so the clip leaves room; before writing, the visible
   strip is empty so no swash peeks out. */
.js .accent {
  clip-path: inset(-60% 115% -60% -15%);
  transition: clip-path 1.2s var(--ease-draw);
}
.js .accent.is-written { clip-path: inset(-60% -15%); }
@media (prefers-reduced-motion: reduce) {
  .js .accent { clip-path: none; transition: none; }
}

/* Small section label: "01 About" over a thin rule */
.label {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font: 500 13px/1.2 var(--sans);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--grey);
}
.label-n { color: var(--orange); font-variant-numeric: tabular-nums; }

/* Big section titles with an accent beside them */
.sk-title, .work-title, .hi-title {
  font: 500 clamp(44px, 6vw, 96px)/.95 var(--sans);
  letter-spacing: -.045em;
}
.sk-title .accent, .work-title .accent, .hi-title .accent {
  font-size: .5em;
  margin-left: .2em;
  vertical-align: .35em;
}

/* Scroll reveal. Only hidden when JS runs, so the page reads fine without it. */
.js .reveal {
  opacity: 0;
  transform: translateY(32px);
  transition: opacity .9s var(--ease-out), transform .9s var(--ease-out);
}
.js .reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; transition: none; }
}

/* ---------- Top bar + menu panel (menu.js) ---------- */
.top-logo {
  position: fixed;
  top: 22px;
  left: var(--gutter);
  z-index: 50;
  font: 600 15px/1 var(--sans);
  letter-spacing: .04em;
  text-decoration: none;
  /* White + difference: reads black on light sections, white over the drone band */
  color: #fff;
  mix-blend-mode: difference;
}
.menu-btn, .menu-close {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 12px;
  background: var(--night);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.menu-btn { position: fixed; top: 12px; right: var(--gutter); z-index: 50; transition: scale .2s var(--ease-out); }
.menu-btn:active { scale: .94; }
.menu-btn-lines, .menu-close span { position: relative; display: block; width: 18px; height: 10px; }
.menu-btn-lines::before, .menu-btn-lines::after,
.menu-close span::before, .menu-close span::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1.5px;
  border-radius: 1px;
  background: #f4f2ee;
}
.menu-btn-lines::before { top: 0; }
.menu-btn-lines::after { bottom: 0; }
.menu-close span::before, .menu-close span::after { top: 50%; margin-top: -.75px; }
.menu-close span::before { rotate: 45deg; }
.menu-close span::after { rotate: -45deg; }
.menu-btn:focus-visible, .menu-close:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; }

.menu {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(17, 17, 17, 0);
  transition: background-color .4s var(--ease-out);
}
.menu.is-open { background: rgba(17, 17, 17, .18); }
.menu[hidden] { display: none; }
.menu-panel {
  position: absolute;
  top: 12px;
  right: 12px;
  bottom: 12px;
  width: min(520px, calc(100% - 24px));
  display: flex;
  flex-direction: column;
  padding: 72px 32px 28px;
  border-radius: 16px;
  background: var(--night);
  color: #f4f2ee;
  box-shadow: 0 30px 80px rgba(0, 0, 0, .25);
  overflow-y: auto; /* phones in landscape */
  opacity: 0;
  transform: translateX(28px);
  transition: opacity .35s var(--ease-out), transform .5s var(--ease-out);
}
.menu.is-open .menu-panel { opacity: 1; transform: none; }
.menu-close { position: absolute; top: 12px; right: 12px; background: rgba(255, 255, 255, .08); }
.menu-links { display: flex; flex-direction: column; }
.menu-links a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 0 12px;
  border-bottom: 1px solid rgba(255, 255, 255, .12);
  font: 500 clamp(44px, 6vw, 72px)/1 var(--sans);
  letter-spacing: -.04em;
  text-decoration: none;
  color: rgba(244, 242, 238, .55);
  transition: color .25s;
}
.menu-links a:hover, .menu-links a:focus-visible { color: #f4f2ee; outline: none; }
.menu-arrow { font-size: .4em; transition: transform .3s var(--ease-out); }
.menu-links a:hover .menu-arrow, .menu-links a:focus-visible .menu-arrow { transform: translateX(4px) rotate(-45deg); color: var(--orange); }
.menu-foot { margin-top: auto; }
.menu-label { font-size: 13px; color: rgba(244, 242, 238, .45); margin-bottom: 10px; }
.menu-social { display: flex; flex-direction: column; gap: 4px; font-size: 15px; }
.menu-social a { text-decoration: none; }
.menu-social a:hover { color: var(--orange); }
@media (prefers-reduced-motion: reduce) { .menu, .menu-panel { transition: none; } }

/* ---------- Hero ---------- */
.hero {
  position: relative;
  height: 100vh;
  height: 100svh;
  min-height: 560px;
  overflow: hidden;
  background: radial-gradient(120% 90% at 50% 20%, var(--paper) 0%, var(--paper-deep) 75%, #cfcfcb 100%);
}
.hero-pitch, .hero-place {
  position: absolute;
  top: 96px;
  z-index: 3;
  font-size: 15px;
  line-height: 1.45;
  color: #444;
}
.hero-pitch { left: var(--gutter); max-width: 300px; }
.hero-place { right: var(--gutter); color: var(--grey); }
.hero-photo {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: clamp(240px, 34vw, 560px);
  aspect-ratio: 3 / 4;
  translate: -50% 0;
  overflow: hidden;
  border-radius: 10px 10px 0 0;
  background: #2b2b2b;
}
/* Blurs into focus on load. CSS only, so a failed script can't leave it blurred. */
.hero-img { display: block; width: 100%; height: 100%; object-fit: cover; animation: hero-focus 1.4s cubic-bezier(.2, .7, .2, 1) .1s both; }
@keyframes hero-focus { from { filter: blur(14px); transform: scale(1.05); } to { filter: none; transform: none; } }
/* Color shows through where the splotch is (splotch.js); under the fade */
.hero-splotch, .hero-splotch-face { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.hero-splotch { filter: url(#hero-liquid); }
/* The face part of the color, drawn by splotch.js without the warp */
.hero-splotch-face { filter: none; }
.hero-defs { position: absolute; }
/* Letter swoosh around the silhouette (outline.js); above the fade */
.hero-outline { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; pointer-events: none; }
/* Dark fade at the bottom so the white name reads over the white tee and bench */
.hero-photo::after {
  content: "";
  position: absolute;
  inset: 45% 0 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .55) 100%);
  pointer-events: none;
}
.hero-name {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1.6vw;
  z-index: 2;
  padding: 0 1.5vw;
  font: 500 12.9vw/.8 var(--sans);
  letter-spacing: -.06em;
  text-align: center;
  white-space: nowrap;
  color: #fff;
  will-change: transform;
}
.hero-sig1, .hero-sig2 { position: absolute; z-index: 3; }
.hero-sig1 { left: 4vw; bottom: 11vw; font-size: clamp(48px, 8vw, 140px); rotate: -7deg; }
.hero-sig2 { right: 5vw; bottom: 12vw; font-size: clamp(32px, 4.6vw, 84px); rotate: -9deg; }

/* ---------- About ---------- */
.about { padding: 140px var(--gutter) 120px; }
.statement {
  margin-top: 48px;
  font: 500 clamp(34px, 4.6vw, 68px)/1.04 var(--sans);
  letter-spacing: -.04em;
}
.statement b { font-weight: 600; }
.statement em {
  display: inline-block;
  font-family: var(--accent);
  font-style: normal;
  font-weight: 400;
  font-size: 1.15em;
  line-height: .8;
  letter-spacing: 0;
  color: var(--orange);
  rotate: -4deg;
}
.lede { max-width: 460px; margin: 40px 0 0 auto; font-size: 20px; line-height: 1.5; color: var(--grey); }
.now {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin-top: 72px;
  font-size: 13px;
  color: var(--grey);
  font-variant-numeric: tabular-nums;
}
.now span:first-child { color: var(--ink); }

/* About word peeks (peek.js, mouse screens only) */
@media (hover: hover) and (pointer: fine) {
  [data-hover] {
    text-decoration: underline;
    text-decoration-color: var(--line);
    text-decoration-thickness: 2px;
    text-underline-offset: .14em;
    transition: color .25s, text-decoration-color .25s;
  }
  [data-hover]:hover { color: var(--orange); text-decoration-color: var(--orange); }
}
.peek { position: fixed; top: 0; left: 0; z-index: 40; pointer-events: none; width: 0; height: 0; }
.peek img {
  position: absolute;
  left: 20px;
  top: -324px; /* above and right of the pointer, clear of the line being read */
  width: 240px;
  max-width: none;
  height: 300px;
  object-fit: cover;
  border-radius: 12px;
  box-shadow: 0 24px 60px rgba(22, 22, 22, .18);
  opacity: 0;
  scale: .7;
  transform-origin: 0 100%;
  transition: opacity .3s, scale .45s cubic-bezier(.3, 1.4, .5, 1);
}
.peek.is-on img { opacity: 1; scale: 1; }

/* ---------- Drone band (drone.js) ---------- */
/* The frame starts as a rounded card and zooms open to full width as it
   scrolls in, while the clip inside eases back from a slight zoom (drone.js
   sets the custom properties). The poster covers the dark fallback until the
   clip plays. */
.aerial-frame {
  position: relative;
  height: clamp(420px, 80vh, 860px);
  overflow: hidden;
  background: radial-gradient(120% 90% at 50% 15%, #3b3b39 0%, var(--night) 70%);
  clip-path: inset(var(--aerial-y, 0px) var(--aerial-x, 0px) round var(--aerial-r, 0px));
}
.aerial-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(var(--aerial-zoom, 1));
}
/* Light shade so the white words read over bright sky or water */
.aerial-frame::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,.18), rgba(0,0,0,0) 40%, rgba(0,0,0,.32));
  pointer-events: none;
}
.aerial-words {
  position: absolute; left: 0; right: 0; top: 50%; z-index: 1;
  transform: translateY(-50%);
  overflow: hidden;
  padding: .45em 0;
  font-size: clamp(56px, 10vw, 180px);
  filter: url(#aerial-liquid);
}
/* Two identical halves; drone.js slides the track and wraps it by half its
   width, so it loops without a seam. It runs faster while you scroll and
   turns around when you scroll back up. */
.aerial-track {
  display: flex; align-items: center;
  width: max-content;
  white-space: nowrap;
  font-size: inherit;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -.04em;
  text-transform: uppercase;
  color: #fff;
  will-change: transform;
}
.aerial-track > * { margin-right: .35em; }
/* drone.js splits the words into letters and runs a wave through them */
.aerial-l { display: inline-block; will-change: transform; }
.aerial-josh { will-change: transform; }
.aerial-track .is-line { color: transparent; -webkit-text-stroke: .025em #fff; }
.aerial-josh {
  font-family: var(--accent);
  font-weight: 400;
  font-size: 1.15em;
  padding: 0 .05em 0 .45em; /* room for the J swash, which reaches left */
  letter-spacing: 0;
  text-transform: none;
  color: var(--orange);
  rotate: -6deg;
}
.aerial-track i { flex: none; width: .16em; height: .16em; border-radius: 50%; background: var(--orange); }
.aerial-note {
  position: absolute; z-index: 2;
  right: var(--gutter); bottom: 14%;
  font-size: clamp(34px, 5.5vw, 88px);
}
.aerial-defs { position: absolute; }
@media (prefers-reduced-motion: reduce) {
  .hero-img { animation: none; }
  .aerial-words { filter: none; }
}

/* ---------- What I do (skills.js) ---------- */
.skills { padding: 80px var(--gutter) 120px; }
.sk-head { display: grid; grid-template-columns: 1fr auto; gap: 40px 32px; align-items: end; }
.sk-head .label { grid-column: 1 / -1; }
.sk-intro { max-width: 280px; font-size: 15px; color: var(--grey); }
.sk-body {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 5vw;
  align-items: start;
  margin-top: 56px;
}
.sk-list li:last-child .sk-row { border-bottom: 1px solid var(--line); }
.sk-row {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  align-items: baseline;
  gap: 10px 16px;
  width: 100%;
  padding: 22px 0;
  border: 0;
  border-top: 1px solid var(--line);
  background: none;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sk-n { font-size: 13px; color: var(--grey); font-variant-numeric: tabular-nums; transition: color .3s; }
.sk-word {
  font: 500 clamp(34px, 4.4vw, 64px)/1 var(--sans);
  letter-spacing: -.035em;
  color: var(--grey-soft);
  white-space: nowrap;
  transition: color .3s;
}
.sk-detail {
  grid-column: 2;
  font-size: 13px;
  color: var(--grey);
  white-space: nowrap;
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity .35s, transform .35s var(--ease-out);
}
.sk-row[aria-pressed="true"] .sk-word { color: var(--ink); }
.sk-row[aria-pressed="true"] .sk-n { color: var(--orange); }
.sk-row[aria-pressed="true"] .sk-detail { opacity: 1; transform: none; }
.sk-row:focus-visible { outline: 2px solid var(--orange); outline-offset: 4px; }
.sk-stage {
  position: sticky;
  top: 96px;
  aspect-ratio: 4 / 5;
  border-radius: 12px;
  overflow: hidden;
  background: #dcdad5;
  container-type: size; /* the brand kit sizes itself to the stage */
}
.sk-stage svg { display: block; width: 100%; height: 100%; }
/* Skill cards: each skill shows a card built in code instead of a project
   picture (brand kit, dieline, browser + code, spec sheet). Sized in container
   units so each card scales with the stage; the wide phone stage gets two
   columns. */
.sk-kit {
  position: absolute;
  inset: 0;
  display: grid;
  gap: 5cqw;
  padding: 7cqw;
  background: var(--paper);
  color: var(--ink);
  visibility: hidden;
}
.sk-stage[data-show="brand"] .sk-kit[data-kit="brand"],
.sk-stage[data-show="pack"] .sk-kit[data-kit="pack"],
.sk-stage[data-show="web"] .sk-kit[data-kit="web"],
.sk-stage[data-show="make"] .sk-kit[data-kit="make"] { visibility: visible; }
.sk-stage.is-wiping .sk-kit { filter: url(#sk-liquid); }
.kit-top {
  display: flex;
  justify-content: space-between;
  gap: 4cqw;
  font-size: max(10px, 2.6cqw);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--grey);
}
@keyframes kit-in { from { opacity: 0; transform: translateY(14%); } }

/* Brand kit: wordmark, colors, type, marks */
.sk-kit[data-kit="brand"] { grid-template-rows: auto minmax(0, 1fr) auto auto; }
.kit-mark { align-self: center; display: flex; flex-direction: column; }
.kit-word { font: 600 27cqw/.82 var(--sans); letter-spacing: -.06em; }
.kit-sig {
  align-self: flex-end;
  margin-top: -1cqw;
  padding: .15em .3em .3em; /* room for the swashes, so nothing clips */
  font: 9cqw/1 var(--accent);
  white-space: nowrap;
  color: var(--orange);
}
.kit-swatches { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 2cqw; }
.kit-swatches li {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: .4em;
  height: 30cqw;
  padding: 2.6cqw;
  border: 1px solid rgba(17, 17, 17, .1);
  border-radius: 2.4cqw;
  background: var(--c);
  font-size: max(9px, 2.6cqw);
  line-height: 1.1;
}
.kit-swatches li.is-dark { border-color: transparent; color: #fff; }
.kit-swatches b { font-weight: 500; }
.kit-swatches span { opacity: .7; font-variant-numeric: tabular-nums; }
.sk-stage[data-show="brand"] .kit-swatches li { animation: kit-in .6s var(--ease-out) calc(var(--i) * 70ms + 120ms) both; }
.kit-foot { display: grid; grid-template-columns: auto auto 1fr; gap: 5cqw; align-items: end; }
.kit-type { display: flex; flex-direction: column; gap: 1.4cqw; }
.kit-aa { font: 500 12cqw/.9 var(--sans); letter-spacing: -.04em; }
.kit-aa-script { padding-right: .15em; font: 12cqw/.9 var(--accent); color: var(--orange); letter-spacing: 0; }
.kit-type small { font-size: max(9px, 2.4cqw); line-height: 1.3; color: var(--grey); }
.kit-marks { display: flex; justify-content: flex-end; align-items: flex-end; gap: 2cqw; }
.kit-icon {
  display: grid;
  place-items: center;
  width: 14cqw;
  aspect-ratio: 1;
  border-radius: 28%;
  background: var(--ink);
  color: #fff;
  font: 600 8cqw/1 var(--sans);
  letter-spacing: -.04em;
}
.kit-icon:nth-child(2) { width: 9.5cqw; font-size: 5.4cqw; background: var(--orange); }
.kit-icon:nth-child(3) { width: 6cqw; font-size: 3.4cqw; }

/* Dieline: a tuck box laid flat. The cut line (solid ink) draws itself in,
   then the folds (dashed orange), the bleed and the printed front panel. */
.sk-kit[data-kit="pack"] { grid-template-rows: auto minmax(0, 1fr) auto auto; }
.die { width: 100%; height: 100%; overflow: visible; }
.die-art { fill: var(--ink); }
.die-logo { font: 600 19px var(--sans); letter-spacing: -.05em; fill: #fff; }
.die-dot { fill: var(--orange); }
.die-bleed { fill: none; stroke: var(--grey); stroke-width: .7; stroke-dasharray: 1 2; }
.die-cut {
  fill: #fff;
  stroke: var(--ink);
  stroke-width: 1.3;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}
.die-fold { fill: none; stroke: var(--orange); stroke-width: 1.1; stroke-dasharray: 4 3; }
.sk-stage[data-show="pack"] .die-cut { animation: die-draw 1.4s var(--ease-draw) .1s both; }
.sk-stage[data-show="pack"] .die-fold { animation: die-fade .5s ease 1.1s both; }
.sk-stage[data-show="pack"] .die-art,
.sk-stage[data-show="pack"] .die-logo,
.sk-stage[data-show="pack"] .die-dot,
.sk-stage[data-show="pack"] .die-bleed { animation: die-fade .6s ease .9s both; }
@keyframes die-draw { from { stroke-dashoffset: 1; fill-opacity: 0; } 70% { fill-opacity: 0; } }
@keyframes die-fade { from { opacity: 0; } }
.die-legend { display: flex; flex-wrap: wrap; gap: 1.6cqw 5cqw; font-size: max(9px, 2.5cqw); color: var(--grey); }
.die-legend li { display: flex; align-items: center; gap: 1.6cqw; }
.die-legend li::before { content: ""; width: 6cqw; border-top: 2px solid var(--ink); }
.die-legend .is-fold::before { border-top: 2px dashed var(--orange); }
.die-legend .is-bleed::before { border-top: 1px dotted var(--grey); }
.die-skus { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 3cqw; }
.die-skus span { display: flex; align-items: center; gap: 2cqw; font-size: max(9px, 2.5cqw); font-variant-numeric: tabular-nums; }
.die-skus i { width: 6.5cqw; aspect-ratio: 2 / 3; border-radius: 1cqw; background: var(--c); box-shadow: inset 0 0 0 1px rgba(17, 17, 17, .1); }
.sk-stage[data-show="pack"] .die-skus span { animation: kit-in .6s var(--ease-out) calc(var(--i) * 80ms + 1.1s) both; }

/* Website: a page in a browser, next to the code that builds it */
.sk-kit[data-kit="web"] { grid-template-rows: auto minmax(0, 1fr) auto; }
.web-win {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  border-radius: 2.4cqw;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(17, 17, 17, .08), 0 3cqw 6cqw -3cqw rgba(17, 17, 17, .25);
}
.web-bar { display: flex; align-items: center; gap: 1.2cqw; padding: 2cqw 2.4cqw; border-bottom: 1px solid var(--line); }
.web-bar i { width: 1.8cqw; aspect-ratio: 1; border-radius: 50%; background: var(--grey-soft); }
.web-bar i:first-child { background: var(--orange); }
.web-url {
  flex: 1;
  margin-left: 2cqw;
  padding: .6cqw 2cqw;
  border-radius: 99px;
  background: var(--paper);
  font-size: max(9px, 2.2cqw);
  color: var(--grey);
}
.web-page { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 3cqw; padding: 4cqw; }
.web-nav { display: flex; align-items: center; gap: 2cqw; }
.web-nav b { margin-right: auto; font: 600 3cqw/1 var(--sans); letter-spacing: -.04em; }
.web-nav span { width: 6cqw; height: .9cqw; border-radius: 1cqw; background: var(--grey-soft); }
.web-h { max-width: 80%; font: 500 6.4cqw/1 var(--sans); letter-spacing: -.04em; }
.web-btn { align-self: flex-start; padding: 1.2cqw 2.6cqw; border-radius: 99px; background: var(--ink); color: #fff; font-size: max(9px, 2.4cqw); }
.web-cards { flex: 1; min-height: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2cqw; }
.web-cards i { border-radius: 1.6cqw; background: var(--paper-deep); }
.web-cards i:first-child { background: var(--orange); }
.sk-stage[data-show="web"] .web-page > * { animation: kit-in .6s var(--ease-out) calc(var(--i) * 90ms + 150ms) both; }
.web-foot { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4cqw; align-items: end; }
.web-code {
  margin: 0;
  padding: 3cqw 3.4cqw;
  overflow: hidden;
  border-radius: 2.4cqw;
  background: var(--night);
  color: #e8e6e1;
  font: max(9px, 2.5cqw)/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  white-space: pre;
}
.c-tag { color: var(--orange); }
.c-attr { color: var(--grey-soft); }
.c-str { color: #ffc29e; }
.c-caret { display: inline-block; width: .5em; height: 1.1em; margin-left: .15em; vertical-align: -.2em; background: var(--orange); animation: caret 1s steps(1) infinite; }
@keyframes caret { 50% { opacity: 0; } }
.web-sizes { display: flex; flex-direction: column; gap: 1cqw; font-size: max(9px, 2.4cqw); color: var(--grey); text-align: right; font-variant-numeric: tabular-nums; }

/* Spec sheet: the product drawn front and side (outline draws itself in),
   dimensions in orange, a specs table and the sample-to-production steps */
.sk-kit[data-kit="make"] { grid-template-rows: auto minmax(0, 1fr) auto auto; }
.spec { width: 100%; height: 100%; overflow: visible; }
.spec-line { fill: #fff; stroke: var(--ink); stroke-width: 1.3; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 0; }
.spec-detail { fill: none; stroke: var(--grey-soft); stroke-width: .8; }
.spec-center { fill: none; stroke: var(--grey); stroke-width: .6; stroke-dasharray: 8 3 2 3; }
.spec-dim { fill: none; stroke: var(--orange); stroke-width: .9; }
.spec-txt { font: 500 9px var(--sans); fill: var(--orange); }
.sk-stage[data-show="make"] .spec-line { animation: die-draw 1.4s var(--ease-draw) .1s both; }
.sk-stage[data-show="make"] .spec-detail,
.sk-stage[data-show="make"] .spec-center { animation: die-fade .5s ease .9s both; }
.sk-stage[data-show="make"] .spec-dim,
.sk-stage[data-show="make"] .spec-txt { animation: die-fade .5s ease 1.1s both; }
.spec-table { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2cqw 5cqw; font-size: max(9px, 2.5cqw); }
.spec-table div { display: flex; justify-content: space-between; gap: 2cqw; padding-bottom: 1.4cqw; border-bottom: 1px solid var(--line); }
.spec-table dt { color: var(--grey); }
.spec-table dd { font-weight: 500; text-align: right; white-space: nowrap; }
.spec-steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); font-size: max(9px, 2.4cqw); color: var(--grey); }
.spec-steps li { position: relative; padding-top: 4.2cqw; }
.spec-steps li::after { content: ""; position: absolute; top: 1.2cqw; left: 0; right: 0; border-top: 1.5px solid var(--ink); }
.spec-steps li:last-child::after { display: none; }
.spec-steps li::before {
  content: "";
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  width: 2.4cqw;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--paper);
  box-shadow: inset 0 0 0 1.5px var(--ink);
}
.spec-steps .is-done::before { background: var(--ink); }
.spec-steps .is-now { color: var(--ink); font-weight: 500; }
.spec-steps .is-now::before { background: var(--orange); box-shadow: none; animation: spec-pulse 1.6s ease-in-out infinite; }
@keyframes spec-pulse { 50% { box-shadow: 0 0 0 1.2cqw rgba(255, 94, 0, .22); } }
.sk-stage[data-show="make"] .spec-steps li { animation: kit-in .6s var(--ease-out) calc(var(--i) * 90ms + 1s) both; }

/* Wide phone stage: two columns */
@container (aspect-ratio > 1) {
  .sk-kit { padding: 5cqw; gap: 3.5cqw 5cqw; }
  .kit-top { grid-column: 1 / -1; }
  .sk-kit[data-kit="brand"] { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr) auto; }
  .kit-foot { grid-column: 1 / -1; }
  .kit-mark { grid-area: 2 / 1; }
  .kit-swatches { grid-area: 2 / 2; }
  .kit-word { font-size: 20cqw; }
  .kit-sig { font-size: 6cqw; }
  .kit-swatches { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: minmax(0, 1fr); }
  .kit-swatches li { height: auto; padding: 2cqw; font-size: max(9px, 2.2cqw); }
  .kit-aa { font-size: 9cqw; }
  .kit-icon { width: 10cqw; font-size: 5.6cqw; }
  .kit-icon:nth-child(2) { width: 7cqw; font-size: 3.9cqw; }
  .kit-icon:nth-child(3) { width: 4.6cqw; font-size: 2.6cqw; }

  .sk-kit[data-kit="pack"] { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr) auto; }
  .die { grid-area: 2 / 1 / 4 / 2; }
  .die-legend { grid-area: 2 / 2; align-self: center; flex-direction: column; }
  .die-skus { grid-area: 3 / 2; grid-template-columns: 1fr; gap: 1.6cqw; }
  .die-skus i { width: 4cqw; }

  .sk-kit[data-kit="web"] { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .web-win { grid-area: 2 / 1; }
  .web-foot { grid-area: 2 / 2; grid-template-columns: 1fr; grid-template-rows: auto auto; align-content: space-between; }
  .web-h { font-size: 4.8cqw; }
  .web-cards i { min-height: 6cqw; }
  .web-code { font-size: max(9px, 2.1cqw); padding: 2.6cqw; }
  .web-sizes { text-align: left; }

  .sk-kit[data-kit="make"] { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr) auto; }
  .spec { grid-area: 2 / 1; }
  .spec-table { grid-area: 2 / 2; align-self: center; grid-template-columns: 1fr; }
  .spec-steps { grid-area: 3 / 1 / 4 / 3; }
}
@media (prefers-reduced-motion: reduce) { .sk-stage[data-show] .sk-kit * { animation: none; } }

/* Toolkit: what the work gets made with, set small like a credits list */
.sk-tools {
  display: grid;
  grid-template-columns: 160px repeat(4, minmax(0, 1fr));
  gap: 24px 32px;
  margin-top: 56px; /* the list's last row already draws the line above */
}
.sk-tools-title { font-size: 13px; font-weight: 500; letter-spacing: .12em; text-transform: uppercase; color: var(--grey); }
.sk-tools-group h3 { margin-bottom: 10px; font-size: 13px; font-weight: 500; color: var(--orange); }
.sk-tools-group ul { display: flex; flex-direction: column; gap: 4px; font-size: 15px; line-height: 1.4; }

/* ---------- Selected work (worklist.js) ---------- */
.work { padding: 80px var(--gutter) 120px; }
.work-title { margin-top: 40px; }
.wl { margin-top: 56px; }
.wl-row { border-top: 1px solid var(--line); }
.wl-row:last-child { border-bottom: 1px solid var(--line); }
.wl-top {
  display: grid;
  grid-template-columns: 64px 1fr auto;
  align-items: center;
  gap: 16px;
  padding: 22px 0;
  cursor: pointer;
}
.wl-n { font-size: 13px; color: var(--grey); font-variant-numeric: tabular-nums; }
.wl-name { font: 500 clamp(36px, 5.4vw, 84px)/1 var(--sans); letter-spacing: -.04em; }
.wl-name a { text-decoration: none; color: var(--grey-soft); transition: color .3s; }
.wl-row.is-open .wl-name a { color: var(--ink); }
.wl-name a:hover { color: var(--orange); }
.wl-name a:focus-visible { outline: 2px solid var(--orange); outline-offset: 4px; }
.wl-view {
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  color: var(--grey);
  transition: background-color .25s, color .25s, border-color .25s;
}
.wl-row.is-open .wl-view { border-color: var(--ink); color: var(--ink); }
.wl-view:hover { background: var(--ink); color: #fff; }
/* A project eases open over about a second, and its image grows in from a
   smaller, rounder crop to fill the list width, so each one arrives. */
.wl-detail { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 1s cubic-bezier(.65, 0, .15, 1); }
.wl-row.is-open .wl-detail { grid-template-rows: 1fr; }
html:not(.js) .wl-detail { grid-template-rows: 1fr; }
.wl-inner {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  gap: 0 16px;
  min-height: 0;
  overflow: hidden;
}
.wl-copy { grid-column: 2; display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 16px 40px; padding-bottom: 24px; }
.wl-spec { display: flex; flex-wrap: wrap; gap: 12px 40px; }
.wl-spec dt { font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--grey); }
.wl-spec dd { margin-top: 4px; font-size: 15px; }
.wl-copy p { max-width: 420px; font-size: 15px; color: var(--grey); }
.wl-img {
  grid-column: 1 / -1;
  width: 100%;
  height: min(78vh, 56vw);
  margin-bottom: 40px;
  object-fit: cover;
  border-radius: 10px;
  clip-path: inset(8% 10% round 28px);
  transform: scale(1.1);
  transition: clip-path 1.2s cubic-bezier(.65, 0, .15, 1) .1s, transform 1.4s var(--ease-out) .1s;
}
.wl-row.is-open .wl-img, html:not(.js) .wl-img { clip-path: inset(0 round 10px); transform: none; }
@media (prefers-reduced-motion: reduce) { .wl-detail, .wl-img { transition: none; } }

/* ---------- Say hi button over the projects (workhi.js) ---------- */
/* Hidden (and out of the tab order) until workhi.js adds .is-on. */
.work-hi {
  position: fixed; z-index: 40;
  left: 50%; bottom: calc(22px + env(safe-area-inset-bottom, 0px));
  display: inline-flex; align-items: center; gap: 10px;
  padding: 14px 22px;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font-size: 15px; font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  box-shadow: 0 14px 40px rgba(17, 17, 17, .22);
  opacity: 0; visibility: hidden;
  transform: translate(-50%, 24px);
  transition: opacity .35s, transform .5s var(--ease-out), visibility 0s .5s, background-color .25s;
}
.work-hi.is-on { opacity: 1; visibility: visible; transform: translate(-50%, 0); transition-delay: 0s; }
.work-hi-q { color: rgba(255, 255, 255, .6); }
.work-hi-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--orange); animation: work-hi-pulse 2s ease-in-out infinite; }
@keyframes work-hi-pulse { 50% { box-shadow: 0 0 0 6px rgba(255, 94, 0, 0); } 0%, 100% { box-shadow: 0 0 0 0 rgba(255, 94, 0, .5); } }
.work-hi:hover { background: var(--orange); }
.work-hi:hover .work-hi-q { color: rgba(255, 255, 255, .85); }
.work-hi:hover .work-hi-dot { background: #fff; }
.work-hi:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .work-hi { transition: opacity .2s, visibility 0s .2s; transform: translate(-50%, 0); }
  .work-hi-dot { animation: none; }
}

/* ---------- Say hi / footer ---------- */
.say-hi { padding: 80px var(--gutter) 1.2vw; overflow: hidden; }
.hi-title { margin: 40px 0 72px; }
.foot-grid {
  display: grid;
  grid-template-columns: 200px 200px minmax(0, 520px);
  gap: 32px;
}
.foot-links { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; font-size: 17px; }

/* Text roll on hover: the word slides up and a copy slides in from below */
.roll { display: inline-block; overflow: hidden; vertical-align: top; text-decoration: none; opacity: .8; transition: opacity .2s; }
.roll:hover { opacity: 1; }
.roll > span { position: relative; display: block; line-height: 1.6; transition: transform .45s var(--ease-out); }
.roll > span::after { content: attr(data-text); position: absolute; left: 0; top: 100%; }
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .roll:hover > span { transform: translateY(-100%); }
}
.roll:focus-visible { outline: 2px solid var(--orange); outline-offset: 4px; border-radius: 4px; }

.join { display: flex; flex-direction: column; gap: 20px; }
.join-label { font-size: 17px; font-weight: 500; }
/* Send me a message: one button that opens a text to Joshua */
.join-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  height: 56px;
  padding: 0 26px 0 30px;
  border-radius: 99px;
  background: var(--ink);
  color: #fff;
  font-size: 17px;
  font-weight: 500;
  text-decoration: none;
  transition: background-color .25s;
}
.join-btn .roll { opacity: 1; }
.join-arrow { transition: transform .35s var(--ease-out); }
.join-btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 4px; }
@media (hover: hover) {
  .join-btn:hover { background: var(--orange); }
  .join-btn:hover .join-arrow { transform: translateX(4px); }
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .join-btn:hover .roll > span { transform: translateY(-100%); }
}

/* Footer name, edge to edge at the very bottom (giant.js fits and splits it):
   an orange wire draws across under each word, then the letters rise out of
   it as outlined script and fill in ink. Padding in em leaves room for the
   swashes and descenders, so nothing clips. */
.giant {
  margin: 96px calc(-1 * var(--gutter)) 0;
  padding: .1em 2vw .2em;
  font: 400 15vw/1 var(--accent);
  white-space: nowrap;
  color: var(--ink);
  user-select: none;
}
.giant-w { position: relative; display: inline-block; }
.giant-w + .giant-w { margin-left: .22em; }
.giant-l { display: inline-block; transform-origin: 50% 80%; }
.giant-w::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 80%;
  height: max(2px, .014em);
  border-radius: 1em;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: left;
}
.js .giant-l { color: transparent; -webkit-text-stroke: max(1px, .01em) var(--orange); transform: scaleY(0); }
.js .giant.is-in .giant-w::before { animation: giant-wire 2.2s var(--ease-draw) both; }
.js .giant.is-in .giant-l {
  animation:
    giant-rise 1.1s cubic-bezier(.3, 1.4, .5, 1) var(--d, 0s) both,
    giant-ink .7s ease calc(var(--d, 0s) + .9s) both;
}
@keyframes giant-wire {
  0%   { transform: scaleX(0); opacity: 1; }
  22%  { transform: scaleX(1); opacity: 1; }
  62%  { opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}
@keyframes giant-rise {
  0%   { transform: scaleY(0); }
  55%  { transform: scaleY(1.12); }
  78%  { transform: scaleY(.96); }
  100% { transform: none; }
}
@keyframes giant-ink { to { color: var(--ink); -webkit-text-stroke-color: transparent; } }
.js .say-hi .land {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
}
.js .say-hi.is-landed .land { opacity: 1; transform: none; }
.js .say-hi.is-landed .foot-grid .land:nth-child(1) { transition-delay: .1s; }
.js .say-hi.is-landed .foot-grid .land:nth-child(2) { transition-delay: .18s; }
.js .say-hi.is-landed .foot-grid .land:nth-child(3) { transition-delay: .26s; }
@media (prefers-reduced-motion: reduce) {
  .js .giant-l { color: var(--ink); -webkit-text-stroke: 0; transform: none; }
  .js .giant.is-in .giant-l, .js .giant.is-in .giant-w::before { animation: none; }
  .js .say-hi .land { opacity: 1; transform: none; transition: none; }
}

/* ---------- Smooth scroll (Lenis) ---------- */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }

/* ---------- Tablet + phone ---------- */
@media (max-width: 991px) {
  .sk-body { grid-template-columns: 1fr; gap: 32px; }
  /* The card sits above the list here, so it stays pinned under the menu
     button while the skills scroll by (skills.js picks the one below it).
     Pinned, it hides rows sliding up behind the header with a white strip. */
  .sk-stage { position: sticky; top: 76px; z-index: 2; order: -1; aspect-ratio: 4 / 3; }
  .sk-stage.is-stuck { box-shadow: 0 -76px 0 var(--bg), 0 14px 14px var(--bg); }
  .sk-tools { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sk-tools-title { grid-column: 1 / -1; }
  .foot-grid { grid-template-columns: 1fr 1fr; row-gap: 56px; }
  .join { grid-column: 1 / -1; }
}
@media (max-width: 767px) {
  :root { --gutter: 20px; }
  .hero-place { display: none; }
  .hero-pitch { top: 84px; max-width: 260px; font-size: 14px; }
  .hero-photo { width: 76vw; }
  .hero-sig1 { left: 3vw; bottom: 12vw; font-size: 15vw; }
  .hero-sig2 { right: 4vw; bottom: auto; top: calc(100% - 76vw * 4 / 3 - 4vw); font-size: 9vw; }
  .about { padding-top: 96px; }
  .lede { margin-left: 0; font-size: 18px; }
  .sk-head { grid-template-columns: 1fr; }
  .sk-row { grid-template-columns: 36px minmax(0, 1fr); padding: 18px 0; }
  .wl-top { grid-template-columns: 44px 1fr; }
  .wl-view { display: none; }
  .wl-inner { grid-template-columns: 44px minmax(0, 1fr); }
  .wl-img { height: auto; aspect-ratio: 1 / 1; margin-bottom: 28px; }
  .giant { margin-top: 72px; }
  .giant-w { display: block; width: fit-content; } /* fit-content so giant.js measures the text, not the line */
  .giant-w + .giant-w { margin-left: 0; }
  .menu-panel { inset: 0; width: 100%; border-radius: 0; padding: 88px 24px 32px; }
}

/* ---------- Page change (transition.js) ---------- */
/* The canvas sits over everything while the liquid pours or drains. A page
   that arrives mid-change starts fully covered (the head script adds
   .pt-in) until transition.js takes over; if it never does, the cover lifts
   on its own after a moment so the page can't stay hidden. */
.pt { position: fixed; inset: 0; z-index: 200; width: 100%; height: 100%; visibility: hidden; pointer-events: none; }
.pt.is-on { visibility: visible; pointer-events: auto; }
html.pt-in body::before {
  content: "";
  position: fixed; inset: 0; z-index: 200;
  background: #111111;
  animation: pt-safety 0s linear 2.5s forwards;
}
@keyframes pt-safety { to { visibility: hidden; } }
