/* ==========================================================================
   Rovian — Stone
   The design system from demos/11d-field-stone.html, extracted and extended
   with the components the case studies need.
   ========================================================================== */

/* ---------- type ---------- */
/* Instrument Sans, self-hosted. It used to come from Google's CDN, which meant
   every visitor's IP reached a third party before a single word was painted —
   awkward for a site whose privacy page says it hands nothing to anyone.
   Serving it from our own origin also drops two DNS lookups and a TLS handshake
   off the critical path.

   Both subsets are required, not just latin: ă, ș and ț live in latin-ext, so
   dropping it would leave the Romanian pages rendering diacritics from a
   fallback face. unicode-range keeps that file unfetched for English readers. */
@font-face{
  font-family:"Instrument Sans";
  font-style:normal;
  font-weight:400 700;
  font-stretch:75% 100%;
  font-display:swap;
  src:url(../fonts/instrument-sans-latin.woff2) format("woff2");
  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:"Instrument Sans";
  font-style:normal;
  font-weight:400 700;
  font-stretch:75% 100%;
  font-display:swap;
  src:url(../fonts/instrument-sans-latin-ext.woff2) format("woff2");
  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{
  --ground:#E6E6E3;
  --ground-2:#DCDCD9;
  --card:#FFFFFF;
  --cream:#F2F2F0;
  --ink:#1C1D1F;
  --mid:#5C5F63;
  --line:rgba(28,29,31,.15);
  --line-soft:rgba(28,29,31,.08);
  /* The emphasis tone. It used to be a navy, which fought the four project
     colours the work section now carries; a near-black in the ink family gets
     out of their way, so the only chroma on the page belongs to the projects. */
  --deep:#212328;
  --deep-hover:#0F1013;
  --r:22px;
  --sp:clamp(20px,5vw,76px);
  /* the vertical rhythm between sections. Named because blocks inside a section
     sometimes have to reproduce it — see .pair--even */
  --sec-gap:clamp(52px,9.5vh,112px);
  --f:"Instrument Sans",ui-sans-serif,-apple-system,"Segoe UI",Arial,sans-serif;
  /* the four project colours, each sampled from that project's poster and taken
     down in lightness until it clears 3:1 on the stone ground — everything set
     in them is large text, so that is the AA bar. Two places name a project in
     its own colour and must agree: the home cards and the next-project title at
     the foot of every case study. */
  --brand-powercast:#E64E0C;
  --brand-sofimar:#159538;
  --brand-ambience:#277AEA;
  --brand-spine:#985AE8;
}
*{box-sizing:border-box;margin:0;padding:0}
/* the ground colour lives on <html> so <body>'s own background never paints over
   the dot field, which sits underneath the content at z-index:-1 */
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;background:var(--ground)}
body{background:transparent;color:var(--ink);font-family:var(--f);
  font-variation-settings:"wdth" 100;-webkit-font-smoothing:antialiased;overflow-x:hidden}
a{color:inherit;text-decoration:none}
img,svg{max-width:100%;height:auto;display:block}
::selection{background:var(--deep);color:var(--cream)}
:focus-visible{outline:2px solid var(--deep);outline-offset:3px}

/* ---------- dot field ---------- */
/* A viewport-fixed measuring grid the whole site sits on. It never scrolls: the
   content slides across a stationary field. Opaque surfaces (cards, the CTA,
   the footer) mask it, so the dots only ever read in the negative space. */
.dotfield{position:fixed;inset:0;z-index:-1;width:100%;height:100%;display:block;
  pointer-events:none;contain:layout style paint}

/* ---------- large-display scale ---------- */
/* Every cap in the type scale has bottomed out by ~1400px, so on a 1920 desktop
   the page keeps getting wider while the text stays the same size and starts to
   read small against the screen. This is the 110% the design was judged at,
   applied only where there is room for it — laptops are left alone.
   The dot field is excluded on purpose: it is a viewport-fixed canvas sized off
   clientWidth, and scaling it would just resample the dots. */
@media(min-width:1500px){.hd,main,.ft{zoom:1.1}}

/* Anchor targets have to clear the sticky bar, or a jump to #work parks the
   heading underneath it. --hd-h is the bar's own height: the fallback below
   covers a no-JS load, and the header script overwrites it with the measured
   value on every resize, so the two can never drift apart. */
:root{--hd-h:64px}
#top,#services,#work,#contact{scroll-margin-top:calc(var(--hd-h) + 26px)}

.wrap{max-width:1300px;margin:0 auto;padding:0 var(--sp)}
.narrow{max-width:1000px;margin:0 auto;padding:0 var(--sp)}
.lbl{font-size:12px;letter-spacing:.11em;text-transform:uppercase;font-weight:600;color:var(--mid)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ---------- header ---------- */
/* The bar carries no surface at rest, so the dot field reads straight through it.
   It only takes one once page text starts passing underneath, where the nav would
   otherwise have to compete with moving content. blur(0) rather than none in the
   bare state: none isn't interpolable, so the blur would snap instead of fade. */
.hd{position:sticky;top:0;z-index:50;background:transparent;backdrop-filter:blur(0px);
  transition:background-color .28s ease,backdrop-filter .28s ease}
.hd.stuck{background:color-mix(in srgb,var(--ground) 88%,transparent);backdrop-filter:blur(10px)}
.hd .wrap{display:flex;align-items:baseline;gap:26px;padding-top:15px;padding-bottom:15px}
.hd .brand{font-size:21px;font-weight:600;letter-spacing:-.035em}
.hd .brand em{font-style:normal;font-weight:400;font-size:13px;color:var(--mid);margin-left:9px}
.hd nav{margin-left:auto;display:flex;gap:26px;align-items:baseline}
.hd nav a{font-size:15px;color:var(--mid);transition:color .18s}
.hd nav a:hover,.hd nav a[aria-current]{color:var(--ink)}
/* without a contrasting hue to mark it, the quote link earns its emphasis from a
   surface instead. Baseline alignment is what keeps its label on the same line
   as the plain links either side, with the pill sitting around it. */
.hd nav a.go{background:var(--deep);color:var(--cream);font-weight:600;font-size:14px;
  padding:9px 14px;border-radius:13px;transition:background-color .2s}
.hd nav a.go:hover{background:var(--deep-hover);color:var(--cream)}
@media(max-width:820px){.hd .brand em{display:none}.hd nav a:not(.go){display:none}}

/* ---------- skip link ---------- */
/* Off-screen until focused. The first Tab on a keyboard should be a way past the
   header, not the first link inside it. */
.skip{position:absolute;left:-9999px;top:0;z-index:100;
  background:var(--deep);color:var(--cream);padding:12px 18px;border-radius:0 0 12px 0;
  font-size:14px;font-weight:600}
.skip:focus{left:0}

/* ---------- buttons ---------- */
.row{display:flex;flex-wrap:wrap;gap:12px;margin-top:clamp(26px,4vh,40px)}
.btn{border-radius:999px;padding:14px 26px;font-size:15px;font-weight:600;transition:.2s;display:inline-block}
.btn-a{background:var(--deep);color:var(--cream)}
.btn-a:hover{background:var(--deep-hover);transform:translateY(-1px)}
.btn-b{background:rgba(255,255,255,.66);color:var(--ink)}
.btn-b:hover{background:var(--card)}
.btn-c{background:var(--cream);color:var(--deep)}
.btn-c:hover{background:#fff}

/* ---------- hero ---------- */
.hero{padding:clamp(46px,10vh,124px) 0 clamp(72px,10vh,120px)}
.hero h1{margin:0;font-size:clamp(38px,6.8vw,102px);line-height:1.0;
  letter-spacing:-.045em;font-weight:400;max-width:15ch}
.hero h1 b{font-weight:600}

/* ---------- sections ---------- */
.sec{padding:var(--sec-gap) 0}
.sec-flush{padding-top:0}
.sec-top{display:flex;align-items:flex-end;justify-content:space-between;gap:22px;flex-wrap:wrap;
  margin-bottom:clamp(24px,4vh,42px)}
.sec-top h2{font-size:clamp(25px,3.2vw,44px);letter-spacing:-.045em;font-weight:500;line-height:1.04;max-width:18ch}
.sec-top p{font-size:15.5px;line-height:1.55;color:var(--mid);max-width:34ch}
/* The work section opens the page's content, so it takes the long pad away from
   the collage above it and only a short one down to "What we do"; that section
   then closes on a longer pad still: the CTA below it is a filled dark block,
   and it needs more clear air above it than a run of text would. */
#work{--sec-pad:clamp(84px,12vh,140px);
  padding-top:var(--sec-pad);padding-bottom:clamp(32px,5vh,58px)}
#services{--sec-pad:clamp(48px,7vh,82px);
  padding-top:var(--sec-pad);padding-bottom:clamp(78px,14vh,170px)}
/* ...but that opening pad is the gap down from whatever sits above, not space the
   anchor should land in: a jump to either section is a request for its heading,
   and the generic scroll-margin above would stack the pad on top of the bar
   clearance. Each section publishes its own pad so this can take it back out,
   landing both headings the same 26px under the bar as every other anchor target.
   The offset goes negative where the pad is taller than the header, which is what
   that case is supposed to resolve to. */
#work,#services{scroll-margin-top:calc(var(--hd-h) + 26px - var(--sec-pad))}
/* the first entry's rule now does most of the work of separating the heading
   from the list, and the entry adds its own top padding under it, so the
   heading's own gap comes down to keep the total where it was */
#work .sec-top{margin-bottom:clamp(20px,3.2vh,36px)}

/* ---------- selected work (home) ---------- */
/* Four crops sharing one plate, and one target: the whole strip points at the
   work section below it. Nothing moves on hover — on a fine pointer the cursor
   itself is replaced by a chip that says where the strip goes, which is both the
   affordance and the label. */
.work-collage{display:flex;width:100%;aspect-ratio:2/1;overflow:hidden;border-radius:var(--r)}
.collage-poster{position:relative;flex:1 1 0;min-width:0;overflow:hidden}
.collage-poster:nth-child(1){z-index:1}
.collage-poster:nth-child(2){z-index:2}
.collage-poster:nth-child(3){z-index:3}
.collage-poster:nth-child(4){z-index:4}
.collage-poster+.collage-poster{box-shadow:-18px 0 30px -14px rgba(8,13,20,.55)}
.collage-poster img{width:100%;height:100%;object-fit:cover}
.collage-poster:nth-child(1) img{object-position:0 50%}
.collage-poster:nth-child(2) img{object-position:47.5% 50%}
.collage-poster:nth-child(3) img{object-position:36% 50%}
.collage-poster:nth-child(4) img{object-position:0 50%}

/* the native cursor is only given up once the chip that replaces it exists, so a
   failed script leaves a pointer rather than nothing. The class comes from JS. */
.work-collage.chip-on{cursor:none}
/* position comes from the `translate` property, which JS drives every frame;
   `scale` is left to CSS so the entrance can be eased without fighting it */
.cursor-chip{position:fixed;left:0;top:0;z-index:80;pointer-events:none;
  display:inline-flex;align-items:center;gap:8px;
  padding:10px 16px;border-radius:13px;
  background:var(--deep);color:var(--cream);font-size:14px;font-weight:600;
  line-height:1;white-space:nowrap;
  box-shadow:0 8px 24px -12px rgba(8,13,20,.55);
  opacity:0;scale:.86;transition:opacity .16s ease,scale .22s cubic-bezier(.22,.7,.2,1)}
.cursor-chip.on{opacity:1;scale:1}
.cursor-chip i{font-style:normal;font-size:15px}
@media(prefers-reduced-motion:reduce){.cursor-chip{transition:opacity .16s ease}}

/* One shape for every entry — no alternating sides. The copy column is set to
   the height of the poster beside it, which is what lets the name sit on the
   poster's top edge and the button on its bottom edge. Name and description are
   two blocks at one size: the name recedes to grey, the sentence carries the
   weight, and they read as consecutive lines of a single paragraph. */
/* Each entry is ruled off top and bottom, edge to edge. The list is the one
   block on the page that leaves .wrap: the hairline runs the full width of the
   page and .card-in puts the measure back, so the rules read as page furniture
   rather than as a border drawn around a card. The gap between entries is now
   their padding, split either side of the shared rule. */
.cards{display:grid;grid-template-columns:1fr;border-bottom:1px solid var(--line);
  --ease:cubic-bezier(.22,.7,.2,1)}
.card{display:block;min-width:0;padding:clamp(30px,4.4vw,60px) 0;border-top:1px solid var(--line)}
.card-in{max-width:1300px;margin:0 auto;padding:0 var(--sp);
  display:grid;grid-template-columns:minmax(0,.86fr) minmax(0,1.14fr);
  column-gap:clamp(30px,6vw,104px);align-items:stretch;min-width:0}
.card-body{display:flex;flex-direction:column;align-items:flex-start;min-width:0}
.card .meta{display:flex;align-items:baseline;gap:14px;margin:0 0 clamp(14px,1.7vw,22px)}
.card .yr{font-size:13px;color:var(--mid)}
.card h3,.card p{font-size:clamp(21px,2.35vw,33px);line-height:1.1;letter-spacing:-.042em;
  font-weight:600;margin:0;text-wrap:pretty}
/* the name is set in the project's own colour — that is what gives the recessive
   line a job. The order here is the order of the cards in the markup. */
.card:nth-child(1){--brand:var(--brand-powercast)}
.card:nth-child(2){--brand:var(--brand-sofimar)}
.card:nth-child(3){--brand:var(--brand-ambience)}
.card:nth-child(4){--brand:var(--brand-spine)}
.card h3{color:var(--brand,rgba(28,29,31,.38))}
.card h3:after{content:"."}
/* the auto margin below decides where the pill lands; this is the floor on how
   close it may come when the description is short */
.card p{color:var(--ink);margin-bottom:clamp(26px,3vw,40px)}
/* the link sits on the poster's bottom edge; margin-top:auto is what pushes it
   there, so the gap above it grows with whatever the description needs */
.card-go{position:relative;margin-top:auto;display:inline-flex;align-items:center;gap:9px;
  padding-bottom:5px;color:var(--ink);font-size:15px;font-weight:600}
.card-go span{font-size:17px;line-height:1;transition:transform .35s var(--ease)}
.card-go:after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:currentColor;
  transform:scaleX(0);transform-origin:left;transition:transform .32s var(--ease)}
.card:hover .card-go:after,.card:focus-visible .card-go:after{transform:none}
/* the link box now runs edge to edge, and a focus ring drawn around that would
   trace the whole viewport; it is moved in onto the measure instead */
.card:focus-visible{outline:0}
.card:focus-visible .card-in{outline:2px solid var(--deep);outline-offset:3px}
.card:hover .card-go span{transform:translateX(4px)}
.card:hover .plate{transform:translateY(-5px)}
/* align-self:start keeps the 4:3 intact — a stretched grid item takes its height
   from the row instead, which would quietly re-crop the poster whenever the
   description happened to run longer than the picture is tall */
.card .plate{align-self:start;border-radius:clamp(12px,1.6vw,20px);overflow:hidden;aspect-ratio:4/3;
  background:var(--cream);transition:transform .45s var(--ease)}
.card .plate img,.card .plate svg{width:100%;height:100%;display:block;object-fit:cover}
/* each poster is a 2:1 plate with its subject in a different place, so the 4:3
   crop is anchored per project rather than centred */
.card:nth-child(1) .plate img{object-position:0 50%}
.card:nth-child(2) .plate img{object-position:100% 50%}
.card:nth-child(3) .plate img{object-position:80% 50%}
.card:nth-child(4) .plate img{object-position:0 50%}
@media(max-width:940px){
  .card{padding:clamp(26px,5vw,38px) 0}
  .card-in{grid-template-columns:1fr}
  .card .plate{order:-1;margin-bottom:clamp(20px,3vw,28px)}
  .card-go{margin-top:0}
}

/* ---------- services ---------- */
/* Six disciplines on the bare ground: no cards, no fills, no rules. Each one
   opens with a drawn mark — small technical diagrams on one 48-unit grid at one
   stroke weight, the only iconography on the site — and those marks are what
   set the rhythm the tiles used to get from their borders.
   The row gap is much wider than the column gap on purpose: it is paying for
   the hairline that used to separate the two rows, and without it the second
   row reads as a continuation of the first. */
.svcs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  column-gap:clamp(24px,3.4vw,58px);row-gap:clamp(46px,5.6vw,82px)}
.svc-mark{color:var(--deep);opacity:.92;margin-bottom:clamp(20px,2.4vw,32px)}
.svc-mark svg{width:clamp(40px,3.6vw,50px);height:auto;display:block}
/* knocked-out shapes: where one drawn element crosses another, the one in front
   is filled with the page ground so the line behind it stops rather than showing
   through. In CSS rather than a fill attribute — var() is not valid in a
   presentation attribute, so the fill has to come from a rule. */
.svc-mark .bg{fill:var(--ground)}
.svc-mark .ink{fill:currentColor;stroke:none}
.svc-mark .tint{fill:currentColor;stroke:none;opacity:.15}
.svc h3{font-size:clamp(18px,1.8vw,23px);letter-spacing:-.035em;font-weight:600;margin-bottom:10px}
.svc p{font-size:15px;line-height:1.6;color:var(--mid);max-width:38ch}
@media(max-width:900px){.svcs{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:600px){
  .svcs{grid-template-columns:1fr;row-gap:40px}
  .svc-mark{margin-bottom:16px}
}

/* ---------- CTA ---------- */
/* The block carries a form now, which is what took it off centre: a statement
   centred over a left-aligned form reads as two blocks that happen to share a
   card. Statement and address on the left, the form on the right, and the whole
   thing lines up with the left-aligned page above it. */
.cta{background:var(--deep);color:var(--cream);border-radius:var(--r);
  padding:clamp(30px,5vw,76px);margin-bottom:clamp(40px,7vh,84px)}
.cta-grid{display:grid;grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
  gap:clamp(32px,4.6vw,76px);align-items:start}
/* the cap comes down with the column: the heading used to have the full width of
   the card to run across, and it now has a little under half of it */
.cta h2{font-size:clamp(28px,3.5vw,50px);line-height:1.02;letter-spacing:-.05em;font-weight:400;max-width:13ch}
.cta h2 b{font-weight:600}
/* scoped to the statement rather than .cta p: the form in the other column has
   paragraphs of its own, and this would outrank every class they carry */
.cta-say p{margin-top:clamp(16px,2.2vh,22px);font-size:16.5px;line-height:1.55;
  color:rgba(242,242,240,.7);max-width:40ch}

/* the other route out of the block. Set at a size that reads as an invitation
   rather than a footnote, and ruled off so it is plainly its own offer. */
.cta-direct{display:flex;flex-direction:column;align-items:flex-start;
  margin-top:clamp(26px,3.6vh,42px);padding-top:clamp(20px,2.8vh,30px);
  border-top:1px solid rgba(242,242,240,.16)}
.cta-direct .lbl{color:rgba(242,242,240,.5);margin-bottom:11px}
.cta-direct .mail{position:relative;font-size:clamp(19px,1.9vw,26px);letter-spacing:-.035em;
  font-weight:600;padding-bottom:4px}
.cta-direct .mail:after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:currentColor;opacity:.38;transition:opacity .22s}
.cta-direct .mail:hover:after{opacity:1}
.cta-direct .call{display:inline-flex;align-items:center;gap:9px;margin-top:15px;
  font-size:15px;color:rgba(242,242,240,.62);transition:color .18s}
.cta-direct .call:hover{color:var(--cream)}
.cta-direct .call span{font-size:16px;line-height:1;transition:transform .3s cubic-bezier(.22,.7,.2,1)}
.cta-direct .call:hover span{transform:translateX(4px)}

/* ---------- contact form ---------- */
/* Fields as soft-cornered wells sunk into the card, in the radius family the nav
   pill and the cursor chip already use. They carry their label inside as the
   placeholder; the <label> stays in the markup, visually hidden, because a
   placeholder is not a label to a screen reader and it leaves the field for
   everyone else the moment they start answering it. */
.cf{display:grid;gap:clamp(14px,1.9vh,18px)}
.cf-body{display:grid;gap:clamp(14px,1.9vh,18px)}
/* auto-fit rather than a breakpoint: name and email sit side by side wherever
   there is room for both, including inside the one-column stack on a phone */
/* align-items:start, or a message under one field stretches the other to match
   and the two wells stop being the same height */
.cf-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(178px,1fr));
  gap:clamp(14px,1.9vh,18px);align-items:start}
/* a grid item refuses to go below its content's minimum unless it is told it may,
   and a textarea's minimum is its `cols` — enough, in the one-column layout, to
   push the whole card wider than the page */
.cf-f{display:grid;gap:8px;min-width:0}
/* 16px is a floor, not a preference: iOS zooms the page in on any focused field
   set smaller than that, and it does not zoom back out afterwards */
.cf-f input,.cf-f textarea{width:100%;font:inherit;font-size:16px;line-height:1.5;color:var(--cream);
  background:rgba(242,242,240,.035);border:1px solid rgba(242,242,240,.14);border-radius:15px;
  padding:16px 18px;transition:border-color .2s,background-color .2s}
/* tall enough to hold the three things the copy beside it asks for, and to bring
   the form column down to roughly the depth of the statement column */
.cf-f textarea{resize:vertical;min-height:clamp(120px,19vh,168px)}
/* Firefox dims placeholders by default, so the alpha here is only half the story
   until opacity is put back */
.cf-f input::placeholder,.cf-f textarea::placeholder{color:rgba(242,242,240,.44);opacity:1}
.cf-f input:hover,.cf-f textarea:hover{border-color:rgba(242,242,240,.26)}
/* the site's focus ring is --deep, which on this card would be a dark outline on
   a dark surface. The focused well takes a near-cream edge instead, and lifts. */
.cf-f input:focus,.cf-f textarea:focus{outline:0;border-color:rgba(242,242,240,.72);
  background:rgba(242,242,240,.06)}
.cf-f input.err,.cf-f textarea.err{border-color:#FF9E80}
/* Chrome paints its autofill state with an inset background that ignores the
   background property; the only thing that covers it is a big inner shadow. The
   colour is the card with the field's own 3.5% cream already mixed in, since a
   shadow cannot be a translucent wash over what is behind it. */
.cf-f input:-webkit-autofill{-webkit-text-fill-color:var(--cream);
  -webkit-box-shadow:0 0 0 40px #282A2F inset;caret-color:var(--cream)}
.cf-msg{font-size:13px;line-height:1.4;color:#FF9E80}
.cf-msg:empty{display:none}
/* the message's footer: what is wrong with the field on the left, how much room
   is left on the right. margin-left rather than space-between, so the count keeps
   its corner on the usual case where there is nothing wrong to report. */
.cf-foot{display:flex;align-items:baseline;gap:14px}
.cf-count{margin-left:auto;flex:none;font-size:13px;line-height:1.4;
  color:rgba(242,242,240,.42);font-variant-numeric:tabular-nums}

/* not display:none — a bot that reads the stylesheet skips anything hidden
   outright, where an off-canvas field still looks like a field worth filling */
.cf-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

/* ---------- attachments ---------- */
/* Two things keep a long filename from setting the width of this column.
   minmax(0,1fr) rather than the implicit auto track, since an auto track is
   floored at its content's min-content; and no justify-items:start, because a
   shrink-to-fit list would be sized from that same min-content — and a nowrap
   filename has no break opportunity, so its min-content is the whole name.
   Filling the track gives the chips a definite width to be clipped against. */
/* note on the left, send on the right, both starting from the same top edge. The
   form used to carry a file picker here; attachments were dropped because every
   form backend puts uploads behind a paid tier, and a cold-inbound 10 MB upload
   is a liability rather than a feature. The note in its place asks for links. */
.cf-act{display:flex;align-items:flex-start;gap:20px;margin-top:clamp(4px,.8vh,10px)}
.cf-aside{flex:1 1 auto;min-width:0}
.cf-hint{font-size:13px;line-height:1.4;color:rgba(242,242,240,.42)}

.cf-go{flex:none;display:flex;flex-wrap:wrap;align-items:center;gap:14px}
/* .btn was written for anchors; a <button> brings its own border, font and cursor */
.cf-go .btn{border:0;font-family:inherit;cursor:pointer}
.cf-go .btn[disabled]{opacity:.55;cursor:default}
/* a rounded rectangle rather than the pill the page's link buttons wear: it closes
   a stack of wells, so it takes their corner rather than the navigation's */
.cf-send{display:inline-flex;align-items:center;gap:10px;border-radius:15px;min-height:50px}
.cf-send i{font-style:normal;font-size:17px;line-height:1;
  transition:transform .3s cubic-bezier(.22,.7,.2,1)}
.cf-send:hover i{transform:translateX(4px)}
.cf-send[disabled]:hover i{transform:none}

.cf-state{font-size:15px;line-height:1.55;color:rgba(242,242,240,.72)}
.cf-state:empty{display:none}
.cf-state:focus{outline:0}
.cf-state--err{color:#FF9E80}
.cf-note{font-size:15px;line-height:1.55;color:rgba(242,242,240,.72)}
/* once it has sent, the answer takes the room the form was using */
.cf.sent .cf-body{display:none}
.cf.sent .cf-state{font-size:clamp(18px,1.7vw,22px);line-height:1.4;letter-spacing:-.025em;
  color:var(--cream);max-width:30ch}

@media(max-width:900px){
  .cta-grid{grid-template-columns:minmax(0,1fr);gap:clamp(30px,4.5vh,46px)}
  .cta h2{max-width:16ch}
}
/* below this the note and the button side by side leave neither a line to sit on */
@media(max-width:480px){
  .cf-act{flex-wrap:wrap}
  .cf-go{width:100%}
  .cf-send{width:100%;justify-content:center}
}

/* ---------- 404 ---------- */
/* Borrows the hero's scale so the page reads as part of the site rather than as
   the server's own error output. */
.nf{padding:clamp(60px,14vh,150px) 0 clamp(60px,12vh,130px)}
.nf-code{font-size:14px;font-weight:600;letter-spacing:.14em;color:var(--mid);margin-bottom:18px}
.nf h1{margin:0;font-size:clamp(34px,5.6vw,78px);line-height:1.02;
  letter-spacing:-.045em;font-weight:400;max-width:14ch}
.nf h1 b{font-weight:600}
.nf .lede{margin-top:22px;max-width:54ch}

/* ---------- document pages ---------- */
/* One measure, one column, nothing decorative. Privacy copy is read by people
   looking for a specific sentence, so the headings do the finding. */
.doc{padding:clamp(48px,9vh,104px) 0 var(--sec-gap)}
.doc-head{max-width:64ch;padding-bottom:clamp(22px,3.4vh,34px);
  margin-bottom:clamp(26px,4vh,44px);border-bottom:1px solid var(--line)}
.doc-head h1{font-size:clamp(34px,5vw,64px);line-height:1.04;
  letter-spacing:-.04em;font-weight:400}
.doc-head .cap{margin-top:14px}
.doc-body{max-width:64ch}
.doc-body>.lede{font-size:clamp(17px,1.55vw,20px);line-height:1.55;color:var(--ink);
  letter-spacing:-.012em}
.doc-sec{margin-top:clamp(30px,4.6vh,50px)}
.doc-sec h2{font-size:clamp(19px,1.8vw,23px);letter-spacing:-.025em;font-weight:600;
  margin-bottom:12px}
.doc-mail{font-weight:600;border-bottom:1px solid var(--line);transition:border-color .2s}
.doc-mail:hover{border-color:var(--ink)}

/* ---------- footer ---------- */
.ft{background:var(--ground-2);padding:clamp(34px,5vh,60px) 0 32px}
/* Three link columns: Work, Studio, Language. The switcher lives here rather than in
   the header because it is a setting, not navigation — and a control nobody needs
   twice was crowding the nav on every page for the sake of one click. */
.ft .cols{display:grid;grid-template-columns:minmax(0,1.5fr) repeat(3,minmax(0,1fr));gap:clamp(22px,3vw,44px)}
.ft h4{margin-bottom:14px}
.ft a,.ft p{display:block;font-size:15px;line-height:1.9;color:var(--mid)}
.ft a:hover{color:var(--deep)}
.ft .sign{font-size:clamp(22px,2.6vw,32px);letter-spacing:-.045em;font-weight:600;color:var(--ink)}
.ft .base{display:flex;flex-wrap:wrap;gap:12px 26px;margin-top:clamp(28px,4vh,50px);padding-top:20px;border-top:1px solid var(--line)}
.ft .base span{font-size:13.5px;color:var(--mid)}
.ft .base .push{margin-left:auto}
.ft .apple-credit{margin-top:10px;font-size:11px;line-height:1.5;color:var(--mid)}
/* Reads as the list it sits in; the one you are already on is simply the one that is
   not grey, which is all the mark a two-item list needs. */
.ft a.ft-lang[aria-current]{color:var(--ink);font-weight:600}
@media(max-width:840px){.ft .cols{grid-template-columns:repeat(2,1fr);gap:30px}}

/* ==========================================================================
   CASE STUDY
   ========================================================================== */

.back{display:inline-flex;align-items:center;gap:8px;font-size:14.5px;color:var(--mid);
  padding:clamp(30px,5vh,52px) 0 0;transition:color .18s}
.back:hover{color:var(--deep)}
.back span{font-size:17px;line-height:1}

.case-hero{padding:clamp(26px,4vh,44px) 0 clamp(34px,5vh,60px)}
.case-hero .kicker{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:baseline;margin-bottom:clamp(18px,3vh,30px)}
.case-hero .kicker .lbl{font-size:clamp(14px,1.2vw,16px);letter-spacing:.075em}
.case-hero .kicker .lbl+.lbl:before{content:"·";margin-right:18px;color:var(--line)}
.case-hero h1{font-size:clamp(34px,5.6vw,84px);line-height:1.02;letter-spacing:-.048em;
  font-weight:400;max-width:17ch}
.case-hero h1 b{font-weight:600}
.case-hero .lede{margin-top:clamp(20px,3vh,30px);font-size:clamp(16.5px,1.45vw,21px);line-height:1.55;
  color:var(--mid);max-width:56ch}

/* the poster: the one image that gives each case page its own face. Real product
   surface, cropped flat onto a ground colour taken from the product itself. */
.poster{position:relative;border-radius:var(--r);overflow:hidden;background:var(--cream);line-height:0;
  aspect-ratio:2/1;box-shadow:0 26px 70px -42px rgba(28,29,31,.55);
  outline:1px solid rgba(28,29,31,.08);outline-offset:-1px}
.poster:after{content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.12)}
.poster img{width:100%;height:100%;object-fit:cover}

/* screenshot plates ------------------------------------------------------ */
.shot{background:var(--card);border-radius:var(--r);padding:clamp(10px,1.4vw,18px);overflow:hidden;
  box-shadow:0 20px 50px -38px rgba(28,29,31,.42);outline:1px solid rgba(28,29,31,.06);outline-offset:-1px}
.shot img{border-radius:calc(var(--r) - 12px);width:100%;background:#F4F4F2;
  outline:1px solid rgba(28,29,31,.06);outline-offset:-1px}
.shot.dark{background:#0D0F12;outline-color:rgba(255,255,255,.07)}
.shot.dark img{background:#0D0F12;outline-color:rgba(255,255,255,.055)}
.shot figcaption,.cap{margin:14px 6px 4px;font-size:14px;line-height:1.55;color:var(--mid);max-width:70ch}
.shot.dark figcaption{color:rgba(242,242,240,.62)}
.shot.bleed{padding:0}
.shot.bleed img{border-radius:var(--r)}

/* Product captures sit edge-to-edge in one card. The card supplies the crop,
   border and caption surface; the UI itself is not framed a second time. */
.shot.seamless{padding:0}
.shot.seamless>img{border-radius:0;background:transparent;outline:0}
.shot.seamless.shot--padded>img{padding:clamp(12px,1.5vw,20px);background:var(--shot-surface,var(--card))}
.shot.seamless figcaption{max-width:none;margin:0;padding:clamp(15px,1.7vw,20px) clamp(18px,2vw,24px);
  border-top:1px solid var(--line-soft)}
.shot--ui{max-width:1120px;margin-inline:auto}
.shot--clinic{--shot-surface:#F0F5F5}
.shot--white{--shot-surface:#FFFFFF}

/* fake browser chrome */
.chrome{display:flex;align-items:center;gap:7px;padding:3px 7px 12px;min-height:30px}
.chrome i{width:8px;height:8px;border-radius:50%;background:var(--line);display:block}
.shot.dark .chrome i{background:rgba(255,255,255,.16)}
.chrome .u{margin-left:10px;height:17px;flex:1;max-width:320px;border-radius:999px;background:var(--cream)}
.shot.dark .chrome .u{background:rgba(255,255,255,.06)}

.shots-2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(14px,2vw,24px);align-items:start}
.shots-3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(14px,2vw,24px);align-items:start}
@media(max-width:860px){.shots-2,.shots-3{grid-template-columns:1fr}}

/* A consistent canvas for UI fragments whose native aspect ratios differ.
   The screenshot itself is never cropped; the extra room becomes part of the prop. */
.shots-2--balanced{align-items:stretch}
.shots-2--balanced .shot{height:100%;display:flex;flex-direction:column}
.media-stage{position:relative;display:grid;place-items:center;overflow:hidden;isolation:isolate;
  aspect-ratio:16/10;padding:clamp(14px,2.2vw,28px);border-radius:calc(var(--r) - 12px);
  background:linear-gradient(145deg,#EEF3F7,#F8F9F9)}
.media-stage:before{content:"";position:absolute;width:74%;aspect-ratio:1;border:1px solid rgba(28,29,31,.13);
  border-radius:50%;right:-34%;top:-62%;z-index:0}
.media-stage img{position:relative;z-index:1;width:100%;height:100%;object-fit:contain;border-radius:10px;
  box-shadow:0 18px 34px -28px rgba(28,29,31,.5)}
.media-stage--fragment{background:linear-gradient(145deg,#E8F2F8,#F5F8F9)}
.media-stage--dark{aspect-ratio:16/7.5;background:linear-gradient(145deg,#15181C,#0C0E11)}
.media-stage--dark:before{width:64%;right:-25%;top:-120%;border-color:rgba(234,92,40,.16)}
.media-stage--dark img{box-shadow:0 18px 34px -28px rgba(0,0,0,.85)}

/* Official Apple iPhone 16 Pro product bezel ----------------------------- */
.phones{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(16px,2.4vw,34px);
  align-items:start;justify-items:center}
@media(max-width:1080px){.phones{grid-template-columns:repeat(2,1fr);gap:clamp(24px,4vw,48px)}}
@media(max-width:560px){.phones{grid-template-columns:1fr;gap:40px}}
.phone{width:100%;max-width:294px}
/* drop-shadow, not box-shadow: the device is a rounded silhouette inside a
   transparent PNG, so the shadow has to follow the artwork's alpha. Two passes —
   a tight contact shadow and a soft cast one — read as one light. */
.phone-device{position:relative;width:100%;aspect-ratio:1350/2760;
  filter:drop-shadow(0 2px 3px rgba(28,29,31,.18)) drop-shadow(0 15px 19px rgba(28,29,31,.26))}
.phone-screen{position:absolute;z-index:1;left:5.333333%;top:2.5%;width:89.333333%;height:95%;
  overflow:hidden;border-radius:10.95% / 5.03%;background:#F0F0EE}
.phone-chrome,.phone-content,.phone-bezel{display:block;position:absolute;width:100%;image-rendering:auto}
.phone-chrome{z-index:1;inset:0;height:100%;object-fit:fill}
.phone-content{z-index:2;left:0;top:5.5292%;height:93.207%;object-fit:fill}
.phone-bezel{z-index:3;inset:0;height:100%;object-fit:contain;pointer-events:none}

/* prose blocks ----------------------------------------------------------- */
/* Copy bands. Posters and captures run the full width of the wrap; copy sits in
   a narrower band centred inside it, inset from the media above and below, so a
   paragraph never runs the width of a screenshot. --band-copy is one measure
   column, --band-wide is for two-up copy and the spec table, which need the
   room. Both collapse to the wrap on anything narrow. */
:root{--band-copy:min(100%,680px);--band-wide:min(100%,980px)}

/* Heading over body in a single narrow column. */
.split{max-width:var(--band-copy);margin-inline:auto}
.split>h2{margin-bottom:clamp(16px,2.4vh,24px)}
.split .prose p,.split .prose ul{max-width:none}
.split--wide{max-width:var(--band-wide)}
.split h2,.grounding-copy h2,.duo-copy h2,.pair h2{font-size:clamp(21px,2.3vw,30px);letter-spacing:-.04em;font-weight:500;line-height:1.1}

/* Two prose columns at equal weight — the problem and the answer to it, read
   together rather than one scroll apart. Collapses in source order. */
.pair{max-width:var(--band-wide);margin-inline:auto;
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(28px,4.5vw,72px);
  align-items:start}
.pair h2{margin-bottom:clamp(16px,2.4vh,24px)}
/* The pair opens a flush section that closes with a capture, so the section only
   pads it from below. Carrying the section gap as a margin puts the same space
   under the pair as the poster above it leaves over it. */
.pair--even{margin-bottom:var(--sec-gap)}
@media(max-width:820px){.pair{grid-template-columns:1fr;gap:clamp(26px,4.5vh,40px)}}
.grounding-layout{max-width:1120px;margin-inline:auto;display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,2fr);gap:clamp(32px,4vw,56px);align-items:start}
.grounding-copy h2{margin-bottom:clamp(16px,2.4vh,24px)}
.grounding-layout .shot{width:100%;justify-self:end}
@media(max-width:900px){.grounding-layout{grid-template-columns:1fr}.grounding-layout .shot{justify-self:center}}

/* Copy beside a capture at 1:2, title over body. A run of these alternates
   --flip so the captures don't all stack against the same edge. The flip is
   placement only — source order stays heading-first, which is also the order
   it collapses to on narrow screens. */
.duo{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,2fr);
  gap:clamp(28px,4vw,56px);align-items:start}
.duo--flip{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}
.duo--flip>.duo-copy{grid-column:2}
.duo--flip>.shot{grid-column:1;grid-row:1}
.duo-copy h2{margin-bottom:clamp(16px,2.4vh,24px)}
@media(max-width:900px){
  .duo,.duo--flip{grid-template-columns:1fr;gap:clamp(20px,3vh,28px)}
  .duo--flip>.duo-copy,.duo--flip>.shot{grid-column:1;grid-row:auto}}
.prose p{font-size:clamp(16px,1.32vw,18.5px);line-height:1.62;color:var(--mid);max-width:62ch}
.prose p+p{margin-top:17px}
.prose p b,.prose p strong{color:var(--ink);font-weight:600}
.prose ul{margin-top:17px;max-width:62ch;list-style:none}
.prose li{position:relative;padding-left:22px;font-size:clamp(15.5px,1.3vw,18px);line-height:1.6;color:var(--mid)}
.prose li+li{margin-top:11px}
.prose li:before{content:"";position:absolute;left:0;top:.62em;width:7px;height:7px;border-radius:50%;
  background:var(--deep);opacity:.55}
.prose li b{color:var(--ink);font-weight:600}

/* statement -------------------------------------------------------------- */
.statement{background:var(--cream);border-radius:var(--r);padding:clamp(28px,5vw,72px)}
.statement p{font-size:clamp(21px,2.7vw,38px);line-height:1.22;letter-spacing:-.04em;font-weight:500;
  max-width:22ch}
.statement .by{margin-top:22px;font-size:15px;line-height:1.6;color:var(--mid);max-width:52ch;
  font-weight:400;letter-spacing:0}

/* spec table ------------------------------------------------------------- */
.spec{border-top:1px solid var(--line)}
.spec div{display:grid;grid-template-columns:minmax(0,22ch) minmax(0,1fr);gap:clamp(14px,3vw,44px);
  padding:15px 0;border-bottom:1px solid var(--line-soft)}
.spec dt{font-size:14.5px;color:var(--mid)}
.spec dd{font-size:15.5px;line-height:1.55}
@media(max-width:640px){.spec div{grid-template-columns:1fr;gap:5px}}

/* Ambience review gate --------------------------------------------------- */
.gate-flow{padding-block:clamp(8px,2vh,20px)}
.gate-flow__head{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(260px,.8fr);
  gap:clamp(24px,5vw,72px);align-items:end;border-top:1px solid var(--line);padding-top:clamp(22px,3.4vw,42px)}
.gate-flow__head .lbl{margin-bottom:12px;color:var(--deep)}
.gate-flow__head h2{font-size:clamp(30px,4.2vw,58px);line-height:1.02;letter-spacing:-.05em;font-weight:400}
.gate-flow__head h2 b{font-weight:600;color:var(--deep)}
.gate-flow__head>p{font-size:clamp(16px,1.35vw,18px);line-height:1.55;color:var(--mid);max-width:34ch}
.gate-track{position:relative;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
  gap:clamp(12px,1.7vw,24px);list-style:none}
.gate-track:before{content:"";position:absolute;left:24px;right:24px;top:31px;height:2px;
  background:linear-gradient(90deg,var(--line),var(--deep) 72%,var(--deep));z-index:0}
.gate-step{position:relative;min-width:0}
.gate-step__number{position:relative;z-index:1;width:64px;height:64px;border-radius:50%;display:grid;place-items:center;
  background:var(--ground);border:2px solid var(--deep);color:var(--deep);font-size:13px;font-weight:700;letter-spacing:.08em}
.gate-step h3{margin-top:18px;font-size:clamp(16px,1.4vw,19px);line-height:1.2;font-weight:600}
.gate-step p{margin-top:7px;font-size:13.5px;line-height:1.45;color:var(--mid);max-width:18ch}
.gate-step--review,.gate-step--focus{margin:-18px -8px -24px;padding:18px 16px 24px;border-radius:var(--r);background:var(--deep);
  color:var(--cream);box-shadow:0 24px 52px -34px rgba(28,29,31,.75)}
.gate-step--review .gate-step__number,.gate-step--focus .gate-step__number{background:var(--cream);border-color:var(--cream);color:var(--deep)}
.gate-step--review h3,.gate-step--focus h3{margin-top:10px}
.gate-step--review p,.gate-step--focus p{color:rgba(242,242,240,.7)}
.gate-step__tag{display:block;margin-top:18px;font-size:10.5px;line-height:1;text-transform:uppercase;
  letter-spacing:.12em;font-weight:700;color:rgba(242,242,240,.62)}
.gate-refusal,.gate-return{max-width:720px;margin:clamp(52px,7vw,86px) auto 0;display:grid;
  grid-template-columns:minmax(180px,.75fr) minmax(260px,1.25fr);gap:24px;align-items:center;
  padding:18px 0 18px 20px;border-left:3px solid var(--deep);
  border-top:1px dashed rgba(28,29,31,.42);border-bottom:1px dashed rgba(28,29,31,.42)}
.gate-refusal .lbl,.gate-return .lbl{display:block;margin-bottom:5px;color:var(--deep)}
.gate-refusal strong,.gate-return strong{display:block;font-size:17px;line-height:1.3}
.gate-refusal p,.gate-return p{font-size:14px;line-height:1.5;color:var(--mid)}
.gate-flow__caption{margin-top:clamp(28px,4vw,44px);font-size:14px;line-height:1.55;color:var(--mid);max-width:70ch}
@media(max-width:920px){
  .gate-flow__head{grid-template-columns:1fr;align-items:start}
  .gate-track{grid-template-columns:1fr;gap:0;padding-left:0}
  .gate-track:before{left:25px;right:auto;top:26px;bottom:26px;width:2px;height:auto;background:var(--deep)}
  .gate-step{display:grid;grid-template-columns:52px minmax(0,1fr);gap:18px;padding:0 0 30px}
  .gate-step__number{width:52px;height:52px}
  .gate-step h3{margin-top:3px}
  .gate-step p{margin-top:5px;max-width:none}
  .gate-step--review,.gate-step--focus{margin:0 0 30px -12px;padding:14px 16px 18px 12px;border-radius:18px}
  .gate-step--review h3,.gate-step--focus h3{margin-top:3px}
  .gate-step__tag{grid-column:2;margin:4px 0 8px}
  .gate-step--review>div,.gate-step--focus>div{display:grid}
  .gate-step--review .gate-step__tag,.gate-step--focus .gate-step__tag{order:-1}
  .gate-refusal,.gate-return{margin-top:24px;grid-template-columns:1fr;gap:10px}
}

/* diagram plate ---------------------------------------------------------- */
.plate-wide{background:var(--card);border-radius:var(--r);padding:clamp(18px,3vw,44px)}
.plate-wide svg{width:100%;height:auto}
.plate-wide .cap{margin:20px 2px 0}

/* next project ----------------------------------------------------------- */
.next{border-top:1px solid var(--line);padding:clamp(34px,6vh,72px) 0 clamp(40px,7vh,84px);
  display:flex;align-items:flex-end;justify-content:space-between;gap:24px;flex-wrap:wrap}
.next .lbl{margin-bottom:12px}
/* the title of the project being pointed at carries that project's colour, the
   same way the home cards do; the modifier goes on .next, one per page */
.next h2{font-size:clamp(27px,3.6vw,52px);letter-spacing:-.045em;font-weight:500;line-height:1.05;
  color:var(--brand,var(--ink))}
.next--powercast{--brand:var(--brand-powercast)}
.next--sofimar{--brand:var(--brand-sofimar)}
.next--ambience{--brand:var(--brand-ambience)}
.next--spine{--brand:var(--brand-spine)}
.next h2 span{color:var(--mid);display:block;font-size:clamp(15px,1.3vw,18px);font-weight:400;
  letter-spacing:-.01em;margin-top:10px}
.next a.arrow{width:56px;height:56px;border-radius:50%;background:var(--deep);color:var(--cream);
  display:grid;place-items:center;font-size:20px;transition:.2s;flex:none}
.next a.arrow:hover{background:var(--deep-hover);transform:translateX(3px)}

/* ---------- reveal ---------- */
.rv{opacity:0;transform:translateY(16px);transition:opacity .7s cubic-bezier(.2,.7,.3,1),transform .7s cubic-bezier(.2,.7,.3,1)}
.rv.in{opacity:1;transform:none}
@media(prefers-reduced-motion:reduce){.rv{opacity:1;transform:none;transition:none}
  *{transition-duration:.001ms!important}html{scroll-behavior:auto}}

/* diagram typography + horizontal scroll container for wide diagrams */
.plate-wide svg text{font-family:var(--f)}
.scroll-x{overflow-x:auto;-webkit-overflow-scrolling:touch}
.scroll-x>svg{min-width:720px}