/* ==========================================================================
   Bones -- shared stylesheet
   ==========================================================================
   Extracted verbatim from the inline <style> block in index.html so a second
   page (projects.html) can reuse the same overlay, nav, and card vocabulary
   instead of duplicating ~1200 lines. Only leading indentation changed.

   Loaded as <link href="assets/site.css?v=N">. /assets/* is cached for a day
   (see _headers), so BUMP ?v= on every change here or browsers keep the stale
   sheet. Same discipline app.js already follows.

   CSP: style-src is 'self' 'unsafe-inline', so a same-origin sheet is fine.
   The inline style="--c: ..." accent attributes on cards, services, and
   dialogs stay in the HTML -- they're per-element data, not styling rules.
   ========================================================================== */

/* ───────────────────────────  FONTS  ─────────────────────────── */
@font-face {
  font-family: "Instrument Sans";
  font-style: normal;
  font-weight: 400 600;           /* variable range: covers 400/500/600 */
  font-display: swap;
  src: url("fonts/instrument-sans.woff2") format("woff2");
}
@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/instrument-serif-italic.woff2") format("woff2");
}

/* ───────────────────────────  TOKENS  ─────────────────────────── */
:root {
  --beige:    #F1E4D9;
  --beige-2:  #E8DAC6;  /* one shade deeper for hairlines/cards */
  --beige-3:  #DCCBB1;  /* deeper still */
  --ink:      #1A1A1A;
  --ink-60:   rgba(26,26,26,.62);
  --ink-45:   rgba(26,26,26,.45);
  --ink-20:   rgba(26,26,26,.20);
  --ink-10:   rgba(26,26,26,.10);
  --paper:    #F8F1E5;  /* slight lift over beige for cards/forms */

  /* logo pastels */
  --sage:     #B5D198;
  --pink:     #F5C2C7;
  --sky:      #A8D0DB;
  --peach:    #F5C99B;
  --lav:      #C9B5DC;

  --col: min(1320px, 92vw);
  --rad: 14px;

  /* Scroll-motion construction lines (hero grid + contact lattice).
     "Barely-there" tuning -- felt more than seen. Nudge these two up
     toward .09 / .19 for the "restrained" look. */
  --geo-ink:        rgba(26,26,26,.055);
  --geo-ink-strong: rgba(26,26,26,.11);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 96px; }
body {
  margin: 0;
  background: var(--beige);
  color: var(--ink);
  font-family: "Instrument Sans", system-ui, sans-serif;
  font-size: 17px;
  line-height: 1.5;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; }

.wrap { width: var(--col); margin-inline: auto; }

/* Placeholder marker — every value Shane still has to supply.
   Faint highlight so it reads as "fill me," never as real content.
   Search the markup for class="tbd" to find them all. */
.tbd {
  background: rgba(176,65,62,.10);
  box-shadow: inset 0 -1px 0 rgba(176,65,62,.35);
  border-radius: 3px;
  padding: 0 .28em;
  white-space: nowrap;
}

/* Screen-reader-only text */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Editorial label */
.label {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-60);
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.label::before {
  content: "";
  width: 22px;
  height: 1px;
  background: var(--ink);
  opacity: .55;
}

/* ───────────────────────────  NAV  ─────────────────────────── */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in oklab, var(--beige) 88%, transparent);
  backdrop-filter: saturate(1.2) blur(10px);
  -webkit-backdrop-filter: saturate(1.2) blur(10px);
  transition: border-color .25s ease, background .25s ease;
  border-bottom: 1px solid transparent;
}
.nav.is-scrolled { border-bottom-color: var(--ink-10); }
.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 0;
  gap: 24px;
}
.nav__logo {
  display: flex;
  align-items: center;
  gap: 12px;
}
.nav__logo img { height: 64px; width: auto; }
.nav__links {
  display: flex;
  gap: 36px;
  align-items: center;
}
.nav__links a {
  position: relative;
  font-size: 15px;
  font-weight: 500;
  padding: 6px 0;
}
.nav__links a:not(.nav__cta)::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1.5px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .35s cubic-bezier(.2,.7,.2,1);
}
.nav__links a:not(.nav__cta):hover::after { transform: scaleX(1); }
.nav__links a.nav__cta {
  font-size: 14px;
  font-weight: 500;
  padding: 11px 20px;
  border: 1px solid var(--ink);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  transition: background .2s ease, color .2s ease;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}
.nav__links a.nav__cta::after { display: none !important; }
.nav__links a.nav__cta:hover { background: var(--ink); color: var(--beige); }

.nav__burger {
  display: none;
  width: 44px; height: 44px;
  border: 1px solid var(--ink);
  border-radius: 999px;
  background: transparent;
  padding: 0;
  position: relative;
}
.nav__burger span {
  position: absolute;
  left: 50%; top: 50%;
  width: 18px; height: 1.5px;
  background: var(--ink);
  transform: translate(-50%, -50%);
  transition: transform .3s ease, opacity .3s ease;
}
.nav__burger span:nth-child(1) { transform: translate(-50%, calc(-50% - 5px)); }
.nav__burger span:nth-child(3) { transform: translate(-50%, calc(-50% + 5px)); }
.nav.is-menu .nav__burger span:nth-child(1) { transform: translate(-50%, -50%) rotate(45deg); }
.nav.is-menu .nav__burger span:nth-child(2) { opacity: 0; }
.nav.is-menu .nav__burger span:nth-child(3) { transform: translate(-50%, -50%) rotate(-45deg); }

/* Mobile sheet */
.menu {
  position: fixed;
  inset: 0;
  background: var(--beige);
  z-index: 40;
  display: flex;
  flex-direction: column;
  padding: 96px 24px 32px;
  transform: translateY(-100%);
  transition: transform .4s cubic-bezier(.2,.7,.2,1);
  visibility: hidden;
}
.menu.is-open { transform: translateY(0); visibility: visible; }
.menu a {
  font-size: 36px;
  font-weight: 500;
  padding: 14px 0;
  border-bottom: 1px solid var(--ink-10);
  letter-spacing: -0.01em;
}
.menu__footer {
  margin-top: auto;
  font-size: 14px;
  color: var(--ink-60);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* ───────────────────────────  HERO  ─────────────────────────── */
.hero {
  padding: 88px 0 120px;
  position: relative;
}
.hero > .wrap { position: relative; z-index: 1; }

/* Drafting-grid parallax. A faint engineering grid that drifts slightly
   slower than the content as you scroll out of the hero. Pure CSS, driven
   by the scroll timeline (see SCROLL-MOTION section) -- no JS. */
.hero__geo {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  /* Clip the oversized grid with clip-path, NOT overflow:hidden. overflow
     would make this a (non-scrolling) scroll container, and animation-timeline:
     view() binds to the nearest scroll container -- freezing the parallax. */
  clip-path: inset(0);
}
.hero__grid {
  position: absolute; left: 0; right: 0; top: -25%; height: 150%;
  background-image:
    repeating-linear-gradient(0deg,  var(--geo-ink) 0 1px, transparent 1px 30px),
    repeating-linear-gradient(90deg, var(--geo-ink) 0 1px, transparent 1px 30px),
    repeating-linear-gradient(0deg,  var(--geo-ink-strong) 0 1px, transparent 1px 150px),
    repeating-linear-gradient(90deg, var(--geo-ink-strong) 0 1px, transparent 1px 150px);
  will-change: transform;
}
.hero__meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 72px;
}
.hero__meta .right {
  font-size: 13px;
  color: var(--ink-60);
  letter-spacing: 0.02em;
}
.hero__meta .right .dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--sage);
  margin-right: 8px;
  transform: translateY(-1px);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--sage) 35%, transparent);
}
.hero__headline {
  font-size: clamp(48px, 8.3vw, 132px);
  line-height: 0.96;
  letter-spacing: -0.035em;
  font-weight: 500;
  margin: 0;
  max-width: 14ch;
  text-wrap: balance;
}
.hero__headline em {
  font-family: "Instrument Serif", serif;
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.02em;
}
.hero__foot {
  margin-top: 80px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 64px;
  align-items: end;
}
.hero__sub {
  font-size: clamp(18px, 1.4vw, 22px);
  line-height: 1.45;
  max-width: 44ch;
  color: var(--ink);
  margin: 0;
}
.hero__sub span { color: var(--ink-60); }
.hero__scroll {
  justify-self: end;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-60);
  display: flex;
  align-items: center;
  gap: 10px;
}
.hero__scroll svg { width: 14px; height: 14px; }

/* Hairline rule between sections */
.rule {
  border: 0;
  height: 1px;
  background: var(--ink-20);
  margin: 0;
}

/* ───────────────────────────  SERVICES  ─────────────────────────── */
.services {
  padding: 96px 0 112px;
  background: var(--beige-2);   /* holds; see HOMEPAGE TONE ARC */
}
.services__head {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 64px;
  align-items: start;
  margin-bottom: 72px;
}
.services__title {
  font-size: clamp(34px, 4vw, 56px);
  line-height: 1.02;
  letter-spacing: -0.03em;
  font-weight: 500;
  margin: 0;
  max-width: 18ch;
}
.services__list {
  list-style: none;
  margin: 0; padding: 0;
}
.service {
  border-top: 1px solid var(--ink-20);
  position: relative;
}
.service:last-child { border-bottom: 1px solid var(--ink-20); }
.service__inner {
  display: grid;
  grid-template-columns: 80px 1.2fr 2fr 1fr;
  gap: 32px;
  align-items: start;
  padding: 36px 8px 36px 0;
  transition: transform .45s cubic-bezier(.16,1,.3,1);
  will-change: transform;
}
.service__num {
  font-family: "Instrument Serif", serif;
  font-size: 22px;
  font-style: italic;
  color: var(--ink-60);
  padding-top: 4px;
}
.service__name {
  font-size: 26px;
  letter-spacing: -0.02em;
  font-weight: 500;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 14px;
}
.service__dot {
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--c, var(--sage));
  flex: 0 0 auto;
  transition: transform .45s cubic-bezier(.16,1,.3,1);
}
.service__desc {
  font-size: 16px;
  color: var(--ink-60);
  line-height: 1.5;
  max-width: 48ch;
  margin: 0;
}
.service__deliv {
  font-size: 13px;
  color: var(--ink-45);
  text-align: right;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding-top: 6px;
}
.service:hover .service__inner { transform: translateX(16px); }
.service:hover .service__dot { transform: scale(1.6); }

/* ───────────────────────────  ABOUT  ─────────────────────────── */
.about {
  padding: 112px 0 120px;
  background: var(--beige-2);   /* holds; see HOMEPAGE TONE ARC */
}
.about__grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: 80px;
  align-items: start;
}
.about__photo {
  position: relative;
  aspect-ratio: 4/5;
  border-radius: var(--rad);
  overflow: hidden;
  background: var(--beige-3);
}
.about__photo img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 30%;
  transition: transform 1.2s cubic-bezier(.2,.7,.2,1);
}
.about__photo:hover img { transform: scale(1.03); }
.about__photo .photo-tag {
  position: absolute;
  left: 16px; bottom: 16px;
  background: var(--beige);
  color: var(--ink);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 8px 12px;
  border-radius: 999px;
}
.about__title {
  font-size: clamp(34px, 4vw, 56px);
  line-height: 1.02;
  letter-spacing: -0.03em;
  font-weight: 500;
  margin: 24px 0 32px;
  max-width: 16ch;
}
.about__title em {
  font-family: "Instrument Serif", serif;
  font-style: italic;
  font-weight: 400;
}
.about__body p {
  font-size: 18px;
  line-height: 1.55;
  max-width: 58ch;
  margin: 0 0 18px;
}
.about__body p:last-of-type { margin-bottom: 36px; }
.about__creds {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px 32px;
  padding-top: 28px;
  border-top: 1px solid var(--ink-20);
}
.cred__k {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-60);
  margin-bottom: 6px;
}
.cred__v {
  font-size: 16px;
  letter-spacing: -0.005em;
}

/* CV download (Session 3). Same wipe-underline vocabulary as the nav and
   "View all projects"; arrow nudges down rather than right. */
.about__cv { margin-top: 28px; }
.about__cv-link {
  position: relative;
  display: inline-block;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.02em;
  padding-bottom: 4px;
  transition: color .3s ease;
}
.about__cv-link::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1.5px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .35s cubic-bezier(.2,.7,.2,1);
}
.about__cv-link:hover::after { transform: scaleX(1); }
.about__cv-link span {
  display: inline-block;
  transition: transform .35s cubic-bezier(.2,.7,.2,1);
}
.about__cv-link:hover span { transform: translateY(3px); }

/* ───────────────────────────  PROJECTS  ─────────────────────────── */
/* HOMEPAGE TONE ARC -- one descent, a hold, one ascent. Every seam matches its
   neighbour so the whole page reads as a single slow gradient rather than bands:

     hero            --beige                  (body)
     Selected Work   --beige -> --beige-2      descends
     Services        --beige-2                 holds
     About           --beige-2                 holds
     Contact         --beige-2 -> --beige      ascends
     footer          --beige

   Before the Session 3 reorder this worked by accident: About faded down to
   --beige-2 and Selected Work sat right after it at that value, so the three
   sections chained. Moving Selected Work up to sit under the hero broke the
   chain and left it an isolated dark slab. Fixed by making it the descent
   itself. Keep the boundary values matching if these sections ever move again.
   NOTE: --beige-3 is deliberately unused as a section background anywhere. */
.projects {
  padding: 112px 0 120px;
  background: linear-gradient(to bottom, var(--beige) 0%, var(--beige-2) 100%);
}
.projects__head {
  display: flex;
  justify-content: space-between;
  align-items: end;
  margin-bottom: 56px;
  gap: 24px;
  flex-wrap: wrap;
}
.projects__title {
  font-size: clamp(34px, 4vw, 56px);
  line-height: 1.02;
  letter-spacing: -0.03em;
  font-weight: 500;
  margin: 16px 0 0;
  max-width: 16ch;
}
/* "View all projects" -> projects.html. Replaced the old project count, which
   Shane asked to remove in Session 3. Borrows the nav's wipe-underline and nudges
   its arrow on hover, so it reads as the same link vocabulary. */
.projects__all {
  position: relative;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--ink-60);
  padding-bottom: 4px;
  white-space: nowrap;
  transition: color .3s ease;
}
.projects__all::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1.5px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .35s cubic-bezier(.2,.7,.2,1);
}
.projects__all:hover { color: var(--ink); }
.projects__all:hover::after { transform: scaleX(1); }
.projects__all span {
  display: inline-block;
  transition: transform .35s cubic-bezier(.2,.7,.2,1);
}
.projects__all:hover span { transform: translateX(4px); }
.projects__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0;
  border-top: 1px solid var(--ink);
}
.project {
  position: relative;
  padding: 28px 24px 36px;
  border-bottom: 1px solid var(--ink);
  border-right: 1px solid var(--ink-20);
  min-height: 320px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  overflow: hidden;
}
.project:nth-child(3n) { border-right: none; }
.project__top {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--ink-60);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
/* Category pill (residential / commercial / municipal). A label, not a filter --
   don't build filtering until the list is long enough to need it. Picks up the
   card's own --c accent so it stays in the existing colour vocabulary. */
.project__pill {
  font-size: 10px;
  letter-spacing: 0.14em;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--c, var(--beige-3));
  color: var(--ink);
  white-space: nowrap;
}
.project__name {
  font-size: 28px;
  line-height: 1.05;
  letter-spacing: -0.02em;
  font-weight: 500;
  margin: 0;
  max-width: 14ch;
}
.project__meta {
  margin-top: 18px;
  display: flex;
  justify-content: space-between;
  align-items: end;
  font-size: 13px;
  color: var(--ink-60);
  /* wrap so the firm-attribution line (projects.html) drops to its own row
     instead of squeezing in beside type and "View +" */
  flex-wrap: wrap;
}
.project__type {
  letter-spacing: 0.04em;
}
.project__placeholder {
  /* image placeholder — pastel wash + hairlines + corner tag */
  flex: 1;
  margin: 28px 0 24px;
  border-radius: 8px;
  position: relative;
  overflow: hidden;
  background-color: color-mix(in oklab, var(--c, var(--sage)) 70%, var(--paper));
  background-image:
    repeating-linear-gradient(135deg,
      rgba(26,26,26,.06) 0,
      rgba(26,26,26,.06) 1px,
      transparent 1px,
      transparent 18px);
  border: 1px solid var(--ink-10);
  min-height: 140px;
}
.project__placeholder::after {
  content: attr(data-tag);
  position: absolute;
  left: 12px; bottom: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-60);
}
.project__placeholder svg {
  position: absolute;
  right: 12px; top: 12px;
  width: 18px; height: 18px;
  color: var(--ink-45);
  opacity: .7;
}

/* Card trigger + "View +" cue. Hidden unless JS confirms <dialog> support
   (.has-pd on <html>), so no-JS visitors never see a dead affordance. */
.project__open {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 1;
  border: 0;
  padding: 0;
  margin: 0;
  background: transparent;
  appearance: none;
  cursor: pointer;
}
.has-pd .project__open { display: block; }
.project__open:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}
.project__view {
  display: none;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-60);
  transition: color .35s cubic-bezier(.2,.7,.2,1), transform .35s cubic-bezier(.2,.7,.2,1);
}
.has-pd .project__view { display: inline-block; }
.has-pd .project {
  transition: background-color .35s cubic-bezier(.2,.7,.2,1);
}
.has-pd .project:hover,
.has-pd .project:focus-within {
  background-color: var(--paper);
}
.has-pd .project:hover .project__view,
.has-pd .project:focus-within .project__view {
  color: var(--ink);
  transform: translateX(2px);
}

/* ───────────────────────────  PROJECT DIALOG  ─────────────────────────── */
/* Full-screen project detail. One <dialog class="pd"> per card, opened by
   app.js via showModal(). Closed dialogs are display:none, so lazy gallery
   images inside them never download until opened. */
dialog.pd {
  --pd-pad: 40px;
  width: min(1120px, 94vw);
  max-width: min(1120px, 94vw);
  max-height: 92vh;
  max-height: 92dvh;
  padding: 0;
  border: 1px solid var(--ink);
  border-radius: var(--rad);
  background: var(--beige);
  color: var(--ink);
}
.pd::backdrop {
  background: rgba(26,26,26,.32);
  backdrop-filter: blur(6px);
}
.pd[open] {
  opacity: 1;
  transform: none;
  transition: opacity .5s cubic-bezier(.2,.7,.2,1), transform .5s cubic-bezier(.2,.7,.2,1);
}
@starting-style {
  .pd[open] { opacity: 0; transform: translateY(12px); }
}
.pd__inner {
  max-height: inherit;
  overflow-y: auto;
  padding: 28px 0 40px;
}
.pd__bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-inline: var(--pd-pad);
  font-size: 12px;
  color: var(--ink-60);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.pd__close {
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  margin-inline-end: -12px;
  border: 0;
  background: transparent;
  appearance: none;
  color: var(--ink);
  border-radius: 50%;
  transition: background-color .35s cubic-bezier(.2,.7,.2,1);
}
.pd__close:hover { background: var(--ink-10); }
.pd__close:focus-visible,
.pd__arrow:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.pd__close svg { width: 18px; height: 18px; }
.pd__title {
  font-size: clamp(30px, 4vw, 48px);
  line-height: 1.05;
  letter-spacing: -0.025em;
  font-weight: 500;
  margin: 18px 0 0;
  padding-inline: var(--pd-pad);
}
.pd__meta {
  margin: 10px 0 0;
  padding-inline: var(--pd-pad);
  font-size: 13px;
  color: var(--ink-60);
  letter-spacing: 0.04em;
}
.pd__blurb {
  margin: 22px 0 0;
  padding-inline: var(--pd-pad);
  max-width: calc(55ch + var(--pd-pad) * 2);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink);
}
.pd__gallery {
  position: relative;
  margin-top: 32px;
}
.pd__strip {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--pd-pad);
  padding-inline: var(--pd-pad);
  padding-bottom: 6px;
}
.pd__strip:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}
.pd__slide {
  flex: 0 0 auto;
  margin: 0;
  scroll-snap-align: start;
}
.pd__media {
  border-radius: 8px;
  overflow: hidden;
  height: min(50vh, 560px);
  height: min(50dvh, 560px);
}
.pd__media img {
  height: 100%;
  width: auto;
  max-width: 80vw;
  object-fit: cover;
}
/* Placeholder slides — same pastel wash + hatch as the card placeholders.
   aspect-ratio keeps empty figures from collapsing before photos exist. */
.pd__slide--ph .pd__media {
  aspect-ratio: 3 / 4;
  position: relative;
  background-color: color-mix(in oklab, var(--c, var(--sage)) 70%, var(--paper));
  background-image:
    repeating-linear-gradient(135deg,
      rgba(26,26,26,.06) 0,
      rgba(26,26,26,.06) 1px,
      transparent 1px,
      transparent 18px);
  border: 1px solid var(--ink-10);
}
.pd__slide--ph:nth-child(2) .pd__media {
  background-color: color-mix(in oklab, var(--c, var(--sage)) 62%, var(--paper));
}
.pd__slide--ph:nth-child(3) .pd__media {
  background-color: color-mix(in oklab, var(--c, var(--sage)) 55%, var(--paper));
}
.pd__slide--ph .pd__media .tbd {
  position: absolute;
  top: 12px; left: 12px;
  font-size: 12px;
}
.pd__slide--ph .pd__media::after {
  content: attr(data-tag);
  position: absolute;
  left: 12px; bottom: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-60);
}
.pd__cap {
  margin-top: 8px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-60);
}
.pd__arrow {
  display: none;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px; height: 40px;
  place-items: center;
  border: 1px solid var(--ink);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  transition: background-color .35s cubic-bezier(.2,.7,.2,1);
}
.pd__arrow:hover { background: var(--beige-3); }
.pd__arrow svg { width: 16px; height: 16px; }
.pd__arrow--prev { left: 14px; }
.pd__arrow--next { right: 14px; }
.pd__arrow[hidden] { display: none; }
@media (hover: hover) and (pointer: fine) {
  .pd__arrow { display: grid; }
  .pd__arrow[hidden] { display: none; }
}
/* .tbd is nowrap globally; inside dialog prose that would overflow phones. */
.pd .tbd { white-space: normal; }

/* ───────────────────────────  CONTACT  ─────────────────────────── */
.contact {
  padding: 120px 0 120px;
  /* The ascent. Eases out of the held --beige-2 (About sits directly above)
     and settles to flat --beige by 300px so the form sits on plain beige. */
  background: linear-gradient(to bottom, var(--beige-2) 0, var(--beige) 300px);
  position: relative;
}
.contact > .wrap { position: relative; z-index: 1; }

/* Isometric lattice. A faint exposed-framing grid deep in the background
   that shifts perspective as the section passes. Pure CSS scroll timeline. */
.contact__geo {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  clip-path: inset(0);            /* clip via clip-path, not overflow -- see .hero__geo */
}
.contact__iso {
  position: absolute; left: 0; right: 0; top: -20%; height: 150%;
  background-image:
    repeating-linear-gradient(30deg,  var(--geo-ink) 0 1px, transparent 1px 38px),
    repeating-linear-gradient(-30deg, var(--geo-ink) 0 1px, transparent 1px 38px),
    repeating-linear-gradient(90deg,  var(--geo-ink-strong) 0 1px, transparent 1px 38px);
  will-change: transform;
}
.contact__grid {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 96px;
  align-items: start;
}
.contact__title {
  font-size: clamp(48px, 7vw, 104px);
  line-height: 0.96;
  letter-spacing: -0.035em;
  font-weight: 500;
  margin: 0 0 40px;
  max-width: 12ch;
}
.contact__title em {
  font-family: "Instrument Serif", serif;
  font-style: italic;
  font-weight: 400;
}
.contact__direct {
  display: flex;
  flex-direction: column;
  gap: 18px;
  margin-top: 32px;
}
.contact__direct a {
  font-size: 22px;
  letter-spacing: -0.01em;
  display: flex;
  align-items: center;
  gap: 14px;
  transition: color .2s ease;
}
.contact__direct a:hover { color: var(--ink-60); }
.contact__direct .ico {
  width: 18px; height: 18px;
  color: var(--ink-60);
  flex: 0 0 18px;
}
.contact__hours {
  margin-top: 36px;
  padding-top: 28px;
  border-top: 1px solid var(--ink-20);
  font-size: 14px;
  color: var(--ink-60);
  display: grid;
  grid-template-columns: 110px 1fr;
  row-gap: 8px;
}
.contact__hours dt { font-weight: 500; color: var(--ink); }
.contact__hours dd { margin: 0; }

/* form */
.form {
  background: var(--paper);
  padding: 36px;
  border-radius: var(--rad);
  border: 1px solid var(--ink-10);
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-60);
}
.field input, .field select, .field textarea {
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink-20);
  padding: 8px 0 10px;
  outline: none;
  transition: border-color .2s ease;
  width: 100%;
  border-radius: 0;
}
.field textarea { resize: vertical; min-height: 90px; line-height: 1.45; }
.field input:focus, .field select:focus, .field textarea:focus {
  border-bottom-color: var(--ink);
}
.field.is-error input, .field.is-error textarea { border-bottom-color: #B0413E; }
.field .err {
  font-size: 11px;
  color: #B0413E;
  letter-spacing: 0.04em;
  min-height: 14px;
}
/* Honeypot — hidden from humans, catches bots. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form__send {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 8px;
}
.form__send small {
  font-size: 12px;
  color: var(--ink-60);
  max-width: 26ch;
}
.btn {
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  padding: 14px 22px;
  border-radius: 999px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--beige);
  letter-spacing: 0.02em;
  transition: transform .2s ease, background .2s ease, color .2s ease;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.btn:hover { background: transparent; color: var(--ink); }
.btn[disabled] { opacity: .55; cursor: default; }
.btn svg { width: 14px; height: 14px; }

.form__note {
  background: var(--sage);
  color: var(--ink);
  padding: 24px;
  border-radius: var(--rad);
  font-size: 16px;
  line-height: 1.4;
  display: none;
}
.form__note.is-error { background: color-mix(in oklab, var(--pink) 80%, var(--paper)); }
.form.is-hidden { display: none; }
.form__note.is-shown { display: block; }

/* ───────────────────────────  FOOTER  ─────────────────────────── */
.footer {
  padding: 64px 0 32px;
  background: var(--beige);
  border-top: 1px solid var(--ink-20);
}
.footer__top {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: 48px;
  align-items: start;
  margin-bottom: 56px;
}
.footer__logo img { height: 68px; width: auto; margin-bottom: 16px; }
.footer__tag {
  font-size: 14px;
  color: var(--ink-60);
  max-width: 28ch;
}
.footer__col h4 {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-60);
  margin: 0 0 14px;
  font-weight: 500;
}
.footer__col ul {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 8px;
  font-size: 15px;
}
.footer__col a:hover { color: var(--ink-60); }
.footer__bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 28px;
  border-top: 1px solid var(--ink-20);
  font-size: 12px;
  color: var(--ink-60);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.footer__bottom .dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--ink-20);
  margin: 0 10px;
  transform: translateY(-2px);
}

/* ───────────────────────────  SCROLL-MOTION  ─────────────────────────── */
/* Compositor-driven parallax on the two background layers. Progressive
   enhancement: browsers without scroll timelines just show the grid/lattice
   static (still on-brand). No scroll listeners, no layout/paint on scroll --
   only transform, which the GPU handles off the main thread. */
@supports (animation-timeline: view()) {
  .hero__grid {
    animation-name: heroGridDrift;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: view();
    animation-range: cover 0% cover 100%;
  }
  @keyframes heroGridDrift {
    from { transform: translateY(-9%); }
    to   { transform: translateY(9%); }
  }
  .contact__iso {
    animation-name: contactIsoDrift;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: view();
    animation-range: cover 0% cover 100%;
  }
  /* translateY only. scale()/translateX would push the layer past clip-path
     (which clips paint, not layout) and reintroduce a horizontal scrollbar. */
  @keyframes contactIsoDrift {
    from { transform: translateY(-11%); }
    to   { transform: translateY(11%); }
  }
}

/* ───────────────────────────  REVEAL  ─────────────────────────── */
/* Content is visible by default. Only when JS is confirmed (.js on <html>)
   do we hide-then-reveal, so a JS-off or CSP-blocked page never goes blank. */
.js .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .9s ease, transform 1.1s cubic-bezier(.2,.7,.2,1);
}
.js .reveal.is-in { opacity: 1; transform: none; }

/* ───────────────────────────  RESPONSIVE  ─────────────────────────── */
@media (max-width: 960px) {
  :root { --col: 100%; }
  body { font-size: 16px; }
  .wrap { width: 100%; padding-inline: 22px; }

  .nav__links, .nav__cta { display: none; }
  .nav__burger { display: block; }
  .nav__logo img { height: 52px; }
  .nav__inner { padding: 16px 0; }

  .hero { padding: 56px 0 80px; }
  .hero__meta { margin-bottom: 48px; }
  .hero__foot { grid-template-columns: 1fr; gap: 32px; margin-top: 56px; }
  .hero__scroll { justify-self: start; }

  .services { padding: 64px 0 72px; }
  .services__head {
    grid-template-columns: 1fr;
    gap: 20px;
    margin-bottom: 36px;
  }
  .service__inner {
    grid-template-columns: 36px 1fr;
    grid-template-areas:
      "num name"
      "num desc"
      ".   deliv";
    gap: 8px 20px;
    padding: 28px 0;
  }
  .service__num { grid-area: num; font-size: 18px; }
  .service__name { grid-area: name; font-size: 22px; }
  .service__desc { grid-area: desc; }
  .service__deliv { grid-area: deliv; text-align: left; padding-top: 12px; }
  .service:hover .service__inner { transform: none; }

  .about { padding: 72px 0 80px; }
  .about__grid {
    grid-template-columns: 1fr;
    gap: 36px;
  }
  .about__photo { max-width: 70%; }
  .about__title { font-size: 36px; margin-top: 8px; }
  .about__body p { font-size: 16.5px; }

  .projects { padding: 72px 0 80px; }
  .projects__grid { grid-template-columns: 1fr; }
  .project { border-right: none; min-height: 220px; }
  .project:nth-child(3n) { border-right: none; }
  .project__name { font-size: 26px; }
  .project__placeholder { margin: 24px 0 16px; }

  /* Project dialog becomes a full-viewport sheet */
  dialog.pd {
    --pd-pad: 20px;
    inset: 0;
    width: 100%;
    max-width: none;
    height: 100vh;
    height: 100dvh;
    max-height: none;
    margin: 0;
    border: 0;
    border-radius: 0;
  }
  .pd__inner { padding-top: 20px; }
  .pd__title { font-size: 32px; }
  .pd__slide { width: 82vw; }
  .pd__media { height: auto; }
  .pd__media img { width: 100%; height: auto; max-width: none; }

  .contact { padding: 72px 0 80px; }
  .contact__grid { grid-template-columns: 1fr; gap: 48px; }
  .contact__title { font-size: 56px; margin-bottom: 28px; }
  .contact__direct a { font-size: 20px; }
  .form { padding: 24px; }
  .form__row { grid-template-columns: 1fr; }
  .form__send { flex-direction: column; align-items: stretch; gap: 14px; }
  .form__send small { order: 2; text-align: center; max-width: none; }
  .form__send .btn { order: 1; justify-content: center; }

  .footer__top {
    grid-template-columns: 1fr 1fr;
    gap: 32px;
    margin-bottom: 36px;
  }
  .footer__logo { grid-column: 1 / -1; }
  .footer__bottom {
    flex-direction: column;
    gap: 8px;
    text-align: center;
  }
}

@media (max-width: 480px) {
  .hero__headline { font-size: 54px; }
  .menu a { font-size: 30px; }
  .pd__title { font-size: 28px; }
}

/* Respect reduced-motion: no reveal hiding, no transforms/transitions. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  .hero__grid, .contact__iso { animation: none !important; transform: none !important; }
  * { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}


/* ═══════════════════  PROJECTS PAGE (projects.html)  ═══════════════════
   Bones work and previous-firm work are separate sections with the firm
   named -- Session 3. Everything else reuses the card and dialog vocabulary
   defined above. */

.page-head {
  padding: 152px 0 64px;
  background: var(--beige);
  position: relative;
}
.page-head > .wrap { position: relative; z-index: 1; }
/* Drafting-grid texture, same vocabulary as .hero__grid but static -- there's no
   parallax here, so this needs neither clip-path nor a scroll timeline. */
.page-head__geo {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.page-head__grid {
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(0deg,  var(--geo-ink) 0 1px, transparent 1px 30px),
    repeating-linear-gradient(90deg, var(--geo-ink) 0 1px, transparent 1px 30px),
    repeating-linear-gradient(0deg,  var(--geo-ink-strong) 0 1px, transparent 1px 150px),
    repeating-linear-gradient(90deg, var(--geo-ink-strong) 0 1px, transparent 1px 150px);
  /* Fade out toward the bottom. Without this the grid ends in a hard horizontal
     seam at the section boundary, which reads worse than having no texture. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 100%);
}
.page-head__title {
  font-size: clamp(44px, 6vw, 82px);
  line-height: 1.02;
  letter-spacing: -0.03em;
  margin: 14px 0 18px;
}
.page-head__lede {
  max-width: 58ch;
  font-size: 18px;
  color: var(--ink-60);
}

/* Background progression across projects.html: light -> darker -> back to light,
   using ONLY the two beiges the homepage uses. --beige-3 is deliberately avoided:
   nothing on index.html is that dark, and a full-height section of it made this
   page read as a different, heavier site.

     page-head    --beige                    (+ grid texture)
     --lead       --beige -> --beige-2        gradient, same pattern as .about
     --prior      --beige-2                   continues where the gradient landed
     cta          --beige-2 -> --beige        hands off to the footer, like .contact
     footer       --beige                                                        */
.projects--lead {
  background: linear-gradient(to bottom, var(--beige) 0%, var(--beige-2) 100%);
}
.projects--prior { background: var(--beige-2); }

/* Section tally. Removed from the home page at Shane's request, but it belongs on
   a page whose whole job is showing scope. */
.projects__tally {
  font-size: 13px;
  color: var(--ink-60);
  letter-spacing: 0.08em;
  white-space: nowrap;
}
/* The count is derived by app.js from the actual number of cards. Without JS
   there is no number to show, so hide it rather than render an empty or wrong
   one -- same rule as the contact form never faking success. */
.no-js .projects__tally { display: none; }

/* ── Closing CTA ── */
.projects-cta {
  padding: 104px 0 112px;
  background: linear-gradient(to bottom, var(--beige-2) 0, var(--beige) 320px);
}
.projects-cta__inner {
  display: flex;
  justify-content: space-between;
  align-items: end;
  gap: 40px;
  flex-wrap: wrap;
}
.projects-cta__title {
  font-size: clamp(32px, 4vw, 54px);
  line-height: 1.05;
  letter-spacing: -0.025em;
  margin: 14px 0 0;
  max-width: 22ch;
}
.projects-cta__title em {
  font-family: "Instrument Serif", Georgia, serif;
  font-style: italic;
  font-weight: 400;
}
.projects-cta__actions {
  display: flex;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
}
.projects-cta__btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  padding: 15px 26px;
  border: 1px solid var(--ink);
  border-radius: 999px;
  white-space: nowrap;
  transition: background .2s ease, color .2s ease;
}
.projects-cta__btn:hover { background: var(--ink); color: var(--beige); }
.projects-cta__btn span { transition: transform .35s cubic-bezier(.2,.7,.2,1); }
.projects-cta__btn:hover span { transform: translateX(4px); }
.projects-cta__mail {
  font-size: 15px;
  color: var(--ink-60);
  box-shadow: inset 0 -1px 0 var(--ink-20);
  transition: color .2s ease, box-shadow .2s ease;
}
.projects-cta__mail:hover { color: var(--ink); box-shadow: inset 0 -1px 0 var(--ink); }

.projects__note {
  max-width: 56ch;
  margin-top: 12px;
  font-size: 15px;
  color: var(--ink-60);
}

/* Firm attribution. Deliberately visible on the card, not only inside the
   dialog -- someone scanning the grid has to be able to tell whose work it is
   without opening anything. */
.project__firm {
  display: block;
  width: 100%;
  order: 3;
  margin-top: 6px;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-45);
}
.pd__firm {
  margin-top: -6px;
  margin-bottom: 10px;
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-45);
}

@media (max-width: 720px) {
  .page-head { padding: 128px 0 48px; }
  .projects-cta { padding: 72px 0 80px; }
  .projects-cta__inner { align-items: start; }
}
