/* ==========================================================================
   Elvision Photography — elvision.nl
   Editorial portfolio. Vanilla CSS, no build step.
   ========================================================================== */

/* ---------- Tokens ---------- */
:root{
  /* ---------------------------------------------------------------
     Design tokens. Everything tunable lives here and nowhere else.
     Open the site with ?design to adjust these on screen.
     Each measurement keeps a responsive floor; the slider drives the
     ceiling, so tuning for desktop cannot break small screens.
     --------------------------------------------------------------- */

  /* Colour */
  --ground:#0b0b0c;
  --ground-lift:#141416;
  --ink:#f2efe9;
  --ink-dim:#a09a91;
  --ink-faint:#6b655e;
  --accent:#b8583f;
  --line-alpha:.14;
  --line:rgba(242,239,233,var(--line-alpha));
  --rule:#e8141c;             /* the horizontal lines: header, heading, footer, folder */

  /* Type family */
  --display:"Bodoni Moda",Georgia,"Times New Roman",serif;
  --sans:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;

  /* Type scale — the max of each clamp is the knob */
  --base-max:17px;
  --display-max:9.5rem;
  --h2-max:4.4rem;
  --lede-max:1.35rem;
  --track:.34em;              /* letter-spacing on eyebrows and labels */

  --base:clamp(15px,1.05vw,var(--base-max));
  --display-size:clamp(3rem,11vw,var(--display-max));
  --h2-size:clamp(2rem,5.5vw,var(--h2-max));
  --lede-size:clamp(1.05rem,1.6vw,var(--lede-max));

  /* Spacing */
  --gutter-max:80px;
  --gap-max:42px;
  --rhythm-max:110px;
  --down-max:60px;

  --gutter:clamp(20px,5vw,var(--gutter-max));
  --gap:clamp(22px,2.2vw,var(--gap-max));
  --rhythm:clamp(80px,14vh,var(--rhythm-max));
  --down:clamp(28px,7vw,var(--down-max));

  /* Hero */
  --hero-h:100svh;            /* full screen by default            */
  --hero-veil:.35;            /* darkening over the photo, 0 to 1.4 */
  --hero-vpos:flex-end;       /* text sits at the bottom           */
  --hero-hpos:flex-start;     /* ...on the left                    */
  --hero-align:left;
  --hero-width:900px;

  /* Frames — moulding on the outside, mat board inside, then the print */
  --frame-w:clamp(9px,1.1vw,16px);   /* the moulding            */
  --frame-col:#2c2c2e;
  --mat:#fbfaf6;                     /* mat board               */
  --inner-w:clamp(4px,.7vw,10px);    /* band next to the print */
  --inner-col:#f1eee7;
  --mat-pad:2%;                      /* mat width               */
  --photo-scale:.6;                  /* framed unit in a filtered view      */
  --photo-scale-all:.42;             /* ...and in NiceLook, which shows all  */
  --frame-lift:0 24px 54px rgba(0,0,0,.6), 0 4px 12px rgba(0,0,0,.5);

  /* Motion */
  --speed:1;                  /* multiplier on every timed transition */
  --hover-scale:1.035;
  --reveal-y:26px;
  --ease:cubic-bezier(.22,.61,.36,1);
}

/* ---------- Reset ---------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--ground);
  color:var(--ink);
  font-family:var(--sans);
  font-size:var(--base);
  line-height:1.65;
  font-weight:300;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%;height:auto;display:block}
a,a:link,a:visited{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

/* ---------- Typography ---------- */
h1,h2,h3{font-family:var(--display);font-weight:400;margin:0;line-height:1.02}
.display{
  font-size:var(--display-size);
  letter-spacing:-.02em;
  font-weight:400;
}
.display em{font-style:italic}
.eyebrow{
  font-family:var(--sans);
  font-size:.68rem;
  font-weight:500;
  letter-spacing:var(--track);
  text-transform:uppercase;
  color:var(--ink-dim);
}
.lede{
  font-size:var(--lede-size);
  line-height:1.55;
  color:var(--ink-dim);
  font-weight:300;
  max-width:44ch;
}

/* ---------- Header ---------- */
.site-header{
  position:fixed;inset:0 0 auto 0;z-index:60;
  display:flex;align-items:center;justify-content:space-between;
  padding:clamp(18px,2.4vw,30px) var(--gutter);
  transition:background .5s var(--ease),backdrop-filter .5s var(--ease),padding .5s var(--ease);
}
.site-header.is-stuck{
  background:rgba(11,11,12,.82);
  backdrop-filter:saturate(1.4) blur(14px);
  padding-block:clamp(12px,1.5vw,18px);
  border-bottom:1px solid var(--rule);
}
.wordmark{
  font-family:var(--display);
  font-size:clamp(1.1rem,1.5vw,1.45rem);
  letter-spacing:.24em;
  text-transform:uppercase;
}
.wordmark span{color:var(--accent)}

.nav{display:flex;gap:clamp(18px,2.6vw,40px);align-items:center}
.nav a{
  font-size:.7rem;letter-spacing:.24em;text-transform:uppercase;
  color:var(--ink-dim);position:relative;padding-block:4px;
  transition:color .3s var(--ease);
}
.nav a::after{
  content:"";position:absolute;left:0;bottom:0;height:1px;width:100%;
  background:var(--accent);transform:scaleX(0);transform-origin:right;
  transition:transform .4s var(--ease);
}
.nav a:hover{color:var(--ink)}
.nav a:hover::after{transform:scaleX(1);transform-origin:left}

.nav-toggle{display:none;width:34px;height:34px;position:relative;z-index:70}
.nav-toggle i{
  position:absolute;left:6px;right:6px;height:1px;background:var(--ink);
  transition:transform .4s var(--ease),opacity .3s var(--ease);
}
.nav-toggle i:nth-child(1){top:12px}
.nav-toggle i:nth-child(2){top:20px}
.nav-toggle[aria-expanded="true"] i:nth-child(1){transform:translateY(4px) rotate(45deg)}
.nav-toggle[aria-expanded="true"] i:nth-child(2){transform:translateY(-4px) rotate(-45deg)}

/* ---------- Hero ---------- */
.hero{position:relative;min-height:var(--hero-h);display:flex;
      align-items:var(--hero-vpos);justify-content:var(--hero-hpos);overflow:hidden}
.hero-media{position:absolute;inset:0}
.hero-media img{width:100%;height:100%;object-fit:cover;object-position:58% center}
.hero-media::after{
  content:"";position:absolute;inset:0;
  background:
    linear-gradient(to right,
      rgba(11,11,12,calc(.92 * var(--hero-veil))) 0%,
      rgba(11,11,12,calc(.55 * var(--hero-veil))) 42%,
      rgba(11,11,12,0) 78%),
    linear-gradient(to top,
      rgba(11,11,12,calc(.55 * var(--hero-veil))) 0%,
      rgba(11,11,12,0) 22%);
}
.hero-inner{
  position:relative;z-index:2;
  padding:0 var(--gutter) clamp(60px,10vh,110px);
  width:min(100%,var(--hero-width));
  text-align:var(--hero-align);
}
.hero .eyebrow{display:block;margin-bottom:clamp(14px,2vw,24px)}
.hero .display{margin-bottom:clamp(18px,2.4vw,30px)}
.hero .lede{margin:0}

/* ---------- Section shell ---------- */
.section{padding:var(--rhythm) var(--gutter)}
/* The portfolio starts right under the hero: a thin band above the heading,
   so the photograph is what fills the screen rather than empty ground. */
#work{padding-top:clamp(18px,3vh,34px)}
.section-head{
  display:flex;flex-wrap:wrap;gap:clamp(16px,3vw,50px);
  align-items:baseline;justify-content:space-between;
  padding-bottom:clamp(8px,1.2vw,16px);
  border-bottom:1px solid var(--rule);
  margin-bottom:clamp(18px,2.6vw,32px);
}
/* Matches the "Portfolio" in the header: the sans, not the display serif. */
.section-head h2{
  font-family:var(--sans);
  font-size:var(--h2-size);
  font-weight:400;
  letter-spacing:-.02em;
}

/* Optional introduction under the heading, written in the admin. It is the
   page's main readable text, for visitors and search engines alike. */
.intro{
  max-width:40em;
  margin:0 0 clamp(24px,3vw,40px);
  color:var(--ink-dim);
  font-size:var(--lede-size);
  line-height:1.6;
  font-weight:300;
}
.intro p{margin:0}
.intro p + p{margin-top:.9em}

/* ---------- Gallery ---------- */
.gallery{
  display:grid;
  grid-template-columns:repeat(12,1fr);
  gap:var(--gap) var(--gap);
  align-items:start;
  grid-auto-flow:row dense;
}
.shot{margin:0;position:relative}
/* Three layers, outside in: the moulding is the border, the mat is the
   padding, and the print sits in the window below. */
/* The framed unit takes a share of its grid cell rather than filling it.
   The photograph is width:100% / height:auto, so its ratio is untouched;
   the mat is a percentage so it scales with the frame, and the moulding is
   scaled by the same factor to keep the whole thing in proportion. */
.shot button{
  display:block;position:relative;
  width:calc(100% * var(--photo-scale));
  border:var(--frame-w) solid var(--frame-col);
  border-width:calc(var(--frame-w) * var(--photo-scale));
  background:var(--mat);
  padding:var(--mat-pad) var(--mat-pad) calc(var(--mat-pad) * 1.5);
  box-shadow:var(--frame-lift);
  transition:box-shadow .55s var(--ease),transform .55s var(--ease);
}
/* The window the print sits in. Clipping happens here, not on the frame,
   or the hover zoom would spill across the mat. */
/* A beige band between the print and the mat. box-sizing is border-box, so
   this eats into the window rather than pushing the frame wider - the
   photograph gets slightly smaller, the frame stays the size it was. */
.shot picture{
  display:block;position:relative;overflow:hidden;
  background:var(--ground-lift);
  border:var(--inner-w) solid var(--inner-col);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.22);
}
.shot img{
  width:100%;display:block;
  transition:transform calc(1.1s * var(--speed)) var(--ease),filter calc(.7s * var(--speed)) var(--ease);
  will-change:transform;
}
.shot picture::after{
  content:"";position:absolute;inset:0;
  background:rgba(11,11,12,.28);opacity:0;transition:opacity .6s var(--ease);
}
.shot button:hover{
  transform:translateY(-3px);
  box-shadow:0 30px 64px rgba(0,0,0,.62), 0 4px 12px rgba(0,0,0,.5);
}
.shot button:hover img{transform:scale(var(--hover-scale))}
.shot button:hover picture::after{opacity:1}
/* The name sits on the mat itself, centred under the print, the way a
   framer letters the bottom margin. */
.shot .plate{
  display:block;text-align:center;
  font-family:var(--display);
  font-size:clamp(.74rem,.92vw,1.05rem);
  letter-spacing:.04em;
  color:#423e35;
  margin-top:calc(var(--mat-pad) * 2.2);
  line-height:1;
}

/* Keep each frame anchored to the side its placement implies, so the
   space freed by scaling reads as intended rather than as a stray gap. */
.shot button{margin-inline:var(--pin-l,0) var(--pin-r,auto)}
.shot.c-nar-r,.shot.c-half-r,.shot.c-sm-r{--pin-l:auto;--pin-r:0}
.shot.c-solo,.shot.c-wide,.shot.c-full{--pin-l:auto;--pin-r:auto}
.gallery.is-filtered .shot{--pin-l:auto;--pin-r:auto}

/* NiceLook shows all 25 at once, so its frames run smaller than a
   filtered view's. Custom properties inherit, so setting it on the figure
   reaches the frame, the mat and the caption together. */
.gallery:not(.is-filtered) .shot{--photo-scale:var(--photo-scale-all)}

/* placements — 12-col editorial rhythm */
.c-full {grid-column:1 / -1}
.c-wide {grid-column:2 / 12}
.c-solo {grid-column:4 / 10}
.c-half-l{grid-column:1 / 7}
.c-half-r{grid-column:7 / 13}
.c-nar-l{grid-column:1 / 6}
.c-nar-r{grid-column:8 / 13}
.c-big-l{grid-column:1 / 8}
.c-sm-r {grid-column:9 / 13}
.down   {margin-top:var(--down)}

/* ---------- Reveal ---------- */
.reveal{opacity:0;transform:translateY(var(--reveal-y));transition:opacity calc(1s * var(--speed)) var(--ease),transform calc(1s * var(--speed)) var(--ease)}
.reveal.in{opacity:1;transform:none}

/* ---------- About ---------- */
.about{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--gap);align-items:center}
.about-media{grid-column:1 / 6}
.about-body{grid-column:7 / 12}
.about-body h2{font-size:calc(var(--h2-size) * .78);margin-bottom:clamp(18px,2.6vw,30px)}
.about-body p{color:var(--ink-dim);margin:0 0 1.15em;max-width:52ch}
.stats{display:flex;flex-wrap:wrap;gap:clamp(24px,4vw,56px);margin-top:clamp(28px,4vw,48px)}
.stat b{display:block;font-family:var(--display);font-size:clamp(1.7rem,3.2vw,2.6rem);font-weight:400}
.stat span{font-size:.64rem;letter-spacing:.24em;text-transform:uppercase;color:var(--ink-faint)}

/* ---------- Contact ---------- */
.contact{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--gap) clamp(30px,5vw,80px)}
.contact-aside{grid-column:1 / 5}
.contact-form{grid-column:6 / 13}
.contact-aside h2{font-size:calc(var(--h2-size) * .78);margin-bottom:clamp(16px,2.4vw,26px)}
.contact-aside p{color:var(--ink-dim);max-width:34ch}
.detail{margin-top:clamp(24px,3.4vw,40px)}
.detail dt{font-size:.64rem;letter-spacing:.24em;text-transform:uppercase;color:var(--ink-faint);margin-bottom:6px}
.detail dd{margin:0 0 20px;font-size:1rem}
.detail a:hover{color:var(--accent)}

.field{position:relative;margin-bottom:clamp(20px,2.6vw,30px)}
.field label{
  display:block;font-size:.64rem;letter-spacing:.24em;text-transform:uppercase;
  color:var(--ink-faint);margin-bottom:10px;
}
.field input,.field textarea{
  width:100%;background:transparent;border:0;border-bottom:1px solid var(--line);
  color:var(--ink);font:inherit;padding:10px 0;border-radius:0;
  transition:border-color .4s var(--ease);
}
.field textarea{resize:vertical;min-height:120px}
.field input:focus,.field textarea:focus{outline:none;border-bottom-color:var(--accent)}
.field .err{display:block;margin-top:8px;font-size:.72rem;color:var(--accent);min-height:1em}
.field.invalid input,.field.invalid textarea{border-bottom-color:var(--accent)}

/* honeypot — hidden from people, visible to naive bots */
.hp{position:absolute!important;left:-9999px;width:1px;height:1px;overflow:hidden}

/* Present for search engines and screen readers, not drawn. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}

/* skip link — off-screen until focused by keyboard */
.skip{
  position:absolute;left:-9999px;top:0;z-index:200;
  background:var(--accent);color:var(--ink);
  padding:12px 20px;font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;
}
.skip:focus{left:0}

.btn{
  display:inline-flex;align-items:center;gap:14px;
  border:1px solid var(--line);padding:16px clamp(24px,3vw,38px);
  font-size:.68rem;letter-spacing:.28em;text-transform:uppercase;
  position:relative;overflow:hidden;transition:color .45s var(--ease),border-color .45s var(--ease);
}
.btn::before{
  content:"";position:absolute;inset:0;background:var(--accent);
  transform:translateY(101%);transition:transform .5s var(--ease);z-index:-1;
}
.btn:hover{border-color:var(--accent)}
.btn:hover::before{transform:translateY(0)}
.btn[disabled]{opacity:.5;pointer-events:none}

.form-note{
  margin-top:20px;font-size:.78rem;color:var(--ink-faint);
  border-left:2px solid var(--accent);padding-left:14px;
}
.form-status{margin-top:18px;font-size:.85rem;min-height:1.4em}
.form-status.ok{color:var(--ink)}
.form-status.bad{color:var(--accent)}

/* ---------- Footer ---------- */
.site-footer{
  border-top:1px solid var(--rule);
  padding:clamp(40px,6vw,70px) var(--gutter);
  display:flex;flex-wrap:wrap;gap:20px;
  align-items:center;justify-content:space-between;
  color:var(--ink-faint);font-size:.7rem;letter-spacing:.18em;text-transform:uppercase;
}
.site-footer a:hover{color:var(--ink)}
.social{display:flex;gap:clamp(16px,2.4vw,30px)}

/* ---------- Lightbox ---------- */
.lightbox{
  position:fixed;inset:0;z-index:100;display:none;
  background:rgba(7,7,8,.97);
  align-items:center;justify-content:center;
  padding:clamp(16px,4vw,54px);
}
.lightbox[open]{display:flex}
.lightbox img{
  max-width:100%;max-height:86svh;width:auto;object-fit:contain;
  opacity:0;transform:scale(.985);
  transition:opacity .5s var(--ease),transform .5s var(--ease);
}
.lightbox img.ready{opacity:1;transform:none}
.lb-close,.lb-prev,.lb-next{
  position:absolute;z-index:2;width:52px;height:52px;
  display:grid;place-items:center;color:var(--ink-dim);
  transition:color .3s var(--ease);
}
.lb-close:hover,.lb-prev:hover,.lb-next:hover{color:var(--ink)}
.lb-close{top:clamp(12px,2.4vw,28px);right:clamp(12px,2.4vw,28px);font-size:1.6rem;line-height:1}
.lb-prev{left:clamp(6px,1.6vw,22px);top:50%;transform:translateY(-50%)}
.lb-next{right:clamp(6px,1.6vw,22px);top:50%;transform:translateY(-50%)}
.lb-prev svg,.lb-next svg{width:26px;height:26px;stroke:currentColor;fill:none;stroke-width:1.2}
.lb-counter{
  position:absolute;bottom:clamp(14px,2.6vw,30px);left:50%;transform:translateX(-50%);
  font-size:.66rem;letter-spacing:.28em;color:var(--ink-faint);
}
body.lb-open{overflow:hidden}

/* ---------- Responsive ---------- */
@media (max-width:1000px){
  .about-media{grid-column:1 / 6}
  .about-body{grid-column:6 / 13}
  .contact-aside{grid-column:1 / 6}
  .contact-form{grid-column:6 / 13}
}
@media (max-width:760px){
  .nav{
    position:fixed;inset:0;z-index:65;
    flex-direction:column;justify-content:center;gap:34px;
    background:rgba(11,11,12,.97);backdrop-filter:blur(16px);
    transform:translateY(-100%);transition:transform .55s var(--ease);
  }
  .nav.open{transform:none}
  .nav a{font-size:1rem;letter-spacing:.3em}
  .nav-toggle{display:block}

  .gallery > .shot{grid-column:1 / -1 !important;margin-top:0 !important}

  /* On a phone each figure already spans the whole column, so the desktop
     scale would strand a 145px photograph in black. The frame takes the
     full column instead. */
  .gallery .shot,
  .gallery:not(.is-filtered) .shot{--photo-scale:1}
  .about,.contact{display:block}
  .about-media{margin-bottom:clamp(30px,6vw,50px)}
  .contact-aside{margin-bottom:clamp(40px,8vw,70px)}
  .hero-media img{object-position:center 18%}
  .hero-media::after{
    background:linear-gradient(to top,
      rgba(11,11,12,calc(.6 * var(--hero-veil))) 0%,
      rgba(11,11,12,0) 30%,
      rgba(11,11,12,calc(.45 * var(--hero-veil))) 100%);
  }
  .site-footer{flex-direction:column;align-items:flex-start;text-align:left}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
  .reveal{opacity:1;transform:none}
}

/* ==========================================================================
   Portfolio filters
   ========================================================================== */
.filters{
  display:flex;flex-wrap:wrap;gap:clamp(6px,1.4vw,18px);
  margin:0 0 clamp(20px,3vw,40px);
}
.filter{
  font-size:.74rem;letter-spacing:.16em;
  color:var(--ink-dim);padding:9px 2px;position:relative;
  transition:color .35s var(--ease);
}
.filter + .filter{margin-left:clamp(8px,1.6vw,22px)}
.filter::after{
  content:"";position:absolute;left:0;right:0;bottom:2px;height:1px;
  background:currentColor;transform:scaleX(0);transform-origin:right;
  transition:transform .4s var(--ease);
}
.filter:hover{color:var(--ink)}
.filter:hover::after,.filter.is-on::after{transform:scaleX(1);transform-origin:left}
.filter.is-on{color:var(--ink)}

.filter-count{
  margin:0 0 clamp(22px,3.4vw,40px);
  font-size:.72rem;letter-spacing:.1em;color:var(--ink-faint);
  min-height:1em;
}

/* A figure hidden by a filter leaves the grid entirely. */
.shot[hidden]{display:none!important}

/* .gallery sets display on a class, which outranks the browser's own
   [hidden] rule - without these the attribute does nothing. */
.gallery[hidden],
.silhouette[hidden]{display:none!important}

/* The editorial placement only reads as deliberate when every frame is
   present. Once a filter is on, fall back to an even grid so the result
   looks chosen rather than broken. */
.gallery.is-filtered .shot{
  grid-column:span 4!important;
  margin-top:0!important;
}
@media (max-width:900px){
  .gallery.is-filtered .shot{grid-column:span 6!important}
}
@media (max-width:760px){
  .gallery.is-filtered .shot{grid-column:1 / -1!important}
  .filters{margin-top:0}
}

/* ==========================================================================
   NiceLook: masonry
   A grid forces every frame in a row to share that row's height, so one tall
   portrait sets the height for everything beside it. Columns let each frame
   sit directly under the one above, which is both shorter and less like a
   photo album - nothing lines up across the page.
   ========================================================================== */
.gallery:not(.is-filtered){
  display:block;
  column-count:6;
  column-gap:0;
  grid-auto-flow:initial;
}
.gallery:not(.is-filtered) .shot{
  break-inside:avoid;
  -webkit-column-break-inside:avoid;
  page-break-inside:avoid;
  grid-column:auto!important;
  width:auto;
  margin:0!important;
  display:block;
}

/* Scatter. Three different cycle lengths - 6 for width, 7 for tilt, 5 for
   shift - so the combination only repeats every 210 frames. With 25 on the
   page nothing reads as a pattern. */
.gallery:not(.is-filtered) .shot:nth-child(6n+1){--photo-scale:0.96}
.gallery:not(.is-filtered) .shot:nth-child(6n+2){--photo-scale:0.74}
.gallery:not(.is-filtered) .shot:nth-child(6n+3){--photo-scale:0.9}
.gallery:not(.is-filtered) .shot:nth-child(6n+4){--photo-scale:0.68}
.gallery:not(.is-filtered) .shot:nth-child(6n+5){--photo-scale:0.86}
.gallery:not(.is-filtered) .shot:nth-child(6n+6){--photo-scale:0.8}

.gallery:not(.is-filtered) .shot:nth-child(7n+1){--tilt:-4.3deg}
.gallery:not(.is-filtered) .shot:nth-child(7n+2){--tilt:2.9deg}
.gallery:not(.is-filtered) .shot:nth-child(7n+3){--tilt:-1.5deg}
.gallery:not(.is-filtered) .shot:nth-child(7n+4){--tilt:5deg}
.gallery:not(.is-filtered) .shot:nth-child(7n+5){--tilt:-3.4deg}
.gallery:not(.is-filtered) .shot:nth-child(7n+6){--tilt:1.7deg}
.gallery:not(.is-filtered) .shot:nth-child(7n+7){--tilt:-5deg}

.gallery:not(.is-filtered) .shot:nth-child(5n+1){--shift:0%;   --pin-l:0;    --pin-r:auto}
.gallery:not(.is-filtered) .shot:nth-child(5n+2){--shift:7%;   --pin-l:auto; --pin-r:0}
.gallery:not(.is-filtered) .shot:nth-child(5n+3){--shift:-5%;  --pin-l:auto; --pin-r:auto}
.gallery:not(.is-filtered) .shot:nth-child(5n+4){--shift:4%;   --pin-l:0;    --pin-r:auto}
.gallery:not(.is-filtered) .shot:nth-child(5n+5){--shift:-8%;  --pin-l:auto; --pin-r:0}

.gallery:not(.is-filtered) .shot{
  transform:rotate(var(--tilt,0deg)) translateX(var(--shift,0));
  transform-origin:50% 40%;
  position:relative;
}
/* Where a tilt makes two frames touch, the larger one sits in front. */
.gallery:not(.is-filtered) .shot:nth-child(2n){z-index:2}
.gallery:not(.is-filtered) .shot:nth-child(6n+1){z-index:3}

/* Vertical drift, on yet another cycle. */
.gallery:not(.is-filtered) .shot:nth-child(4n+2){margin-top:28px!important}
.gallery:not(.is-filtered) .shot:nth-child(7n+5){margin-top:48px!important}
.gallery:not(.is-filtered) .shot:nth-child(9n+7){margin-top:64px!important}

@media (max-width:1500px){ .gallery:not(.is-filtered){column-count:5} }
@media (max-width:1200px){ .gallery:not(.is-filtered){column-count:4} }
@media (max-width:900px){  .gallery:not(.is-filtered){column-count:3} }
@media (max-width:760px){
  .gallery:not(.is-filtered){column-count:1}
  .gallery:not(.is-filtered) .shot{
    --photo-scale:1!important;margin-top:0!important;
    transform:none!important;
  }
}

/* ==========================================================================
   Silhouette
   Nine photographs laid as puzzle pieces, the whole tile clipped to the
   outline of one of the sittings. The mask is derived from DSCF3691 -
   a lit subject on a near-black backdrop thresholds cleanly - so the
   shape is the photographer's own subject rather than generic clip art.
   ========================================================================== */
.silhouette{
  display:grid;
  place-items:center;
  padding-block:clamp(10px,3vw,40px);
  /* The shadow sits on the parent, not the masked child: a filter on the
     masked element itself would be clipped away with everything else.
     Without it the outline is invisible - dark photographs on a dark page. */
  filter:drop-shadow(0 0 1px rgba(241,238,231,.55))
         drop-shadow(0 0 22px rgba(241,238,231,.10))
         drop-shadow(0 26px 44px rgba(0,0,0,.65));
}
.sil-inner{
  --sil-ratio:779/1085;          /* the mask's own proportions */
  width:min(100%, 560px);
  aspect-ratio:var(--sil-ratio);
  display:grid;
  grid-template-columns:repeat(3,1fr);
  grid-template-rows:repeat(3,1fr);
  gap:clamp(3px,.6vw,7px);       /* the seams between the pieces */

  -webkit-mask-image:url("../../images/mask/silhouette.png");
          mask-image:url("../../images/mask/silhouette.png");
  -webkit-mask-size:contain;      mask-size:contain;
  -webkit-mask-repeat:no-repeat;  mask-repeat:no-repeat;
  -webkit-mask-position:center;   mask-position:center;
}
.sil-inner .piece{overflow:hidden;background:var(--ground-lift)}
.sil-inner .piece picture{display:block;width:100%;height:100%}
.sil-inner .piece img{
  width:100%;height:100%;object-fit:cover;display:block;
  filter:brightness(1.12) contrast(1.06);
  transition:transform 1.2s var(--ease),filter .8s var(--ease);
}
.sil-inner .piece:hover img{transform:scale(1.06)}

/* Stagger which part of each photograph shows, so nine near-identical
   frames do not read as one repeated crop. */
.sil-inner .piece:nth-child(1) img{object-position:50% 22%}
.sil-inner .piece:nth-child(2) img{object-position:44% 35%}
.sil-inner .piece:nth-child(3) img{object-position:56% 18%}
.sil-inner .piece:nth-child(4) img{object-position:48% 45%}
.sil-inner .piece:nth-child(5) img{object-position:52% 30%}
.sil-inner .piece:nth-child(6) img{object-position:40% 52%}
.sil-inner .piece:nth-child(7) img{object-position:58% 40%}
.sil-inner .piece:nth-child(8) img{object-position:46% 60%}
.sil-inner .piece:nth-child(9) img{object-position:54% 48%}

@media (max-width:760px){
  .sil-inner{width:min(100%,340px);gap:3px}
}

/* ==========================================================================
   Folder overlay
   Portraits opens a sheet of thumbnails over the dimmed page first; the
   full-size photograph comes from choosing one of those.
   ========================================================================== */
.folder{
  position:fixed;inset:0;z-index:95;display:none;
  align-items:center;justify-content:center;
  background:rgba(7,7,8,.88);
  backdrop-filter:blur(6px);
  padding:clamp(16px,4vw,54px);
}
.folder[open]{display:flex}

.folder-panel{
  width:min(100%,1180px);height:92svh;
  display:grid;grid-template-rows:auto minmax(0,1fr) auto;
  background:var(--ground-lift);
  border:1px solid var(--line);
  box-shadow:0 40px 90px rgba(0,0,0,.7);
}
/* Three tracks, so the count sits centred in the panel regardless of how
   long the folder name or the photograph's name happens to be. */
.folder-head{
  display:grid;
  grid-template-columns:1fr auto 1fr;
  align-items:baseline;gap:16px;
  /* Sized against viewport height, not a breakpoint: stepping the head and
     the strip made the photograph shrink as the display got taller. */
  padding:clamp(8px,1.7svh,22px) clamp(16px,2.4vw,26px);
  border-bottom:1px solid var(--rule);
}
.folder-id{display:flex;align-items:baseline;gap:12px;min-width:0}
.folder-count{
  font-size:.68rem;letter-spacing:.18em;text-transform:uppercase;
  color:var(--ink-dim);text-align:center;white-space:nowrap;
}
#folderClose{justify-self:end}
#folderTitle{
  font-family:var(--display);font-size:clamp(1.1rem,1.8vw,1.5rem);
}
.folder-hint{
  font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--ink-faint);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
#folderClose{
  font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--ink-dim);padding:6px 2px;position:relative;
}
#folderClose::after{
  content:"";position:absolute;left:0;right:0;bottom:2px;height:1px;
  background:currentColor;transform:scaleX(0);transform-origin:right;
  transition:transform .35s var(--ease);
}
#folderClose:hover{color:var(--ink)}
#folderClose:hover::after{transform:scaleX(1);transform-origin:left}

/* The large photograph fills whatever the head and the strip leave. */
/* The large photograph wears the same frame as one on the wall: the same
   moulding, mat, inner band and lettered name, driven by the same tokens. */
.folder-stage{
  min-height:0;
  display:grid;
  /* Both tracks bounded, or the row is auto-sized to the photograph and
     max-height:100% on the frame resolves to the frame's own height -
     which caps nothing. */
  grid-template-rows:minmax(0,1fr);
  grid-template-columns:minmax(0,1fr);
  place-items:center;
  padding:clamp(8px,1.4svh,24px);
  background:var(--ground);
  overflow:hidden;
}
.stage-frame{
  position:relative;
  margin:0;display:flex;flex-direction:column;
  /* Centre, do not stretch: a stretched window keeps the frame's full
     width, so when max-height clamps it the aspect ratio cannot hold and
     the photograph gets cropped instead of scaled. */
  align-items:center;
  max-width:100%;max-height:100%;box-sizing:border-box;
  border:var(--frame-w) solid var(--frame-col);
  background:var(--mat);
  /* The mat on the wall is a share of the frame's width. Here it competes
     for height as well, so it takes whichever of the two is smaller: on a
     short display a width-derived mat would swallow the whole picture. */
  --stage-mat:min(2%, 2svh);
  padding:var(--stage-mat) var(--stage-mat) calc(var(--stage-mat) * 1.5);
  box-shadow:var(--frame-lift);
}
/* Arrows sit on the moulding, left and right, halfway up: outside the
   photograph, so they never cover it. */
.stage-arrow{
  position:absolute;top:50%;z-index:2;
  width:34px;height:34px;border-radius:50%;
  display:grid;place-items:center;
  background:rgba(11,11,12,.72);
  border:1px solid rgba(242,239,233,.35);
  color:var(--ink-dim);
  cursor:pointer;
  transition:background .25s var(--ease),color .25s var(--ease),border-color .25s var(--ease),transform .25s var(--ease);
}
.stage-arrow.prev{left:calc(var(--frame-w) * -.5);transform:translate(-50%,-50%)}
.stage-arrow.next{right:calc(var(--frame-w) * -.5);transform:translate(50%,-50%)}
.stage-arrow svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.stage-arrow:hover,.stage-arrow:focus-visible{
  background:var(--ink);color:var(--ground);border-color:var(--ink);
}
.stage-arrow.prev:hover{transform:translate(-50%,-50%) scale(1.1)}
.stage-arrow.next:hover{transform:translate(50%,-50%) scale(1.1)}
/* Phones and tablets step with the strip; the arrows are for a mouse. */
@media (hover:none){ .stage-arrow{display:none} }

/* The window carries the photograph's own aspect ratio, set from its
   natural size when it loads. That gives it a definite shape, so it can
   shrink to whatever height the frame leaves and the mat still hugs the
   picture instead of letterboxing it. */
.stage-window{
  display:block;min-height:0;flex:0 1 auto;
  /* content-box, so the ratio describes the picture and the band sits
     outside it - otherwise the band eats into the ratio and the photograph
     letterboxes inside its own window. */
  box-sizing:content-box;
  max-height:100%;overflow:hidden;
  border:var(--inner-w) solid var(--inner-col);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.22);
  background:var(--ground-lift);
}
.folder-stage img{
  display:block;width:auto;height:auto;
  width:100%;height:100%;
  object-fit:contain;
  opacity:0;transition:opacity .35s var(--ease);
}
.folder-stage img.ready{opacity:1}
.stage-frame .plate{
  display:block;text-align:center;
  font-family:var(--display);
  font-size:clamp(.62rem,.9vw,1.05rem);
  letter-spacing:.04em;color:#423e35;line-height:1;
  margin-top:calc(var(--stage-mat) * 1.1);
}

/* One row along the bottom, scrolling sideways if the folder is long. */
.folder-strip{
  flex:0 0 auto;
  display:flex;gap:clamp(6px,.8vw,12px);
  overflow-x:auto;overflow-y:hidden;
  padding:clamp(6px,1.3svh,16px);
  border-top:1px solid var(--line);
  scrollbar-width:thin;
}
.folder-strip button{
  flex:0 0 auto;
  width:clamp(40px,8svh,96px);
  aspect-ratio:3/4;
  position:relative;overflow:hidden;
  background:var(--mat);
  padding:2px;
  border:2px solid var(--frame-col);
  opacity:.55;
  transition:opacity .3s var(--ease),border-color .3s var(--ease),transform .3s var(--ease);
}
.folder-strip img{width:100%;height:100%;object-fit:cover;display:block}
.folder-strip button:hover{opacity:1;transform:translateY(-2px)}
.folder-strip button[aria-selected="true"]{
  opacity:1;border-color:var(--ink-faint);
  box-shadow:0 0 0 1px var(--mat);
}
.folder-strip button img{border:1px solid var(--inner-col)}
.folder-grid .cap{
  position:absolute;left:0;right:0;bottom:0;
  padding:14px 6px 5px;
  font-size:.62rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink);text-align:center;
  background:linear-gradient(to top,rgba(7,7,8,.85),rgba(7,7,8,0));
}
@media (max-width:600px){
  .folder-grid{grid-template-columns:repeat(auto-fill,minmax(104px,1fr))}
}

/* Below this the mat has no room for lettering; the name stays in the head. */
@media (max-height:560px){
  .stage-frame .plate{display:none}
}

/* ==========================================================================
   Filmstrip on the left
   The strip along the bottom competed with the photograph for height, which
   is the scarce dimension on a laptop. Down the left it competes for width
   instead, which there is plenty of. Below 760px wide it goes back to the
   bottom, where width is the scarce one.
   ========================================================================== */
@media (min-width:760px){
  .folder-panel{
    grid-template-columns:auto minmax(0,1fr);
    grid-template-rows:auto minmax(0,1fr);
  }
  .folder-head{grid-column:1 / -1}

  .folder-strip{
    grid-column:1;grid-row:2;
    flex-direction:column;
    overflow-x:hidden;overflow-y:auto;
    border-top:0;
    border-right:1px solid var(--line);
    padding:clamp(8px,1.2svh,14px);
    align-content:flex-start;
  }
  .folder-strip button{
    width:clamp(54px,6.5vw,86px);
    flex:0 0 auto;
  }
  .folder-strip button:hover{transform:translateX(3px)}

  .folder-stage{grid-column:2;grid-row:2}
}
