/* ============================================================
   bakedlights.com — dark cinematic
   Near-black surround so the work is the only thing that glows.
   Accents are tungsten (3200K) and daylight (5600K) rather than
   an arbitrary brand colour.
   ============================================================ */

:root{
  --bg:          #161513;   /* page                        */
  --bg-deep:     #131211;   /* recessed panels             */
  --ink:         #0E0D0C;   /* tile bed, inputs            */
  --viewer:      #0B0A0A;   /* full-screen viewer          */
  --paper:       #EDE9E2;   /* primary text                */
  --paper-dim:   #D4CFC6;   /* hero copy                   */
  --body:        #C9C4BB;   /* body copy                   */
  --muted:       #8E8981;   /* labels, secondary text      */
  --muted-lift:  #A8A29A;   /* slate meta                  */
  --hair:        #2B2825;   /* hairlines                   */
  --hair-lift:   #443F39;   /* ghost button border         */
  --raise:       #2A2724;   /* pressed / active surface    */
  --raise-hi:    #3A3632;   /* hovered chrome              */
  --row:         #1D1B19;   /* index row hover             */
  --tungsten:    #FFB25A;   /* 3200K                       */
  --tungsten-hi: #FFC98D;
  --on-tungsten: #231B0F;
  --daylight:    #6FA8D6;   /* 5600K                       */

  --gutter: clamp(18px,4vw,56px);
  --mono: "IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;

  /* The two mono label sizes. Every tracked-caps label in the design draws
     from one of these, so the whole micro-type scale moves from here. */
  --micro:   12px;   /* numbers, kind badges, field labels, footer */
  --mono-sm: 13px;   /* eyebrows, nav, chips, tags, viewer meta */
  --ease: cubic-bezier(.2,.7,.3,1);
}

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

html{ scroll-behavior:smooth; }
/* The header is fixed on the home page and sticky on the galleries, so an
   anchor jump would otherwise put the heading underneath it. */
#top,#work,#about,#contact{ scroll-margin-top:84px; }
@media (max-width:620px){ #top,#work,#about,#contact{ scroll-margin-top:112px; } }
@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{ animation-duration:.01ms !important; transition-duration:.01ms !important; }
}

html,body{ margin:0; padding:0; background:var(--bg); }

body{
  color:var(--paper);
  font-family:Archivo,system-ui,-apple-system,"Segoe UI",sans-serif;
  font-size:16px;
  line-height:1.6;
  min-height:100vh;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
}
body.is-locked{ overflow:hidden; }

img{ display:block; max-width:100%; }
a{ color:var(--tungsten); text-decoration:none; }
a:hover{ color:var(--tungsten-hi); }
::selection{ background:var(--tungsten); color:var(--bg); }
:focus-visible{ outline:2px solid var(--tungsten); outline-offset:3px; }

@keyframes bl-fade{ from{ opacity:0 } to{ opacity:1 } }

/* ---------- scroll motion ----------
   Only applied once motion.js adds .motion to <html>, so the page is fully
   visible without JS. Opacity and transform only: both composited, so none of
   this reflows while scrolling. motion.js bails out entirely under
   prefers-reduced-motion, which leaves these rules unused. */
html.motion .reveal{
  opacity:0;
  transform:translate3d(0,20px,0);
  transition:opacity .75s ease var(--reveal-delay,0ms),
             transform .75s var(--ease) var(--reveal-delay,0ms);
}
html.motion .reveal.is-in{ opacity:1; transform:none; }

/* The header rides on the hero's gradient at the top, then settles onto a
   solid ground once the hero is behind you. A gradient cannot transition to a
   colour, so the solid state is a layer that fades up underneath. */
.topbar::before{
  content:""; position:absolute; inset:0; z-index:-1;
  background:rgba(14,13,12,.92);
  border-bottom:1px solid var(--hair);
  opacity:0; transition:opacity .45s ease;
}
.topbar.is-scrolled::before{ opacity:1; }

/* The hero is the one thing that moves on arrival rather than on scroll. */
html.motion .hero-slate > *{
  animation:bl-rise .85s var(--ease) both;
  animation-delay:calc(var(--i,0) * 90ms + 120ms);
}
@keyframes bl-rise{
  from{ opacity:0; transform:translate3d(0,22px,0) }
  to{ opacity:1; transform:none }
}

.eyebrow{ font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.2em; color:var(--muted); }
.label{ font-family:var(--mono); font-size:var(--micro); letter-spacing:.18em; color:var(--muted); }

/* Clipped rather than parked at -9999px. The old offset made the skip link a
   positioned child hanging ten thousand pixels outside its clipping parent on
   every page, which is also what the overflow detector kept catching. */
.skip{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap;
  background:var(--paper); color:var(--bg);
}
.skip:focus{
  position:fixed; z-index:200; width:auto; height:auto; margin:0;
  overflow:visible; clip-path:none; padding:10px 16px;
}
/* a:hover sets --tungsten-hi, which on this light chip measures 1.24:1 — the
   skip link all but vanishes exactly when a keyboard user mouses onto it.
   Same specificity as a:hover, and later, so it wins. */
.skip:hover,.skip:focus{ color:var(--bg); }
.skip:focus{ left:8px; top:8px; }

/* ---------- top bar ---------- */
.topbar{
  position:fixed; inset:0 0 auto 0; z-index:60;
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:14px var(--gutter);
  background:linear-gradient(to bottom,rgba(14,13,12,.9),rgba(14,13,12,0));
  backdrop-filter:blur(8px);
}
.topbar--page{
  position:sticky; inset:auto;
  background:rgba(14,13,12,.92);
  border-bottom:1px solid var(--hair);
}
.mark{ display:flex; flex-direction:column; line-height:1.12; color:var(--paper); }
.mark:hover{ color:var(--paper); }
.mark-name{ font-variation-settings:"wdth" 112,"wght" 650; font-size:14px; letter-spacing:.01em; }
.mark-role{ font-family:var(--mono); font-size:var(--micro); letter-spacing:.18em; color:var(--muted); }

.topnav{ display:flex; gap:clamp(14px,2.4vw,28px); font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.14em; }
.topnav a{ color:var(--muted); padding-bottom:3px; border-bottom:1px solid transparent; }
.topnav a:hover{ color:var(--paper); border-bottom-color:var(--tungsten); }

/* ---------- hero ---------- */
.hero{
  position:relative; min-height:min(94vh,900px);
  display:flex; align-items:flex-end; overflow:hidden;
}
.hero-plate{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  /* Defocused so the plate sets a mood without competing with the headline,
     and desaturated so the forest greens sit inside the near-black and tungsten
     palette instead of fighting it. The scale hides the soft edge the blur
     leaves at the frame. */
  filter:saturate(var(--hero-saturate,.25)) blur(var(--hero-blur,8px));
  transform:scale(1.06);
}
.hero-veil,.hero-glow{ position:absolute; inset:0; }
.hero-veil{ background:linear-gradient(to top,rgba(14,13,12,.96) 3%,rgba(14,13,12,.6) 40%,rgba(14,13,12,.18) 76%); }
.hero-glow{ background:radial-gradient(120% 80% at 12% 92%,rgba(255,178,90,.13),transparent 60%); }

.hero-slate{
  position:relative; z-index:2; width:100%; max-width:1100px;
  padding:140px var(--gutter) clamp(40px,6vw,76px);
}
.hero-slate .eyebrow{ color:var(--tungsten); margin-bottom:22px; }
.hero-slate h1{
  margin:0; font-size:clamp(2.6rem,7.4vw,6.4rem); line-height:.94;
  font-variation-settings:"wdth" 122,"wght" 750; letter-spacing:-.02em; text-wrap:balance;
}
.hero-line{
  margin:22px 0 30px; max-width:58ch;
  font-size:clamp(1rem,1.25vw,1.2rem); color:var(--paper-dim); text-wrap:pretty;
}
.hero-actions{ display:flex; flex-wrap:wrap; gap:12px; }

/* ---------- buttons ---------- */
.btn{
  font:inherit; font-size:14px; font-variation-settings:"wdth" 100,"wght" 500;
  border:1px solid var(--hair-lift); background:rgba(20,19,18,.4); color:var(--paper);
  padding:13px 26px; border-radius:2px; cursor:pointer;
  transition:background .18s var(--ease),border-color .18s var(--ease);
}
.btn:hover{ background:var(--raise); border-color:var(--muted); color:var(--paper); }
.btn-primary{
  display:flex; align-items:center; gap:10px;
  font-variation-settings:"wdth" 100,"wght" 600;
  border-color:var(--tungsten); background:var(--tungsten); color:var(--on-tungsten);
}
.btn-primary:hover{ background:var(--tungsten-hi); border-color:var(--tungsten-hi); color:var(--on-tungsten); }
.btn-primary::before{
  content:""; width:0; height:0;
  border-left:9px solid var(--on-tungsten);
  border-top:6px solid transparent; border-bottom:6px solid transparent;
}

/* ---------- spec strip ---------- */
.spec{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr));
  border-top:1px solid var(--hair); border-bottom:1px solid var(--hair);
  background:var(--bg-deep);
}
.spec > div{ padding:18px var(--gutter); }
.spec > div:not(:last-child){ border-right:1px solid var(--hair); }
.spec .label{ margin-bottom:6px; }
.spec-val{ font-size:14px; font-variation-settings:"wdth" 104,"wght" 550; }
.spec-val.is-open{ color:var(--tungsten); }

/* ---------- work ---------- */
.work{ padding:clamp(64px,8vw,120px) var(--gutter) clamp(56px,7vw,100px); }
.work-head{
  display:flex; flex-wrap:wrap; align-items:flex-end; justify-content:space-between; gap:20px;
  padding-bottom:20px; border-bottom:1px solid var(--hair); margin-bottom:28px;
}
.work-head .eyebrow{ margin-bottom:10px; }
.work-head h2{
  margin:0; font-size:clamp(1.8rem,3.6vw,3rem); line-height:1;
  font-variation-settings:"wdth" 118,"wght" 720; letter-spacing:-.015em;
}
.work-controls{ display:flex; flex-wrap:wrap; align-items:center; gap:14px; }

.filters{ display:flex; flex-wrap:wrap; gap:7px; }
.chip{
  font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.12em;
  padding:7px 15px; border-radius:99px; cursor:pointer; white-space:nowrap;
  border:1px solid var(--hair); background:transparent; color:var(--muted);
  transition:color .18s var(--ease),border-color .18s var(--ease),background .18s var(--ease);
}
.chip:hover{ color:var(--paper); border-color:var(--muted); }
.chip.is-on{ border-color:var(--tungsten); background:var(--tungsten); color:var(--on-tungsten); }

/* ---------- contact sheet ---------- */
.reels{
  display:grid; gap:18px; margin-bottom:18px;
  grid-template-columns:repeat(2,minmax(0,1fr));
}
.stills{
  display:grid; gap:14px;
  grid-template-columns:var(--still-cols,minmax(0,1fr));
  grid-auto-rows:max(200px,22vw);
}
@media (max-width:779px){
  .reels,.stills{ grid-template-columns:minmax(0,1fr); }
}
.reels:empty,.stills:empty{ display:none; }

.tile{
  position:relative; display:block; width:100%; margin:0; padding:0; border:0;
  background:var(--ink); color:inherit; font:inherit; text-align:left;
  cursor:pointer; overflow:hidden; border-radius:2px;
  animation:bl-fade .4s ease both;
  transition:transform .35s var(--ease);
}
.tile:hover,.tile:focus-visible{ transform:scale(1.025); z-index:3; }
.tile img{
  width:100%; height:100%; object-fit:cover;
  transition:filter .3s ease;
}
.tile-reel{ aspect-ratio:16 / 9; }
.tile-reel img{ filter:brightness(.8); }
.tile-reel:hover img,.tile-reel:focus-visible img{ filter:brightness(1); }
.tile-still{ height:100%; }
.tile-still img{ filter:brightness(.82); }
.tile-still:hover img,.tile-still:focus-visible img{ filter:brightness(1); }

/* A text-shadow earns no contrast credit and measured as low as 2.2:1 over a
   bright frame. These now carry their own ground, like the kind badge beside
   them, which is also what makes the pair read as a set. */
.tile-num{
  position:absolute; z-index:2; font-family:var(--mono); font-size:var(--micro); letter-spacing:.16em;
  color:var(--paper);
  background:rgba(14,13,12,.68); backdrop-filter:blur(4px);
  border-radius:3px; padding:3px 7px; line-height:1;
}
.tile-reel .tile-num{ top:12px; left:14px; }
.tile-still .tile-num{ top:10px; left:10px; }

.tile-kind{
  position:absolute; z-index:2; font-family:var(--mono); font-size:var(--micro); letter-spacing:.14em;
  border-radius:99px; border:1px solid var(--hair); color:var(--muted);
  background:rgba(14,13,12,.6); backdrop-filter:blur(4px);
}
.tile-reel .tile-kind{ top:12px; right:16px; padding:4px 11px; }
.tile-still .tile-kind{ top:10px; right:12px; padding:3px 9px; }
.kind-video,.kind-page{ color:var(--tungsten); border-color:rgba(255,178,90,.45); }
.kind-link{ color:var(--daylight); border-color:rgba(111,168,214,.45); }

.tile-slate{
  position:absolute; inset:auto 0 0 0; z-index:2;
  display:flex; align-items:flex-end; justify-content:space-between;
}
.tile-reel .tile-slate{
  padding:78px 20px 18px; gap:14px;
  background:linear-gradient(to top,rgba(12,11,10,.95),rgba(12,11,10,0));
}
.tile-still .tile-slate{
  padding:52px 14px 13px; gap:12px;
  background:linear-gradient(to top,rgba(12,11,10,.94),rgba(12,11,10,0));
}
.tile-title{ display:block; }
.tile-reel .tile-title{ font-size:clamp(1.15rem,1.8vw,1.6rem); font-variation-settings:"wdth" 110,"wght" 660; line-height:1.12; }
.tile-still .tile-title{ font-size:15px; font-variation-settings:"wdth" 104,"wght" 620; line-height:1.2; }
.tile-meta{ display:block; font-family:var(--mono); letter-spacing:.02em; color:var(--muted-lift); }
.tile-reel .tile-meta{ font-size:var(--mono-sm); margin-top:6px; }
.tile-still .tile-meta{ font-size:var(--micro); margin-top:4px; }
.tile-cat{ font-family:var(--mono); font-size:var(--micro); letter-spacing:.14em; color:var(--muted); white-space:nowrap; }

.play{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); z-index:2;
  --play:clamp(56px,5.8vw,168px); width:var(--play); height:var(--play); border-radius:50%;
  background:rgba(14,13,12,.5); border:1px solid rgba(237,233,226,.45);
  display:grid; place-items:center; backdrop-filter:blur(3px);
  transition:background .2s var(--ease),border-color .2s var(--ease);
}
.play:hover{ background:rgba(255,178,90,.92); border-color:transparent; }
.play::before{
  content:""; width:0; height:0; margin-left:calc(var(--play) * .06);
  border-left:calc(var(--play) * .226) solid var(--paper);
  border-top:calc(var(--play) * .143) solid transparent;
  border-bottom:calc(var(--play) * .143) solid transparent;
}

/* a gallery frame that has a clip behind it — smaller target than the reels */
.frame .play{ --play:clamp(40px,4.2vw,88px); width:var(--play); height:var(--play); }
.frame .play::before{
  margin-left:calc(var(--play) * .07);
  border-left:calc(var(--play) * .23) solid var(--paper);
  border-top:calc(var(--play) * .145) solid transparent;
  border-bottom:calc(var(--play) * .145) solid transparent;
}
.viewer-stage video{ position:absolute; inset:0; width:100%; height:100%; object-fit:contain; }
.viewer-stage [hidden]{ display:none; }

.grid-empty{ font-family:var(--mono); font-size:12px; letter-spacing:.1em; color:var(--muted); margin:28px 0 0; }

/* ---------- about ---------- */
.about,.contact{
  padding:clamp(56px,7vw,100px) var(--gutter);
  border-top:1px solid var(--hair);
  display:grid; gap:clamp(28px,5vw,72px);
}
.about{ grid-template-columns:repeat(auto-fit,minmax(min(100%,340px),1fr)); }
.contact{ grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr)); }
.about .eyebrow,.contact .eyebrow{ margin-bottom:12px; }
.about h2,.contact h2{
  margin:0; font-size:clamp(1.6rem,3vw,2.4rem); line-height:1.05;
  font-variation-settings:"wdth" 116,"wght" 700; letter-spacing:-.015em;
}
.about h2{ margin-bottom:22px; }
.contact h2{ margin-bottom:18px; }
.about p{ margin:0 0 1em; max-width:60ch; color:var(--body); text-wrap:pretty; }
.about p:last-child{ margin-bottom:0; }

.about-side{ display:grid; gap:26px; align-content:start; }
.about-side .label{ margin-bottom:12px; }
.tags{ list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:7px; }
.tags li{
  font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.08em; color:var(--body);
  border:1px solid var(--hair); border-radius:2px; padding:6px 12px;
}
/* Same chip, tightened for the spec strip: the cell is a quarter of the page
   and seven of them at full size wrap to three rows. */
.tags--spec{ gap:6px; margin-top:2px; }
.tags--spec li{ font-size:var(--micro); letter-spacing:.06em; padding:4px 9px; }

/* ---------- contact ---------- */
.contact p{ margin:0 0 24px; max-width:48ch; color:var(--body); }
.mailto{
  display:inline-block; font-size:clamp(1.05rem,2.2vw,1.5rem);
  font-variation-settings:"wdth" 108,"wght" 560; color:var(--tungsten);
  border-bottom:1px solid rgba(255,178,90,.35); padding-bottom:4px; word-break:break-word;
}
.mailto:hover{ color:var(--tungsten); border-bottom-color:var(--tungsten); }
.social{
  list-style:none; margin:28px 0 0; padding:0;
  display:flex; flex-wrap:wrap; gap:18px;
  font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.14em;
}
.social a{ color:var(--muted); border-bottom:1px solid var(--hair); padding:6px 0 4px; }
.social a:hover{ color:var(--paper); border-bottom-color:var(--daylight); }

.contact-form{
  display:flex; flex-direction:column; gap:6px;
  background:var(--bg-deep); border:1px solid var(--hair); border-radius:2px;
  padding:clamp(20px,3vw,32px);
}
.contact-form label{
  font-family:var(--mono); font-size:var(--micro); letter-spacing:.16em; color:var(--muted); margin-top:14px;
}
.contact-form label:first-of-type{ margin-top:4px; }
.contact-form input,.contact-form textarea{
  background:var(--ink); border:1px solid var(--hair); color:var(--paper);
  font:inherit; font-size:15px; padding:11px 13px; border-radius:2px; width:100%;
}
.contact-form textarea{ resize:vertical; }
/* The border shift alone is barely perceptible on this ground, so the global
   :focus-visible ring has to stand. Only the mouse-click ring is dropped. */
.contact-form input:focus,.contact-form textarea:focus{ border-color:var(--muted); }
.contact-form input:focus:not(:focus-visible),
.contact-form textarea:focus:not(:focus-visible){ outline:none; }
.contact-form button{
  margin-top:22px; align-self:flex-start;
  font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.16em; cursor:pointer;
  border:1px solid var(--tungsten); background:var(--tungsten); color:var(--on-tungsten);
  padding:12px 24px; border-radius:2px;
}
.contact-form button:hover{ background:var(--tungsten-hi); border-color:var(--tungsten-hi); }
/* Same treatment for the spam trap: clipped, not flung off-canvas. */
.hp{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap;
}

/* ---------- footer ---------- */
.foot{
  display:flex; flex-wrap:wrap; gap:10px; justify-content:space-between;
  padding:24px var(--gutter); border-top:1px solid var(--hair);
  font-family:var(--mono); font-size:var(--micro); letter-spacing:.14em; color:var(--muted);
}

/* ---------- full-screen viewer ---------- */
.viewer{
  position:fixed; inset:0; z-index:120; background:var(--viewer);
  display:grid; grid-template-rows:1fr auto auto auto;
  padding:clamp(46px,4.5vw,58px) clamp(14px,4.4vw,62px) 0;
  animation:bl-fade .25s ease both;
}

/* A blurred copy of whatever is open, sat behind everything. The viewer's own
   dark background shows through it, which is what keeps the chrome readable. */
.viewer-backdrop{
  position:absolute; inset:0; pointer-events:none;
  background-size:cover; background-position:center;
  filter:blur(64px) saturate(140%);
  transform:scale(1.2);        /* hides the soft edge the blur leaves behind */
  /* Low enough to tint the surround without casting colour onto the neutral
     ground the images are judged against. */
  opacity:.14;
  /* Negative z-index paints it over the viewer's own background but under
     everything in flow — and leaves the absolutely positioned close and arrow
     buttons alone, which a blanket position rule here would break. */
  z-index:-1;
}
.viewer[hidden]{ display:none; }
/* The media is absolutely positioned and letterboxed rather than relying on
   max-height:100%, which Chrome declines to resolve against an fr row and so
   lets a tall image blow past the viewport. */
/* A size container, so the embed below can be sized against the stage's
   height as well as its width. */
.viewer-stage{
  position:relative; display:grid; place-items:center; min-height:0; overflow:hidden;
  container-type:size;
}
.viewer-stage img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:contain;
  transition:opacity .2s ease;
}
.viewer-stage img.is-loading{ opacity:.25; }
/* The largest 16/9 box the stage can hold, whichever way it is short.
   max-height cannot do this on its own: against a definite width:100% it
   clips the box rather than narrowing it, so a short window cut the player
   off -- 648px of it inside 521px of stage at 1280x700. */
.viewer-stage iframe{
  width:min(100%, calc(100cqh * 16 / 9));
  height:auto; aspect-ratio:16/9; border:0;
}
.viewer-slate{
  display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:12px;
  padding:13px 0 16px; border-top:1px solid var(--hair); margin-top:13px;
}
.viewer-title{ margin:0; font-size:16px; font-variation-settings:"wdth" 108,"wght" 620; }
.viewer-meta{ margin:4px 0 0; font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.02em; color:var(--muted); }
.viewer-pos{ font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.14em; color:var(--muted); }
.viewer-slate--mono{
  font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.14em; color:var(--muted);
}

.viewer-close,.viewer-nav{
  position:absolute; z-index:3; cursor:pointer; line-height:1;
  background:rgba(42,39,36,.8); color:var(--paper);
  border:1px solid var(--hair); border-radius:2px;
  display:grid; place-items:center;
}
.viewer-close:hover,.viewer-nav:hover{ background:var(--raise-hi); border-color:var(--muted); }
.viewer-close{ top:14px; right:14px; width:40px; height:40px; font-size:22px; }
.viewer-nav{ top:50%; transform:translateY(-50%); width:44px; height:66px; font-size:26px; }
.viewer-prev{ left:10px; }
.viewer-next{ right:10px; }

/* ---------- gallery pages ---------- */
.page-head{ padding:clamp(44px,6vw,84px) var(--gutter) clamp(24px,3vw,36px); }
.page-head .eyebrow{ margin-bottom:12px; }
.page-head h1{
  margin:0; font-size:clamp(2.2rem,6vw,4.6rem); line-height:.96;
  font-variation-settings:"wdth" 120,"wght" 740; letter-spacing:-.02em;
}
.page-head p{
  margin:20px 0 0; max-width:56ch; color:var(--body);
  font-size:clamp(1rem,1.2vw,1.12rem); text-wrap:pretty;
}

.banner{ position:relative; overflow:hidden; border-bottom:1px solid var(--hair); }
.banner img{ width:100%; height:clamp(260px,42vw,560px); object-fit:cover; filter:brightness(.72); }
.banner-slate{
  position:absolute; inset:auto 0 0 0;
  padding:clamp(40px,8vw,90px) var(--gutter) clamp(22px,3vw,34px);
  background:linear-gradient(to top,rgba(14,13,12,.96),rgba(14,13,12,0));
}
.banner-slate .eyebrow{ color:var(--tungsten); margin-bottom:10px; }
.banner-slate h1{
  margin:0; font-size:clamp(2rem,5.4vw,4.2rem); line-height:.98;
  font-variation-settings:"wdth" 120,"wght" 740; letter-spacing:-.02em;
}

.brief{
  padding:clamp(36px,5vw,64px) var(--gutter) clamp(24px,3vw,36px);
  display:grid; gap:clamp(24px,5vw,64px);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr));
}
.brief p{ margin:0; color:var(--body); font-size:clamp(1rem,1.2vw,1.14rem); text-wrap:pretty; }
.brief-specs{
  display:grid; gap:18px; align-content:start;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,150px),1fr));
}
.brief-specs .label{ margin-bottom:6px; }
.brief-specs .spec-val{ font-size:14px; font-variation-settings:"wdth" 104,"wght" 550; }

.gallery{ padding:0 var(--gutter) clamp(56px,7vw,100px); }
.rows{ display:flex; flex-direction:column; gap:var(--gap,12px); }
.row{ display:flex; gap:var(--gap,12px); }
.frame{
  position:relative; margin:0; padding:0; border:0; background:var(--ink);
  cursor:pointer; overflow:hidden; border-radius:2px;
  animation:bl-fade .4s ease both;
  transition:transform .35s var(--ease);
}
.frame:hover,.frame:focus-visible{ transform:scale(1.035); z-index:3; }
.frame img{
  width:100%; height:100%; object-fit:cover; display:block; filter:brightness(.86);
  transition:filter .3s ease;
}
.frame:hover img,.frame:focus-visible img{ filter:brightness(1.06); }
/* Measured as low as 1.14:1 over a bright frame — a text-shadow earns no
   contrast credit. Same treatment as the tile numbers. */
.frame-num{
  position:absolute; top:var(--num-top,9px); left:var(--num-left,11px); z-index:2;
  font-family:var(--mono); font-size:var(--micro); letter-spacing:.16em;
  color:var(--paper); line-height:1;
  background:rgba(14,13,12,.68); backdrop-filter:blur(4px);
  border-radius:3px; padding:3px 7px;
}

/* ---------- viewer filmstrip ---------- */
.viewer-strip{
  display:flex; gap:8px; align-items:center;
  /* safe centring: centred while the strip fits, falling back to start when it
     overflows, since a centred overflowing flex row makes its first items
     unreachable */
  justify-content:center;
  justify-content:safe center;
  overflow-x:auto; overscroll-behavior-x:contain;
  padding:10px 2px 4px;
  --thumb:clamp(68px,6.6vw,164px);
  max-height:clamp(110px,12vw,220px); opacity:1;
  transition:max-height .3s var(--ease),opacity .25s ease,padding .3s var(--ease);
  scrollbar-width:thin; scrollbar-color:var(--hair) transparent;
}
.viewer-strip::-webkit-scrollbar{ height:6px; }
.viewer-strip::-webkit-scrollbar-thumb{ background:var(--hair); border-radius:99px; }

.strip-thumb{
  position:relative; flex:0 0 auto; margin:0; padding:0; border:0; cursor:pointer;
  width:var(--thumb); aspect-ratio:8 / 5;
  border-radius:2px; overflow:hidden; background:var(--ink);
  outline:1px solid transparent; outline-offset:0;
  transition:outline-color .2s ease,opacity .2s ease,width .25s var(--ease);
  opacity:.5;
}
.strip-thumb img{ width:100%; height:100%; object-fit:cover; display:block; }
.strip-thumb:hover{ opacity:.85; }
.strip-thumb.is-on{ opacity:1; outline-color:var(--tungsten); width:calc(var(--thumb) * 1.3); }
/* .strip-thumb sets a transparent outline for the is-on state, which lands
   after the global :focus-visible rule and silently wins on source order. */
.strip-thumb:focus-visible{ outline:2px solid var(--tungsten); outline-offset:2px; opacity:1; }
.strip-play{
  position:absolute; inset:0; display:grid; place-items:center;
  background:rgba(12,11,10,.35);
}
.strip-play::before{
  content:""; width:0; height:0; margin-left:2px;
  border-left:11px solid var(--paper);
  border-top:7px solid transparent; border-bottom:7px solid transparent;
}

/* lowers the strip out of the way, centred under the image */
.strip-toggle{
  justify-self:center; margin:0 0 -4px; padding:9px 24px;
  cursor:pointer; border:1px solid var(--hair); border-bottom:0;
  border-radius:3px 3px 0 0; background:rgba(20,19,18,.75);
  backdrop-filter:blur(4px);
  transition:background .2s ease,border-color .2s ease;
}
.strip-toggle:hover{ background:var(--raise); border-color:var(--muted); }
.strip-toggle span{
  display:block; width:9px; height:9px;
  border-right:1.5px solid var(--paper); border-bottom:1.5px solid var(--paper);
  transform:translateY(-2px) rotate(45deg);
  transition:transform .3s var(--ease);
}
.viewer.strip-down .strip-toggle span{ transform:translateY(1px) rotate(-135deg); }
.viewer.strip-down .viewer-strip{
  max-height:0; opacity:0; padding-top:0; padding-bottom:0;
}


/* ============================================================
   PANEL DECK — the home page only (body.is-deck)

   Three widgets, each sized to its own content, scrolled one at
   a time. Everything above this block still describes the sub
   pages, which keep the ordinary document layout; these rules
   only re-frame the home page's sections inside it.
   ============================================================ */

body.is-deck{ overflow:hidden; }
body.is-deck.is-locked{ overflow:hidden; }

:root{ --radius:18px; }

/* The ambient wash: the hero, pushed far enough out of focus to read as
   light rather than as a picture. */
.plate{
  position:fixed; inset:0; z-index:0;
  background-size:cover; background-position:center;
  filter:saturate(.25) blur(44px); transform:scale(1.15);
}
.plate-veil{
  position:fixed; inset:0; z-index:1;
  background:linear-gradient(to bottom,rgba(14,13,12,.74),rgba(14,13,12,.58));
}

.deck{
  position:relative; z-index:2;
  height:100dvh; overflow-y:auto; scroll-behavior:smooth;
  scroll-snap-type:y proximity;
  /* Almost nothing above, so the header sits in the frame from the first
     paint rather than a fifth of the way down it; generous below, so the last
     panel can still centre itself instead of sticking to the bottom edge. */
  padding:10px clamp(14px,4vw,56px) 18dvh;
  display:flex; flex-direction:column; gap:clamp(20px,4vh,44px);
}

.panel{
  position:relative;
  scroll-snap-align:center; flex:0 0 auto;
  border-radius:var(--radius); border:1px solid var(--hair);
  background:rgba(14,13,12,.68); backdrop-filter:blur(20px);
  padding:38px clamp(22px,4vw,46px);
  display:flex; flex-direction:column; gap:22px;
  opacity:.42; transform:scale(.955);
  transition:opacity .5s ease, transform .5s var(--ease),
             border-color .5s ease, box-shadow .5s ease;
}
/* The glow is a stack: a tight accent line hugging the border, then a wide
   soft bloom that reads as light spilling off the frame rather than a ring.
   Both are box-shadows, so none of it costs layout. */
.panel.is-focus{
  opacity:1; transform:none;
  border-color:rgba(255,178,90,.46);
  box-shadow:
    0 0 0 1px rgba(255,178,90,.14),
    0 0 22px -4px rgba(255,178,90,.30),
    0 0 70px -10px rgba(255,178,90,.16),
    0 30px 90px rgba(0,0,0,.45);
}

/* ---------- header panel ---------- */

/* The header's bottom edge is the bottom of the screen now, so its step button
   comes inside the panel — hanging 20px below would put it off-screen. The
   padding is what keeps it off the spec strip's bottom border; without it the
   button lands inside the SOFTWARE cell. Both hold at every size, so the
   button can never overlap the strip. */
.panel--hero{ padding-bottom:78px; }
.panel--hero .step.down{ bottom:18px; }

/* The hero, on the live site's own settings: defocused 8px so it sets a mood
   without competing with the headline, desaturated to 25% so the forest greens
   sit inside the near-black and tungsten palette. Its own clipped layer, so the
   frame's rounded corners hold it and the step buttons can still hang outside. */
.hero-bg{
  position:absolute; inset:0; z-index:0;
  border-radius:inherit; overflow:hidden; pointer-events:none;
}
.hero-bg .plate-img{
  position:absolute; inset:0;
  background-size:cover; background-position:50% 42%;
  filter:saturate(.25) blur(4px);
  /* The creature stands in the left third of the render, which is where the
     headline goes. Mirrored, it lands in the open right-hand side and the copy
     sits over plain forest instead of over the subject. At 8px of blur the flip
     is not readable as one. The 1.06 hides the soft edge the blur leaves. */
  transform:scale(1.06) scaleX(-1);
}
/* Light at the top where the creature reads, deepening toward the spec strip,
   with a gentle left bias to hold the headline. */
.hero-bg .veil{
  position:absolute; inset:0;
  background:
    linear-gradient(to top,rgba(14,13,12,.94) 0%,rgba(14,13,12,.72) 26%,
                           rgba(14,13,12,.34) 58%,rgba(14,13,12,.10) 100%),
    linear-gradient(to right,rgba(14,13,12,.52) 0%,rgba(14,13,12,.22) 46%,
                             rgba(14,13,12,.04) 100%);
}
.hero-bg .glow{
  position:absolute; inset:0;
  background:radial-gradient(120% 80% at 12% 92%,rgba(255,178,90,.13),transparent 60%);
}
/* The same plate again, untouched — full saturation, no defocus — revealed
   only where the light lands. The mask is the light's own falloff, so the
   sharp image and the pool are the same shape; cursorlight.js writes the
   coordinates in this element's own space. */
/* Two elements, not one. A mask is applied in the element's own coordinate
   space BEFORE its transform, so putting the mirror and the mask on the same
   element reveals the image on the opposite side from the cursor. The outer
   box carries the mask and is never transformed; the inner one carries the
   image and the transform. */
.hero-bg .plate-sharp{
  position:absolute; inset:0; overflow:hidden;
  opacity:0; transition:opacity .35s ease;
  -webkit-mask-image:var(--reveal-mask,none);
          mask-image:var(--reveal-mask,none);
}
.hero-bg .plate-sharp-img{
  position:absolute; inset:0;
  background-size:cover; background-position:50% 42%;
  transform:scale(1.06) scaleX(-1);   /* matches the plate underneath exactly */
}
.hero-bg.is-revealing .plate-sharp{ opacity:1; }
/* Lift the copy above the plate without touching .step, which is positioned
   against the panel itself. */
.panel--hero > *:not(.hero-bg):not(.step){ position:relative; z-index:1; }

.panel--hero .eyebrow{ color:var(--tungsten); }
.panel--hero h1{
  margin:0; font-size:clamp(2.4rem,6vw,4.6rem); line-height:.94;
  font-variation-settings:"wdth" 122,"wght" 750; letter-spacing:-.02em; text-wrap:balance;
}
.panel--hero .hero-line{ margin:0; }
.panel--hero .spec{
  border:1px solid var(--hair); border-radius:calc(var(--radius) * .5);
  overflow:hidden; border-bottom:1px solid var(--hair);
}
/* Let a little of the plate through, so the strip reads as layered on it. */
.panel--hero .spec > div{ padding:16px 18px; background:rgba(19,18,17,.82); }

/* ---------- work panel ---------- */

/* This is the panel the site exists for, so it takes the screen. Both rows are
   sized off the viewport, which keeps the same proportions on a laptop and on a
   4K monitor. */
.panel--work .work-head{ margin:0; }
.panel--work .reels{
  margin:0; grid-template-columns:var(--reel-cols,repeat(2,minmax(0,1fr)));
  grid-auto-rows:clamp(165px,36dvh,420px);
}
.panel--work .stills{
  grid-template-columns:var(--still-cols,repeat(4,minmax(0,1fr)));
  grid-auto-rows:clamp(150px,33dvh,390px);
  margin-top:14px;
}
.panel--work .tile{ border-radius:calc(var(--radius) * .5); }

/* ---------- about + contact panel ---------- */

.panel--about .split{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(26px,4vw,56px); }
.panel--about .col{ display:flex; flex-direction:column; gap:16px; }
.panel--about .col > h2{ margin:0; }
.panel--about .col > p{ margin:0; }
.panel--about .divider{ height:1px; background:var(--hair); }
.panel--about .mailto{ align-self:flex-start; }
.panel--about .contact-form{ margin:0; }
.panel--about .contact-form textarea{ min-height:0; }
.panel-foot{
  margin:0; padding-top:16px; border-top:1px solid var(--hair);
  display:flex; flex-wrap:wrap; gap:10px; justify-content:space-between;
  font-family:var(--mono); font-size:var(--micro); letter-spacing:.12em; color:var(--muted);
}

/* ---------- moving between panels ---------- */

/* Step buttons straddle the panel's own edge, centred — they belong to the
   widget you are reading, not to the window, so they travel with it. */
/* opacity alone left these focusable and clickable while invisible: tabbing
   through the page stopped on a control nobody could see, on a panel nobody
   was reading. visibility:hidden takes them out of the tab order, and it
   still animates because it is a discrete-but-transitionable property. */
.step{
  visibility:hidden;
  position:absolute; left:50%; translate:-50% 0; z-index:3;
  width:40px; height:40px; padding:0; cursor:pointer;
  border-radius:50%; border:1px solid rgba(255,178,90,.42);
  background:rgba(14,13,12,.92); backdrop-filter:blur(10px);
  display:grid; place-items:center;
  opacity:0; pointer-events:none;
  transition:opacity .4s ease, visibility .4s ease,
             background .25s ease, border-color .25s ease;
  box-shadow:0 0 18px -5px rgba(255,178,90,.4);
}
.panel.is-focus .step{ visibility:visible; opacity:1; pointer-events:auto; }
.step.up{ top:-20px; }
.step.down{ bottom:-20px; }
.step:hover{ background:var(--tungsten); border-color:var(--tungsten); }
.step::before{
  content:""; width:9px; height:9px; display:block;
  border-left:1.6px solid var(--tungsten); border-top:1.6px solid var(--tungsten);
  transition:border-color .25s ease;
}
.step:hover::before{ border-color:var(--on-tungsten); }
.step.up::before{ rotate:45deg; margin-top:3px; }
.step.down::before{ rotate:225deg; margin-bottom:3px; }
/* Nothing above the first panel, nothing below the last. */
.panel:first-child .step.up,
.panel:last-child .step.down{ display:none; }

.rail{
  position:fixed; z-index:4; right:clamp(10px,2vw,22px); top:50%; translate:0 -50%;
  display:flex; flex-direction:column; gap:9px;
}
.rail button{
  width:3px; height:30px; padding:0; border:0; cursor:pointer; border-radius:99px;
  background:var(--hair); transition:background .35s ease, height .35s var(--ease);
}
.rail button[aria-current="true"]{ background:var(--tungsten); height:46px; }

@media (prefers-reduced-motion:reduce){
  .deck{ scroll-behavior:auto; }
  .panel{ transition:none; opacity:1; transform:none; }
}

@media (max-width:860px){
  .deck{ padding:10px clamp(14px,4vw,56px) 8dvh; scroll-snap-type:none; }
  .panel{ padding:28px 20px; opacity:1; transform:none; }
  .panel--hero .spec{ grid-template-columns:1fr 1fr; }
  /* Seven chips in a half-width cell is one chip per line. */
  .panel--hero .spec > div:nth-child(3){ grid-column:1 / -1; }
  .panel--work .reels{ grid-template-columns:minmax(0,1fr); grid-auto-rows:clamp(150px,24dvh,260px); }
  .panel--work .stills{ grid-template-columns:repeat(2,minmax(0,1fr)); grid-auto-rows:clamp(120px,19dvh,220px); }
  .panel--about .split{ grid-template-columns:1fr; }
  .rail{ display:none; }
}

/* The tiles carry their own aspect ratios in the document layout. Inside the
   work panel the grid rows set the height instead, so the ratio has to give
   way or a 16:9 tile overflows its row and lands on the row below. */
.panel--work .tile-reel{ aspect-ratio:auto; }
.panel--work .tile{ height:100%; }

/* The work panel is meant to be the tallest of the three, so the other two
   have to stay out of its way. The form is what makes this one grow. */
.panel--about .col{ gap:14px; }
/* The work panel has to stay the tallest of the three, and the reply promise
   under the button added a line to the column that sets this panel's height. */
.panel--about .contact-form textarea{ height:62px; }
.panel--about .contact-form{ gap:2px; }
.panel--about .form-send{ margin-top:16px; }
.panel--about .contact-form input,
.panel--about .contact-form textarea{ padding:9px 13px; }

/* Four tiles two-up on a phone leaves each caption about twenty characters
   wide, which wraps the title into the badge. One column below that. */
@media (max-width:680px){
  .panel--work .stills{
    grid-template-columns:minmax(0,1fr);
    grid-auto-rows:clamp(150px,26dvh,250px);
  }
}

/* ---------- tool marks, about panel only ---------- */

.sprite{ position:absolute; width:0; height:0; overflow:hidden; }

/* The marks are brand logos, so they keep their own shapes — but they are all
   drawn in currentColor at one size, which is what makes a row of seven
   different houses' logos read as one set rather than seven. */
.tags--tools li{ display:inline-flex; align-items:center; gap:7px; padding-left:8px; }
.tool-mark{
  width:15px; height:15px; flex:0 0 auto;
  fill:currentColor; color:var(--muted-lift);
  /* Optical sizing: these paths fill their viewBox to different extents, and
     a hair of extra room keeps the densest of them from crowding the text. */
  opacity:.92;
}

/* ---------- step buttons carry their destination ---------- */

/* One width for every button, set by the longest label, so they stay a matched
   pair top and bottom however far apart the names are in length. */
.step{
  width:var(--step-w,190px); height:40px;
  border-radius:99px;
  grid-auto-flow:column; align-content:center; justify-content:center; gap:9px;
}
.step-label{
  font-family:var(--mono); font-size:var(--micro); letter-spacing:.14em;
  color:var(--tungsten); white-space:nowrap; transition:color .25s ease;
}
.step:hover .step-label{ color:var(--on-tungsten); }

@media (max-width:520px){
  .step{ --step-w:150px; }
  .step-label{ font-size:10px; letter-spacing:.1em; }
}

/* The disciplines and tools are the part of the About panel a studio actually
   scans, so they are set larger than the chips elsewhere on the site rather
   than at the same footnote size. Scoped to this panel — the spec strip in the
   header keeps its tighter measure. */
.panel--about .tags{ gap:9px; margin-top:9px; }
/* The two groups were reading as one block at the larger size. There is room
   in this column for the separation. */
.panel--about .col > div + div{ margin-top:6px; }
.panel--about .tags li{
  font-size:15px; letter-spacing:.06em; padding:9px 15px; border-radius:3px;
  color:var(--paper-dim);
}
.panel--about .tags--tools li{ gap:9px; padding-left:11px; }
.panel--about .tool-mark{ width:18px; height:18px; }
.panel--about .label{ font-size:var(--mono-sm); }

/* ============================================================
   CURSOR LIGHT

   Both layers are painted by cursorlight.js, which owns the
   gradient — the rules here only place them and set how they
   mix. Neither exists until that script runs.
   ============================================================ */

.cursor-light{
  position:fixed; inset:0; pointer-events:none;
  opacity:1; transition:opacity .45s ease;
  /* The gradient moves through custom properties, so the compositor can
     repaint it without the main thread rebuilding anything. */
  will-change:background-position;
}
body.light-out .cursor-light{ opacity:0; }

/* Both passes sit above the deck, so the light falls on the widgets rather
   than behind them.

   The beam is the light itself: plus-lighter adds its energy to whatever is
   underneath, which is what a light actually does — it can only ever brighten,
   never tint something darker.

   The bloom underneath it is wider and much weaker, on screen, and does the
   job the old behind-the-deck pass did: it keeps the pool from ending at a
   hard edge. */
.cursor-light--bloom{ z-index:299; mix-blend-mode:screen; }
.cursor-light--beam{ z-index:300; mix-blend-mode:plus-lighter; }

/* ---------- what you click, lights up ---------- */

/* A ring that leaves the edge and fades, in the light's own colour.

   It is drawn with outline rather than a pseudo-element: ::before and ::after
   are already spoken for on half the things you can click here — the play
   triangle on the primary button, the chevron on a step button — and outline
   follows an element's border-radius on its own, so a pill rings as a pill and
   a tile rings as a rounded rectangle without being told which it is.

   Spread is quoted at the reference width and scaled with --light-scale, so
   the ring leaves the edge by the same apparent distance on a laptop and on a
   4K monitor rather than looking tighter the larger the screen gets. */
@keyframes bl-ring{
  0%{
    outline-color:rgb(var(--light-rgb) / .95);
    outline-offset:0;
    box-shadow:0 0 calc(7px * var(--light-scale,1)) calc(-1px * var(--light-scale,1))
               rgb(var(--light-rgb) / .55);
  }
  100%{
    outline-color:rgb(var(--light-rgb) / 0);
    outline-offset:calc(7px * var(--light-scale,1));
    box-shadow:0 0 calc(14px * var(--light-scale,1)) calc(-2px * var(--light-scale,1))
               rgb(var(--light-rgb) / 0);
  }
}
/* Nothing here may set position. The step buttons, the viewer's close and
   arrows and the tiles' own overlays are all absolutely placed against a
   parent, and giving them position:relative on click drops them out of that
   placement — they jump the moment you press them. Neither outline nor
   box-shadow needs positioning to paint, so this sets none. */
.is-lit{
  outline:calc(1.5px * var(--light-scale,1)) solid transparent;
  animation:bl-ring .55s cubic-bezier(.2,.7,.3,1) forwards;
}

/* The tiles already scale on hover and are position:relative in their own
   right, so raising them here is safe and keeps the ring off the neighbour. */
.tile.is-lit{ z-index:5; }

@media (prefers-reduced-motion:reduce){
  .is-lit{ animation:none; }
}

/* ---------- resume ---------- */

.panel--about .resume{ margin:2px 0 0; }
.panel--about .resume .btn{
  display:inline-flex; align-items:center; gap:9px;
  font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.12em;
  padding:11px 18px; text-decoration:none;
}
.panel--about .resume svg{
  width:15px; height:15px; fill:currentColor; flex:0 0 auto; opacity:.85;
}

/* ---------- resume reader ----------

   The PDF itself, framed in the page rather than thrown at a new tab. The bar
   does not scroll away, because DOWNLOAD PDF is the reason most people opened
   this and it should never be somewhere else.
   ---------------------------------------------------------------- */

.sheet{
  position:fixed; inset:0; z-index:130; background:var(--viewer);
  display:grid; place-items:center;
  padding:clamp(10px,2.5vw,28px);
  animation:bl-fade .25s ease both;
}
.sheet[hidden]{ display:none; }

.sheet-panel{
  display:flex; flex-direction:column;
  width:min(940px,100%); height:min(1180px,100%);
  background:var(--bg-deep); border:1px solid var(--hair); border-radius:3px;
  overflow:hidden;
}

.sheet-bar{
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  flex-wrap:wrap; flex:0 0 auto;
  padding:11px 12px 11px clamp(14px,2vw,20px);
  border-bottom:1px solid var(--hair); background:var(--ink);
}
.sheet-title{
  margin:0; font-family:var(--mono); font-size:var(--mono-sm);
  letter-spacing:.14em; color:var(--muted);
}
.sheet-tools{ display:flex; align-items:center; gap:10px; margin-left:auto; }

/* With no page to show there is nothing to give the panel its height, so it
   takes the height of what it does have: a bar and a sentence. */
.sheet-panel.is-note{ height:auto; }
.sheet-panel.is-note .sheet-bar .sheet-get{ display:none; }

.sheet-get{
  display:inline-flex; align-items:center; gap:8px;
  font-family:var(--mono); font-size:var(--micro); letter-spacing:.12em;
  padding:9px 15px; border-radius:2px; text-decoration:none;
  border:1px solid var(--tungsten); background:var(--tungsten); color:var(--on-tungsten);
  transition:background .18s var(--ease),border-color .18s var(--ease);
}
.sheet-get:hover{
  background:var(--tungsten-hi); border-color:var(--tungsten-hi); color:var(--on-tungsten);
}
.sheet-get svg{ width:14px; height:14px; fill:currentColor; flex:0 0 auto; }

.sheet-close{
  width:34px; height:34px; padding:0; cursor:pointer; line-height:1;
  font:inherit; font-size:20px; color:var(--paper);
  background:rgba(42,39,36,.8); border:1px solid var(--hair); border-radius:2px;
  display:grid; place-items:center;
}
.sheet-close:hover{ background:var(--raise-hi); border-color:var(--muted); }

.sheet-doc{ flex:1 1 auto; min-height:0; display:grid; }
/* The viewer paints its own grey surround around the page, so the frame's bed
   is grey too -- a near-black gap that turns grey a moment later reads as a
   flash. Only when there is a frame: the fallback below wants the panel. */
.sheet-doc.is-framed{ background:#3A3A3A; }
.sheet-doc iframe{ width:100%; height:100%; border:0; }

/* Only reached where the browser has said it cannot draw a PDF inline. */
.sheet-note{
  place-self:center; text-align:center; padding:24px;
  display:grid; justify-items:center; gap:16px;
}
.sheet-note p{
  margin:0; max-width:34ch;
  font-family:var(--mono); font-size:var(--mono-sm); letter-spacing:.06em;
  color:var(--muted);
}

@media (max-width:620px){
  /* The name is the part a phone can spare; the word RESUME still labels the
     bar, which would otherwise be a close button on its own. */
  .sheet-title span{ display:none; }
  .sheet-bar{ padding:10px; gap:10px; }
}


/* ============================================================
   POLISH PASS — phones

   Everything below was measured on a 390x844 viewport, not
   guessed at: the numbers in the comments are what the browser
   actually reported before the fix.
   ============================================================ */

@media (max-width:620px){
  /* The gallery topbar had no wrap rule at all, so at 390px the nav had ~206px
     for three tracked mono links and "← BACK TO WORK" broke into a three-line
     column beside ABOUT and CONTACT — the nav read
     "← BACK  ABOUT  CONTACT / TO / WORK". This is the exit control on every
     gallery page. */
  .topbar{ gap:10px; padding:11px var(--gutter); }
  .mark-role{ display:none; }
  .topnav{ gap:14px; font-size:11px; letter-spacing:.08em; }
  .topnav a{ white-space:nowrap; }

  /* Touch targets. Measured before: nav links 25-26px, chips 31px,
     social 26px, mailto 32px — all under the 44px minimum, and the chips and
     social links sit on the panel where the contact actually happens. */
  /* A short label like ABOUT is only 38px wide, so the padding has to go
     sideways as well as up and down to clear the minimum. */
  .topnav a{ padding:13px 7px 12px; border-bottom-width:2px; }
  .mark{ padding:14px 0; }
  .chip{ padding:14px 16px; }
  .social a{ display:inline-block; padding:12px 0; }
  .contact-form button{ padding:15px 24px; }
  .mailto{ padding-top:8px; padding-bottom:12px; }
  .foot a{ display:inline-block; padding:11px 0; }
  /* The step buttons are the only navigation on the phone home page, and they
     were 4px under the minimum. */
  .step{ height:46px; }
}

/* A link can never wrap mid-phrase, at any width. */
.topnav a{ white-space:nowrap; }

@media (max-width:860px){
  /* The reel row is forced to a fixed height on phones while .tile-slate still
     reserved 78px of top padding for a headline that then wrapped to two
     lines: the title was painted 34px across the play button on "Feature
     Animation Reel" and 13px on "Arch Viz Reel". The stills row was already
     given this treatment; the reel row was not. */
  .tile-reel .tile-slate{ padding:44px 14px 14px; }
  .tile-reel .tile-title{ font-size:16px; line-height:1.15; }
  .tile-reel .tile-meta{ font-size:var(--micro); margin-top:3px; }
  .play{ top:38%; }
}

/* The poster holds the frame while an embedded player connects. Contained so a
   16:9 poster behind a 16:9 iframe lines up exactly rather than bleeding past
   it, and dimmed so it reads as "loading", not as the video itself. */
.viewer-stage{ background-repeat:no-repeat; background-position:center; background-size:contain; }
.viewer-stage.is-waiting::after{
  content:""; position:absolute; inset:0; margin:auto;
  width:min(100%, calc(100cqh * 16 / 9)); aspect-ratio:16/9; height:fit-content;
  background:rgba(11,10,10,.55);
  animation:bl-hold 1.6s ease-in-out infinite;
  pointer-events:none;
}
@keyframes bl-hold{ 0%,100%{ opacity:.55 } 50%{ opacity:.3 } }
@media (prefers-reduced-motion:reduce){
  .viewer-stage.is-waiting::after{ animation:none; }
}

/* thanks.html had no min-height, so the footer landed mid-page with ~400px of
   dead black beneath it — the last thing a visitor sees after writing to you. */
body:has(> footer.foot){ min-height:100dvh; display:flex; flex-direction:column; }
body:has(> footer.foot) > main{ flex:1 0 auto; }

/* The reply promise now sits with the button that needs it, not on the page
   after — beside it where there is room, under it where there is not, so it
   reads as part of the same action either way and costs the panel one line
   instead of three. */
.form-send{ display:flex; flex-wrap:wrap; align-items:center; gap:14px 18px; margin-top:20px; }
.form-send button{ margin-top:0; }
.form-note{
  margin:0; flex:1 1 15ch; min-width:0;
  font-family:var(--mono); font-size:var(--micro);
  letter-spacing:.02em; line-height:1.6; color:var(--muted);
}

/* A real note is a sentence, not a category tag — it drops the tracking and
   takes the brighter ink so it reads as writing rather than as a label. */
.viewer-slate--mono #viewerCaption.has-note{
  letter-spacing:.01em; color:var(--paper-dim); text-transform:none;
  max-width:70ch;
}

/* The header fills the frame, so nothing of the work panel shows until you
   move and the gallery arrives as a reveal rather than as something already
   half-visible under the fold.

   Only where the frame can actually hold it. Below this the header's own
   content is already taller than the window, so min-height would do nothing
   and margin-top:auto would push the spec strip further out of reach. */
@media (min-height:720px){
  .panel--hero{ min-height:calc(100dvh - 20px); }
  /* Panel taller than its content: the spec strip anchors the bottom edge
     rather than everything stacking at the top over a pool of empty black. */
  .panel--hero .spec{ margin-top:auto; }
}

/* The narrow breakpoint sets padding with the shorthand, which resets the
   bottom inset the step button needs. */
@media (max-width:860px){
  .panel--hero{ padding:28px 20px 78px; }
}

/* A laptop window with the usual chrome lands around 640px of viewport, where
   the header was overshooting by a dozen pixels and putting its own step button
   just past the fold. Tightening the panel's own rhythm — not the type — brings
   it back inside. */
@media (max-height:719px) and (min-width:861px){
  .panel--hero{ padding-top:24px; padding-bottom:64px; gap:16px; }
  .panel--hero .step.down{ bottom:12px; }
  .panel--hero .spec > div{ padding:12px 18px; }
}

/* ============================================================
   HEADER: COPY AT THE BOTTOM, AND THE REVEAL

   Timings are the ones set in the reveal tuner on 15 Sep 2026,
   not defaults to drift from. They are deliberately quick — the
   whole sequence is about a second, and slowing any one of them
   makes the gallery feel withheld rather than revealed.
   ============================================================ */

:root{
  --reveal:350ms;   /* the plate coming clear */
  --clear:260ms;    /* the copy leaving */
  --hold:320ms;     /* the beat on the clear frame */
  --move:375ms;     /* the travel to the gallery — read by panels.js */
}

/* Everything in the header sits at the end of the box, so the copy reads
   bottom-left against a full frame rather than floating at the top of it. */
.panel--hero{ justify-content:flex-end; }
/* The spec strip used to be pushed down on its own; now the whole stack is
   already there and pushing again would open a gap above it. */
.panel--hero .spec{ margin-top:22px; }

.hero-copy{
  display:flex; flex-direction:column; gap:18px;
  /* Short measure against a wide plate: the picture gets the rest. */
  max-width:min(56ch,62%);
  transition:opacity var(--clear) ease, transform var(--clear) var(--ease),
             filter var(--clear) ease;
}

/* ---------- the reveal ---------- */

.hero-bg .plate-img,
.hero-bg .veil,
.hero-bg .glow{
  transition:filter var(--reveal) var(--ease), transform var(--reveal) var(--ease),
             opacity var(--reveal) var(--ease);
}

/* SEE THE WORK clears the grade off the plate and lets the copy go, both at
   once — it is one gesture, not two. */
.panel--hero.is-revealing-all .plate-img{
  filter:saturate(1) blur(0px); transform:scale(1.01) scaleX(-1);
}
.panel--hero.is-revealing-all .veil,
.panel--hero.is-revealing-all .glow{ opacity:0; }
.panel--hero.is-revealing-all{ border-color:rgba(255,178,90,.7); }
.panel--hero.is-revealing-all .hero-copy,
.panel--hero.is-revealing-all .spec{
  opacity:0; transform:translate3d(0,14px,0); pointer-events:none;
}
.panel--hero.is-revealing-all .hero-copy{ filter:blur(3px); }
/* The cursor light's own sharp-plate reveal would fight a plate that is
   already sharp. */
.panel--hero.is-revealing-all .plate-sharp{ opacity:0; }

@media (prefers-reduced-motion:reduce){
  .hero-copy,
  .hero-bg .plate-img,.hero-bg .veil,.hero-bg .glow{ transition:none; }
}

@media (max-width:860px){
  .hero-copy{ max-width:100%; }
}
