/* THE TYPEFACE, SERVED FROM HERE. It used to arrive through Google, which cost three
   round trips before a single word could be drawn: this document, then their stylesheet
   from one host, then the font itself from another, with font-display:block holding every
   word on the page until the last of them landed. One file from this origin, preloaded in
   the head, is already on its way while the stylesheet is still being parsed.
   It also removes the last third party that saw a visitor's IP address here. */
@font-face{
  font-family:'Familjen Grotesk';
  src:url('fonts/familjen-grotesk-latin.woff2') format('woff2');
  font-weight:400 700;font-style:normal;font-display:block;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,
    U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Familjen Grotesk';
  src:url('fonts/familjen-grotesk-latin-ext.woff2') format('woff2');
  font-weight:400 700;font-style:normal;font-display:block;
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,
    U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,
    U+2C60-2C7F,U+A720-A7FF;
}

:root{
  --paper:#fbfbf9; --ink:#121212; --night:#0c0c10;
  --grey:#6b675f; --hair:rgba(18,18,18,.14); --white72:rgba(251,251,249,.72);
  --blue:#1c3af7; --blue320:#8aa0ff;
  --ease:cubic-bezier(.22,1,.36,1);
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--paper);color:var(--ink);overflow-x:clip;
  font-family:'Familjen Grotesk',Arial,sans-serif;-webkit-font-smoothing:antialiased}

/* ---------------- display voice: caps, heavy, sub single leading ---------------- */
.stmt,.h2,.ctitle,.ftag,.sname,.vt,.fname{
  text-transform:uppercase;font-weight:700;letter-spacing:-.045em}
.stmt{font-size:clamp(38px,7.4vw,100px);line-height:.86;margin:0}
.h2{font-size:clamp(26px,3.6vw,50px);line-height:.9;margin:0}
.sname{font-size:clamp(20px,2.4vw,32px);line-height:.94;letter-spacing:-.035em}
.mani{text-transform:uppercase;font-weight:700;letter-spacing:-.03em;
  font-size:clamp(18px,1.85vw,27px);line-height:.98;margin:0}
.meta{font-size:10px;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--grey)}
.meta a{color:inherit;text-decoration:none}
.body{font-size:14px;line-height:1.46;color:var(--grey);font-weight:400}
.lead{font-size:clamp(15px,1.25vw,18px);line-height:1.46;color:var(--grey);
  font-weight:400;max-width:680px;margin:18px 0 0}
.lead.on-blue{color:rgba(255,255,255,.9);max-width:620px}
.ctr{text-align:center;max-width:560px;margin:16px auto 0}
.lab{margin-bottom:24px}
.wrap{width:min(1400px,calc(100vw - 68px));margin:0 auto}
.pad-b{padding-bottom:110px}
.pad-t{padding-top:96px}


/* ---------------- tile dissolve ---------------- */
#tiles{position:fixed;inset:0;z-index:9000;pointer-events:none;display:grid;
  grid-template-columns:repeat(20,1fr);grid-template-rows:repeat(10,1fr)}
#tiles i{display:block;background:var(--paper)}
@media (max-width:760px){#tiles{grid-template-columns:repeat(10,1fr);grid-template-rows:repeat(20,1fr)}}

/* ---------------- top bar ---------------- */
.hbar{display:flex;justify-content:space-between;align-items:center;gap:18px}
.lock{display:flex;align-items:center;gap:13px;font-weight:700;font-size:32px;
  letter-spacing:-.035em;color:var(--ink);text-decoration:none;margin:0}
.lock svg{width:46px;height:38px}
.lock.on-dark{color:var(--paper)}

/* ---------------- home hero ---------------- */
.hero{min-height:100svh;display:flex;padding:34px 0 150px;position:relative}
.hero>.wrap{display:flex;flex-direction:column;justify-content:space-between;
  flex:1 1 auto;width:100%;max-width:min(1400px,calc(100vw - 68px));margin:0 auto;min-width:0}
.wordmark{font-weight:700;letter-spacing:-.052em;line-height:.74;
  font-size:clamp(56px,15.5vw,208px);margin:0}
.hsub{font-size:clamp(10px,1.05vw,13px);font-weight:600;letter-spacing:.34em;
  text-transform:uppercase;margin:18px 0 0;color:var(--grey)}
.cta{display:inline-block;margin-top:28px;background:var(--ink);color:var(--paper);
  font-size:11px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;
  padding:18px 30px;text-decoration:none;border-radius:2px;border:0;cursor:pointer;
  font-family:inherit;transition:background .28s var(--ease)}
.cta:hover{background:var(--blue)}
.cta.on-blue{background:var(--paper);color:var(--blue)}
.cta.on-blue:hover{background:#fff}

/* ---------------- inner page head ---------------- */
.phead{padding:34px 0 0}
.ptitle{padding:96px 0 0}
.ptitle .meta{display:block;margin-bottom:16px}

/* ---------------- statement + manifesto ---------------- */
.statement{min-height:88svh;display:grid;place-items:center;text-align:center;padding:110px 0}
.mani-row{text-align:center;padding:0 0 110px}

/* ---------------- THE BLUE ROOM (services) ---------------- */
.blueroom{background:var(--blue);color:#fff;padding:118px 0;margin:96px 0 0;
  position:relative}
.blueroom .stmt{color:#fff}

/* ---------------- work cards ---------------- */

.casehero{padding-top:70px}

/* ---------------- what it comes out as: the shapes, on mist ----------------
   Alex asked for this and the section above it to stop sharing a colour: paired under one
   heading style on one ground they read as a single long section rather than as two
   answers to two questions. Mist is the right one of the three to give it, because this
   section IS the shape of the offer, which is the job that ground does. The stacking rows
   inside it carry the same ground, since their whole trick is that each covers the last as
   it scrolls, and a paper row over a mist band would show the seam. */
.fmtband{background:var(--mist)}
.fmtband .srow{background:var(--mist);border-top-color:rgba(18,18,18,.16)}
.blue-route .fmtband .srow{border-top-color:rgba(28,58,247,.22)}
.fmtband .srow:hover,.fmtband .srow:focus-visible{background:#e6eafb}

/* the redirect targets from the old /work and /services pages: real elements, so a browser
   arriving at #services has something to find even before the script switches the route */
.anchor{position:absolute;width:1px;height:1px;overflow:hidden;pointer-events:none}

/* ---------------- sticky stacking rows ---------------- */
.stack{position:relative}
.srow,.prow{position:relative;background:var(--paper);border-top:1px solid var(--hair);
  padding:30px 0 34px;display:grid;align-items:start;gap:24px;text-decoration:none;color:var(--ink)}
.srow{grid-template-columns:1fr 1.15fr auto}
.prow{grid-template-columns:auto 1fr 1.15fr auto}
.arw{font-size:21px;color:var(--blue);line-height:.8;transition:transform .3s var(--ease)}
.srow:hover .arw{transform:translateX(6px)}
.pnum{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--blue);min-width:26px}
.pstage{text-align:right;white-space:nowrap}

/* ---------------- case body ---------------- */
.casebody{display:grid;grid-template-columns:1fr 1fr;gap:56px}
.casebody .meta{display:block;margin-bottom:12px}
.outlink{display:inline-block;margin-top:18px;color:var(--blue);text-decoration:none;
  font-size:14px;font-weight:600;border-bottom:1px solid transparent}
.outlink:hover{border-bottom-color:var(--blue)}
.nextcase{display:block;margin-top:10px;font-size:clamp(30px,5.4vw,72px);font-weight:700;
  text-transform:uppercase;letter-spacing:-.045em;color:var(--ink);text-decoration:none;line-height:.9}
.nextcase:hover{color:var(--blue)}

/* ---------------- service points ---------------- */
.points{list-style:none;margin:0;padding:0;border-top:1px solid var(--hair)}
.points li{border-bottom:1px solid var(--hair);padding:20px 0;font-size:clamp(16px,1.7vw,22px);
  font-weight:400;color:var(--ink)}

/* ---------------- our story ---------------- */
.valband{background:var(--night);color:var(--paper);padding:104px 0;margin-top:96px}
.valband .meta{color:var(--white72)}
.valband .h2{color:var(--paper)}
.vgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:rgba(251,251,249,.14);
  border:1px solid rgba(251,251,249,.14)}
.vcard{background:var(--night);padding:26px 22px 30px}
.vt{display:block;font-size:13px;letter-spacing:.06em;margin-bottom:10px;color:var(--paper)}
.valband .body{color:var(--white72)}
.fname{display:block;font-size:clamp(26px,3.4vw,46px);line-height:1;margin-bottom:8px}

/* ---------------- contact ---------------- */
.contactgrid{display:grid;grid-template-columns:1.2fr .8fr;gap:60px}
.cform{display:grid;grid-template-columns:1fr 1fr;gap:20px}
.cform label{display:block}
.cform label.full,.cform .cta,.cform .reassure{grid-column:1/-1}
.cform .meta{display:block;margin-bottom:8px}
.cform input,.cform textarea{width:100%;font-family:inherit;font-size:15px;color:var(--ink);
  background:transparent;border:0;border-bottom:1px solid var(--ink);padding:10px 0;border-radius:0}
.cform input:focus,.cform textarea:focus{outline:0;border-bottom-color:var(--blue)}
.cform .cta{justify-self:start}
.steps{margin:12px 0 0;padding-left:18px}
.steps li{font-size:14px;line-height:1.5;color:var(--grey);padding:5px 0}
.reassure{font-size:12px;font-weight:600;color:var(--grey);margin:12px 0 0;letter-spacing:.06em}
.ctr-l{text-align:left}

/* ---------------- the ask band ---------------- */
.ask{background:var(--blue);color:#fff;text-align:center;padding:112px 0;margin-top:96px}
.ask .stmt{color:#fff}
.ask .cta{margin-top:34px}
.ask .reassure{color:rgba(255,255,255,.82)}

/* ---------------- legal ---------------- */
.legal{padding:80px 0 96px}
.upd{display:block;margin-bottom:40px}
.lsec{display:grid;grid-template-columns:64px 1fr;gap:24px;border-top:1px solid var(--hair);
  padding:26px 0 30px}
.lnum{font-size:13px;font-weight:700;color:var(--blue);font-variant-numeric:tabular-nums}
.lsec .sname{margin:0 0 12px;font-size:clamp(17px,1.8vw,23px)}
.lsec .body{max-width:720px;margin:0 0 10px}

/* ---------------- pill nav + handle ---------------- */
.dock{position:fixed;left:50%;bottom:34px;transform:translateX(-50%);z-index:8000;
  transition:transform .34s var(--ease)}
.dock.away{transform:translateX(-50%) translateY(140px)}
.pill{display:grid;grid-template-columns:1fr 1fr;background:var(--ink);padding:4px;
  border-radius:15px;width:232px;position:relative}
.pill button{position:relative;z-index:2;text-align:center;padding:15px 0;font-size:9px;line-height:14px;
  font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:var(--paper);
  background:none;border:0;cursor:pointer;border-radius:13px;font-family:inherit;
  transition:color .28s var(--ease)}
.pill button.on{color:var(--ink)}
.pill .ind{position:absolute;top:4px;left:4px;width:calc(50% - 4px);height:calc(100% - 8px);
  background:var(--paper);border-radius:13px;z-index:1;transition:transform .3s var(--ease)}
.handle{position:fixed;left:50%;bottom:20px;transform:translateX(-50%);width:180px;height:7px;
  border-radius:99px;background:var(--ink);opacity:0;z-index:7999;transition:opacity .3s var(--ease)}
.handle.show{opacity:.45}
.route{display:none}
.route.live{display:block}

/* ---------------- footer ---------------- */
footer{border-top:1px solid var(--ink);overflow:hidden}
.ftable{display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr}
.fcell{border-right:1px solid var(--hair);border-bottom:1px solid var(--hair);padding:26px 22px 30px}
.fcell:last-child{border-right:0}
.fcell a,.fcell .flink{display:block;color:var(--ink);text-decoration:none;
  font-size:13.5px;padding:4px 0;font-family:inherit;font-weight:inherit;
  letter-spacing:inherit;line-height:inherit;text-align:left;
  background:none;border:0;cursor:pointer;width:100%}
.fcell a:hover,.fcell .flink:hover{color:var(--blue);text-decoration:none}
.fcell .meta{margin-bottom:10px;display:block}
.ftag{font-size:19px;line-height:1.05;margin:0 0 16px}
.flogo{font-weight:700;letter-spacing:-.055em;line-height:.92;
  font-size:clamp(44px,10vw,142px);margin:14px 0 4px;color:var(--ink);
  overflow:visible;max-height:none}
.flegal{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;
  padding:18px 0 30px;font-size:11px;color:var(--grey)}

/* ================================ MOBILE ================================ */
@media (max-width:860px){
  .wrap{width:calc(100vw - 32px)}
  .lock{font-size:24px;gap:10px}
  .lock svg{width:34px;height:28px}
  .pad-b{padding-bottom:76px}.pad-t{padding-top:64px}
  .hero{padding:24px 0 140px}
  .hbar{gap:10px}
  .hbar .meta{font-size:9px;letter-spacing:.14em;text-align:right}
  .hsub{margin-top:14px}
  .cta{display:block;text-align:center;width:100%}
  .ptitle{padding-top:60px}
  .statement{min-height:auto;padding:80px 0}
  .mani-row{padding-bottom:76px}
  .blueroom{padding:80px 0;margin-top:64px}
  .valband{padding:76px 0;margin-top:64px}
  .ask{padding:80px 0;margin-top:64px}
  .ctitle{font-size:clamp(17px,4.6vw,24px)}
  .srow,.prow{position:relative;top:auto!important;grid-template-columns:1fr;gap:8px;
    padding:22px 0 26px}
  /* The row keeps its arrow on a phone. It used to be hidden here along with the
     process row's stage label, which left these rows looking like paragraphs: there is no
     hover on a touch screen, so whatever says "this is a link" has to be visible at rest.
     The title and the arrow share the first line, the body spans underneath, and a tap
     gets an acknowledgement of its own. */
  .prow .pstage{display:none}
  .srow{grid-template-columns:1fr auto;gap:6px 16px;align-items:center}
  .srow .sname{grid-column:1}
  .srow .arw{display:block;grid-column:2;grid-row:1;font-size:26px;line-height:1;
    color:var(--blue);transition:transform .22s var(--ease)}
  .srow .body{grid-column:1/-1;grid-row:2}
  .srow:active{background:#e6eafb}
  .srow:active .arw{transform:translateX(5px)}
  .fmtband .srow:active{background:#dfe5f9}
  .prow{grid-template-columns:auto 1fr;gap:6px 14px}
  .prow .body{grid-column:1/-1}
  .casebody{grid-template-columns:1fr;gap:34px}
  .vgrid{grid-template-columns:1fr}
  .contactgrid{grid-template-columns:1fr;gap:44px}
  .cform{grid-template-columns:1fr}
  .lsec{grid-template-columns:1fr;gap:8px}
  .ftable{grid-template-columns:1fr 1fr}
  .fcell{padding:20px 16px 24px}
  .pill{width:min(314px,calc(100vw - 44px))}
  .dock{bottom:20px}
  .flegal{flex-direction:column;gap:6px}
}
@media (max-width:400px){
  .hbar .meta{display:none}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  #tiles{display:none}
  .wstage,.wlogo,.pill .ind,.dock,.cta,.arw{transition:none}
  .wlogo{transform:none;opacity:.95}
}

/* ---- 44px minimum tap targets. Measured in a 390 harness, not assumed. ---- */
@media (max-width:860px){
  .fcell a,.fcell .flink{padding:12px 0;line-height:20px}
  .lock{padding:12px 0}
  .outlink{padding:13px 0;line-height:18px;display:inline-block}
  .cform input{padding:13px 0;line-height:20px}
  .cform textarea{padding:12px 0}
  .steps li{padding:8px 0}
  .points li{padding:24px 0}
  .nextcase{padding:10px 0}
  .hbar .meta a{display:inline-block;padding:15px 0;line-height:14px}
}


/* ================= THE RUNNING MARK: lanes streaming into the ring ================= */
.marksvg{display:block;width:100%;height:auto;overflow:visible}
.ptitle .markstage{max-width:280px;margin:26px 0 0;opacity:.9}

/* ================= brand rule: the three lanes as a divider ================= */
.brandrule{padding:0 0 96px}
.brandrule .wrap{display:flex;flex-direction:column;gap:7px}
.bl{display:block;height:2px;border-radius:1px;
  transform:scaleX(0);transform-origin:left center;
  transition:transform .62s cubic-bezier(.3,0,.2,1)}
.bl.b1{background:var(--blue);width:100%}
.bl.b2{background:var(--blue320);width:74%}
.bl.b3{background:var(--steel,#aeb4bf);width:46%}
/* the stagger is the logo's, in the same order and roughly the same spacing, so the rule
   under the statement and the mark in the hero read as one idea */
.brandrule.in .bl.b1{transform:scaleX(1);transition-delay:0s}
.brandrule.in .bl.b2{transform:scaleX(1);transition-delay:.12s}
.brandrule.in .bl.b3{transform:scaleX(1);transition-delay:.24s}
@media (prefers-reduced-motion:reduce){
  .bl{transition:none;transform:scaleX(1)}
}

/* ================= hero row under the wordmark ================= */
.hrow2{display:flex;align-items:center;justify-content:space-between;gap:24px;
  margin-top:20px;flex-wrap:wrap}
.hrow2 .hsub{margin:0}
.hrow2 .cta{margin-top:0}

/* ================= SCROLL REVEALS ================= */
.rv{opacity:0;transform:translateY(22px);
  transition:opacity .62s cubic-bezier(.22,1,.36,1),transform .62s cubic-bezier(.22,1,.36,1)}
.rv.seen{opacity:1;transform:none}
/* first fold display type must never start hidden: it is the LCP element */
.hero .rv,.ptitle .rv{opacity:1;transform:none}

@media (max-width:860px){
  .hrow2{flex-direction:column;align-items:stretch;gap:16px}
  .brandrule{padding-bottom:64px}
  .ptitle .markstage{max-width:200px}
  .pill{width:min(232px,calc(100vw - 60px))}
}
@media (prefers-reduced-motion:reduce){
    .ring{animation:none;stroke-dashoffset:0}
  .rv{opacity:1;transform:none;transition:none}
}


/* ================= SERVICES ROUTE: blue is the primary ground ================= */
.blue-route .h2{color:var(--blue)}
.blue-route .pnum,.blue-route .arw{color:var(--blue)}
.blue-route .srow{border-top-color:rgba(28,58,247,.24)}
.blue-route .prow{border-top-color:rgba(28,58,247,.18)}
.blue-route .points li{border-bottom-color:rgba(28,58,247,.2)}

/* the ink counterpoint inside the blue route */
.inkroom{background:var(--ink);color:var(--paper);padding:112px 0;margin:0}
.inkroom .stmt{color:var(--paper)}
.lead.on-ink{color:rgba(251,251,249,.82);max-width:620px}

@media (max-width:860px){
  .inkroom{padding:76px 0}
}


/* ================= CURSOR TRAIL: a deck of logo cards dealt along the path ================= */
#trail{position:fixed;inset:0;pointer-events:none;z-index:60;overflow:hidden}
#trail figure{position:absolute;top:0;left:0;width:172px;height:172px;margin:0;
  background:var(--paper);border:1px solid var(--ink);border-radius:5px;
  display:grid;place-items:center;opacity:0;will-change:transform,opacity;
  box-shadow:0 6px 22px rgba(18,18,18,.09)}
#trail img{width:66%;height:66%;object-fit:contain}

@media (max-width:1024px){#trail{display:none}}

/* ================= CONTACT QUESTIONNAIRE ================= */
.quiz{max-width:860px}
.qbar{display:flex;align-items:center;gap:16px;padding-bottom:22px;
  border-bottom:2px solid var(--blue);margin-bottom:34px}
.qbar b{color:var(--blue)}
.qdots{display:flex;gap:6px;margin-left:auto}
.qdots i{width:26px;height:3px;border-radius:2px;background:rgba(28,58,247,.22);display:block;
  transition:background .3s var(--ease)}
.qdots i.on{background:var(--blue)}
.qback{background:none;border:0;font-family:inherit;font-size:10px;font-weight:600;
  letter-spacing:.2em;text-transform:uppercase;color:var(--grey);cursor:pointer;padding:10px 0}
.qback:hover{color:var(--blue)}
.qstep{display:none;border:0;padding:0;margin:0}
.qstep.on{display:block;animation:qin .44s cubic-bezier(.22,1,.36,1) both}
@keyframes qin{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
.qq{font-size:clamp(26px,3.4vw,46px);line-height:.98;letter-spacing:-.035em;font-weight:700;
  text-transform:uppercase;margin:0 0 30px;padding:0;color:var(--blue)}
.qgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.qopt{position:relative;display:flex;flex-direction:column;gap:14px;cursor:pointer;
  background:var(--blue);border:1px solid var(--blue);border-radius:4px;padding:20px 18px 24px;
  transition:transform .22s var(--ease),background .22s var(--ease)}
.qopt:hover{background:#1730d6;transform:translateY(-2px)}
.qopt input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;
  clip-path:inset(50%);margin:-1px;border:0;padding:0;overflow:hidden}
.qopt .qn{font-size:11px;font-weight:700;color:rgba(255,255,255,.72);letter-spacing:.1em}
.qopt .qt{font-size:16px;font-weight:700;letter-spacing:-.02em;line-height:1.1;
  text-transform:uppercase;color:#fff}
.qopt:has(input:checked){background:var(--ink);border-color:var(--ink)}
.qopt:has(input:checked) .qn{color:rgba(255,255,255,.72)}
.qopt:has(input:checked) .qt{color:#fff}
.qopt input:focus-visible + .qn{outline:2px solid var(--blue);outline-offset:3px}
.quiz textarea{width:100%;font-family:inherit;font-size:16px;color:var(--ink);background:transparent;
  border:1px solid var(--hair);border-radius:4px;padding:16px;resize:vertical}
.quiz textarea:focus{outline:0;border-color:var(--blue)}
.qskip{display:inline-block;margin-top:18px;background:none;border:0;font-family:inherit;
  font-size:10px;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--grey);
  cursor:pointer;padding:14px 0}
.qskip:hover{color:var(--blue)}
.qfields{display:grid;grid-template-columns:1fr 1fr;gap:22px}
.qfields label{display:block}
.qfields .meta{display:block;margin-bottom:8px}
.qfields input{width:100%;font-family:inherit;font-size:16px;color:var(--ink);background:transparent;
  border:0;border-bottom:1px solid var(--ink);padding:12px 0}
.qfields input:focus{outline:0;border-bottom-color:var(--blue)}
.qsend{margin-top:32px}
.qdone .qq{color:var(--blue)}

@media (max-width:860px){
  .qgrid{grid-template-columns:1fr}
  .qfields{grid-template-columns:1fr}
  .qbar{flex-wrap:wrap;gap:10px}
  .qdots{margin-left:0;order:3;width:100%}
  .qsend{width:100%}
}


/* ================= hero call to action ================= */
.cta-lg{padding:22px 40px;font-size:12px;letter-spacing:.2em}
.cta-lg:hover{background:var(--blue)}

/* ================= 4. the toggle never sits on top of content ================= */
:root{--docksafe:120px}
footer{padding-bottom:var(--docksafe)}
.dock{transition:transform .42s cubic-bezier(.22,1,.36,1),opacity .3s linear}
.handle{transition:opacity .32s linear,transform .42s cubic-bezier(.22,1,.36,1)}
.pill{box-shadow:0 6px 26px rgba(18,18,18,.16)}

/* ================= 5. opening: the mark fades into the page ================= */

@media (max-width:860px){
  :root{--docksafe:104px}
  .cta-lg{padding:19px 0;font-size:11px}
  }
@media (prefers-reduced-motion:reduce){
    .dock,.handle{transition:none}
}


/* ================= contact: questionnaire sits higher ================= */
.contact-page .ptitle{padding-top:34px}
.contact-page .lead{margin-top:14px}
.contact-page .stmt{font-size:clamp(32px,4.6vw,62px)}
.contact-page .pad-t{padding-top:40px}

/* ================= How it goes: its own composition, not another row list ======= */
.procband{background:var(--ink);color:var(--paper);padding:104px 0;margin-top:0}
.procband .meta{color:rgba(251,251,249,.66)}
/* ENOUGH SPECIFICITY TO ACTUALLY WIN. This said paper and lost to .blue-route .h2, which
   is two classes to its one, so the process title has been rendering in blue on the near
   black band: 2.67:1, under the 3:1 that large text needs. It was there before today and
   the design audit walked past it. */
/* BOTH dark bands, not one. Scoping this to .procband fixed the band that was failing at
   the time and left the other to fail the moment it went back to ink, which it did within
   the hour: .blue-route .h2 is two classes to .procttl's one, so without a named ground the
   title renders blue on near black at 2.67:1, under the 3:1 large text needs. */
.procband .procttl,.sigband .procttl{color:var(--paper)}
.procttl{margin-top:10px}
.ptrack{display:grid;grid-template-columns:repeat(5,1fr);gap:1px;
  background:rgba(251,251,249,.16);border:1px solid rgba(251,251,249,.16);margin-top:38px}
.pcard{background:var(--ink);padding:26px 20px 30px;display:flex;flex-direction:column;gap:10px}
.pcard .pnum{font-size:34px;font-weight:700;color:var(--blue320);line-height:1;
  font-variant-numeric:tabular-nums;letter-spacing:-.03em}
.pcard .pstage{color:rgba(251,251,249,.6);text-align:left}
.pcard .sname{font-size:17px;line-height:1.06;color:var(--paper)}
.pcard .body{color:rgba(251,251,249,.7);font-size:13px}

/* ================= trail card variants ================= */
#trail figure.text-card{background:var(--ink);border-radius:5px}
#trail .tcard{font-weight:700;text-transform:uppercase;letter-spacing:-.03em;
  font-size:19px;line-height:.98;color:var(--paper);text-align:center;padding:0 16px}

#trail img{filter:none}

@media (max-width:980px){
  .ptrack{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:860px){
  .procband{padding:76px 0}
  .ptrack{grid-template-columns:1fr}
}


/* ================= the mark draws itself, once ================= */
.markstage{width:100%;margin:0 0 14px;pointer-events:none;max-width:132px}
.marksvg{display:block;width:100%;height:auto;overflow:visible}
.ring{stroke-dasharray:133;stroke-dashoffset:133;
  animation:drawRing 1.05s cubic-bezier(.33,0,.2,1) .15s forwards}
@keyframes drawRing{from{stroke-dashoffset:133}to{stroke-dashoffset:0}}
.ml{stroke-dasharray:var(--len);stroke-dashoffset:var(--len);
  animation:drawLine .52s cubic-bezier(.3,0,.2,1) var(--dl) forwards}
@keyframes drawLine{from{stroke-dashoffset:var(--len)}to{stroke-dashoffset:0}}
@media (prefers-reduced-motion:reduce){
  .ring,.ml{animation:none;stroke-dashoffset:0}
}

/* ================= work card: logo in its own colours, fully visible ============ */

@media (max-width:860px){
    .markstage{max-width:96px}
}


/* ================= case page: the client's real site, then problem and solution ===== */
.casefilm{padding:56px 0 0}
.filmcap{display:block;margin:14px 0 0}
.ps{display:grid;grid-template-columns:1fr 1fr;gap:64px;align-items:start}
.psl{display:block;font-size:10px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;
  color:var(--grey);padding-bottom:12px;border-bottom:2px solid var(--hair);margin-bottom:20px}
.psl.blue{color:var(--blue);border-bottom-color:var(--blue)}
.psbody{font-size:clamp(16px,1.35vw,20px);line-height:1.45;color:var(--ink);font-weight:400;
  margin:0;max-width:520px}

@media (max-width:860px){
  .casefilm{padding-top:34px}
  .ps{grid-template-columns:1fr;gap:38px}
  .film{border-radius:8px}
}


/* the trail card's whole life is one keyframe, so it cannot be swallowed by
   a style change landing in the same frame */
@keyframes trailcard{
  0%   {opacity:1}
  26%  {opacity:1}
  100% {opacity:0}
}
#trail figure.live{animation:trailcard .82s linear forwards}
@media (prefers-reduced-motion:reduce){#trail{display:none}}


/* ================= contact: the questionnaire is the primary element ================= */
.contact-page .ptitle{padding-top:26px}
.contact-page .stmt{font-size:clamp(26px,3.2vw,44px)}
.contact-page .lead{margin-top:12px;font-size:15px;max-width:520px}
.contact-page .pad-t{padding-top:26px}
.quiz{max-width:900px;margin:0 auto}
.qbar{justify-content:center;text-align:center}
.qq{text-align:center}
.qgrid{max-width:820px;margin:0 auto}
.quiz textarea,.qfields{max-width:700px;margin-left:auto;margin-right:auto}
.qstep{text-align:center}
.qstep .qskip,.qstep .cta{margin-left:auto;margin-right:auto;display:inline-block}
.qopt{text-align:left}
.qfields label{text-align:left}
.reassure{text-align:center}
.ctr-l{text-align:center}

@media (max-width:860px){
  .contact-page .ptitle{padding-top:18px}
  .qbar{justify-content:flex-start;text-align:left}
}


/* the opener hands the mark to the page: the hero mark waits, the intro mark flies
   onto its exact position, and they swap on the landing frame */
/* The nav mark is the one the flight lands ON, so it waits out of sight until then.
   The mark above the name is NOT hidden: it is held UNDRAWN, which is a different thing
   and the reason the handover no longer has a pause in it. Hiding it meant its entrance
   could only begin once it was revealed, and revealing it cost a 120ms fade first; held
   undrawn, it is already on screen with nothing painted, and the draw can start whenever
   we like, including while the flight is still settling. */
.intro-armed .route.live .hbar .lock svg{opacity:0}
.route.live .hbar .lock svg{transition:opacity .12s linear}
.intro-armed .hero .markstage .ring{stroke-dashoffset:133}
.intro-armed .hero .markstage .ml{stroke-dashoffset:var(--len)}


/* ================= the opener, stated once =================
   The overlay clears while the mark is still travelling, and the page mark is revealed
   on the landing frame, so the two never both show and nothing jumps. */
/* The BACKGROUND is a layer of its own so it can clear on its own. It used to be the
   overlay's own background, which meant fading the overlay faded the travelling mark with
   it: the mark vanished 0.58s into a 0.86s flight and the page mark was not revealed for
   another 0.36s, so there was a gap with no mark anywhere. Now the paper clears, the mark
   flies the whole way, and the handoff happens on the landing frame as intended. */
#intro{position:fixed;inset:0;z-index:9500;
  display:grid;place-items:center;pointer-events:none}
#intro::before{content:"";position:absolute;inset:0;background:var(--paper);
  transition:opacity .58s cubic-bezier(.4,0,.2,1)}
/* the svg is what is measured and what is transformed, so the origin and the transition
   belong to it and not to its wrapper */
#intro .introMark svg{transform-origin:top left;
  transition:transform .86s cubic-bezier(.45,.02,.18,1)}
#intro.out::before{opacity:0}

@media (prefers-reduced-motion:reduce){
  #intro{display:none}
  .wstage,.wlogo{transition:none}
  .wlogo{transform:none;opacity:1}
}


/* ============================ THE FOUNDER GATEWAY ============================ */
.gate-page{background:#1738f5}
.gate{min-height:100svh;display:flex;flex-direction:column;padding:30px 0 26px}
.gatebar{display:flex;align-items:center;justify-content:space-between;gap:20px}
/* Two doors, nothing between them. The centre column that held the founder is gone, so
   the doors take the room rather than leaving a hole where he used to be. */
.gatestage{flex:1;display:grid;grid-template-columns:1fr 1fr;
  align-content:center;align-items:center;gap:clamp(16px,3vw,40px);
  width:min(1180px,calc(100vw - 60px));margin:0 auto;padding:12px 0}
/* the doors carry the page on their own now, so they get real presence and their
   call to action sits at the foot of the card rather than crowding the heading */
.door{min-height:clamp(230px,34vh,320px);justify-content:space-between;
  padding:34px 34px 30px}
.doorarrow{margin-top:auto;font-size:26px}

/* the figure. transform only, so the browser can carry every frame on the compositor */


/* the two doors */
.door{position:relative;display:flex;flex-direction:column;gap:8px;text-decoration:none;
  color:var(--ink);padding:30px 28px 34px;border:1px solid var(--hair);border-radius:6px;
  background:var(--paper);
  transition:transform .55s cubic-bezier(.2,.75,.25,1),border-color .35s var(--ease),
             background .35s var(--ease)}
.door:hover{transform:translateY(-6px);border-color:var(--blue);background:#fff}
.doorlabel{color:var(--grey)}
.doortitle{font-size:clamp(34px,4.6vw,66px);font-weight:700;text-transform:uppercase;
  letter-spacing:-.045em;line-height:.92;margin-top:4px}
.doorsub{font-size:14px;color:var(--grey);line-height:1.4;max-width:230px}
.doorarrow{font-size:22px;color:var(--blue);margin-top:10px;
  transition:transform .5s cubic-bezier(.2,.75,.25,1)}
.door:hover .doorarrow{transform:translateX(8px)}
.door-r{align-items:flex-end;text-align:right}
.door-r .doorsub{text-align:right}
.gatehint{text-align:center;padding-top:6px;color:var(--grey)}
/* the founder is the subject here; the doors are the choice beside him */

@media (max-width:900px){
  .gatestage{grid-template-columns:1fr;gap:18px;padding:10px 0}
  .door{padding:20px 20px 24px}
  .door-r{align-items:flex-start;text-align:left}
  .door-r .doorsub{text-align:left}
  .doorsub{max-width:none}
  .gatebar .meta{display:none}
}
@media (prefers-reduced-motion:reduce){
    .blink   .door,.doorarrow{transition:none}
}


/* ---- the gateway sits on blue ---- */
.gate-page .gate{color:#fff}
.gate-page .lock{color:#fff}
.gate-page .lock svg path[stroke="#1c3af7"]{stroke:#fff}
.gate-page .gatebar .meta{color:rgba(255,255,255,.72)}
.gate-page .gatehint{color:rgba(255,255,255,.66)}
.gate-page .door{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.3);color:#fff}
.gate-page .door:hover{background:#fff;border-color:#fff;color:var(--ink)}
.gate-page .doorlabel{color:rgba(255,255,255,.7)}
.gate-page .door:hover .doorlabel{color:var(--grey)}
.gate-page .doorsub{color:rgba(255,255,255,.78)}
.gate-page .door:hover .doorsub{color:var(--grey)}
.gate-page .doorarrow{color:#fff}
.gate-page .door:hover .doorarrow{color:var(--blue)}

/* ---- footer: close the dead space ---- */
:root{--docksafe:64px}
footer{padding-top:0;padding-bottom:var(--docksafe)}
.fcell{padding:22px 20px 24px}
.ftag{font-size:18px;margin:0 0 12px}
.fcell a,.fcell .flink{font-size:13px;padding:3px 0}
.flogo{font-weight:700;letter-spacing:-.055em;line-height:.92;
  font-size:clamp(44px,10vw,142px);margin:14px 0 4px;color:var(--ink);
  overflow:visible;max-height:none}
.flegal{padding:12px 0 14px;font-size:10.5px}

@media (max-width:860px){
  :root{--docksafe:92px}
  .fcell{padding:16px 14px 18px}
  .flegal{padding:10px 0 12px}
}





/* ---- the route change carries its ground instead of cutting ---- */
body{transition:background-color 1s cubic-bezier(.87,0,.13,1)}
body.routing{transition:background-color 1s cubic-bezier(.87,0,.13,1)}
.route{transition:opacity .5s cubic-bezier(.87,0,.13,1)}

/* Native smooth scrolling, restored. The JS wheel hijack that used to fight it is gone:
   it took scrolling off the compositor and added ~10 frames of lag. This runs on the
   compositor and costs nothing. */
html{scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  }




/* ---- the toggle is a curtain, not a fade ----
   Measured off the reference: a two layer counter slide over 1.0s on expo.inOut. The
   shell rises from below while its inner content starts above and settles, so the
   boundary sweeps past content that appears to stay put. */
.route{overflow:clip}
body.routing .route.live{animation:curtainShell 1s cubic-bezier(.87,0,.13,1) both}
body.routing .route.live > *{animation:curtainInner 1s cubic-bezier(.87,0,.13,1) both}
@keyframes curtainShell{from{transform:translate3d(0,100vh,0)}to{transform:none}}
@keyframes curtainInner{from{transform:translate3d(0,-70vh,0)}to{transform:none}}
@media (prefers-reduced-motion:reduce){
  body.routing .route.live,
  body.routing .route.live > *{animation:none}
}




/* ---- project films on small screens ----
   The whole screenshot has to be visible, so there is no crop: the image keeps its
   natural aspect. To win back the size a crop would have bought, the frame breaks out
   of the wrap's padding and runs edge to edge, which is the only honest way to make it
   larger while still showing all of it. */
@media (max-width:860px){
  /* Full bleed measured against the DOCUMENT, not 100vw. 100vw counts the scrollbar,
     so it renders ~17px wider than the page and pushes a horizontal scrollbar on. The
     wrap's parent is already document width, so width:100% is the safe full bleed. */
  .casefilm .wrap{width:100%}
  .film{border-radius:0}
  .filmcap{padding-inline:34px}
}




/* ---- service rows: a hover you can actually see ----
   The only previous hover was the arrow moving six pixels, which is invisible in use.
   Four quiet things now happen together: a blue rule draws across the top edge, the row
   takes a faint blue wash, the name and its line step to the right, and the arrow
   travels further. Transform and opacity carry the movement, so it stays on the
   compositor; only the wash touches paint, which a hover can afford.
   The same rule serves the Services route and the service pages, which share .srow. */
.srow{transition:background-color .34s var(--ease)}
.srow::after{content:'';position:absolute;left:0;right:0;top:-1px;height:2px;
  background:var(--blue);transform:scaleX(0);transform-origin:left center;
  transition:transform .46s var(--ease)}
.srow .sname,.srow .body{transition:transform .46s var(--ease)}
.srow:hover,.srow:focus-visible{background:rgba(28,58,247,.05)}
.srow:hover::after,.srow:focus-visible::after{transform:scaleX(1)}
.srow:hover .sname,.srow:focus-visible .sname{transform:translateX(10px)}
.srow:hover .body,.srow:focus-visible .body{transform:translateX(5px)}
.srow:hover .arw,.srow:focus-visible .arw{transform:translateX(12px)}

/* on the blue route the wash and the rule invert so they read on colour */
.blue-route .srow:hover,.blue-route .srow:focus-visible{background:rgba(255,255,255,.07)}
.blue-route .srow::after{background:var(--blue)}

@media (hover:none){
  .srow::after{display:none}
  .srow:hover .sname,.srow:hover .body,.srow:hover .arw{transform:none}
}
@media (prefers-reduced-motion:reduce){
  .srow,.srow::after,.srow .sname,.srow .body,.srow .arw{transition:none}
  .srow:hover .sname,.srow:hover .body{transform:none}
}


/* ================= work: a grid of project cards =================
   Each card carries the client's own hero behind their mark, and on hover the still
   hands over to a recording of that site actually scrolling. The recording is a real
   desktop capture: 1440x900 at 2x, taken only after every image had loaded, fonts were
   ready and nothing was left mid-reveal.

   The video is not fetched until the pointer arrives (preload="none" plus a data-src),
   so a visitor who never hovers downloads six JPEGs and no video at all. Every still is
   frame 0 of its own recording, so the hand-over between them is a dissolve between two
   identical frames. A touch screen has no pointer to bring, and there the cards play in
   view instead. */
.wgrid{display:grid;grid-template-columns:repeat(2,1fr);gap:clamp(18px,2.4vw,34px)}
.wcard{position:relative;display:block;text-decoration:none;color:var(--ink)}
.wstage{position:relative;display:block;overflow:hidden;border-radius:16px;
  background:var(--night);aspect-ratio:16/10;isolation:isolate}
.wshot,.wvid{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  display:block}
/* THE RECORDING IS NEVER TRANSFORMED.
   The card used to hold its media at scale(1.02) and take it to 1.05 on hover. Both
   are a SECOND resampling stage: the video is already fitted to the card by object-fit,
   and a transform makes the compositor scale that result again, at a fractional factor,
   and on hover at a factor that changes on every frame for 1.4 seconds. Crisp web type
   sliding under a resample that keeps changing phase is exactly what shimmers. Drawn
   untransformed, the recording is fitted once and then simply slides.

   The card does not lose its hover: the mark steps aside and the wash lifts, which is
   the whole point of the gesture. The still and the recording still share one geometry,
   so the hand-over between them is registered. */
.wshot,.wvid{transform:none;transition:opacity .6s var(--ease)}
.wvid{opacity:0;z-index:1}
/* .ready means the recording is running AND the card is the one being asked. It is set
   no earlier than the frame the recording actually starts, and taken off the moment the
   pointer or the focus leaves, which hands the frame back to the still. */
.wcard.ready .wvid{opacity:1}
.wcard.ready .wshot{opacity:0}

/* the client's own site is busy, so the mark sits on a wash rather than fighting it */
.wveil{position:absolute;inset:0;z-index:2;
  background:
    radial-gradient(ellipse 66% 54% at 50% 50%,
      rgba(8,8,12,.80) 0%, rgba(8,8,12,.46) 62%, rgba(8,8,12,0) 100%),
    linear-gradient(180deg,
      rgba(8,8,12,.44) 0%, rgba(8,8,12,.30) 46%, rgba(8,8,12,.56) 100%);
  transition:opacity .5s var(--ease)}
.wcard:hover .wveil,.wcard:focus-visible .wveil{opacity:.30}

.wlogo{position:absolute;z-index:3;top:50%;left:50%;
  transform:translate(-50%,-50%) scale(.97);opacity:0;
  width:var(--lw,34%);height:auto;max-width:72%;object-fit:contain;
  /* the mark carries its own separation, so it stays legible over a client hero that
     happens to put its own type in the middle of the frame */
  filter:drop-shadow(0 2px 20px rgba(8,8,12,.62));
  transition:transform 1.2s var(--ease),opacity .62s var(--ease)}
.wcard:hover .wlogo,.wcard:focus-visible .wlogo{
  transition:transform .9s var(--ease),opacity .34s var(--ease)}
.wlogo.in{opacity:1;transform:translate(-50%,-50%) scale(1)}
.wcard:hover .wlogo.in,.wcard:focus-visible .wlogo.in{
  opacity:0;transform:translate(-50%,-50%) scale(1.06)}

.wmeta{display:block;padding:15px 2px 0}
.wname{display:block;font-size:10px;font-weight:700;letter-spacing:.22em;
  text-transform:uppercase;color:var(--grey)}
.ctitle{display:block;margin-top:7px;font-size:clamp(17px,1.7vw,25px);line-height:.98;
  color:var(--ink)}
.blue-route .wname{color:rgba(255,255,255,.7)}
.blue-route .ctitle{color:var(--paper)}

/* the project page runs its recording continuously */
.film{position:relative;aspect-ratio:16/10;border-radius:12px;overflow:hidden;
  background:var(--night);border:1px solid var(--hair);max-width:1040px;margin:0}
.film .filmvid{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;display:block;background:var(--night)}

@media (max-width:820px){
  .wgrid{grid-template-columns:1fr;gap:20px}
  /* the phone card is about half the linear size of the desktop one, so the mark takes
     a little more of it to stay legible: measured, 1.10 puts the ink at 7.3 per cent of
     the card against 6.0 on desktop, where 1.22 made it 9.0 and crowded the frame */
  .wlogo{width:calc(var(--lw,34%) * 1.10)}
}
@media (prefers-reduced-motion:reduce){
  .wshot,.wvid,.wveil,.wlogo{transition:none}
  .wlogo{opacity:1;transform:translate(-50%,-50%)}
  /* no recording is fetched under reduced motion, so the mark stays where it is */
  .wcard:hover .wlogo.in,.wcard:focus-visible .wlogo.in{
    opacity:1;transform:translate(-50%,-50%)}
}
/* a touch screen has no hover, so the mark simply stays on the card */
@media (hover:none){
  .wcard:hover .wlogo.in{opacity:1;transform:translate(-50%,-50%)}
  .wcard:hover .wveil{opacity:1}
}

/* ================= the method sections =================
   Added with Strategy V2, then redrawn once, because the first pass invented three
   different layouts for four blocks that are all saying the same kind of thing.

   ONE SYSTEM FOR ALL FOUR. What you buy, the two things we make, the rules and the six
   steps are now the same object: a hairline grid of cards, each one a blue numeral or
   eyebrow, a title on the display face, and body copy at a size a person can actually
   read. The route reads as one page instead of four unrelated tables, and nothing sits
   at a position decided by the width of its own text, which is what made the rules row
   look misaligned on every line.

   THE BODY SIZE. Card copy is 15px, not the 14px used for captions elsewhere. At three
   and four columns the measure is short, and 14px inside a 26px padded card reads as
   fine print rather than as the argument. */

/* the shared card body: one size for every card on the route.
   No .blue-route override here. One existed, and being two classes deep it outranked
   .sigbody and painted the signature copy mid grey on the near black band. */
.cbody{font-size:15px;line-height:1.5;color:var(--grey);font-weight:400;margin:0}

/* the shared hairline card grid */
.bgrid,.siggrid,.rgrid{display:grid;gap:1px;background:var(--hair);
  border:1px solid var(--hair)}
.brow,.rcard{background:var(--paper);padding:30px 26px 34px;
  display:flex;flex-direction:column;gap:12px;align-items:flex-start}
.bgrid{grid-template-columns:repeat(3,1fr)}
.rgrid{grid-template-columns:repeat(4,1fr)}
.blue-route .bgrid,.blue-route .rgrid{background:rgba(28,58,247,.24);
  border-color:rgba(28,58,247,.24)}

/* the blue numeral, one size across the route, matching the process cards */
.bnum{font-size:34px;font-weight:700;color:var(--blue);line-height:1;
  font-variant-numeric:tabular-nums;letter-spacing:-.04em}
.brow .sname,.rcard .rval{font-size:clamp(18px,1.75vw,23px);line-height:1.02;
  letter-spacing:-.035em}

/* the rules: the eyebrow names the thing, the display line is the number.
   These labels are content rather than decoration, so they sit above the 10px the site
   uses for section markers, with the tracking eased to match. */
.rlab{color:var(--grey)}
.rcard .rlab,.ptrack.six .pcard .pstage{font-size:12px;letter-spacing:.14em;line-height:1.3}
.rval{font-weight:700;color:var(--blue);
  font-family:inherit;text-transform:none}

/* THE GROUNDS OF THIS ROUTE, and the rule they follow.
   It used to run mist, ink, paper, ink, paper, ink, blue: three near-black slabs
   alternating with three paper ones, six times over, with the colour carrying nothing
   except "here is the next section". Then the two paired sections shared paper and read as
   one long section instead of two answers. Both are fixed by giving each ground a job:

       INK is where the site asserts something and nothing else is on screen. Two of them,
           the furthest apart sections on the page: the room that opens the route, and the
           process band near the end. This is one of them.
       MIST is the shape of the offer: the hero you arrive on, and "What it comes out as",
           which is literally the list of shapes. It is the palest tone in our own mark, so
           it joins the light family rather than being a fourth colour.
       PAPER is where you read detail: "What you get", and the deadlines.

   mist, ink, paper, mist, ink, paper, ink, blue. No two neighbours share a ground, and the
   blue still appears exactly once, on the ask. */
.sigband{background:var(--ink);color:var(--paper);padding:104px 0}
.sigband .meta{color:rgba(251,251,249,.66)}
.siggrid{grid-template-columns:repeat(2,1fr);
  background:rgba(251,251,249,.14);border-color:rgba(251,251,249,.14)}
.sigcard{background:var(--ink);padding:34px 30px 38px;
  display:flex;flex-direction:column;gap:16px}
.sighead{display:flex;align-items:center;justify-content:space-between;gap:20px;
  padding-bottom:16px;border-bottom:1px solid rgba(251,251,249,.14)}
.sigcard .sname{color:var(--paper);font-size:clamp(20px,2vw,27px)}
/* ONE declaration of each thing. This rule used to set font-size and letter-spacing
   twice over, the second of each quietly beating the first, which is what a rule looks
   like when it has been added to rather than edited.

   CENTRED PROPERLY, both ways, and neither is automatic here:

   VERTICALLY, the padding was 5px on top against 4px underneath, so the word sat a pixel
   high inside its own border. It is a fixed height now with the word centred in it by the
   flexbox, so there is no pair of numbers to keep in sync.

   HORIZONTALLY, letter-spacing adds its space AFTER the last letter as well as between
   letters, and centring counts that trailing space as part of the word. At .18em the ink
   therefore sits half a space, .09em, left of the middle. The text-indent puts it back.
   This is the same trap the hero wordmark hit from the other direction. */
.sigtag{display:inline-flex;align-items:center;justify-content:center;
  height:24px;min-width:66px;padding:0 11px;box-sizing:border-box;
  font-size:11px;font-weight:700;line-height:1;letter-spacing:.18em;text-indent:.09em;
  text-transform:uppercase;white-space:nowrap;
  color:var(--blue320);border:1px solid rgba(138,160,255,.42);border-radius:2px}
.sigband .cbody,.sigbody{color:rgba(251,251,249,.86)}
.sigpoints{list-style:none;margin:2px 0 0;padding:0}
.sigpoints li{padding:12px 0;font-size:14px;line-height:1.5;
  color:rgba(251,251,249,.7);border-bottom:1px solid rgba(251,251,249,.1)}
.sigpoints li:last-child{border-bottom:0;padding-bottom:0}

/* six steps read as two rows of three rather than six thin columns */
.ptrack.six{grid-template-columns:repeat(3,1fr)}
.ptrack.six .pcard{padding:30px 26px 34px;gap:12px}
.ptrack.six .pcard .body{font-size:15px;line-height:1.5}
.ptrack.six .pcard .sname{font-size:clamp(17px,1.6vw,21px);line-height:1.02}

/* the section leads on the route need room before the grid under them */
.rgrid,.bgrid,.stack{margin-top:34px}
.sigband .siggrid{margin-top:34px}
.procband .ptrack{margin-top:34px}

/* what we bring: one passage, set as two columns of real prose */
.expogrid{margin-top:30px;display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(28px,4vw,64px)}
.expolead{grid-column:1 / -1;font-size:clamp(20px,2.4vw,30px);line-height:1.16;
  letter-spacing:-.03em;color:var(--ink);margin:0 0 14px;max-width:40ch;font-weight:400}
.expocol p{font-size:16px;line-height:1.62;color:var(--grey);margin:0 0 18px}
.expocol p:last-child{margin-bottom:0}

.qhint{margin:-6px 0 18px}

@media (max-width:1100px){
  .rgrid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:900px){
  .bgrid,.siggrid{grid-template-columns:1fr}
  .ptrack.six{grid-template-columns:repeat(2,1fr)}
  .expogrid{grid-template-columns:1fr;gap:0}
  .expolead{max-width:none}
  .expocol p{margin-bottom:18px}
}
@media (max-width:620px){
  .ptrack.six{grid-template-columns:1fr}
  .rgrid{grid-template-columns:1fr}
  .sighead{align-items:flex-start}
}

/* a longer statement, set a step down so five lines still read as one block */
.stmt.sml{font-size:clamp(30px,5.4vw,72px);line-height:.92}

/* ================= the two call-to-action buttons =================
   Rebuilt after Alex called the pair wonky, and the measurements agreed with him:

     radius 99px          both buttons were lozenges
     pad 0 15px 0 40px    a leftover from the deleted glyph coin, which put the WhatsApp
                          content 25px off centre (measured L41 against R16)
     two breakpoints      the row stacked at 860px but the buttons only went full width at
                          760px, so between those two the pair wrapped into a broken column

   The rules now, and they are deliberately few:

   ONE HEIGHT, one number, --ctah. Both buttons take it, so the pair can never disagree.
   The height comes from the variable and the type is centred inside it, rather than from
   padding that has to be kept in sync with a font size in three places.

   SYMMETRIC PADDING on both. The content of each button is centred by the flexbox, so the
   label and the mark sit as one group in the middle with equal air either side.

   ONE BREAKPOINT, 860px, the same one the hero row itself uses. Above it the pair sits
   side by side at its natural width; below it the two share the full width equally. There
   is no band where they wrap. */
:root{--wa:#25D366;--ctah:58px;--ctah-sm:52px}

.ctarow{display:flex;align-items:stretch;gap:12px;flex-wrap:nowrap}
.ctr.ctarow{justify-content:center}
.hrow2 .ctarow{margin-top:0}
.ask .ctarow{margin-top:34px;justify-content:center}
.ask .ctarow .cta{margin-top:0}

/* one height for the pair, and the type optically centred in it */
.ctarow .cta{display:inline-flex;align-items:center;justify-content:center;
  height:var(--ctah);padding-top:0;padding-bottom:0;line-height:1;white-space:nowrap}
.ctarow .cta-lg{height:var(--ctah)}
.ask .ctarow .cta{height:var(--ctah-sm)}

/* the second door: the mark and the word as one centred group, equal air either side */
.cta-wa{gap:11px;padding-left:30px;padding-right:30px;
  background:var(--wa);color:var(--ink);border:1px solid transparent;
  transition:background-color .28s var(--ease),color .28s var(--ease),
             border-color .28s var(--ease)}
.cta-wa .lbl{white-space:nowrap}
/* No disc behind the mark. WHITE on the green pill, which is WhatsApp's own lockup, and
   WhatsApp green wherever the ground is not green. The word beside it carries the meaning
   at 9.45:1. */
.cta-wa .wamark{color:#fff;flex:0 0 auto;display:block;
  transition:color .28s var(--ease)}

.cta-wa:hover,.cta-wa:focus-visible{background:var(--ink);color:var(--paper);
  border-color:var(--ink)}
.cta-wa:hover .wamark,.cta-wa:focus-visible .wamark{color:var(--wa)}

/* on blue the hover goes to paper, the same move the primary button makes there */
.cta-wa.on-blue,.ask .cta-wa{
  background:var(--wa);color:var(--ink);border-color:transparent}
.cta-wa.on-blue:hover,.cta-wa.on-blue:focus-visible,
.ask .cta-wa:hover,.ask .cta-wa:focus-visible{
  background:var(--paper);color:var(--ink);border-color:var(--paper)}
.cta-wa.on-blue:hover .wamark,.cta-wa.on-blue:focus-visible .wamark,
.ask .cta-wa:hover .wamark,.ask .cta-wa:focus-visible .wamark{color:var(--wa)}

.cta-wa:focus-visible{outline:2px solid var(--ink);outline-offset:3px}

/* the same breakpoint the hero row uses, so there is no band where the pair wraps */
@media (max-width:860px){
  :root{--ctah:54px}
  .ctarow{width:100%}
  .ctarow .cta{flex:1 1 0;min-width:0}
  .cta-wa{padding-left:16px;padding-right:16px}
}
/* two full width buttons cannot hold their labels much under this, so they stack */
@media (max-width:400px){
  .ctarow{flex-wrap:wrap}
  .ctarow .cta{flex:1 1 100%}
}
@media (prefers-reduced-motion:reduce){
  .cta-wa,.cta-wa .wamark{transition:none}
}

/* ================= the cookie choice =================
   Accept and Reject are the SAME control, drawn the same size with the same weight.
   A prominent Accept beside a faint Reject is not a free choice, and this bar exists
   precisely so the answer can be no. Do not restyle one to lead the other. */
.cbar{position:fixed;left:0;right:0;bottom:0;z-index:9000;background:var(--ink);
  color:var(--paper);border-top:1px solid rgba(251,251,249,.16);
  transform:translateY(101%);transition:transform .5s var(--ease)}
.cbar.in{transform:translateY(0)}
.cbarin{max-width:1400px;margin:0 auto;padding:22px clamp(18px,4vw,54px);
  display:flex;align-items:center;justify-content:space-between;gap:clamp(20px,4vw,56px)}
.cbartext{max-width:62ch}
.cbartl{margin:0 0 6px;font-size:16px;font-weight:700;letter-spacing:-.02em;color:var(--paper)}
.cbar .body{color:rgba(251,251,249,.72);margin:0}
.cbar .body a{color:var(--blue320);text-decoration:underline;text-underline-offset:3px}
.cbarbtns{display:flex;gap:10px;flex-shrink:0}
.cbtn{font:inherit;font-size:13px;font-weight:700;letter-spacing:.02em;
  padding:13px 22px;border-radius:2px;cursor:pointer;min-height:44px;
  transition:background-color .25s var(--ease),color .25s var(--ease)}
.cbtn-y{background:var(--paper);color:var(--ink);border:1px solid var(--paper)}
.cbtn-n{background:transparent;color:var(--paper);border:1px solid rgba(251,251,249,.5)}
.cbtn-y:hover{background:var(--blue320);border-color:var(--blue320);color:var(--ink)}
.cbtn-n:hover{background:rgba(251,251,249,.12);border-color:var(--paper)}
.cbtn:focus-visible{outline:2px solid var(--blue320);outline-offset:2px}

/* While the choice is open the route toggle stands down. It is fixed at the bottom of
   the viewport at z-index 8000 and was drawing straight over the bar, which put a control
   on top of the question it was covering. */
.consent-open .dock{transform:translateX(-50%) translateY(140px);pointer-events:none}
.consent-open .handle{opacity:0;pointer-events:none}

/* the way back to the choice, styled as the footer links beside it */
.flink{font:inherit;background:none;border:0;padding:3px 0;cursor:pointer;
  color:inherit;text-align:left;line-height:19px;text-decoration:none}
.flink:hover{text-decoration:underline}

/* the form saying it did not send, rather than losing the message quietly */
.qerr{margin:12px 0 0;font-size:14px;line-height:1.5;color:#b3261e}

@media (max-width:820px){
  .cbarin{flex-direction:column;align-items:flex-start;gap:16px}
  .cbarbtns{width:100%}
  .cbtn{flex:1}
}
@media (prefers-reduced-motion:reduce){ .cbar{transition:none} }

/* ---- pointer targets ----
   Every standalone link is at least 24px tall. These sit low in the sheet so they
   apply after the footer's own sizing, and they buy the height with line-height
   rather than padding, which would move the text off its baseline. */
.meta a{display:inline-block;line-height:24px}
.outlink{margin-top:12px;padding-top:6px;line-height:19px}
.fcell a,.fcell .flink{line-height:19px;padding:3px 0}

/* ---- the phone pass ----
   Measured on a real 390px viewport, not an iframe. */

/* A forced break helps a line that fits either side of it. On a phone this one did not:
   the first half wrapped anyway and left a single word stranded on its own line. */
@media (max-width:820px){
  br.wide{display:none}
}
.mani{text-wrap:balance}

/* The label system is 10px uppercase with wide tracking, which is right on a desktop
   and a little tight to read at arm's length on a phone. One pixel, no change of
   character. The footer's legal line stays where it is: small print reads as small
   print, and it is not something anyone has to scan. */
@media (max-width:860px){
  .meta,.wname{font-size:11px}
  .hsub{font-size:11px}
  /* the legal line is the one run of ordinary sentences left at small-print size, and
     on a phone it is close enough to the thumb to be read: give it a readable size
     rather than leaving it as the smallest thing on the page */
  .flegal{font-size:12px}
}

/* ================= the hero name, choreographed =================
   Elias: the hero is the weakest screen, and the name just sits there. So the word is now
   built one letter at a time.

   THREE LEVELS, and each does exactly one job. .ch is the clipping box, <i> carries the
   load animation that rises the letter out of it, <b> carries the pointer lift. They
   cannot be collapsed into one element: an animation with fill:forwards permanently wins
   over a transition on the same property, so a single element can have the entrance or
   the hover, never both.

   THE BOX IS TALLER THAN THE GLYPH, by .16em at the top, so the hover lift has somewhere
   to go without meeting the clip. The matching negative margin keeps the line box the
   size it always was, so nothing below the wordmark moves.

   TRACKING IS REBUILT AS MARGIN, AND CANCELLED INSIDE THE LETTERS. letter-spacing only
   applies between characters inside one text run, and five separate spans are five runs,
   so the -.052em that gives the wordmark its set would vanish. It comes back as a negative
   right margin. The inherited letter-spacing then has to be turned OFF inside each span,
   because CSS also adds it after the last character of a run: leaving it on applied the
   tracking twice, pulled the word 54px tighter than it should be at hero size, and drove
   the l and the t into each other. */
.wordmark.anim{display:flex;align-items:flex-end}
.wordmark.anim .ch{display:block;overflow:hidden;letter-spacing:normal;
  margin-right:-.052em;padding:.16em 0 .22em;margin-top:-.16em;margin-bottom:-.22em}
.wordmark.anim .ch i{display:block;font-style:normal}
/* The rise belongs to the moment the opener starts clearing, not to page load: at load the
   overlay is still opaque and the whole thing happened behind it. NOTE the shape of this
   rule. The letters are upright and visible by default and the animation is added by a
   class, rather than the letters starting hidden and being animated into place. That way a
   page whose script never runs still shows its own name, which is the one thing the hero
   cannot get wrong. animation-fill-mode: both supplies the hidden start once it does. */
/* HELD DOWN WHILE THE OPENER IS ARMED. The letters are upright by default so a page with
   no script still shows its name, but the overlay's paper starts clearing 0.58s into the
   flight, which put the finished name on screen a full second before its own entrance:
   it appeared, snapped back down at the landing and rose a second time. .intro-armed is
   set by the same script that runs the opener, so hiding them there costs the no-script
   case nothing, and the animation's backwards fill takes over on the very frame that class
   is removed, so there is no gap between the two. */
.intro-armed .wordmark.anim .ch i{transform:translateY(152%)}
/* THE CLASS IS ON THE WORDMARK, not on <html>. Adding it to the root element to start
   this cost a 50ms frame on desktop, because a root class invalidates style for the whole
   document and this one carries two complete routes. Here it invalidates one element and
   its five children. */
/* Each letter is promoted for the length of its own rise, so the five of them composite
   rather than repainting the hero's largest type five times over. will-change is dropped
   the moment the animation ends, because a layer that outlives its animation is just memory
   the compositor has to keep. */
.wordmark.anim.rise .ch i{
  will-change:transform;
  animation:wmrise .8s cubic-bezier(.19,1,.22,1) both;
  animation-delay:calc(.6s + var(--i) * .062s)}
/* BOTH ends stated. This used to carry only a `to` frame and took its start from the
   element's own transform. Once the letters were made upright by default, so that a
   page with no script still shows its name, that implied start became `none` and the
   animation ran for 950ms moving nothing at all. */
@keyframes wmrise{from{transform:translateY(152%)}to{transform:translateY(0)}}
/* the pointer runs through the word rather than lighting all of it at once */
.wordmark.anim .ch i b{display:block;font-weight:inherit;
  transition:transform .5s var(--ease),color .5s var(--ease);
  transition-delay:calc(var(--i) * .04s)}
.wordmark.anim:hover .ch i b{transform:translateY(-.055em);color:var(--blue)}

/* ================= the hero mark arrives on its own =================
   The opener now lands on the NAV mark in the corner, which frees the mark above the name
   to make its own entrance rather than being the thing handed to. Alex asked for exactly
   that: the mark enters alone, and the name follows it.

   It draws itself, the same stroke-by-stroke build the opener uses, because that is the
   mark's own language and it is already written. Two things make it safe:

   THE DRAWN STATE IS THE DEFAULT. The base rule leaves the strokes complete and the
   animation is added by a class, so a page whose script never runs shows a finished mark
   rather than an empty one. The keyframes state BOTH ends for the same reason: with the
   base at zero an implied `from` would animate from complete to complete, which is the
   silent no-op the name's rise already shipped once.

   IT IS DRIVEN BY THE LANDING, not by the start of the flight. The mark is hidden until
   the opener finishes, so an entrance armed any earlier plays behind the overlay and is
   never seen. */
.hero .markstage .ring,.hero .markstage .ml{stroke-dashoffset:0;animation:none}
/* No delay on the ring. The draw is started at the moment we want it seen, from JS, so a
   delay here would only reintroduce the pause it was written to remove. */
/* Quicker than the opener's own draw, on purpose. The opening mark has a second of
   silence to fill and can take its time; this one is answering a movement that has just
   happened and has to keep up with it. */
.hero .markstage.draw .ring{
  animation:drawRing .66s cubic-bezier(.33,0,.2,1) 0s both}
.hero .markstage.draw .ml{
  animation:drawLine .44s cubic-bezier(.3,0,.2,1) calc(var(--dl) * .45) both}

/* the whole lockup answers the scroll: app.js writes the transform, this only declares
   the property so the compositor promotes the layer once instead of every frame */
.hero .hfoot{will-change:transform}

@media (prefers-reduced-motion:reduce){
  .wordmark.anim.rise .ch i{animation:none}
  .hero .markstage.draw .ring,.hero .markstage.draw .ml{animation:none}
  .wordmark.anim .ch i b,.wordmark.anim:hover .ch i b{transition:none;transform:none}
  .hero .hfoot{will-change:auto;transform:none!important;opacity:1!important}
}

/* ================= the services hero, in the logo's lighter tones =================
   Elias: the full-bleed saturated blue is too heavy for the screen a prospect lands on.
   The ground is now the palest tone in our own mark, #8aa0ff taken almost to paper, with
   one soft bloom of that periwinkle so the field is not flat.

   The consequence worth stating: this room is LIGHT, so everything in it goes back to ink.
   The topbar and the running mark are built in their light variants again, and the mark
   gets its real colours back instead of being flattened to white. The near-black statement
   room directly below now does the contrast work the blue used to do, which is a better
   rhythm anyway: light, then dark, then paper. */
:root{--mist:#eef0fb;--mist2:#e3e8f9}
.hero.onmist{background:var(--mist);color:var(--ink);margin:0;overflow:hidden}
.hero.onmist::before{content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(120% 78% at 82% 4%,rgba(138,160,255,.42) 0%,rgba(138,160,255,0) 62%),
             linear-gradient(180deg,rgba(227,232,249,0) 42%,var(--mist2) 100%)}
.hero.onmist>.wrap{position:relative;z-index:1}
.hero.onmist .wordmark{color:var(--ink)}
.hero.onmist .hsub{color:var(--grey)}
.hero.onmist .lock{color:var(--ink)}
.hero.onmist .meta,.hero.onmist .meta a{color:var(--grey)}

/* ================= the second door, floating =================
   It came out of the hero, so it has to exist somewhere on every page. Fixed to the right
   edge at mid height on a pointer screen: that is the one band clear of the toggle at the
   bottom centre, the consent bar along the bottom and the footer. It does not move with
   the scroll, does not dock and does not transform. Alex cut that.
   On a phone the right edge at mid height would sit on top of the reading column, so
   there it goes to the bottom right, above the toggle, with the notch inset respected. */
.wafloat{position:fixed;right:24px;bottom:calc(env(safe-area-inset-bottom,0px) + 30px);
  width:54px;height:54px;
  border-radius:50%;display:flex;align-items:center;justify-content:center;z-index:7998;
  background:var(--wa);color:#fff;text-decoration:none;
  box-shadow:0 10px 26px rgba(18,18,18,.18);
  transition:background-color .28s var(--ease),color .28s var(--ease),
             transform .28s var(--ease),opacity .24s var(--ease)}
.wafloat .wamark{display:block}
.wafloat:hover,.wafloat:focus-visible{background:var(--ink);color:var(--wa);transform:scale(1.07)}
.wafloat:focus-visible{outline:2px solid var(--ink);outline-offset:4px}
.watip{position:absolute;right:calc(100% + 12px);bottom:16px;white-space:nowrap;
  font-size:9px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;
  background:var(--ink);color:var(--paper);padding:8px 11px;border-radius:2px;
  opacity:0;transform:translateX(7px);pointer-events:none;
  transition:opacity .24s var(--ease),transform .24s var(--ease)}
.wafloat:hover .watip,.wafloat:focus-visible .watip{opacity:1;transform:none}
/* the consent bar owns the screen until it is answered, exactly as the toggle does */
.consent-open .wafloat{opacity:0;pointer-events:none}
@media (prefers-reduced-motion:reduce){
  .wafloat,.watip{transition:none}
  .wafloat:hover,.wafloat:focus-visible{transform:none}
}

/* NO PLAY BUTTON, ON ANY RECORDING, EVER. None of these carry a controls attribute, so
   a browser should paint nothing over them, and on a desktop none does. iOS is the
   exception: whenever it refuses to autoplay, which it does with Low Power Mode on, it
   paints its own start-playback control on top of the poster. That control is the one
   thing on a case page a visitor might press expecting sound, and it makes a recording
   look like a video player rather than like the site it is showing. It is hidden here, and
   app.js retries play() on the first gesture so the recording actually runs rather than
   sitting silently behind a hidden button. */
.filmvid::-webkit-media-controls,
.filmvid::-webkit-media-controls-start-playback-button,
.filmvid::-webkit-media-controls-panel,
.filmvid::-webkit-media-controls-overlay-play-button,
.wvid::-webkit-media-controls,
.wvid::-webkit-media-controls-start-playback-button,
.wvid::-webkit-media-controls-overlay-play-button{
  display:none!important;-webkit-appearance:none;appearance:none;opacity:0}
.filmvid,.wvid{pointer-events:none}

/* ================= the case film: desktop and phone, in parallel =================
   Alex, from lateliergrowth.com: the desktop recording plays automatically and a phone
   recording of the same site plays beside it. Both are captured through the same gate as
   each other, so the two are the same site in the same state, not a screen and a mockup.

   Laid out as a grid rather than an absolutely positioned overlay, so the phone can never
   escape its column or land on the caption. Both are bottom aligned: at these ratios the
   16:10 desktop is the taller of the two, so they stand on one line. */
.filmpair{display:grid;grid-template-columns:minmax(0,1fr) clamp(132px,16.5%,208px);
  gap:clamp(14px,2.2vw,30px);align-items:end;max-width:1260px}
.filmpair .film{max-width:none}
:root{--bezel:6px}
.film-d,.film-m{aspect-ratio:auto;height:auto;background:var(--ink);
  border:var(--bezel) solid var(--ink)}

/* THE STILL IS WHAT IS ON SCREEN until the recording is genuinely running.
   The video is transparent until it fires `playing`, so a phone that refuses to autoplay
   shows a frame of the site and NOT a play button: the element that would carry one cannot
   be seen. The still is frame 0 of the recording, an invariant this project already keeps,
   so the swap is invisible. It also gives the film its box before the video has loaded a
   byte, which is one less thing moving while the page settles. */
.film-d .filmstill,.film-m .filmstill{display:block;width:100%;height:auto}
.film-d .filmvid,.film-m .filmvid{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;opacity:0;transition:opacity .38s var(--ease)}
.film-d .filmvid.on,.film-m .filmvid.on{opacity:1}
/* the screen and the phone differ in their corners, which is what a screen and a phone
   actually differ in, and in nothing else */
.film-d{border-radius:14px}
.film-d .filmstill{border-radius:8px}
.film-m{border-radius:26px;box-shadow:0 20px 46px rgba(18,18,18,.22)}
.film-m .filmstill{border-radius:19px}

@media (max-width:820px){
  /* On a phone the two stop sitting side by side, because side by side the phone would be
     about 55px wide, which is a thumbnail rather than a device. Alex asked for it small and
     standing on the lower LEFT of the desktop recording, overlapping it, so that is what it
     does: out of the grid, over the screen, hanging below it by a little.
     It is sized from the screen's HEIGHT rather than the page width, so it keeps the same
     proportion against the thing it is standing on at every width. */
  .filmpair{display:block;position:relative;max-width:none}
  .filmpair .film-d{width:100%}
  :root{--bezel:4px}
  .film-m{position:absolute;right:5.5%;bottom:-24px;height:84%;width:auto;
    border-radius:16px;box-shadow:0 12px 26px rgba(18,18,18,.34)}
  .film-m .filmstill{height:100%;width:auto;border-radius:12px}
  .film-d{border-radius:11px}
  .film-d .filmstill{border-radius:7px}
  /* The phone hangs below the screen, so the caption starts below the phone. Both figures
     are fixed px: the overhang used to be a percentage of the film's height, which grew with
     the viewport while this margin did not, and at 768px the phone landed on the caption. */
  .casefilm .filmcap{margin-top:42px}
}

/* ================= what we did, on a case study =================
   Elias: the cases said what the site is and never what the work was. */
.points.did{display:grid;grid-template-columns:1fr 1fr;gap:0 56px;margin-top:26px}
.points.did li{font-size:clamp(15px,1.3vw,18px);line-height:1.4;padding:16px 0}
.didnote{margin:22px 0 0;max-width:660px;font-size:13.5px;line-height:1.55;color:var(--grey)}

/* ================= where this comes up =================
   The use cases on a service page. One hairline grid, the same construction as the values
   block, so the site keeps one way of making a set of cards rather than inventing a
   second. The link sits on the bottom edge whatever the body length, so the row of cards
   reads as a row. */
.ugrid{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--hair);
  border:1px solid var(--hair);margin-top:34px}
.ucard{background:var(--paper);padding:28px 24px 30px;display:flex;flex-direction:column}
.uct{display:block;font-size:clamp(17px,1.45vw,21px);font-weight:700;letter-spacing:-.022em;
  line-height:1.12;margin-bottom:12px;text-wrap:balance}
.ucard .body{margin:0}
.ucard .outlink{margin-top:auto;padding-top:18px}

@media (max-width:860px){
  .ugrid{grid-template-columns:1fr}
  .ucard{padding:22px 18px 26px}
  .points.did{grid-template-columns:1fr;gap:0}
  .points.did li{font-size:16px;padding:14px 0}
  /* On an ordinary page there is no toggle, so the corner is free and the second door
     sits where a second door sits: bottom right. */
  .wafloat{right:14px;width:50px;height:50px;
    bottom:calc(env(safe-area-inset-bottom,0px) + 20px)}

  /* THE WORK AND SERVICES PAGE IS DIFFERENT, and Alex set both halves of it. The bottom
     belongs to the toggle there, so past the first section the door moves to the top
     right, which is empty once the hero's own bar has scrolled away. And in the first
     section it is not there at all: that screen is the mark, the name and one button. */
  .has-routes .wafloat{bottom:auto;top:calc(env(safe-area-inset-top,0px) + 16px)}
  .has-routes.hero-in-view .wafloat{display:none}
  .watip{display:none}
}

/* ================= THE OPENER, REBUILT AS A LOCKUP THAT WRITES ITSELF =================
   Alex's direction, in order: the mark alone in the centre; "built" types in beside it in
   the real typeface and pushes the mark left so the finished lockup is centred; the word
   untypes and the mark returns to the centre; then it flies to the corner.

   THE WORD IS OUT OF FLOW. It hangs off the right edge of the mark and never contributes
   width, so revealing a letter costs no layout at all. The push is ONE transform on the
   wrapper and each letter is opacity and transform only, which is the whole reason this
   can hold 60fps on a phone. Letting the letters take width and the flexbox re-centre
   would relayout the lockup ten times across the sequence.

   THE PUSH DISTANCE IS COMPUTED, NOT GUESSED. app.js measures the word once the real
   typeface has loaded and translates the wrapper by exactly half of what the word and its
   gap add to the group, which is what leaves the finished lockup centred on the screen.
   Everything here waits for document.fonts.ready for the same reason: typing in Arial and
   swapping to Familjen Grotesk half way through would be the hardest cut in the sequence.

   The mark's own draw is faster here than anywhere else on the site: it has to be finished
   before the first letter lands, and the sequence after it is long. */
:root{--imark:clamp(104px,24vw,220px)}

.introLock{position:relative;z-index:1;width:var(--imark);
  transform:translate3d(0,0,0);
  transition:transform .52s cubic-bezier(.33,0,.18,1)}
#intro .introMark{width:100%;margin:0}
#intro .markstage{max-width:none;margin:0}

/* the mark draws itself quicker in the opener than it does on the page */
#intro .markstage .ring{animation-duration:.68s;animation-delay:.08s}
#intro .markstage .ml{animation-duration:.34s;animation-delay:calc(var(--dl) * .58)}

.introWord{position:absolute;left:100%;top:50%;
  margin-left:calc(var(--imark) * .15);
  transform:translateY(-50%);
  display:flex;align-items:baseline;
  font-weight:700;line-height:.74;font-size:calc(var(--imark) * .72);
  color:var(--ink);white-space:nowrap;pointer-events:none}
/* ONE transition, declared once, and the classes change only VALUES.
   The untype used to redeclare the whole transition shorthand, which restarts the property
   on all five letters at the moment they are asked to move, and that showed up as the one
   late frame per run that survived everything else. will-change lives on the base rule for
   the same reason: adding it per phase creates five compositor layers in the middle of an
   animation. The letters exist for about two seconds inside an overlay that is then
   removed, so holding the layers for that long costs nothing. */
.introWord i{display:block;font-style:normal;letter-spacing:normal;
  margin-right:-.052em;
  opacity:0;transform:translateY(.14em) scale(.92);
  will-change:opacity,transform;
  transition:opacity .13s linear,transform .2s cubic-bezier(.2,.86,.28,1)}

/* typed: each letter lands a beat after the one before it */
.introWord.typing i{opacity:1;transform:none;
  transition-delay:calc(var(--i) * .064s)}
/* untyped: taken away from the end of the word backwards, the way a backspace does it */
.introWord.untyping i{opacity:0;transform:translateY(.14em) scale(.92);
  transition-delay:calc((4 - var(--i)) * .05s)}

@media (prefers-reduced-motion:reduce){
  .introLock,.introWord i{transition:none}
  #intro .markstage .ring,#intro .markstage .ml{animation:none;stroke-dashoffset:0}
}

/* ---------------- the 404 ----------------
   The list of places to go instead is the whole point of the page, so the links look like
   links at rest: the site's other lists are not clickable and these are. */
.points a.nf{color:var(--ink);text-decoration:none;display:flex;align-items:baseline;
  gap:12px;transition:color .24s var(--ease)}
.points a.nf::after{content:"92";color:var(--blue);margin-left:auto;font-size:.9em;
  transition:transform .24s var(--ease)}
.points a.nf:hover,.points a.nf:focus-visible{color:var(--blue)}
.points a.nf:hover::after{transform:translateX(5px)}

/* ---------------- questions ----------------
   Written as <details> so it needs no script and is keyboard operable by default, and so
   the answers stay in the DOM whether a row is open or shut, which is what makes them
   readable by a crawler and quotable by an answer engine.
   The marker is replaced rather than hidden: a summary with no affordance at all is the
   same mistake the format rows made on a phone. */
.faq{border-top:1px solid var(--hair)}
.faqrow{border-bottom:1px solid var(--hair)}
.faqq{list-style:none;cursor:pointer;padding:22px 44px 22px 0;position:relative;
  font-size:clamp(16px,1.6vw,21px);font-weight:600;letter-spacing:-.015em;line-height:1.3;
  color:var(--ink);transition:color .24s var(--ease)}
.faqq::-webkit-details-marker{display:none}
.faqq::after{content:"";position:absolute;right:6px;top:50%;width:13px;height:2px;
  background:var(--blue);transform:translateY(-50%)}
.faqq::before{content:"";position:absolute;right:11.5px;top:50%;width:2px;height:13px;
  background:var(--blue);transform:translateY(-50%);transition:transform .28s var(--ease)}
.faqrow[open] .faqq::before{transform:translateY(-50%) rotate(90deg)}
.faqq:hover,.faqq:focus-visible{color:var(--blue)}
.faqq:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
.faqa{margin:0 0 24px;max-width:70ch;color:var(--grey)}
@media (prefers-reduced-motion:reduce){ .faqq,.faqq::before{transition:none} }
