/* ---- self-hosted webfonts -------------------------------------------
   Inter only, latin and latin-ext. Served from our own origin so no DNS/TLS
   round trip to Google is needed before text can paint, and no third party
   sees visitor IPs.

   Inter is the STAND-IN. The type system is SF Pro Display / SF Pro Text, so
   the stack in --display / --body leads with -apple-system: every Apple
   device draws the real thing, optically sized, with no download at all.
   Inter only ever loads on Windows, Android and Linux, where it is the
   closest available match. Jost was removed with the old identity — nothing
   references it. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(../fonts/inter-var-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;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(../fonts/inter-var-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;
}

/* ---- palette ------------------------------------------------------------
   Colours are held as bare RGB triples so every rgba() in the sheet can be
   re-tinted by swapping one token. :root is the light page; .on-dark restores
   dark values for the full-bleed photo sections, which keep white type over
   the imagery.

   THE RULE THIS PALETTE FOLLOWS: the page is neutral — Fog ground, Pure White
   surfaces, Space Black type, grey hairlines — and exactly ONE saturated
   colour is allowed, the #0071e3 that fills the primary button. Links carry
   the slightly deeper #0066cc so a link and a button never read as the same
   action. Nothing else on the page is coloured. */
:root{
  --navh:88px;   /* the fixed bar — panels clear it, anchors offset by it */

  /* ---- the named palette, at the reference's own values ---- */
  --color-apple-blue:#0071e3;
  --color-link-blue:#0066cc;
  --color-space-black:#1d1d1f;
  --color-sterling:#707070;
  --color-graphite:#474747;
  --color-slate:#333336;
  --color-smoke:#777779;
  --color-pebble:#d6d6d6;
  --color-concrete:#e2e2e5;
  --color-fog:#f5f5f7;
  --color-pure-white:#ffffff;
  --color-absolute:#000000;
  /* The activity-ring accents. Decorative only — data rings, metric strokes,
     illustration. NEVER a call to action, and nothing on this site uses them
     yet; they are here so anyone extending the page knows the sanctioned set
     and does not invent a colour of their own. */
  --color-signal-green:#03aa49;
  --color-ember-orange:#ed6300;
  --color-iris-violet:#8668ff;
  --color-deep-teal:#00a1b3;

  /* ---- the same palette, wired to the names this sheet already uses ---- */
  --surf:245,245,247;        /* Fog — page + section ground   */
  --surf2:255,255,255;       /* Pure White — card surfaces    */
  --fgc:29,29,31;            /* Space Black — body text       */
  --linec:112,112,112;       /* Sterling — hairlines are grey, never blue */
  --fg:var(--color-space-black);
  --ink:var(--color-fog);
  --ink2:var(--color-pure-white);
  --ink3:var(--color-concrete);   /* secondary buttons, chips, list fills */
  --ink4:var(--color-pebble);     /* the state below Concrete: hover, dividers */
  --panel:rgba(255,255,255,.72);
  --panel2:rgba(255,255,255,.92);
  --blue:var(--color-apple-blue); /* the one saturated fill: primary CTA only */
  --azure:var(--color-apple-blue);
  --sky:var(--color-link-blue);   /* links, footnote refs, underlines */
  --mist:#6e6e73;
  --white:#fff;
  --txt:var(--color-space-black);
  /* Sterling (#707070) is the reference's secondary text colour. It measures
     4.48:1 on Fog — a hair under the 4.5 threshold, and this token carries
     real sentences. It is held two steps darker so body copy passes; Sterling
     itself stays in the palette above and is used where it belongs, on
     hairlines and dividers rather than on words. Same reasoning for --faint,
     which lands on 12px labels: the reference's #86868b measures 3.5:1.
     The hierarchy is carried by size and weight instead of a weaker grey.
     This is the one place the system does not copy a reference value. */
  --soft:#6e6e73;            /* secondary copy — 4.53:1 on Fog */
  --faint:#6e6e73;           /* 12px labels and meta */
  --line:var(--color-pebble);       /* Pebble hairline */
  --line2:rgba(0,0,0,.16);
  --gold:#9A8434;
  --warm:#b98219;
  /* One stack for both roles. -apple-system resolves to SF Pro, which is
     optically sized — the browser picks Display above 20px and Text below it,
     which is exactly the split the design calls for. Inter is the fallback
     everywhere else. */
  --display:-apple-system,BlinkMacSystemFont,'SF Pro Display','SF Pro Text','Inter',system-ui,'Segoe UI',Roboto,sans-serif;
  --body:-apple-system,BlinkMacSystemFont,'SF Pro Text','SF Pro Display','Inter',system-ui,'Segoe UI',Roboto,sans-serif;

  /* ---- the type scale, verbatim ----------------------------------------
     Eight steps, each with its own line-height and its own tracking in PX,
     not em — that is how the reference specifies them, and the two are not
     interchangeable: tracking loosens in em as the size grows (-0.015em at
     40px, -0.005em at 56px, -0.003em at 64px), so a single em value applied
     across the scale sets the big headlines far too tight. */
  --text-caption:12px;      --leading-caption:1.33;      --tracking-caption:-0.22px;
  --text-body-sm:14px;      --leading-body-sm:1.43;      --tracking-body-sm:-0.19px;
  --text-body:17px;         --leading-body:1.47;         --tracking-body:-0.27px;
  --text-subheading:21px;   --leading-subheading:1.38;   --tracking-subheading:-0.32px;
  --text-heading-sm:28px;   --leading-heading-sm:1.14;   --tracking-heading-sm:-0.42px;
  --text-heading:40px;      --leading-heading:1.1;       --tracking-heading:-0.6px;
  --text-heading-lg:56px;   --leading-heading-lg:1.07;   --tracking-heading-lg:-0.28px;
  --text-display:64px;      --leading-display:1.06;      --tracking-display:-0.19px;
  /* Headlines on this site are fluid, so their tracking has to be fluid too.
     These interpolate the scale above across the same viewport range the
     font-size clamp uses: -0.015em at the small end, -0.005em at the top.
     --tracking-h  pairs with clamp(32px,4.2vw,56px)  (section headlines)
     --tracking-h1 pairs with clamp(40px,6.4vw,80px)  (the hero) */
  --tracking-h:clamp(-0.48px,calc(-0.75px + 0.035vw),-0.28px);
  --tracking-h1:clamp(-0.6px,calc(-0.96px + 0.0576vw),-0.24px);

  --ease:cubic-bezier(.28,.11,.32,1);
  --gut:clamp(22px,5vw,64px);
  --max:1440px;              /* page max-width */
  --pad:clamp(40px,6vw,80px);/* section gap */
  --card-padding:clamp(20px,2.6vw,28px);
  --element-gap:10px;
  /* Radii, and only these. 10px is the floor (links, small utility surfaces),
     28px is the container radius — cards, panels, chips, accordions, media
     frames — and 980px is reserved for true pill buttons. */
  --r:28px;
  --r-sm:10px;
  --r-pill:980px;
}
/* Sections that sit on photography stay dark — the imagery is night-shot and
   white type is the only thing that reads over it. */
/* ===== theme =============================================================
   :root above is the light theme. This block was written for the dark SECTIONS
   (hero, projects room, ecosystems stage) and is now shared with the whole-page
   dark theme, so there is one set of dark values rather than two that drift.

   The site ships LIGHT — <html> carries no data-theme, so :root applies as-is
   and there is nothing to undo on first paint. Dark is opt-in: the first-visit
   gate offers it beside the language choice, the footer toggle switches it any
   time, and the choice is remembered in localStorage.                        */
:root[data-theme="dark"],
.on-dark{
  --surf:0,0,0;              /* true black ground, as the dark product pages */
  --surf2:29,29,31;          /* raised card surface                          */
  --fgc:245,245,247;
  --linec:255,255,255;
  --fg:#f5f5f7;
  /* literal, not var(--ink) — a self-reference resolves to empty, and an empty
     var() inside a background shorthand invalidates the whole declaration */
  --ink:#000000;
  --ink2:#1d1d1f;
  --ink3:#2c2c2e;
  --sky:#2997ff;             /* the link blue lightens on black             */
  --blue:#0071e3;            /* the CTA fill does NOT — it is the constant  */
  --azure:#0071e3;
  --mist:#a1a1a6;
  --txt:#f5f5f7;
  --soft:#a1a1a6;
  --faint:#86868b;
  --line:rgba(255,255,255,.14);
  --line2:rgba(255,255,255,.22);
  --panel:rgba(29,29,31,.72);
  --panel2:rgba(29,29,31,.92);
  color:var(--txt);
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  font-family:var(--body);
  color:var(--txt);
  background:var(--ink);      /* one flat ground — no washes, no gradients */
  font-size:var(--text-body); /* the canonical body size */
  line-height:var(--leading-body);
  letter-spacing:var(--tracking-body);
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
}
img,svg,canvas{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button{font-family:inherit}
::selection{background:var(--sky);color:#06101d}
.wrap{max-width:var(--max);margin:0 auto;padding:0 var(--gut)}
/* Eyebrow: 12px semibold caps in the SAME colour as the copy around it — the
   category label above a headline, not a coloured kicker. No rule, no wide
   tracking; it sits 3px above the headline it belongs to. */
.eyebrow{
  display:inline-flex;
  align-items:center;
  gap:8px;
  color:var(--fg);
  font-family:var(--body);
  font-size:var(--text-caption);
  line-height:var(--leading-caption);
  /* caps, and still tracked NEGATIVE — the reference sets its eyebrows tight
     rather than letter-spacing them out, which is what stops them reading as
     a kicker from another design */
  letter-spacing:var(--tracking-caption);
  text-transform:uppercase;
  font-weight:600;
}
.eyebrow::before{content:none}
.eyebrow.center{justify-content:center}
/* Headlines are one weight, one colour, tight. The tracking closes up as the
   size grows so a headline locks into a single visual block. */
h1,h2,h3,h4{
  font-family:var(--display);
  font-weight:600;
  line-height:1.07;
  letter-spacing:-.015em;
  color:var(--fg);
}
.lead{
  color:var(--soft);
  font-size:clamp(16px,1.2vw,var(--text-body));
  line-height:var(--leading-body);
  letter-spacing:var(--tracking-body);
  max-width:60ch;
}
/* Buttons are pills. The primary is a flat #0071e3 fill with no shadow and no
   gradient — the only saturated element on the page. The secondary is the
   Concrete fill, never an outline, and never the link blue. */
.btn{
  position:relative;
  overflow:hidden;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:6px;
  padding:12px 22px;
  border-radius:var(--r-pill);
  border:0;
  background:transparent;
  color:var(--fg);
  cursor:pointer;
  font-family:var(--body);
  font-weight:600;
  font-size:var(--text-body);
  line-height:1.24;              /* the button step: 17px/600, lh 1.24 */
  letter-spacing:var(--tracking-body);
  transition:background .22s var(--ease),color .22s var(--ease),opacity .22s var(--ease);
  -webkit-tap-highlight-color:transparent;
}
/* The markup carries a → in .arr. Apple's system uses the single chevron ›,
   so the glyph is swapped here rather than in twelve HTML files — English and
   Arabic both pick it up, and rtl.css already mirrors .arr for RTL. */
.btn .arr{font-size:0;line-height:0;display:inline-flex;align-items:center;transition:transform .3s var(--ease)}
.btn .arr::after{content:"\203A";font-size:19px;line-height:1;font-weight:400}
.btn:hover .arr{transform:translateX(2px)}
.btn:active{opacity:.82}
.btn-primary{background:var(--blue);color:#fff;box-shadow:none}
.btn-primary:hover{background:#0077ed;box-shadow:none}
.btn-ghost{background:var(--ink3);color:var(--fg);border:0}
.btn-ghost:hover{background:#dcdce1;color:var(--fg);box-shadow:none}
/* the tap ripple belonged to the old identity — nothing here reacts that way */
.ripple{display:none}
.section{position:relative;padding:var(--pad) 0}
.section-head{max-width:850px;margin-bottom:clamp(36px,4.4vw,56px)}
.section-head.center{text-align:center;margin-left:auto;margin-right:auto}
/* One weight, one colour, ending in a full stop — the <b> in the markup is
   part of the sentence, not a second voice, so it disappears into the line. */
.section-head h2{
  font-size:clamp(32px,4.2vw,var(--text-heading-lg));
  color:var(--fg);
  margin-top:3px;              /* the 3px gap under the eyebrow */
  font-weight:600;
  line-height:var(--leading-heading-lg);
  letter-spacing:var(--tracking-h);
}
.section-head h2 b{font-weight:600;color:inherit}
.section-head .lead{margin-top:18px}
.reveal{opacity:0;transform:translateY(22px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
.reveal.in-view{opacity:1;transform:none}
.reveal.d1{transition-delay:.08s}

/* NAV — one thin translucent bar that never changes shape.
   The old bar grew to 96px over the hero and collapsed into a floating pill on
   scroll. This system has a single 48px utility bar, backdrop-blurred, with a
   hairline under it at every scroll position: the chrome is meant to be
   forgotten, not choreographed. .scrolled is left in the markup and the JS —
   it simply no longer changes anything. */
.nav{
  position:fixed;
  z-index:100;
  inset:0 0 auto 0;
  border-bottom:1px solid var(--line);
  transition:border-color .25s var(--ease);
}
/* Two background layers cross-fade rather than one being reshaped: the
   full-width bar at the top of the page, and the floating pill once scrolled.
   Keeping them separate avoids any 100vw maths (which would be thrown off by
   the scrollbar) — the pill simply inherits .nav-in's own box.

   The blur lives on pseudo-elements, NOT on .nav or .nav-in themselves:
   backdrop-filter makes an element the containing block for fixed-position
   descendants, which would collapse the compact-width menu sheet. */
.nav::before{
  content:"";
  position:absolute;
  inset:0;
  z-index:-1;
  background:rgba(var(--surf2),.72);
  backdrop-filter:blur(20px) saturate(180%);
  -webkit-backdrop-filter:blur(20px) saturate(180%);
  transition:opacity .26s var(--ease),background .26s var(--ease);
}
.nav-in{
  position:relative;z-index:1;height:48px;
  display:flex;align-items:center;justify-content:space-between;gap:18px;
  /* Only transform is animated. Transitioning width / max-width / padding made
     the browser re-lay-out the whole bar every frame — that was the lag. The
     size change now lands in one frame, hidden under the background cross-fade. */
  transition:transform .3s var(--ease);
}
/* the floating pill is retired — the bar is the same object at every offset */
.nav-in::before{content:none}
.nav.scrolled .nav-in{transform:none;width:auto;max-width:none}

/* The open mobile sheet always wants the plain full-width bar above it.
   These are scoped through .nav.scrolled so they outrank the scrolled pill
   rules, and the bottom border is dropped so the bar meets the sheet exactly
   rather than leaving a 1px sliver of page between them. */
body.nav-open .nav.scrolled{border-bottom-color:transparent}
body.nav-open .nav.scrolled::before{opacity:1}
body.nav-open .nav.scrolled .nav-in::before{opacity:0}
body.nav-open .nav.scrolled .nav-in{
  width:auto;max-width:var(--max);padding:0 var(--gut);
  transform:none;   /* sit back on the sheet, not 18px below it */
}
body.nav-open .nav.scrolled .logo{position:relative;opacity:1;visibility:visible;pointer-events:auto}
.logo{display:inline-flex;align-items:center;flex:0 0 auto}
.nav .brandmark{height:22px;filter:none}

/* the link cluster sits centred and spreads itself across the bar */
.nav-links{
  display:flex;
  align-items:center;
  flex:1 1 auto;
  justify-content:center;
  gap:clamp(16px,2.6vw,34px);
}
.nav-links a:not(.btn){
  font-family:var(--body);
  font-size:12px;
  font-weight:400;
  letter-spacing:var(--tracking-caption);
  color:var(--fg);
  opacity:.88;
  position:relative;
  padding:6px 2px;
  white-space:nowrap;
  transition:opacity .28s var(--ease);
}
.nav-links a:not(.btn):hover{opacity:1}
/* the active dot is the one mark the bar makes */
.nav-links a:not(.btn)::after{
  content:"";
  position:absolute;
  left:50%;bottom:0;
  width:3px;height:3px;border-radius:50%;
  background:currentColor;
  opacity:.6;
  transform:translateX(-50%) scale(0);
  transition:transform .34s var(--ease);
}
.nav-links a.active{opacity:1}
.nav-links a.active::after{transform:translateX(-50%) scale(1)}

.nav .btn{
  flex:0 0 auto;
  padding:6px 15px;
  font-family:var(--body);
  font-size:var(--text-caption);
  font-weight:400;
  letter-spacing:var(--tracking-caption);
  box-shadow:none;
}
.nav .btn:hover{box-shadow:none}
.nav .btn .arr{display:none}

/* THE BARS ARE 18x12; THE BUTTON MUST NOT BE. Drawn at its own ink size, the
   hamburger gave a thumb an 18x12 target — a fifth of the 44x44 a finger needs,
   on the one control that opens the entire menu on a phone. The button becomes
   44x44 and the three bars are simply positioned to the centre of it, so no
   markup changes and the mark sits exactly where it did. Negative margin keeps
   the larger box from pushing the nav around. */
.nav-toggle{
  display:none;background:transparent;border:0;position:relative;cursor:pointer;flex:0 0 auto;
  width:44px;height:44px;margin:-16px -13px;
}
.nav-toggle span{position:absolute;left:13px;width:18px;height:1.5px;border-radius:2px;background:var(--fg);transition:.34s var(--ease)}
.nav-toggle span:nth-child(1){top:18px}
.nav-toggle span:nth-child(2){top:18px;opacity:0}
.nav-toggle span:nth-child(3){top:25px}
.nav-toggle.open span:nth-child(1){top:21px;transform:rotate(45deg)}
.nav-toggle.open span:nth-child(3){top:21px;transform:rotate(-45deg)}

/* HERO */
.hero{
  min-height:100svh;
  position:relative;
  overflow:hidden;
  display:flex;
  flex-direction:column;
}
.hero-bg{
  position:absolute;
  inset:0;
  z-index:0;
  overflow:hidden;
  background:url('../video/hero-poster.jpg') center/cover no-repeat;
  transform:scale(1.02);
}
/* the clip sits over the still and fades in only once it is genuinely playing,
   so a missing file, a blocked autoplay or reduced-motion all leave the poster */
.hero-video{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;
  opacity:0;
  transition:opacity .9s var(--ease);
}
.hero-video.is-playing{opacity:1}
/* darkening pass, above the footage, so the headline keeps its contrast */
.hero-scrim{
  position:absolute;inset:0;
  background:
    linear-gradient(90deg,rgba(5,10,20,.78) 0%,rgba(5,10,20,.48) 40%,rgba(5,10,20,.08) 100%),
    linear-gradient(180deg,rgba(5,10,20,.05) 0%,rgba(5,10,20,.12) 54%,var(--ink) 100%);
}
.hero-bg::after{
  content:"";
  position:absolute;
  inset:0;
  background:
    radial-gradient(38% 50% at 67% 58%,rgba(62,143,214,.22),transparent 70%),
    radial-gradient(60% 46% at 20% 58%,rgba(10,16,24,.40),transparent 70%);
}
.hero-main{position:relative;z-index:4;flex:1;display:flex;align-items:center;padding-top:120px;padding-bottom:40px}
.hero-copy{max-width:780px}
.hero h1{
  margin-top:24px;
  color:var(--fg);
  font-size:clamp(46px,7.5vw,104px);
  font-weight:300;
  line-height:.99;
  max-width:10.7ch;
}
.hero h1 b{font-weight:500}
.hero h1 .line{display:block}
.hero h1 .line b{color:var(--sky)}
.hero-sub{margin-top:28px;font-size:clamp(17px,1.35vw,20px);color:rgba(var(--fgc),.78);max-width:60ch}
.hero-cta{display:flex;flex-wrap:wrap;gap:14px;margin-top:34px}
.hero-regions{
  position:relative;
  z-index:4;
  display:flex;
  justify-content:center;
  gap:clamp(24px,7vw,92px);
  padding-bottom:26px;
}
.hero-regions::before{
  content:"";
  position:absolute;
  left:18%;right:18%;top:13px;height:1px;
  background:linear-gradient(90deg,transparent,rgba(62,143,214,.6),transparent);
}
.region-node{text-align:center;position:relative}
.region-node .pin{width:10px;height:10px;border-radius:50%;background:#c9ecff;margin:0 auto 14px;box-shadow:0 0 0 5px rgba(62,143,214,.18),0 0 20px var(--sky);animation:pin 2.8s infinite}
.region-node:nth-child(2) .pin{animation-delay:.4s}.region-node:nth-child(3) .pin{animation-delay:.8s}
@keyframes pin{50%{box-shadow:0 0 0 11px rgba(62,143,214,.02),0 0 34px var(--sky)}}
.region-node strong{font-family:var(--display);font-weight:500;color:var(--fg);font-size:15px}
.region-node small{display:block;margin-top:4px;color:var(--sky);font-family:var(--display);letter-spacing:.2em;text-transform:uppercase;font-size:11px}
.hero-foot{position:relative;z-index:4;border-top:1px solid var(--line);padding:18px 0 30px;display:flex;align-items:center;justify-content:space-between;gap:18px;color:var(--faint);font-family:var(--display);font-size:12px;letter-spacing:.15em;text-transform:uppercase}
.hero-foot .dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--sky);margin-right:10px;box-shadow:0 0 18px var(--sky)}
.scroll-line{display:inline-flex;align-items:center;gap:10px}
.scroll-line::after{content:"";width:38px;height:1px;background:linear-gradient(90deg,var(--sky),transparent);display:block}

/* TRUST */
.trust{padding:54px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:rgba(5,12,22,.32)}
.trust-grid{display:grid;grid-template-columns:1fr 1fr 1fr 1.15fr;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:0;overflow:hidden}
.trust-card{background:rgba(var(--surf),.86);padding:28px 26px;min-height:142px;position:relative}
.trust-card .num{font-family:var(--display);font-size:clamp(38px,4.4vw,64px);line-height:1;color:var(--fg);font-weight:300}
.trust-card .num span{color:var(--sky)}
.trust-card p{margin-top:12px;color:var(--soft);font-size:13.5px}
.trust-card.highlight{
  background:
    linear-gradient(135deg,rgba(16,95,190,.32),rgba(10,16,24,.82)),
    url('../projects/project-dubai.webp') center/cover;
}
.trust-card.highlight::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(var(--surf),.35),rgba(var(--surf),.78))}
.trust-card.highlight>*{position:relative;z-index:1}

/* EXPERIENCE */
.experience{background:linear-gradient(180deg,rgba(var(--surf),.0),rgba(var(--surf2),.38),rgba(var(--surf),.0))}
.exp-grid{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(340px,.8fr);gap:clamp(28px,4vw,58px);align-items:start}
.home-stage{
  position:relative;
  border:1px solid var(--line2);
  border-radius:0;
  overflow:hidden;
  background:var(--ink);
  box-shadow:0 38px 100px -62px rgba(0,0,0,.9),inset 0 0 0 1px rgba(255,255,255,.04);
  min-height:520px;
  transform-style:preserve-3d;
}
.home-stage::before{
  content:"";
  display:block;
  aspect-ratio:4/3;
}
.home-photo{object-fit:cover;filter:brightness(.82) contrast(1.04) saturate(1.08)}
.sim-effects{z-index:2;pointer-events:none;transition:background .5s var(--ease)}
.sim-effects::before,.sim-effects::after{content:"";position:absolute;inset:0;opacity:0;transition:opacity .5s var(--ease)}
.home-stage[data-mode="lighting"] .sim-effects{background:radial-gradient(circle at 27% 52%,rgba(255,205,125,.24),transparent 35%),radial-gradient(circle at 70% 28%,rgba(255,205,125,.20),transparent 30%)}
.home-stage[data-mode="climate"] .sim-effects::before{
  opacity:1;
  background:
    repeating-linear-gradient(115deg,transparent 0 38px,rgba(var(--linec),.18) 42px,transparent 74px);
  animation:air 4.2s linear infinite;
}
@keyframes air{to{transform:translateX(80px)}}
.home-stage[data-mode="security"] .sim-effects::before{
  opacity:1;background:radial-gradient(circle at 25% 72%,rgba(62,143,214,.38),transparent 18%),radial-gradient(circle at 92% 18%,rgba(62,143,214,.30),transparent 20%)
}
.home-stage[data-mode="security"] .sim-effects::after{
  opacity:1;inset:12%;border:2px solid rgba(62,143,214,.35);border-radius:50%;animation:pulseRing 2.4s infinite
}
@keyframes pulseRing{50%{transform:scale(1.06);opacity:.42}}
.home-stage[data-mode="shading"] .sim-effects{background:linear-gradient(100deg,rgba(255,220,150,.24),transparent 45%,rgba(0,10,24,.32) 76%)}
.home-stage[data-mode="entertainment"] .sim-effects{background:radial-gradient(circle at 78% 28%,rgba(62,143,214,.35),transparent 30%),linear-gradient(180deg,rgba(0,0,0,.20),rgba(0,0,0,.44))}
.home-stage[data-mode="energy"] .sim-effects::before{
  opacity:1;background:radial-gradient(circle at 68% 52%,rgba(62,143,214,.36),transparent 28%),linear-gradient(135deg,transparent,rgba(62,143,214,.22),transparent);animation:energySweep 3.5s linear infinite
}
@keyframes energySweep{50%{transform:translateX(22px) translateY(-18px)}}
.hotspot{
  position:absolute;
  z-index:5;
  width:46px;height:46px;
  border-radius:50%;
  border:1px solid rgba(255,255,255,.26);
  background:rgba(var(--surf),.72);
  color:var(--fg);
  display:grid;
  place-items:center;
  cursor:pointer;
  box-shadow:0 0 0 7px rgba(62,143,214,.11),0 0 24px rgba(62,143,214,.5);
  transition:transform .28s var(--ease),background .28s,border-color .28s;
}
.hotspot svg{width:21px;height:21px}
.hotspot:hover,.hotspot.active{transform:scale(1.08);background:rgba(62,143,214,.86);border-color:rgba(255,255,255,.5)}

.hotspot::after{content:attr(data-label);position:absolute;left:50%;bottom:calc(100% + 10px);transform:translateX(-50%);background:rgba(4,10,18,.88);border:1px solid var(--line2);color:var(--fg);font-family:var(--display);font-size:11px;white-space:nowrap;padding:5px 9px;border-radius:0;opacity:0;pointer-events:none;transition:opacity .25s,transform .25s}
.hotspot:hover::after{opacity:1;transform:translateX(-50%) translateY(-3px)}
.hs-living{left:30%;top:54%}.hs-climate{left:52%;top:30%}.hs-security{left:12%;top:72%}.hs-shading{left:68%;top:24%}.hs-cinema{left:82%;top:30%}.hs-energy{left:61%;top:64%}
.exp-panel{
  border:1px solid var(--line2);
  border-radius:0;
  padding:clamp(26px,3vw,38px);
  background:linear-gradient(180deg,rgba(var(--surf2),.76),rgba(var(--surf),.82));
  backdrop-filter:blur(16px);
  box-shadow:0 36px 90px -66px rgba(0,0,0,.9);
  position:sticky;
  top:74px;
}
.mode-kicker{font-family:var(--display);font-size:12px;letter-spacing:.22em;text-transform:uppercase;color:var(--sky)}
.exp-panel h3{font-size:clamp(26px,3vw,38px);color:var(--fg);margin-top:16px}
.exp-panel h3 b{color:var(--sky);font-weight:500}
.exp-panel p{margin-top:16px;color:var(--soft)}
.mode-list{display:grid;gap:10px;margin-top:22px}
.mode-list span{position:relative;padding-left:21px;color:rgba(var(--fgc),.76);font-size:14px}
.mode-list span::before{content:"";position:absolute;left:0;top:.7em;width:7px;height:7px;border-radius:50%;background:var(--sky);box-shadow:0 0 16px var(--sky)}
.metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line);border:1px solid var(--line);border-radius:0;overflow:hidden;margin-top:26px}
.metric{background:rgba(var(--surf),.68);padding:16px 14px;text-align:center}
.metric strong{display:block;color:var(--fg);font-family:var(--display);font-size:21px}
.metric small{display:block;margin-top:4px;color:var(--faint);font-size:11px}
.mode-tabs,.scenario-bar{display:flex;flex-wrap:wrap;gap:9px;margin-top:24px}
.mode-tab,.scenario,.sound-toggle{
  border:1px solid var(--line2);
  background:rgba(255,255,255,.035);
  color:var(--soft);
  border-radius:0;
  padding:9px 13px;
  cursor:pointer;
  font-family:var(--display);
  font-size:12.5px;
  transition:background .3s,border-color .3s,color .3s,transform .18s var(--ease);
}
.mode-tab:hover,.mode-tab.active,.scenario:hover,.scenario.active,.sound-toggle.on{background:rgba(62,143,214,.18);border-color:rgba(62,143,214,.72);color:var(--fg)}
.scenario-bar{margin:18px 0 0}
.sound-toggle{margin-left:auto}

/* HARDWARE */
.specs{display:flex;flex-wrap:wrap;gap:10px;margin-top:28px}
.spec{border:1px solid var(--line2);border-radius:0;padding:8px 13px;color:var(--soft);font-family:var(--display);font-size:12.5px}

/* WHY */

/* PROJECTS */


/* Alam Al Tob Hospital is on the client's project list with no photograph of
   any kind — its cell in their PDF is blank. It is a real delivered project
   and a hospital is a credential worth showing, so it gets a card with no
   image rather than being left off the site or given a stock photo of some
   other building. The dark ground and the gradient are already there; this
   only says plainly why there is nothing to look at. It cannot go in the 3D
   room: that geometry seats ten, and an eleventh pane overlaps at the seam. */


/* TIMELINE — the company history, on one spine ============================
   Five milestones, each revealing as it arrives. Deliberately NOT the pinned
   arc stage: that hijacks the scroll for five screens, which suits a sequence
   of steps but makes a short history feel longer than it is. The spine is
   drawn on the marker column so it inherits the RTL flip for free. */
/* The rail is ONE continuous line for the whole list, with a second line
   drawn over it that fills as you scroll — that fill is the "flow". Per-item
   segments could not do this: a line that grows has to be a single element.
   JS sets --tl-p (0..1) plus the rail's exact top and height, measured from
   the first and last dot so the fill starts and ends on them. */
.tl{
  list-style:none;margin:clamp(28px,4vh,52px) 0 0;padding:0;
  position:relative;--tl-p:0;
}
.tl::before,.tl::after{
  content:"";position:absolute;inset-inline-start:6px;width:1px;
  top:var(--tl-rail-top,13px);height:var(--tl-rail-h,0px);
}
.tl::before{background:var(--line)}
.tl::after{
  background:linear-gradient(180deg,var(--sky),rgba(62,143,214,.55));
  transform:scaleY(var(--tl-p));transform-origin:top;
  will-change:transform;
}
.tl-item{
  display:grid;
  /* Copy and image carry comparable weight. A plain 1fr for the copy left the
     text wrapping at its 58ch measure and a wide empty gutter before the
     photograph, which read as a mistake rather than as space. */
  grid-template-columns:auto minmax(0,1.15fr) minmax(0,1fr);
  gap:clamp(18px,2.6vw,38px);
  align-items:start;
  padding-bottom:clamp(34px,5vh,64px);
  max-width:1120px;
}
.tl-item:last-child{padding-bottom:0}

/* marker column: the dot, and the line running down to the next entry */
.tl-mark{position:relative;width:13px;align-self:stretch;display:flex;justify-content:center}
.tl-mark i{
  width:13px;height:13px;border-radius:50%;flex:0 0 auto;margin-top:7px;
  background:var(--surf);border:1px solid var(--line2);
  position:relative;z-index:1;
  transition:background .45s var(--ease),border-color .45s var(--ease),
             box-shadow .45s var(--ease),transform .45s var(--ease);
}
/* the dot lights as the fill reaches it, and pops very slightly — the flow
   arriving, rather than the dot announcing itself */
.tl-item.is-live .tl-mark i{
  background:var(--sky);border-color:var(--sky);
  box-shadow:0 0 0 5px rgba(62,143,214,.14);
  transform:scale(1.12);
}

/* Content WIPES UP behind a mask instead of fading. Nothing changes opacity:
   a fade makes five entries blink on independently, which is the opposite of
   a flow. The mask opens downward while the block rises, so each entry reads
   as being carried in by the line. The image follows the copy by 110ms. */
.tl-body,.tl-media{
  clip-path:inset(0 0 100% 0);
  transform:translateY(20px);
  transition:clip-path .72s var(--ease),transform .72s var(--ease);
}
.tl-item.is-live .tl-body,
.tl-item.is-live .tl-media{clip-path:inset(0 0 0 0);transform:none}
.tl-item.is-live .tl-media{transition-delay:.11s}

.tl-year{
  display:block;font-family:var(--display);font-size:12px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--sky);
}
.tl-body h3{font-size:clamp(22px,2.4vw,30px);color:var(--fg);font-weight:500;margin-top:9px}
.tl-body p{color:var(--soft);max-width:58ch;margin-top:10px}
.tl-chips{display:flex;flex-wrap:wrap;gap:9px;margin-top:16px;list-style:none}
.tl-chips li{
  font-family:var(--display);font-size:11.5px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--soft);border:1px solid var(--line);padding:7px 12px;
}
.tl-media{
  margin:0;overflow:hidden;border:1px solid var(--line);background:var(--surf2);
  aspect-ratio:3/2;          /* shorter than 4:3 so a row stays near one screen-third */
}
.tl-media img{width:100%;height:100%;object-fit:cover;display:block}

@media(max-width:900px){
  /* the image drops below the copy, and the spine keeps the two aligned */
  .tl-item{grid-template-columns:auto minmax(0,1fr);gap:16px}
  .tl-media{grid-column:2;margin-top:18px;aspect-ratio:16/9}
}
@media(max-width:560px){
  .tl-media{display:none}   /* five 16:9 photos is a lot of scrolling on a phone */
}
@media(prefers-reduced-motion:reduce){
  .tl::after{transform:none}                      /* the rail is simply drawn */
  .tl-body,.tl-media{clip-path:none;transform:none;transition:none}
  .tl-mark i{transition:none}
}

/* CONTACT */
.contact{background:radial-gradient(60% 60% at 80% 10%,rgba(16,95,190,.18),transparent 70%)}
.contact-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(36px,6vw,82px);align-items:start}
.contact h2{font-size:clamp(36px,5vw,70px);color:var(--fg);font-weight:300}
.contact h2 b{color:var(--sky);font-weight:500}
.contact p{color:var(--soft);margin-top:22px;max-width:46ch}
.offices{border-top:1px solid var(--line)}
.office{border-bottom:1px solid var(--line);padding:24px 0;transition:padding-left .35s}
.office:hover{padding-left:12px}
.office-top{display:flex;align-items:baseline;gap:12px}
.office-city{font-family:var(--display);font-size:22px;color:var(--fg);font-weight:500}
.office-country{font-family:var(--display);font-size:11px;letter-spacing:.20em;text-transform:uppercase;color:var(--sky)}
.office-address{color:var(--soft);font-size:14px;margin-top:7px}
.office a{display:inline-block;margin-top:10px;color:var(--fg);font-family:var(--display)}
.footer{padding:52px 0 34px;border-top:1px solid var(--line)}
.footer-grid{display:grid;grid-template-columns:1.5fr repeat(3,1fr);gap:36px}
.footer img{height:48px}
.footer p{color:var(--soft);margin-top:18px;max-width:32ch}
.foot-col h2{font-family:var(--display);font-size:12px;letter-spacing:.2em;text-transform:uppercase;color:var(--sky);margin-bottom:14px}
.foot-col a{display:block;color:var(--soft);font-size:14px;padding:6px 0;transition:color .25s,padding-left .25s}
.foot-col a:hover{color:var(--fg);padding-left:5px}
.footer-bottom{display:flex;align-items:center;justify-content:space-between;gap:18px;flex-wrap:wrap;margin-top:44px;padding-top:24px;border-top:1px solid var(--line);font-size:12px;color:var(--faint)}
/* Privacy Policy and Cookie settings sit together — without this they run
   into each other, because one is a link and the other a button. */
.foot-legal{display:inline-flex;align-items:center;gap:16px;flex-wrap:wrap}

/* MOBILE */
.mobile-cta{display:none}
@media(max-width:1100px){
  .exp-grid,.contact-grid{grid-template-columns:1fr}
  .exp-panel{position:relative;top:auto}
}
@media(max-width:900px){

  .trust-grid{grid-template-columns:1fr 1fr}
}
/* Compact nav: links collapse into a full-height sheet under the bar */
@media(max-width:860px){
  .nav-toggle{display:block}
  .nav-links{
    position:fixed;
    inset:48px 0 0 0;   /* starts exactly under the bar */
    flex-direction:column;
    align-items:stretch;
    justify-content:flex-start;
    gap:0;
    padding:8px var(--gut) 40px;
    background:rgba(var(--surf),.94);
    backdrop-filter:blur(26px) saturate(180%);
    -webkit-backdrop-filter:blur(26px) saturate(180%);
    overflow-y:auto;
    opacity:0;
    visibility:hidden;
    transition:opacity .38s var(--ease),visibility .38s var(--ease);
  }
  .nav-links.open{opacity:1;visibility:visible}
  .nav-links a:not(.btn){
    font-size:21px;
    color:var(--fg);
    padding:19px 2px;
    border-bottom:1px solid rgba(var(--linec),.10);
    opacity:0;
    transform:translateY(12px);
    transition:opacity .5s var(--ease),transform .5s var(--ease),color .28s;
  }
  .nav-links a:not(.btn)::after{display:none}
  .nav-links .btn{
    margin-top:26px;
    align-self:flex-start;
    padding:13px 24px;
    font-size:15px;
    opacity:0;
    transform:translateY(12px);
    transition:opacity .5s var(--ease),transform .5s var(--ease);
  }
  /* stagger the items in behind the sheet */
  .nav-links.open a{opacity:1;transform:none}
  .nav-links.open a:nth-child(1){transition-delay:.06s}
  .nav-links.open a:nth-child(2){transition-delay:.11s}
  .nav-links.open a:nth-child(3){transition-delay:.16s}
  .nav-links.open a:nth-child(4){transition-delay:.21s}
  .nav-links.open a:nth-child(5){transition-delay:.26s}
  .nav-links.open a:nth-child(6){transition-delay:.31s}
  .nav-links.open a:nth-child(7){transition-delay:.36s}
  body.nav-open{overflow:hidden}
}
@media(max-width:720px){
  .hero h1{font-size:clamp(43px,14vw,68px)}
  .hero-regions{flex-wrap:wrap;gap:18px}
  .hero-regions::before{display:none}
  .hero-foot{flex-direction:column;align-items:flex-start}
  .trust-grid,.projects-grid{grid-template-columns:1fr}
  .home-stage{min-height:auto}
  .home-stage::before{aspect-ratio:4/3}
  .exp-panel{padding:24px 20px}
  .metrics{grid-template-columns:1fr 1fr 1fr}
  .sound-toggle{margin-left:0}
  .footer-grid{grid-template-columns:1fr}
  /* A CORNER BUTTON, NOT A BAR. The full-width version stood across the bottom
     of every screen for the whole visit — 45px of permanent blue over whatever
     the visitor was reading, repeating an offer the page makes twice already.
     A circle in the corner says the same thing and gives the content its screen
     back. inset-inline-end rather than right, so it sits on the correct side in
     Arabic; the safe-area inset keeps it off the home indicator. */
  .mobile-cta{
    display:inline-flex;align-items:center;justify-content:center;
    position:fixed;z-index:96;
    inset-inline-end:16px;
    bottom:calc(16px + env(safe-area-inset-bottom));
    width:56px;height:56px;border-radius:50%;
    background:var(--blue);color:#fff;
    text-decoration:none;
    box-shadow:0 10px 30px -10px rgba(0,113,227,.7);
    transition:transform .3s var(--ease),background .3s var(--ease);
  }
  .mobile-cta svg{width:25px;height:25px}
  .mobile-cta:active{transform:scale(.94)}
  .mobile-cta:hover{background:#0077ed}
  /* the label is for assistive tech and for a browser that never loads the icon */
  .mobile-cta-label{
    position:absolute;width:1px;height:1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap;
  }
}
@media(prefers-reduced-motion:reduce){
  *{animation:none!important;scroll-behavior:auto!important}
  .reveal{opacity:1;transform:none;transition:none}
}

/* Final polish refinements */
.hero-copy{position:relative;z-index:3}
.hero-cta .btn{min-width:196px}
.exp-panel{transition:box-shadow .35s var(--ease), border-color .35s var(--ease)}
.home-stage[data-mode="lighting"] + .scenario-bar .scenario[data-scenario="welcome"],
.home-stage[data-mode="security"] + .scenario-bar .scenario[data-scenario="away"],
.home-stage[data-mode="entertainment"] + .scenario-bar .scenario[data-scenario="cinema"],
.home-stage[data-mode="climate"] + .scenario-bar .scenario[data-scenario="night"]{box-shadow:0 16px 34px -22px rgba(62,143,214,.9)}

.office a{word-break:break-word}
@media(max-width:900px){
  .sol p{min-height:unset}
}
@media(max-width:720px){
  .hero-cta{display:grid;grid-template-columns:1fr;max-width:360px}
  .hero-cta .btn{width:100%}
  .mode-tabs{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
  .scenario-bar{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
  .scenario,.sound-toggle{width:100%;justify-content:center;text-align:center}
  .sound-toggle{grid-column:1/-1;margin-left:0}
  .metrics{grid-template-columns:repeat(3,1fr)}
}

/* Review pass corrections */
.hero-main .wrap{width:100%;max-width:none;padding-left:max(12px, env(safe-area-inset-left));padding-right:max(18px, env(safe-area-inset-right))}
.hero-main{padding-top:104px;padding-bottom:34px}
.hero-copy{max-width:min(48vw,760px);margin-left:0;position:relative;z-index:4}
.hero .eyebrow{max-width:max-content}
.hero h1{max-width:8.1ch;font-size:clamp(56px,7.1vw,100px);line-height:.96;letter-spacing:-.04em}
.hero h1 .line{margin-top:6px}
.hero-sub{max-width:31ch;font-size:clamp(18px,1.32vw,21px)}
.hero-cta{justify-content:flex-start}
.hero-bg{background-position:65% center}

.trust-grid{grid-template-columns:repeat(4,minmax(0,1fr))}
.trust-card,.trust-card.highlight{background:rgba(var(--surf),.88)!important;min-height:220px;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:34px 28px}
.trust-card.highlight::after{display:none!important}
.trust-card .num{font-size:clamp(52px,5.4vw,78px)}
.trust-card p{max-width:17ch;margin:18px auto 0;font-size:14.5px;line-height:1.55}


.experience .section-head{max-width:920px}
.exp-grid{grid-template-columns:minmax(0,1.26fr) minmax(340px,.86fr)}
.home-stage{min-height:560px;border-radius:0;background:linear-gradient(180deg,rgba(var(--surf),.96),rgba(5,10,18,.98));box-shadow:0 44px 120px -72px rgba(0,0,0,.95),inset 0 0 0 1px rgba(255,255,255,.03)}
.home-stage::before{aspect-ratio:16/11}
.home-photo{object-fit:cover;filter:brightness(.9) contrast(1.04) saturate(1.08)}
.home-stage > img,.sim-effects,.home-hotspots{position:absolute;inset:0;width:100%;height:100%}
.hotspot{width:52px;height:52px;box-shadow:0 0 0 10px rgba(62,143,214,.08),0 0 28px rgba(62,143,214,.35)}
.hotspot:hover,.hotspot.active{transform:scale(1.08);background:rgba(62,143,214,.92);border-color:rgba(255,255,255,.56)}
.hotspot.active::after,.hotspot:hover::after{opacity:1;transform:translateX(-50%) translateY(-3px)}
.hs-living{left:31%;top:64%}.hs-climate{left:52%;top:39%}.hs-security{left:72%;top:24%}.hs-shading{left:68%;top:42%}.hs-cinema{left:82%;top:47%}.hs-energy{left:63%;top:78%}
.home-stage[data-mode="lighting"] .sim-effects{background:radial-gradient(circle at 31% 64%,rgba(255,205,125,.22),transparent 18%),radial-gradient(circle at 12% 25%,rgba(255,205,125,.16),transparent 24%)}
.home-stage[data-mode="climate"] .sim-effects::before{opacity:1;background:repeating-linear-gradient(115deg,transparent 0 42px,rgba(var(--linec),.12) 48px,transparent 82px);animation:air 4.5s linear infinite}
.home-stage[data-mode="security"] .sim-effects::before{opacity:1;background:radial-gradient(circle at 74% 24%,rgba(62,143,214,.34),transparent 16%),radial-gradient(circle at 68% 42%,rgba(62,143,214,.24),transparent 16%)}
.home-stage[data-mode="security"] .sim-effects::after{opacity:1;inset:12%;border:1px solid rgba(62,143,214,.28);border-radius:0;animation:pulseRing 2.6s infinite}
.home-stage[data-mode="shading"] .sim-effects{background:linear-gradient(90deg,rgba(255,245,220,.20) 0 17%,transparent 17% 83%,rgba(255,245,220,.20) 83% 100%),linear-gradient(180deg,rgba(255,230,168,.08),rgba(var(--surf),.14))}
.home-stage[data-mode="entertainment"] .sim-effects{background:radial-gradient(circle at 82% 47%,rgba(62,143,214,.30),transparent 18%),linear-gradient(180deg,rgba(0,0,0,.12),rgba(0,0,0,.34))}
.home-stage[data-mode="energy"] .sim-effects::before{opacity:1;background:radial-gradient(circle at 63% 78%,rgba(62,143,214,.34),transparent 16%),linear-gradient(135deg,transparent,rgba(62,143,214,.18),transparent);animation:energySweep 3.8s linear infinite}
.mode-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
.mode-tab,.scenario,.sound-toggle{display:flex;align-items:center;justify-content:center;min-height:44px;text-align:center}
.scenario-bar{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;align-items:stretch}
.sound-toggle{margin-left:0}

@media(max-width:1100px){
  .exp-grid{grid-template-columns:1fr}
  .mode-tabs{grid-template-columns:repeat(3,minmax(0,1fr))}
  .scenario-bar{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media(max-width:900px){
  .hero-copy{max-width:min(100%,640px)}
  .hero-sub{max-width:36ch}
    .trust-grid{grid-template-columns:1fr 1fr}
}
@media(max-width:720px){
    .hero-main{padding-top:86px}
  .hero-copy,.hero-sub{max-width:100%}
  .hero-main .wrap{padding-left:max(10px, env(safe-area-inset-left));padding-right:max(10px, env(safe-area-inset-right))}
  .hero h1{max-width:8.5ch;font-size:clamp(44px,14vw,68px)}
  .trust-card,.trust-card.highlight{min-height:182px}
  .mode-tabs{grid-template-columns:repeat(2,minmax(0,1fr))}
  .scenario-bar{grid-template-columns:repeat(2,minmax(0,1fr))}
  .sound-toggle{grid-column:1/-1}
}

/* ===== review pass: accessibility + inline logo ===== */
.brandmark{height:54px;width:auto;display:block;filter:drop-shadow(0 12px 28px rgba(0,0,0,.55))}
.footer .brandmark{height:48px;filter:none}
@media(max-width:680px){.brandmark{height:44px}}

/* ===== PROJECTS — WebGL room ===== */


/* Tall track = scroll distance. Height is set by JS from the pane count so the
   travel speed stays constant no matter how many projects are added. */
/* Matches the product panels: same gutter, radius and hairline, so the two
   sections read as one family of full-height panels. The track only takes
   horizontal padding — its height is set by JS and box-sizing is border-box,
   so the scroll maths is untouched. */
.room-track{position:relative;height:calc(100vh + 560vh);padding:0 clamp(12px,1.5vw,26px)}
.room-stage{
  position:sticky;top:0;height:100svh;overflow:hidden;
  border:1px solid var(--line);
  border-radius:0;
}
#roomCanvas{position:absolute;inset:0;width:100%;height:100%;display:block;touch-action:pan-y}
.room-stage::after{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:2;
  background:linear-gradient(180deg,var(--ink) 0%,transparent 16%,transparent 84%,var(--ink) 100%);
}
.room-ui{position:absolute;inset:0;z-index:3;pointer-events:none}
/* scrim so the heading stays legible when a pane turns in behind it */
.room-ui::before{
  content:"";position:absolute;left:0;top:0;right:0;height:52%;
  background:linear-gradient(180deg,rgba(var(--surf),.88) 0%,rgba(var(--surf),.55) 42%,transparent 100%);
  pointer-events:none;
}
.room-head{position:absolute;left:var(--gut);top:clamp(78px,11vh,124px);max-width:min(46vw,560px)}
.room-head h2{font-size:clamp(30px,3.9vw,54px);color:var(--fg);margin-top:18px;font-weight:300}
.room-head h2 b{font-weight:500;color:var(--sky)}

/* Follows the pointer rather than sitting on the pane, so the copy stays
   legible whatever the pane is doing behind it. JS positions the top-left
   corner; the flip/clamp logic keeps it inside the stage near the edges. */
.room-caption{
  position:absolute;left:0;top:0;
  width:max-content;max-width:min(330px,44vw);
  padding:14px 17px 16px;
  border:1px solid var(--line2);
  border-radius:0;
  background:rgba(var(--surf),.66);
  backdrop-filter:blur(16px) saturate(160%);
  -webkit-backdrop-filter:blur(16px) saturate(160%);
  box-shadow:0 26px 60px -34px rgba(0,0,0,.95);
  text-align:left;
  pointer-events:none;
  opacity:0;
  transition:opacity .26s var(--ease);
  will-change:transform,opacity;
}
.room-caption.on{opacity:1}
.room-caption small{
  display:block;font-family:var(--display);font-size:10.5px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--sky);
}
.room-caption strong{
  display:block;margin-top:8px;font-family:var(--display);font-weight:400;
  font-size:clamp(17px,1.55vw,21px);color:var(--fg);letter-spacing:-.01em;line-height:1.15;
}
.room-caption-desc{
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden;
  margin-top:9px;
  font-size:13px;line-height:1.55;color:rgba(var(--fgc),.72);
}

.room-foot{
  position:absolute;left:var(--gut);right:var(--gut);bottom:34px;
  display:flex;align-items:baseline;justify-content:space-between;gap:20px;
  font-family:var(--display);font-size:11.5px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--faint);
}

/* Fallback grid — shown only when the room cannot run */
/* Was display:none until WebGL failed. It is now the page's CLOSING SECTION
   and always visible, because the page had none: 4,680px of room and then the
   footer. Three things fixed by showing it —
     "Skip the room" had nowhere to land, it scrolled to the track's bottom
       which was the footer;
     a visitor who has just looked round nine panes had no next step;
     the only machine-readable list of the projects was hidden from search
       engines and from anyone who does not engage with a WebGL canvas.
   It still doubles as the fallback: .is-fallback hides the track above it. */
.room-fallback{display:block;padding-top:var(--pad);padding-bottom:clamp(30px,4vw,56px)}


/* ===== Project detail — full page ==========================================
   A takeover rather than a modal: the pane flies to the middle of the room,
   this page fades in over it carrying the same image, so the two read as one
   continuous move. */


/* hero */


/* body */


/* the copy keeps a readable measure even though the page does not */


/* Flex, not a 3-column grid. room.js hides the Year cell on the six projects
   the client supplied no delivery year for, and a fixed repeat(3,1fr) kept the
   empty third track — which, with the 1px gaps showing the container colour
   through, rendered as a dark blank cell. Flex children just close up. */


/* gallery — every slot is framed, whether or not a photo exists yet */


/* A repeating nine-frame rhythm, so the grid keeps its shape however many
   photographs a project ends up with. Each row adds up to the 12 tracks:
   8+4, 4+4+4, 6+6, 4+8. */


/* ---- photographs smaller than the frame they were given -------------------
   room.js measures each image against its box and adds .is-soft when the
   source cannot cover it at 1x. Rather than enlarge it 3-5x, the frame keeps
   its size and the photo is drawn at its own pixel width, sharp, on a blurred
   bed of itself — the treatment record sleeves and album art have used for
   years, and it reads as deliberate instead of broken. See the note above
   fitToSource() in room.js for the measurements and why no better source
   exists. --shot-w and --shot-src are set per image by that function. */


/* The hero already carries a dark scrim and the project title, so a soft
   backdrop is the honest reading of a small source — sharpening is not on
   offer. Blur scales with how far the source falls short (--soft). */


/* empty frame: a deliberate placeholder, not a broken image */


body.pd-open{overflow:hidden}


@media(max-width:900px){
  .room-head{max-width:min(78vw,520px)}
}
@media(max-width:720px){
  .room-head{top:96px}
  .room-head h2{font-size:clamp(26px,7vw,36px)}
  .room-caption strong{font-size:15px;white-space:normal;max-width:60vw}
  .room-hint{display:none}
  /* clear the fixed mobile CTA bar */
  .room-foot{bottom:96px}

}

/* ===== FULL-BLEED BANDS =====================================================
   Solutions, hardware interfaces and the Why-Mass points are edge-to-edge rows
   instead of card grids. Each band splits the viewport in half and the halves
   swap sides on every other row.

   --edge lines the text up with the rest of the page: on wide screens the site
   container is centred at var(--max), so the gutter is that offset plus the
   normal page gutter.                                                       */

.band{
  position:relative;
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  align-items:stretch;
  border-top:1px solid var(--line);
}
/* every other row swaps sides */
.band:nth-child(even) > :first-child{grid-column:2;grid-row:1}
.band:nth-child(even) > :last-child{grid-column:1;grid-row:1}

/* ---- Hardware interfaces ---- */
/* margin resets the `auto` sides from the old centred card rule */

/* section heads keep the page gutter; the bands themselves go edge to edge */
#solutions,#products,#about{padding-bottom:0}
#solutions .section-head,#about .section-head{margin-bottom:clamp(38px,5vw,68px)}


/* ===== EXPERIENCE — one full-height panel ==================================
   Same shell as the product panels and the projects room: a single 100svh
   panel with the shared gutter, radius and hairline. Only applied where the
   two-column grid survives; below that the columns stack and the content is
   far taller than a screen, so the panel is allowed to grow instead. */
@media(min-width:1101px){
  .experience{padding:0 clamp(12px,1.5vw,26px)}
  .experience > .wrap{
    height:100svh;
    max-width:none;
    margin:0;
    padding:clamp(22px,3.2vh,42px) clamp(20px,3vw,52px);
    display:flex;
    flex-direction:column;
    border:1px solid var(--line);
    border-radius:0;
    overflow:hidden;
    background:linear-gradient(180deg,rgba(var(--surf2),.55),rgba(var(--surf),.86));
  }
  /* This panel is 100svh with scroll-margin-top:0, so an anchor jump parks its
     top edge at y=0 and the collapsed nav floats over it. #solutions and
     #contact survive that because their heads are LEFT-ALIGNED at the gutter
     and the collapsed nav is a centred capsule (x 338-1102 at 1440) — they
     pass either side of each other. This head is CENTRED, so it landed
     directly underneath: the eyebrow was completely hidden and "Explore a home
     that responds." was sliced in half by the capsule's bottom edge.
     Reserve the bar's height at the top. --navh (96) clears the capsule's
     bottom edge (measured 81-85) with room to spare and tracks the token.
     .exp-grid is flex:1 1 auto, so this comes out of the grid, not the page. */
  .experience > .wrap{padding-top:var(--navh)}

  /* the heading has to earn its space inside a fixed-height panel */
  .experience .section-head{
    flex:0 0 auto;
    max-width:820px;
    margin-bottom:clamp(12px,2vh,24px);
  }
  .experience .section-head h2{font-size:clamp(26px,3.2vw,44px);margin-top:12px}
  .experience .section-head .lead{margin-top:12px;font-size:clamp(14px,1.05vw,16px)}

  .experience .exp-grid{flex:1 1 auto;min-height:0;align-items:stretch}
  /* left column: the stage takes the slack, the scenario bar keeps its size */
  .experience .exp-grid > div:first-child{display:flex;flex-direction:column;min-height:0}
  .experience .home-stage{flex:1 1 auto;min-height:0;height:auto}
  .experience .home-stage::before{content:none}  /* the aspect spacer is what capped its height */
  .experience .scenario-bar{flex:0 0 auto}
  /* right column: fills the panel, scrolls internally if a mode runs long */
  .experience .exp-panel{position:static;top:auto;height:100%;min-height:0;overflow-y:auto}
}
/* short desktops: the lead paragraph is the first thing to go */
@media(min-width:1101px) and (max-height:780px){
  .experience .section-head .lead{display:none}
}

/* ===== CONTACT — one full-height panel =====================================
   Same shell again. Content is centred rather than top-aligned, which suits a
   closing call to action, and the section's radial glow moves inside the panel
   so it is not left glowing in the gutter. Same 1101px cut-off: below it the
   two columns stack and the panel grows naturally. */
@media(min-width:1101px){
  .contact{padding:0 clamp(12px,1.5vw,26px);background:none}
  .contact > .wrap{
    height:100svh;
    max-width:none;
    margin:0;
    padding:clamp(22px,3.2vh,42px) clamp(20px,3vw,52px);
    display:flex;
    flex-direction:column;
    justify-content:center;
    border:1px solid var(--line);
    border-radius:0;
    overflow:hidden;
    background:
      radial-gradient(60% 60% at 80% 10%,rgba(16,95,190,.18),transparent 70%),
      linear-gradient(180deg,rgba(var(--surf2),.55),rgba(var(--surf),.86));
  }
  /* Same nav reservation as .experience. This head is left-aligned, so at 1440
     it passes to the left of the centred capsule — but the capsule widens as
     the viewport narrows and the centred content rides up as it shortens, and
     at 1280x720 "START YOUR PROJECT" overlapped it by 31x15px. Content is
     centred here, so this only shifts the composition down; .contact-grid
     absorbs it and the office column already scrolls internally. */
  .contact > .wrap{padding-top:var(--navh)}
  /* And the panel has to be allowed to GROW. It was height:100svh with
     overflow:hidden, which already cut the Send button 39px off the bottom on
     a 1280x720 window before any of this — a submit control you cannot reach.
     Reserving the nav band made that 112px. min-height keeps the full-screen
     composition wherever it fits and lets the page scroll where it doesn't;
     justify-content:center still centres it whenever there is slack. */
  .contact > .wrap{height:auto;min-height:100svh;overflow:visible}
  .contact .contact-grid{flex:0 1 auto;min-height:0;align-items:center}
  /* if the office list ever outgrows the panel it scrolls rather than clips */
  .contact .offices{max-height:100%;overflow-y:auto}
}

/* ===== ECOSYSTEMS — full-viewport stage with a tab strip ==================
   One ecosystem fills the screen at a time. The strip along the bottom lists
   all six; the active column lifts, reveals its icon and runs a progress bar
   while it auto-advances.                                                   */
.eco{position:relative;padding:0}

/* crossfading image layers */


/* active ecosystem's commands sit above the strip */
@keyframes ecoFade{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}

/* the strip */

/* the icon only exists for the active column, so the column grows upward */

/* progress rail — fills while the active column is on screen */


/* ===== PRODUCT MOSAIC =====================================================
   A wide hero tile, then paired half-width tiles. Tiles sit near the screen
   edge with a small gutter so the imagery reads as the subject, not as cards
   in a container.                                                           */
.pgrid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(10px,1vw,16px);
  padding:0 clamp(12px,1.5vw,26px);
  margin-top:clamp(34px,5vw,60px);
}
.ptile{
  position:relative;
  margin:0;
  border-radius:0;
  overflow:hidden;
  background:#0b1626;
  border:1px solid var(--line);
  isolation:isolate;
}
.ptile-wide{grid-column:1 / -1}

/* Every panel fills the viewport, so scrolling never shows one tile peeking
   in below another. Height lives on the tile (border-box) and the media just
   fills it, which keeps the tile exactly one screen tall including its border. */
/* A full viewport per tile: five tiles over three grid rows was 2,729px of the
   home page to show five products. A tile needs to hold a photograph and its
   caption, not the whole screen. */
.ptile{height:clamp(340px,52svh,560px)}
.ptile-media{position:relative;width:100%;height:100%;overflow:hidden}
.ptile-media img{
  width:100%;height:100%;object-fit:cover;
  transition:transform 1.2s var(--ease),filter .6s var(--ease);
}
.ptile:hover .ptile-media img{transform:scale(1.035);filter:saturate(1.06)}

/* stand-in panel used until a product photo exists for the tile */
.ptile-icon{
  display:grid;place-items:center;
  background:
    linear-gradient(152deg,rgba(16,95,190,.20),rgba(var(--surf),.66) 62%,rgba(var(--surf),.9));
}
.ptile-icon::after{
  content:"";position:absolute;width:74%;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle,rgba(62,143,214,.34),transparent 66%);
  filter:blur(34px);
}
.ptile-icon svg{
  position:relative;z-index:1;
  width:clamp(112px,13vw,196px);height:auto;
  margin-bottom:6%;
  color:var(--sky);
  filter:drop-shadow(0 0 30px rgba(62,143,214,.38));
  transition:transform .8s var(--ease);
}
.ptile:hover .ptile-icon svg{transform:scale(1.05)}

.ptile figcaption{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  padding:clamp(20px,2.4vw,34px);
  background:linear-gradient(0deg,rgba(4,10,18,.92) 0%,rgba(4,10,18,.62) 46%,transparent 100%);
}
.ptile figcaption h4{
  font-family:var(--display);font-weight:400;
  font-size:clamp(19px,1.85vw,27px);
  letter-spacing:-.015em;color:var(--fg);
}
.ptile figcaption p{
  margin-top:8px;max-width:46ch;
  font-size:clamp(13px,1.02vw,15px);line-height:1.55;
  color:rgba(var(--fgc),.74);
}

/* ---- hero tile: centred title over the image, split caption along the foot */
.ptile-hero::after{
  /* lifts the centred type off a busy image without flattening the photo */
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:radial-gradient(62% 48% at 50% 30%,rgba(4,10,18,.52),transparent 72%);
}
.ptile-hero-center{
  position:absolute;left:0;right:0;top:19%;z-index:3;
  display:flex;flex-direction:column;align-items:center;
  padding:0 var(--gut);text-align:center;
}
.ptile-hero-center h3{
  font-family:var(--display);font-weight:400;
  font-size:clamp(32px,4.6vw,70px);
  letter-spacing:-.025em;line-height:1.02;color:var(--fg);
  text-shadow:0 4px 34px rgba(3,8,16,.62);
}
.ptile-discover{
  margin-top:clamp(14px,1.6vw,22px);
  padding:7px 16px;border-radius:0;
  background:rgba(255,255,255,.17);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  border:1px solid rgba(255,255,255,.16);
  color:var(--fg);font-family:var(--body);font-size:13px;font-weight:500;
  transition:background .3s var(--ease),border-color .3s var(--ease);
}
.ptile-discover:hover{background:rgba(255,255,255,.28);border-color:rgba(255,255,255,.34)}

/* six-column foot: short label, then the paragraph across the last two */
/* NOTE: these are scoped as `.ptile-hero figcaption` rather than by the
   .ptile-hero-foot class alone — the base `.ptile figcaption` rule is more
   specific than a lone class and was overriding the padding, scrim and
   paragraph styling. */
.ptile-hero figcaption{
  display:grid;
  grid-template-columns:repeat(6,minmax(0,1fr));
  gap:clamp(14px,2vw,32px);
  align-items:start;
  /* taller runway than the standard tile: the hero image is busy right where
     the foot text sits, so the scrim needs room to fade in */
  padding-top:clamp(64px,8vw,128px);
  background:linear-gradient(0deg,
    rgba(4,10,18,.95) 0%,rgba(4,10,18,.80) 32%,rgba(4,10,18,.38) 64%,transparent 100%);
}
.ptile-hero figcaption .ptile-label{
  grid-column:4;
  font-family:var(--body);font-weight:600;font-size:13px;line-height:1.38;
  color:var(--fg);
}
.ptile-hero figcaption p{
  grid-column:5 / 7;
  margin-top:0;max-width:none;
  font-size:13px;line-height:1.5;color:rgba(var(--fgc),.80);
}

@media(max-width:900px){
  .ptile-hero figcaption{grid-template-columns:minmax(0,1fr);gap:8px;padding-top:clamp(48px,9vw,90px)}
  .ptile-hero figcaption .ptile-label,.ptile-hero figcaption p{grid-column:1}
}
@media(max-width:760px){
  .pgrid{grid-template-columns:minmax(0,1fr)}
  /* keep the same 19% inset as desktop: measured against the shorter phone
     media box it still clears the fixed 56px nav when the panel aligns to the
     top of the viewport */
  .ptile figcaption p{font-size:13px}
  /* THE SCRIM WAS TUNED FOR A SLAT, NOT A CARD. On the desktop wall the caption
     sits over a narrow open panel; stacked on a phone it lies across the middle
     of a full photograph, and one of those photographs is a brightly lit floor
     plan. Measured there: white copy against the brightest part of the picture
     came out at 1.29:1, and failed AA across the top tenth of it. The gradient
     starts higher and holds more of its opacity, which costs a little of the
     photograph and buys back the words. */
  .ptile figcaption{
    background:linear-gradient(0deg,
      rgba(4,10,18,.96) 0%,
      rgba(4,10,18,.88) 38%,
      rgba(4,10,18,.62) 66%,
      rgba(4,10,18,0) 100%);
    padding-top:clamp(46px,12vw,72px);
  }
  /* On a phone the caption cannot float over the hero without colliding with
     the centred title, so it flows underneath. The panel is allowed to grow
     past one screen here: pinning it to 100svh clipped the description
     mid-sentence, and readable copy beats an exact screen height. */
  .ptile-hero{height:auto;display:flex;flex-direction:column}
  .ptile-hero .ptile-media{flex:0 0 auto;height:62svh}
  .ptile-hero figcaption{
    position:static;
    flex:0 0 auto;
    padding:22px var(--gut) 26px;
    background:rgba(6,13,24,.72);
  }
  /* clear the fixed mobile CTA so the last panel's caption is never hidden */
  .pgrid{padding-bottom:82px}
}

/* ===== light-theme corrections ============================================
   Controls that sit on their own branded or dark surface keep white type
   whatever the page theme is, and a few translucent-white fills need a dark
   tint to be visible on a light background. */
.btn-primary,.mobile-cta .btn,.pd-close,.nav .btn.btn-primary{color:#fff}
.btn-ghost{border-color:var(--line2);color:var(--txt);background:rgba(var(--linec),.04)}
.btn-ghost:hover{border-color:rgba(var(--linec),.42);color:var(--fg);background:rgba(var(--linec),.07)}

/* page ground + the faint measure grid behind everything */
body{background:
  radial-gradient(70% 50% at 78% 0%,rgba(31,110,196,.07),transparent 70%),
  radial-gradient(60% 40% at 8% 16%,rgba(31,110,196,.05),transparent 70%),
  var(--ink);
  color:var(--txt);
}
::selection{background:var(--sky);color:#fff}

/* nav: light bar and light pill */
.nav::before{background:rgba(var(--surf2),.72)}
.nav.scrolled::before{background:rgba(var(--surf2),.86)}
.nav-in::before{background:rgba(var(--surf2),.9);box-shadow:0 18px 40px -26px rgba(var(--linec),.45)}
.nav-links a:not(.btn){color:rgba(var(--fgc),.72)}
.nav-links a:not(.btn):hover,.nav-links a.active{color:var(--fg)}
.nav-toggle span{background:var(--fg)}
@media(max-width:860px){
  .nav-links{background:rgba(var(--surf2),.96)}
  .nav-links a:not(.btn){color:var(--fg);border-bottom-color:var(--line)}
}

/* light panels */
.trust-card,.trust-card.highlight{background:rgba(var(--surf2),.86)!important}
.trust{background:rgba(var(--surf2),.45)}
.exp-panel{background:linear-gradient(180deg,rgba(var(--surf2),.9),rgba(var(--surf2),.72))}
.mode-tab,.scenario,.sound-toggle,.spec,.command{background:rgba(var(--linec),.045)}
.mode-tab:hover,.mode-tab.active,.scenario:hover,.scenario.active,.sound-toggle.on,
.metric{background:rgba(var(--surf2),.75)}


/* the projects room canvas keeps its dark scene, so soften where it meets the
   light page rather than fading to the old ink */
.room-stage::after{background:linear-gradient(180deg,rgba(7,16,28,.55) 0%,transparent 16%,transparent 84%,rgba(7,16,28,.55) 100%)}

/* ===== keyboard route into the projects room ==============================
   The scene is one canvas, so pointer users get the panes and nobody else got
   anything. This list is always in the DOM for assistive tech, parked off the
   viewport, and slides in as soon as anything inside it takes focus. */
.room-index{
  position:fixed;z-index:150;
  left:-9999px;top:76px;
  margin:0;padding:14px;list-style:none;
  width:min(360px,82vw);
  border:1px solid var(--line2);border-radius:0;
  background:rgba(7,16,28,.95);
  backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  box-shadow:0 30px 70px -34px rgba(0,0,0,.9);
}
.room-index:focus-within{left:clamp(14px,3vw,40px)}
.room-index li + li{margin-top:6px}
.room-index button{
  display:block;width:100%;text-align:left;cursor:pointer;
  padding:9px 11px;border-radius:0;
  border:1px solid transparent;background:transparent;
  font-family:var(--body);
}
.room-index button:hover,.room-index button:focus-visible{
  background:rgba(62,143,214,.18);border-color:rgba(62,143,214,.6);
}
.room-index .ri-kicker{
  display:block;font-family:var(--display);font-size:10px;letter-spacing:.2em;
  text-transform:uppercase;color:#3E8FD6;
}
.room-index .ri-title{display:block;margin-top:3px;font-size:14px;color:#fff}

/* the fallback cards carry a full-card button so they are operable too */


/* ===== remaining audit fixes ==============================================*/
@media(max-width:720px){
  /* office phone / email links measured 26px tall — below the 44px guideline */
  .office a{padding:9px 0;min-height:44px;display:inline-flex;align-items:center}
  .foot-col a{padding:11px 0}
}

/* Hit areas for the two remaining sub-44px controls. The hamburger keeps its
   20x14 look but gets a padded strike zone via a pseudo-element, so nothing
   in the bar shifts. */
.nav-toggle{position:relative}
.nav-toggle::after{content:"";position:absolute;inset:-15px -13px}
.logo{padding:8px 0}

/* ===== contact form ======================================================= */
.cform{margin-top:clamp(20px,3vh,30px)}
.cf-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(12px,1.4vw,18px)}
.cf-field{margin-top:clamp(10px,1.6vh,16px);min-width:0}
.cf-row .cf-field{margin-top:0}
.cf-row + .cf-field,.cf-row + .cf-row{margin-top:clamp(10px,1.6vh,16px)}
.cf-field label{
  display:block;margin-bottom:6px;
  font-family:var(--display);font-size:11.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--soft);
}
.cf-opt{letter-spacing:0;text-transform:none;color:var(--faint)}
.cf-field input,.cf-field select,.cf-field textarea{
  width:100%;
  padding:11px 13px;
  border:1px solid var(--line2);
  border-radius:0;
  background:rgba(var(--surf2),.72);
  color:var(--fg);
  font-family:var(--body);font-size:14.5px;
  transition:border-color .25s var(--ease),background .25s var(--ease),box-shadow .25s var(--ease);
}
.cf-field textarea{resize:vertical;min-height:96px;line-height:1.55}
.cf-field input::placeholder,.cf-field textarea::placeholder{color:var(--faint)}
.cf-field input:focus,.cf-field select:focus,.cf-field textarea:focus{
  outline:none;
  border-color:var(--sky);
  background:rgb(var(--surf2));
  box-shadow:0 0 0 3px rgba(31,110,196,.16);
}
/* the browser's own validity styling stays off until we have actually judged
   the field, so nothing turns red while the visitor is still typing */
.cf-field [aria-invalid="true"]{border-color:#b3261e;background:rgba(179,38,30,.05)}
.cf-err{margin-top:6px;font-size:12.5px;color:#b3261e}
.cf-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.cf-actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:clamp(16px,2.4vh,24px)}
.cf-status{margin-top:14px;font-size:13.5px;min-height:1.2em}
.cf-status.ok{color:#146c43}
.cf-status.bad{color:#b3261e}
button[disabled]{opacity:.6;cursor:progress}

@media(max-width:640px){
  .cf-row{grid-template-columns:minmax(0,1fr);gap:0}
  .cf-row .cf-field + .cf-field{margin-top:12px}
}
/* the panel is a fixed 100svh at desktop — give the form room by trimming the
   heading rather than letting the column overflow */
@media(min-width:1101px){
  .contact h2{font-size:clamp(30px,3.4vw,46px)}
  .contact-grid > div:first-child > p{font-size:14.5px;margin-top:16px}
}

/* ===== squared, full-bleed system ========================================
   Sharp corners throughout — the panels are meant to read as engineered
   modules, not cards. The collapsed nav keeps its oval on purpose, and true
   circles (hotspots, status dots, glow blobs) keep 50%. */
.nav-in::before{border-radius:999px}
.nav .btn{border-radius:999px}
.hotspot,.pin,.dot,.mode-list span::before{border-radius:50%}

/* Sections that were still capped by the old 1320px container now share the
   same edge as every full-bleed panel, so nothing reads as inset. */
.trust > .wrap,
#process > .wrap,
#about > .wrap,
.footer > .wrap{
  max-width:none;
  padding-left:clamp(14px,1.6vw,28px);
  padding-right:clamp(14px,1.6vw,28px);
}

/* the project page is light now — its close control has to stop being
   white-on-white */


/* empty gallery frames on a light ground */


/* the close control was the last round element on an otherwise squared page */


/* ===== support / faq =====================================================
   Same squared, full-bleed system as the rest of the page. Anything the client
   still has to supply is marked with [data-ph] or .ph-note so an unfinished
   figure can never quietly ship as if it were real. */
.support > .wrap,
.faq > .wrap{
  max-width:none;
  padding-left:clamp(14px,1.6vw,28px);
  padding-right:clamp(14px,1.6vw,28px);
}

/* placeholder markers */
[data-ph]{color:var(--faint)!important;outline:1px dashed rgba(var(--linec),.32);outline-offset:3px}
.ph-note{
  display:inline-block;margin-top:6px;
  font-family:var(--display);font-size:11px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--sky);border-bottom:1px dashed rgba(var(--linec),.4);
}


/* ---- support ---- */
.sup-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:var(--line);border:1px solid var(--line)}
.sup-card{background:rgba(var(--surf2),.72);padding:clamp(22px,2.6vw,34px)}
.sup-figure{
  display:block;font-family:var(--display);font-weight:300;
  font-size:clamp(34px,4vw,54px);line-height:1;color:var(--sky);
}
.sup-card .sup-card-label{margin-top:14px;font-size:19px;font-weight:500;color:var(--fg)}
.sup-card p{margin-top:10px;font-size:14px;line-height:1.6;color:var(--soft)}

.sup-plans{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(12px,1.4vw,20px);margin-top:clamp(18px,2.4vw,30px)}
.sup-plan{border:1px solid var(--line);background:rgba(var(--surf2),.62);padding:clamp(22px,2.4vw,32px)}
.sup-plan.is-featured{border-color:rgba(31,110,196,.42);background:rgba(31,110,196,.05)}
.sup-plan h2{font-family:var(--display);font-size:22px;font-weight:500;color:var(--fg)}
.sup-plan > p{margin-top:6px;font-size:13px;color:var(--soft)}
.sup-plan ul{margin-top:18px;padding:0;list-style:none}
.sup-plan li{position:relative;padding-left:20px;margin-top:10px;font-size:14px;line-height:1.55;color:var(--soft)}
.sup-plan li::before{content:"";position:absolute;left:0;top:.55em;width:6px;height:6px;background:var(--sky)}
.sup-plan li[data-ph]{outline-offset:2px}

/* ---- faq ---- */
.faq-list{border-top:1px solid var(--line);max-width:1100px}
.faq-item{border-bottom:1px solid var(--line)}
.faq-item summary{
  cursor:pointer;list-style:none;
  display:flex;align-items:flex-start;justify-content:space-between;gap:20px;
  padding:clamp(18px,2.2vw,26px) 0;
  font-family:var(--display);font-size:clamp(16px,1.5vw,21px);font-weight:400;
  color:var(--fg);transition:color .25s var(--ease);
}
.faq-item summary::-webkit-details-marker{display:none}
.faq-item summary::after{
  content:"";flex:0 0 auto;width:13px;height:13px;margin-top:.35em;
  border-right:1.5px solid var(--sky);border-bottom:1.5px solid var(--sky);
  transform:rotate(45deg);transition:transform .3s var(--ease);
}
.faq-item[open] summary::after{transform:rotate(-135deg)}
.faq-item summary:hover{color:var(--sky)}
.faq-a{padding:0 0 clamp(20px,2.4vw,28px);max-width:78ch}
.faq-a p{font-size:clamp(14.5px,1.15vw,16.5px);line-height:1.72;color:var(--soft)}
.faq-a a{color:var(--sky);border-bottom:1px solid rgba(31,110,196,.4)}

@media(max-width:1000px){
  .sup-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .sup-plans{grid-template-columns:minmax(0,1fr)}
}
@media(max-width:640px){
  .sup-grid{grid-template-columns:minmax(0,1fr)}
}


/* ===== our team =========================================================== */
.team > .wrap{
  max-width:none;
  padding-left:clamp(14px,1.6vw,28px);
  padding-right:clamp(14px,1.6vw,28px);
}
.tm-grid{
  display:grid;
  /* auto-fit rather than a hard 4: the team was four, is five, and will change
     again. A fixed column count leaves an empty cell the moment the head count
     stops dividing into it, and an empty cell reads as a missing person. */
  grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:clamp(12px,1.4vw,22px);
  list-style:none;margin:0;padding:0;
}
/* Head: copy on one side, "read more" on the other. It wraps to stacked
   below 760px rather than letting the button crush the headline. */
.tm-head{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:clamp(20px,4vw,60px);flex-wrap:wrap;
  margin-bottom:clamp(26px,4vh,48px);
}
.tm-head-copy{max-width:72ch}
/* it borrows .section-head for the type scale; the shared bottom margin would
   double up with .tm-head's, so drop it here */
.tm-head-copy.section-head{margin-bottom:0}
.tm-num{font-style:normal;color:var(--faint);margin-inline-end:.75em}
.tm-more{
  flex:0 0 auto;
  display:inline-flex;align-items:center;
  font-family:var(--display);font-size:13px;letter-spacing:.02em;
  color:var(--fg);border:1px solid var(--line2);
  padding:11px 20px;border-radius:999px;   /* the one pill on a squared site —
                                              it is what the reference shows */
  transition:border-color .3s var(--ease),background .3s var(--ease);
}
.tm-more:hover{border-color:var(--sky);background:rgba(62,143,214,.06)}

/* Cards carry no chrome of their own: the panel IS the card, with the name and
   role sitting on the page ground beneath it. */
.tm{border:0;background:none;padding:0}
/* height:auto matters. The <img> carries width="800" height="1000", which the
   UA turns into presentational hints; width:100% overrides the width but
   nothing overrode the height, so aspect-ratio was ignored, the portrait
   rendered 1000px tall and stretched every card in the row to match. */
.tm-slot,.tm img{display:block;width:100%;height:auto;aspect-ratio:4 / 5;object-fit:cover}
.tm-slot{
  display:grid;place-items:center;
  background:rgba(var(--linec),.07);
  border:1px solid var(--line);
}
/* Placeholder is the person's initials: it reads as a deliberate avatar
   rather than an empty box, and it stays usable for anyone who never supplies
   a portrait — two people in the org chart have none. */
.tm-slot i{
  font-style:normal;font-family:var(--display);font-weight:300;
  font-size:clamp(30px,3.4vw,44px);letter-spacing:.06em;
  color:rgba(var(--linec),.34);
}
/* the dashed "still to supply" outline is too loud across ten cards — the
   grey panel already reads as pending, and [data-ph] still marks it in source */
.tm-slot[data-ph]{outline:none}
.tm h3{
  margin:clamp(14px,1.6vw,18px) 0 0;
  font-family:var(--display);font-weight:500;font-size:17px;color:var(--fg);
}
.tm-role{
  margin:6px 0 0;
  font-family:var(--display);font-size:12.5px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--faint);
}
.tm-note{margin-top:clamp(22px,3vw,34px);border-top:1px solid var(--line);padding-top:18px}
.tm-note p{font-size:14.5px;color:var(--soft);max-width:70ch}

@media(max-width:760px){.tm-head{flex-direction:column}}
/* TWO ACROSS, NOT ONE. A single column of five portrait cards ran to 3.7
   screens on a phone for five people — the same mistake the wall of voices
   made. A face and a name do not need the full width to be read, and two to a
   row halves the section without shrinking anything that matters. */
@media(max-width:560px){
  .tm-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(10px,3vw,16px)}
  .tm h3{font-size:15px}
  .tm-role{font-size:12px}
}

/* The media panel sits first in the DOM (it used to be the absolute backdrop),
   so the flex order is set explicitly rather than reshuffling the markup. */

/* the heading was taking more of the panel than the photograph — trim it back
   so the image is the dominant element again */

/* On a phone the chrome no longer overlays the photograph — heading, commands
   and a two-column strip all stack — so the section genuinely needs more than
   one screen. Forcing 100svh only squeezed the image to a letterbox, so the
   stage sizes to its content here instead and the photo keeps a real height. */


/* ===== hero: full-bleed cinematic opener ===================================
   100vw of footage, edge to edge, with the editorial block sitting over it and
   the regional meta along the foot. No panel, no inset, no gutter: the image
   IS the first screen, and the type is set on it like a title card. The scrim
   is a single soft bottom gradient — just enough to hold white type, not
   enough to grey the footage out. */
.hero{
  height:100svh;
  background:#000;
  display:flex;flex-direction:column;
  padding:0 0 clamp(20px,3vh,34px);
  overflow:hidden;
}

/* headline block */
.hero-main{
  order:1;flex:1 1 auto;position:relative;z-index:1;
  display:flex;align-items:center;justify-content:center;
  padding:calc(48px + clamp(20px,4vh,54px)) 0 clamp(10px,1.8vh,24px);
}
.hero-main .wrap{max-width:none;padding:0 clamp(20px,4vw,64px)}
.hero-copy{
  max-width:min(980px,92vw);
  margin:0 auto;
  text-align:center;
}
.hero .eyebrow{max-width:max-content;margin:0 auto;color:#fff;opacity:.92}
.hero h1{
  max-width:none;margin:3px auto 0;
  color:#fff;
  font-size:clamp(40px,6.4vw,80px);
  line-height:var(--leading-display);
  letter-spacing:var(--tracking-h1);
  font-weight:600;
}
.hero h1 b{font-weight:600;color:#fff}
.hero h1 .line b{color:#fff}
.hero-sub{
  max-width:52ch;margin:clamp(14px,1.8vh,22px) auto 0;
  color:rgba(255,255,255,.86);text-align:center;
  font-size:clamp(16px,1.25vw,19px);line-height:1.42;
}
.hero-cta{justify-content:center;margin-top:clamp(20px,2.6vh,30px);gap:16px}
/* on footage the secondary button is glass, not Concrete — a light grey pill
   on a photograph reads as a hole punched in the image */
.hero-cta .btn-ghost{background:rgba(255,255,255,.18);color:#fff;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
.hero-cta .btn-ghost:hover{background:rgba(255,255,255,.28);color:#fff}

/* the footage runs the full bleed, behind everything */
.hero-bg{
  position:absolute;inset:0;
  z-index:0;
  flex:none;min-height:0;border:0;
}
.hero-bg::after{content:none}     /* the old blue pools over the footage */
.hero-scrim{
  background:linear-gradient(180deg,
    rgba(0,0,0,.34) 0%,rgba(0,0,0,.12) 26%,rgba(0,0,0,.30) 62%,rgba(0,0,0,.62) 100%);
}

/* bottom meta */
.hero > .wrap{
  order:3;flex:0 0 auto;position:relative;z-index:1;
  width:auto;max-width:none;margin:0;padding:0 clamp(20px,4vw,64px);
}
.hero-regions{
  padding:clamp(10px,1.5vh,17px) 0 0;
  gap:clamp(20px,5vw,72px);
}
.hero-regions::before{display:none}
.region-node strong{color:#fff;font-family:var(--body);font-weight:600;font-size:14px;letter-spacing:-.016em}
.region-node small{color:rgba(255,255,255,.66);font-family:var(--body);font-size:var(--text-caption);letter-spacing:var(--tracking-caption);font-weight:400}
.region-node .pin{background:#fff;width:6px;height:6px;box-shadow:none;animation:none;margin-bottom:10px}
.hero-foot{
  border-top:1px solid rgba(255,255,255,.18);
  margin-top:clamp(10px,1.6vh,18px);
  padding:clamp(12px,1.6vh,17px) 0 0;
  color:rgba(255,255,255,.62);
  font-family:var(--body);font-size:var(--text-caption);font-weight:400;
  letter-spacing:var(--tracking-caption);text-transform:uppercase;
}
.hero-foot .dot{background:#fff;box-shadow:none;width:5px;height:5px}
.scroll-line::after{background:rgba(255,255,255,.5)}

@media(max-width:720px){
  .hero .eyebrow{justify-content:center;text-align:center}
  /* clear the fixed Start Your Project bar, or the foot meta sits under it */
  .hero{padding-bottom:78px}
  .hero-main{padding-top:calc(48px + 22px)}
  /* the phone headline runs 32-46px, which is the heading step's territory.
     --tracking-h1 is interpolated against the desktop clamp and reads too
     loose down here, so the step's own -0.015em applies instead. */
  .hero h1{font-size:clamp(32px,8.6vw,46px);letter-spacing:-.015em}
  .hero-sub{max-width:40ch;font-size:16px}
  .hero-cta{display:grid;grid-template-columns:1fr;max-width:320px;margin-left:auto;margin-right:auto;gap:12px}
  .hero-bg{min-height:26svh}
  .hero-foot{flex-direction:row;flex-wrap:wrap;gap:8px}
  .hero-foot span:nth-child(2){display:none}}

/* ===== nav: one bar, always ===============================================
   The bar is translucent white with a blur at every scroll position, on every
   page, over footage or over the Fog ground. That is the whole behaviour.

   body.dark-top is left in the markup — it used to flip the bar to white type
   over a dark first screen. It flips nothing now: a blurred white bar over the
   hero video is the reference treatment, and it means the bar cannot be
   unreadable on a page that happens to open light. */
.nav{height:auto;border-bottom:1px solid var(--line)}
.nav-in{height:48px;padding-left:clamp(16px,2.2vw,40px);padding-right:clamp(16px,2.2vw,40px);max-width:none}
.nav.scrolled .nav-in{height:48px;transform:none}

/* ===== language gate + storage notice ======================================
   Both are markup-hidden and revealed by JS, so no-JS visitors and crawlers
   see the page rather than an overlay they cannot dismiss. */

/* ---- first-visit language gate ---- */
.lgate{
  position:fixed;inset:0;z-index:9000;
  display:grid;place-items:center;
  background:var(--ink);
  opacity:0;transition:opacity .45s var(--ease);
}
.lgate.on{opacity:1}
.lgate[hidden]{display:none}
.lgate-panel{
  text-align:center;padding:clamp(24px,5vw,48px);
  max-width:min(560px,90vw);
}
.lgate-logo{width:clamp(112px,14vw,158px);height:auto;color:var(--fg);display:block;margin:0 auto}
.lgate-kicker{
  margin-top:clamp(22px,3vh,32px);
  font-family:var(--display);font-size:12.5px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--faint);
}
.lgate-kicker + .lgate-kicker{margin-top:6px;text-transform:none;letter-spacing:.04em;font-size:14px}
.lgate-actions{
  display:flex;gap:clamp(10px,1.6vw,16px);
  justify-content:center;margin-top:clamp(26px,4vh,38px);flex-wrap:wrap;
}
.lgate-pick{
  flex:1 1 190px;max-width:240px;
  font-family:var(--display);font-size:16px;color:var(--fg);
  background:none;border:1px solid var(--line2);padding:17px 26px;cursor:pointer;
  transition:border-color .28s var(--ease),background .28s var(--ease),color .28s var(--ease);
}
.lgate-pick:hover,.lgate-pick:focus-visible{
  border-color:var(--sky);background:rgba(62,143,214,.07);color:var(--sky);
}

/* ---- storage notice ---- */
.cbar{
  position:fixed;z-index:8900;
  left:clamp(12px,2vw,24px);right:clamp(12px,2vw,24px);bottom:clamp(12px,2vw,24px);
  display:flex;align-items:center;gap:clamp(14px,2.4vw,32px);flex-wrap:wrap;
  padding:clamp(16px,2vw,22px) clamp(18px,2.4vw,26px);
  background:rgba(var(--surf),.97);border:1px solid var(--line2);
  box-shadow:0 26px 60px -30px rgba(0,0,0,.45);
  transform:translateY(14px);opacity:0;
  transition:opacity .4s var(--ease),transform .4s var(--ease);
}
.cbar.on{transform:none;opacity:1}
.cbar[hidden]{display:none}
.cbar-text{flex:1 1 420px;font-size:13.5px;line-height:1.6;color:var(--soft);margin:0}
.cbar-text a{color:var(--sky);text-decoration:underline;text-underline-offset:2px}
.cbar-actions{display:flex;gap:10px;flex:0 0 auto;flex-wrap:wrap}
.cbar-btn{
  font-family:var(--display);font-size:13.5px;cursor:pointer;
  padding:12px 22px;border:1px solid var(--line2);background:none;color:var(--fg);
  transition:border-color .28s var(--ease),background .28s var(--ease);
}
.cbar-no:hover{border-color:var(--line2);background:rgba(var(--linec),.05)}
.cbar-yes{background:var(--sky);border-color:var(--sky);color:#fff}
.cbar-yes:hover{filter:brightness(1.06)}

/* the footer control that reopens it */
.foot-cookies{
  font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer;
  text-decoration:underline;text-underline-offset:2px;
}
.foot-cookies:hover{color:var(--sky)}

@media(max-width:620px){
  .cbar{flex-direction:column;align-items:stretch}
  .cbar-actions .cbar-btn{flex:1 1 auto}
}
@media(prefers-reduced-motion:reduce){
  .lgate,.cbar{transition:none}
}

/* ===== theme toggle ========================================================
   Sits in the footer beside the language switch, because that is where the
   other "how do I want to read this" controls live. Reflects state with
   aria-pressed, so a screen reader hears it as a switch rather than a link. */
/* the gate's appearance picker — same button shape as the language picks,
   with a filled/hollow disc so the two options read at a glance */
.lgate-themes{margin-bottom:clamp(18px,3vh,30px)}
.lgate-theme{display:inline-flex;align-items:center;justify-content:center;gap:9px}
.lgate-theme i{
  width:13px;height:13px;border-radius:50%;flex:0 0 auto;
  border:1px solid currentColor;
  background:linear-gradient(90deg,currentColor 0 50%,transparent 50% 100%);
}
.lgate-theme[data-theme-pick="dark"] i{background:currentColor}
.lgate-theme.is-on{border-color:var(--sky);color:var(--sky)}
.lgate-kicker-lang{margin-top:clamp(14px,2.4vh,24px)}

.theme-toggle{
  display:inline-flex;align-items:center;gap:8px;
  background:none;border:1px solid var(--line2);border-radius:999px;
  padding:7px 14px 7px 11px;cursor:pointer;
  font-family:var(--display);font-size:12px;letter-spacing:.06em;
  color:var(--soft);
  transition:border-color .3s var(--ease),color .3s var(--ease),background .3s var(--ease);
}
.theme-toggle:hover{border-color:var(--sky);color:var(--fg);background:rgba(62,143,214,.08)}
.theme-toggle i{
  width:13px;height:13px;border-radius:50%;flex:0 0 auto;
  border:1px solid currentColor;
  /* a half-filled disc: the universal light/dark mark, drawn rather than
     shipped as an icon font or an SVG file */
  background:linear-gradient(90deg,currentColor 0 50%,transparent 50% 100%);
}

/* ===== page loader =========================================================
   Covers the first paint so the page assembles behind it rather than in front
   of the visitor. Three rules it has to obey:

   1. It must NEVER be able to strand someone. site.js removes it, but the
      keyframe below hides it at 4s regardless — if site.js 404s, is blocked,
      or throws before it runs, the page still becomes usable.
   2. It must not wait on the hero video. site.js releases it on
      DOMContentLoaded, not load, or a preloading clip would hold it for
      seconds. A 600ms floor stops it flashing on a warm cache.
   3. It is aria-hidden and holds no text, so assistive tech skips it and reads
      the real content underneath, which is already parsed.                  */
.pl{
  position:fixed;inset:0;z-index:9999;
  display:grid;place-items:center;
  /* --ink IS the light page ground (#f5f8fc), not a dark one — the hero only
     looks dark because of the video behind it. Matching the ground means the
     loader dissolves into the page instead of flashing dark-to-light, and it
     is the same on all five pages rather than only suiting the two that open
     on footage. */
  background:var(--ink);
  animation:plFallback 0s linear 4s forwards;
}
/* THE MARK DRAWS ITSELF. The loader used to be the logo with a one-pixel bar
   under it — a progress meter borrowed from any site, sitting beneath a logo it
   had nothing to do with. Now the progress IS the logo: a pale ghost of the
   mark with the real one wiped across it, left to right. Nothing on the screen
   that is not the brand, and the thing you watch fill is the thing you came for.

   Two copies of the same <use>, so it costs no extra artwork and inherits every
   future change to the logo automatically. */
.pl-in{display:flex;flex-direction:column;align-items:center;gap:26px}
/* ONE SOURCE OF TRUTH FOR THE MARK'S WIDTH, because the board is sized off it.
   The mark has a 132px floor, so on a phone it does not shrink — and when the
   wire field was sized to the viewport instead, the package closed to 133px
   around a 132px logo: one pixel of clearance, wordmark touching the buses.
   Sizing the field off the mark keeps the package at twice the mark's width at
   every viewport, so the clearance is always half a mark. */
.pl{--mark-w:clamp(132px,18vw,208px)}
.pl-mark{position:relative;z-index:0;width:var(--mark-w)}  /* z-index:0 makes this a stacking context so the sheen at z:-1 stays
     behind the logo and above the wires, not behind both */
.pl-ghost,.pl-fill{display:block;width:100%;height:auto}
.pl-ghost{color:var(--ink4);opacity:.55}
.pl-fill{
  position:absolute;inset:0;color:var(--fg);
  /* closed from the right, opening left to right */
  clip-path:inset(0 100% 0 0);
  /* .9s, and the number is set by the loader's dismissal floor rather than by
     taste. site.js holds the sheet a minimum of 900ms and then lets it go, so
     anything longer and the warm-cache visitor — which is most of them, on
     every page after the first — never sees the mark finish. At 1.45s it drew
     to 46% and jumped the rest. The wipe now completes exactly as the floor
     lifts, and .pl.is-done's catch-up rule goes back to being what it was meant
     to be: the safety net for a slow load, not the normal path.

     The curve is an in-and-out, not the site's usual ease-out, and that is
     deliberate for this one element. Under the ease-out the mark was 62% drawn
     at 225ms and 92% at 450ms — the drawing was finished before you had
     registered it starting, and the rest of the hold was a static logo. This
     one waits a beat (5% at 225ms), sweeps through the wordmark, and settles.
     Same 900ms, but the whole 900ms is the draw. */
  animation:plWipe .9s cubic-bezier(.76,0,.24,1) forwards;
}
/* Arabic reads the other way, and so does the wipe. */
[dir="rtl"] .pl-fill{clip-path:inset(0 0 0 100%);animation-name:plWipeRtl}
@keyframes plWipe{to{clip-path:inset(0 0 0 0)}}
@keyframes plWipeRtl{to{clip-path:inset(0 0 0 0)}}

/* THE LIGHT THAT DOES THE DRAWING. A clip on its own is a shutter: the mark is
   uncovered, and the leading edge is a hard vertical cut you can see moving.
   This is a soft blue band riding the same curve for the same duration, placed
   so its brightest point sits exactly on that cut for the whole travel. The
   mark now reads as being lit into existence rather than mechanically wiped.

   IT SITS BEHIND THE LETTERS, not over them. Painted on top at any strength
   that was actually visible it stopped being light and became a blue smudge
   parked on the 'a' — you read the blob, not the mark. z-index:-1 drops it
   behind both SVGs but still inside .pl's stacking context, so it lifts off
   the fog ground and glows out from behind the letterforms instead of veiling
   them. That in turn is why it can be wide and soft: 38% of the mark across
   with a 10px blur reads as a light source with a position, where the tight,
   opaque version read as a shape.

   The offsets are arithmetic, not eyeballed. The band's peak sits at 88% of
   its own width, so to hold that peak against an edge at p·W it must start at
   (p − .3344)·W — which is −88% of its own width at p=0 and +175% at p=1.
   Sharing one easing curve locks them together at every point in between, not
   just at the ends; measured mid-sweep the two are 0.1px apart. */
.pl-mark::after{
  content:"";position:absolute;top:-20%;bottom:-20%;left:0;width:38%;
  z-index:-1;pointer-events:none;
  background:linear-gradient(90deg,
    rgba(0,113,227,0) 0%,rgba(0,113,227,.14) 62%,
    rgba(0,113,227,.42) 88%,rgba(0,113,227,0) 100%);
  filter:blur(10px);
  animation:plSheen .9s cubic-bezier(.76,0,.24,1) forwards;
}
[dir="rtl"] .pl-mark::after{
  left:auto;right:0;
  background:linear-gradient(270deg,
    rgba(0,113,227,0) 0%,rgba(0,113,227,.14) 62%,
    rgba(0,113,227,.42) 88%,rgba(0,113,227,0) 100%);
  animation-name:plSheenRtl;
}
@keyframes plSheen{
  0%{transform:translate3d(-88%,0,0);opacity:0}
  14%{opacity:1}
  86%{opacity:1}
  100%{transform:translate3d(175%,0,0);opacity:0}
}
@keyframes plSheenRtl{
  0%{transform:translate3d(88%,0,0);opacity:0}
  14%{opacity:1}
  86%{opacity:1}
  100%{transform:translate3d(-175%,0,0);opacity:0}
}

/* THE BOARD BEHIND THE MARK. A smart-building company's loading screen should
   look like one. This is a real trace network rather than a decorative
   squiggle: every run is horizontal, vertical or exactly 45 degrees and every
   elbow is mitred. Two vertical buses stand either side of the wordmark and
   mitred bridges join their ends over and under it, so the buses close into a
   package around the mark — the logo sits IN the network like a die in its
   carrier, not in front of a wallpaper. Every branch taps a bus at a junction
   via, so you can see where each wire connects instead of watching lines
   float. The geometry is generated rather than eyeballed, which is what keeps
   the diagonals honest and the pads on the canvas.

   EVERY TRACE CARRIES pathLength="100". That is the one thing making a single
   stroke-dasharray rule work across twenty paths of wildly different lengths:
   it renormalises each path so 100 units means "all of it", and a 520-unit
   trace and a 90-unit stub draw in the same time off the same declaration.
   Without it they would draw at wildly different speeds and the network would
   look like it was buffering.

   Traces fire 16ms apart in build order — buses, bridges, branches, caps, then
   the long outer runs — so the board powers up from the mark outward. Each pad
   is keyed to ITS OWN trace finishing rather than to its index in the pad
   list, or it would seat before the wire reaching it had been drawn. The last
   one lands at 856ms, inside the 900ms hold, so nothing is still moving when
   the sheet lifts. */
.pl{--wire:#b3bbc7}
:root[data-theme="dark"] .pl{--wire:rgba(255,255,255,.26)}
/* The package spans 428 of the field's 1200 units, so a field 5.6x the mark
   puts the package at exactly twice the mark's width — half a mark of clear
   air on each side, at any viewport. On a phone the field is then wider than
   the screen and the outer traces run off the edges, which is what a board
   does; it is the package crowding the wordmark that looked broken. */
.pl-wires{
  position:absolute;left:50%;top:50%;
  width:calc(var(--mark-w) * 5.6);aspect-ratio:1200/700;
  /* the global `img,svg,canvas{max-width:100%}` reset silently capped this at
     the viewport width — the calc resolved to 739px and the box still measured
     375px, which is exactly the crowding this rule exists to fix. This layer is
     deliberately wider than the screen on a phone. */
  max-width:none;
  transform:translate(-50%,-50%);
  pointer-events:none;
}
.pl-w path{
  fill:none;stroke:var(--wire);stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;
  /* the field is fitted to the viewport, so an unscaled stroke is the only way
     a hairline stays a hairline on a 27-inch display and on a phone */
  vector-effect:non-scaling-stroke;
  stroke-dasharray:100;stroke-dashoffset:100;
  animation:plTrace 380ms cubic-bezier(.32,.72,.24,1) forwards;
}
.pl-w path.hot{stroke:#0071e3}
@keyframes plTrace{to{stroke-dashoffset:0}}

/* pads seat with a little overshoot, so the trace arriving reads as a contact
   being made rather than a dot fading up */
.pl-p circle{
  fill:var(--wire);opacity:0;
  transform-box:fill-box;transform-origin:50% 50%;
  animation:plPad 240ms cubic-bezier(.2,.9,.3,1.35) forwards;
}
.pl-p circle.hot{fill:#0071e3}
@keyframes plPad{from{opacity:0;transform:scale(.2)}to{opacity:.9;transform:scale(1)}}
.pl-v circle{fill:var(--wire);opacity:0;animation:plVia 240ms var(--ease) forwards}
@keyframes plVia{to{opacity:.75}}

.pl-w path:nth-child(1){animation-delay:0ms}
.pl-w path:nth-child(2){animation-delay:16ms}
.pl-w path:nth-child(3){animation-delay:32ms}
.pl-w path:nth-child(4){animation-delay:48ms}
.pl-w path:nth-child(5){animation-delay:64ms}
.pl-w path:nth-child(6){animation-delay:80ms}
.pl-w path:nth-child(7){animation-delay:96ms}
.pl-w path:nth-child(8){animation-delay:112ms}
.pl-w path:nth-child(9){animation-delay:128ms}
.pl-w path:nth-child(10){animation-delay:144ms}
.pl-w path:nth-child(11){animation-delay:160ms}
.pl-w path:nth-child(12){animation-delay:176ms}
.pl-w path:nth-child(13){animation-delay:192ms}
.pl-w path:nth-child(14){animation-delay:208ms}
.pl-w path:nth-child(15){animation-delay:224ms}
.pl-w path:nth-child(16){animation-delay:240ms}
.pl-w path:nth-child(17){animation-delay:256ms}
.pl-w path:nth-child(18){animation-delay:272ms}
.pl-w path:nth-child(19){animation-delay:288ms}
.pl-w path:nth-child(20){animation-delay:304ms}
.pl-w path:nth-child(21){animation-delay:320ms}
.pl-w path:nth-child(22){animation-delay:336ms}

.pl-p circle:nth-child(1){animation-delay:364ms}
.pl-p circle:nth-child(2){animation-delay:380ms}
.pl-p circle:nth-child(3){animation-delay:396ms}
.pl-p circle:nth-child(4){animation-delay:412ms}
.pl-p circle:nth-child(5){animation-delay:428ms}
.pl-p circle:nth-child(6){animation-delay:444ms}
.pl-p circle:nth-child(7){animation-delay:460ms}
.pl-p circle:nth-child(8){animation-delay:476ms}
.pl-p circle:nth-child(9){animation-delay:492ms}
.pl-p circle:nth-child(10){animation-delay:508ms}
.pl-p circle:nth-child(11){animation-delay:524ms}
.pl-p circle:nth-child(12){animation-delay:540ms}
.pl-p circle:nth-child(13){animation-delay:556ms}
.pl-p circle:nth-child(14){animation-delay:572ms}
.pl-p circle:nth-child(15){animation-delay:588ms}
.pl-p circle:nth-child(16){animation-delay:604ms}
.pl-p circle:nth-child(17){animation-delay:620ms}
.pl-p circle:nth-child(18){animation-delay:620ms}
.pl-p circle:nth-child(19){animation-delay:636ms}
.pl-p circle:nth-child(20){animation-delay:636ms}

.pl-v circle:nth-child(1){animation-delay:120ms}
.pl-v circle:nth-child(2){animation-delay:129ms}
.pl-v circle:nth-child(3){animation-delay:138ms}
.pl-v circle:nth-child(4){animation-delay:147ms}
.pl-v circle:nth-child(5){animation-delay:156ms}
.pl-v circle:nth-child(6){animation-delay:165ms}
.pl-v circle:nth-child(7){animation-delay:174ms}
.pl-v circle:nth-child(8){animation-delay:183ms}
.pl-v circle:nth-child(9){animation-delay:192ms}
.pl-v circle:nth-child(10){animation-delay:201ms}
.pl-v circle:nth-child(11){animation-delay:210ms}
.pl-v circle:nth-child(12){animation-delay:219ms}
.pl-v circle:nth-child(13){animation-delay:228ms}
.pl-v circle:nth-child(14){animation-delay:237ms}
.pl-v circle:nth-child(15){animation-delay:246ms}
.pl-v circle:nth-child(16){animation-delay:255ms}
.pl-v circle:nth-child(17){animation-delay:264ms}
.pl-v circle:nth-child(18){animation-delay:273ms}
.pl-v circle:nth-child(19){animation-delay:282ms}
.pl-v circle:nth-child(20){animation-delay:291ms}
.pl-v circle:nth-child(21){animation-delay:300ms}
.pl-v circle:nth-child(22){animation-delay:309ms}
.pl-v circle:nth-child(23){animation-delay:318ms}
.pl-v circle:nth-child(24){animation-delay:327ms}
.pl-v circle:nth-child(25){animation-delay:336ms}
.pl-v circle:nth-child(26){animation-delay:345ms}

/* A breath under the mark: the ground lightens as the wipe travels, so the
   screen is not perfectly still while it waits. */
.pl::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(46% 38% at 50% 50%,rgba(0,113,227,.10),transparent 70%);
  opacity:0;animation:plGlow .9s var(--ease) forwards;
}
@keyframes plGlow{0%{opacity:0}55%{opacity:1}100%{opacity:.35}}

/* whatever is left of the wipe completes, then the whole sheet lifts */
.pl.is-done .pl-fill{clip-path:inset(0 0 0 0);transition:clip-path .26s var(--ease)}
.pl.is-done{
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .52s var(--ease) .12s,visibility .52s var(--ease) .12s;
}
@keyframes plFallback{to{opacity:0;visibility:hidden;pointer-events:none}}

@media(prefers-reduced-motion:reduce){
  .pl.is-done{transition:none}
  /* The wipe is the only thing making the mark visible, so with motion off it
     has to be shown complete rather than left half-drawn — the same reason the
     old bar was pinned at 78% here. */
  .pl-fill{animation:none;clip-path:inset(0 0 0 0)}
  .pl-ghost{opacity:0}
  /* nothing to trail when nothing is drawing */
  .pl-mark::after{display:none}
  /* the board is simply shown routed */
  .pl-w path{animation:none;stroke-dashoffset:0}
  .pl-p circle,.pl-v circle{animation:none;opacity:.75;transform:none}
  .pl::after{animation:none;opacity:.35}
}

/* ===== process: stepped arc stage ===========================================
   A tall track pins the stage; scroll advances one step at a time. The arc,
   the numbered markers, the copy and the visual are all driven by one index.
   The SVG uses preserveAspectRatio:none so the viewBox maps straight onto the
   box — that is what lets the JS place markers exactly on the curve. */
.arc-track{position:relative}
.arc-stage{
  position:sticky;top:0;height:100svh;overflow:hidden;
  display:grid;
  grid-template-columns:minmax(0,.82fr) minmax(0,1.5fr) minmax(0,1.02fr);
  align-items:center;gap:clamp(18px,2.6vw,52px);
  padding-left:clamp(14px,1.6vw,28px);
}

/* the arc and its markers */
.arc-curve{position:relative;height:100%;pointer-events:none}
.arc-curve svg{position:absolute;inset:0;width:100%;height:100%}
.arc-marks{position:absolute;inset:0}
.arc-mark{
  position:absolute;left:0;top:0;
  display:flex;align-items:center;gap:14px;
  transition:transform .85s var(--ease),opacity .55s var(--ease);
  opacity:.3;will-change:transform;
}
.arc-mark.is-active{opacity:1}
.arc-mark i{
  width:9px;height:9px;border-radius:50%;flex:0 0 auto;
  background:var(--line2);transition:background .5s var(--ease),box-shadow .5s var(--ease);
}
.arc-mark.is-active i{background:var(--sky);box-shadow:0 0 0 6px rgba(62,143,214,.14)}
.arc-mark span{
  font-family:var(--display);font-size:46px;line-height:1;letter-spacing:-.03em;
  color:var(--soft);transform:scale(.6);transform-origin:left center;
  transition:transform .6s var(--ease),color .5s var(--ease);
}
.arc-mark.is-active span{transform:scale(1);color:var(--fg)}

/* the copy */
.arc-copy{position:relative;max-width:640px}
.arc-count{
  display:inline-flex;align-items:baseline;gap:5px;
  font-family:var(--display);font-size:12px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--sky);
}
.arc-count i{font-style:normal;color:var(--faint)}
.arc-slides{position:relative;margin-top:clamp(16px,2.4vh,26px);min-height:clamp(300px,44vh,392px)}
.arc-slide{
  position:absolute;inset:0 0 auto 0;
  opacity:0;transform:translateY(20px);pointer-events:none;
  transition:opacity .5s var(--ease),transform .62s var(--ease);
}
.arc-slide.is-active{opacity:1;transform:none;pointer-events:auto}
.arc-slide h3{
  font-size:clamp(30px,4.15vw,58px);font-weight:300;line-height:1.03;
  letter-spacing:-.035em;color:var(--fg);
}
.arc-slide p{
  margin-top:clamp(14px,2vh,22px);max-width:44ch;color:var(--soft);
  font-size:clamp(15px,1.2vw,18px);line-height:1.62;
}
/* squared, per the site's edge rule — the reference uses pills */
.arc-chips{display:flex;flex-wrap:wrap;gap:10px;margin-top:clamp(20px,3vh,34px);list-style:none}
.arc-chips li{
  border:1px solid var(--line2);background:rgba(var(--surf2),.7);
  padding:11px 20px;font-size:14px;color:var(--txt);white-space:nowrap;
}

/* the visual, flush to the right edge */
.arc-visual{
  position:relative;height:min(76vh,620px);overflow:hidden;
  border:1px solid var(--line);border-right:0;background:#07101c;
}
.arc-visual img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transform:scale(1.06);
  transition:opacity .8s var(--ease),transform 1.3s var(--ease);
}
.arc-visual img.is-active{opacity:1;transform:scale(1)}

/* Static form: narrow screens and reduced motion both get all four steps
   stacked, with no scroll-jacking and no pinned stage. */
@media(max-width:860px){
  .arc-track{height:auto!important}
  .arc-stage{
    position:static;height:auto;grid-template-columns:minmax(0,1fr);
    padding:0 clamp(14px,1.6vw,28px);gap:0;
  }
  .arc-curve,.arc-count{display:none}
  .arc-slides{min-height:0;margin-top:0}
  .arc-slide{position:static;opacity:1;transform:none;pointer-events:auto;
            padding:26px 0;border-top:1px solid var(--line)}
  .arc-slide h3{font-size:clamp(24px,6.4vw,33px)}
  .arc-slide p{max-width:52ch}
  .arc-visual{height:42svh;order:-1;margin-bottom:22px;border-right:1px solid var(--line)}
  .arc-visual img:not(.is-active){display:none}
}
.arc-track.is-static{height:auto!important}
.arc-track.is-static .arc-stage{
  position:static;height:auto;grid-template-columns:minmax(0,1fr);
  padding:0 clamp(14px,1.6vw,28px);gap:0;
}
.arc-track.is-static .arc-curve,.arc-track.is-static .arc-count{display:none}
.arc-track.is-static .arc-slides{min-height:0;margin-top:0}
.arc-track.is-static .arc-slide{
  position:static;opacity:1;transform:none;pointer-events:auto;
  padding:26px 0;border-top:1px solid var(--line);
}
.arc-track.is-static .arc-visual{height:42svh;order:-1;margin-bottom:22px;border-right:1px solid var(--line)}
.arc-track.is-static .arc-visual img:not(.is-active){display:none}


/* ===== nav geometry, wide screens =========================================
   Logo left, links centred, CTA right — the utility-bar arrangement, and it is
   the same arrangement at every scroll position, so there is no collapse to
   animate and nothing that can snap. The links are absolutely centred on the
   BAR rather than centred in the space the logo leaves, so the row stays
   optically balanced whatever the logo's width. */
@media(min-width:861px){
  .nav-in{height:48px;position:relative}
  .nav-links{
    position:absolute;left:50%;top:50%;
    transform:translate(-50%,-50%);
    flex:0 0 auto;
    width:max-content;
    max-width:calc(100vw - 320px);
  }
  .nav-links::before{content:none}
  .nav-in::before{display:none}
  .nav.scrolled .nav-in{
    height:48px;width:auto;max-width:none;
    padding-left:clamp(16px,2.2vw,40px);padding-right:clamp(16px,2.2vw,40px);
    transform:none;
  }
  .nav-links,
  .nav.scrolled .nav-links{gap:clamp(16px,2.6vw,34px)}
  /* the logo stays — a utility bar that drops its own mark on scroll is the
     old choreography, and there is no pill for it to make room for now */
  .nav.scrolled .logo{position:static;opacity:1;visibility:visible;pointer-events:auto}
}

/* Anchor targets must clear the fixed bar. Full-height panels handle it with
   their own top padding (--navh above); everything else needs the offset here,
   or a nav click drops the heading behind the bar. */
section[id],header[id]{scroll-margin-top:calc(var(--navh) + 14px)}
#home{scroll-margin-top:0}
@media(max-width:860px){
  :root{--navh:72px}
  section[id],header[id]{scroll-margin-top:calc(var(--navh) + 10px)}
}

/* ===== projects teaser (homepage) =======================================
   The full WebGL room lives on projects.html. This block keeps a real visual
   moment on the homepage without pulling in 594 KB of Three.js. */
.pj-grid{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(12px,1.4vw,22px);
  list-style:none;margin:clamp(34px,4.4vw,56px) 0 0;padding:0;
}
.pj-card a{display:block;text-decoration:none;color:inherit}
.pj-shot{
  display:block;position:relative;overflow:hidden;
  aspect-ratio:4 / 3;border:1px solid var(--line);background:#07101c;
}
.pj-shot img{
  width:100%;height:100%;object-fit:cover;display:block;
  transform:scale(1.02);
  transition:transform .9s var(--ease),opacity .5s var(--ease);
  opacity:.88;
}
.pj-card a:hover .pj-shot img,
.pj-card a:focus-visible .pj-shot img{transform:scale(1.07);opacity:1}
.pj-meta{display:block;padding:14px 2px 0}
.pj-meta strong{
  display:block;font-family:var(--display);font-weight:500;
  font-size:clamp(14px,1.05vw,16px);color:var(--fg);letter-spacing:-.005em;
}
.pj-meta small{
  display:block;margin-top:4px;font-size:12px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--faint);
}
.pj-cta{
  display:flex;align-items:center;gap:clamp(14px,2vw,26px);flex-wrap:wrap;
  margin-top:clamp(30px,4vw,48px);
  border-top:1px solid var(--line);padding-top:clamp(22px,3vw,34px);
}
.pj-hint{font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}

@media(max-width:900px){.pj-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:520px){
  .pj-grid{grid-template-columns:minmax(0,1fr)}
  .pj-cta{flex-direction:column;align-items:flex-start;gap:14px}
  .pj-cta .btn{width:100%;justify-content:center}
}

/* projects.html: the page head sits above the room, so it needs the bar's
   clearance without the homepage hero's full-screen treatment */

/* The projects page used to open on a light header band, which is why the nav
   was forced back to dark tokens here. It now opens on the dark room, so the
   default light-on-dark treatment is correct again and this override is gone.
   Any FUTURE page that opens on the light ground needs it back. */

/* ===== ecosystems: give the photograph the screen =======================
   The stage splits one viewport between head, photo, commands and strip. It
   was landing at head 198 / photo 396 / commands 108 / strip 179 — the chrome
   taking 54% and the image 44%.

   The head's top padding was a full --navh of nav clearance, and that was
   trimmed to clamp(46px,6.6vh,62px) on the reasoning that the collapsed nav is
   a centred capsule while this heading is left-aligned at the gutter, so they
   never meet horizontally.

   That holds at 1440 (capsule x 338-1102, eyebrow x 23-298) but NOT as the
   viewport narrows: the capsule grows toward the gutter while the heading
   stays put. Measured at 1101, the capsule is x 168-933 and the eyebrow
   x 18-293 — 125px of it sits under the capsule. The clearance cannot depend
   on a horizontal coincidence, so the bar's height goes back.
 */

/* Full-height panels align flush to the top of the viewport — their own
   padding clears the bar. Leaving the generic scroll-margin on them counted
   the nav twice and pushed their headings 110px further down than intended. */
#home,#experience,#contact{scroll-margin-top:0}

/* ===== client logo wall ================================================== */


/* Grayscale at .62 opacity washed the lighter marks (Lapis, Al Barari,
   Emirates Hills) almost off the page. Keep the wall uniform but legible. */

/* Two problems with the 1px-gap-as-border wall: 26 logos over 6 columns left
   four empty cells showing the container colour as a grey block, and grayscale
   flattened the gold marks (Lapis, 8 Real Estate, Al Barari) into near-white.
   Separate tiles solve the first; dropping grayscale solves the second — the
   uniform white tiles already hold the wall together. */

/* ===== clients: split copy + drifting logo columns =======================
   Copy on the left, three columns of logo cards on the right drifting in
   alternating directions (up / down / up) at the same time. */
.cl-split{
  display:grid;
  grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
  gap:clamp(26px,4.4vw,80px);
  align-items:center;
}
.cl-copy{max-width:44ch}
.cl-cta{margin-top:clamp(24px,3vw,36px)}

.cl-marquee{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:var(--clgap);
  height:clamp(420px,66vh,660px);
  overflow:hidden;
  /* soften the top and bottom so cards drift in and out rather than clipping */
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 11%,#000 89%,transparent 100%);
  mask-image:linear-gradient(180deg,transparent 0,#000 11%,#000 89%,transparent 100%);
}
:root{--clgap:clamp(10px,1.2vw,18px)}
.cl-col{overflow:hidden}
/* middle column starts part-way along so the three never march in lockstep */
.cl-col:nth-child(2){margin-top:calc(var(--clgap) * -4)}

.cl-track{
  list-style:none;margin:0;padding:0;
  will-change:transform;
  animation:clDrift 42s linear infinite;
}
.cl-col[data-dir="down"] .cl-track{animation-name:clDriftDown}
.cl-marquee:hover .cl-track,
.cl-marquee:focus-within .cl-track{animation-play-state:paused}

/* The gap is a margin on each card, NOT grid gap: translating by exactly one
   set height only loops seamlessly when every card contributes an identical
   card+gap block. A flex gap leaves a half-gap remainder and the loop jumps. */
.cl-card{
  border:1px solid var(--line);
  /* Deliberately white in BOTH themes. The client logo files are opaque white
     tiles — 0% transparency, mean luma 250 — so a dark card would just put a
     white rectangle on a mismatched background. Verified, not assumed. */
  background:rgba(255,255,255,.9);
}
:root[data-theme="dark"] .cl-card{
  /* .9 over the dark ground renders grey, and the logo image inside is pure
     white — which drew a visible ring around every tile. Match the artwork. */
  background:#fff;
  border-color:rgba(255,255,255,.16);
  display:grid;place-items:center;
  padding:clamp(14px,1.7vw,24px);
  min-height:clamp(100px,11.5vw,142px);
  margin-bottom:var(--clgap);
}
.cl-card img{max-width:100%;height:auto;display:block;transition:transform .4s var(--ease)}
.cl-card:hover img{transform:scale(1.06)}

@keyframes clDrift{from{transform:translateY(0)}to{transform:translateY(-50%)}}
@keyframes clDriftDown{from{transform:translateY(-50%)}to{transform:translateY(0)}}

/* PACE IS PER CARD, NOT PER LOOP. The 42s was set against nine logos a column;
   the columns now hold eleven, eleven and ten, and one duration across all
   three would have run the wall 22% faster than it was tuned for and made the
   short column visibly quicker than its neighbours. These hold the original
   4.7s-per-card pace at each column's own length — and the slight difference
   that remains between 11 and 10 is welcome, since three columns sharing an
   exact period start to march in step. */
.cl-col:nth-child(1) .cl-track,
.cl-col:nth-child(2) .cl-track{animation-duration:51s}
.cl-col:nth-child(3) .cl-track{animation-duration:47s}

@media(max-width:900px){
  .cl-split{grid-template-columns:minmax(0,1fr);gap:clamp(28px,5vw,44px)}
  .cl-copy{max-width:none}
  .cl-marquee{height:clamp(360px,52vh,460px)}
}


/* Motion off: no drift, no mask, and the duplicate set is dropped so the same
   logo is not announced or shown twice. */
@media(prefers-reduced-motion:reduce){
  .cl-track{animation:none}
  .cl-dupe{display:none}
  .cl-marquee{height:auto;-webkit-mask-image:none;mask-image:none}
  .cl-col{overflow:visible}
  .cl-col:nth-child(2){margin-top:0}
}

/* Seamless-loop fix. With margin-bottom on the cards the track's own height
   excluded the LAST card's margin, so translateY(-50%) moved 1425px while one
   set measured 1434 — a 9px jump every cycle. Flex gap plus a padding-bottom
   of exactly one gap makes the track height precisely two sets, so -50% is
   exactly one set. */
.cl-track{
  display:flex;flex-direction:column;
  gap:var(--clgap);
  padding-bottom:var(--clgap);
}
.cl-card{margin-bottom:0;flex:0 0 auto}

/* The extracted logo files already carry their own whitespace from the source
   artwork, so heavy card padding shrank the marks twice over. */
.cl-card{padding:clamp(6px,0.7vw,10px)}
.cl-card img{width:100%;max-width:none}
.cl-copy h2{font-size:clamp(28px,3.4vw,46px)}


/* ===== testimonials: full-frame portrait + quote slider ==================
   Replaces the 8-up grid. One person at a time: a large portrait on the left
   crossfading, the quote and controls on the right. Advances on its own and
   on the arrows; the timer only runs while the section is on screen and the
   visitor is not interacting with it. */


/* the portrait runs full frame rather than sitting in a small tile */


/* Every quote is stacked in the same place so the block never changes height
   as the text length varies — otherwise the arrows would jump on each turn. */


/* The real quotes off the client's own site run 112 to 474 characters, against
   the ~60 of the profile-PDF lines they replaced. Every .vq-item sits in the
   same grid cell, so the tallest one sets the height of the whole section —
   one long quote at display size would leave every other slide floating in a
   tall empty box. Size is therefore tiered by length at build time, and the
   longer ones get a wider measure so they run shorter rather than deeper.
   Tier is written into the class by the generator, not guessed by CSS. */


/* Roles were only on the credit slides. The old site supplies real ones —
   "CEO, Telescope", "CEO, DMS" — and a named role is most of what makes a
   testimonial worth reading, so quote slides carry them too. */

/* PDF item 4 — "influencer" becomes "Influencer & other Client". These six
   arrived with a portrait and a role but NO quote, so the slide leads with the
   person: name at quote scale, role beneath. .vq-side.is-credit drops the big
   opening quotation mark, or the slide reads as a quote that failed to load. */


@media(max-width:860px){
  .vq{grid-template-columns:minmax(0,1fr);gap:clamp(24px,5vw,38px)}
  .vq-shots{aspect-ratio:1;max-height:56svh}
  .vq-item blockquote{max-width:none}
  .vq-rail{max-width:none}
}

/* No auto-advance, no crossfade drift — the arrows still work. */
@media(prefers-reduced-motion:reduce){
  .vq-shots img,.vq-item{transition:none}
  .vq-rail{display:none}
}

/* Jost draws U+201C as two slanted strokes rather than a curly quote, so the
   mark gets a serif face — the only place on the site that does. */


/* The room loops for ever downward, so this is the way past it to the footer.
   Scrolling up always leaves normally, so it is the only exit that needs one. */
.room-skip{
  background:none;border:1px solid var(--line2);color:var(--txt);
  font-family:var(--display);font-size:11px;letter-spacing:.16em;
  text-transform:uppercase;padding:9px 15px;cursor:pointer;
  pointer-events:auto;
  transition:background .25s var(--ease),border-color .25s var(--ease),color .25s var(--ease);
}
.room-skip:hover{background:var(--sky);border-color:var(--sky);color:#fff}
.room-skip .arr{margin-left:6px}
@media(max-width:720px){.room-skip{display:none}}

/* ===== projects page: the room owns the whole page ======================
   No header band above the track any more — the room starts at the top of the
   document and the title sits ON it, inside .room-ui (which already has the
   scrim behind it for legibility). */

.room-head{
  position:absolute;
  left:clamp(14px,1.6vw,28px);
  right:clamp(14px,1.6vw,28px);
  top:calc(var(--navh) + clamp(18px,3.4vh,44px));
  max-width:min(52vw,640px);
}
.room-head h1{
  font-size:clamp(30px,4.1vw,58px);
  font-weight:300;line-height:1.03;letter-spacing:-.035em;
  color:var(--fg);margin-top:16px;
}
.room-head h1 b{font-weight:500;color:var(--sky)}

/* The room is a moving backdrop, so the heading can end up sitting on a lit
   pane. The stage-wide scrim in .room-ui::before fades out downward, which
   left the LAST line of the heading with almost none — measured 1.0:1 against
   a bright pane at mid-scroll, i.e. invisible. A single audit at one scroll
   position will not catch this: the backdrop is different at every offset.
   This one is anchored to the copy itself and feathers out to the right, so
   the type is protected without dulling the room the copy is describing. */
.room-head{isolation:isolate}
.room-head::before{
  content:"";position:absolute;z-index:-1;pointer-events:none;
  inset:-16% -34% -22% -12%;
  background:
    linear-gradient(102deg, rgba(var(--surf),.86) 0%,   rgba(var(--surf),.80) 38%,
                            rgba(var(--surf),.48) 68%,  transparent 100%),
    linear-gradient(180deg, rgba(var(--surf),.36) 0%,   rgba(var(--surf),.42) 62%,
                            transparent 100%);
}
.room-lead{
  margin-top:clamp(12px,1.6vh,18px);
  max-width:46ch;color:var(--soft);
  font-size:clamp(14px,1.05vw,16px);line-height:1.6;
}
@media(max-width:900px){
  .room-head{max-width:none;right:clamp(14px,6vw,40px)}
  .room-lead{display:none}          /* the room needs the room on a phone */
}

/* Edge to edge: the room is the page here, so the track gutter and the stage's
   hairline both go. On the homepage the room no longer exists at all, so this
   only ever applies to projects.html. */
body.is-projects-page .room-track{padding:0}
body.is-projects-page .room-stage{border:0}

/* ===== Wave 1: landmarks and target sizes ==============================
   Skip link — keyboard users had to tab through all eight nav items on every
   page before reaching content. Visible only once focused. */
/* Hidden by clipping, NOT by parking it 9999px off-screen: the browser would
   not move focus to something that far outside the viewport, so the link never
   became visible and was effectively unusable. Clipped, it stays at 0,0. */
.skip-link{
  position:absolute;left:0;top:0;z-index:200;
  width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
  /* --blue, not --sky: the dark theme's sky (#3E8FD6) is light enough that
     white on it measures 3.44:1, under the 4.5 this 14px text needs. The
     deeper blue passes in both themes — 7.2:1 light, 6.2:1 dark. */
  background:var(--blue);color:#fff;
  padding:12px 20px;font-family:var(--display);font-size:14px;
  text-decoration:none;
}
.skip-link:focus{
  width:auto;height:auto;overflow:visible;
  clip:auto;clip-path:none;
}
#main{display:block}

/* The toggle's box is 20x14 and ::after supplies the hit area. It measured
   44x42 by hit-test — two pixels under the WCAG 2.2 minimum on the vertical. */
.nav-toggle::after{inset:-16px -13px}

/* The About stepper now carries dated milestones, so each slide leads with its
   year. The stepper mechanics are unchanged — it sizes itself off the slide
   count, so going from four steps to five needed no JS change. */
.arc-year{
  display:block;
  font-family:var(--display);
  font-size:clamp(13px,1.05vw,15px);
  letter-spacing:.2em;
  color:var(--sky);
  margin-bottom:clamp(8px,1.1vh,12px);
}

/* ===== 404 ============================================================== */
.nf{min-height:70svh;display:flex;align-items:center}
.nf h1{font-size:clamp(34px,5vw,68px);font-weight:300;letter-spacing:-.035em;
  line-height:1.03;color:var(--fg);margin-top:16px}
.nf-links{display:flex;flex-wrap:wrap;gap:clamp(12px,1.6vw,20px);
  margin-top:clamp(30px,4vw,48px);align-items:center}
.nf-links a:not(.btn){
  border:1px solid var(--line2);padding:12px 20px;
  font-family:var(--display);font-size:14px;color:var(--txt);text-decoration:none;
  transition:background .25s var(--ease),border-color .25s var(--ease),color .25s var(--ease);
}
.nf-links a:not(.btn):hover{background:var(--sky);border-color:var(--sky);color:#fff}

/* WhatsApp is listed alongside every phone number in the company profile and is
   the usual channel in these markets, so it sits beside the number, not buried. */
.wa{
  display:inline-flex;align-items:center;gap:6px;
  margin-left:12px;padding:4px 10px;
  border:1px solid var(--line2);
  font-family:var(--display);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--soft);text-decoration:none;
  transition:background .25s var(--ease),border-color .25s var(--ease),color .25s var(--ease);
}
.wa:hover{background:#25D366;border-color:#25D366;color:#fff}

/* ===== legal pages ====================================================== */
.legal{padding-top:calc(var(--navh) + clamp(28px,5vh,64px))}
.legal h1{font-size:clamp(32px,4.4vw,58px);font-weight:300;letter-spacing:-.035em;
  line-height:1.04;color:var(--fg);margin-top:16px}
.legal-body{max-width:72ch;margin-top:clamp(30px,4vw,52px)}
.legal-meta{font-family:var(--display);font-size:12px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--faint);margin-bottom:clamp(24px,3vw,38px)}
.legal-body h2{font-size:clamp(19px,1.7vw,24px);font-weight:500;color:var(--fg);
  margin:clamp(30px,3.4vw,44px) 0 12px}
.legal-body p,.legal-body li{color:var(--txt);font-size:clamp(15px,1.15vw,17px);line-height:1.68}
.legal-body p+p{margin-top:14px}
.legal-body ul{margin:14px 0 0 20px}
.legal-body li+li{margin-top:8px}
.legal-body a{color:var(--sky)}

/* Support now has its own page; the homepage keeps the reassurance and hands
   off rather than carrying the whole section. */
.st-row{display:flex;align-items:center;gap:clamp(14px,2vw,26px);flex-wrap:wrap;
  margin-top:clamp(26px,3.4vw,42px)}
.st-hint{font-size:13px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint)}
@media(max-width:560px){.st-row{flex-direction:column;align-items:flex-start}
  .st-row .btn{width:100%;justify-content:center}}

/* On its own page the support section follows a hero that already carries the
   heading, so its own top padding would stack into a dead gap. */
.sup-hero{padding-top:calc(var(--navh) + clamp(28px,5vh,64px));padding-bottom:0}
.sup-hero h1{font-size:clamp(32px,4.4vw,58px);font-weight:300;letter-spacing:-.035em;
  line-height:1.04;color:var(--fg);margin-top:16px}
.sup-hero h1 b{font-weight:500;color:var(--sky)}
body:not(.dark-top) #support{padding-top:clamp(24px,3vh,40px)}
.foot-legal a{color:var(--faint);text-decoration:none;transition:color .25s var(--ease)}
.foot-legal a:hover{color:var(--sky)}
/* the logo symbol container — a class, not a style attribute, so the CSP can
   forbid inline styles outright */
.svg-defs{position:absolute}
.cf-gap{margin-top:20px}

/* Language switch lives in the footer bottom row. It used to sit in the nav;
   moved out because the bar reads cleaner without it, and because the
   first-visit gate now handles the case that actually matters — someone
   arriving who needs the other language. */
.lang-switch{
  display:inline-flex;align-items:center;
  padding:7px 14px;margin:0;
  border:1px solid var(--line2);
  font-family:var(--display);font-size:12px;letter-spacing:.1em;
  color:var(--txt);text-decoration:none;white-space:nowrap;
  transition:background .25s var(--ease),border-color .25s var(--ease),color .25s var(--ease);
}
.lang-switch:hover{background:var(--sky);border-color:var(--sky);color:#fff}
@media(max-width:860px){
  .lang-switch{font-size:16px;padding:14px 2px;border:0;border-bottom:1px solid var(--line)}
}

/* ===========================================================================
   START YOUR PROJECT — contact.html
   ---------------------------------------------------------------------------
   THE BACKGROUND. The old contact section carried
     .contact{background:radial-gradient(60% 60% at 80% 10%, ...)}
   and then switched it off again above 1101px, so desktop got a flat ground
   while phones got a wash — the inconsistency was the bug. This page has one
   ground that holds at every width: two soft brand-blue pools, one warm at the
   top right where the eye lands, one cooler bottom left to stop the page
   bottom going dead. Both from tokens, so it themes rather than needing a
   dark variant.

   THERE WAS ALSO AN 88px HAIRLINE GRID HERE — drawing-paper reference, 3% of
   --linec. Removed at the client's request: at 3% on a white ground it was
   loud enough to be noticed and asked about, which is the one thing a
   background must not do. It also competed with the wireframe the WebGL room
   draws on /projects. The pools alone still unify the pages, which was the
   actual job. Do not reintroduce it without looking at a real screen first.

   SHARED by all four pages — home, projects, support, contact. One ground, rather
   than each standalone page inventing its own — support was inheriting the
   global two-radial body wash while contact had this, which is why the two
   did not read as the same site.
   =========================================================================== */
body.cpage,
body.spage,
body.ppage,
body.hpage{
  background:
    radial-gradient(78% 58% at 84% -6%, rgba(var(--linec),.11), transparent 62%),
    radial-gradient(64% 46% at 4% 104%, rgba(var(--linec),.075), transparent 66%),
    rgb(var(--surf));
  background-attachment:fixed;    /* the pools stay put; only content travels */
}

/* ---- the page header ---- */
.cpage-hero{padding:calc(var(--navh) + clamp(34px,6vw,86px)) 0 clamp(26px,4vw,52px)}
.cpage-head{max-width:60ch}
.cpage-hero h1{
  margin:.34em 0 .42em;
  font-family:var(--display);font-weight:300;
  font-size:clamp(38px,5.4vw,74px);line-height:1.03;letter-spacing:-.028em;color:var(--fg);
}
.cpage-hero h1 b{color:var(--sky);font-weight:400}
.cpage-hero .lead{max-width:56ch}

.cpage-assure{
  list-style:none;margin:clamp(22px,3vw,32px) 0 0;padding:0;
  display:flex;flex-wrap:wrap;gap:10px 26px;
}
.cpage-assure li{
  position:relative;padding-left:22px;
  font-size:13.5px;color:var(--soft);
}
.cpage-assure li::before{
  content:"";position:absolute;left:0;top:.52em;
  width:9px;height:9px;border-radius:50%;
  border:1px solid var(--sky);background:rgba(var(--linec),.06);
}

/* ---- the two columns ---- */
.cpage-body{padding:0 0 var(--pad)}
.cpage-grid{
  display:grid;
  grid-template-columns:minmax(0,1.25fr) minmax(0,.85fr);
  gap:clamp(24px,3.4vw,54px);
  align-items:start;
}
.cpage-h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(17px,1.5vw,21px);letter-spacing:-.01em;color:var(--fg);
  margin:0 0 clamp(14px,2vw,20px);
}

/* The form is the reason the page exists, so it gets the raised panel and the
   aside stays flat against the ground behind it. */
.cpage-form{
  background:rgba(var(--surf2),.92);
  border:1px solid rgba(var(--linec),.14);
  border-radius:var(--r,2px);
  padding:clamp(22px,2.6vw,38px);
  box-shadow:0 30px 70px -46px rgba(var(--linec),.45);
}
.cpage-form .cform{margin-top:0}

/* The form sits in a padded panel and the aside does not, so their two
   headings were 28px out of line. Matching the panel's padding puts them on
   the same baseline without boxing the aside as well. */
.cpage-aside{padding-top:clamp(22px,2.6vw,38px)}
.cpage-aside .offices{border-top:1px solid var(--line)}
.cpage-note{margin-top:20px;font-size:14px;color:var(--soft)}
.cpage-note a{color:var(--sky)}

@media (max-width:900px){
  .cpage-grid{grid-template-columns:minmax(0,1fr)}
  .cpage-form{padding:clamp(18px,4vw,26px)}
  body.cpage,body.spage,body.ppage,body.hpage{background-attachment:scroll}  /* fixed janks on iOS */
}

/* the home page's closing band, which replaced the section */
.cta-band .section-head{max-width:760px;margin-inline:auto}
.cta-actions{display:flex;flex-wrap:wrap;gap:14px;justify-content:center;margin-top:clamp(22px,3vw,34px)}


/* ---- support plans -------------------------------------------------------
   The three cards carry 4, 6 and 7 bullets and the grid stretches them all to
   the tallest, so Handover ended with a large empty bottom that read as
   unfinished. Making each card a column and pinning a footer to the end gives
   the short card something to close on — and the plans stop being a dead end,
   which they were: three panels describing service with no way to ask for it.
   ------------------------------------------------------------------------- */
.sup-plan{display:flex;flex-direction:column}
.sup-plan ul{flex:1 0 auto}
.sup-plan-foot{
  margin-top:clamp(18px,2.2vw,26px);
  padding-top:14px;
  border-top:1px solid var(--line);
  display:inline-flex;align-items:center;gap:.55em;
  font-family:var(--display);font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--sky);text-decoration:none;
}
.sup-plan-foot .arr{transition:transform .4s var(--ease)}
.sup-plan-foot:hover .arr{transform:translateX(5px)}
.sup-plan.is-featured .sup-plan-foot{border-top-color:rgba(31,110,196,.28)}

/* The plans and the FAQ each carried a full section padding, stacking ~300px
   of empty page between them. */
.support{padding-bottom:clamp(34px,4vw,60px)}
.faq{padding-top:clamp(34px,4vw,60px)}


/* ---- the projects page's closing band ------------------------------------
   The room is dark and full-bleed; the section under it is on the page ground.
   Without a transition the boundary is a hard line across the viewport. */
.room-outro{
  /* var(--pad) here left ~200px of empty page between a one-line band and the
     footer. The band is short; it does not need a full section's breathing. */
  padding:0 0 clamp(34px,4.5vw,68px);
}
.room-outro-in{
  border-top:1px solid var(--line);
  padding-top:clamp(26px,3.4vw,44px);
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:clamp(16px,2.4vw,32px);
}
.room-outro p{margin:0;max-width:46ch;color:var(--soft);font-size:15px}
.room-outro .btn{flex:none}


/* ===========================================================================
   THE SYSTEM LAYER
   ---------------------------------------------------------------------------
   Everything above is the site's structure — grids, sticky tracks, the pieces
   the JavaScript measures and drives. This block is its APPEARANCE, and it is
   deliberately one place rather than scattered edits, so the visual language
   can be read, argued with and changed in a single sitting.

   The language, in five rules:

     1. TWO SURFACES. The page is Fog (#f5f5f7); anything raised is Pure White.
        That tonal step is the ONLY elevation signal — there is not one drop
        shadow on the site.
     2. ONE RADIUS. 28px on every container, 980px on true pill buttons, 50%
        on things that are actually circles. Nothing else.
     3. ONE SATURATED COLOUR. #0071e3 fills the primary button. Links wear the
        deeper #0066cc so a link and a button never read as the same action.
        Every other element is neutral — no coloured headlines, no glows.
     4. TYPE CARRIES THE PAGE. Headlines are 600 weight with negative tracking
        that closes up as the size grows; body is 17px, secondary copy 14px,
        and nothing sits between or outside that.
     5. SPACE IS THE LAYOUT. Sections breathe at ~80px, copy runs short, and
        an image is given room rather than a border.
   =========================================================================== */

/* ---- ground -------------------------------------------------------------
   The old identity washed every page in two soft blue radials. One flat Fog
   ground replaces them, on all four page classes at once. */
body,
body.cpage,body.spage,body.ppage,body.hpage{
  background:var(--ink);
  background-attachment:scroll;
}

/* focus ring follows the shape it is on, in the action blue */
a:focus-visible,button:focus-visible,summary:focus-visible,
.command:focus-visible,.mode-tab:focus-visible,.scenario:focus-visible,
.hotspot:focus-visible,.sound-toggle:focus-visible,.nav-toggle:focus-visible,
.btn:focus-visible{
  outline:2px solid var(--blue);
  outline-offset:3px;
  border-radius:inherit;
}
.brandmark{filter:none}          /* the mark never carried a shadow */
.skip-link{background:var(--blue);border-radius:0 0 var(--r-sm) 0;font-family:var(--body);font-weight:600;letter-spacing:var(--tracking-body-sm)}

/* ---- one chip ------------------------------------------------------------
   Every small standalone control on the site — spec tags, scene commands,
   timeline and process chips, system tags, the language switch, WhatsApp, the
   404 links, the room's skip control — is the same object: a Concrete pill
   with no border, darkening to Space Black when it is the active one. */
.spec,.command,.mode-tab,.scenario,.sound-toggle,
.tl-chips li,.arc-chips li,.pd-systems span,
.lang-switch,.room-skip,.wa,.nf-links a:not(.btn),.tm-more,.cbar-btn,.lgate-pick{
  border:0;
  border-radius:var(--r);
  background:var(--ink3);
  color:var(--fg);
  font-family:var(--body);
  font-size:14px;
  font-weight:400;
  line-height:1.43;
  letter-spacing:var(--tracking-body-sm);
  text-transform:none;
  padding:9px 16px;
  transition:background .22s var(--ease),color .22s var(--ease);
}
.spec:hover,.command:hover,.mode-tab:hover,.scenario:hover,
.lang-switch:hover,.room-skip:hover,.wa:hover,.nf-links a:not(.btn):hover,
.tm-more:hover,.cbar-btn:hover,.lgate-pick:hover{
  background:var(--ink4);color:var(--fg);border-color:transparent;
}
.command.active,.mode-tab.active,.scenario.active,.sound-toggle.on,
.lgate-pick.is-on,.lgate-theme.is-on{
  background:var(--fg);color:var(--ink2);border-color:transparent;box-shadow:none;
}
.wa{font-size:12px;padding:6px 12px;margin-left:10px}
.wa:hover{background:#25D366;color:#fff}
.room-skip{font-size:12px;padding:8px 15px}
.room-skip .arr{font-size:0}
.room-skip .arr::after{content:"\203A";font-size:15px}

/* ---- section rhythm ------------------------------------------------------ */
.section{padding:var(--pad) 0}
.section-head .lead{max-width:56ch}
.wrap{max-width:var(--max)}

/* =====================================================================
   TRUST — four figures, four white cards
   The band used a 1px grid gap showing a hairline through it. Cards on a
   Fog ground with real space between them is the same information with the
   chrome taken off.
   ===================================================================== */
.trust{background:none;border:0;padding:clamp(48px,5vw,80px) 0}
.trust-grid{
  grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(12px,1.2vw,20px);
  background:none;border:0;border-radius:0;overflow:visible;
}
.trust-card,.trust-card.highlight{
  background:rgb(var(--surf2))!important;
  border-radius:var(--r);
  padding:var(--card-padding);
  min-height:0;
  box-shadow:none;
}
.trust-card .num{
  font-family:var(--display);font-weight:600;
  font-size:clamp(44px,4.4vw,60px);line-height:1.06;letter-spacing:var(--tracking-body-sm);
  color:var(--fg);
}
.trust-card .num span{color:inherit}
.trust-card p{margin-top:12px;max-width:22ch;font-size:var(--text-body-sm);line-height:var(--leading-body-sm);color:var(--soft)}
@media(max-width:900px){.trust-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:560px){.trust-grid{grid-template-columns:minmax(0,1fr)}}

/* =====================================================================
   ECOSYSTEMS — the photograph in a rounded panel, the strip beneath it
   ===================================================================== */
/* the strip reads as a list of feature rows: circular icon, label, hairline */
               /* the scanning sweep */
/* The scene tints stay — pressing "Evening Scene" really should warm the
   photograph, and warm light is light, not brand colour. What goes is the
   ambient blue glow that sat on the panel whether or not anyone pressed
   anything, and the blue washes on the three scenes that were never about
   colour: they become plain light and shade. */
.mobile-cta .btn{box-shadow:none}

/* =====================================================================
   PRODUCTS — five photographic tiles
   Every tile carries a real product photograph now, so each one is an image
   in a 28px frame with its caption set on the picture. The scrim under that
   caption is pure black, not the old blue-black: nothing on this site tints
   a neutral.
   ===================================================================== */
.pgrid{padding:0 clamp(20px,4vw,64px);gap:clamp(12px,1.2vw,20px)}
.ptile figcaption{
  background:linear-gradient(0deg,rgba(0,0,0,.88) 0%,rgba(0,0,0,.55) 46%,transparent 100%);
}
/* Literal colours, not tokens, through this whole block: every tile carries
   .on-dark in the markup, which redefines --surf2 locally — so a tokenised
   white here would resolve to #1d1d1f and paint the card black. */
.ptile{
  border:0;border-radius:var(--r);
  background:#fff;
  box-shadow:none;
}
.ptile-hero::after{
  background:linear-gradient(180deg,rgba(0,0,0,.34) 0%,rgba(0,0,0,.05) 46%,transparent 100%);
}
.ptile-hero-center h3{
  font-weight:600;font-size:clamp(30px,4vw,var(--text-heading-lg));
  letter-spacing:var(--tracking-h);line-height:var(--leading-heading-lg);
  color:#fff;text-shadow:none;
}
.ptile-discover{
  margin-top:clamp(16px,1.8vw,24px);
  padding:11px 21px;border:0;border-radius:var(--r-pill);
  background:var(--blue);color:#fff;backdrop-filter:none;
  font-family:var(--body);font-size:17px;font-weight:600;letter-spacing:var(--tracking-body);
}
.ptile-discover:hover{background:#0077ed;border-color:transparent}
.ptile figcaption h4{
  font-family:var(--display);font-weight:600;
  font-size:clamp(19px,1.7vw,24px);letter-spacing:-.015em;line-height:1.14;
}
.ptile figcaption p{font-size:var(--text-body-sm);line-height:var(--leading-body-sm);letter-spacing:var(--tracking-body-sm)}
.ptile-hero figcaption{
  background:linear-gradient(0deg,rgba(0,0,0,.80) 0%,rgba(0,0,0,.55) 40%,transparent 100%);
}
.ptile-hero figcaption .ptile-label{font-family:var(--body);font-weight:600;font-size:14px}
.ptile-hero figcaption p{color:rgba(255,255,255,.82)}
/* NO TILE USES THIS TODAY — every one carries a photograph. It is kept, and
   kept short, because the .ptile-icon rules further up this file are the old
   identity (a brand-blue blob behind a glowing blue mark), and a photo-less
   tile added later would fall straight into them. This is what that component
   looks like in this system: a white product card, dark mark, dark type, and
   the caption sitting on the card rather than in a scrim over it. */
.ptile:has(.ptile-icon){background:#fff}
.ptile-icon{background:#fff}
.ptile-icon::after{display:none}          /* the blue blob behind the icon */
.ptile-icon svg{color:#1d1d1f;filter:none;width:clamp(96px,10vw,150px);margin-bottom:14%}
.ptile:has(.ptile-icon) figcaption{
  background:linear-gradient(0deg,#fff 0%,#fff 62%,rgba(255,255,255,0) 100%);
  padding-top:clamp(40px,4vw,60px);
}
.ptile:has(.ptile-icon) figcaption h4{color:#1d1d1f}
.ptile:has(.ptile-icon) figcaption p{color:#6e6e73}

/* =====================================================================
   PROJECTS teaser + client wall
   ===================================================================== */
.pj-shot{border:0;border-radius:var(--r);background:var(--ink3)}
.pj-shot img{opacity:1;transform:none}
.pj-card a:hover .pj-shot img,.pj-card a:focus-visible .pj-shot img{transform:scale(1.03)}
.pj-meta{padding:14px 4px 0}
.pj-meta strong{font-family:var(--body);font-weight:600;font-size:17px;letter-spacing:var(--tracking-body)}
.pj-meta small{
  margin-top:2px;font-size:14px;letter-spacing:var(--tracking-body-sm);
  text-transform:none;color:var(--soft);
}
.pj-cta{border-top:1px solid var(--line)}
.pj-hint,.st-hint,.room-foot,.legal-meta,.cbar-text{
  font-size:14px;letter-spacing:var(--tracking-body-sm);text-transform:none;color:var(--soft);
}
.cl-card{border:0;border-radius:var(--r-sm);background:#fff;box-shadow:none}
:root[data-theme="dark"] .cl-card{border:0;border-radius:var(--r-sm)}
.cl-copy h2{font-weight:600;letter-spacing:-.015em}

/* =====================================================================
   TIMELINE + PROCESS — neutral rails, rounded media, chip rows
   ===================================================================== */
.tl::after{background:var(--fg)}
.tl-mark i{background:rgb(var(--surf2));border:1px solid var(--line2)}
.tl-item.is-live .tl-mark i{background:var(--fg);border-color:var(--fg);box-shadow:none;transform:none}
.tl-year{
  font-family:var(--body);font-size:12px;font-weight:600;
  letter-spacing:var(--tracking-caption);color:var(--faint);
}
.tl-body h3{font-weight:600;font-size:clamp(21px,2vw,28px);letter-spacing:-.015em}
.tl-body p{font-size:var(--text-body);line-height:var(--leading-body);color:var(--soft)}
.tl-media{border:0;border-radius:var(--r);background:var(--ink3)}
.arc-count,.arc-year{
  font-family:var(--body);font-size:12px;font-weight:600;
  letter-spacing:var(--tracking-caption);text-transform:uppercase;color:var(--faint);
}
.arc-count i{color:var(--faint)}
.arc-mark i{background:var(--line2)}
.arc-mark.is-active i{background:var(--fg);box-shadow:none}
.arc-mark span{font-family:var(--display);font-weight:600;letter-spacing:var(--tracking-body-sm);color:var(--faint)}
.arc-mark.is-active span{color:var(--fg)}
.arc-slide h3{font-weight:600;font-size:clamp(28px,3.6vw,48px);letter-spacing:-.015em;line-height:1.07}
.arc-slide p{font-size:var(--text-body);line-height:var(--leading-body);color:var(--soft);max-width:46ch}
.arc-visual{
  border:0;border-radius:var(--r);
  margin-right:clamp(20px,4vw,64px);
  background:var(--ink3);
}
.arc-curve svg circle{stroke:rgba(0,0,0,.10)}
@media(max-width:860px){
  .arc-visual{margin-right:0;border:0}
  .arc-slide{border-top:1px solid var(--line)}
}

/* =====================================================================
   TEAM
   ===================================================================== */
.tm img,.tm-slot{border-radius:var(--r);border:0;background:var(--ink3)}
.tm h3{font-family:var(--body);font-weight:600;font-size:17px;letter-spacing:var(--tracking-body);margin-top:14px}
.tm-role{
  font-family:var(--body);font-size:14px;font-weight:400;
  letter-spacing:var(--tracking-body-sm);text-transform:none;color:var(--soft);
}
.tm-more{padding:11px 21px;border-radius:var(--r-pill);font-size:17px;font-weight:600;letter-spacing:var(--tracking-body)}
.tm-note{border-top:1px solid var(--line)}
.tm-note p{font-size:14px;color:var(--soft)}
.tm-num{color:var(--faint)}

/* =====================================================================
   VOICES — portrait, quote, controls
   ===================================================================== */

            /* the display quotation mark */


/* the longest quotes stop being display type and become readable copy */


/* =====================================================================
   SUPPORT + FAQ
   ===================================================================== */
.sup-grid{
  gap:clamp(12px,1.2vw,20px);
  background:none;border:0;
}
.sup-card{background:rgb(var(--surf2));border-radius:var(--r);padding:var(--card-padding)}
.sup-figure{
  font-family:var(--display);font-weight:600;
  font-size:clamp(34px,3.6vw,48px);letter-spacing:var(--tracking-body-sm);color:var(--fg);
}
.sup-card .sup-card-label{font-size:17px;font-weight:600;letter-spacing:var(--tracking-body);margin-top:12px;color:var(--fg)}
.sup-card p{font-size:var(--text-body-sm);line-height:var(--leading-body-sm);color:var(--soft)}
.sup-plan{background:rgb(var(--surf2));border:0;border-radius:var(--r);padding:var(--card-padding)}
.sup-plan.is-featured{background:rgb(var(--surf2));box-shadow:inset 0 0 0 2px rgba(0,113,227,.5)}
.sup-plan h2{font-family:var(--display);font-weight:600;font-size:24px;letter-spacing:-.015em}
.sup-plan > p{font-size:14px;color:var(--soft)}
.sup-plan li{font-size:14px;color:var(--soft);padding-left:18px}
.sup-plan li::before{width:5px;height:5px;border-radius:50%;background:var(--faint);top:.62em}
.sup-plan-foot{
  border-top:1px solid var(--line);
  font-family:var(--body);font-size:14px;font-weight:400;
  letter-spacing:var(--tracking-body-sm);text-transform:none;color:var(--sky);
}
.sup-plan-foot .arr{font-size:0}
.sup-plan-foot .arr::after{content:"\203A";font-size:17px}
.sup-plan.is-featured .sup-plan-foot{border-top-color:var(--line)}
.faq-list{border-top:1px solid var(--line)}
.faq-item{border-bottom:1px solid var(--line)}
.faq-item summary{
  font-family:var(--body);font-size:17px;font-weight:600;letter-spacing:var(--tracking-body);
}
.faq-item summary::after{border-right-color:var(--sky);border-bottom-color:var(--sky)}
.faq-item summary:hover{color:var(--sky)}
.faq-a p{font-size:var(--text-body);line-height:var(--leading-body);color:var(--soft)}
.faq-a a{color:var(--sky);border-bottom:0;text-decoration:underline;text-underline-offset:2px}

/* =====================================================================
   CONTACT — the form panel, the fields, the office list
   ===================================================================== */
.cpage-hero h1,.sup-hero h1,.legal h1,.nf h1,.room-head h1{
  font-family:var(--display);font-weight:600;
  font-size:clamp(34px,4.4vw,var(--text-heading-lg));
  line-height:var(--leading-heading-lg);letter-spacing:var(--tracking-h);
  margin-top:3px;color:var(--fg);
}
.cpage-hero h1 b,.sup-hero h1 b,.room-head h1 b{font-weight:600;color:inherit}
.cpage-h2{font-family:var(--body);font-size:17px;font-weight:600;letter-spacing:var(--tracking-body)}
.cpage-form{
  background:rgb(var(--surf2));
  border:0;border-radius:var(--r);
  padding:var(--card-padding);
  box-shadow:none;
}
.cpage-assure li{font-size:14px;color:var(--soft);padding-left:20px}
.cpage-assure li::before{border-color:var(--faint);background:none;width:8px;height:8px}
.cpage-note{font-size:14px;color:var(--soft)}
.cpage-note a,.legal-body a,.office a,.cbar-text a{color:var(--sky)}
.cf-field label{
  font-family:var(--body);font-size:12px;font-weight:400;
  letter-spacing:var(--tracking-body-sm);text-transform:none;color:var(--soft);margin-bottom:6px;
}
.cf-field input,.cf-field select,.cf-field textarea{
  border:1px solid var(--line2);
  border-radius:var(--r-sm);
  background:rgb(var(--surf2));
  padding:13px 16px;
  font-family:var(--body);font-size:17px;letter-spacing:var(--tracking-body);
}
.cf-field input:focus,.cf-field select:focus,.cf-field textarea:focus{
  border-color:var(--blue);
  box-shadow:0 0 0 4px rgba(0,113,227,.18);
}
.cf-status,.cf-err{font-size:14px}
.office{border-bottom:1px solid var(--line)}
.office:hover{padding-left:0}
.office-city{font-family:var(--body);font-size:17px;font-weight:600;letter-spacing:var(--tracking-body)}
.office-country{
  font-family:var(--body);font-size:12px;font-weight:600;
  letter-spacing:var(--tracking-caption);color:var(--faint);
}
.office-address{font-size:14px;color:var(--soft)}
.office a{font-family:var(--body);font-size:14px}

/* =====================================================================
   PROJECTS ROOM + project detail page
   ===================================================================== */
/* The scrim behind the room's heading was two linear gradients, which left a
   visible rectangle edge along the bottom now that the room is near-black.
   One radial anchored to the copy feathers out in every direction instead. */
.room-head::before{
  inset:-40% -60% -60% -30%;
  background:radial-gradient(58% 62% at 22% 42%,
    rgba(0,0,0,.86) 0%,rgba(0,0,0,.62) 42%,rgba(0,0,0,.28) 68%,transparent 100%);
}
.room-caption{
  border:0;border-radius:var(--r-sm);
  background:rgba(255,255,255,.80);
  box-shadow:none;
  padding:14px 18px 16px;
}
.room-caption small{
  font-family:var(--body);font-size:12px;font-weight:600;
  letter-spacing:var(--tracking-caption);color:var(--faint);
}
.room-caption strong{
  font-family:var(--body);font-weight:600;font-size:17px;
  letter-spacing:var(--tracking-body);color:#1d1d1f;
}
.room-caption-desc{font-size:14px;color:#6e6e73}
.room-lead{font-size:17px;color:var(--soft)}
.room-index{border:0;border-radius:var(--r);background:rgb(var(--surf2));box-shadow:none}
.room-index button{border-radius:var(--r-sm);border:0}
.room-index button:hover,.room-index button:focus-visible{background:var(--ink3);border-color:transparent}
.room-index .ri-kicker{font-family:var(--body);font-size:12px;font-weight:600;letter-spacing:var(--tracking-caption);color:var(--faint);text-transform:none}
.room-index .ri-title{font-size:17px;font-weight:600;letter-spacing:var(--tracking-body);color:var(--fg)}
.room-outro-in{border-top:1px solid var(--line)}
.room-outro p{font-size:17px;color:var(--soft)}


/* =====================================================================
   FOOTER — small, quiet, one hairline
   ===================================================================== */
.footer{border-top:1px solid var(--line);padding:clamp(34px,4vw,52px) 0 30px}
.footer .brandmark{height:26px}
.footer p{font-size:12px;line-height:1.33;color:var(--faint);margin-top:14px;max-width:34ch}
.foot-col h2{
  font-family:var(--body);font-size:12px;font-weight:600;
  letter-spacing:var(--tracking-body-sm);text-transform:none;color:var(--fg);margin-bottom:10px;
}
.foot-col a{font-size:12px;line-height:1.33;color:var(--soft);padding:5px 0}
/* A THUMB NEEDS MORE THAN 26px. This layer's 12px type and 5px padding come
   after the responsive rule that used to widen these, so on a phone eighteen
   footer links ended up 26px tall and stacked — small enough that hitting the
   wrong one is the likely outcome. Only the touch height changes; the type,
   the colour and the spacing between columns are untouched. */
@media(max-width:860px){
  .foot-col a{padding:12px 0;font-size:13px}
  /* The bottom row is the smallest thing on the page and the one with the most
     consequential controls in it — the privacy policy, the cookie settings, the
     language switch and the theme. Measured at 18-31px tall; raised to 40, and
     now to the full 44px floor. This rule owns all four controls at (0,2,0),
     which is why the later touch-target block cannot reach them with bare class
     selectors — the fix belongs here rather than in a competing rule. */
  .footer-bottom a,.footer-bottom button{
    min-height:44px;display:inline-flex;align-items:center;
  }
  .foot-legal{display:inline-flex;align-items:center;gap:16px;flex-wrap:wrap}
  /* a link that opens the contact page deserves a thumb-sized target too */
  .pv-links a{min-height:44px;display:inline-flex;align-items:center}
}
.foot-col a:hover{color:var(--fg);padding-left:0;text-decoration:underline}
.footer-bottom{border-top:1px solid var(--line);font-size:12px;color:var(--faint);margin-top:34px}
.foot-legal a,.foot-cookies{color:var(--faint);font-size:12px}
.foot-legal a:hover,.foot-cookies:hover{color:var(--fg)}
.lang-switch{font-size:12px;padding:7px 14px}
.theme-toggle{
  border:0;border-radius:var(--r-pill);background:var(--ink3);
  font-family:var(--body);font-size:12px;letter-spacing:var(--tracking-body-sm);color:var(--fg);
  padding:7px 14px 7px 11px;
}
.theme-toggle:hover{background:var(--ink4);color:var(--fg)}

/* =====================================================================
   OVERLAYS — first-visit gate, storage notice, page loader
   ===================================================================== */
.lgate{background:var(--ink)}
.lgate-kicker{
  font-family:var(--body);font-size:12px;font-weight:600;
  letter-spacing:var(--tracking-caption);color:var(--faint);
}
.lgate-kicker + .lgate-kicker{font-size:14px;font-weight:400;letter-spacing:var(--tracking-body-sm)}
.lgate-pick{
  border-radius:var(--r-pill);
  font-size:17px;font-weight:600;letter-spacing:var(--tracking-body);padding:14px 26px;
}
.lgate-theme.is-on{background:var(--blue);color:#fff}
.cbar{
  border:0;border-radius:var(--r);
  background:rgba(255,255,255,.94);
  backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
  box-shadow:0 1px 3px rgba(0,0,0,.10);   /* the one exception: it floats */
}
:root[data-theme="dark"] .cbar{background:rgba(29,29,31,.94)}
.cbar-btn{border-radius:var(--r-pill);font-size:14px;padding:11px 22px}
.cbar-yes{background:var(--blue);color:#fff;border-color:transparent}
.cbar-yes:hover{background:#0077ed;filter:none;color:#fff}
.pl{background:var(--ink)}
.pl-ghost{color:var(--line)}
.pl-fill{color:var(--fg)}

/* =====================================================================
   404 + legal
   ===================================================================== */
.nf-links a:not(.btn){border-radius:var(--r-pill);padding:12px 22px;font-size:17px;font-weight:600}
.nf-links a:not(.btn):hover{background:var(--ink4);color:var(--fg)}
.legal-body h2{font-size:24px;font-weight:600;letter-spacing:-.015em}
.legal-body p,.legal-body li{font-size:var(--text-body);line-height:var(--leading-body);color:var(--txt)}
.legal-body ul + p{margin-top:14px}   /* a list ran straight into the next line */
.legal-meta{font-family:var(--body);font-size:12px;font-weight:600;letter-spacing:var(--tracking-caption);text-transform:uppercase}

/* =====================================================================
   DARK — the same system on black
   ===================================================================== */
:root[data-theme="dark"] body,
:root[data-theme="dark"] body.cpage,
:root[data-theme="dark"] body.spage,
:root[data-theme="dark"] body.ppage,
:root[data-theme="dark"] body.hpage{background:#000}
:root[data-theme="dark"] .nav::before{background:rgba(29,29,31,.72)}
:root[data-theme="dark"] .trust-card,
:root[data-theme="dark"] .trust-card.highlight,
:root[data-theme="dark"] .sup-card,
:root[data-theme="dark"] .sup-plan,
:root[data-theme="dark"] .cpage-form,
:root[data-theme="dark"] .ptile,
:root[data-theme="dark"] .ptile-icon,
:root[data-theme="dark"] .room-index,

:root[data-theme="dark"] .ptile,
:root[data-theme="dark"] .ptile:has(.ptile-icon),
:root[data-theme="dark"] .ptile-icon{background:#1d1d1f}
:root[data-theme="dark"] .ptile-icon svg{color:#f5f5f7}
:root[data-theme="dark"] .ptile:has(.ptile-icon) figcaption{
  background:linear-gradient(0deg,#1d1d1f 0%,#1d1d1f 62%,rgba(29,29,31,0) 100%);
}
:root[data-theme="dark"] .ptile:has(.ptile-icon) figcaption h4{color:#f5f5f7}
:root[data-theme="dark"] .ptile:has(.ptile-icon) figcaption p{color:#a1a1a6}
:root[data-theme="dark"] .spec,
:root[data-theme="dark"] .command,
:root[data-theme="dark"] .mode-tab,
:root[data-theme="dark"] .scenario,
:root[data-theme="dark"] .sound-toggle,
:root[data-theme="dark"] .tl-chips li,
:root[data-theme="dark"] .arc-chips li,
:root[data-theme="dark"] .pd-systems span,
:root[data-theme="dark"] .lang-switch,
:root[data-theme="dark"] .room-skip,
:root[data-theme="dark"] .wa,
:root[data-theme="dark"] .nf-links a:not(.btn),
:root[data-theme="dark"] .tm-more,
:root[data-theme="dark"] .cbar-btn,
:root[data-theme="dark"] .lgate-pick,
:root[data-theme="dark"] .btn-ghost,
:root[data-theme="dark"] .theme-toggle,

:root[data-theme="dark"] .spec:hover,
:root[data-theme="dark"] .command:hover,
:root[data-theme="dark"] .btn-ghost:hover,
:root[data-theme="dark"] .tm-more:hover,
:root[data-theme="dark"] .vq-btn:hover,
:root[data-theme="dark"] .theme-toggle:hover,
:root[data-theme="dark"] .lgate-pick:hover{background:#3a3a3c;color:var(--fg)}
:root[data-theme="dark"] .command.active,
:root[data-theme="dark"] .mode-tab.active,
:root[data-theme="dark"] .scenario.active,
:root[data-theme="dark"] .sound-toggle.on{background:#f5f5f7;color:#1d1d1f}
:root[data-theme="dark"] .cl-card{background:#fff}

/* NOTE ON PLACEMENT: this block sits here, at the very end of the clients
   rules, and not up with the other breakpoints — because it has to. Four
   later unscoped rules (.cl-track's flex column, .cl-card's flex and
   padding, and the restyle's .cl-card border) all match at the same
   specificity, so from its original position the media query lost every
   property it shared with them on source order alone: flex-direction
   stayed `column` and the wall grew to 5268px on a phone rather than
   rotating. Being in a media query buys no weight. */
/* PHONE: THE WALL TURNS ON ITS SIDE. This used to drop to two columns and
   hide the third outright, which cost ten of the thirty-two logos — Samsung
   and Tilal Al Ghaf among them — on exactly the device most visitors arrive
   on. Neither obvious fix works: three vertical columns at 375px gives a
   104px card, and the denser marks (Iconic's two sublines, Olive's three)
   stop being readable; a static two-up grid of all thirty-two runs 1572px
   tall. Rotating the same three tracks to run horizontally keeps every logo,
   keeps the drift, and costs about 250px of page.

   Same markup, same duplicate sets, same durations — only the axis changes.
   The seamless-loop padding has to change axis with it: a track that pads its
   bottom while translating along X leaves the last card's gap out of the
   measurement, which is the 9px-per-cycle jump this pattern already had once. */
@media(max-width:560px){
  .cl-marquee{
    display:flex;flex-direction:column;gap:var(--clgap);
    height:auto;
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 9%,#000 91%,transparent 100%);
    mask-image:linear-gradient(90deg,transparent 0,#000 9%,#000 91%,transparent 100%);
  }
  .cl-col{overflow:hidden}
  .cl-col:nth-child(2){margin-top:0}
  .cl-track{
    flex-direction:row;width:max-content;
    padding-bottom:0;padding-right:var(--clgap);
    animation-name:clDriftX;
  }
  .cl-col[data-dir="down"] .cl-track{animation-name:clDriftXBack}
  .cl-card{width:132px}
}
@keyframes clDriftX{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@keyframes clDriftXBack{from{transform:translateX(-50%)}to{transform:translateX(0)}}
:root[data-theme="dark"] .cf-field input,
:root[data-theme="dark"] .cf-field select,
:root[data-theme="dark"] .cf-field textarea{background:#2c2c2e;border-color:var(--line2);color:var(--fg)}
:root[data-theme="dark"] .room-caption{background:rgba(29,29,31,.82)}
:root[data-theme="dark"] .room-caption strong{color:#f5f5f7}
:root[data-theme="dark"] .room-caption-desc{color:#a1a1a6}


/* ---- tracking follows the step, not the element -------------------------
   Letter-spacing is specified per size, and it does NOT inherit usefully: a
   14px paragraph inside a 17px page carries the body step's -0.27px unless it
   is told otherwise, which sets small copy visibly too tight. These two groups
   put every remaining piece of small text back on its own step. Add to them
   whenever you add 12px or 14px text. */
.trust-card p,.ptile-hero figcaption .ptile-label,
.tm-note p,.sup-card p,.sup-plan > p,.sup-plan li,.cpage-assure li,
.cpage-note,.cf-status,.cf-err,.office-address,.office a,
.room-caption-desc,.project p,.cbar-btn,.pj-hint,.st-hint,.room-foot,
.legal-body .legal-note{letter-spacing:var(--tracking-body-sm)}

.wa,.room-skip,.footer p,.foot-col a,.footer-bottom,
.foot-legal a,.foot-cookies,.lang-switch,.theme-toggle,
.cf-field label{letter-spacing:var(--tracking-caption)}

/* =====================================================================
   THE EXPERIENCE — /experience.html
   ---------------------------------------------------------------------
   The prototype arrived in its own identity: gold accents, glass panels,
   letter-spaced caps. All of it is replaced here by the page system —
   pill buttons, one blue, Space Black type, 28px radius — with one rule
   added for this page only: everything sits ON footage, so the controls
   are white glass rather than Concrete, and type is white rather than
   Space Black. A light grey pill on a photograph reads as a hole punched
   in the image.

   Scroll drives the camera on every device, touch included. The only other
   mode is static (prefers-reduced-motion), which is the storyboard. The two
   are mutually exclusive, and the DEFAULT — no attribute, meaning scripting is
   off or the script failed — is the storyboard, so the page can never be a
   dead black rectangle.
   ===================================================================== */
/* The site sets html{scroll-behavior:smooth} for its anchor links. Here it is
   actively harmful: the gate clamp calls scrollTo(0,maxScroll) on every
   refused scroll, and a SMOOTH scrollTo animates — so a visitor pushing
   against a gate gets a soft rubber-band fight instead of a wall, and the
   scrub lags the wheel by the length of the animation. This page moves the
   viewport itself; it does not want a tween on top. */
:root[data-xp="scrub"]{
  scroll-behavior:auto;
  /* Scroll is the instrument, and the gates clamp it. Chaining and
     pull-to-refresh both hand the gesture to the browser mid-walk, which on a
     phone reads as the page giving up. */
  overscroll-behavior-y:none;
}

.xp{display:none}
.xp-spacer{display:none}
:root[data-xp="scrub"] .xp{display:block}
:root[data-xp="scrub"] .xp-spacer{display:block;position:relative;z-index:1;pointer-events:none}
:root[data-xp="scrub"] .xp-static{display:none}

/* the stage owns the viewport while the walk is running */
.xp{
  position:fixed;inset:0;z-index:2;
  overflow:hidden;background:#000;
}
/* A fixed stage never leaves, so everything AFTER the walk has to be given a
   layer above it or it scrolls up behind the footage and the page ends in a
   dead frame. The spacer deliberately stays below (z-index 1): it is a
   scroll-height generator and must never take a pointer. */
:root[data-xp="scrub"] .xp-outro,
:root[data-xp="scrub"] .footer{
  position:relative;z-index:5;
  /* and they need a GROUND of their own. Being above the stage is not enough:
     the fixed stage paints over the body's background, so a transparent
     section simply lets the footage show through its own text. */
  background:var(--ink);
}

.xp-stage{position:absolute;inset:0}
.xp-stage video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;opacity:0;
  transition:opacity .28s linear;
  will-change:opacity;
}
.xp-stage video.is-on{opacity:1}
/* Same scrim as the home hero: enough to hold white type, not enough to
   grey out the footage the page exists to show. */
.xp-scrim{
  position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,
    rgba(0,0,0,.42) 0%,rgba(0,0,0,.10) 24%,rgba(0,0,0,.28) 58%,rgba(0,0,0,.72) 100%);
}

/* ---- the copy block ---- */
.xp-copy{
  position:absolute;z-index:6;
  left:clamp(20px,4vw,64px);bottom:clamp(150px,20vh,190px);
  max-width:min(30ch,72vw);
  opacity:0;transform:translateY(12px);
  transition:opacity .6s var(--ease),transform .6s var(--ease);
}
.xp-copy.is-on{opacity:1;transform:none}
.xp-copy .eyebrow{color:#fff;opacity:.92}
.xp-copy h1{
  margin-top:3px;color:#fff;
  font-size:clamp(30px,3.6vw,var(--text-heading-lg));
  line-height:var(--leading-heading-lg);letter-spacing:var(--tracking-h);
  font-weight:600;
}
.xp-copy p{
  margin-top:10px;color:rgba(255,255,255,.78);
  font-size:var(--text-body-sm);line-height:var(--leading-body-sm);
  letter-spacing:var(--tracking-body-sm);
}

/* ---- progress rail ---- */
.xp-rail{
  position:absolute;z-index:6;list-style:none;margin:0;padding:0;
  top:50%;right:clamp(16px,2.4vw,34px);transform:translateY(-50%);
  display:flex;flex-direction:column;gap:14px;
}
.xp-rail li{
  width:6px;height:6px;border-radius:50%;
  background:rgba(255,255,255,.28);
  transition:background .4s var(--ease),transform .4s var(--ease);
}
.xp-rail li.is-done{background:rgba(255,255,255,.55)}
.xp-rail li.is-on{background:#fff;transform:scale(1.5)}

/* ---- gates ---- */
.xp-gate{
  position:absolute;z-index:8;
  left:50%;bottom:clamp(34px,6vh,60px);
  transform:translate(-50%,14px);
  display:none;flex-direction:column;align-items:center;gap:12px;
  width:min(760px,calc(100% - 32px));
  opacity:0;
  transition:opacity .4s var(--ease),transform .4s var(--ease);
}
.xp-gate.is-on{display:flex;opacity:1;transform:translate(-50%,0)}
.xp-row{display:flex;gap:12px;flex-wrap:wrap;justify-content:center}
/* on footage every control is glass; the primary keeps the one blue */
.xp-gate .btn-ghost{
  background:rgba(255,255,255,.18);color:#fff;
  backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
}
.xp-gate .btn-ghost:hover:not(:disabled){background:rgba(255,255,255,.30);color:#fff}
.xp-gate .btn:disabled{opacity:.4;cursor:default}
.xp-hint{
  color:rgba(255,255,255,.62);
  font-size:var(--text-caption);line-height:var(--leading-caption);
  letter-spacing:var(--tracking-caption);
}

/* a refused scroll has to say so, or the page reads as broken */
.xp-gate.is-nudging{animation:xpNudge .55s var(--ease)}
@keyframes xpNudge{
  0%,100%{transform:translate(-50%,0)}
  35%{transform:translate(-50%,-9px)}
}

/* ---- the climate panel ---- */
.xp-panel{
  display:flex;align-items:center;gap:clamp(18px,2.6vw,34px);
  padding:18px clamp(18px,2.4vw,28px);
  border-radius:var(--r);
  background:rgba(255,255,255,.16);
  backdrop-filter:blur(20px) saturate(1.4);
  -webkit-backdrop-filter:blur(20px) saturate(1.4);
}
.xp-field{display:flex;flex-direction:column;gap:7px;min-width:150px}
.xp-field-act{min-width:0}
.xp-field label{
  color:rgba(255,255,255,.66);
  font-size:var(--text-caption);letter-spacing:var(--tracking-caption);
}
.xp-readout{
  color:#fff;font-family:var(--display);font-weight:600;
  font-size:24px;line-height:1.1;letter-spacing:-.015em;
  font-variant-numeric:tabular-nums;
}
.xp-readout small{font-size:14px;font-weight:400;margin-left:2px;opacity:.7}
.xp-fan{display:flex;gap:5px}
.xp-fan i{
  flex:1;height:4px;border-radius:2px;background:rgba(255,255,255,.26);
  transition:background .25s var(--ease);
}
.xp-fan i.is-on{background:#fff}
.xp-field input[type=range]{
  -webkit-appearance:none;appearance:none;
  width:100%;height:3px;border-radius:2px;
  background:rgba(255,255,255,.3);outline:none;cursor:pointer;
}
.xp-field input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;
  width:18px;height:18px;border-radius:50%;background:#fff;border:0;cursor:pointer;
}
.xp-field input[type=range]::-moz-range-thumb{
  width:18px;height:18px;border-radius:50%;background:#fff;border:0;cursor:pointer;
}
.xp-field input[type=range]:focus-visible{outline:2px solid #fff;outline-offset:4px}

/* the way out — the same chip as "Skip the room" on the projects page */
.xp-skip{
  position:absolute;z-index:9;
  top:calc(48px + clamp(14px,2.4vh,26px));right:clamp(16px,2.4vw,34px);
  display:inline-flex;align-items:center;gap:6px;
  border:0;border-radius:var(--r-pill);cursor:pointer;
  background:rgba(255,255,255,.18);color:#fff;
  backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  font-family:var(--body);font-size:var(--text-caption);font-weight:400;
  letter-spacing:var(--tracking-caption);
  padding:8px 15px;
  transition:background .22s var(--ease);
}
.xp-skip:hover{background:rgba(255,255,255,.3)}
.xp-skip .arr{font-size:0}
.xp-skip .arr::after{content:"\203A";font-size:15px}

/* ---- scroll cue ---- */
.xp-cue{
  position:absolute;z-index:7;left:50%;bottom:14px;transform:translateX(-50%);
  color:rgba(255,255,255,.55);
  font-size:var(--text-caption);letter-spacing:var(--tracking-caption);
  opacity:0;transition:opacity .5s var(--ease);
  pointer-events:none;
}
.xp-cue.is-on{opacity:1}

/* ---- the storyboard: reduced motion, no script, or no footage ---- */
.xp-frames{list-style:none;margin:0;padding:0;display:grid;gap:clamp(28px,4vw,56px)}
.xp-frames li{
  display:grid;grid-template-columns:minmax(0,1.4fr) minmax(0,1fr);
  gap:clamp(20px,3vw,44px);align-items:center;
}
.xp-frames li:nth-child(even) figure{order:2}
.xp-frames figure{margin:0;border-radius:var(--r);overflow:hidden;background:var(--ink3)}
.xp-frames img{width:100%;height:auto;display:block}
.xp-frames h3{
  margin-top:3px;font-size:clamp(24px,2.6vw,32px);
  line-height:1.14;letter-spacing:-.015em;font-weight:600;
}
.xp-frames p{
  margin-top:10px;color:var(--soft);
  font-size:var(--text-body);line-height:var(--leading-body);
}
@media(max-width:760px){
  .xp-frames li{grid-template-columns:minmax(0,1fr)}
  .xp-frames li:nth-child(even) figure{order:0}
}

/* ---- what you just operated ---- */
.xp-grid{
  list-style:none;margin:clamp(28px,4vw,48px) 0 0;padding:0;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
  gap:clamp(16px,2vw,28px);
}
.xp-grid li{background:rgb(var(--surf2));border-radius:var(--r);padding:var(--card-padding)}
.xp-grid h3{font-family:var(--body);font-size:var(--text-body);font-weight:600;letter-spacing:var(--tracking-body)}
.xp-grid p{
  margin-top:8px;color:var(--soft);
  font-size:var(--text-body-sm);line-height:var(--leading-body-sm);
  letter-spacing:var(--tracking-body-sm);
}
.xp-note{
  margin-top:clamp(24px,3vw,38px);color:var(--faint);
  font-size:var(--text-caption);line-height:var(--leading-caption);
  letter-spacing:var(--tracking-caption);max-width:70ch;
}
.xp-outro .cta-actions{justify-content:flex-start}

@media(max-width:860px){
  .xp-copy{
    left:clamp(20px,5vw,40px);right:clamp(20px,5vw,40px);
    bottom:auto;top:calc(48px + clamp(24px,7vh,60px));
    max-width:none;
  }
  .xp-rail{
    top:auto;bottom:clamp(12px,2vh,20px);right:50%;
    transform:translateX(50%);flex-direction:row;
  }
  .xp-gate{bottom:clamp(46px,9vh,74px)}
  .xp-panel{flex-direction:column;align-items:stretch;gap:14px;width:100%}
  .xp-field{min-width:0}
  /* the cue stays on a phone — scrolling IS the interaction here, and it is
     the one instruction the page needs to give */
  .xp-cue{bottom:8px}
}

/* =====================================================================
   PROJECTS — the arc
   ---------------------------------------------------------------------
   A centred display headline, then the work as one long look along a
   wall: a native horizontal scroller whose cards are rotated by their
   distance from the middle, so the row reads as the inside of a
   cylinder. The curve is added by projects-arc.js; without it — no
   script, or reduced motion — this stays a flat, scrollable strip and
   the page is complete.
   ===================================================================== */
/* One screen: headline at the top, the wall taking the slack in the middle,
   the closing copy at the foot. min-height rather than height so a short
   laptop can still scroll the composition rather than have it clipped. */
.pv{
  min-height:100svh;
  display:flex;flex-direction:column;
  padding:calc(var(--navh) + clamp(10px,2.5vh,34px)) 0 clamp(20px,4vh,44px);
}
.pv-head,.pv-foot{flex:0 0 auto}
.pv-head{text-align:center;max-width:none}
/* The one place the system uses 700: the reference's opening line is set
   as heavy as the face goes, and the scale sanctions 700 at display
   sizes. Everything else on the site stays 600. */
.pv-head h1{
  /* measured to two lines rather than broken with a <br>: a <br> splits the
     text node, and the Arabic build matches whole text nodes — the headline
     would quietly stop being translatable. */
  max-width:14ch;margin:0 auto;
  font-family:var(--display);font-weight:700;
  /* capped against the viewport's HEIGHT as well: this section is one screen,
     and a headline sized only against width pushes the wall and the closing
     links off the bottom of a laptop */
  font-size:min(clamp(40px,7.4vw,96px),10svh);
  line-height:1.03;letter-spacing:var(--tracking-h1);
  color:var(--fg);
}
.pv-kicker{
  margin:clamp(14px,2vw,20px) auto 0;max-width:44ch;
  color:var(--soft);
  font-size:var(--text-body);line-height:var(--leading-body);
  letter-spacing:var(--tracking-body);
}

/* ---- the belt ---- */
.pv-arc{
  position:relative;
  flex:1 1 auto;min-height:0;
  display:flex;align-items:center;justify-content:center;
  margin:clamp(16px,3vh,40px) 0 0;
  /* No perspective here on purpose — the belt below scrolls, and a scrolling
     box forces transform-style:flat on its contents, so a shared 3D space
     cannot exist. Each card carries its own perspective instead; see
     projects-arc.js. */
}
.pv-belt{
  display:flex;
  /* Wider than it looks like it needs to be. Each frame turns about the
     circle's centre, so neighbours swing their inner edges toward each other
     and the seam closes up across the middle of the wall. */
  gap:22px;
  list-style:none;margin:0;
  padding:clamp(26px,5vh,56px) 0;
  overflow-x:auto;
  overscroll-behavior-x:contain;
  scrollbar-width:none;
  -webkit-overflow-scrolling:touch;
  cursor:grab;
}
.pv-belt::-webkit-scrollbar{display:none}
/* ---- the ring ----------------------------------------------------------
   Everything above describes the FLAT fallback: a plain scrollable strip, which
   is what reduced motion and a failed script get. projects-arc.js adds .is-ring
   and from here the wall is one cylinder in one 3D space.

   The overflow lives on .pv-arc, NOT on the belt: an element with overflow
   other than visible forces transform-style:flat on its contents, and a belt
   that scrolls could never hold a shared 3D space. The arc clips (so the wall
   runs off both edges of the screen), the belt preserves 3D, and the frames
   are stacked at one point and swung out around the circle from there. */
.is-ring{
  overflow:hidden;
  perspective:1150px;
  perspective-origin:50% 50%;
}
.is-ring .pv-belt{
  display:block;position:relative;width:100%;padding:0;
  overflow:visible;
  transform-style:preserve-3d;
  cursor:grab;
  touch-action:pan-y;      /* the page keeps vertical; the wall takes sideways */
  -webkit-user-select:none;user-select:none;
}
.is-ring .pv-slide{
  /* centred in the reserved band, so a frame grows into the room above and
     below it evenly instead of pushing out of the bottom */
  position:absolute;left:50%;top:50%;
  margin-top:calc(var(--pv-h,0px) / -2);
  /* one variable so the width is stated once and the ring, the offset and the
     media queries below can never drift apart */
  margin-left:calc(var(--pv-w) / -2);
  width:var(--pv-w);
  transform-origin:50% 50%;
  transition:none;
  will-change:transform,opacity;
}
.is-ring .pv-belt.is-dragging{cursor:grabbing}
.pv-belt.is-dragging{cursor:grabbing;scroll-behavior:auto;user-select:none}
.pv-belt:focus-visible{outline:2px solid var(--blue);outline-offset:6px;border-radius:var(--r)}

/* Capped by the viewport's HEIGHT as well as its width. A frame is 3:4, and
   the ones out at the edges of the wall are drawn some 25% larger as they come
   toward the viewer — on a short laptop a width-only cap makes a wall that
   cannot fit the screen it is supposed to fill. */
.pv-arc{--pv-w:min(21vw,300px,20svh)}
.pv-slide{
  /* five-ish across: enough of the wall in frame for the curve to read */
  flex:0 0 var(--pv-w);
  transform-origin:50% 50%;
  /* transform and opacity are written by the script every frame; the
     transition is only here for the first paint and for resize */
  transition:opacity .3s linear;
  will-change:transform;
}
/* Not a button any more — nothing to press. The reset that made a <button>
   look like a frame goes with it, and so does the pointer cursor, which was
   promising a click that no longer happens. */
.pv-card{display:block;width:100%;border-radius:var(--r-sm);color:inherit}
.pv-shot{
  display:block;position:relative;overflow:hidden;
  /* The radius floor rather than the 28px container radius: these are frames
     in a continuous band, not cards sitting on the page, and 28px on a 300px
     frame reads as a row of app icons. */
  border-radius:var(--r-sm);
  background:var(--ink3);
  aspect-ratio:3 / 4;
}
.pv-shot img{width:100%;height:100%;object-fit:cover;display:block}
.pv-empty{
  display:grid;place-items:center;height:100%;padding:20px;text-align:center;
  color:var(--faint);
  font-size:var(--text-caption);letter-spacing:var(--tracking-caption);
}
/* The reference's band is bare images, and it is right: eleven captions strung
   along a curve is a list wearing a carousel. The name lives INSIDE the frame
   and arrives on hover or keyboard focus, over a scrim that only exists while
   it is being read. Everything is named in full in the list below, and each
   frame carries an aria-label, so nothing depends on the hover. */
/* THE NAME IS ALWAYS ON THE FRAME NOW, and that is a correction, not a
   preference. It used to appear on hover only, which was defensible while the
   full list sat below the wall and every frame carried an aria-label naming the
   project it opened. Both of those went when the list and the detail page were
   removed — leaving the names reachable by hover and nothing else, which on a
   touch screen means not reachable at all. The wall is now the only place these
   projects are named, so it names them. */
.pv-meta{
  position:absolute;left:0;right:0;bottom:0;z-index:1;
  padding:34px 16px 14px;
  background:linear-gradient(0deg,rgba(0,0,0,.78) 0%,rgba(0,0,0,.34) 58%,transparent 100%);
}
.pv-meta strong{
  display:block;font-family:var(--body);font-weight:600;
  font-size:var(--text-body-sm);letter-spacing:var(--tracking-body-sm);color:#fff;
}
.pv-meta small{
  display:block;margin-top:1px;color:rgba(255,255,255,.72);
  font-size:var(--text-caption);letter-spacing:var(--tracking-caption);
}
.pv-card:hover .pv-shot img{transform:scale(1.04)}
.pv-shot img{transition:transform .6s var(--ease)}

/* the ring that says the row is something you take hold of */
.pv-cursor{
  position:absolute;left:0;top:0;z-index:5;
  width:62px;height:62px;margin:-31px 0 0 -31px;
  border-radius:50%;
  background:rgba(255,255,255,.86);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  opacity:0;pointer-events:none;
  transition:opacity .25s var(--ease);
}
.pv-arc.has-cursor .pv-cursor{opacity:1}
/* two chevrons, not a bar: the ring has to say WHICH WAY the wall moves, and a
   single line reads as a minus sign */
.pv-cursor::after{
  content:"\2039\2003\203A";
  position:absolute;inset:0;
  display:grid;place-items:center;
  color:#1d1d1f;opacity:.72;
  font-family:var(--body);font-size:17px;line-height:1;
}
:root[data-theme="dark"] .pv-cursor{background:rgba(29,29,31,.72)}
:root[data-theme="dark"] .pv-cursor::after{color:#f5f5f7}

/* ---- the closing copy ---- */
.pv-foot{text-align:center;margin-top:clamp(32px,5vw,64px)}
.pv-foot p{
  margin:0 auto;max-width:52ch;color:var(--soft);
  font-size:var(--text-body);line-height:var(--leading-body);
  letter-spacing:var(--tracking-body);
}
.pv-links{
  display:flex;gap:clamp(20px,3vw,40px);justify-content:center;flex-wrap:wrap;
  margin-top:clamp(20px,3vw,30px);
}
.pv-links a{
  color:var(--sky);
  font-size:var(--text-body);letter-spacing:var(--tracking-body);
  border-bottom:1px solid transparent;
}
.pv-links a:hover{border-bottom-color:currentColor}
.pv-links .arr{font-size:0}
.pv-links .arr::after{content:"\203A";font-size:19px}


@media(max-width:1100px){.pv-arc{--pv-w:min(30vw,260px,22svh)}}
@media(max-width:720px){
  .pv-arc{--pv-w:min(62vw,290px,26svh)}
  .pv-belt{gap:14px}
}
@media(prefers-reduced-motion:reduce){
  .pv-shot img{transition:none}
  .pv-slide{transition:none}
}

/* ---- a frame flying into the detail page --------------------------------
   Fixed to the viewport and above the detail takeover (.pd is z-index 200), so
   it lands ON the hero image it is becoming and fades out to reveal it.

   The box is animated, not a transform: the image is object-fit:cover and the
   crop has to re-resolve as the frame goes from 3:4 to a full-width hero. A
   scale would stretch it instead. */


/* =====================================================================
   PRODUCTS — the wall of panels
   ---------------------------------------------------------------------
   Five panels in a row. Each is a slat until it is asked for, and the one
   being asked for takes about half the row.

   IT IS CSS THAT OPENS THEM. flex-grow on :hover, with :focus-within for
   the keyboard, so the wall works with scripting switched off; home.js
   adds tap for touch screens, which have no hover to give.

   The order of the two hover rules matters and they are deliberately the
   same specificity: the second has to win when the pointer is on the
   panel that was already open, or hovering it would collapse it.
   ===================================================================== */
/* THE PANELS are the full-height thing, not the section around them. The
   heading sits above at its normal rhythm and the wall then takes a whole
   screen on its own, so scrolling to it puts nothing else in frame. */
#products{padding-bottom:clamp(20px,4vh,44px)}
/* the wall carries its own top margin; the heading's would double it */
#products .section-head{margin-bottom:0}
.pgrid{
  display:flex;
  gap:clamp(6px,.7vw,12px);
  padding:0 clamp(20px,4vw,64px);
  margin-top:clamp(24px,3.4vw,48px);
  height:100svh;
}
.ptile{
  flex:1 1 0;min-width:0;height:auto;
  position:relative;overflow:hidden;
  border-radius:var(--r-sm);
  transition:flex-grow .8s var(--ease);
}
/* The wall rests CLOSED — five equal slats — and opens whatever is being
   pointed at. Nothing is sticky, so moving along the row hands the opening
   from one panel to the next and the wall closes again when the pointer
   leaves it.

   That also makes this simple. An earlier version kept one panel open by
   default, which meant every other panel needed a rule telling the open one to
   yield — and getting that wrong collapsed the whole row whenever the pointer
   crossed the gaps or the row's own padding. With nothing open, hovering one
   panel is the only thing that ever changes a width. */
@media(hover:hover){
  .ptile:hover{flex-grow:4.2}
}
.ptile:focus-within{flex-grow:4.2}
/* touch has no hover: home.js sets this on tap, and only there */
.ptile.is-open{flex-grow:4.2}

.ptile-media{position:absolute;inset:0;width:100%;height:100%}
/* The panel is a slat most of the time, so the image is cropped to a sliver.
   Holding it at the width of an OPEN panel and centring it means the sliver is
   a slice of the real photograph rather than a stretched squeeze of it. */
.ptile-media img{
  position:absolute;top:0;left:50%;transform:translateX(-50%);
  width:auto;height:100%;max-width:none;
  min-width:100%;
  object-fit:cover;
  transition:none;
}
.ptile:hover .ptile-media img{transform:translateX(-50%)}

/* The mark that says a slat opens: a short white bar on its LEADING edge —
   inset-inline-start, not left, so it lands on the right-hand edge in the
   Arabic tree without rtl.css having to know about it. */
.ptile::after{
  content:"";position:absolute;z-index:2;
  inset-inline-start:clamp(8px,1vw,14px);top:50%;
  width:3px;height:46px;margin-top:-23px;
  border-radius:3px;
  background:rgba(255,255,255,.85);
  opacity:1;
  transition:opacity .3s var(--ease);
}
.ptile.is-open::after,
.ptile:hover::after,
.ptile:focus-within::after{opacity:0}

/* the caption belongs to the open panel only */
.ptile figcaption{
  position:absolute;left:0;right:0;bottom:0;z-index:3;
  padding:clamp(20px,2.4vw,34px);
  background:linear-gradient(0deg,rgba(0,0,0,.86) 0%,rgba(0,0,0,.5) 46%,transparent 100%);
  opacity:0;transform:translateY(10px);
  transition:opacity .45s var(--ease) .12s,transform .45s var(--ease) .12s;
  pointer-events:none;
}
.ptile.is-open figcaption,
.ptile:hover figcaption,
.ptile:focus-within figcaption{opacity:1;transform:none;pointer-events:auto}
.ptile figcaption p{max-width:52ch}
.ptile-discover{
  display:inline-flex;align-items:center;gap:6px;
  margin-top:clamp(12px,1.4vw,18px);
  padding:0;border:0;background:none;backdrop-filter:none;
  color:#fff;font-family:var(--body);font-size:var(--text-body);font-weight:600;
  letter-spacing:var(--tracking-body);
  border-bottom:1px solid rgba(255,255,255,.5);
}
.ptile-discover:hover{background:none;border-bottom-color:#fff}
.ptile-discover .arr{font-size:0}
.ptile-discover .arr::after{content:"\203A";font-size:19px}

/* A phone cannot hover and a row of five slats is unreadable at that width, so
   the wall unstacks into a column of real panels. */
@media(max-width:860px){
  .pgrid{
    flex-direction:column;height:auto;
    gap:clamp(10px,1.6vw,16px);
  }
  .ptile,.ptile.is-open{flex:0 0 auto;height:clamp(240px,42svh,360px)}
  .ptile::after{display:none}
  .ptile figcaption{opacity:1;transform:none;pointer-events:auto}
  .ptile-media img{position:absolute;left:0;transform:none;width:100%}
}
@media(prefers-reduced-motion:reduce){
  .ptile{transition:none}
  .ptile figcaption{transition:none}
}

/* =====================================================================
   SUPPORT + CONTACT — a picture beside the hero copy
   ---------------------------------------------------------------------
   Both heroes were a single column of copy against roughly 40% empty
   space. These two pages are a pair, so one getting a picture means both
   do. Upstream shipped these framed — a hairline border and square
   corners — which is the old identity; here the picture is a rounded
   surface on the Fog ground with no border, like every other image on
   the site.
   ===================================================================== */
/* The picture runs the full height of the screen, and the copy sits beside it
   on the centre line. The section is a flex column so the row can take every
   pixel the heading and the page padding do not, and the picture simply fills
   its cell — no aspect ratio, or it would size itself and leave a gap. */
.sup-hero,
.cpage-hero{
  min-height:100svh;
  display:flex;flex-direction:column;
  padding-top:calc(var(--navh) + clamp(12px,2.5vh,32px));
  padding-bottom:clamp(18px,3.5vh,44px);
}
.sup-hero > .wrap,
.cpage-hero > .wrap{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
.sup-hero-in,
.cpage-hero-in{
  flex:1 1 auto;min-height:0;
  display:grid;
  grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:clamp(26px,4vw,64px);
  align-items:stretch;
}
/* the words stay on the centre line rather than stretching with the picture */
.sup-hero-in > .section-head,
.cpage-hero-in > .cpage-head{align-self:center}
.sup-hero-media,
.cpage-hero-media{
  margin:0;overflow:hidden;border:0;
  border-radius:var(--r);
  background:var(--ink3);
  height:100%;min-height:0;
}
.sup-hero-media img,
.cpage-hero-media img{width:100%;height:100%;object-fit:cover;display:block}
@media(max-width:900px){
  /* Stacked, and shallower, so the copy still leads on a phone. A full-height
     picture only means anything while it has a column of its own; stacked it
     would just be a screen of photograph with the words pushed under it. */
  .sup-hero,
  .cpage-hero{min-height:0;display:block;padding-top:calc(var(--navh) + clamp(20px,4vh,44px))}
  .sup-hero > .wrap,
  .cpage-hero > .wrap{display:block}
  .sup-hero-in,
  .cpage-hero-in{grid-template-columns:minmax(0,1fr);gap:clamp(20px,4vw,32px)}
  .sup-hero-media,
  .cpage-hero-media{height:auto;aspect-ratio:16 / 9}
}

/* =====================================================================
   ABOUT — the page, and the summary the homepage keeps
   ---------------------------------------------------------------------
   The homepage carried the whole story: a five-milestone timeline and the
   team, roughly two screens of it, in the middle of a page that already
   had eight other sections. It is a page's worth of content, so it is a
   page. What stays on the homepage is who Mass is in three sentences
   beside one picture, and a way through to the rest.
   ===================================================================== */
.ab-hero{
  min-height:100svh;
  display:flex;flex-direction:column;
  padding-top:calc(var(--navh) + clamp(12px,2.5vh,32px));
  padding-bottom:clamp(18px,3.5vh,44px);
}
.ab-hero > .wrap{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
.ab-hero-in{
  flex:1 1 auto;min-height:0;
  display:grid;
  grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:clamp(26px,4vw,64px);
  align-items:stretch;
}
.ab-hero-in > .section-head{align-self:center;margin-bottom:0}
.ab-hero-in h1{
  font-family:var(--display);font-weight:600;
  font-size:min(clamp(34px,4.6vw,60px),9svh);
  line-height:var(--leading-heading-lg);letter-spacing:var(--tracking-h);
  margin-top:3px;color:var(--fg);
}
.ab-hero-in h1 b{font-weight:600;color:inherit}
.ab-hero-media{
  margin:0;overflow:hidden;border:0;
  border-radius:var(--r);background:var(--ink3);
  height:100%;min-height:0;
}
.ab-hero-media img{width:100%;height:100%;object-fit:cover;display:block}

.ab-story{padding-top:clamp(48px,7vw,96px)}

/* ---- the summary that stays on the homepage ---- */
.ab-lite-in{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(26px,4vw,64px);
  align-items:center;
}
.ab-lite-in > .section-head{margin-bottom:0}
.ab-lite-cta{margin-top:clamp(20px,2.6vw,30px)}
.ab-lite-media{
  margin:0;overflow:hidden;border:0;
  border-radius:var(--r);background:var(--ink3);
  aspect-ratio:4 / 3;
}
.ab-lite-media img{width:100%;height:100%;object-fit:cover;display:block}

@media(max-width:900px){
  .ab-hero{min-height:0;display:block;padding-top:calc(var(--navh) + clamp(20px,4vh,44px))}
  .ab-hero > .wrap{display:block}
  .ab-hero-in,
  .ab-lite-in{grid-template-columns:minmax(0,1fr);gap:clamp(20px,4vw,32px)}
  .ab-hero-media{height:auto;aspect-ratio:16 / 9}
  .ab-lite-media{aspect-ratio:16 / 9}
}

/* =====================================================================
   MOTION — the arrival choreography
   ---------------------------------------------------------------------
   Every hidden state below is scoped to html.mo, which motion.js adds
   when it runs. No script, an error, or reduced motion, and none of this
   applies: the page is simply visible. The old .reveal did the opposite —
   it hid content in CSS and depended on JS to reveal it, so a script
   failure left a blank page. That is fixed here too.

   Transform and opacity only. Nothing in this block can cause layout.
   ===================================================================== */
:root{--mo-ease:cubic-bezier(.22,.8,.26,1);--mo-step:80ms}

/* ---- 1. headlines wipe up, line by line ---- */
.mo-w{
  display:inline-block;overflow:hidden;vertical-align:top;
  /* a mask that stops at the baseline clips the descenders off g, y and p —
     the box is grown downward and the space taken back with a margin */
  padding-bottom:.18em;margin-bottom:-.18em;
}
/* NO will-change HERE, and that is deliberate. It used to be on this rule, so
   every word of every headline held its own compositor layer for the life of
   the page — 91 of them on the home page alone, 103 promoted elements in total,
   most of them words that had finished animating minutes earlier or would never
   animate because they sit six screens down. The browser composites a transform
   transition on its own while it runs; will-change bought a fraction of a
   millisecond at the start of each word and charged a standing GPU cost for it
   on every frame of every scroll. */
.mo-wi{display:inline-block}
:root.mo .mo-split .mo-wi{
  transform:translate3d(0,110%,0);
  transition:transform .95s var(--mo-ease);
  transition-delay:calc(var(--i,0) * var(--mo-step));
}
:root.mo .mo-split.is-in .mo-wi{transform:none}

/* ---- 2. supporting copy follows the headline ---- */
:root.mo [data-mo="rise"]{
  opacity:0;transform:translate3d(0,16px,0);
  transition:opacity .8s var(--mo-ease) .12s,transform .8s var(--mo-ease) .12s;
}
:root.mo [data-mo="rise"].is-in{opacity:1;transform:none}

/* ---- 3. pictures open ----
   The frame wipes up from its own bottom edge and the photograph inside
   settles from slightly overscaled, so the image arrives rather than
   fading on. --i staggers a row: one card, then the next. */
:root.mo [data-mo="frame"]{
  clip-path:inset(0 0 100% 0);
  transition:clip-path 1.05s var(--mo-ease);
  transition-delay:calc(var(--i,0) * 90ms);
}
:root.mo [data-mo="frame"].is-in{clip-path:inset(0 0 0 0)}
:root.mo [data-mo="frame"] > img,
:root.mo [data-mo="frame"] .pv-shot img{
  transform:scale(1.1);
  transition:transform 1.4s var(--mo-ease);
  transition-delay:calc(var(--i,0) * 90ms);
}
:root.mo [data-mo="frame"].is-in > img,
:root.mo [data-mo="frame"].is-in .pv-shot img{transform:none}
/* a portrait IS the frame, so it wipes and settles at once */
:root.mo img[data-mo="frame"]{transform:scale(1.06);transition:clip-path 1.05s var(--mo-ease),transform 1.4s var(--mo-ease)}
:root.mo img[data-mo="frame"].is-in{transform:none}

/* ---- 4. and then they drift ----
   Written by the rAF loop; the frame never moves, only the photograph in it.
   The 1.12 scale is what keeps an edge from showing at the extremes. */
:root.mo .mo-drift{will-change:transform}
:root.mo .hero-bg,
:root.mo .hero-copy{will-change:transform}

/* The old single gesture. Kept because the markup still carries .reveal in
   places, but now scoped to html.mo so it can never hide anything on its own,
   and softened so it never fights the four moves above. */
:root.mo .reveal{opacity:0;transform:translate3d(0,18px,0);transition:opacity .8s var(--mo-ease),transform .8s var(--mo-ease)}
:root.mo .reveal.in-view{opacity:1;transform:none}
:root:not(.mo) .reveal{opacity:1;transform:none}

@media(prefers-reduced-motion:reduce){
  :root.mo .mo-split .mo-wi,
  :root.mo [data-mo],
  :root.mo .reveal{opacity:1!important;transform:none!important;clip-path:none!important;transition:none!important}
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE STACK — sheets on a table
   ═══════════════════════════════════════════════════════════════════════════
   The four moves above animate things INSIDE a section. This animates the
   sections themselves, and it is the one the page was missing: a section does
   not slide away when you leave it, it stays pinned to the screen while the
   next one rises and covers it — scaled back and dimmed underneath, the way a
   sheet of paper laid on a desk covers the one before it.

   Everything here is scoped to :root.mo, so with JS off or reduced motion on,
   the page is an ordinary stack of sections in normal flow.               */

/* An opaque ground on every section is the precondition. A transparent
   section scrolling over another shows both at once, which reads as a bug,
   not a fold. The dark sections already paint their own. */
/* An opaque ground on a stacked section is the precondition for the stack: a
   sheet has to cover the one it is laid over. It is also what stops the fluid
   showing through, so it is asked for by NAME rather than applied to every
   section on the page — the sections that do not stack stay transparent and
   keep the colour behind them. */
:root.mo .hpage main > section{position:relative}
:root.mo .hpage main > section.stk,
/* …and the section that rises OVER a pinned sheet, which is doing the covering
   whether or not it is a pinned sheet itself. Without this the panel underneath
   shows straight through its replacement — measured with #solutions, which is
   too tall to pin here and so was transparent while covering #proof. */
:root.mo .hpage main > section.stk + section{background:var(--ink)}
:root.mo .hpage main > section.on-dark{background:#000}

/* Written long-hand — :root.mo .hpage main > section — for a reason. The ground
   rule above carries that same weight, so a plain `.stk` selector loses to it
   and the panel is handed position:relative instead of sticky. Nothing about
   the effect breaks loudly when that happens: the sections still scroll, the
   scale and the shade are still written every frame, and only a measurement
   shows the panels never actually pin. */
:root.mo .hpage main > section.stk{
  position:sticky;top:0;
  min-height:100svh;
  display:flex;flex-direction:column;justify-content:center;
  /* Anchored at the top edge, and that is a decision, not a default. Scaled
     about its centre, a pinned sheet pulls away from the top of the screen as
     it shrinks and opens a band of bare page under the nav. Anchored at the
     top, the edge stays welded to the viewport and only the sides and the
     bottom draw in — and the bottom is always behind the sheet covering it. */
  transform-origin:50% 0;
  /* The tell that it is a sheet and not a page: the top two corners are cut,
     and the section it is covering shows through the cut for the moment it
     takes to arrive. */
  border-radius:var(--r) var(--r) 0 0;
}
/* And the section that closes a run needs the same cut, since it is the last
   thing to come over the top of a pinned panel. */
:root.mo .hpage main > section.stk + section{border-radius:var(--r) var(--r) 0 0}

/* The shade cast by the sheet above. Opacity is written per frame by the
   scroll loop; it is a flat black at low alpha rather than a filter because
   brightness() on a full-screen section costs a repaint of the whole thing. */
:root.mo .hpage main > section.stk::after{
  content:"";position:absolute;inset:0;
  background:#000;opacity:var(--stk-dim,0);
  border-radius:inherit;pointer-events:none;z-index:40;
}

/* The stacked sections have to fit one screen for the pin to read — a panel
   taller than the viewport scrolls to its end before it ever sticks. These
   four were laid out to a padding, so they are given the screen instead and
   their content is centred in it. */
:root.mo .hpage #proof,
:root.mo .hpage #projects,
:root.mo .hpage #clients,
:root.mo .hpage #about{padding-top:clamp(48px,6vh,90px);padding-bottom:clamp(48px,6vh,90px)}

@media(prefers-reduced-motion:reduce){
  :root.mo .stk{position:static;transform:none!important;min-height:0}
  :root.mo .hpage main > section.stk::after{display:none}
}

/* The scroll-spy has to know where each section SITS in the page, not where it
   is currently stuck to the screen — so site.js measures with this class on the
   body and sticky switched off for one frame. The class has been asserted there
   for a long time with nothing on this side to answer it, which cost nothing
   while only inner stages pinned and every section was static. The stack makes
   the sections themselves pin, so the rule now has to exist and has to list
   everything that pins. */
body.is-measuring .stk,
body.is-measuring .arc-stage,
body.is-measuring .room-stage,
body.is-measuring .exp-panel{position:static!important}

/* ═══════════════════════════════════════════════════════════════════════════
   THE ECOSYSTEMS BENTO
   ═══════════════════════════════════════════════════════════════════════════
   Six systems, laid out as a bento: one tall card holding the flagship, one
   wide card, and four at equal weight. It replaces a tab strip that showed one
   photograph at a time and hid the other five behind a dwell timer — six things
   the client sells, five of them invisible at any moment, and a timer deciding
   which. The grid shows all six at once and lets the eye choose.

   Nothing here is scoped to html.mo: this is the layout, not the motion, and it
   has to stand up with scripts off.                                          */
.eco{background:var(--ink)}
.eco-lede{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:clamp(20px,4vw,60px);flex-wrap:wrap;
  margin-bottom:clamp(24px,3vw,44px);
}
.eco-lede-copy{flex:1 1 auto;max-width:62ch}
.eco-lede h2{
  font-size:clamp(28px,3vw,40px);font-weight:600;color:var(--fg);
  letter-spacing:var(--tracking-h1);line-height:1.06;margin:14px 0 0;
}
.eco-lede h2 b{font-weight:600;color:var(--fg)}   /* one weight; the accent was a second voice */
.eco-lede .lead{margin:14px 0 0;color:var(--soft);max-width:46ch}
.eco-cta{flex:none;margin-top:26px}

/* The bento. Four columns: the flagship holds the left column over both rows,
   the wide card takes the middle of the top row, and the remaining four sit at
   equal weight. Named areas rather than spans so the shape is legible here
   instead of assembled from six separate line numbers. */
.bento{
  display:grid;gap:clamp(10px,1vw,16px);
  grid-template-columns:repeat(4,minmax(0,1fr));
  grid-template-areas:
    "a b b c"
    "a d e f";
}
.bento > :nth-child(1){grid-area:a}
.bento > :nth-child(2){grid-area:b}
.bento > :nth-child(3){grid-area:c}
.bento > :nth-child(4){grid-area:d}
.bento > :nth-child(5){grid-area:e}
.bento > :nth-child(6){grid-area:f}

.bx{
  display:flex;flex-direction:column;
  background:rgb(var(--surf2));
  border-radius:var(--r);
  padding:clamp(18px,1.7vw,26px);
  min-width:0;
  scroll-margin-top:calc(var(--navh) + 14px);   /* the footer links land here */
}

/* The label row: what it is on the left, where it sits in the six on the
   right. Micro type, wide tracking, and the same grey as every other eyebrow
   on the site — it is a caption, not a heading. */
.bx-top{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  margin-bottom:clamp(14px,1.4vw,20px);
}
.bx-label{display:inline-flex;align-items:center;gap:9px;min-width:0}
.bx-ic{display:inline-flex;color:var(--faint)}
.bx-ic svg{width:16px;height:16px;stroke-width:3.4}
.bx-tag,.bx-no{
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint);
  white-space:nowrap;
}
.bx-no{font-variant-numeric:tabular-nums}

.bx-shot{
  margin:0;overflow:hidden;border-radius:var(--r-sm);background:var(--ink2);
  aspect-ratio:16 / 10;
}
.bx-shot img{width:100%;height:100%;object-fit:cover;display:block}

.bx-foot{margin-top:clamp(14px,1.4vw,20px)}
.bx-title{
  font-family:var(--display);font-weight:600;color:var(--fg);
  font-size:clamp(16px,1.25vw,19px);letter-spacing:-.01em;line-height:1.25;margin:0;
}
.bx-copy{
  margin:8px 0 0;color:var(--soft);
  font-size:clamp(13px,1vw,15px);line-height:1.5;letter-spacing:-.01em;
}

/* The flagship. Its photograph takes whatever height the column has left over
   after the label and the copy, which is what makes the left column read as one
   tall picture rather than a stretched small card. */
.bx-tall .bx-shot{flex:1 1 auto;aspect-ratio:auto;min-height:180px}
.bx-tall .bx-lead{
  font-family:var(--display);font-weight:600;color:var(--fg);margin:0;
  font-size:clamp(24px,2.3vw,34px);letter-spacing:var(--tracking-h);line-height:1.1;
}
.bx-tall .bx-copy{margin-top:12px;max-width:34ch}

/* The wide card reads across instead of down: copy on one side, picture on the
   other, so a card twice as wide does not become a card with a stretched photo
   and a lot of air. */
.bx-wide .bx-split{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);
  gap:clamp(14px,1.6vw,24px);align-items:stretch;flex:1 1 auto;
}
.bx-wide .bx-foot{margin-top:0;display:flex;flex-direction:column;align-items:flex-start}
.bx-wide .bx-shot{aspect-ratio:auto;height:100%;min-height:150px}
.bx-link{
  margin-top:auto;padding-top:14px;
  font-size:14px;color:var(--link);text-decoration:none;letter-spacing:-.01em;
  display:inline-flex;align-items:center;gap:7px;
}
.bx-link:hover{text-decoration:underline}
.bx-link .arr{transition:transform .4s var(--ease)}
.bx-link:hover .arr{transform:translateX(3px)}

/* Two columns at tablet width — four is too many for a card that has to hold a
   photograph, a name and two lines of copy. The flagship gives up its tall
   column here and becomes a wide one, which is the only shape that works when
   the grid is half as wide. */
@media(max-width:1099px){
  .bento{
    grid-template-columns:repeat(2,minmax(0,1fr));
    grid-template-areas:"a a" "b b" "c d" "e f";
  }
  .bx-tall{flex-direction:row;gap:clamp(14px,2vw,24px);flex-wrap:wrap}
  .bx-tall .bx-top{width:100%;margin-bottom:0}
  .bx-tall .bx-shot{flex:1 1 260px;aspect-ratio:16/10;min-height:0}
  .bx-tall .bx-foot{flex:1 1 260px;align-self:center}
}
@media(max-width:640px){
  .bento{grid-template-columns:1fr;grid-template-areas:"a" "b" "c" "d" "e" "f"}
  .bx-tall{flex-direction:column}
  /* THE PHOTOGRAPH WAS WIDER THAN ITS CARD — 416px of picture in a 346px card,
     running off the right of the screen. `flex: 1 1 260px` was written for the
     two-column layout, where the basis is a WIDTH. Turned upright here, the
     basis becomes a HEIGHT, and aspect-ratio:16/10 then computed the width from
     it: 260 x 1.6 = 416. The basis has to go with the direction it was written
     for; upright, the width is simply the card. */
  .bx-tall .bx-shot,
  .bx-tall .bx-foot{flex:0 0 auto;width:100%}
  .bx-tall .bx-shot{aspect-ratio:16/10;height:auto}
  /* The wide card reads across on a desktop — copy, then picture beside it.
     Stacked upright that order puts the photograph below the link, so this one
     card leads with words while the other five lead with a picture. Ordering
     the shot first gives the whole grid one shape on a phone: label, picture,
     name, copy. */
  .bx-wide .bx-split{grid-template-columns:1fr}
  .bx-wide .bx-shot{order:-1}
  .bx-wide .bx-shot{min-height:0;aspect-ratio:16/10}
  /* Not a full-width slab. The sticky "Start Your Project" bar is already on
     screen at the bottom of every phone view; a second identical blue block at
     the top of the section is the same offer shouted twice. */
  .eco-cta{align-self:flex-start}
}

/* ═══════════════════════════════════════════════════════════════════════════
   LIQUID ETHER — one fluid, behind the whole site
   ═══════════════════════════════════════════════════════════════════════════
   WHY IT IS BEHIND AND NOT ON TOP. The first site-wide version composited it
   over the page with soft-light, to avoid putting moving colour between the
   reader and the text. It was invisible, and the arithmetic says it had to be:
   soft-light preserves near-whites, so on the Fog ground it moved
   rgb(245,245,247) to rgb(236,244,250) — ten points, at any opacity. To put
   colour on a light page you have to darken it, and anything that darkens
   between the eye and the text takes the contrast with it.

   So the fluid sits behind the page, at the strength it had in the closing
   band, and the page's grounds are cleared out of its way. The type is what
   changes instead: over moving colour, secondary grey is not available, so the
   copy that sits directly on the fluid is set in Space Black. That is the same
   decision already made for the closing band, applied everywhere it now
   applies. */
.le-page{
  --le-stops:#e2e2e5,#7ab3ed,#0071e3;
  position:fixed;inset:0;z-index:0;
  pointer-events:none;                 /* every link and button stays live */
  opacity:0;transition:opacity 1.6s var(--ease);
}
.le-page.is-live{opacity:.62}
.le-canvas{display:block;width:100%;height:100%}

/* The page rides above it. html keeps the Fog ground so there is still a base
   colour under everything; body and its sections get out of the way. */
html{background:var(--ink)}
body{background:transparent}
/* ONLY THE THINGS THAT ARE NOT ALREADY POSITIONED GET position:relative. The
   first version of this rule listed the nav, the language gate and the cookie
   bar alongside main and the footer — all five needed to sit above the fluid,
   so all five were given the same declaration. But those three are already
   `position: fixed`, and `relative` overrode it: the nav stopped being fixed
   and scrolled away with the page on every page of the site. A positioned
   element only needs the z-index; it must keep the position it already has. */
main,.footer{position:relative;z-index:1}
.nav,.lgate,.cbar{z-index:80}
main > section,
main > section.section,
main > header{background:transparent}
/* The dark sections keep their own ground — the fluid is a light-page effect
   and would be lost inside them anyway. */
main > section.on-dark,
.hero{background:#000}

@media(prefers-reduced-motion:reduce){ .le-page{display:none} }

/* OVER MOVING COLOUR, SECONDARY GREY IS NOT AVAILABLE. --soft #6e6e73 measures
   4.66:1 on the Fog ground — a tenth of a point above the AA floor — and fell
   to 1.91:1 against the strongest blue the fluid produces. Copy that sits
   directly on the fluid takes Space Black, which holds 6.3:1 against the same
   blue. Copy inside a white card is unaffected: the card is its own ground. */
main > section > .wrap .lead,
main > section > .wrap .eyebrow,
.section-head .lead,
.section-head .eyebrow,
.cta-band .eyebrow,
.cta-band .lead{color:var(--fg)}

/* ═══════════════════════════════════════════════════════════════════════════
   THE MASKED HERO — the curtain, and everything it holds back
   ═══════════════════════════════════════════════════════════════════════════
   Every hidden state here is scoped to .hero.is-intro, a class the script adds.
   Nothing is held back in the base sheet, so with JS off, on a repeat visit, or
   under reduced motion the hero is simply itself from the first frame — the
   curtain is something that happens, never something that has to be undone. */

.mh-defs{position:absolute;width:0;height:0;overflow:hidden}
.mh-w{display:inline-block;white-space:pre}
.mh-base{display:inline-block;width:0;height:0}   /* sits on the baseline */

/* The media fills the HERO, not the headline — same box, same object-fit as the
   background about to appear behind it, so the letters are a window onto the
   exact frame that will fill the screen. */
.mh-fill{
  position:absolute;inset:0;z-index:2;
  pointer-events:none;
  opacity:1;transition:opacity .62s var(--ease);
  /* The floor the picture is screened over — see masked-hero.js for the
     measurements. Alpha is driven per frame so it unwinds with the rest. */
  background:rgba(74,79,87,var(--mh-floor,1));
  isolation:isolate;                 /* the blend stays inside the letters */
}
.mh-media{position:absolute;inset:0;will-change:transform;mix-blend-mode:screen}
.mh-media img{
  width:100%;height:100%;object-fit:cover;display:block;
  user-select:none;-webkit-user-drag:none;
}

/* --- held back ---------------------------------------------------------- */
.hero.is-intro{background:#000}
.hero.is-intro .hero-bg{opacity:0;transition:opacity .9s var(--ease)}
.hero.is-intro h1{position:relative;z-index:3;color:transparent}
.hero.is-intro h1 b,
.hero.is-intro h1 .line b{color:transparent}
.hero.is-intro .eyebrow,
.hero.is-intro .hero-sub,
.hero.is-intro .hero-cta,
.hero.is-intro .hero-regions,
.hero.is-intro .hero-foot{
  opacity:0;transform:translate3d(0,14px,0);
}

/* --- and let go ---------------------------------------------------------- */
.hero.is-open .hero-bg{opacity:1}
.hero.is-open .mh-fill{opacity:0}
.hero.is-open h1,
.hero.is-open h1 b{transition:color .62s var(--ease)}
.hero.is-open h1{color:var(--fg)}
.hero.is-open h1 .line b{color:var(--sky)}
.hero.is-open .eyebrow,
.hero.is-open .hero-sub,
.hero.is-open .hero-cta,
.hero.is-open .hero-regions,
.hero.is-open .hero-foot{
  opacity:1;transform:none;
  transition:opacity .7s var(--ease),transform .7s var(--ease);
}
/* The order the hero is read in, not the order it is written in. */
.hero.is-open .eyebrow{transition-delay:.10s}
.hero.is-open .hero-sub{transition-delay:.19s}
.hero.is-open .hero-cta{transition-delay:.28s}
.hero.is-open .hero-regions{transition-delay:.37s}
.hero.is-open .hero-foot{transition-delay:.44s}

@media(prefers-reduced-motion:reduce){
  .mh-fill{display:none}
  .hero.is-intro,.hero.is-intro *{opacity:1!important;transform:none!important;color:revert}
}


/* ═══════════════════════════════════════════════════════════════════════════
   IN THEIR WORDS — the wall
   ═══════════════════════════════════════════════════════════════════════════
   Fourteen people, all of them on screen at once. It was a slider before, one
   face at a time on a dwell timer, which is the wrong instrument entirely: the
   value of this section is that there ARE fourteen of them, and a carousel
   shows you one and asks you to believe in the rest.

   THE LAYOUT IS THE CONTENT'S, NOT A TEMPLATE'S. The quotes run from 9 words to
   85, and four of these people gave no quote at all. A uniform grid would crush
   the longest, strand the shortest, and leave four cards with a hole in them.
   So the tile follows the person:

     · 55 words or more → a WIDE tile, portrait beside the words, because a long
       quote needs a measure it can breathe in
     · under 55 → a NOTE, portrait above and the line beneath it
     · no quote → a FACE: the photograph full-bleed with the name on it. For an
       influencer the face IS the credential, so a card with nothing to say
       becomes the card that says it loudest

   Ordered so a four-column grid tiles with no gaps: 2+1+1, 2+2, 1+1+2, 2+1+1,
   2+1+1. dense is set as a safety net for when a card is missing.            */
.vw{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  grid-auto-flow:dense;
  /* Every row the same height. Left to size themselves the rows came out at
     301, 404, 444 and 526px — driven by whichever card in each happened to hold
     the most text — and a wall of faces that changes rhythm every row reads as
     an accident rather than a grid. */
  grid-auto-rows:1fr;
  gap:clamp(10px,1vw,16px);
  list-style:none;margin:clamp(30px,4vw,52px) 0 0;padding:0;
}
.vw-card{
  display:flex;flex-direction:column;min-width:0;
  background:rgb(var(--surf2));
  border-radius:var(--r);
  overflow:hidden;                    /* the portrait meets the card's corners */
}
.vw-wide{grid-column:span 2;flex-direction:row}

.vw-shot{margin:0;overflow:hidden;background:var(--ink3);flex:none}
.vw-shot img{width:100%;height:100%;object-fit:cover;display:block}
/* The photograph takes whatever the words leave. A fixed square forced a card
   with nine words in it to be as tall as one with fifty, and put an arbitrary
   gap between the quote and the name; letting the portrait flex means the text
   sets its own height and the picture fills the rest. */
/* flex-basis 0, not auto. With auto the figure's base size is the square
   photograph's own 286px, so every note card asked for photo + quote + name and
   the tallest row came out at 526 — which then stretched the wide cards'
   portraits into a 234x526 sliver that cuts people's faces in half. From zero,
   the text sets the height and the picture takes what is left. */
/* 200, not 150. These are square headshots: at 150 the longest note card cropped
   its portrait to 1.9:1, which takes the forehead and the chin with it. The floor
   costs the wall about half a screen and keeps every face intact. */
.vw-note .vw-shot{flex:1 1 0;min-height:clamp(190px,17vw,230px)}
.vw-face .vw-shot{flex:1 1 0}
.vw-wide .vw-shot{width:42%;align-self:stretch}

.vw-body{
  display:flex;flex-direction:column;justify-content:space-between;
  gap:clamp(12px,1.4vw,18px);
  padding:clamp(16px,1.6vw,24px);
  flex:1 1 auto;min-width:0;
}
/* The quotation mark is drawn once, large and pale, as the section's motif —
   not on every card, where fourteen of them would be wallpaper. */
.vw-wide .vw-body{position:relative}
.vw-wide .vw-body::before{
  content:"\201C";
  position:absolute;top:clamp(-6px,-.4vw,0px);inset-inline-start:clamp(10px,1.2vw,18px);
  font-family:var(--display);font-size:clamp(54px,5vw,86px);line-height:1;
  color:var(--ink4);pointer-events:none;
}
.vw-wide .vw-quote{position:relative;padding-top:clamp(14px,1.6vw,22px)}

.vw-quote{
  margin:0;font-size:clamp(13px,1vw,15px);line-height:1.55;
  letter-spacing:var(--tracking-body-sm);color:var(--soft);
}
.vw-name{display:block}
.vw-name strong{
  display:block;font-family:var(--display);font-weight:600;color:var(--fg);
  font-size:clamp(14px,1.05vw,16px);letter-spacing:-.01em;line-height:1.3;
}
.vw-role{
  display:block;margin-top:3px;
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint);
}

/* A face card carries its name ON the photograph, over a scrim that only
   covers the bottom of it — the picture is the point. */
.vw-face{position:relative}
.vw-face .vw-shot{height:100%}
.vw-face .vw-name{
  position:absolute;inset-inline:0;bottom:0;z-index:1;
  padding:34px clamp(14px,1.4vw,20px) clamp(13px,1.3vw,17px);
  background:linear-gradient(0deg,rgba(0,0,0,.78) 0%,rgba(0,0,0,.34) 58%,transparent 100%);
}
.vw-face .vw-name strong{color:#fff}
.vw-face .vw-role{color:rgba(255,255,255,.74)}

@media(max-width:680px){
  /* A STRIP, NOT A STACK. Fourteen cards under one another is three screens of
     vertical scrolling through people you are meant to browse rather than read
     in order. On a phone they lie side by side and move on their own.

     Native overflow scrolling does the sliding, so a thumb drags it with real
     momentum and the browser owns the physics; the script only nudges it along
     when nobody is touching. scroll-snap stops it resting between two cards. */
  .vw{
    display:flex;
    grid-template-columns:none;
    grid-auto-rows:auto;
    overflow-x:auto;
    scroll-snap-type:x mandatory;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:none;
    /* NO BLEED. Running the strip to the screen edges meant guessing the
       section's gutter with a clamp, and the guess was 20px against an actual
       22 — which put the first card 2px from the edge while the heading above
       it sat at 22, and offset every scroll-snap point so a step jumped two
       cards instead of one. Inside the wrap the card edges line up with the
       heading for free and scroll position 0 is a real snap point. */
    padding-bottom:4px;
  }
  .vw::-webkit-scrollbar{display:none}
  .vw-card{
    flex:0 0 78%;max-width:320px;
    /* start, not centre. Centre-aligned, the first card's snap point sits at a
       positive offset, so returning to the beginning of the strip lands at 242
       rather than 0 and the left edge never lines up with the section's gutter.
       Aligning to the start gives every card the same left edge as the heading
       above it, and scroll position 0 becomes a real snap point. */
    scroll-snap-align:start;
    /* every card is one column of the strip: the desktop spans and the mobile
       ordering both have to be released or the flex line breaks apart */
    grid-column:auto;order:0;
  }

  /* THE CARD IS A HEADER AND A QUOTE. Side by side, a square portrait beside a
     ten-line quote leaves a column of empty card under the photograph —
     measured at 118px of picture in a 370px card. So it is the shape every
     messaging app uses: who said it across the top, what they said underneath.
     display:contents lifts the quote and the name out of their wrappers so all
     three sit on the card's own grid — no markup change, desktop untouched. */
  .vw-wide,.vw-note{
    display:grid;
    grid-template-columns:auto minmax(0,1fr);
    column-gap:12px;row-gap:10px;
    padding:14px 15px;align-items:center;
  }
  .vw-wide .vw-split,.vw-wide .vw-body,.vw-note .vw-body{display:contents}
  .vw-wide .vw-shot,.vw-note .vw-shot{
    grid-column:1;grid-row:1;
    width:64px;height:64px;aspect-ratio:1/1;min-height:0;
    border-radius:var(--r-sm);
  }
  .vw-wide .vw-name,.vw-note .vw-name{grid-column:2;grid-row:1;align-self:center}
  .vw-wide .vw-quote,.vw-note .vw-quote{grid-column:1 / -1;grid-row:2;font-size:13px;line-height:1.5}
  .vw-wide .vw-body::before{display:none}
  .vw-wide .vw-quote{padding-top:0}

  /* a face card is a picture and a name, so it keeps its portrait shape */
  .vw-face{aspect-ratio:3/4}
  .vw-face .vw-name{padding:26px 12px 11px}
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE RAIN — cards that fall
   ═══════════════════════════════════════════════════════════════════════════
   Fourteen cards arriving together is a grid appearing. Fourteen arriving on
   their own delays, from two directions, as you scroll past them, is weather.

   On the wall of voices the direction carries meaning: a card with words in it
   SWEEPS IN FROM THE SIDE, along the line you read it on; a card that is only a
   face DROPS FROM ABOVE. On the wall of products every panel is the same kind
   of thing, so there the axis is rolled per tile.

   AN ANIMATION, NOT A TRANSITION. A products tile owns
   `transition: flex-grow .8s` for its hover accordion, and transition is a
   shorthand — an entrance written as a transition replaces it and the accordion
   snaps instead of easing. That bug has already been shipped once from this
   file. Animations run on their own channel and leave it alone.            */
:root.mo [data-mo="rain"]{opacity:0}
:root.mo [data-mo="rain"].is-in{
  animation:mo-rain-x .86s cubic-bezier(.16,.84,.3,1) var(--rain-d,0s) both;
}
:root.mo [data-mo="rain"].rain-y.is-in{animation-name:mo-rain-y}
[dir="rtl"] :root.mo [data-mo="rain"].is-in,
:root.mo [dir="rtl"] [data-mo="rain"].is-in{animation-name:mo-rain-x-rtl}
[dir="rtl"] :root.mo [data-mo="rain"].rain-y.is-in,
:root.mo [dir="rtl"] [data-mo="rain"].rain-y.is-in{animation-name:mo-rain-y}

/* --rain-far varies the distance per card; without it every card travels the
   same span and the eye reads a machine rather than rain. */
@keyframes mo-rain-x{
  from{opacity:0;transform:translate3d(calc(var(--rain-far,1) * -64px),0,0)}
  to{opacity:1;transform:none}
}
@keyframes mo-rain-x-rtl{
  from{opacity:0;transform:translate3d(calc(var(--rain-far,1) * 64px),0,0)}
  to{opacity:1;transform:none}
}
@keyframes mo-rain-y{
  from{opacity:0;transform:translate3d(0,calc(var(--rain-far,1) * -72px),0)}
  to{opacity:1;transform:none}
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE TIMELINE UNFURLS FROM ITS OWN RAIL
   ═══════════════════════════════════════════════════════════════════════════
   Every entry used to rise 20px behind a wipe — the same gesture the whole
   site uses for a paragraph, which is what made this page read as generic. A
   timeline is not a stack of paragraphs: it is a line, and the line is already
   drawing itself down the page as you scroll.

   So the content is drawn OUT OF THE LINE. Each entry's copy unfurls sideways
   from the rail it is pinned to, left to right, and its photograph follows
   110ms behind — the delay was already there, it just had nothing worth
   waiting for. The dot lands first, as it always did.

   Written as clip-path on the inline axis so it mirrors for Arabic without a
   second rule: in RTL the rail is on the right and the entries unfurl the
   other way, because inset()'s second value is the RIGHT edge and the
   [dir] rule below swaps which edge starts closed. */
.tl-body,.tl-media{
  clip-path:inset(0 100% 0 0);
  transform:translateX(-22px);
  transition:clip-path .78s var(--ease),transform .78s var(--ease);
}
.tl-media{transform:translateX(22px)}      /* the picture comes from outside */
[dir="rtl"] .tl-body{clip-path:inset(0 0 0 100%);transform:translateX(22px)}
[dir="rtl"] .tl-media{clip-path:inset(0 0 0 100%);transform:translateX(-22px)}

.tl-item.is-live .tl-body,
.tl-item.is-live .tl-media{clip-path:inset(0 0 0 0);transform:none}
.tl-item.is-live .tl-media{transition-delay:.11s}

@media(prefers-reduced-motion:reduce){
  .tl-body,.tl-media{clip-path:none;transform:none;transition:none}
}

/* ═══════════════════════════════════════════════════════════════════════════
   ABOUT — type that assembles, pictures you are zoomed through
   ═══════════════════════════════════════════════════════════════════════════
   Scoped to html.ab-mo, which about-motion.js sets, so with no JS or reduced
   motion the page is simply itself. Every value below is driven by --ab-p or
   --ab-i, both written from the scroll loop — nothing here is a keyframe on a
   timer, so the movement tracks the scrollbar and reverses when you do. */

/* Words keep their boxes so a heading still breaks on word boundaries; the
   letters inside them are what move. */
.ab-w{display:inline-block;white-space:pre}
.ab-c{display:inline-block;will-change:transform,opacity}

.ab-mo .ab-split{
  /* the letters turn in real depth rather than being squashed vertically */
  perspective:620px;
  perspective-origin:50% 60%;
}
/* Each letter carries its own point on the stagger: --ab-i is its index and
   --ab-p is the heading's scroll progress, so the character's own progress is
   the section progress shifted by where it sits in the line. Clamped, so the
   last letter still finishes inside the window. */
.ab-mo .ab-split .ab-c{
  /* --ab-i is a fraction of the heading's length, so 1.55 - 0.55 = 1 exactly:
     the last character of every heading lands as its progress reaches 1, and no
     heading can be long enough to strand its own tail. */
  --d:clamp(0, calc((var(--ab-p, 0) * 1.55) - (var(--ab-i, 0) * 0.55)), 1);
  opacity:var(--d);
  transform:
    translate3d(0, calc((1 - var(--d)) * 0.52em), 0)
    rotateX(calc((1 - var(--d)) * -78deg));
  transform-origin:50% 100%;
}

/* The photograph is zoomed through: the frame is fixed and clips, the picture
   inside it is scaled and lifted by the loop. */
.ab-mo .ab-zoom{will-change:transform}
.ab-mo .ab-hero-media,
.ab-mo .tl-media,
.ab-mo .ab-lite-media,
.ab-mo .tm{overflow:hidden}
/* The team portrait is inside a card that also falls; the card owns the fall,
   the picture owns the zoom, and neither writes the other's property. */
.ab-mo .tm img{display:block;width:100%}

.ab-mo .ab-drift{will-change:transform}

@media(prefers-reduced-motion:reduce){
  .ab-mo .ab-split .ab-c{opacity:1;transform:none}
  .ab-mo .ab-zoom,.ab-mo .ab-drift{transform:none!important}
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE CALL SHEET — which office, before the phone dials
   ═══════════════════════════════════════════════════════════════════════════
   The closing call to action used to be a link straight to the Jordanian
   number. Mass has three offices and a visitor in Dubai or Baghdad was being
   sent abroad to reach a team that sits in their own city. The button now asks.

   It is a sheet on a phone (rising from the bottom edge, where a thumb is) and
   a small centred panel on a desktop — the same markup either way. */
.cs-wrap{position:fixed;inset:0;z-index:120;display:flex;align-items:center;justify-content:center}
.cs-wrap[hidden]{display:none}
.cs-veil{position:absolute;inset:0;background:rgba(0,0,0,.44);backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px)}
.cs-panel{
  position:relative;width:min(380px,calc(100vw - 32px));
  background:rgb(var(--surf2));border-radius:var(--r);
  padding:clamp(18px,2vw,24px);
  display:flex;flex-direction:column;gap:8px;
  animation:cs-in .34s var(--ease) both;
}
@keyframes cs-in{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:none}}
.cs-title{
  margin:0 0 6px;font-size:var(--text-body-sm);color:var(--soft);
  letter-spacing:var(--tracking-body-sm);
}
.cs-opt{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  min-height:56px;padding:10px 14px;                 /* a phone number is a thumb target */
  border-radius:var(--r-sm);background:var(--ink3);
  text-decoration:none;color:inherit;
  transition:background .22s var(--ease);
}
.cs-opt:hover,.cs-opt:focus-visible{background:var(--ink4)}
.cs-place strong{display:block;font-weight:600;color:var(--fg);font-size:var(--text-body)}
.cs-place small{display:block;margin-top:1px;font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}
.cs-num{font-size:var(--text-body-sm);color:var(--soft);white-space:nowrap;font-variant-numeric:tabular-nums}
.cs-cancel{
  margin-top:4px;min-height:48px;border:0;background:none;cursor:pointer;
  font:inherit;font-size:var(--text-body-sm);color:var(--soft);border-radius:var(--r-sm);
}
.cs-cancel:hover{color:var(--fg)}

@media(max-width:560px){
  /* a sheet from the bottom edge, which is where the thumb already is */
  .cs-wrap{align-items:flex-end}
  .cs-panel{
    width:100%;border-radius:var(--r) var(--r) 0 0;
    padding:20px 16px calc(20px + env(safe-area-inset-bottom));
    animation-name:cs-up;
  }
  @keyframes cs-up{from{opacity:0;transform:translateY(100%)}to{opacity:1;transform:none}}
}
@media(prefers-reduced-motion:reduce){
  .cs-panel{animation:none}
}

/* =====================================================================
   FIRST-VISIT GATE — a dialog, not a splash screen
   ---------------------------------------------------------------------
   It was a full-bleed opaque page: the site's own ground colour across the
   whole viewport, a 158px logo lockup, four stacked pills at 17px, and two
   bilingual labels stacked into four lines of chrome. For a two-tap
   preference that reads as a wall — and it hides the site completely at the
   one moment a first-time visitor is deciding whether to stay.

   Same markup, same code path, rendered as what it actually is: a 340px card
   over the page, with the page still there and blurred behind it. The panel
   scales in rather than the whole screen fading, which is the difference
   between a popup arriving and a page loading.

   THESE RULES SIT AT THE END OF THE SHEET DELIBERATELY. Four earlier blocks
   style .lgate-pick and .lgate-kicker at the same specificity, and one blanket
   rule near the very bottom re-declares .lgate's z-index; from anywhere higher
   up, half of this would lose on source order alone.
   ===================================================================== */
.lgate{
  background:rgba(0,0,0,.28);
  -webkit-backdrop-filter:blur(18px) saturate(140%);
  backdrop-filter:blur(18px) saturate(140%);
  padding:24px;
  z-index:9000;                 /* re-asserted: `.nav,.lgate,.cbar{z-index:80}` */
}
:root[data-theme="dark"] .lgate{background:rgba(0,0,0,.52)}
:root[data-theme="dark"] .lgate-panel{background:rgb(var(--surf2));border:1px solid var(--line2);box-shadow:0 30px 70px -28px rgba(0,0,0,.8)}

.lgate-panel{
  width:min(340px,100%);max-width:none;
  padding:26px 24px 24px;
  /* White, not var(--surf). --surf is the PAGE ground — Fog in light and true
     black in dark — so a panel painted with it disappears: measured in dark it
     came out rgb(0,0,0) sitting on a 52%-black scrim, a card with no visible
     edge. This is the same raised white the storage notice uses, with the dark
     side lifted to the raised surface and given a hairline. */
  background:#fff;
  border-radius:26px;
  box-shadow:0 30px 70px -28px rgba(0,0,0,.42);
  transform:scale(.94) translateY(10px);opacity:0;
  transition:transform .42s var(--ease),opacity .3s var(--ease);
}
.lgate.on .lgate-panel{transform:none;opacity:1}

/* the nav mark, not the full lockup — a tagline at 78px is unreadable anyway */
.lgate-logo{width:78px;height:auto;margin:0 auto 22px}

/* Both halves of each bilingual label on ONE line, separated by a middot. */
.lgate-lab{display:flex;align-items:baseline;justify-content:center;gap:7px;margin:0 0 10px}
.lgate-kicker,
.lgate-kicker + .lgate-kicker,
.lgate-kicker-lang{
  margin:0;font-family:var(--body);font-size:11.5px;font-weight:500;
  letter-spacing:var(--tracking-caption);text-transform:none;color:var(--faint);
}
/* The separator hangs off the FIRST label, not the second. On the second it
   sits at that element's own start edge — and the second label is dir="rtl",
   so its start edge is its right-hand side: the dot rendered after the
   Arabic rather than between the two. Anchored to the first label it lands
   between them in both trees, since the flex row reverses with the page. */
.lgate-lab .lgate-kicker:first-child::after{content:"·";margin-inline-start:7px;opacity:.5}

/* Appearance is a segmented control: one track, a thumb on the live choice.
   Two separate filled pills gave a secondary preference the same visual weight
   as the language buttons that actually dismiss the thing. */
.lgate-actions.lgate-themes{
  display:flex;gap:3px;margin:0 0 18px;padding:3px;
  background:var(--ink3);border-radius:var(--r-pill);flex-wrap:nowrap;
}
.lgate-actions .lgate-theme{
  flex:1 1 0;max-width:none;gap:6px;
  padding:8px 10px;border-radius:var(--r-pill);
  font-size:13px;font-weight:500;background:transparent;color:var(--soft);
  box-shadow:none;
}
.lgate-actions .lgate-theme i{width:11px;height:11px}
.lgate-actions .lgate-theme:hover{background:transparent;color:var(--fg)}
.lgate-actions .lgate-theme.is-on{
  background:rgb(var(--surf));color:var(--fg);box-shadow:0 1px 3px rgba(0,0,0,.12);
}
:root[data-theme="dark"] .lgate-actions.lgate-themes{background:#2c2c2e}
:root[data-theme="dark"] .lgate-actions .lgate-theme{background:transparent;color:var(--soft)}
:root[data-theme="dark"] .lgate-actions .lgate-theme.is-on{background:#48484a;color:var(--fg)}

/* Language is the primary action — it is what closes the gate. */
.lgate-actions:not(.lgate-themes){display:flex;gap:8px;margin:0;flex-wrap:nowrap}
.lgate-actions:not(.lgate-themes) .lgate-pick{
  flex:1 1 0;max-width:none;
  border-radius:var(--r-pill);
  font-size:16px;font-weight:600;letter-spacing:var(--tracking-body);
  padding:13px 14px;background:var(--ink3);color:var(--fg);
}
.lgate-actions:not(.lgate-themes) .lgate-pick:hover,
.lgate-actions:not(.lgate-themes) .lgate-pick:focus-visible{background:var(--blue);color:#fff}

/* Phone: the segmented buttons measured 35px tall, under the 44px a thumb
   actually needs. The language pills were already 49px; only this row was
   short, and it is the one you are meant to hit before choosing a language. */
@media(max-width:560px){
  .lgate-actions .lgate-theme{padding:13px 10px;font-size:14px}
}

@media(prefers-reduced-motion:reduce){
  .lgate-panel{transition:none;transform:none;opacity:1}
}

/* =====================================================================
   TOUCH TARGETS — the 44px floor, applied where the audit found it missed
   ---------------------------------------------------------------------
   Measured on a 375x812 phone across all eight pages. The site had no
   horizontal overflow, no overhanging elements and no broken images; what it
   had was controls a thumb cannot reliably hit. Footer column links came in at
   41px and the whole footer utility row at 40px — close enough to look fine
   and far enough to mis-tap, and they are stacked, so a miss lands on the
   neighbouring link rather than nothing. "Discover" inside a product tile was
   29px and the walk-through link 35px.

   The footer's bottom row is handled by its own rule further up, which owns
   those four controls at a specificity bare class selectors here cannot
   reach — raised there rather than fought with from down here.

   Every element here was already inline-flex and centred, so a min-height
   changes the box and nothing else — no layout mode switches, no reflow of the
   surrounding text.

   NOT INCLUDED, DELIBERATELY: links sitting inside a sentence — the
   info@we-mass.com address in the contact and privacy body copy, measured at
   17 and 20px. WCAG 2.5.8 exempts inline targets in a block of text precisely
   because the fix would be to break the paragraph around them, and a 44px tall
   link mid-sentence is worse for everyone than a small one. The skip link and
   the form honeypot are off-screen by design and are not targets at all.
   ===================================================================== */
@media(max-width:900px){
  .foot-col a{min-height:44px;display:flex;align-items:center}
  .logo{min-height:44px}
  .ptile-discover,.bx-link,.sup-plan-foot,.xp-skip{
    min-height:44px;display:inline-flex;align-items:center;
  }
  .ptile-discover{justify-content:center}

  /* 11px is below the floor for a label you are expected to read at arm's
     length — these are the bento tags and numbers and the voices-strip roles. */
  .bx-tag,.bx-no,.vw-role{font-size:12px}

  /* "Prefer email? info@we-mass.com" — inline in a sentence, so WCAG exempts
     it, but it is the fallback way to reach the company ON the contact page and
     it measured 17px tall. The note is a single short line of its own, so
     vertical padding on the inline box grows the hit area to 45px without
     touching the line box: nothing above or below it moves. */
  .cpage-note a{padding-block:14px}
}
