/* Luke Holman Photography. Rebuilt from Site18 on the Claude Design canvas (desktop 1440, phone 390), with only the settings
   Site18 uses: the 12-column wall (grid12), three columns, gaps 2vw with matching margins, the name on one line at 57.6%
   of the page, the links on one line, Portfolio and @luke.bholman on the rule, About as one row of the wall. */

/* ---------- fonts ----------
   Obviously Wide Bold (the name, lightbox titles) and Neue Haas Grotesk Text Regular (all small type), both served by
   Adobe Fonts: the kit "Luke Holman Photography website" (igi1fzw) is linked in each page's <head> and is tied to Luke's
   Creative Cloud. They replaced ABC Ginto Nord Bold and Founders Grotesk Regular on 10 Nov 2026 (Notion Decision Log),
   and are fitted to sit exactly where those sat, so the numbers below still describe the original layout. */

html{-webkit-text-size-adjust:100%;text-size-adjust:100%;background:#FFFFFF}
body{margin:0;background:#FFFFFF;color:#141414}
a{color:inherit;text-decoration:none}
h1,h2{margin:0;font:inherit}

/* ---------- tokens ----------
   The layout was drawn around Ginto Nord Bold: "LUKE HOLMAN" at -0.05em, ink 8.387em wide, capitals 0.70em tall, in a box
   of line-height .74 that runs from the top of the capitals to the baseline, so everything can align to it.
   Obviously Wide Bold, "LUKE HOLMAN" at -0.06em: the ink is 9.881em wide and starts 0.074em in; capitals are 0.75em tall.
   At the same 57.6% width its capitals are shorter than Ginto's, so the name is stretched 110% tall (scaleY 1.0996), which
   makes them exactly Ginto's height. Line-height .5704 and a .3015em bottom margin then give the stretched name Ginto's box,
   with the baseline in the same place. To change the name's font, spacing or stretch, all of these move together.
   Small type was drawn around Founders Grotesk: at line-height 1 its capitals (0.63em) start 0.22em below the line top.
   Neue Haas Grotesk keeps the 14px size but font-size-adjust scales it so its capitals are 0.63em too; its baseline then
   sits 0.06em higher than Founders', so the links, rule labels and lightbox controls are set 0.06em lower (the .1601em
   and .0599em below are .22em less that difference, and the difference itself). */
.lh,.lb{--m:max(1.6vw,16px);--ink:#141414;--mute:#555555;--eo:cubic-bezier(.23,1,.32,1)}
.lh{display:flex;flex-direction:column;width:100%;min-height:100vh;background:#FFFFFF;color:var(--ink)}
.site{--g:2vw;--cols:3;--nwf:.576;--air:3.6;--topk:1.5;--under:calc(4 * var(--g));
--col:calc((100cqw - (var(--cols) - 1) * var(--g)) / var(--cols));
--name-w:calc(100cqw * var(--nwf));
--fs:calc(var(--name-w) / 9.881);
--top:max(calc(var(--g) * var(--topk)), 20px);--lgap:24px;
flex:1 1 auto;container-type:inline-size;box-sizing:border-box;padding:0 var(--m);background:#FFFFFF;color:var(--ink);overflow-x:clip}
@media (min-width: 900px){
/* the wall's gutter is 2vw (29px at 1440) and the page margin takes the same, so name, rule, wall and footer sit on one unit */
.site{--wg:2vw;--m:var(--wg);--under:calc(var(--g) * var(--air) - 21px - var(--name-w) * .002385)} /* .002385 = .02 of Ginto's size (name-w / 8.387) */
.rule > *{position:relative;top:.0599em}
}

/* ---------- type ---------- */
.name{display:block;font-family:'obviously-wide',sans-serif;font-weight:700;font-size:var(--fs);line-height:.5704;letter-spacing:-.06em;white-space:nowrap;margin:0 0 .3015em -.074em;transform:scaleY(1.0996);transform-origin:left top}
.t{font-family:'neue-haas-grotesk-text',sans-serif;font-size:14px;font-size-adjust:cap-height .63;line-height:1;letter-spacing:.08em;text-transform:uppercase;white-space:nowrap}
.t a{position:relative;transition:opacity 200ms ease}
.t a::after{content:'';position:absolute;inset:-15px -10px}
@media (hover: hover) and (pointer: fine){.t a:hover{opacity:.55}}
.t a:focus-visible,.ph:focus-visible,.lb button:focus-visible{outline:2px solid var(--ink);outline-offset:4px}
/* current page: a 1px line the weight of the rule, stopping at the last letter (not under the trailing tracking) */
.cur::before{content:'';position:absolute;left:0;right:.08em;bottom:-4px;height:1px;background:currentColor}
.dim{color:var(--mute)}
/* skip link: hidden until a keyboard user tabs to it, then it sits in the top left corner in the site's small capitals */
.skip{position:absolute;left:14px;top:14px;z-index:50;padding:10px 12px;background:#FFFFFF;color:#141414;outline:2px solid #141414;outline-offset:0;transform:translateY(-200%)}
.skip:focus{transform:none}
main:focus{outline:none}
/* read out, not shown: "(opens in a new tab)" on the outside links */
.sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}

/* ---------- header ----------
   Every distance is a multiple of the photo gap G (--g): page top 1.5 G, the name's baseline to the rule 3.6 G, rule to photos 1 G.
   The links sit level with the top of the capitals and end flush with the right edge of the photos. */
.top{display:flex;align-items:flex-start;justify-content:space-between;padding-top:var(--top)}
.nav{display:flex;gap:var(--lgap);margin:-.1601em -.08em 0 0}

/* ---------- rule: the labels sit on it ---------- */
.rule{display:flex;justify-content:space-between;align-items:baseline;height:14px;margin-top:var(--under);padding-bottom:7px;border-bottom:1px solid var(--ink)}
.rule > :last-child{margin-right:-.08em}

/* ---------- wall ----------
   Every photo on a 12-column grid, portraits 3 columns, landscapes 6. All rows share one height (a landscape's 3:2 at 6 columns),
   so every split lines up with the rows above and below. Row 4 is the bigger row (8 + 4), row 5 is three across (4 + 4 + 4).
   No photo may be taller than the window: every row is capped at the window's height. */
.wall{margin-top:var(--m)}
.g12{--gg:var(--wg,var(--m));--gcw:calc((100cqw - 11 * var(--gg)) / 12);
--grh:calc((6 * var(--gcw) + 5 * var(--gg)) / 1.5);
--rh:min(var(--grh), calc(100svh - 2 * var(--m)));
--rb:min(calc((8 * var(--gcw) + 7 * var(--gg)) / 1.5), calc(100svh - 2 * var(--m)));
--rs:calc((4 * var(--gcw) + 3 * var(--gg)) / 1.5);
display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--gg);grid-auto-rows:var(--rh)}
.cell{position:relative;min-width:0}
.g12 .cell{height:100%}
.ph{display:block;position:relative;width:100%;height:100%;padding:0;border:0;background:none;font:inherit;color:inherit;cursor:pointer;transform-origin:center;transition:transform 200ms var(--eo)}
.ph picture{display:contents}
.ph img{display:block;width:100%;height:100%;object-fit:cover;object-position:var(--pos)}
.ph:focus-visible{z-index:2}
@media (hover: hover) and (pointer: fine){.ph:hover{transform:scale(1.012);z-index:2}}
.ph:active{transform:scale(.99);transition-duration:120ms}

/* ---------- footer: a hairline, then three links: left, centre, right ----------
   The hairline sits one margin below the last photo (Luke, 10 Nov: was 3 G), the same air as everywhere else on the wall. */
/* --sep: the space between groups (wall, Back to top, footer; Places and footer on About). One margin on desktop (29px at 1440);
   on a phone, where the margin is 14px, two of them (28px), so the end of the page breathes (Luke, 10 Nov). */
.foot{--sep:max(var(--m), 28px);padding-bottom:calc(2 * var(--g))}
/* Inside Instagram, Facebook, Threads, TikTok and LinkedIn the page runs down to the phone's rounded corners and home bar, so the
   footer links get twice the room under them there (Luke, 10 Nov: Shop sat tight in the corner). The head script adds .inapp. */
.inapp .foot{padding-bottom:calc(4 * var(--g) + env(safe-area-inset-bottom, 0px))}
.foot .links{display:grid;grid-template-columns:1fr auto 1fr;column-gap:24px;margin-top:var(--sep);border-top:1px solid var(--ink);padding-top:9px}
.foot .links a::after{inset:-7px -10px}
/* Portfolio only: "Back to top" centred on its own line, one group-space below the wall and one above the footer's
   hairline (Luke, 10 Nov: was right-aligned on the hairline, which felt cluttered on a phone) */
.foot .back{display:flex;justify-content:center;margin-top:var(--sep)}
.foot .back a{margin-right:-.08em}
/* Back to top moves focus to the name so keyboard users start again from the top; the name is not a control, so no ring */
.name:focus{outline:none}
/* the name is a link home on every page (Luke, 10 Nov); on Portfolio it scrolls back to the top instead (js/main.js) */
.name .home{color:inherit;text-decoration:none}
.name .home:focus-visible{outline:2px solid var(--ink);outline-offset:4px}
.foot .links a:nth-child(1){justify-self:start}
.foot .links a:nth-child(2){justify-self:center}
.foot .links a:nth-child(3){justify-self:end;margin-right:-.08em}

/* ---------- lightbox ----------
   One fixed frame: Close sits exactly where the nav sits, Previous / count / Next sit the same distance from the foot,
   the caption line is pinned above them, and the photo takes every pixel that is left. Nothing but the photo changes size. */
html:has(.lb){overflow:hidden}
.lb{--top:max(3vw,20px);--sx:0px;--head:34px;
position:fixed;inset:0;z-index:40;box-sizing:border-box;display:grid;grid-template-rows:minmax(0,1fr) auto;
padding:calc(var(--top) + var(--head)) var(--m) calc(var(--top) - 2px);
background:#FFFFFF;color:var(--ink);outline:none;overscroll-behavior:contain;-webkit-tap-highlight-color:transparent;animation:lbIn 200ms ease-out}
.lb-fig{margin:0;min-height:0;display:grid;grid-template-rows:minmax(0,1fr) auto}
/* one cell the size of the stage: without it the row shrank to the photos mounted, so a landscape between two landscapes
   sat at the top while one next to a portrait sat in the middle (Luke, 10 Nov: Orange 2CV higher than Amélie's corner shop) */
.lb-stage{container-type:size;min-height:0;margin:0 var(--sx);display:grid;grid-template:minmax(0,1fr) / minmax(0,1fr);place-items:center;animation:lbSettle 280ms var(--eo)}
.lb-stage picture{display:contents}
.lb-img{grid-area:1 / 1;display:block;width:min(100cqw,calc(100cqh * var(--ar)));height:auto;aspect-ratio:var(--ar);visibility:hidden;cursor:default}
.lb-img.on{visibility:visible}
@media (hover: hover) and (pointer: fine){.lb-img{cursor:pointer}} /* clicking the photo moves on or back */
.lb-cap{display:flex;flex-wrap:wrap;align-items:baseline;align-content:flex-start;justify-content:center;column-gap:18px;row-gap:6px;margin-top:16px;min-height:41px;text-align:center}
.lb-cap span{cursor:auto}
.lb-cap .t{margin-right:-.08em}
.lb-title{font-family:'obviously-wide',sans-serif;font-weight:700;font-size:17px;line-height:21px;letter-spacing:-.015em}
.lb-foot{display:grid;grid-template-columns:1fr auto 1fr;align-items:baseline;column-gap:24px;margin-top:max(2vw,24px)}
.lb-foot > *{position:relative;top:.0599em}
.lb-n{display:grid;grid-template-columns:1fr auto 1fr;column-gap:.24em;cursor:auto}
.lb-n span:first-child{text-align:right}
.lb-n span:nth-child(2){letter-spacing:0}
.lb-n span:last-child{padding-left:.08em}
.lb button{position:relative;-webkit-appearance:none;appearance:none;background:none;border:0;border-radius:0;padding:0;margin:0;color:inherit;cursor:pointer;transition:opacity 200ms ease}
.lb button::after{content:'';position:absolute;inset:-16px -24px}
.lb .lb-close{position:absolute;top:calc(var(--top) - .1601em);right:calc(var(--m) - .08em)}
.lb .lb-close::after{top:calc(-1 * var(--top));right:calc(-1 * var(--m))}
.lb-prev{justify-self:start}
.lb-next{justify-self:end;margin-right:-.08em}
.lb .lb-prev::after{left:calc(-1 * var(--m));bottom:calc(2px - var(--top))}
.lb .lb-next::after{right:calc(-1 * var(--m));bottom:calc(2px - var(--top))}
@media (hover: hover) and (pointer: fine){.lb button:hover{opacity:.55}}
.lb button:active{opacity:.55;transition-duration:0s}
.lb-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media (min-width: 600px){.lb-cap{flex-wrap:nowrap;min-height:21px}}
@media (min-width: 900px){.lb{--head:0px;--sx:70px}}
/* a photo changed by swiping slides in a little from the side it came from */
.lb-img.from-r{animation:fromR 260ms var(--eo)}
.lb-img.from-l{animation:fromL 260ms var(--eo)}
@keyframes fromR{from{opacity:0;transform:translateX(32px)}}
@keyframes fromL{from{opacity:0;transform:translateX(-32px)}}
@keyframes lbIn{from{opacity:0}}
@keyframes lbSettle{from{transform:scale(.985)}}

/* ---------- entrance: the name fades in centred, holds, glides left; the rest follows, barely there ----------
   The whole sequence plays once per session. After that, and for anyone who turns motion off, the page arrives in one plain fade.
   Opacity and transform only, so nothing shifts and nothing is ever unclickable. Without JavaScript nothing is hidden (.js). */
.js .pre .ent,.js .pre .rule{opacity:0}
/* Safety net: if the script never runs (blocked or failed), everything still appears after 4s instead of a blank page. */
@keyframes failsafe{to{opacity:1;transform:none}}
.js .pre .ent,.js .pre .rule,.js .pre .g12 .ph img{animation:failsafe 0s 4s forwards}
@keyframes fade{from{opacity:0}}
@keyframes rise{from{opacity:0;transform:translateY(var(--dy,6px))}}
@keyframes slide{from{transform:translateX(calc((100cqw - var(--name-w)) / 2))}}
/* phone (Luke, 10 Nov): the name already fills the width, so it arrives in the middle of the screen instead and glides up
   into place. --ny (how far below its place the screen's centre is) is measured by main.js before the sequence starts. */
@keyframes slideV{from{transform:translateY(var(--ny,0px))}}
.run .ent:not(.e-rtext),.run .rule{animation:fade 240ms ease-out both}
@media (prefers-reduced-motion: no-preference){
.run.full{--e0:900ms}
.run.full.still{--e0:260ms}
.run.full .e-name{animation:fade 400ms ease-out both}
.run.full .e-slide{animation:slide 1200ms cubic-bezier(.32,.72,0,1) 400ms both}
.run.full.still .e-slide{animation:none}
/* the same iOS curve and timing as the desktop glide, so both feel like one gesture */
.run.full.vert .e-slide{animation:slideV 1200ms cubic-bezier(.32,.72,0,1) 400ms both}
/* phone: the links wait for the name to settle */
.run.full.vert{--e0:1050ms}

/* ---------- phone: each photo reveals itself once its image has arrived ----------
   (Luke, 10 Nov: the photos "flashed" in: their fade could finish before the image loaded.) The cell stays put; the image
   inside fades and rises 14px on a long exponential ease-out. main.js adds .ld when the image has loaded, and on the first
   screen it holds each one back so they ripple down in order after the name settles (and gently on return visits).
   Photos further down get the same reveal as they load while scrolling. Desktop keeps its entrance. */
@media (max-width: 899px){
.js .pre .g12 .cell{opacity:1}
.run .g12 .e-ph{animation:none}
.js .g12 .ph img{opacity:0;transform:translateY(14px);transition:opacity 900ms cubic-bezier(.25,.1,.25,1),transform 1100ms cubic-bezier(.16,1,.3,1)}
.js .g12 .cell.ld .ph img{opacity:1;transform:none}
}
@media (max-width: 899px) and (prefers-reduced-motion: reduce){
.js .g12 .ph img{transform:none;transition:opacity 400ms ease}
}
.run.full .e-nav{--dy:4px;animation:rise 500ms var(--eo) var(--e0) both}
.run.full .rule{animation:fade 500ms ease-out calc(var(--e0) + 80ms) both}
.run.full .e-rtext{--dy:4px;animation:rise 500ms var(--eo) calc(var(--e0) + 140ms) both}
.run.full .e-ph{--dy:6px;animation:rise 600ms cubic-bezier(.16,1,.3,1) calc(var(--e0) + 200ms + var(--pd, 0ms)) both}
}
@media (prefers-reduced-motion: reduce){
.ph,.ph:hover,.ph:active{transform:none;transition:none}
.lb,.lb-stage,.lb-img.from-r,.lb-img.from-l{animation:none}
}
.pgin{animation:pgIn 260ms ease-out both}
@keyframes pgIn{from{opacity:0}}
@media (prefers-reduced-motion: reduce){.pgin{animation:none}}

/* ---------- About: one row of the wall ----------
   Portrait on columns 1 to 3 (one portrait slot, at the wall's own row height), 4 to 6 left empty for air,
   each section a label on 7 to 9 with its text or list on 10 to 12, Bio above Places. */
.about{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));column-gap:var(--wg,var(--m));margin-top:var(--m);align-items:start}
.ab-pic{grid-column:1 / span 3;min-width:0}
.ab-pic picture{display:contents}
.portrait{display:block;width:100%;height:auto;aspect-ratio:4 / 5;object-fit:cover;object-position:50% 30%;background:#D8CDBE}
.ab-info{grid-column:7 / span 6;display:flex;flex-direction:column;gap:calc(2 * var(--wg,var(--m)));min-width:0}
.sec{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));column-gap:var(--wg,var(--m));border-top:1px solid var(--ink);padding-top:9px}
.sec .lab{grid-column:1 / span 3;margin:0;font-weight:400;font-size:14px}
.sec .bio,.sec .rows{grid-column:4 / span 3;min-width:0}
/* Neue Haas at 14px on the 16px line spacing (ex-height .4699 of 16px): matching Founders' lowercase exactly was 13px, too small to read */
.bio{font-family:'neue-haas-grotesk-text',sans-serif;font-size:16px;font-size-adjust:ex-height .4699;line-height:1.4;padding-bottom:4px}
.bio p{margin:0 0 .75em}.bio p:last-child{margin:0}
.rows{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.rows .row{display:flex;justify-content:space-between;align-items:baseline;gap:16px;padding:0 0 9px}
.rows .row + .row{border-top:1px solid var(--ink);padding-top:9px}
@media (min-width: 900px){
/* no rule on About, so the page moves up into its place (Luke, 10 Nov): the portrait's top edge and the sections' hairlines
   sit exactly on the line where Portfolio's rule is (the rule is 14px tall, 7px padding, then its 1px line) */
.ab-rule{display:none}
.about{margin-top:calc(var(--under) + 21px)}
.portrait{aspect-ratio:auto;height:calc((6 * ((100cqw - 11 * var(--wg)) / 12) + 5 * var(--wg)) / 1.5)}
}

/* ---------- smaller screens ---------- */
@media (max-width: 899px){
/* one unit, the photo gap G: page top 1.5, name to rule 4, rule to photos 1, side margins 14px */
.lh,.lb{--m:14px}
.site{--cols:3;--g:max(2vw,14px);--name-w:100cqw;--under:calc(4 * var(--g))}
.site .top{display:flex;flex-direction:column;align-items:stretch;padding-top:calc(1.5 * var(--g))}
/* all four links sit on the rule themselves, one line the width of the name (the rule's two labels would only repeat them),
   and the rule thickens under the page you are on */
.site .nav{justify-content:space-between;gap:10px;margin:calc(var(--under) - 7px - .8101em) -.08em 0 0;padding-bottom:calc(7px - .0599em)}
.site .rule{position:relative;margin-top:0;padding-bottom:0}
.site .rule > *{position:absolute;visibility:hidden}
/* the current page keeps the desktop's 1px line (Luke, 10 Nov: the 3px bar was too heavy) */
/* no hover here, so every link gets a 44px tall target */
.t a::after{inset:-15px -7px}
.foot .links a::after{inset:-15px -8px}
.lb button{padding:15px 0;margin:-15px 0}

/* the wall keeps its pairings. Two columns: portraits side by side, landscapes and the feature across both.
   Each desktop row breaks into phone rows in place, so the colour and mood groups stay together. */
.g12{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:none!important;grid-auto-rows:auto;grid-auto-flow:row dense}
.g12 .cell{grid-column:auto!important;grid-row:auto!important;height:auto}
.g12 .cell.l,.g12 .cell.feat{grid-column:span 2!important}
.g12 .ph{height:auto;max-height:calc(100svh - 2 * var(--m))}
.g12 .cell.p .ph{aspect-ratio:0.726}
.g12 .cell.l .ph{aspect-ratio:1.5}

.about{grid-template-columns:1fr;row-gap:32px}
.ab-pic,.ab-info{grid-column:1 / -1}
.ab-pic{max-width:66%}
.sec{grid-template-columns:minmax(0,1fr) minmax(0,2fr)}
.sec .lab{grid-column:1}.sec .bio,.sec .rows{grid-column:2}
}
@media (max-width: 599px){
.site{--cols:2;--g:14px}
/* lightbox: the photo at the full width of the wall, title over place, a thumb sized target for each control */
.lb-stage{padding:0 var(--m)}
.lb-fig{gap:14px}
.lb-img{max-width:100%;max-height:calc(100vh - 200px);max-height:calc(100dvh - 200px)}
/* nowrap: a wrapping column packs to the left (align-content), which put the caption about 76px off centre */
.lb-cap{flex-direction:column;flex-wrap:nowrap;align-items:center;row-gap:8px}
.lb-foot{gap:8px;padding:0 var(--m) max(6px,env(safe-area-inset-bottom))}
.lb-foot button{padding:21px 0;margin:0;min-width:96px}
.lb-foot button:first-child{text-align:left}
.lb-foot button:last-child{text-align:right;margin-right:-.08em}
}

/* ---------- 404 (Cloudflare Pages serves site/404.html for any address that does not exist) ----------
   Dry on purpose (Luke, 10 Nov): the About page's frame with no entrance animation and no script, one line in the
   lightbox title's style, and the footer held to the foot of the window however short the page is. */
.nf-site{display:flex;flex-direction:column}
.nf{flex:1 0 auto;margin-top:calc(var(--g) * 2)}
.nf-h{margin:0;font-family:'obviously-wide',sans-serif;font-weight:700;font-size:17px;line-height:21px;letter-spacing:-.015em}
