/* ============================================================
   NUMIDEA LABS — LANDING
   "Rooted in Numidia, built on ideas."
   Design-token system. :root holds the base palette (the "Neon
   Noir" set); Arcanum is the primary theme applied by default
   (<html data-theme="arcanum">). Each [data-theme] reskins the
   tokens, so every rule that uses var(--token) reskins for free.
   ============================================================ */
:root{
  /* colour, effect, type-per-theme and radius tokens live in themes.css */

  /* — TYPE — */
  --font-display:"Geist", "Geist Fallback", system-ui, sans-serif;
  --font-swash:"Geist", "Geist Fallback", system-ui, sans-serif;
  --font-body:"Geist", "Geist Fallback", system-ui, sans-serif;
  --font-mono:"Geist Mono", "Geist Mono Fallback", ui-monospace, monospace;
  --font-deco:"Cinzel", "Cinzel Fallback", Georgia, serif;
  --font-deco-dec:"Cinzel Decorative", "Cinzel Decorative Fallback", Georgia, serif;
  --font-ar-display:"IBM Plex Sans Arabic", "IBM Plex Sans Arabic Fallback", sans-serif;
  --font-ar-body:"IBM Plex Sans Arabic", "IBM Plex Sans Arabic Fallback", sans-serif;

  /* — SCALE / RADIUS / MOTION — */
  --s-2:8px;--s-3:12px;--s-4:16px;--s-5:24px;--s-6:32px;--s-7:48px;--s-8:64px;--s-9:96px;--s-10:128px;
  --r-pill:4px;--r-lg:22px;
  --ease-out:cubic-bezier(.16,1,.3,1);
  --dur-fast:.2s;--dur-base:.3s;--dur-slow:.7s;
  --max:1200px;--gutter:48px;
  /* Measured height of the fixed navbar. Everything that must clear it —
     hero top padding, anchor scroll offset — derives from this, so the
     three values can never drift apart again. */
  --nav-h:72px;   /* measured, not guessed — see _nav check in BRAND.md */
}

/* ============================================================
   THEMES — the palettes live in themes.css as tokens only. These
   rules are where the per-theme type, glow and kicker node are read;
   nothing in this file names a theme.
   ============================================================ */
html[dir="ltr"] :is(.hero-h,h2.sec,.contact h2,.mobile-menu a,.sb-txt b){
  font-family:var(--font-head);font-weight:var(--head-weight);letter-spacing:var(--head-tracking)}
html[dir="ltr"] .hero-h{font-size:var(--hero-fs);line-height:var(--hero-lh)}
html[dir="ltr"] :is(.hero-h .swash,.contact h2 em){font-family:var(--font-head-em);font-weight:var(--head-weight);padding-right:.06em}
html[dir="ltr"] .logo{font-family:var(--font-logo);font-weight:var(--logo-weight);letter-spacing:var(--logo-tracking)}
.btn-primary{box-shadow:var(--btn-glow)}
/* the kicker's node: a small rotated square after the rule; 0px hides it */
.kicker::after{content:"";width:var(--kicker-node);height:var(--kicker-node);margin-inline-start:calc(var(--kicker-node) * -.66);
  background:var(--crimson);transform:rotate(45deg);box-shadow:0 0 10px color-mix(in srgb,var(--crimson) 70%,transparent)}

/* smooth the moment of switching */
body,.navbar.scrolled,.svc-row,.proj,.card,.field input,.field textarea,.form,.capabilities{transition:background-color var(--dur-base) var(--ease-out),border-color var(--dur-base) var(--ease-out),color var(--dur-base) var(--ease-out)}

/* ---------- theme switcher ---------- */
.theme-switch{position:relative}
.theme-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:30px;border:1px solid var(--hairline-2);border-radius:var(--r-pill);background:transparent;cursor:pointer;transition:all var(--dur-fast)}
.theme-btn:hover{border-color:var(--teal)}
.theme-btn .ic{width:15px;height:15px;border-radius:50%;background:conic-gradient(from 220deg,var(--crimson),var(--teal),var(--crimson));box-shadow:inset 0 0 0 1px rgba(255,255,255,.15)}
.theme-menu{position:absolute;top:calc(100% + 8px);right:0;min-width:172px;background:var(--navy);border:1px solid var(--hairline-2);border-radius:var(--r-md);padding:6px;display:none;flex-direction:column;gap:2px;z-index:130;box-shadow:0 22px 55px -22px rgb(var(--shade) / calc(.6 * var(--shade-k)))}
html[dir="rtl"] .theme-menu{right:auto;left:0}
.theme-menu.open{display:flex}
.theme-menu button{display:flex;align-items:center;gap:10px;padding:9px 10px;border:none;background:transparent;color:var(--muted);font-family:var(--font-mono);font-size:12px;border-radius:var(--r-sm);cursor:pointer;text-align:left;transition:all var(--dur-fast)}
html[dir="rtl"] .theme-menu button{text-align:right}
.theme-menu button:hover{background:rgba(127,127,127,.10);color:var(--ice)}
.theme-menu button[aria-checked="true"]{color:var(--teal-text)}
/* the current theme is marked by a shape, not by its colour alone (1.18:1 from
   its neighbours in Arcanum). aria-checked already names it, so the mark is
   kept out of the accessible name where the alt-text syntax is understood. */
.theme-menu button::after{content:"";flex:none;width:1em;margin-inline-start:auto}
.theme-menu button[aria-checked="true"]::after{content:"✓";content:"✓" / "";text-align:center;font-weight:700}
.theme-menu .dot{width:15px;height:15px;border-radius:50%;flex:none;box-shadow:inset 0 0 0 1px rgba(127,127,127,.4)}
.theme-menu .dot.arcanum{background:linear-gradient(135deg,#34D0E8,#E6B450)}
.theme-menu .dot.noir{background:linear-gradient(135deg,#E5484D,#0B0B0E)}
.theme-menu .dot.daylight{background:linear-gradient(135deg,#F6F3EC,#8C5E0A)}
.theme-menu .dot.mono{background:linear-gradient(135deg,#F5F5F5,#0A0A0A)}
.theme-menu .dot.engineering{background:linear-gradient(135deg,#15181B 48%,#FF5B1F 52%)}
.theme-menu .dot.altneon{background:linear-gradient(135deg,#FF3EA5,#3EE6FF)}

*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;scroll-padding-top:calc(var(--nav-h) + 15px)}
body{
  background:var(--void);color:var(--ice);
  font-family:var(--font-body);font-size:16px;line-height:1.6;
  -webkit-font-smoothing:antialiased;overflow-x:hidden}
/* Arabic switches the whole document's type families */
html[dir="rtl"] body{font-family:var(--font-ar-body)}
/* Arabic never goes through the mono face. Geist Mono has no Arabic glyphs, so
   every mono label (kickers, buttons, legends, durations) fell back to a
   system monospace and set Arabic letters one cell apart; listing Plex Arabic
   right after Geist Mono hands Arabic glyphs to it while Latin and digits keep
   the mono face. Tracking breaks the joins of a cursive script outright, so
   it is off for the whole Arabic page (the Latin logo keeps its own). */
html[dir="rtl"]{--font-mono:"Geist Mono", "IBM Plex Sans Arabic", "IBM Plex Sans Arabic Fallback", "Geist Mono Fallback", ui-monospace, monospace}
html[dir="rtl"] body :not(.logo, .logo *){letter-spacing:0!important}
html[dir="rtl"] .font-swap-display{font-family:var(--font-ar-display)}
/* An Arabic run on a French or English page (the ع language button, anything
   marked lang="ar") takes the Arabic face too, not whatever the system has. */
html[dir="ltr"] [lang="ar"]{font-family:var(--font-ar-body);letter-spacing:0}

/* layered gradient atmosphere */
body::before{
  content:"";position:fixed;inset:0;z-index:0;pointer-events:none;background:var(--atmos)}
/* The construction grid — the drawing-sheet surface the title block sits on.
   Fixed and masked so it establishes the sheet at the top of the viewport and
   is gone by the fold: a full-height grid competes with body copy. */
body::after{
  content:"";position:fixed;inset:0;z-index:0;pointer-events:none;opacity:var(--grid-opacity);
  background-image:linear-gradient(var(--grid-color) 1px,transparent 1px),linear-gradient(90deg,var(--grid-color) 1px,transparent 1px);
  background-size:var(--grid-size) var(--grid-size);
  -webkit-mask-image:linear-gradient(#000,#000 32%,transparent 76%);
  mask-image:linear-gradient(#000,#000 32%,transparent 76%)}
.wrap{position:relative;z-index:1;max-width:var(--max);margin:0 auto;padding:0 var(--gutter)}
section{padding:var(--s-9) 0;border-top:1px solid var(--hairline);position:relative;z-index:1}

a{color:inherit}
img{max-width:100%;display:block}
:focus-visible{outline:2px solid var(--teal);outline-offset:3px;border-radius:2px}
/* visually-hidden helper for skip link / a11y */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
#main:focus{outline:none}
.skip{position:fixed;top:8px;inset-inline-start:8px;z-index:1000;background:var(--navy);color:var(--ice);padding:10px 16px;border-radius:var(--r-sm);border:1px solid var(--teal);transform:translateY(-150%);transition:transform var(--dur-base)}
.skip:focus{transform:translateY(0)}

/* shared section heads */
.kicker{font-family:var(--font-mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--crimson-text);display:flex;align-items:center;gap:var(--s-3);margin-bottom:var(--s-5)}
.kicker::before{content:"";width:28px;height:1px;background:var(--crimson)}
h2.sec{font-family:var(--font-display);font-weight:600;font-size:clamp(25px, 14.67px + 2.87vw, 56px);letter-spacing:-.02em;line-height:1.04;margin-bottom:var(--s-7);max-width:11.934em}
/* section-head emphasis kept quiet — the flourish is reserved for hero + contact */
h2.sec em{font-family:inherit;font-style:normal;color:inherit}

/* ---------- scroll progress ---------- */
.progress{position:fixed;top:0;left:0;height:3px;width:100%;transform:scaleX(0);transform-origin:0 50%;background:var(--grad-blood);box-shadow:0 0 10px color-mix(in srgb,var(--crimson) 60%,transparent);z-index:200;will-change:transform}
html[dir="rtl"] .progress{transform-origin:100% 50%}

/* ---------- navbar ---------- */
.navbar{position:fixed;top:0;left:0;right:0;z-index:100;transition:background var(--dur-slow) var(--ease-out),backdrop-filter var(--dur-slow),border-color var(--dur-slow)}
.navbar .bar{display:flex;align-items:center;justify-content:space-between;max-width:var(--max);margin:0 auto;padding:18px var(--gutter)}
.navbar .nav-inner{border-bottom:1px solid transparent;transition:border-color var(--dur-slow)}
.navbar.scrolled{background:var(--nav-glass);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
.navbar.scrolled .nav-inner{border-color:color-mix(in srgb,var(--teal) 18%,transparent)}
.logo{font-family:var(--font-display);font-weight:600;font-size:22px;letter-spacing:-.01em;text-decoration:none;white-space:nowrap}
.logo i{font-family:var(--font-swash);font-style:normal;font-weight:600;color:var(--teal-text);transition:color var(--dur-base)}
.logo:hover i{filter:hue-rotate(40deg) saturate(1.3)}
.cursor{display:inline-block;width:9px;height:20px;background:var(--crimson);margin-left:5px;transform:translateY(3px);box-shadow:var(--glow-crimson)}

.nav-links{display:flex;gap:26px;align-items:center}
.nav-links a{font-family:var(--font-mono);font-size:13px;color:var(--muted);text-decoration:none;transition:color var(--dur-fast)}
.nav-links a:hover{color:var(--teal-text)}
.nav-right{display:flex;align-items:center;gap:var(--s-4)}
.lang{display:inline-flex;gap:2px;font-family:var(--font-mono);font-size:12px;border:1px solid var(--hairline-2);border-radius:var(--r-pill);overflow:hidden}
.lang button{font-family:var(--font-mono);font-size:12px;background:transparent;border:none;color:var(--muted);padding:5px 9px;cursor:pointer;transition:all var(--dur-fast)}
.lang button[aria-pressed="true"]{color:var(--on-teal);background:var(--teal)}
.lang button:hover{color:var(--ice)}
.lang button[aria-pressed="true"]:hover{color:var(--on-teal)}
.menu-toggle{display:none;background:transparent;border:1px solid var(--hairline-2);border-radius:var(--r-sm);color:var(--ice);width:44px;height:44px;cursor:pointer;font-size:18px}

/* mobile full-screen menu */
/* `justify-content:safe center` + overflow-y, not plain `center`.
   Plain centring distributes surplus out of BOTH ends, so once the links
   exceeded the viewport the first two sat at negative offsets and no
   amount of scrolling could reach them (scrollTop stayed pinned at 0).
   In landscape that made two nav items and the language switcher
   permanently unreachable. `safe` collapses the overflowing side instead. */
.mobile-menu{position:fixed;inset:0;z-index:150;background:var(--grad-noir);display:flex;flex-direction:column;justify-content:safe center;gap:var(--s-5);
  padding:calc(var(--gutter) + env(safe-area-inset-top)) calc(var(--gutter) + env(safe-area-inset-right)) calc(var(--gutter) + env(safe-area-inset-bottom)) calc(var(--gutter) + env(safe-area-inset-left));
  overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
  transform:translateX(100%);transition:transform var(--dur-base) var(--ease-out);visibility:hidden}
/* The pill carries overflow:hidden, which nullifies its automatic minimum
   size — as a flex item it was the only one able to shrink, and collapsed
   to a 2px sliver while its 44px buttons were clipped away inside it. */
.mobile-menu .lang{flex:none;align-self:flex-start}
/* the links sit in a <nav> landmark inside the dialog; it stacks like the dialog did */
.mobile-menu nav{display:flex;flex-direction:column;gap:inherit}
.mobile-menu.open{transform:translateX(0);visibility:visible}
/* in Arabic the menu slides in from the reading start (the left) */
html[dir="rtl"] .mobile-menu:not(.open){transform:translateX(-100%)}
.mobile-menu a{font-family:var(--font-display);font-weight:600;font-size:40px;color:var(--ice);text-decoration:none;opacity:0;transform:translateY(14px)}
html[dir="rtl"] .mobile-menu a{font-family:var(--font-ar-display)}
.mobile-menu .lang{opacity:0}
.mobile-menu.open a,.mobile-menu.open .lang{animation:stagger .5s var(--ease-out) forwards}
.mobile-menu.open a:nth-of-type(1){animation-delay:.05s}
.mobile-menu.open a:nth-of-type(2){animation-delay:.1s}
.mobile-menu.open a:nth-of-type(3){animation-delay:.15s}
.mobile-menu.open a:nth-of-type(4){animation-delay:.2s}
.mobile-menu.open a:nth-of-type(5){animation-delay:.25s}
.mobile-menu.open a:nth-of-type(6){animation-delay:.3s}
.mobile-menu.open a:nth-of-type(7){animation-delay:.35s}
.mobile-menu.open .lang{animation-delay:.4s}
@keyframes stagger{to{opacity:1;transform:translateY(0)}}
.mobile-menu .lang{margin-top:var(--s-4);align-self:flex-start}
.mobile-menu .close{position:absolute;top:20px;inset-inline-end:24px;background:transparent;border:none;color:var(--ice);font-size:34px;cursor:pointer}

/* ---------- hero ---------- */
/* overflow:hidden is for the glows, but the hero has no inline padding, so it
   also cut the focus ring (2px at 3px offset) off every control flush with its
   edge: the CTAs, the pause button, the last rail tab. --ring-room widens the
   clip into the page gutter by the ring's reach and pads the content back, so
   nothing moves and a whole ring always fits inside the clip. */
.hero{--ring-room:6px;padding:calc(var(--s-10) + 40px) var(--ring-room) var(--s-9);margin-inline:calc(-1 * var(--ring-room));
  position:relative;overflow:hidden}
.hero-mesh{position:absolute;inset:0;z-index:0;pointer-events:none}
.hero-mesh span{position:absolute;border-radius:50%;filter:blur(40px);opacity:.55}
.hero-mesh .m1{width:460px;height:460px;background:radial-gradient(circle,color-mix(in srgb,var(--crimson) 50%,transparent),transparent 65%);top:-120px;inset-inline-end:-60px}
.hero-mesh .m2{width:420px;height:420px;background:radial-gradient(circle,color-mix(in srgb,var(--teal) 40%,transparent),transparent 65%);bottom:-140px;inset-inline-start:-40px;animation-delay:-6s}

.hero-grid{position:relative;z-index:1;display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,.75fr);gap:var(--s-8);align-items:center}
.eyebrow{font-family:var(--font-mono);font-size:12px;letter-spacing:.2em;text-transform:uppercase;color:var(--teal-text);display:block;margin-bottom:var(--s-5)}
.hero-h{font-family:var(--font-display);font-weight:600;font-size:clamp(31px, 13.33px + 4.91vw, 84px);line-height:.96;letter-spacing:-.02em}
/* Solid ink, not a gradient fill. A three-stop gradient across the primary
   headline is the loudest "generated" tell in the vocabulary, and it is not
   free: --grad-text's own stops measure 2.7:1 on noir and 3.1:1 on daylight
   against those backgrounds — under the 4.5 floor, on the largest text on the
   page. axe never caught it: gradient-filled text computes as
   color:transparent, so its contrast rule skips the node entirely. */
.hero-h .swash{font-family:var(--font-swash);font-style:normal;font-weight:600;color:var(--teal-text);padding-right:.08em}
html[dir="rtl"] .hero-h{font-family:var(--font-ar-display);line-height:1.3}
html[dir="rtl"] .hero-h .swash{font-family:var(--font-ar-display);font-style:normal;padding:0 .08em}
.lede{font-size:clamp(18px,1.9vw,23px);color:var(--body-strong);max-width:29.172em;margin-top:var(--s-5);line-height:1.5}
.lede b,.lede strong{color:var(--teal-text);font-weight:600}
.hero-cta{display:flex;flex-wrap:wrap;gap:var(--s-4);margin-top:var(--s-7)}




/* ============================================================
   THE SHEET — hero as a drafting plate
   Same dialect as scene/: mono labels, tight tracking, corner
   L-brackets, dimension lines, a title block carrying real values.
   Everything here is inked from tokens, so it reskins with the theme.
   ============================================================ */
/* The top edge must clear the fixed navbar or the corner brackets — the whole
   point of the frame — are painted underneath it and never seen. */
.sheet-frame{position:absolute;inset:calc(var(--nav-h) + 10px) var(--ring-room) var(--s-4);z-index:0;pointer-events:none;
  border:1px solid color-mix(in srgb,var(--muted) 15%,transparent)}
/* not placed in the wide layout's 12-column grid: its box is always the
   hero's padding box, so the inset above holds at every width */
.hero>.sheet-frame{grid-column:auto}
.sheet-frame .sf{position:absolute;width:15px;height:15px;border:1px solid var(--crimson);opacity:.75}
.sheet-frame .sf.tl{top:-1px;left:-1px;border-right:0;border-bottom:0}
.sheet-frame .sf.tr{top:-1px;right:-1px;border-left:0;border-bottom:0}
.sheet-frame .sf.bl{bottom:-1px;left:-1px;border-right:0;border-top:0}
.sheet-frame .sf.br{bottom:-1px;right:-1px;border-left:0;border-top:0}

/* a dimension line, annotated with where the workshop actually is */
.dim-line{display:flex;align-items:center;gap:12px;margin:var(--s-5) 0 0;max-width:20.4em;
  font-family:var(--font-mono);font-size:11px;letter-spacing:.18em;color:var(--faint)}
.dim-line i{flex:1;height:1px;background:color-mix(in srgb,var(--muted) 38%,transparent);position:relative}
.dim-line i::before,.dim-line i::after{content:"";position:absolute;top:-3px;width:1px;height:7px;
  background:color-mix(in srgb,var(--muted) 55%,transparent)}
.dim-line i::before{left:0}.dim-line i::after{right:0}
.dim-line b{font-weight:400;white-space:nowrap;color:var(--muted)}

/* the title block: a field strip along the bottom edge of the sheet */
.titleblock{position:relative;z-index:1;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
  margin-top:var(--s-8);border:1px solid color-mix(in srgb,var(--muted) 26%,transparent);
  background:color-mix(in srgb,var(--void) 55%,transparent);backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px)}
.tb-f{padding:11px 16px;border-inline-end:1px solid color-mix(in srgb,var(--muted) 18%,transparent);min-width:0}
.tb-f:last-child{border-inline-end:0}
.titleblock dt{font-family:var(--font-mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--faint);margin-bottom:5px}
.titleblock dd{font-family:var(--font-mono);font-size:13px;letter-spacing:.03em;color:var(--ice);
  display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;min-width:0}
.titleblock dd i{font-style:normal;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--teal-text)}
.tb-live{width:6px;height:6px;border-radius:50%;background:var(--teal);flex:none;align-self:center;
  box-shadow:0 0 0 0 color-mix(in srgb,var(--teal) 55%,transparent)}


@media(max-width:960px){
  .sheet-frame{display:none}   /* a frame that tight around a phone reads as a box, not a sheet */
  .titleblock{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:var(--s-7)}
  .tb-f{border-block-end:1px solid color-mix(in srgb,var(--muted) 18%,transparent)}
  .tb-f:nth-child(2n){border-inline-end:0}
  .tb-f:last-child{grid-column:1/-1;border-block-end:0}
  .dim-line{max-width:none}
}
@media(max-width:420px){
  .titleblock{grid-template-columns:minmax(0,1fr)}
  .tb-f{border-inline-end:0}
  .tb-f:nth-last-child(2){border-block-end:1px solid color-mix(in srgb,var(--muted) 18%,transparent)}
}
@media(prefers-reduced-motion:reduce){.tb-live{animation:none}}

/* ---------- hero entrance ----------
   The hero used to simply BE there. Staging it — brand line, then the
   headline, then the argument, then the proof — is what makes it read as
   authored rather than loaded. Opacity/transform only, so it composes
   with the parallax translate underneath. */
@media (prefers-reduced-motion:no-preference){
  .hero .eyebrow,.dim-line,.hero-cta,.titleblock{
    animation:hero-rise .9s var(--ease-out) both}
  /* The headline, the lede and the plate are the LCP candidates. Fading an LCP
     element in from opacity:0 means the browser does not credit it as painted
     until the fade is well under way: measured on Slow 3G, the plate's image
     was downloaded and visible at 3.6s but LCP was reported at 7.5s; with the
     fade removed, 3.7s. These three still rise, but they are never transparent. */
  .hero-h,.hero .lede,.plate-wrap{animation:hero-rise-solid .9s var(--ease-out) both}
  .hero .eyebrow{animation-delay:.05s}
  .hero-h{animation-delay:.14s}
  .hero .lede{animation-delay:.26s}
  .hero-cta{animation-delay:.36s}
  .plate-wrap{animation-delay:.30s;animation-duration:1.1s}
  .dim-line{animation-delay:.22s}
  .titleblock{animation-delay:.46s}
}
@keyframes hero-rise{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
@keyframes hero-rise-solid{from{transform:translateY(14px)}to{transform:none}}

/* ============================================================
   THE HERO PLATE
   One live client site at a time, at a size you can actually read.
   Screenshots are WIPED, never cross-faded — a dissolve muddies two
   flat UIs into a smear, while a hard edge keeps both razor sharp. That
   is the same rule scene/ is built on.
   ============================================================ */
.plate-wrap{position:relative;display:grid;gap:var(--s-4);align-content:start}
.plate{position:relative;aspect-ratio:16/10;overflow:hidden;border-radius:var(--r-md);
  background:var(--navy);border:1px solid var(--hairline-2);
  box-shadow:0 0 0 6px color-mix(in srgb,var(--void) 55%,transparent),
             0 42px 84px -38px rgb(var(--shade) / calc(.92 * var(--shade-k)))}
.plate .pl{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:top center;
  opacity:0;visibility:hidden}
.plate .pl.on{opacity:1;visibility:visible}
/* the leading edge of the wipe: a bright rule that crosses the plate */
.pl-wipe{position:absolute;inset:0;z-index:3;pointer-events:none;background:var(--void);
  transform:scaleX(var(--wipe,0));transform-origin:left center}
html[dir="rtl"] .pl-wipe{transform-origin:right center}
.pl-wipe::after{content:"";position:absolute;right:0;top:0;bottom:0;width:2px;
  background:var(--crimson-hi);box-shadow:0 0 20px 2px color-mix(in srgb,var(--crimson-hi) 75%,transparent);
  opacity:var(--wipeEdge,0)}
html[dir="rtl"] .pl-wipe::after{right:auto;left:0}
.pl-scan{position:absolute;inset:0;z-index:2;pointer-events:none;opacity:.18;
  background:repeating-linear-gradient(0deg,rgba(0,0,0,.5) 0 1px,transparent 1px 3px)}
/* drafting corner ticks */
.pk{position:absolute;width:14px;height:14px;z-index:4;border:1px solid var(--crimson)}
.pk.tl{top:-1px;left:-1px;border-right:0;border-bottom:0}
.pk.tr{top:-1px;right:-1px;border-left:0;border-bottom:0}
.pk.bl{bottom:-1px;left:-1px;border-right:0;border-top:0}
.pk.br{bottom:-1px;right:-1px;border-left:0;border-top:0}

.plate-meta{display:flex;align-items:baseline;gap:var(--s-4);flex-wrap:wrap;min-height:34px}
.pm-idx{font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;color:var(--faint);flex:none}
.pm-idx b{font-weight:400;color:var(--crimson-text)}
.pm-rot{position:relative;flex:1 1 auto;min-width:0;display:grid}
.pm-item{grid-area:1/1;display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  text-decoration:none;opacity:0;visibility:hidden;transform:translateY(5px);
  transition:opacity .4s var(--ease-out),transform .4s var(--ease-out)}
.pm-item.on{opacity:1;visibility:visible;transform:none}
.pm-item b{font-family:var(--font-display);font-weight:600;font-size:17px;color:var(--ice);letter-spacing:-.01em}
html[dir="rtl"] .pm-item b{font-family:var(--font-ar-display)}
.pm-item i{font-family:var(--font-mono);font-style:normal;font-size:11px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--teal-text)}
.pm-item:hover b{color:var(--crimson-text)}
.pm-live{flex:none;display:inline-flex;align-items:center;gap:7px;font-family:var(--font-mono);
  font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.pm-live i{width:6px;height:6px;border-radius:50%;background:var(--teal);
  box-shadow:0 0 0 0 color-mix(in srgb,var(--teal) 60%,transparent)}


.plate-ctl{display:flex;align-items:center;gap:10px}
.plate-rail{display:flex;gap:8px;flex:1}
.plate-rail button{flex:1;background:none;border:0;padding:9px 0 0;cursor:pointer;min-height:24px}
.plate-rail button i{display:block;height:2px;border-radius:2px;background:var(--hairline-2);
  transition:background var(--dur-base) var(--ease-out)}
.plate-rail button[aria-current="true"] i{background:var(--crimson)}
@media (hover:hover){.plate-rail button:hover i{background:var(--crimson-hi)}}   /* a tapped tab kept it after the deck moved on (r3/a) */
.plate-rail button:focus-visible i{outline:2px solid var(--teal);outline-offset:3px}
/* pause/play: pressed = paused, so the icon shows what a press will do next */
.pl-pause{flex:none;width:24px;height:24px;display:grid;place-items:center;padding:0;background:none;cursor:pointer;
  border:1px solid var(--hairline-2);border-radius:50%;color:var(--muted)}
.pl-pause:hover{color:var(--ice);border-color:var(--crimson-hi)}
.pl-pause:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.pl-pause svg{width:10px;height:10px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
.pl-pause .ic-play{fill:currentColor;stroke:none;display:none}
.pl-pause[aria-pressed="true"] .ic-play{display:inline}
.pl-pause[aria-pressed="true"] .ic-pause{display:none}
@media(prefers-reduced-motion:reduce){
  .pl-wipe{display:none}
  .pm-item{transition:none}
  .pm-live i{animation:none}
}

/* hero deck — three REAL client sites, fanned; links to #work */











/* ---------- buttons / pills (from system) ---------- */
.btn{font-family:var(--font-mono);font-size:14px;font-weight:700;padding:14px 28px;border-radius:var(--r-sm);border:1px solid transparent;cursor:pointer;transition:all var(--dur-base) var(--ease-out);text-decoration:none;display:inline-flex;align-items:center;gap:8px}
/* Flat, not a gradient sweep. A gradient-filled primary button is the same
   generated-looking vocabulary as the gradient headline was, and the diagonal
   fade made the label sit on two different backgrounds at once. */
.btn-primary{background:var(--crimson);color:var(--on-crimson);border-color:var(--crimson)}
.btn-primary:hover{box-shadow:var(--glow-crimson);transform:translateY(-2px)}
.btn-teal{background:transparent;color:var(--teal-text);border-color:var(--teal)} /* text-safe teal: on Daylight the raw teal is 3.0:1 */
.btn-teal:hover{box-shadow:var(--glow-teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}
.btn-ghost{background:transparent;color:var(--ice);border-color:var(--hairline-2)}
.btn-ghost:hover{border-color:var(--ice)}
.pill{font-family:var(--font-mono);font-size:11px;padding:5px 11px;border:1px solid var(--hairline-2);border-radius:var(--r-pill);color:var(--muted);background:var(--navy);display:inline-block}
.pill.live{color:var(--teal-text);border-color:var(--teal)}
/* a portfolio piece, not client work: dashed, and never hidden (see the phone rule) */
.pill.concept{border-style:dashed}
.pill.live::before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--teal);margin-inline-end:7px;box-shadow:0 0 8px var(--teal);vertical-align:middle}

/* ---------- proof bar ---------- */
.proof{background:var(--navy);border-radius:0}
.proof-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--s-6);text-align:center}
.stat{font-family:var(--font-display);font-weight:600;font-size:clamp(29px, 17.33px + 3.24vw, 64px);line-height:1;color:var(--ice)}
.stat-label{font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-top:var(--s-3)}
.proof-line{text-align:center;color:var(--muted);margin-top:var(--s-7);font-size:15px}


/* ---------- selected work ---------- */
/* ===== r3/b: client logos, the work cards' benefit line ===== */
/* "Ils nous ont confié leur site": the four delivered client sites, under the
   proof line. Each logo is the client's own file, as its site serves it
   (scripts/client-logos.mjs), and the page paints all four in ONE theme
   colour through a mask: no recoloured copies, and the row reskins with the
   six themes. The <img> carries the mask and keeps its alt; its own pixels
   are pushed out of its box (object-position) so only the theme colour
   shows through the silhouette. crossorigin on the <img> makes its request
   the same CORS request as the mask's, so each file downloads once. The
   mask is fetched as soon as the strip is styled, so the four files load
   with the page whatever loading="lazy" says (24 KB gzipped in all): keep
   them small (scripts/client-logos.mjs).
   Three logos are masked by their shape (alpha). Nomara's mark is a dark
   tile with a light glyph inside: as a shape it would be a blank square, so
   it is masked by its brightness, from the light-ink file its own site shows
   on a dark ground: glyph and wordmark take the colour, the tile stays a
   faint ground behind the glyph.
   Widths share one visual weight (width ~ aspect^0.65); Bordj Steel's solid
   block reads heavier than the wordmarks, so it sits a little under that.
   Four abreast never wraps (an orphan fourth logo at 601-680px did); below
   720px the row turns two by two. */
.clients{margin-top:var(--s-7);display:grid;justify-items:center;gap:var(--s-4)}
.cl-h{display:flex;align-items:center;gap:var(--s-4);margin:0;font-family:var(--font-mono);font-size:var(--t-label);
  font-weight:400;letter-spacing:var(--ls-label);text-transform:uppercase;line-height:1.4;color:var(--muted);text-align:center;
  text-wrap:balance}
.cl-h::before,.cl-h::after{content:"";flex:none;width:clamp(20px,5vw,64px);border-top:1px solid var(--hairline-2)}
.cl-row{list-style:none;margin:0;padding:0;display:flex;justify-content:center;align-items:center;max-width:100%;
  gap:var(--s-2) clamp(12px,3vw,44px)}
.cl-row li{display:flex;justify-content:center;min-width:0}
.cl{display:flex;align-items:center;justify-content:center;min-height:68px;padding:8px 12px;border-radius:var(--r-sm);
  color:var(--muted);transition:color var(--dur-fast)}
.cl:focus-visible{color:var(--ice)}
/* a phone keeps :hover on the last thing tapped: a tapped logo stayed lit
   among the grey ones, as if selected, when the visitor came back */
@media (hover:hover){.cl:hover{color:var(--ice)}}
.cl img{--cl-s:1;display:block;width:calc(var(--w) * var(--cl-s));max-width:100%;height:auto;
  background:currentColor;object-position:-9999px 0;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain}
.cl-bordjsteel img{--w:56px;-webkit-mask-image:url(clients/bordjsteel.svg);mask-image:url(clients/bordjsteel.svg)}
.cl-alliance img{--w:110px;-webkit-mask-image:url(clients/alliance.svg);mask-image:url(clients/alliance.svg)}
.cl-almaflow img{--w:124px;-webkit-mask-image:url(clients/almaflow.webp);mask-image:url(clients/almaflow.webp)}
.cl-nomara img{--w:136px}
/* Nomara needs a luminance mask (Chromium 120+, Firefox, WebKit's prefixed
   form). Where neither exists, an alpha mask would paint the opaque tile as
   a solid block and lose the glyph, so the file is shown as it is instead,
   in grey, on the dark ground its own site gives it (--shade is dark in
   every theme). */
@supports (mask-mode:luminance) or (-webkit-mask-source-type:luminance){
  .cl-nomara img{-webkit-mask-image:url(clients/nomara.webp);mask-image:url(clients/nomara.webp);
    -webkit-mask-source-type:luminance;mask-mode:luminance}
}
@supports not ((mask-mode:luminance) or (-webkit-mask-source-type:luminance)){
  .cl-nomara img{object-position:center;background:rgb(var(--shade));box-shadow:0 0 0 6px rgb(var(--shade));
    border-radius:2px;filter:grayscale(1)}
}
/* phones and small tablets: two by two, a little smaller, every cell the
   same width, and the pairs kept close enough to read as one group */
@media (max-width:720px){
  .cl-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));width:min(100%,440px);gap:var(--s-2) var(--s-4)}
  .cl{min-height:60px;width:100%}
  .cl img{--cl-s:.86}
}
/* A logo lands on its card, and the card says so in the client's colour.
   The pointer is still over the card after the jump, so the mark must beat
   :hover too. A card the reveal has not reached yet sits low by the
   reveal's rise (its translateY, 12px), and the browser scrolls to that
   box: after the reveal the card ended that much higher than the scroll
   padding allows, its top against the fixed nav. A targeted card is shown
   at once, so the jump measures the box where it stays. The same holds for
   every reveal that is itself an anchor target (the hero's #apercu, the
   #estimator): transition-property, not the transition shorthand, so the
   card's rule below keeps the 0.7s durations for its outline. */
.js-ready .reveal:target{opacity:1;transform:none;transition-property:none}
.js-ready .proj.reveal:target{opacity:1;transform:none;transition-property:border-color,box-shadow}
.proj:target,.proj:target:hover{border-color:var(--teal);box-shadow:0 0 0 1px var(--teal)}
.proj--brand:target,.proj--brand:target:hover{border-color:rgb(var(--brand));box-shadow:0 0 0 1px rgb(var(--brand))}
/* print keeps the silhouettes (backgrounds are dropped by default) */
@media print{.cl img{-webkit-print-color-adjust:exact;print-color-adjust:exact}}
/* forced colours replace backgrounds with Canvas: the silhouettes take the
   text colour instead, so they stay visible (the heading's rules are borders,
   which forced colours keep) */
@media (forced-colors:active){
  .cl img{forced-color-adjust:none;background:CanvasText}
  .cl:focus-visible img{background:LinkText}
}
@media (forced-colors:active) and (hover:hover){.cl:hover img{background:LinkText}}

/* What the client gets, in one line under the card's description. It links
   to the full list on the project sheet (scene/, "Ce que le site apporte");
   Nomara has no sheet, so its line is plain text. A diamond marks the line
   as an outcome, like the deliverables on the sheets; the arrow points the
   reading way. */
.proj .body .gain{position:relative;display:block;min-height:0;margin:0 0 var(--s-4);padding:var(--s-3) 0 0;padding-inline-start:20px;
  border-top:1px solid var(--hairline);font-size:var(--t-small);line-height:1.55;color:var(--body-strong);text-decoration:none}
.proj .body .gain::before{content:"";position:absolute;inset-inline-start:2px;top:calc(var(--s-3) + .5em);width:7px;height:7px;
  border:1.5px solid var(--teal);transform:rotate(45deg)}
.proj .body a.gain::after{content:"→" / "";display:inline-block;margin-inline-start:.4em;color:var(--teal-text);transition:translate var(--dur-fast)}
html[dir="rtl"] .proj .body a.gain::after{content:"←" / ""}
@media (hover:hover){
  .proj .body a.gain:hover{color:var(--ice)}
  .proj .body a.gain:hover::after{translate:3px 0}
  html[dir="rtl"] .proj .body a.gain:hover::after{translate:-3px 0}
}
@media (prefers-reduced-motion:reduce){.proj .body a.gain::after{transition:none}.proj .body a.gain:hover::after{translate:none}}

.work-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s-6)}
/* odd card count: center the trailing card so it never orphans with a void beside it */
.work-grid>.proj:last-child:nth-child(odd){grid-column:1 / -1;max-width:calc(50% - var(--s-6) / 2);margin-inline:auto}
.proj{background:var(--navy);border:1px solid var(--hairline);border-radius:var(--r-md);overflow:hidden;transition:transform var(--dur-slow) var(--ease-out),box-shadow var(--dur-base)}
.proj:hover{transform:translateY(-4px)}
.proj .thumb{aspect-ratio:16/10;background:var(--grad-blood);display:grid;place-items:center;position:relative;overflow:hidden;perspective:800px}
.proj .thumb::before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 70% 30%,color-mix(in srgb,var(--teal) 35%,transparent),transparent 60%);mix-blend-mode:screen;transition:opacity var(--dur-base)}
.proj:hover .thumb::before{opacity:1.4}
.proj .browser{position:absolute;top:0;left:0;right:0;z-index:2;display:flex;align-items:center;gap:5px;padding:9px 12px;background:color-mix(in srgb,var(--void) 50%,transparent);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}
.proj .browser i{width:8px;height:8px;border-radius:50%;background:color-mix(in srgb,var(--ice) 45%,transparent)}
.proj .browser span{font-family:var(--font-mono);font-size:11px;color:color-mix(in srgb,var(--ice) 85%,transparent);margin-left:8px;letter-spacing:.03em}
.proj .cover{position:relative;font-family:var(--font-display);font-weight:600;font-size:clamp(20px, 14px + 1.67vw, 38px);color:var(--ice);letter-spacing:-.02em;text-align:center;padding:0 18px;transform:rotateX(8deg) rotateY(-6deg);transition:transform var(--dur-slow) var(--ease-out);text-shadow:var(--cover-shadow)}
html[dir="rtl"] .proj .browser span{margin-left:0;margin-right:8px}
.proj:hover .cover{transform:rotateX(0) rotateY(0) scale(1.03)}
.proj .body{padding:var(--s-5)}
.proj .cat{font-family:var(--font-mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--teal-text);display:block;margin-bottom:var(--s-3)}
.proj h3{font-family:var(--font-display);font-weight:600;font-size:22px;margin-bottom:var(--s-3)}
html[dir="rtl"] .proj h3{font-family:var(--font-ar-display)}
.proj p{font-size:14px;color:var(--muted);line-height:1.5;margin-bottom:var(--s-4);min-height:calc(1.5em * 3)}
.proj .pills{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:var(--s-4)}
.proj .actions{display:flex;flex-wrap:wrap;gap:8px}
.proj .actions a{font-family:var(--font-mono);font-size:12px;font-weight:700;padding:9px 16px;min-height:38px;display:inline-flex;align-items:center;border:1px solid var(--hairline-2);border-radius:var(--r-pill);color:var(--ice);text-decoration:none;transition:all var(--dur-fast)}
.proj .actions a:hover{border-color:var(--teal);color:var(--teal-text)}
.proj .actions .soon{font-family:var(--font-mono);font-size:11px;padding:6px 12px;border:1px dashed var(--hairline-2);border-radius:var(--r-pill);color:var(--faint);display:inline-flex;align-items:center;gap:7px}
.proj .actions .soon::before{content:"";width:5px;height:5px;border-radius:50%;background:var(--faint)}

/* ---------- project previews ---------- */
/* committed screenshots (live sites) */
/* Overscan, because the shot drifts inside its clipped frame (data-parallax).
   Visible travel is (viewportHalf - cardHalf) x k ≈ ±18px at these depths, so
   the extra 18% of height is what stops a bare edge appearing at the top or
   bottom of the frame at the extremes of the drift. */
.proj .shot{position:absolute;inset:0;top:-12%;width:100%;height:124%;object-fit:cover;object-position:top left;z-index:1;display:block;transform:scale(1.002);transition:transform var(--dur-slow) var(--ease-out);filter:saturate(1.04)}
.proj:hover .shot{transform:scale(1.045)}
.proj .thumb:has(.shot){background:var(--navy)}
.proj .thumb:has(.shot) .cover{display:none}
.proj .browser{z-index:3}
.proj .thumb:has(.shot)::before{z-index:2;opacity:1;mix-blend-mode:normal;background:linear-gradient(180deg,color-mix(in srgb,var(--void) 10%,transparent),transparent 34%),radial-gradient(circle at 72% 16%,color-mix(in srgb,var(--teal) 16%,transparent),transparent 60%)}
/* Arcanum abstract covers (sites not yet live) */
.proj .thumb--art{background:var(--grad-noir)}
.proj[data-cat="health"] .thumb--art{--plate:52,208,232}
.proj[data-cat="travel"] .thumb--art,.proj[data-cat="pwa"] .thumb--art{--plate:230,180,80}
.proj .thumb--art .art{position:absolute;inset:0;z-index:1;display:grid;place-items:center;background:radial-gradient(70% 80% at 50% 40%,rgba(var(--plate),.22),transparent 70%),url("data:image/svg+xml,%3Csvg width='28' height='49' viewBox='0 0 28 49' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23E6B450' fill-opacity='0.06' fill-rule='evenodd'%3E%3Cpath d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l11 6.35 11-6.35V17.9l-11-6.35L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/%3E%3C/g%3E%3C/svg%3E");background-size:auto,42px 73px}
.proj .thumb--art .art::before{content:"";position:absolute;inset:16px;border:1px solid color-mix(in srgb,var(--crimson) 16%,transparent)}
.proj .thumb--art .art::after{content:"";position:absolute;width:10px;height:10px;top:20px;inset-inline-start:20px;border-top:1.5px solid color-mix(in srgb,var(--teal) 50%,transparent);border-inline-start:1.5px solid color-mix(in srgb,var(--teal) 50%,transparent)}
.proj .thumb--art .art-glyph{width:60px;height:60px;fill:none;stroke:rgba(var(--plate),.95);stroke-width:1.3;stroke-linecap:round;stroke-linejoin:round;filter:drop-shadow(0 0 12px rgba(var(--plate),.55));transition:transform var(--dur-slow) var(--ease-out)}
.proj:hover .thumb--art .art-glyph{transform:scale(1.08) rotate(-3deg)}
.proj .thumb--art .cover{position:absolute;z-index:2;top:auto;bottom:16px;left:0;right:0;padding:0 16px;transform:none;font-size:clamp(15px,2vw,19px);letter-spacing:.01em;color:var(--ice);text-shadow:0 2px 12px rgba(0,0,0,.55)}
/* light theme: dark ink needs no dark glow behind it */
.proj:hover .thumb--art .cover{transform:none}
@media (prefers-reduced-motion:reduce){.proj .shot,.proj .thumb--art .art-glyph{transition:none}}

/* ---------- how we work ---------- */
.steps{position:relative;display:grid;gap:var(--s-7);max-width:760px}
/* Logical properties only: the old physical left/right rules plus their
   html[dir=rtl] overrides out-specified the wide layout below, so in Arabic
   at >=1100px the rail collapsed to zero width and every step kept a 60px
   inline indent. */
.steps::before{content:"";position:absolute;inset-inline-start:19px;top:10px;bottom:10px;width:2px;background:linear-gradient(var(--crimson),color-mix(in srgb,var(--crimson) 10%,transparent))}
.step{position:relative;padding-inline-start:60px}
.step .node{position:absolute;inset-inline-start:0;top:0;width:40px;height:40px;border-radius:50%;border:2px solid var(--teal);background:var(--void);display:grid;place-items:center;font-family:var(--font-mono);font-size:14px;color:var(--teal-text);box-shadow:0 0 14px color-mix(in srgb,var(--teal) 40%,transparent)}
.step h3{font-family:var(--font-display);font-weight:600;font-size:22px;margin-bottom:var(--s-3)}
html[dir="rtl"] .step h3{font-family:var(--font-ar-display)}
.step p{color:var(--muted);font-size:15px;line-height:1.6;max-width:34.476em}


/* ---------- generated illustrations ----------
   Monochrome white-on-black rasters used as luminance masks over
   `currentColor`, so one asset re-tints itself in all six palettes
   instead of shipping six copies. Every figure sits in a bounded,
   fixed-size box: the mask never participates in intrinsic sizing,
   so there is nothing for a late-decoding PNG to shift. */
.ill{display:block;width:100%;height:100%;background:currentColor;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
  -webkit-mask-source-type:luminance;mask-mode:luminance}
.ill-faq1{-webkit-mask-image:url(illus/faq1.png);mask-image:url(illus/faq1.png)}
.ill-faq2{-webkit-mask-image:url(illus/faq2.png);mask-image:url(illus/faq2.png)}
.ill-faq3{-webkit-mask-image:url(illus/faq3.png);mask-image:url(illus/faq3.png)}
.ill-faq4{-webkit-mask-image:url(illus/faq4.png);mask-image:url(illus/faq4.png)}
.ill-faq5{-webkit-mask-image:url(illus/faq5.png);mask-image:url(illus/faq5.png)}
.ill-faq6{-webkit-mask-image:url(illus/faq6.png);mask-image:url(illus/faq6.png)}

/* ---------- founder ---------- */
.founder-card{background:var(--navy);border:1px solid var(--hairline-2);border-radius:var(--r-lg);
  padding:clamp(17px, 9.33px + 2.13vw, 40px);max-width:940px;position:relative;overflow:hidden;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 34px 70px -40px rgb(var(--shade) / calc(.85 * var(--shade-k)))}
.founder-card::before{content:"";position:absolute;top:0;left:0;right:0;height:2px;
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--crimson) 70%,transparent),transparent)}
.fd-top{display:flex;gap:clamp(16px,3vw,26px);align-items:center;margin-bottom:var(--s-5)}
.fd-photo{width:clamp(59.3px, 45.73px + 3.77vw, 100px);height:clamp(59.3px, 45.73px + 3.77vw, 100px);flex:none;border-radius:50%;
  display:grid;place-items:center;background:var(--grad-noir);border:2px solid color-mix(in srgb,var(--crimson) 50%,transparent);position:relative;overflow:hidden}
.fd-photo::after{content:"";position:absolute;inset:0;background:radial-gradient(circle at 30% 25%,color-mix(in srgb,var(--crimson) 22%,transparent),transparent 60%)}
.fd-photo .mono{font-family:var(--font-display);font-weight:700;font-size:clamp(26px, 19.33px + 1.85vw, 46px);color:var(--crimson-hi);position:relative;z-index:1}
.fd-id h3{font-family:var(--font-display);font-weight:600;font-size:clamp(16.4px, 11.87px + 1.26vw, 30px);letter-spacing:.01em}
.fd-role{font-family:var(--font-mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--teal-text);margin:6px 0 12px}
.fd-pills{display:flex;flex-wrap:wrap;gap:6px}
.fd-pills .pill{font-family:var(--font-mono);font-size:11px;letter-spacing:.04em;padding:4px 10px;border:1px solid var(--hairline-2);border-radius:var(--r-pill);color:var(--muted)}
.fd-bio{color:var(--body-strong);line-height:1.65;max-width:43.758em;margin-bottom:var(--s-5)}
.fd-actions{display:flex;flex-wrap:wrap;gap:12px}

/* verified profiles — the same URLs the hub's archive uses */
.fd-links{list-style:none;display:flex;flex-wrap:wrap;gap:8px;margin:0 0 var(--s-5)}
.fd-links a{display:inline-flex;align-items:center;gap:9px;min-height:44px;padding-block:0;padding-inline:6px 14px;
  border:1px solid var(--hairline-2);border-radius:var(--r-sm);color:var(--body-strong);font-size:14px;
  text-decoration:none;transition:border-color var(--dur-fast),color var(--dur-fast)}
.fd-links a:hover{border-color:var(--teal);color:var(--ice)}
.fd-links a:focus-visible{outline:2px solid var(--teal);outline-offset:3px}
.fl-b{display:grid;place-items:center;width:30px;height:30px;border-radius:6px;font-family:var(--font-mono);
  font-size:11px;font-weight:700;color:var(--teal-text);background:color-mix(in srgb,var(--teal) 10%,transparent)}

/* résumé accordion. It animated to max-height:1600px, which silently clips
   anything taller — and the richer timeline is taller on a phone. A
   0fr -> 1fr grid row animates to the content's real height instead. */
.cv-panel{display:grid;grid-template-rows:0fr;opacity:.4;
  transition:grid-template-rows .45s var(--ease-out),opacity .3s}
.cv-panel.open{grid-template-rows:1fr;opacity:1}
/* the collapsing row must carry no block padding or border, or it cannot reach
   0. Inline padding is free: the clip reaches 6px into the card's own padding
   and the content is padded back, and the inner block keeps 6px at its foot,
   so a focus ring (5px) on a control at the edge — the download button on a
   phone — is drawn whole instead of losing two sides to the clip. */
.cv-clip{min-height:0;overflow:hidden;margin-inline:-6px;padding-inline:6px}
.cv-inner{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);gap:clamp(15px, 5.33px + 2.69vw, 44px);
  border-top:1px solid var(--hairline);margin-top:var(--s-6);padding-top:var(--s-6);padding-bottom:6px}
.cv-col h4{font-family:var(--font-mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--faint);margin-bottom:14px}

/* The date sits on its own line: the CV's dates carry months ("Août 2025 –
   fév. 2026") and no fixed column fits them in every language. A rail with a
   node per role; current roles get a filled node, so "ongoing" reads at a
   glance instead of being implied by a trailing dash. */
.timeline{list-style:none;display:grid;gap:var(--s-5);position:relative;padding-inline-start:22px}
.timeline::before{content:"";position:absolute;inset-block:6px;inset-inline-start:5px;width:1px;background:var(--hairline-2)}
.timeline li{position:relative;display:grid;gap:3px}
.timeline li::before{content:"";position:absolute;inset-inline-start:-22px;top:4px;width:11px;height:11px;border-radius:50%;
  border:1.5px solid var(--hairline-2);background:var(--navy)}
.timeline li.tl-now::before{border-color:var(--teal);background:var(--teal)}
.tl-date{font-family:var(--font-mono);font-size:11px;letter-spacing:.04em;color:var(--crimson-text)}
.tl-now .tl-date{color:var(--teal-text)}
.timeline b{font-family:var(--font-display);font-weight:600;font-size:15px;color:var(--ice)}
html[dir="rtl"] .timeline b{font-family:var(--font-ar-display)}
.timeline i{font-style:normal;font-size:13px;color:var(--body-strong)}
.timeline p{font-size:13.5px;line-height:1.6;color:var(--muted);margin-top:2px}

.cv-block{margin-bottom:var(--s-5)}
.cv-block p{color:var(--muted);font-size:13.5px;line-height:1.6}
.skills{display:grid;gap:var(--s-3);margin-bottom:var(--s-3)}
.skills dt{font-family:var(--font-display);font-weight:600;font-size:14px;color:var(--ice);margin-bottom:2px}
html[dir="rtl"] .skills dt{font-family:var(--font-ar-display)}
.skills dd{margin:0;font-size:13.5px;line-height:1.6;color:var(--muted)}
.cv-block .cv-note{font-size:12.5px;color:var(--faint);margin-top:var(--s-2)}
.cv-dl{margin-top:6px}

/* the team: anonymized by design, so the initial is the name */
.team{margin-top:var(--s-6);max-width:940px}
.team-h{font-family:var(--font-mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--faint);
  font-weight:500;margin-bottom:var(--s-4)}
.team-grid{list-style:none;display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3)}
.tm{display:grid;grid-template-columns:44px minmax(0,1fr);grid-template-areas:"i fn" "i do";column-gap:var(--s-4);row-gap:2px;
  align-items:center;padding:var(--s-4);border:1px solid var(--hairline);border-radius:var(--r-md);
  background:color-mix(in srgb,var(--navy) 70%,transparent)}
.tm-i{grid-area:i;width:44px;height:44px;border-radius:50%;display:grid;place-items:center;
  font-family:var(--font-display);font-weight:700;font-size:18px;color:var(--teal-text);
  border:1.5px solid color-mix(in srgb,var(--teal) 55%,transparent);background:var(--void)}
.tm b{grid-area:fn;font-family:var(--font-display);font-weight:600;font-size:15px;color:var(--ice)}
html[dir="rtl"] .tm b{font-family:var(--font-ar-display)}
.tm span:not(.tm-i){grid-area:do;font-size:13.5px;line-height:1.5;color:var(--muted)}
@media (min-width:720px){.team-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:640px){
  .fd-top{flex-direction:column;text-align:center;align-items:center}
  .cv-inner{grid-template-columns:minmax(0,1fr)}
}
@media (prefers-reduced-motion:reduce){.cv-panel{transition:none}}

/* ---------- stack ---------- */


/* ---------- stack (one slim strip) ---------- */
.stack-strip{display:flex;flex-wrap:wrap;gap:var(--s-4) var(--s-7);align-items:center}
.stack-strip .grp{display:inline-flex;align-items:center;gap:12px;flex-wrap:wrap}
.stack-strip .grp>b{font-family:var(--font-mono);font-size:11px;font-weight:400;letter-spacing:.16em;text-transform:uppercase;color:var(--teal-text)}
.stack-strip .pills{display:inline-flex;flex-wrap:wrap;gap:6px}

/* ---------- contact ---------- */
/* Full-bleed closing band. No overflow:hidden here — the display face's swash
   glyphs overhang their box and were getting clipped at the left edge. */
.contact{position:relative}
.contact::before{content:"";position:absolute;width:600px;height:600px;border-radius:50%;background:radial-gradient(circle,color-mix(in srgb,var(--teal) 16%,transparent),transparent 65%);top:-120px;left:50%;transform:translateX(-50%);filter:blur(30px);pointer-events:none}
.contact h2{position:relative;font-family:var(--font-display);font-weight:600;font-size:clamp(29px, 10.67px + 5.09vw, 84px);letter-spacing:-.02em;line-height:1.04;margin-bottom:var(--s-8);max-width:none}
.contact h2 em{font-family:var(--font-swash);font-style:normal;color:var(--teal-text)}
html[dir="rtl"] .contact h2{font-family:var(--font-ar-display);line-height:1.3}
html[dir="rtl"] .contact h2 em{font-family:var(--font-ar-display);font-style:normal}
.contact-grid{position:relative;z-index:1;display:grid;grid-template-columns:minmax(300px,.85fr) minmax(0,1.15fr);gap:var(--s-6) clamp(26px, -12px + 10.56vw, 140px);align-items:start}
.contact .sub{color:var(--body-strong);max-width:29.172em;line-height:1.65;font-size:16.5px}
/* direct channels — aligned rows, shared hairlines with the form side */
.channels{margin-top:var(--s-6);display:grid}
.channels .ch{display:grid;grid-template-columns:96px minmax(0,1fr);gap:var(--s-4);align-items:baseline;padding:14px 0;border-top:1px solid var(--hairline)}
.channels .ch:last-child{border-bottom:1px solid var(--hairline)}
.channels dt{font-family:var(--font-mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--teal-text)}
.channels dd{font-size:15px;color:var(--ice);overflow-wrap:anywhere}
.channels dd a{color:var(--crimson-text);text-decoration:none}
.channels dd a:hover{text-decoration:underline}
.channels .ch-note{font-size:12px;color:var(--muted)}
/* the form sits directly on the band — no card box around it */
.form-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:0 var(--s-5)}
.field{display:flex;flex-direction:column;gap:8px;margin-bottom:var(--s-5)}
.field label{font-family:var(--font-mono);font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted)}
.field input,.field textarea{background:var(--field-bg);border:1px solid var(--hairline-2);border-radius:var(--r-sm);padding:13px 16px;color:var(--ice);font-family:var(--font-body);font-size:15px;transition:all var(--dur-fast);width:100%;resize:vertical}
/* A real ring, outside the border. The border alone could not show focus on a
   field in error: the error border comes later at the same specificity and
   won, so after an empty submit the focused field looked like the other two,
   bar a faint 1px glow. The ring is a shape the error state never touches, so
   the field keeps its error colour and still reads as the focused one. */
.field input:focus,.field textarea:focus{outline:2px solid var(--teal);outline-offset:2px;border-color:var(--teal);box-shadow:var(--glow-teal)}
.field input::placeholder,.field textarea::placeholder{color:var(--faint)}
.field .err{font-family:var(--font-mono);font-size:11px;color:var(--crimson-text);min-height:14px}
.field.invalid input,.field.invalid textarea{border-color:var(--crimson)}
/* Arabic in the fields (placeholders, typed text) gets the Arabic face, not a system fallback */
html[dir="rtl"] .field :is(input,textarea){font-family:var(--font-ar-body)}
.form-note{font-family:var(--font-mono);font-size:12px;line-height:1.6;color:var(--muted);margin-top:var(--s-4);max-width:52ch}
.form-success:focus{outline:none}
.form .btn{padding:15px 40px}
.form-success{display:none;padding:var(--s-7) 0}
.form-success.show{display:block;animation:stagger .5s var(--ease-out)}
.form-success .tick{width:64px;height:64px;margin-bottom:var(--s-5);border-radius:50%;border:2px solid var(--teal);display:grid;place-items:center;color:var(--teal-text);font-size:30px;box-shadow:var(--glow-teal)}
.form-success .fallback{font-family:var(--font-mono);font-size:13px;color:var(--muted);margin-top:var(--s-3)}
.form-success .fallback a{color:var(--teal-text);text-decoration:underline}
.form-success p{font-family:var(--font-display);font-size:22px;color:var(--ice);max-width:29.172em}
html[dir="rtl"] .form-success p{font-family:var(--font-ar-display)}
@media(max-width:960px){.contact-grid{grid-template-columns:minmax(0,1fr)}}
@media(max-width:560px){.form-row{grid-template-columns:minmax(0,1fr)}}

/* ---------- footer ---------- */
/* The inline value MUST be the gutter, not 0. This rule sits after
   .wrap{padding:0 var(--gutter)} at equal specificity, so a shorthand
   with `0` silently wiped the footer's horizontal gutter — logo, nav and
   copyright all sat flush against the screen edge on every phone. Second
   time this exact trap has landed in this file; see BRAND.md. */
.footer{padding:var(--s-8) var(--gutter) var(--s-7);border-top:1px solid var(--hairline)}
.footer-top{display:flex;justify-content:space-between;flex-wrap:wrap;gap:var(--s-6);margin-bottom:var(--s-7)}
.footer .logo{font-size:24px}
.footer nav{display:flex;flex-wrap:wrap;gap:20px}
.footer nav{flex-wrap:wrap;max-width:100%;min-width:0}
.footer nav a{font-family:var(--font-mono);font-size:13px;color:var(--muted);text-decoration:none;transition:color var(--dur-fast);overflow-wrap:anywhere;word-break:break-word}
.footer nav a:hover{color:var(--teal-text)}
.footer .meta{display:flex;justify-content:space-between;flex-wrap:wrap;gap:var(--s-3);font-family:var(--font-mono);font-size:12px;color:var(--faint);padding-top:var(--s-5);border-top:1px solid var(--hairline)}
.footer .meta span{overflow-wrap:anywhere}
.footer .signoff{font-family:var(--font-swash);font-style:normal;font-size:16px;color:var(--muted)}
/* three short columns: who, how to reach, where to follow */
.footer-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:var(--s-6);margin-bottom:var(--s-6)}
.fc{min-width:0;display:grid;gap:var(--s-3);align-content:start}
.fc-h{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin:0}
html[dir="rtl"] .fc-h{letter-spacing:0;font-family:var(--font-ar-body)}
.fc p,.fc li{color:var(--muted);font-size:14px;line-height:1.6}
.fc ul{list-style:none;margin:0;padding:0;display:grid;gap:6px}
.fc a{font-family:var(--font-mono);font-size:13px;color:var(--muted);text-decoration:none;overflow-wrap:anywhere;display:inline-flex;align-items:center;min-height:32px}
.fc a:hover{color:var(--teal-text)}
.footer .meta-legal{display:inline-flex;flex-wrap:wrap;gap:var(--s-4)}
.footer .meta-legal:empty{display:none}
.footer .meta-legal a,.footer .meta-top{color:var(--muted);text-decoration:none}
.footer .meta-legal a:hover,.footer .meta-top:hover{color:var(--teal-text)}
.footer .meta-end{display:inline-flex;align-items:center;flex-wrap:wrap;gap:var(--s-4)}
.footer .meta-top{display:inline-flex;align-items:center;gap:6px;min-height:32px}
/* the mono face has no Arabic: the fallback spaced every letter apart */
html[dir="rtl"] :is(.footer nav a,.fc a,.footer .meta){font-family:var(--font-ar-body)}
html[dir="rtl"] .footer .signoff{font-family:var(--font-ar-body);font-style:normal}

/* ---------- services: line-glyph ---------- */
.glyph{width:46px;height:46px;border:1px solid var(--hairline-2);border-radius:var(--r-sm);display:grid;place-items:center;color:var(--teal-text);flex:none;transition:border-color var(--dur-base),box-shadow var(--dur-base)}
.glyph svg{width:26px;height:26px;display:block;overflow:visible}
.glyph svg :is(path,circle,line,polyline,polygon,rect){fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;
  transition:stroke-dashoffset .6s var(--ease-out)}
.js-ready .glyph svg :is(path,circle,line,polyline,polygon,rect){stroke-dasharray:1;stroke-dashoffset:1}
/* draw when revealed (scroll) or when a disclosure opens */
.in .glyph svg :is(path,circle,line,polyline,polygon,rect),
details[open] .glyph svg :is(path,circle,line,polyline,polygon,rect){stroke-dashoffset:0}
.glyph svg :nth-child(2){transition-delay:.07s}
.glyph svg :nth-child(3){transition-delay:.14s}
.glyph svg :nth-child(4){transition-delay:.21s}
.glyph svg :nth-child(n+5){transition-delay:.28s}
@keyframes glyph-pulse{0%,100%{transform:scale(1)}45%{transform:scale(1.14)}}





/* ---------- services index ---------- */
.svc-lead{font-size:clamp(16px,1.6vw,19px);color:var(--muted);max-width:39.78em;margin:0 0 var(--s-8);line-height:1.6}
/* shared section intro — gives #work / #stack the same lead-in cadence as #services */
.sec-lead{font-size:clamp(16px,1.6vw,19px);color:var(--muted);max-width:39.78em;margin:0 0 var(--s-7);line-height:1.6}

/* services grid — five compact offers; depth lives in the capabilities shelf */
.svc-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s-5);align-items:stretch}
.svc-grid .svc-row+.svc-row{margin-top:0}
.svc-grid .svc-row:last-child:nth-child(odd){grid-column:1/-1}
@media(max-width:960px){.svc-grid{grid-template-columns:minmax(0,1fr)}}

/* service row card */
.svc-row{position:relative;display:grid;grid-template-columns:46px minmax(0,1fr);gap:var(--s-5);padding:var(--s-6);border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--navy);overflow:hidden;transition:transform var(--dur-slow) var(--ease-out)}
.svc-row + .svc-row{margin-top:var(--s-5)}
.svc-row::after{content:"";position:absolute;inset:0;border-radius:var(--r-md);padding:1px;background:var(--grad-teal);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity var(--dur-base)}
.svc-row:hover{transform:translateY(-3px)}
.svc-row:hover::after{opacity:.75}
.svc-row:hover .glyph{border-color:var(--teal);box-shadow:var(--glow-teal);animation:glyph-pulse .6s var(--ease-out)}
.svc-row h3{font-family:var(--font-display);font-weight:600;font-size:24px;line-height:1.1}
html[dir="rtl"] .svc-row h3{font-family:var(--font-ar-display)}
.svc-row .promise{font-family:var(--font-swash);font-style:normal;font-size:21px;color:var(--teal-text);margin:6px 0 var(--s-4);line-height:1.25}
html[dir="rtl"] .svc-row .promise{font-family:var(--font-ar-body);font-style:normal;font-weight:600}
.svc-row .brief{font-size:14.5px;color:var(--muted);line-height:1.6;margin-bottom:var(--s-4)}
.svc-row .outcome{font-size:14px;color:var(--ice);line-height:1.5;margin-bottom:var(--s-5);padding-left:14px;border-left:2px solid var(--crimson)}
html[dir="rtl"] .svc-row .outcome{padding-left:0;padding-right:14px;border-left:none;border-right:2px solid var(--crimson)}
.svc-row .outcome b{display:block;font-family:var(--font-mono);font-size:11px;font-weight:400;letter-spacing:.14em;text-transform:uppercase;color:var(--teal-text);margin-bottom:5px}
.svc-row .proof{font-family:var(--font-mono);font-size:12px;color:var(--faint);line-height:1.6;margin-bottom:var(--s-4)}
.svc-row .proof b{color:var(--muted);font-weight:400;letter-spacing:.12em;text-transform:uppercase;margin-right:8px}
html[dir="rtl"] .svc-row .proof b{margin-right:0;margin-left:8px}
.svc-row .tags{display:flex;flex-wrap:wrap;gap:6px}

/* signature offer — pinned */
.preview{position:relative;border:1px solid var(--hairline-2);border-radius:var(--r-lg);padding:var(--s-7);margin-bottom:var(--s-8);overflow:hidden;background:var(--grad-noir);display:grid;grid-template-columns:46px minmax(0,1fr);gap:var(--s-5)}
.preview .glow{position:absolute;width:360px;height:360px;border-radius:50%;background:radial-gradient(circle,color-mix(in srgb,var(--teal) 22%,transparent),transparent 65%);top:-140px;right:-60px;filter:blur(24px);pointer-events:none}
html[dir="rtl"] .preview .glow{right:auto;left:-60px}
.preview .glyph{border-color:var(--crimson);color:var(--crimson-text)}
.preview .body{position:relative}
.preview .tag{font-family:var(--font-mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--crimson-text)}
.preview h3{font-family:var(--font-display);font-weight:600;font-size:clamp(20.3px, 14.4px + 1.64vw, 38px);margin:var(--s-3) 0 6px}
html[dir="rtl"] .preview h3{font-family:var(--font-ar-display)}
.preview .promise{font-family:var(--font-swash);font-style:normal;font-size:23px;color:var(--teal-text);margin-bottom:var(--s-4)}
html[dir="rtl"] .preview .promise{font-family:var(--font-ar-body);font-style:normal;font-weight:600}
.preview .brief{font-size:15px;color:var(--muted);line-height:1.6;max-width:41.106em;margin-bottom:var(--s-4)}
/* the offer's terms and its two ways in: the form, or WhatsApp */
.preview .prev-terms{font-family:var(--font-mono);font-size:12px;line-height:1.6;color:var(--body-strong);max-width:52em;margin-bottom:var(--s-5)}
.preview .prev-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-3) var(--s-5);margin-bottom:var(--s-5)}
.preview .prev-wa{font-family:var(--font-mono);font-size:13px;color:var(--teal-text);text-decoration:underline;text-underline-offset:3px;min-height:44px;display:inline-flex;align-items:center}
.preview .prev-wa[data-wa-pending]{color:var(--muted);text-decoration:none}
.preview .outcome{font-size:14px;color:var(--ice);padding-left:14px;border-left:2px solid var(--crimson)}
html[dir="rtl"] .preview .outcome{padding-left:0;padding-right:14px;border-left:none;border-right:2px solid var(--crimson)}
.preview .outcome b{display:block;font-family:var(--font-mono);font-size:11px;font-weight:400;letter-spacing:.14em;text-transform:uppercase;color:var(--teal-text);margin-bottom:5px}

/* capabilities shelf — disclosure */
.capabilities{margin-top:var(--s-8);border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--glass);overflow:hidden}
.capabilities>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:var(--s-4);padding:var(--s-5) var(--s-6);font-family:var(--font-mono);font-size:13px;letter-spacing:.1em;text-transform:uppercase;color:var(--ice);transition:background var(--dur-base)}
.capabilities>summary::-webkit-details-marker{display:none}
@media (hover:hover){.capabilities>summary:hover{background:color-mix(in srgb,var(--teal) 5%,transparent)}}
.capabilities>summary .chev{margin-left:auto;color:var(--teal-text);transition:transform var(--dur-base)}
html[dir="rtl"] .capabilities>summary .chev{margin-left:0;margin-right:auto}
.capabilities[open]>summary .chev{transform:rotate(90deg)}
/* RTL mirrors › into ‹: turn it the other way so open still points down */
html[dir="rtl"] .capabilities[open]>summary .chev{transform:rotate(-90deg)}
.capabilities>summary .hint{color:var(--faint);text-transform:none;letter-spacing:.02em;font-size:12px}
.cap-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:var(--s-5);padding:0 var(--s-6) var(--s-6)}
.cap{display:flex;flex-direction:column;gap:var(--s-3);padding-top:var(--s-5);border-top:1px solid var(--hairline)}
.cap .glyph{width:38px;height:38px}
.cap .glyph svg{width:21px;height:21px}
.cap .fam{font-family:var(--font-mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint)}
.cap h4{font-family:var(--font-display);font-weight:600;font-size:18px}
html[dir="rtl"] .cap h4{font-family:var(--font-ar-display)}
.cap .promise{font-family:var(--font-swash);font-style:normal;font-size:16px;color:var(--teal-text)}
html[dir="rtl"] .cap .promise{font-family:var(--font-ar-body);font-style:normal;font-weight:600}
.cap .brief{font-size:13px;color:var(--muted);line-height:1.5}

/* ---------- 3D scene banner (inside the work section) ---------- */
.scene-banner{display:flex;align-items:center;gap:var(--s-5);margin-top:var(--s-7);padding:var(--s-5) var(--s-6);
  background:var(--navy);border:1px solid color-mix(in srgb,var(--crimson) 35%,transparent);border-radius:var(--r-md);text-decoration:none;color:inherit;
  position:relative;overflow:hidden;transition:border-color var(--dur-base),transform var(--dur-base) var(--ease-out),box-shadow var(--dur-base)}
.scene-banner::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(70% 120% at 88% 50%,color-mix(in srgb,var(--teal) 12%,transparent),transparent 60%)}
.scene-banner:hover{border-color:var(--crimson);transform:translateY(-3px);
  box-shadow:0 26px 54px -30px rgb(var(--shade) / calc(.9 * var(--shade-k))),0 0 0 1px color-mix(in srgb,var(--crimson) 28%,transparent)}
.sb-art{flex:none;width:76px;height:56px;position:relative;perspective:420px}
.sb-art i{position:absolute;inset:0;border:1px solid var(--hairline-2);border-radius:6px;background:var(--plum);
  transform-style:preserve-3d;transition:transform var(--dur-slow) var(--ease-out)}
.sb-art i:nth-child(1){transform:rotateY(-26deg) translateZ(-14px) scale(.86);opacity:.5}
.sb-art i:nth-child(2){transform:rotateY(-18deg) translateZ(0) scale(.94);opacity:.75;border-color:color-mix(in srgb,var(--teal) 40%,transparent)}
.sb-art i:nth-child(3){transform:rotateY(-10deg) translateZ(14px);border-color:color-mix(in srgb,var(--crimson) 55%,transparent);
  background:linear-gradient(140deg,var(--plum),var(--navy))}
.scene-banner:hover .sb-art i:nth-child(1){transform:rotateY(-34deg) translateZ(-20px) scale(.84)}
.scene-banner:hover .sb-art i:nth-child(3){transform:rotateY(-4deg) translateZ(22px)}
.sb-txt{display:flex;flex-direction:column;gap:5px;flex:1;min-width:0}
.sb-kick{font-family:var(--font-mono);font-size:11px;letter-spacing:.2em;color:color-mix(in srgb,var(--crimson-text) 75%,var(--ice))}
.sb-txt b{font-family:var(--font-display);font-weight:600;font-size:clamp(13.3px, 10.4px + 0.81vw, 22px)}
html[dir="rtl"] .sb-txt b{font-family:var(--font-ar-display)}
.sb-lead{font-size:13.5px;color:var(--body-strong);line-height:1.5}
.sb-go{flex:none;font-family:var(--font-mono);font-size:12px;letter-spacing:.06em;color:color-mix(in srgb,var(--crimson-text) 70%,var(--ice));
  border:1px solid color-mix(in srgb,var(--crimson) 45%,transparent);border-radius:var(--r-pill);padding:9px 16px;white-space:nowrap}
.scene-banner:hover .sb-go{background:color-mix(in srgb,var(--crimson) 12%,transparent)}
@media(max-width:760px){
  .scene-banner{flex-direction:column;align-items:flex-start;gap:var(--s-4)}
  .sb-go{align-self:stretch;text-align:center}
}
@media(prefers-reduced-motion:reduce){.scene-banner,.sb-art i{transition:none}}

/* ---------- FAQ ---------- */
.faq-list{display:grid;gap:var(--s-4);max-width:840px}
.faq-item{border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--navy);overflow:hidden;
  background-image:linear-gradient(180deg,rgba(255,255,255,.04),rgba(255,255,255,0) 40%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 22px 46px -32px rgb(var(--shade) / calc(.85 * var(--shade-k)));
  transition:border-color var(--dur-base) var(--ease-out)}
.faq-item[open]{border-color:var(--hairline-2)}
.faq-item summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:var(--s-4);
  padding:var(--s-5);font-family:var(--font-display);font-weight:600;font-size:clamp(16px,1.9vw,19px);
  letter-spacing:-.01em;color:var(--ice);transition:color var(--dur-fast)}
.faq-item summary::-webkit-details-marker{display:none}
/* hover only where there is a pointer that hovers: a phone keeps :hover on
   the last thing tapped, and this is the open colour, so a question tapped
   shut went on looking open (r3/a) */
@media (hover:hover){.faq-item summary:hover{color:var(--teal-text)}}
.faq-item summary:focus-visible{outline:2px solid var(--teal);outline-offset:-3px;border-radius:var(--r-sm)}
/* .faq-item, .capabilities and .lang clip their overflow, so an outside ring
   would be cut away entirely: these controls draw it inside. */
.capabilities>summary:focus-visible,.lang button:focus-visible{outline-offset:-3px}
.lang button[aria-pressed="true"]:focus-visible{outline-color:var(--on-teal)}
/* a directional arrow inside translated copy (founder buttons) turns with the reading direction */
html[dir="rtl"] .arr{display:inline-block;transform:scaleX(-1)}
html[dir="rtl"] .faq-item summary{font-family:var(--font-ar-display)}
.faq-item .q{flex:1}

/* plus-mark that morphs to a minus when open */
.faq-mark{position:relative;flex:none;width:18px;height:18px}
.faq-mark::before,.faq-mark::after{content:"";position:absolute;background:var(--teal);border-radius:2px;
  transition:transform var(--dur-base) var(--ease-out),opacity var(--dur-base)}
.faq-mark::before{top:50%;left:0;right:0;height:2px;transform:translateY(-50%)}
.faq-mark::after{left:50%;top:0;bottom:0;width:2px;transform:translateX(-50%)}
.faq-item[open] .faq-mark::after{transform:translateX(-50%) scaleY(0);opacity:0}
.faq-item[open] summary{color:var(--teal-text)}
.faq-a{padding:0 var(--s-5) var(--s-5);color:var(--muted);font-size:15px;line-height:1.65;
  display:flex;gap:var(--s-5);align-items:flex-start}
/* The summary row is 34px tall — far too small for these drawings to
   survive. The open panel has the room, and the figure belongs with the
   answer it illustrates rather than with the question. */
.faq-fig{flex:none;width:92px;height:92px;display:grid;place-items:center;padding:8px;
  border:1px solid var(--hairline);border-radius:var(--r-sm);background:var(--void);color:var(--teal-text)}
@media (max-width:560px){.faq-fig{display:none}}
.faq-a p{max-width:42.432em}
@media (prefers-reduced-motion:reduce){.faq-mark::before,.faq-mark::after{transition:none}}

/* ---------- work thumbnails: crop anchors ---------- */
/* The thumbnail is overscanned to 124% for the parallax drift, so cover crops
   ~42px off each side. Centred, that cut every left-aligned headline mid-word
   ("e monde, uidé et rganisé"); anchored to where a site's logo and headline
   live, the whole crop lands on empty space. The RTL site anchors right. */
.proj .shot[src*="nomara"]{object-position:top right}
.proj .shot[src*="bordjsteel"]{object-position:top center}   /* its headline is centred */

/* ---------- reveal-on-scroll ---------- */
.svc-row .promise,.preview .promise,.cap .promise{font-weight:600;letter-spacing:-.01em}
/* Gated on html.js-ready, which app.js sets as its first act. If app.js never
   arrives — 404, a content blocker, a corporate proxy — nothing is hidden. The
   old form hid everything unconditionally and relied on the same script to
   un-hide it: measured with app.js blocked, 27/27 sections stayed invisible.
   <noscript> only covers JS being *disabled*, not JS failing to load. */
/* 12px of rise, not 24: the reveal moved text upward faster than the finger
   scrolling it, and twice the travel read as the page jumping (r3/a) */
.js-ready .reveal{opacity:0;transform:translateY(12px);transition:opacity var(--dur-slow) var(--ease-out),transform var(--dur-slow) var(--ease-out)}
.reveal.in{opacity:1;transform:none}
/* No JavaScript (the head script marks <html class="js"> before first paint):
   controls that only app.js can drive — theme, menu, language, plate rail,
   pause, the career toggle — are taken away rather than left doing nothing,
   and the career panel they would have opened is simply open. The panel also
   honours .js-ready, so it is never stuck open on a page where app.js ran. */
html:not(.js) .js-only{display:none!important}
html:not(.js):not(.js-ready) .cv-panel{grid-template-rows:1fr;opacity:1}

/* ============================================================
   PREMIUM PASS — atmosphere · depth · editorial rhythm
   Additive layer (kept before @media so breakpoints still win).
   Remove this block to revert to the flat baseline.
   ============================================================ */

/* 1 — no film grain. Noise laid over a page that is otherwise precise reads
   as a rendering fault rather than as texture; the construction grid below
   carries the surface instead. */
html::after{display:none}


/* 2 — editorial rhythm: drop the flat hairline, draw a fading brass→teal divider */
main.wrap>section{border-top:none}
main.wrap>section+section::before{content:"";position:absolute;top:0;left:50%;transform:translateX(-50%);
  width:min(100%,720px);height:1px;opacity:.8;
  background:linear-gradient(90deg,transparent,var(--hairline-2) 16%,color-mix(in srgb,var(--crimson) 55%,transparent) 50%,var(--hairline-2) 84%,transparent)}

/* 3 — full-bleed alternating surface bands so sections stop looking identical
   (body{overflow-x:hidden} contains the 100vmax bleed) */
section.band{position:relative}
section.band::after{content:"";position:absolute;inset:0;z-index:-1;background:var(--band);
  box-shadow:0 0 0 100vmax var(--band);clip-path:inset(0 -100vmax)}
section.band+section.band::after{background:var(--band)}

/* 4 — give every card family real depth instead of a flat 1px box */
.proj,.svc-row,.preview{
  background-image:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,0) 38%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06), 0 24px 50px -32px rgb(var(--shade) / calc(.85 * var(--shade-k)))}
.proj:hover,.svc-row:hover{
  border-color:color-mix(in srgb,var(--teal) 35%,transparent);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08), 0 34px 64px -30px rgb(var(--shade) / calc(.9 * var(--shade-k))), 0 0 0 1px color-mix(in srgb,var(--teal) 22%,transparent), 0 0 34px -8px color-mix(in srgb,var(--teal) 28%,transparent)}

/* 5 — sharper display type + a brass foil accent on the heading flourish */
h2.sec{font-size:clamp(26px, 14px + 3.33vw, 62px);letter-spacing:-.025em}
/* NOT --grad-blood. That ramp ends in #500A1F / #411E3A, which measure
   1.16:1 and 1.22:1 against --void — and even its brightest stop is
   2.68:1, under the 3:1 large-text floor. On desktop the dark tail falls
   past the last glyph so nobody noticed; on a phone the <em> wraps onto
   its own short line and the whole ramp compresses into two or three
   words, which then disappear. Ramping toward --ice instead is
   theme-safe by construction: --ice is the foreground colour, so the mix
   always moves AWAY from the background — lighter on the dark themes,
   darker on Daylight. Measured 3.6:1–11.7:1 across all five palettes. */
main.wrap>section h2.sec em{color:var(--crimson-text)}
/* kicker: glowing node + gradient tick */
.kicker::before{width:32px;background:linear-gradient(90deg,var(--crimson),transparent)}
/* the tick stays at the start and fades toward the text, in either direction */
html[dir="rtl"] .kicker::before{background:linear-gradient(270deg,var(--crimson),transparent)}
.kicker{gap:var(--s-2)}

/* 6 — per-project brand accent: each live card echoes its own site's identity
   (accent RGB triplet is set inline on the card: style="--brand:R,G,B") */
.proj--brand .cat{color:color-mix(in srgb,rgb(var(--brand)) 55%,var(--ice))}
/* The address is real text (the card's host), not decoration: the raw brand
   colour put it at 1.35:1 on Daylight's cream bar. Mixed 40/60 toward --ice it
   keeps the brand's hue and clears 4.5:1 in every theme, for every brand, even
   with the 12% of screenshot that shows through the bar (worst: 4.7:1). */
.proj--brand .browser span{color:color-mix(in srgb,rgb(var(--brand)) 40%,var(--ice))}
.proj--brand .pill.live{color:color-mix(in srgb,rgb(var(--brand)) 55%,var(--ice));border-color:rgba(var(--brand),.6)}
.proj--brand .pill.live::before{background:rgb(var(--brand));box-shadow:0 0 8px rgb(var(--brand))}
/* tint the screenshot's overlay sheen + a brand seam under the preview */
.proj--brand .thumb:has(.shot)::before{background:
  linear-gradient(180deg,color-mix(in srgb,var(--void) 14%,transparent),transparent 36%),
  radial-gradient(circle at 74% 12%,rgba(var(--brand),.30),transparent 58%)}
.proj--brand .thumb::after{content:"";position:absolute;bottom:0;left:0;right:0;height:2px;z-index:4;
  background:linear-gradient(90deg,transparent,rgba(var(--brand),.85),transparent)}
/* hover lifts into the brand colour instead of the global teal */
.proj--brand:hover{border-color:rgba(var(--brand),.45);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08), 0 34px 64px -30px rgb(var(--shade) / calc(.9 * var(--shade-k))), 0 0 0 1px rgba(var(--brand),.32), 0 0 38px -8px rgba(var(--brand),.4)}
.proj--brand .actions a:hover{border-color:rgb(var(--brand));color:var(--ice)}


/* ============================================================
   AMBIENT DEPTH — parallax · staggered reveal
   Motion here is deliberately slower than it feels natural to write:
   the point is a page that settles rather than reacts.

   Parallax rides the `translate` PROPERTY, not `transform`. That is
   load-bearing — `translate`, `rotate` and `transform` are independent
   properties, so an element can be parallaxed while it still runs a
   reveal transform, a hover scale or a spin animation, and none of
   them clobber the others. app.js only ever writes the CSS variables.
   ============================================================ */
[data-parallax]{translate:var(--px,0px) calc(var(--py,0px) + var(--sy,0px));will-change:translate}

/* The aurora is gone: three colour fields drifting under every page, forever.
   Decoration with no job — it carried nothing, it muddied the construction grid
   it floated over, and it ran three infinite animations for the life of the
   tab. The grid and the floor gradient are the depth. */











/* drifting geometry in the hero — hand-drawn SVG, no raster assets.
   `rotate` spins, `transform` bobs, `translate` parallaxes: three
   properties, three owners, zero collisions. */
.floaters{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.floaters svg{position:absolute;fill:none;stroke:currentColor;stroke-width:1;opacity:.2}
/* Placed in the hero's real negative space — the band above the deck and
   the band below it — never over the headline, lede or CTA. Decoration
   drifting behind a button reads as a rendering fault, not atmosphere.
   Each one carries a ~40px motion envelope (bob ±11px plus its parallax
   travel), so the clearances below are deliberately generous. */
.fl-ring{color:var(--teal-text);width:170px;height:170px;top:2%;inset-inline-end:4%}
.fl-dot{color:var(--crimson-hi);width:56px;height:56px;top:6%;inset-inline-end:31%}
.fl-tri{color:var(--crimson-hi);width:96px;height:96px;top:77%;inset-inline-end:30%}
.fl-cross{color:var(--teal-text);width:70px;height:70px;top:80%;inset-inline-end:7%}



/* Below 960 the hero collapses to one column: the deck stacks under the
   copy and the negative space these live in disappears entirely. Rather
   than shuffle them into the headline, drop them — the sheet still
   carries the atmosphere on small screens. */
@media(max-width:960px){.floaters{display:none}}

/* staggered reveal — siblings entering together are delayed in sequence
   (--rd, set by app.js) so a row assembles as a wave, not a flashbulb. */
.reveal{transition-delay:var(--rd,0ms)}

/* ============================================================
   THE EDITORIAL GRID — desktop layout
   The page was a 1104px column parked in the middle of the screen: at
   1920px that is 43% empty margin, at 2560px it is 57%. Worse, every
   block was left-flush with an arbitrary max-width, so the right edge
   landed on six different values (725, 893, 928, 1008, 1108, 1272px) —
   nothing shared a rhythm, which is what reads as "stray components".

   Fix: one twelve-column grid that everything snaps to. Right edges now
   fall on grid lines instead of wherever a max-width happened to end,
   and section headers become two-column so the width is USED rather
   than left as void beside a wrapped headline.
   ============================================================ */
@media(min-width:1100px){
  :root{--max:min(1580px,93vw);--gutter:clamp(40px,3.4vw,64px)}
  /* Engineering sets a different measure (--max 1200 against 93vw = 1190 here).
     That token has to live in THIS sheet, not in theme-engineering.css: this one
     is render-blocking and data-theme is set before first paint, so the first
     layout is already correct. Loaded from the alternate sheet it arrived after
     a layout had happened and .wrap jumped 1190px -> 1200px — CLS 0.0768, in
     roughly half of loads. Layout-critical tokens belong in the blocking sheet;
     colour and component styling can follow whenever it likes. */

  main.wrap>section,.footer{
    display:grid;grid-template-columns:repeat(12,minmax(0,1fr));
    column-gap:clamp(20px,1.7vw,34px);align-content:start}
  /* Default span: nothing silently escapes the grid. .footer MUST be in this
     selector — it takes `display:grid` from the rule above, but the original
     default only covered `main.wrap>section>*`, so every footer child landed
     in a single 1/12 track: 72px wide holding 191px of content, at every
     viewport over 1100px. */
  main.wrap>section>*,.footer>*{grid-column:1/-1}

  /* — the two-column section header — */
  main.wrap>section>.kicker{grid-column:1/-1;margin-bottom:var(--s-6)}
  main.wrap>section>h2.sec{grid-column:1/span 6;max-width:none;margin-bottom:0;align-self:start}
  main.wrap>section>.sec-lead,
  main.wrap>section>.svc-lead{grid-column:8/-1;align-self:end;margin-bottom:0;
    max-width:30.498em;padding-bottom:.4em}
  /* the content block below the header gets its own breathing room */
  main.wrap>section>.kicker+h2.sec+.sec-lead+*,
  main.wrap>section>.kicker+h2.sec+.svc-lead+*,
  main.wrap>section>.kicker+h2.sec+*{margin-top:var(--s-8)}

  /* — per-section content spans — */
  .work-grid,.svc-grid,.stack-strip,.founder-card,.contact-grid,.proof-grid,.preview,.scene-banner,.voices{grid-column:1/-1}
  /* three abreast: the process reads as a sequence instead of a column
     with half the screen empty beside it */
  .steps{grid-column:1/-1;max-width:none;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(24px,2.4vw,48px)}
  .steps::before{inset-inline:0;top:19px;bottom:auto;width:auto;height:2px;
    background:linear-gradient(90deg,var(--crimson),color-mix(in srgb,var(--crimson) 8%,transparent))}
  /* the rail fades toward the end of the sequence, which is the left in Arabic */
  html[dir="rtl"] .steps::before{background:linear-gradient(270deg,var(--crimson),color-mix(in srgb,var(--crimson) 8%,transparent))}
  /* Node ABOVE the copy, not beside it. Turning the rail horizontal put it
     at node-centre height — straight through the step titles, which sat on
     the same line. Stacking clears a band for the rail to run through, and
     the nodes (opaque, painted after) sit on it like beads. */
  /* align-content:start, or the two rows stretch to the tallest step and
     each column's heading lands at a different height */
  .steps .step{padding-inline-start:0;padding-top:60px;
    grid-template-columns:minmax(0,1fr);
    justify-items:start;align-content:start;gap:var(--s-5)}
  .steps .step .node{inset-inline-start:0;top:0}
  .steps .step p{max-width:22.542em}
  /* two columns of questions rather than one tall stack */
  .faq-list{grid-column:1/-1;max-width:none;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--s-4) clamp(20px,1.7vw,34px);align-content:start}

  /* the hero gets the same footing as everything else */
  .hero{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));column-gap:clamp(20px,1.7vw,34px)}
  .hero>.hero-mesh,.hero>.floaters{grid-column:1/-1;grid-row:1}
  .hero-grid{grid-column:1/-1;grid-row:1;grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr);gap:clamp(40px,4vw,80px)}
  .lede{max-width:30.498em;font-size:clamp(18px,1.35vw,21px)}
  /* Display type sized for the canvas it actually sits on. 84px was a
     1200px-page value; the identity is engraved Cinzel and it should
     carry the screen. */
  .hero-h{font-size:clamp(52px,5.6vw,104px);line-height:.98;letter-spacing:-.028em}
  html[dir="ltr"] .hero-h{font-size:var(--hero-fs-wide)}
  .hero .eyebrow{margin-bottom:var(--s-6)}

  /* — breathing room. "Too tight" was measured too: 96px of section
       padding on a 1580px canvas is the rhythm of a 1100px one. — */
  main.wrap>section{padding-block:clamp(104px,8.5vw,152px)}
  .footer{padding-block:var(--s-9) var(--s-8);row-gap:var(--s-6)}
  .work-grid{gap:clamp(24px,2vw,40px)}
  .svc-grid{gap:clamp(22px,1.8vw,34px)}

  /* The signature card was reproducing the page's own problem one level
     down: full-bleed box, copy crammed into the left 40%, dead space to
     the right. Split it — identity left, argument right — so the card
     uses the width it claims. */
  .preview{grid-template-columns:46px minmax(0,.8fr) minmax(0,1.1fr);
    column-gap:clamp(32px,3.4vw,72px);align-items:start;padding:clamp(34px,3vw,52px)}
  .preview .body{display:contents}
  .preview .tag{grid-column:2;align-self:end}
  .preview h3{grid-column:2;margin-top:6px}
  .preview .promise{grid-column:2;margin-bottom:0}
  .preview .brief{grid-column:3;grid-row:1/span 2;max-width:34.476em;align-self:start;margin:0}
  /* the offer's terms sit between the pitch and the outcome; its two ways in
     sit under the promise, where the eye lands after the name */
  .preview .prev-terms{grid-column:3;grid-row:3;align-self:start;margin:var(--s-4) 0 0}
  .preview .outcome{grid-column:3;grid-row:4;align-self:start;margin-top:var(--s-4)}
  .preview .prev-actions{grid-column:2;grid-row:4;align-self:start;margin:var(--s-4) 0 0}
  .preview .glyph{grid-row:1/span 2}
}

@media(min-width:1500px){
  /* at this width the header can afford a wider gutter between the two
     halves without the lead drifting away from its headline */
  main.wrap>section>h2.sec{grid-column:1/span 5}
  main.wrap>section>.sec-lead,
  main.wrap>section>.svc-lead{grid-column:7/span 5}
  .faq-list{column-gap:clamp(34px,2.6vw,56px)}
}

/* ---------- responsive ---------- */
@media(min-width:1100px){
  .work-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
  .work-grid>.proj:last-child:nth-child(odd){grid-column:auto;max-width:none;margin-inline:0}
  .work-grid>.proj:last-child:nth-child(3n+1){grid-column:2}
}

@media(max-width:960px){
  .hero-grid{grid-template-columns:minmax(0,1fr)}
  .work-grid{grid-template-columns:minmax(0,1fr)}
  .work-grid>.proj:last-child:nth-child(odd){max-width:none;margin-inline:0}
  .proof-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s-7)}
}
/* ============================================================
   THE MOBILE PASS — 960px, not 720px.
   This file used to have two load-bearing blocks: "collapse to one
   column" at 960 and "make it mobile" at 720, leaving a 240px band where
   only the first had fired. Everything in it got single-column
   DESKTOP-SIZED components — the worst available combination. Measured
   across the old boundary: a 721px viewport rendered 14292px tall
   against 9255px at 719px. Crossing the breakpoint upward made the page
   55% TALLER and broke the navbar.

   That band is not an edge case: iPad portrait (768), iPad Air (820),
   and every large phone in landscape (844, 915, 932) live in it. The two
   blocks now fire together.
   ============================================================ */
@media(max-width:960px){
  /* Fluid rather than a hard 22px: at 960 a phone gutter looks like a
     mistake, and at 360 a tablet gutter eats the content. */
  :root{--gutter:clamp(20px,4.6vw,44px);--nav-h:73px}
  /* work cards become compact media-left rows: the 7-card stack drops from
     ~4.5 viewports to ~1.5 while keeping the real screenshots visible */
  .work-grid{gap:var(--s-4)}
  /* minmax(0,1fr), never a bare 1fr: a bare 1fr track carries
     min-width:auto, so it inflates to the longest unbreakable word
     ("AlmaFlowClim", "applications") instead of shrinking. At 280px that
     pushed card bodies ~30px outside their own card, where overflow:hidden
     amputated them. The thumb track is fluid so it doesn't eat half a
     small screen for a decorative glyph, nor stay tiny on a tablet. */
  .work-grid>.proj{display:grid;grid-template-columns:clamp(104px,32%,232px) minmax(0,1fr)}
  .work-grid>.proj .thumb{aspect-ratio:auto;height:100%;min-height:116px}
  /* A 16:10 screenshot cropped into a narrow portrait box shows a meaningless
     vertical slice of its middle — at 820px the Bordj Steel card rendered as
     "R L'AVENIR, ENSE". The base .proj .shot rule now anchors top-left at
     every width (and top-right for the RTL site), so nothing to restate. */
  .work-grid>.proj .browser{display:none}
  .work-grid>.proj .body{padding:var(--s-4);min-width:0}
  .work-grid>.proj .cat{font-size:11px;letter-spacing:.12em;margin-bottom:6px}
  .work-grid>.proj h3{font-size:clamp(16px,4.2vw,19px);margin-bottom:6px;overflow-wrap:anywhere}
  /* No line-clamp. Truncating to two lines discarded 50–86% of every
     description — one card wanted 254px of copy and showed 36px. If the
     card is too tall the answer is shorter copy, not hidden copy. */
  .work-grid>.proj p{font-size:13px;line-height:1.5;min-height:0;margin-bottom:10px}
  /* tags fold away on phones — except "Démo portfolio", which is a claim, not decoration */
  .work-grid>.proj .pills:not(:has(.concept)),.work-grid>.proj .pill:not(.concept){display:none}
  .work-grid>.proj .actions a{padding:9px 14px;min-height:44px;font-size:11px}
  /* Touch targets: everything tappable gets to 44px. These were 18–33px —
     fine with a mouse, a coin-toss with a thumb. Grown with padding and
     min-height so nothing reflows around them. */
  .theme-btn{min-width:44px;min-height:44px;display:grid;place-items:center}
  .footer nav a{min-height:44px;display:inline-flex;align-items:center;padding:0}
  .contact .ch dd a{min-height:44px;display:inline-flex;align-items:center}
  .work-grid>.proj .thumb--art .cover{display:none}   /* the name is right beside it */
  .work-grid>.proj .thumb--art .art-glyph{width:38px;height:38px}
  .nav-links{display:none}
  .menu-toggle{display:grid;place-items:center}
  .nav-right .lang{display:none}
  .navbar .bar{padding:14px var(--gutter)}
  /* Rhythm built for a 1440px canvas costs ~2 viewports of dead space on a
     phone: 96px of padding on both sides of every section stacks into a
     192px void between each one. Halved here — still generous at this
     width. padding-block, not the shorthand, so the full-bleed inline
     padding set elsewhere survives. */
  section{padding-block:52px}
  .hero{padding-block:calc(var(--nav-h) + var(--s-5)) var(--s-7)}
  .footer{padding-block:var(--s-6) var(--s-5)}
  /* Keep the glyph beside the copy as it is on desktop, rather than
     stacking it onto its own line — that row cost ~54px in every one of
     the five service cards. */
  .preview{grid-template-columns:minmax(0,1fr)}
  .preview .glyph{margin-bottom:var(--s-2)}
  .svc-row{grid-template-columns:40px minmax(0,1fr);gap:var(--s-4);padding:clamp(18px,4.5vw,28px)}
  .svc-row .glyph{width:40px;height:40px}
  .svc-row h3{overflow-wrap:anywhere}
  .svc-row + .svc-row{margin-top:var(--s-4)}
  .preview{padding:26px;margin-bottom:var(--s-6)}
  /* Section headers were spending ~266px before a single word of content,
     nine times over. Tighten the kicker → title → lead rhythm; the type
     scale already shrinks here, so the desktop spacing reads as a gap
     rather than as breathing room. */
  .kicker{margin-bottom:var(--s-4)}
  h2.sec{margin-bottom:var(--s-5)}
  .sec-lead,.svc-lead{margin-bottom:var(--s-6)}
  .capabilities{margin-top:var(--s-6)}
  /* mobile overlay: bring the language + close controls up to a 44px touch target */
  .mobile-menu .lang button{padding:12px 16px;font-size:14px;min-height:44px}
  .mobile-menu .close{width:44px;height:44px;display:grid;place-items:center;top:14px;inset-inline-end:14px;line-height:1}
  .footer nav{gap:var(--s-3) var(--s-5)}
  .footer nav a{padding-inline:6px;display:inline-flex}
  /* the wordmark is a tap target too */
  .logo{min-height:44px;display:inline-flex;align-items:center}
}

/* ============================================================
   EDGE TO EDGE — safe-area insets
   viewport-fit=cover (set in every page's <head>) lets the page paint
   under the notch and the home indicator. That is what makes it read as
   an app rather than a document in a frame — but it also means content
   must be inset by hand, or it lands underneath the hardware.
   max() so there is always a sane gutter on devices with no insets.
   ============================================================ */
@supports (padding:max(0px)){
  .wrap{padding-inline:max(var(--gutter),env(safe-area-inset-left)) }
  .wrap{padding-right:max(var(--gutter),env(safe-area-inset-right))}
  .footer{padding-left:max(var(--gutter),env(safe-area-inset-left));
          padding-right:max(var(--gutter),env(safe-area-inset-right));
          padding-bottom:calc(var(--s-7) + env(safe-area-inset-bottom))}
  .navbar .bar{padding-left:max(var(--gutter),env(safe-area-inset-left));
               padding-right:max(var(--gutter),env(safe-area-inset-right))}
}

/* ============================================================
   SHORT VIEWPORTS — a phone in landscape is ~390px TALL.
   Nothing in this file used to key off height, so a rotated phone kept a
   168px hero pad and a 40px menu type scale, and the hero alone ran 2.6
   screens. Height-based, so it catches landscape phones and short
   windows alike without guessing at widths.
   ============================================================ */
@media (max-height:560px) and (min-width:480px){
  /* must clear the fixed navbar, not merely be small: a flat 50px here
     put the eyebrow underneath a 73px bar */
  .hero{padding-block:calc(var(--nav-h) + var(--s-3)) var(--s-6)}
  section{padding-block:36px}
  .hero-grid{gap:var(--s-5)}
  .mobile-menu{gap:var(--s-3);justify-content:safe flex-start}
  .mobile-menu a{font-size:clamp(20px,6.4vh,32px)}
  .mobile-menu .close{top:10px;inset-inline-end:10px}
}

/* ============================================================
   VERY NARROW — folded phones and the iPhone SE generation.
   ============================================================ */
@media(max-width:400px){
  /* At 280px the bar needed 302px: a 176px rigid wordmark plus a 104px
     control cluster inside a 236px content box. The hamburger ran clean
     off the right edge, so the menu could not be opened at all. */
  .logo{font-size:18px;min-width:0;overflow:hidden}
  .footer .logo{font-size:20px}
  .navbar .bar{gap:var(--s-3)}
  .nav-right{gap:var(--s-3)}
  /* a 96px label column is a third of a 280px screen, and forced
     hello@numidealabs.com to break mid-domain */
  .channels .ch{grid-template-columns:minmax(0,1fr);gap:6px}
  .hero-cta{gap:var(--s-3)}
  .hero-cta .btn{width:100%;justify-content:center}
}
@media(max-width:340px){
  /* two 70px columns cannot hold "PROJETS LIVRÉS" — the stats stopped
     sharing a baseline */
  .proof-grid{grid-template-columns:minmax(0,1fr);gap:var(--s-5)}
}

/* Legibility floor. Nothing below 11px on a handset — several labels sat
   at 9.5–10.5px, and the smallest also carried .14em tracking, which
   makes a small size harder to read, not easier. */
@media(max-width:960px){
  .work-grid>.proj .cat,.sb-kick,.cap .fam,.fd-pills .pill,.pill,.tag{font-size:11px}
}

/* ============================================================
   TOUCH INPUT, not screen width.
   An iPad in landscape is 1024px wide and gets the desktop navigation —
   correctly, there is room for it — but it is still driven by a finger,
   and 21px links and a 34x30 theme button are a coin toss for a thumb.
   Width is simply the wrong question here; pointer:coarse is the right
   one, and it also covers touch laptops and TVs that no width query
   would ever catch.
   ============================================================ */
@media (pointer:coarse){
  .nav-links a{padding-block:12px}
  .theme-btn{min-width:44px;min-height:44px;display:grid;place-items:center}
  .lang button{min-height:40px;padding-inline:12px}
  .footer nav a{min-height:44px;display:inline-flex;align-items:center;padding-inline:6px}
  .contact .ch dd a{min-height:44px;display:inline-flex;align-items:center}
  .logo{min-height:44px;display:inline-flex;align-items:center}
  .work-grid>.proj .cat,.sb-kick,.cap .fam,.fd-pills .pill,.pill,.tag{font-size:11px}
}

@media(prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important;scroll-behavior:auto!important}
  .reveal,.js-ready .reveal{opacity:1;transform:none}
  .hero-mesh span{animation:none}
  .mobile-menu a,.mobile-menu .lang{opacity:1;transform:none}
  .glyph svg :is(path,circle,line,polyline,polygon,rect),.js-ready .glyph svg :is(path,circle,line,polyline,polygon,rect){stroke-dashoffset:0}
  /* the ambient layer is decoration, not content — flatten it entirely */
  [data-parallax]{translate:none!important}
  .floaters{display:none}
}


/* ================= PRICING ================= */
/* The whole section is built from the panel idiom the rest of the page uses —
   navy ground, hairline edge, inset top highlight — so it reads as part of the
   same system rather than a bolted-on price table. */


/* ---------- tiers ---------- */
.tiers{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-5);margin-bottom:var(--s-5)}
.tier{position:relative;display:flex;flex-direction:column;padding:var(--s-6) var(--s-5) var(--s-5);
  border:1px solid var(--hairline);border-radius:var(--r-lg);background:var(--navy);
  background-image:linear-gradient(180deg,rgba(255,255,255,.04),rgba(255,255,255,0) 42%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 24px 50px -36px rgb(var(--shade) / calc(.85 * var(--shade-k)));
  transition:border-color var(--dur-base) var(--ease-out),transform var(--dur-base) var(--ease-out)}
.tier:hover{border-color:var(--hairline-2);transform:translateY(-3px)}
/* The chosen tier earns a ring and a label, not a different size — a scaled
   card would change the row height and shift the two beside it. */
.tier-pop{border-color:color-mix(in srgb,var(--teal) 45%,transparent);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06),
  0 0 0 1px color-mix(in srgb,var(--teal) 18%,transparent),0 28px 60px -36px rgb(var(--shade) / calc(.9 * var(--shade-k)))}
.t-pop{position:absolute;top:0;inset-inline-start:var(--s-5);transform:translateY(-50%);
  font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--void);background:var(--teal-text);padding:4px 10px;border-radius:var(--r-pill)}
.tier h3{font-family:var(--font-display);font-weight:600;font-size:clamp(22px,2.6vw,30px);
  letter-spacing:-.02em;color:var(--ice);margin-bottom:6px}
html[dir="rtl"] .tier h3{font-family:var(--font-ar-display)}
.t-for{color:var(--body-strong);font-size:14px;line-height:1.55;margin-bottom:var(--s-5);min-height:2.8em}
.t-range{font-family:var(--font-display);font-size:clamp(19px,2.2vw,25px);letter-spacing:-.02em;
  color:var(--ice);margin-bottom:4px}
.t-range b{font-weight:700}
.t-range .cur,.eo-range .cur{font-family:var(--font-mono);font-size:.62em;letter-spacing:.08em;
  color:var(--teal-text);margin-inline-start:4px}
.t-time{font-family:var(--font-mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--faint);padding-bottom:var(--s-5);margin-bottom:var(--s-5);border-bottom:1px solid var(--hairline)}
.t-list{list-style:none;display:grid;gap:var(--s-3)}
.t-list li{position:relative;padding-inline-start:22px;color:var(--body-strong);font-size:14px;line-height:1.55}
.t-list li::before{content:"";position:absolute;inset-inline-start:0;top:.52em;width:9px;height:9px;
  border-inline-start:1.5px solid var(--teal);border-bottom:1.5px solid var(--teal);
  transform:rotate(-45deg) translateY(-1px)}
html[dir="rtl"] .t-list li::before{transform:rotate(45deg) translateY(-1px)}

.price-note{color:var(--muted);font-size:14px;line-height:1.6;max-width:52em;margin-bottom:var(--s-8)}

/* ---------- estimator ---------- */
.estimator{border:1px solid var(--hairline-2);border-radius:var(--r-lg);overflow:hidden;
  background:var(--grad-noir);box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 30px 70px -44px rgb(var(--shade) / calc(.9 * var(--shade-k)));
  margin-bottom:var(--s-8)}
.est-head{padding:var(--s-5);border-bottom:1px solid var(--hairline)}
.est-head h3{font-family:var(--font-display);font-weight:600;font-size:clamp(18px,2.2vw,24px);
  letter-spacing:-.01em;color:var(--ice);margin-bottom:6px}
html[dir="rtl"] .est-head h3{font-family:var(--font-ar-display)}
.est-head p{color:var(--muted);font-size:14px;line-height:1.6;max-width:56em}
.est-grid{display:grid;grid-template-columns:minmax(0,1fr)}
.est-controls{padding:var(--s-5);display:grid;gap:var(--s-6)}
.est-field{border:0;padding:0;margin:0;min-width:0}
.est-field legend,.est-field>label,.est-lab{font-family:var(--font-mono);font-size:10px;letter-spacing:.18em;
  text-transform:uppercase;color:var(--faint);margin-bottom:var(--s-3);padding:0;display:flex;
  align-items:baseline;gap:var(--s-3)}
.est-sizev{font-family:var(--font-display);font-size:17px;letter-spacing:-.01em;color:var(--teal-text);font-weight:600}
.est-hint{font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--faint);display:block;margin-top:var(--s-3)}

.est-chips,.est-opts{display:flex;flex-wrap:wrap;gap:var(--s-3)}
.est-opts{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3)}
.echip,.eopt{position:relative;display:inline-flex;align-items:center;gap:10px;cursor:pointer;
  border:1px solid var(--hairline-2);border-radius:var(--r-sm);background:rgba(255,255,255,.02);
  color:var(--body-strong);font-size:14px;line-height:1.3;padding:11px 14px;min-height:44px;
  transition:border-color var(--dur-fast),background var(--dur-fast),color var(--dur-fast)}
.echip input,.eopt input{position:absolute;opacity:0;width:0;height:0;pointer-events:none}
/* hover only for a pointer that hovers: a phone keeps :hover on the last
   option tapped, and this is the ticked look, so an option tapped OFF went on
   looking ticked (measured: border teal, text ice, box empty) (r3/a) */
@media (hover:hover){.echip:hover,.eopt:hover{border-color:var(--teal);color:var(--ice)}}
.echip:has(input:checked){border-color:var(--teal);
  background:color-mix(in srgb,var(--teal) 12%,transparent);color:var(--ice)}
.eopt::before{content:"";flex:none;width:17px;height:17px;border:1.5px solid var(--hairline-2);
  border-radius:4px;transition:border-color var(--dur-fast),background var(--dur-fast)}
.eopt:has(input:checked){border-color:var(--teal);color:var(--ice)}
.eopt:has(input:checked)::before{border-color:var(--teal);background:var(--teal)}
/* The tick is an L turned -45deg, the mark the tier lists use (and, like
   theirs, mirrored in Arabic). The two crossed gradient bands it replaces drew
   an X — "not included" — right beside the INCLUS tags. It sits centred on the
   20px box above, which starts after the label's 14px inline padding. */
.eopt:has(input:checked)::after{content:"";position:absolute;top:50%;inset-inline-start:18px;box-sizing:border-box;
  width:12px;height:7px;margin-top:-4.5px;border-inline-start:2px solid var(--on-teal);border-bottom:2px solid var(--on-teal);
  transform:rotate(-45deg);pointer-events:none}
html[dir="rtl"] .eopt:has(input:checked)::after{transform:rotate(45deg)}
/* The ring has to live on the label — the real input is visually hidden,
   so its own focus ring would be invisible. */
.echip:has(input:focus-visible),.eopt:has(input:focus-visible){outline:2px solid var(--teal);outline-offset:3px}
/* An option the chosen tier already includes: ticked, locked, labelled. */
.eopt:has(input:disabled){cursor:default}
.eopt:has(input:disabled):hover{border-color:var(--teal)}
/* 11px, the page's floor for labels (it sat at 10px with tracking) */
.eo-incl{margin-inline-start:auto;font-family:var(--font-mono);font-size:var(--t-label);letter-spacing:.1em;
  text-transform:uppercase;color:var(--teal-text)}
html[dir="rtl"] .eo-incl{letter-spacing:0}

.est-field input[type="range"]{-webkit-appearance:none;appearance:none;width:100%;height:30px;
  background:transparent;cursor:pointer;display:block}
.est-field input[type="range"]::-webkit-slider-runnable-track{height:4px;border-radius:2px;
  background:linear-gradient(90deg,var(--teal),var(--hairline-2))}
.est-field input[type="range"]::-moz-range-track{height:4px;border-radius:2px;
  background:linear-gradient(90deg,var(--teal),var(--hairline-2))}
/* in Arabic the slider runs right to left: the filled end follows the minimum */
/* (two rules: a selector list mixing -webkit- and -moz- pseudo-elements is dropped whole) */
html[dir="rtl"] .est-field input[type="range"]::-webkit-slider-runnable-track{background:linear-gradient(270deg,var(--teal),var(--hairline-2))}
html[dir="rtl"] .est-field input[type="range"]::-moz-range-track{background:linear-gradient(270deg,var(--teal),var(--hairline-2))}
.est-field input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:22px;height:22px;border-radius:50%;background:var(--void);border:2px solid var(--teal);
  margin-top:-9px;box-shadow:0 0 12px color-mix(in srgb,var(--teal) 45%,transparent)}
.est-field input[type="range"]::-moz-range-thumb{width:18px;height:18px;border-radius:50%;
  background:var(--void);border:2px solid var(--teal);
  box-shadow:0 0 12px color-mix(in srgb,var(--teal) 45%,transparent)}
.est-field input[type="range"]:focus-visible{outline:2px solid var(--teal);outline-offset:4px;border-radius:var(--r-sm)}

.est-out{padding:var(--s-5);border-top:1px solid var(--hairline);
  background:color-mix(in srgb,var(--void) 55%,transparent);
  display:flex;flex-direction:column;gap:var(--s-3)}
.eo-k{font-family:var(--font-mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--teal-text)}
/* tabular figures, or the range jitters sideways on every slider step */
.eo-range{font-family:var(--font-display);font-weight:700;font-variant-numeric:tabular-nums;
  font-size:clamp(23px,2.6vw,31px);letter-spacing:-.025em;color:var(--ice);line-height:1.05}
.eo-dash{color:var(--faint);margin:0 .22em;font-weight:400}
.eo-time{font-family:var(--font-mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--faint);font-variant-numeric:tabular-nums}
.eo-sum{list-style:none;display:flex;flex-wrap:wrap;gap:8px;margin:var(--s-2) 0}
.eo-sum li{font-family:var(--font-mono);font-size:var(--t-label);letter-spacing:.06em;color:var(--body-strong);
  border:1px solid var(--hairline-2);border-radius:var(--r-pill);padding:4px 9px}
.eo-sum .eo-head{border-color:var(--teal);color:var(--teal-text)}
.eo-fine{color:var(--faint);font-size:13px;line-height:1.6;max-width:46em}
.est-out .btn{align-self:flex-start;margin-top:var(--s-2)}

/* ---------- method: call to action under the four steps ---------- */
.how-cta{display:flex;flex-wrap:wrap;gap:var(--s-3)}

/* ===== r3/c: visualisations and motion =====
   Pictures of figures the page already states (app.js 13b builds and arms
   them): the method's rail, the Aperçu's five days, each tier's weeks, the
   estimate against the three packages, the market ranges on one price
   scale. Each is aria-hidden beside its text equivalent. Tokens only; the
   marks are thin and the text keeps the text tokens, never a series colour.
   Motion is entrance only, on transform and opacity: .vz-arm holds a
   picture back, .vz-go / .vz-lit (and the rail's --rail) play it. Without those classes
   (scripts off, reduced motion, print) every picture is simply drawn.
   Pictures whose marks are placed by app.js keep their room from the first
   paint and stay invisible until placed (.vz-on): no layout shift, and
   nothing half-drawn if the script never arrives. */

/* -- method: the rail draws itself, node to node --
   The rail is the section's own (.steps::before, untouched). Held back, it
   is scaled to nothing from its start, and app.js grows it to each node in
   turn (--rail, the share of its length the line has reached), so its end
   state is the static rail exactly: what reduced motion and scripts-off
   show at once. A node waits small, not faded (its number is text, and a
   faded number would be unreadable, 2.8:1, until the line reached it),
   then opens out to its size when the line arrives. */
.steps.vz-arm::before{transform:scaleY(var(--rail,0));transform-origin:50% 0;
  transition:transform .42s var(--ease-out)}
@media (min-width:1100px){
  .steps.vz-arm::before{transform:scaleX(var(--rail,0));transform-origin:0 50%}
  html[dir="rtl"] .steps.vz-arm::before{transform-origin:100% 50%}
}
.steps.vz-arm .step .node{z-index:1;transform:scale(.7);
  transition:transform .5s cubic-bezier(.34,1.56,.64,1) .3s}
.steps.vz-arm .step:first-child .node{transition-delay:0s}
.steps.vz-arm .step.vz-lit .node{transform:none}

/* -- the Aperçu: send, five working days, the call, the decision --
   A vertical rail by default; laid along a line once its box is wide
   enough (a container query, so the card's own width decides, not the
   screen's): from 420px, which the split card's column always is (435px
   at 1100), the four columns about 80-120px wide. Segments sit behind opaque markers. The one figure, what has
   been paid when the visitor decides, is set as a figure. */
.ptl-w{container-type:inline-size;margin:0 0 var(--s-5)}
.ptl{list-style:none;margin:0;padding:0;display:grid;gap:var(--s-4)}
.ptl-s{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr);column-gap:var(--s-3);align-content:start}
.ptl-s:not(:last-child)::after{content:"";position:absolute;z-index:0;inset-inline-start:11px;top:10px;
  width:2px;height:calc(100% + var(--s-4));background:color-mix(in srgb,var(--teal) 45%,transparent);
  transform-origin:50% 0}
.ptl-m{position:relative;z-index:1;grid-row:1/span 2;justify-self:center;margin-top:4px;
  width:12px;height:12px;border:2px solid var(--teal);border-radius:50%;background:var(--void)}
/* the five working days, one cell each */
.ptl-build .ptl-m{margin-top:0;width:8px;height:auto;border:0;border-radius:0;background:none;
  display:grid;grid-template-rows:repeat(5,6px);gap:2px}
.ptl-build .ptl-m i{display:block;border-radius:1px;background:var(--teal)}
/* the decision is the visitor's, in the page's primary accent */
.ptl-dec .ptl-m{border:0;border-radius:2px;background:var(--crimson);transform:rotate(45deg) scale(.86)}
.ptl-t{grid-column:2;font-size:var(--t-small);line-height:1.4;color:var(--body-strong)}
.ptl-d{grid-column:2;font-family:var(--font-mono);font-size:var(--t-label);line-height:1.5;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-top:2px}
html[dir="rtl"] .ptl-d{letter-spacing:0}
.ptl-zero{grid-column:2;justify-self:start;margin-top:2px;font-family:var(--font-display);font-weight:700;
  font-size:var(--t-title);line-height:1.15;letter-spacing:-.01em;color:var(--ice);white-space:nowrap;
  font-variant-numeric:tabular-nums}
.ptl-zero .cur{font-family:var(--font-mono);font-size:var(--t-small);font-weight:400;letter-spacing:.06em;color:var(--teal-text)}
html[dir="rtl"] .ptl-zero,html[dir="rtl"] .ptl-zero .cur{letter-spacing:0}
@container (min-width:420px){
  .ptl{grid-template-columns:minmax(0,.95fr) minmax(0,1.4fr) minmax(0,.95fr) minmax(0,1.2fr);gap:0 var(--s-4)}
  .ptl-s{grid-template-columns:minmax(0,1fr);grid-template-rows:24px auto auto;row-gap:0}
  .ptl-s:not(:last-child)::after{top:11px;inset-inline-start:6px;width:calc(100% + var(--s-4));height:2px;
    transform-origin:0 50%}
  html[dir="rtl"] .ptl-s:not(:last-child)::after{transform-origin:100% 50%}
  .ptl-m{grid-row:1;justify-self:start;margin-top:6px}
  .ptl-build .ptl-m{justify-self:stretch;width:auto;height:8px;margin-top:8px;
    grid-template-rows:none;grid-template-columns:repeat(5,minmax(0,1fr));gap:3px}
  .ptl-dec .ptl-m{margin-inline-start:1px}
  .ptl-t,.ptl-d,.ptl-zero{grid-column:1}
  .ptl-t{margin-top:6px}
  .ptl-zero{margin-top:4px}
}
/* entrance: each moment in turn — the line out of it draws once it has
   appeared, the five days fill one by one, the decision lands last */
.ptl.vz-arm .ptl-s:not(:last-child)::after{transform:scale(0)}
.ptl.vz-arm :is(.ptl-s>.ptl-m,.ptl-build .ptl-m i,.ptl-t,.ptl-d,.ptl-zero){opacity:0}
.ptl.vz-arm :is(.ptl-s:not(.ptl-build)>.ptl-m,.ptl-build .ptl-m i){transform:scale(.4)}
.ptl.vz-arm .ptl-s.ptl-dec>.ptl-m{transform:rotate(45deg) scale(.3)}
.ptl.vz-arm :is(.ptl-t,.ptl-d,.ptl-zero){transform:translateY(4px)}
.ptl.vz-arm.vz-go :is(.ptl-s>.ptl-m,.ptl-build .ptl-m i,.ptl-t,.ptl-d,.ptl-zero){
  opacity:1;transform:none;transition:opacity .35s var(--ease-out) var(--pd,0s),transform .45s cubic-bezier(.34,1.4,.64,1) var(--pd,0s)}
.ptl.vz-arm.vz-go .ptl-s.ptl-dec>.ptl-m{transform:rotate(45deg) scale(.86)}
.ptl.vz-arm.vz-go .ptl-s:not(:last-child)::after{transform:none;transition:transform .4s var(--ease-out) var(--sd,0s)}
/* the whole picture is in place by about 1.8s: a timeline that keeps the
   reader waiting for its last step reads as unfinished */
.ptl-s:nth-child(1){--pd:0s;--sd:.1s}
.ptl-s:nth-child(2){--pd:.3s;--sd:.75s}
.ptl-build .ptl-m i:nth-child(1){--pd:.35s}.ptl-build .ptl-m i:nth-child(2){--pd:.43s}
.ptl-build .ptl-m i:nth-child(3){--pd:.51s}.ptl-build .ptl-m i:nth-child(4){--pd:.59s}
.ptl-build .ptl-m i:nth-child(5){--pd:.67s}
.ptl-s:nth-child(3){--pd:.95s;--sd:1.05s}
.ptl-s:nth-child(4){--pd:1.25s}
.ptl-dec .ptl-zero{--pd:1.4s}
@media (min-width:1100px){
  /* In the split card it reads under the brief it draws, beside the promise
     and the call to action. It spans the promise's row and the button's, so
     the button still starts where the promise ends, as it did before the
     timeline: its height never pushes the button down. The promise is placed
     by row so the button's rows can never displace it. */
  .preview .promise{grid-row:3}
  .preview .ptl-w{grid-column:3;grid-row:3/span 2;align-self:start;margin:var(--s-5) 0 0}
  .preview .prev-actions{grid-row:4/span 3}
  .preview .prev-terms{grid-row:5}
  .preview .outcome{grid-row:6}
}

/* -- tiers: the delivery window on one ruler shared by the three cards --
   The bar runs from the tier's shortest delivery to its longest, on a ruler
   from 0 to the longest of all (16 weeks), one tick a week and both ends
   labelled: it reads "2 to 4 weeks", never as a share done. Its room is kept
   from first paint when scripts run (html.js), so the card never grows when
   the widths and the end label arrive. */
.t-weeks{display:none}
html.js .tier .t-time{padding-bottom:var(--s-3);margin-bottom:0;border-bottom:0}
html.js .t-weeks{display:block;padding-bottom:var(--s-4);margin-bottom:var(--s-5);border-bottom:1px solid var(--hairline)}
.t-weeks:not(.vz-on) :is(.t-wk,.t-wk-n){visibility:hidden}
.t-wk{position:relative;display:block;height:5px;border-radius:min(var(--r-sm),3px);
  background:color-mix(in srgb,var(--hairline-2) 70%,transparent)}
/* week ticks under the bar, one per week; both ends closed, so the ruler
   is the same either way round */
.t-wk::after{content:"";position:absolute;inset-inline:0;top:8px;height:4px;
  border-inline:1px solid var(--hairline-2);
  background:repeating-linear-gradient(90deg,var(--hairline-2) 0 1px,transparent 1px calc(100% / var(--wn,16)))}
.t-wk b{position:absolute;inset-block:0;inset-inline-start:0;border-radius:inherit;background:var(--teal);
  transform-origin:0 50%}
html[dir="rtl"] .t-wk b{transform-origin:100% 50%}
/* the ruler's two ends, under its ticks: 0 and the longest delivery */
.t-wk-n{display:flex;justify-content:space-between;gap:var(--s-3);height:16px;margin-top:12px;
  font-family:var(--font-mono);font-size:var(--t-label);line-height:16px;letter-spacing:.06em;
  color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}
html[dir="rtl"] .t-wk-n{letter-spacing:0}
/* entrance: the window opens from its first week to its last */
.t-weeks.vz-arm .t-wk b{transform:scaleX(0)}
.t-weeks.vz-arm.vz-go .t-wk b{transform:none;transition:transform .7s var(--ease-out) .3s}

/* -- estimator: the estimate as a dimension line over the packages --
   The track clips: the line and its two end marks are track-wide boxes
   moved and scaled by app.js (a translate in % is a share of the box's own
   width), and only their visible part may show. The package the chosen
   type belongs to lights by opacity, not by a colour transition. */
.esc{display:none}
html.js .esc{display:block;container-type:inline-size;margin:var(--s-2) 0 var(--s-1)}
.esc:not(.vz-on){visibility:hidden}
.esc-trk{position:relative;display:block;height:30px;overflow:hidden}
/* a package band keeps its light fill (the lit one is told apart by its
   fill) and gets an edge in --muted, a text-grade token, so an unlit band
   still stands 3:1 off the track in every theme (the fill alone: 1.6-1.8:1) */
.esc-band{position:absolute;bottom:0;height:8px;border-radius:min(var(--r-sm),3px);
  background:color-mix(in srgb,var(--muted) 30%,transparent);box-shadow:inset 0 0 0 1px var(--muted);
  transition:opacity .5s var(--ease-out) var(--bd,0s)}
.esc-band::after{content:"";position:absolute;inset:0;border-radius:inherit;
  background:color-mix(in srgb,var(--teal) 55%,transparent);opacity:0;transition:opacity var(--dur-base) var(--ease-out)}
.esc-band.on::after{opacity:1}
.esc-bar{position:absolute;inset-inline:0;top:7px;height:3px;background:var(--teal);transform-origin:0 50%;
  transition:transform .45s var(--ease-out),opacity .3s var(--ease-out)}
.esc-cap{position:absolute;inset:0;transition:transform .45s var(--ease-out),opacity .3s var(--ease-out)}
.esc-cap::before{content:"";position:absolute;inset-inline-start:-1px;top:2px;bottom:0;width:2px;
  background:linear-gradient(var(--teal) 0 14px,color-mix(in srgb,var(--teal) 45%,transparent) 14px)}
html[dir="rtl"] .esc-bar{transform-origin:100% 50%}
.esc-nm{position:relative;display:block;height:16px;margin-top:6px}
.esc-nm span{position:absolute;top:0;transform:translateX(-50%);white-space:nowrap;font-family:var(--font-mono);
  font-size:var(--t-label);line-height:16px;color:var(--muted);transition:opacity .5s var(--ease-out) var(--bd,0s)}
html[dir="rtl"] .esc-nm span{transform:translateX(50%)}
.esc-nm span.on{color:var(--ice)}
.esc [data-tier="surmesure"]{--bd:.1s}.esc [data-tier="app"]{--bd:.2s}
/* under ~270px (a 320-360px phone) three names do not fit on one line */
@container (max-width:270px){
  .esc-nm{height:32px}
  .esc-nm [data-tier="surmesure"]{top:16px}
}
.esc.vz-arm:not(.vz-go) :is(.esc-band,.esc-bar,.esc-cap,.esc-nm span){opacity:0}

/* -- market: every range on one price scale, Numidea first --
   One grid for the whole plot: the lane labels share a column sized by the
   longest (subgrid), the tracks share the rest. Gridlines belong to each
   group, so a group's heading never has a line struck through it. Numidea's
   range is carried down through the group as a band between two extension
   lines, so where each market meets it reads without a ruler. */
.mkv{margin:0 0 var(--s-6);container-type:inline-size}
.mkv[hidden]{display:none}
.mkv-plot{position:relative;display:grid;grid-template-columns:max-content minmax(0,1fr);
  column-gap:var(--s-3);row-gap:var(--s-4)}
.mkv-ax,.mkv-g,.mkv-ln{grid-column:1/-1;display:grid;
  grid-template-columns:7.5em minmax(0,1fr);grid-template-columns:subgrid}
.mkv-ax{align-items:end}
/* the same scale again under the last group, so the lower rows read
   without a trip back to the top */
.mkv-ax.bot{align-items:start;margin-top:calc(-1 * var(--s-2))}
.mkv-ax.bot .mkv-tk span{bottom:auto;top:0}
.mkv-unit{font-family:var(--font-mono);font-size:var(--t-label);line-height:16px;color:var(--muted)}
.mkv-tk{position:relative;display:block;height:16px}
.mkv-tk span{position:absolute;bottom:0;transform:translateX(-50%);white-space:nowrap;
  font-family:var(--font-mono);font-size:var(--t-label);line-height:16px;color:var(--muted);font-variant-numeric:tabular-nums}
/* the powers of ten are the graduations the caption counts in */
.mkv-tk .mj{color:var(--body-strong)}
html[dir="rtl"] .mkv-tk span{transform:translateX(50%)}
.mkv-tk .mn{display:none}
.mkv-g{grid-template-rows:auto repeat(4,17px)}
.mkv-hd{grid-column:1/-1;grid-row:1;display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:2px var(--s-3);padding-bottom:6px;margin-bottom:6px;border-bottom:1px solid var(--hairline)}
.mkv-ty{font-family:var(--font-display);font-weight:600;font-size:var(--t-ui);color:var(--ice)}
html[dir="rtl"] .mkv-ty{font-family:var(--font-ar-display)}
.mkv-nv{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-display);font-size:var(--t-small);
  font-weight:600;color:var(--ice);font-variant-numeric:tabular-nums;white-space:nowrap}
.mkv-nv::before{content:"";flex:none;width:12px;height:8px;border-radius:min(var(--r-sm),2px);background:var(--teal)}
/* decoration under the lanes, never the pointer's target: on a phone-width
   chart it spans the name lines too, and would swallow their hover */
.mkv-gl{grid-column:2;grid-row:2/-1;position:relative;pointer-events:none}
.mkv-gl i{position:absolute;top:0;bottom:0;width:1px;background:var(--hairline)}
.mkv-gl i.mj{background:var(--hairline-2)}
.mkv-gl .mkv-nb{position:absolute;top:-3px;bottom:-3px;box-sizing:border-box;
  border-inline:1px dashed color-mix(in srgb,var(--teal) 60%,transparent);
  background:color-mix(in srgb,var(--teal) 8%,transparent)}
.mkv-ln{align-items:center}
.mkv-lab{font-family:var(--font-mono);font-size:var(--t-label);line-height:17px;color:var(--muted);white-space:nowrap}
.mkv-ln.nl .mkv-lab{color:var(--ice)}
.mkv-trk{position:relative;display:block;height:17px}
.mkv-bar{position:absolute;top:50%;height:6px;margin-top:-3px;border-radius:min(var(--r-sm),3px);
  background:color-mix(in srgb,var(--muted) 75%,transparent);transform-origin:0 50%;transition:opacity var(--dur-fast)}
.mkv-ln.nl .mkv-bar{height:9px;margin-top:-4.5px;background:var(--teal)}
/* "+": no ceiling, so the bar fades out instead of ending. It is solid up
   to the range's stated top (--solid, its share of the bar, set by the
   script) and fades only past it, so the fade never shortens the range */
.mkv-bar.open{-webkit-mask-image:linear-gradient(90deg,#000 var(--solid,50%),transparent);mask-image:linear-gradient(90deg,#000 var(--solid,50%),transparent)}
html[dir="rtl"] .mkv-bar.open{-webkit-mask-image:linear-gradient(270deg,#000 var(--solid,50%),transparent);mask-image:linear-gradient(270deg,#000 var(--solid,50%),transparent)}
html[dir="rtl"] .mkv-bar{transform-origin:100% 50%}
/* hover (a mouse): the lane under the pointer stays, the rest step back */
.mkv-plot.hov .mkv-ln:not(.hl) .mkv-bar{opacity:.4}
.mkv-ln.hl .mkv-lab{color:var(--ice)}
/* Pinned on its inline-start side (the right in Arabic) and moved only by
   its transform: a new label is a new width, and a box pinned by its other
   edge would move its start corner with every change of text, which Chrome
   counts as a layout shift (a mouse move or a scroll is not an input). */
.mkv-tip{position:absolute;top:0;inset-inline-start:0;z-index:3;display:grid;gap:2px;padding:8px 11px;pointer-events:none;
  opacity:0;transition:opacity var(--dur-fast);border:1px solid var(--hairline-2);border-radius:var(--r-sm);
  background:var(--plum);box-shadow:0 12px 30px -14px rgb(var(--shade) / calc(.8 * var(--shade-k)));
  font-family:var(--font-display);font-size:var(--t-small);line-height:1.5;color:var(--body-strong);
  font-variant-numeric:tabular-nums}
/* no wider than the plot: on a 320px chart the heading wraps at its spaces,
   and each range holds together (no-break spaces) */
.mkv-tip b{font-weight:600;color:var(--ice)}
.mkv-tip.on{opacity:1}
.mkv-cap{margin-top:var(--s-4);max-width:62em;color:var(--muted);font-size:var(--t-small);line-height:1.6}
@container (min-width:520px){.mkv-tk .mn{display:block}}
/* A narrow chart (a phone: 180-290px) gives the names a line of their own
   above each bar instead of a column beside it, so the bars keep the whole
   width: beside a 66px name column a 320px phone had 100px of scale, and the
   Arabic "100 ألف" and "1 مليون" touched. The gridlines and Numidea's band
   are masked off the name lines, so no name has a line struck through it. */
@container (max-width:440px){
  .mkv-plot{grid-template-columns:minmax(0,1fr)}
  .mkv-ax>*{grid-row:1;grid-column:1}
  .mkv-unit{justify-self:end}
  .mkv-g{grid-template-rows:auto repeat(4,28px)}
  .mkv-gl{grid-column:1;
    -webkit-mask-image:repeating-linear-gradient(transparent 0 15px,#000 15px 28px);
    mask-image:repeating-linear-gradient(transparent 0 15px,#000 15px 28px)}
  .mkv-ln{grid-template-rows:15px 13px;align-items:stretch}
  .mkv-lab{grid-row:1;line-height:15px}
  .mkv-trk{grid-row:2;height:13px}
  /* the hover label in the label size, so its longest range fits a 180px plot */
  .mkv-tip{padding:6px 8px;font-size:var(--t-label)}
}
/* entrance: the gridlines and Numidea's band settle, then each range draws
   from its low end, group by group */
.mkv.vz-arm .mkv-gl{opacity:0}
.mkv.vz-arm .mkv-bar{transform:scaleX(0)}
.mkv.vz-arm.vz-go .mkv-gl{opacity:1;transition:opacity .5s var(--ease-out)}
.mkv.vz-arm.vz-go .mkv-bar{transform:none;transition:transform .7s var(--ease-out) var(--d,0s),opacity var(--dur-fast)}

/* -- focus: a keyboard reaches a button with the same answer a pointer gets
   on hover (the ring stays the focus indicator; this is the response) -- */
.btn-primary:focus-visible{box-shadow:var(--glow-crimson);transform:translateY(-2px)}
.btn-teal:focus-visible{box-shadow:var(--glow-teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}
.btn-ghost:focus-visible{border-color:var(--ice)}

/* -- press: buttons and chips answer the finger (hover lifts were already
   there; this is the moment of the press) -- */
@media (prefers-reduced-motion:no-preference){
  .btn:active{transform:translateY(0) scale(.97);transition-duration:.08s}
  .echip,.eopt{transition:border-color var(--dur-fast),background var(--dur-fast),color var(--dur-fast),
    transform var(--dur-fast) var(--ease-out)}
  .echip:active,.eopt:active{transform:scale(.97)}
  .eopt:has(input:disabled):active{transform:none}
}
/* The page-wide reduced-motion rule (*{transition:none}) does not reach
   pseudo-elements: the marks here that move on their own get it too (the
   lit band's fade, the method's rail, the Aperçu's segments). It matters
   for a visitor who turns reduced motion on during the visit, with a
   picture already held back; !important beats the specificity of the
   .ptl.vz-arm.vz-go … ::after rule. */
@media (prefers-reduced-motion:reduce){.esc-band::after,.steps.vz-arm::before,.ptl .ptl-s::after{transition:none!important}}

/* -- forced colours: backgrounds become Canvas, which would erase every
   mark; the marks are redrawn in the user's own system colours -- */
@media (forced-colors:active){
  :is(.mkv-bar,.esc-band,.ptl-build .ptl-m i,.ptl-dec .ptl-m,.mkv-nv::before){
    forced-color-adjust:none;background:GrayText}
  .esc-band{box-shadow:none}
  :is(.mkv-ln.nl .mkv-bar,.esc-band.on::after,.esc-bar,.t-wk b){forced-color-adjust:none;background:Highlight}
  /* the weeks ruler is an outline, so its empty part never takes the
     colour of the delivery window */
  .t-wk{forced-color-adjust:none;background:Canvas;box-shadow:inset 0 0 0 1px GrayText}
  /* gridlines stay quieter than the bars they measure */
  .mkv-gl i{forced-color-adjust:none;background:GrayText;opacity:.35}
  .mkv-gl i.mj{opacity:.6}
  .esc-cap::before{forced-color-adjust:none;background:CanvasText}
  .ptl-s:not(:last-child)::after{forced-color-adjust:none;background:CanvasText}
  .mkv-gl .mkv-nb{forced-color-adjust:none;background:none;border-color:Highlight}
  /* the method's rail is a gradient, which forced colours drop: drawn flat */
  #how .steps::before{forced-color-adjust:none;background:CanvasText}
}
/* ===== end r3/c ===== */

/* ---------- market comparison + pricing models ----------
   Same panel idiom as the estimator, and token-driven
   throughout: a literal colour here would be right in one palette only. */
.market,.models{border:1px solid var(--hairline);border-radius:var(--r-lg);padding:var(--s-6) var(--s-5);
  background:var(--navy);background-image:linear-gradient(180deg,rgba(255,255,255,.04),rgba(255,255,255,0) 40%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 24px 50px -36px rgb(var(--shade) / calc(.85 * var(--shade-k)));
  margin-bottom:var(--s-8)}
.mk-head h3{font-family:var(--font-display);font-weight:600;font-size:clamp(19px,2.4vw,26px);
  letter-spacing:-.015em;color:var(--ice);margin-bottom:6px}
html[dir="rtl"] .mk-head h3{font-family:var(--font-ar-display)}
.mk-head p{color:var(--body-strong);font-size:14px;line-height:1.6;max-width:56em;margin-bottom:var(--s-5)}
.mk-cur{margin-bottom:var(--s-5)}
.mk-cur legend{color:var(--body-strong)}
/* the price currency switch: the same chips, above the tiers */
.pr-cur{margin-bottom:var(--s-4)}
.pr-cur legend{color:var(--body-strong)}
.pr-cur-note{color:var(--muted);font-size:13px;line-height:1.6;max-width:56em;margin:0 0 var(--s-5)}
.pr-cur-note[hidden]{display:none}
.price-note .pr-ret{white-space:nowrap}

/* The table keeps its columns on a phone and scrolls sideways inside its own
   frame; the frame is focusable and labelled, so a keyboard can reach it. */
/* it is a <section> (a labelled scroll region), so it must shed the page's
   section padding — 96px above and below the table otherwise */
.mk-scroll{padding:0;overflow-x:auto;border:1px solid var(--hairline);border-radius:var(--r-md);
  background:color-mix(in srgb,var(--void) 40%,transparent)}
.mk-scroll:focus-visible{outline:2px solid var(--teal);outline-offset:3px}
.mk-table{width:100%;min-width:640px;border-collapse:collapse;font-variant-numeric:tabular-nums}
.mk-table caption{caption-side:top;text-align:start;padding:var(--s-4) var(--s-4) var(--s-2);
  font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}
.mk-table th,.mk-table td{padding:13px var(--s-4);text-align:start;border-bottom:1px solid var(--hairline);vertical-align:middle}
.mk-table tbody tr:last-child>*{border-bottom:0}
.mk-table thead th{font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--faint);font-weight:500}
.mk-table tbody th{font-family:var(--font-display);font-weight:600;font-size:14px;color:var(--ice);white-space:nowrap}
html[dir="rtl"] .mk-table tbody th{font-family:var(--font-ar-display)}
.mk-table td{font-size:14px;color:var(--body-strong);white-space:nowrap}
.mk-table .cur{font-family:var(--font-mono);font-size:.78em;letter-spacing:.06em;color:var(--faint)}
/* the row label stays put while the figures scroll beneath it */
.mk-table th:first-child{position:sticky;inset-inline-start:0;z-index:1;background:var(--navy)}
.mk-table thead th.mk-nl{color:var(--teal-text)}
.mk-table td.mk-nl{color:var(--ice);font-weight:600;background:color-mix(in srgb,var(--teal) 9%,transparent)}
.mk-table td.mk-nl .cur{color:var(--teal-text)}

.mk-day{margin:var(--s-5) 0 var(--s-3);padding:var(--s-4) var(--s-5);border-inline-start:2px solid var(--teal);
  border-radius:var(--r-sm);background:color-mix(in srgb,var(--teal) 7%,transparent);
  color:var(--body-strong);font-size:15px;line-height:1.6}
.mk-num{font-family:var(--font-display);font-weight:700;color:var(--ice);font-variant-numeric:tabular-nums;white-space:nowrap}
.mk-note{color:var(--muted);font-size:13px;line-height:1.65;max-width:62em;margin-bottom:var(--s-4)}
.mk-src summary{cursor:pointer;font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--teal-text);display:inline-flex;align-items:center;min-height:44px}
.mk-src summary:focus-visible{outline:2px solid var(--teal);outline-offset:3px;border-radius:var(--r-sm)}
.mk-src p,.mk-src li{color:var(--muted);font-size:13px;line-height:1.65}
.mk-src p{margin:var(--s-2) 0 var(--s-3);max-width:62em}
.mk-src ul{list-style:none;display:grid;gap:var(--s-2)}
.mk-src li b{color:var(--body-strong);font-weight:600}
.mk-src a{color:var(--teal-text);text-decoration:underline;text-underline-offset:2px}

.md-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-4);margin-bottom:var(--s-7)}
.md-card{display:flex;flex-direction:column;gap:var(--s-2);padding:var(--s-5);border:1px solid var(--hairline);
  border-radius:var(--r-md);background:color-mix(in srgb,var(--void) 35%,transparent)}
.md-ours{border-color:color-mix(in srgb,var(--teal) 45%,transparent)}
.md-tag{align-self:flex-start;font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--void);background:var(--teal-text);padding:3px 9px;border-radius:var(--r-pill);margin-bottom:var(--s-2)}
.md-card h4{font-family:var(--font-display);font-weight:600;font-size:18px;letter-spacing:-.01em;color:var(--ice)}
html[dir="rtl"] .md-card h4{font-family:var(--font-ar-display)}
.md-card p{color:var(--body-strong);font-size:14px;line-height:1.6}
.md-card .md-where{margin-top:auto;padding-top:var(--s-3);border-top:1px solid var(--hairline);color:var(--muted);font-size:13px}
.md-sub{font-family:var(--font-mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint);
  margin-bottom:var(--s-4)}
.md-drivers{list-style:none;display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-4) var(--s-5)}
.md-drivers li{display:flex;flex-direction:column;gap:4px;padding-inline-start:var(--s-4);border-inline-start:2px solid var(--hairline-2)}
.md-drivers b{font-family:var(--font-display);font-weight:600;font-size:15px;color:var(--ice)}
html[dir="rtl"] .md-drivers b{font-family:var(--font-ar-display)}
.md-drivers span{color:var(--muted);font-size:14px;line-height:1.55}
@media (min-width:720px){
  .md-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .md-drivers{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (min-width:1100px){
  .md-grid{grid-template-columns:repeat(4,minmax(0,1fr))}
  .md-drivers{grid-template-columns:repeat(3,minmax(0,1fr))}
  .mk-table{min-width:0}
}

/* ---------- WhatsApp ---------- */
.wa-link{display:inline-flex;align-items:center;gap:9px}
.wa-i{flex:none;width:19px;height:19px;display:block}
.wa-i svg{width:100%;height:100%;fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round}
/* No number configured yet: the control stays legible and plainly not-live,
   rather than linking somewhere wrong. Same idiom as hub/'s pending handles. */
.wa-link[data-wa-pending]{opacity:.5;cursor:default;pointer-events:none}
.wa-link[data-wa-pending]::after{content:attr(data-soon);font-family:var(--font-mono);
  font-size:var(--t-label);letter-spacing:.1em;text-transform:uppercase;white-space:pre}
a.wa-link[data-wa-pending]{text-decoration:none}

@media (min-width:720px){
  .tiers{grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(16px,1.6vw,24px)}
  .est-opts{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (min-width:1100px){
  #pricing>*{grid-column:1/-1}
  .est-grid{grid-template-columns:minmax(0,1.25fr) minmax(0,.75fr)}
  .est-out{border-top:0;border-inline-start:1px solid var(--hairline);justify-content:center}
}

/* ---------- parallax fields ----------
   A wide technical texture drifting behind three otherwise empty sections.
   Same luminance-mask-over-currentColor trick as the illustrations, so one
   asset re-tints itself in all six palettes; the opacity is deliberately low
   enough that body copy never has to compete with it.

   Stacking: the section takes `isolation:isolate` and the wrap sits at z-index
   0, so every sibling needs an explicit layer above it — a static child would
   otherwise paint *below* a positioned one and vanish behind the texture. */
#services,#pricing,#contact{position:relative;isolation:isolate}
#services>:not(.px-wrap),#pricing>:not(.px-wrap),#contact>:not(.px-wrap){position:relative;z-index:1}
.px-wrap{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0}
/* Overscan: the field is taller than its frame so the drift never exposes an
   edge. data-parallax-cap bounds the travel to less than this margin. */
.px-field{position:absolute;inset:-14% 0;display:block;background:currentColor;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:cover;mask-size:cover;
  -webkit-mask-source-type:luminance;mask-mode:luminance;
  color:var(--teal-text);opacity:calc(.12 * var(--ink-k));will-change:transform}
#pricing .px-field{color:var(--crimson-hi);opacity:calc(.1 * var(--ink-k))}
#contact .px-field{color:var(--teal-text);opacity:calc(.1 * var(--ink-k))}
:is(.lz-on,html:not([data-lz]) .lz) .px-plan{-webkit-mask-image:url(illus/px-plan.webp);mask-image:url(illus/px-plan.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .px-contour{-webkit-mask-image:url(illus/px-contour.webp);mask-image:url(illus/px-contour.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .px-stars{-webkit-mask-image:url(illus/px-stars.webp);mask-image:url(illus/px-stars.webp)}
/* Decoration only: it costs a repaint on every frame and says nothing. */
@media (prefers-reduced-motion:reduce){.px-field{will-change:auto}}
@media (max-width:640px){.px-wrap{display:none}}

/* The ambient glows are placed for the LTR layout — warm light top-right behind
   the plate, cool light top-left behind the copy. In Arabic the layout mirrors
   but the glows did not, so the text column and the logo sat directly on the
   warm one (3.8:1 on Daylight). The layer is a fixed full-viewport field of
   gradients with nothing directional in it, so flipping it is exact, and one
   rule covers all six palettes. */
html[dir="rtl"] body::before{transform:scaleX(-1)}

/* Nine interactive rules above use `transition:all`, which animates the outline
   too: on keyboard focus the ring grew from 0 to 2px over 200-300ms, and a
   probe reading the style at the instant of focus saw no ring at all. A focus
   indicator must be instant. Same durations and easings; outline excluded. Lives at the END of the sheet
   on purpose: a later `transition:all` shorthand would reset the property list. */
.theme-btn,.theme-menu button,.lang button,.btn,.proj .actions a,.field input,.field textarea{
  transition-property:color,background-color,border-color,box-shadow,transform,opacity,filter}

/* ═══ forced colours (Windows High Contrast) ═══ Backgrounds are discarded, so a
   borderless button or chip is text floating on Canvas with no edge — 12 of 32
   controls measured that way. A system-coloured border gives every control a
   boundary; ButtonText/LinkText are the palette the user chose. */
@media (forced-colors:active){
  button,.btn,a.btn,.theme-btn,.lang button,.plate-rail button,.pl-pause{border:1px solid ButtonText!important}
  a:not(.btn){text-decoration:underline}
  .proj,.svc-row,.faq-item,.step,.titleblock,.capabilities{border:1px solid CanvasText!important}
  /* Every selected, pressed or current state on the page was drawn with a
     background colour or a gradient, and forced colours replace the first with
     Canvas and drop the second: the checked estimator chip, the option ticks,
     the pressed language, the current plate and section all looked like their
     neighbours. These states are redrawn in the user's own Highlight pair, with
     forced-color-adjust:none so the system colours are used as written. */
  .echip:has(input:checked),.lang button[aria-pressed="true"]{forced-color-adjust:none;
    background:Highlight;color:HighlightText;border-color:Highlight!important}
  .eopt::before{border-color:ButtonText}
  /* the ticked box: Highlight, with the tick in HighlightText */
  .eopt:has(input:checked)::before{forced-color-adjust:none;border-color:Highlight;background:Highlight}
  .eopt:has(input:checked)::after{forced-color-adjust:none;border-color:HighlightText}
  .eopt:has(input:checked){border-color:Highlight!important}
  /* the plate rail: every bar is drawn, the current one thicker and in Highlight */
  .plate-rail button i{forced-color-adjust:none;background:CanvasText}
  .plate-rail button[aria-current="true"] i{background:Highlight;height:4px;margin-top:-1px}
  .nav-links a[aria-current="true"]::after,.timeline li.tl-now::before{forced-color-adjust:none;background:Highlight;border-color:Highlight}
  .faq-mark::before,.faq-mark::after{forced-color-adjust:none;background:CanvasText}
  /* the theme button's icon was a conic gradient, so the button was an empty
     square: a half-filled disc keeps a recognisable mark */
  .theme-btn .ic{forced-color-adjust:none;background:none;box-shadow:inset 7.5px 0 ButtonText,inset 0 0 0 1px ButtonText}
  .est-field input[type="range"]{forced-color-adjust:none}
  /* html[dir] matches both directions and outranks the RTL gradient rule */
  html[dir] .est-field input[type="range"]::-webkit-slider-runnable-track{background:CanvasText}
  html[dir] .est-field input[type="range"]::-moz-range-track{background:CanvasText}
  .est-field input[type="range"]::-webkit-slider-thumb{background:Canvas;border-color:Highlight}
  .est-field input[type="range"]::-moz-range-thumb{background:Canvas;border-color:Highlight}
  /* badges set in --void on an accent fill would be bare words without a box */
  .t-pop,.md-tag{border:1px solid CanvasText}
  /* focus rings on the parts that opted out above would keep the theme's teal */
  .lang button[aria-pressed="true"]:focus-visible{outline-color:HighlightText}
  .echip:has(input:focus-visible),.eopt:has(input:focus-visible),.plate-rail button:focus-visible i,
  .est-field input[type="range"]:focus-visible{outline-color:CanvasText}
}

/* ═══ print ═══ Someone printing a quote or the contact block should not get
   nine pages of navy. Decoration off, ink-safe palette, everything revealed. */
@media print{
  /* !important: the theme blocks are :root[data-theme] (0,2,0) and would win */
  :root{--void:#fff!important;--navy:#fff!important;--plum:#fff!important;--ice:#000!important;--body-strong:#111!important;
    --muted:#333!important;--faint:#555!important;--hairline:#bbb!important;--hairline-2:#888!important;
    /* the accents too: left to the theme, Arcanum's cyan and brass printed the
       email, the WhatsApp number, every kicker and label at ~1.9:1 on paper.
       Daylight's ink accents (7:1 on white) stand in for every theme. Fills do
       not print by default, so text that sat on a fill turns to ink. */
    --teal:#0E6E78!important;--teal-text:#0B5F68!important;--crimson:#8C5E0A!important;--crimson-hi:#A8741A!important;
    --crimson-text:#7A5208!important;--on-teal:#000!important;--on-crimson:#000!important}
  /* the "Recommandé" and "Notre modèle" badges were --void on a fill: white on
     white once the fill is dropped. Ink and an outline instead. */
  .t-pop,.md-tag{color:#000!important;background:none!important;border:1px solid #000}
  /* closed disclosures (the FAQ answers, the capabilities, how prices are
     made) print open, where the browser lets CSS reach their content */
  details::details-content{content-visibility:visible!important}
  /* the WorkspaceHQ cartridge is light type on its own dark box: print the box */
  .whq-cart{-webkit-print-color-adjust:exact;print-color-adjust:exact}
  body{background:#fff!important;color:#000!important}
  body::before,body::after,html::after,.floaters,.hero-mesh,.progress,.skip,.nav-right,.theme-menu,.mobile-menu,.menu-toggle,.sheet-frame,.browser{display:none!important}
  .faq-fig{display:none!important}
  *{box-shadow:none!important;text-shadow:none!important;backdrop-filter:none!important;animation:none!important;transition:none!important}
  .reveal,.js-ready .reveal{opacity:1!important;transform:none!important}
  .glyph svg :is(path,circle,line,polyline,polygon,rect){stroke-dashoffset:0!important}
  .navbar{position:static!important}
  section,.proj,.svc-row,.faq-item{break-inside:avoid}
  a[href^="http"]::after{content:" (" attr(href) ")";font-size:.85em;color:#555}
  /* gradient-filled type falls back to solid ink: its pale middle stops vanish on paper */
  .hero-h .swash,.logo i,.stat,main.wrap>section h2.sec em{background:none!important;-webkit-text-fill-color:#000!important;color:#000!important}
}

/* ═══════════════════════════════════════════════════════════════════════
   THE ATLAS — engraved plates and animated icons
   ═══════════════════════════════════════════════════════════════════════
   Every plate is a monochrome engraving used as a luminance mask over
   currentColor (same rule as .ill: one file, every palette). Masks are only
   assigned under .lz-on — app.js adds it a screen ahead of the viewport —
   or when scripts never ran (no [data-lz] on <html>). */
:is(.lz-on,html:not([data-lz]) .lz) .v-far .v-ink{-webkit-mask-image:url(illus/vista-far.webp);mask-image:url(illus/vista-far.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .v-mid .v-ink{-webkit-mask-image:url(illus/vista-mid.webp);mask-image:url(illus/vista-mid.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .v-near .v-ink{-webkit-mask-image:url(illus/vista-near.webp);mask-image:url(illus/vista-near.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .pi-arch>i{-webkit-mask-image:url(illus/arch.webp);mask-image:url(illus/arch.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .pc-king>i{-webkit-mask-image:url(illus/coin-king.webp);mask-image:url(illus/coin-king.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .pc-horse>i{-webkit-mask-image:url(illus/coin-horse.webp);mask-image:url(illus/coin-horse.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .pc-palm>i{-webkit-mask-image:url(illus/coin-palm.webp);mask-image:url(illus/coin-palm.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .pi-bench>i{-webkit-mask-image:url(illus/bench.webp);mask-image:url(illus/bench.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .pi-strata>i{-webkit-mask-image:url(illus/strata.webp),radial-gradient(closest-side,#000 55%,transparent);mask-image:url(illus/strata.webp),radial-gradient(closest-side,#000 55%,transparent)}
:is(.lz-on,html:not([data-lz]) .lz) .pi-lantern>i{-webkit-mask-image:url(illus/lantern.webp);mask-image:url(illus/lantern.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .pi-light>i{-webkit-mask-image:url(illus/lighthouse.webp);mask-image:url(illus/lighthouse.webp)}
:is(.lz-on,html:not([data-lz]) .lz) .fd-ink{-webkit-mask-image:url(illus/dunes.webp);mask-image:url(illus/dunes.webp)}

.v-ink,.pi>i,.fd-ink{display:block;background:currentColor;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
  -webkit-mask-source-type:luminance;mask-mode:luminance}
.pi-strata>i{-webkit-mask-source-type:luminance,alpha;mask-mode:luminance,alpha;
  -webkit-mask-composite:source-in;mask-composite:intersect}

/* the small engraved caption every plate carries */
.pl-cap{position:absolute;font-family:var(--font-mono);font-size:10px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--muted);white-space:nowrap;pointer-events:none;
  /* a solid chip: the caption sits on engraving, and line work behind 10px
     type takes it under 4.5:1 wherever the hatching is dense */
  background:var(--void);padding:3px 7px;border-radius:3px}
html[dir="rtl"] .pl-cap{letter-spacing:0;font-family:var(--font-ar-body);font-size:11px}

/* ---------- Plate I — the vista under the hero ----------
   Full-bleed, three depth layers. Each layer is wider than the viewport and
   reaches below the frame by more than its parallax cap, so no drift can
   ever bare an edge. Nearer layers carry darker fills — aerial perspective —
   so they read as standing in front of, not printed over, the far ridge. */
.vista{position:relative;z-index:1;height:clamp(240px,34vw,500px);overflow:hidden;
  width:100vw;margin-inline:calc(50% - 50vw);margin-top:calc(-1 * var(--s-7));pointer-events:none;
  -webkit-mask-image:linear-gradient(transparent,#000 22%,#000 82%,transparent);mask-image:linear-gradient(transparent,#000 22%,#000 82%,transparent)}
.v-sky{position:absolute;inset:0 0 auto;width:100%;height:70%;color:var(--muted)}
.v-sky .tw{fill:currentColor;opacity:.55}
.v-sky .v-moon{fill:var(--crimson-text);opacity:.55}
.v-sky .v-const{fill:none;stroke:var(--teal-text);stroke-width:1;opacity:.45;stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset 2.6s ease .4s}
.v-sky .v-const-s{fill:var(--teal-text);opacity:.8}
.lz-on .v-sky .v-const,html:not([data-lz]) .v-sky .v-const{stroke-dashoffset:0}
.v-l{position:absolute;bottom:-60px;width:max(calc(100% + 80px),860px);aspect-ratio:1600/679;
  left:50%;margin-left:calc(max(calc(100% + 80px),860px) / -2)}
.v-l>i{position:absolute;inset:0;display:block}
.v-mid{width:max(calc(82% + 40px),700px);margin-left:0;left:auto;right:-40px}
/* each fill is clipped to its own layer's skyline, traced from the engraving */
.v-far .v-fill{clip-path:polygon(0.00% 61.32%,0.63% 61.02%,1.25% 60.72%,1.88% 60.43%,2.50% 59.98%,3.13% 59.98%,3.75% 60.28%,4.38% 60.87%,5.00% 60.87%,5.63% 60.87%,6.25% 61.32%,6.88% 61.62%,7.50% 61.62%,8.13% 61.91%,8.75% 61.91%,9.38% 61.47%,10.00% 60.28%,10.63% 59.53%,11.25% 59.53%,11.88% 59.68%,12.50% 59.98%,13.13% 59.83%,13.75% 58.64%,14.37% 57.75%,15.00% 57.45%,15.63% 56.56%,16.25% 55.66%,16.88% 55.66%,17.50% 55.96%,18.13% 56.41%,18.75% 57.00%,19.38% 58.05%,20.00% 57.60%,20.63% 57.60%,21.25% 57.75%,21.88% 57.60%,22.50% 57.45%,23.13% 55.37%,23.75% 55.37%,24.38% 55.52%,25.00% 53.73%,25.62% 52.69%,26.25% 52.69%,26.88% 52.69%,27.50% 52.99%,28.13% 53.13%,28.75% 53.43%,29.38% 53.43%,30.00% 53.58%,30.63% 54.47%,31.25% 55.07%,31.87% 54.47%,32.50% 53.58%,33.13% 53.13%,33.75% 53.13%,34.38% 52.84%,35.00% 52.84%,35.63% 52.84%,36.25% 52.84%,36.88% 53.43%,37.50% 53.88%,38.13% 54.77%,38.75% 54.62%,39.38% 54.77%,40.00% 55.37%,40.63% 55.96%,41.25% 55.81%,41.88% 55.07%,42.50% 55.07%,43.13% 55.22%,43.75% 55.52%,44.38% 55.52%,45.00% 55.66%,45.63% 56.11%,46.25% 57.75%,46.88% 57.60%,47.50% 57.30%,48.13% 57.45%,48.75% 57.45%,49.38% 57.45%,50.00% 57.60%,50.63% 58.34%,51.25% 58.94%,51.88% 59.24%,52.50% 59.98%,53.13% 59.68%,53.75% 58.64%,54.37% 58.49%,55.00% 58.49%,55.63% 58.49%,56.25% 58.64%,56.88% 59.53%,57.50% 59.68%,58.13% 59.98%,58.75% 59.98%,59.38% 58.94%,60.00% 57.90%,60.62% 57.90%,61.25% 58.05%,61.88% 58.34%,62.50% 57.30%,63.13% 56.85%,63.75% 56.56%,64.38% 56.56%,65.00% 56.71%,65.63% 56.11%,66.25% 55.66%,66.88% 54.92%,67.50% 54.47%,68.13% 54.33%,68.75% 54.33%,69.38% 55.96%,70.00% 56.26%,70.63% 56.71%,71.25% 56.26%,71.88% 56.26%,72.50% 55.07%,73.13% 55.07%,73.75% 55.22%,74.38% 55.66%,75.00% 56.11%,75.63% 56.71%,76.25% 57.15%,76.88% 58.19%,77.50% 58.49%,78.13% 58.64%,78.75% 59.09%,79.38% 59.09%,80.00% 59.09%,80.63% 59.24%,81.25% 58.94%,81.88% 58.64%,82.50% 57.60%,83.13% 57.45%,83.75% 57.90%,84.38% 58.19%,85.00% 57.90%,85.63% 57.30%,86.25% 57.30%,86.88% 58.19%,87.50% 58.79%,88.13% 58.79%,88.75% 59.24%,89.38% 58.94%,90.00% 58.94%,90.63% 58.34%,91.25% 57.75%,91.88% 57.75%,92.50% 58.05%,93.13% 59.24%,93.75% 59.83%,94.38% 60.43%,95.00% 61.17%,95.63% 61.17%,96.25% 61.17%,96.88% 61.02%,97.50% 60.43%,98.13% 60.13%,98.75% 60.13%,99.38% 59.98%,100.00% 60.13%,100% 100%,0% 100%)}
.v-mid .v-fill{clip-path:polygon(0.00% 100.00%,0.63% 100.00%,1.25% 100.00%,1.88% 100.00%,2.50% 100.00%,3.13% 100.00%,3.75% 98.52%,4.38% 98.22%,5.00% 98.08%,5.63% 97.78%,6.25% 97.63%,6.88% 97.18%,7.50% 96.88%,8.13% 96.59%,8.75% 96.44%,9.38% 95.99%,10.00% 95.55%,10.63% 95.10%,11.25% 94.95%,11.88% 94.80%,12.50% 94.35%,13.13% 94.06%,13.75% 93.91%,14.37% 93.46%,15.00% 93.16%,15.63% 92.87%,16.25% 92.57%,16.88% 92.12%,17.50% 91.68%,18.13% 91.08%,18.75% 90.78%,19.38% 90.49%,20.00% 90.19%,20.63% 89.74%,21.25% 89.30%,21.88% 88.85%,22.50% 88.40%,23.13% 88.10%,23.75% 87.96%,24.38% 87.96%,25.00% 87.96%,25.62% 87.66%,26.25% 87.51%,26.88% 87.51%,27.50% 87.36%,28.13% 87.21%,28.75% 87.21%,29.38% 87.21%,30.00% 87.21%,30.63% 87.21%,31.25% 87.06%,31.87% 86.91%,32.50% 86.91%,33.13% 86.62%,33.75% 86.02%,34.38% 85.43%,35.00% 85.28%,35.63% 84.83%,36.25% 84.68%,36.88% 84.38%,37.50% 84.38%,38.13% 84.38%,38.75% 84.24%,39.38% 83.94%,40.00% 83.64%,40.63% 83.64%,41.25% 83.49%,41.88% 83.19%,42.50% 82.75%,43.13% 82.30%,43.75% 81.85%,44.38% 81.56%,45.00% 80.96%,45.63% 80.52%,46.25% 79.92%,46.88% 79.62%,47.50% 79.47%,48.13% 79.18%,48.75% 79.03%,49.38% 78.88%,50.00% 78.88%,50.63% 64.59%,51.25% 64.44%,51.88% 61.77%,52.50% 60.28%,53.13% 60.43%,53.75% 60.58%,54.37% 61.02%,55.00% 61.32%,55.63% 62.06%,56.25% 61.32%,56.88% 60.43%,57.50% 60.43%,58.13% 59.83%,58.75% 59.98%,59.38% 61.62%,60.00% 61.62%,60.62% 62.66%,61.25% 44.80%,61.88% 44.21%,62.50% 42.72%,63.13% 40.78%,63.75% 39.15%,64.38% 37.36%,65.00% 35.87%,65.63% 34.24%,66.25% 32.00%,66.88% 30.37%,67.50% 28.88%,68.13% 27.54%,68.75% 25.90%,69.38% 23.97%,70.00% 22.03%,70.63% 20.40%,71.25% 19.06%,71.88% 18.76%,72.50% 18.76%,73.13% 18.76%,73.75% 19.06%,74.38% 20.10%,75.00% 21.88%,75.63% 23.37%,76.25% 25.16%,76.88% 27.24%,77.50% 29.03%,78.13% 30.52%,78.75% 32.15%,79.38% 34.38%,80.00% 35.87%,80.63% 37.66%,81.25% 39.30%,81.88% 41.08%,82.50% 42.72%,83.13% 44.35%,83.75% 44.95%,84.38% 52.99%,85.00% 51.05%,85.63% 51.05%,86.25% 51.35%,86.88% 53.13%,87.50% 53.58%,88.13% 52.99%,88.75% 51.94%,89.38% 51.94%,90.00% 52.99%,90.63% 53.73%,91.25% 57.15%,91.88% 57.30%,92.50% 58.05%,93.13% 56.71%,93.75% 56.26%,94.38% 56.56%,95.00% 55.66%,95.63% 55.66%,96.25% 55.96%,96.88% 59.24%,97.50% 59.68%,98.13% 69.65%,98.75% 79.62%,99.38% 100.00%,100.00% 100.00%,100% 100%,0% 100%)}
.v-near .v-fill{clip-path:polygon(0.00% 19.35%,0.63% 19.65%,1.25% 15.19%,1.88% 14.44%,2.50% 14.74%,3.13% 15.49%,3.75% 14.30%,4.38% 13.55%,5.00% 13.40%,5.63% 13.55%,6.25% 14.74%,6.88% 15.93%,7.50% 17.42%,8.13% 15.63%,8.75% 15.49%,9.38% 14.89%,10.00% 14.30%,10.63% 14.44%,11.25% 15.34%,11.88% 19.95%,12.50% 19.80%,13.13% 19.95%,13.75% 26.35%,14.37% 26.80%,15.00% 27.84%,15.63% 36.47%,16.25% 37.81%,16.88% 39.59%,17.50% 41.23%,18.13% 41.53%,18.75% 41.53%,19.38% 43.46%,20.00% 42.72%,20.63% 42.27%,21.25% 42.12%,21.88% 42.12%,22.50% 42.72%,23.13% 45.84%,23.75% 52.39%,24.38% 53.13%,25.00% 54.03%,25.62% 54.18%,26.25% 52.24%,26.88% 51.20%,27.50% 50.90%,28.13% 60.28%,28.75% 55.37%,29.38% 55.37%,30.00% 59.53%,30.63% 56.71%,31.25% 53.13%,31.87% 53.13%,32.50% 55.81%,33.13% 55.07%,33.75% 61.47%,34.38% 60.28%,35.00% 58.79%,35.63% 57.90%,36.25% 65.63%,36.88% 65.19%,37.50% 64.89%,38.13% 64.30%,38.75% 64.30%,39.38% 68.46%,40.00% 68.46%,40.63% 71.44%,41.25% 72.48%,41.88% 68.76%,42.50% 66.08%,43.13% 63.85%,43.75% 63.10%,44.38% 63.10%,45.00% 69.95%,45.63% 69.21%,46.25% 68.76%,46.88% 68.76%,47.50% 76.50%,48.13% 76.50%,48.75% 75.90%,49.38% 75.46%,50.00% 77.24%,50.63% 77.24%,51.25% 79.33%,51.88% 76.94%,52.50% 75.90%,53.13% 79.33%,53.75% 75.75%,54.37% 75.16%,55.00% 75.16%,55.63% 85.72%,56.25% 86.02%,56.88% 87.21%,57.50% 88.25%,58.13% 85.28%,58.75% 81.85%,59.38% 81.85%,60.00% 80.52%,60.62% 78.28%,61.25% 78.28%,61.88% 68.46%,62.50% 67.12%,63.13% 66.97%,63.75% 67.12%,64.38% 69.65%,65.00% 74.56%,65.63% 70.69%,66.25% 70.69%,66.88% 64.30%,67.50% 55.52%,68.13% 55.52%,68.75% 55.66%,69.38% 56.56%,70.00% 58.05%,70.63% 60.87%,71.25% 61.77%,71.88% 60.13%,72.50% 58.79%,73.13% 58.94%,73.75% 60.58%,74.38% 62.21%,75.00% 50.60%,75.63% 50.60%,76.25% 52.54%,76.88% 52.39%,77.50% 50.90%,78.13% 48.82%,78.75% 41.23%,79.38% 40.34%,80.00% 39.89%,80.63% 39.89%,81.25% 39.89%,81.88% 38.70%,82.50% 37.51%,83.13% 36.77%,83.75% 36.47%,84.38% 36.47%,85.00% 36.47%,85.63% 35.13%,86.25% 33.94%,86.88% 26.94%,87.50% 26.05%,88.13% 25.46%,88.75% 19.95%,89.38% 19.80%,90.00% 19.95%,90.63% 15.19%,91.25% 14.59%,91.88% 14.44%,92.50% 14.44%,93.13% 15.04%,93.75% 15.78%,94.38% 14.15%,95.00% 12.96%,95.63% 12.36%,96.25% 11.91%,96.88% 12.06%,97.50% 12.81%,98.13% 14.89%,98.75% 14.44%,99.38% 14.59%,100.00% 15.49%,100% 100%,0% 100%)}
.v-fill{background:var(--void)}
.v-far .v-fill{background:color-mix(in srgb,var(--navy) 62%,var(--void))}
.v-mid .v-fill{background:color-mix(in srgb,var(--navy) 30%,var(--void))}
.v-far .v-ink{color:var(--muted);opacity:calc(.32 * var(--ink-k))}
.v-mid .v-ink{color:var(--teal-text);opacity:calc(.42 * var(--ink-k))}
.v-near .v-ink{color:var(--body-strong);opacity:calc(.26 * var(--ink-k))}
.v-near{bottom:-150px}
.v-cap{bottom:24%;inset-inline-start:max(var(--gutter),calc(50vw - var(--max) / 2 + var(--gutter)));z-index:2}
@media (max-width:960px){.vista{margin-top:calc(-1 * var(--s-5))}}

/* ---------- shared: plates inside sections ---------- */
#work,#founder,#stack,#faq{position:relative;isolation:isolate}
#work>:not(.px-wrap),#founder>:not(.px-wrap),#stack>:not(.px-wrap),#faq>:not(.px-wrap){position:relative;z-index:1}
.pi{position:absolute;display:block}
.pi>i{position:absolute;inset:0}
.pi .pl-cap{inset-inline-start:0;top:calc(100% + 8px)}

/* Plate II — the arch behind "the proof", a gate into the work */
.pi-arch{top:-10px;inset-inline-end:-2%;width:min(620px,52%);aspect-ratio:1000/558;color:var(--teal-text)}
.pi-arch>i{opacity:calc(.13 * var(--ink-k))}
.pi-arch .pl-cap{top:-6px;inset-inline-start:auto;inset-inline-end:4%}

/* Plate III — three coins over the price list, each turning on its own axis */
.pi-coins{position:absolute;top:clamp(70px,7vw,110px);inset-inline-end:0;width:min(400px,34%);aspect-ratio:1.35;display:none}
.pc{width:44%;aspect-ratio:1;color:var(--crimson-text)}
.pc>i{opacity:calc(.5 * var(--ink-k))}
.live .pc>i{animation:coin-turn 9s ease-in-out 2}
.pc-king{top:0;left:28%;width:46%}
.pc-horse{top:46%;left:0}
.pc-palm{top:40%;left:56%}
.live .pc-horse>i{animation-delay:1.2s}.live .pc-palm>i{animation-delay:2.4s}
.pi-coins .pl-cap{top:auto;bottom:-26px;inset-inline-start:auto;inset-inline-end:0}
@keyframes coin-turn{0%,100%{transform:perspective(500px) rotateY(0)}45%,55%{transform:perspective(500px) rotateY(32deg)}}
@media (min-width:1100px){.pi-coins{display:block}}

/* founder: the workbench, seen from above, beside the card */
.pi-bench{top:16%;inset-inline-end:-4%;width:min(600px,46%);aspect-ratio:1100/614;color:var(--teal-text)}
.pi-bench>i{opacity:calc(.1 * var(--ink-k))}

/* Plate IV — the stack as strata, with a core sample being drawn */
.pi-strata{top:4%;inset-inline-end:-2%;width:min(720px,62%);aspect-ratio:1100/463;color:var(--teal-text)}
.pi-strata>i{opacity:calc(.16 * var(--ink-k))}
.pi-strata .core{position:absolute;top:0;bottom:0;width:2px;inset-inline-start:62%;
  background:linear-gradient(transparent,var(--crimson-text),transparent);opacity:.55;
  opacity:0}
.pi-strata.live .core{animation:core-drill 7s ease-in-out 3}
.pi-strata .pl-cap{top:calc(100% - 8px);inset-inline-start:auto;inset-inline-end:8%}
@keyframes core-drill{0%,100%{transform:translateX(-140px);opacity:0}15%,85%{opacity:.55}50%{transform:translateX(140px)}}
html[dir="rtl"] .pi-strata>i{transform:scaleX(-1)}

/* FAQ — a lantern hangs in the gutter between the heading and the lead */
.pi-lantern{display:none;top:0;left:calc(50% - 64px);width:128px;aspect-ratio:560/835;
  transform-origin:50% 0;color:var(--crimson-text)}
.pi-lantern.live{animation:lantern-swing 6.5s ease-in-out 4}
.pi-lantern::before{content:"";position:absolute;bottom:100%;left:50%;width:1px;height:200px;background:currentColor;opacity:.35}
.pi-lantern>i{opacity:calc(.6 * var(--ink-k))}
.pi-lantern .glow{position:absolute;left:-40%;right:-40%;top:38%;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(closest-side,color-mix(in srgb,var(--crimson-text) 32%,transparent),transparent);
  opacity:.55}
.pi-lantern.live .glow{animation:lantern-glow 3.2s ease-in-out 8}
@keyframes lantern-swing{0%,100%{rotate:0deg}25%{rotate:3deg}75%{rotate:-3deg}}
@keyframes lantern-glow{0%,100%{opacity:.55}50%{opacity:1}}
@media (min-width:1280px){.pi-lantern{display:block}}

/* Plate V — the lighthouse; its beam sweeps across the contact section */
.pi-light{bottom:0;inset-inline-start:-3%;width:min(640px,54%);aspect-ratio:1100/595;color:var(--teal-text)}
.pi-light>i{opacity:calc(.16 * var(--ink-k))}
.pi-light .beam{position:absolute;left:79.6%;top:13%;width:150%;height:56%;translate:0 -50%;transform-origin:0 50%;
  background:linear-gradient(90deg,color-mix(in srgb,var(--crimson-text) 30%,transparent),transparent 70%);
  clip-path:polygon(0 48%,100% 0,100% 100%,0 52%);opacity:.5}
.pi-light.live .beam{animation:beam 8s linear 3}
.pi-light .pl-cap{top:auto;bottom:14px;inset-inline-start:9%}
@keyframes beam{0%{transform:scaleX(1)}25%{transform:scaleX(.05)}50%{transform:scaleX(-1)}75%{transform:scaleX(-.05)}100%{transform:scaleX(1)}}
html[dir="rtl"] .pi-light{transform:scaleX(-1)}
html[dir="rtl"] .pi-light .pl-cap{transform:scaleX(-1)}

/* Plate VI — the erg under the footer */
.foot-dunes{position:relative;z-index:1;height:clamp(150px,22vw,330px);overflow:hidden;pointer-events:none;margin-top:var(--s-7)}
.fd-ink{position:absolute;left:50%;bottom:-30px;width:max(calc(100% + 60px),900px);aspect-ratio:1500/596;
  margin-left:calc(max(calc(100% + 60px),900px) / -2);color:var(--crimson-text);opacity:.2}
.foot-dunes .pl-cap{bottom:14px;inset-inline-end:max(var(--gutter),calc(50vw - var(--max) / 2 + var(--gutter)))}
.foot-dunes::after{content:"";position:absolute;inset:auto 0 0;height:40%;background:linear-gradient(transparent,var(--void))}


@media (max-width:640px){.pi-arch,.pi-bench{display:none}}
@media (prefers-reduced-motion:reduce){
  .pc>i,.pi-lantern,.pi-lantern .glow,.pi-strata .core,.pi-light .beam,.v-sky .tw{animation:none!important}
  .v-sky .v-const{transition:none}
}

/* ---------- twinkle ---------- */
@media (prefers-reduced-motion:no-preference){
  .vista.live .v-sky .tw{animation:twinkle 4.8s ease-in-out 4}
  .vista.live .v-sky .tw1{animation-delay:-1.2s}.vista.live .v-sky .tw2{animation-delay:-2.6s;animation-duration:6.2s}.vista.live .v-sky .tw3{animation-delay:-3.7s;animation-duration:3.9s}
}
@keyframes twinkle{0%,100%{opacity:.6}50%{opacity:.12}}

/* ═══ animated icons (.ai) ═══
   24×24, stroke 1.5, currentColor — the service-mark grammar. Every stroked
   part with pathLength="1" draws itself in the first time the icon is seen
   (.on). The idle loop then plays a few times and settles; it replays each
   time the icon comes back on screen (.live is toggled by visibility). Nothing
   here, icon or plate, loops forever: that rule from the anti-slop sweep
   stands, and `npm run audit` fails on any endless animation. */
.ai{width:26px;height:26px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;overflow:visible;color:var(--teal-text)}
.ai .f{fill:currentColor;stroke:none}
.ai .orb{stroke-dasharray:1.4 2.6;opacity:.45}
/* Drawable parts carry class .p — never select them as [pathLength]. Chromium
   does not invalidate a camelCase SVG attribute selector when an ancestor's
   class changes: `.ai.on [pathLength]` matched, yet the computed style stayed
   stale at dashoffset 1 until something else (a .reveal on an ancestor)
   happened to restyle the subtree. The contact channels have no such
   ancestor, so their icons simply never drew. */
.ai .p{stroke-dasharray:1;stroke-dashoffset:1}
.ai.on .p,html:not([data-lz]) .ai .p{stroke-dashoffset:0}
@media (prefers-reduced-motion:no-preference){
  .ai.on .p{animation:ai-draw 1.2s cubic-bezier(.6,0,.2,1) backwards}
  .ai.on .p:nth-child(2){animation-delay:.15s}.ai.on .p:nth-child(3){animation-delay:.3s}
}
@keyframes ai-draw{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
.ai g,.ai path,.ai circle,.ai ellipse{transform-box:fill-box;transform-origin:center}
.ai [style*="--o"]{transform-box:view-box;transform-origin:var(--o)}

@media (prefers-reduced-motion:no-preference){
  .ai.live .a-spin{animation:ai-spin var(--t,10s) linear var(--n,3)}
  .ai.live .a-hand{animation:ai-spin var(--t,6s) linear var(--n,3)}
  .ai.live .a-orbit{animation:ai-spin 7s linear var(--n,3)}
  .ai.live .a-globe{animation:ai-globe 5s ease-in-out var(--n,3)}
  .ai.live .a-blink{animation:ai-blink 1.1s steps(2,jump-none) var(--n,3)}
  .ai.live .a-dt{animation:ai-dot 1.4s ease-in-out calc(var(--i) * .18s) var(--n,3)}
  .ai.live .a-led{animation:ai-led 2.2s steps(2,jump-none) calc(var(--i) * -1.1s) var(--n,3)}
  .ai.live .a-hop{animation:ai-hop 2.6s cubic-bezier(.3,0,.3,1) var(--n,3)}
  .ai.live .a-shadow{animation:ai-shadow 2.6s cubic-bezier(.3,0,.3,1) var(--n,3)}
  .ai.live .a-flap{animation:ai-flap 3.6s ease-in-out 1.2s var(--n,3)}
  .ai.live .a-up{animation:ai-up 2.4s ease-in-out var(--n,3)}
  .ai.live .a-scan{animation:ai-scan 3s ease-in-out var(--n,3)}
  .ai.live .a-wave{animation:ai-wave 2s ease-out calc(var(--i) * .3s) var(--n,3)}
  .ai.live .a-flip{animation:ai-flip 5s cubic-bezier(.7,0,.3,1) var(--n,3)}
  .ai.live .a-swing{animation:ai-swing 3.4s ease-in-out var(--n,3)}
  .ai.live .a-alt{animation:ai-alt 3.2s ease-in-out calc(var(--i) * -1.6s) var(--n,3)}
  .ai.live .a-ekg{stroke-dasharray:.28 .72;animation:ai-ekg 2.2s linear var(--n,3)}
  .ai.live .a-travel{animation:ai-travel 2.4s ease-in-out var(--n,3)}
  .ai.live .a-pulse{animation:ai-pulse 2.6s ease-in-out var(--n,3)}
  .ai.live .a-bob{animation:ai-bob 2.8s ease-in-out var(--n,3)}
  .ai.live .a-twinkle{animation:ai-twinkle 2.4s ease-in-out var(--n,3)}
  .ai.live .a-redraw{animation:ai-redraw 4.5s ease-in-out 1.6s var(--n,3)}
  .ai.live .a-sl{animation:ai-sl 3.6s ease-in-out calc(var(--i) * -1.2s) var(--n,3)}
  .ai.live .a-tilt{animation:ai-tilt 4.2s ease-in-out var(--n,3)}
}
@keyframes ai-spin{to{transform:rotate(360deg)}}
@keyframes ai-globe{0%,100%{transform:scaleX(1)}50%{transform:scaleX(.12)}}
@keyframes ai-blink{0%{opacity:1}100%{opacity:0}}
@keyframes ai-dot{0%,60%,100%{transform:translateY(0);opacity:.45}30%{transform:translateY(-1.6px);opacity:1}}
@keyframes ai-led{0%{opacity:1}100%{opacity:.25}}
@keyframes ai-hop{0%,55%,100%{transform:translateY(0)}25%{transform:translateY(-2.4px)}}
@keyframes ai-shadow{0%,55%,100%{transform:scaleX(1);opacity:.8}25%{transform:scaleX(.6);opacity:.35}}
@keyframes ai-flap{0%,70%,100%{transform:scaleY(1)}80%{transform:scaleY(-.35)}90%{transform:scaleY(1)}}
@keyframes ai-up{0%{transform:translateY(1.5px);opacity:0}30%{opacity:1}70%{opacity:1}100%{transform:translateY(-2.5px);opacity:0}}
@keyframes ai-scan{0%,100%{transform:translateY(-4px)}50%{transform:translateY(4px)}}
@keyframes ai-wave{0%{opacity:0}30%{opacity:1}100%{opacity:0}}
@keyframes ai-flip{0%,40%{transform:rotate(0)}60%,100%{transform:rotate(180deg)}}
@keyframes ai-swing{0%,100%{transform:rotate(-9deg)}50%{transform:rotate(9deg)}}
@keyframes ai-alt{0%,100%{opacity:1}50%{opacity:.3}}
@keyframes ai-ekg{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
@keyframes ai-travel{0%,100%{transform:translateX(0)}50%{transform:translateX(6.8px)}}
@keyframes ai-pulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.6);opacity:.5}}
@keyframes ai-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(1px)}}
@keyframes ai-twinkle{0%,100%{transform:scale(1) rotate(0);opacity:1}50%{transform:scale(.4) rotate(45deg);opacity:.4}}
@keyframes ai-sl{0%,100%{transform:translateX(0)}50%{transform:translateX(4px)}}
@keyframes ai-tilt{0%,100%{transform:rotate(-5deg)}50%{transform:rotate(5deg)}}
@keyframes ai-redraw{0%,70%{stroke-dashoffset:0}80%{stroke-dashoffset:1}100%{stroke-dashoffset:0}}

/* placements */
.ai-stat{width:30px;height:30px;margin:0 auto var(--s-3);display:block;color:var(--crimson-text)}
.ai-corner{position:absolute;top:var(--s-5);inset-inline-end:var(--s-5);width:28px;height:28px}
.md-card{position:relative}
.est-head,.mk-head{position:relative}
.est-head .ai-corner,.mk-head .ai-corner{top:var(--s-5);inset-inline-end:var(--s-5)}
.est-head h3,.mk-head h3,.est-head p,.mk-head p{padding-inline-end:44px}
.md-card .ai-corner{top:var(--s-5);inset-inline-end:var(--s-5);width:24px;height:24px}
.tier-pop .ai-corner{color:var(--crimson-text)}
.stack-strip .grp>b{display:inline-flex;align-items:center;gap:8px}
.ai-grp{width:20px;height:20px}
.ai-faq{width:24px;height:24px}
.faq-item[open] .ai-faq{color:var(--crimson-text)}
@media (hover:hover){.faq-item summary:hover .ai-faq{color:var(--crimson-text)}}
.channels .ch-ic{grid-template-columns:22px 96px minmax(0,1fr)}
.ai-ch{width:20px;height:20px;align-self:center}
@media (max-width:400px){
  .channels .ch-ic{grid-template-columns:22px minmax(0,1fr)}
  .channels .ch .ai-ch{grid-row:1/span 2}
}

@media print{
  .vista,.foot-dunes,.px-wrap{display:none!important}
  .ai .p{stroke-dashoffset:0!important}
}
@media (max-width:640px){
  .v-mid{right:-150px}
  .v-cap{bottom:14%}
}

/* ============ WorkspaceHQ cartridge ============
   The founder's own product, presented as a game cartridge with Bit on the
   label. It wears WorkspaceHQ's arcade palette whatever the page theme (it is
   a different product's box), on its own dark ground, so contrast holds in
   light themes too. Still at rest — nothing here animates until hover/focus,
   so the page's no-endless-animation rule holds. */
@font-face{font-family:"Press Start 2P";src:url(../workspacehq/fonts/PressStart2P.woff2) format("woff2");font-display:swap}
.whq-cart{--g:#ffd23f;--g2:#ffe27a;position:relative;display:flex;align-items:center;gap:14px;max-width:440px;
  margin-top:22px;padding:14px 16px 24px 14px;color:#e8e6ff;text-decoration:none;background:linear-gradient(160deg,#1a1538,#100d24);
  border:2px solid #322a5e;outline:2px solid #050409;outline-offset:0;box-shadow:0 5px 0 #050409;
  transition:box-shadow .2s,border-color .2s,transform .2s cubic-bezier(.34,1.56,.64,1)}
.whq-cart::before{content:"";position:absolute;left:12px;right:12px;top:0;height:5px;
  background:repeating-linear-gradient(90deg,#322a5e 0 6px,transparent 6px 10px)}
/* the cartridge's cut corner, drawn rather than clipped so the hover ring shows */
.whq-cart::after{content:"";position:absolute;top:-2px;right:-2px;width:16px;height:16px;
  background:linear-gradient(225deg,var(--whq-page,#07060f) 0 50%,transparent 50%)}
.whq-bit{flex:none;width:54px;height:44px;display:grid;place-items:end center;
  background:repeating-linear-gradient(90deg,transparent 0 7px,rgba(90,78,168,.18) 7px 8px),#0c0a1e;border:2px solid #050409;padding:4px 0 3px}
.whq-bit svg{width:36px;height:27px;display:block;filter:drop-shadow(0 0 6px rgba(217,119,87,.45));transition:transform .18s cubic-bezier(.34,1.56,.64,1)}
.whq-eyes{transform-box:fill-box;transform-origin:center}
.whq-txt{display:grid;gap:5px;min-width:0}
.whq-kick{font-family:"Press Start 2P",monospace;font-size:7px;letter-spacing:1px;line-height:1.6;color:#00f0ff}
.whq-name{font-family:"Press Start 2P",monospace;font-weight:400;font-size:13px;letter-spacing:1px;line-height:1.3;color:#e8e6ff;white-space:nowrap}
.whq-name i{font-style:normal;color:var(--g);text-shadow:0 0 8px rgba(255,210,63,.6)}
.whq-name em{font-style:normal;font-size:9px;color:#ff2bd6}
.whq-sub{font-size:12px;line-height:1.35;color:#b3add9}
.whq-start{position:absolute;right:12px;bottom:7px;font-family:"Press Start 2P",monospace;font-size:6px;letter-spacing:1px;color:var(--g2);opacity:0}
.whq-xp{position:absolute;inset-inline-start:34px;top:4px;font-family:"Press Start 2P",monospace;font-size:7px;color:var(--g);text-shadow:0 0 8px rgba(255,210,63,.8);opacity:0;pointer-events:none}
.whq-cart:hover,.whq-cart:focus-visible{transform:translateY(-3px);border-color:var(--g);box-shadow:0 5px 0 #8a6200,0 0 28px -6px rgba(255,210,63,.75)}
.whq-cart:focus-visible{outline:2px solid var(--g);outline-offset:4px}
.whq-cart:hover .whq-bit svg,.whq-cart:focus-visible .whq-bit svg{animation:whq-hop .55s steps(4) 2}
.whq-cart:hover .whq-eyes,.whq-cart:focus-visible .whq-eyes{animation:whq-blink .9s steps(1) 1 .3s}
.whq-cart:hover .whq-start,.whq-cart:focus-visible .whq-start{animation:whq-press 1.1s steps(1) 3}
.whq-cart:hover .whq-xp,.whq-cart:focus-visible .whq-xp{animation:whq-xp .9s ease-out 1 forwards}
@keyframes whq-hop{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
@keyframes whq-blink{0%,100%{transform:scaleY(1)}20%,40%{transform:scaleY(.1)}}
@keyframes whq-press{0%{opacity:1}50%{opacity:0}}
@keyframes whq-xp{0%{opacity:0;transform:translateY(4px)}25%{opacity:1}100%{opacity:0;transform:translateY(-22px)}}
html[dir="rtl"] .whq-cart::after{right:auto;left:-2px;background:linear-gradient(135deg,var(--whq-page,#07060f) 0 50%,transparent 50%)}
/* Press Start 2P has no Arabic glyphs */
html[dir="rtl"] .whq-kick{font-family:var(--font-ar-body,system-ui),sans-serif;font-size:12px;letter-spacing:0}
html[dir="rtl"] .whq-start{right:auto;left:12px}
@media (prefers-reduced-motion:reduce){.whq-cart,.whq-bit svg{transition:none}.whq-cart *{animation:none!important}.whq-cart:hover{transform:none}}
.footer .whq-cart{margin:var(--s-4) 0 var(--s-6)}
/* "WORKSPACEHQ v3" is one unbreakable line of a 1em-wide pixel face: at 13px it
   ran 12px past a 280px screen, pushing the label and the sub-line with it */
@media (max-width:340px){.whq-name{font-size:10px}}
.whq-cart{--whq-page:var(--void)}

/* ═══ visual pass, Oct 2026 ═══ */
/* The proof band is a compact strip, not a chapter: it inherited the full
   section padding (109px a side at 1280) and read as an empty box. */
main.wrap>section.proof{padding-block:clamp(48px,5.5vw,84px)}

/* The scene banner's three plates were blank cards that read as a missing
   image. They now carry the real client screenshots, fanned like a stack of
   drawing sheets. */
.sb-art i{background-size:cover;background-position:top left}
.sb-art i:nth-child(1){background-image:url(work/alliance/d1.webp)}
.sb-art i:nth-child(2){background-image:url(previews/glaive.webp)}
.sb-art i:nth-child(3){background-image:url(previews/bordjsteel.webp)}
.sb-art{width:92px;height:62px}

/* Footer links: WhatsApp is a flex item (icon slot) and centred its label in
   the 39px row while the others sat at the top — 10px lower. One rule for all. */
.footer nav a{display:inline-flex;align-items:center;justify-content:center;min-width:24px;min-height:24px}

@media (max-width:599px){
  /* Work cards on phones: a 16:10 screenshot squeezed into a narrow strip
     beside the text cut every headline mid-word ("VENIR, E", "BUILT FO").
     Stacked, each shot keeps its real shape. Tablets keep the side layout,
     where the strip is wide enough to read. */
  .work-grid>.proj{display:block}
  .work-grid>.proj .thumb{aspect-ratio:16/10;height:auto;min-height:0}
  .work-grid>.proj .thumb--art .cover{display:block}
  .work-grid>.proj .thumb--art .art-glyph{width:52px;height:52px}
  /* "Voir toutes les capacités": the label was squeezed to three lines beside
     its hint. The hint drops to its own line. */
  .capabilities>summary{flex-wrap:wrap;row-gap:6px}
  .capabilities>summary .hint{order:3;flex-basis:100%}
}

/* RTL: the wordmark is three runs (Num · idea · Labs) plus the brick; in an
   Arabic paragraph the bidi algorithm reordered them to "Labs idea Num".
   A brand name has one direction. */
.logo{direction:ltr;unicode-bidi:isolate}
/* "<24h": the "<" is a neutral, so in RTL it migrated to the far end ("24h>") */
.stat{direction:ltr;unicode-bidi:isolate}
/* In Arabic the figure carries an Arabic unit ("<24 س"), and a forced-LTR box
   put the unit in front of the number for a right-to-left reader. There the
   box takes its direction from its own text (plaintext): app.js isolates "<24"
   (U+2066…U+2069), so the Arabic unit is the first strong letter, the line is
   RTL and the unit lands after the figure, on the left; the baked "<24h" that
   shows before the count-up has a Latin unit, so it stays LTR and whole. Geist
   keeps the digits, Plex Arabic takes the unit. */
html[dir="rtl"] .stat{unicode-bidi:plaintext;font-family:"Geist", var(--font-ar-display)}

/* ═══════════════════════════ BENTO + LOADING + WAYFINDING ═══════════════════════════ */

/* ---- Work: two featured wide cards; the grid closes flush ----
   3 cols: wide(2)+1 / 1+wide(2) / 1+1+1 = 9 cells. 2 cols (961–1099): only the first
   stays wide, 2+6 = 8. The old orphan rules (centre the odd last card) are
   neutralised: the bento has no orphan. A wide card keeps the stacked card
   anatomy with a 21:9 banner shot: side-by-side made the image box taller
   than wide and cropped every headline ("L'AVENIR, ENS"); 21:9 across two
   columns shows each site's whole top band and lands within ~30px of its
   neighbour's height. */
.work-grid>.proj:last-child{grid-column:auto!important;max-width:none!important;margin-inline:0!important}
@media (min-width:961px){
  .work-grid>.proj--wide:first-child{grid-column:span 2!important}
  .work-grid>.proj--wide:first-child .thumb{aspect-ratio:21/9}
}
@media (min-width:1100px){
  .work-grid>.proj--wide{grid-column:span 2!important}
  .work-grid>.proj--wide .thumb{aspect-ratio:21/9}
  .work-grid>.proj--wide h3{font-size:clamp(22px,2vw,28px)}
  .work-grid>.proj--wide p{min-height:0}
}
/* every card in a row ends on the same line: actions pinned to the bottom */
.proj{display:flex;flex-direction:column}
.proj .body{flex:1;display:flex;flex-direction:column}
.proj .actions{margin-top:auto}

/* ---- Services: 6-col bento, two large offers over three ---- */
@media (min-width:961px){
  .svc-grid{grid-template-columns:repeat(6,minmax(0,1fr))}
  .svc-grid>.svc-row{grid-column:span 2!important}
  .svc-grid>.svc-row:nth-child(-n+2){grid-column:span 3!important}
}

/* ---- Pricing: the free-preview banner spans the tier grid it introduces ---- */

/* ---- Founder: the card and the team share the row ----
   The card left ~25% of the row empty beside it; the team now fills it. */
@media (min-width:1100px){
  #founder>.founder-card{grid-column:1/span 8;max-width:none;align-self:start}
  #founder>.team{grid-column:9/-1;max-width:none;margin-top:0;align-self:start}
  #founder .team-grid{grid-template-columns:minmax(0,1fr)!important}
  #founder>.kicker+h2.sec+.sec-lead+.founder-card+.team{margin-top:var(--s-8)}
}

/* ---- Stack: groups become tiles (3+3 / 2+2+2) ---- */
.stack-strip{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:clamp(10px,1.2vw,16px)}
.stack-strip .grp{grid-column:span 2;display:flex;flex-direction:column;align-items:flex-start;gap:12px;
  padding:clamp(16px,1.8vw,22px);background:color-mix(in srgb,var(--navy) 88%,transparent);border:1px solid var(--hairline);border-radius:var(--r-md)}
.stack-strip .grp:nth-child(-n+2){grid-column:span 3}
@media (max-width:719px){
  .stack-strip{grid-template-columns:minmax(0,1fr)}
  .stack-strip .grp,.stack-strip .grp:nth-child(-n+2){grid-column:auto}
}

/* ---- Skeletons: a picture's box shimmers until the picture has decoded ----
   Boxes already have their final size (width/height + aspect-ratio), so
   nothing moves; the shimmer runs a bounded number of times — the page's
   no-endless-animation rule holds even on a slow network. */
.sk{position:relative}
.sk::after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;opacity:1;transition:opacity .45s ease;
  background:linear-gradient(100deg,transparent 30%,color-mix(in srgb,var(--ice) 7%,transparent) 50%,transparent 70%) var(--navy);
  background-size:220% 100%;animation:sk-shine 1.3s ease-in-out 8}
.sk.sk-done::after{opacity:0}
@keyframes sk-shine{from{background-position:120% 0}to{background-position:-120% 0}}
@media (prefers-reduced-motion:reduce){.sk::after{animation:none}}
.proj .thumb.sk::after{z-index:2}
.proj .thumb .browser{z-index:3}

/* ---- Wayfinding: the section you are in ---- */
.nav-links a[aria-current="true"]{color:var(--ice)}
.nav-links a{position:relative}
.nav-links a[aria-current="true"]::after{content:"";position:absolute;left:0;right:0;bottom:-6px;height:2px;border-radius:2px;background:var(--crimson)}
/* phones: the links are folded into the menu, so a breadcrumb names the section */
.crumb{display:none;align-items:center;gap:6px;min-width:0;font-family:var(--font-mono);font-size:var(--t-label);letter-spacing:.04em;text-transform:uppercase;color:var(--muted);
  opacity:0;transition:opacity .25s}
.crumb i{font-style:normal;color:var(--faint)}
.crumb b{font-weight:400;color:var(--crimson-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.crumb.on{opacity:1}
@media (max-width:960px){.crumb{display:inline-flex;flex:1 1 auto;margin-inline-start:12px}}
@media (max-width:359px){.crumb{display:none}}
html[dir="rtl"] .crumb{letter-spacing:0}

/* ---- Back to top ---- */
.to-top{position:fixed;inset-inline-end:max(16px,env(safe-area-inset-right));bottom:max(16px,env(safe-area-inset-bottom));z-index:90;
  width:44px;height:44px;display:grid;place-items:center;border-radius:50%;cursor:pointer;
  background:var(--navy);color:var(--ice);border:1px solid var(--hairline-2);box-shadow:0 12px 30px -14px rgb(var(--shade) / calc(.9 * var(--shade-k)));
  opacity:0;transform:translateY(12px);pointer-events:none;
  /* hidden, not just transparent: an invisible button still took Tab focus.
     visibility flips after the fade out and at once on the way in. */
  visibility:hidden;transition:opacity .3s,transform .3s var(--ease-out),border-color .2s,visibility 0s .3s}
.to-top.on{opacity:1;transform:none;pointer-events:auto;visibility:visible;transition:opacity .3s,transform .3s var(--ease-out),border-color .2s,visibility 0s}
/* the inline end is the left in Arabic, so it clears the left inset (a notch in landscape) */
html[dir="rtl"] .to-top{inset-inline-end:max(16px,env(safe-area-inset-left))}
.to-top:hover{border-color:var(--teal)}
.to-top:focus-visible{outline:2px solid var(--teal);outline-offset:3px}
.to-top svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
@media print{.to-top,.crumb{display:none!important}}
/* Service cards: the icon sits above the copy at every width, so a narrow
   card gives its text the full width. Rows hold their content height and the
   spare height goes to the bottom, so eyebrows line up across a row. */
.svc-grid>.svc-row{grid-template-columns:minmax(0,1fr);row-gap:var(--s-4);align-content:start}

/* ═══════════════════════════ DESIGN SYSTEM ═══════════════════════════
   One scale, applied last so it wins over the per-section sizes that had
   piled up: the page was rendering 36 font sizes, 41 letter-spacings and
   seven different card-title sizes. Seven text steps below the display
   headings, three corner radii, one section rhythm. Engineering keeps its
   own system in theme-engineering.css. */
:root{
  --t-label:11px;    /* mono caps: kickers, eyebrows, captions, column heads */
  --t-small:13px;    /* fine print, notes, sources */
  --t-ui:14px;       /* buttons, chips, form controls */
  --t-body:15px;     /* card copy */
  --t-lead:18px;     /* section intros, promises, bios, questions */
  --t-title:22px;    /* every card title */
  --t-feature:30px;  /* featured titles and price figures */
  --ls-label:.14em;
  --r-lg:var(--r-md);   /* panels share the card radius: 4 / 8 / 14 */
  --sec-pad:clamp(72px,7vw,112px);
  --band-fade:clamp(56px,8vw,120px);
}

/* labels */
.kicker,.eyebrow,.tag,.svc-row .fam,.cap .fam,.t-pop,.md-tag,.md-sub,.eo-k,.est-hint,
.stat-label,.pl-cap,.v-cap,.sb-kick,.proj .cat,.titleblock dt,.channels dt,.field label,
.est-field legend,.est-field>label,.est-lab,.mk-table caption,.mk-table thead th,.team-h,
.t-time,.eo-time,.fd-role,.outcome b,.stack-strip .grp>b{
  font-family:var(--font-mono);font-size:var(--t-label);letter-spacing:var(--ls-label);
  text-transform:uppercase;font-weight:400}
.pill{font-size:var(--t-label);letter-spacing:.02em}
/* the service family was a sentence-case white line here: it is the same
   eyebrow as everywhere else now. Not the kicker's accent: at 11px on a card
   surface the accent fell under 4.5:1 in Noir and Mono. */
.svc-row .fam,.cap .fam{display:block;color:var(--body-strong);margin-bottom:8px}
.t-time{color:var(--muted)}

/* small */
.eo-fine,.mk-note,.md-where,.mk-src p,.mk-src li,.sb-lead,.cv-note,.capabilities>summary .hint{font-size:var(--t-small);line-height:1.6}
/* the disclosure hints sat on --faint, 4.44:1 on the pricing surface */
.capabilities>summary .hint{color:var(--muted)}

/* body */
.proj .body p,.svc-row .brief,.preview .brief,.cap .brief,.step p,.t-for,.t-list li,.est-head p,
.mk-head p,.price-note,.md-card p:not(.md-where),.proof-line,.mk-day,.outcome span,
.faq-item .a,.faq-item p{font-size:var(--t-body);line-height:1.6}

/* lead */
.sec-lead,.svc-lead,.contact .sub,.hero .lede,.fd-bio,.faq-item summary .q,
.svc-row .promise,.preview .promise,.cap .promise,.est-sizev{font-size:var(--t-lead);line-height:1.5}
.svc-row .promise,.preview .promise,.cap .promise{margin:4px 0 var(--s-3)}

/* titles */
.proj h3,.work-grid>.proj h3,.svc-row h3,.step h3,.steps .step h3,.cap h4,.md-card h4,
.scene-banner b{font-size:var(--t-title);line-height:1.2;letter-spacing:-.01em}
.preview h3,.tier h3,.fd-id h3,.est-head h3,.mk-head h3,
.work-grid>.proj--wide h3{font-size:var(--t-feature);line-height:1.15;letter-spacing:-.015em}
/* price figures cannot wrap ("1 200 000" is one unit), so they scale down to
   fit three tiers abreast on a tablet, currency included: at 2.2vw the "DA"
   dropped to its own line on two tiers of three and their lists started at
   different heights */
.t-range b,.eo-range b,.eo-range .eo-dash{font-size:clamp(19px,1.9vw,var(--t-feature));letter-spacing:-.02em}
.t-range .cur,.eo-range .cur,.mk-table .cur{font-size:var(--t-small);letter-spacing:.06em}

/* one display size for every section heading, contact included, and one
   accent colour for the emphasised words */
.contact h2{font-size:clamp(26px, 14px + 3.33vw, 62px);margin-bottom:var(--s-7)}
.contact h2 em{color:var(--crimson-text)}

/* ---- rhythm: one section padding, one header-to-content gap ---- */
@media (min-width:961px){
  main.wrap>section{padding-block:var(--sec-pad)}
  main.wrap>section>.kicker+h2.sec+.sec-lead+*,
  main.wrap>section>.kicker+h2.sec+.svc-lead+*,
  main.wrap>section>.kicker+h2.sec+*{margin-top:var(--s-7)}
}

/* ---- surfaces: bands fade in and out instead of ending on a hard edge ----
   The old wash was a 100vmax box-shadow, which cannot carry a gradient, so
   every band started and stopped on a ruler-straight line. */
section.band::after,section.band+section.band::after{
  inset:0 calc(50% - 50vw);box-shadow:none;clip-path:none;
  background:linear-gradient(180deg,transparent,var(--band) var(--band-fade),
    var(--band) calc(100% - var(--band-fade)),transparent)}
/* the proof strip was the only navy slab inside the page width. It takes the
   page colour instead: invisible as a box, but still opaque, so the landscape
   plate's parallax layers never slide under its kicker */
.proof{background:var(--void)}

/* ---- hero: the landscape plate and the footer dunes were ~440px and ~360px
   of pure picture between content; both are now an interlude, not a stop */
.vista{height:clamp(160px,22vw,320px)}
.foot-dunes{height:clamp(110px,14vw,210px)}
@media (max-width:600px){.titleblock{display:none}}

/* ---- work ---- */
/* the placeholder art repeated the card title right above it */
.proj .thumb--art .cover,.work-grid>.proj .thumb--art .cover{display:none}
/* the wide banner crop: 2:1 from the centre keeps Bordj's headline whole */
@media (min-width:961px){
  .work-grid>.proj--wide .thumb,.work-grid>.proj--wide:first-child .thumb{aspect-ratio:2/1}
  .work-grid>.proj--wide .thumb img{object-position:50% 55%}
}
/* the address bar was half-transparent over bright screenshots (Nomara) */
.proj .browser{background:color-mix(in srgb,var(--void) 88%,transparent)}

/* ---- pricing ---- */
/* "8" and its unit were split by the slider; they read as one value now */
.est-lab{display:flex;align-items:baseline;gap:8px}
.est-lab label{margin-inline-end:auto}
.est-lab .est-hint{margin:0;color:var(--muted)}
/* the estimate sits at the top of its panel, not floating mid-height */
@media (min-width:900px){.est-out{justify-content:flex-start}}
/* "Notre modèle" pushed two of the four titles down: it is a badge on the
   card edge now, like "Recommandé" on the tiers */
.md-card{position:relative;padding-top:var(--s-6)}
.md-tag{position:absolute;top:-10px;inset-inline-start:var(--s-5);margin:0}
.price-more{margin-top:var(--s-7)}
.price-more .pm-body{padding:0 var(--s-5) var(--s-5);display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-5)}
.price-more .market,.price-more .models{margin:0}

/* ---- method ---- */
ol.steps{list-style:none;margin:0;padding:0}
@media (min-width:1100px){.steps--4{grid-template-columns:repeat(4,minmax(0,1fr))}}
.how-cta{margin-top:var(--s-7)}
@media (min-width:1100px){.how-cta{grid-column:1/-1}}

/* ---- phone ---- */
/* a flex logo drops the space before "Labs" ("NUMideaLABS") */
.logo{white-space:pre}
@media (max-width:479px){.crumb{display:none}}
/* the round button covered copy on nearly every phone screen */
@media (max-width:720px){.to-top{display:none}}

/* ---- stack + contact plates ---- */
/* the strata caption hung below its plate, inside the BACK tile: it sits on
   the plate's top edge now, clear of the tiles */
.pi-strata .pl-cap{top:8px}
/* tiles in a row share a top and a height (row two started at three heights) */
.stack-strip{align-items:stretch}
.stack-strip .grp{justify-content:flex-start}
/* the lighthouse plate ended on a hard vertical edge beside the form */
.pi-light{-webkit-mask-image:linear-gradient(90deg,#000 55%,transparent);mask-image:linear-gradient(90deg,#000 55%,transparent)}
html[dir="rtl"] .pi-light{-webkit-mask-image:linear-gradient(270deg,#000 55%,transparent);mask-image:linear-gradient(270deg,#000 55%,transparent)}

/* ---- tablet + estimator (third audit) ---- */
/* FAQ: one 620px column beside an empty half from 900 to 1099px */
@media (min-width:900px) and (max-width:1099px){
  .faq-list{max-width:none;grid-template-columns:repeat(2,minmax(0,1fr))}
}
/* contact rows: the side column is ~300px until 1280, so the email address
   broke mid-word ("@g / mail.com"); the label sits above its value there */
@media (min-width:961px) and (max-width:1279px){
  .channels .ch-ic{grid-template-columns:22px minmax(0,1fr);row-gap:2px}
  .channels .ch .ai-ch{grid-row:1/span 2}
  .channels dd a[href^="mailto"]{overflow-wrap:normal;word-break:normal}
}
/* the estimate follows the controls down instead of leaving an empty column
   under its button. overflow:clip, not hidden: hidden makes the panel a
   scroll container and pins the sticky column to it. */
@media (min-width:1100px){
  .estimator{overflow:clip}
  .est-controls{border-inline-end:1px solid var(--hairline)}
  .est-out{position:sticky;top:96px;align-self:start;border-inline-start:0}
}

/* ---- Phone contact bar (assets/app.js toggles .on) ----
   Hidden means visibility:hidden too, so the two links leave the Tab order;
   the footer reserves the bar's height so it never covers the last line. */
.m-contact{display:none}
@media (max-width:720px){
  .m-contact{position:fixed;inset-inline:0;bottom:0;z-index:80;display:flex;gap:10px;
    padding:10px max(var(--gutter),env(safe-area-inset-right)) calc(10px + env(safe-area-inset-bottom)) max(var(--gutter),env(safe-area-inset-left));
    background:color-mix(in srgb,var(--void) 90%,transparent);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-top:1px solid var(--hairline);
    transform:translateY(110%);visibility:hidden;transition:transform .3s var(--ease-out),visibility 0s .3s}
  .m-contact.on{transform:none;visibility:visible;transition:transform .3s var(--ease-out),visibility 0s}
  /* One height whatever the label does. "Démarrer un projet" wraps to two
     lines at 390px and below (and "Start a project" at 320), and with 12px
     padding and the body's 1.6 leading that grew the buttons to 68px and the
     bar to 89px, 11px more than the page reserved for it. At 1.2 leading and
     6px padding two lines (43px) sit inside the 48px minimum, so the bar is
     10 + 48 + 10 + 1 = 69px from 280px up, in every language. */
  .m-contact .btn{flex:1;display:inline-flex;align-items:center;justify-content:center;min-height:48px;padding:6px 12px;font-size:13px;line-height:1.2;text-align:center}
  .footer{padding-bottom:calc(var(--s-5) + 70px + env(safe-area-inset-bottom))}
  /* a control focused under the bar is scrolled clear of it, ring included:
     the bar's 69px plus 9px for the 5px focus ring and some air */
  html{scroll-padding-bottom:calc(69px + 9px + env(safe-area-inset-bottom))}
}
@media (prefers-reduced-motion:reduce){.m-contact,.m-contact.on{transition:none}}
@media print{.m-contact{display:none!important}}

/* ---- Client voices (scripts/build-voices.mjs; absent until a real quote exists) ---- */
.voices{margin-top:var(--s-7);display:grid;gap:var(--s-5)}
.voices-h{font-family:var(--font-mono);font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--teal-text);font-weight:600}
.voices-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));gap:var(--s-5)}
.voice{margin:0;padding:var(--s-5);border:1px solid var(--hairline-2);border-radius:var(--r-lg);background:var(--navy);display:grid;gap:var(--s-4);align-content:start}
.voice blockquote{margin:0;font-size:17px;line-height:1.55;color:var(--ice)}
.voice blockquote p::before{content:"« "}.voice blockquote p::after{content:" »"}
.voice[lang="en"] blockquote p::before{content:"\201C"}.voice[lang="en"] blockquote p::after{content:"\201D"}
.voice[lang="ar"] blockquote p::before{content:"«"}.voice[lang="ar"] blockquote p::after{content:"»"}
.voice[lang="ar"]{font-family:var(--font-ar-body)}
.voice figcaption{font-size:13px;color:var(--muted)}
.voice figcaption b{color:var(--body-strong);font-weight:600}

/* ---- Photo slots: a real <img> replaces the monogram or the initial ---- */
.fd-photo img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:2}
.fd-photo:has(img) .mono{visibility:hidden}
.tm-i img{width:100%;height:100%;object-fit:cover;border-radius:50%;display:block}
.tm-i:has(img){font-size:0;overflow:hidden}

/* ===== r3/a: FAQ columns, scroll comfort, mobile ===== */
/* FAQ: two independent columns (questions 1-3 and 4-6), not one grid of
   shared rows. In a row-based grid the open answer set the row's height and
   the closed question beside it stretched to match (197px against 77px), so
   one click looked like two drawers opening. Each column is now its own
   stack: opening a question moves only what sits below it in its own column.
   DOM, tab and reading order stay 1 to 6; on one column the two stacks simply
   follow each other with the same gap. align-content:start keeps a column
   that is shorter than its neighbour from stretching its own rows. */
.faq-list{align-items:start}
.faq-col{display:grid;gap:var(--s-4);align-content:start;min-width:0}

/* Scroll comfort. The reading-progress bar follows the scroll on the
   compositor where the browser runs scroll-driven animations; app.js then
   stops writing it on every scroll event (and still draws it elsewhere, and
   under reduced motion, where this animation is switched off). */
@keyframes r3a-progress{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@supports (animation-timeline:scroll()){
  @media (prefers-reduced-motion:no-preference){
    .progress{animation:r3a-progress linear both;animation-timeline:scroll(root block)}
  }
}
/* Parallax that app.js leaves still (read content on a touch screen) or that
   reduced motion flattens keeps no compositor layer of its own. */
.px-still{will-change:auto}
@media (prefers-reduced-motion:reduce){[data-parallax]{will-change:auto}}
/* A skeleton shimmers only while its box is on screen: app.js marks it
   .sk-run from an IntersectionObserver and drops the mark once the picture is
   in. It animates a background, which the compositor cannot take over, so
   every running copy restyled and repainted each frame, off screen too. Paused,
   it keeps its place in its 8 cycles and resumes when the box comes back. */
.sk:not(.sk-run)::after{animation-play-state:paused}

/* ---- Mobile audit (r3/a): every page at 320-430px and 667/844 landscape,
   fr/en/ar, touch emulation. Each fix below names what was measured. ---- */

/* Phone menu. The close button sat UNDER the first link: the links come
   later in the DOM and carry a transform (their stagger), so they painted
   over it, and a flex column stretched each one across the whole width.
   Wherever the list is taller than the screen it starts level with the
   button, and a tap on x followed "Projets" instead (hit-tested at 320x568
   fr/ar, 360x740 en, 667x375). The button now paints on top and each link is
   as wide as its words. It also clears the notch: in landscape on a notched
   phone the safe-area inset is ~47px a side and the button sat inside it. */
.mobile-menu .close{z-index:2}
.mobile-menu nav a{align-self:flex-start}
@media (max-width:960px){
  .mobile-menu .close{top:calc(14px + env(safe-area-inset-top));inset-inline-end:calc(14px + env(safe-area-inset-right))}
  html[dir="rtl"] .mobile-menu .close{inset-inline-end:calc(14px + env(safe-area-inset-left))}
}
/* A short portrait phone: seven links at 40px ran 719px on a 568px screen,
   so Contact and the language switch were below the fold of the menu and
   had to be found by scrolling a dialog. Type and rhythm follow the height
   there (5.4% of it, 26-36px); 390x844 and taller keep the 40px list. */
@media (max-width:479px) and (max-height:719px){
  .mobile-menu{gap:clamp(10px,2.2vh,24px);gap:clamp(10px,2.2svh,24px)}
  .mobile-menu a{font-size:clamp(26px,5.4vh,36px);font-size:clamp(26px,5.4svh,36px);line-height:1.4}
}
/* A phone on its side: the list ran 477px on a 375px screen (504 on 390),
   so the language switch needed a scroll. Two columns of four rows (DOM and
   reading order unchanged, down the first column then the second), and the
   button clears the notch at its smaller landscape offset. */
@media (max-height:560px) and (min-width:480px){
  /* svh: the short-viewport block above sizes this type in vh, the LARGE
     viewport on iOS, taller than the screen while the toolbars show */
  .mobile-menu a{font-size:clamp(20px,6.4svh,32px)}
  .mobile-menu nav{display:grid;grid-template-rows:repeat(4,auto);grid-auto-flow:column;justify-content:start;column-gap:clamp(32px,8vw,80px)}
  .mobile-menu .close{top:calc(10px + env(safe-area-inset-top));inset-inline-end:calc(10px + env(safe-area-inset-right))}
  html[dir="rtl"] .mobile-menu .close{inset-inline-end:calc(10px + env(safe-area-inset-left))}
}

/* Landscape phones: the fixed navbar (73px) and the phone contact bar (69px)
   covered 142px of a 667x375 screen, 38%, leaving 233px to read in. The bar
   now gives way on a short screen, as it already did at 844x390 (wider than
   its 720px breakpoint), and the back-to-top button it replaces returns; the
   navbar drops its vertical padding to 6px around its 44px controls (57px,
   the same tap targets). */
@media (max-height:560px) and (min-width:480px) and (max-width:960px){
  :root{--nav-h:57px}
  .navbar .bar{padding-block:6px}
}
@media (max-width:720px) and (max-height:560px) and (min-width:480px){
  .m-contact{display:none}
  .to-top{display:grid}
  .footer{padding-bottom:calc(var(--s-7) + env(safe-area-inset-bottom))}
  html{scroll-padding-bottom:auto}
}

/* Form fields at 16px wherever a finger types. iOS Safari zooms the page
   into any field under 16px when it takes focus, and leaves it zoomed after;
   the contact fields were 15px at every width. */
@media (max-width:960px),(pointer:coarse){
  .field input,.field textarea{font-size:16px}
}

/* Estimator options. The "already in the plan" tag sat in its own column
   beside the label: at 320 it squeezed the label to 69px ("CMS / back- /
   office") and itself to three stacked words, and French needs ~150px for
   it. Under 480px it drops to its own line, aligned with the label text
   (the 17px box and the 10px gap before it); the box and its tick stay on
   the first line (the tick was centred on the whole two-line label). */
@media (max-width:479px){
  .eopt:has(.eo-incl){flex-wrap:wrap;row-gap:2px;align-items:flex-start}
  .eopt:has(.eo-incl)>span:not(.eo-incl){flex:1 1 0;min-width:0}
  .eopt .eo-incl{flex:1 0 calc(100% - 27px);margin-inline-start:27px}
  .eopt:has(.eo-incl):has(input:checked)::after{top:19.5px}
}

/* The market table on a phone. "How we price" nests it in two framed panels
   (the folded card, then the market panel), each padded 24px a side: at 320
   the table's scroll frame was 178px wide and its sticky first column 140px,
   so 38px of figures showed, a sliver of "DA" (218px at 360, 248 at 390).
   Under 600px the panels pad 12px, the frame runs to the panel's edges, and
   the row label may wrap between words, so the label column is 112px and
   the figures get the rest (140px at 320, 210 at 390). */
@media (max-width:599px){
  .price-more .pm-body{padding-inline:var(--s-3)}
  .price-more .market,.price-more .models{padding-inline:var(--s-3)}
  .price-more .mk-scroll{margin-inline:calc(-1 * var(--s-3));border-inline:0;border-radius:0}
  .mk-table th,.mk-table td{padding-inline:12px}
  .mk-table tbody th{white-space:normal;width:8em;min-width:8em}   /* "E-commerce", the longest word, is 85px */
}

/* The vista's engraved caption is one unbreakable chip: at 320 French
   ("PL. I — LES BIBANS · BORDJ BOU ARRÉRIDJ") ran past the screen edge and
   the vista's clip cut it at "ARRÉR". On a narrow screen it may wrap. */
@media (max-width:479px){
  .v-cap{white-space:normal;max-width:calc(100% - 2 * var(--gutter))}
}
