/* ============================================================
   Max Danger Photography
   Tokens ported from the NovaLab design system (dark scheme).
   The site is dark-only: a photograph of the night sky has no
   light-mode equivalent, so there is one palette and no toggle.
   ============================================================ */

@font-face{font-family:Geist;font-weight:300;font-style:normal;font-display:swap;
  src:url('/fonts/geist-sans-300.woff2') format('woff2')}
@font-face{font-family:Geist;font-weight:400;font-style:normal;font-display:swap;
  src:url('/fonts/geist-sans-400.woff2') format('woff2')}
@font-face{font-family:Geist;font-weight:500;font-style:normal;font-display:swap;
  src:url('/fonts/geist-sans-500.woff2') format('woff2')}
@font-face{font-family:Geist;font-weight:600;font-style:normal;font-display:swap;
  src:url('/fonts/geist-sans-600.woff2') format('woff2')}
@font-face{font-family:'Geist Mono';font-weight:400;font-style:normal;font-display:swap;
  src:url('/fonts/geist-mono-400.woff2') format('woff2')}
@font-face{font-family:'Geist Mono';font-weight:500;font-style:normal;font-display:swap;
  src:url('/fonts/geist-mono-500.woff2') format('woff2')}

:root{
  --font-sans:Geist,system-ui,-apple-system,'Segoe UI',sans-serif;
  --font-mono:'Geist Mono',ui-monospace,'SF Mono',Menlo,monospace;

  --bg:#141414;            /* surface base   — 8% greyscale  */
  --raised:#1F1F1F;        /* cards, sheets  — 12%           */
  --sunken:#1A1A1A;        /* wells, tiles   — 10%           */
  --fg:#F4F3EF;            /* primary text                   */
  --fg-2:#B8B7B2;          /* body copy                      */
  --fg-3:#7A7A7E;          /* eyebrows, captions, glyphs     */
  --line:rgba(255,255,255,.10);
  --line-soft:rgba(255,255,255,.06);
  --accent:#9CABC8;
  --accent-soft:rgba(156,171,200,.18);
  --on-accent:#0A0A0B;

  --radius-card:16px;
  --radius-control:12px;
  --hairline:.5px;
  --control-h:48px;

  --wrap:1280px;
  --pad:40px;
  --nav-h:84px;
  --gap:8px;

  /* The monogram has two sizes. At rest it is larger than the bar and overhangs
     it, which reads as a mark laid over the photograph. Once you are into the
     page the bar is chrome, so the mark tucks inside it and lines up with the
     words. `top` moves with the height: the compact centre sits on the same
     line as the nav links, the resting one deliberately does not. */
  --mark-h:83px;      --mark-top:10px;
  --mark-h-min:56px;  --mark-top-min:7px;

  --ease:cubic-bezier(.2,0,0,1);
  --dur-fast:120ms; --dur-base:200ms; --dur-slow:320ms;
}

*,*::before,*::after{box-sizing:border-box}
html{background:var(--bg);-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--fg);
  font:400 16.5px/1.6 var(--font-sans);
  -webkit-font-smoothing:antialiased;
}
img{max-width:100%;display:block;user-select:none;-webkit-user-drag:none}
a{color:var(--fg);text-decoration:none}
::selection{background:var(--accent);color:var(--on-accent)}
svg{display:block}

.skip{position:absolute;left:-9999px;top:0;background:var(--fg);color:var(--on-accent);
  padding:12px 18px;z-index:200}
.skip:focus{left:8px;top:8px}

/* ---- type roles (NovaType parity) ---- */
.eyebrow{font:500 11px/1.4 var(--font-mono);letter-spacing:1.6px;text-transform:uppercase;
  color:var(--fg-3);margin:0}
.caption{font:500 9px/1.4 var(--font-mono);letter-spacing:1.4px;text-transform:uppercase;
  color:var(--fg-3);margin:0}

/* ============================================================
   navigation — fixed, over the photograph, no bar of its own
   ============================================================ */
.nav{
  position:fixed;inset:0 0 auto 0;z-index:50;
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  height:var(--nav-h);padding:0 var(--pad) 14px;
  background:linear-gradient(to bottom,rgba(10,10,11,.72),rgba(10,10,11,.38) 55%,transparent);
  pointer-events:none;
}
.nav>*{pointer-events:auto}
.nav-toggle{
  display:none;align-items:center;justify-content:center;
  width:34px;height:34px;padding:0;margin-left:-6px;
  background:none;border:0;cursor:pointer;color:var(--fg-2);
  transition:color var(--dur-base) var(--ease);
}
.nav-toggle:hover{color:var(--fg)}
.nav-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}
.nav-toggle .i-shut{display:none}
.nav.is-open .nav-toggle{color:var(--fg)}
.nav.is-open .nav-toggle .i-open{display:none}
.nav.is-open .nav-toggle .i-shut{display:block}
.nav-links{display:flex;align-items:center;gap:28px}
/* Same idiom as the chips and the hero actions: a rule that arrives under the
   word rather than a fill behind it. Idle colour stays one step brighter than
   theirs — the bar sits over a photograph, where #7A7A7E disappears. */
.nav-links a{
  font:500 11px/1 var(--font-mono);letter-spacing:2px;text-transform:uppercase;
  color:var(--fg-2);border-bottom:1px solid transparent;padding-bottom:6px;
  transition:color var(--dur-base) var(--ease),border-color var(--dur-base) var(--ease);
}
.nav-links a:hover{color:var(--fg);border-bottom-color:rgba(244,243,239,.5)}
.nav-links a.on{color:var(--fg);border-bottom-color:var(--fg)}
/* The monogram, not the full signature: at this size the signature is a thin
   scrawl, and the mark reads at a glance. Width follows its own 1240:996 box,
   so the height is what is fixed and the mark keeps its shape as it collapses. */
.nav-mark{
  position:absolute;left:50%;top:var(--mark-top);transform:translateX(-50%);
  display:block;height:var(--mark-h);aspect-ratio:1240/996;
  background:var(--fg);
  -webkit-mask:url('/assets/monogram.svg') center/contain no-repeat;
          mask:url('/assets/monogram.svg') center/contain no-repeat;
  transition:opacity var(--dur-base) var(--ease),
             height var(--dur-slow) var(--ease),
             top var(--dur-slow) var(--ease);
}
.nav-mark:hover{opacity:.82}
/* Set by site.js on the way down the page, cleared on the way back up. */
.nav.is-compact{--mark-h:var(--mark-h-min);--mark-top:var(--mark-top-min)}
.nav-social{display:flex;align-items:center;gap:20px}
.nav-social a{display:flex;color:var(--fg-3);transition:color var(--dur-base) var(--ease)}
.nav-social a:hover{color:var(--fg)}
.nav-social svg{width:18px;height:18px}

/* ============================================================
   page furniture
   ============================================================ */
.wrap{max-width:var(--wrap);margin:0 auto;padding:0 var(--pad)}
.page{padding-top:140px}
.rule{border:0;border-top:var(--hairline) solid var(--line);margin:0}

/* The gap below the header belongs to the header, not to its last paragraph —
   a page whose title stands alone still needs it. */
.phead{max-width:56ch;margin:0 0 36px}
.phead h1{font:600 clamp(32px,4vw,48px)/1.1 var(--font-sans);letter-spacing:-.4px;
  margin:0 0 14px}
.phead h1:only-child{margin:0}
.phead p{color:var(--fg-2);margin:0;text-wrap:pretty}

.sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:20px;
  border-bottom:var(--hairline) solid var(--line);padding-bottom:14px;margin-bottom:20px}
.sec-head h2{font:500 11px/1.4 var(--font-mono);letter-spacing:1.6px;text-transform:uppercase;
  color:var(--fg-3);margin:0}
.sec-head a{font:500 11px/1.4 var(--font-mono);letter-spacing:1.6px;text-transform:uppercase;
  color:var(--fg-2);transition:color var(--dur-base) var(--ease)}
.sec-head a:hover{color:var(--fg)}

/* ---- actions ----
   Underlined text rather than a filled control. Over a photograph a solid pill
   is a second subject competing with the first; a rule under a word is not. */
.btn{
  display:inline-flex;align-items:center;gap:10px;
  font:500 12px/1 var(--font-mono);letter-spacing:2.6px;text-transform:uppercase;
  color:var(--fg-3);background:none;border:0;cursor:pointer;
  padding:0 0 8px;border-bottom:1px solid transparent;
  transition:color var(--dur-base) var(--ease),border-color var(--dur-base) var(--ease);
}
.btn:hover{color:var(--fg);border-bottom-color:rgba(244,243,239,.5)}
/* The one action on the screen that is meant to be taken. */
.btn.primary{color:var(--fg);border-bottom-color:rgba(244,243,239,.9)}
.btn.primary:hover{border-bottom-color:var(--fg)}
.btn:focus-visible{outline:2px solid var(--accent);outline-offset:4px}
.btn-arrow{display:inline-block;transition:transform var(--dur-base) var(--ease)}
.btn:hover .btn-arrow{transform:translateX(4px)}
.btn.back:hover .btn-arrow{transform:translateX(-4px)}

/* ---- chips (album filter) ----
   A row of words under one rule, the current album carrying its own. */
.chips{display:flex;flex-wrap:wrap;gap:28px;
  border-bottom:var(--hairline) solid var(--line);padding-bottom:20px;margin-bottom:24px}
.chip{
  font:500 11px/1 var(--font-mono);letter-spacing:2px;text-transform:uppercase;
  color:var(--fg-3);border-bottom:1px solid transparent;padding-bottom:6px;
  transition:color var(--dur-base) var(--ease),border-color var(--dur-base) var(--ease);
}
.chip:hover{color:var(--fg)}
.chip.on{color:var(--fg);border-bottom-color:var(--fg)}

/* ---- breadcrumb ---- */
.crumb{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin:0 0 24px;
  font:500 11px/1.4 var(--font-mono);letter-spacing:1.6px;text-transform:uppercase;
  color:var(--fg-3)}
.crumb a{color:var(--fg-2);transition:color var(--dur-base) var(--ease)}
.crumb a:hover{color:var(--fg)}
.crumb b{font-weight:500;color:var(--fg)}

/* ============================================================
   grid — justified rows, at most three across

   Positions are solved at build time and emitted as percentages, so there is
   no layout JavaScript and nothing reflows after paint. The container's height
   comes from padding-top, which resolves against its own width; tile top and
   height are percentages of that height, already rebased by the packer.
   ============================================================ */
.grid{position:relative;width:100%;height:0;padding-top:var(--pt3)}
.tile{
  position:absolute;
  left:var(--l3);top:var(--t3);width:var(--w3);height:var(--h3);
  overflow:hidden;background:var(--sunken);
  background-size:cover;background-position:center;
  box-shadow:inset 0 0 0 var(--hairline) var(--line-soft);
}
.tile img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform var(--dur-slow) var(--ease)}
.tile:hover img,.tile:focus-visible img{transform:scale(1.03)}
.tile:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.tile-cap{
  position:absolute;inset:auto 0 0 0;z-index:2;padding:32px 12px 10px;
  background:linear-gradient(to top,rgba(10,10,11,.78),transparent);
  opacity:0;transition:opacity var(--dur-base) var(--ease);pointer-events:none;
}
.tile-cap span{font:500 9px/1.4 var(--font-mono);letter-spacing:1.4px;
  text-transform:uppercase;color:var(--fg)}
.tile:hover .tile-cap,.tile:focus-visible .tile-cap{opacity:1}

@media (max-width:1100px){
  .grid{padding-top:var(--pt2)}
  .tile{left:var(--l2);top:var(--t2);width:var(--w2);height:var(--h2)}
}
@media (max-width:700px){
  .grid{padding-top:var(--pt1)}
  .tile{left:var(--l1);top:var(--t1);width:var(--w1);height:var(--h1)}
}

/* ============================================================
   footer
   ============================================================ */
.foot{max-width:var(--wrap);margin:0 auto;padding:96px var(--pad) 44px}
.foot-in{display:flex;justify-content:space-between;align-items:center;gap:20px;
  flex-wrap:wrap;border-top:var(--hairline) solid var(--line);padding-top:22px}
.foot p{margin:0;font:500 9px/1.6 var(--font-mono);letter-spacing:1.4px;
  text-transform:uppercase;color:var(--fg-3)}
.foot-links{display:flex;gap:22px}
.foot-links a{font:500 9px/1.6 var(--font-mono);letter-spacing:1.4px;
  text-transform:uppercase;color:var(--fg-3);transition:color var(--dur-base) var(--ease)}
.foot-links a:hover{color:var(--fg-2)}

/* ============================================================
   lightbox
   ============================================================ */
.lb{
  position:fixed;inset:0;z-index:100;display:grid;opacity:1;
  /* minmax(0,1fr) not 1fr: a bare 1fr has a min-content floor and will not
     shrink, which lets a tall image push the caption off-screen. */
  grid-template-rows:minmax(0,1fr) auto;
  /* Opaque, not 97%. That last 3% was still enough to put a bright tile on
     screen at 19 against a ground of 12 — faint, but shapes, and the reason
     the album could be made out behind the photograph at all. The blur was
     doing nothing at that opacity except costing a filter pass. */
  background:#0C0C0C;
}
.lb[hidden]{display:none}
.lb.is-closing{opacity:0}
.lb-fig{margin:0;display:contents}
.lb-imgwrap{position:relative;min-height:0;overflow:hidden;z-index:3;
  --pad-t:64px;--pad-x:88px;--pad-b:20px}
/* Magnified, the photograph is the only thing worth looking at: it leaves the
   grid, covers the viewport, and sits above the caption in the stack. The text
   fades rather than being clipped, so nothing appears cut off. */
.lb.is-magnified .lb-imgwrap{position:fixed;inset:0;z-index:4;
  --pad-t:0px;--pad-x:0px;--pad-b:0px}
.lb.is-magnified .lb-cap,.lb.is-magnified .lb-count{opacity:0;pointer-events:none}
/* Definite lengths, not percentages: a percentage max-height resolves against
   the image's own height and portraits then overflow onto the caption.
   Absolute insets give a box of known size; object-fit contain letterboxes
   inside it — never cropped, whatever the aspect ratio. */
.lb-imgwrap picture{display:block;position:absolute;
  top:var(--pad-t);right:var(--pad-x);bottom:var(--pad-b);left:var(--pad-x)}
.lb-img{
  display:block;width:100%;height:100%;
  object-fit:contain;object-position:center;transform-origin:center center;
  cursor:zoom-in;touch-action:none;-webkit-user-select:none;user-select:none;
}
/* A copy of whatever is on its way out — the photograph and the caption alike —
   held over the spot it occupied and faded. Only these ever change opacity; the
   backdrop behind them is opaque and static, so a cross-fade cannot put the
   gallery on screen the way a whole-page one did. */
.lb-ghost{position:fixed;z-index:5;pointer-events:none;
  opacity:1;transition:opacity var(--dur-slow) var(--ease)}
.lb-ghost.out{opacity:0}
/* A clone is a fresh element, so entrance animations would replay on the copy
   that is meant to be leaving. */
.lb-ghost,.lb-ghost *{animation:none !important}
.lb-img.is-zoomed{cursor:grab}
.lb-img.is-dragging{cursor:grabbing}
.lb-cap{
  position:relative;z-index:2;text-align:center;
  width:100%;max-width:900px;margin:0 auto;padding:18px 88px 30px;
  border-top:var(--hairline) solid rgba(255,255,255,.07);
  max-height:38vh;overflow-y:auto;overscroll-behavior:contain;
}
.lb-cap h2{font:500 22px/1.25 var(--font-sans);letter-spacing:-.3px;color:var(--fg);
  margin:0 0 6px}
.lb-d{color:var(--fg-2);font-size:15px;line-height:1.6;margin:0 0 10px;
  max-width:62ch;margin-inline:auto;text-wrap:pretty}
.lb-d:empty{display:none}
.lb-m{font:500 11px/1.4 var(--font-mono);letter-spacing:1.6px;text-transform:uppercase;
  color:var(--fg-3);margin:0}
.lb-c{font:500 9px/1.6 var(--font-mono);letter-spacing:1.4px;text-transform:uppercase;
  color:var(--fg-3);margin:8px 0 0;opacity:.7}
.lb-count{position:absolute;top:24px;left:28px;z-index:6;
  font:500 11px/1 var(--font-mono);letter-spacing:1.6px;color:var(--fg-3);
  font-variant-numeric:tabular-nums}
.lb-zoom{position:absolute;top:24px;left:50%;transform:translateX(-50%);z-index:6;
  font:500 11px/1 var(--font-mono);letter-spacing:1.6px;color:var(--fg-3);
  opacity:0;transition:opacity var(--dur-base) var(--ease);pointer-events:none}
.lb-zoom.on{opacity:1}
.lb-x,.lb-p,.lb-n{
  position:absolute;z-index:6;display:flex;align-items:center;justify-content:center;
  border:0;cursor:pointer;color:var(--fg-2);
  box-shadow:inset 0 0 0 var(--hairline) var(--line);
  transition:color var(--dur-base) var(--ease),background var(--dur-base) var(--ease);
}
.lb-x{top:18px;right:22px;width:32px;height:32px;border-radius:50%;background:var(--sunken)}
.lb-p,.lb-n{top:50%;margin-top:-20px;width:40px;height:40px;border-radius:50%;
  background:rgba(31,31,31,.85);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.lb-p{left:20px} .lb-n{right:20px}
.lb-x:hover,.lb-p:hover,.lb-n:hover{color:var(--fg);background:#242424}
.lb-x:focus-visible,.lb-p:focus-visible,.lb-n:focus-visible{
  outline:2px solid var(--accent);outline-offset:2px;color:var(--fg)}
.lb-x svg{width:14px;height:14px}
.lb-p svg,.lb-n svg{width:18px;height:18px}

/* ============================================================
   motion
   ============================================================ */
@media (prefers-reduced-motion:no-preference){
  .reveal{opacity:0;transform:translateY(12px)}
  .reveal.in{opacity:1;transform:none;
    transition:opacity .5s var(--ease) var(--d,0ms),transform .5s var(--ease) var(--d,0ms)}
  .fade-up{animation:fadeUp .48s var(--ease) both}
  .fade-in{animation:fadeIn .32s var(--ease) both}
  .lb{animation:fadeIn var(--dur-base) var(--ease);transition:opacity var(--dur-slow) var(--ease)}
  .lb-cap,.lb-count{transition:opacity var(--dur-slow) var(--ease)}
  /* opacity only: transform belongs to the zoom engine, and an animation on the
     same property would override the inline transform. */
  .lb-img{animation:fadeIn .4s var(--ease);transition:transform .28s var(--ease)}
  /* no easing while a gesture is in flight — it must track the fingers */
  .lb-img.is-live{transition:none}
  .lb-cap>*{animation:fadeUp .5s var(--ease) both}
  .lb-cap h2{animation-delay:.06s} .lb-d{animation-delay:.1s}
  .lb-m{animation-delay:.14s} .lb-c{animation-delay:.18s}
}
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important}
}

/* ============================================================
   responsive
   ============================================================ */
@media (max-width:900px){
  :root{--pad:22px;--nav-h:70px;
    --mark-h:64px;     --mark-top:6px;
    --mark-h-min:44px; --mark-top-min:6px}
  .nav-links{gap:20px}
  .nav-links a{font-size:10px;letter-spacing:1.4px}
  .nav-links{gap:18px}
  .nav-links a{font-size:10px;letter-spacing:1.2px}
  .page{padding-top:104px}
  .lb-imgwrap{--pad-t:56px;--pad-x:16px;--pad-b:12px}
  .lb-cap{padding:16px 22px 28px;max-height:42vh}
  .lb-cap h2{font-size:19px}
  .lb-p,.lb-n{width:34px;height:34px;margin-top:-17px}
  .lb-p{left:10px} .lb-n{right:10px}
}
/* Four words, a centred mark and two glyphs stop fitting on one line well
   before a phone's width. The words fold into a panel behind a button, which
   keeps the mark where it belongs — in the middle — and lets the social glyphs
   stay, rather than dropping them and hoping the footer is enough. */
@media (max-width:780px){
  :root{--nav-h:66px;--mark-h:54px;--mark-top:5px;--mark-h-min:40px;--mark-top-min:5px}
  .nav{padding-bottom:8px}
  .nav-toggle{display:flex}
  .nav-social{gap:16px}
  .page{padding-top:96px}

  /* The panel lives inside the bar rather than hanging beneath it.
     Two adjoining elements cannot share a blur: each samples only its own area
     and clamps at its edge, so the blurred picture does not line up across the
     join and a line appears there — which is what showed mid-tween. One box,
     one blur, no join. */
  .nav{
    height:auto;padding:0 var(--pad);
    flex-wrap:wrap;align-content:flex-start;
    /* The bar's 24px gap becomes a row gap once it wraps, which left the bar
       24px taller than it should be even with the panel collapsed. */
    gap:0 24px;
    /* Pin the bar's own gradient to the bar's height, or it stretches over the
       whole thing as the panel opens. */
    background-size:100% var(--nav-h);background-repeat:no-repeat;
  }
  .nav-toggle,.nav-social{height:var(--nav-h)}

  /* The open scrim: one layer over the whole nav — bar and panel together —
     fading away at whatever the bottom edge happens to be. It cross-fades in
     rather than swapping the background, because a gradient and a flat colour
     do not interpolate and the swap would land in one frame. */
  .nav::after{
    /* Reaches 34px past the bar so the fade happens below the last word rather
       than over it. Padding on the panel would do the same, except padding
       survives max-height:0 — the bar sat 34px too tall while shut. */
    content:"";position:absolute;inset:0 0 -34px;pointer-events:none;
    /* Behind the bar's own contents but above its background. A pseudo-element
       paints after its siblings, so without this the scrim sits on top of the
       toggle and the social glyphs and dims them. */
    z-index:-1;
    background:rgba(10,10,11,.74);
    backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
    -webkit-mask:linear-gradient(to bottom,#000 calc(100% - 34px),transparent);
            mask:linear-gradient(to bottom,#000 calc(100% - 34px),transparent);
    opacity:0;transition:opacity var(--dur-slow) var(--ease);
  }
  .nav.is-open::after{opacity:1}
  .nav-links{
    order:3;flex:0 0 100%;
    flex-direction:column;align-items:stretch;gap:0;
    padding:0;
    /* Collapsed by height rather than display:none so it can animate, and
       taken out of the tab order while it is shut. The nav's height follows,
       so the scrim and its fade track the bottom edge for the whole tween.
       The open height is set in pixels by the script: a `max-height` big
       enough for any menu is reached a quarter of the way through the tween,
       and the remaining three quarters animate a number nothing can see. */
    max-height:0;overflow:hidden;visibility:hidden;
    transition:max-height var(--dur-slow) var(--ease),
               visibility 0s linear var(--dur-slow);
  }
  .nav.is-open .nav-links{
    visibility:visible;
    transition:max-height var(--dur-slow) var(--ease),visibility 0s;
  }
  .nav-links a{
    font-size:12px;letter-spacing:2px;
    padding:16px 0;border-bottom:0;
    border-top:var(--hairline) solid var(--line-soft);
  }
  .nav-links a:first-child{border-top:0}
  .nav-links a:hover{border-bottom-color:transparent}
  /* Underlining a full-width row reads as a rule, not as state — the current
     page is marked by a leading bar instead. */
  .nav-links a.on{border-bottom-color:transparent;padding-left:12px;
    box-shadow:inset 2px 0 0 var(--fg)}
}

/* ============================================================
   per-page furniture
   ============================================================ */

/* ---- home hero ---- */
.hero{position:relative;height:92vh;min-height:540px;overflow:hidden}
/* The stack fills the hero exactly, so what the crop says is what you land on.

   It used to sit 240px higher and stand 240px taller, because a parallax that
   lags — the image drifting down as the page goes up — has to have somewhere
   above to drift out of. The cost was that the window showed the bottom of the
   box: the top 240px of every frame was above the fold before a wheel was
   touched, and a photograph wider than the band has no vertical overflow for
   the crop to claw it back with. A panorama lost the top fifth of itself and
   the Down control could do nothing about it.

   The drift is a slow scale now instead. Growing only ever covers more, so it
   needs no headroom and cannot move the frame off its mark. */
.hero-slides{position:absolute;inset:0;will-change:transform;
  /* Feathered out at the foot rather than cut off. The shade above reaches 92%
     at the bottom edge, so 8% of the photograph was still on screen in the last
     row before the page began — a small step, but a straight horizontal one
     across the full width, which is exactly the kind the eye finds.
     Masking the picture rather than deepening the shade means nothing has to be
     colour-matched to the page: it fades to nothing and the page is simply what
     is behind it. */
  -webkit-mask:linear-gradient(to bottom,#000 76%,transparent 100%);
          mask:linear-gradient(to bottom,#000 76%,transparent 100%)}
/* Each frame arrives carrying two crops — one per hero shape — and the
   stylesheet picks the pair, because the breakpoint already lives here.
   Scaling about the focal point is what makes panning possible at all for a
   photograph already the shape of the band: at 1× there is no overflow to move
   into. The values below are only a floor for a frame that arrives without.

   `transform-origin` matches `object-position` deliberately. object-position
   puts the image's x%,y% point at the box's x%,y% point; scaling about that
   same point of the box therefore holds it still while everything grows
   around it. */
.hero-img{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;
  object-position:var(--fx,50%) var(--fy,55%);
  transform:scale(var(--fz,1));
  transform-origin:var(--fx,50%) var(--fy,55%);
  opacity:0;transition:opacity 1.2s var(--ease)}
/* The first frame is marked on at build time, so with JavaScript off — or one
   photograph chosen — the hero is a still image rather than a black box. */
.hero-img.on{opacity:1}
.hero-shade{position:absolute;inset:0;
  background:linear-gradient(to top,rgba(20,20,20,.92) 4%,rgba(20,20,20,.25) 45%,rgba(20,20,20,.45) 100%)}
.hero-in{position:relative;height:100%;max-width:var(--wrap);margin:0 auto;
  padding:0 var(--pad) 72px;display:flex;flex-direction:column;justify-content:flex-end;
  will-change:transform,opacity}

/* ---- hero drift, driven by the scroller itself ----
   The same motion the JavaScript fallback performs — the signature rising a
   seventh as fast as the page and fading out by 520px, the photograph growing
   7% over 700px — expressed as a scroll-driven animation.

   The difference is where it runs. A scroll listener updates a transform on the
   main thread while the page itself scrolls on the compositor, and on a phone
   those two are not in step: iOS coalesces scroll events during an inertial
   flick, so the signature arrives late and in jumps against a page moving
   smoothly. That is the stutter. A scroll timeline is evaluated by the
   compositor alongside the scroll, so the two cannot drift apart.

   `parallax()` in site.js stands down wherever this is supported, so the motion
   is defined twice but only ever runs once. */
@keyframes hero-drift{
  from{transform:translateY(0);opacity:1}
  to{transform:translateY(78px);opacity:0}
}
@keyframes hero-zoom{ from{transform:scale(1)} to{transform:scale(1.07)} }
@supports (animation-timeline: scroll()){
  @media (prefers-reduced-motion: no-preference){
    .hero-in{
      animation:hero-drift linear both;
      animation-timeline:scroll(root block);
      /* 520px of page scroll, matching `1 - y / 520` in the fallback. */
      animation-range:0 520px;
    }
    .hero-slides{
      animation:hero-zoom linear both;
      animation-timeline:scroll(root block);
      animation-range:0 700px;
    }
  }
}
/* The signature carries the whole lockup, wordmark included, so the heading is
   one piece of artwork rather than a mark with type set under it. */
.hero h1{margin:0 0 34px;width:clamp(280px,34vw,440px)}
/* Slightly transparent so the photograph reads through it rather than the
   lockup sitting on top as a separate object.

   The transparency is in the fill, not in `opacity`. An element with opacity
   below 1 is composited as a layer of its own, which a browser may rasterise
   once and then scale — so zooming enlarged a bitmap rather than redrawing the
   mask, and the softening that came with it read as the signature going more
   transparent the further in you went. A translucent colour is one paint with
   no layer, redrawn at whatever zoom is current. */
.hero-mark{display:block;width:100%;aspect-ratio:2823/1242;
  background:rgba(244,243,239,.8);
  background:color-mix(in srgb, var(--fg) 80%, transparent);
  -webkit-mask:url('/assets/signature.svg') center/contain no-repeat;
          mask:url('/assets/signature.svg') center/contain no-repeat}
/* Wider gaps than a pair of pills needed: with no fill around them the words
   run together at 12px, and the rules underneath merge into one. */
.hero-btns{display:flex;gap:40px;align-items:center;flex-wrap:wrap}

.strip{padding-top:88px}
.strip+.strip{padding-top:64px}

/* ---- journal ---- */
.jcards{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:var(--gap)}
.jcard{display:block;background:var(--raised);border-radius:var(--radius-card);overflow:hidden;
  box-shadow:inset 0 0 0 var(--hairline) var(--line);
  transition:background var(--dur-base) var(--ease),transform var(--dur-base) var(--ease)}
.jcard:hover{background:#242424;transform:translateY(-2px)}
.jcard-img{display:block;aspect-ratio:16/9;overflow:hidden;background:var(--sunken)}
.jcard-img img{width:100%;height:100%;object-fit:cover;
  transition:transform var(--dur-slow) var(--ease)}
.jcard:hover .jcard-img img{transform:scale(1.03)}
.jcard-body{display:block;padding:18px 20px 24px}
.jcard-meta{display:block;font:500 9px/1.4 var(--font-mono);letter-spacing:1.4px;
  text-transform:uppercase;color:var(--fg-3);margin-bottom:10px}
.jcard-t{display:block;font:500 20px/1.3 var(--font-sans);letter-spacing:-.3px;
  color:var(--fg);margin-bottom:10px;text-wrap:balance}
.jcard-x{display:block;font-size:13px;line-height:1.6;color:var(--fg-2);text-wrap:pretty}

/* ---- blog post ---- */
.post{max-width:1000px;margin:0 auto}
.post-head{max-width:62ch;padding-bottom:32px}
.post-meta{font-size:13px;color:var(--fg-3);margin:0 0 16px}
.post-head h1{font:600 clamp(32px,4vw,48px)/1.1 var(--font-sans);letter-spacing:-.4px;
  margin:0 0 16px;text-wrap:balance}
.post-lede{color:var(--fg-2);font-size:18px;line-height:1.6;margin:0;text-wrap:pretty}
.post-hero{margin:0 0 44px;border-radius:var(--radius-card);overflow:hidden}
.post-hero img{width:100%}
.post-hero figcaption{padding:12px 4px 0;text-align:center;
  font:500 9px/1.6 var(--font-mono);letter-spacing:1.4px;text-transform:uppercase;
  color:var(--fg-3)}
.prose{max-width:62ch;margin:0 auto}
.prose p{color:var(--fg-2);margin:0 0 22px;text-wrap:pretty}
.prose h2{font:600 24px/1.25 var(--font-sans);letter-spacing:-.3px;color:var(--fg);
  margin:44px 0 16px}
.prose a{color:var(--accent);border-bottom:1px solid var(--accent-soft)}
.prose a:hover{border-bottom-color:var(--accent)}
.prose blockquote{margin:32px 0;padding:2px 0 2px 22px;
  border-left:2px solid var(--accent);color:var(--fg);font-size:19px;line-height:1.5}
.prose code{font-family:var(--font-mono);font-size:13px;background:var(--raised);
  padding:2px 6px;border-radius:6px}
.post-foot{border-top:var(--hairline) solid var(--line);padding-top:26px;margin-top:44px}

/* ---- prev / next ---- */
.pagenav{display:flex;justify-content:space-between;gap:16px;
  border-top:var(--hairline) solid var(--line);padding-top:22px;margin-top:64px}
.pagenav a{display:flex;flex-direction:column;gap:6px;max-width:46%}
.pagenav a.r{text-align:right;margin-left:auto}
.pagenav span{font:500 9px/1.4 var(--font-mono);letter-spacing:1.4px;
  text-transform:uppercase;color:var(--fg-3)}
.pagenav b{font:500 17px/1.25 var(--font-sans);letter-spacing:-.2px;color:var(--fg-2);
  transition:color var(--dur-base) var(--ease)}
.pagenav a:hover b{color:var(--fg)}

/* ---- about ---- */
.two{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:72px;align-items:start}
.two .prose{margin:0}
.two .prose p{max-width:58ch}
.side{display:flex;flex-direction:column;gap:20px;position:sticky;top:110px}
.side .btn{align-self:flex-start}
.portrait{width:100%;border-radius:var(--radius-card);object-fit:cover}
.facts{margin:0;border-top:var(--hairline) solid var(--line)}
.fact{display:flex;justify-content:space-between;gap:16px;padding:12px 0;
  border-bottom:var(--hairline) solid var(--line-soft)}
.fact dt{font:500 11px/1.4 var(--font-mono);letter-spacing:1.6px;text-transform:uppercase;
  color:var(--fg-3)}
.fact dd{margin:0;font-size:13px;color:var(--fg-2);text-align:right}
.gear{list-style:none;padding:0;margin:0;border-top:var(--hairline) solid var(--line)}
.gear li{padding:14px 0;border-bottom:var(--hairline) solid var(--line-soft);
  font-size:14px;color:var(--fg-2)}
.gear strong{display:block;font:500 9px/1.4 var(--font-mono);letter-spacing:1.4px;
  text-transform:uppercase;color:var(--fg-3);font-weight:500;margin-bottom:7px}

/* ---- contact ---- */
.card{max-width:560px;background:var(--raised);border-radius:var(--radius-card);
  overflow:hidden;box-shadow:inset 0 0 0 var(--hairline) var(--line)}
.card-row{display:flex;align-items:center;gap:16px;padding:20px 22px;
  border-bottom:var(--hairline) solid var(--line-soft);
  transition:background var(--dur-base) var(--ease)}
.card-row:last-child{border-bottom:0}
.card-row:hover{background:#242424}
.card-row>svg{width:18px;height:18px;flex:0 0 auto;color:var(--fg-2)}
.card-row .ext{width:14px;height:14px;color:var(--fg-3)}
.card-txt{flex:1;display:flex;flex-direction:column;gap:3px;min-width:0}
.card-txt small{font:500 9px/1.4 var(--font-mono);letter-spacing:1.4px;
  text-transform:uppercase;color:var(--fg-3)}
.card-txt span{font-size:15px;color:var(--fg);overflow-wrap:anywhere}
.contact-note{font-size:13px;color:var(--fg-3);margin:32px 0 0}

/* ---- single photograph (no-JS target, and shareable) ---- */
.single{max-width:1200px;margin:0 auto}
.single-fig{margin:0 0 26px}
.single-fig img{width:100%;max-height:78vh;object-fit:contain}
.single-cap{text-align:center;max-width:62ch;margin:0 auto}
.single-cap h1{font:600 clamp(24px,3vw,34px)/1.2 var(--font-sans);letter-spacing:-.4px;
  margin:0 0 12px}
.single-d{color:var(--fg-2);font-size:16px;margin:0 0 16px;text-wrap:pretty}
.single-m{font:500 11px/1.4 var(--font-mono);letter-spacing:1.6px;text-transform:uppercase;
  color:var(--fg-3);margin:0}
.single-c{font:500 9px/1.6 var(--font-mono);letter-spacing:1.4px;text-transform:uppercase;
  color:var(--fg-3);margin:8px 0 0;opacity:.7}

/* ---- misc ---- */
.empty{color:var(--fg-3);font-size:15px;padding:20px 0}
.empty code{background:var(--raised);padding:3px 7px;border-radius:6px;
  font-family:var(--font-mono);font-size:13px}

/* Same breakpoint the hero changes height on, so the crop and the shape it was
   chosen for switch together. */
@media (max-width:900px){
  .hero-img{
    object-position:var(--mfx,50%) var(--mfy,55%);
    transform:scale(var(--mfz,1));
    transform-origin:var(--mfx,50%) var(--mfy,55%)}
  .two{grid-template-columns:1fr;gap:40px}
  .side{position:static}
  .strip{padding-top:56px}
  .hero{height:80vh;min-height:460px}
  .hero-in{padding-bottom:48px}
  .pagenav b{font-size:15px}
}
