/* Focused typography and a consistent, stable action system. */
/* Hero title size law lives in polish.css (loads after this file). Keep base styling only. */
.hero .hero-title{animation:none;letter-spacing:-.04em;line-height:1.04;pointer-events:auto;perspective:900px;overflow-x:clip}
.hero-title .hero-line{display:block;overflow:clip;color:var(--stone);padding-bottom:.035em;margin-bottom:-.035em}
.hero-line>span{display:block;transform:translateX(var(--type-shift,0px));transition:transform .45s var(--ease)}
.hero-title .hero-word{font-size:1.28em;line-height:1.08;margin:9px 0 10px 14px;transform:rotate(-2deg);color:var(--coral);padding:0 .12em .03em;isolation:isolate;display:inline-block}
.hero-title .you-stroke{position:absolute;left:-12%;top:-10%;width:127%;height:128%;z-index:-1;stroke:var(--coral);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;overflow:visible;transform:rotate(2deg);pointer-events:none}
.you-stroke path{stroke-dasharray:1;stroke-dashoffset:0}
@media(hover:hover) and (pointer:fine){.hero.is-ready .hero-word:hover .you-stroke path{animation:draw-you .64s cubic-bezier(.32,.72,.24,1) both}}
.hero-title .hero-char{transition:transform .5s var(--ease),color .3s}
.hero-w{display:inline-block}
/* One ordered entrance, keyed entirely off .hero.is-ready — which studio.js only sets once the
   preloader curtain has actually left. Before this, only the headline words were gated: the sub
   copy ran overhaul.css's unconditional `.hero-bottom{animation:answer…}` on every load, so it
   finished *behind* the curtain, and the stroke drew itself under the words instead of after.
   Order: words → sub + CTA → proof → stroke around "you." → baseline rule.
   `word-arrive-rot` exists because the plain `word-arrive` ends at `transform:none`, which would
   straighten the "you." tile out of its -5deg tilt before `word-float` took over. */
.hero.is-ready .hero-w{animation:word-arrive .4s var(--ease) both;animation-delay:calc(var(--w)*50ms)}
.hero.is-ready .hero-word{animation:word-arrive-rot .44s var(--ease) .18s both}
.hero.is-ready .you-stroke path{animation:draw-you .5s cubic-bezier(.32,.72,.24,1) .2s both}
.hero .hero-bottom{animation:none}
.hero.is-ready .hero-bottom{animation:hero-rise .3s var(--ease) .3s both}
.hero.is-ready .hero-proof{animation:hero-rise .3s var(--ease) .38s both}
.hero.is-ready .hero-baseline{animation:hero-rise .3s var(--ease) .46s both}
.hero.is-ready .sculpture canvas{animation:hero-emerge .6s cubic-bezier(.23,1,.32,1) .3s both}
/* The supporting copy is hidden from the moment studio.js runs, NOT from the moment `is-ready`
   lands. The curtain finishes fading ~50ms before loader.js sets `hidden`, so gating on is-ready
   alone let the copy sit fully visible for those frames and then snap back to 0 to rise in.
   Declared here and switched on by JS, so no-JS and reduced-motion visits see a complete hero. */
.hero.is-entering .hero-w,.hero.is-entering .hero-bottom,.hero.is-entering .hero-proof,.hero.is-entering .hero-baseline,.hero.is-entering .sculpture canvas{opacity:0}
.hero.motion-paused .hero-w,.hero.motion-paused .hero-word,.hero.motion-paused .you-stroke path,.hero.motion-paused .hero-bottom,.hero.motion-paused .hero-proof,.hero.motion-paused .hero-baseline,.hero.motion-paused .sculpture canvas{animation-play-state:paused}
@keyframes word-arrive{from{opacity:0;transform:translateY(16px) rotate(2deg)}to{opacity:1;transform:none}}
@keyframes draw-you{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
@keyframes word-arrive-rot{from{opacity:0;transform:rotate(-2deg) translateY(16px)}to{opacity:1;transform:rotate(-2deg)}}
@keyframes hero-rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes hero-emerge{from{opacity:0;transform:translateY(8px) scale(.985)}to{opacity:1;transform:none}}
.sculpture{position:relative;isolation:isolate}
.sculpture canvas{position:relative;z-index:1;cursor:grab;touch-action:pan-y}.sculpture canvas.is-tensioned{cursor:grabbing}
.ribbon-control{position:absolute;inset:0;z-index:2;border:0;border-radius:50%;background:transparent;pointer-events:none}.ribbon-control:focus-visible{outline:2px solid var(--coral);outline-offset:-8px}.ribbon-status{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.hero-copy,.sculpture,.hero-baseline{transition:opacity .72s var(--ease),transform .72s var(--ease)}.section-intro{position:relative}.section-intro::after{content:'';position:absolute;left:0;right:0;bottom:-18px;height:1px;background:linear-gradient(90deg,var(--coral),var(--gold),var(--lilac),transparent);transform:scaleX(0);transform-origin:left;transition:transform 1.1s var(--ease)}.section-intro.is-shown::after{transform:scaleX(1)}.hero.is-departing .hero-copy{opacity:.6;transform:translateY(-8%)}.hero.is-departing .sculpture{opacity:.6;transform:translateY(-8%) scale(.985)}.hero.is-departing .hero-baseline{opacity:0;transform:translateY(-12px)}
.hero-bottom{margin-top:32px;max-width:540px}.hero-bottom p{font-size:14px;line-height:1.8}
.hero-bottom .button,.header .small-cta{border-radius:8px;gap:22px;padding:7px 7px 7px 22px;background:var(--coral);border:1px solid transparent;color:var(--plum);box-shadow:none;transform:none;transition:background .25s var(--ease),box-shadow .25s var(--ease)}
.header .small-cta{font-size:14px;min-height:48px;white-space:nowrap}
.header .small-cta::before{display:none}
.button-arrow{display:grid;place-items:center;width:44px;height:44px;background:var(--plum);color:var(--stone);border-radius:4px;overflow:hidden;flex:none}
.small-cta .button-arrow{width:34px;height:34px}.button-arrow svg{width:21px;height:21px;transition:transform .3s var(--ease)}
@media(hover:hover){.hero-bottom .button:hover,.header .small-cta:hover{background:var(--lilac);box-shadow:0 8px 20px #10091640}.hero-bottom .button:hover svg,.header .small-cta:hover svg{transform:translate(2px,-2px)}}
.hero-bottom .button:active,.header .small-cta:active{box-shadow:none;translate:0 1px}
.hero-baseline{margin-top:38px}.hero-baseline a{font-size:12px}
.playground .playground-note{font-size:15px;font-weight:500;line-height:1.75;margin-top:20px;max-width:440px;padding-top:20px;border-top:1px solid #bdaabb}
.section-intro h2{font-size:clamp(3rem,5.7vw,5rem)}
.contact .build-heading{font-size:clamp(2.5rem,4.7vw,4.6rem);line-height:1.07}.contact .build-spark{display:none}.contact .build-heading>span::after{height:2px}
@media(min-width:1101px){.hero{grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);padding-top:52px}.sculpture{height:auto;min-height:440px;margin-top:0}.hero-baseline{padding-top:18px;padding-bottom:22px}}
@media(min-width:761px) and (max-width:1100px){.header .primary-nav{gap:14px}.header .small-cta{padding-left:14px;gap:12px}.hero-title .hero-word{margin-left:8px}.sculpture{height:auto;min-height:340px}}
@media(max-width:760px){.hero-title .hero-word{font-size:1.25em;margin:13px 0 12px 8px}.hero-bottom{margin-top:30px}.header .small-cta{font-size:12px;padding:6px 6px 6px 12px;gap:10px;border-radius:6px}.small-cta .button-arrow{width:30px;height:30px}.header .nav-end{gap:8px}.header .brand{font-size:30px}.hero .sculpture{height:220px;margin-top:-40px}.hero-title .you-stroke{stroke-width:2.4}.hero-baseline{margin-top:12px}.hero-bottom .button{padding:7px 7px 7px 20px;min-height:58px}.playground .playground-note{font-size:14px}.contact .build-heading{font-size:clamp(2.3rem,8.5vw,4rem)}}
@media(max-width:360px){.header .small-cta{display:flex;font-size:11px;min-height:42px;padding-left:9px;gap:7px}.header .small-cta .button-arrow{width:24px;height:28px}.header .brand{font-size:26px}.header{padding-inline:5%;gap:6px}.header .brand span{font-size:9px;margin-left:3px}.header .nav-end{gap:4px}.header .menu{width:28px}.hero .hero-title{font-size:2.8rem}}
@media(prefers-reduced-motion:reduce){.hero-title .hero-word{transform:none}.hero-title .hero-line>span{transform:none!important}.hero.is-ready .hero-line>span,.hero.is-ready .hero-w,.hero.is-ready .hero-word,.hero.is-ready .hero-bottom,.hero.is-ready .hero-proof,.hero.is-ready .hero-baseline,.hero.is-ready .sculpture canvas,.hero.is-ready .you-stroke path{animation:none}.you-stroke path{stroke-dashoffset:0}.sculpture canvas{clip-path:none!important}.you-stroke path{stroke-dashoffset:0}.hero-title .hero-char{transform:none!important}.button-arrow svg{transition:none}.hero-copy,.sculpture,.hero-baseline{transition:none}.hero.is-departing .hero-copy,.hero.is-departing .sculpture,.hero.is-departing .hero-baseline{opacity:1;transform:none}}

/* --- Section intros: one type size, one measure, one alignment -------------------------------
   The bands were always right about the part that matters: the deck hangs at the headline's feet
   (`align-items:end`), so a two-line deck bookends a two-line headline. What was wrong was
   everything around it, measured at 1440px: the deck ran at 14px in Our work and The deal, 16px
   in Services and 17px in the playground; its measure was unset / 380px / 420px; and a
   `margin-right:5%` / `3%` / `10px` dragged it 40-70px inside the content edge so it aligned to
   nothing — not to the headline, not to the film card, not to the rows below it. That is the
   "alot of space around them" in the first place: 273px of dead air between headline and deck
   at 1440, 548px at 1920.

   Then I made it worse. I read "space around them" as a vertical problem and re-aligned the deck
   to the TOP of the headline. Affan: "you just ruined the text beside the headlines." He was
   right, and the measurement agrees: a 55px-tall deck pinned 2px under the top of a 168-212px
   band leaves its last line 120-190px above the headline's baseline, which is the one alignment
   the eye reads as deliberate. The deck was a speck in a corner.

   Now (2026-09-22): bottom alignment was only half the fix. Measured at 1440 with the deck
   bottom-flush to the heading, the void sat entirely ABOVE the deck — ownership measured 212px
   heading vs 58px deck = 154px of dead air above the deck, 0 below (work 110/0, services 93/0).
   Affan: "tiny speck floating in a void", "not vertically balanced against the tall heading."
   The deck now sits on the heading's vertical midline (align-items:center), which splits that
   void across both sides instead of dumping it all on top, and the band's lead-out is cut
   64px -> 40px so the space under the deck tightens with it. The one size (17px), one measure
   (58ch) and the flush right edge are unchanged — that flush is horizontal and align-items does
   not touch it. Check it by measuring, per band: space above the deck vs space below it inside
   the band (was 154 vs 0, want them comparable), and content edge minus deck right edge (want 0,
   still reads 0 at 1280/1440/1920/2560). */
.work-heading,.ownership-title,.section-intro,.services-heading{align-items:center}
.work-heading,.section-intro,.services-heading{margin-bottom:40px}
.work-heading>p,.ownership-title>p{width:45%;margin:0}
.services-heading>p,.section-intro>div>p:first-child{margin:0;justify-self:end}
/* 58ch is the measure cap. The bands stop growing at 1550px but the deck is 45% of whatever is
   left, so without a cap the line reaches 85 characters at 1920 (measured: 664px of Manrope
   against ~7.8px a character) while the copy never gets longer. 58ch lands at 601px — just over
   the column at 1440, so nothing moves there, and it holds ~75 characters above it. */
/* 19px (was 17px, 2026-09-22): against a 168-212px heading the 17px deck still read as small
   print. 58ch is a character measure so the line length in characters is unchanged — only the
   pixel width grows. */
/* Font pass (2026-09-22, take 2): Newsreader tried first — serif deck under grotesque
   headline — and Affan: "out of place and so small." Serif was the wrong bet: the bands
   live or die on family unity with the display type, and Newsreader's smaller x-height
   lost the size gain even at 20px. Now the deck speaks the headline's own family one
   weight lighter (Bricolage 400 vs the headings' 500): it fits by construction, and the
   weight drop keeps it a standfirst, not a second headline. 22px because "so small" was
   the other half of the verdict — against a 168-212px heading, 20px was still small print.
   line-height 1.5 (33px): Bricolage runs looser than Manrope at equal leading, 1.55+
   starts floating again. Measure stays 58ch — ch travels with the font. Flush right is
   margin-left:auto and does not care which font is inside. The playground footnote stays
   Manrope: a neutral sans caption under the deck keeps the hairline note reading as a note. */
/* Deck size law lives in polish.css (loads after this file). Family + margins stay here. */
.work-heading>p,.services-heading>p,.ownership-title>p,.section-intro>div>p:first-child{font-family:'Bricolage Grotesque',sans-serif;margin-left:auto;text-wrap:pretty}
/* The playground note is the deck's footnote: same measure, same right edge, so its hairline spans
   the column instead of stopping 170px short of it. `margin-left:auto` is what holds the right
   edge once `max-width` starts biting — for a block-level box as well as a flex or grid item. */
.playground .playground-note{max-width:58ch;margin-left:auto}
/* The deal keeps its own 56px lead-in to .ownership-body. */
.ownership-title{margin-bottom:0}
/* Too little air under the heading (Affan, 2026-09-22): "place Design playground and Don't
   take our word for it a bit up — almost minuscule space below them", then "you didn't fix
   the other headings — the What we actually do one". Measured at 1440 before: playground
   116 above / 40 below, work 112 / 40, services 110 / 29.5 — the worst, because the band's
   deck column hangs 10.5px below the h2, so the heading text only had 29.5px of air before
   .build-lab. Fix moves each heading UP 32px (padding-top 116->84, 112->80, 110->78) and
   gives those 32px back below the band (margin-bottom 40->72), so the blocks that follow —
   play-layout, cinema, build-lab — don't move: -32 above + 32 below nets zero on their
   offset from the section top. Services heading text now gets 61.5px below (29.5+32).
   Ownership untouched — not flagged; its 120/56 predates this and stays until asked. Under
   761px the stacked layout keeps the 40px band margin and the 68px section padding.
   Playground lifted once more the same day (Affan: "lift the design playground one a bit
   more up"): padding-top 84->60 only — play-layout rides up with the heading, the 72px
   band lead-out below is what he approved and stays. */
@media(min-width:761px){.playground{padding-top:60px}.work{padding-top:80px}.services{padding-top:78px}.section-intro,.work-heading,.services-heading{margin-bottom:72px}}
/* Stacked on phones: heading and deck become separate rows, so the deck needs a lead-in again,
   and `margin-left:auto` — which is what holds the deck on the content edge in the side-by-side
   layout — has to go, or the deck indents 39px from the heading it now sits under. */
@media(max-width:760px){.work-heading>p,.ownership-title>p{width:auto}.work-heading>p,.services-heading>p,.ownership-title>p{margin-top:16px}.work-heading>p,.services-heading>p,.ownership-title>p,.section-intro>div>p:first-child{font-size:18px;margin-left:0;justify-self:start}.playground .playground-note{margin-left:0}}


/* The film stage only admits to loading when a swap is slow enough to notice (JS: 450ms). */
.film-stage[data-loading]::after{content:'Loading demo\2026';position:absolute;top:14px;left:50%;transform:translateX(-50%);padding:7px 14px;border-radius:20px;background:#0b0b0bcc;color:var(--stone);font-size:11px;letter-spacing:.04em;pointer-events:none}

/* --- Motion layer -------------------------------------------------------------------------
   Measured before this existed: the ribbon sculpture animated (frames 89 -> 143 -> 159) and
   then the page did nothing. Scrolling from top to bottom produced ZERO running animations,
   because every move on the page was a hover state. These four additions put motion on the
   scroll itself. Every rule here is applied by JS (studio.js), so with JS off the page is
   exactly as still — and as visible — as it was before; prefers-reduced-motion is checked in
   JS and again below. Reveals use @keyframes, not `transition`, so they cannot clobber the
   transitions already on things like .project-choices button / .key-card / .service-row. */

/* Headings arrive a line at a time from behind a mask. The .14em padding gives descenders
   (the g in "playground.") somewhere to live inside the clip box; the negative margin keeps
   the heading's box exactly the height it was, so the intro geometry is unchanged. */
h2.mask .line{display:block;overflow:clip;padding-bottom:.14em;margin-bottom:-.14em}
h2.mask .line-inner{display:block;transform:translateY(118%);opacity:0}
h2.mask.is-shown .line-inner{animation:line-rise 1.05s var(--ease) both;animation-delay:calc(var(--i,0)*95ms)}
@keyframes line-rise{from{transform:translateY(118%);opacity:0}to{transform:none;opacity:1}}
.reveal{opacity:0;transform:translateY(22px)}
.reveal.is-shown{animation:rise-in .9s var(--ease) both;animation-delay:calc(var(--i,0)*80ms)}
@keyframes rise-in{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}

/* Read progress, and the only motion that runs at the very top of the document. */
.scroll-progress{position:fixed;top:0;left:0;right:0;height:2px;z-index:60;pointer-events:none}
.scroll-progress i{display:block;height:100%;background:var(--coral);transform:scaleX(var(--p,0));transform-origin:left}

/* Depth on the pieces that were flat: the hero sculpture, the film card, the ownership card. */
.parallax{transform:translate3d(0,var(--par,0px),0);will-change:transform}

@media(prefers-reduced-motion:reduce){h2.mask .line-inner,.reveal,.reveal.is-shown{opacity:1!important;transform:none!important;animation:none!important}.parallax{transform:none!important;will-change:auto}.aurora i{animation:none}}


/* ================================================================================================
   THE BUILD LAB — the six services build one website, while you scroll
   ------------------------------------------------------------------------------------------------
   The services section was the emptiest on the page (28% content coverage, six rows of ~112px with
   ~60px of text). It is also the section that explains what we sell, so the fix is not padding: it
   is a pinned panel showing a small website going through the six stages, wired to the six rows.
   Scroll past "Website building" and the boxes become a real layout; past "SEO" and the titles,
   headings and sitemap fill in; past "Integrations" and the booking chip plugs in. Nothing here is
   content — the rows are the content, this is the illustration — so with JS off the panel simply
   shows its base wireframe and nothing is hidden. See studio.js setBuildStage(). */
.build-lab{display:grid;grid-template-columns:minmax(0,0.8fr) minmax(0,1fr);gap:clamp(26px,3.4vw,64px);align-items:start;margin-bottom:26px}
.build-stage{position:sticky;top:104px;display:grid;gap:15px}
.mock{position:relative;border:1px solid #cbb9c6;border-radius:15px;overflow:hidden;background:#fff;box-shadow:0 34px 64px -46px #281b2f66}
.mock-bar{display:flex;align-items:center;gap:6px;padding:9px 12px;border-bottom:1px solid #ddcfd8;background:#f7f1ee}
.mock-bar i{width:8px;height:8px;border-radius:50%;background:#dccbd6}
.mock-bar span{margin-left:8px;font-size:10px;letter-spacing:.04em;color:#7c6a80}
.mock-page{--ink:#d3c2cf;--sk:#e4d8e2;--sk2:#efe7ee;--btn:#e4d8e2;--art:#ded0dc;--artline:#d3c2cf;position:relative;padding:13px;display:grid;gap:11px;min-height:250px;transition:background .6s}
.mock-nav{display:flex;align-items:center;gap:8px}
.mock-logo{width:54px;height:9px;border-radius:3px;background:var(--sk);transition:background .6s}
.mock-links{display:flex;gap:6px;margin-left:auto}
.mock-links i{width:24px;height:5px;border-radius:2px;background:var(--sk);transition:background .6s}
.mock-cta{width:46px;height:15px;border-radius:8px;background:var(--sk);transition:background .6s}
.mock-hero{display:grid;grid-template-columns:1.05fr .95fr;gap:11px;align-items:center;position:relative}
.mock-copy{display:grid;gap:6px;transition:opacity .5s}
.mock-tag{width:64px;height:6px;border-radius:3px;background:var(--sk);transition:background .6s}
.mock-h1{height:14px;border-radius:4px;background:var(--ink);transition:background .6s,height .6s}
.mock-h1.mock-short{width:64%}
.mock-p{height:6px;border-radius:3px;background:var(--sk);transition:background .6s}
.mock-p.mock-short{width:76%}
.mock-button{width:60px;height:17px;border-radius:9px;background:var(--btn);transition:background .6s,transform .6s var(--ease)}
.mock-art{position:relative;overflow:hidden;min-height:108px;border-radius:11px;background:var(--sk2);transition:background .6s}
.mock-art span{position:absolute;left:14%;top:12%;width:72%;height:76%;border:10px solid var(--artline);border-radius:52% 52% 8px 8px;transition:border-color .6s}
.mock-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.mock-cards span{height:32px;border-radius:6px;background:var(--sk2);transition:background .6s,transform .6s var(--ease)}
.mock-meta,.mock-ai,.mock-plug{display:flex;flex-wrap:wrap;gap:6px}
.mock-meta i{font-style:normal;font-size:9px;letter-spacing:.02em;padding:3px 7px;border-radius:20px;background:#f1e8f1;color:#7c6a80;transition:opacity .45s,transform .45s}
.mock-meta i::before{content:'✓ ';color:#2e7e70}
.mock-ai{align-items:center;border-top:1px dashed #e2d4e0;padding-top:9px;transition:opacity .5s,transform .5s}
.mock-ai b{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:#8b7791}
.mock-line{flex:1 1 100%;height:5px;border-radius:3px;background:#eae0e8}
.mock-line.mock-short{flex:0 1 62%}
.mock-lock{position:absolute;right:15px;bottom:15px;width:26px;height:29px;transition:opacity .4s,transform .55s var(--ease)}
.mock-lock::before{content:'';position:absolute;left:8px;top:0;width:10px;height:12px;border:2px solid #2e7e70;border-bottom:0;border-radius:6px 6px 0 0}
.mock-lock span{position:absolute;left:5px;top:10px;width:16px;height:13px;border-radius:3px;background:#2e7e70}
.mock-plug{transition:opacity .45s,transform .45s}
.mock-chip{font-size:9px;padding:3px 8px;border-radius:20px;background:#281b2f;color:#f6eee9}
/* The "before" panel is a strip across the foot of the mock, not a full overlay: with JS off it is
   visible by default (nothing may be hidden from a no-JS visit) and a full-bleed panel would then
   sit on top of the mock it is meant to sit beside. */
.mock-before{position:absolute;left:13px;bottom:13px;width:54%;display:grid;gap:6px;border-radius:8px;background:#efe9ec;padding:11px 12px;transition:opacity .5s,transform .5s}
.mock-before i{height:7px;border-radius:2px;background:#cfc3cd}
.mock-before i:nth-child(2){width:84%}
.mock-before i:nth-child(3){width:62%}
.mock-before b{font:600 9px Manrope,sans-serif;letter-spacing:.09em;text-transform:uppercase;color:#7c6a80}
/* Stage states. Everything is revealed by default; JS adds `.staged` to the panel and then s1..s6
   (cumulative) reveal each layer as its row arrives. A no-JS or failed-JS visit therefore sees the
   finished panel with every layer present, which is also what prefers-reduced-motion gets. */
.build-stage.staged .mock-meta i,.build-stage.staged .mock-ai,.build-stage.staged .mock-plug{opacity:0;transform:translateY(8px)}
.build-stage.staged .mock-lock{opacity:0;transform:scale(.68)}
.build-stage.staged .mock-before{opacity:0;transform:translateX(-12px)}
.build-stage.s1 .mock-page{--ink:#3b2945;--sk:#c6b4c7;--sk2:#e7dbee;--btn:#ff8064;--art:#d6c6f5;--artline:#b9a4e0}
.build-stage.s1 .mock-cards span{transform:translateY(-2px)}
.build-stage.s2 .mock-before{opacity:.34;transform:none}
.build-stage.s3 .mock-before{opacity:0;transform:translateX(-14px)}
.build-stage.s3 .mock-meta i{opacity:1;transform:none}
.build-stage.s3 .mock-meta i:nth-child(2){transition-delay:70ms}
.build-stage.s3 .mock-meta i:nth-child(3){transition-delay:140ms}
.build-stage.s3 .mock-meta i:nth-child(4){transition-delay:210ms}
.build-stage.s3 .mock-meta i:nth-child(5){transition-delay:280ms}
.build-stage.s4 .mock-ai{opacity:1;transform:none}
.build-stage.s5 .mock-lock{opacity:1;transform:none}
.build-stage.s6 .mock-plug{opacity:1;transform:none}
.build-stage.s6 .mock-chip{opacity:1;transform:none}
.build-caption{margin:0;font-size:13px;line-height:1.6;color:#6d586c;min-height:42px}
.build-caption.swap{animation:cap-in .5s var(--ease)}
@keyframes cap-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
/* the row that owns the current stage */
.service-row.is-active .service-number{color:var(--coral)}
.service-row.is-active .service-arrow svg{transform:translate(3px,-3px)}
.service-row.is-active::before{opacity:1}

/* Hero proof strip. (The blueprint build overlay and its "Replay the build" control were retired
   2026-09-23: the "1200 × 640 / grid 12 / 88px" annotation read as designer tooling on a
   client-facing page. The hero's entrance lives in .hero.is-ready below, not in an overlay.) */
/* The three facts share the first hero column as equal columns instead of each fighting a fixed
   210px: at 1440 they needed 698px inside a 666px column, so the third fact used to drop to a line
   of its own. Equal columns make an orphan impossible. Below 1101px the first column is too narrow
   to hold three measures (344–505px), so the facts stack instead of orphaning a third one. */
/* Rhythm law (W0): the proof strip breathes relative to the hero-bottom gap (32px), so
   the heading→copy→proof ladder reads as one rhythm at every width. */
.hero-proof{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px 22px;margin:44px 0 0;padding:24px 0 0;border-top:1px solid var(--line);list-style:none}
.hero-proof li{display:grid;gap:2px;min-width:0}
@media(max-width:1100px){.hero-proof{grid-template-columns:1fr;gap:10px}}
.hero-proof b{font:600 13px Manrope,sans-serif;color:var(--stone)}
.hero-proof span{font-size:12px;line-height:1.5;color:var(--muted)}
/* .replay-build removed with the blueprint overlay it used to replay. */

/* Our work: the film caption had an 836px hole in the middle of the page. Proof goes there. */
.film-links{display:grid;gap:8px;justify-items:end;text-align:right}
.film-links a{display:inline-flex;align-items:center;gap:12px;font-size:12px;text-decoration:none;color:var(--stone);border-bottom:1px solid #ffffff40;padding-bottom:3px}
.film-links a svg{width:16px}
.film-links a:hover{color:var(--coral);border-color:var(--coral)}
.film-proof{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;margin:0;padding:20px 0 0;list-style:none;border-top:1px solid #ffffff1f}
.film-proof li{display:grid;gap:3px}
.film-proof b{font:600 11px Manrope,sans-serif;letter-spacing:.06em;text-transform:uppercase;color:var(--coral)}
.film-proof span{font-size:12.5px;line-height:1.55;color:var(--muted)}

/* Contact: the left column was 20% full. It now carries what happens next, so the form is not
   floating in red space. */
.next-steps{display:grid;gap:18px;margin:34px 0 0;padding:0;list-style:none}
.next-steps li{display:grid;grid-template-columns:26px minmax(0,1fr);gap:14px;align-items:start}
.next-steps>li>span{font:600 11px Manrope,sans-serif;color:#592e46;padding-top:3px}
.next-steps b{display:block;font-size:13px}
.next-steps p{margin:4px 0 0;font-size:12.5px;line-height:1.6;color:#5b3348}
.contact-trust{margin:26px 0 0;padding-top:20px;border-top:1px solid #a35c58;font-size:12px;line-height:1.6;color:#5b3348}
.contact-trust a{color:#592e46}
.field-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
.field{display:grid;gap:7px;font-size:12px;font-weight:700}
.field em{font-style:normal;font-weight:400;color:#7a4145}
.field input{width:100%;background:#ffffff2e;border:1px solid #8d4a4a;border-radius:8px;padding:11px 13px;color:var(--plum);font-size:14px;caret-color:var(--plum)}
.field input:focus-visible{border-color:var(--plum)}
.field input[aria-invalid=true]{border-color:#7a1f1f;background:#ffd0c0}
.hp-field{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}
.form-status[data-state=error]{color:#5b1b1b;font-weight:600}
.form-status[data-state=sent]{color:#4b2020;font-weight:600}
.form-actions{row-gap:10px}

/* Footer: 4% full, with an 832px hole in the bottom row. Real links fix that and internal linking. */
.footer-links{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:28px;margin-top:46px;padding-top:34px;border-top:1px solid #c6b8c3}
.footer-links h3{font:600 11px Manrope,sans-serif;letter-spacing:.12em;text-transform:uppercase;color:#6d586c;margin-bottom:14px}
.footer-links a{display:block;padding:6px 0;font-size:13px;text-decoration:none;color:var(--plum)}
.footer-links a:hover{color:var(--coral);text-decoration:underline;text-underline-offset:4px}

/* --- Colour: auroras behind the flat dark bands, and a light that follows the pointer --------
   Both are added by JS (studio.js paintDepth), so a no-JS visit is exactly the page it was.
   W0 2026-09-23 wash law: brand tones ONLY (coral #ff8064, lilac #d6c6f5, plum family).
   The old off-palette teal #54c6b8 / violet #8f6cff washes are retired — the wash now
   deepens the plum ground instead of introducing colors the rest of the page never uses. */
.aurora{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0}
.aurora i{position:absolute;width:52vw;height:52vw;border-radius:50%;opacity:.26;background:radial-gradient(closest-side,var(--au,#ff8064) 45%,transparent 100%);--au:#ff8064;animation:aurora-drift 30s ease-in-out infinite alternate}
.aurora i:nth-child(1){left:-14%;top:-22%;--au:#ff8064}
.aurora i:nth-child(2){right:-16%;bottom:-26%;--au:#d6c6f5;animation-delay:-9s;animation-duration:36s}
.aurora i:nth-child(3){left:38%;bottom:-34%;--au:#3b2945;opacity:.32;animation-delay:-18s;animation-duration:42s}
@keyframes aurora-drift{from{transform:translate3d(0,0,0) scale(1)}to{transform:translate3d(6%,4%,0) scale(1.16)}}
.aurora[data-hue=coast] i:nth-child(1){--au:#3b2945}
.aurora[data-hue=coast] i:nth-child(2){--au:#d6c6f5}
.aurora[data-hue=sunset] i:nth-child(1){--au:#ffad79}
.aurora[data-hue=sunset] i:nth-child(2){--au:#ff8064}
.paused .aurora i{animation-play-state:paused}
.has-aurora{position:relative;overflow:hidden}
/* every child of an aurora band is lifted above it — except the two absolutely positioned overlays,
   which the `:not()` clause keeps out so their inset:0 survives this rule */
.has-aurora>*:not(.aurora){position:relative;z-index:1}
/* (hero.assembling / @keyframes hero-settle retired with the blueprint overlay.) */
.spotlight::after{content:'';position:absolute;inset:0;pointer-events:none;z-index:0;background:radial-gradient(420px circle at var(--mx,50%) var(--my,0%),#ff806429,transparent 62%);opacity:0;transition:opacity .5s}
.spotlight.is-live::after{opacity:1}

/* The process line draws itself instead of sitting there, and cards lift a little. */
.process-track article{position:relative}
.process-track article::after{content:'';position:absolute;left:0;top:-1px;width:100%;height:1px;background:var(--coral);transform:scaleX(0);transform-origin:left;transition:transform .7s var(--ease)}
.process-track article.is-shown::after{transform:scaleX(1)}
.process-track article:nth-child(2)::after{transition-delay:90ms}
.process-track article:nth-child(3)::after{transition-delay:180ms}
.process-track article:nth-child(4)::after{transition-delay:270ms}
@media(hover:hover) and (pointer:fine){.magnetic{transition:transform .25s var(--ease)}.mock:hover .mock-cards span:nth-child(2){transform:translateY(-4px)}}

/* Responsive: the build lab collapses above the rows, the aurora stays, the fields stack. */
@media(max-width:1100px){.mock-page{min-height:220px}}
@media(max-width:900px){.build-lab{grid-template-columns:1fr}.build-stage{position:static;order:-1;margin-bottom:34px}.mock-page{min-height:0}.field-row{grid-template-columns:1fr 1fr}.field:nth-child(3){grid-column:1/-1}}
@media(max-width:760px){.hero-proof{gap:12px 22px;margin-top:20px}.hero-proof li{max-width:none}.film-proof{grid-template-columns:1fr;gap:12px}.film-links{justify-items:start;text-align:left}.footer-links{grid-template-columns:1fr 1fr}.field-row{grid-template-columns:1fr}.next-steps{gap:14px}}
.hero-copy{transform:translate3d(var(--copy-x,0px),var(--copy-y,0px),0);will-change:transform}.hero.is-departing .hero-copy{transform:translate3d(var(--copy-x,0px),calc(var(--copy-y,0px) - 8%),0)}.project-choices button,.process-track article,.questions details,.field{transition:transform .35s var(--ease),border-color .25s var(--ease),background .25s var(--ease)}.project-choices button:hover,.process-track article:hover{transform:translateY(-3px)}.project-choices button[aria-pressed="true"]{box-shadow:0 10px 24px #10091626}.questions details:hover{border-color:rgba(255,128,100,.45);background:rgba(255,128,100,.045)}.field:focus-within{transform:translateY(-2px);border-color:var(--coral)}
@media(hover:hover) and (pointer:fine){.project-choices button:hover .button-arrow{transform:translate(2px,-2px)}}
.hero{position:relative}.hero-copy,.sculpture,.hero-baseline{position:relative;z-index:2}.header{position:sticky;top:0;background:rgba(40,27,47,.92);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}nav.section-rail::after{content:'';position:absolute;left:10px;top:20px;width:3px;height:var(--rail-progress,0%);background:rgba(255,128,100,.72);transition:height .18s linear}@media(max-width:760px){.hero-copy,.sculpture,.hero-baseline{position:relative;z-index:2}}@media(prefers-reduced-motion:reduce){.hero-copy{transform:none!important;will-change:auto}.project-choices button,.process-track article,.questions details,.field{transition:none}.section-intro::after{transition:none;transform:scaleX(1)}.project-choices button:hover,.process-track article:hover,.field:focus-within{transform:none}.aurora i{animation:none}.build-caption.swap{animation:none}.process-track article::after{transform:scaleX(1)}.magnetic{transition:none}}
