/* dittmar.works, one stylesheet, every page */
:root{
  --ground:#08080A; --raised:#111113; --rule:#1E1E22; --inner:#141416;
  --silver:#86868B; --copy:#B9B9B6; --paper:#E8E8E5; --grease:#C8352C;
  --gut:18px; --pad:clamp(20px,4vw,64px); --col:1360px;
  --read:-apple-system,BlinkMacSystemFont,'SF Pro Text',Archivo,'Helvetica Neue',Arial,sans-serif;
  --dsp:Archivo,'Helvetica Neue',Arial,sans-serif;
  --plate:'Spline Sans Mono',ui-monospace,Menlo,monospace;
  /* THE PLATE REGISTER
     Spline Sans Mono is ALWAYS uppercase, via text-transform, never typed.
     Uppercase is what monospace is for: a data plate, a slate, a frame stamp.
     Lowercase mono reads as a terminal prompt, which is a different world.
     9 to 11px, tracking +.15 to +.16em (+.26em for the cities line).
     Amendment 6, 24 Aug 2026. Every rule below that names --plate obeys this. */
}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
/* THE GROUND IS FLAT AND IT IS BLACK.
   It carried a noise layer and a scanline, and together they lifted the page
   from #08080A to #1B1B1C: more than three times lighter than the value the
   token claimed. A texture you can measure on the background is a texture
   competing with the work. The material belongs to the prints on the sheet,
   never to the paper under them, so the ground is now exactly its token and
   the only texture on this page sits inside a frame. */
body{margin:0;background:var(--ground);color:var(--paper);font-family:var(--read);
  font-size:15px;line-height:1.55;-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}
:focus-visible{outline:2px solid var(--grease);outline-offset:3px}
img,video{max-width:100%}
/* MARGIN IS PART OF THE ARGUMENT.
   The page stops at a column and lets the ground show on both sides. A layout
   that runs to the edge of whatever monitor it lands on has not decided
   anything; a column has. It also fixes the card size: three across inside
   1360 keeps a frame around 440px, which is the size that reads as a print on
   a sheet rather than a hero. */
.wrap{width:min(var(--col),100% - var(--pad) * 2);margin-inline:auto;padding:0}
.skip{position:absolute;left:-9999px}
.skip:focus{left:var(--pad);top:12px;z-index:20;background:var(--raised);padding:10px 14px;
  font-family:var(--plate);font-size:10px;letter-spacing:.16em;text-transform:uppercase}

/* ---- header ---- */
header{position:sticky;top:0;z-index:10;background:rgba(10,10,11,.92);
  backdrop-filter:blur(10px);border-bottom:1px solid var(--rule)}
.hd{display:flex;justify-content:space-between;align-items:center;height:64px}
/* THE MARK
   The plate face, tightened. The person's mark is a string, not an object:
   it reads as a filename or a frame stamp, which is the same voice as every
   plate on the site. The studio is a welded grotesque; this is the opposite
   kind of thing, and the difference needs no second palette to be obvious.
   One weight throughout, so it survives one ink. */
/* At plate size the mark is typographically identical to the nav: same face,
   size, weight and tracking. Colour is the only thing left doing the work,
   paper against the nav's silver. The header is one strip, not a logo and links.
   Tight and heavy stay display moves. */
.mark{font-family:var(--plate);font-weight:400;font-size:10px;letter-spacing:.16em;
  white-space:nowrap;text-transform:uppercase}
.mark .dot,.mark .tld{font-weight:inherit;letter-spacing:inherit}
.mark.lg{font-size:34px;font-weight:500;letter-spacing:-.09em}
/* All caps throughout. Uppercase is what monospace is for, and the
   no-caps rule belonged to the Studio Atelier aesthetic, which is retired.
   Amendment 6, 24 Aug 2026. */
nav ul{display:flex;gap:26px;list-style:none;margin:0;padding:0}
nav a{font-family:var(--plate);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--silver);padding-bottom:2px;border-bottom:1px solid transparent;transition:color .18s,border-color .18s}
nav a:hover,nav a[aria-current]{color:var(--paper);border-color:var(--grease)}
.menu{display:none;font-family:var(--plate);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--silver);background:none;border:0;cursor:pointer;padding:8px 0}

/* ---- masthead ---- */
.mast{display:flex;justify-content:space-between;align-items:flex-end;gap:32px;
  padding:clamp(48px,9vw,104px) 0 clamp(30px,5vw,48px);border-bottom:1px solid var(--rule)}
.mast h1{font-family:var(--dsp);font-weight:600;font-size:clamp(22px,3.2vw,40px);
  line-height:1.08;letter-spacing:-.03em;margin:0;text-wrap:balance}
/* variant B, two lines on desktop */
body.two .mast h1{max-width:22ch}
.mast p{margin:12px 0 0;color:var(--silver)}
.cities{font-family:var(--plate);font-size:10px;letter-spacing:.26em;text-transform:uppercase;
  color:var(--copy);display:flex;align-items:center;gap:12px;margin-top:18px!important}
.cities .deg{color:#5A5A5F;font-size:11px;line-height:1;position:relative;top:-1px}
.roll{font-family:var(--plate);font-size:9px;letter-spacing:.3em;text-transform:uppercase;
  color:var(--grease);margin:0 0 16px}
/* the roll count set as a display numeral. the sentence says what the work is;
   the number says how much of it there is, and only one of those needs to be
   read from across the room. */
.count{display:flex;flex-direction:column;align-items:flex-end;text-align:right;white-space:nowrap}
.count .big{font-family:var(--dsp);font-weight:700;font-size:clamp(44px,5.4vw,76px);
  letter-spacing:-.05em;line-height:.86;color:var(--paper)}
.count .lb{font-family:var(--plate);font-size:9px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--silver);margin-top:8px}
.count .lb.on{color:var(--grease);margin-top:5px}

/* ---- the sheet ---- */
.sheet{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:38px var(--gut);
  padding:clamp(34px,5vw,54px) 0 clamp(50px,7vw,80px)}
.sheet.dense{grid-template-columns:repeat(4,minmax(0,1fr));gap:30px var(--gut)}
.frame{display:block;position:relative}
/* THE FRAME
   4:3, the aspect it was shot in. Never cropped to a cinema ratio.
   Cropping a 4:3 negative to 2.40 throws away 44% of the frame and every
   pixel of resolution that goes with it. The sheet takes the frame as shot. */
.win{position:relative;aspect-ratio:4/3;overflow:hidden;background:var(--inner)}
.win video,.win img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.win img{z-index:1}
.win video{z-index:2;opacity:0}
.frame.lit .win video{opacity:1}
.win img,.win video{filter:grayscale(1) brightness(1.04) contrast(.94);
  transition:filter .6s cubic-bezier(.4,0,.2,1),opacity .5s linear}
.frame.lit .win img,.frame.lit .win video{filter:none}

/* THE DATA BACK
   Read off the negative and laid on the frame, the way a camera back does it.
   Every value is true of the source file. Nothing here is invented, because a
   fake f-stop is the one lie on this page a camera person would catch. */
.hud::before,.hud::after{content:"";position:absolute;left:0;right:0;height:52px;z-index:-1}
.hud::before{top:0;background:linear-gradient(to bottom,rgba(0,0,0,.42),transparent)}
.hud::after{bottom:0;background:linear-gradient(to top,rgba(0,0,0,.42),transparent)}
.hud{position:absolute;inset:0;z-index:5;padding:9px 10px;display:flex;
  flex-direction:column;justify-content:space-between;pointer-events:none;
  font-family:var(--plate);font-size:8.5px;font-weight:400;letter-spacing:.1em;
  text-transform:uppercase;color:#DDE8F2;text-shadow:0 1px 3px rgba(0,0,0,.85)}
.hud .row{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
.hud .bx{display:flex;gap:5px}
.hud .b{border:1px solid rgba(221,232,242,.6);padding:1px 4px;line-height:1.35}
.hud .lo{color:#9FB2C4}
.hud .ct{color:#DDE8F2}
/* the AF brackets. four corners, hairline, never a closed border. */
.af{position:absolute;inset:16px;z-index:4;pointer-events:none}
.af i{position:absolute;width:13px;height:13px;border:1px solid rgba(255,255,255,.46)}
.af i:nth-child(1){top:0;left:0;border-right:0;border-bottom:0}
.af i:nth-child(2){top:0;right:0;border-left:0;border-bottom:0}
.af i:nth-child(3){bottom:0;left:0;border-right:0;border-top:0}
.af i:nth-child(4){bottom:0;right:0;border-left:0;border-top:0}
.rec{position:absolute;top:9px;left:50%;transform:translateX(-50%);z-index:6;
  display:flex;align-items:center;gap:5px;pointer-events:none;
  font-family:var(--plate);font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--grease);text-shadow:0 1px 3px rgba(0,0,0,.85)}
.rec b{width:5px;height:5px;border-radius:50%;background:var(--grease);display:block}
/* the hero is the film itself: no back, no brackets, nothing over the picture */
.hero .hud,.hero .af,.hero .rec{display:none}

/* THE PRINT TEXTURE
   Grain and scanline, on THUMBNAILS ONLY. A thumbnail is a print on a sheet, so
   it carries the material of the sheet. The hero is the film itself and nothing
   is laid over it. Texture belongs to prints, never to the picture you came
   to watch. */
.tex{position:absolute;inset:0;z-index:3;pointer-events:none;opacity:.22;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='80' height='80'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/></filter><rect width='80' height='80' filter='url(%23n)' opacity='.55'/></svg>")}
.tex::after{content:"";position:absolute;inset:0;
  background:repeating-linear-gradient(to bottom,rgba(255,255,255,.06) 0 1px,transparent 1px 3px)}
.hero .tex{display:none}
/* the chinagraph select. preserveAspectRatio="none" on the svg lets the oval
   take the shape of the frame it is drawn on. */
svg.sel{position:absolute;inset:0;z-index:4;width:100%;height:100%;pointer-events:none;overflow:visible;
  transform:rotate(-1.1deg) scale(.86);transform-origin:52% 50%;filter:url(#wax)}
svg.sel path{fill:none;stroke:var(--grease);stroke-linecap:round;stroke-linejoin:round;
  vector-effect:non-scaling-stroke;stroke-dasharray:1900;stroke-dashoffset:1900}
svg.sel .bleed{stroke-width:6;opacity:.10}
svg.sel .core{stroke-width:2;opacity:.55}
.frame.lit svg.sel path{transition:stroke-dashoffset 1s cubic-bezier(.42,0,.28,1) .15s;stroke-dashoffset:0}
.plate{display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  border-top:1px solid var(--rule);margin-top:10px;padding-top:8px;
  font-family:var(--plate);font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--silver)}
.plate .id{color:var(--paper);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.frame.is-sel .plate .n{color:var(--grease)}
.slot{border:1px dashed var(--rule);aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;
  font-family:var(--plate);font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:#3A3A3E}
.more{text-align:center;padding-bottom:clamp(50px,7vw,84px);
  font-family:var(--plate);font-size:10px;letter-spacing:.4em;color:#3A3A3E}

/* ---- project page ---- */
/* THE SEED
   Where the film came from, one line. It sits with the rest of the thinking,
   underneath the film, because the film is what the page is for and every
   word on the page is downstream of watching it. First of the thinking blocks:
   seed is the origin, brief is the job, anatomy is the build. */
.blk .sd{margin:0;max-width:54ch;color:var(--copy);font-size:clamp(16px,1.5vw,19px);
  line-height:1.5;text-wrap:pretty}
.hero{padding:clamp(28px,4vw,44px) 0 0}
/* A 4:3 hero is tall. Hold the aspect and cap the width instead, so the
   whole frame plus its plate sits inside one screen without scrolling. */
.hero .frame{max-width:min(100%,calc((100vh - 210px) * 4 / 3));margin:0 auto}
.hero .win{aspect-ratio:4/3}
.hero .plate{font-size:10px;padding-top:9px}
.sound{background:none;border:0;cursor:pointer;color:var(--silver);font:inherit;
  font-family:var(--plate);font-size:9px;letter-spacing:.15em;text-transform:uppercase;padding:0}
.sound:hover{color:var(--paper)}
.blk{border-top:1px solid var(--rule);padding:clamp(34px,5vw,52px) 0;
  display:grid;grid-template-columns:170px minmax(0,1fr);gap:clamp(20px,4vw,56px)}
.blk h2{font-family:var(--plate);font-size:9px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--silver);font-weight:400;margin:0}
.blk p{margin:0 0 12px;max-width:62ch;color:var(--copy);font-size:17px;line-height:1.55;text-wrap:pretty}
.blk p:last-child{margin-bottom:0}
.dec{display:grid;gap:26px}
.dec .row{display:grid;grid-template-columns:110px minmax(0,1fr);gap:20px;align-items:start}
.dec .k{font-family:var(--plate);font-size:9px;letter-spacing:.18em;text-transform:uppercase;padding-top:5px}
.dec .k.no{color:var(--grease)}
.dec .k.yes{color:var(--paper)}
.dec .k.why{color:var(--silver)}
.stills{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:26px var(--gut)}
.stills .plate{font-size:8px;letter-spacing:.14em;margin-top:8px;padding-top:7px}
.stills .win{aspect-ratio:4/3}
.cred{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 40px;max-width:620px;
  font-family:var(--plate);font-size:10px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--silver)}
.cred div{display:flex;justify-content:space-between;border-bottom:1px solid var(--inner);padding:6px 0}
.cred b{font-weight:400;color:var(--paper)}
.next{border-top:1px solid var(--rule);padding-top:clamp(34px,5vw,52px);padding-bottom:clamp(50px,7vw,80px)}
.next .lb{font-family:var(--plate);font-size:9px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--silver);display:block;margin-bottom:18px}
.next .frame{max-width:470px}

/* ---- simple pages ---- */
.page{padding:clamp(60px,10vw,120px) 0 clamp(60px,9vw,110px);max-width:70ch}
.page h1{font-family:var(--dsp);font-weight:600;font-size:clamp(26px,3.6vw,42px);
  letter-spacing:-.028em;line-height:1.08;margin:0 0 22px}
.page p{color:var(--copy);font-size:17px;margin:0 0 16px}
.page a.big{font-family:var(--dsp);font-size:clamp(20px,2.6vw,30px);font-weight:600;
  letter-spacing:-.02em;border-bottom:1px solid var(--grease);padding-bottom:3px;display:inline-block;margin-top:8px}
.page ul{list-style:none;padding:0;margin:26px 0 0}
.page li{border-top:1px solid var(--rule);padding:14px 0;display:flex;justify-content:space-between;gap:20px;
  font-family:var(--plate);font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--silver)}
.page li b{font-weight:400;color:var(--paper)}

/* ---- footer ---- */
footer{border-top:1px solid var(--rule);padding:30px 0 44px;
  display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
footer a,footer span{font-family:var(--plate);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--silver)}
footer a{border-bottom:1px solid transparent;padding-bottom:2px}
footer a:hover{color:var(--paper);border-color:var(--grease)}
.fl{display:flex;gap:24px;flex-wrap:wrap}
footer .col{color:#3A3A3E}

@media (max-width:1000px){
  .sheet{grid-template-columns:repeat(2,minmax(0,1fr))}
  .sheet.dense{grid-template-columns:repeat(3,minmax(0,1fr))}
  .blk{grid-template-columns:1fr;gap:16px}
  .stills{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:680px){
  .sheet,.sheet.dense{grid-template-columns:1fr;gap:30px}
  .stills{grid-template-columns:1fr}
  .cred{grid-template-columns:1fr}
  nav{display:none} .menu{display:block}
  nav.open{display:block;position:fixed;inset:64px 0 0;background:var(--ground);z-index:11}
  nav.open ul{flex-direction:column;gap:0;padding:var(--pad)}
  nav.open a{display:block;padding:18px 0;font-size:13px;border-bottom:1px solid var(--inner)}
  .mast{flex-direction:column;align-items:flex-start;gap:22px}
  .count{text-align:left}
  .plate .fmt{display:none}
  .dec .row{grid-template-columns:1fr;gap:8px}
}
@media (prefers-reduced-motion:reduce){
  .win img,.win video{filter:none;transition:none}
  svg.sel path{stroke-dashoffset:0;transition:none}
}

/* THE CONTACT FORM.
   A Netlify form. It has to exist in the deployed HTML for Netlify to detect
   it at build time, so it is written by build.py, never injected by script.
   Submitted with fetch so nobody leaves the page or sees Netlify's own
   success screen. Fields are underlines on the ground, not boxes: a boxed
   input is a different material to everything else here. */
.form{margin:34px 0 0;max-width:44rem}
.fld{display:block;margin-bottom:20px}
.fld>span{display:block;font-family:var(--plate);font-size:10px;letter-spacing:.15em;
  text-transform:uppercase;color:var(--silver);margin-bottom:8px}
.fld input,.fld textarea{width:100%;font:inherit;font-size:16px;color:var(--paper);
  background:transparent;border:0;border-bottom:1px solid var(--rule);
  padding:8px 0 10px;border-radius:0;-webkit-appearance:none}
.fld textarea{resize:vertical;min-height:104px;line-height:1.55}
.fld input:focus,.fld textarea:focus{outline:0;border-bottom-color:var(--grease)}
.fld input::placeholder,.fld textarea::placeholder{color:var(--rule)}
.hp{position:absolute;left:-9999px}
.send{display:inline-flex;align-items:center;gap:12px;margin-top:12px;cursor:pointer;
  background:none;border:1px solid var(--rule);color:var(--paper);
  font-family:var(--plate);font-size:10px;letter-spacing:.15em;text-transform:uppercase;
  padding:14px 20px;transition:border-color .18s ease,background .18s ease}
.send:hover{border-color:var(--silver);background:var(--raised)}
.send i{width:6px;height:6px;background:var(--grease);flex:none}
.fnote{font-family:var(--plate);font-size:10px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--silver);margin:18px 0 0;min-height:1em}
.fnote.bad{color:var(--grease)}
.form[data-sent="1"] form{display:none}
.form .done{display:none;font-family:var(--dsp);font-weight:600;
  font-size:clamp(20px,2.6vw,28px);letter-spacing:-.02em;line-height:1.2;margin:0}
.form[data-sent="1"] .done{display:block}

/* ---------------------------------------------------------------- THE FILTER
   The detector, classifying. Plate chips, not a dropdown, because the plate
   register is already the site's language for machine-read facts. Default is
   all: a filter that hides three quarters of the work on arrival is not a
   filter, it is a smaller site. */
.filters{display:flex;align-items:center;flex-wrap:wrap;gap:10px 22px;
  padding:0 0 4px;margin:0 0 var(--gut)}
.fl-lab,.plate-lab{font-family:var(--plate);font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--silver)}
.chips{display:flex;gap:6px}
.chip{font-family:var(--plate);font-size:10px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--silver);background:none;border:1px solid var(--rule);padding:6px 11px;
  cursor:pointer;transition:color .16s,border-color .16s,background .16s}
.chip:hover{color:var(--paper);border-color:var(--silver)}
.chip.on{color:var(--paper);border-color:var(--grease)}
.frame[hidden]{display:none}
.empty{font-family:var(--plate);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--silver);padding:40px 0}

/* the window now takes each cut's own aspect, set inline per card. a sheet of
   mixed ratios is still one sheet: the grade and the plate hold it together. */
.sheet .win{aspect-ratio:auto}
.win iframe{position:absolute;inset:0;width:100%;height:100%;border:0;
  opacity:0;transition:opacity .45s ease;pointer-events:none}
.win iframe.in{opacity:1}

/* ---------------------------------------------------------------- THE STAGE
   Vimeo in background mode: no controls, no title, no logo, on any plan.
   The controls below are ours, in the plate register, driven by the player
   SDK. Sound is off until asked for, and asking is one button. */
.stage{position:relative;width:100%;background:var(--inner);overflow:hidden;margin:26px 0 0}
.stage .poster{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  filter:grayscale(1) contrast(1.04) brightness(.96);transition:opacity .5s ease}
.stage.live .poster{opacity:0}
.stage .mount,.stage .mount iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.playbtn{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  font-family:var(--plate);font-size:10px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--paper);background:rgba(8,8,10,.55);border:1px solid var(--paper);
  padding:13px 22px;cursor:pointer;backdrop-filter:blur(3px)}
.playbtn:hover{background:var(--grease);border-color:var(--grease)}
.stage.live .playbtn{display:none}
.ctrls{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;gap:14px;
  padding:12px 14px;background:linear-gradient(transparent,rgba(8,8,10,.82));
  font-family:var(--plate);font-size:10px;letter-spacing:.15em;text-transform:uppercase}
.ctrls button{font:inherit;letter-spacing:inherit;text-transform:inherit;color:var(--paper);
  background:none;border:1px solid rgba(232,232,229,.28);padding:6px 10px;cursor:pointer}
.ctrls button:hover{border-color:var(--paper)}
.c-snd[aria-pressed="true"]{border-color:var(--grease);color:var(--grease)}
.scrub{flex:1;height:1px;background:rgba(232,232,229,.28);position:relative}
.scrub .bar{position:absolute;left:0;top:0;height:100%;width:0;background:var(--grease)}
.c-time{color:var(--silver);min-width:38px;text-align:right}

.stub{border:1px dashed var(--rule);padding:52px;margin-top:30px;
  display:flex;align-items:center;justify-content:center}
.hero .seed{color:var(--copy);font-size:17px;max-width:60ch;margin:14px 0 0}
/* no masthead. the work opens the page. */
.wrap.top{padding-top:clamp(26px,4vw,44px)}

/* ONE SHEET, ONE FRAME SIZE.
   Mixed ratios on a contact sheet do not get mixed frames: every negative
   prints at the same size on the same paper, and the ones that do not fill it
   sit inside it with the paper showing. object-fit contain, never cover,
   because the rule that the frame is never cropped does not stop being true
   when the sheet is the thing that would do the cropping. */
.sheet .win{aspect-ratio:4/3;background:var(--inner)}
.sheet .win img{width:100%;height:100%;object-fit:contain}
.sheet .frame[data-ar="9:16"] .win img,
.sheet .frame[data-ar="16:9"] .win img{object-fit:contain}
.sheet .win iframe{object-fit:contain}

/* THE OUTCOME LINE.
   A result, stamped in the plate register, not a badge. It reads as a fact on
   the file because that is what it is. It appears only where one exists. */
.outcome{font-family:var(--plate);font-size:9px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--grease);padding-top:6px}
.outcome.big-note{font-size:10px;letter-spacing:.16em;padding:14px 0 0}

/* THE SHEET IS MASONRY, NOT A GRID OF EQUAL BOXES.
   Forcing every cut into one frame size made the sheet uniform and lifeless,
   and it wasted half of every card on paper. Columns keep each negative at its
   true aspect with no holes: a 9:16 is tall, a 16:9 is wide, and the column
   packs them. Reading order runs down each column rather than across, which
   the roll number on every plate makes recoverable. */
.sheet{display:block !important;columns:3;column-gap:var(--gut)}
.sheet .frame{break-inside:avoid;display:block;width:100%;margin:0 0 var(--gut)}
.sheet .win{aspect-ratio:auto}
.sheet .win img{width:100%;height:100%;object-fit:cover}
@media (max-width:1100px){ .sheet{columns:2} }
@media (max-width:620px){ .sheet{columns:1} }

/* THE STILLS.
   Same masonry as the cuts. Mono until you touch it: the grade comes off on
   hover rather than on load, so the sheet reads as one material and the colour
   is something you cause. Same rule the frames follow when they play. */
.stills-sheet .still{break-inside:avoid;display:block;width:100%;margin:0 0 var(--gut)}
.stills-sheet .win img{width:100%;height:100%;object-fit:cover;
  filter:grayscale(1) contrast(1.05) brightness(.95);
  transition:filter .12s cubic-bezier(.2,0,0,1)}
.stills-sheet .still:hover .win img{filter:grayscale(0) contrast(1) brightness(1)}
.stills-sheet .plate{opacity:.55;transition:opacity .12s ease}
.stills-sheet .still:hover .plate{opacity:1}
@media (hover:none){ .stills-sheet .win img{filter:none} }

/* the hidden attribute loses to display:flex, so the controls were showing
   before anything was playing. state beats layout. */
.ctrls[hidden]{display:none}

/* ---------------------------------------------------------------- 29 AUG
   NO MONO ANYWHERE.
   The grade was applied by the page in three places: the sheet frames, the
   stage poster and the stills. It is off in all three. The work was shot in
   colour and the mono was the page having an opinion about it. */
.win img,.win video{filter:none !important;transition:none}
.stage .poster{filter:none !important}
.stills-sheet .win img{filter:none !important;transition:none}
.stills-sheet .plate{opacity:1}

/* ROOM BETWEEN THE CUTS.
   Fifty one titles stacked at an 18px gutter read as a wall of text before
   they read as a body of work. The frames were never the problem; the plates
   under them were touching. Wider columns, more air under each frame, and the
   title given its own line so the eye lands on one name at a time. */
.sheet{column-gap:46px}
.sheet .frame,.stills-sheet .still{margin:0 0 60px}
.plate{margin-top:16px;padding-top:12px;gap:10px 20px;flex-wrap:wrap;align-items:flex-start}
.plate .id{flex:1 1 100%;white-space:normal;overflow:visible;font-size:10px;line-height:1.45}
.plate .id .n{color:var(--silver);margin-right:4px}
.plate > span:last-child{flex:1 1 100%;color:var(--silver);opacity:.72}
@media (max-width:1100px){ .sheet{column-gap:32px} .sheet .frame{margin-bottom:46px} }
.stage.nofilm .poster{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.pending{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  font-family:var(--plate);font-size:10px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--paper);background:rgba(8,8,10,.6);border:1px solid var(--rule);padding:11px 18px}

/* ---------------------------------------------------------------- WRITTEN PAGES
   about and process. The copy is authored in content.json, so it never lives
   in the HTML and a rebuild can never eat it. */
.page.wide{max-width:none}
.page.wide h1{max-width:18ch}
.page .lede{color:var(--copy);font-size:clamp(18px,2vw,22px);line-height:1.5;max-width:58ch;margin:18px 0 0}
.written{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(34px,5vw,64px);
  border-top:1px solid var(--rule);padding-top:clamp(34px,5vw,56px);margin-top:clamp(40px,6vw,72px)}
.written .blk{border:0;padding:0}
.written h2{font-family:var(--dsp);font-weight:600;font-size:clamp(20px,2.2vw,26px);
  letter-spacing:-.02em;line-height:1.15;margin:0 0 12px;color:var(--paper)}
.written p{color:var(--copy);font-size:16px;line-height:1.62;margin:0;max-width:52ch}

.steps{border-top:1px solid var(--rule);margin-top:clamp(40px,6vw,72px)}
.step{display:grid;grid-template-columns:64px minmax(0,1fr);gap:clamp(20px,3vw,40px);
  border-bottom:1px solid var(--rule);padding:clamp(30px,4vw,48px) 0}
.step .n{font-family:var(--plate);font-size:10px;letter-spacing:.16em;color:var(--grease);padding-top:6px}
.step h2{font-family:var(--dsp);font-weight:600;font-size:clamp(22px,2.6vw,30px);
  letter-spacing:-.024em;line-height:1.12;margin:0 0 14px;color:var(--paper)}
.step p{color:var(--copy);font-size:16px;line-height:1.62;margin:0;max-width:60ch}
.step .win{margin-top:22px;max-width:520px}
.close-blk{padding-top:clamp(40px,6vw,72px)}

/* an unwritten slot looks unwritten on purpose */
.slot-i{color:#3A3A3E;font-style:normal;border-bottom:1px dashed var(--rule);padding-bottom:2px}
@media (max-width:820px){ .written{grid-template-columns:1fr} .step{grid-template-columns:1fr;gap:10px} }

/* THE STILLS SHEET IS TIGHTER THAN THE CUTS SHEET.
   The cuts needed air because each one carries a title and 49 names stacked
   read as a wall. A still carries a number and a size, so the frames can sit
   close: the eye is reading pictures, not a list. Four columns, half the gap. */
.stills-sheet{columns:4;column-gap:14px}
.stills-sheet .still{margin:0 0 14px}
.stills-sheet .plate{margin-top:7px;padding-top:6px;gap:4px 12px;font-size:8px}
.stills-sheet .plate .id{flex:0 0 auto;font-size:8px}
.stills-sheet .plate > span:last-child{flex:0 0 auto}
@media (max-width:1250px){ .stills-sheet{columns:3} }
@media (max-width:820px){ .stills-sheet{columns:2} }
@media (max-width:520px){ .stills-sheet{columns:1} }

/* ---------------------------------------------------------------- THE BUG
   The filter set [hidden] on every card correctly and nothing disappeared,
   because the masonry rule `.sheet .frame{display:block}` sits later in this
   file at the same specificity as `.frame[hidden]{display:none}` and simply
   won. The chips were doing their job into a void.

   This goes last and carries !important, because a layout rule must never be
   able to outrank a state rule again. State beats layout, every time. */
.frame[hidden],.still[hidden]{display:none !important}

/* ---------------------------------------------------------------- PROCESS
   Text left, a stack of captures right. Empty plates until real screenshots
   land: a labelled empty frame is honest, a borrowed frame is not. */
.step{grid-template-columns:64px minmax(0,1fr) 260px}
.step-body p + p{margin-top:14px}
.stack{display:grid;grid-template-columns:1fr;gap:10px;align-content:start}
.shot.empty{border-style:dashed;display:flex;align-items:center;justify-content:center}
.shot.empty span{font-family:var(--plate);font-size:9px;letter-spacing:.16em;color:#3A3A3E}

/* THE CLOSE IS A NOTE ON STEP FOUR, NOT A FIFTH STEP.
   Same indent, same body size, and a red rule down its left edge instead of a
   number. It has to look subordinate, because that is its whole argument. */
.close-step{border-bottom:0}
.close-step .step-body{border-left:1px solid var(--grease);padding-left:22px}
.close-step p{font-size:16px;line-height:1.62;color:var(--copy);max-width:60ch;margin-top:10px}
.close-step .plate-lab{color:var(--grease)}
@media (max-width:1100px){ .step{grid-template-columns:64px minmax(0,1fr)} .stack{display:none} }
@media (max-width:820px){ .step{grid-template-columns:1fr;gap:10px} }

/* the stack was taller than the copy beside it, so the step read as a picture
   with a caption. two up keeps it a strip: a reference beside the text, not
   the subject of the row. */
.step{grid-template-columns:64px minmax(0,1fr) 300px}
.stack{grid-template-columns:1fr 1fr;gap:8px}

/* ---------------------------------------------------------------- FEATURED
   The selects view is not the roll. It is an edit, so it gets a layout that
   makes an argument: two pieces carry the page, the rest sit to the right and
   along the bottom. Columns cannot span, so this view is a grid.
   Every card still keeps its own aspect. Nothing is cropped to fit a cell. */
.sheet.featured{columns:auto !important;display:grid !important;
  grid-template-columns:repeat(3,minmax(0,1fr));gap:46px 46px;grid-auto-flow:dense}
.sheet.featured .frame{margin:0;break-inside:auto}
.sheet.featured .frame.xl{grid-column:span 2}
@media (max-width:1100px){
  .sheet.featured{grid-template-columns:repeat(2,minmax(0,1fr));gap:34px}
  .sheet.featured .frame.xl{grid-column:span 2}
}
@media (max-width:620px){
  .sheet.featured{grid-template-columns:1fr}
  .sheet.featured .frame.xl{grid-column:span 1}
}

/* ------------------------------------------------------- THE DELIVERABLE
   The process was describing what happens and never naming what you walk
   away with, which is the difference between a description and an offer.
   Plate register, because it is a fact about the engagement, not a claim
   about the work. Sits under the copy and above the next step. */
.page.bare{padding:clamp(30px,4vw,48px) 0 0}
.page.bare + .steps{margin-top:clamp(20px,2.4vw,30px)}
.page.bare .plate-lab{display:block}
.gets{margin-top:20px;padding-top:14px;border-top:1px solid var(--rule);max-width:60ch}
.gets .plate-lab{display:block;margin-bottom:7px;color:var(--grease)}
.step .gets p{font-size:15px;line-height:1.6;color:var(--paper)}

/* the bible is twelve sections and the deck is seven pieces. A sentence
   listing twelve nouns is unreadable; a column of them is a contents page,
   which is what it actually is. */
.gets-list{list-style:none;margin:12px 0 0;padding:0;
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:5px 26px}
.gets-list li{font-family:var(--plate);font-size:10px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--silver);
  border-top:1px solid var(--rule);padding-top:6px}
@media (max-width:640px){ .gets-list{grid-template-columns:1fr} }

/* ---------------------------------------------------------------- BOOKING
   Booking is the primary action on the contact page, so it gets the filled
   button and the form gets the outlined one. Same geometry as .send, so the
   two read as one family with a clear hierarchy between them, rather than as
   two unrelated controls. */
.book{display:flex;align-items:center;flex-wrap:wrap;gap:10px 18px;margin:26px 0 0}
.bookbtn{display:inline-flex;align-items:center;gap:12px;cursor:pointer;
  background:var(--paper);border:1px solid var(--paper);color:var(--ground);
  font-family:var(--plate);font-size:10px;letter-spacing:.15em;text-transform:uppercase;
  padding:14px 20px;text-decoration:none;
  transition:background .18s ease,border-color .18s ease,color .18s ease}
.bookbtn:hover{background:var(--grease);border-color:var(--grease);color:var(--paper)}
.bookbtn i{width:6px;height:6px;background:var(--grease);flex:none;transition:background .18s ease}
.bookbtn:hover i{background:var(--paper)}
.bookbtn[data-busy]{opacity:.55;pointer-events:none}
.booknote{font-family:var(--plate);font-size:10px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--silver)}

/* the two paths are alternatives, not a sequence, so the divider says or */
.orline{display:flex;align-items:center;gap:16px;margin:32px 0 4px;color:var(--silver)}
.orline::before,.orline::after{content:"";height:1px;background:var(--rule);flex:1}
.orline span{font-family:var(--plate);font-size:9px;letter-spacing:.2em;text-transform:uppercase}
.done-book{display:inline-block;margin-top:14px;font-size:17px}


/* ============================================================ MOBILE PASS
   Four defects, three of them mine, found on a 390px viewport.

   1. THE MENU PANEL HAD NO GROUND.
   `header` carries `backdrop-filter:blur(10px)`, and a filter makes an
   element the containing block for its `position:fixed` descendants. So
   `nav.open{position:fixed;inset:64px 0 0}` was resolving against a 65px
   tall header instead of the viewport: a panel one pixel high, with its
   links overflowing over the page and no background behind them. The rule
   was right and the containing block was not. Absolute against the header
   it is already trapped in, with a real height, fixes it without moving the
   nav out of the header.

   2. THE PROCESS TEXT WAS ONE WORD PER LINE.
   `.step{grid-template-columns:64px minmax(0,1fr) 300px}` sits later in this
   file than the media queries that collapse it, so at 390px the row still
   reserved 64 + 300 and left 26px for the copy. The page rendered 25,000px
   tall. Same failure as the filter bug: a later unconditional rule beating
   an earlier conditional one. Responsive overrides go last, always.

   3. THE SELECTS PLACEMENT LEAKED ONTO THE PHONE.
   Explicit grid coordinates were emitted as inline `grid-column`, and inline
   beats every media query, so the one column mobile rule never applied and
   the edit stayed jammed into a broken two column arrangement. The
   coordinates now ride in as custom properties, which place nothing on their
   own, and only get read above the breakpoint.

   4. THE HUD IS TOO MUCH AT 390px.
   Four mono tags floating on every frame, fifty frames deep, is texture on a
   desktop sheet and noise on a phone. The plate under the card already
   carries the number, the title and the year, so the overlay is repeating
   itself at the size where there is least room to repeat. The plate stays,
   because it is the caption. The overlay goes. */

@media (min-width:1101px){
  .sheet.featured .frame[style*="--gc"]{grid-column:var(--gc);grid-row:var(--gr,auto)}
}

@media (max-width:680px){
  nav.open{position:absolute;inset:auto;top:100%;left:0;right:0;
    height:calc(100vh - 64px);height:calc(100dvh - 64px);
    overflow-y:auto;background:var(--ground);z-index:11}
  body.navlock{overflow:hidden}

  .hud{display:none}

  .step{grid-template-columns:1fr;gap:14px}
  .step .n{padding-top:0}
  .stack{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:18px}
  .gets-list{grid-template-columns:1fr}

  .filters{gap:8px 12px}
  .chip{padding:8px 12px}
}

/* ================================================= THE CAPTURES, ENLARGED
   The strip was 300px in the corner, which made every capture a postage
   stamp: a twelve row table and a four month waterfall reduced to the same
   grey smudge. The captures ARE the evidence for the claim each step makes,
   so they get half the row. Text left, gallery right, roughly even.

   Captures also keep their own proportions now (build.py reads each PNG's
   IHDR and writes the real ratio inline), so a tall document stays tall and
   a wide diagram stays wide. One or two captures run full width of the
   column; three or more fall back to two up. */
@media (min-width:1101px){
  .step{grid-template-columns:56px minmax(0,1fr) minmax(380px,0.95fr);
    gap:clamp(26px,3vw,46px);padding:clamp(40px,5vw,66px) 0}
  .stack{margin-top:4px}
}
.stack{display:grid;grid-template-columns:1fr;gap:12px;align-content:start}
.stack.grid2{grid-template-columns:1fr 1fr;gap:10px}
/* an odd count leads with its widest capture across the whole column */
.stack.grid2 .shot:first-child:nth-last-child(odd){grid-column:1 / -1}

/* responsive overrides go LAST. this file has now produced the same bug
   twice by putting an unconditional rule after a conditional one. */
@media (max-width:1100px){
  .step{grid-template-columns:56px minmax(0,1fr)}
  .stack{display:grid;grid-template-columns:1fr 1fr;margin-top:20px}
}
@media (max-width:680px){
  .step{grid-template-columns:1fr;gap:14px}
  .step .n{padding-top:0}
  .stack,.stack.grid2{grid-template-columns:1fr;margin-top:18px}
  .gets-list{grid-template-columns:1fr}
}

/* No width cap on a tall capture. Capping it left an empty half column
   beside it, which read as a grey block sitting in the layout. A capture
   that does not pair gets the whole column. */

/* ================================================ THE CROP BUG, ACTUAL CAUSE
   The capture frame set `aspect-ratio` on the DIV and let the image size
   itself inside it. Chromium resolved that to the same number every time,
   which is why four widths of testing said the layout was exact. Safari did
   not, so the box and the picture disagreed: the picture got clipped at the
   bottom and the frame's own background showed through underneath as a grey
   block. Every fix so far was aimed at the crops, and the crops were never
   the problem.

   There is now no aspect-ratio anywhere in this component. The <img> carries
   real width and height attributes, which is how a browser has reserved
   space for an image since before CSS had opinions, and the frame is a
   border drawn around whatever height the image reports. Nothing can clip,
   because nothing constrains the height. Nothing can show grey, because the
   frame has no background to show. */
.shot{aspect-ratio:auto !important;height:auto !important;
  background:none !important;overflow:visible !important;
  border:1px solid var(--rule);display:block;min-height:0}
.shot img{width:100% !important;height:auto !important;
  aspect-ratio:auto !important;object-fit:fill !important;display:block}
.stack{align-items:start}

/* Every step keeps the same shape: text left, gallery right, squared to each
   other. A lone capture stays in its column with the rest. */
