/* Noir Garden. One stylesheet for every page on the site.
   Section order matches the order things appear on the page. */

:root{
  --bg:#fff;
  --ink:#000;
  --ink-strong:#191919;
  --ink-body:#474747;
  /* running copy under a heading, on white. It is the light-mode twin of the
     rgba(255,255,255,.66) the dark plates set their body text in, so a
     paragraph reads the same distance below its title on either ground. */
  --ink-soft:#6b6b6b;
  --ink-muted:#919191;
  --ink-faded:rgba(25,25,25,.6);
  --surface:#f5f5f5;
  --hairline:#eaeaea;

  --night:#111;

  --nav-h:85px;
  /* the gutter the whole site is written against. A full-bleed plate cannot
     use max-width to find it, so it computes the same inset by hand. The
     percentage resolves against whatever box uses it, which is always the
     full-width one, so every section lands on one left edge. */
  --edge:max(60px, calc((100% - 1800px) / 2));
  --night-2:#1b1b1b;

  --r-pill:40px;   /* CTAs only */
  --r-panel:16px;  /* every card, frame and image */

  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-in-out:cubic-bezier(.65,0,.35,1);
  --fast:.15s; --base:.25s; --slow:.4s;
  /* hover is its own pair. --ease-out is nearly vertical at the start,
     which reads as a snap on a fill or a nudge; this one leaves gently
     and lands gently, over a longer beat. */
  --hover:.42s; --ease-soft:cubic-bezier(.33,0,.2,1);
  /* the hover zoom, one value for every image on the site. --ease-out starts
     nearly vertical, which on a photograph reads as a jolt rather than a
     drift, so this gesture takes --ease-soft and a longer beat. The amount is
     deliberately small: past this the crop visibly changes and the picture
     stops being the same picture. */
  --zoom:1.018; --zoom-t:.78s;
  /* entrances: decelerating, but without --ease-out's near-vertical
     launch, which reads as a snap on anything that travels far */
  --ease-entry:cubic-bezier(.22,.68,.3,1);
}

*{box-sizing:border-box}
html{letter-spacing:0}
/* black behind the page itself: it is what shows when the scroll rubber-bands
   past the footer, which is black on every page */
html{scroll-behavior:smooth;background:#000}
body{
  margin:0;background:var(--bg);color:var(--ink-strong);
  font-family:"Inter Tight",sans-serif;font-weight:500;
  -webkit-font-smoothing:antialiased;
}
a{color:inherit;text-decoration:none}
button{font:inherit;border:0;background:none;padding:0;color:inherit}
img{display:block}
h1,h2,h3,p,figure,blockquote,figcaption{margin:0}
h2{font-size:clamp(32px,5.4vw,52px);line-height:1.02;font-weight:500;letter-spacing:-.02em}
h3{font-size:26px;line-height:1.2;font-weight:500}
p{font-size:16px;line-height:1.5;color:var(--ink-body)}
section[id]{scroll-margin-top:96px}
:focus-visible{outline:2px solid var(--ink-strong);outline-offset:3px}

/* ══════════════════════════════════════════════════════════════ buttons */
.button{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:11px 22px;border:0;border-radius:var(--r-pill);
  font-size:16px;line-height:1.4;white-space:nowrap;
  transition:background-color var(--hover) var(--ease-soft),color var(--hover) var(--ease-soft),box-shadow var(--hover) var(--ease-soft);
}
/* No growing on hover: the fill carries the state change, and on the
   buttons that have one, the arrow steps to the right. */
.button svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:1.5;transition:transform var(--hover) var(--ease-soft)}
.button:hover svg{transform:translateX(3px)}
.button-primary{background:#1a1a1a;color:#fff}
.button-primary:hover{background:#2a2a2a}
.button-secondary{background:var(--surface);color:var(--ink)}
.button-secondary:hover{background:#ededed}

/* ══════════════════════════════════════════════════════════════ navbar */
/* The bar at the top of the page does not follow the scroll. It sits over the
   hero, it leaves with it, and its job ends there. What comes back on the way
   down is .navcap, a separate element. */
.navbar{
  position:relative;z-index:50;
  display:flex;align-items:center;justify-content:space-between;
  padding:20px 60px;
}
/* on-dark is the ink, not the plate: it is what both bars use over black */
.navbar.on-dark .brand{color:#fff}
.navbar.on-dark .nav-links a{color:rgba(255,255,255,.72)}
.navbar.on-dark .nav-links a:hover{color:#fff}
.navbar.on-dark .nav-burger svg{stroke:#fff}

/* the nav CTA is the same object as "See the work": a stroke and a blur, no
   fill of its own, so it belongs to whatever is behind it */
.navbar .nav-action .button{
  background:rgba(0,0,0,.05);color:var(--ink-strong);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.14);
  backdrop-filter:blur(14px) saturate(1.2);-webkit-backdrop-filter:blur(14px) saturate(1.2);
  transition:background-color var(--hover) var(--ease-soft),box-shadow var(--hover) var(--ease-soft),color var(--hover) var(--ease-soft);
}
.navbar .nav-action .button:hover{background:rgba(0,0,0,.1)}
.navbar.on-dark .nav-action .button{
  background:rgba(0,0,0,.34);color:#fff;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.32);
}
.navbar.on-dark .nav-action .button:hover{background:rgba(255,255,255,.14)}

/* ── the capsule ──────────────────────────────────────────────────────────
   A clone of the bar, fixed, that rides in from above the fold once the
   original has scrolled out and rides back out when it returns. It is only
   ever a plate and a position: the ink comes from .on-dark, shared with the
   bar, so the two can never drift apart. */
.navcap{
  position:fixed;top:14px;left:50%;z-index:60;
  width:auto;max-width:calc(100vw - 28px);
  padding:16px 16px 16px 34px;gap:56px;
  /* fully round, so the pill CTA sitting inside it shares its geometry
     instead of arguing with a softer corner */
  border-radius:999px;
  background:rgba(255,255,255,.6);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.09),0 12px 34px rgba(0,0,0,.1);
  backdrop-filter:blur(18px) saturate(1.6);-webkit-backdrop-filter:blur(18px) saturate(1.6);
  /* out and in are not the same gesture, so they do not share a curve.
     --ease-out launches nearly vertically, which on something that travels its
     own height reads as a snap; arriving takes --ease-entry over a longer beat
     and lands rather than stops, leaving takes --ease-soft and goes gently at
     both ends. The fade is shorter than the travel in both directions, so the
     capsule is already gone before it finishes moving instead of being seen
     out to the edge of the screen. */
  opacity:0;
  transform:translateX(-50%) translateY(calc(-100% - 22px));
  pointer-events:none;
  transition:transform .58s var(--ease-soft),
             opacity .34s var(--ease-soft),
             background-color var(--base) var(--ease-out),
             box-shadow var(--base) var(--ease-out);
}
.navcap.is-in{
  opacity:1;
  transform:translateX(-50%) translateY(0);
  pointer-events:auto;
  transition:transform .74s var(--ease-entry),
             opacity .32s var(--ease-entry),
             background-color var(--base) var(--ease-out),
             box-shadow var(--base) var(--ease-out);
}
.navcap.on-dark{
  background:rgba(16,16,16,.46);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.16),0 12px 34px rgba(0,0,0,.4);
}
/* the links are full ink in the capsule. In the bar they sit over a hero and
   muted ink is what keeps them off the headline; the capsule is a plate of its
   own, floating over whatever it happens to be passing, and grey on that reads
   as switched off rather than as quiet. .on-dark still wins over this. */
.navcap .nav-links a{color:var(--ink-strong)}
/* inside the capsule nothing stretches: the row is only as wide as it reads */
/* the row does not stretch here, but the gap between the links and the
   wordmark are the bar's: only the plate around them is the capsule's */
.navcap .nav-links{flex:0 0 auto}
.navcap .nav-action{margin-left:2px}

/* the phone menu. Hidden at every width until the query above turns it on */
.nav-menu{
  position:fixed;inset:0;z-index:70;display:none;
  flex-direction:column;align-items:flex-start;justify-content:center;
  gap:22px;padding:0 30px;background:var(--bg);
  opacity:0;pointer-events:none;transform:translateY(-8px);
  transition:opacity .3s ease,transform .4s var(--ease-out);
}
body.menu-open .nav-menu{opacity:1;pointer-events:auto;transform:none}
.nav-menu a{font-size:26px;color:var(--ink-strong);letter-spacing:-.03em}
.nav-menu a:hover{color:var(--ink)}
.nav-links{display:flex;gap:28px}
.nav-links a{font-size:16px;color:var(--ink-muted);white-space:nowrap;transition:color var(--hover) var(--ease-soft)}
.nav-links a:hover{color:var(--ink-strong)}
.brand{display:flex;align-items:center;color:var(--ink-strong)}
.brand svg{display:block;width:auto;height:23px}
.brand-nav{flex:0 0 auto}
/* the links take the centre: the logo and the action hold equal-weight
   flanks so the row stays optically centred at any width */
.nav-links{flex:1 1 auto;justify-content:center}
.nav-action{flex:0 0 auto;display:flex;justify-content:flex-end;min-width:0}
.nav-burger{display:none;width:22px;height:22px}
.nav-burger svg{width:22px;height:22px;stroke:var(--ink-strong);fill:none;stroke-width:1.5}
.nav-burger .x{display:none}

/* ═══════════════════════════════════════════════════════════════ hero */
/* A full-bleed black plate that starts behind the sticky bar, a looping film
   underneath it, and the headline living in the same space as the footage.

   The inversion is on the type, never on the film. `difference` subtracts the
   backdrop from white, so the words read white over the dark field and flip
   dark exactly where a bright edge of the object travels under them. One
   declaration, and the film needs no alpha channel or masking of its own.

   Two things this depends on, and both are easy to break later:
     · .hero isolates, so the blend stops at the hero instead of reaching the
       page and the navbar behind it.
     · nothing between the headline and the film may open a stacking context,
       because that would cut the headline off from the backdrop it blends
       with. That is why .hero-copy is positioned but carries no z-index. */
.hero{
  position:relative;isolation:isolate;overflow:hidden;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  min-height:100vh;min-height:100svh;
  padding:72px 80px 44px;
  background:#000;text-align:center;
}
/* Only the plate at the top of the page slides under the sticky bar. Applied
   to every plate it would make each one climb over the one before it and
   swallow its bottom edge. */
.hero:first-of-type{margin-top:calc(-1 * var(--nav-h));padding-top:calc(var(--nav-h) + 72px)}
.hero-media{position:absolute;inset:0;pointer-events:none;background:#000}
/* the plate under it is already black, so this is a fade up out of the
   black rather than off a blank page. It also covers the one frame where
   the headline stops being a plain white word and starts being blended:
   difference against black is the identity, so early on there is nothing
   to see change. */
.hero-video{display:block;width:100%;height:100%;object-fit:cover;opacity:0;
  transition:opacity 1.5s var(--ease-entry) .3s}
body.ready .hero-video{opacity:1}
/* Cover crops a 16:9 film hard once the frame is taller than it is wide, and
   the object ends up filling the screen. Letterboxing instead costs nothing
   here, because what surrounds the film is the same black. */
@media(max-aspect-ratio:1/1){
  /* contain alone leaves the object tiny in a tall frame, so the film is
     scaled back up past the viewport. The letterbox scales with it and is
     black on black, so nothing shows a seam. */
  .hero-video{object-fit:contain;transform:scale(1.75)}
}
/* ── the dot field ─────────────────────────────────────────────────────────
   The film is an object on flat black, and flat black behind a dithered
   object reads as nothing at all. A grid of single-pixel dots gives the
   plate a surface. It has to sit over the film, because the film is opaque
   edge to edge; `lighten` keeps whichever of the two is brighter, so the grid
   shows on the black and through the gaps in the object and never dims a dot
   of the film itself.

   The pitch is several times the film's own dither so the two never read as
   one pattern, and the mask thins the field out behind the copy and lets it
   die before the edges: a grid that runs to the border is wallpaper. */
.hero[data-film="dither"] .hero-media::after{
  content:"";position:absolute;inset:0;
  background-image:radial-gradient(circle,rgba(255,255,255,.2) 1px,transparent 1.5px);
  background-size:28px 28px;background-position:center;
  mix-blend-mode:lighten;
  -webkit-mask-image:radial-gradient(ellipse 75% 80% at 50% 50%,rgba(0,0,0,.35) 0,rgba(0,0,0,.35) 22%,#000 55%,transparent 100%);
          mask-image:radial-gradient(ellipse 75% 80% at 50% 50%,rgba(0,0,0,.35) 0,rgba(0,0,0,.35) 22%,#000 55%,transparent 100%);
  opacity:0;transition:opacity 1.5s var(--ease-entry) .3s;
}
body.ready .hero[data-film="dither"] .hero-media::after{opacity:1}

.hero-copy{position:relative;display:flex;flex-direction:column;align-items:center;gap:28px;max-width:900px}
.hero h1{
  position:relative;color:#fff;
  font-size:clamp(40px,5vw,72px);line-height:1;letter-spacing:-.025em;font-weight:500;
}
/* the film is not ours to control, so the paragraph carries its own contrast
   rather than trusting whatever happens to be behind it */
.hero-copy p{
  max-width:580px;font-size:18px;line-height:1.5;color:#fff;
  text-shadow:
    0 0 14px rgba(0,0,0,.92),
    0 0 46px rgba(0,0,0,.85),
    0 0 110px rgba(0,0,0,.75),
    0 0 200px rgba(0,0,0,.6);
}
/* the labels do not wrap inside a button, so the row has to: a long email
   address beside a CTA is wider than a phone. */
.button-row{display:flex;flex-wrap:wrap;gap:12px}
.hero .button-primary{background:#fff;color:#111}
.hero .button-primary:hover{background:#e2e2e2}
/* The stroke lives in box-shadow, so anything else setting box-shadow on
   .button wipes it out. Keep drop shadows off these. */
.hero .button-secondary{
  background:rgba(0,0,0,.34);color:#fff;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.32);
  backdrop-filter:blur(14px) saturate(1.2);-webkit-backdrop-filter:blur(14px) saturate(1.2);
}
.hero .button-secondary:hover{background:rgba(255,255,255,.14)}

/* The copy lifts in out of a blur, the headline word by word. The buttons
   end on filter:none, not blur(0): a filter of any kind makes the element a
   containing block, and "See the work" carries a backdrop-filter that would
   then have nothing left to sample. The words and the paragraph end on
   blur(0px) instead: dropping the filter at the end of the move costs a
   repaint that stalled the film for a third of a second. */
.hero-copy>*{opacity:0;transform:translateY(20px);filter:blur(10px);
  transition:opacity .7s var(--ease-entry) var(--d,0ms),transform .9s var(--ease-entry) var(--d,0ms),
             filter .7s var(--ease-entry) var(--d,0ms)}
body.ready .hero-copy>*{opacity:1;transform:none;filter:none}
.hero-copy h1{opacity:1;transform:none;filter:none;transition:none}
.hero-copy p{--d:420ms}
body.ready .hero-copy p{filter:blur(0px)}
.hero-copy .button-row{--d:540ms}

.hero h1 .hw{
  display:inline-block;opacity:0;transform:translateY(30px);filter:blur(14px);
  transition:opacity .8s var(--ease-entry) var(--d,0ms),transform 1.05s var(--ease-entry) var(--d,0ms),
             filter .85s var(--ease-entry) var(--d,0ms);
}
body.ready .hero h1 .hw{opacity:1;transform:none;filter:blur(0px)}
/* each moving piece has its own layer, so the film under it is not repainted
   with it. Kept for good: letting go of the layers later is itself a hitch */
.hero h1 .hw,.hero-copy>*:not(h1){will-change:transform,opacity,filter}

/* the client strip now lives at the foot of the plate. It rides the same
   entry as the copy, one beat later, and keeps the marquee full width by
   escaping the hero's side padding rather than being inset by it. */

/* ══════════════════════════════════════════════════════════════ sections */
.section,.trusted{width:100%;max-width:1920px;margin:0 auto;padding:96px 60px}
.trusted{text-align:center;padding-top:72px;padding-bottom:56px}
.trusted .eyebrow{margin-bottom:0}
.eyebrow{font-size:18px;line-height:1.5;color:var(--ink-muted)}
.section-heading{max-width:720px;margin:0 0 60px}
.section-heading .eyebrow{margin-bottom:16px}
/* centred variant: the block and everything in it share one axis, so the
   eyebrow sits dead centre over the title instead of on the text edge */
.section-heading--center{max-width:640px;margin:0 auto 48px;text-align:center}
.section-heading--center p{margin-left:auto;margin-right:auto}
.section-heading p:not(.eyebrow){margin-top:20px;font-size:18px;line-height:1.5;max-width:560px}

.marquee{margin-top:38px;overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 120px,#000 calc(100% - 120px),transparent);
  mask-image:linear-gradient(90deg,transparent,#000 120px,#000 calc(100% - 120px),transparent)}
.marquee-track{display:flex;width:max-content;align-items:center;animation:marquee 80s linear infinite}
.marquee-group{display:flex;align-items:center;gap:84px;padding-right:84px;flex:0 0 auto}
/* Logos arrive at wildly different aspect ratios, so height alone would
   make the wide ones dominate. Capping both axes lets each one sit on the
   same optical weight. */
.marquee img{
  width:auto;height:32px;object-fit:contain;
  opacity:.6;transition:opacity var(--hover) var(--ease-soft);
}
@keyframes marquee{to{transform:translateX(-50%)}}

/* ═══════════════════════════════════════════════════════════ disciplines */
/* Five across on a wide screen: the row itself says how much the studio
   covers, before a word is read. Portrait frames so five still breathe. */
.disc-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
.disc-card{min-width:0}
/* the accent rides the position, so adding a discipline needs no new class */
.disc-card:nth-child(1){--accent:#B4530E}
.disc-card:nth-child(2){--accent:#1856db}
.disc-card:nth-child(3){--accent:#6B4FA8}
.disc-visual{aspect-ratio:1;border-radius:var(--r-panel);overflow:hidden;background:var(--ph,#f7f7f5)}

/* the front itself: a bright lip on the line, so the eye reads a direction
   rather than a band that happens to be there */

/* this frame's lower left corner is under the one in front of it */

.disc-visual img{width:100%;height:100%;object-fit:cover;transition:transform var(--zoom-t) var(--ease-soft)}
.disc-card:hover .disc-visual img{transform:scale(var(--zoom))}
.disc-label{display:flex;align-items:center;gap:8px;margin:24px 0 10px;color:var(--accent)}
.disc-label svg{width:18px;height:18px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
/* a supplied icon is drawn as a filled shape, not a stroke */
.disc-label svg.disc-icon{fill:currentColor;stroke:none}
.disc-label h3{font-size:20px;line-height:1.25;color:currentColor}
.disc-card>p{font-size:18px;line-height:1.4}
/* the three headings are a summary, so the grid ends on the way into the
   page that opens them out */
.disc-more{display:flex;justify-content:center;margin-top:56px}

@media(max-width:860px){.disc-grid{grid-template-columns:1fr;gap:52px}}

/* ═════════════════════════════════════════════════════════════ selected work
   Four projects, nothing else. The full, filterable wall lives on /work;
   this is the trailer for it. */
.works-head{display:flex;align-items:baseline;justify-content:space-between;gap:24px;flex-wrap:wrap;row-gap:14px;margin-bottom:28px}
.work-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}
.work-card{display:block;min-width:0}
.work-media{
  position:relative;aspect-ratio:2/3;overflow:hidden;
  border-radius:var(--r-panel);background:var(--ph,#f0efec);
}
/* MEDIA SLOT: drop <img class="work-src"> here, or a <video class="work-src"
   autoplay muted loop playsinline> to have it run on hover. */
.work-src{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:transform var(--zoom-t) var(--ease-soft)}
.work-card:hover .work-src{transform:scale(var(--zoom))}
.work-foot{padding-top:18px}
.work-name{font-size:16px;line-height:1.3;color:var(--ink-strong)}
.work-desc{margin-top:6px;font-size:16px;line-height:1.35;color:var(--ink-muted)}
.work-more{display:flex;justify-content:center;margin-top:56px}

/* four across needs the width for it. Two-up before the single column,
   so the cards never get narrower than their own caption. */
@media(max-width:1199px){.work-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:24px}}
@media(max-width:960px){.work-grid{grid-template-columns:1fr;gap:44px}.work-media{aspect-ratio:16/11}}

/* ══════════════════════════════════════════════════════════════ the wheel
   Moved out of the hero and given its own band. Cards ride the rim of a disc
   whose hub sits far below the section, so only the top arc shows, and each card
   is tangent to the rim, so it tilts as it travels. Driven frame by frame
   rather than by a CSS animation: hovering has to bleed the speed off over a
   beat and pick it back up, and a keyframe animation can only be hard-paused. */
.archive{background:#000;color:#fff;overflow:hidden}
/* the wheel and the reviews share one plate, so the black runs unbroken
   from one to the other and cuts the white page in a single block */

.wheel-zone{position:relative;margin-top:64px;width:100%;height:521px;overflow:hidden;pointer-events:none}
.wheel{position:absolute;left:50%;top:0;width:0;height:0}
.wheel .disc{position:absolute;left:0;top:0;will-change:transform}
.card{
  position:absolute;left:0;top:0;
  border-radius:var(--r-panel);overflow:hidden;
  background:var(--ph,#232323);
  border:1px solid rgba(255,255,255,.07);
  pointer-events:auto;   /* the zone ignores the pointer; the cards catch it */
}
.card img{width:100%;height:100%;object-fit:cover}
/* no fall-off under the wheel any more: the arc is flat enough to leave by
   the sides of the page, so there is nothing sinking that needs hiding */
.wheel-fade{display:none}
/* the reviews are a different subject on the same plate: the gap is what
   says so, since there is no rule or tone change to say it */

/* ═══════════════════════════════════════════════════════════════ reviews
   A full-bleed rail rather than a grid: the quotes keep moving, so the
   section never reads as three fixed testimonials picked to flatter. Same
   two-group seam trick as the client strip, so the loop has no cut. The
   rail breaks the page container; the heading above it does not.
   ────────────────────────────────────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════ closing */
.closing{max-width:1920px;margin:0 auto;padding:64px 60px}
.closing-panel{
  display:grid;grid-template-columns:minmax(0,.82fr) minmax(380px,1fr);
  gap:88px;padding:56px 0;
  max-width:1180px;margin-inline:auto;
}
.closing-panel h2{max-width:440px}
.closing-note{margin-top:22px;max-width:400px;font-size:18px;line-height:1.45}
.closing-content{align-self:center}
.closing-content>p{font-size:22px;line-height:1.15;color:var(--ink-strong)}
.closing-content .button-row{margin-top:32px;align-items:center}

/* ═════════════════════════════════════════════════════════════ page head
   How a sub-page opens. It carries no plate of its own: the page is one white
   sheet from the bar down, and the title is simply the first thing on it. The
   pull-up stays, so the bar sits over the head rather than above it, and the
   bottom padding is short because the section below brings its own 96.
   Type is the hero's, to the pixel, so a title reads the same on any page. */
.page-head{
  margin-top:calc(-1 * var(--nav-h));
  padding:calc(var(--nav-h) + 96px) 60px 20px;
  text-align:center;
}
.page-head>*{max-width:640px;margin-inline:auto}
.page-head h1{font-size:clamp(40px,5vw,72px);line-height:1;letter-spacing:-.025em;font-weight:500;color:var(--ink-strong)}
.page-head__note{margin-top:24px;font-size:18px;line-height:1.45;color:var(--ink-body)}

/* The head arrives the way the hero does: up and into focus, title first.
   Both start behind the veil's fade rather than under it, so the movement is
   seen instead of covered. */
.page-head h1,.page-head__note{
  opacity:0;transform:translateY(22px);filter:blur(10px);
  transition:opacity .8s var(--ease-entry) var(--d,0ms),
             transform 1s var(--ease-entry) var(--d,0ms),
             filter .85s var(--ease-entry) var(--d,0ms);
}
.page-head h1{--d:260ms}
.page-head__note{--d:400ms}
body.ready .page-head h1,body.ready .page-head__note{opacity:1;transform:none;filter:none}

/* ═══════════════════════════════════════════════════════════════ contact
   The details column carries what a form cannot: where the studio actually
   is, and two ways to skip the form entirely. */
.contact-layout{
  display:grid;grid-template-columns:minmax(0,.72fr) minmax(0,1fr);
  gap:88px;align-items:start;max-width:1180px;margin-inline:auto;
}
.contact-details{display:flex;flex-direction:column;gap:44px}
.contact-details>div{display:flex;flex-direction:column;align-items:flex-start;gap:9px}
.contact-details strong{margin-bottom:8px;font-size:18px;line-height:1.2;font-weight:500;color:var(--ink-strong)}
.contact-details address,.contact-details a{
  font-style:normal;font-size:16px;line-height:1.45;color:var(--ink-muted);
  transition:color var(--hover) var(--ease-soft);
}
.contact-details a:hover{color:var(--ink-strong)}
@media(max-width:960px){.contact-layout{grid-template-columns:1fr;gap:52px}}
@media(max-width:620px){
  .page-head{padding:calc(var(--nav-h) + 64px) 20px 8px}
  .contact-details{gap:34px}
}

/* ══════════════════════════════════════════════════════════════════ form
   Thomas's Formspree form, redrawn in this page's vocabulary: filled
   fields on the surface tone instead of hairline underlines, the page's
   own radii, and the shared .button for submit. Behaviour is unchanged,
   including the reserved error row so nothing shifts when one appears. */
.cform{width:100%}
.cform-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px}
.cform .field{margin-bottom:18px;min-width:0}
.cform label{display:block;margin-bottom:8px;font-size:14px;line-height:1.4;color:var(--ink-muted)}
.cform label .req{color:var(--ink-muted);opacity:.7}
.cform input[type="text"],
.cform input[type="email"],
.cform textarea,
.cform .select-display{
  width:100%;padding:13px 16px;border:1px solid transparent;border-radius:8px;
  background:var(--surface);color:var(--ink-strong);
  font-family:inherit;font-weight:500;font-size:16px;line-height:1.4;
  outline:none;-webkit-appearance:none;appearance:none;
  transition:background-color var(--base) var(--ease-out),border-color var(--base) var(--ease-out);
  -webkit-text-fill-color:var(--ink-strong);
  -webkit-box-shadow:0 0 0 1000px var(--surface) inset;
  box-shadow:0 0 0 1000px var(--surface) inset;
}
.cform input:-webkit-autofill,
.cform input:-webkit-autofill:hover,
.cform input:-webkit-autofill:focus{
  -webkit-box-shadow:0 0 0 1000px #f5f5f5 inset;
  -webkit-text-fill-color:#191919;
  transition:background-color 5000s ease-in-out 0s;
}
.cform input::placeholder,.cform textarea::placeholder{color:var(--ink-muted);-webkit-text-fill-color:var(--ink-muted)}
.cform input:focus,.cform textarea:focus,.cform .custom-select.open .select-display{border-color:var(--ink-strong)}
.cform input.error,.cform textarea.error,.cform .custom-select.error .select-display{border-color:#e0403f}
.cform textarea{resize:none;overflow:hidden;min-height:150px;line-height:1.5;display:block;box-shadow:none;-webkit-box-shadow:none}

.cform .custom-select{position:relative;user-select:none}
.cform .select-display{display:flex;align-items:center;justify-content:space-between;gap:12px;cursor:pointer;color:var(--ink-muted);box-shadow:none;-webkit-box-shadow:none}
.cform .select-display.has-value{color:var(--ink-strong)}
.cform .select-arrow{display:flex;flex:0 0 auto;transition:transform var(--base) var(--ease-out)}
.cform .select-arrow svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.6}
.cform .custom-select.open .select-arrow{transform:rotate(180deg)}
.cform .select-dropdown{
  display:none;position:absolute;z-index:20;top:calc(100% + 6px);left:0;right:0;
  padding:6px;border-radius:8px;background:#fff;
  border:1px solid var(--hairline);box-shadow:0 14px 40px rgba(0,0,0,.10);
}
.cform .custom-select.open .select-dropdown{display:block}
.cform .select-option{
  padding:11px 14px;border-radius:6px;font-size:15px;line-height:1.3;
  color:var(--ink-body);cursor:pointer;
  transition:background-color var(--hover) var(--ease-soft),color var(--hover) var(--ease-soft);
}
.cform .select-option:hover{background:var(--surface);color:var(--ink-strong)}

.cform .error-msg{
  margin-top:6px;font-size:13px;line-height:1.3;color:#e0403f;
  min-height:17px;visibility:hidden;opacity:0;transition:opacity var(--base) ease;
}
.cform .field.show-error .error-msg{visibility:visible;opacity:1}
.cform-send{margin-top:10px}
.cform-success{display:none;padding:28px 0;font-size:20px;line-height:1.45;color:var(--ink-strong)}
@media(max-width:620px){.cform-grid{grid-template-columns:1fr;gap:0}}

/* the last plate on every page. Full bleed like the hero, so the site opens
   and closes on the same black, and it finds the gutter with --edge. */
footer{background:#000;padding:88px var(--edge) 44px}
footer .brand{color:#fff}
/* three columns, and the last one only as wide as its longest link, so it
   sits on the right edge instead of floating in a third of the page */
.footer-main{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr) auto;gap:72px;align-items:start}
.brand-footer svg{height:52px}
/* the address is a footer entry like the phone number, not a paragraph:
   same size, same tone as the links beside it */
.footer-address{margin-top:26px;font-style:normal;font-size:16px;line-height:1.35;color:rgba(255,255,255,.5)}
.footer-col{display:flex;flex-direction:column;align-items:flex-start;gap:14px}
.footer-col strong{margin-bottom:20px;font-size:18px;line-height:1.2;font-weight:500;color:#fff;white-space:nowrap}
.footer-col a{font-size:16px;line-height:1.35;color:rgba(255,255,255,.5);white-space:nowrap;transition:color var(--hover) var(--ease-soft)}
.footer-col a:hover,.footer-bottom a:hover{color:#fff}
.footer-social{display:flex;gap:20px}
.footer-social a{display:flex;color:#fff;transition:opacity var(--hover) var(--ease-soft)}
.footer-social a:hover{opacity:.55}
.footer-social svg{width:21px;height:21px}
/* the socials and the copyright share the last line, one at each edge */
.footer-bottom{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;margin-top:68px;font-size:14px;text-transform:uppercase;color:rgba(255,255,255,.4)}

/* ═══════════════════════════════════════════════════════ ai integration
   Chapters rather than sections, and the tone is held rather than alternated:
   the hero and the two chapters after it are all black, then the page breaks
   out into white and stays there. Switching every section turned the tone
   into a stripe and it stopped meaning anything.

   Both tones are one system. The plate sets four tokens and everything inside
   reads from them, so nothing is restyled per colour and no rule is written
   twice. Type is the site's own throughout: h2 is h2, no display scale. */
.chap{
  --chap-ink:var(--ink-strong);
  --chap-soft:var(--ink-soft);
  --chap-faint:var(--ink-muted);
  --chap-line:var(--hairline);
  position:relative;
  max-width:1920px;margin:0 auto;padding:140px 60px;
  color:var(--chap-ink);
}
.chap--dark{
  --chap-ink:#fff;
  --chap-soft:rgba(255,255,255,.66);
  --chap-faint:rgba(255,255,255,.5);
  --chap-line:rgba(255,255,255,.14);
  max-width:none;background:#000;
}
.chap-head h2{color:var(--chap-ink)}
.chap-head .eyebrow{margin-bottom:18px;color:var(--chap-faint)}
.chap-head p{margin:26px 0 0;max-width:520px;font-size:18px;line-height:1.5;color:var(--chap-soft)}

/* a head that belongs to what is under it rather than to the left edge: the
   count is a symmetrical row, so the title over it is centred on the same
   axis and the note keeps a measure of its own inside that. */
.chap-head--center{max-width:900px;margin-left:auto;margin-right:auto;text-align:center}
/* balanced, because a centred paragraph that leaves one word alone on the
   last line is the one thing the eye reads before the sentence */
.chap-head--center p{max-width:620px;margin-left:auto;margin-right:auto;text-wrap:balance}

/* drawn rather than filled: on a black plate a disc reads as an object sat on
   top of the picture, an outline reads as a control belonging to it. */
.vidcar-btn{
  display:grid;place-items:center;width:42px;height:42px;padding:0;
  border:1px solid rgba(255,255,255,.32);border-radius:50%;cursor:pointer;
  background:transparent;color:#fff;
  transition:border-color var(--hover) var(--ease-soft),
             background-color var(--hover) var(--ease-soft),
             opacity var(--hover) var(--ease-soft);
}
.vidcar-btn svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.5}
.vidcar-btn:hover{border-color:rgba(255,255,255,.85);background:rgba(255,255,255,.08)}
.vidcar-btn:disabled{opacity:.28;cursor:default}
.vidcar-btn:disabled:hover{border-color:rgba(255,255,255,.32);background:transparent}

/* ── the repetitive-work rail ──────────────────────────────────────────────
   Cards on a scroll container with snap points. The rail bleeds to the right
   edge of the screen so the row is visibly cut rather than tidily ending,
   which is what says there is more of it. */

/* Once the chain is drawn it starts running. A dot walks each link in turn,
   which is the one thing a static diagram cannot say: this is not a picture
   of a workflow, it is a workflow that is currently on. The cycle is long and
   the dot is small on purpose, so it reads as a heartbeat, not a progress bar. */

/* ── the belt ──────────────────────────────────────────────────────────────
   Campaign volume is an argument about quantity, so the work makes it: one
   unbroken belt of frames running on its own. Same construction as the
   reviews, and it takes the same coast-to-a-stop on hover. */
/* the belt runs the full width of the window, so the chapter drops the 1920
   cap it inherits from .chap: capped, a screen wider than that leaves a white
   band at either end and the belt stops reading as continuous. The head keeps
   its gutter through --edge, which computes the same inset a capped section
   would have had, so nothing else moves. */

.ad-rail{overflow:hidden}
/* the belt got half as long again when the frames grew, so the duration grew
   with it: the argument is the quantity, not the speed. */
.ad-track{display:flex;width:max-content;animation:adroll var(--beltdur,81s) linear infinite}
.ad-group{display:flex;gap:16px;padding-right:16px;flex:0 0 auto}
@keyframes adroll{to{transform:translateX(-50%)}}
.ad-cell{
  flex:0 0 auto;width:450px;aspect-ratio:4/5;
  border-radius:var(--r-panel);overflow:hidden;background:var(--ph,#f2efe9);
}
.ad-cell img{display:block;width:100%;height:100%;object-fit:cover}
/* a belt of vertical frames. The cell takes the ratio the work was cut in,
   so a series made for a phone runs past as phone frames rather than being
   cropped into the 4:5 a campaign belt wants. Narrower cells mean more of
   them on screen at once, which is the argument on that page anyway. */

@media(max-width:1199px){
  :root{--edge:max(40px, calc((100% - 1800px) / 2))}
  .chap{padding:112px 40px}

  .ad-cell{width:360px}
  .ad-track{animation-duration:66s}
}
@media(max-width:860px){
  /* the picture leads on a narrow screen whichever side it took on a wide
     one: a flipped column would put it after copy written to sit beside it */

  .chap-head p{max-width:none}
}
@media(max-width:620px){
  :root{--edge:max(20px, calc((100% - 1800px) / 2))}
  .chap{padding:84px 20px}

  .chap-head p{font-size:17px;margin-top:22px}

  .ad-cell{width:min(66vw,300px)}
  .ad-track{animation-duration:54s}
}

/* ══════════════════════════════════════════════════════════ project pages
   The hero is a template and does not change from one project to the next:
   the copy and the meta on the left, one product frame on the right. What
   comes under it is written per project. Everything here is shared, so a
   second project page is a new document and no new CSS. */

/* the heading belongs to the frame under it, not to the page margin: same
   1600 cap, centred on the same axis, so the title sits over the middle of
   the film rather than out at the left edge of a section it is not part of */

.film-frame{
  position:relative;max-width:1600px;margin:0 auto;
  aspect-ratio:16/9;border-radius:var(--r-panel);overflow:hidden;background:#0b0b0b;
}
.film-frame video{display:block;width:100%;height:100%;object-fit:cover}

/* ── a Vimeo film ──────────────────────────────────────────────────────────
   The frame is whatever it is put in (.film-frame, .chap-frame, a carousel
   slide). This is only the poster, the button over it and the player that
   replaces them. */
/* a carousel slide is already positioned, and absolutely: leave it alone */
.vimeo:not(.vidcar-slide){position:relative}
.vimeo-play,.vimeo iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.vimeo-play{display:block;cursor:pointer;background:#0b0b0b}
.vimeo-play img{width:100%;height:100%;object-fit:cover;transition:opacity var(--hover) var(--ease-soft)}
.vimeo-play:hover img{opacity:.82}
.vimeo-disc{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  display:grid;place-items:center;width:96px;height:96px;border-radius:50%;
  color:#fff;background:rgba(0,0,0,.34);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.4);
  backdrop-filter:blur(14px) saturate(1.2);-webkit-backdrop-filter:blur(14px) saturate(1.2);
  transition:background-color var(--hover) var(--ease-soft);
}
.vimeo-play:hover .vimeo-disc{background:rgba(255,255,255,.18)}
/* the triangle carries a stroke in its own colour with round joins: that is
   what rounds its corners, a filled path alone has none to round */
/* no nudge: the path is drawn with its centroid on the middle of the viewBox,
   which is where a triangle looks centred, so the box is simply centred */
.vimeo-disc svg{display:block;width:48px;height:48px}
/* stacked carousel slides: only the one on stage takes the pointer, or the
   slide on top of the pile would swallow the click meant for the film */

@media(max-width:620px){.vimeo-disc{width:68px;height:68px}.vimeo-disc svg{width:34px;height:34px}}

/* the home page's film chapter: the head sits over the middle of the frame,
   the frame is as wide as a film is allowed to get */
.chap--film .chap-head{margin-bottom:64px}
.chap--film .chap-head p{max-width:640px}
.chap--film .film-frame{max-width:1440px}
/* the jury under the film. Four small cards on the frame's measure: a face,
   the name in gold between laurels, and a credit whose first sentence is the
   claim (white) and the rest the evidence (stepped back). One line over them
   says what they are. */
/* narrower than the film on purpose and centred under it: flush with the
   frame's edges the row read as a caption bar bolted to the video */
.jury{max-width:1180px;margin:32px auto 0;padding:0 24px}
.jury-label{font-size:14px;line-height:1.4;color:rgba(255,255,255,.5);text-align:center}
.jury-row{list-style:none;margin:40px 0 0;padding:0;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:24px 36px}
/* the face is the card's left column, the writing takes the rest */
.jury-card{
  display:flex;align-items:center;gap:12px;
}

.jury-copy{min-width:0}
.jury-name{display:flex;align-items:center;gap:5px;margin:0}
.jury-name span{
  font-size:12px;line-height:1.15;font-weight:500;text-transform:uppercase;letter-spacing:.03em;
  background:linear-gradient(180deg,#f3dc9a,#c9a04a);-webkit-background-clip:text;background-clip:text;color:transparent;
}
.laurel{flex:0 0 auto;width:7px;height:14px;fill:#d2ab57;color:#d2ab57}
.laurel:last-child{transform:scaleX(-1)}
.jury-bio{margin:5px 0 0;font-size:12px;line-height:1.35;color:rgba(255,255,255,.46)}
.jury-bio strong{font-weight:500;color:#fff}
@media(max-width:1199px){.jury-row{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}}
@media(max-width:620px){.jury{padding:0}.jury-row{grid-template-columns:1fr;gap:18px}}

/* the film runs silent so the page is alive without ambushing anyone, and
   the sound is one button away rather than behind a full control bar */
.film-sound{
  position:absolute;right:16px;bottom:16px;z-index:2;
  display:inline-flex;align-items:center;gap:8px;
  padding:9px 16px;border:1px solid rgba(255,255,255,.34);border-radius:var(--r-pill);
  background:rgba(0,0,0,.32);color:#fff;cursor:pointer;
  font-size:15px;line-height:1.2;
  transition:border-color var(--hover) var(--ease-soft),background-color var(--hover) var(--ease-soft);
}
.film-sound:hover{border-color:rgba(255,255,255,.85);background:rgba(0,0,0,.5)}
.film-sound svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}

/* ── picture first, argument after ─────────────────────────────────────────
   On a project page the work has to speak before the studio does, so every
   block below leads with frames and puts the writing under them. */

.pj-strip{display:grid;gap:16px}
.pj-strip--2{grid-template-columns:repeat(2,minmax(0,1fr))}
.pj-strip--3{grid-template-columns:repeat(3,minmax(0,1fr))}
.pj-strip--4{grid-template-columns:repeat(4,minmax(0,1fr))}
/* a strip of vertical frames stays three across at every width. Falling to
   two and letting the odd one out run full width, which is what the 4:5 strip
   does, would turn a phone frame into a wall. */

.pj-frame{
  aspect-ratio:var(--ar,4/5);border-radius:var(--r-panel);overflow:hidden;
  background:var(--ph,#eef1f6);
}
/* no hover on these. A frame on a project page leads nowhere, and a picture
   that moves under the pointer says it can be clicked. The zoom stays on the
   work cards, which are links. */
.pj-frame img{display:block;width:100%;height:100%;object-fit:cover}
/* parallax, opt-in per frame with data-parallax. The script writes the
   transform; will-change keeps the drift on the compositor. */
.pj-frame[data-parallax] img{will-change:transform}

/* ── the count ─────────────────────────────────────────────────────────────
   Numbers that can be checked against the page they sit on: the frames are
   countable, the flavours are countable, and the shoot days are the argument.
   Nothing here is a market claim, because a market claim is the one a client
   asks you to source.
   No frame around them: a hairline box turns four facts into a widget, and
   the numbers are large enough to hold the row on their own. */
.pj-stats{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  /* the row sits well clear of the head above it: centred, the title has no
     left edge to hand the eye down, so the space is what separates them */
  gap:44px 32px;margin-top:112px;
}
.pj-stat{text-align:center;padding:0 8px}
.pj-stat dt{
  font-size:clamp(44px,4.6vw,68px);line-height:1;letter-spacing:-.03125em;
  font-weight:500;color:#fff;
  /* the figure counts up, so the digits have to be one width or the number
     shifts under itself on the way there */
  font-variant-numeric:tabular-nums;
}
/* the count arrives the way the frames do: out of focus and from below, one
   figure after the next. --ld is the same stagger the script writes on any
   .reveal--lines block, so the order follows the markup. */
.pj-stats.reveal--lines>*{
  transform:translateY(34px);filter:blur(12px);
  transition:opacity .8s var(--ease-entry) var(--ld,0ms),
             transform 1s var(--ease-entry) var(--ld,0ms),
             filter .85s var(--ease-entry) var(--ld,0ms);
}
.pj-stats.reveal--lines.lit>*{filter:none}
.pj-stat dd{margin:16px 0 0;font-size:16px;line-height:1.4;color:rgba(255,255,255,.6)}

/* ── the rail, on white ────────────────────────────────────────────────────
   Same component as the one on /ai-integration, four colours different. */

.pj-more{padding-top:132px}
/* the CTA sits on the title's line, not the eyebrow's: the head is a block of
   two lines, so it is aligned by its foot rather than by its first baseline */
.pj-more .works-head{align-items:flex-end;margin-bottom:40px}
.pj-more .works-head h2{margin-top:6px}

@media(max-width:1199px){
  .pj-stats{margin-top:88px;gap:40px 24px}
}
@media(max-width:960px){
  /* the frame leads on a narrow screen: a hero that opens on a column of
     type is every other page on the site, and this one is about the work */
  /* stacked, the hero is taller than a screen by definition, so it stops
     trying to be one */
  /* stacked it is no longer centred in a screen, so the plate needs a foot
     of its own instead of leaning on the flex box for one */

  .pj-stats{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:72px;gap:44px 24px}
  .pj-strip--4{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media(max-width:620px){
  .pj-strip--2{grid-template-columns:1fr}
  /* two up, and the odd one out takes the full width rather than being
     dropped or left as an orphan half */
  .pj-strip--3{grid-template-columns:repeat(2,minmax(0,1fr))}
  .pj-strip--3 .pj-frame:last-child{grid-column:span 2;aspect-ratio:16/10}

  .pj-more{padding-top:88px}
}

/* ══════════════════════════════════════════════════════ page transition
   A sub-page is arrived at by clicking something, so it should arrive rather
   than appear. Two halves that meet in the middle: the page being left fades
   down, the page being opened lifts a veil of the same colour.

   The veil is a pseudo-element on a plain animation, deliberately. Nothing
   about it waits on the script, so there is no version of this where a page
   is left sitting behind a cover that failed to lift. It is also inert the
   moment it exists: pointer-events are off for its whole life. */
body.subpage::before{
  content:"";position:fixed;inset:0;z-index:200;
  background:var(--bg);pointer-events:none;
  animation:veil-lift .55s var(--ease-entry) both;
}
@keyframes veil-lift{from{opacity:1}to{opacity:0}}

/* The other half, worn for the ~260ms between the click and the next
   document. Both fixed bars are children of body, so they leave with it. */
body.leaving{opacity:0;transition:opacity .26s var(--ease-soft)}

/* ═══════════════════════════════════════════════════════════════ reveals
   One vocabulary for the whole page. Everything arrives from slightly below,
   nothing slides in from the side and nothing bounces; only the travel and
   the order change. .reveal is the hook the observer watches, and the two
   modifiers below swap what actually moves, so a block can stagger its parts
   or wipe its image without a second observer. */
.reveal{opacity:0;transform:translateY(20px);transition:opacity .6s var(--ease-entry),transform .7s var(--ease-entry);transition-delay:var(--rd,0ms)}
.reveal.lit{opacity:1;transform:none}

/* the block holds still and its parts arrive one beat apart: eyebrow, then
   title, then the note under it. --ld is written by the script, so the order
   follows the markup and nothing has to be numbered by hand. */
.reveal--lines{opacity:1;transform:none;transition:none}
.reveal--lines>*{
  opacity:0;transform:translateY(24px);
  transition:opacity .6s var(--ease-entry) var(--ld,0ms),transform .75s var(--ease-entry) var(--ld,0ms);
}
.reveal--lines.lit>*{opacity:1;transform:none}

/* a card: the frame rises out of focus, the caption follows a beat later.
   The blur is what carries it. Travel and fade alone read as a panel being
   moved; letting the image resolve at the same time reads as it arriving.
   The caption takes a shorter throw and a lighter blur, so it settles first
   and the eye ends on the image. */
.reveal--card{opacity:1;transform:none;transition:none}
.reveal--card .work-media,.reveal--card .disc-visual,.reveal--card .pj-frame{
  opacity:0;transform:translateY(52px);filter:blur(16px);
  transition:opacity .9s var(--ease-entry) var(--rd,0ms),
             transform 1.15s var(--ease-entry) var(--rd,0ms),
             filter .95s var(--ease-entry) var(--rd,0ms);
}
.reveal--card.lit .work-media,.reveal--card.lit .disc-visual,.reveal--card.lit .pj-frame{opacity:1;transform:none;filter:none}
.reveal--card .work-foot,.reveal--card .disc-label,.reveal--card>p{
  opacity:0;transform:translateY(24px);filter:blur(7px);
  transition:opacity .75s var(--ease-entry) calc(var(--rd,0ms) + 180ms),
             transform .9s var(--ease-entry) calc(var(--rd,0ms) + 180ms),
             filter .75s var(--ease-entry) calc(var(--rd,0ms) + 180ms);
}
.reveal--card.lit .work-foot,.reveal--card.lit .disc-label,.reveal--card.lit>p{
  opacity:1;transform:none;filter:none;
}

/* the project film. A 16:9 plate is the largest object on the page, and the
   20px the plain .reveal travels is nothing across that much width: it read
   as already being there. So it takes the card's own vocabulary at the scale
   of the thing moving, and grows the last of the way in the way the carousel
   stage does. Rise, focus and scale on one curve, or the frame arrives twice. */
.film-frame.reveal{
  opacity:0;transform:translateY(72px) scale(.955);filter:blur(18px);
  transition:opacity 1s var(--ease-entry),
             transform 1.35s var(--ease-entry),
             filter 1.1s var(--ease-entry);
}
.film-frame.reveal.lit{opacity:1;transform:none;filter:none}

/* ══════════════════════════════════════════════════════════════ responsive */
@media(max-width:1199px){
  .hero{padding-left:40px;padding-right:40px}
  .navbar{padding-left:40px;padding-right:40px}
  .section,.trusted,.closing,footer{padding-left:40px;padding-right:40px}
}
@media(max-width:960px){
  .closing-panel{grid-template-columns:1fr;gap:44px}
  .footer-main{grid-template-columns:repeat(2,minmax(0,1fr));gap:48px}
  .footer-main>.footer-brand{grid-column:1 / -1}
  .closing-panel{padding:44px 0}
}
@media(max-width:767px){
  .navbar{padding:16px 20px}
  .navcap{top:10px;padding:12px 12px 12px 22px;gap:16px;border-radius:999px}
  /* The row itself is gone on a phone and the menu is .nav-menu, a panel that
     lives on the body. It has to live there: both bars carry a transform or a
     backdrop-filter, and either one turns the bar into a containing block, so
     a position:fixed panel nested inside would size itself to the bar rather
     than to the screen. */
  .nav-links{display:none}
  .nav-action{display:none}
  .nav-burger{display:block;z-index:80}
  .brand-nav{position:static;transform:none}
  .nav-menu{display:flex}

  /* the panel is cream whatever plate the bar was on, so the ink comes back */
  /* the burger sits inside a bar, and the bar's own z-index is the one that
     counts against the panel, so the bars rise above it while it is open */
  body.menu-open .navbar,body.menu-open .navcap{z-index:80}
  body.menu-open .nav-burger .menu{display:none}
  body.menu-open .nav-burger .x{display:block}
  /* .navbar has to be in these selectors: without it they lose to
     .navbar.on-dark and the wordmark stays white on the cream panel */
  body.menu-open .navbar .brand{color:var(--ink-strong)}
  body.menu-open .navbar .nav-burger svg{stroke:var(--ink-strong)}
  body.menu-open .navcap{background:transparent;box-shadow:none;backdrop-filter:none;-webkit-backdrop-filter:none}

  .hero{min-height:min(100svh,760px);padding:64px 20px}
  .hero:first-of-type{padding-top:calc(var(--nav-h) + 64px)}
  .hero h1{letter-spacing:-.03em}
  .hero-copy{gap:22px}
  .hero-copy p{font-size:16px}
  /* the bar is authored for a wide frame; on a phone it needs to stay a bar */

  .section{padding:64px 20px}
  .trusted{padding:44px 20px}
  .closing,footer{padding-left:20px;padding-right:20px}

  .wheel-zone{margin-bottom:150px}
  .marquee{-webkit-mask-image:linear-gradient(90deg,transparent,#000 50px,#000 calc(100% - 50px),transparent);mask-image:linear-gradient(90deg,transparent,#000 50px,#000 calc(100% - 50px),transparent)}
  .marquee span{font-size:20px}

  .closing-content>p{font-size:19px}
  .footer-main{grid-template-columns:1fr;gap:36px}
  .footer-bottom{flex-direction:column;align-items:flex-start;gap:22px;margin-top:44px}
}

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;transition-delay:0ms!important}
  .hero-copy>*,.hero h1 .hw,.reveal,.reveal--lines>*,
  /* carries its own transform and blur at a higher specificity than the
     plain block above, so it has to be named here to be undone */
  .pj-stats.reveal--lines>*,.film-frame.reveal,.reveal--card .work-media,.reveal--card .disc-visual,.reveal--card .pj-frame,.reveal--card .work-foot,.reveal--card .disc-label,.reveal--card>p,.page-head h1,.page-head__note{opacity:1;transform:none;filter:none}

  /* the blanket rule above cuts every animation to nothing, which would leave
     the veil painted at its first frame. Removing it is the only safe end. */
  body.subpage::before{display:none}
  /* the fan is composition, not motion: flattening it to transform:none would
     stack the three frames on top of each other */

  .marquee-track,.ad-track{transform:none;animation:none}
}

/* the "New" badge on a work card. A white pill with a wheel of pearl greys
   turning slowly behind the word, so it catches light like brushed metal
   rather than flashing colour. The stroke is on ::after because the wheel
   would paint over an inset shadow on the pill itself. */
.badge-new{
  position:absolute;top:14px;left:14px;z-index:2;isolation:isolate;overflow:hidden;
  display:inline-flex;align-items:center;gap:6px;
  padding:6px 13px 6px 10px;border-radius:var(--r-pill);
  font-size:13px;line-height:1.3;color:#0b0b0b;background:#fff;
  box-shadow:0 2px 10px rgba(0,0,0,.14);
}
.badge-new svg{width:12px;height:12px}
.badge-new::before{
  content:"";position:absolute;z-index:-1;left:50%;top:50%;width:170%;aspect-ratio:1;translate:-50% -50%;
  background:conic-gradient(from 0deg,#fff,#c9ccd3,#fff,#e4e6ea,#fff,#bfc3cb,#fff);
  filter:blur(6px);animation:badge-drift 7s linear infinite;
}
.badge-new::after{content:"";position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 0 0 1px rgba(255,255,255,.9)}
@keyframes badge-drift{to{rotate:360deg}}
@media(prefers-reduced-motion:reduce){.badge-new::before{animation:none}}

/* the short film chapter's tag wears the card badge's dress: white pill,
   pearl wheel turning behind, capitals */
.badge-new{text-transform:uppercase;letter-spacing:.04em;font-size:12px}

/* the festival's own mark over the short film's title, in place of a tag */
.film-mark{display:block;width:auto;height:62px;margin:0 auto 28px}
@media(max-width:620px){.film-mark{height:50px;margin-bottom:22px}}

/* the film's first frame, a second unplayed copy of the film lying over it. Lifted at once
   when the film starts, faded back in when the pointer leaves. */
.work-freeze{opacity:0;pointer-events:none;transition:opacity 0s}
.work-freeze.on{opacity:1;transition:opacity .7s var(--ease-soft)}
/* a card whose cover is a film does not zoom on hover: the film playing is
   the hover */
.work-card:hover video.work-src{transform:none}
/* the film overshoots its rounded frame by a pixel: flush with it, the plate
   behind showed through the corner's anti-aliasing as a thin dark line */
.work-media video.work-src,.work-media .work-freeze{inset:-1px;width:calc(100% + 2px);height:calc(100% + 2px)}

/* a belt of 16:9 frames, each with its name under it: the Déjà Vu cast and
   places. The figure is the belt's unit, the plate inside it keeps the
   frame. */
.ad-rail--wide .ad-cell{width:840px;aspect-ratio:16/9}
/* a second belt under the first runs the other way, so the two read as two */
.ad-track--rev{animation-direction:reverse}
.ad-fig{flex:0 0 auto;margin:0}
.ad-fig figcaption{margin-top:14px;font-size:16px;line-height:1.35;color:var(--chap-soft)}
@media(max-width:1199px){.ad-rail--wide .ad-cell{width:620px}}
@media(max-width:620px){.ad-rail--wide .ad-cell{width:min(78vw,340px)}.ad-fig figcaption{font-size:15px;margin-top:12px}}

/* the wide belts keep their own pace at every width: the general belt rules
   shorten the duration on smaller screens, which would speed these up */
.ad-rail--wide .ad-track{animation-duration:var(--beltdur,110s)}

/* ── the stack ─────────────────────────────────────────────────────────────
   A project told in pictures: rows of frames one under the next on a single
   gap, with no writing between them. */
.pj-stack{display:grid;gap:32px;padding-top:32px}
.pj-stack .pj-strip{gap:32px}
@media(max-width:620px){.pj-stack,.pj-stack .pj-strip{gap:16px}.pj-stack{padding-top:16px}}

/* ── a page that is dark from top to bottom ────────────────────────────────
   One class on the body turns the tokens over, so every block that reads its
   colours from them follows without being restyled. What is listed after is
   only what had a colour written in by hand. Each section on such a page
   also carries data-tone="dark", which is what the navbar reads its ink from. */
body.page-dark{
  --bg:#000;--ink:#fff;--ink-strong:#fff;--ink-body:rgba(255,255,255,.8);
  --ink-soft:rgba(255,255,255,.66);--ink-muted:rgba(255,255,255,.5);
  --ink-faded:rgba(255,255,255,.6);--surface:#161616;--hairline:rgba(255,255,255,.14);
}
.page-dark main .button-primary{background:#fff;color:#000}
.page-dark main .button-primary:hover{background:#e6e6e6}
.page-dark main .button-secondary:hover{background:#222}
.page-dark .cform .select-dropdown{background:#161616;box-shadow:0 14px 40px rgba(0,0,0,.5)}
.page-dark .cform .select-option:hover{background:#242424}
.page-dark .cform input:-webkit-autofill,
.page-dark .cform input:-webkit-autofill:hover,
.page-dark .cform input:-webkit-autofill:focus{-webkit-box-shadow:0 0 0 1000px #161616 inset;-webkit-text-fill-color:#fff}

/* ── Rolly: statements between the frames, the mark, the palette ─────────── */

.pj-stats--3{grid-template-columns:repeat(3,minmax(0,1fr));margin-top:40px}

.pj-swatch{display:flex;flex-direction:column;justify-content:flex-end;gap:6px;aspect-ratio:4/3;
  padding:24px;border-radius:var(--r-panel);background:var(--sw);color:#fff;
  box-shadow:inset 0 0 0 1px var(--hairline);font-size:18px}
.pj-swatch strong{font-weight:500}
.pj-swatch span{font-size:14px;opacity:.7}
.pj-swatch--light{color:#000}
.pj-strip--3.pj-trio .pj-frame:last-child{grid-column:auto;aspect-ratio:var(--ar)}
@media(max-width:960px){.pj-stats--3{grid-template-columns:1fr}}

/* ── Déjà Vu: belt arrows, the second caption line ───────────────────────── */
.belt-nav{display:flex;justify-content:center;gap:8px;margin-top:36px}
.ad-sub{display:block;margin-top:2px;font-size:14px;color:var(--chap-faint)}

/* the ring goes round, slowly: the angle is what turns, not the ring, so
   every frame stays upright as it travels */
@property --spin{syntax:"<angle>";inherits:true;initial-value:0deg}

.props-copy{position:relative;max-width:min(600px,calc(var(--Rx)*2 - var(--s)*2 - 96px));text-align:center}
.props-copy p{margin-top:22px;font-size:17px;line-height:1.5;color:var(--chap-soft);text-wrap:balance}
@media(max-width:820px){
  .props-copy{max-width:none;position:static}
  .props-copy p{position:absolute;left:0;right:0;top:100%;margin:28px auto 0;max-width:440px;font-size:16px}
}

/* a centred head that is given the width: title on one line, a longer measure */

.pj-strip--6{grid-template-columns:repeat(6,minmax(0,1fr))}
@media(max-width:960px){
  .pj-strip--6{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media(max-width:620px){.pj-strip--6{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* ── Rolly: the palette as a loose pile of bars ───────────────────────────
   Four bars, one under the other, every other one tipped. --p runs from -1
   to 1 as the pile crosses the screen (written by the script on any
   [data-scrollp]): each bar turns and slides a little with it, by its own
   amount, so the pile shifts as it goes by. */
.pj-palette{--p:0;--mx:0;--my:0;display:grid;gap:38px;width:min(100%,580px);margin:0 auto;padding:120px 0 96px}
.pj-palette .pj-swatch{
  aspect-ratio:auto;justify-content:center;gap:8px;padding:28px 32px;border-radius:20px;font-size:clamp(22px,2vw,29px);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.36);
  /* scroll turns and slides the bar, the pointer pushes it: --m is how far */
  transform:translate(calc(var(--mx)*var(--m,0px)),calc(var(--p)*var(--d,0px) + var(--my)*var(--m,0px)*.6))
            rotate(calc(var(--r,0deg) + var(--p)*var(--dr,0deg) + var(--mx)*var(--mr,0deg)));
  will-change:transform;
}
.pj-palette .pj-swatch--light{box-shadow:none}
.pj-palette .pj-swatch span{font-size:clamp(15px,1.2vw,18px);opacity:.85}
.pj-palette .pj-swatch:nth-child(1){--r:-6deg;--dr:3deg;--d:-26px;--m:26px;--mr:1.2deg}
.pj-palette .pj-swatch:nth-child(2){--r:1deg;--dr:-2deg;--d:10px;--m:-14px;--mr:-.8deg;z-index:1}
.pj-palette .pj-swatch:nth-child(3){--r:-6deg;--dr:2.5deg;--d:-14px;--m:18px;--mr:1deg}
.pj-palette .pj-swatch:nth-child(4){--r:1deg;--dr:-1.5deg;--d:22px;--m:-22px;--mr:-.6deg;z-index:1}
@media(max-width:620px){.pj-palette{padding:72px 0 56px}.pj-palette .pj-swatch{padding:24px 26px;border-radius:18px}}
/* a frame that runs to both edges of the screen, square */
.pj-bleed{width:100vw;margin-left:calc(50% - 50vw)}
.pj-bleed .pj-frame{border-radius:0}

/* ── the project page, opened on its name ─────────────────────────────────
   The name runs very large across the top with its descriptor in a quieter
   ink, the project's picture sits under it at the full measure, and the
   facts and the introduction share the next row. Then the work: big frames
   close together, and a ruled two-column row wherever something is said. */
.pj-open{max-width:1920px;margin:0 auto;padding:calc(var(--nav-h) + 96px) 60px 0}
.pj-open-title{
  margin-bottom:44px;font-size:clamp(52px,8.6vw,150px);line-height:.96;
  font-weight:500;letter-spacing:-.045em;color:var(--ink);text-wrap:balance;
}
.pj-open-title span{color:var(--ink-muted)}
.pj-info{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:32px;
  padding:36px 0 0;
}
.pj-facts{margin:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;align-content:start}
.pj-facts dt{font-size:14px;line-height:1.4;color:var(--ink-muted)}
.pj-facts dd{margin:6px 0 0;font-size:16px;line-height:1.4;color:var(--ink)}
.pj-intro{font-size:clamp(22px,2.1vw,32px);line-height:1.25;letter-spacing:-.02em;color:var(--ink)}
.pj-big{gap:16px;padding-top:120px}
.pj-big .pj-strip{gap:16px}
.pj-row{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:32px;
  margin-top:104px;padding:28px 0 56px;border-top:1px solid var(--hairline);
}
.pj-row:first-child{margin-top:0}
.pj-row h2{font-size:18px;line-height:1.4;letter-spacing:0;font-weight:500;color:var(--ink-muted)}
.pj-row p{font-size:clamp(22px,2.1vw,32px);line-height:1.25;letter-spacing:-.02em;color:var(--ink)}
.pj-stats--row{margin:clamp(56px,6vw,96px) 0 72px}
.pj-big .pj-palette{padding:104px 0 88px}
@media(max-width:1199px){.pj-open{padding-left:40px;padding-right:40px}}
@media(max-width:960px){
  .pj-info,.pj-row{grid-template-columns:1fr;gap:20px}
  .pj-row{margin-top:72px;padding-bottom:36px}
  .pj-big{padding-top:80px}
}
@media(max-width:620px){
  .pj-open{padding:calc(var(--nav-h) + 56px) 20px 0}
  .pj-open-title{margin-bottom:28px}
  .pj-facts{grid-template-columns:1fr;gap:16px}
  .pj-big,.pj-big .pj-strip{gap:10px}
}

/* on a dark page an empty slot is a dark plate: the light ones were drawn for
   the white pages and read as a hole of light on black. A slot with its
   picture in it is untouched. */
.page-dark .pj-frame:empty,.page-dark .ad-cell:empty{background:#17171d!important}

/* ── the theme switch ─────────────────────────────────────────────────────
   One dot. It is filled with the mode the page is in: black in dark mode,
   white in light mode, with a ring in the bar's ink. Hovering only brings
   the ring up to full strength; nothing grows. The cross-fade between the
   two modes is the view transition's, slowed. */
.theme-dot{
  width:12px;height:12px;padding:0;margin-right:22px;border-radius:50%;cursor:pointer;
  background:#000;border:1.5px solid #000;
  transition:border-color var(--hover) var(--ease-soft),background-color var(--base) var(--ease-out);
}
.theme-dot:hover{border-color:#000}
body.page-dark .theme-dot{background:#fff;border-color:#fff}
body.page-dark .theme-dot:hover{border-color:#fff}
::view-transition-old(root),::view-transition-new(root){animation-duration:.7s;animation-timing-function:cubic-bezier(.4,0,.2,1)}

/* light mode on the sections that are drawn in black: the whole section is
   inverted (black plate to white, white type to black, the hero film with
   it), the hue turned back so colours keep their name, and the pictures
   inside are inverted a second time so they come out as they were. */
/* Only the hero is still inverted: its film is a black and white dither, so
   flipping it is exact. Every other black section is redrawn in light colours
   below, so no picture, video or embed is ever touched by the theme. */
body.theme-light .hero[data-invert]{filter:invert(1) hue-rotate(180deg)}

/* the dark chapters (the short film, the bench) */
body.theme-light .chap--dark[data-invert]{
  --chap-ink:#111;--chap-soft:rgba(0,0,0,.62);--chap-faint:rgba(0,0,0,.5);--chap-line:rgba(0,0,0,.12);
  background:#fff;
}
body.theme-light [data-invert] .jury-label{color:rgba(0,0,0,.5)}
body.theme-light [data-invert] .jury-bio{color:rgba(0,0,0,.5)}
body.theme-light [data-invert] .jury-bio strong{color:#111}

/* the footer */
body.theme-light footer[data-invert]{background:#fff}
body.theme-light footer .brand,
body.theme-light .footer-col strong,
body.theme-light .footer-social a,
body.theme-light .footer-col a:hover,
body.theme-light .footer-bottom a:hover{color:#111}
body.theme-light .footer-address,
body.theme-light .footer-col a{color:rgba(0,0,0,.5)}
body.theme-light .footer-bottom{color:rgba(0,0,0,.45)}
/* and the backdrop behind the page follows, for the rubber-band past the footer */
html:has(body.theme-light){background:#fff}
/* the right flank is exactly as wide as the wordmark (161px), so the links
   between the two sit on the true centre of the bar. The button sits at the right end of that flank with
   the dot close beside it; the spare width stays empty on the left. */
.nav-action{align-items:center;width:161px}
.navcap .nav-action{margin-left:0}
.theme-dot{margin-right:22px}

/* ── the closing form, set forward ────────────────────────────────────────
   The panel becomes a card: a quiet hairline all round, and over it a short
   run of light that travels the edge (a conic gradient turning behind a
   mask that leaves only the 1px border). The same gradient, blurred, sits
   behind the card as a glow that moves with it, and two faint washes rise
   from the bottom corners inside. --ring is the angle; it is the only thing
   animated. */
@property --ring{syntax:"<angle>";inherits:true;initial-value:0deg}
@keyframes ringturn{to{--ring:360deg}}
.closing{isolation:isolate}
.closing-panel{
  --ring-grad:conic-gradient(from var(--ring),transparent 0 52%,#8fb8ff 62%,#c9a6ff 72%,#ffb48a 82%,#9ff0d8 90%,transparent 98%);
  position:relative;border-radius:36px;padding:72px 64px;
  background:
    radial-gradient(60% 55% at 0% 100%,rgba(255,170,140,.13),transparent 70%),
    radial-gradient(60% 55% at 100% 100%,rgba(120,220,200,.12),transparent 70%),
    var(--bg);
  box-shadow:inset 0 0 0 1px var(--hairline);
  animation:ringturn 9s linear infinite;
}
.closing-panel::before,.closing-panel::after{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:var(--ring-grad);
}
.closing-panel::before{
  padding:1.5px;
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
}
.closing-panel::after{z-index:-1;inset:-2px;filter:blur(34px);opacity:.38}
.closing-panel>*{position:relative}
@media(max-width:960px){.closing-panel{padding:48px 32px;border-radius:28px}}
@media(max-width:620px){.closing-panel{padding:36px 20px;border-radius:22px}}
@media(prefers-reduced-motion:reduce){.closing-panel{animation:none}}

/* ── the service titles, in the same moving light as the form's edge ──────
   The gradient is four times the title's width and ends on the colour it
   starts with, so sliding it one full tile (133.33% in position terms) loops
   without a seam. Each card starts a little further along, so the three do
   not pulse together. The icon cannot be clipped to a gradient, so it walks
   through the same colours on the same clock. On a white page the colours
   are deepened to stay readable. */
/* kept quiet: the title is near-white with two soft tints passing through
   it (a pale blue, a pale lilac), not a run of colours */
.disc-label{--g1:#f4f6ff;--g2:#bcd2ff;--g3:#f4f6ff;--g4:#d9c8ff}
body:not(.page-dark) .disc-label{--g1:#16181d;--g2:#3f5fb8;--g3:#16181d;--g4:#6f55b5}
.disc-label h3{
  background:linear-gradient(90deg,var(--g1),var(--g2),var(--g3),var(--g4),var(--g1)) 0 0/400% 100% repeat-x;
  -webkit-background-clip:text;background-clip:text;color:transparent;
  animation:titleflow 14s linear infinite;
}
.disc-label svg{animation:iconflow 14s linear infinite}
@keyframes titleflow{to{background-position:133.333% 0}}
@keyframes iconflow{0%,100%{color:var(--g1)}25%{color:var(--g4)}50%{color:var(--g3)}75%{color:var(--g2)}}
.disc-card:nth-child(2) .disc-label h3,.disc-card:nth-child(2) .disc-label svg{animation-delay:-4.6s}
.disc-card:nth-child(3) .disc-label h3,.disc-card:nth-child(3) .disc-label svg{animation-delay:-9.3s}
@media(prefers-reduced-motion:reduce){.disc-label h3,.disc-label svg{animation:none}}
/* a wider capsule: with more air on both sides of the links, the spare
   17px to the left of the dot stops reading as an uneven gap */
.navcap{gap:80px}

/* the badge's turning wheel takes the same two pale tints as the service
   titles (a pale blue, a pale lilac) in place of its greys: still a white
   pill, the colour only passes through it */
.badge-new::before{
  background:conic-gradient(from 0deg,#fff,#bcd2ff,#fff,#d9c8ff,#fff,#bcd2ff,#fff,#d9c8ff,#fff);
}

/* the badge again, without the wheel: a turning conic gradient shows its
   spokes on a shape this small. The tints now slide across in one direction,
   the way they do in the service titles, so nothing reads as rotating. */
.badge-new::before{
  left:0;top:0;width:100%;height:100%;aspect-ratio:auto;translate:none;rotate:none;filter:none;
  background:linear-gradient(100deg,#fff 0%,#dbe6ff 18%,#fff 36%,#e9defe 54%,#fff 72%,#dbe6ff 90%,#fff 108%) 0 0/360% 100% repeat-x;
  animation:badgeflow 10s linear infinite;
}
@keyframes badgeflow{to{background-position:138.46% 0}}
@media(prefers-reduced-motion:reduce){.badge-new::before{animation:none}}

/* the closing card, wider: the title sits on two lines, one sentence each */
.closing-panel{max-width:1320px;grid-template-columns:minmax(0,1fr) minmax(380px,1fr);gap:72px}
.closing-panel h2{max-width:none}
.closing-note{max-width:440px}
@media(max-width:960px){.closing-panel{grid-template-columns:1fr;gap:44px}}

/* the form card's left column: the title, then the direct lines a clear
   step under the description rather than dropped to the foot of the card */
.closing-lead{display:flex;flex-direction:column;justify-content:flex-start;gap:52px}
/* three rows, no rule: a quiet label on the left, the detail on the right */
.closing-direct{margin:0;display:grid;gap:28px}
.closing-direct>div{display:grid;grid-template-columns:84px minmax(0,1fr);align-items:baseline;gap:16px}
.closing-direct dt{font-size:14px;line-height:1.4;color:var(--ink-muted)}
.closing-direct dd{margin:0;font-size:17px;line-height:1.45;color:var(--ink)}
.closing-direct a{transition:opacity var(--hover) var(--ease-soft)}
.closing-direct a:hover{opacity:.65}
@media(max-width:620px){.closing-direct>div{grid-template-columns:1fr;gap:4px}}

/* air between the form card and the footer: the card has a glow round it,
   and the footer read as pressed up against it */
.closing{padding-bottom:140px}
@media(max-width:620px){.closing{padding-bottom:88px}}

/* the AI icon tiles keep their four colours and take the badge's movement:
   each tile's own tint slides across it, light to a shade deeper and back,
   in one direction. --t1 is the tile's pale ground, --t2 the deeper pass. */

/* a frame holding a pasted block instead of a picture: the block fills it */
.pj-frame--embed>.rc{width:100%;height:100%;max-width:none}

/* the palette inside a square frame, and a film in a frame */
.pj-frame--palette{display:grid;place-items:center;overflow:hidden}
.pj-frame--palette .pj-palette,.pj-big .pj-frame--palette .pj-palette{width:66%;padding:0;gap:clamp(18px,2.6vw,38px)}
.pj-frame--palette .pj-swatch{padding:clamp(14px,1.9vw,28px) clamp(16px,2.2vw,32px);font-size:clamp(16px,1.7vw,26px)}
.pj-frame--palette .pj-swatch span{font-size:clamp(12px,1.05vw,16px)}
.pj-frame video{display:block;width:100%;height:100%;object-fit:cover}

/* Screen boards: phone screenshots set on a coloured panel, one arrangement
   per board (trio, steps, crop, fan, solo). */
.pj-set{position:relative;display:flex;align-items:center;justify-content:center;gap:clamp(14px,3.4vw,64px)}
.pj-set .ph{flex:none;width:var(--w,19%);aspect-ratio:1170/2532;border-radius:clamp(10px,1.5vw,26px);overflow:hidden;
  box-shadow:0 30px 60px -30px rgba(0,0,0,.45),0 0 0 1px rgba(255,255,255,.08);will-change:transform}
.pj-set--trio .ph--lead{--w:22%}

.pj-set--crop{align-items:flex-start}
.pj-set--crop .ph{--w:64%;margin-top:11%;border-bottom-left-radius:0;border-bottom-right-radius:0}
.pj-set--fan{--p:0;gap:0}
.pj-set--fan .ph{--w:35%}
.pj-set--fan .ph:nth-child(1){transform:translate(8%,3%) rotate(calc(-7deg - var(--p)*3deg))}
.pj-set--fan .ph:nth-child(2){transform:translate(-8%,-3%) rotate(calc(7deg + var(--p)*3deg))}

@media(max-width:620px){
  .pj-set--trio{--ar:1!important;aspect-ratio:1;gap:10px}
  .pj-set--trio .ph{--w:27%}.pj-set--trio .ph--lead{--w:31%}
}

.pj-bleed + .pj-strip{margin-top:clamp(48px,7vw,112px)}
/* a phone that plays its flow: the screens sit stacked and slide through */
.pj-set--play .ph{--w:38%;position:relative}
.ph--seq img{position:absolute;inset:0;transform:translateX(100%)}
.ph--seq img.on,.ph--seq img.off{transition:transform .8s cubic-bezier(.7,0,.2,1)}
.ph--seq img.on{transform:none}
.ph--seq img.off{transform:translateX(-100%)}
.pj-set--fan3 .ph{--w:31%}
.pj-set--fan3 .ph:nth-child(1){transform:translate(22%,4%) rotate(calc(-9deg - var(--p)*3deg))}
.pj-set--fan3 .ph:nth-child(2){transform:none;z-index:1;--w:34%}
.pj-set--fan3 .ph:nth-child(3){transform:translate(-22%,4%) rotate(calc(9deg + var(--p)*3deg))}

.pj-frame--doc iframe{display:block;width:100%;height:100%;border:0;pointer-events:none}

/* "the Daltons": the left story climbs slowest, the right one fastest, so by
   the time the row leaves the screen the right one has overtaken the left.
   The drift uses `translate`, which is separate from the reveal's transform
   and its long transition, so it tracks the scroll without lag. */
.pj-drift,.pj-big .pj-drift{--p:0;gap:clamp(48px,8vw,150px);padding:clamp(80px,11vw,170px) clamp(0px,4vw,64px)}
.pj-drift .pj-frame{will-change:translate}
.pj-drift .pj-frame:nth-child(1){translate:0 calc(var(--p)*-50px)}
.pj-drift .pj-frame:nth-child(2){translate:0 calc(var(--p)*-130px)}
.pj-drift .pj-frame:nth-child(3){translate:0 calc(var(--p)*-210px)}
@media(max-width:620px){.pj-drift,.pj-big .pj-drift{grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;padding:80px 0}}

/* project pages show two other projects, the home keeps four */
@media(min-width:961px){.pj-more .work-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}

/* only the last two rows of dots at the hero's foot are dimmed, so the
   field does not stop on a hard line; everything above stays at full strength */
.hero-media{-webkit-mask-image:linear-gradient(#000 calc(100% - 64px),rgba(0,0,0,.2) 100%);mask-image:linear-gradient(#000 calc(100% - 64px),rgba(0,0,0,.2) 100%)}

/* the work page runs three to a row; the cards keep their 2:3 picture */
@media(min-width:961px){.page-work #work .work-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:24px 16px}}

/* an embedded block stays hidden until it has been sized to its frame, so
   the phone never shows at the block's own scale first */
.pj-frame--doc iframe{opacity:1}
.pj-frame--doc iframe.fit{opacity:1}

/* a one-line title: one colour, a size down from the two-tone title */
.pj-open-title--line{font-size:clamp(38px,5.2vw,88px);line-height:1.02;letter-spacing:-.04em;max-width:14em}

/* chatbot: just the orb and the conversation over it */

/* accounting: the receipt comes in, gets its category, gets filed */

/* scheduling: a wide week, the new call drops into the gap and stays */

/* follow-up: three notifications slide in one after the other */

/* documents: a page from the template, its fields filled once */

/* data: the question, then the bars rise once */

/* automation: one workflow, the tools around it, lines drawn once */

/* Genesis: the film as the opening picture, a CTA under a row's sentence,
   and a board holding two empty 9:16 slots */
.pj-open .film-frame{max-width:none}
.pj-row>div>p{font-size:clamp(22px,2.1vw,32px);line-height:1.25;letter-spacing:-.02em;color:var(--ink)}

.pj-stack .ad-rail{margin:0 calc(50% - 50vw)}
/* the three facts share the width by the space between them, not by equal
   columns, so a short value does not leave a hole beside it */
.pj-facts{grid-template-columns:repeat(3,auto);justify-content:space-between;column-gap:40px}
.pj-facts dd{max-width:20em}
@media(max-width:620px){.pj-facts{grid-template-columns:1fr;justify-content:stretch}}

/* an image with a two-dot switch on it (Chapplet logo on white / on blue) */
.pj-swap{position:relative}
.pj-swap>img{position:absolute;inset:0;opacity:0;transition:opacity .6s ease}
.pj-swap>img.on{opacity:1}
.pj-swap-dots{position:absolute;right:clamp(14px,2vw,28px);bottom:clamp(14px,2vw,28px);display:flex;gap:10px;z-index:1}
.pj-swap-dot{all:unset;cursor:pointer;width:15px;height:15px;border-radius:50%;box-sizing:border-box;transition:box-shadow .25s}
.pj-swap-dot--white{background:#fff;box-shadow:0 0 0 1px rgba(0,0,0,.18)}

.pj-swap-dot.on{box-shadow:0 0 0 2px rgba(255,255,255,.9),0 0 0 3.5px #009dff}
.pj-swap-dot--white.on{box-shadow:0 0 0 2px #fff,0 0 0 3.5px #009dff}
.pj-swap-dot:focus-visible{outline:2px solid #009dff;outline-offset:3px}
.pj-frame--doc:not(:has([data-phone])) iframe{opacity:1}

/* the facts and the introduction need air between them, side by side or stacked */
/* the intro and every row's sentence start on the same line: both grids
   share the same two columns and the same gap */
:root{--pj-gap:clamp(48px,7vw,140px)}
.pj-info,.pj-row{column-gap:var(--pj-gap)}
.pj-info{row-gap:44px}
/* Genesis: the drifting trio keeps the full width of the trio below it */
.pj-big .pj-drift--full{gap:16px;padding-left:0;padding-right:0}

/* a small label over a frame, naming what the block shows */
.pj-frame:has(>.pj-tag){position:relative}
.pj-tag{position:absolute;left:clamp(14px,1.6vw,24px);top:clamp(14px,1.6vw,24px);z-index:2;padding:7px 13px;border-radius:99px;
  font-size:13px;line-height:1;color:#fff;background:rgba(12,12,12,.55);
  -webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.14);pointer-events:none}

/* switch dots, centred under the logo; each dot is its own colour (--c),
   the chosen one gets a ring in the frame's accent (--ring) */
.pj-swap-dots{left:50%;right:auto;translate:-50% 0}
.pj-swap-dot{background:var(--c,#fff)!important;box-shadow:0 0 0 1px rgba(127,127,127,.45)!important}
.pj-swap-dot.on{box-shadow:0 0 0 2px var(--c,#fff),0 0 0 3.5px var(--ring,#fff)!important}
.pj-swap-dot{box-shadow:0 0 0 1px rgba(0,0,0,.28),0 0 0 2px rgba(255,255,255,.35)!important}
.pj-swap-dot.on{box-shadow:0 0 0 2px var(--c,#fff),0 0 0 3.5px var(--ring,#fff),0 0 0 4.5px rgba(0,0,0,.3)!important}
/* less air between the last block of a project and "Other projects" */
.pj-big .pj-drift{padding-bottom:24px}
.pj-big#work{padding-bottom:32px}
.pj-more{padding-top:96px}
.pj-swap-dot--dark:not(.on){box-shadow:inset 0 0 0 1.5px #fff!important}
.pj-swap-dot--dark.on{box-shadow:inset 0 0 0 1.5px #fff,0 0 0 2px var(--c),0 0 0 3.5px #fff!important}
.pj-swap-dot--lime:not(.on){box-shadow:inset 0 0 0 1.5px #0c0c0c,0 0 0 1px rgba(255,255,255,.35)!important}
.pj-swap-dot--lime.on{box-shadow:inset 0 0 0 1.5px #0c0c0c,0 0 0 2px var(--c),0 0 0 3.5px #0c0c0c!important}
/* no selection ring: the chosen dot is simply the brighter, fuller one */
.pj-swap-dot,.pj-swap-dot.on{transition:opacity .3s ease,scale .3s ease}
.pj-swap-dot{opacity:.4;scale:.86}
.pj-swap-dot.on{opacity:1;scale:1}
.pj-swap-dot:hover{opacity:.8}
.pj-swap-dot.on,.pj-swap-dot:not(.on){box-shadow:0 0 0 1px rgba(127,127,127,.5)!important}
.pj-swap-dot--dark.on,.pj-swap-dot--dark:not(.on){box-shadow:inset 0 0 0 1.5px #fff!important}
.pj-swap-dot--lime.on,.pj-swap-dot--lime:not(.on){box-shadow:inset 0 0 0 1.5px #0c0c0c!important}
.pj-swap-dot--white.on,.pj-swap-dot--white:not(.on){box-shadow:inset 0 0 0 1.5px rgba(0,0,0,.25)!important}

/* Chapplet opening: the app's screens in one row, edge to edge */
.pj-lineup{--p:0;width:100vw;margin-left:calc(50% - 50vw);overflow:hidden;padding:clamp(24px,3vw,48px) 0}
.pj-lineup-row{display:flex;justify-content:center;align-items:center;gap:clamp(12px,1.8vw,32px);translate:calc(var(--p)*-3vw) 0;will-change:translate}
.pj-lineup .ph{flex:none;width:clamp(150px,17vw,330px);aspect-ratio:1170/2532;border-radius:clamp(14px,1.9vw,36px);overflow:hidden;
  box-shadow:0 30px 60px -30px rgba(0,0,0,.8),0 0 0 1px rgba(255,255,255,.06)}
.pj-lineup .ph--lead{width:clamp(180px,21vw,410px)}
.pj-lineup .ph img{display:block;width:100%;height:100%;object-fit:cover}
.pj-lineup .ph{border-radius:clamp(10px,1.15vw,22px)}
.pj-lineup .ph--lead{border-radius:clamp(12px,1.4vw,27px)}
/* same corner as the phones on the other boards of the page */
.pj-lineup .ph,.pj-lineup .ph--lead{border-radius:clamp(10px,1.5vw,26px)}
/* stats follow the page ink, so they read in light mode too */
.pj-stat dt{color:var(--ink)}
.pj-stat dd{color:var(--ink-muted)}

/* a mosaic on no ground of its own (Chapplet components): above and below
   it, the same air as between its own tiles, about 4.7% of its width */
.pj-strip--air{padding:calc(4.7% - 16px) 0}

/* a laptop with a live page in its screen (Chapplet landing page). The
   picture's screen is see-through; the page sits under it in a box cut to
   that hole (measured on the 1727x1130 picture) and stays scrollable, the
   picture letting the pointer through. */
.pj-mac-frame{display:grid;place-items:center}
.pj-mac{position:relative;width:86%;aspect-ratio:1727/1130}
.pj-frame .pj-mac img{position:relative;z-index:1;width:100%;height:100%;object-fit:contain;pointer-events:none}
.pj-mac-screen{position:absolute;z-index:0;left:14.48%;top:4.78%;width:70.64%;height:67.52%;overflow:hidden;background:#fff}
.pj-mac-screen iframe{position:absolute;left:0;top:0;width:1440px;border:0;transform-origin:0 0}
/* the page in the screen is moved only by the two arrows: the wheel goes on
   scrolling the case study, so there is never a scroll inside a scroll */
.pj-mac-screen iframe{pointer-events:none}
.pj-mac-frame{position:relative}
.pj-mac-nav{position:absolute;left:calc(81.7% + 24px);top:39.6%;translate:0 -50%;display:flex;flex-direction:column;gap:8px;z-index:2}
/* drawn, like the arrows elsewhere on the site, in the dark ink of the plate */
.pj-mac-btn{width:42px;height:42px;border-radius:50%;padding:0;cursor:pointer;display:grid;place-items:center;
  border:1px solid rgba(12,12,12,.32);background:transparent;color:#0c0c0c;
  transition:border-color var(--hover) var(--ease-soft),background-color var(--hover) var(--ease-soft),opacity var(--hover) var(--ease-soft)}
.pj-mac-btn:hover{border-color:rgba(12,12,12,.85);background:rgba(12,12,12,.06)}
.pj-mac-btn:disabled{opacity:.28;cursor:default}
.pj-mac-btn:disabled:hover{border-color:rgba(12,12,12,.32);background:transparent}
.pj-mac-btn svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
/* stronger, so they read on the pale plate; the middle one plays the page */
.pj-mac-btn{border-color:rgba(12,12,12,.55)}
.pj-mac-btn:disabled{opacity:.35}
.pj-mac-btn:disabled:hover{border-color:rgba(12,12,12,.55)}
.pj-mac-btn svg{stroke-width:1.7}
.pj-mac-play .i-play{fill:currentColor;stroke:none}
.pj-mac-play .i-pause,.pj-mac-play.on .i-play{display:none}
.pj-mac-play.on .i-pause{display:block}

/* the phone's screens change in place, a soft cross-fade like switching tabs,
   instead of sliding across */
.ph--seq img{transform:none!important;opacity:0}
.ph--seq img.off{opacity:1;z-index:0;transition:none}
.ph--seq img.on{opacity:1;z-index:1;transition:opacity .6s var(--ease-soft)}
/* the play button leads: larger than the arrows, its glyphs rounded */
.pj-mac-nav{align-items:center}
.pj-mac-play{width:52px;height:52px}
.pj-mac-play svg{width:20px;height:20px}
.pj-mac-play .i-pause{stroke-width:2.6;stroke-linecap:round}

/* Genesis EX, one episode: a dark stage lit by the film's own colours, the
   film large at its 9:16 with a shadow under it, the words close beside it.
   The stage stays dark in light mode too: it is a screening room. */
.pj-ep{position:relative;isolation:isolate;overflow:hidden;border-radius:var(--r-panel);background:#050507;
  padding:clamp(48px,6vw,104px) clamp(24px,5vw,88px);display:grid;place-items:center}

.pj-ep::after{content:"";position:absolute;inset:0;z-index:-1;background:radial-gradient(ellipse at 35% 50%,transparent 30%,rgba(5,5,7,.75) 100%)}
.pj-ep-inner{display:flex;align-items:center;gap:clamp(40px,5vw,96px)}
.pj-ep-video{flex:none;height:min(82vh,860px);aspect-ratio:9/16;border-radius:clamp(14px,1.6vw,24px);overflow:hidden;
  box-shadow:0 50px 120px rgba(0,0,0,.65),0 0 0 1px rgba(255,255,255,.08)}
.pj-ep-video video{display:block;width:100%;height:100%;object-fit:cover;background:#000}

@media(max-width:960px){
  .pj-ep-inner{flex-direction:column;gap:32px}
  .pj-ep-video{height:auto;width:min(100%,380px)}
}

/* the episode's own player: a big play in the middle while paused, and a bar
   along the bottom (progress, play, time, sound, full screen) that shows on
   hover and while paused. Full screen keeps the film at 9:16 on black. */
.pj-ep-video{position:relative}
.pj-ep-video video{cursor:pointer}
.pl-big{position:absolute;left:50%;top:50%;translate:-50% -50%;width:76px;height:76px;border-radius:50%;padding:0;cursor:pointer;
  display:grid;place-items:center;color:#fff;border:1px solid rgba(255,255,255,.3);
  background:rgba(12,12,12,.35);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  transition:opacity var(--base) var(--ease-soft),scale var(--base) var(--ease-soft)}
.pl-big svg{width:28px;height:28px;fill:currentColor;margin-left:3px}
.pl-big:hover{scale:1.06}
[data-player].playing .pl-big{opacity:0;scale:.9;pointer-events:none}

.pl-track{position:relative;height:14px;cursor:pointer;touch-action:none}
.pl-track::before,.pl-buf,.pl-fill{content:"";position:absolute;left:0;top:50%;height:3px;translate:0 -50%;border-radius:3px;transition:height var(--hover) var(--ease-soft)}
.pl-track::before{right:0;background:rgba(255,255,255,.22)}
.pl-buf{width:0;background:rgba(255,255,255,.35)}
.pl-fill{width:0;background:#fff}
.pl-fill::after{content:"";position:absolute;right:-6px;top:50%;width:12px;height:12px;border-radius:50%;background:#fff;translate:0 -50%;scale:0;transition:scale var(--hover) var(--ease-soft)}
.pl-track:hover::before,.pl-track:hover .pl-buf,.pl-track:hover .pl-fill{height:5px}
.pl-track:hover .pl-fill::after,.pl-track.drag .pl-fill::after{scale:1}

.pl-btn{width:36px;height:36px;padding:0;border:0;border-radius:50%;background:transparent;color:#fff;cursor:pointer;display:grid;place-items:center;
  transition:background-color var(--hover) var(--ease-soft)}
.pl-btn:hover{background:rgba(255,255,255,.12)}
.pl-btn svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.pl-btn .i-play{fill:currentColor;stroke:none}
.pl-btn .i-pause,[data-player].playing .pl-btn .i-play{display:none}
[data-player].playing .pl-btn .i-pause{display:block}
.pl-btn .i-on,[data-player].sound .pl-btn .i-off{display:none}
[data-player].sound .pl-btn .i-on{display:block}

[data-player]:fullscreen{width:100%;height:100%;aspect-ratio:auto;border-radius:0;background:#000}
[data-player]:fullscreen video{object-fit:contain}
[data-player]:-webkit-full-screen{width:100%;height:100%;aspect-ratio:auto;border-radius:0;background:#000}
[data-player]:-webkit-full-screen video{object-fit:contain}

/* app-style controls: glass circles stacked on the right, always there, and
   the progress as a hairline along the bottom edge. No time, no web bar. */
.pl-side{position:absolute;right:14px;bottom:28px;display:flex;flex-direction:column;gap:12px;z-index:2}
.pl-side .pl-btn{width:46px;height:46px;border:1px solid rgba(255,255,255,.22);
  background:rgba(12,12,12,.38);-webkit-backdrop-filter:blur(14px) saturate(140%);backdrop-filter:blur(14px) saturate(140%)}
.pl-side .pl-btn:hover{background:rgba(12,12,12,.58)}
.pl-side .pl-btn svg{width:20px;height:20px}
[data-player] > .pl-track{position:absolute;left:0;right:0;bottom:0;height:16px;z-index:2}
[data-player] > .pl-track::before,[data-player] > .pl-track .pl-buf,[data-player] > .pl-track .pl-fill{top:auto;bottom:0;translate:none;border-radius:0}
[data-player]:fullscreen .pl-side{right:calc(50% - min(50vw, 100vh * 9 / 32) + 18px)}
[data-player]:fullscreen > .pl-track{left:50%;right:auto;width:min(100%,calc(100vh * 9 / 16));translate:-50% 0}
/* cleaner: no stage, no glow. The film and its words sit on the page as a
   pair, centred, in the page's own ink so light mode follows. The progress
   line only shows while the pointer is on the film. */
.pj-ep{background:none;border-radius:0;padding:clamp(24px,3vw,48px) 0 0;overflow:visible}
.pj-ep::after{display:none}
.pj-ep-video{box-shadow:none;outline:1px solid var(--hairline);outline-offset:-1px}

[data-player] > .pl-track{opacity:0;transition:opacity var(--base) var(--ease-soft)}
[data-player]:hover > .pl-track,[data-player] > .pl-track:focus-visible,[data-player] > .pl-track.drag{opacity:1}
@media(hover:none){[data-player] > .pl-track{opacity:1}}
/* the episode plays inside an iPhone: the picture's screen is see-through
   (measured on the 952x1951 picture: 43,37 to 908,1913) and the film sits
   under it, cut to the screen's rounded corners; the player lives on the
   screen, the phone lets the pointer through */
.pj-ep-video{height:min(84vh,880px);aspect-ratio:952/1951;border-radius:0;overflow:visible;outline:none;box-shadow:none;
  filter:drop-shadow(0 40px 80px rgba(0,0,0,.45))}
.pj-ep-screen{position:absolute;left:4.517%;top:1.896%;width:90.966%;height:96.207%;overflow:hidden;
  border-radius:12.5% / 5.8%;background:#000}
.pj-ep-screen video{display:block;width:100%;height:100%;object-fit:cover}
.pj-ep-phone{position:absolute;inset:0;width:100%;height:100%;z-index:3;pointer-events:none}
.pj-ep-screen .pl-side{bottom:34px;right:12px}
.pj-ep-screen > .pl-track{bottom:14px;left:18%;right:18%;width:auto}
[data-player].pj-ep-screen:fullscreen{position:static;width:100%;height:100%;border-radius:0}
@media(max-width:960px){.pj-ep-video{height:auto;width:min(100%,360px)}}

/* Genesis EX, in the order of the first site: each step, then its pictures
   across the page. The episode plays in a phone on a panel, the stills run
   edge to edge under the numbers. */
.pj-ep{background:#0f0f11;border-radius:var(--r-panel);padding:clamp(48px,6vw,96px) 24px;overflow:hidden}
.pj-ep-inner{justify-content:center}
.pj-big .pj-stills{gap:16px}

/* a text link with its arrow; the underline draws in on hover */
.pj-link{display:inline-flex;align-items:center;gap:8px;font-size:16px;color:var(--ink);text-decoration:none;
  background:linear-gradient(currentColor,currentColor) 0 100%/0 1px no-repeat;padding-bottom:3px;
  transition:background-size var(--base) var(--ease-out)}
.pj-link:hover{background-size:100% 1px}
.pj-link svg{width:12px;height:12px;fill:none;stroke:currentColor;stroke-width:1.5}
/* the spinning mark follows the theme: in light mode it is drawn dark on white */
body.theme-light .pj-logo-spin{filter:invert(1)}

/* the panel behind the episode's phone is a photograph of dark stone */
.pj-ep{background:#0f0f11 url("files/images/genesis-ex/iphone_background.webp") center/cover no-repeat}
.pj-ep-cta{display:flex;justify-content:center;margin-top:clamp(28px,3vw,44px)}

/* Déjà Vu: the whole film, now further down the page, keeps its look; its
   title is an h2 there, set as the h1 was */

.film-mark-below{display:flex;justify-content:center;margin-top:clamp(40px,5vw,72px)}
.film-mark-below .film-mark{margin-bottom:0}

/* Déjà Vu, the props: a field of them coming at the viewer out of the dark,
   small and faint far off, larger and sharp as they pass, leaving the middle
   clear for the title. Each card is placed by app.js every frame. */
.props-fly{position:relative;width:100vw;margin-left:calc(50% - 50vw);height:min(100vh,940px);min-height:620px;
  overflow:hidden;display:grid;place-items:center;isolation:isolate}
.pf-field{position:absolute;inset:0;z-index:0}
.pf-card{position:absolute;left:50%;top:50%;width:230px;aspect-ratio:16/10;border-radius:12px;overflow:hidden;
  background:#111;opacity:0;will-change:transform,opacity;box-shadow:0 20px 50px rgba(0,0,0,.5)}
.pf-card img{display:block;width:100%;height:100%;object-fit:cover}
.props-fly::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:radial-gradient(ellipse 38% 34% at 50% 50%,rgba(0,0,0,.85),transparent 100%)}
.pf-copy{position:relative;z-index:2;max-width:520px;padding:0 24px;text-align:center}
.pf-copy h2{font-size:clamp(34px,4.4vw,64px);line-height:1.02;letter-spacing:-.035em;font-weight:500;color:#fff}
.pf-copy p{margin-top:20px;font-size:17px;line-height:1.5;color:rgba(255,255,255,.66);text-wrap:balance}
@media(max-width:620px){.pf-card{width:130px}}
.pf-card{opacity:1}
/* the props field as a 3D scene: perspective on the field, the cards hung
   in it by app.js */
.pf-field{perspective:1000px;perspective-origin:50% 50%}

.pf-card{width:200px;backface-visibility:hidden}
/* the props field, rebuilt after on.design: one perspective over the whole
   field, the cards placed in % with their own size, depth and turn (app.js) */
.pf-field{perspective:1300px;perspective-origin:50% 50%}

.pf-card{left:auto;top:auto;width:auto;aspect-ratio:4/3;border-radius:7%;backface-visibility:hidden;
  margin:0;translate:-50% -50%;box-shadow:inset 0 0 0 1px rgba(255,255,255,.08),0 20px 50px rgba(0,0,0,.45)}
.pf-card img{opacity:var(--in,0)}
/* more room for the props' title and words, and more height so no card is cut */
.props-fly{height:min(100vh,1000px);min-height:700px}
.pf-copy{max-width:900px}
.pf-copy p{max-width:760px;margin-left:auto;margin-right:auto}
.props-fly::after{background:radial-gradient(ellipse 46% 40% at 50% 50%,rgba(0,0,0,.9),transparent 100%)}

/* Déjà Vu, pieces to shots: four references in a 2x2 on the left, a plus at
   their crossing, an arrow, and the shot they make on the right, the two
   sides the same size so they read as an equation */
.dv-combo{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:clamp(16px,2vw,32px)}
.dv-combo + .dv-combo{margin-top:clamp(24px,3vw,48px)}
.dv-parts{position:relative;display:grid;grid-template-columns:1fr 1fr;gap:10px}
.dv-parts .pj-frame{border-radius:calc(var(--r-panel) * .6)}
.dv-parts .pj-tag{font-size:12px;padding:6px 11px}

.dv-shot{position:relative}
@media(max-width:960px){
  .dv-combo{grid-template-columns:1fr}
}
/* the combinations slide one after another */
.dv-slider{overflow:hidden}
.dv-track{display:flex;transition:transform .9s cubic-bezier(.65,0,.25,1)}
.dv-track > .dv-combo{flex:0 0 100%;min-width:0}
.dv-track > .dv-combo + .dv-combo{margin-top:0}
.dv-slider .belt-nav{margin-top:clamp(28px,3vw,44px)}
/* three pieces: the place across the top, the two others under it */
/* six pieces: three by two, still the size of the shot beside them */
/* the combinations cross-fade in one place instead of sliding */
.dv-track{display:grid;transition:none;transform:none!important}
.dv-track > .dv-combo{grid-area:1 / 1;opacity:0;visibility:hidden;transition:opacity .7s var(--ease-soft),visibility 0s .7s}
.dv-track > .dv-combo.on{opacity:1;visibility:visible;transition:opacity .7s var(--ease-soft),visibility 0s}
/* the line under them, as on the home page's AI themes: one part each */
.dv-bar{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:12px;margin-top:clamp(28px,3vw,44px)}
.dv-bar button{all:unset;cursor:pointer;padding:10px 0}
.dv-bar button:focus-visible{outline:1px solid var(--ink-muted);outline-offset:4px;border-radius:4px}
.dv-bar i{position:relative;display:block;height:2px;border-radius:2px;background:var(--hairline);overflow:hidden}
.dv-bar i::after{content:"";position:absolute;inset:0;background:var(--ink);transform-origin:left;transform:scaleX(var(--f,0))}
.dv-slider .dv-bar + .belt-nav{margin-top:20px}
/* every piece keeps its 16:9; the place in the three-piece block spans the
   top row, the six-piece block runs three across */

.dv-parts .pj-frame{aspect-ratio:16/9}

/* the three-piece block is narrowed so it stands no taller than the shot */
.dv-parts:has(.dv-wide){width:67%;justify-self:end}

/* the pieces as a pinned board: each reference at its own 16:9, a little
   turned, overlapping the next, the whole board the size of the shot. They
   are placed by app.js from the number of pieces, and settle in one after
   the other each time their combination comes up. Hovering one brings it
   to the front and straightens it. */
.dv-parts,.dv-parts:has(.dv-wide){display:block;position:relative;width:100%;aspect-ratio:16/9;gap:0}
.dv-parts .pj-frame{position:absolute;left:calc(var(--x) * 1%);top:calc(var(--y) * 1%);width:calc(var(--w) * 1%);aspect-ratio:16/9;
  border-radius:calc(var(--r-panel) * .55);rotate:calc(var(--r) * 1deg);
  box-shadow:0 18px 40px rgba(0,0,0,.5),0 0 0 1px rgba(255,255,255,.06);
  transition:rotate .5s var(--ease-out),scale .5s var(--ease-out),translate .9s var(--ease-out),opacity .7s var(--ease-soft);
  transition-delay:calc(var(--k) * 90ms)}
.dv-combo:not(.on) .dv-parts .pj-frame{opacity:0;translate:-24px 14px;scale:.94;transition-delay:0s}
.dv-parts .pj-frame:hover{rotate:0deg;scale:1.05;z-index:9;transition-delay:0s}

@media(max-width:960px){.dv-parts,.dv-parts:has(.dv-wide){width:100%}}
/* full width again, the arrow closer, so both sides are large */
.dv-combo{gap:clamp(12px,1.4vw,24px)}
/* the pieces settle softly: each fades in and rises a little, one after the
   other; on the way out they go with their combination's fade and are reset
   only once it is gone */
.dv-parts .pj-frame{transition:rotate .5s var(--ease-out),scale .5s var(--ease-out),translate .8s var(--ease-out),opacity .8s var(--ease-soft);
  transition-delay:calc(150ms + var(--k) * 80ms)}
.dv-combo:not(.on) .dv-parts .pj-frame{opacity:0;translate:0 18px;scale:1;transition-duration:0s;transition-delay:.75s}
.dv-parts .pj-frame:hover{transition-delay:0s}

/* Pieces to shots, as a workflow: the references in a row at the top, a
   wire from each running down into the shot, the shot large under them.
   When a combination comes up the pieces arrive one by one, the wires draw
   themselves, and the shot comes in last. */
.dv-combo{display:flex;flex-direction:column;align-items:center;gap:0;position:relative}
.dv-parts,.dv-parts:has(.dv-wide){display:flex;justify-content:center;gap:clamp(10px,1.2vw,18px);width:100%;aspect-ratio:auto;position:relative;z-index:1}
.dv-parts .pj-frame{position:relative;left:auto;top:auto;rotate:0deg;flex:0 1 220px;width:auto;aspect-ratio:16/9;
  border-radius:calc(var(--r-panel) * .5);box-shadow:0 0 0 1px var(--hairline);
  transition:opacity .6s var(--ease-soft),translate .7s var(--ease-out);transition-delay:calc(100ms + var(--k) * 90ms)}
.dv-parts .pj-frame:hover{rotate:0deg;scale:1}
.dv-wires{display:block;width:100%;height:clamp(64px,7vw,110px);overflow:visible;z-index:0}
.dv-wires path{fill:none;stroke:var(--ink-muted);stroke-width:1.2;stroke-linecap:round;opacity:.55;
  stroke-dasharray:var(--len);stroke-dashoffset:var(--len);transition:stroke-dashoffset 1s var(--ease-out);transition-delay:calc(500ms + var(--k) * 90ms)}
.dv-wires circle{fill:var(--ink);opacity:0;transition:opacity .4s var(--ease-soft) 1.3s}
.dv-combo.on .dv-wires path{stroke-dashoffset:0}
.dv-combo.on .dv-wires circle{opacity:.9}
.dv-shot{width:min(100%,1040px);transition:opacity .8s var(--ease-soft) 1.1s}
.dv-combo:not(.on) .dv-shot{opacity:0;transition-duration:0s;transition-delay:.75s}
.dv-combo:not(.on) .dv-wires path{transition-duration:0s;transition-delay:.75s}
.dv-combo:not(.on) .dv-wires circle{transition-delay:0s}
.dv-combo:not(.on) .dv-parts .pj-frame{opacity:0;translate:0 -12px;scale:1}
@media(max-width:620px){.dv-parts{flex-wrap:wrap}.dv-parts .pj-frame{flex-basis:calc(50% - 6px)}}
/* the pieces, the line and the arrows all take the shot's width */
.dv-parts,.dv-slider .dv-bar{width:min(100%,1040px);margin-left:auto;margin-right:auto}
/* more than six pieces: two rows of up to five, so each stays a fair size */
.dv-parts:has(> .pj-frame:nth-child(7)){flex-wrap:wrap;row-gap:clamp(10px,1.2vw,18px)}
.dv-parts:has(> .pj-frame:nth-child(7)) .pj-frame{flex:0 0 calc((100% - 4 * clamp(10px,1.2vw,18px)) / 5)}
/* every shot sits at the same height whatever the pieces above it, and the
   wires are drawn over the whole board from the foot of each piece, behind
   the pieces, so a wire from the top row passes under the row below */
.dv-track > .dv-combo{justify-content:flex-end}
.dv-parts{margin-bottom:clamp(64px,7vw,110px)}
.dv-wires{position:absolute;left:0;top:0;width:100%;height:var(--wh,0px);z-index:0;pointer-events:none}

/* Pieces to shots, side by side: the pieces in a column (or two, three) on
   the left, the shot large on the right, a wire from the right edge of each
   piece into the middle of the shot's left edge. app.js sizes the pieces so
   the column stands about as tall as the shot, and every shot sits at the
   same place from one combination to the next. */
.dv-track > .dv-combo{flex-direction:row;align-items:center;justify-content:space-between;gap:0}
.dv-parts,.dv-parts:has(> .pj-frame:nth-child(7)){display:grid;grid-template-columns:repeat(var(--c,1),var(--tw,160px));gap:var(--g,12px);
  width:auto;margin:0;flex:none;justify-content:start;align-content:center}
.dv-parts .pj-frame,.dv-parts:has(> .pj-frame:nth-child(7)) .pj-frame{width:var(--tw,160px);flex:none}
.dv-track .dv-shot{width:var(--sw,56%);flex:none}
.dv-wires{height:100%}
.dv-parts .pj-frame{transition-delay:calc(100ms + var(--k) * 70ms)}
.dv-combo:not(.on) .dv-parts .pj-frame{translate:-12px 0}
@media(max-width:760px){
  .dv-track > .dv-combo{flex-direction:column;gap:20px}
  .dv-track .dv-shot{width:100%}
  .dv-wires{display:none}
}
/* the line and the arrows sit under the shot, at its width */
@media(min-width:761px){
  .dv-slider .dv-bar{width:54%;margin-left:auto;margin-right:auto}
  .dv-slider .belt-nav{width:auto;margin-left:auto;margin-right:auto}
}

/* the pieces live in one "Assets" panel, always the shot's height and the
   same width from one combination to the next, so nothing shifts when they
   change; the pieces are fitted inside it. The shot gets a light edge. */
.dv-parts,.dv-parts:has(> .pj-frame:nth-child(7)){position:relative;width:var(--aw,40%);height:var(--ah,auto);box-sizing:border-box;
  padding:46px 16px 16px;border-radius:calc(var(--r-panel) * .8);background:color-mix(in srgb,var(--ink) 3%,var(--bg));box-shadow:inset 0 0 0 1px var(--hairline);
  justify-content:center;align-content:center}
.dv-parts::before{content:"Assets";position:absolute;left:18px;top:16px;font-size:13px;line-height:1;color:var(--ink-muted)}
.dv-parts::after{content:attr(data-n);position:absolute;right:18px;top:16px;font-size:13px;line-height:1;color:var(--ink-muted);opacity:.7}
.dv-shot::after{content:"";position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 0 0 1px rgba(255,255,255,.14);pointer-events:none}
body.theme-light .dv-shot::after{box-shadow:inset 0 0 0 1px rgba(0,0,0,.14)}
/* the link between the panel and the shot: one line, a port at each end */
.dv-wires path{opacity:.7;stroke-width:1.3}
.dv-wires circle{fill:var(--bg);stroke:var(--ink-muted);stroke-width:1.3}
.dv-combo.on .dv-wires circle{opacity:1}
.dv-parts{align-content:center;justify-content:center}

/* a film frame placed straight in the project stack fills its column */
.pj-stack > .film-frame{width:100%}

/* Pulpa, one photo to the whole campaign: the source photo on the left at
   the height of the 3 by 2 grid of generated frames on the right */

.dv-bar[hidden]{display:none}

/* more air between the sections of a case study */
.pj-big .pj-row{margin-top:168px}
.pj-big .pj-row:first-child{margin-top:0}
@media(max-width:900px){.pj-big .pj-row{margin-top:104px}}

/* ── Pulpa, one photo canvas: a node board on a dot grid. The photo on the
   left, three pills, a row of four per pill. Wires and rows play in once. */
.op-canvas{position:relative;display:grid;grid-template-columns:minmax(0,.62fr) minmax(0,2.4fr);column-gap:clamp(90px,11vw,180px);align-items:center;
  padding:clamp(48px,5vw,80px) clamp(24px,3vw,56px);border-radius:var(--r-panel);box-shadow:inset 0 0 0 1px var(--hairline);
  background-color:var(--bg);background-image:radial-gradient(circle,rgba(255,255,255,.11) 1px,transparent 1.4px);background-size:22px 22px;background-position:center}
body.theme-light .op-canvas{background-image:radial-gradient(circle,rgba(0,0,0,.13) 1px,transparent 1.4px)}
.op-wires{position:absolute;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none;z-index:0}
.op-wires path{fill:none;stroke:var(--ink-muted);stroke-width:1.3;opacity:.7;stroke-dasharray:var(--len,2000);stroke-dashoffset:var(--len,2000);
  transition:stroke-dashoffset 1s cubic-bezier(.65,0,.25,1) calc(.25s + var(--k,0) * .15s)}
.op-wires path.op-w2{transition-delay:calc(1.05s + var(--k,0) * .15s);transition-duration:.45s}
.op-wires circle{fill:var(--bg);stroke:var(--ink-muted);stroke-width:1.3;opacity:0;transition:opacity .4s var(--ease-soft) calc(.9s + var(--k,0) * .15s)}
.op-canvas.run .op-wires path{stroke-dashoffset:0}
.op-canvas.run .op-wires circle{opacity:1}
.op-src,.op-out{position:relative;z-index:1}
.op-cap{position:absolute;top:-30px;left:0;font-size:13px;line-height:1;color:var(--ink-muted)}
.op-src .pj-frame{aspect-ratio:1536/2752;box-shadow:0 0 0 1px rgba(255,255,255,.14)}
.op-out{display:grid;gap:clamp(14px,1.4vw,22px)}
.op-branch{display:grid;grid-template-columns:7.6rem minmax(0,1fr);align-items:center;column-gap:clamp(40px,4.5vw,72px)}
.op-pill{display:inline-block;width:max-content;padding:9px 15px;border-radius:99px;font-size:13px;line-height:1;color:var(--ink);background:var(--bg);
  box-shadow:inset 0 0 0 1px var(--hairline);opacity:0;transition:opacity .5s var(--ease-soft) calc(.9s + var(--r) * .15s)}
.op-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(8px,.9vw,14px)}
.op-node{border-radius:calc(var(--r-panel) * .55);opacity:0;translate:0 14px;scale:.94;
  transition:opacity .6s var(--ease-soft),translate .8s var(--ease-out),scale .8s var(--ease-out);transition-delay:calc(1.3s + var(--r) * .15s + var(--i) * .07s)}
.op-canvas.run .op-pill{opacity:1}
.op-canvas.run .op-node{opacity:1;translate:0 0;scale:1}
@media(prefers-reduced-motion:reduce){.op-wires path,.op-wires circle,.op-pill,.op-node{transition:none}}
@media(max-width:900px){
  .op-canvas{grid-template-columns:1fr;row-gap:56px;padding:48px 20px 28px}
  .op-src{width:min(52%,260px);justify-self:center}
  .op-wires{display:none}
  .op-branch{grid-template-columns:1fr;row-gap:12px}
  .op-row{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* a row's text: a short line sits against the right edge instead of
   leaving a gap after it, and no word is left alone on the last line */
.pj-row > p{justify-self:end;width:fit-content;max-width:100%;text-wrap:pretty}

/* the last block of a case study gets room before the next case study */
.pj-big > .dv-slider:last-child{margin-bottom:clamp(80px,9vw,150px)}

/* the row that opens the second part of a case study gets more room */
.pj-big .pj-row--part{margin-top:clamp(220px,20vw,320px)}
@media(max-width:900px){.pj-big .pj-row--part{margin-top:160px}}

/* ── services: a large list; the picture follows the pointer on hover */
.svl{padding-top:clamp(140px,12vw,200px);padding-bottom:clamp(120px,12vw,200px)}
.svl-list{list-style:none;margin:0;padding:0;border-top:1px solid var(--hairline)}
.svl-list li{border-bottom:1px solid var(--hairline)}
.svl-row{all:unset;box-sizing:border-box;width:100%;cursor:pointer;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:32px;padding:clamp(22px,2.4vw,40px) 0;color:var(--ink)}
.svl-row:focus-visible{outline:1px solid var(--ink-muted);outline-offset:6px;border-radius:6px}
.svl-name{font-size:clamp(40px,5.4vw,96px);line-height:1.05;letter-spacing:-.03125em;font-weight:500;transition:color .45s var(--ease-soft),translate .6s var(--ease-out)}

.svl-thumb{display:none}
.svl-list:hover .svl-name{color:var(--ink-muted)}
.svl-list:hover .svl-row:hover .svl-name{color:var(--ink);translate:clamp(8px,1vw,18px) 0}

.svl-float{position:fixed;left:0;top:0;z-index:30;width:clamp(240px,22vw,340px);aspect-ratio:4/5;border-radius:var(--r-panel);overflow:hidden;pointer-events:none;
  opacity:0;scale:.86;transition:opacity .35s var(--ease-soft),scale .5s var(--ease-out);background:#111;will-change:transform}
.svl-float.on{opacity:1;scale:1}
.svl-float > *{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .3s var(--ease-soft)}
.svl-float > .on{opacity:1}
@media(hover:none),(max-width:860px){
  .svl-float{display:none}
  .svl-row{grid-template-columns:minmax(0,1fr) 88px;row-gap:10px}

  .svl-thumb{display:block;grid-column:2;grid-row:1 / span 2;width:88px;aspect-ratio:4/5;border-radius:10px;overflow:hidden;background:#111}
  .svl-thumb img,.svl-thumb video{width:100%;height:100%;object-fit:cover;display:block}
}
.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* the plus, turning into a minus when the service is open */
.svl-plus{position:relative;width:40px;height:40px;border-radius:50%;box-shadow:inset 0 0 0 1px var(--hairline);transition:box-shadow .35s var(--ease-soft),background .35s var(--ease-soft)}
.svl-plus::before,.svl-plus::after{content:"";position:absolute;left:50%;top:50%;width:14px;height:1.5px;translate:-50% -50%;background:var(--ink);border-radius:1px;transition:rotate .5s var(--ease-out)}
.svl-plus::after{rotate:90deg}
.svl-row:hover .svl-plus{box-shadow:inset 0 0 0 1px var(--ink-muted)}
.svl-item.open .svl-plus::after{rotate:0deg}
.svl-item.open .svl-name{color:var(--ink)}
/* the panel opens on its own height (grid rows 0fr to 1fr) */
.svl-panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .7s var(--ease-out)}
.svl-item.open .svl-panel{grid-template-rows:1fr}
.svl-panel-in{min-height:0;overflow:hidden;display:grid;grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);column-gap:clamp(40px,7vw,140px)}
.svl-body,.svl-deliver{opacity:0;translate:0 12px;transition:opacity .4s var(--ease-soft),translate .6s var(--ease-out)}
.svl-item.open .svl-body,.svl-item.open .svl-deliver{opacity:1;translate:0 0;transition-delay:.15s}
.svl-body{padding:4px 0 clamp(40px,4vw,64px)}
.svl-body p{font-size:clamp(18px,1.5vw,22px);line-height:1.45;color:var(--ink-muted);max-width:42em}
.svl-body p + p{margin-top:1em}
.svl-deliver{list-style:none;margin:0;padding:4px 0 clamp(40px,4vw,64px)}
.svl-deliver li{padding:12px 0;font-size:16px;line-height:1.3;color:var(--ink);border-bottom:1px solid var(--hairline)}
.svl-deliver li:first-child{padding-top:0}
@media(max-width:860px){
  .svl-row{grid-template-columns:minmax(0,1fr) 72px 40px;column-gap:16px}
  .svl-thumb{grid-column:2;grid-row:1;width:72px}
  .svl-plus{grid-column:3;grid-row:1}
  .svl-panel-in{grid-template-columns:1fr}
  .svl-deliver{padding-top:0}
}

/* a frame that opens a live page: the same hover zoom as the work cards */
/* the reveal sets this frame's transition, so the scale is added to it
   (without the stagger delay) rather than replacing it */
.pj-pagelink{display:block}
.reveal--card .pj-frame.pj-pagelink{
  transition:opacity .9s var(--ease-entry) var(--rd,0ms),transform 1.15s var(--ease-entry) var(--rd,0ms),
             filter .95s var(--ease-entry) var(--rd,0ms),scale .7s var(--ease-soft) 0s}
.pj-pagelink:hover{scale:1.025}

/* ── insights: the list, and an article */
.ins{padding-top:clamp(24px,3vw,48px);padding-bottom:clamp(120px,12vw,200px)}
.ins-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(24px,3vw,40px) 16px}
.ins-card{display:block;text-decoration:none;color:var(--ink)}
.ins-card .pj-frame img{transition:scale var(--zoom-t) var(--ease-soft)}
.ins-card:hover .pj-frame img{scale:var(--zoom)}
.ins-meta{display:flex;flex-wrap:wrap;gap:6px 16px;margin-top:18px;font-size:14px;line-height:1.3;color:var(--ink-muted)}
.ins-card h2{margin-top:10px;font-size:clamp(22px,1.8vw,28px);line-height:1.2;letter-spacing:0}
.ins-card p{margin-top:10px;font-size:16px;line-height:1.5;color:var(--ink-muted)}
@media(max-width:960px){.ins-list{grid-template-columns:1fr 1fr}}
@media(max-width:620px){.ins-list{grid-template-columns:1fr}}

.post{padding:calc(var(--nav-h) + clamp(48px,6vw,96px)) var(--edge) clamp(100px,10vw,160px)}
.post-head{max-width:860px;margin:0 auto}
.post-back{color:var(--ink-muted)}
.post-head h1{margin-top:28px;font-size:clamp(38px,5vw,72px);line-height:1.04;letter-spacing:-.03125em;font-weight:500;color:var(--ink);text-wrap:balance}
.post-head .ins-meta{margin-top:24px}
.post-hero{max-width:1200px;margin:clamp(40px,5vw,72px) auto 0}
.post-body{max-width:720px;margin:clamp(48px,5vw,80px) auto 0;font-size:18px;line-height:1.65;color:var(--ink-muted)}
.post-body strong{font-weight:500;color:var(--ink)}
.post-body p + p{margin-top:1em}
.post-lead{font-size:clamp(20px,1.7vw,24px);line-height:1.5;color:var(--ink)}
.post-body h2{margin:2.2em 0 .6em;font-size:clamp(26px,2.4vw,34px);line-height:1.15;letter-spacing:-.01em;color:var(--ink)}
.post-body ul,.post-body ol{margin:0;padding-left:1.2em}
.post-body li + li{margin-top:.6em}
.post-fig{margin:2em 0}
.post-fig--narrow{max-width:320px}
.post-fig figcaption{margin-top:12px;font-size:14px;color:var(--ink-muted)}
.post-trio{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}

.post-body p,.post-body li{font-size:18px;line-height:1.65}
.post-body .post-lead{font-size:clamp(20px,1.7vw,24px);line-height:1.5}

/* a picture with a transparent background laid over a video texture */
.pj-layered{position:relative}
.pj-layered > img{position:absolute;inset:0;z-index:1;object-fit:contain}

/* a phone shown big and cut off at the bottom, on a solid colour */
.pj-layered--crop{background:var(--ph)!important}
.pj-layered--crop > img{inset:auto 0;top:14%;height:125%;width:100%}

/* home: latest insights */
.ins-card h3{margin-top:10px;font-size:clamp(22px,1.8vw,28px);line-height:1.2;letter-spacing:0;font-weight:inherit}
.ins-more{margin-top:44px;display:flex;justify-content:center}
/* a little air between two cards' texts */
.ins-card .ins-meta,.ins-card h2,.ins-card h3,.ins-card p{padding-right:16px}
.post-duo{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.post-duo .pj-frame{aspect-ratio:1}

/* ══ tracking: two values only ══════════════════════════════════════════
   Display type (titles that grow past ~44px) sits at -.03em, mid-size
   titles and statements (24 to 40px) at -.01em, everything smaller at 0.
   Children inherit, so a word wrapped in a span keeps its heading's
   tracking. */
h2,.hero h1,.page-head h1,.pj-stat dt,.pj-open-title,.pj-open-title--line,.pf-copy h2,.svl-name,.post-head h1,.hero-copy h1{letter-spacing:-.03em}
.nav-menu a,.pj-intro,.pj-row p,.pj-row>div>p,.post-body h2,.ins-card h2,.ins-card h3{letter-spacing:-.01em}
.pj-row h2,.sr-only{letter-spacing:0}
.post-lead,.pj-swatch{letter-spacing:-.01em}

/* same air under the work cards and the home disciplines */
.work-foot,.disc-label,.disc-label + p{padding-right:16px}
/* the wheel plate with no reviews: the film section is drawn up under it */
.archive--open{overflow:visible;position:relative;z-index:1;display:flow-root}
.archive--open + section{position:relative}
.archive--open + section > .chap-head{position:relative;z-index:2}
.chap--film .chap-head{margin-bottom:40px}
/* Déjà Vu props: one statement in the middle, set like the section rows */
.pf-copy .pf-lead{margin:0;max-width:640px;font-size:clamp(22px,2.1vw,32px);line-height:1.25;letter-spacing:-.01em;color:#fff;text-wrap:balance}
@media(max-width:900px){.props-copy .pf-lead{position:static;margin:0 auto;font-size:22px}}

/* light mode: the wheel plate is redrawn white instead of being inverted,
   because a picture inverted twice never comes back to its own colours */
body.theme-light .archive[data-invert]{background:#fff;color:#111}

/* ══ mobile pass (2026-10-17) ════════════════════════════════════════════
   One gutter for every phone page, the cards keep a portrait shape, and the
   phone menu is a proper panel: big links on hairlines, then the way to talk. */
@media(max-width:767px){
  :root{--edge:16px}
  .navbar{padding-left:16px;padding-right:16px}
  .hero,.section,.trusted,.page-head,.pj-open,.chap:not(.chap--belt):not(.chap--bench){padding-left:16px;padding-right:16px}
  .closing,footer{padding-left:16px;padding-right:16px}

  /* the capsule: lighter, the wordmark smaller than in the bar */
  .navcap{padding:8px 20px 8px 20px;max-width:calc(100vw - 32px)}
  .navcap .nav-burger{margin-right:0}
  .navcap .brand svg{height:22px}
  .nav-burger{width:44px;height:44px;display:grid!important;place-items:center}
  .navbar .nav-burger{margin-right:-11px}

  /* the menu panel */
  .nav-menu{
    justify-content:space-between;align-items:stretch;gap:0;
    padding:calc(var(--nav-h) + 24px) 16px calc(28px + env(safe-area-inset-bottom));
    transform:none;transition:opacity .35s var(--ease-soft);
  }
  .nav-menu-links{display:flex;flex-direction:column}
  .nav-menu-links a{
    display:flex;align-items:center;justify-content:space-between;
    padding:18px 0;border-bottom:1px solid var(--hairline);
    font-size:40px;line-height:1.05;letter-spacing:-.03em;color:var(--ink-strong);
  }
  .nav-menu-links a:first-child{border-top:1px solid var(--hairline)}
  .nav-menu-links svg{width:18px;height:18px;flex:none;fill:none;stroke:currentColor;stroke-width:1.4;opacity:.5}
  /* an animation, not a transition: it runs every time the panel opens and,
     if it cannot run, the links are simply there */
  body.menu-open .nav-menu-links a{animation:nav-in .6s var(--ease-out) both;animation-delay:calc(80ms + var(--i) * 60ms)}
  .nav-menu-foot{display:flex;flex-direction:column;gap:22px}
  body.menu-open .nav-menu-foot{animation:nav-in .6s var(--ease-out) .34s both}
  .nav-menu-foot .button{width:100%;justify-content:center}
  .nav-menu-meta{display:flex;align-items:center;justify-content:space-between;font-size:15px;color:var(--ink-muted)}
  .nav-menu-meta .theme-dot{margin:0}
}
.page-dark .nav-menu-links a{border-color:rgba(255,255,255,.14)}
.nav-menu-foot,.nav-menu-links{width:100%}

/* project cards: one column on a phone, two below the desktop grid, and
   always a portrait shape so a 2:3 visual is never turned into a landscape */
@media(max-width:960px){.work-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:44px 16px}.work-media{aspect-ratio:2/3}}
@media(max-width:620px){.work-grid{grid-template-columns:1fr}.work-media{aspect-ratio:2/3}}
.nav-menu .nav-menu-foot a{font-size:15px;letter-spacing:0}
.nav-menu .nav-menu-foot .button{font-size:16px;padding-top:16px;padding-bottom:16px}
.page-dark .nav-menu .button-primary{background:#fff;color:#000}
.nav-menu-meta a{color:var(--ink-muted)}
/* portrait pairs stay side by side on a phone: one 4:5 poster per row is a full screen each */
@media(max-width:620px){.pj-strip--2:has(> .pj-frame[style*="--ar:4/5"]){grid-template-columns:repeat(2,minmax(0,1fr))}}
/* the one-photo map on a phone: four outputs across, so the map stays a
   map and not a second gallery above the real one */
@media(max-width:900px){.op-row{grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.op-canvas{row-gap:40px}}
@media(max-width:620px){.props-fly{height:620px;min-height:0}}
/* Chapplet lineup on a phone: the lead screen is third of seven, so the row
   is slid right by one phone to put it on the centre of the screen */
@media(max-width:620px){.pj-lineup-row{translate:calc(177px + var(--p) * -3vw) 0}}
@keyframes nav-in{from{translate:0 18px}to{translate:none}}
/* when the form stacks under its intro, the direct details (address, email,
   phone) come after the form, not between the title and it */
@media(max-width:960px){
  .contact-details{order:2}
  .closing-lead{display:contents}
  .closing-direct{order:3}
}
/* stat labels hold a short measure under their figure, wrapped evenly */
.pj-stat dd{max-width:220px;margin-left:auto;margin-right:auto;text-wrap:balance}
/* Next case study: the title and the button share one centre line, and a
   phone shows only the next project */
.pj-more .works-head{align-items:center}
.pj-more .works-head h2{margin-top:0}
@media(max-width:620px){.pj-more .work-grid > .work-card:not(:first-child){display:none}}

/* Déjà Vu on phones: tighter air between the film, the rows and the rails */
@media(max-width:620px){
  .chap--film{padding-bottom:48px}
  .pj-big#work{padding-top:40px}
  .pj-big .pj-row{margin-top:56px;padding:24px 0 28px}
  .pj-big .pj-row:first-child{margin-top:0}
  .pj-big > .belt-nav{margin-top:20px}
}
/* the props in the light theme: a light glow behind the words, dark words */
body.theme-light .props-fly::after{background:radial-gradient(ellipse 46% 40% at 50% 50%,rgba(255,255,255,.92),transparent 100%)}
body.theme-light .pf-copy .pf-lead{color:var(--ink)}
/* Services on phones and tablets: just the names and the plus, no thumbnails */
@media(hover:none),(max-width:1024px){
  .svl-float,.svl-thumb{display:none}
  .svl-row{grid-template-columns:minmax(0,1fr) 40px}
  .svl-plus{grid-column:2;grid-row:1}
}
/* insight cards: the date and title come in with the picture, not after it sits still */
.ins-card.reveal--card .ins-meta,.ins-card.reveal--card h2,.ins-card.reveal--card h3{
  opacity:0;transform:translateY(24px);filter:blur(7px);
  transition:opacity .75s var(--ease-entry) calc(var(--rd,0ms) + 180ms),
             transform .9s var(--ease-entry) calc(var(--rd,0ms) + 180ms),
             filter .75s var(--ease-entry) calc(var(--rd,0ms) + 180ms);
}
.ins-card.reveal--card.lit .ins-meta,.ins-card.reveal--card.lit h2,.ins-card.reveal--card.lit h3{opacity:1;transform:none;filter:none}
@media(prefers-reduced-motion:reduce){
  .ins-card.reveal--card .ins-meta,.ins-card.reveal--card h2,.ins-card.reveal--card h3{opacity:1;transform:none;filter:none;transition:none}
}
/* home hero on phones: the dithered logo in the top half, the words under it */
@media(max-width:767px){
  .hero:first-of-type{min-height:100svh;justify-content:flex-end;padding-bottom:56px}
  .hero:first-of-type .hero-video{position:absolute;left:0;right:0;top:var(--nav-h);height:46%}
}
/* the header CTA in the light theme: a lighter grey than before */
body.theme-light .navbar:not(.on-dark) .nav-action .button{background:rgba(0,0,0,.025)}
body.theme-light .navbar:not(.on-dark) .nav-action .button:hover{background:rgba(0,0,0,.06)}
/* the wheel without a title over it: it sits closer to the work above */
.archive > .wheel-zone:first-child{margin-top:120px}
@media(max-width:767px){.archive > .wheel-zone:first-child{margin-top:72px}}

/* ══ mobile corners (2026-10-17) ═════════════════════════════════════════
   One radius family on phones: what was 16 becomes 12, everything else
   becomes 16. Pills, circles and square-edged frames are left alone. */
@media(max-width:767px){
  :root{--r-panel:12px}
  .card,.disc-visual,.film-frame,.pj-ep,.pj-frame:not(.pj-frame--bare),.vimeo,.work-media,.ad-cell,.op-canvas{border-radius:12px}
  .ph,.pj-swatch,.closing-panel,.pf-card,.op-node,
  .dv-parts,.dv-parts:has(> .pj-frame:nth-child(7)),.dv-parts .pj-frame,
  input,textarea,.select-display,.select-dropdown,.select-option{border-radius:16px}
}
/* phones: the capsule spans the screen inside the gutter, and the jury
   sits two by two */
@media(max-width:767px){
  .navcap{width:calc(100vw - 32px);max-width:none;justify-content:space-between}
}
@media(max-width:620px){
  .jury-row{grid-template-columns:repeat(2,minmax(0,1fr));gap:24px 16px}
  .jury-name span{white-space:normal}
}

/* insights: one colour for all body text, paragraphs, lists and their labels alike */
.post-body,.post-body p,.post-body li,.post-body strong{color:var(--ink-body)}

.nowrap{white-space:nowrap}

/* no lonely last word: headings balance their lines, text avoids an orphan */
h1,h2,h3{text-wrap:balance}
p,li,dd,figcaption{text-wrap:pretty}

/* home hero line: narrow enough that the second sentence starts its own line on desktop */
.hero-copy p{max-width:540px}

/* project rows on a phone: the line under the label sits left, like the label */
@media(max-width:960px){.pj-row > p{justify-self:start}}

/* Genesis episode on a phone: the panel centres its content, which left the
   phone with no width to size from; give the row the full width */
@media(max-width:960px){.pj-ep-inner{width:100%}}

/* phone fixes, project pages */
@media(max-width:767px){
  /* the small pictures in the "pieces" panels and the Genesis trios get a tighter corner */
  .dv-parts .pj-frame,.pj-trio .pj-frame{border-radius:8px}
  /* the Genesis stills sit three across, like the shots above them */
  .pj-strip--3.pj-stills{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
  .pj-big .pj-stills{gap:8px}
  /* the Chapplet phones stay inside the page margins, square */
  .pj-bleed{width:auto;margin-left:0}
  .pj-bleed .pj-frame{border-radius:12px;aspect-ratio:1}
  /* the landing page's buttons go under the laptop, in a row */
  .pj-mac-frame{aspect-ratio:auto;display:flex;flex-direction:column;align-items:center;padding:28px 0 24px}
  .pj-mac{width:92%}
  .pj-mac-nav{position:static;translate:none;flex-direction:row;justify-content:center;margin-top:16px}
}

/* contact intro: narrow enough that the second sentence starts its own line on desktop */
.page-head__note--narrow{max-width:556px}

/* the language switch: in the footer, beside the copyright, and in the
   phone menu's foot */
.footer-meta{display:flex;align-items:flex-end;gap:28px}
.footer-meta .lang-switch{color:inherit;text-decoration:none;transition:color var(--hover) var(--ease-soft)}
.nav-menu-meta .lang-switch{color:inherit;text-decoration:none}

/* the French hero line is longer: wide enough that its first sentence holds one line */
html[lang="fr"] .hero-copy p{max-width:700px}
