/* Studio Kestrel
   Palette and type per Studio Kestrel - Branding.
   Dark/moody is scoped to the intro + hero only; the rest of the site
   sits on the bone base. */

:root {
  --navy: #17212B;
  --slate: #536473;
  --mist: #AEB8BC;
  --bone: #F4F1E9;
  --charcoal: #2B2B2B;

  --rule: rgba(43, 43, 43, 0.14);
  --rule-dark: rgba(244, 241, 233, 0.16);

  --pad: clamp(20px, 5vw, 64px);
  --maxw: 1200px;
  --gap: clamp(20px, 3vw, 40px);
  --section: clamp(72px, 11vw, 150px);

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

  /* the kestrel mark: its own proportions, and its full size in the hero
     lockup. The pinned size is that width times the scale in .brandmark, and
     site.js is given both so the travel lands on exactly the same number. */
  --mark-ratio: 2124 / 1262;
  --mark-w: 140px;

  /* the burger's inset, and the header bar sized so the burger and pinned
     mark sit on its centre line with the same gap above and below */
  --edge: clamp(18px, 3vw, 30px);
  --bar-h: calc(var(--edge) * 2 + 44px);
}

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: var(--bar-h);   /* anchor jumps land below the header bar */
  /* html carries its own background so body's bone does not propagate to the
     page canvas and show in any overscroll gutter. */
  background: var(--bone);
  /* Reserve the scrollbar's space permanently. The intro locks scrolling with
     overflow: hidden, which removes the scrollbar - without this the viewport
     widens by its width and everything centred jumps sideways as the splash
     ends, throwing the lockup out of alignment. */
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: rgba(83, 100, 115, 0.45) transparent;
}

/* the same bar in Chrome and Safari, which ignore scrollbar-color */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }

::-webkit-scrollbar-thumb {
  background: rgba(83, 100, 115, 0.4);
  background-clip: content-box;
  border: 3px solid transparent;   /* inset, so the thumb reads as a thin pill */
  border-radius: 10px;
}

::-webkit-scrollbar-thumb:hover {
  background: rgba(83, 100, 115, 0.7);
  background-clip: content-box;
}

/* the homepage opens on the dark hero and closes on the dark footer, so its
   canvas is navy at both ends */
html:has(body:not(.page)) { background: var(--navy); }

body {
  margin: 0;
  background: var(--bone);
  color: var(--charcoal);
  font-family: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

body.is-locked { overflow: hidden; }

/* Header bar: a flat bone strip behind the pinned mark and burger, so neither
   ever sits on top of page content. It is always there, but on the homepage
   the hero media is stacked above it (see .hero-media), so the banner simply
   covers it - it shows up exactly as page content scrolls up under it, with
   no JS and nothing to time. */
body::before {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--bar-h);
  z-index: 1;
  background: var(--bone);
  pointer-events: none;
}

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

a { color: inherit; }

h1, h2, h3, h4, .display, .wordmark, .nav-link {
  font-family: "Sora", system-ui, sans-serif;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.08;
  margin: 0;
}

p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

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

.section { padding-block: var(--section); }
.section--tight { padding-block: clamp(48px, 7vw, 96px); }
.section + .section { border-top: 1px solid var(--rule); }

.kicker {
  font-family: "Sora", system-ui, sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--slate);
  margin: 0 0 18px;
}

.display { font-size: clamp(2.4rem, 6.5vw, 5rem); }
.h2 { font-size: clamp(1.7rem, 3.4vw, 2.7rem); }
.h3 { font-size: clamp(1.15rem, 1.8vw, 1.4rem); }

.lede {
  font-size: clamp(1.05rem, 1.5vw, 1.3rem);
  line-height: 1.55;
  color: var(--charcoal);
  max-width: 60ch;
}

.muted { color: var(--slate); }
.measure { max-width: 62ch; }

/* ---------- placeholder content ---------- */
/* Everything visual is a placeholder until the spec films and stills exist. */

.ph {
  position: relative;
  display: grid;
  place-items: center;
  background:
    repeating-linear-gradient(
      135deg,
      rgba(83, 100, 115, 0.07) 0 10px,
      rgba(83, 100, 115, 0) 10px 20px
    ),
    var(--mist);
  border: 1px solid var(--rule);
  overflow: hidden;
}

.ph::after {
  content: attr(data-label);
  font-family: "Sora", system-ui, sans-serif;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(23, 33, 43, 0.55);
  text-align: center;
  padding: 0 16px;
}

.ph--16x9 { aspect-ratio: 16 / 9; }
.ph--4x5  { aspect-ratio: 4 / 5; }
.ph--1x1  { aspect-ratio: 1 / 1; }
.ph--3x2  { aspect-ratio: 3 / 2; }

.ph--dark {
  background:
    repeating-linear-gradient(
      135deg,
      rgba(244, 241, 233, 0.05) 0 10px,
      rgba(244, 241, 233, 0) 10px 20px
    ),
    var(--navy);
  border-color: var(--rule-dark);
}
.ph--dark::after { color: rgba(244, 241, 233, 0.5); }

/* ---------- logo ---------- */
/* The kestrel mark. Traced from Media/Branding/Kestrel Logo.png, and painted
   as a MASK rather than an <img>: the shape comes from the SVG's alpha and the
   colour from the element's own background, so one file serves every context -
   charcoal on the bone sections, bone on the navy ones, and white under the
   difference blend on the travelling brandmark. Nothing needs an inverted copy.
   The mask URL resolves against this stylesheet, so it is correct from
   /work/ as well as the root. */

.logo-mark,
.brandmark,
.nav-brand {
  -webkit-mask: url("assets/kestrel-mark.svg") no-repeat center / contain;
  mask: url("assets/kestrel-mark.svg") no-repeat center / contain;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  text-decoration: none;
}

.logo-mark {
  display: block;
  width: 54px;
  aspect-ratio: var(--mark-ratio);
  background-color: currentColor;
  flex: none;
}

.logo-type {
  display: block;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  white-space: nowrap;
}

.logo-sub {
  position: relative;
  display: block;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 9.5px;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-top: 5px;
  padding-top: 5px;
  /* this rule is the loading bar's track - the bar sits exactly on it */
  border-top: 1px solid rgba(255, 255, 255, 0.24);
  white-space: nowrap;
}

.logo-sub-text {
  display: block;
  opacity: 0.62;
  transition: opacity 700ms var(--ease) 260ms;
}

/* ---------- intro splash ---------- */
/* Backdrop only. The mark, wordmark and loading bar seen during the intro are
   the real hero lockup rendered on top of this, so there is nothing to keep
   aligned - when the backdrop fades, not a pixel of the lockup moves. */

.intro {
  position: fixed;
  inset: 0;
  z-index: 95;
  background: var(--navy);
  transition: opacity 700ms var(--ease), visibility 700ms;
}

.intro.is-out {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.intro-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.55;
}

/* the bar sits on the rule under the wordmark, full width of it */
.intro-bar {
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
  overflow: hidden;
  opacity: 0;
  transition: opacity 400ms var(--ease);
}

.intro-bar > span {
  display: block;
  height: 100%;
  width: 0;
  background: #fff;
  transition: width 140ms linear;
}

/* while the splash is up: bar visible, subline and tagline held back */
.is-intro .intro-bar { opacity: 1; }
.is-intro .logo-sub-text { opacity: 0; transition-delay: 0s; }
.is-intro .hero-tag,
.is-intro .buildnote,
.is-intro .burger { opacity: 0; }
.is-intro .burger { pointer-events: none; }

/* Nothing the script positions may paint before the script has positioned it.
   The CSS fallback transforms are approximations - showing them first and then
   correcting is the jump. Revealed by .is-ready, once measured. */
body:not(.page) .brandmark,
body:not(.page) .hero-inner { visibility: hidden; }
body.is-ready .brandmark,
body.is-ready .hero-inner { visibility: visible; }

/* NOT .hero-scroll - its opacity is written per frame by the scroll handler,
   and a transition on it would make it lag the scroll. It sits below the
   backdrop during the intro anyway. */
.hero-tag, .burger, .buildnote { transition: opacity 700ms var(--ease) 260ms; }

/* ---------- burger ---------- */

.burger {
  position: fixed;
  top: clamp(18px, 3vw, 30px);
  right: clamp(18px, 3vw, 30px);
  z-index: 120;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  /* difference blending keeps the icon readable whatever is behind it */
  mix-blend-mode: difference;
}

/* Over the hero it is white on a dark image anyway, so blending buys nothing
   and costs a backdrop read across the top band on every frame. */
body:not(.page) .burger { mix-blend-mode: normal; }
body:not(.page) .burger.is-inverting { mix-blend-mode: difference; }

.burger span {
  display: block;
  width: 24px;
  height: 1.5px;
  background: #fff;
  grid-area: 1 / 1;
  transition: transform 320ms var(--ease), opacity 200ms var(--ease);
}

.burger span:nth-child(1) { transform: translateY(-5px); }
.burger span:nth-child(3) { transform: translateY(5px); }

.burger[aria-expanded="true"] { mix-blend-mode: normal; }
.burger[aria-expanded="true"] span:nth-child(1) { transform: rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: rotate(-45deg); }

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

.nav {
  position: fixed;
  inset: 0;
  z-index: 110;
  background: var(--navy);
  color: var(--bone);
  display: grid;
  align-content: center;
  padding: var(--pad);
  opacity: 0;
  visibility: hidden;
  transition: opacity 420ms var(--ease), visibility 420ms;
}

.nav.is-open { opacity: 1; visibility: visible; }

.nav-list {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  width: 100%;
  max-width: var(--maxw);
}

.nav-list li { border-top: 1px solid var(--rule-dark); }
.nav-list li:last-child { border-bottom: 1px solid var(--rule-dark); }

.nav-link {
  display: flex;
  align-items: baseline;
  gap: 20px;
  padding: clamp(14px, 2.4vw, 26px) 0;
  font-size: clamp(2rem, 6vw, 4rem);
  text-decoration: none;
  color: var(--bone);
  opacity: 0.72;
  transition: opacity 220ms var(--ease), padding-left 320ms var(--ease);
}

.nav-link:hover,
.nav-link[aria-current="page"] { opacity: 1; padding-left: 14px; }

.nav-num {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.2em;
  color: var(--mist);
}

.nav-foot {
  max-width: var(--maxw);
  width: 100%;
  margin: clamp(30px, 5vh, 60px) auto 0;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--mist);
}

/* ---------- brandmark ---------- */
/* One element, everywhere. On the homepage it starts centred in the hero at
   full size, rides up with the scroll, shrinks, and sticks at the top. On
   interior pages it is simply pinned from the start. Once pinned it sits on
   the bone header bar (body::before), so it never overlaps page content.

   The mark is the element's own background showing through the mask, so the
   white below is the mark's colour - there is no image to swap for a light
   version. */

.brandmark {
  position: fixed;
  left: 50%;
  top: 0;
  z-index: 100;
  display: block;
  width: var(--mark-w);
  aspect-ratio: var(--mark-ratio);
  background-color: var(--bone);
  text-decoration: none;
  /* No will-change. A composited layer here renders the mark and the wordmark
     with a faint shadow while they move. Both this and .hero-inner are left
     unpromoted so they stay matched - what keeps them in sync is that both are
     fixed and positioned from the same JS frame, not the layer. They are one
     masked box and one line of text, so repainting them is cheap. */
  /* pinned position is the JS default too - this is the no-JS fallback */
  transform: translate(-50%, -50%) translateY(40px) scale(0.457);
}

/* Pinned (class toggled by site.js once the icon reaches the header line):
   darkest brand colour, flat - no blend mode, so it reads the same over any
   section instead of inverting per background. */
.brandmark.is-inverting { background-color: var(--charcoal); }

/* The travelling mark is wherever the page scroll left it - mid-hero if the
   visitor opens the burger before scrolling. The nav has its own separate,
   always-pinned mark instead of trying to reuse this one, so it hides while
   the overlay is open. */
.nav.is-open ~ .brandmark { visibility: hidden; }

/* ---------- nav brand ---------- */
/* A second, independent mark for the nav overlay only: fixed at the top on
   the burger's line, full stop - no scroll travel, no pinned/hero states to
   account for. Hidden until the overlay opens. */

.nav-brand {
  position: fixed;
  left: 50%;
  /* the burger's own vertical centre, not its top edge - it's a fixed 44px
     box, so this is the same line the pinned .brandmark lands on via JS */
  top: calc(clamp(18px, 3vw, 30px) + 22px);
  z-index: 115;
  width: 64px;
  aspect-ratio: var(--mark-ratio);
  background-color: var(--bone);
  transform: translate(-50%, -50%);
  text-decoration: none;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 300ms var(--ease), visibility 300ms;
}

.nav.is-open ~ .nav-brand {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* homepage starts life in the hero lockup at full size. The offset lands it
   roughly on the slot, so there is no jump when the script takes over. */
body:not(.page) .brandmark {
  transform: translate(-50%, -50%) translateY(calc(50vh - 62px)) scale(1);
}

/* reserves the icon's space in the hero lockup, since the icon itself is fixed */
.brandmark-slot {
  display: block;
  width: var(--mark-w);
  aspect-ratio: var(--mark-ratio);
}

/* ---------- hero ---------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  place-items: center;
  background: var(--navy);
  color: var(--bone);
  overflow: hidden;
}

.hero-media {
  position: absolute;
  inset: 0;
  z-index: 2;   /* above the header bar, so the banner hides it */
  /* No will-change. Its opacity was animated once; that fade is gone, so this
     was promoting a full-screen image layer for an animation that no longer
     exists - a composited layer sitting directly behind unpromoted white text. */
}

.hero-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
  aspect-ratio: auto;
}

.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 50% 45%, rgba(23, 33, 43, 0.1) 0%, rgba(23, 33, 43, 0.72) 100%),
    linear-gradient(to bottom, rgba(23, 33, 43, 0.35), rgba(23, 33, 43, 0.2) 40%, rgba(23, 33, 43, 0.85));
}

.hero-inner {
  /* Fixed, not in flow. The text used to get its position from the browser's
     native scroll PLUS a JS transform, while the icon got its position from a
     JS transform alone - so on scroll the icon lagged and the text led, and
     they visibly moved apart. Both are fixed and purely JS-positioned now, so
     they share one timing source and stay locked together. */
  position: fixed;
  left: 50%;
  top: 0;
  z-index: 99;
  text-align: center;
  display: grid;
  justify-items: center;
  gap: 22px;
  padding: var(--pad);
  /* deliberately not promoted - see the note on .brandmark */
  /* no-JS fallback: centred in the hero */
  transform: translate(-50%, -50%) translateY(50vh);
}

.hero-lockup { display: block; }
.hero .logo-type { font-size: clamp(16px, 2.6vw, 24px); letter-spacing: 0.28em; }
.hero .logo-sub { font-size: 10.5px; letter-spacing: 0.22em; }

.hero-tag {
  font-size: clamp(0.95rem, 1.4vw, 1.15rem);
  color: var(--mist);
  max-width: 46ch;
  margin: 0;
}

.hero-scroll {
  position: absolute;
  z-index: 3;   /* above .hero-media now that it is lifted */
  bottom: clamp(22px, 4vh, 44px);
  left: 50%;
  transform: translateX(-50%);
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(244, 241, 233, 0.5);
}

/* ---------- page header (interior) ---------- */

.pagehead {
  padding-block: clamp(120px, 16vh, 210px) clamp(40px, 6vw, 76px);
  border-bottom: 1px solid var(--rule);
}

.pagehead .display { max-width: 16ch; }
.pagehead .lede { margin-top: 26px; }

/* ---------- grids ---------- */

.grid { display: grid; gap: var(--gap); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }

/* always three across, phones included - the home page service tiles */
.grid--trio { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 599px) {
  .grid--trio { gap: 10px; }
  .grid--trio .tile-meta { flex-direction: column; align-items: flex-start; gap: 2px; margin-top: 10px; padding-top: 8px; }
  .grid--trio .tile-title { font-size: 0.8rem; }
  .grid--trio .tile-tag { font-size: 9px; }
}
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

.split {
  display: grid;
  gap: var(--gap);
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 860px) {
  .split { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.4fr); gap: clamp(40px, 6vw, 90px); }
}

/* ---------- work tiles ---------- */

.tile { display: block; text-decoration: none; color: inherit; }
.tile .ph { transition: transform 600ms var(--ease); }
.tile:hover .ph { transform: scale(1.015); }

.tile-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
}

.tile-title { font-size: clamp(1.05rem, 1.6vw, 1.25rem); }

.tile-tag {
  font-family: "Sora", sans-serif;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--slate);
  white-space: nowrap;
}

.tile--wide { grid-column: 1 / -1; }

/* Behance wall. Covers are Behance's own 808 x 632 crop, shown whole */
.grid--covers { grid-template-columns: 1fr; }
@media (min-width: 600px) { .grid--covers { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .grid--covers { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.cover {
  aspect-ratio: 808 / 632;
  border-radius: 8px;
  overflow: hidden;
  /* Rounded corners are clipped ONCE, here, and the img stays square.
     - no fill behind the image: a navy fill bled through the anti-aliased
       corner as a dark hairline on light covers
     - the mask makes WebKit clip the img's hover-scale layer with the same
       rounded edge instead of compositing it separately (iOS Safari otherwise
       leaves a hairline round the corner) */
  -webkit-mask-image: -webkit-radial-gradient(white, black);
  transform: translateZ(0);
}

.cover img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 600ms var(--ease);
}

.tile:hover .cover img { transform: scale(1.015); }

/* home page services tiles - portrait frames from the showreel */
.cover--4x5 { aspect-ratio: 4 / 5; }

/* personal development wall - square icon tiles, each opens its own page */
.cover--sq { aspect-ratio: 1; }
.grid--icons { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 860px) { .grid--icons { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 599px) {
  .grid--icons { gap: 12px; }
  .grid--icons .tile-meta { flex-direction: column; align-items: flex-start; gap: 2px; margin-top: 10px; padding-top: 8px; }
  .grid--icons .tile-title { font-size: 0.9rem; }
}

.section-head { margin-bottom: clamp(28px, 4vw, 48px); }
.section-head .lede { margin-top: 18px; }

/* ---------- services ---------- */

.svc { padding-top: 26px; border-top: 1px solid var(--rule); }
.svc .kicker { margin-bottom: 12px; }
.svc h3 { margin-bottom: 12px; }

.svc ul {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  font-size: 15px;
  color: var(--slate);
}

.svc li { padding: 6px 0; border-bottom: 1px dotted var(--rule); }

.panel {
  background: #fff;
  border: 1px solid var(--rule);
  padding: clamp(24px, 4vw, 48px);
}

.panel--dark { background: var(--navy); color: var(--bone); border-color: transparent; }
.panel--dark .kicker, .panel--dark .muted { color: var(--mist); }

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

.contact-link {
  font-family: "Sora", sans-serif;
  font-size: clamp(1.2rem, 2.4vw, 2rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 6px;
  transition: border-color 260ms var(--ease);
  overflow-wrap: anywhere;
}

.contact-link:hover { border-color: var(--charcoal); }

.dl { display: grid; gap: 2px; margin: 0; font-size: 15px; }

.dl dt {
  font-family: "Sora", sans-serif;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--slate);
  margin-top: 20px;
}

.dl dd { margin: 0; }

/* ---------- case study ---------- */

.cs-hero { padding-block: clamp(120px, 16vh, 200px) 0; }
.cs-hero .display { max-width: 18ch; }

.cs-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 40px;
  margin-top: 34px;
  padding-top: 20px;
  border-top: 1px solid var(--rule);
}

.cs-meta div { min-width: 130px; }

.cs-meta dt {
  font-family: "Sora", sans-serif;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--slate);
}

.cs-meta dd { margin: 4px 0 0; font-size: 15px; }

/* Behance project card. Behance's embed is a fixed-size card (404 x 316), not
   the whole post, and the post itself refuses to be framed */
.cs-embed {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  padding-block: clamp(40px, 8vh, 96px);
  background: var(--navy);
}

.cs-embed iframe {
  display: block;
  width: 404px;
  max-width: 100%;
  height: 316px;
  border: 0;
}

.cs-embed a { color: var(--bone); }

.bleed {
  width: 100%;
  max-width: 1560px;
  margin-inline: auto;
  padding-inline: var(--pad);
}

/* personal project pages - stills and films stacked full width */
.gallery { display: grid; gap: clamp(12px, 2vw, 24px); }
.gallery--2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.gallery + .gallery { margin-top: clamp(12px, 2vw, 24px); }
.gallery--narrow { max-width: 1100px; margin-inline: auto; }
.gallery img,
.gallery video {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
  background: var(--navy);
}

.reel--square { aspect-ratio: 1; max-width: 1100px; margin-inline: auto; }

figure { margin: 0; }

figcaption {
  margin-top: 12px;
  font-size: 12.5px;
  letter-spacing: 0.02em;
  color: var(--slate);
}

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

.foot {
  background: var(--navy);
  color: var(--mist);
  padding-block: clamp(50px, 7vw, 90px);
  font-size: 13px;
}

.foot .wordmark { color: var(--bone); }

.foot-row {
  display: flex;
  flex-wrap: wrap;
  gap: 28px;
  justify-content: space-between;
  align-items: flex-end;
}

.foot-links {
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
  list-style: none;
  margin: 0;
  padding: 0;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 11px;
}

.foot a { text-decoration: none; opacity: 0.8; }
.foot a:hover { opacity: 1; }

.foot-note {
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid var(--rule-dark);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: rgba(174, 184, 188, 0.6);
}

/* ---------- build note ---------- */
/* Temporary marker so it is obvious which parts are standing in for
   content that does not exist yet. Delete the element to remove. */

.buildnote {
  position: fixed;
  left: 0;
  bottom: 0;
  z-index: 90;
  background: var(--charcoal);
  color: var(--bone);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 7px 12px;
}

/* ---------- showreel ---------- */
/* Has audio, so it waits for a press. The button covers the poster; once
   playing it goes and the native controls take over. */

.reel {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--navy);
  overflow: hidden;
}
.reel video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.reel-play {
  position: absolute;
  inset: 0;
  width: 100%;
  border: 0;
  padding: 0;
  cursor: pointer;
  background: rgba(23, 33, 43, 0.25);
  transition: background 0.3s var(--ease);
}
.reel-play::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: clamp(64px, 8vw, 96px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 1px solid rgba(244, 241, 233, 0.7);
  transition: transform 0.3s var(--ease);
}
.reel-play::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-35%, -50%);
  border-style: solid;
  border-width: clamp(10px, 1.2vw, 14px) 0 clamp(10px, 1.2vw, 14px) clamp(16px, 2vw, 23px);
  border-color: transparent transparent transparent var(--bone);
}
.reel-play:hover { background: rgba(23, 33, 43, 0.1); }
.reel-play:hover::before { transform: translate(-50%, -50%) scale(1.06); }
.reel-play:focus-visible { outline: 2px solid var(--bone); outline-offset: -6px; }
.reel.is-playing .reel-play { display: none; }

/* ---------- motion ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* No travel - the mark is simply pinned, as on interior pages. The slot
     reserves space for an icon that is now up in the corner, so collapse it
     or the hero lockup renders with a mark-sized hole above the wordmark. */
  body:not(.page) .brandmark {
    transform: translate(-50%, -50%) translateY(40px) scale(0.457);
  }
  .brandmark-slot { display: none; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
