/* ──────────────────────────────────────────────────────────────
   Retouching portfolio

   One visual world, deliberately: a neutral ground the visitor
   sets themselves with the slider in the bar. Every ink and rule
   colour is solved at runtime from that ground for a fixed
   contrast ratio (see applyGround in app.js), so there is no
   light/dark pair to keep in sync — the ground IS the theme.
   The values below are the white-ground defaults, used for the
   first paint before the script runs.
   ────────────────────────────────────────────────────────────── */

:root{
  --bg:#ffffff;
  /* Frosted glass, for every piece of chrome that can sit over a
     photograph: the ground at 52% over a blur of what lies beneath.
     --glass is recomputed with the ground; GLASS in app.js. */
  --glass:rgba(255,255,255,0.52);
  --frost:blur(20px);
  /* A very subtle grain on the glass: desaturated fractal noise, tiled,
     blended as overlay so it is lightness-neutral on any ground. The
     opacity is the whole of its strength. Dom asked, 18 September 2026. */
  --grain:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n' x='0' y='0' width='100%25' height='100%25'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>");
  --grain-opacity:.09;
  --ink:#3c3c3c;      /* 11.0:1  — headings, body */
  --ink-2:#747474;    /*  4.8:1  — captions, notes */
  --ink-3:#9a9a9a;    /*  2.8:1  — quiet marks */
  --rule-2:#bcbcbc;   /*  1.9:1  — slider track, focus */
  --rule:#e3e3e3;     /* 1.28:1  — hairlines */
  /* The canvas behind a full screen frame steps a fixed distance in L*
     from the ground instead — 5, 9 and 35 — see CANVAS in app.js. */
  --canvas-minor:#f0f0f0;
  --canvas-major:#e5e5e5;
  --canvas-mark:#9e9e9e;

  /* --pad is the text gutter. --pad-l/r add the side safe-area insets,
     which are zero in portrait and clear the notch in landscape. Frames
     cancel only --pad, so they bleed to the usable edge in both. */
  --pad:clamp(16px,2.6vw,34px);
  --pad-l:calc(var(--pad) + env(safe-area-inset-left,0px));
  --pad-r:calc(var(--pad) + env(safe-area-inset-right,0px));
  --gap:clamp(10px,1.6vw,20px);
  --bar:54px;

  --ease:cubic-bezier(.2,.6,.3,1);
  --tone:180ms linear;

  --sans:"Archivo",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  color-scheme:light;
}
@media (min-width:800px){ :root{ --bar:60px } }

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

html{
  background:var(--bg);
  -webkit-text-size-adjust:100%;
  padding-top:env(safe-area-inset-top,0px);
  padding-bottom:env(safe-area-inset-bottom,0px);
}
html,body{ margin:0 }

body{
  background:var(--bg);
  color:var(--ink);
  font-family:var(--sans);
  font-size:15px;
  line-height:1.55;
  letter-spacing:-.004em;
  font-weight:400;
  -webkit-font-smoothing:antialiased;
  transition:background-color var(--tone),color var(--tone);
}

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

/* Class rules below set display:flex on the menu and the full screen
   view, which would otherwise out-specify the hidden attribute. */
[hidden]{ display:none !important }

/* While the full screen view is open, two fingers running past the
   edge must not become the browser's back swipe — they are panning
   the canvas. app.js cancels every wheel event in the view, which is
   what Safari goes by; this is what Chrome and Firefox go by. Dom
   asked on 18 September 2026. */
html[data-lb="true"],html[data-lb="true"] body{ overscroll-behavior:none }
::selection{ background:var(--ink); color:var(--bg) }

/* Focus rings only after a Tab: the browser's own heuristic shows one
   after any key, so a Space to swap or an R for the rulers drew a box
   round whichever button held the focus. app.js sets data-tabbing on
   Tab and clears it on any pointer press. Keyboard navigators keep
   their rings. Dom, 18 September 2026. */
:focus-visible{ outline:none }
html[data-tabbing="true"] :focus-visible{ outline:1px solid var(--ink); outline-offset:3px }

/* Navigation carries its target in data-go rather than href, so the
   browser draws no address bubble on hover (see `go` in app.js). Give
   it back what a real link has by default; the component rules below
   take the underline away again where they always did. */
[data-go]{ cursor:pointer; text-decoration:underline }

.skip{
  position:absolute; left:-9999px; top:0; z-index:200;
  background:var(--bg); color:var(--ink);
  padding:12px 16px; border:1px solid var(--ink);
  font-family:var(--mono); font-size:11px; letter-spacing:.14em; text-transform:uppercase;
}
.skip:focus{ left:var(--pad-l); top:calc(env(safe-area-inset-top,0px) + 8px) }

/* ── Shared type ─────────────────────────────────────────────── */

.eyebrow{
  margin:0;
  font-family:var(--mono); font-weight:400; font-size:10.5px;
  letter-spacing:.2em; text-transform:uppercase; color:var(--ink-2);
  transition:color var(--tone);
}

/* ── Glass grain ─────────────────────────────────────────────── */

/* Every glass surface carries the grain as a layer of its own, under
   its content: the bar and the menu are stacking contexts, so a
   negative z-index sits the layer above their background and beneath
   their type; the sheet in the full screen view has no content and is
   clipped with it. */
.bar::after,.menu::after,.lb__glass::after{
  content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background-image:var(--grain);
  opacity:var(--grain-opacity);
  mix-blend-mode:overlay;
}

/* The glass can be switched off — the Glass button in the full screen
   bar, remembered per visitor — for the original look: solid ground,
   no blur, no grain, everywhere at once. Dom asked to be able to
   compare the two, 18 September 2026. */
html[data-glass="off"] .bar,html[data-glass="off"] .menu,html[data-glass="off"] .lb__glass{
  background:var(--bg);
  -webkit-backdrop-filter:none; backdrop-filter:none;
}
html[data-glass="off"] .bar::after,html[data-glass="off"] .menu::after,html[data-glass="off"] .lb__glass::after{ display:none }


/* ── Top bar ─────────────────────────────────────────────────── */

.bar{
  position:fixed; top:0; left:0; right:0; z-index:80;
  display:grid; grid-template-columns:auto 1fr auto; align-items:center;
  gap:clamp(10px,3vw,32px);
  height:calc(var(--bar) + env(safe-area-inset-top,0px));
  padding:env(safe-area-inset-top,0px) var(--pad-r) 0 var(--pad-l);
  background:var(--glass);
  -webkit-backdrop-filter:var(--frost); backdrop-filter:var(--frost);
  border-bottom:1px solid var(--rule);
  transition:transform 320ms var(--ease),background-color var(--tone),border-color var(--tone);
}
.bar[data-tucked="true"]{ transform:translateY(-100%) }

.mark{
  font-size:12px; font-weight:500; letter-spacing:.26em; text-transform:uppercase;
  color:var(--ink); text-decoration:none; white-space:nowrap;
  transition:color var(--tone);
}
@media (max-width:400px){ .mark{ letter-spacing:.16em; font-size:11px } }

.menu-btn{
  appearance:none; background:none; border:0; cursor:pointer; color:var(--ink);
  font-family:var(--mono); font-size:10.5px; letter-spacing:.2em; text-transform:uppercase;
  padding:12px 0; transition:color var(--tone);
}

/* ── Ground slider ───────────────────────────────────────────── */

.tone{
  display:flex; align-items:center; gap:10px;
  justify-self:center; width:min(100%,280px);
}
/* Colour mode: the ground is a colour set by hue, saturation and value.
   The mode switch is the first thing in the block; the H and S sliders
   appear only in colour mode, and the main slider then sets value.
   Each track shows what it runs through — a spectrum, grey to the hue,
   black to the colour — the one place the interface itself is allowed
   colour, by Dom's amendment of 18 September 2026. */
.tone .tone__mode{ font-family:var(--mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase; white-space:nowrap }
.tone__hsv{ display:none; flex:1.4; gap:8px; min-width:0 }
html[data-tone="colour"] .tone__hsv{ display:flex }
html[data-tone="colour"] .tone{ width:min(100%,600px) }
html[data-tone="colour"] .tone__tick{ display:none }
.tone__key{ font-family:var(--mono); font-size:9px; letter-spacing:.1em; color:var(--ink-3); margin-right:4px }
/* The colour by code: a field that reads the ground as hex and takes
   any code back — click, type, Enter. Dom asked on 18 September 2026. */
.tone__code{
  appearance:none; background:none; border:0; border-bottom:1px solid var(--rule-2); border-radius:0;
  font-family:var(--mono); font-size:10px; letter-spacing:.06em; color:var(--ink); text-transform:none;
  width:8ch; padding:2px 0 3px; margin:0; flex:0 0 auto;
  transition:border-color 160ms var(--ease),color var(--tone);
}
.tone__code:focus{ outline:none; border-bottom-color:var(--ink) }
.tone__code[data-bad="true"]{ border-bottom-color:var(--ink); text-decoration:line-through }
@media (max-width:600px){ .tone__code{ width:7ch } }
.range--h::-webkit-slider-runnable-track{ height:3px; background:linear-gradient(to right,hsl(0 100% 50%),hsl(60 100% 50%),hsl(120 100% 50%),hsl(180 100% 50%),hsl(240 100% 50%),hsl(300 100% 50%),hsl(360 100% 50%)) }
.range--h::-moz-range-track{ height:3px; background:linear-gradient(to right,hsl(0 100% 50%),hsl(60 100% 50%),hsl(120 100% 50%),hsl(180 100% 50%),hsl(240 100% 50%),hsl(300 100% 50%),hsl(360 100% 50%)) }
.range--s::-webkit-slider-runnable-track{ height:3px; background:var(--track-s,var(--rule-2)) }
.range--s::-moz-range-track{ height:3px; background:var(--track-s,var(--rule-2)) }
html[data-tone="colour"] .range--main::-webkit-slider-runnable-track{ height:3px; background:var(--track-v,var(--rule-2)) }
html[data-tone="colour"] .range--main::-moz-range-track{ height:3px; background:var(--track-v,var(--rule-2)) }
.range--h::-webkit-slider-thumb,.range--s::-webkit-slider-thumb,html[data-tone="colour"] .range--main::-webkit-slider-thumb{ margin-top:-5px }
@media (max-width:600px){ .tone__key{ display:none } }
.tone__label,.tone__val{
  font-family:var(--mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--ink-3); white-space:nowrap; transition:color var(--tone);
}
.tone__val{ font-variant-numeric:tabular-nums; min-width:3ch; text-align:right }
.tone__label{ display:none }
@media (min-width:880px){ .tone__label{ display:block } }
@media (max-width:420px){ .tone__val{ display:none } }

/* The track carries a detent at L* 50 — an 18% grey card, sRGB 119.
   The mark sits below the track so the thumb never hides it. */
.tone__slot{ position:relative; flex:1; display:flex; align-items:center; min-width:0 }
.tone__tick{
  position:absolute; left:50%; top:calc(50% + 7px);
  width:1px; height:5px; transform:translateX(-50%);
  background:var(--rule-2); pointer-events:none;
  transition:background-color 160ms var(--ease),height 160ms var(--ease);
}
.tone[data-neutral="true"] .tone__tick{ background:var(--ink); height:8px }
.tone[data-neutral="true"] .tone__val{ color:var(--ink) }

.range{
  -webkit-appearance:none; appearance:none;
  background:none; border:0; margin:0; padding:0;
  width:100%; height:30px; cursor:pointer; accent-color:var(--ink);
}
.range::-webkit-slider-runnable-track{ height:1px; background:var(--rule-2); border:0 }
.range::-moz-range-track{ height:1px; background:var(--rule-2); border:0 }
.range::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none;
  width:13px; height:13px; margin-top:-6px;
  border:0; border-radius:50%; background:var(--ink);
  transition:transform 140ms var(--ease);
}
.range::-moz-range-thumb{
  width:13px; height:13px; border:0; border-radius:50%; background:var(--ink);
  transition:transform 140ms var(--ease);
}
.range:active::-webkit-slider-thumb{ transform:scale(1.3) }
.range:active::-moz-range-thumb{ transform:scale(1.3) }
.range:focus{ outline:none }
html[data-tabbing="true"] .range:focus-visible::-webkit-slider-thumb{ box-shadow:0 0 0 1px var(--bg),0 0 0 2px var(--ink) }
html[data-tabbing="true"] .range:focus-visible::-moz-range-thumb{ box-shadow:0 0 0 1px var(--bg),0 0 0 2px var(--ink) }

/* ── Menu overlay ────────────────────────────────────────────── */

.menu{
  position:fixed; inset:0; z-index:70;
  background:var(--glass);
  -webkit-backdrop-filter:var(--frost); backdrop-filter:var(--frost);
  display:flex; flex-direction:column;
  justify-content:center;
  /* `safe` falls back to start rather than clipping the top links
     when the viewport is too short to centre them. */
  justify-content:safe center;
  gap:clamp(20px,5vh,46px);
  padding:calc(var(--bar) + env(safe-area-inset-top,0px) + 4vh) var(--pad-r) calc(4vh + env(safe-area-inset-bottom,0px)) var(--pad-l);
  overflow-y:auto;
  transition:background-color var(--tone);
}
.menu__list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column }
.menu__link{
  display:flex; align-items:baseline; gap:14px;
  text-decoration:none; color:var(--ink);
  font-size:clamp(28px,7vw,60px); font-weight:300; letter-spacing:-.03em; line-height:1.18;
  padding:2px 0;
  transition:color var(--tone),opacity 160ms var(--ease);
}
.menu__link[aria-current="page"]{ color:var(--ink-2) }
.menu__n{
  font-family:var(--mono); font-size:10px; letter-spacing:.14em; color:var(--ink-3);
  font-variant-numeric:tabular-nums;
}
.menu__foot{
  display:flex; flex-wrap:wrap; gap:6px 22px;
  border-top:1px solid var(--rule); padding-top:16px;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-2);
}
.menu__foot a{ color:inherit }
.menu__fs{
  appearance:none; background:none; border:0; padding:0; margin:0;
  font:inherit; color:inherit; cursor:pointer;
  text-decoration:underline; text-underline-offset:3px; text-decoration-thickness:1px;
  transition:color 160ms var(--ease);
}
@media (hover:hover){ .menu__fs:hover{ color:var(--ink) } }
@media (hover:hover){ .menu__link:hover{ opacity:.55 } }
@media (prefers-reduced-motion:no-preference){
  .menu[data-menu-open="true"] .menu__link{ animation:rise 420ms var(--ease) both }
}
@keyframes rise{ from{ opacity:0; transform:translateY(14px) } to{ opacity:1; transform:none } }

/* ── Frames: the before / after unit ─────────────────────────── */

.app{
  padding:calc(var(--bar) + var(--gap)) var(--pad-r) 0 var(--pad-l);
}

.frame{ margin:0 }

.stage{
  display:block; position:relative; width:100%; overflow:hidden;
  margin:0; padding:0; border:0; background:none;
  aspect-ratio:var(--ar,3/2);
  cursor:pointer; touch-action:manipulation;
  -webkit-tap-highlight-color:transparent;
  -webkit-touch-callout:none;   /* no Save Image sheet on a long press: a tap or a pan is what a finger means here */
  /* Without this a mouse drag starts a native image drag, which fires
     pointercancel and freezes a pan after its first few pixels. */
  -webkit-user-select:none; user-select:none;
}
.shot{ -webkit-user-drag:none; user-drag:none }
.shots{ position:absolute; inset:0; display:block }
.shot{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:contain;
  opacity:0;
  transition:opacity 170ms var(--ease);
}
.shot.is-on{ opacity:1 }
.frame--fit .stage{ aspect-ratio:auto; height:var(--h,60vh); height:var(--h,60svh) }

.cap{
  display:flex; align-items:center; gap:14px;
  /* Frames run to the edge, so the caption carries the gutter that
     keeps its text off the screen edge. */
  padding:9px var(--pad-r) 0 var(--pad-l);
  font-family:var(--mono); font-size:10.5px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--ink-2);
  transition:color var(--tone);
}
.cap__id{ font-variant-numeric:tabular-nums }
.cap__state{ margin-left:auto; color:var(--ink); transition:color var(--tone) }

.icon{
  appearance:none; background:none; border:0; padding:4px; margin:-4px -5px -4px 0;
  cursor:pointer; color:var(--ink-3); line-height:0;
  transition:color 160ms var(--ease);
}
.icon svg{ display:block; width:13px; height:13px }
@media (hover:hover){ .icon:hover{ color:var(--ink) } }

/* ── Home ────────────────────────────────────────────────────── */

/* The hero fits the first screen. It cancels the page gutter and the top
   gap so it can meet the bar and both edges — but if the frame's own
   ratio would push it below the fold, it is sized to the height left
   under the bar (with room for its caption) and centred instead. Nothing
   is cropped either way: the whole photograph is always shown, and a
   square or portrait frame simply leaves ground on both sides. Dom
   asked for the height cap on 18 September 2026; before that the hero
   ran full width at any height. */
.hero{ margin-inline:calc(var(--pad) * -1); margin-top:calc(var(--gap) * -1) }
.hero .frame{ width:100% }
.hero .stage{
  --hero-room:44px;   /* the caption row and a little air above the fold */
  width:min(100%, calc((100vh - var(--bar) - env(safe-area-inset-top,0px) - var(--hero-room)) * (var(--ar,3/2))));
  margin-inline:auto;
}
/* svh is the height with the phone's browser chrome showing — the true
   first screen. vh above is the fallback where it is unsupported. */
@supports (height:1svh){
  .hero .stage{ width:min(100%, calc((100svh - var(--bar) - env(safe-area-inset-top,0px) - var(--hero-room)) * (var(--ar,3/2)))) }
}

/* A true heading for search engines and screen readers that the page
   does not draw. It states what the page is and nothing more — this is
   not a place to bury keywords. */
.seo-heading{
  position:absolute; width:1px; height:1px;
  margin:-1px; padding:0; border:0;
  clip-path:inset(50%); overflow:hidden; white-space:nowrap;
}

.band{ padding-block:clamp(44px,8vh,92px) 0 }
.band__head{
  display:flex; align-items:baseline; justify-content:space-between; gap:16px;
  padding-bottom:calc(var(--gap) + 2px);
}

/* Columns, not rows: frames of mixed shape pack down each column
   instead of leaving a void under every short one. They are real
   columns, dealt by gridHTML() in app.js, shortest first — not CSS
   multicol, whose balancing pushed the fifth of five equal squares into
   a phantom column off the right edge of the page. --cols is what the
   script reads; change the counts here, not there. */
:root{ --cols:1 }
@media (min-width:700px){ :root{ --cols:2 } }
@media (min-width:1200px){ :root{ --cols:3 } }
.grid{ display:flex; align-items:flex-start; gap:var(--gap); margin-inline:calc(var(--pad) * -1) }
.grid__col{ flex:1 1 0; min-width:0 }
.grid .frame{ margin-bottom:calc(var(--gap) * 1.6) }

.index__list{ list-style:none; margin:0; padding:0; border-top:1px solid var(--rule) }
.index__link{
  display:grid; grid-template-columns:1fr auto; gap:2px 22px;
  align-items:baseline;
  padding:clamp(15px,2.4vw,24px) 0;
  border-bottom:1px solid var(--rule);
  text-decoration:none; color:var(--ink);
  transition:border-color var(--tone),color var(--tone);
}
/* Every cell is placed explicitly. Left to auto-placement, the count
   takes the wide column first — an item with a definite row is placed
   before the auto ones — and the category name gets squeezed into the
   narrow one meant for the count. */
.index__name{
  grid-column:1; grid-row:1;
  margin:0;   /* it is an h2 now; the layout wants none of the default */
  font-size:clamp(25px,4.6vw,44px); font-weight:300; letter-spacing:-.03em; line-height:1.06;
  transition:transform 200ms var(--ease);
}
.index__note{
  grid-column:1; grid-row:2;
  color:var(--ink-2); font-size:13.5px; max-width:54ch;
  transition:color var(--tone);
}
.index__n{
  grid-column:2; grid-row:1 / span 2; align-self:center;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.14em; color:var(--ink-3);
  font-variant-numeric:tabular-nums; transition:color var(--tone);
}
@media (hover:hover){
  .index__link:hover .index__name{ transform:translateX(7px) }
  .index__link:hover .index__n{ color:var(--ink) }
}
@media (prefers-reduced-motion:reduce){ .index__name{ transition:none } }

/* ── Page heads, category nav ────────────────────────────────── */

.head{ padding-block:clamp(18px,4vh,46px) clamp(22px,4vh,42px); max-width:72ch }
.head h1{
  margin:10px 0 0;
  font-size:clamp(30px,6vw,58px); font-weight:300; letter-spacing:-.035em; line-height:1.04;
  text-wrap:balance;
}
.lede{ margin:14px 0 0; color:var(--ink-2); font-size:clamp(14px,1.6vw,17px); max-width:56ch; transition:color var(--tone) }

.catnav{
  display:flex; justify-content:space-between; gap:16px;
  border-top:1px solid var(--rule);
  margin-top:clamp(44px,8vh,92px); padding-top:18px;
}
.catnav a{
  color:var(--ink); text-decoration:none;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.16em; text-transform:uppercase;
}
.catnav span{ color:var(--ink-3); font-family:var(--mono); font-size:10.5px; letter-spacing:.16em; text-transform:uppercase }

/* ── Contact ─────────────────────────────────────────────────── */

.contact{ display:grid; gap:clamp(34px,6vw,72px); grid-template-columns:1fr; padding-bottom:20px }
@media (min-width:860px){ .contact{ grid-template-columns:minmax(0,1.3fr) minmax(220px,.7fr) } }

.form{ display:flex; flex-direction:column; gap:22px; max-width:56ch }
.field{ display:flex; flex-direction:column; gap:7px }
.field > span{
  font-family:var(--mono); font-size:10px; letter-spacing:.18em; text-transform:uppercase; color:var(--ink-2);
  transition:color var(--tone);
}
.field input,.field textarea{
  appearance:none; background:none; color:var(--ink);
  border:0; border-bottom:1px solid var(--rule-2); border-radius:0;
  font-family:var(--sans); font-size:16px; line-height:1.5;
  padding:6px 0 9px; width:100%;
  transition:border-color 160ms var(--ease),color var(--tone);
}
.field textarea{ resize:vertical; min-height:110px }
.field input:focus,.field textarea:focus{ outline:none; border-bottom-color:var(--ink) }
.field input::placeholder,.field textarea::placeholder{ color:var(--ink-3) }

.submit{
  align-self:flex-start;
  appearance:none; background:none; cursor:pointer;
  border:1px solid var(--ink); color:var(--ink);
  font-family:var(--mono); font-size:10.5px; letter-spacing:.2em; text-transform:uppercase;
  padding:13px 26px;
  transition:background-color 160ms var(--ease),color 160ms var(--ease),border-color var(--tone);
}
@media (hover:hover){ .submit:hover{ background:var(--ink); color:var(--bg) } }

.note{ margin:0; font-size:13px; color:var(--ink-2); max-width:48ch; transition:color var(--tone) }

.facts{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:20px }
.facts div{ display:flex; flex-direction:column; gap:4px }
.facts dt{
  font-family:var(--mono); font-size:10px; letter-spacing:.18em; text-transform:uppercase; color:var(--ink-3);
  transition:color var(--tone);
}
.facts dd{ margin:0; font-size:14.5px; color:var(--ink); transition:color var(--tone) }
.facts a{ color:inherit }

a{ color:var(--ink); text-decoration-thickness:1px; text-underline-offset:3px }

/* ── Footer ──────────────────────────────────────────────────── */

.foot{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:10px 26px;
  border-top:1px solid var(--rule);
  margin-top:clamp(48px,9vh,110px);
  padding:20px 0 calc(28px + env(safe-area-inset-bottom,0px));
  font-family:var(--mono); font-size:10.5px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--ink-2);
  transition:color var(--tone),border-color var(--tone);
}
.foot a{ color:inherit; text-decoration:none }
@media (hover:hover){ .foot a:hover{ color:var(--ink) } }
.foot__end{ margin-left:auto; color:var(--ink-3) }

/* ── Full screen view ────────────────────────────────────────── */

/* The phone's insets — the notch at the top, the home indicator at the
   bottom — belong to the bars, not to the view: the bars carry them as
   extra padding and so run to the screen's edges, over anything the
   frame puts there. With the padding on the view instead, the frame
   showed through above the top bar. Dom found it on a phone, 18
   September 2026. */
.lb{
  position:fixed; inset:0; z-index:90;
  display:flex; flex-direction:column;
  background:var(--bg);
  transition:background-color var(--tone);
}
/* The bars sit above the stage and its one sheet of glass (.lb__glass),
   so a frame panned under them shows through softly rather than
   covering them. They carry no glass of their own: two panes meeting
   make a visible seam, since each blurs its own backdrop. */
.lb__top,.lb__bottom{
  position:relative; z-index:1;
  display:flex; align-items:center; gap:10px 16px;
  padding:8px var(--pad-r) 8px var(--pad-l);
  font-family:var(--mono); font-size:10.5px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--ink-2);
  transition:color var(--tone);
}
.lb__bottom{ flex-wrap:wrap; border-top:1px solid var(--rule); padding-block:10px calc(10px + env(safe-area-inset-bottom,0px)); transition:border-color var(--tone) }
.lb__top{ justify-content:space-between; border-bottom:1px solid var(--rule); padding-top:calc(8px + env(safe-area-inset-top,0px)); transition:color var(--tone),border-color var(--tone) }   /* the same hairline the bottom bar carries — Dom, 18 September 2026 */
/* The image's number, state and zoom sit in the top bar, between the
   count and Back; the bottom bar keeps the controls. Dom, 18 September
   2026. */
.lb__info{ display:flex; flex-wrap:wrap; align-items:center; gap:4px 16px; justify-content:center }
.lb__count{ font-variant-numeric:tabular-nums }
.lb__count #lb-index{ color:var(--ink) }
.lb__state{ color:var(--ink); transition:color var(--tone) }
.tone--lb{ margin-left:auto; width:min(230px,42vw); justify-self:auto }

.lb__stage{
  flex:1; min-height:0; position:relative;
  padding:var(--gap) env(safe-area-inset-right,0px) var(--gap) env(safe-area-inset-left,0px);
}
/* Two canvases sandwich the frame: a grid beneath it and rulers above
   it, both in the photograph's own pixel space with 0,0 at its top left
   corner, drawn by drawCanvas() in app.js. DOM order does the layering;
   neither takes pointer events, so every gesture still reaches the
   frame. Dom asked for the grid and the rulers on 18 September 2026. */
.lb__grid,.lb__ruler{ position:absolute; inset:0; width:100%; height:100%; pointer-events:none }
/* One sheet of frosted glass for all the chrome of the view — the top
   bar, both ruler strips and the bottom bar — with a hole cut where the
   picture shows, so the blur is computed once and there is no seam
   where one pane would meet another. It lives inside the stage, between
   the frame and the ruler canvas, and reaches up under the top bar and
   down under the bottom one (--lb-top, --lb-bottom, measured by
   sizeCanvases in app.js). The hole is the evenodd polygon's inner
   rectangle: the frame's box. Dom disliked the seam, 18 September
   2026. */
.lb__glass{
  position:absolute; left:0; right:0;
  /* A pixel past each bar's outer edge, which is off the screen, so no
     sub-pixel sliver of the frame shows at the screen's bottom edge. */
  top:calc(-1 * var(--lb-top,0px) - 1px); bottom:calc(-1 * var(--lb-bottom,0px) - 1px);
  pointer-events:none;
  background:var(--glass);
  -webkit-backdrop-filter:var(--frost); backdrop-filter:var(--frost);
  --band-top:calc(var(--lb-top,0px) + 1px + var(--ruler,0px));     /* the sheet starts a pixel above the bar */
  --band-left:calc(var(--ruler,0px) + env(safe-area-inset-left,0px));
  --band-bottom:calc(100% - var(--lb-bottom,0px) - 1px);
  clip-path:polygon(evenodd,
    0 0, 100% 0, 100% 100%, 0 100%, 0 0,
    var(--band-left) var(--band-top),
    var(--band-left) var(--band-bottom),
    100% var(--band-bottom),
    100% var(--band-top),
    var(--band-left) var(--band-top));
  transition:background-color var(--tone);
}
/* The frame's box starts at the inner corner of the rulers (--ruler is
   their depth, set by paintLb in app.js), so a frame at home sits with
   its top left corner exactly where the two 0 marks meet. */
.lb__stage .frame{
  position:absolute;
  inset:var(--ruler,0px) env(safe-area-inset-right,0px) var(--gap) calc(var(--ruler,0px) + env(safe-area-inset-left,0px));
}
.lb__stage .stage{ height:100% }
/* The frame's box starts inside the rulers, but the frame itself must
   run out of it — under the glass of the strips and the bars, and off
   the screen — or a pan just clips it at the box's edge and the glass
   has nothing beneath it. The grid's frames keep overflow:hidden. */
.lb__stage .stage{ overflow:visible }
/* Raw pointer events: the browser must not claim the gestures. */
.lb__stage .stage{ touch-action:none }
/* No focus ring round the full screen frame: a tap focuses its button
   and the next Space would draw a rectangle round the whole box, seen
   as a stray line above the bottom bar. The state readout is the
   feedback there; the grid's frames keep their ring. Dom, 18 September
   2026. */
.lb__stage .stage:focus-visible{ outline:none }
.lb__stage .shots{ transition:transform 180ms var(--ease); will-change:transform }
.lb__stage .stage{ cursor:grab }
.lb__stage .stage[data-panning="true"]{ cursor:grabbing }
/* The swap is instant in the view: the grid's short crossfade costs a
   phone a re-composite of two very large images on every tap. Dom, 19
   September 2026 — and his rule: a tap swaps, never delayed. */
.lb__stage .shot{ transition:none }
/* The eyedropper: a crosshair while it is on; the readout in the top
   bar, in the zoom's tone. */
.lb__stage[data-pick="true"] .stage{ cursor:crosshair }
.lb__pixel{ color:var(--ink-3); font-variant-numeric:tabular-nums; white-space:nowrap; transition:color var(--tone) }

/* The zoom readout, and the two text buttons beside it. The readout is
   one image pixel per screen pixel at 100%, the way a retoucher counts,
   so it reads about 15% at fit. The buttons are plain type that lift to
   full ink on hover, and the one that describes the current view stays
   lit. Dom asked for them on 18 September 2026. */
.lb__zoom{ color:var(--ink-3); font-variant-numeric:tabular-nums; min-width:4ch; transition:color var(--tone) }
.act,.lb__act{
  appearance:none; background:none; border:0; padding:0; margin:0;
  font:inherit; letter-spacing:inherit; text-transform:inherit; color:var(--ink-3); cursor:pointer;
  font-variant-numeric:tabular-nums;
  transition:color 160ms var(--ease);
}
@media (hover:hover){ .act:hover,.lb__act:hover{ color:var(--ink) } }
.act[aria-pressed="true"],.lb__act[aria-pressed="true"]{ color:var(--ink); text-decoration:underline; text-underline-offset:3px; text-decoration-thickness:1px }
/* The Background toggle, in a caption or the full screen bar: lit while
   the background shows; off, the cutout sits on the page's own ground. */
.cap__bg{ margin-left:2px }
/* The scroll toggle states a mode rather than a destination, so it is
   never underlined; and it is for devices with a scroll surface, so a
   phone never sees it. */
.lb__act--wheel{ margin-left:6px }
.lb__act--wheel[aria-pressed="true"]{ text-decoration:none }
.lb__act--glass{ margin-left:6px }
.lb__act--rulers{ margin-left:6px }
.lb__act--unit{ margin-left:6px; min-width:2.4ch; text-transform:none }   /* the unit as written: px, in, mm */
.lb__act--grid{ margin-left:6px }
.lb__act--fs{ margin-left:6px }
@media (pointer:coarse){ .lb__act--wheel{ display:none } }

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

/* ── Landscape on a phone ──────────────────────────────────────────
   Wide and very short: roughly 844 x 390. Height, not width, is the
   scarce axis here, so the chrome shrinks, type scales off vh, the
   grid takes more columns so a whole frame fits on screen at once,
   and the menu splits into two so every link stays reachable.
   ────────────────────────────────────────────────────────────────── */
@media (orientation:landscape) and (max-height:520px){
  :root{ --bar:44px; --gap:clamp(8px,1.2vw,14px) }

  .band{ padding-block:clamp(26px,7vh,52px) 0 }

  /* Two columns below 700px, three above, so frames stay near the
     height of the viewport instead of towering well past it. */
  :root{ --cols:2 }

  .head{ padding-block:clamp(10px,3vh,20px) clamp(12px,3vh,24px) }
  .head h1{ font-size:clamp(26px,7vh,44px) }
  .lede{ font-size:14px }

  .menu{ padding-block:calc(var(--bar) + env(safe-area-inset-top,0px) + 12px) 12px; gap:14px }
  .menu__list{ display:grid; grid-template-columns:repeat(2,1fr); column-gap:clamp(20px,4vw,56px) }
  .menu__link{ font-size:clamp(18px,4.4vh,28px) }
  .menu__foot{ padding-top:10px }

  .lb__top,.lb__bottom{ padding-block:4px }
  .lb__stage{ padding-block:5px }

  .foot{ margin-top:clamp(30px,8vh,64px); padding-top:16px }
}
@media (orientation:landscape) and (max-height:520px) and (min-width:700px){
  :root{ --cols:3 }
}
