@font-face{font-family:'Poppins';src:url(/assets/fonts/poppins-400.woff2)format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Poppins';src:url(/assets/fonts/poppins-500.woff2)format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:'Poppins';src:url(/assets/fonts/poppins-600.woff2)format("woff2");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'Poppins';src:url(/assets/fonts/poppins-700.woff2)format("woff2");font-weight:700;font-style:normal;font-display:swap}
:root{
  --bg: #000000;
  --ink: #ffffff;
  --muted: #9a9a9e;
  --line: rgba(255,255,255,.16);
  --hair: rgba(255,255,255,.13);
  --brand: #30c0c0;
  --brand-ink: #000000;
  --accent: #f0a800;
  --accent-2: #30c0c0;
  --accent-text: #f0a800;
  --chip-ink: #000000;
  --btn-bg: #f0a800;
  --btn-ink: #000000;
  --panel: #0b0b0c;
  --radius: 0px;
  --display: 700;
  --font-body: Poppins,system-ui,-apple-system,'Segoe UI',sans-serif;
  --font-display: Poppins,system-ui,-apple-system,'Segoe UI',sans-serif;
}
/* =========================================================================
   Shared layout + components. All colour comes from the per-site theme block
   prepended at build time (see theme.py). Nothing here hardcodes a hue.
   ========================================================================= */

/* @font-face rules are emitted per site by theme.py and prepended above this file.
   They used to live here, hardcoded to Poppins, which meant a site with its own
   typography could not have it without every other site shipping both families. */

*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  background:var(--bg);color:var(--ink);
  font-family:var(--font-body);
  font-weight:400;line-height:1.6;-webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
/* Display face. Identical to --font-body on the three Poppins sites, so this rule
   changes nothing there; Good Willed sets a serif and only the headings pick it up. */
/* The token carries its OWN fallback stack, so a site that sets a serif display face
   falls back to a serif and a site on Poppins falls back to the same sans its body
   uses. Hardcoding `Georgia,serif` here made the three Poppins sites flash a serif
   heading over a sans body during font-display:swap. */
h1,h2,h3,h4,.card__t,.card__artist,.contact__big,.pull blockquote{
  font-family:var(--font-display);
}
img{display:block;max-width:100%;height:auto}
a{color:inherit;text-decoration:none}
/* --accent-text, not --accent. Every theme darkens the accent into --accent-text
   precisely because the raw one fails contrast; three rules hardcoded the raw
   token and never got that fix. A focus ring needs 3:1 (WCAG 1.4.11) and the raw
   accent gave 2.48:1 on Good Willed. */
:focus-visible{outline:2px solid var(--accent-text,var(--accent));outline-offset:3px}
/* The FIRST thing a keyboard user reaches on every page, and it was white-on-raw
   -accent: 2.04:1 on Wallflower, 2.48:1 on Good Willed, 3.64:1 on Motion Grove.
   --btn-bg/--btn-ink are the contrast-checked pair and fix all four sites at once
   (5.45 / 17.40 / 10.31 / 4.80). Note Wallflower needs BLACK ink on amber, which
   the hardcoded #fff could never give. */
.skip{position:absolute;left:-9999px;top:0;background:var(--btn-bg,var(--accent));
  color:var(--btn-ink,#fff);padding:12px 18px;font-weight:600;z-index:200}
.skip:focus{left:8px;top:8px}

:root{
  --pad:clamp(18px,4.2vw,64px);
  --maxw:1560px;
}
.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad)}

/* ------------------------------------------------------------------ masthead */
.masthead{position:relative;z-index:20;display:flex;align-items:center;
  justify-content:space-between;gap:24px;padding:22px var(--pad);
  max-width:var(--maxw);margin-inline:auto}
.masthead--over{position:absolute;inset:0 0 auto 0}
.mark{display:flex;align-items:center;gap:12px;font-weight:var(--display);
  letter-spacing:-.02em;font-size:clamp(1.05rem,2vw,1.4rem)}
.mark img{width:auto;height:clamp(30px,3.4vw,42px)}
.nav{display:flex;flex-wrap:wrap;gap:6px 28px;align-items:center}
/* Nav links and social icons are primary navigation and sit at 32px, under the
   44px touch target the card play control already uses — an oversight, not a
   different intent. Padding does the work so the visible underline stays put. */
.nav a{font-size:.88rem;font-weight:500;color:var(--muted);padding:13px 0;
  border-bottom:2px solid transparent;transition:color .18s,border-color .18s}
.nav a:hover,.nav a[aria-current=page]{color:var(--ink);border-bottom-color:var(--accent)}
.masthead--invert .mark,.masthead--invert .nav a{color:#fff}
.masthead--invert .nav a:hover,.masthead--invert .nav a[aria-current=page]{color:#fff}
.social{display:flex;gap:2px}
/* The box came from an era when these were bare letters and needed something to sit
   in. With real glyphs the frame just adds noise — keep the 44px target, drop the
   border, and let the mark itself carry the hover. */
.social a{width:44px;height:44px;display:grid;place-items:center;
  font-size:.82rem;font-weight:600;color:var(--muted);
  border-radius:var(--radius);transition:color .18s,transform .18s}
.social a:hover,.social a:focus-visible{color:var(--btn-bg,var(--accent));
  transform:translateY(-1px)}
.masthead--invert .social a{color:rgba(255,255,255,.86)}
.masthead--invert .social a:hover,.masthead--invert .social a:focus-visible{color:#fff}

/* ---------------------------------------------------------------------- hero */
.hero{position:relative;min-height:clamp(440px,78vh,860px);display:grid;
  align-items:end;overflow:hidden;background:var(--brand)}
.hero__media{position:absolute;inset:0}
.hero__media img,.hero__media video{width:100%;height:100%;object-fit:cover}
/* The full-height home hero carries NO whole-frame scrim. Measured 2026-09-07 across 15
   frames of hero.mp4: on the brightest frame 75.3% of the headline area sat below 3:1
   WITH both scrims and 84.3% with none — a 9-point gain for dimming the whole film.
   It was buying almost no legibility and costing the footage. Glyph-level contrast
   (text-shadow, below) does the same job locally without touching the image.
   .hero--short keeps its own scrim: those heroes are stills, not video, and their copy
   sits over a much larger share of the frame. */
.hero--scrim .hero__media::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(0,0,0,.42) 0%,rgba(0,0,0,.12) 42%,rgba(0,0,0,.58) 100%)}
/* Must sit above .hero__media::after, which is the darkening scrim at z-index 2.
   Without a z-index here the copy relied on paint order and lost to that scrim —
   it rendered veiled, and under the <video> layer entirely. The home hero carries
   no copy, so nothing surfaced this until /passion got a heading and a button. */
.hero__body{position:relative;z-index:3;padding:0 var(--pad) clamp(40px,7vh,86px);
  max-width:var(--maxw);margin-inline:auto;width:100%}
/* A guaranteed base for the copy, independent of what the photograph happens to be
   doing behind it. The scrims above are whole-frame gradients, so a bright patch that
   lands under the heading still fails: measured 2.74:1 minimum on /the-process and a
   1.44:1 sky patch on the home hero. This one is anchored to the text block, so the
   worst case is bounded by the gradient rather than by the image. Sits BELOW the copy
   (z-index 3 on .hero__body) but above the media layers. */
.hero--scrim .hero__body::before{content:"";position:absolute;inset:auto 0 0 0;height:150%;
  z-index:-1;pointer-events:none;
  background:linear-gradient(180deg,rgba(0,0,0,0) 0%,rgba(0,0,0,.28) 42%,rgba(0,0,0,.52) 100%)}
/* Contrast where it is actually needed: around the letterforms, not across the picture.
   A shadow this soft is invisible on the dark frames (most of them) and only reads as
   weight on a blown-out one. */
.hero__body h1{text-shadow:0 1px 2px rgba(0,0,0,.34),0 3px 18px rgba(0,0,0,.42);
  color:#fff;font-weight:var(--display);letter-spacing:-.03em;text-wrap:balance;
  font-size:clamp(2rem,5.4vw,4.2rem);line-height:1.02;max-width:17ch}
/* The heading got a shadow and the paragraph did not, which showed immediately on
   /passion: the Wallflower film has near-white frames, and body copy needs 4.5:1 where
   the display line only needs 3:1. Full white rather than 90%, and a tighter shadow —
   small text needs the halo close to the stroke or it just reads as blur. */
.hero__body p{color:#fff;margin-top:16px;max-width:52ch;
  text-shadow:0 1px 2px rgba(0,0,0,.55),0 2px 10px rgba(0,0,0,.45);
  font-size:clamp(.95rem,1.5vw,1.1rem)}

/* The scrim is now OPT-IN via .hero--scrim, not automatic on every short hero.
   Measured 2026-09-07/08 across all six heroes, white h1, % of the copy area below the
   3:1 large-text threshold, with the scrim off:

       goodwilled /the-process   96.7%   <- unreadable without it. KEEPS the scrim.
       motiongrove /              90.0%   video; carried by text-shadow
       motiongrove /passion       29.6%   video; carried by text-shadow
       wallflower  /contact       20.8%   carried by text-shadow
       motiongrove /contact        1.7%   never needed one
       goodwilled  /               1.4%   never needed one

   Only one page in four sites actually depended on it, and it was dimming the
   photography on the other five to no purpose. Glyph-level contrast (the text-shadow
   on .hero__body h1) does the work everywhere else without touching the image. */
.hero--scrim .hero__media::before{content:"";position:absolute;inset:0;z-index:2;
  background:linear-gradient(180deg,rgba(0,0,0,0) 0%,rgba(0,0,0,.30) 45%,rgba(0,0,0,.62) 100%)}

/* --------------------------------------------------------------- band / rule */
.band{background:var(--brand);color:var(--brand-ink)}
.band__inner{max-width:var(--maxw);margin-inline:auto;padding:clamp(26px,4.5vw,54px) var(--pad);
  text-align:center;font-size:clamp(1rem,1.9vw,1.35rem);font-weight:500;line-height:1.45}
/* Every other prose block is capped in ch; this one was bounded only by --maxw, so
   the 121-character tagline ran to roughly 120 characters on a single line at 1920.
   ch resolves against this element's own font-size, which is the band's. */
.band__inner{max-width:min(var(--maxw), 62ch)}
.rule{height:2px;background:var(--line);margin:0}

/* -------------------------------------------------------------- section head */
.sec{max-width:var(--maxw);margin-inline:auto;padding:clamp(46px,7vw,92px) var(--pad) 0}
.sec__head{display:flex;align-items:baseline;justify-content:space-between;gap:20px;
  padding-bottom:16px;border-bottom:2px solid var(--line);flex-wrap:wrap}
.sec__head h2{font-weight:var(--display);letter-spacing:-.025em;
  font-size:clamp(1.35rem,3.1vw,2.3rem);line-height:1.05}
.sec__count{font-size:.78rem;font-weight:700;color:var(--chip-ink,var(--ink));background:var(--accent-2,transparent);
  padding:3px 10px;font-variant-numeric:tabular-nums;letter-spacing:.02em}

/* ---------------------------------------------------------------------- grid */
.grid{list-style:none;display:grid;gap:clamp(26px,3vw,44px) clamp(16px,2vw,30px);
  padding-top:clamp(26px,3.4vw,44px);
  grid-template-columns:repeat(auto-fill,minmax(min(100%,330px),1fr))}
.grid--tight{gap:0;padding-top:0;grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr))}
/* This one keeps its ragged last row, and that IS the considered answer. The flush
   orphan-widening below works because .grid--wide holds 16:9 cards — a full-width
   16:9 closer looks deliberate. These tiles are forced to 1/1, so widening a lone
   orphan to the full viewport would render a single ~1920px square photograph as the
   final image on the page. The hole is the lesser problem. */

/* The field of images has to end flush. auto-fill leaves a hole in the closing row
   whenever the count is not a multiple of the column count — 17 films in 3 columns
   left a third of the last row white, which reads as a bug rather than as a
   composition. A padding tile cannot fix it, because the column count changes with
   the viewport. Instead the orphans widen to close the row: the block is a clean
   rectangle at every width, for any number of films, with no per-site tuning.

   The 2-per-row orphan rule MUST be range-scoped. min-width queries are additive,
   so an unbounded one stays live at 3-per-row widths — and being two pseudo-classes
   deep it outranks the plain `.grid--wide>*` that sets the 3-col span, no matter the
   source order. Verified in-browser: 3, 9, 15 and 21 tiles rendered a gappy row of
   two followed by one full-width tile. 17 only escaped by an equal-specificity
   source-order tiebreak, which any minifier could have reordered away. */
.grid--wide{gap:0;grid-template-columns:repeat(6,1fr)}
.grid--wide>*{grid-column:span 6}                                   /* 1 per row */
@media(min-width:830px){                                            /* 2 per row */
  .grid--wide>*{grid-column:span 3}
}
@media(min-width:830px) and (max-width:1244px){
  .grid--wide>:nth-child(2n+1):last-child{grid-column:span 6}
}
@media(min-width:1245px){                                           /* 3 per row */
  .grid--wide>*{grid-column:span 2}
  .grid--wide>:nth-child(3n+1):last-child{grid-column:span 6}
  .grid--wide>:nth-child(3n+1):nth-last-child(2),
  .grid--wide>:nth-child(3n+1):nth-last-child(2)+*{grid-column:span 3}
}

.card__link{display:block}
.card__frame{position:relative;display:block;overflow:hidden;background:var(--panel);
  aspect-ratio:16/9;border-radius:var(--radius)}
.grid--tight .card__frame{aspect-ratio:1/1}
.card__frame img{width:100%;height:100%;object-fit:cover;
  transition:transform .62s cubic-bezier(.19,.62,.28,1),filter .35s}
.card__link:hover .card__frame img,.card__link:focus-visible .card__frame img{transform:scale(1.04)}

/* Hover overlay: translucent white play control with the title beneath it.
   Nothing sits under the tile — the grid stays an uninterrupted field of images. */
.card__over{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:16px;opacity:0;
  background:rgba(10,10,14,.34);transition:opacity .28s ease}
.card__link:hover .card__over,.card__link:focus-visible .card__over{opacity:1}
.card__play{width:46px;height:46px;border-radius:50%;flex:none;
  background:rgba(255,255,255,.16);border:1.5px solid rgba(255,255,255,.9);
  backdrop-filter:blur(3px);display:grid;place-items:center;
  transform:scale(.94);transition:transform .28s cubic-bezier(.2,.7,.3,1),background .22s}
.card__play::before{content:"";border-style:solid;border-width:8px 0 8px 14px;
  border-color:transparent transparent transparent rgba(255,255,255,.96);margin-left:4px}
.card__link:hover .card__play{transform:scale(1);background:rgba(255,255,255,.3)}
.card__t{color:#fff;text-shadow:0 2px 10px rgba(0,0,0,.5);font-weight:600;
  font-size:clamp(1.1rem,1.9vw,1.6rem);line-height:1.3;text-align:center;
  letter-spacing:-.008em;padding:0 18px;max-width:92%;
  transform:translateY(6px);transition:transform .3s cubic-bezier(.2,.7,.3,1)}
.card__link:hover .card__t{transform:none}
/* Two tiers where there is a subtitle: the artist leads, the song sits under it
   lighter and smaller. Packing both into one string in one weight is what flattened
   Wallflower's "artist primary, song secondary" back to a single grey line. */
.card__artist,.card__song{display:block}
.card__song{margin-top:5px;font-weight:400;font-size:.74em;opacity:.82;
  letter-spacing:0}
.card__frame iframe,.card__frame video{position:absolute;inset:0;width:100%;height:100%;border:0}

/* reveal — default state visible so no-JS and paused renderers still show content */
.card{opacity:1;transform:none}
.js .card.reveal{opacity:0;transform:translateY(12px)}
.js .card.reveal.in{opacity:1;transform:none;
  transition:opacity .5s ease,transform .5s cubic-bezier(.2,.6,.3,1)}

/* -------------------------------------------------------------------- prose */
.prose{max-width:58ch;padding-top:clamp(22px,3vw,34px)}
.prose p{margin-top:15px;color:var(--muted);font-size:clamp(.96rem,1.4vw,1.06rem)}
.prose p:first-child{margin-top:0}
/* prose_html() emits <h2> (an <h3> under the page's only <h1> skipped a level).
   These stay visually subordinate — the size is set here, not by the tag. */
.prose h2{margin-top:28px;font-weight:600;font-size:1.1rem;color:var(--ink);
  letter-spacing:normal;line-height:1.4}
.lead{font-size:clamp(1.05rem,2vw,1.35rem);color:var(--ink);font-weight:500;line-height:1.45}
/* An email address is one unbreakable 33-character token. body{overflow-x:hidden}
   means it would be silently clipped rather than scrolled at ~320px. */
.prose a{overflow-wrap:anywhere}

/* ------------------------------------------------------------------ project */
.proj{max-width:var(--maxw);margin-inline:auto;padding:clamp(46px,7vw,92px) var(--pad) 0}
.proj__top{display:grid;gap:clamp(18px,3vw,44px);grid-template-columns:1fr;
  padding-bottom:clamp(24px,3vw,38px);border-bottom:2px solid var(--line)}
/* Deliberately NOT two columns. Pairing a one-line title against the full body copy
   in one stretched grid row meant the row was as tall as the prose: 56px of title
   beside ~1,380px of dead white on the longest post. A post header is a header —
   it runs full width and the copy flows beneath it at its own measure. */
.proj__top .prose{max-width:64ch}
.proj__top h1{font-weight:var(--display);letter-spacing:-.03em;text-wrap:balance;
  font-size:clamp(1.9rem,4.4vw,3.4rem);line-height:1.03}
.proj__kick{display:block;font-size:.78rem;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--accent-text,var(--accent));margin-bottom:12px}
/* Project page: the film's name over it, the caption centred beneath. The running
   number and the "Project 13" kicker are gone — the title is the title. */
.proj__head{text-align:center;padding-bottom:clamp(18px,2.6vw,30px)}
.proj__head h1{font-weight:var(--display);letter-spacing:-.03em;text-wrap:balance;
  font-size:clamp(1.9rem,4.4vw,3.4rem);line-height:1.03;margin-inline:auto;
  max-width:20ch}
.proj__note{max-width:64ch;margin:clamp(18px,2.4vw,28px) auto 0;text-align:center}
.proj__note p{color:var(--muted);font-size:clamp(.96rem,1.4vw,1.06rem)}
.proj__note p+p{margin-top:14px}
.proj__note h2{margin-top:22px;font-weight:600;font-size:1.05rem;color:var(--ink);
  letter-spacing:normal;line-height:1.4}

.player{position:relative;aspect-ratio:16/9;background:#000;overflow:hidden;
  border-radius:var(--radius);margin-top:clamp(24px,3vw,38px)}
.player img{width:100%;height:100%;object-fit:cover}
.player iframe,.player video{position:absolute;inset:0;width:100%;height:100%;border:0}
.player video{background:#000}
.stills{list-style:none;display:grid;gap:clamp(10px,1.4vw,18px);
  grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr));
  padding-top:clamp(26px,3vw,42px)}
.stills img{width:100%;aspect-ratio:3/2;object-fit:cover;border-radius:var(--radius)}
/* Frame grabs pulled from the films themselves. Cropping them to 3:2 would slice
   the sides off shots that were composed for 16:9, so this strip keeps the format
   of the film it came from. Mixed-source photo posts keep the 3:2 crop above. */
.stills--film img{aspect-ratio:16/9}
.stills--film a{display:block;position:relative;overflow:hidden;border-radius:var(--radius)}
.stills--film img{transition:transform .6s cubic-bezier(.19,.62,.28,1)}
.stills--film a:hover img,.stills--film a:focus-visible img{transform:scale(1.04)}
/* auto-fill gives a lone still one 300px cell and three empty ones, so it sits
   stranded under a full-width film. The build used to "solve" this by discarding the
   image; a single still is a companion frame, so give it a width that says so. */
.stills--one{grid-template-columns:minmax(0,min(58%,620px))}
.backlink{display:inline-block;margin-top:clamp(32px,4vw,54px);font-weight:600;
  font-size:.88rem;border-bottom:2px solid var(--accent);padding-bottom:2px}

/* ---------------------------------------------------------------------- cta */
.cta{max-width:var(--maxw);margin:clamp(56px,8vw,110px) auto 0;padding:0 var(--pad);
  text-align:center}
.cta h2{font-size:clamp(1.4rem,3.1vw,2.1rem);line-height:1.1;font-weight:var(--display);
  letter-spacing:-.028em}
.cta p{margin-top:10px;color:var(--muted)}
.btn{display:inline-block;margin-top:24px;padding:14px 34px;border-radius:var(--radius);
  background:var(--btn-bg,var(--accent));color:var(--btn-ink,#fff);font-weight:600;
  font-size:.94rem;transition:.2s;border:2px solid var(--btn-bg,var(--accent))}
.btn:hover{background:transparent;color:var(--btn-bg,var(--accent))}

/* -------------------------------------------------------------------- footer */
.foot{margin-top:clamp(60px,9vw,120px);border-top:2px solid var(--line);
  padding:34px var(--pad) 46px;color:var(--muted);font-size:.88rem;
  max-width:var(--maxw);margin-inline:auto}
.foot__grid{display:flex;flex-wrap:wrap;gap:14px 34px;justify-content:space-between}
.foot a{display:inline-block;padding:7px 0}
.foot a:hover{color:var(--accent-text,var(--accent))}
/* No opacity here: --muted already clears 4.5:1 on its own, but diluting it to 75%
   dropped the copyright line to 3.07:1 (MG), 2.88:1 (DR) and 4.47:1 (WW). */
.foot__fine{margin-top:18px;font-size:.78rem}
/* Clutch injects an iframe of its own fixed height. Reserving that height up front
   keeps the badge from shoving the copyright line down as it loads — the widget is
   the last thing on the page, so a late shift there is the most visible kind. */
.foot__clutch{margin-top:22px;min-height:45px}
.foot__clutch iframe{max-width:100%}

/* ------------------------------------------------------------------ lightbox */
body.lb-open{overflow:hidden}
.lb{position:fixed;inset:0;z-index:120;display:grid;place-items:center;
  padding:clamp(10px,3vw,44px)}
.lb[hidden]{display:none}
.lb__backdrop{position:absolute;inset:0;background:rgba(8,8,10,.94);
  backdrop-filter:blur(7px);animation:lbf .2s ease}
.lb__inner{position:relative;width:min(1220px,100%);max-height:100%;display:flex;
  flex-direction:column;gap:12px;animation:lbr .24s cubic-bezier(.2,.7,.3,1)}
@keyframes lbf{from{opacity:0}to{opacity:1}}
@keyframes lbr{from{opacity:0;transform:translateY(12px) scale(.987)}to{opacity:1;transform:none}}
.lb__bar{display:flex;align-items:center;gap:16px;justify-content:space-between;color:#fff}
.lb__title{font-weight:600;font-size:clamp(.95rem,2.1vw,1.18rem);line-height:1.3}
.lb__x{flex:none;width:44px;height:44px;border-radius:var(--radius);
  border:1px solid rgba(255,255,255,.28);background:transparent;color:#fff;
  font-size:1.45rem;line-height:1;cursor:pointer;display:grid;place-items:center;transition:.18s}
/* --btn-ink, not the inherited #fff. theme.py already records why: on Wallflower the
   accent is amber and white-on-amber is 2.04:1. .btn was fixed for this; the lightbox
   controls kept white text and failed contrast in the one state you're looking at. */
.lb__x:hover,.lb__x:focus-visible{background:var(--accent);border-color:var(--accent);
  color:var(--btn-ink,#fff)}
.lb__stage{position:relative;width:100%;aspect-ratio:16/9;background:#000;
  overflow:hidden;border-radius:var(--radius)}
.lb__stage iframe,.lb__stage video{position:absolute;inset:0;width:100%;height:100%;border:0}
/* photo entries size to the image rather than a fixed 16:9 box */
.lb__stage--photo{aspect-ratio:auto;background:transparent;display:grid;place-items:center;
  max-height:82vh}
.lb__stage--photo img{width:auto;height:auto;max-width:100%;max-height:82vh;object-fit:contain}
.lb__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.lb__nav,.lb__out{background:none;border:1px solid rgba(255,255,255,.28);color:#fff;
  cursor:pointer;font:inherit;font-size:.88rem;font-weight:500;padding:9px 18px;
  border-radius:var(--radius);transition:.18s}
.lb__nav:hover,.lb__nav:focus-visible,.lb__out:hover,.lb__out:focus-visible{
  border-color:var(--accent);background:var(--accent);color:var(--btn-ink,#fff)}
/* "Watch on YouTube" is the one control that LEAVES the site, and it sat dead centre
   between Previous and Next with identical chrome — a stray click while paging
   through 35 sessions. Demote it to a text link and move it off the nav axis. */
.lb__out{order:3;margin-left:auto;border-color:transparent;padding-left:0;
  padding-right:0;font-size:.82rem;opacity:.78}
.lb__out:hover,.lb__out:focus-visible{background:none;border-color:transparent;
  color:var(--accent);opacity:1;text-decoration:underline}
/* Prev and Next sit together; the leave-the-site link is pushed to the far edge. */
.lb__foot{justify-content:flex-start;gap:10px}
.lb__nav{flex:0 0 auto}
@media(max-width:560px){.lb__foot{justify-content:center}
  .lb__out{width:100%;margin-left:0;text-align:center}}

@media(max-width:760px){
  .masthead{flex-wrap:wrap;justify-content:center;text-align:center}
  .nav{justify-content:center;width:100%;order:3}
}
@media (prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important}
  .js .card.reveal{opacity:1;transform:none}
}

/* visually-hidden heading — present for structure and SEO, not shown */
.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
.sec--flush{padding-inline:0;max-width:none}

/* intro block (Wallflower) */
.intro{max-width:var(--maxw);margin-inline:auto;padding:clamp(30px,6vh,64px) var(--pad) 0;
  text-align:center}
.intro h1{font-weight:var(--display);font-size:clamp(2.1rem,6.2vw,4.1rem);line-height:1.04;
  letter-spacing:-.025em}
/* .82rem floored at 13.1px below 692px — too small for a line carrying real
   descriptive content rather than decoration. */
.intro h1 span{display:block;margin-top:14px;font-size:clamp(.88rem,1.9vw,1.05rem);
  font-weight:500;color:var(--brand)}
.lede{max-width:46ch;margin:26px auto 0;color:var(--muted);
  font-size:clamp(.96rem,1.5vw,1.09rem)}

/* Photograph tiles: scale on hover, nothing else. No play control, no scrim —
   a play arrow on a still image reads as a mistake. */
.card--photo .card__frame img{transition:transform .7s cubic-bezier(.19,.62,.28,1)}
.card--photo .card__link:hover .card__frame img{transform:scale(1.05)}
.card--photo .card__over{display:none}

/* Text-only pages (about / contact) were a left-hugged paragraph adrift in an
   otherwise empty viewport. Narrow the whole section so the page reads as
   composed rather than unfinished. */
.sec--narrow{max-width:min(var(--maxw),860px)}
.sec--narrow .sec__head{border-bottom-width:2px}

/* ---------------------------------------------- about / contact / passion
   Narrowing the column was a patch on the real problem: three unrelated pages
   sharing one template. Each is now laid out for what it actually carries. */
.band--about{padding-block:clamp(40px,6vw,86px)}
.band__split{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad);
  display:grid;gap:clamp(28px,5vw,64px);grid-template-columns:1fr;align-items:start}
@media(min-width:900px){.band__split{grid-template-columns:1.35fr .85fr}}
.band__split h1{font-weight:var(--display);letter-spacing:-.03em;text-wrap:balance;
  font-size:clamp(2rem,4.4vw,3.3rem);line-height:1.03}
.eyebrow{margin-top:clamp(20px,3vw,32px);font-size:.76rem;font-weight:700;
  letter-spacing:.16em;text-transform:uppercase;opacity:.72}
.band__prose p{margin-top:14px;font-size:clamp(.98rem,1.5vw,1.08rem);line-height:1.62;
  color:rgba(255,255,255,.88);max-width:60ch}
.band__prose p:first-child{margin-top:10px;color:#fff;font-weight:500;
  font-size:clamp(1.05rem,1.9vw,1.28rem);line-height:1.45}

/* Centred against the mission column: top-aligned it left ~330px of bare navy under
   a two-line quote, which read as an unfinished column rather than a composition. */
.pull{margin:0;border-left:3px solid var(--accent-2);padding-left:clamp(16px,2.2vw,26px)}
@media(min-width:900px){.pull{align-self:center}}
.pull blockquote{margin:0}
.pull blockquote p{font-size:clamp(1.15rem,2.3vw,1.6rem);line-height:1.3;font-weight:600;
  letter-spacing:-.02em;text-wrap:balance}
.pull figcaption{margin-top:14px;font-size:.85rem;opacity:.75;line-height:1.5}

.bio{display:grid;gap:clamp(26px,4vw,56px);grid-template-columns:1fr;align-items:start;
  padding-top:clamp(22px,3vw,34px)}
@media(min-width:900px){.bio{grid-template-columns:1fr 1fr}}
.bio__text h2{font-weight:var(--display);letter-spacing:-.025em;
  font-size:clamp(1.5rem,2.8vw,2.1rem);line-height:1.08}
.bio__role{margin-top:6px;color:var(--accent-text);font-weight:600;font-size:1.02rem}
.bio__text p+p{margin-top:15px}
.bio__text p:not(.bio__role){color:var(--muted);line-height:1.66;max-width:56ch;
  font-size:clamp(.96rem,1.4vw,1.06rem)}
/* The frames are 1.29 and 0.75 — widths in that same ratio land both bottoms on one
   line with no cropping, which a 1fr 1fr pair could only fake by cutting one of them. */
.bio__shots{display:grid;gap:clamp(10px,1.4vw,18px);grid-template-columns:1fr;
  align-items:start}
/* Side by side the pair holds at 127px and 217px wide, which is too small to read
   as a photograph — one column until there is room for both. */
@media(min-width:560px){.bio__shots{grid-template-columns:1.7fr 1fr}}
.bio__shots img{width:100%;height:auto;display:block;border-radius:var(--radius)}

/* Only reachable if a film card ever ships without a playable source. The build
   refuses to emit one, so this is the message of last resort rather than a layout. */
.lb__miss{color:#fff;text-align:center;padding:clamp(28px,6vw,64px);font-size:1rem}

/* Columns, not a grid: the categories run 7 to 34 items, and grid rows are as tall
   as their tallest cell, so LIGHTING / GRIP punched a 400px hole beside every short
   list. Column flow balances the whole block instead. */
.gear{columns:230px 4;column-gap:clamp(20px,3vw,44px);padding-top:clamp(24px,3vw,38px)}
.gear__grp{break-inside:avoid;margin-bottom:clamp(22px,3vw,36px)}
.gear__grp h3{font-size:.76rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink);padding-bottom:9px;border-bottom:1px solid var(--hair)}
.gear__grp ul{list-style:none;margin-top:12px}
.gear__grp li{color:var(--muted);font-size:.88rem;line-height:1.5;padding:4px 0}

.contact{display:grid;gap:clamp(30px,4vw,64px);grid-template-columns:1fr;
  padding-top:clamp(22px,3vw,34px)}
@media(min-width:860px){.contact{grid-template-columns:.85fr 1.15fr}
  /* Same fix as .pull on /about: without it the short details column pins to the top
     and leaves ~354px of dead white beside a full-height form, on the one page whose
     whole job is to convert. */
  .contact__details{align-self:center}}
.contact__details h2{font-weight:var(--display);letter-spacing:-.025em;
  font-size:clamp(1.4rem,2.4vw,1.9rem)}
.contact__big{margin-top:14px;font-size:clamp(1rem,1.7vw,1.16rem);font-weight:500}
.contact__big a{overflow-wrap:anywhere}
.contact__big a:hover{color:var(--accent-text)}
.contact__where{margin-top:14px;color:var(--muted);font-size:.95rem}
.contact__details .social{margin-top:18px;margin-left:-11px}

.cform p{margin-top:16px}
.cform p:first-of-type{margin-top:0}
.cform label{display:block;margin-bottom:7px;font-size:.78rem;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink)}
.cform input,.cform textarea{width:100%;font:inherit;font-size:.98rem;color:var(--ink);
  background:var(--panel);border:1px solid var(--hair);border-radius:var(--radius);
  padding:13px 14px;transition:border-color .18s,background .18s}
/* Keep the sitewide focus ring. outline:none here won on specificity (0,2,0 beats
   the global :focus-visible at 0,1,0 whatever the source order), so the only focus
   cue left on the site's one form was a hairline border changing colour. */
.cform input:focus-visible,.cform textarea:focus-visible{background:#fff;
  border-color:var(--btn-bg,var(--accent));
  outline:2px solid var(--accent);outline-offset:3px}
.cform .req{color:var(--accent-text,var(--accent));margin-left:3px}
.cform textarea{resize:vertical;min-height:132px}
.cform .btn{margin-top:8px;cursor:pointer}

/* Multi-film project pages stacked later films 38px apart with nothing between them. */
.player__n{margin-top:clamp(30px,4vw,52px);text-align:center;font-size:.76rem;
  font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
.player__n+.player{margin-top:12px}

/* A post with no photograph and no film still — the title carries the tile. */
.card--text .card__frame{display:grid;place-items:center;background:var(--panel);
  aspect-ratio:3/2;padding:clamp(14px,2vw,26px)}
.card--text .card__txt{font-weight:600;font-size:clamp(1rem,1.7vw,1.22rem);
  line-height:1.25;text-align:center;text-wrap:balance;color:var(--ink)}
.card--text .card__link:hover .card__txt{color:var(--accent-text,var(--accent))}

/* --------------------------------------------- danielregner /about
   Same flattening as Motion Grove's subpages: a portrait, a résumé link and three
   interview links had collapsed into one grey column of unlinked text. */
.me__wrap{display:grid;gap:clamp(24px,4vw,56px);grid-template-columns:1fr;
  align-items:start;padding-top:clamp(22px,3vw,34px)}
@media(min-width:860px){.me__wrap{grid-template-columns:.95fr 1.05fr}}
.me{width:100%;height:auto;display:block;border-radius:var(--radius)}
.me__wrap .prose{padding-top:0}
.linklist{list-style:none;margin-top:12px}
.linklist li{padding:7px 0;border-bottom:1px solid var(--hair)}
.linklist li:first-child{border-top:1px solid var(--hair)}
.linklist a{font-size:.98rem}
.linklist a:hover{color:var(--accent-text,var(--accent))}
.me__wrap .social{margin-top:14px;margin-left:-11px}
.sep{opacity:.45;padding:0 4px}
/* A secondary action: reads as a control without competing with the primary button. */
.btn--quiet{background:transparent;color:var(--ink);border-color:var(--ink)}
.btn--quiet:hover{background:var(--ink);color:var(--bg)}

/* The passion banner was a 450px decorative strip with the copy stranded below it. */
.btn--bright{background:var(--accent-2);border-color:var(--accent-2);color:var(--ink)}
.btn--bright:hover{background:transparent;color:var(--accent-2)}
.hero--short .hero__body .btn{margin-top:20px}

/* There is deliberately NO .card--lead rule. An earlier comment here described a
   lead tile spanning two columns; it was never implemented, and it should not be.
   The orphan-widening maths above was verified tile-by-tile for counts 1-40 at two
   breakpoints assuming uniform tiles — a wider first tile changes every row boundary
   and invalidates that sweep. The uniform edge-to-edge grid is also what Daniel's own
   live site does and what he signed off on. (link_card() still accepts a `lead`
   argument; nothing passes it.) */


/* Touch devices have no hover, so the play control and title were invisible until
   the tap that already committed to opening an autoplaying video. Show the
   affordance persistently where hover does not exist; desktop is untouched. */
@media (hover:none), (pointer:coarse){
  .card__over{opacity:1;justify-content:flex-end;gap:10px;padding-bottom:14px;
    background:linear-gradient(180deg,transparent 42%,rgba(10,10,12,.78) 100%)}
  .card__play{width:44px;height:44px}
  .card__play::before{border-width:6px 0 6px 10px}
  .card__t{transform:none;font-size:.92rem}
  .card--photo .card__over{display:none}
}

/* Grid section with no heading bar — the work speaks first. */
.sec--bare{padding-top:clamp(28px,4vw,56px)}
.sec--bare .grid{padding-top:0}

/* Full-service process band — six steps on the brand navy, matching the live site */
.band__inner--wide{max-width:var(--maxw);text-align:center}
.process__h{font-weight:var(--display);letter-spacing:-.03em;line-height:1.05;
  font-size:clamp(1.9rem,4.6vw,3.4rem)}
.process__lede{max-width:62ch;margin:14px auto 0;font-size:clamp(.95rem,1.5vw,1.08rem);
  font-weight:400;opacity:.92}
.steps{list-style:none;display:grid;gap:clamp(30px,4vw,58px) clamp(20px,3vw,44px);
  padding-top:clamp(34px,5vw,64px);text-align:center;
  grid-template-columns:repeat(3,1fr)}
@media(max-width:900px){.steps{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.steps{grid-template-columns:1fr}}
.step img{width:clamp(84px,9vw,130px);height:auto;margin:0 auto 16px}
/* Brand yellow on the navy band — one colour, one stroke weight, no fills. */
.step__i{display:block;margin:0 auto 18px;color:var(--accent-2);
  width:clamp(38px,4vw,48px);height:auto}
.step h3{font-weight:700;font-size:clamp(1rem,1.6vw,1.18rem);letter-spacing:-.01em}
.step p{margin-top:10px;font-size:.92rem;line-height:1.6;opacity:.88;
  max-width:38ch;margin-inline:auto}

/* Hero background plate. The <video> sits over the still; if it cannot play
   (reduced motion, save-data, decode failure) the still is what remains. */
.hero__media video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:1}
.hero__still{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0}
.hero__media::after{z-index:2}
@media (prefers-reduced-motion:reduce){ .hero__media video{display:none} }

/* Shorter banner for interior pages that carry their own background video */
.hero--short{min-height:clamp(280px,46vh,520px)}

/* ===================================================== Good Willed Consulting
   Components only this site uses. Everything chromatic comes from the theme
   block, same as the rest of the file — no hue is hardcoded here. */

/* The masthead CTA. Lives inside .social, which is a tight icon row, so it needs
   its own box or the label spills out from under the fill (it did). */
.btn--sm{padding:10px 18px;font-size:.74rem;letter-spacing:.12em;text-transform:uppercase}
/* The CTA is an <a> inside .social, so `.social a{width:44px;height:44px}` — written
   for square icon targets — was sizing the button to 44px and letting the label spill
   out from under the fill. Both dimensions must be released explicitly; the 44px
   minimum touch target is preserved by min-height, not by a fixed square. */
.nav__cta{width:auto!important;height:auto!important;min-height:44px;
  margin-left:14px;white-space:nowrap;display:inline-flex;align-items:center;
  line-height:1;color:var(--btn-ink)!important;background:var(--btn-bg)}
.nav__cta:hover,.nav__cta:focus-visible{color:var(--btn-ink)!important;transform:none}

/* Full-bleed image band. .sec--flush only removes the section's top padding; it still
   sits inside .sec's max-width and horizontal padding, which left the closing
   photograph as a ~650px block floating in white. */
.fullbleed{padding:clamp(46px,7vw,92px) 0 0}
/* object-position, not just cover: the source is a 2048 square and the subject —
   the CHANGES shopfront sign — sits in its upper half, so a centred crop cut the
   sign out and left a band of shutters. */
.fullbleed img{width:100%;max-height:min(60vh,620px);object-fit:cover;
  object-position:center 28%}

/* NOT .sec--narrow — that already exists at the top of this file (860px) and is
   used by Wallflower's contact page. Redefining it here silently widened that
   page, because a later rule of equal specificity wins on source order. */
.sec--prose{max-width:78ch;margin-inline:auto}
.center{text-align:center}
.center .btn{margin-top:26px}
.center p{margin-inline:auto}

.hero__lede{margin-top:18px;max-width:52ch;font-size:clamp(.98rem,1.5vw,1.1rem);
  line-height:1.65}
/* Her live hero sets the headline and the mission SIDE BY SIDE, not stacked —
   measured off the real page at 1440: headline x=58 w=649, mission x=773 w=594, both
   on the same row. A modifier rather than a bare .hero__body rule, so the three other
   sites' heroes are untouched. */
.hero__body--cols h1{max-width:20ch}
@media(min-width:900px){
  .hero__body--cols{display:grid;grid-template-columns:1fr 1fr;
    gap:clamp(28px,4vw,64px);align-items:end}
  .hero__body--cols h1{max-width:none}
  .hero__body--cols .hero__lede{margin-top:0;padding-bottom:.5em}
}

/* Services. A grid, not the bare <ul> it rendered as before the CSS existed:
   four unconstrained 760px images stacked down the page with bullet markers. */
.svcs{list-style:none;display:grid;gap:clamp(18px,2.2vw,30px);
  grid-template-columns:1fr;padding-top:clamp(20px,3vw,34px)}
/* Four across on desktop, which is what her live site does — the four service
   headings measure x=99/426/753/1081, all on one row at 1440. Two-up is the
   intermediate step so the cards never get narrower than they are readable. */
@media(min-width:660px){.svcs{grid-template-columns:1fr 1fr}}
@media(min-width:1080px){.svcs{grid-template-columns:repeat(4,1fr)}}
.svc{display:flex;flex-direction:column;background:var(--panel)}
/* Fixed ratio so four photographs of four different shapes still line up. */
.svc__img{width:100%;aspect-ratio:3/2;object-fit:cover}
.svc__body{padding:clamp(18px,2.2vw,28px)}
.svc__body h3{font-size:clamp(1.1rem,1.7vw,1.32rem);font-weight:var(--display)}
.svc__body p{margin-top:10px;color:var(--muted);font-size:.94rem;line-height:1.62}

/* Alternating text / image rows on /our-approach. */
.split{display:grid;gap:clamp(24px,4vw,56px);grid-template-columns:1fr;
  align-items:center}
@media(min-width:820px){.split{grid-template-columns:1fr 1fr}
  .sec.alt:nth-of-type(even) .split__text{order:2}}
.split__img{width:100%;aspect-ratio:4/3;object-fit:cover}
.split__text h2{font-size:clamp(1.4rem,2.6vw,2rem);font-weight:var(--display)}
.split__text p{margin-top:14px;color:var(--muted);line-height:1.68;max-width:56ch}

/* Numbered process steps. .steps already exists for Motion Grove's icon grid;
   this variant is a vertical ordered list with a numeral instead of an icon. */
.steps--num{list-style:none;display:grid;gap:clamp(18px,2.4vw,30px);
  grid-template-columns:1fr;padding-top:clamp(20px,3vw,34px);max-width:80ch}
.steps--num .step{display:grid;grid-template-columns:auto 1fr;gap:clamp(16px,2vw,26px);
  align-items:start;padding-bottom:clamp(18px,2.4vw,30px);
  border-bottom:1px solid var(--hair)}
.steps--num .step:last-child{border-bottom:0}
.step__n{font-family:var(--font-display);
  font-size:clamp(1.6rem,3vw,2.4rem);line-height:1;color:var(--accent-text,var(--accent));
  font-weight:var(--display);min-width:1.4em}
.steps--num h3{font-size:clamp(1.02rem,1.6vw,1.2rem);font-weight:var(--display)}
.steps--num p{margin-top:8px;color:var(--muted);line-height:1.64}

.band--split .band__where{align-self:center}
.band--split .band__where p+p{margin-top:8px}
.band--vision .band__inner{max-width:74ch}
.band--vision h2{font-size:clamp(1.4rem,2.6vw,2rem);margin-bottom:14px}
.band--vision p{line-height:1.72}

/* Two photographs side by side, equal height, no crop surprises. */
.pair{display:grid;gap:clamp(10px,1.4vw,18px);grid-template-columns:1fr}
@media(min-width:640px){.pair{grid-template-columns:1fr 1fr}}
.pair img{width:100%;aspect-ratio:1/1;object-fit:cover}

.linkrow{margin-top:24px;display:flex;flex-wrap:wrap;gap:12px}
