/* ============================================================
   Frame Studio — "من العشوائية إلى الاحتراف"
   Noise resolves into signal. Every motion is that resolve.

   Gating model (load-bearing):
     .no-js  -> no JS at all
     .js     -> the inline <head> script ran
     .fx     -> js/frame.js booted AND finished preparing the DOM
   Nothing is ever hidden except under .fx, so a dead script
   can never leave the page blank.
   ============================================================ */

/* ---------- tokens ---------- */
:root {
    --void:      #050506;
    --ink:       #0a0a0c;
    --surface:   #101014;
    --raised:    #16161b;
    --paper:     #ededE8;

    --fg:        #f4f4f2;
    --muted:     rgba(244, 244, 242, .56);
    --faint:     rgba(244, 244, 242, .30);
    --line:      rgba(244, 244, 242, .13);
    --line-2:    rgba(244, 244, 242, .07);

    /* Taken from the official lockup (assets/images/logo-lockup.svg), whose
       mark is rgb(43,217,86). The site follows the logo, not the other way
       round — two different greens on one page reads as a mistake.
       NOTE: the favicons/manifest still carry the older #00FF66. */
    --signal:    #2bd956;
    --signal-2:  #1fa843;

    --f-sans: 'Alexandria', 'Tajawal', system-ui, -apple-system, sans-serif;
    /* Alexandria second so Arabic inside a .mono run has a real face to fall back to */
    --f-mono: 'JetBrains Mono', 'Alexandria', ui-monospace, SFMono-Regular, monospace;

    --safe:  clamp(18px, 5vw, 84px);
    --gap:   clamp(14px, 1.6vw, 24px);
    --sec-y: clamp(88px, 12vh, 152px);

    --ease:    cubic-bezier(.19, 1, .22, 1);
    --ease-io: cubic-bezier(.76, 0, .24, 1);

    --t-display: clamp(3.6rem, 15vw, 13rem);
    --t-h1:      clamp(2.5rem, 8.5vw, 7.5rem);
    --t-h2:      clamp(2rem, 5.5vw, 4.6rem);
    --t-h3:      clamp(1.35rem, 3vw, 2.4rem);
    --t-body:    clamp(1rem, 1.05vw, 1.12rem);
    --t-mono:    clamp(.66rem, .78vw, .76rem);
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }

html {
    /* clip, never hidden: overflow:hidden on an ancestor silently kills position:sticky */
    overflow-x: clip;
    scroll-behavior: auto;
    -webkit-text-size-adjust: 100%;
}

body {
    overflow-x: clip;
    background: var(--void);
    color: var(--fg);
    font-family: var(--f-sans);
    font-size: var(--t-body);
    font-weight: 300;
    line-height: 1.75;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

img, svg, canvas { display: block; max-width: 100%; }
img { height: auto; }
ul, ol { list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
[hidden] { display: none !important; }

::selection { background: var(--signal); color: var(--void); }

:focus-visible {
    outline: 2px solid var(--signal);
    outline-offset: 3px;
}

.skip {
    position: fixed; inset-inline-start: 50%; top: 10px;
    transform: translate(50%, -300%);
    z-index: 999; padding: 12px 22px;
    background: var(--signal); color: var(--void); font-weight: 600;
}
.skip:focus { transform: translate(50%, 0); }

/* ---------- arabic type rules ----------
   1. never split arabic per letter — cursive joining breaks
   2. never negative-track arabic — it crowds the joins
   3. leading stays loose — arabic ink overshoots its line box   */
h1, h2, h3, h4 {
    font-weight: 700;
    line-height: 1.22;
    letter-spacing: normal;
    text-wrap: balance;
}

/* .mono is the monospace FACE only. Tracking is opt-in, because
   letter-spacing on Arabic inserts a gap between every glyph and pulls
   the cursive joins apart — it is as wrong as negative tracking.
   Latin-only runs opt back in via a child element or .trk.          */
.mono {
    font-family: var(--f-mono);
    font-size: var(--t-mono);
    font-weight: 400;
    letter-spacing: normal;
    text-transform: uppercase;
    line-height: 1.6;
    /* isolate, but deliberately NOT direction:ltr — that would flip
       text-align and logical margins on block-level users like .tag */
    unicode-bidi: isolate;
}
.mono b, .mono bdi, .mono i,
.hero-sig, .tile-c, .wgroup-t .mono, .ct-rows .mono,
.menu nav a b, .lb-fig figcaption b, .proof-track b,
.trk { letter-spacing: .16em; }

/* ---------- section shell ---------- */
.sec {
    position: relative;
    padding: var(--sec-y) var(--safe);
}

.tag {
    color: var(--muted);
    margin-bottom: clamp(20px, 3vh, 42px);
    display: flex;
    align-items: center;
    gap: .9em;
}
.tag b {
    font-weight: 500;
    color: var(--signal);
}
.tag::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--line);
    max-width: 220px;
}

.sec-h {
    font-size: var(--t-h2);
    font-weight: 800;
    margin-bottom: clamp(28px, 5vh, 64px);
}

/* small inline separator dot used inside .mono runs */
s { text-decoration: none; }
s::before { content: '·'; margin: 0 .7em; opacity: .55; }

/* ---------- line mask ----------
   padding-block + matching negative margin so overflow:hidden
   does not shave hamzas / ascenders off arabic ink            */
.ln {
    display: block;
    overflow: hidden;
    padding-block: .16em;
    margin-block: -.16em;
}
.ln-i { display: block; }

/* word spans are created by JS (split on spaces only — a space is
   already a non-joining boundary, so word spans are safe) */
.w { display: inline-block; }

.fx .w {
    transform: translateY(110%);
    opacity: 0;
    will-change: transform;
}
.fx .is-in .w {
    transform: none;
    opacity: 1;
    transition: transform .95s var(--ease), opacity .5s ease;
    transition-delay: calc(var(--i, 0) * 65ms);
}
/* frame-independent landing state: a hidden tab gets no frames, so a
   transition can freeze mid-way and never finish. JS sets .is-set on a
   timer with no transition at all. */
.fx .is-set .w {
    transform: none;
    opacity: 1;
    transition: none;
}

.fx .reveal {
    opacity: 0;
    transform: translateY(26px);
}
.fx .reveal.is-in {
    opacity: 1;
    transform: none;
    transition: opacity .8s ease, transform 1s var(--ease);
}
.fx .reveal.is-set {
    opacity: 1;
    transform: none;
    transition: none;
}

/* ---------- buttons ---------- */
.btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: .6em;
    padding: 1.05em 2.2em;
    font-size: .95rem;
    font-weight: 500;
    border: 1px solid var(--line);
    overflow: hidden;
    isolation: isolate;
}
.btn span { position: relative; z-index: 1; }
.btn::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--signal);
    transform: scaleY(0);
    transform-origin: bottom;
    transition: transform .5s var(--ease);
    z-index: 0;
}
.btn:hover { border-color: var(--signal); color: var(--void); }
.btn:hover::before { transform: scaleY(1); transform-origin: top; }

.btn--fill { background: var(--signal); color: var(--void); border-color: var(--signal); font-weight: 600; }
.btn--fill::before { background: var(--fg); }
.btn--fill:hover { color: var(--void); }

/* ============================================================
   loader
   ============================================================ */
.ldr {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: var(--void);
    display: grid;
    place-items: center;
}
/* no JS at all -> never show it */
.no-js .ldr { display: none; }

/* CSS failsafe: if frame.js dies, the loader still clears itself.
   Runs regardless of any JS. */
.js .ldr {
    animation: ldrFail 0s linear 4.5s forwards;
}
@keyframes ldrFail {
    to { opacity: 0; visibility: hidden; pointer-events: none; }
}
.ldr.is-done {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .5s ease, visibility 0s linear .5s;
}

.ldr-blinds {
    position: absolute;
    inset: 0;
    display: flex;
}
.ldr-blinds i {
    flex: 1;
    background: var(--void);
    transform-origin: top;
}
.ldr.is-out .ldr-blinds i {
    transform: scaleY(0);
    transition: transform .85s var(--ease-io);
}
.ldr.is-out .ldr-blinds i:nth-child(1) { transition-delay: 0s; }
.ldr.is-out .ldr-blinds i:nth-child(2) { transition-delay: .06s; }
.ldr.is-out .ldr-blinds i:nth-child(3) { transition-delay: .12s; }
.ldr.is-out .ldr-blinds i:nth-child(4) { transition-delay: .18s; }
.ldr.is-out .ldr-blinds i:nth-child(5) { transition-delay: .24s; }
.ldr.is-out .ldr-blinds i:nth-child(6) { transition-delay: .30s; }

.ldr-mid {
    position: relative;
    z-index: 1;
    display: grid;
    justify-items: center;
    gap: 26px;
}
.ldr.is-out .ldr-mid { opacity: 0; transition: opacity .3s ease; }

.ldr-bar {
    width: min(240px, 46vw);
    height: 1px;
    background: var(--line);
    overflow: hidden;
}
.ldr-bar i {
    display: block;
    height: 100%;
    width: 100%;
    background: var(--signal);
    transform: scaleX(0);
    transform-origin: right;
}

.ldr-num {
    position: absolute;
    inset-block-end: var(--safe);
    inset-inline-end: var(--safe);
    z-index: 1;
    color: var(--signal);
    font-size: clamp(2.2rem, 7vw, 4.5rem);
    letter-spacing: .04em;
    line-height: 1;
}
.ldr.is-out .ldr-num { opacity: 0; transition: opacity .3s ease; }

/* ---------- the brand lockup ----------
   The real asset, used verbatim as an <img>: white wordmark + green mark,
   which is exactly what the dark surfaces need, so it never needs recolouring.
   Only the trapezoid is ever re-drawn inline (loader + gate), from the path
   below, which was derived from the lockup and diffed against it. */
.lockup {
    display: block;
    width: auto;
    height: var(--lockup-h, 40px);
}
.hdr-mark { display: block; }
.hdr-mark .lockup { height: clamp(30px, 3.2vw, 42px); }
.ft-mark .lockup { height: clamp(40px, 5vw, 60px); }

/* ---------- the trapezoid, drawn ----------
   Geometry derived from the lockup's own path and verified against it
   (identical 0.7375 aspect). Outer + inner subpath, evenodd = the ring.
     outer     : 16.53,0  83.47,0  100,135.5  0,135.5
     aperture  : 34.07,19.82  65.93,19.82  77.62,115.68  22.38,115.68   */
.trap { display: block; overflow: visible; }
.trap path { fill: none; stroke: var(--signal); stroke-width: 7; }

.ldr-mark {
    width: min(150px, 34vw);
    height: auto;
    aspect-ratio: 100 / 135.5;
}
/* the outline traces itself, then the real (filled) mark resolves in */
.ldr-mark .ldr-ring { fill: var(--signal); stroke: none; opacity: 1; }
.ldr-mark .ldr-line { fill: none; stroke: var(--signal); stroke-width: 3; }
.fx .ldr-mark .ldr-ring { opacity: 0; }
.fx .ldr-mark .ldr-line { stroke-dasharray: 1; stroke-dashoffset: 1; }
.fx .ldr.is-draw .ldr-mark .ldr-line {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 1.05s var(--ease);
}
.fx .ldr.is-draw .ldr-mark .ldr-ring {
    opacity: 1;
    transition: opacity .45s ease .9s;
}

/* ============================================================
   chrome: grain / progress / cursor
   ============================================================ */
.grain {
    position: fixed;
    inset: -50%;
    z-index: 150;
    pointer-events: none;
    opacity: .045;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
    animation: grainShift 700ms steps(2) infinite;
}
@keyframes grainShift {
    0%   { transform: translate(0, 0); }
    50%  { transform: translate(-2%, 1.5%); }
    100% { transform: translate(1.5%, -1%); }
}

.prog {
    position: fixed;
    top: 0;
    inset-inline: 0;
    height: 2px;
    z-index: 160;
    background: var(--signal);
    transform: scaleX(0);
    transform-origin: right; /* RTL: grows from the start edge */
    pointer-events: none;
}

.cur {
    position: fixed;
    top: 0; left: 0;
    z-index: 170;
    pointer-events: none;
    mix-blend-mode: difference;
    opacity: 0;
    transition: opacity .3s ease;
}
.cur i {
    display: block;
    width: 26px;
    height: 26px;
    margin: -13px 0 0 -13px;
    border: 1px solid #fff;
    border-radius: 50%;
    transition: transform .28s var(--ease), background-color .28s ease;
}
.cur.is-on { opacity: 1; }
.cur.is-hot i { transform: scale(1.9); background: #fff; }
/* difference blending is right on the dark 90% of the site (and on the paper
   client chips), but over the brand green it renders the white cursor hot
   pink: |#2BD956 - #ffffff| = rgb(212,38,169). On green surfaces, drop the
   blend and go solid dark instead. JS toggles .is-inv. */
.cur.is-inv { mix-blend-mode: normal; }
.cur.is-inv i { border-color: var(--void); }
.cur.is-inv.is-hot i { background: var(--void); }
/* touch / coarse pointers never get a fake cursor */
@media (hover: none), (pointer: coarse) {
    .cur { display: none; }
}

/* ============================================================
   header
   ============================================================ */
.hdr {
    position: fixed;
    top: 0;
    inset-inline: 0;
    z-index: 120;
    display: flex;
    align-items: center;
    gap: clamp(16px, 3vw, 48px);
    padding: 18px var(--safe);
    background: linear-gradient(to bottom, rgba(5, 5, 6, .82), rgba(5, 5, 6, 0));
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    mask-image: linear-gradient(to bottom, #000 62%, transparent);
    transition: transform .5s var(--ease);
}
.hdr.is-hidden { transform: translateY(-102%); }


.hdr-nav {
    display: flex;
    gap: clamp(14px, 2vw, 34px);
    margin-inline-start: auto;
    font-size: .92rem;
    font-weight: 300;
}
.hdr-nav a {
    position: relative;
    color: var(--muted);
    padding-block: 4px;
    transition: color .3s ease;
}
.hdr-nav a::after {
    content: '';
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 1px;
    background: var(--signal);
    transform: scaleX(0);
    transform-origin: inline-end;
    transition: transform .45s var(--ease);
}
.hdr-nav a:hover { color: var(--fg); }
.hdr-nav a:hover::after,
.hdr-nav a.is-cur::after { transform: scaleX(1); transform-origin: inline-start; }
.hdr-nav a.is-cur { color: var(--fg); }

.hdr-cta {
    padding: .7em 1.5em;
    border: 1px solid var(--line);
    font-size: .85rem;
    font-weight: 500;
    transition: background-color .35s ease, color .35s ease, border-color .35s ease;
}
.hdr-cta:hover { background: var(--signal); color: var(--void); border-color: var(--signal); }

.burger {
    display: none;
    width: 42px; height: 42px;
    margin-inline-start: auto;
    position: relative;
}
.burger i {
    position: absolute;
    inset-inline: 9px;
    height: 1.5px;
    background: var(--fg);
    transition: transform .4s var(--ease), opacity .2s ease;
}
.burger i:nth-child(1) { top: 17px; }
.burger i:nth-child(2) { top: 24px; }
.burger[aria-expanded="true"] i:nth-child(1) { transform: translateY(3.5px) rotate(45deg); }
.burger[aria-expanded="true"] i:nth-child(2) { transform: translateY(-3.5px) rotate(-45deg); }

/* ---------- mobile menu ---------- */
.menu {
    position: fixed;
    inset: 0;
    z-index: 115;
    background: var(--void);
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 40px;
    padding: 96px var(--safe) var(--safe);
    clip-path: inset(0 0 100% 0);
}
.menu.is-open { clip-path: inset(0 0 0 0); transition: clip-path .7s var(--ease-io); }
/* landing state. Without it, a tab occluded mid-transition leaves the menu
   frozen almost-closed WHILE body.is-locked — no menu and no scrolling.
   JS adds .is-set on a setTimeout, which fires with or without frames. */
.menu.is-open.is-set { transition: none; }
.menu nav { display: grid; gap: clamp(6px, 2vh, 18px); }
.menu nav a {
    display: flex;
    align-items: baseline;
    gap: 1em;
    font-size: clamp(1.8rem, 9vw, 3rem);
    font-weight: 700;
    line-height: 1.3;
}
.menu nav a b { color: var(--signal); font-size: var(--t-mono); font-weight: 500; }
.menu-foot { color: var(--muted); }

/* ============================================================
   hero
   ============================================================ */
.hero {
    position: relative;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding: 0 var(--safe) clamp(28px, 5vh, 56px);
    overflow: hidden;
}
.hero-fx {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
}
/* fallback field if WebGL is unavailable — the hero is never empty */
.hero-scrim {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background:
        radial-gradient(120% 90% at 70% 15%, rgba(0, 255, 102, .10), transparent 55%),
        /* physical `to left` on purpose: the page is RTL-only and the headline
           sits against the right edge, so this is the ground under the type */
        linear-gradient(to left, rgba(5, 5, 6, .62) 0%, rgba(5, 5, 6, .28) 36%, rgba(5, 5, 6, 0) 68%),
        linear-gradient(to top, var(--void) 4%, rgba(5, 5, 6, .55) 42%, rgba(5, 5, 6, .18) 100%);
}

.hero-in {
    position: relative;
    z-index: 2;
    max-width: 1180px;
    margin-top: auto;
    padding-top: 128px;
}

.eyebrow { color: var(--muted); margin-bottom: clamp(18px, 3vh, 30px); }
.eyebrow bdi { color: var(--signal); }

.hero-h1 {
    font-size: var(--t-h1);
    font-weight: 800;
    line-height: 1.14;
    margin-bottom: clamp(20px, 3.5vh, 38px);
}

.hero-lead {
    max-width: 46ch;
    color: var(--muted);
    font-size: clamp(1rem, 1.5vw, 1.28rem);
    line-height: 1.85;
    margin-bottom: clamp(26px, 4vh, 46px);
}

.hero-act { display: flex; flex-wrap: wrap; gap: 14px; }

.hero-foot {
    position: relative;
    z-index: 2;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 20px;
    margin-top: clamp(36px, 7vh, 82px);
    padding-top: 18px;
    border-top: 1px solid var(--line-2);
    color: var(--muted);
}
.hero-sig b { color: var(--signal); font-weight: 500; }
.hero-cue { display: inline-flex; align-items: center; gap: .8em; }
.hero-cue i {
    width: 1px; height: 26px;
    background: linear-gradient(to bottom, var(--signal), transparent);
    animation: cue 2.2s var(--ease-io) infinite;
}
@keyframes cue {
    0%, 100% { transform: scaleY(.3); transform-origin: top; opacity: .4; }
    50%      { transform: scaleY(1); transform-origin: top; opacity: 1; }
}

/* ============================================================
   about / manifesto
   ============================================================ */
.about-h {
    font-size: var(--t-h2);
    font-weight: 800;
    max-width: 18ch;
    margin-bottom: clamp(24px, 4vh, 44px);
}
.about-h em { font-style: normal; color: var(--signal); }

.about-lead {
    max-width: 52ch;
    color: var(--muted);
    font-size: clamp(1.02rem, 1.5vw, 1.3rem);
    line-height: 1.9;
    margin-bottom: clamp(40px, 8vh, 96px);
}

/* من العشوائية ← إلى الاحتراف : the concept, literal */
.shift {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: clamp(14px, 3vw, 40px);
    padding-block: clamp(28px, 5vh, 56px);
    border-block: 1px solid var(--line);
    margin-bottom: clamp(40px, 7vh, 90px);
}
.shift-a {
    font-size: var(--t-h3);
    font-weight: 300;
    color: var(--faint);
    text-decoration: line-through;
    text-decoration-color: var(--faint);
}
.shift-ar {
    flex: 1;
    min-width: 60px;
    height: 1px;
    background: linear-gradient(to left, var(--line), var(--signal));
    position: relative;
}
.shift-ar::before {
    content: '';
    position: absolute;
    left: 0; top: 50%;
    width: 8px; height: 8px;
    border-left: 1px solid var(--signal);
    border-bottom: 1px solid var(--signal);
    transform: translateY(-50%) rotate(45deg);
}
.shift-b {
    font-size: clamp(2rem, 6vw, 4.4rem);
    font-weight: 800;
    color: var(--signal);
    line-height: 1.2;
}

/* separators are a 1px ring on each CELL, not a background on the grid:
   a grid background would show through while the cells are still
   revealing (opacity 0) and flash as a grey slab. */
/* ============================================================
   gate — the word threads THROUGH the mark, right to left

   The depth illusion is three stacked layers inside .gate-box, all sharing
   its exact coordinate box (the standard "duplicate the occluder, mask one
   copy" trick — cf. Codrops' SVG mask scroll transitions):

     z0  .gate-ring--back    the full green ring
     z1  .gate-text--lit     the word, WHITE the whole way across. Where it
                             overlaps the left bar it simply covers the green.
     z2  .gate-ring--front   the ring re-drawn, clipped to everything right of
                             the aperture's slanted edge = the RIGHT bar only

   So the word is swallowed by the right bar, crosses the opening, rides
   over the left bar, and exits left — it reads as passing through the
   frame in 3D. The word is never recoloured; occlusion alone sells it.
   ============================================================ */
.gate { position: relative; }
.gate-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Fallback for no-JS AND reduced-motion: just a legible list. Nothing here
   depends on .fx, so the words are always readable. */
.gate-stage {
    display: grid;
    place-items: center;
    gap: clamp(20px, 4vh, 40px);
    padding: var(--sec-y) var(--safe);
}
.gate-tag { color: var(--muted); }
.gate-tag b { color: var(--signal); font-weight: 500; }
.gate-box { position: relative; }
.gate-ring { display: none; }
.gate-rail { display: grid; justify-items: center; gap: .35em; text-align: center; }
.gate-rail span { font-size: clamp(2rem, 7vw, 3.6rem); font-weight: 800; line-height: 1.25; }

/* ---------- the cinematic version ----------
   Height covers (n+1) word-slots of travel, not (n-1): the first word needs
   a slot to come in from and the last one a slot to leave into. Keep this in
   proportion to .gate-rail's gap or the sweep changes speed. */
.fx .gate { height: 420vh; }
.fx .gate-stage {
    position: sticky;
    top: 0;
    height: 100svh;
    padding: 0;
    gap: 0;
    overflow: hidden;
    /* fade only the LEFT edge, where a word finally leaves */
    -webkit-mask-image: linear-gradient(to right, transparent, #000 13%, #000 100%);
    mask-image: linear-gradient(to right, transparent, #000 13%, #000 100%);
}
.fx .gate-tag {
    position: absolute;
    top: clamp(84px, 12vh, 120px);
    inset-inline-start: var(--safe);
    margin: 0;
}
.fx .gate-box {
    /* height wins, but never let the width exceed the viewport */
    height: min(64vh, 580px, calc(86vw / 0.738));
    aspect-ratio: 100 / 135.5;
    will-change: transform;
}

.fx .gate-ring {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}
.fx .gate-ring path { fill: var(--signal); stroke: none; }
.fx .gate-ring--back { z-index: 0; }
/* The RIGHT bar, re-drawn on top. The clip follows the aperture's own
   slanted edge — the line through (65.93,14.63)->(77.62,85.37) extended to
   the full height, i.e. 63.51% at y=0 and 80.04% at y=100% — so the seam
   sits exactly where the green begins and is invisible. A flat 50% split
   would cut across the opening and clip the word in mid-air. */
.fx .gate-ring--front {
    z-index: 2;
    clip-path: polygon(63.51% 0, 100% 0, 100% 100%, 80.04% 100%);
}

.fx .gate-text { position: absolute; inset: 0; }
.fx .gate-text--lit {
    z-index: 1;
    color: var(--fg);
    /* cut along the trapezoid's outer right edge so the word is not visible
       on the black BEFORE it reaches the frame */
    clip-path: polygon(-400% 0, 83.47% 0, 100% 100%, -400% 100%);
}

.fx .gate-rail {
    position: absolute;
    top: 50%;
    left: 0;
    display: flex;
    align-items: center;
    gap: clamp(220px, 32vw, 600px);
    width: max-content;
    /* LTR so offsetLeft ordering is predictable; each word re-isolates as RTL */
    direction: ltr;
    text-align: initial;
    will-change: transform;
}
.fx .gate-rail span {
    direction: rtl;
    unicode-bidi: isolate;
    white-space: nowrap;
    font-size: var(--gate-fs, clamp(3rem, 11vw, 9rem));
    font-weight: 800;
    line-height: 1.2;
}

/* ============================================================
   services
   ============================================================ */
.srv-list { border-top: 1px solid var(--line); }

.srv-row {
    position: relative;
    display: grid;
    grid-template-columns: auto 1fr 1.1fr;
    align-items: baseline;
    gap: clamp(14px, 3vw, 48px);
    padding: clamp(24px, 3.6vw, 46px) clamp(12px, 2vw, 28px);
    border-bottom: 1px solid var(--line);
    isolation: isolate;
    transition: color .45s ease;
}
.srv-row::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: var(--signal);
    transform: scaleY(0);
    transform-origin: bottom;
    transition: transform .55s var(--ease);
}
.srv-row:hover { color: var(--void); }
.srv-row:hover::before { transform: scaleY(1); transform-origin: top; }

.srv-row b { color: var(--signal); font-weight: 500; transition: color .45s ease; }
.srv-row:hover b { color: var(--void); }

.srv-row h3 {
    font-size: clamp(1.4rem, 3.4vw, 2.9rem);
    font-weight: 700;
    line-height: 1.25;
}
.srv-row p {
    color: var(--muted);
    font-size: clamp(.92rem, 1.1vw, 1.05rem);
    line-height: 1.8;
    transition: color .45s ease;
}
.srv-row:hover p { color: rgba(5, 5, 6, .78); }

/* ============================================================
   works — horizontal rail
   ============================================================ */
.works { position: relative; padding-block: var(--sec-y); }
.works-head { padding-inline: var(--safe); margin-bottom: clamp(28px, 5vh, 56px); }
.works-hint { color: var(--muted); }

/* default (and no-JS, and mobile): a real, native, swipeable strip */
.works-sticky {
    overflow-x: auto;
    overflow-y: hidden;
    /* No scroll-snap. The tiles' snap points sit at their CENTRES, and the
       first snap point is the first tile — not the group label that precedes
       it. So the browser snapped on load, scrolled the strip 113px in and cut
       the first client's name off the right edge before the user touched
       anything. `proximity` did not help: it still snapped and scrollLeft
       could not be reset to 0. Free scrolling is correct for a gallery. */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}
.works-sticky::-webkit-scrollbar { display: none; }

.works-track {
    display: flex;
    align-items: center;
    gap: var(--gap);
    width: max-content;
    /* as a flex item on desktop it must not be allowed to shrink to fit */
    flex: none;
    padding-inline: var(--safe);
}

.tile {
    position: relative;
    flex: none;
    width: clamp(230px, 62vw, 300px);
    aspect-ratio: 1;
    overflow: hidden;
    background: var(--surface);
    isolation: isolate;
}
.tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform .9s var(--ease), filter .6s ease;
}
.tile::after {
    content: '';
    position: absolute;
    inset: 0;
    border: 1px solid transparent;
    transition: border-color .4s ease;
}
.tile:hover img { transform: scale(1.06); }
.tile:hover::after { border-color: var(--signal); }

.tile-c {
    position: absolute;
    inset-block-end: 10px;
    inset-inline-start: 12px;
    z-index: 1;
    color: var(--fg);
    background: rgba(5, 5, 6, .62);
    padding: 3px 8px;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.wgroup {
    flex: none;
    display: grid;
    align-content: center;
    padding-inline: clamp(10px, 2vw, 34px);
}
.wgroup-t {
    font-size: clamp(1.1rem, 2vw, 1.7rem);
    font-weight: 700;
    white-space: nowrap;
    display: flex;
    align-items: center;
    gap: .7em;
}
.wgroup-t .mono {
    color: var(--signal);
    border: 1px solid var(--line);
    padding: .35em .7em;
    font-size: .62rem;
}

/* desktop + JS: the rail is driven by page scroll while the viewport sticks */
@media (min-width: 960px) {
    .fx .works-sticky {
        position: sticky;
        top: 0;
        height: 100svh;
        overflow: hidden;
        display: flex;
        align-items: center;
    }
    .fx .works-track { will-change: transform; }
    .fx .tile { width: clamp(320px, 36vw, 540px); }
}

/* ============================================================
   impact
   ============================================================ */
.imp-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
    gap: 1px;
    margin-bottom: clamp(40px, 7vh, 90px);
}
.imp-cell {
    background: var(--void);
    box-shadow: 0 0 0 1px var(--line);
    padding: clamp(26px, 4vw, 52px) clamp(16px, 1.8vw, 28px);
    /* the number is sized from the CELL, not the viewport — see dt below */
    container-type: inline-size;
}
.imp-cell dt {
    font-family: var(--f-mono);
    /* A viewport-based size drifts away from the cell: the grid decides how
       many columns fit, so at some widths the cell shrinks while 7vw keeps
       growing. "175K+" (the longest value) overflowed its box by 52px at
       1512px. cqw ties the size to the box that has to contain it, so it
       cannot overflow at any width. The vw line is the fallback for
       browsers without container queries and is deliberately conservative. */
    /* cqw resolves against the container's CONTENT box, so 24cqw is 24% of
       the space actually available. The longest value, "175K+", measures
       3.79 x font-size, so the ceiling is 100/3.79 = 26cqw; 24 leaves ~9%. */
    font-size: clamp(2rem, 5vw, 3.6rem);
    font-size: clamp(2rem, 24cqw, 5rem);
    font-weight: 500;
    line-height: 1;
    color: var(--signal);
    letter-spacing: -.01em; /* latin numerals only — never applied to arabic */
    margin-bottom: .35em;
}
.imp-cell dd { color: var(--muted); font-size: 1rem; }

/* direction:ltr belongs on the CLIPPING box, not the track. An
   over-wide block inside an RTL container is right-aligned and
   overflows LEFT, so a negative translate would walk it off screen.
   Forcing the container to LTR makes the marquee maths absolute. */
.proof {
    overflow: hidden;
    direction: ltr;
    border-block: 1px solid var(--line);
    padding-block: 22px;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
    mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
}
.proof-track {
    display: flex;
    align-items: center;
    gap: clamp(28px, 5vw, 72px);
    width: max-content;
}
.proof-track > span {
    direction: rtl;      /* each item renders as proper arabic */
    unicode-bidi: isolate;
    white-space: nowrap;
    color: var(--muted);
    font-size: clamp(.9rem, 1.2vw, 1.05rem);
    display: inline-flex;
    align-items: center;
    gap: .6em;
}
.proof-track b { color: var(--signal); font-weight: 500; unicode-bidi: isolate; }
.fx .proof-track { animation: marq var(--d, 46s) linear infinite; }
.proof:hover .proof-track { animation-play-state: paused; }

/* --x is set by JS to exactly one copy width + one gap, so the loop is
   seamless. -50% is only the pre-JS fallback (it drifts by half a gap). */
@keyframes marq {
    from { transform: translateX(0); }
    to   { transform: translateX(var(--x, -50%)); }
}

/* ============================================================
   team
   ============================================================ */
.team-note { color: var(--muted); margin-top: -18px; margin-bottom: clamp(30px, 5vh, 56px); }

.faces {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
    gap: var(--gap);
    margin-bottom: clamp(56px, 9vh, 120px);
}
.face { position: relative; }
.face-im {
    position: relative;
    aspect-ratio: 3 / 4;
    overflow: hidden;
    background: var(--surface);
}
.face-im img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: grayscale(1) contrast(1.05);
    transition: filter .6s ease;
}
/* masked photo sits on top; hover wipes it away to reveal the face */
.face-on {
    z-index: 1;
    clip-path: inset(0 0 0 0);
    transition: clip-path .8s var(--ease-io);
}
.face:hover .face-on { clip-path: inset(0 0 100% 0); }
.face:hover img { filter: grayscale(0) contrast(1); }

.face figcaption {
    display: grid;
    gap: .2em;
    padding-top: 14px;
}
.face figcaption b { font-weight: 600; font-size: 1.02rem; }
.face figcaption span { color: var(--muted); font-size: .88rem; }

.roster {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
    gap: clamp(28px, 4vw, 56px);
    border-top: 1px solid var(--line);
    padding-top: clamp(30px, 5vh, 56px);
}
.rgroup-t { color: var(--signal); margin-bottom: 1.2em; }
.rgroup li {
    color: var(--muted);
    font-size: 1rem;
    line-height: 2.1;
    transition: color .3s ease;
}
.rgroup li:hover { color: var(--fg); }

/* ============================================================
   clients — paper chips on a dark wall
   the artwork ranges from pure black to pure white and several
   carry baked-in white boxes, so a light chip is the only field
   that renders all 24 honestly. the 3 pure-white marks invert.
   ============================================================ */
.wall {
    overflow: hidden;
    direction: ltr;  /* see .proof — the clipping box owns the direction */
    margin-bottom: var(--gap);
    -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
    mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}
.wall-track {
    display: flex;
    gap: var(--gap);
    width: max-content;
}
.fx .wall-track { animation: marq var(--d, 60s) linear infinite; }
.fx .wall[data-dir="-1"] .wall-track { animation-direction: reverse; }
.wall:hover .wall-track { animation-play-state: paused; }

/* an explicit height, not aspect-ratio: the logos have wildly different
   intrinsic sizes and a content-sized grid row overrides aspect-ratio,
   which left the chips ragged. A definite height also makes the
   img's max-height:100% resolvable. */
.chip {
    flex: none;
    width: clamp(132px, 15vw, 186px);
    height: clamp(76px, 8.6vw, 106px);
    background: var(--paper);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    padding: clamp(10px, 1.4vw, 18px);
    filter: grayscale(1);
    opacity: .62;
    transition: opacity .45s ease, filter .45s ease, transform .45s var(--ease);
}
.chip:hover { opacity: 1; filter: none; transform: translateY(-4px); }
.chip img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.chip.is-light img { filter: invert(1); }

/* ============================================================
   contact — the one inverted band
   ============================================================ */
.ct {
    background: var(--signal);
    color: var(--void);
}
.ct .tag { color: rgba(5, 5, 6, .62); }
.ct .tag b { color: var(--void); }
.ct .tag::after { background: rgba(5, 5, 6, .28); }
.ct ::selection { background: var(--void); color: var(--signal); }

.ct-h {
    font-size: var(--t-h1);
    font-weight: 800;
    line-height: 1.14;
    margin-bottom: clamp(40px, 7vh, 88px);
}

.ct-rows { border-top: 1px solid rgba(5, 5, 6, .22); }
.ct-rows li { border-bottom: 1px solid rgba(5, 5, 6, .22); }
.ct-rows a {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: clamp(14px, 3vw, 40px);
    padding: clamp(20px, 2.8vw, 34px) clamp(4px, 1.5vw, 20px);
    transition: padding-inline .45s var(--ease), background-color .45s ease;
}
.ct-rows a:hover { background: var(--void); color: var(--signal); padding-inline: clamp(14px, 3vw, 40px); }
.ct-rows span { opacity: .6; }
.ct-rows b { font-size: clamp(1.2rem, 3vw, 2.2rem); font-weight: 700; line-height: 1.3; }
.ct-rows i {
    font-style: normal;
    font-family: var(--f-mono);
    font-size: var(--t-mono);
    letter-spacing: .1em;
    unicode-bidi: isolate;
    /* these carry dir="ltr" so the phone number renders correctly, which
       also makes text-align:start mean LEFT. Harmless on desktop where this
       has its own column, but once the row stacks on mobile the number ends
       up flush left under a right-aligned label. Pin it to the RTL edge. */
    text-align: right;
    opacity: .75;
}

/* ============================================================
   footer
   ============================================================ */
.ft {
    display: grid;
    justify-items: center;
    gap: 22px;
    padding: clamp(56px, 9vh, 110px) var(--safe);
    text-align: center;
    border-top: 1px solid var(--line);
}
.ft-mark { font-size: 1.5rem; }
.ft-tag { font-size: clamp(1.3rem, 3.4vw, 2.2rem); font-weight: 700; }
.ft-meta { color: var(--faint); }

/* ============================================================
   lightbox
   ============================================================ */
.lb {
    position: fixed;
    inset: 0;
    z-index: 180;
    background: rgba(5, 5, 6, .96);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    display: grid;
    place-items: center;
    padding: clamp(48px, 8vw, 90px);
    opacity: 0;
    transition: opacity .35s ease;
}
.lb.is-on { opacity: 1; }
/* the element is only `hidden` on a timer after the fade, so kill
   pointer events in the gap or the invisible sheet eats clicks */
.lb:not(.is-on) { pointer-events: none; }

.lb-fig { display: grid; gap: 16px; justify-items: center; max-height: 100%; }
.lb-fig img {
    max-width: 100%;
    max-height: 76vh;
    object-fit: contain;
    transform: scale(.96);
    opacity: 0;
    transition: transform .5s var(--ease), opacity .4s ease;
}
.lb.is-on .lb-fig img { transform: none; opacity: 1; }
.lb-fig figcaption {
    color: var(--muted);
    font-size: .92rem;
    display: flex;
    align-items: center;
    gap: 1em;
}
.lb-fig figcaption b { color: var(--signal); font-weight: 500; }

.lb-x, .lb-nav {
    position: absolute;
    width: 52px; height: 52px;
    border: 1px solid var(--line);
    transition: background-color .3s ease, border-color .3s ease, color .3s ease;
}
.lb-x:hover, .lb-nav:hover { background: var(--signal); border-color: var(--signal); color: var(--void); }
.lb-x { top: var(--safe); inset-inline-end: var(--safe); }
.lb-x::before, .lb-x::after {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    width: 17px; height: 1.5px;
    background: currentColor;
}
.lb-x::before { transform: translate(-50%, -50%) rotate(45deg); }
.lb-x::after  { transform: translate(-50%, -50%) rotate(-45deg); }

.lb-nav { top: 50%; margin-top: -26px; }
/* physical borders: the glyph is a "⌜" whose apex points north-west,
   so the rotations below are absolute, not direction-relative */
.lb-nav::before {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    width: 11px; height: 11px;
    border-top: 1.5px solid currentColor;
    border-left: 1.5px solid currentColor;
}
/* RTL: the gallery runs right-to-left, so "next" lives on the LEFT
   and points left; "prev" sits on the right and points right. */
.lb-prev { inset-inline-start: var(--safe); }          /* RTL -> right edge */
.lb-prev::before { transform: translate(-50%, -50%) rotate(135deg); }  /* ► */
.lb-next { inset-inline-end: var(--safe); }            /* RTL -> left edge  */
.lb-next::before { transform: translate(-50%, -50%) rotate(-45deg); }  /* ◄ */

body.is-locked { overflow: hidden; }

/* ============================================================
   responsive
   ============================================================ */
@media (max-width: 1080px) {
    .srv-row { grid-template-columns: auto 1fr; }
    .srv-row p { grid-column: 2; }
}

@media (max-width: 860px) {
    .hdr-nav, .hdr-cta { display: none; }
    .burger { display: block; }
    .hero-in { padding-top: 108px; }
    /* narrow viewports put the copy right on top of the busiest part of
       the field — the side gradient no longer covers it, so darken the
       whole lower half instead. */
    .hero-scrim {
        background:
            radial-gradient(130% 62% at 70% 8%, rgba(0, 255, 102, .09), transparent 58%),
            linear-gradient(to top, var(--void) 8%, rgba(5, 5, 6, .88) 44%, rgba(5, 5, 6, .5) 100%);
    }
    .hero-foot { flex-direction: column; align-items: flex-start; gap: 10px; }
    .shift { gap: 16px; }
    .shift-ar { width: 100%; flex: none; min-width: 0; }
    .faces { grid-template-columns: repeat(2, 1fr); }
    .ct-rows a { grid-template-columns: auto 1fr; }
    .ct-rows i { grid-column: 2; }
}

@media (max-width: 480px) {
    .srv-row { grid-template-columns: 1fr; gap: 10px; }
    .srv-row p { grid-column: 1; }
}

/* ============================================================
   reduced motion — everything lands, nothing moves
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
    .fx .w,
    .fx .reveal {
        opacity: 1 !important;
        transform: none !important;
    }
    .grain { animation: none; }
    .hero-cue i { animation: none; }
    .fx .wall-track, .fx .proof-track { animation: none; }
}
