/* Tetyana's Piano Studio - shared stylesheet.
   Extracted 2026-08-10 from the single inline <style> that used to live in
   index.html, when the site grew from one page to three (home, /about,
   /notes). All three pages link this file; nothing here is page-specific
   except where a rule is namespaced to a page body-class (search
   "page-about" / "page-notes" / "footer-slim" below). */
:root{
  --ink:#101010; --grey:#585858; --line:#e5e5e5; --bg2:#f6f6f6; --tile:#131313;
  --display:"ivypresto-display", Georgia, serif;
  --sans:"freight-sans-pro", 'Helvetica Neue', Arial, sans-serif;
  --ease:cubic-bezier(.16,1,.3,1);
  /* LiftKit scale (@chainlift/liftkit-css globals.css) */
  --wholestep:1.618; --halfstep:1.272; --quarterstep:1.128; --eighthstep:1.061;
  --lk:18px;
  --t-sm:calc(var(--lk) / var(--halfstep));       /* ~14.2 eyebrow/legal */
  --t-xs:calc(var(--lk) / var(--quarterstep));    /* ~16 card body/nav */
  --t-md:var(--lk);                                /* 18 body */
  --t-md2:calc(var(--lk) * var(--eighthstep));     /* ~19.1 hero sub */
  --t-lg:calc(var(--lk) * var(--quarterstep));     /* ~20.3 card q */
  --t-xl:calc(var(--lk) * var(--halfstep));        /* ~22.9 creds/contact */
  --t-2xl:calc(var(--lk) * var(--wholestep) * var(--halfstep)); /* ~37 h2 */
  --t-3xl:calc(var(--lk) * var(--wholestep) * var(--wholestep) * var(--wholestep)); /* ~76 h1 cap */
}
*{box-sizing:border-box; margin:0; padding:0}
html{scroll-behavior:smooth}
body{font-family:var(--sans); color:var(--ink); background:#fff; font-size:var(--t-md); line-height:1.55}
img{max-width:100%; display:block}
a{color:inherit; text-decoration:none}
.wrap{padding-left:72px; padding-right:72px}
.eyebrow{font-size:var(--t-sm); letter-spacing:.14em; color:#6b6b6b; text-transform:uppercase; font-weight:500}
h1,h2{font-family:var(--display); font-weight:300; text-wrap:balance}
p{text-wrap:pretty}
.btn{display:inline-block; background:var(--ink); color:#fff; border-radius:999px; padding:13px 28px 15px; font-weight:500; font-size:17px;
     transition:transform .35s var(--ease), box-shadow .35s var(--ease), background .2s}
.btn:hover{background:#000; transform:translateY(-2px); box-shadow:0 10px 28px rgba(16,16,16,.22)}
.btn:active{transform:translateY(0) scale(.98); box-shadow:0 4px 12px rgba(16,16,16,.18)}

/* Nav: sticky, gains hairline + blur once scrolled. Present on all three pages. */
nav{position:sticky; top:0; z-index:20; display:flex; justify-content:space-between; align-items:center;
    padding:22px 72px; background:rgba(255,255,255,.86); backdrop-filter:blur(0px);
    border-bottom:1px solid transparent; transition:border-color .3s, backdrop-filter .3s, padding .3s}
nav.scrolled{border-bottom-color:var(--line); backdrop-filter:blur(14px); padding-top:14px; padding-bottom:14px}
.wordmark{font-family:var(--display); font-size:22px; font-weight:400; display:flex; align-items:center; gap:13px}
.wordmark img{height:36px; width:auto}
.footmark{height:76px; width:auto; filter:invert(1); margin-bottom:30px}
/* nav-right groups the link set with the CTA and (mobile-only) the menu
   toggle, so nav itself stays a plain two-child flex (wordmark, nav-right)
   exactly as it was when .navlinks held the CTA directly - desktop spacing
   is unchanged, see the max-width:960px block below for the mobile split. */
.nav-right{display:flex; gap:34px; align-items:center}
.navlinks{display:flex; gap:34px; align-items:center; font-weight:500; font-size:var(--t-xs)}
.navlinks a{position:relative; padding-bottom:3px}
.navlinks a::after{content:""; position:absolute; left:0; bottom:0; height:1px; width:100%;
    background:var(--ink); transform:scaleX(0); transform-origin:right; transition:transform .35s var(--ease)}
.navlinks a:hover::after{transform:scaleX(1); transform-origin:left}
.nav-right .btn{padding:9px 20px 11px; font-size:16px}
.navlinks a.current{opacity:.5; pointer-events:none}
.navlinks a.current::after{content:none}
/* Mobile menu toggle. Quiet text affordance (no hamburger icon), matching
   the nav links' own weight/size. Hidden above the 960px breakpoint where
   the full ribbon shows; see the max-width:960px block for its open state
   and the sheet it controls. position:relative so the ::after hit-slop
   below can expand the tap target without touching the visible box - the
   text stays exactly the size and position it is now. */
.nav-toggle{display:none; position:relative; background:none; border:0; padding:10px 2px; margin:0;
  font-family:var(--sans); font-weight:500; font-size:var(--t-xs); color:var(--ink); cursor:pointer}
.nav-toggle:focus-visible{outline:2px solid var(--ink); outline-offset:3px; border-radius:2px}
/* Invisible hit-slop: WCAG 2.5.8 (AA) needs 24x24 CSS px, Apple HIG/Android
   Material target 44x44. The rendered box (~36x36-38 at the mobile
   font-size/padding below) already clears AA but not the 44px best-practice
   floor. Extending the tap area via an absolutely-positioned pseudo-element
   (rather than more padding) grows the target with zero effect on layout or
   the visible glyph. */
.nav-toggle::after{content:""; position:absolute; inset:-7px -6px}

/* Hero */
.hero{display:grid; grid-template-columns:1fr 46%; gap:56px; align-items:stretch}
.hero-copy{padding:88px 0 96px 72px; align-self:center}
.hero-copy .eyebrow{margin-bottom:26px; display:block}
.hero h1{font-size:clamp(37px,4.5vw,var(--t-3xl)); line-height:1.06; margin-bottom:26px}
.hero h1 .mask{display:block; overflow:hidden; padding-bottom:.14em; margin-bottom:-.14em}
.hero h1 .mask > span{display:block}
.hero p{font-size:var(--t-md2); color:var(--grey); max-width:29em; margin-bottom:30px}
.ipar{display:inline-block; transform:skewX(-12deg)}
.np{white-space:nowrap}  /* stops the inline-block "(" stranding at a line end */
.hero-ctas{display:flex; gap:18px 26px; align-items:center; font-weight:500; flex-wrap:wrap}
.arrow-stack{display:flex; flex-direction:column; align-items:flex-start; gap:9px}
.hero-ctas .arrow-link .arr{display:inline-block; transition:transform .3s var(--ease)}
.hero-ctas .arrow-link:hover .arr{transform:translateX(5px)}
.hero-img{background:#eee; overflow:hidden; position:relative}
.hero-img img{width:100%; height:100%; object-fit:cover; object-position:26% 32%; filter:grayscale(1)}
.hero-img::after{content:""; position:absolute; inset:14px; border:1px solid rgba(255,255,255,.5); pointer-events:none}
.flourish{height:15px; width:auto; display:block; margin-bottom:20px; opacity:.9}
.hero h1 em{font-style:italic; font-weight:400}

/* Load-in choreography (hero only, plays once page is visibly ready) */
@media (prefers-reduced-motion: no-preference){
  html.js .hero-copy .eyebrow, html.js .hero p, html.js .hero-ctas{opacity:0; transform:translateY(22px)}
  html.js .hero h1 .mask > span{transform:translateY(132%)}
  html.js .hero-img img{opacity:0; transform:scale(1.05);
    filter:grayscale(1) contrast(.82) brightness(1.12)}
  html.ready .hero-copy .eyebrow{animation:rise .9s var(--ease) .1s forwards}
  html.ready .hero h1 .mask > span{animation:maskrise 1s var(--ease) .22s forwards}
  html.ready .hero p{animation:rise .9s var(--ease) .42s forwards}
  html.ready .hero-ctas{animation:rise .9s var(--ease) .55s forwards}
  html.ready .hero-img img{animation:develop 1.6s var(--ease) .15s forwards}
}
@keyframes rise{to{opacity:1; transform:translateY(0)}}
@keyframes maskrise{to{transform:translateY(0)}}
@keyframes develop{60%{opacity:1} to{opacity:1; transform:scale(1); filter:grayscale(1) contrast(1) brightness(1)}}

/* Scroll reveals */
html.js .reveal{opacity:0; transform:translateY(26px)}
html.js .reveal.in{opacity:1; transform:none;
  transition:opacity .8s var(--ease), transform .8s var(--ease);
  transition-delay:calc(var(--i,0)*90ms)}
@media (prefers-reduced-motion: reduce){
  html.js .reveal{opacity:1; transform:none}
}

/* Credentials */
.creds{background:var(--ink); color:#fff; display:flex; justify-content:space-between; gap:32px; padding:40px 72px 44px; flex-wrap:wrap}
.creds b{font-family:var(--display); font-weight:400; font-size:var(--t-xl); display:block; margin-bottom:5px}
.creds span{font-size:var(--t-sm); opacity:.66}

/* /about - "The quiet page" (rebuilt 2026-08-18 to the research-led Figma
   concept; rationale in the file + figma.com/design/ZBNMXRiiFCKgrcEmSRr2ni).
   One structural device: continuous prose in a 600px column. Rhythm rules:
   serif body 18px/1.6 = 28.8px leading; paragraph gap = half-leading (14px,
   inside Butterick's 50-100%-of-body band); block-quote margins = one full
   leading (29px, ETS 2.3.3); welcome (sans, first person) gaps = half its
   own 30.4px leading (15px). All .abt- prefixed: never bare global names
   (the .colophon lesson). freight-text-pro was already in the kit - the
   text-serif body cost nothing. */
.abt-hero{padding-top:40px}
.abt-plate{position:relative; margin:0}
.abt-plate img{width:100%; height:auto; display:block; filter:grayscale(1)}
.abt-hero .abt-plate img{height:min(64vh,600px); object-fit:cover; object-position:50% 38%}
.abt-plate::after{content:""; position:absolute; top:14px; left:14px; right:14px; bottom:14px;
  border:1px solid rgba(255,255,255,.5); pointer-events:none}
.abt-plate figcaption{font-size:14.5px; color:#6b6b6b; margin-top:12px; line-height:1.5}
/* captions sit outside the inset border's box */
.abt-mid .abt-plate::after, .abt-nch::after{bottom:calc(14px + 34px)}
.abt-col{max-width:600px; margin:0 auto}
.abt-welcome{padding-top:64px}
.abt-welcome h2{font-size:clamp(32px,3.2vw,44px); line-height:1.12; margin-bottom:30px}
/* first person, the site's sans voice: 19px/1.6 = 30.4 leading, gaps = half */
.abt-welcome p{font-family:var(--sans); font-size:19px; line-height:1.6; color:var(--ink);
  margin-bottom:15px}
.abt-welcome p:last-child{margin-bottom:0}
/* third person, the printed-programme register: freight-text serif */
.abt-bio{padding-top:0}
.abt-bio .abt-col > p, .abt-close .abt-col > p{
  font-family:"freight-text-pro", Georgia, serif;
  font-size:18px; line-height:1.6; color:#3c3c3c; margin-bottom:14px}
.abt-honours{margin:14px 0; padding:14px 0 14px 24px; border-left:1px solid #d8d8d8}
.abt-honours p{font-family:"freight-text-pro", Georgia, serif; font-size:18px; line-height:1.6;
  color:#3c3c3c; margin-bottom:7px}
.abt-honours ul{list-style:none; margin:0; padding:0}
.abt-honours li{font-family:"freight-text-pro", Georgia, serif; font-size:15.5px; line-height:1.55;
  color:#6b6b6b; margin-bottom:7px}
.abt-honours li:last-child{margin-bottom:0}
/* the page's one typographic moment: real ivypresto italic, CMOS block form */
.abt-quote{margin:29px 0 29px 40px}
.abt-quote p{font-family:var(--display); font-style:italic; font-weight:400;
  font-size:23px; line-height:1.48; color:var(--ink); margin-bottom:14px}
/* the bare global footer selector paints ink + 96px padding; this is an
   inline attribution, not a page footer - reset it fully */
.abt-quote footer{background:none; padding:0; font-family:var(--sans); font-size:15px; color:#6b6b6b}
/* the harpsichord plate is the pause between her two voices - slightly
   wider than the measure so the rhythm audibly changes, then the text
   resumes. It replaced the hairline rule: one device at the seam. */
.abt-mid{padding-top:44px; padding-bottom:44px}
.abt-mid .abt-plate{max-width:760px; margin:0 auto}
.abt-mid .abt-plate img{aspect-ratio:16/10; object-fit:cover; object-position:26% 45%}
/* the NCH print sits with the review it corroborates, at its native size */
.abt-nch{max-width:480px; margin:29px 0}
.abt-close{padding-top:28px; padding-bottom:96px}
@media (max-width:960px){
  .abt-hero{padding-top:16px}
  .abt-hero .abt-plate img{height:56vw; min-height:260px}
  .abt-welcome{padding-top:44px}
  .abt-welcome p{font-size:18px; margin-bottom:14px}
  .abt-bio .abt-col > p, .abt-close .abt-col > p{font-size:16.5px; margin-bottom:13px}
  .abt-honours p{font-size:16.5px}
  .abt-honours{padding-left:16px}
  .abt-quote{margin-left:20px}
  .abt-quote p{font-size:19px}
  .abt-mid{padding-top:24px; padding-bottom:24px}
  .abt-mid .abt-plate img{aspect-ratio:auto}
  .abt-nch{margin:26px 0}
  .abt-close{padding-bottom:64px}
}

/* FAQ */
.faq{background:var(--bg2); padding-top:148px; padding-bottom:0; overflow:hidden}
.faq h2{font-size:var(--t-2xl); margin-top:14px; margin-bottom:38px}
.faq-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:24px}
.faq-card{background:#fff; border:1px solid var(--line); border-radius:6px; padding:26px 26px 30px;
  transition:transform .4s var(--ease), box-shadow .4s var(--ease)}
.faq-card:hover{transform:translateY(-5px); box-shadow:0 16px 40px rgba(16,16,16,.09)}
.faq-card h3{font-family:var(--display); font-weight:400; font-size:var(--t-lg); line-height:1.2; margin-bottom:12px}
.faq-card p{font-size:var(--t-xs); color:var(--grey)}

/* Piano keys strip (extends the crest's piano-key crown motif) */
.keys{position:relative; height:92px; margin:76px -72px 0; background:#fff;
  border-top:1px solid var(--line); overflow:hidden}
.keys .row{display:flex; height:100%}
.wk{flex:1; background:#fff; border-right:1px solid var(--line); border-radius:0 0 4px 4px}
.row .wk:last-child{border-right:0}
.bk{position:absolute; top:-1px; height:56%; background:var(--ink); border-radius:0 0 3px 3px; z-index:2}
@media (prefers-reduced-motion: no-preference){
  .wk{animation:wpress 12s var(--ease) infinite; animation-delay:calc(var(--k)*.34s)}
  .bk{animation:bpress 12s var(--ease) infinite; animation-delay:calc(var(--k)*.34s + .17s)}
}
@keyframes wpress{0%,3%,100%{transform:none; background:#fff} 1.4%{transform:translateY(3px); background:#f1f1ef}}
@keyframes bpress{0%,3%,100%{transform:none} 1.4%{transform:translateY(3px)}}

/* Fees */
.fees{padding-top:88px; padding-bottom:96px; text-align:center}
.fees .flourish{margin-left:auto; margin-right:auto}
.fees h2{font-size:var(--t-2xl); margin-top:14px; margin-bottom:12px}
.fees .fees-sub{color:var(--grey); max-width:31em; margin:0 auto 44px}
.fees-calc{display:flex; flex-direction:column; align-items:center; gap:36px}
.terms .trow{display:flex; flex-direction:column; gap:1px; padding:11px 0 12px; border-bottom:1px solid var(--line)}
.terms .trow:first-of-type{border-top:1px solid var(--line)}
.terms .trow b{font-family:var(--display); font-weight:400; font-size:var(--t-md2)}
.terms .trow span{font-size:var(--t-xs); color:var(--grey)}
.terms .trow i{font-size:var(--t-sm); color:var(--grey); opacity:.75}
.seg-label{font-size:var(--t-sm); letter-spacing:.14em; text-transform:uppercase; color:#6b6b6b; font-weight:500; display:block; margin-bottom:14px}
.seg{display:flex; gap:10px; flex-wrap:wrap; justify-content:center}
.seg button{font-family:var(--sans); font-size:var(--t-xs); font-weight:500; color:var(--ink);
  background:#fff; border:1px solid var(--line); border-radius:999px; padding:11px 24px 13px; cursor:pointer;
  transition:background .25s, color .25s, border-color .25s, transform .25s var(--ease)}
.seg button:hover{border-color:var(--ink)}
.seg button:active{transform:scale(.97)}
.seg button[aria-pressed="true"]{background:var(--ink); color:#fff; border-color:var(--ink)}
/* Billing-critical line, promoted from footnote to statement 2026-08-18
   (his ask: "needs to be made more obvious"): display serif at reading
   size, ink not grey, ruled above and below so it reads as part of the
   price card's information, not small print. */
.fees-note{font-family:var(--display); font-weight:300; font-size:var(--t-lg); line-height:1.45;
  color:var(--ink); margin:0 auto; max-width:26em; padding:18px 0;
  border-top:1px solid var(--line); border-bottom:1px solid var(--line)}
.fees-calc .btn{margin-top:-4px}
.fees-out{border:1px solid var(--line); border-radius:6px; padding:52px 56px 56px; background:#fff; position:relative;
  width:min(100%, 520px)}
.fees-out::after{content:""; position:absolute; inset:10px; border:1px solid var(--line); border-radius:3px; pointer-events:none}
.fees-out .amount{font-family:var(--display); font-weight:300; font-size:clamp(64px,6.4vw,92px); line-height:1}
.fees-out .per{font-size:var(--t-xs); color:var(--grey); margin-top:8px}
.fees-out .term{margin-top:30px; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; align-items:baseline}
.fees-out .term b{font-family:var(--display); font-weight:400; font-size:var(--t-xl)}
.fees-out .term span{font-size:var(--t-sm); color:var(--grey)}

/* Videos */
.videos{padding-top:88px; padding-bottom:96px}
.videos h2{font-size:var(--t-2xl); margin-top:14px; margin-bottom:34px}
.vid-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:34px 24px}
/* Tiles are injected at the edge from her YouTube channel feed (../src/index.js).
   The only markup below the grid in this file is the fallback for a feed outage. */
.vid{display:block; color:inherit}
.vid-frame{position:relative; display:block; background:var(--tile); border-radius:6px;
  aspect-ratio:16/9; overflow:hidden;
  transition:transform .4s var(--ease), box-shadow .4s var(--ease)}
.vid:hover .vid-frame, .vid:focus-visible .vid-frame{transform:translateY(-4px); box-shadow:0 18px 44px rgba(16,16,16,.18)}
/* page is monochrome: thumbnails sit in grey and take their colour on hover */
.vid img{width:100%; height:100%; object-fit:cover; display:block;
  filter:grayscale(1) contrast(1.03); transition:filter .5s var(--ease), transform .7s var(--ease)}
.vid:hover img, .vid:focus-visible img{filter:grayscale(0) contrast(1); transform:scale(1.03)}
.vid-play{position:absolute; inset:0; margin:auto; width:54px; height:54px; border-radius:50%;
  background:rgba(255,255,255,.92); transition:transform .35s var(--ease)}
.vid-play::after{content:""; position:absolute; inset:0; margin:auto; width:0; height:0;
  border-left:16px solid var(--ink); border-top:10px solid transparent; border-bottom:10px solid transparent;
  transform:translateX(3px)}
.vid:hover .vid-play{transform:scale(1.12)}
.vid-title{display:block; margin-top:13px; font-size:var(--t-sm); line-height:1.45; color:var(--grey);
  transition:color .3s var(--ease)}
.vid:hover .vid-title{color:var(--ink)}
.vid-frame iframe{position:absolute; inset:0; width:100%; height:100%; border:0}
.vid-note{grid-column:1/-1; font-size:var(--t-md); color:var(--grey)}
.vid-note a{text-decoration:underline; text-underline-offset:3px}
.vid-more{margin-top:34px; font-weight:500}
.vid-more .arr{display:inline-block; transition:transform .3s var(--ease)}
.vid-more a:hover .arr{transform:translateX(5px)}

/* Contact (home page footer, full version with the map) */
footer{background:var(--ink); color:#fff; padding:96px 72px 56px}
footer h2{font-size:clamp(34px,4vw,50px); margin-bottom:52px}
.contact-row{display:grid; grid-template-columns:repeat(3,1fr); gap:40px; margin-bottom:64px}
.contact-row b{font-family:var(--display); font-weight:400; font-size:var(--t-xl); display:block; margin-bottom:9px}
.contact-row p{font-size:var(--t-xs); opacity:.66; max-width:24em}
.contact-row a{text-decoration:underline; text-underline-offset:3px}
.map{width:100%; height:380px; border:0; border-radius:6px; display:block;
  filter:grayscale(1) contrast(1.04); margin-bottom:56px}
.legal{font-size:var(--t-sm); opacity:.4}
/* /about and /notes close on a lighter version of the same footer: same
   ink block, crest and contact details, no live Google Maps embed (that
   embed only ever needs to load once, on the page with the full contact
   section) so the closing block never fires an extra third-party request. */
footer.footer-slim{padding-top:76px; padding-bottom:48px}
footer.footer-slim h2{margin-bottom:40px}
footer.footer-slim .contact-row{grid-template-columns:repeat(2,1fr); margin-bottom:0; max-width:56em}

/* Notes */
.notes-hero{padding-top:min(16vh,140px); padding-bottom:8px}
.notes-hero h2{font-size:var(--t-2xl); margin-top:14px; margin-bottom:18px}
.notes-lede{color:var(--grey); max-width:34em; font-size:var(--t-md2)}
.notes-index{padding-top:24px; padding-bottom:140px}
.notes-list{list-style:none}
.notes-list:not(:empty){border-top:1px solid var(--line); margin-bottom:8px}
.notes-list li{border-bottom:1px solid var(--line)}
.notes-list a{display:grid; grid-template-columns:11em 1fr; gap:24px; padding:26px 0;
  transition:opacity .25s var(--ease)}
.notes-list a:hover{opacity:.6}
.notes-date{font-size:var(--t-sm); color:var(--grey); letter-spacing:.03em; padding-top:.2em}
.notes-title{font-family:var(--display); font-weight:400; font-size:var(--t-lg); display:block; margin-bottom:6px}
.notes-dek{font-size:var(--t-xs); color:var(--grey); grid-column:2}
/* Empty state: shown only while notes-list has no <li> children. Once the
   first entry is added, hide this paragraph (see the worked example
   comment in public/notes/index.html and README.md). */
.notes-empty{color:var(--grey); font-size:var(--t-md2); max-width:32em; padding:8px 0 12px;
  border-top:1px solid var(--line); padding-top:36px}
/* A note's own page (generated by the worker from D1 - see src/cms.js).
   Reading column mirrors the /about page: freight-text serif at 18/1.62,
   600px measure, paragraph gap at half-leading. */
.note-body{max-width:600px}
.note-body p{font-family:"freight-text-pro", Georgia, serif; font-size:18px; line-height:1.62;
  color:#3c3c3c; margin-bottom:14px}
.note-body h3{font-family:var(--display); font-weight:400; font-size:24px; margin:32px 0 14px}
.note-body a{text-decoration:underline; text-underline-offset:3px}
.note-back{margin-top:44px; font-weight:500}
.note-back a{text-decoration:underline; text-underline-offset:3px}
/* Attached images: monochrome at rest like every photograph on the site,
   true colour on hover/touch. */
.note-fig{margin:29px 0}
.note-fig img{width:100%; height:auto; border-radius:6px; filter:grayscale(1);
  transition:filter .5s var(--ease)}
.note-fig:hover img, .note-fig:focus-within img{filter:none}
.note-fig figcaption{font-size:14.5px; color:#6b6b6b; margin-top:10px; line-height:1.5}
/* A run of 2+ attached images (src/cms.js renderBody) groups into a grid
   instead of stacking one figure per line. Same hairline frame as the
   rest of the site (faq-card, vid-frame), same monochrome-at-rest,
   colour-on-hover treatment as .note-fig above. Each cell links to the
   uncropped file. */
.note-gallery{display:grid; grid-template-columns:repeat(2,1fr); gap:10px; margin:29px 0}
.note-gallery-3plus{grid-template-columns:repeat(3,1fr)}
.note-gallery-item{display:block; aspect-ratio:1/1; overflow:hidden; border:1px solid var(--line); border-radius:6px}
.note-gallery-item img{width:100%; height:100%; object-fit:cover; filter:grayscale(1); transition:filter .5s var(--ease)}
.note-gallery-item:hover img, .note-gallery-item:focus-visible img{filter:none}
/* Lightbox for gallery photos (public/site.js). Progressive enhancement over
   the .note-gallery-item <a href> above: JS off (or CSP-blocked, as on
   /studio/preview) leaves the plain new-tab link working. Scrim is ink-black
   with a blur behind it (his call 2026-08-26: the colour reveal reads as a
   spotlight against dark, matching the grayscale-at-rest/colour-on-hover
   language above, and it reuses the one dark surface already on the page,
   the footer); the near-opaque background alone carries contrast where
   backdrop-filter is unsupported, so the blur is a pure enhancement. .lb
   stays display:flex at all times (never display:none) so opacity can
   transition; visibility + pointer-events keep it out of the tab order and
   unclickable while closed. */
.lb{position:fixed; inset:0; z-index:60; display:flex; align-items:center; justify-content:center;
  padding:28px; opacity:0; visibility:hidden; pointer-events:none}
.lb.lb-open{opacity:1; visibility:visible; pointer-events:auto}
.lb-scrim{position:absolute; inset:0; background:rgba(16,16,16,.9);
  backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px)}
.lb-dialog{position:relative; z-index:1; display:flex; flex-direction:column; align-items:center;
  max-width:100%; max-height:100%; pointer-events:none}
.lb-frame{pointer-events:auto; display:block; line-height:0}
.lb-img{display:block; max-width:88vw; max-height:76vh; width:auto; height:auto; border-radius:4px}
.lb-count{pointer-events:auto; margin-top:18px; font-size:var(--t-sm); letter-spacing:.03em; color:rgba(255,255,255,.68)}
.lb-close, .lb-prev, .lb-next{pointer-events:auto; position:fixed; background:none; border:0; cursor:pointer;
  color:rgba(255,255,255,.78); font-family:var(--sans); line-height:1; transition:color .2s, transform .2s var(--ease)}
.lb-close:hover, .lb-prev:hover, .lb-next:hover,
.lb-close:focus-visible, .lb-prev:focus-visible, .lb-next:focus-visible{color:#fff}
.lb-close:focus-visible, .lb-prev:focus-visible, .lb-next:focus-visible{outline:2px solid #fff; outline-offset:4px; border-radius:4px}
.lb-close{top:18px; right:20px; font-size:34px; width:44px; height:44px}
.lb-prev, .lb-next{top:50%; transform:translateY(-50%); font-size:48px; width:52px; height:64px}
.lb-prev{left:8px}
.lb-next{right:8px}
.lb-prev:hover{transform:translateY(-50%) translateX(-3px)}
.lb-next:hover{transform:translateY(-50%) translateX(3px)}
html.lb-lock, html.lb-lock body{overflow:hidden}
/* Mobile nav sheet scroll lock. overflow:hidden alone is known-unreliable
   on iOS Safari (touch scroll can still drag the document under it), so
   site.js additionally swaps body to position:fixed with a negative top
   offset while nav-open is set (the documented iOS-safe technique) and
   restores the scroll position on close. This rule stays as the
   synchronous belt-and-braces for the instant before/after that JS runs,
   and is scoped inside the max-width:960px block below so it can never
   apply outside the breakpoint the toggle actually lives in - see the
   resize-to-desktop fix in site.js for why that scoping matters. */
@media (prefers-reduced-motion: no-preference){
  .lb{transition:opacity .32s var(--ease), visibility 0s linear .32s}
  .lb.lb-open{transition:opacity .32s var(--ease), visibility 0s linear 0s}
}
@media (prefers-reduced-motion: reduce){
  .lb{transition:none}
  .lb-prev:hover, .lb-next:hover{transform:translateY(-50%)}
}
/* Preview banner (only ever rendered on /studio previews) */
.preview-band{background:var(--ink); color:#fff; font-size:14.5px; text-align:center;
  padding:10px 16px 11px}

@media (max-width:960px){
  .wrap{padding-left:24px; padding-right:24px}
  nav{padding:16px 24px}
  nav.scrolled{padding-top:12px; padding-bottom:12px}
  .nav-right{gap:14px}
  .nav-toggle{display:inline-block; font-size:var(--t-sm); padding:8px 2px}
  /* Scoped copy of the nav-open lock (see the comment by .lb-lock above):
     only reachable at/under the breakpoint the toggle itself lives in, so
     a stray nav-open class can never freeze scroll on the desktop ribbon. */
  html.nav-open, html.nav-open body{overflow:hidden}
  /* One line each: the pill and the toggle must never wrap the bar taller */
  .nav-right .btn{padding:7px 16px 9px; font-size:14px; white-space:nowrap}
  /* No-JS / JS-failed fallback: html never gets the "js" class (see the
     inline script in <head> on every page), so without the rules below the
     links simply wrap in the bar - visible and reachable, just not pretty.
     This is the deliberate degrade path, not an oversight. */
  .navlinks{gap:12px 20px; flex-wrap:wrap; justify-content:flex-end}
  /* JS present: the link row becomes a drop-down sheet under the sticky
     nav, closed by default. nav is the positioning context (sticky
     establishes one), so top:100% tracks the bar's own height through its
     scrolled/unscrolled padding change with no JS measuring needed.

     max-height: --sheet-max is set in JS (site.js, openMenu/resize) to the
     exact viewport space below the nav's live bottom edge, read from
     visualViewport.height where available (window.innerHeight can lag
     iOS's collapsing toolbar) so it tracks the real visible area. The
     dvh fallback covers the instant before JS has measured, and any
     no-JS/JS-off path never reaches this rule at all. Landscape phones
     (sheet taller than the five links) get overflow-y:auto instead of
     bleeding off the bottom edge with no way to reach the rest.
     overscroll-behavior:contain stops a scroll-to-edge inside the panel
     from rubber-banding into the (position:fixed-locked) body behind it. */
  html.js .navlinks{position:absolute; top:100%; left:0; right:0; z-index:1;
    flex-direction:column; align-items:flex-start; justify-content:flex-start; flex-wrap:nowrap; gap:2px;
    /* justify-content:flex-start overrides the flex-end set on the base
       .navlinks rule above (there for the no-JS fallback, right-aligning
       wrapped links in the closed bar). That flex-end was harmless while
       this panel had no max-height, but once content taller than the
       available space is real (short/landscape viewports, see max-height
       below), flex-end on a column axis bottom-aligns the flex items and
       pushes the overflow off the TOP instead of leaving it reachable by
       scrolling down from the top - reproduced empirically: About's own
       rect.top measured -15.97px (clipped above the sheet) in a 390px-tall
       viewport until this override was added. */
    max-height:var(--sheet-max, calc(100dvh - 84px)); overflow-y:auto; overscroll-behavior:contain;
    -webkit-overflow-scrolling:touch;
    padding:8px calc(24px + env(safe-area-inset-right)) calc(28px + env(safe-area-inset-bottom)) calc(24px + env(safe-area-inset-left));
    background:rgba(255,255,255,.97);
    border-bottom:1px solid var(--line);
    backdrop-filter:blur(20px); -webkit-backdrop-filter:blur(20px);
    opacity:0; visibility:hidden; pointer-events:none}
  html.js.nav-open .navlinks{opacity:1; visibility:visible; pointer-events:auto}
  html.js .navlinks a{font-family:var(--display); font-weight:300; font-size:clamp(26px,7.5vw,32px);
    padding:12px 0}
  html.js .navlinks a::after{content:none}
  html.js .navlinks a:focus-visible{outline:2px solid var(--ink); outline-offset:3px; border-radius:2px}
  @media (prefers-reduced-motion:no-preference){
    html.js .navlinks{transition:opacity .3s var(--ease), visibility 0s linear .3s, transform .3s var(--ease);
      transform:translateY(-8px)}
    html.js.nav-open .navlinks{transition:opacity .3s var(--ease), visibility 0s linear 0s, transform .3s var(--ease);
      transform:translateY(0)}
  }
  @media (prefers-reduced-motion:reduce){
    html.js .navlinks{transition:none; transform:none}
  }
  .hero{grid-template-columns:1fr; gap:0}
  .hero-copy{padding:40px 24px 48px}
  .hero-img{order:-1; height:56vw; min-height:280px}
  .hero-img img{object-position:22% 30%}
  .creds{padding:32px 24px; gap:20px}
  .creds > div{flex:1 1 40%}
  .about{grid-template-columns:1fr; gap:28px; padding-top:56px; padding-bottom:56px}
  body.page-about .about{padding-top:48px; padding-bottom:56px}
  .plate{height:auto; min-height:0}
  .plate img{height:auto; object-fit:contain; object-position:center}
  .bio{grid-template-columns:1fr; gap:22px; padding-top:56px; padding-bottom:64px}
  body.page-about .about:has(+ .bio){padding-bottom:48px}
  .bio blockquote{padding-left:18px}
  .era{margin-top:34px}
  .bio .body p.honours{column-count:1}
  .bio{grid-template-rows:auto}
  .bio .body{grid-column:auto; grid-row:auto}
  .bio-plate{position:static; max-width:none; justify-self:stretch; margin-top:0}
  .bio-plate--harp{grid-column:auto; grid-row:auto}
  .bio-plate--harp img{aspect-ratio:auto; object-fit:contain; object-position:center}
  .faq{padding-top:72px; padding-bottom:0}
  .fees{padding-top:56px; padding-bottom:64px}
  .fees-calc{gap:28px}
  .keys{height:60px; margin:48px -24px 0}
  .faq-grid{grid-template-columns:1fr 1fr}
  .videos{padding-top:56px; padding-bottom:64px}
  .vid-grid{grid-template-columns:1fr 1fr; gap:28px 20px}
  footer{padding:56px 24px 40px}
  .contact-row{grid-template-columns:1fr; gap:28px; margin-bottom:40px}
  footer.footer-slim .contact-row{grid-template-columns:1fr}
  .notes-hero{padding-top:48px}
  .notes-index{padding-top:16px; padding-bottom:72px}
  .notes-list a{grid-template-columns:1fr; gap:6px; padding:20px 0}
  .notes-dek{grid-column:1}
  .note-body p{font-size:16.5px; margin-bottom:13px}
  .note-body h3{font-size:21px; margin:26px 0 12px}
  .note-gallery-3plus{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:560px){
  .faq-grid{grid-template-columns:1fr}
  .fees-out{padding:36px 26px 40px}
  .vid-grid{grid-template-columns:1fr}
  .lb{padding:16px}
  .lb-close{top:10px; right:10px; font-size:28px; width:38px; height:38px}
  .lb-prev, .lb-next{font-size:36px; width:40px; height:56px}
  .lb-prev{left:0}
  .lb-next{right:0}
}
