/* lukeholman.co.uk: every style for the one page (and the 404), the computer layout first, then phones (900px and below), tablets, and Safari on a computer. */
/* Display face: Henrietta (Very Cool Studio), licensed (Luke, 13 Oct 2026): the web files from VC-Henrietta-V0.52.zip in Dropbox 5 BRAND/FONTS, renamed. */
@font-face{font-family:'LH Display'; src:url('../fonts/display-regular.woff2') format('woff2'); font-weight:400; font-display:swap}
@font-face{font-family:'LH Display'; src:url('../fonts/display-semibold.woff2') format('woff2'); font-weight:600; font-display:swap}
/* Lora (body) and Reenie Beanie (handwriting), hosted here rather than loaded from Google Fonts (entry 124): the same files Google serves, Latin letters only (every character on the site is in them), so visitors' browsers never contact Google. Both are under the SIL Open Font Licence. */
@font-face{font-family:'Lora'; src:url('../fonts/lora-regular.woff2') format('woff2'); font-weight:400; font-style:normal; font-display:swap}
@font-face{font-family:'Reenie Beanie'; src:url('../fonts/reenie-beanie.woff2') format('woff2'); font-weight:400; font-style:normal; font-display:swap}
/* Type system (Fonts exploration, round 11): Henrietta SemiBold 600 for the name and headings, Henrietta Regular 400 for lead, feature, nav and buttons; Lora for everything read; Reenie Beanie for the hand. */
:root{
  /* colour lab (Luke, 10 Oct 2026): blue #004995, accent #FFD79E (first tried #F9C16E). Before: ground #1F2F63, ink #F3F0E8, ink-soft #D6DAEA, muted #A9B1D1,
     hair #3A4A86, frame #F6F2E8, pencil #E9C96B, on-blue-soft #C7CDE4 */
  --ground:#004995; --ink:#F5EFE8; --ink-soft:#D3E0F7; --muted:#B8CDF1; --hair:#2666C0; --frame:#F8F1E8; --pencil:#FFD79E;
  --blue:#004995; --on-blue:#F5EFE8; --on-blue-soft:#C2D5F6; --chalk:#FFD79E; --dark:#004995; --on-dark:#F5EFE8; --on-dark-soft:#C2D5F6;
  --m:clamp(24px, 9.444vw, 136px); --gutter:32px;
  --display:'LH Display', Georgia, serif;
  --serif:'Lora', Georgia, serif;
  --sans:'Lora', Georgia, serif;
  --hand:'Reenie Beanie', 'Bradley Hand', cursive;
  --print-shadow:0 1px 2px rgba(28,27,25,.10), 0 30px 50px -30px rgba(28,27,25,.35);
  --lift-shadow:0 2px 4px rgba(28,27,25,.10), 0 44px 64px -30px rgba(28,27,25,.42);
  --ease:cubic-bezier(.2,.7,.2,1);
}
*{box-sizing:border-box}
:root{color-scheme:light}
html{scroll-behavior:smooth; scrollbar-color:var(--ink) var(--ground)}
body{margin:0; background:var(--ground); color:var(--ink); font-family:var(--serif); font-size:18px; line-height:1.6; -webkit-font-smoothing:antialiased; caret-color:var(--pencil)}
::selection{background:var(--pencil); color:var(--ground)}
a{color:inherit; text-decoration:none}
:focus-visible{outline:2px solid var(--pencil); outline-offset:4px; border-radius:1px}
img{display:block; max-width:100%; height:auto}
h1,h2,h3,p,figure,blockquote{margin:0}
.disp{font-family:var(--display); font-weight:600; line-height:1; letter-spacing:0}
.lbl{font-family:var(--display); font-weight:400; font-size:18px; line-height:1.2; letter-spacing:0}
.hand{font-family:var(--hand); color:var(--pencil); line-height:1}
.grid{display:grid; grid-template-columns:repeat(12, minmax(0,1fr)); column-gap:var(--gutter)}
.wrap{padding-left:var(--m); padding-right:var(--m)}
.small{font-family:var(--serif); font-size:14px; line-height:1.5; color:var(--muted)}

/* header (parked: option 1's, unchanged) */
.hdr{position:absolute; left:var(--m); right:var(--m); top:0; height:104px; display:flex; justify-content:flex-end; align-items:center; gap:40px; z-index:5}
.hdr nav{display:flex; gap:40px}
.hdr nav a{position:relative; padding:4px 0; font-family:var(--display); font-weight:400; font-size:18px; line-height:1.2; min-height:44px; display:inline-flex; align-items:center}
.hdr nav a::after{content:""; position:absolute; left:0; right:0; bottom:0; height:1.5px; background:var(--pencil); transform:scaleX(0); transform-origin:left; transition:transform .3s var(--ease)}
.hdr nav a:hover::after{transform:scaleX(1)}
.soc{display:flex; gap:14px}
.soc a{width:40px; height:40px; display:inline-flex; align-items:center; justify-content:center; color:var(--ink); transition:color .2s var(--ease)}
.soc a:hover{color:var(--pencil)}
.soc svg{width:24px; height:24px}
.soc svg.li{width:20px; height:20px}

/* hero: option 3's layout on paper */
#home{position:relative; min-height:100vh; min-height:100svh; padding:88px var(--m) calc(110px + clamp(0px, 62vh - 500px, 260px)); /* extra room below lifts the name block towards the middle (the underline hangs below the block, so short screens get a little lift too); the lift grows with screen height, and 900px and taller screens sit exactly as before */ display:flex; flex-direction:column; justify-content:center}
.hero-in{width:fit-content; margin:0 auto}
.title{display:grid; grid-template-columns:auto auto; column-gap:28px; align-items:end; text-align:left; width:fit-content}
.title h1{display:contents}
/* the name's heading as a grid of its own that shares the name's tracks (subgrid), so its two lines sit exactly where they
   did, but the heading keeps a box: with display:contents Safari left "Luke Holman" out of what VoiceOver reads (entry 132;
   Chrome kept it). Every box measured identical at seven sizes in Chrome and Safari's engine. */
@supports (grid-template-columns:subgrid){
  .title h1{display:grid; grid-column:1 / -1; grid-row:1 / span 2; grid-template-columns:subgrid; grid-template-rows:subgrid; align-items:end}
}
.title .l1{grid-column:1; grid-row:1}
.title .l2{grid-column:1 / span 2; grid-row:2}
.title .polaroid{grid-column:2; grid-row:1}
.title .line{display:block; line-height:.86}
.title .w{display:inline-block; white-space:nowrap; font-size:160px; font-weight:600}
.title .ch{font-style:normal; transition:opacity .1s ease-out}
.tw .tch{font-style:normal; opacity:0}
.tw .tch.on{opacity:1}
.tw .caret{height:.7em; width:.03em}
.caret{display:inline-block; width:.028em; height:.74em; background:var(--ink); vertical-align:-.02em; margin-left:.03em; animation:blink 1.1s ease-in-out infinite}
@keyframes blink{0%,18%{opacity:1} 45%,68%{opacity:0} 95%,100%{opacity:1}} /* a soft fade out and back, holding a moment at each end */
.role{display:block; margin:40px 0 0 .4em; font-size:42px; line-height:1.05; transform:rotate(-3.5deg); transform-origin:left center; white-space:nowrap}
.role .wd{display:inline-block}
.role .wd:nth-child(1){transform:translateY(2px) rotate(-1deg)} .role .wd:nth-child(2){transform:translateY(-2px) rotate(1.5deg)} .role .wd:nth-child(3){transform:translateY(1px)} .role .wd:nth-child(4){transform:translateY(-3px) rotate(-1deg)}
.polaroid{position:relative; flex:none; width:clamp(205px, 18.5vw, 270px); margin-bottom:.06em; padding:14px 14px 46px; background:var(--frame); box-shadow:var(--print-shadow); transform:rotate(3deg); z-index:2; transition:transform .5s var(--ease), box-shadow .5s var(--ease)}
.polaroid img{width:100%; aspect-ratio:1; object-fit:cover; filter:saturate(.92) contrast(.98)}
.polaroid::after{content:""; position:absolute; inset:0; box-shadow:inset 0 0 0 1px rgba(28,27,25,.05); pointer-events:none}
html.settled .polaroid:hover{transform:rotate(1.5deg) translateY(-6px); box-shadow:var(--lift-shadow)}
.phone-br{display:none}            /* a line break for phones only (index.html) */
.arrow{position:absolute; left:50%; bottom:36px; transform:translateX(-50%); width:48px; height:56px; display:flex; align-items:center; justify-content:center; color:var(--pencil)}
.arrow svg{width:24px; height:40px; overflow:visible; transform:rotate(-4deg)}
.arrow path{stroke-dasharray:60; stroke-dashoffset:60}
html.shown .arrow .a1{animation:draw .4s cubic-bezier(.3,.7,.3,1) .95s forwards}
html.shown .arrow .a2{animation:draw .25s cubic-bezier(.3,.7,.3,1) 1.3s forwards}
html.shown .arrow .a3{animation:draw .25s cubic-bezier(.3,.7,.3,1) 1.4s forwards}
/* about five bobs, then it rests (Luke, 14 Oct 2026, entry 135: the accessibility rules ask that nothing moves on its own for
   more than about five seconds); pointing at it makes it bob again (.bob, js/main.js), and when the pointer leaves it
   finishes the bob it is on and rests */
html.shown .arrow svg{animation:bob 1s 1.9s 5}
html.shown .arrow.bob svg{animation:bob 1s infinite}
html.shown #home.scrolled .arrow svg{animation:none}
@keyframes draw{to{stroke-dashoffset:0}}
/* a steady, even bounce on the beat: falls 8px gathering speed, rises easing off, no pause and no overshoot */
@keyframes bob{0%,100%{transform:rotate(-4deg) translateY(0); animation-timing-function:cubic-bezier(.5,0,.75,.6)} 50%{transform:rotate(-4deg) translateY(8px); animation-timing-function:cubic-bezier(.25,.4,.5,1)}}
/* the thread: a loose yellow line that picks up where the arrow tip left off and runs through the page */
#thread{position:absolute; left:0; top:0; width:100%; pointer-events:none; z-index:1; overflow:visible}
#thread path{fill:none; stroke:var(--pencil); stroke-width:2; stroke-linecap:round; stroke-linejoin:round}
/* smoothness (entry 54): the line is drawn in short pieces (js/main.js), so as you scroll only the piece the pen is on is
   repainted. The thread is faded as a whole, on a layer of its own, so the pieces join without a seam (each piece at .9
   would show a brighter dot where it overlaps the next). #threadPath is the whole line, kept unpainted for measuring. */
#thread{opacity:.9; will-change:opacity}
#threadPath{visibility:hidden}

/* about: option 1's book column with the working shots */
#about{position:relative; padding-top:128px; overflow-x:clip; min-height:780px}
.tumble{position:absolute; left:calc(var(--m) - 56px); top:72px; width:min(640px, 42vw); aspect-ratio:720/860; pointer-events:none}
.print{position:absolute; background:var(--frame); box-shadow:var(--print-shadow); pointer-events:auto; transition:transform .6s var(--ease), box-shadow .6s var(--ease)}
.print::after{content:""; position:absolute; inset:0; box-shadow:inset 0 0 0 1px rgba(28,27,25,.05); pointer-events:none}
.print img{width:100%; object-fit:cover; filter:saturate(.9) contrast(.98)}
.print:hover{box-shadow:var(--lift-shadow)}
.print{width:36%; padding:2.2% 2.2% 8%}
.print img{aspect-ratio:1; filter:saturate(.78) contrast(1.02) sepia(.08)}
.print{width:30%}
.p1{left:-8%; top:0; transform:rotate(-22deg); z-index:1}
.p2{left:18%; top:13%; transform:rotate(9deg); z-index:2}
.p3{left:46%; top:22%; transform:rotate(-6deg); z-index:3}
.p4{left:28%; top:46%; transform:rotate(16deg); z-index:4}
.pnote{position:absolute; margin:0; display:flex; align-items:flex-start; gap:6px; font-size:28px; line-height:1; color:var(--pencil); transform-origin:left center; pointer-events:none; white-space:nowrap; z-index:6}
.pnote svg{width:26px; height:30px; overflow:visible; flex:none}
.pnote svg path{stroke-dasharray:60; stroke-dashoffset:60; transition:stroke-dashoffset .6s cubic-bezier(.3,.7,.3,1) var(--ad, 2.6s)}
.pnote span{clip-path:inset(-30% 102% -40% -4%); transition:clip-path .9s cubic-bezier(.45,.1,.6,.95) var(--td, 1.9s)}
.tumble.noted .pnote span{clip-path:inset(-30% -4% -40% -4%)}
.tumble.noted .pnote svg path{stroke-dashoffset:0}
/* Oliver!: above the third print, arrow down to it */
.pn-oli{left:60%; top:8%; align-items:flex-end; transform:rotate(-4deg); --td:1.9s; --ad:2.6s}
.pn-oli svg{transform:scaleY(-1); margin-bottom:-36px}
/* Crazy For You: under the last print, arrow up to it */
.pn-cfy{left:73%; top:90%; transform:rotate(-3deg); --td:2.7s; --ad:3.4s}
.pn-cfy svg{margin-top:-30px}
.p5{left:60%; top:52%; transform:rotate(-11deg); z-index:5}
.print.rv.on:hover{transform:rotate(calc(var(--r, 0deg) * .8))}
.bio{grid-column:7 / span 5; position:relative; z-index:2; display:flex; flex-direction:column; gap:20px}
.bio .body + .body, .bio div + .body{margin-top:-8px}
.bio h2{font-size:clamp(44px, 5.2vw, 74px); margin-bottom:8px}
.aquote{margin:0 0 28px; max-width:620px}
.aquote blockquote{margin:0; font-family:var(--serif); font-size:18px; line-height:1.5; color:var(--ink)}
.bio mark{background:none; color:var(--pencil)}
.listen{color:inherit; text-decoration:none; background:linear-gradient(var(--pencil), var(--pencil)) no-repeat left 100% / 100% 2px; padding-bottom:1px; transition:background-size .3s var(--ease), color .3s var(--ease)}
.listen:hover, .listen:focus-visible{color:var(--pencil); background-size:100% 2.5px}
.aquote figcaption{margin-top:6px; font-size:14px; line-height:1.5; color:var(--muted)}
.bio p.body{font-size:18px; line-height:1.65; max-width:580px; text-align:justify; text-indent:2em; color:var(--ink-soft); hyphens:auto}
.bio p.body sup{font-family:var(--sans); font-size:11px; color:var(--pencil); vertical-align:top; position:relative; top:-2px}
/* credits: full-colour stream that moves with the page */
#credits{padding-top:56px}
.currently{text-decoration:none; color:inherit; width:fit-content; display:flex; flex-direction:column; gap:4px}
.currently em{font-family:var(--hand); font-style:normal; color:var(--pencil); font-size:28px; line-height:1.05; transform:rotate(-1.5deg); transform-origin:left center; margin-bottom:4px}
.currently b{font-family:var(--display); font-weight:400; font-size:24px; line-height:1.32; color:var(--ink); text-wrap:balance; background:linear-gradient(var(--pencil), var(--pencil)) no-repeat left 100% / 0 2px; transition:background-size .35s var(--ease)}
.currently:hover b, .currently:focus-visible b{background-size:100% 2px}
.currently span{font-size:14px; line-height:1.5; color:var(--muted)}
.cvline{display:flex; align-items:baseline; gap:12px; white-space:nowrap}
.cv{font-family:var(--display); font-weight:400; font-size:20px; line-height:1.2; color:var(--ink); display:inline-flex; align-items:center; gap:12px; padding-bottom:6px; background:linear-gradient(var(--pencil), var(--pencil)) no-repeat left 100% / 0 2px; transition:background-size .3s var(--ease), color .3s var(--ease)}
.cvline.rv.on .cv{background-size:100% 2px; transition:background-size .7s cubic-bezier(.3,.7,.3,1) .4s, color .3s var(--ease)}
.cv:hover{background-size:100% 4px; color:var(--pencil)}
/* a 44px tap area round Download CV (it was 30px tall; the CV is the site's main goal), invisible, so nothing moves */
.cv{position:relative} .cv::after{content:""; position:absolute; inset:-7px -6px}
.cv svg{width:18px; height:18px; color:var(--pencil); transition:transform .3s var(--ease)}
.cv:hover svg{transform:translateY(3px)}
.cvline .small{margin:0}
/* credits: two columns, words left, posters right (grid inspired by photographers' portfolios) */
.cred{align-items:start; padding-bottom:24px}
.cred-side{grid-column:1 / span 5; display:flex; flex-direction:column}
.cred-side h2{font-size:clamp(44px, 5.2vw, 74px)}
.now{margin-top:52px; display:flex; flex-direction:column; gap:28px; width:fit-content}
.now .currently, .now .cvline{grid-column:auto; grid-row:auto; padding-top:0; margin:0}
.wall{grid-column:6 / -1; width:100%; max-width:min(820px, calc(100vh - 96px)); justify-self:start; margin-top:10px; position:relative; z-index:2}
.posters{list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:16px}
.tile{position:relative; outline:none; cursor:default; z-index:1}
.tile .art{position:relative; aspect-ratio:1; overflow:hidden; background:#0956AB; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:12px; box-shadow:0 0 0 rgba(3,27,61,0); transition:box-shadow .5s var(--ease)}
.tile .art img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; transition:filter .45s var(--ease)}
.tile .art b{font-family:var(--display); font-weight:400; font-size:clamp(16px, 1.5vw, 22px); line-height:1.1; color:var(--on-dark)}
.tile .art span{font-size:12px; color:var(--on-dark-soft); margin-top:6px}
/* hover: one small bounce up, then it settles a little lower */
.tile{transition:transform .3s cubic-bezier(.16,1,.3,1)}
.tile:hover, .tile:has(:focus-visible){z-index:3; animation:hop .5s cubic-bezier(.25,.6,.35,1) forwards}
.tile.tapped{z-index:3; transform:translateY(-5px)}
@keyframes hop{0%{transform:translateY(0)} 45%{transform:translateY(-9px)} 100%{transform:translateY(-5px)}}
@keyframes tapbob{0%,100%{transform:translateY(-5px)} 50%{transform:translateY(-9px)}}
.tile:hover .art, .tile:has(:focus-visible) .art, .tile.tapped .art{box-shadow:0 18px 30px -12px rgba(3,27,61,.8)}
.tile .art{transition:box-shadow .48s var(--ease)}
/* the credit, on the poster: a dark gradient rises and the lines come in one after another */
.tile .cap{position:absolute; inset:0; display:flex; flex-direction:column; justify-content:flex-end; padding:14px 14px 13px; text-align:left; background:linear-gradient(180deg, rgba(14,38,75,.2) 0%, rgba(14,38,75,.84) 42%, rgba(14,38,75,.97) 100%); opacity:0; transition:opacity .3s var(--ease); pointer-events:none}
.tile .cap b{font-family:var(--display); font-weight:400; font-size:clamp(15px, 1vw, 18px); line-height:1.15; color:var(--ink); text-wrap:balance; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden}
.tile .cap i{font-style:normal; font-family:var(--display); font-weight:400; font-size:clamp(12.5px, .8vw, 14.5px); line-height:1.25; color:var(--chalk); margin-top:6px; text-wrap:balance}
.tile .cap span{font-family:var(--serif); font-size:clamp(11.5px, .72vw, 13px); line-height:1.35; color:var(--on-dark-soft); margin-top:3px; text-wrap:balance}
.tile .cap > *{transform:translateY(8px); opacity:0; transition:transform .45s cubic-bezier(.2,.8,.2,1), opacity .35s var(--ease)}
.tile:hover .cap, .tile:has(:focus-visible) .cap, .tile.tapped .cap{opacity:1}
.tile:hover .cap > *, .tile:has(:focus-visible) .cap > *, .tile.tapped .cap > *{transform:none; opacity:1}
.tile:hover .cap > :nth-child(2), .tile:has(:focus-visible) .cap > :nth-child(2){transition-delay:.06s}
.tile:hover .cap > :nth-child(3), .tile:has(:focus-visible) .cap > :nth-child(3){transition-delay:.12s}

.quotes{display:flex; flex-direction:column; gap:28px; margin-top:52px; max-width:410px}
.quotes figure{margin:0}
/* Credits, the words beside the wall (Luke, 12 Oct 2026, with Impeccable): three groups, the heading, Currently with the
   CV (inside the drawn circle) and the quotes. The circle swings about 45px out from Currently, so the old 52px steps left
   it all but touching the heading (15px) and the first quote (5px). The column is now a grid with a room between the
   groups: at least about 24px of clear blue from the heading to the circle and 40px from the circle to the first quote,
   ink to ink; on a taller screen the spare height under the quotes is shared out, a quarter above Currently, a quarter
   below the circle and half kept under the quotes. (Was: .now and .quotes each 52px below the group above.) */
@media (min-width:901px){
  /* (eight rows that only ever share out spare height, never add it: the heading's row and a room, Currently's row and a
     room, then the quotes' row and three more rooms, so the spare goes a quarter, a quarter and a half) */
  html:not(.tablet) .cred-side{display:grid; align-self:stretch; align-items:start; grid-template-rows:auto minmax(66px, auto) auto minmax(82px, auto) auto auto auto auto}   /* (stretched to the wall's height, so it knows the spare) */
  /* (the two rooms set so the circle sits evenly between the heading and the first quote, ink to ink, Luke 13 Oct 2026:
     the circle reaches further below Currently than above it, hence the second room's extra 16px) */
  html:not(.tablet) .cred-side h2{grid-row:1} html:not(.tablet) .now{grid-row:3; margin-top:0} html:not(.tablet) .quotes{grid-row:5; margin-top:0}   /* (computers only: the iPad has its own Credits, entries 90 to 98) */
}
/* a shorter window (Chrome with its bookmarks bar, 13 Oct 2026): no spare height to share out, so the circle came within
   24px of the heading and 36px of the first quote. 12px more is taken from between the two quotes (28px to 16px), so
   Credits is no taller and still fits the screen, and the circle sits evenly between the two, about 36px each side */
@media (min-width:901px) and (max-height:830px){
  html:not(.tablet) .cred-side{grid-template-rows:auto minmax(73px, auto) auto minmax(88px, auto) auto auto auto auto}
  html:not(.tablet) .quotes{gap:16px}
}
.quotes figure:first-child blockquote{font-family:var(--serif); font-size:clamp(18px, 1.35vw, 21px); line-height:1.45; text-wrap:pretty}
.quotes mark{background:none; color:var(--pencil)}
.quotes blockquote{font-size:16px; line-height:1.6; text-wrap:pretty}
.quotes figcaption{margin-top:10px; color:var(--muted); font-family:var(--serif); font-size:13px}
.quotes figure.rv, .quotes figure.rv.pre, .aquote.rv, .aquote.rv.pre{opacity:1; transform:none; filter:none}

/* contact: option 1's */
#contact{padding-top:104px}
.contact-head{grid-column:1 / span 5; display:flex; flex-direction:column; gap:32px}
.contact-head h2{font-size:clamp(44px, 5.2vw, 74px); white-space:nowrap}
.elsewhere{list-style:none; margin:-8px 0 0 -9px; padding:0; display:flex; gap:6px}
.elsewhere a{width:44px; height:44px; display:inline-flex; align-items:center; justify-content:center; color:var(--on-blue); transition:color .25s var(--ease), transform .35s cubic-bezier(.3,.7,.4,1)}
.elsewhere a:hover, .elsewhere a:focus-visible{color:var(--chalk); transform:translateY(-3px)}
.elsewhere svg{width:24px; height:24px}
.elsewhere svg.li{width:20px; height:20px}
.formcell{grid-column:6 / span 7}
form{display:flex; flex-direction:column; gap:20px; padding-top:18px}
.two{display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:24px}
.field{display:flex; flex-direction:column; position:relative; padding-top:6px}
.field label{position:absolute; left:0; top:12px; font-family:var(--hand); font-size:28px; line-height:1.05; color:var(--pencil); transform:rotate(-1deg); transform-origin:left center; pointer-events:none; transition:opacity .25s var(--ease), transform .25s var(--ease)}
.field.filled label{opacity:0; transform:rotate(-1deg) translateY(-6px)}
/* autofill (entry 134): a browser filling in a saved name or email painted its own pale box and black text over the field,
   and took the pencil line with it (it removes background images). Here the field stays as designed: its background is
   clipped to the letters, which are drawn over it in cream, so the browser's box never shows (holding its colour change
   off with a long transition wasn't enough in Luke's Chrome), the text keeps its cream, the label goes (as when typed) and the pencil line is drawn as a shadow,
   which autofill leaves alone. (Two rules: a browser that doesn't know one of the names would drop the whole rule.) */
.field input:autofill{-webkit-background-clip:text; background-clip:text; transition:background-color 600000s 0s, color 600000s 0s; -webkit-text-fill-color:var(--ink); box-shadow:inset 0 -1.5px 0 var(--ink)}
.field input:-webkit-autofill{-webkit-background-clip:text; background-clip:text; transition:background-color 600000s 0s, color 600000s 0s; -webkit-text-fill-color:var(--ink); box-shadow:inset 0 -1.5px 0 var(--ink)}
.field input:autofill:focus{box-shadow:inset 0 -2px 0 var(--pencil)} .field input:-webkit-autofill:focus{box-shadow:inset 0 -2px 0 var(--pencil)}
/* 1Password paints its own box on a field it fills (it marks it data-com-onepassword-filled, "light" or "dark"): the box Luke
   saw on 14 Oct 2026 (rgb(36,107,179), read from his Chrome), with the text and the pencil line left alone. Its colour can't
   be overridden (a page style loses to it, even marked important), so, as for autofill, the background is clipped to the
   letters, drawn over it in cream, and the pencil line is drawn as a shadow (focus and error as usual). */
#contact .field input[data-com-onepassword-filled]{-webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:var(--ink); box-shadow:inset 0 -1.5px 0 var(--ink)}
#contact .field input[data-com-onepassword-filled]:focus{box-shadow:inset 0 -2px 0 var(--pencil)}
#contact .field.bad input[data-com-onepassword-filled]{box-shadow:inset 0 -2px 0 #F2A89C}
.field:has(input:autofill) label{opacity:0; transform:rotate(-1deg) translateY(-6px)}
.field:has(input:-webkit-autofill) label{opacity:0; transform:rotate(-1deg) translateY(-6px)}
.field input,.field textarea{font:inherit; font-size:18px; color:var(--ink); background:linear-gradient(var(--ink), var(--ink)) no-repeat left 100% / 0 1.5px; border:0; padding:6px 0 10px; outline:none; resize:none; border-radius:0; transition:background-size .8s cubic-bezier(.3,.7,.3,1) var(--d,0s), background-color .2s}
/* the message box: no grey corner handle; it grows as it is written in, from its four lines up to twelve (Luke, 13 Oct 2026,
   entry 131; js/main.js does the growing in browsers without field-sizing) */
.field textarea{field-sizing:content; min-height:calc(4lh + 16px); max-height:calc(12lh + 16px)}
.formcell.rv.on .field input, .formcell.rv.on .field textarea{background-size:100% 1.5px}
.formcell.rv.on .field:nth-child(1) input{--d:.1s} .formcell.rv.on .two .field:nth-child(2) input{--d:.4s} .formcell.rv.on .field textarea{--d:.7s}
.field input:focus,.field textarea:focus{background-image:linear-gradient(var(--pencil), var(--pencil)); background-size:100% 2px}
.field.bad input,.field.bad textarea{background-image:linear-gradient(#F2A89C, #F2A89C); background-size:100% 2px}
.err{display:none; font-family:var(--serif); font-size:15px; color:#F2A89C}
.field.bad .err{display:block}
.send{align-self:flex-start; background:linear-gradient(var(--pencil), var(--pencil)) no-repeat left 92% / 0 2px; border:0; padding:6px 0; margin-top:8px; min-height:44px; cursor:pointer; color:var(--ink); display:inline-flex; align-items:center; gap:12px; font-family:var(--display); font-weight:400; font-size:18px; line-height:1.2; transition:background-size .45s var(--ease)}
.send:hover{background-size:100% 2px}
.send .env{width:30px; height:22px; color:var(--pencil); overflow:visible; transition:transform .4s var(--ease)}
.send .env path{stroke-dasharray:90; stroke-dashoffset:90; transition:stroke-dashoffset .7s cubic-bezier(.3,.7,.3,1)}
.formcell.rv.on .send .e-body{stroke-dashoffset:0; transition-delay:1s} .formcell.rv.on .send .e-v{stroke-dashoffset:0; transition-delay:1.5s}
.send .e-flap{opacity:0}
.send:hover .env{transform:rotate(-6deg) translateY(-2px)}
.send.sealing .e-flap{opacity:1; stroke-dashoffset:0; transition:stroke-dashoffset .5s cubic-bezier(.3,.7,.3,1), opacity .1s} .send.sealing .e-v{opacity:.35; transition:opacity .4s .2s}
/* the envelope folds shut on Send (.sealing) and waits there, sealed, until the message has gone; then it flies off (.flying,
   js/main.js; entry 134: before, it flew 0.6s after Send whatever happened, so a slow send showed nothing) */
.send.flying .env{transform:translate(6px,-10px) rotate(-10deg); opacity:0; transition:transform .6s cubic-bezier(.4,0,.8,.6), opacity .3s .3s}
.sent{padding-top:16px}
.sent p:empty{display:none}
.sent:has(p:empty){padding:0}
.sent p:focus{outline:none}
.sent p{font-family:var(--display); font-weight:400; font-size:24px; line-height:1.3}
form.done{display:none}
form.failed .send-err{display:block}
footer{margin-top:64px; padding:0 var(--m); height:72px; display:grid; grid-template-columns:1fr auto 1fr; align-items:center}
footer .back{grid-column:2} footer .copy{grid-column:3; justify-self:end}
.back{min-height:44px; display:inline-flex; align-items:center; gap:8px; color:var(--muted)}
.back svg{width:12px; height:12px}
.back:hover{color:var(--pencil)}
footer .copy{color:var(--muted); font-size:14px}


/* entrance: the name types itself, then the rest arrives */
html.typing .ch:not(.on){opacity:0}
html.typing .polaroid{opacity:0; transform:rotate(9deg) translateY(-70px); box-shadow:var(--lift-shadow)}
html.typing .hdr, html.typing .arrow{opacity:0}
html.typing .hdr nav a, html.typing .soc a{opacity:0; transform:translateY(-6px)}
/* the polaroid, header and arrow wait until the subtitle is written (html.shown) */
html.shown .polaroid{opacity:1; transform:rotate(3deg); box-shadow:var(--print-shadow); transition:opacity .5s var(--ease), transform 1s cubic-bezier(.2,.9,.25,1.12), box-shadow 1s var(--ease)}
html.shown .hdr{opacity:1; transition:opacity 1s var(--ease)} html.shown .arrow{opacity:1; transition:opacity .35s var(--ease) .9s}
html.shown .hdr nav a, html.shown .soc a{opacity:1; transform:none; transition:opacity 1s var(--ease), transform 1s var(--ease)}
/* the header arrives all at once, with the underline and the polaroid */

html.settled .polaroid{transition:transform .5s var(--ease), box-shadow .5s var(--ease)}
#home.exiting .polaroid{transition:transform .25s linear, opacity .2s linear; pointer-events:none}
#home.exiting .ch:not(.on){opacity:0}
#home.exiting .role{transition:none}
html.settled .hdr nav a, html.settled .soc a, html.settled .role, html.settled .role .wd{transition:none}
.rv.pre{opacity:0; transform:translateY(22px); filter:blur(6px)}
.rv{transition:opacity .9s var(--ease) var(--d,0s), transform .9s var(--ease) var(--d,0s), filter .9s var(--ease) var(--d,0s)}
.rv.on{opacity:1; transform:none; filter:none}
.print.rv.pre{transform:translate(-40vw, -45vh) rotate(calc(var(--r, 0deg) - 40deg))}
.print.rv{transition:opacity .4s var(--ease) var(--d,0s), transform 1.9s cubic-bezier(.12,.82,.16,1) var(--d,0s), box-shadow .6s var(--ease)}
.print.rv.on{transform:rotate(var(--r, 0deg))}
@media (prefers-reduced-motion: reduce){
  .pnote span{clip-path:none !important; transition:none !important} .pnote svg path{stroke-dashoffset:0 !important; transition:none !important}
  .ch, .role, .polaroid, .hdr, .hdr nav a, .soc a, .arrow, .rv{opacity:1 !important; transform:none !important; transition:none !important; clip-path:none !important}
  .arrow path{stroke-dashoffset:0; animation:none} .arrow svg{animation:none}
  .polaroid{transform:rotate(3deg) !important}
  .print.rv{transform:rotate(var(--r,0deg)) !important}
  .caret{animation:none}
  html{scroll-behavior:auto}
}

/* ===== phones (900px and below; designed at the iPhone 17, 402 x 874). The same story as the desktop, told down one
   column: the thread runs down the margins, crossing from side to side through the open spaces (under the polaroids, in
   loops between the sections, under the Credits heading and down behind the posters), and each part appears as it arrives. No holds: on a phone the
   page never stops under your thumb. Spacing on 8px steps. ===== */
@media (max-width: 900px){
  :root{--m:clamp(24px, 7vw, 56px)}
  .grid{grid-template-columns:1fr; column-gap:0}
  /* the thread: the same 90% yellow, mixed with the blue rather than faded, so it needs no layer of its own (a layer the
     height of the whole page is a lot to ask of a phone) */
  #thread{opacity:1; will-change:auto} #thread path{stroke:#E6C99D}
  /* words arrive gently (Luke, 11 Oct 2026: too harsh on a phone): a soft start and an even rise (1.2s), settling from
     10px (1.5s), so you see each one come in */
  html .settle{transition:none}     /* played by js/main.js (appear) as an explicit animation: Safari skipped the transition */
  html .await{transform:translateY(10px)}
  /* posters and polaroids can't be selected (Luke saw a "sepia" poster: a long press or a quick double tap had selected
     it, and Safari painted the yellow selection colour over it until he tapped elsewhere) */
  .wall, .tumble{-webkit-user-select:none; user-select:none}
  /* "(Chandos)." on a line of its own rather than split over two (Luke, 11 Oct 2026) */
  .phone-br{display:inline}
  /* once the first journey has begun, the hero arrow stays away when you scroll back to the top; it comes back with the
     thread wound back (Back to top, or at the top once complete) */
  html.journey #home .arrow{opacity:0; transition:opacity .25s var(--ease)}
  /* the hero polaroid is tossed away by the scroll itself where the browser can (main.js sets --toss, the scroll it takes):
     smooth on an iPhone, where a polaroid moved by script each scroll was a frame out of step with the page (jittery) */
  @supports (animation-timeline: scroll()) and (animation-range: 0px 100px){
    @media (prefers-reduced-motion: no-preference){
      html.settled #home .polaroid{animation:polToss linear both; animation-timeline:scroll(root); animation-range:0px var(--toss, 800px)}
    }
  }
  .hdr{height:76px; gap:16px} .hdr nav{gap:20px}

  /* hero: "Holman" fills the width (js/main.js, fitName), the polaroid beside "Luke", rising above it, as on the desktop */
  #home{padding:84px var(--m) 136px; min-height:var(--hero-h, 100svh)}   /* --hero-h: the screen's height, held by js/main.js (holdHero), so an in-app browser growing taller as you scroll doesn't grow the hero (entry 146) */
  .hero-in{width:100%}
  .title{width:100%; grid-template-columns:auto minmax(0,1fr); column-gap:12px; row-gap:0}
  .title .line{line-height:.9}
  .title .l2{grid-column:1 / span 2}
  .title .polaroid{grid-column:2; grid-row:1; justify-self:end; width:calc(100% + var(--m)*.4); margin:0 calc(var(--m)*-.4) .16em 0; padding:7px 7px 24px}
  .role{font-size:clamp(28px, 7.4vw, 40px); margin:32px 0 0 .25em}

  /* About, in reading order: heading, quote, first paragraph, the thrown polaroids, second paragraph, photographers.
     (The grid and the bio step aside, display:contents, so the polaroids can sit between the paragraphs.) */
  main #about{display:flex; flex-direction:column; min-height:0; padding:112px 0 167px}   /* (Luke, 11 Oct 2026: about 28% less space before Credits, the diagonal flatter; was 220px) */
  #about > .grid, #about .bio{display:contents}
  #about .bio > *{margin-left:var(--m); margin-right:calc(var(--m) + 4px)}
  #about .bio h2{order:1; margin-bottom:20px}
  #about .aquote{order:2; margin-bottom:24px}
  #about .bio p:nth-of-type(1){order:3}
  #about .tumble{order:4}
  #about .bio p:nth-of-type(2){order:5; margin-top:0}
  #about .bio p:nth-of-type(3){order:6; margin-top:20px}
  .bio p.body{text-align:left; text-indent:0; font-size:17px; line-height:1.62}
  /* after the polaroids the thread runs down the left, so the second paragraph and the photographers' line sit to the right (Luke, 11 Oct 2026) */
  #about .bio p:nth-of-type(2), #about .bio p:nth-of-type(3){text-align:right; margin-left:calc(var(--m) + 4px); margin-right:var(--m)}
  .aquote blockquote{font-size:19px; line-height:1.45}
  /* the pile: five prints thrown across the full width, overlapping like a real pile, with the two captions in the gaps */
  .tumble{position:relative; left:0; top:0; width:100%; aspect-ratio:402/540; margin:56px 0 24px}
  .tumble{aspect-ratio:402/620}
  .tumble .print{width:44%; padding:2.4% 2.4% 8.6%}
  /* upper left, the middle (Oliver!, with its caption above it) and the right; the last two low, Crazy For You on the
     left with its caption under it. The thread passes beneath them, from the top right to a loop on the left. */
  .tumble .p1{left:2%; top:1%} .tumble .p2{left:25%; top:17%} .tumble .p3{left:56.5%; top:24%}   /* p3 a little right, clear of Luke's face on the middle print (Luke, 11 Oct 2026; was 53%) */
  .tumble .p4{left:47%; top:52%} .tumble .p5{left:6%; top:50%}
  .pnote{font-size:24px}
  /* Oliver!: tilted clockwise (Luke, 11 Oct 2026), its arrow from the start of the words down to the middle print */
  .pn-oli{left:43%; right:auto; top:-.5%; transform:rotate(27deg)}
  /* the arrow hangs from under the middle of the words, pointing straight down from them (so down and to the left on the
     page) at the middle print's top corner */
  .pn-oli > svg{position:absolute; left:44%; top:calc(100% + 2px); transform:rotate(-32deg) scaleY(-1); margin:0}
  /* Crazy For You: a little lower, so its arrow stops short of the print's frame */
  .pn-cfy{left:17%; top:89.5%}
  .pn-cfy > svg{margin-top:-20px}

  /* Credits: heading, Currently and the CV, then the wall of all sixteen posters with the credit line under it, then the quotes */
  #credits{padding-top:8px; z-index:2}   /* above the thread, so it passes behind the posters (it shows only between them) */
  .cred{display:flex; flex-direction:column; padding-bottom:0}
  .cred-side{display:contents}
  .cred-side h2{order:1}
  .now{order:2; margin:32px 0 0 24px; gap:24px}            /* set in a little; close under the heading (Luke, 11 Oct 2026: was 48px) */
  .currently b{font-size:22px}                      /* the show's name on one line */
  .wall{order:3; max-width:640px; margin-top:64px}
  /* the quotes centred, with room for the thread down their right (Luke, 11 Oct 2026) */
  .quotes{order:4; margin-top:40px; max-width:310px; align-self:center; text-align:center}
  .posters{grid-template-columns:repeat(4, minmax(0,1fr)); gap:8px}
  /* each poster fades up gently, a row at a time */
  html .tile .art{transition:box-shadow .48s var(--ease), opacity .9s cubic-bezier(.16,1,.3,1) var(--td2,0s), transform 1.2s cubic-bezier(.16,1,.3,1) var(--td2,0s)}
  /* no hover on a phone: the credit is shown in the line under the wall, never over a poster this small */
  .tile:hover, .tile:has(:focus-visible){animation:none}
  .tile .cap{opacity:0 !important}
  .tile:hover .art, .tile:has(:focus-visible) .art{box-shadow:none}
  /* the desktop's hop (up 9px, settling at 5px), then a gentle bob for as long as it is chosen (Luke, 11 Oct 2026) */
  .tile.tapped{z-index:3; animation:hop .5s cubic-bezier(.25,.6,.35,1) forwards, tapbob 1.6s ease-in-out .5s 3}   /* three bobs, then it rests, still lifted (entry 135; was for as long as it was chosen) */
  .tile.tapped .art{box-shadow:0 18px 30px -12px rgba(3,27,61,.8)}   /* Poster lifted */
  .tile:has(:focus-visible){outline:none}
  /* the credit line: the chosen poster's show, role, venue and year (filled in by js/main.js); room for the longest, so nothing below ever moves.
     Once the wall is in, the latest credit fades up, until another poster is tapped (Luke, 11 Oct 2026) */
  .credit-line{display:block; position:relative; margin:24px 0 0; min-height:92px; font-size:14px; max-width:250px}   /* kept to the left, so the thread can curve past it; min-height: the tallest credit */
  .credit-line a{display:flex; flex-direction:column; gap:2px; width:fit-content; transition:opacity .25s var(--ease), transform .35s var(--ease)}
  .credit-line a.swap{opacity:0; transform:translateY(4px); transition:none}
  .credit-line b{font-family:var(--display); font-weight:400; font-size:20px; line-height:1.2; color:var(--ink)}
  .credit-line i{font-style:normal; font-family:var(--display); font-weight:400; font-size:15px; line-height:1.3; color:var(--pencil)}
  .credit-line span{font-family:var(--serif); font-size:14px; line-height:1.4; color:var(--on-blue-soft)}
  .credit-line a:empty{visibility:hidden}

  /* Contact */
  #contact{padding-top:80px}       /* the quotes to the Contact heading as the credit line to the quotes (Luke, 11 Oct 2026: was 128px) */
  .contact-head, .formcell{grid-column:1 / -1}
  #contact .grid{row-gap:32px; position:relative}
  .contact-head{gap:0}
  /* the icons share the Send row, to the right (Luke, 11 Oct 2026): both rows are 44px tall and end at the foot of the form */
  .contact-head .elsewhere{position:absolute; right:calc(var(--m) - 10px); bottom:0; margin:0}
  .contact-head h2{white-space:normal}
  .two{grid-template-columns:1fr; gap:20px}
  form{padding-top:8px}
  footer{margin-top:48px; height:auto; padding:0 var(--m) 24px; display:flex; flex-direction:column; align-items:center; gap:0}
  footer .copy{font-size:13px}
}
/* touch screens: a tap leaves a print "hovered", so the lift and turn that a mouse brings would make it jump (Luke, 11 Oct 2026) */
@media (hover:none) and (max-width: 900px){   /* (phones only: the desktop layout as before, even on a touch screen) */
  .print:hover{box-shadow:var(--print-shadow)}
  .print.rv.on:hover{transform:rotate(var(--r, 0deg))}
  html.settled .polaroid:hover{transform:rotate(3deg); box-shadow:var(--print-shadow)}
}
/* the credit line belongs to the phone layout only */
@media (min-width:901px){ .credit-line{display:none} }

/* ===== tablets (12 Oct 2026): a touch screen showing the desktop layout, an iPad wider than 900px both ways (js/main.js
   sets html.tablet; never on a computer, which has no touch screen, or a phone). The desktop as it is, with only what
   Safari's touch scrolling and an iPad's graphics need, most of it as the phone version already does. ===== */
/* no Lenis on tablets (html.no-lenis, which stays if an iPad turns to the phone layout): when a script moves the scroll (the
   holds switching off, ScrollTrigger measuring the page as it rebuilds), it must jump, not glide (Lenis did this before) */
html.no-lenis{scroll-behavior:auto}
/* the thread mixed with the blue rather than faded, as on phones (the same colour), so it needs no layer of its own: a
   layer the size of the whole page is a lot to ask of an iPad, which draws twice as many pixels each way */
html.tablet #thread{opacity:1; will-change:auto}
html.tablet #thread path{stroke:#E6C99D}
/* the stage lights stay still in their resting place, as on phones: their drift kept large pools of light moving on
   every frame of the scroll */
html.tablet .light::before{animation:none}
/* the hero polaroid tossed away by the scroll itself, as on phones (main.js sets --toss), in step with the finger */
@supports (animation-timeline: scroll()) and (animation-range: 0px 100px){
  @media (prefers-reduced-motion: no-preference) and (min-width:901px){
    html.tablet.settled #home .polaroid{animation:polToss linear both; animation-timeline:scroll(root); animation-range:0px var(--toss, 800px)}
  }
}
/* no holds on tablets (html.paced; Luke, 12 Oct 2026): the page moves with the finger, as on phones, so the words arrive as
   they do on phones: a soft start and an even rise, settling from 10px, played by js/main.js (appear) */
html.paced .settle{transition:none}
html.paced .await{transform:translateY(10px)}
/* the room between the sections, where the thread's diagonal loops into Credits and its S runs down into the form (Luke,
   12 Oct 2026: on its side about half what it was, upright less again, so the diagonal is gradual, as on the phone) */
html.paced #about{padding-bottom:calc(210px + 3vh)}
html.paced #contact{padding-top:calc(83px + 5.2vh)}   /* (Luke, 12 Oct 2026: about 20% less, to keep things moving; was 104px + 6.5vh) */
/* tablets: everything sized from the screen's height uses the height held by js/main.js (holdHero: --hold-vh is 100vh,
   --hold-svh 100svh, measured once and again when the iPad turns), so an in-app browser (Instagram...) growing taller as its
   bars hide doesn't move the page under the thread (entry 147). The same sizes as above and in the main rules */
html.tablet #home{min-height:var(--hold-svh, 100svh); padding-bottom:calc(110px + clamp(0px, var(--hold-vh, 100vh) * .62 - 500px, 260px))}
html.tablet .wall{max-width:min(820px, calc(var(--hold-vh, 100vh) - 96px))}
html.tablet .role{margin-top:clamp(40px, var(--hold-vh, 100vh) * .07, 64px)}
html.paced #about{padding-bottom:calc(210px + var(--hold-vh, 100vh) * .03)}
html.paced #contact{padding-top:calc(83px + var(--hold-vh, 100vh) * .052)}
/* Currently and the CV close under the heading, the quotes a step further on, as one group after another (Luke, 12 Oct
   2026: with no circle round it, about 30% less than the circle needed: 68px and 84px; the desktop's are 52px and 52px) */
html.paced .now{margin-top:48px}
html.paced .quotes{margin-top:56px}
/* on its side, the poster wall centred on the words beside it (Luke, 13 Oct 2026; was at the top, 10px down) */
@media (orientation:landscape){ html.paced .wall{align-self:center; margin-top:0} }
@media (orientation:portrait){
  /* upright, the Credits words a quarter closer together (Luke, 13 Oct 2026): Currently 36px under the heading (was 48px),
     the CV 21px under Currently (was 28px), the quotes 42px under the CV (was 56px) */
  html.paced .now{margin-top:36px; gap:21px}
  html.paced .quotes{margin-top:42px}
  html.paced #about{padding-bottom:172px}
  html.paced #contact{padding-top:150px}          /* (room for the thread's S to pass under the credit written under the posters, 13 Oct 2026; was 96px, and 120px before that) */
  /* upright: the name, polaroid and subtitle centred on the screen (on the desktop they sit high, to clear a short laptop's
     arrow) */
  html.tablet #home{padding-bottom:150px}
  /* upright, About: the bio runs on to the right into what was the thread's margin (it was squashed at 327px), and the
     prints are a little bigger and centred on it, the two sharing one row of a grid */
  html.paced #about{display:grid}
  html.paced #about > .grid{grid-area:1 / 1}
  html.paced #about > .tumble{grid-area:1 / 1; align-self:center; position:relative; left:auto; top:auto; margin-left:calc(var(--m) - 64px); width:min(640px, 46vw)}
  html.paced .bio{grid-column:7 / -1}
  /* the captions clear of the wider bio, their arrows where Luke drew them (12 Oct 2026): Oliver!'s hangs from under the end
     of "Playhouse" and points down and to the left onto the third print; Crazy For You's stands to the left of the words
     and points up into the last print */
  html.paced .pn-oli{left:calc(49% + 30px); top:11%}
  html.paced .pn-oli > svg:not(.ink){position:absolute; right:58px; top:calc(100% + 8px); margin:0; transform:scale(1.35)}   /* (its own drawing on tablets upright, js/main.js: from under "Playhouse", down and hooking left onto the third print, as Luke drew it) */
  html.paced .pn-cfy{left:63%; top:92%}
  html.paced .pn-cfy > svg:not(.ink){margin-top:-18px; transform:rotate(28deg) scale(1.15)}
  /* the posters centred on the words beside them (Luke, 12 Oct 2026: top-aligned, they left a big empty space under the
     wall above the arrow into Contact); the thread's S into the form then turns about level with the end of the quotes */
  html.paced .wall{align-self:center; margin-top:0}
  /* the posters as on phones (Luke, 13 Oct 2026: at this size, tapped, their credits were cut off): no credit over a
     poster; the chosen one hops and bobs, and its credit is written under the wall, on the right, clear of the thread
     (js/main.js fills it in; the latest until another is tapped). Room for the longest, so nothing below moves. */
  html.tablet .tile:hover, html.tablet .tile:has(:focus-visible){animation:none}
  html.tablet .tile .cap{opacity:0 !important}
  html.tablet .tile:hover .art, html.tablet .tile:has(:focus-visible) .art{box-shadow:none}
  html.tablet .tile.tapped{z-index:3; animation:hop .5s cubic-bezier(.25,.6,.35,1) forwards, tapbob 1.6s ease-in-out .5s 3}   /* (three bobs, then it rests: entry 135) */
  html.tablet .tile.tapped .art{box-shadow:0 18px 30px -12px rgba(3,27,61,.8)}
  html.tablet .tile:has(:focus-visible){outline:none}
  html.tablet .credit-line{display:block; position:absolute; top:100%; right:0; margin:22px 0 0; min-height:76px; width:360px; text-align:right}   /* hung under the wall, so the posters alone are centred on the words (Luke, 13 Oct 2026) */
  html.tablet .credit-line a{display:flex; flex-direction:column; align-items:flex-end; gap:2px; width:fit-content; margin-left:auto; transition:opacity .25s var(--ease), transform .35s var(--ease)}
  html.tablet .credit-line a.swap{opacity:0; transform:translateY(4px); transition:none}
  html.tablet .credit-line b{font-family:var(--display); font-weight:400; font-size:21px; line-height:1.2; color:var(--ink)}
  html.tablet .credit-line i{font-style:normal; font-family:var(--display); font-weight:400; font-size:16px; line-height:1.3; color:var(--pencil)}
  html.tablet .credit-line span{font-family:var(--serif); font-size:14px; line-height:1.4; color:var(--on-blue-soft)}
  html.tablet .credit-line a:empty{visibility:hidden}
}
/* once the first journey has begun, the hero arrow stays away when you scroll back to the top; it comes back with the
   thread wound back (Back to top, or at the top once complete), as on phones */
html.paced.journey #home .arrow{opacity:0; transition:opacity .25s var(--ease)}
/* a tap leaves a print "hovered", so the lift and turn a mouse brings would make it jump (as on phones); and posters and
   polaroids can't be selected (a long press selected a poster on the iPhone and Safari painted it sepia) */
@media (hover:none){
  html.tablet .print:hover{box-shadow:var(--print-shadow)}
  html.tablet .print.rv.on:hover{transform:rotate(var(--r, 0deg))}
  html.tablet.settled .polaroid:hover{transform:rotate(3deg); box-shadow:var(--print-shadow)}
}
html.tablet .wall, html.tablet .tumble{-webkit-user-select:none; user-select:none}

/* linen (Luke, 11 Oct 2026): the blue reads as ink on book cloth rather than flat colour: a faint plain weave.
   It is the page's own background (#page), so it moves exactly with the content: it stops in the scroll holds, glides
   with the page and carries on with it. (Not the body's: in a hold the browser's real scroll carries on while js/main.js
   pins the page, so a texture on the body slid on behind the held page.) The sections paint no blue of their own, so the
   cloth runs on unbroken between them.
   Layers, top first: a touch of darkening; a veil of the blue (keeps the weave subtle); the weave (img/texture/linen.png,
   threads 3px apart, broken by slubs); the blue again, half over the cloth's faint unevenness (img/texture/clouds.png,
   a tiny tile enlarged 8 times, so it is smooth); the blue. The tiles are made by tools/texture/make.js.
   Contrast: the small print (--muted, 5.46:1 on the flat blue since entry 133; was 4.67) must stay over AA's 4.5, so the weave is mostly darker
   threads, and the darkening (3.2%) is just enough to keep its lightest spot at 4.5:1. On average the page is about
   1 point of lightness (L*, out of 100) darker than the flat blue. #page is isolated so the stage lights sit above it. */
/* The three plain washes are small repeating tiles (64px), not one gradient the size of the page: Chrome painted each as a
   picture about 5,500px tall, and repainting those whenever the page's layer changed (as the holds start and stop, and
   when the thread completes) took the graphics chip about 150ms, a visible bump. A plain colour repeated looks the same. */
#page{isolation:isolate; background:
  linear-gradient(rgb(0 0 0 / .032), rgb(0 0 0 / .032)) 0 0 / 64px 64px,
  linear-gradient(color-mix(in srgb, var(--ground) 45%, transparent), color-mix(in srgb, var(--ground) 45%, transparent)) 0 0 / 64px 64px,
  url(../img/texture/linen.png) 0 0 / 240px 240px,
  linear-gradient(color-mix(in srgb, var(--ground) 55%, transparent), color-mix(in srgb, var(--ground) 55%, transparent)) 0 0 / 64px 64px,
  url(../img/texture/clouds.png) 0 0 / 1024px 1024px,
  var(--ground)}

/* house lights: the colours on the blue (the blue itself is the linen above, Luke 11 Oct 2026: before, #home and #contact
   painted var(--blue) and #credits var(--dark)) */
#home, #contact{color:var(--on-blue)}
#home .hdr nav a, #home .soc a{color:var(--on-blue)} #home .hdr nav a::after{background:var(--chalk)} #home .hdr nav a:hover, #home .soc a:hover{color:var(--chalk)}
#home .role, #home .arrow{color:var(--chalk)} #home .caret{background:var(--ink)}
#home ::selection, #contact ::selection, #credits ::selection{background:var(--chalk); color:var(--blue)}
#credits{color:var(--on-dark); padding-bottom:0; margin-top:0}
#about{padding-bottom:112px}
#credits #credits .cv{color:var(--on-dark); background-image:linear-gradient(var(--chalk), var(--chalk))} #credits .cv svg{color:var(--chalk)} #credits .cvline .small{color:var(--on-dark-soft)}
#credits .quotes blockquote{color:var(--on-dark)} #credits .quotes figcaption{color:var(--on-dark-soft)}
#credits :focus-visible{outline-color:var(--chalk)}
#contact .contact-head h2{color:var(--on-blue)}
#contact .field label{color:var(--chalk)}
#contact .field input, #contact .field textarea{color:var(--on-blue); border-bottom-color:var(--on-blue-soft)}
#contact .field input:focus, #contact .field textarea:focus{border-bottom-color:var(--chalk)}
#contact .send{color:var(--on-blue); background-image:linear-gradient(var(--chalk), var(--chalk))} #contact .send svg{color:var(--chalk)}
#contact .err{color:#F2A89C}
#contact .sent p{color:var(--on-blue)}
#contact .back{color:var(--on-blue-soft)} #contact .back:hover{color:var(--chalk)}
#contact footer .copy{color:var(--on-blue-soft)}
#contact :focus-visible, #home :focus-visible{outline-color:var(--chalk)}
.polaroid, .print{--print-shadow:0 2px 4px rgba(0,0,0,.25), 0 40px 60px -30px rgba(0,0,0,.6)}
html{scrollbar-color:var(--ink) var(--ground)}

/* build: Lenis does the smooth scrolling, so the browser's own smooth scroll steps aside while it runs */
html.lenis, html.lenis body{height:auto}
html.lenis{scroll-behavior:auto}
.lenis.lenis-stopped{overflow:hidden}

/* hero polaroid 10% smaller (Luke, 10 Oct). Scaled rather than resized, so the name keeps its size;
   the nudge keeps its bottom-left corner where it was, beside "Luke" and on the baseline. */
@media (min-width:901px){ .title .polaroid{scale:.9; translate:-5% 5%} }

/* hero entrance, pass 2 (Luke, 10 Oct) */
/* the caret floats over the name (placed by script), so it never breaks the kerning between letters and nothing shifts as it moves;
   it holds solid while typing, and blinks before and after. --cbase is the baseline, measured by script. */
.title .w{position:relative}
.title .caret{position:absolute; left:0; top:calc(var(--cbase, .8em) - .72em); margin:0}
.caret.solid{animation:none}
/* resting (js/main.js, caretWake; entry 135): after three blinks it fades, from fully shown */
.caret.rest{animation:caret-rest .45s ease-out forwards}
@keyframes caret-rest{from{opacity:1} to{opacity:0}}
/* the subtitle is drawn letter by letter from Reenie Beanie outlines: a pen-shaped mask reveals each finished letter, so its colour and weight never change */
.role .ink{height:1em; vertical-align:-.25em; overflow:visible; fill:currentColor; stroke:currentColor; stroke-width:0; stroke-linecap:round; stroke-linejoin:round}
html.typing .role .ink{visibility:hidden}
.vh{position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0}
/* a cursive underline beneath the subtitle, drawn once the words are written */
.role{position:relative}
.role .uline{position:absolute; left:-.15em; top:1em; width:10.4em; height:1em; overflow:visible; pointer-events:none}
/* stroke width is in drawing units (4.8 = 2px at desktop size). Not non-scaling-stroke: that breaks the drawing's timing in browsers. */
.role .uline path{fill:none; stroke:currentColor; stroke-width:4.8; stroke-linecap:round; stroke-linejoin:round}
html.typing .role .uline{opacity:0}

/* hero room to breathe (Luke, 10 Oct): more space between the name, polaroid and subtitle, and a slightly larger subtitle.
   The polaroid keeps its size. Spacing grows with screen height, so short screens stay as they were. */
@media (min-width:901px){
  .title{column-gap:48px}
  .title .line{line-height:.9}
  .role{font-size:46px; margin-top:clamp(40px, 7vh, 64px)}
}

/* the page wrapper: the scroll holds pin this, so everything, thread included, freezes together */
#page{position:relative}
#page-holder{position:relative}
#page.held{position:fixed; top:0; left:0; will-change:transform}

/* reveals (Luke, 10 Oct): kept simple. A block waiting for its moment is held back (.await); letting it go plays a
   gentle fade with a 10px rise, as an explicit animation (js/main.js, appear), the same on a computer as on phones and the
   iPad (Luke, 12 Oct 2026: in Safari the desktop's crisp 0.45s fade with a 6px settle read as a pop, and he wanted the
   versions alike; .settle's transition was that fade). Posters fade more slowly, rolling in from the top left (was all at
   once in 0.55s). */
.settle{transition:none}
.await{opacity:0; transform:translateY(10px)}
.cvline.rv.pre{opacity:1; transform:none; filter:none}
.tile .art{transition:box-shadow .48s var(--ease), opacity .9s cubic-bezier(.16,1,.3,1) var(--td2,0s), transform 1.2s cubic-bezier(.16,1,.3,1) var(--td2,0s)}
html.tablet .tile .art{transition:box-shadow .48s var(--ease), opacity .55s cubic-bezier(.16,1,.3,1) var(--td2,0s), transform .75s cubic-bezier(.16,1,.3,1) var(--td2,0s)}   /* (the iPad as before: its own reveals) */
.tile.pre .art{opacity:0; transform:translateY(10px)}
@media (prefers-reduced-motion: reduce){ .tile.pre .art{opacity:1 !important; transform:none !important} .await{opacity:1 !important; transform:none !important} }
/* a reload part way down (entry 141): the browser puts the page back where it was before js/main.js has held back what is
   still to come, and that showed it for a moment in its finished place (the polaroids, the words, the posters), then hid
   it, then brought it in. So it stays hidden until the script has held it back: html.prep, set in index.html's head and
   taken off by js/main.js just after (never with reduced motion, where nothing is held back). The same things js/main.js
   holds back; once it has, they are hidden anyway, so taking html.prep off changes nothing on screen. */
html.prep :is(.tumble .print, .tumble .pnote, #about .bio h2, #about .aquote, #about .bio p.body, #about .bio p.small, .cred-side h2, .now, .quotes figure, #posters .tile .art, .contact-head h2, .contact-head .elsewhere, .formcell){opacity:0 !important; transition:none !important}
/* the captions are handwritten letter outlines, written by script; their arrows draw head first */
.pnote span.cap-ink{clip-path:none; transition:none}
.pnote svg.ink{height:1em; vertical-align:-.25em; overflow:visible; fill:currentColor; flex:none; transform:none; margin:0}
/* room under About for the loop-the-loop, so none of it shows at the top of the Credits frame */
@media (min-width:901px){ #about{padding-bottom:210px} }

/* hero exit (Luke, 10 Oct): the eraser that rubs out the subtitle; the arrow goes as soon as scrolling starts */
/* kept to a box tight round the subtitle and underline, so its turns are hidden and it never reaches the name above */
.role .eraser{position:absolute; left:-.45em; top:-.12em; width:10.8em; height:2.22em; overflow:hidden; pointer-events:none}
/* the eraser's frame: never painted, the stroke is used as a mask on the subtitle (js/main.js, eraser) */
/* once it is all rubbed out, the subtitle is simply not drawn (its masks were costly to keep drawing; entry 54) */
.role.rubbed .ink, .role.rubbed .uline{visibility:hidden}
html.shown #home.scrolled .arrow{opacity:0; transition:opacity .25s var(--ease)}
/* nothing may push the page wider than the screen */
#page-holder{overflow-x:clip}

/* once the thread is complete, the arrow sits still on its start rather than bobbing off it */
html.complete .arrow svg, html.journey .arrow svg{animation:none}   /* phones: still from the moment the thread is on its way (Luke) */
/* contact (Luke, 10 Oct): the form simply appears with its pencil lines already drawn */
#contact .field input, #contact .field textarea{background-size:100% 1.5px; transition:background-color .2s}
#contact .field input:focus, #contact .field textarea:focus, #contact .field.bad input, #contact .field.bad textarea{background-size:100% 2px}
/* the envelope waits open, flap up; Send folds the flap down to seal it, then it flies off as before */
.send .env path{stroke-dasharray:none; stroke-dashoffset:0}
.send .e-v{opacity:0}
.send .e-flap{opacity:1; transform-box:view-box; transform-origin:17px 4.7px; transition:transform .38s cubic-bezier(.5,0,.3,1)}
.send.sealing .e-flap{opacity:1; transform:scaleY(-1); transition:transform .38s cubic-bezier(.5,0,.3,1)}
.send.sealing .e-v{opacity:0}

/* ===== soft stage light (Luke, 11 Oct 2026): a pool of light behind each section's content, a little lighter than the
   blue at its centre, drifting slowly, gone well before the section's edges. Placed by js/light.js; always on. ===== */
#home, #credits, #contact{isolation:isolate}                   /* so each light sits just above the linen, behind its section's content */
#credits, #contact{position:relative}
.light{position:absolute; inset:0; z-index:-1; overflow:hidden; pointer-events:none; --light-a:.12}
/* placed with translate (a transform), not left/top, so moving a pool never counts as the page shifting; hidden until placed */
.light:not(.placed){visibility:hidden}
/* data-spill: the pool may carry on past the section's top and bottom, across the join (the sides are still cut at the page's edge) */
.light.spill{overflow:visible; overflow-x:clip}
.light::before, .light::after{content:''; position:absolute; left:0; top:0; width:calc(var(--lrx, 400px) * 2); height:calc(var(--lry, 300px) * 2); translate:calc(var(--lx, 0px) - var(--lrx, 400px)) calc(var(--ly, 0px) - var(--lry, 300px))}
/* the pool: broad and soft, so it reads as a wide wash rather than a spot (from full at the centre to nothing at its rim) */
.light::before{background:radial-gradient(closest-side,
  rgb(120 170 235 / var(--light-a)) 0%, rgb(120 170 235 / calc(var(--light-a) * .95)) 20%, rgb(120 170 235 / calc(var(--light-a) * .8)) 40%,
  rgb(120 170 235 / calc(var(--light-a) * .62)) 55%, rgb(120 170 235 / calc(var(--light-a) * .4)) 70%, rgb(120 170 235 / calc(var(--light-a) * .25)) 80%,
  rgb(120 170 235 / calc(var(--light-a) * .09)) 90%, rgb(120 170 235 / 0) 100%);
  animation:light-drift var(--drift, 28s) ease-in-out var(--drift-delay, 0s) infinite}
/* a very faint grain (3.5%), only where the light is, so the gradient never shows steps */
.light::after{opacity:.035;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n' x='0' y='0' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E") 0 0 / 180px 180px;
  -webkit-mask-image:radial-gradient(closest-side, #000 25%, rgb(0 0 0 / .5) 60%, transparent 100%); mask-image:radial-gradient(closest-side, #000 25%, rgb(0 0 0 / .5) 60%, transparent 100%)}
/* the drift: each pool wanders a few pixels and breathes, on its own slow cycle */
@keyframes light-drift{0%,100%{transform:none} 25%{transform:translate(1.4%, -1.2%) scale(1.02)} 50%{transform:translate(-.8%, -2%) scale(.99)} 75%{transform:translate(-1.4%, .6%) scale(1.015)}}
@media (prefers-reduced-motion: reduce){ .light::before{animation:none} }
/* phones: the pools stay still in their resting place (the drift cost about 1ms of the browser's work on every frame of the
   scroll, for a wander of 1 or 2% over half a minute; Luke, 11 Oct 2026: buttery smooth on phones) */
@media (max-width: 900px){ .light::before{animation:none} }
/* Safari on a computer (12 Oct 2026): Safari redraws what Chrome draws once and keeps, and on Luke's Mac the drifting pools
   froze the page in the holds (2 to 3 seconds a frame, measured in Safari itself). So on the desktop layout in Safari's
   engine (html.safari, set in index.html) the pools stay still, as on phones, and the grain is a picture of itself
   (img/texture/grain.png: the same noise, drawn once by Chrome at twice the size) instead of noise worked out afresh on
   every redraw. Chrome and phones never see this. */
@media (min-width:901px){
  html.safari .light::before{animation:none}
  html.safari .light::after{background:url(../img/texture/grain.png) 0 0 / 180px 180px}
  /* and the page keeps a layer of its own: Safari moves it through the holds (js/main.js, the track), and when the track
     came off as the thread completed, Safari redrew the whole screen (a hitch of 0.2s; 0.09s with the layer kept) */
  html.safari #page{will-change:transform}
  /* what changes (words typing and fading in, the polaroids thrown and tossed, the handwriting written and rubbed out,
     the posters) is drawn on small layers of its own: drawn into the page, every change made Safari redraw the linen and
     the stage light beneath it. Measured on Luke's Mac at a reading pace: leaving the hero 36 to 44 frames a second, the
     thrown polaroids 39 to 56, About to Credits 47 to 56; the opening as smooth as before. (Layers on the lights
     themselves cost more than they saved: Safari blends those big soft layers on every frame.) */
  /* (the name's heading is not in this list: it had no box until entry 132, so it never had a layer of its own, and Safari
     stays as Luke tested it) */
  html.safari #home .polaroid, html.safari .role, html.safari .print, html.safari .pnote,
  html.safari .bio h2, html.safari .aquote, html.safari .bio p, html.safari .now, html.safari .cvline,
  html.safari .cred-side h2, html.safari .quotes figure, html.safari .tile .art, html.safari .contact-head,
  html.safari .formcell{will-change:transform}
  /* each light drawn once into a picture of itself, the pool and its grain together (js/light.js, bake): as Currently, the
     quotes or the form appeared, Safari redrew the light behind them, and its gradient and masked grain took the frame to
     about 0.1s on Luke's Mac (13 Oct 2026); the picture redraws in a fraction of that. Until it is ready (or if it can't
     be made), the light is drawn as above. */
  html.safari .light.baked::before{background:var(--bake) 0 0 / 100% 100% no-repeat}
  html.safari .light.baked::after{display:none}
  /* while the eraser is at work, the subtitle is drawn on a canvas instead (js/main.js, rubOnCanvas) */
  /* (!important: the opening sets the drawings visible on the elements themselves, js/main.js inkWords, which beat a plain rule:
     on a first visit the subtitle stayed whole behind the canvas, Luke 12 Oct 2026) */
  html.safari .role.on-canvas svg.ink, html.safari .role.on-canvas svg.uline, html.safari .role.rubbed svg.ink, html.safari .role.rubbed svg.uline{visibility:hidden !important}
}
@keyframes polToss{0%{transform:rotate(3deg); opacity:1} 99.5%{opacity:1} 100%{transform:translate(3vw, -75vh) rotate(-11deg); opacity:0}}   /* phones: the hero polaroid tossed by the scroll (see the phone section) */

/* a section the menu, the arrow or Back to top has taken focus to (js/main.js, focusSection, entry 132): no ring round the
   whole section; the next Tab carries on from inside it */
main section[tabindex="-1"]{outline:none}

/* reduced motion (entry 132): everything shown and nothing moving, as Luke asked. Before, these still moved: the caret
   after "Holman" (a still bar, read as a stray letter), a poster's hop on hover or focus and, on phones and the iPad
   upright, the chosen poster's endless bob; the envelope folding and flying off; field labels lifting away; the CV arrow,
   the Contact icons and the envelope tipping on hover; the credit lines sliding up on a poster and under the wall; the hero
   arrow drawing itself and bobbing. Now they
   change at once, or not at all (colours and fades stay: they don't move). */
@media (prefers-reduced-motion: reduce){
  .caret{display:none}
  html .tile:hover, html .tile:has(:focus-visible), html .tile.tapped, html.tablet .tile.tapped{animation:none}
  html .tile .cap > *{transform:none}
  html .credit-line a.swap, html.tablet .credit-line a.swap{transform:none}
  .send .env, .send .e-flap, .send.sealing .env, .send.sealing .e-flap, .send.flying .env, .field label{transition:none}
  .send:hover .env, .cv:hover svg, .elsewhere a:hover, .elsewhere a:focus-visible{transform:none}
  .hdr nav a::after{transition:none}
  /* the hero arrow: it still drew itself and bobbed for ever (the rules that start them outranked the old reduced-motion
     ones); now it is simply there */
  html.shown .arrow svg, html.shown .arrow.bob svg, html.shown .arrow path{animation:none} html.shown .arrow path{stroke-dashoffset:0}
}

/* Very wide screens (entry 150; Luke, 15 Oct 2026: on monitors 2560 wide and more the page felt empty, the polaroids far
   left and the bio far right): past 1920px the layout stops spreading and sits in the middle, 1920px wide, with the linen
   running on to the edges. 1920 and narrower are exactly as before. (The thread's S beside About follows, in js/main.js.) */
@media (min-width: 1921px){
  .wrap{max-width:1920px; margin-left:auto; margin-right:auto}
  .hdr{left:calc(var(--m) + max(0px, (100% - 1920px) / 2)); right:calc(var(--m) + max(0px, (100% - 1920px) / 2))}
  .tumble{left:calc(var(--m) - 56px + max(0px, (100% - 1920px) / 2))}
  footer{max-width:1920px; margin-left:auto; margin-right:auto}
}
