/* Soundscapes — the front page. Loads after site.css.

   © 2026 Oliver Freiländer · https://www.oliver-freilaender.com
   Concept, design and code, with the playground team.
   Licensed to Soundscapes FZC for this festival — see /LICENSE.txt. */

:root{
  /* the two cuts of the hero film, at their true pixel ratios. The cut is
     chosen by orientation, not by width, so the frame and the file always
     agree — a portrait tablet gets the portrait crop and the ratio to match. */
  --film-ar:1600/1070;
  --film-fit:70vw;
  --film-h:64svh;
  --film-w:min(var(--film-fit), calc(var(--film-h) * (var(--film-ar))));
  /* how far the film fades in from each edge — see .stage-vid */
  --film-fade-x:transparent 0,rgba(0,0,0,.18) 5%,rgba(0,0,0,.62) 12%,#000 22%,#000 78%,rgba(0,0,0,.62) 88%,rgba(0,0,0,.18) 95%,transparent 100%;
  --film-fade-y:var(--film-fade-x);
  /* the photographs sit a hair back from the paper */
  --xray-blur:.4px;
}

/* ==================================================
   CHROME — shell, call, map menu
   ================================================== */
.topbar{
  position:fixed;inset-inline:0;top:0;z-index:280;height:74px;
  pointer-events:none;
}
@media (max-width:900px){.topbar{height:64px}}

#menuShell{
  position:fixed;left:calc(var(--pad) - 6px);top:12px;z-index:290;
  padding:4px;opacity:0;
  transition:opacity .45s var(--ease) .1s,transform .8s var(--ease);
}
body.ready #menuShell{opacity:1}
#menuShell img{
  width:clamp(62px,6.6vw,84px);height:auto;
  transition:transform .4s var(--ease);
  clip-path:inset(0 0 0 0);
}
body.ready #menuShell img{animation:shellDraw 1.1s var(--ease) both}
@keyframes shellDraw{
  from{clip-path:inset(0 100% 0 0);opacity:.2}
  to{clip-path:inset(0 0 0 0);opacity:1}
}
#menuShell:hover img{transform:rotate(-6deg) scale(1.06)}
#menuShell:active img{transform:translateY(4px) scale(.95);transition:transform .09s ease-out}
#menuShell:active .ms-bars{transform:translate(-52%,-42%) translateY(4px) scale(.95)}
/* three strokes in the mouth of the wave; they fold into an x */
#menuShell .ms-bars{
  position:absolute;left:50%;top:50%;
  width:clamp(34px,3.6vw,46px);height:clamp(34px,3.6vw,46px);
  transform:translate(-52%,-42%);
  overflow:visible;pointer-events:none;
  transition:transform .16s var(--ease);
}
#menuShell .ms-bars path{
  fill:none;stroke:var(--ink);stroke-width:6.2;stroke-linecap:round;
  transform-origin:51px 51px;
  transition:transform .42s var(--ease),opacity .26s var(--ease);
}
body.menu-open #menuShell{z-index:310}
body.menu-open #menuShell .ms-bars path:nth-child(1){transform:translateY(11.5px) rotate(39deg)}
body.menu-open #menuShell .ms-bars path:nth-child(2){opacity:0}
body.menu-open #menuShell .ms-bars path:nth-child(3){transform:translateY(-11.5px) rotate(-39deg)}
/* past the header the wave crosses to the top right — the call has moved down */
body.past #menuShell{transform:translateX(calc(100vw - 100% - var(--pad) - (var(--pad) - 6px) + 4px))}

/* the call is a painted pill, not a CSS button */
#ctaBtn{
  position:fixed;top:15px;right:var(--pad);z-index:285;
  display:block;width:clamp(126px,13vw,168px);
  text-decoration:none;line-height:0;
  opacity:0;visibility:hidden;pointer-events:auto;
  transition:transform .8s var(--ease),background .25s;
}
#ctaBtn img{width:100%;height:auto}
body.ready #ctaBtn{opacity:1;visibility:visible;transition:opacity .6s var(--ease) .5s,transform .8s var(--ease)}
#ctaBtn:hover{transform:scale(1.05)}
#ctaBtn:active{transform:translateY(4px) scale(.97);transition:transform .09s ease-out}
body.past #ctaBtn{transform:translateY(calc(100svh - 100% - 30px))}
/* over the orange footer the painted pill needs paper under it */
body.foot-in #ctaBtn{background:var(--paper);border-radius:999px;padding:5px 10px;box-shadow:0 10px 26px -12px #06202480}
/* once the form is on screen it carries its own call */
body.form-in #ctaBtn{opacity:0;visibility:hidden;pointer-events:none}
@media (max-width:900px){#ctaBtn{width:clamp(112px,30vw,140px)}}
@media (max-width:380px){#ctaBtn{width:108px}}

/* nothing floats over an open sheet */
body.bo-open :is(.topbar,#menuShell,#ctaBtn),
body.lb-open :is(.topbar,#menuShell,#ctaBtn){opacity:0;pointer-events:none}

/* nothing moves under the opening — overflow:hidden on the body alone is not
   enough on iOS, the page has to leave the scroll flow entirely */
html.holding{overflow:hidden;height:100%}
body.holding{
  position:fixed;inset:0;width:100%;height:100svh;
  overflow:hidden;touch-action:none;overscroll-behavior:none;
}
body.holding :is(#menuShell,.topbar){pointer-events:none}

#menu{
  position:fixed;inset:0;z-index:300;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:var(--pad);background:var(--bg);overflow:hidden;
  opacity:0;pointer-events:none;transition:opacity .45s var(--ease);
}
body.menu-open #menu{opacity:1;pointer-events:auto}
.menu-wash{
  position:absolute;inset:-20%;z-index:0;pointer-events:none;opacity:.5;
  background:
    radial-gradient(46% 42% at 26% 30%,#7B5EA738 0%,transparent 70%),
    radial-gradient(50% 46% at 76% 66%,#E63B0D2e 0%,transparent 72%),
    radial-gradient(40% 40% at 62% 18%,#1C35501f 0%,transparent 70%);
  filter:blur(26px);animation:menuDrift 34s ease-in-out infinite;
}
@keyframes menuDrift{0%,100%{transform:none}50%{transform:translate(-3%,2.5%) scale(1.08)}}

.mapwrap{position:relative;z-index:2;width:min(1040px,94vw);aspect-ratio:16/10;max-height:74svh}
.map-thread{position:absolute;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none}
.map-thread .t-tall{display:none}
.map-thread path{
  fill:none;stroke:var(--orange);stroke-width:2.1;stroke-linecap:round;
  vector-effect:non-scaling-stroke;
  stroke-dasharray:1;stroke-dashoffset:1;pathLength:1;
}
body.menu-open .map-thread path{animation:threadDraw .9s var(--ease) forwards}
body.menu-open .map-thread g path:nth-child(1){animation-delay:.10s}
body.menu-open .map-thread g path:nth-child(2){animation-delay:.18s}
body.menu-open .map-thread g path:nth-child(3){animation-delay:.26s}
body.menu-open .map-thread g path:nth-child(4){animation-delay:.34s}
body.menu-open .map-thread g path:nth-child(5){animation-delay:.42s}
body.menu-open .map-thread g path:nth-child(6){animation-delay:.50s}
@keyframes threadDraw{to{stroke-dashoffset:0}}

.map{list-style:none;position:absolute;inset:0}
.node{position:absolute;left:var(--x);top:var(--y);transform:translate(-50%,-50%);opacity:0}
body.menu-open .node{animation:nodeIn .7s var(--ease) var(--d) forwards}
@keyframes nodeIn{
  from{opacity:0;transform:translate(-50%,-50%) scale(.86)}
  to{opacity:1;transform:translate(-50%,-50%) scale(1)}
}
.node a{
  display:flex;flex-direction:column;align-items:center;gap:9px;
  text-decoration:none;color:var(--ink);
  transform:rotate(var(--r,0deg));transition:transform .4s var(--ease);
}
.node a:hover,.node a:focus-visible{transform:rotate(0deg) scale(1.09)}
.glyph{
  display:block;width:clamp(74px,9.4vw,128px);aspect-ratio:1;
  animation:glyphFloat 9s ease-in-out infinite;animation-delay:calc(var(--d) * -7);
}
@keyframes glyphFloat{0%,100%{transform:none}50%{transform:translateY(-7px) rotate(1.4deg)}}
.glyph img{width:100%;height:100%;object-fit:contain;transition:filter .3s}
.node a:hover .glyph img{filter:saturate(1.25) contrast(1.06)}
.lab{
  font-family:var(--hand);font-weight:700;
  font-size:clamp(19px,2.2vw,30px);line-height:1;white-space:nowrap;
  transition:color .3s;
}
.node a:hover .lab{color:var(--orange)}

.menu-cta{
  position:absolute;left:50%;top:50%;
  transform:translate(-50%,-50%) rotate(-1.5deg);
  display:block;width:clamp(190px,21vw,268px);
  opacity:0;transition:transform .3s var(--ease),opacity .5s var(--ease) .55s;
}
.menu-cta img{width:100%;height:auto}
body.menu-open .menu-cta{opacity:1}
.menu-cta:hover{transform:translate(-50%,-50%) rotate(0deg) scale(1.06)}
.menu-cta:active{transform:translate(-50%,-50%) translateY(4px) scale(.98);transition:transform .09s ease-out}

.menu-fine{
  position:absolute;left:50%;bottom:clamp(16px,3.4vh,30px);transform:translateX(-50%);
  z-index:3;display:flex;align-items:center;gap:22px;
}
.menu-fine a{display:block;width:30px;height:30px;transition:transform .3s var(--ease)}
.menu-fine a:hover{transform:scale(1.14)}
.menu-fine svg{width:100%;height:100%;overflow:visible}
.menu-fine svg *{fill:none;stroke:var(--ink);stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round;transition:stroke .25s}
.menu-fine svg .solid{fill:var(--ink);stroke:none}
.menu-fine a:hover svg *{stroke:var(--orange)}
.menu-fine a:hover svg .solid{fill:var(--orange)}

@media (max-width:820px){
  /* the field stands upright and the marks walk down it */
  .mapwrap{width:94vw;aspect-ratio:auto;height:min(58svh,500px);max-height:none;margin:44px 0 128px}
  .map-thread .t-wide{display:none}
  .map-thread .t-tall{display:block}
  .map-thread path{stroke-width:1.8}
  .glyph{width:clamp(62px,19vw,88px)}
  .lab{font-size:clamp(16px,4.4vw,21px)}
  .node:nth-child(1){left:28%;top:8%}
  .node:nth-child(2){left:72%;top:22%}
  .node:nth-child(3){left:30%;top:38%}
  .node:nth-child(4){left:70%;top:54%}
  .node:nth-child(5){left:32%;top:70%}
  .node:nth-child(6){left:68%;top:86%}
  .menu-cta{
    width:clamp(180px,52vw,230px);
    top:auto;bottom:-96px;transform:translateX(-50%) rotate(-1.5deg);
  }
  .menu-cta:hover{transform:translateX(-50%) rotate(0deg) scale(1.05)}
  .menu-fine{bottom:clamp(26px,5vh,44px);gap:30px}
}

/* after the header the cursor becomes the little shell */
body.shellcur{
  cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='34' height='34' viewBox='0 0 34 34'%3E%3Cg fill='none' stroke='%231C3550' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M17 4c6.6 0 11.4 4.6 11.4 10.2 0 6.8-5.5 12.4-11.4 15.3C11.1 26.6 5.6 21 5.6 14.2 5.6 8.6 10.4 4 17 4z' fill='%23F7F4EC'/%3E%3Cpath d='M17 8.4c3.9 0 6.9 2.7 6.9 6 0 4.2-3.3 7.7-6.9 9.7-3.6-2-6.9-5.5-6.9-9.7 0-3.3 3-6 6.9-6z'/%3E%3Cpath d='M17 13c1.8 0 3.1 1.2 3.1 2.7 0 2-1.5 3.6-3.1 4.6-1.6-1-3.1-2.6-3.1-4.6 0-1.5 1.3-2.7 3.1-2.7z'/%3E%3C/g%3E%3C/svg%3E") 17 17, auto;
}

/* ==================================================
   INTRO — the circle draws itself, then flies to the corner
   ================================================== */
#intro{
  position:fixed;inset:0;z-index:200;background:var(--bg);
  overflow:hidden;pointer-events:none;
  transition:opacity .9s var(--ease);
}
#intro *{pointer-events:none}
#intro.done{opacity:0}
#intro[hidden]{display:none}
#introVid{
  position:absolute;left:50%;top:50%;
  width:min(310px,54vw);height:auto;
  transform:translate(-50%,-50%);
  mix-blend-mode:multiply;
  animation:introFloat 5.6s ease-in-out infinite;
}
@keyframes introFloat{
  0%,100%{transform:translate(-50%,-50%) translateY(0) rotate(-.6deg)}
  50%{transform:translate(-50%,-50%) translateY(-18px) rotate(.8deg)}
}

/* ==================================================
   HERO — one screen: ink field, film, name, date
   The film is a landscape drawing and is never cropped or stretched. What
   fills the rest of a phone screen is the artwork's own vein field, which is
   drawn portrait, plus the type. Rows share the slack, so the composition
   holds from a 320px phone to a 27" screen.
   ================================================== */
.stage{
  position:relative;overflow:hidden;
  min-height:100svh;
  display:grid;
  /* air / film / type / air / cue. Two thirds of the slack goes above the
     film, which sets the drawing at about the optical centre of a phone
     screen and carries the name and the date down into the lower half. */
  grid-template-rows:minmax(64px,1.25fr) auto auto minmax(16px,.62fr) auto;
  justify-items:center;align-items:center;
  padding-bottom:clamp(18px,3.4vh,34px);
}

/* the vein field: the artwork's letter-free ink, drawn 1200x2600, so a phone
   screen is almost exactly its own frame — cover crops nothing worth keeping */
.stage-field{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  pointer-events:none;mix-blend-mode:multiply;filter:blur(1.4px);
  opacity:0;transition:opacity 3.2s var(--ease) 1s;
}
body.ready .stage-field{opacity:.5}

/* no transform, opacity or filter on this wrapper: any of them would isolate
   the film and its multiply would blend against nothing */
.stage-film{
  position:relative;grid-row:2;
  width:var(--film-w);aspect-ratio:var(--film-ar);
  /* the phone cut is wider than the screen, and an item wider than its track
     is pinned to the start rather than centred. Symmetric margins centre it
     without a transform, which would isolate the film and kill the multiply. */
  margin-inline:calc((100vw - var(--film-w)) / 2);
}
.stage-halo{
  position:absolute;left:50%;top:50%;
  width:150%;height:auto;max-width:none;
  transform:translate(-50%,-50%);
  pointer-events:none;mix-blend-mode:multiply;
  filter:blur(2.4px) saturate(.92);
  opacity:0;transition:opacity 3.4s var(--ease) 1.2s;
  -webkit-mask-image:radial-gradient(closest-side,#000 16%,rgba(0,0,0,.72) 44%,rgba(0,0,0,.32) 70%,transparent 100%);
  mask-image:radial-gradient(closest-side,#000 16%,rgba(0,0,0,.72) 44%,rgba(0,0,0,.32) 70%,transparent 100%);
}
body.ready .stage-halo{opacity:.3}

.stage-vid{
  position:absolute;inset:0;width:100%;height:100%;object-fit:contain;
  /* multiply drops the drawing onto the paper of the page instead of laying a
     second sheet over it. Two things stop it drawing its own rectangle.
     One: the film's paper is white in the file. It was photographed at about
     187 of 255 — a warm grey — and multiplying by that darkened the page
     across the whole frame. assets/video-hero-recipe.txt has the correction,
     which is why nothing but saturation is left in the filter below.
     Two: the frame is feathered on all four sides, because the ink runs off
     every edge and a hard cut leaves a line. An ellipse cannot do this. Radii
     small enough to reach zero at the middle of an edge have already eaten the
     middle of the word; radii large enough to spare the word leave the edges
     at nine-tenths opacity, which was the rectangle. Two ramps intersected
     fade each edge on its own terms. */
  mix-blend-mode:multiply;
  filter:saturate(.95);
  transition:filter 7s cubic-bezier(.22,1,.36,1);
  -webkit-mask-image:linear-gradient(to right,var(--film-fade-x)),linear-gradient(to bottom,var(--film-fade-y));
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(to right,var(--film-fade-x)),linear-gradient(to bottom,var(--film-fade-y));
  mask-composite:intersect;
}
body.ready .stage-vid{filter:saturate(1.5)}

.stage-say{
  grid-row:3;text-align:center;
  padding-inline:clamp(16px,5vw,40px);
  margin-top:clamp(26px,5vh,44px);
}
.stage-say h1{display:grid;gap:.08em;justify-items:center}
.stage-where,.stage-when{
  font-family:var(--hand);font-weight:700;
  text-shadow:0 0 3px var(--bg),0 0 14px var(--bg),0 0 34px var(--bg);
}
.stage-where{
  font-size:clamp(40px,12.5vw,74px);line-height:.92;
  transform:rotate(-1.6deg);
  /* --redmix runs 0 to 1 across the first screen, so the turn to orange
     travels with the scroll instead of switching on you */
  color:color-mix(in oklab, var(--orange) calc(var(--redmix,0) * 100%), var(--tx));
}
.stage-when{font-size:clamp(19px,5.6vw,30px);line-height:1.05;transform:rotate(-.7deg)}

.stage-cue{
  grid-row:5;display:block;width:26px;color:var(--orange);
  opacity:0;transition:opacity .8s var(--ease) 1.6s;
}
body.ready .stage-cue{opacity:.85}
.stage-cue svg{width:100%;height:auto;animation:cueBob 2.8s ease-in-out infinite}
.stage-cue path{fill:none;stroke:currentColor;stroke-width:5;stroke-linecap:round;stroke-linejoin:round}
.stage-cue:hover{opacity:1}
@keyframes cueBob{0%,100%{transform:translateY(0)}50%{transform:translateY(7px)}}

@media (min-width:821px){
  .stage{grid-template-rows:minmax(74px,.7fr) auto auto minmax(10px,.6fr) auto}
  .stage-where{font-size:clamp(38px,4.2vw,60px)}
  .stage-when{font-size:clamp(19px,1.9vw,27px)}
  .stage-halo{width:132%}
  body.ready .stage-field{opacity:.42}
}
/* The phone cut is the same composition as the desktop one, only smaller —
   it used to be framed tighter, which forced a shallow fade and left the two
   hard edges the film was being blamed for. Same framing means it inherits the
   deep fade, sits inside the screen the way it does on a wide one, and comes
   apart into the paper on all four sides. */
:root[data-hero="mobile"]{
  --film-ar:1040/696;
  --film-fit:100vw;
  --film-h:44svh;
}
@media (max-width:820px){
  :root{--xray-blur:0px}
  .stage-halo{width:168%;filter:blur(2.2px) saturate(.92)}
  body.ready .stage-halo{opacity:.32}
}

/* ==================================================
   THE SCROLL LINE
   ================================================== */
#mawimbi{
  position:absolute;inset:0;width:100%;height:100%;
  pointer-events:none;z-index:60;
  /* born below the header, so no stub hangs above it */
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,transparent 116px,#000 220px);
  mask-image:linear-gradient(to bottom,transparent 0,transparent 116px,#000 220px);
}
#mawimbi path{stroke:var(--orange);fill:none;stroke-width:2.1;stroke-linecap:round;opacity:.92}


/* ==================================================
   PLATES
   ================================================== */
.plate{position:relative;padding:clamp(90px,13vh,150px) var(--pad)}
.plate-label{display:flex;justify-content:space-between;align-items:baseline;gap:20px;margin-bottom:clamp(36px,6vh,64px)}
.hh{
  font-family:var(--head);font-weight:400;line-height:1.18;color:var(--tx);
  font-size:clamp(34px,4.9vw,72px);transform:rotate(-1.4deg);
  /* Summer Soul's caps run 2336-2723 upm, so uppercase still reads as a hand */
  text-transform:uppercase;letter-spacing:.004em;
}
.hh i{font-style:normal;color:var(--orange)}
.hh-xl{font-size:clamp(46px,8vw,128px);line-height:1.34;transform:rotate(-1.2deg)}
.hh-xl span{display:block}
.hh-xl span:nth-child(2){margin-left:.22em;font-size:.78em}
.hh-xl .nb{white-space:nowrap}

.story-grid{
  display:grid;grid-template-columns:1.5fr .8fr;gap:clamp(32px,6vw,90px);
  margin-top:clamp(44px,7vh,76px);
}
.story-copy p{font-family:var(--serif);font-size:clamp(17px,1.7vw,21px);line-height:1.7;max-width:58ch}
.story-copy p+p{margin-top:18px}
.story-copy .sw{color:var(--lilac);font-style:italic;font-family:var(--serif)}
.story-more a{
  font-family:var(--hand);font-weight:700;font-size:clamp(19px,2vw,24px);
  color:var(--orange);text-decoration:none;border-bottom:2px solid #E63B0D55;
}
.story-more a:hover{border-color:var(--orange)}

/* ---------- washes ---------- */
.wash{
  position:absolute;pointer-events:none;z-index:-1;
  opacity:0;transform:scale(1.05);
  /* the ellipse reaches zero on all four edges, so photo and grain dissolve
     together and neither can leave a border behind */
  -webkit-mask-image:
    linear-gradient(to right,transparent 0,rgba(0,0,0,.22) 9%,rgba(0,0,0,.72) 22%,#000 34%,#000 66%,rgba(0,0,0,.72) 78%,rgba(0,0,0,.22) 91%,transparent 100%),
    linear-gradient(to bottom,transparent 0,rgba(0,0,0,.22) 9%,rgba(0,0,0,.72) 22%,#000 34%,#000 66%,rgba(0,0,0,.72) 78%,rgba(0,0,0,.22) 91%,transparent 100%);
  -webkit-mask-composite:source-in;
  mask-image:
    linear-gradient(to right,transparent 0,rgba(0,0,0,.22) 9%,rgba(0,0,0,.72) 22%,#000 34%,#000 66%,rgba(0,0,0,.72) 78%,rgba(0,0,0,.22) 91%,transparent 100%),
    linear-gradient(to bottom,transparent 0,rgba(0,0,0,.22) 9%,rgba(0,0,0,.72) 22%,#000 34%,#000 66%,rgba(0,0,0,.72) 78%,rgba(0,0,0,.22) 91%,transparent 100%);
  mask-composite:intersect;
  transition:opacity 1.1s var(--ease),transform 2.6s var(--ease);
}
/* it hangs off the bottom of the section rather than the top, so it lands
   between the two headings. It is absolute and z-index -1, so dropping it
   moves nothing: the gap between the sections is exactly what it was. */
.wash-story{right:-4%;top:auto;bottom:-30%;width:min(74vw,1180px)}
.wash img{width:100%;height:auto;filter:saturate(.95) blur(var(--xray-blur))}
.wash.in{opacity:.44;transform:none}
body.scrolling .wash.in{opacity:.56}
/* at rest the photographs sit back in the paper; while the page moves they
   surface, as if catching the sun */
.wash img,.breather img,.foot-poster img{
  transition:filter 1.1s var(--ease),opacity 1.1s var(--ease),transform 3s var(--ease);
}
body.scrolling :is(.wash,.breather,.foot-poster) img{
  filter:saturate(1.22) contrast(1.06) brightness(1.06) blur(var(--xray-blur));
}

/* ---------- full-bleed drawing ---------- */
.breather{
  position:relative;height:clamp(440px,86vh,820px);overflow:hidden;
}
/* the mask used to live here. A masked element is a stacking context, and the
   drawing inside it then had nothing to blend against but this box — which is
   why the spire arrived with its own white paper. Frame, mask and blend all
   sit on the image now. */
.b-frame{position:absolute;inset:0;pointer-events:none}
.breather img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  mix-blend-mode:multiply;
  filter:saturate(.98);opacity:0;
  /* --spire is written by the scroll handler and runs 1 to 1.28 across the
     window; the extra .1 is the arrival, and it lands on the first frame the
     drawing is seen. The mask reaches zero at 78%, which is exactly the part
     still inside the frame at full zoom, so growing never uncovers an edge. */
  transform:scale(calc(var(--spire,1) * 1.1));
  transition:opacity 2.4s var(--ease),transform .45s linear;
  -webkit-mask-image:radial-gradient(54% 54% at 50% 50%,#000 0%,#000 34%,rgba(0,0,0,.8) 52%,rgba(0,0,0,.3) 68%,transparent 78%);
  mask-image:radial-gradient(54% 54% at 50% 50%,#000 0%,#000 34%,rgba(0,0,0,.8) 52%,rgba(0,0,0,.3) 68%,transparent 78%);
}
.breather.in img{opacity:1;transform:scale(var(--spire,1))}

/* ==================================================
   THE FIVE BEINGS — left, right, left
   ================================================== */
.being{
  display:grid;grid-template-columns:1.05fr .95fr;
  gap:clamp(20px,4vw,64px);align-items:center;
  padding:clamp(30px,6vh,70px) 0;cursor:pointer;
}
.being:nth-child(even){grid-template-columns:.95fr 1.05fr}
.being:nth-child(even) .b-media{order:2}
.being:nth-child(even) .b-meta{order:1;text-align:right}

.b-media{
  position:relative;cursor:grab;
  touch-action:pan-y;-webkit-user-select:none;user-select:none;
}
.b-media.grabbing{cursor:grabbing}
/* the movement lives on the drawing itself; on the wrapper it would isolate
   the video and kill the multiply */
.b-media :is(video,img.poster){
  width:min(100%,560px);height:auto;margin-inline:auto;
  mix-blend-mode:multiply;
  transform:translate(var(--dx,0px),var(--dy,0px))
            translate(var(--fx,0px),var(--fy,0px))
            rotate(var(--fr,0deg));
  transition:transform 4.6s cubic-bezier(.42,0,.36,1);
}
.b-media.grabbing :is(video,img.poster){transition:none}
.being:nth-child(odd) .b-media :is(video,img.poster){margin-left:0}
.being:nth-child(even) .b-media :is(video,img.poster){margin-right:0}

.whisper{
  position:absolute;left:50%;top:-13%;z-index:3;
  width:min(268px,68%);
  background:var(--paper);border:1.5px solid var(--ink);
  border-radius:225px 25px 255px 25px/25px 255px 25px 225px;
  padding:13px 17px;opacity:0;pointer-events:none;
  font-family:var(--hand);font-weight:600;font-size:18px;line-height:1.18;color:var(--tx);
  transform:translate(var(--dx,0px),var(--dy,0px))
            translate(var(--fx,0px),var(--fy,0px))
            translateX(-50%) rotate(-2deg) scale(.9);
  /* it used to snap open the instant the being was touched; now the creature
     is what you see first and the words arrive a beat later */
  transition:opacity .55s var(--ease) .18s,transform .55s var(--ease) .18s;
}
@media (hover:hover) and (pointer:fine){
  .b-media:hover .whisper{
    opacity:1;
    transform:translate(var(--dx,0px),var(--dy,0px))
              translate(var(--fx,0px),var(--fy,0px))
              translateX(-50%) rotate(-2deg) scale(1);
  }
}
/* no hover on a touch screen, so it opens by itself as the being comes up */
.b-media.peek .whisper{
  opacity:.94;pointer-events:auto;
  transform:translate(var(--dx,0px),var(--dy,0px))
            translate(var(--fx,0px),var(--fy,0px))
            translateX(-50%) rotate(-2deg) scale(.97);
}
.b-media.dismissed .whisper{opacity:0;pointer-events:none}
.whisper-x{
  position:absolute;top:-9px;right:-9px;width:24px;height:24px;
  display:none;align-items:center;justify-content:center;
  border-radius:999px;border:1.2px solid #1C355059;background:var(--paper);
  color:var(--ink);font-family:var(--sans);font-size:13px;line-height:1;
  pointer-events:auto;
}
.b-media.peek .whisper-x{display:flex}
.whisper-x:hover{color:var(--orange);border-color:var(--orange)}

.b-meta h3{
  font-family:var(--head);font-weight:700;
  font-size:clamp(40px,6vw,74px);line-height:.95;
  transform:rotate(-1.2deg);padding-right:.18em;
}
.being:nth-child(even) .b-meta h3{transform:rotate(1.2deg)}
.b-meta .role{display:block;font-family:var(--serif);font-style:italic;font-size:clamp(16px,1.6vw,20px);margin-top:10px}
.b-meta .offer{
  display:inline-block;margin-top:14px;padding:0 0 2px;
  text-decoration:none;cursor:pointer;
  font-weight:600;font-size:10.5px;letter-spacing:.26em;text-transform:uppercase;
  border-bottom:1.7px solid var(--orange);
  transition:color .2s,border-color .2s;
}
.b-meta .offer:hover{color:var(--orange)}

.pilg-foot{
  margin-top:clamp(40px,6.5vh,72px);text-align:center;
  font-family:var(--serif);font-style:italic;
  font-size:clamp(21px,2.4vw,30px);line-height:1.4;
  max-width:24ch;margin-inline:auto;
}

/* ---------- the being sheet ---------- */
.bo{position:fixed;inset:0;z-index:90;overflow:hidden;isolation:isolate;background:var(--bg)}
.bo[hidden]{display:none}
.bo-bg{position:absolute;inset:0;background:var(--bg)}
.bo video{
  position:absolute;left:56%;top:50%;z-index:2;
  width:min(96vw,1400px);height:auto;
  mix-blend-mode:multiply;
  /* --dx/--dy belong to the drag, --fx/--fy to the drift, kept apart so
     picking the creature up never fights its own floating */
  transform:translate(-50%,-50%)
            translate(var(--dx,0px),var(--dy,0px))
            translate(var(--fx,0px),var(--fy,0px))
            rotate(var(--fr,0deg)) scale(.8);
  opacity:0;visibility:hidden;   /* a video with no frame yet paints white */
  transition:opacity .6s var(--ease),transform .7s var(--ease);
  cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none;
}
.bo.ready video{visibility:visible}
.bo.open video{
  opacity:1;
  transform:translate(-50%,-50%)
            translate(var(--dx,0px),var(--dy,0px))
            translate(var(--fx,0px),var(--fy,0px))
            rotate(var(--fr,0deg)) scale(1);
  transition:opacity .6s var(--ease),transform 4.2s cubic-bezier(.42,0,.36,1);
}
.bo video.grabbing{cursor:grabbing;transition:opacity .6s var(--ease)}
.bo-txt{
  position:absolute;left:26%;top:52%;z-index:4;
  width:min(460px,42vw);max-height:74svh;overflow:auto;overscroll-behavior:contain;
  transform:translate(-50%,-50%) translateY(24px);
  background:#F1EEE4f2;
  backdrop-filter:blur(16px) saturate(.9);-webkit-backdrop-filter:blur(16px) saturate(.9);
  border:1.5px solid var(--line);
  box-shadow:0 40px 90px -50px #06202459;
  border-radius:255px 20px 225px 20px/20px 225px 20px 255px;
  padding:clamp(24px,4vw,44px);
  opacity:0;transition:opacity .6s var(--ease) .25s,transform .6s var(--ease) .25s;
}
.bo.open .bo-txt{opacity:1;transform:translate(-50%,-50%)}
.bo-txt h2{font-family:var(--head);font-weight:400;font-size:clamp(32px,4.4vw,54px);line-height:.9;margin-top:6px}
.bo-road{
  display:inline-block;margin-top:6px;
  font-family:var(--sans);font-weight:600;font-size:10.5px;
  letter-spacing:.24em;text-transform:uppercase;color:var(--orange);
  text-decoration:none;border-bottom:1.7px solid var(--orange);padding-bottom:3px;
  transition:color .2s,border-color .2s;
}
.bo-road:hover{color:var(--ink);border-color:var(--ink)}
.bo-txt .role{display:block;font-family:var(--serif);font-style:italic;margin:8px 0 18px;font-size:17px}
.bo-txt p{font-family:var(--serif);font-size:15.5px;line-height:1.65;margin-bottom:12px}
.bo-txt ul{margin:0 0 12px 18px;font-family:var(--serif);font-size:15.5px;line-height:1.65}
.bo-close{
  position:fixed;top:16px;left:var(--pad);z-index:96;
  display:inline-flex;align-items:center;gap:8px;
  font-family:var(--hand);font-weight:700;font-size:15px;
  background:#F1EEE4cc;border:1.2px solid #1C355059;border-radius:999px;
  padding:5px 13px 6px;color:var(--ink);opacity:.82;
  transition:color .25s,border-color .25s,transform .3s var(--ease);
}
.bo-close:hover{color:var(--orange);border-color:var(--orange);transform:translateY(-1px)}
.bo-nav{
  position:absolute;top:50%;left:clamp(8px,2vw,26px);z-index:5;
  transform:translateY(-50%);
  width:46px;height:46px;border-radius:999px;
  background:var(--paper);border:1.5px solid var(--ink);
  font-family:var(--serif);font-size:26px;line-height:1;color:var(--ink);
  box-shadow:0 8px 24px -10px #1C355066;
  transition:color .25s,border-color .25s,transform .3s var(--ease);
}
.bo-nav.next{left:auto;right:clamp(8px,2vw,26px)}
.bo-nav:hover{color:var(--orange);border-color:var(--orange);transform:translateY(-50%) scale(1.08)}
@media (max-width:1100px){
  .bo video{left:54%;top:44%;width:min(118vw,900px)}
  .bo-txt{left:46%;top:70%;width:min(560px,86vw);max-height:46svh}
}

/* ==================================================
   THE SHELL — the sign-up
   ================================================== */
.shell-plate{overflow:visible}
.shell-grid{display:grid;grid-template-columns:.9fr 1.1fr;gap:clamp(20px,4vw,60px);align-items:center}
.shell-media{position:relative}
/* a soft field of paper: the shell multiplies against flat ground and reads
   solid, while the drawing keeps breathing all around it */
.shell-media::before{
  content:"";position:absolute;left:50%;top:46%;transform:translate(-50%,-50%);
  width:min(124%,660px);height:min(112%,560px);pointer-events:none;
  background:var(--bg);
  -webkit-mask-image:radial-gradient(50% 50% at 50% 50%,#000 0%,#000 42%,rgba(0,0,0,.6) 66%,transparent 100%);
  mask-image:radial-gradient(50% 50% at 50% 50%,#000 0%,#000 42%,rgba(0,0,0,.6) 66%,transparent 100%);
}
/* positioned, but no z-index: a stacking context here would blend the
   multiply against nothing and turn it opaque */
.shell-media :is(video,img){
  position:relative;width:min(100%,520px);height:auto;margin-inline:auto;
  transform:scaleX(-1);mix-blend-mode:multiply;
}
.shell-media .psst{
  display:block;margin:0 auto 14px;transform:rotate(-2deg);text-align:center;
  font-family:var(--hand);font-weight:600;font-size:clamp(20px,2.2vw,28px);
  max-width:340px;line-height:1.05;
}
.shell-media .psst b{color:var(--orange)}

.shell-form{
  position:relative;background:var(--paper);
  border:2px solid var(--ink);
  border-radius:48% 52% 55% 45%/60% 48% 52% 40%;
  padding:clamp(34px,5vw,64px) clamp(30px,5vw,70px);
  box-shadow:0 30px 70px -30px #1C355066, inset 0 0 60px #1C355012;
  transform:rotate(-.8deg);
}
.shell-form h2{font-family:var(--hand);font-weight:700;font-size:clamp(34px,4.2vw,54px);line-height:.95;transform:rotate(-1deg)}
.shell-form h2 i{font-style:normal;color:var(--orange)}
.shell-form .sub{font-family:var(--serif);font-style:italic;font-size:15.5px;margin:12px 0 26px}
.sf-field{margin-bottom:20px}
.sf-field label{
  display:block;margin-bottom:2px;transform:rotate(-.8deg);
  font-family:var(--hand);font-weight:600;font-size:21px;
}
.sf-field label b{color:var(--orange);font-weight:600}
.sf-field :is(input,textarea){
  width:100%;background:none;border:0;border-bottom:1.6px solid var(--ink);
  border-radius:0;resize:none;color:var(--tx);
  font-family:var(--serif);font-size:17px;padding:6px 2px;
}
.sf-field textarea{min-height:54px;line-height:1.5}
.sf-field :is(input,textarea)::placeholder{color:#1C355075;font-style:italic;font-size:15px}
.sf-field :is(input,textarea):focus{outline:none;border-color:var(--orange)}
.shell-form .send{
  margin-top:14px;display:inline-flex;align-items:center;
  font-family:var(--hand);font-weight:700;font-size:clamp(21px,2.3vw,28px);
  color:var(--paper);background:var(--orange);
  padding:14px 30px 15px;border-radius:999px;
  box-shadow:0 14px 34px -14px #E63B0Dab;transform:rotate(-1.2deg);
  transition:background .25s,transform .3s var(--ease),box-shadow .3s;
}
.shell-form .send:hover{background:#C8300A;transform:rotate(0deg) scale(1.04);box-shadow:0 18px 40px -14px #E63B0Dc4}
.shell-form .send:active{transform:rotate(0deg) translateY(4px) scale(.98);transition:transform .09s ease-out}
#shellNote{
  display:none;margin-top:18px;transform:rotate(-1deg);
  font-family:var(--hand);font-weight:600;font-size:24px;color:var(--lilac);
}
#shellNote.show{display:block}
.shell-fine{margin-top:18px;font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:#1C355080}

/* ---------- socials ---------- */
.socials{display:grid;grid-template-columns:1fr 1fr;border-block:1px solid var(--ink)}
.socials a{display:block;text-decoration:none;padding:clamp(24px,4vh,42px) var(--pad);transition:background .35s var(--ease)}
.socials a+a{border-left:1px solid var(--ink)}
.socials a:hover{background:var(--orange)}
.socials a:hover :is(.wobj,.slabel){color:var(--bg)}
.socials .slabel{
  display:block;margin-bottom:8px;
  font-weight:600;font-size:10px;letter-spacing:.24em;text-transform:uppercase;
}
.socials .wobj{
  display:block;font-family:var(--hand);font-weight:700;
  font-size:clamp(38px,6.5vw,92px);line-height:.85;transform:rotate(-1.4deg);
}
.socials a:nth-child(2) .wobj{transform:rotate(1.2deg)}

/* ---------- consent: a quiet bar, not a wall ---------- */
#consentBar{
  position:fixed;left:16px;bottom:16px;z-index:85;
  max-width:min(620px,calc(100vw - 110px));
  background:var(--paper);border:1.5px solid var(--ink);
  border-radius:255px 18px 225px 18px/18px 225px 18px 255px;
  padding:14px 22px;display:flex;gap:20px;align-items:baseline;flex-wrap:wrap;
  font-size:13px;
  transform:translateY(140%);transition:transform .6s var(--ease);
}
#consentBar.show{transform:none}
#consentBar[hidden]{display:none}
#consentBar .hand{font-size:19px;transform:rotate(-1deg);white-space:nowrap}
#consentBar p{flex:1;min-width:220px;line-height:1.5}
#consentBar .cbtns{display:flex;gap:18px}

/* ==================================================
   PHONE
   ================================================== */
@media (max-width:820px){
  .plate{padding-block:clamp(56px,9vh,90px)}
  .plate-label{flex-direction:column;align-items:flex-start;gap:4px}
  .hh{line-height:1.24;margin-bottom:.35em}
  .hh-xl{line-height:1.3;font-size:clamp(36px,10vw,52px)}

  .story-grid,.shell-grid{grid-template-columns:1fr}
  .story-grid{margin-top:clamp(30px,5vh,44px)}
  /* behind the text a photograph only makes the text harder to read. On a
     phone it stops being a wash and becomes a picture, after the words. */
  #story{display:flex;flex-direction:column}
  #story .wash-story{
    position:relative;order:9;inset:auto;width:100%;
    margin-top:clamp(30px,5vh,48px);z-index:0;
  }
  #story .wash-story.in{opacity:.78}
  /* full strength here; a soft photograph on a phone reads as a bad upload */
  .wash img,.breather img,.foot-poster img{filter:none}
  .wash{width:104vw;left:-2vw;right:auto}
  .wash.in{opacity:.5}

  .breather{height:clamp(420px,64svh,620px);margin-block:clamp(26px,6vh,52px)}

  .being,.being:nth-child(even){grid-template-columns:1fr}
  .being:nth-child(even) .b-media{order:1}
  .being:nth-child(even) .b-meta{order:2;text-align:left}
  /* the card was nearly the size of the drawing it belonged to */
  .b-media{padding-top:96px}
  .whisper{width:min(200px,58%);top:0;padding:9px 12px;font-size:14px;line-height:1.24}
  .pilg-foot{font-size:clamp(19px,5.2vw,24px);max-width:20ch}

  .bo video{left:50%;top:38%;width:min(150vw,640px)}
  .bo-txt{left:50%;top:72%;width:min(560px,92vw);max-height:44svh}

  .socials{grid-template-columns:1fr}
  .socials a+a{border-left:0;border-top:1px solid var(--ink)}

  .shell-media .psst{transform:rotate(-1deg);margin:0 auto 10px}
  .shell-form{border-radius:36px}

  /* the bar was built as a wide strip with a very organic radius; squeezed
     into a phone it kept pushing its own content past the corners */
  #consentBar{
    left:12px;right:12px;bottom:12px;max-width:none;
    border-radius:26px 12px 24px 12px/12px 24px 12px 26px;
    padding:16px 18px 18px;gap:10px;align-items:flex-start;
  }
  #consentBar .hand{white-space:normal;font-size:17px;line-height:1.2}
  #consentBar p{min-width:0;flex:1 1 100%}
  #consentBar .cbtns{width:100%;gap:24px;margin-top:2px;flex-wrap:wrap}
}

@media (prefers-reduced-motion:reduce){
  #intro{display:none}
  .menu-wash,.glyph,.stage-cue svg{animation:none}
  .node{opacity:1;animation:none}
  .map-thread path{stroke-dashoffset:0;animation:none}
  .menu-cta{opacity:1}
  #mawimbi path{stroke-dashoffset:0}
  .b-media :is(video,img.poster){transition:none;--fx:0px;--fy:0px;--fr:0deg}
  .bo video,.bo.open video{transition:none;--fx:0px;--fy:0px;--fr:0deg}
  .stage-where{transform:rotate(-1.6deg)}
}
