/* ==========================================================================
   Haydn McIntyre personal site
   Dark by design: green-black ground, brass accent, wide Archivo lettering.
   ========================================================================== */

:root {
  --ground:    #0A100E;
  --raised:    #0F1815;
  --card:      #131E1A;
  --chalk:     #ECF1EC;
  --dim:       #93A79A;   /* 7.53:1 on ground */
  --faint:     #6F8276;   /* 4.69:1. The old #64766B measured 3.97:1 and failed */
  --green:     #1C5F4A;
  --electric:  #3FD9C0;   /* hero effect only. Brass stays the brand accent */
  --brass:     #E5B457;
  --brass-dim: #B98C33;
  --line:      rgba(236, 241, 236, 0.11);
  --line-2:    rgba(236, 241, 236, 0.22);   /* decorative only, 1.73:1 */
  --line-int:  rgba(236, 241, 236, 0.38);   /* anything interactive, 3.05:1 */
  --danger:    #F08C7A;
  --success:   #57C9A2;   /* 8.85:1 on raised. A tint of --green, not --electric */

  --wrap: 1180px;
  --gutter: clamp(1.25rem, 4vw, 3rem);
  --section-y: clamp(4.5rem, 10vw, 9rem);

  --sans: "Archivo", "Segoe UI", system-ui, -apple-system, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  --fs-label: 0.75rem;
  --fs-sm:    0.9rem;
  --fs-base:  1.0625rem;
  --fs-lg:    1.1875rem;
  --fs-h3:    1.375rem;
  --fs-h2:    clamp(2.1rem, 5vw, 3.6rem);
  --fs-hero:  clamp(3rem, 11vw, 8.5rem);

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* clip beats hidden: hidden still lets script scroll the page sideways */
  overflow-x: hidden;
  overflow-x: clip;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ground);
  color: var(--chalk);
  font-family: var(--sans);
  font-size: var(--fs-base);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  overflow-x: clip;
}

/* Content sits above the hero's own glow. Nothing else on either page has a
   light behind it: a fixed page-wide wash showed a hard edge wherever a tinted
   section painted over it. */
main, .footer { position: relative; z-index: 1; }

/* Pause every animation when the tab is hidden. play-state does not inherit,
   so it has to be set on the elements and their pseudo-elements directly. */
body.paused *, body.paused *::before, body.paused *::after {
  animation-play-state: paused !important;
}

img { max-width: 100%; display: block; height: auto; }
h1, h2, h3 { margin: 0; line-height: 1.02; text-wrap: balance; }
p { margin: 0; }
ul, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }
a { color: inherit; }

:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--brass); color: #17130A;
  padding: 0.75rem 1.25rem; font-size: var(--fs-sm); font-weight: 600;
}
.skip:focus { left: 0; }

.wrap {
  width: 100%; max-width: var(--wrap);
  margin-inline: auto; padding-inline: var(--gutter);
}

/* --- Shared type ---------------------------------------------------------- */
.eyebrow {
  font-size: var(--fs-label); font-weight: 600;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--brass);
}
.h2 {
  font-variation-settings: "wdth" 115;
  font-weight: 700; font-size: var(--fs-h2);
  letter-spacing: -0.025em;
  margin-top: 0.9rem;
}

/* --- Top bar -------------------------------------------------------------- */
.topbar {
  position: sticky; top: 0; z-index: 50;
  border-bottom: 1px solid transparent;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
.topbar.is-stuck {
  background: rgba(10, 16, 14, 0.86);
  backdrop-filter: blur(10px);
  border-bottom-color: var(--line);
}
.topbar-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; min-height: 68px;
}
.mark {
  display: inline-flex; align-items: center; gap: 0.6rem;
  text-decoration: none; font-weight: 600; font-size: 0.95rem;
  font-variation-settings: "wdth" 105;
}
.mark-glyph {
  width: auto; height: 23px; flex: none;
  color: var(--brass);
  transition: color 0.25s var(--ease);
}
.mark:hover .mark-glyph { color: var(--chalk); }

.nav { display: none; align-items: center; gap: 2rem; }
@media (min-width: 860px) { .nav { display: flex; } }
.nav a {
  position: relative; font-size: var(--fs-sm); font-weight: 500;
  text-decoration: none; color: var(--dim);
  transition: color 0.2s var(--ease);
}
.nav a:not(.nav-cta)::after {
  content: ""; position: absolute; left: 0; bottom: -6px;
  width: 100%; height: 1px; background: var(--brass);
  transform: scaleX(0); transform-origin: right;
  transition: transform 0.35s var(--ease);
}
.nav a:hover { color: var(--chalk); }
.nav a.is-current { color: var(--chalk); }
.nav a.is-current::after { transform: scaleX(1); transform-origin: left; }
.nav .nav-cta {
  background: var(--brass); color: #17130A;
  padding: 0.6rem 1.15rem; font-weight: 600;
}
.nav .nav-cta:hover { background: var(--chalk); color: var(--ground); }

.menu-toggle {
  display: grid; gap: 6px; align-content: center;
  width: 44px; height: 44px; padding: 0 10px;
  background: none; border: 1px solid var(--line-int); cursor: pointer;
}
.menu-toggle span { display: block; height: 2px; background: var(--chalk); }
@media (min-width: 860px) { .menu-toggle { display: none; } }

.mobile-nav { display: grid; background: var(--raised); border-top: 1px solid var(--line); }
.mobile-nav[hidden] { display: none; }
.mobile-nav a {
  text-decoration: none; font-weight: 500;
  padding: 1rem var(--gutter); border-bottom: 1px solid var(--line);
}

/* --- Hero ----------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: min(88vh, 780px);
  display: grid; align-items: center;
  padding-block: clamp(3.5rem, 10vw, 7rem) clamp(3.5rem, 9vw, 7rem);
  overflow: hidden;
}
/* Layer 1: slow gradient wash. Three blurred blobs drifting on their own clocks. */
.hero-glow {
  position: absolute; inset: -20%;
  pointer-events: none;
  filter: blur(70px);
  opacity: 0.85;
}
.hero-glow span {
  position: absolute;
  display: block;
  border-radius: 50%;
  will-change: transform;
}
/* weighted to the right. The headline lives on the left and needs a calm ground */
.hero-glow span:nth-child(1) {
  width: 50vw; height: 50vw; min-width: 380px; min-height: 380px;
  top: 4%; left: 46%;
  background: radial-gradient(circle, rgba(28, 95, 74, 1), transparent 66%);
  animation: drift1 26s ease-in-out infinite alternate;
}
.hero-glow span:nth-child(2) {
  width: 40vw; height: 40vw; min-width: 300px; min-height: 300px;
  top: 40%; left: 64%;
  background: radial-gradient(circle, rgba(63, 217, 192, 0.42), transparent 64%);
  animation: drift2 34s ease-in-out infinite alternate;
}
.hero-glow span:nth-child(3) {
  width: 32vw; height: 32vw; min-width: 240px; min-height: 240px;
  top: 2%; left: 20%;
  background: radial-gradient(circle, rgba(229, 180, 87, 0.2), transparent 66%);
  animation: drift3 42s ease-in-out infinite alternate;
}
@keyframes drift1 { to { transform: translate3d(14%, 10%, 0) scale(1.15); } }
@keyframes drift2 { to { transform: translate3d(-16%, -12%, 0) scale(1.2); } }
@keyframes drift3 { to { transform: translate3d(-10%, 16%, 0) scale(0.9); } }

/* Layer 2: particles and electric arcs, drawn in script.js. */
.hero-canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  pointer-events: none;
}
/* keeps the type readable over the brightest part of the wash */
.hero::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(100deg, rgba(10, 16, 14, 0.88) 0%, rgba(10, 16, 14, 0.62) 38%, rgba(10, 16, 14, 0.12) 78%, rgba(10, 16, 14, 0) 100%);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .hero-glow span { animation: none; }
}
.hero-inner { position: relative; z-index: 1; }
.hero-eyebrow {
  font-size: var(--fs-label); font-weight: 600;
  letter-spacing: 0.26em; text-transform: uppercase; color: var(--brass);
  margin-bottom: 1.5rem;
}
.hero-title {
  display: grid;
  font-variation-settings: "wdth" 122;
  font-weight: 800; font-size: var(--fs-hero);
  letter-spacing: -0.04em; line-height: 0.88;
}
.hero-title .accent { color: var(--brass); }
.hero-sub {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem;
  margin-top: 1.9rem;
  font-size: clamp(0.95rem, 1.7vw, 1.2rem); font-weight: 500;
  letter-spacing: 0.01em; color: var(--dim);
}
.hero-sub em { color: var(--brass); font-style: normal; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 2.6rem; }
@media (max-width: 430px) {
  .hero-actions { display: grid; }
  .hero-actions .btn { text-align: center; }
}

/* load-in */
.rise { opacity: 0; transform: translateY(24px); animation: rise 0.9s var(--ease) forwards; animation-delay: var(--d, 0ms); }
@keyframes rise { to { opacity: 1; transform: none; } }

/* --- Buttons -------------------------------------------------------------- */
.btn {
  display: inline-block;
  font-size: var(--fs-sm); font-weight: 600; letter-spacing: 0.02em;
  padding: 0.95rem 1.7rem; text-decoration: none;
  border: 1px solid transparent; cursor: pointer;
  transition: background 0.25s var(--ease), color 0.25s var(--ease),
              border-color 0.25s var(--ease), transform 0.25s var(--ease);
}
.btn-primary { background: var(--brass); color: #17130A; }
.btn-primary:hover { background: var(--chalk); }
.btn-ghost { color: var(--chalk); border-color: var(--line-int); }
.btn-ghost:hover { border-color: var(--brass); color: var(--brass); }

/* --- Sections ------------------------------------------------------------- */
.section { padding-block: var(--section-y); }
/* No border. The change in ground is the edge now that the bands alternate,
   and a hairline on top of it just read as a drawn line. */
.section-tint { background: var(--raised); }
.section-head { margin-bottom: clamp(2.5rem, 6vw, 4rem); }

/* --- Projects ------------------------------------------------------------- */
.project {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3.5rem);
  align-items: center;
  padding-block: clamp(2rem, 5vw, 3.5rem);
}
@media (min-width: 900px) {
  .project { grid-template-columns: 1.15fr 1fr; }
  .project-alt .project-media { order: 2; }
}
.project-media { overflow: hidden; background: var(--card); }
.project-media img:not(.reel-page) { transition: transform 0.2s ease; width: 100%; }
/* scoped to the hovered image itself, so one shot moving never moves its neighbours */
.zoom:not(.strip-item):focus-visible img:not(.reel-page):not(.reel-head) { transform: scale(1.04); }
/* A preview is a stack of positioned images, so the lift belongs to the whole
   device. Scaling one image inside it moved the pinned header on its own. */
.zoom .device { transition: transform 0.2s ease; }
.zoom:focus-visible .device { transform: scale(1.025); }
.project-media-shots {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.6rem; background: none; overflow: visible;
}
.project-media-shots img { border: 1px solid var(--line); }

/* the whole image is the button that opens the lightbox */
.zoom {
  display: block; width: 100%;
  padding: 0; margin: 0; border: 0; background: none;
  cursor: zoom-in; overflow: hidden;
  font: inherit; color: inherit;
}
.zoom:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }

/* --- Lightbox ------------------------------------------------------------- */
.lightbox {
  border: 0; padding: 0; background: none;
  max-width: 100vw; max-height: 100vh;
  overflow: visible;
  color: var(--chalk);
}
.lightbox::backdrop {
  background: rgba(5, 9, 8, 0.88);
  backdrop-filter: blur(6px);
}
.lightbox-inner { position: relative; display: block; }
.lightbox img {
  max-width: min(92vw, 1180px);
  max-height: 82vh;
  width: auto; height: auto;
  object-fit: contain;
  border: 1px solid var(--line-2);
  background: var(--card);
}
.lightbox-close {
  position: absolute; top: -3.1rem; right: 0;
  display: grid; place-items: center;
  width: 40px; height: 40px;
  padding: 0; cursor: pointer;
  color: var(--chalk);
  background: none; border: 1px solid var(--line-2);
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease), color 0.2s var(--ease);
}
.lightbox-close:hover { background: var(--brass); border-color: var(--brass); color: #17130A; }
/* on short screens the button has nowhere to sit above the image */
@media (max-height: 620px), (max-width: 560px) {
  .lightbox-close { top: 0.5rem; right: 0.5rem; background: rgba(10, 16, 14, 0.8); }
}
.lightbox[open] { animation: lightboxIn 0.28s var(--ease); }
@keyframes lightboxIn {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .lightbox[open] { animation: none; }
}
body.is-locked { overflow: hidden; }

.project-body { display: grid; align-content: center; gap: 0.85rem; }
.project-meta {
  font-size: var(--fs-label); font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--brass);
}
.project-title {
  font-variation-settings: "wdth" 118;
  font-weight: 800; font-size: clamp(1.9rem, 4vw, 2.9rem);
  letter-spacing: -0.03em;
}
.project-desc { color: var(--dim); max-width: 46ch; }
.repo {
  display: inline-flex; align-items: center; gap: 0.55rem;
  justify-self: start;
  margin-top: 0.7rem; padding: 0.7rem 1.15rem;
  font-size: var(--fs-sm); font-weight: 600;
  color: var(--chalk); text-decoration: none;
  border: 1px solid var(--line-int);
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease),
              background 0.25s var(--ease);
}
.repo svg { flex: none; }
/* A live link sits beside the repo link. Client sites get the live one and
   usually no repo, so the row has to read right with either or both. */
.project-links {
  display: flex; flex-wrap: wrap; gap: 0.7rem;
  justify-self: start; margin-top: 0.7rem;
}
.project-links .repo { margin-top: 0; }
.repo-live { border-color: var(--brass-dim); color: var(--brass); }
.repo-live:hover { border-color: var(--brass); }
.repo:hover { color: var(--brass); border-color: var(--brass); background: rgba(229, 180, 87, 0.07); }
.repo span::after { content: " \2197"; }

.tags { display: flex; flex-wrap: wrap; gap: 0.45rem; margin-top: 0.4rem; }
.tags li {
  font-size: var(--fs-label); font-weight: 500; letter-spacing: 0.04em;
  padding: 0.32rem 0.7rem; border: 1px solid var(--line-2); color: var(--dim);
}

/* --- The stack -----------------------------------------------------------
   Four ordered layers, top of the stack down to delivery. The numbering is
   real information here: it is the order the request travels. */
/* The 1px gaps show the ground through as a divider between rows. No outer
   border: that put a rule directly under the section heading. */
.layers { display: grid; gap: 1px; background: var(--line); }
.layer {
  position: relative;
  background: var(--ground);
  padding: clamp(1.5rem, 3.2vw, 2.25rem) clamp(1.35rem, 3vw, 2.25rem);
  display: grid; gap: 0.8rem;
  transition: background 0.35s var(--ease);
}
@media (min-width: 860px) {
  .layer { grid-template-columns: 14rem 1fr; align-items: baseline; gap: 0 2.5rem; }
  .layer-note { grid-column: 2; }
  .layer-tech { grid-column: 2; }
}
.layer:hover, .layer:focus-within { background: var(--card); }
/* the accent edge draws down the layer you are on */
.layer::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--brass);
  transform: scaleY(0); transform-origin: 50% 0;
  transition: transform 0.5s var(--ease);
}
.layer:focus-within::after { transform: scaleY(1); }

.layer-head { display: flex; align-items: baseline; gap: 0.85rem; }
.layer-n {
  font-variation-settings: "wdth" 125;
  font-weight: 800; font-size: var(--fs-label);
  letter-spacing: 0.1em; color: var(--brass);
  font-variant-numeric: tabular-nums;
}
.layer-head h3 {
  font-variation-settings: "wdth" 110;
  font-weight: 700; font-size: var(--fs-h3); letter-spacing: -0.01em;
}
.layer-note { color: var(--dim); font-size: var(--fs-base); max-width: 52ch; }
.layer-tech { display: flex; flex-wrap: wrap; gap: 0.45rem; margin-top: 0.35rem; }
.layer-tech li {
  font-size: var(--fs-label); font-weight: 500; letter-spacing: 0.04em;
  padding: 0.32rem 0.7rem; border: 1px solid var(--line-2); color: var(--dim);
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.layer:hover .layer-tech li, .layer:focus-within .layer-tech li {
  border-color: var(--line-int); color: var(--chalk);
}

/* --- The bolt rule (the site's signature mark, echoed from the logo) ------- */
.bolt-rule { width: 124px; height: 18px; margin-top: 1.3rem; }
.bolt-rule svg { display: block; width: 100%; height: 100%; overflow: visible; }
.bolt-rule polyline {
  fill: none; stroke: var(--brass); stroke-width: 2;
  stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10;
  stroke-dasharray: 1; stroke-dashoffset: 1;
}
.bolt-rule.is-in polyline {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.1s var(--ease) 0.15s;
}
@media (prefers-reduced-motion: reduce) {
  .bolt-rule polyline { stroke-dashoffset: 0; transition: none; }
}

/* --- Touch targets -------------------------------------------------------- */
@media (pointer: coarse) {
  .footer-gh, .fact-link { min-height: 44px; }
  .footer-gh { display: inline-flex; align-items: center; }
}

/* --- About ---------------------------------------------------------------- */
.about { display: grid; gap: clamp(2rem, 5vw, 4rem); }
@media (min-width: 900px) { .about { grid-template-columns: 1.4fr 1fr; align-items: start; } }
.about-lede { color: var(--dim); font-size: var(--fs-lg); max-width: 56ch; margin-top: 1.5rem; }
.about-actions { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 2.2rem; }
@media (max-width: 430px) {
  .about-actions { display: grid; }
  .about-actions .btn { text-align: center; }
}

.facts { display: grid; gap: 1.5rem; align-content: start; }
@media (min-width: 900px) { .facts { padding-top: 4rem; } }
.facts > div {
  display: grid; gap: 0.3rem;
  padding-top: 1.1rem; border-top: 1px solid var(--line);
}
.facts dt {
  font-size: var(--fs-label); font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--faint);
}
.facts dd { font-size: var(--fs-base); font-weight: 500; }
.facts dd span { display: block; color: var(--brass); font-weight: 400; font-size: var(--fs-sm); }

/* --- Contact -------------------------------------------------------------- */
.contact { display: grid; gap: clamp(2.5rem, 5vw, 4rem); }
@media (min-width: 900px) { .contact { grid-template-columns: 1fr 1fr; align-items: start; } }

.form { display: grid; gap: 1.1rem; }
.field { display: grid; gap: 0.4rem; }
.field label { font-size: var(--fs-sm); font-weight: 600; }
.field input, .field textarea {
  font-family: var(--sans); font-size: var(--fs-base); color: var(--chalk);
  background: var(--ground); border: 1px solid var(--line-int);
  border-radius: 0; padding: 0.8rem 0.9rem; width: 100%;
  transition: border-color 0.25s var(--ease);
}
.field input:focus, .field textarea:focus { border-color: var(--brass); }
.field textarea { resize: vertical; min-height: 6.5rem; }
.field input[aria-invalid="true"], .field textarea[aria-invalid="true"] { border-color: var(--danger); }
.err { font-size: var(--fs-sm); color: var(--danger); }
.btn-submit { justify-self: start; margin-top: 0.35rem; }
/* full width on a phone, an easier target and it looks intentional */
@media (max-width: 520px) {
  .btn-submit { justify-self: stretch; text-align: center; }
}
.form-note { font-size: var(--fs-sm); color: var(--dim); min-height: 1.25rem; }
.form-note[data-state="ok"] { color: var(--success); font-weight: 600; }
.form-note[data-state="bad"] { color: var(--danger); }
.form-alt { font-size: var(--fs-sm); color: var(--faint); }
.form-alt a {
  color: var(--dim); text-decoration: none;
  border-bottom: 1px solid var(--line-2); padding-bottom: 1px;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.form-alt a:hover { color: var(--brass); border-color: var(--brass); }

/* --- Footer --------------------------------------------------------------- */
.footer { border-top: 1px solid var(--line); padding-block: 2.75rem; }
.footer-inner { display: grid; gap: 0.75rem; font-size: var(--fs-sm); color: var(--dim); }
@media (min-width: 700px) {
  .footer-inner { grid-template-columns: 1fr auto auto; align-items: center; gap: 2rem; }
}
.footer-mark {
  font-variation-settings: "wdth" 112;
  font-weight: 700; font-size: 1rem; color: var(--chalk);
}
.footer a { color: var(--brass); text-decoration: none; }
.footer a:hover { color: var(--chalk); }
.footer-gh {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-weight: 500;
  transition: color 0.25s var(--ease);
}
.footer-gh svg { flex: none; }

.fact-link {
  color: var(--brass); text-decoration: none;
  border-bottom: 1px solid var(--brass-dim); padding-bottom: 1px;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.fact-link:hover { color: var(--chalk); border-color: var(--chalk); }

/* --- Craft details -------------------------------------------------------- */

/* Film grain. Flat dark grounds look cheap; a little tooth fixes that. */
body::after {
  content: "";
  position: fixed; inset: 0; z-index: 9;
  pointer-events: none;
  opacity: 0.04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 190px 190px;
}

::selection { background: var(--brass); color: #17130A; }

html { scrollbar-color: #2C3D34 var(--ground); scrollbar-width: thin; }
::-webkit-scrollbar { width: 11px; }
::-webkit-scrollbar-track { background: var(--ground); }
::-webkit-scrollbar-thumb { background: #2C3D34; border: 3px solid var(--ground); }
::-webkit-scrollbar-thumb:hover { background: var(--brass-dim); }

/* Scroll progress hairline */
.progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px;
  z-index: 60; pointer-events: none;
}
.progress span {
  display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, var(--brass), var(--electric));
  transform: scaleX(0); transform-origin: 0 50%;
}


.project { position: relative; }

/* Sheen across the primary buttons */
.btn-primary { position: relative; overflow: hidden; }
.btn-primary::after {
  content: ""; position: absolute; top: 0; left: 0;
  width: 55%; height: 100%; pointer-events: none;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.45), transparent);
  /* translate rather than `left`: `left` is a layout property and repaints the
     button on every frame, where a transform runs on the GPU. */
  transform: translateX(-230%) skewX(-18deg);
  transition: transform 0.7s var(--ease);
}

/* Headings rise word by word */
.head-anim .w {
  display: inline-block; overflow: hidden; vertical-align: top;
  padding-bottom: 0.12em; margin-bottom: -0.12em;
}
.head-anim .w > span { display: inline-block; transform: translateY(108%); }
.head-anim.is-in .w > span {
  transform: none;
  transition: transform 0.85s var(--ease) var(--wd, 0ms);
}

@media (prefers-reduced-motion: reduce) {
  .head-anim .w > span,
  .head-anim.is-in .w > span { transform: none; transition: none; }
  .btn-primary::after { display: none; }
  .progress span { transition: none; }
}

/* --- Scroll reveal -------------------------------------------------------- */
.reveal { opacity: 0; transform: translateY(26px); }
.reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity 0.5s var(--ease) var(--d, 0ms), transform 0.5s var(--ease) var(--d, 0ms);
}
@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal.is-in { opacity: 1; transform: none; transition: none; }
  .rise { opacity: 1; transform: none; animation: none; }
}

/* --- Portfolio teaser (home page) ----------------------------------------- */
.teaser-lede { color: var(--dim); font-size: var(--fs-lg); max-width: 54ch; margin-top: 1.25rem; }
/* Three across on a wide screen. These are a glance at the work with the real
   detail a click away, so they stay a row rather than a stack that eats the
   page. They fall to one column only when the screen cannot hold three. */
.teaser-grid {
  display: grid; gap: clamp(2rem, 4vw, 3rem);
  margin-top: clamp(2.5rem, 4.5vw, 3.5rem);
  margin-bottom: clamp(2.75rem, 5vw, 4rem);
}
@media (min-width: 760px) {
  .teaser-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* On a wide screen the row breaks out past the text column, and the widths
   shift toward the site pair and the phone. Movement Unlimited keeps roughly
   the size it had; the other two grow into the reclaimed space. Bottom
   alignment keeps the three names on one line even though the boxes are no
   longer the same height. */
@media (min-width: 1200px) {
  .teaser-grid {
    --breakout: min(100vw - 2rem, 1360px);
    width: var(--breakout);
    margin-inline: calc((100% - var(--breakout)) / 2);
    grid-template-columns: 1.28fr 0.94fr 1.08fr;
    align-items: end;
  }
}
.teaser {
  display: grid; gap: 0.4rem; align-content: start;
  justify-items: center; text-align: center;
  text-decoration: none;
}
.teaser-name { transition: color 0.3s var(--ease); }
.teaser:hover .teaser-name, .teaser:focus-visible .teaser-name { color: var(--brass); }
/* Devices sit at their own real proportions and share a baseline, so a phone
   standing beside a browser lines up along the bottom. */
/* Every shot box is the same shape, and the device inside is sized by height
   rather than width. A phone is more than twice as tall as a browser at the
   same width, so sizing by width left it hanging far below the others and the
   three names no longer lined up across the row. */
.teaser-shot {
  display: flex; align-items: flex-end; justify-content: center;
  width: 100%; aspect-ratio: 16 / 10; margin-bottom: 1.4rem;
}
.teaser-shot .device-browser { width: 100%; height: 100%; aspect-ratio: 16 / 10; }
.teaser-shot-phone .device-phone { width: auto; height: 100%; aspect-ratio: 1080 / 2400; }
/* The phone on its own gets a taller box than the pair, so it stands larger
   than the small phone beside the browser rather than matching it. */
@media (min-width: 1200px) {
  .teaser-shot-phone { aspect-ratio: 16 / 13.5; }
}
/* A pair inside a teaser: the phone takes the full height of the box and the
   browser takes whatever width is left, so both fit a third of the row and
   still sit on the same baseline. Sizing the browser by width instead would
   push the phone out of the column. */
.teaser-shot .reel-pair { width: 100%; height: 100%; gap: 4%; align-items: flex-end; }
.teaser-shot .reel-pair .device-browser { flex: 1 1 auto; width: auto; height: auto; aspect-ratio: 16 / 10; }
.teaser-shot .reel-pair .device-phone { flex: 0 0 auto; width: auto; height: 100%; aspect-ratio: 422 / 966; }
.teaser-meta {
  font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--brass);
}
.teaser-name { font-variation-settings: "wdth" 112; font-weight: 700; font-size: var(--fs-h3); }
.teaser-cta { display: flex; width: max-content; margin-inline: auto; }

/* --- Portfolio page ------------------------------------------------------- */
.page-head { padding-block: clamp(3.5rem, 8vw, 6rem) clamp(2rem, 4vw, 3rem); position: relative; }
.page-head .h2 { font-size: var(--fs-hero); font-variation-settings: "wdth" 122; letter-spacing: -0.04em; }
.page-lede { color: var(--dim); font-size: var(--fs-lg); max-width: 60ch; margin-top: 1.6rem; }
.back-link {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: var(--fs-sm); font-weight: 600; color: var(--dim); text-decoration: none;
  margin-top: 2.25rem; transition: color 0.25s var(--ease);
}
.back-link:hover { color: var(--brass); }
.back-link::before { content: "\2190"; }
.case-role {
  display: grid; gap: 0.5rem; margin-top: 1.4rem;
  padding-top: 1.25rem; border-top: 1px solid var(--line);
}
.case-role dt {
  font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--faint);
}
.case-role dd { color: var(--dim); font-size: var(--fs-base); }
.case-role li { color: var(--dim); position: relative; padding-left: 1.05rem; }
.case-role li::before {
  content: ""; position: absolute; left: 0; top: 0.68em;
  width: 5px; height: 5px; background: var(--brass);
}
.case-role ul { display: grid; gap: 0.5rem; }

/* --- Skills (portfolio page) ---------------------------------------------- */
.skills { display: grid; gap: 2rem 3rem; }
@media (min-width: 680px)  { .skills { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .skills { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.skill-group h3 {
  font-size: var(--fs-label); font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--brass); margin-bottom: 0.85rem;
}
.skill-group ul { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.skill-group li {
  font-size: var(--fs-label); font-weight: 500; letter-spacing: 0.03em;
  padding: 0.3rem 0.62rem;
  border: 1px solid var(--line-2); color: var(--dim);
}

/* --- Portfolio dropdown --------------------------------------------------- */
.nav-drop { position: relative; display: flex; align-items: center; }
.drop-menu {
  position: absolute; top: calc(100% + 14px); left: 50%;
  transform: translateX(-50%) translateY(-6px);
  display: grid; min-width: 190px;
  background: rgba(15, 24, 21, 0.97);
  backdrop-filter: blur(10px);
  border: 1px solid var(--line-int);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.22s var(--ease), transform 0.22s var(--ease), visibility 0.22s;
  z-index: 60;
}
/* bridges the gap so the menu does not close as the pointer travels to it */
.nav-drop::after { content: ""; position: absolute; top: 100%; left: 0; right: 0; height: 16px; }
.nav-drop:hover .drop-menu,
.nav-drop:focus-within .drop-menu {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}
.drop-menu a {
  padding: 0.7rem 1rem;
  font-size: var(--fs-sm); font-weight: 500; color: var(--dim);
  text-decoration: none; border-bottom: 1px solid var(--line);
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.drop-menu a:last-child { border-bottom: 0; }
.drop-menu a:hover, .drop-menu a:focus-visible { background: var(--card); color: var(--brass); }
.drop-menu a::after { content: none; }
@media (prefers-reduced-motion: reduce) { .drop-menu { transition: none; } }

/* --- Cursor light in the hero ---------------------------------------------
   Sits above the readability scrim but below the type, so it genuinely
   lifts the dark left side instead of being crushed by the gradient. */
.hero-cursor {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(240px circle at var(--cx, -300px) var(--cy, -300px),
      rgba(63, 217, 192, 0.13), transparent 70%),
    radial-gradient(480px circle at var(--cx, -300px) var(--cy, -300px),
      rgba(28, 95, 74, 0.22), transparent 72%);
  opacity: 0;
  transition: opacity 0.45s var(--ease);
  mix-blend-mode: screen;
}
.hero-cursor.on { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .hero-cursor { display: none; } }

/* --- Portfolio page header (stands alone when linked from a resume) -------- */
.page-name {
  font-variation-settings: "wdth" 122;
  font-weight: 800; font-size: clamp(2.6rem, 7vw, 5rem);
  letter-spacing: -0.035em; margin-top: 0.7rem;
}
.page-role {
  font-size: clamp(1rem, 2vw, 1.35rem); font-weight: 500;
  color: var(--brass); margin-top: 0.65rem; letter-spacing: 0.01em;
}
.page-links {
  display: flex; flex-wrap: wrap; gap: 0.6rem 1.75rem;
  margin-top: 1.9rem; font-size: var(--fs-sm);
}
.page-links a {
  color: var(--dim); text-decoration: none;
  border-bottom: 1px solid var(--line-2); padding-bottom: 2px;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.page-links a:hover { color: var(--brass); border-color: var(--brass); }

/* --- Multi-shot project media (landscape screens) ------------------------- */
.project-media-set {
  display: grid; gap: 0.6rem;
  background: none; overflow: visible;
}
@media (min-width: 520px) { .project-media-set { grid-template-columns: 1fr 1fr; } }
.project-media-set .wide { grid-column: 1 / -1; }
.project-media-set img { border: 1px solid var(--line); width: 100%; }

/* --- Animated project previews -------------------------------------------
   A "device" is a frame (browser chrome or a phone) wrapping a screen that
   scrolls through one or more tall page captures. The script in script.js
   drives it: a page that overflows the screen gets scrolled, a page that fits
   is simply held, and a cursor drifts to the next target before each cut.
   Without JavaScript, or with reduced motion on, the first page sits still at
   the top and the frame still reads as a screenshot. -------------------- */
.device {
  position: relative; display: block; overflow: hidden;
  background: var(--ground);
  border: 1px solid var(--line-2);
  border-radius: 4px;
}

/* Browser chrome. Three dots and a URL, sized off the frame so it stays in
   proportion whether the device is a small teaser or a full project shot. */
.device-browser { --bar: clamp(16px, 7%, 30px); }
.device-bar {
  position: absolute; inset: 0 0 auto; height: var(--bar); z-index: 2;
  display: flex; align-items: center; gap: 0.32em;
  padding-inline: 0.7em; background: var(--raised);
  border-bottom: 1px solid var(--line);
  font-size: calc(var(--bar) * 0.42);
}
.device-bar i {
  width: 0.62em; height: 0.62em; border-radius: 50%;
  background: var(--line-2); flex: none;
}
.device-bar b {
  margin-left: 0.6em; padding: 0.1em 0.9em; border-radius: 999px;
  background: var(--ground); color: var(--faint);
  font-weight: 500; letter-spacing: 0.02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.device-screen {
  position: absolute; inset: var(--bar, 0) 0 0; overflow: hidden;
  /* Falls back to the site ground. A frame showing a light application sets
     its own, so a page shorter than the frame ends in the colour that page
     actually has, rather than a black band. */
  background: var(--screen-bg, var(--ground));
}
/* Slides stack in the same box and cross fade. Inside one, the header is
   pinned the way a real sticky header is and the page scrolls under it. */
.reel-slide {
  position: absolute; inset: 0; display: block;
  opacity: 0; transition: opacity 0.5s var(--ease);
}
.reel-slide.is-live { opacity: 1; }
/* Both sit in normal flow, so the page starts below the header exactly as it
   does on the real page. Absolutely positioning them both put the top of the
   page behind the header, which only looked right once it had scrolled.
   Transforming the page does not move the header, so the header stays put and
   the page slides under it. */
.reel-head {
  position: relative; z-index: 2;
  width: 100%; height: auto; display: block;
}
/* Height is auto so a tall capture keeps its own aspect and overflows
   downward, which is exactly the overflow the script scrolls through. */
.reel-page {
  position: relative; z-index: 1;
  width: 100%; height: auto; display: block;
}
.reel-slide.is-live .reel-page { will-change: transform; }

/* Phone. The bezel is drawn with a border, so there is no device image to
   load. The captures carry their own status bar, so no notch is drawn over it. */
.device-phone {
  border-radius: clamp(8px, 6%, 22px);
  border-width: clamp(3px, 2.4%, 9px);
  border-color: #1B2422;
  background: #1B2422;
  box-shadow: 0 0 0 1px var(--line-2);
}
.device-phone .device-screen { border-radius: inherit; }

/* The cursor. Hidden until the script has a reason to move it. */
.reel-cursor {
  position: absolute; z-index: 4; top: 0; left: 0;
  width: 15px; height: 15px; margin: -2px 0 0 -2px;
  opacity: 0; pointer-events: none;
  background: var(--chalk);
  clip-path: polygon(0 0, 0 76%, 22% 58%, 40% 96%, 62% 86%, 44% 49%, 74% 46%);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.7));
  transition: opacity 0.3s linear;
}
.reel-cursor.is-on { opacity: 1; }
/* The click pulse: a ring that expands out from the cursor tip once. */
.reel-cursor::before {
  content: ""; position: absolute; top: -4px; left: -4px;
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--brass); opacity: 0; transform: scale(0.3);
}
.reel-cursor.is-click::before { animation: reelClick 0.5s var(--ease); }
@keyframes reelClick {
  0%   { opacity: 0.9; transform: scale(0.3); }
  100% { opacity: 0;   transform: scale(1.4); }
}

@media (prefers-reduced-motion: reduce) {
  /* The crossfade stays. It is opacity only, and it is how a reduced-motion
     visitor still gets to see every screen of a project. Only the cursor and
     the scrolling go, and both of those are movement. */
  .reel-cursor { display: none; }
}

/* Device sizing on the portfolio page. The browser frames fill the media
   column; the phone stands at its own proportions rather than being stretched
   across a landscape slot. */
.project-media .device-browser { width: 100%; aspect-ratio: 16 / 10; }
.project-media-phone { background: none; overflow: visible; display: flex; justify-content: center; }
.project-media-phone .device-phone { width: min(300px, 80%); height: auto; aspect-ratio: 1080 / 2400; }
.project-media .zoom { display: block; width: 100%; }

/* A site shown on both screens at once. The phone runs the same two pages as
   the browser beside it, in step, and carries no cursor of its own. */
/* The phone stands taller than the browser beside it, so the media box that
   holds a pair must not clip it. */
.project-media-pair { overflow: visible; background: none; }
.reel-pair { display: flex; align-items: flex-end; gap: 4%; width: 100%; }
.reel-pair .device-browser { flex: 1 1 auto; width: auto; aspect-ratio: 16 / 10; }
.reel-pair .device-phone { flex: 0 0 27%; aspect-ratio: 422 / 966; }
/* On a phone the browser needs the whole width to be legible, and the visitor
   is already looking at the mobile view for real. */
@media (max-width: 600px) {
  .reel-pair { gap: 0; }
  .reel-pair .device-phone { display: none; }
}

/* --- Hover motion ---------------------------------------------------------
   Gated twice over: only for a real pointer, because a touch tap fires a
   :hover that then sticks until the next tap, and only where movement is
   welcome. Colour and border feedback stays ungated above, and so do the
   :focus-visible / :focus-within variants, so a keyboard gets the same
   affordance either way. ---------------------------------------------- */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .nav a:not(.nav-cta):hover::after { transform: scaleX(1); transform-origin: left; }
  .btn-primary:hover, .btn-ghost:hover { transform: translateY(-2px); }
  .btn-primary:hover::after { transform: translateX(270%) skewX(-18deg); }
  .zoom:not(.strip-item):hover img:not(.reel-page):not(.reel-head) { transform: scale(1.04); }
  .zoom:hover .device { transform: scale(1.025); }
  .layer:hover::after { transform: scaleY(1); }
}

/* --- System labels ---------------------------------------------------------
   Every small uppercase label is mono. Against Archivo headings they read as
   instrument markings rather than more of the same voice, which is what makes
   the page feel measured instead of merely dark. Mono glyphs are wider, so the
   size comes down a notch and the tracking loosens slightly. -------------- */
.eyebrow,
.hero-eyebrow,
.project-meta,
.teaser-meta,
.facts dt,
.case-role dt,
.skill-group h3,
.field label,
.tags li,
.device-bar b {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
}
.hero-eyebrow { letter-spacing: 0.24em; }
.tags li { text-transform: none; letter-spacing: 0.02em; }

/* --- Hit areas -------------------------------------------------------------
   WCAG 2.5.8 asks for 24x24 CSS pixels on a standalone control. These links
   sat a pixel or two under on height, so the padding brings them up without
   moving anything: the extra sits inside existing flex gaps. Links inside a
   sentence are covered by the inline exception and are left alone. ------- */
.nav a,
.footer-gh,
.back-link {
  padding-block: 4px;
}
.nav { align-items: center; }
/* --- Screenshot carousels (portfolio) -------------------------------------
   One capture holds the middle at full size; the rest sit further back,
   smaller and dimmer, and slide forward into focus as the strip moves. The
   view is a native scroller with centre snap points, the depth comes from
   scroll position in script.js, and the big end arrows step one picture at a
   time. Cells taller than their box crop from the top, exactly like a browser
   showing the top of that page. */
.project-media-strip { background: none; overflow: visible; }
/* write-up first at every width, pictures after it */
.project-strip .project-media { order: 2; }
@media (min-width: 900px) and (max-width: 1099px) {
  /* Two columns here would leave the carousel too narrow to read, so the
     pictures keep the full width and sit under the write-up. */
  .project-strip { grid-template-columns: 1fr; align-items: start; }
  .project-strip .project-body { max-width: 640px; }
}
@media (min-width: 1100px) {
  /* Beside the write-up, the same shape as the site project above, instead of
     stacked underneath it. The pictures take the wider share because the view
     has to hold three captures: the one in the middle, the end of the one
     before it, and the start of the next. */
  .project-strip {
    grid-template-columns: minmax(0, 0.68fr) minmax(0, 1.32fr);
    align-items: center;
  }
  .project-strip .project-body { max-width: 44ch; }
  /* Sized so a capture either side reaches well into the view rather than
     showing as a sliver. Wider than this and the neighbours disappear behind
     the fade at the ends. */
  .project-strip .strip { --cellw: 60cqi; }
  .project-strip .strip-phone { --cellw: 36cqi; }
}

/* Cell widths in container units of the view, because a percentage
   flex-basis resolves against the track, whose content box the centring
   padding has already eaten. cqi always means the view. */
/* Each strip says what colour its captures end in, so a page shorter than the
   cell runs out into its own background rather than the dark card. */
.strip { position: relative; --cellw: 58cqi; --screen-bg: #F4F5F7; }
.strip-phone { --cellw: 27cqi; --screen-bg: #000; }
@media (max-width: 900px) {
  .strip { --cellw: 62cqi; }
  .strip-phone { --cellw: 44cqi; }
}
@media (max-width: 560px) {
  .strip { --cellw: 82cqi; }
  .strip-phone { --cellw: 60cqi; }
}

.strip-view {
  container-type: inline-size;
  overflow-x: auto;
  /* No snap: the strip drifts continuously, and a snap would keep hauling it
     back to the nearest cell mid-drift. */
  scrollbar-width: none;
  /* a trackpad swipe past the end stays on the page instead of going back */
  overscroll-behavior-x: contain;
  /* headroom so the focused cell can stand proud without clipping */
  padding-block: 0.6rem;
  /* Captures dissolve at both ends instead of being cut off by the edge of
     the scroller, so one fades out as it leaves and the next fades in as it
     arrives. The arrows sit outside this box, so they stay solid. */
  --edge: 2rem;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--edge), #000 calc(100% - var(--edge)), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--edge), #000 calc(100% - var(--edge)), transparent 100%);
}
.strip-view::-webkit-scrollbar { display: none; }
/* A phone leaves only a narrow margin either side of the focused capture, so
   the fade has to be narrower too or it would reach onto the capture itself.
   This has to sit after the rule above: same specificity, so source order is
   what decides it. */
@media (max-width: 560px) {
  .strip-view { --edge: 1.25rem; }
}
.strip-track {
  display: flex; gap: 1rem;
  /* lets the first and last capture reach the centre */
  padding-inline: calc((100cqi - var(--cellw)) / 2);
}
.strip-item {
  flex: 0 0 var(--cellw);
  will-change: transform;
  display: block; overflow: hidden; padding: 0;
  /* Wide enough that the shortest captures, the Dashboard, Staff Accounts and
     the two Admin Setup pages, run out only a little short of the bottom, and
     what shows there is the page's own colour rather than a black band. */
  aspect-ratio: var(--cell, 3 / 2);
  border: 1px solid var(--line);
  /* the colour the captured page itself ends in, so a short one runs out into
     its own background instead of a black band */
  background: var(--screen-bg, var(--card));
  cursor: pointer;
  transition: border-color 0.25s var(--ease);
}
.strip-item.is-focus { border-color: var(--line-2); }
.strip-item:hover, .strip-item:focus-visible { border-color: var(--line-int); }
.strip-item img { display: block; width: 100%; height: auto; }
.strip-phone .strip-item { --cell: 540 / 1200; }

/* Big bare chevrons at either end, floating over the dimmed edge cells. */
.strip-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  z-index: 3;
  width: 56px; height: 72px;
  display: grid; place-items: center;
  padding: 0; border: 0; background: none;
  color: var(--dim); cursor: pointer;
  transition: color 0.2s var(--ease), opacity 0.2s var(--ease);
}
.strip-arrow svg { width: 34px; height: 34px; display: block; }
.strip-arrow:hover:not(:disabled),
.strip-arrow:focus-visible { color: var(--brass); }
.strip-arrow:disabled { opacity: 0.22; cursor: default; }
.strip-prev { left: clamp(-0.5rem, -1vw, 0rem); }
.strip-next { right: clamp(-0.5rem, -1vw, 0rem); }

/* The site preview on this page is the animated pair, same as the home page,
   at a size that fits the column. It is not a button: the full pages are far
   too tall to be worth a lightbox. */
.project-media-pair { background: none; overflow: visible; }
.project-media-pair .reel-pair { max-width: 880px; }

/* --- The site project on the portfolio page --------------------------------
   The write-up sits left and the two screens sit beside it, so the preview
   fills the space the text leaves instead of a band of empty column above it.
   The phone tucks in close to the browser rather than drifting to the far
   edge, which is what left a gap between them. */
@media (min-width: 900px) {
  .project-site { grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr); align-items: center; }
  .project-site .project-body { max-width: 38ch; }
  .project-site .project-media { order: 2; }
}
.project-media-pair .reel-pair { gap: 2.5%; align-items: flex-end; }
