/* ==========================================================================
   DevelopiTech — design system, second pass.

   Studio-site aesthetic: near-black canvas, off-white ink, one restrained
   accent. Editorial type carries the page; there are no gradients, glows,
   aurora blobs or glass panels. Motion is masked reveals, weight shifts and
   media that answers the cursor — never decoration for its own sake.

   Self-hosted fonts and no CDN of any kind: the site has to work from
   networks where third-party hosts are unreliable.
   ========================================================================== */

/* ------------------------------------------------------------------ fonts */
@font-face{font-family:'Inter';font-style:normal;font-weight:100 900;font-display:swap;
  src:url('../fonts/inter-var-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:100 900;font-display:swap;
  src:url('../fonts/geistmono-var-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Cairo';font-style:normal;font-weight:200 1000;font-display:swap;
  src:url('../fonts/cairo-var-arabic.woff2') format('woff2');
  unicode-range:U+0600-06FF,U+0750-077F,U+0870-088E,U+0890-0891,U+0898-08E1,U+08E3-08FF,U+FB50-FDFF,U+FE70-FEFF,U+102E0-102FB,U+10E60-10E7E,U+10EFD-10EFF,U+1EE00-1EEFF;}
@font-face{font-family:'Cairo';font-style:normal;font-weight:200 1000;font-display:swap;
  src:url('../fonts/cairo-var-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}

/* ----------------------------------------------------------------- tokens */
:root{
  /* Ground. Pure black, the way the reference is — not a near-black that
     still carries a hue. Colour arrives as light, never as a tint. */
  --bg:        #000000;
  --bg-2:      #0b0b0b;
  --bg-3:      #161616;

  /* Ink. The grey ramp read straight off the reference's own text styles. */
  --ink:       #eeeeee;
  --ink-strong:#ffffff;
  --ink-muted: #aaaaaa;
  --ink-faint: #888888;

  /* Emissive. Not a brand fill: these are the point-light colours the
     reference burns through black, and they only ever appear as glow,
     hairline or hover state. */
  --glow:      #93e5ff;
  --glow-warm: #b4e0e3;
  --glow-hot:  #d600ff;
  --accent:    var(--glow);

  --line:      rgba(255,255,255,.13);
  --line-soft: rgba(255,255,255,.07);

  --ok:        #0bed90;
  --danger:    #f87171;

  --font-body:    'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  /* One squared monospace carries display, UI and metadata — the single
     typographic decision that makes the page read as a stage rather than a
     marketing template. Geist Mono stands in for NB Architekt Std, which is
     licensed and cannot be self-hosted here. */
  --font-display: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --font-mono:    'Geist Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  --shell:  1560px;
  --gutter: clamp(1.25rem, 4.5vw, 4rem);

  --ease:    cubic-bezier(.16,1,.3,1);
  --ease-io: cubic-bezier(.4,0,.2,1);

  /* Horizontal direction multiplier. Every left/right transform is written
     as (something * var(--dir)) so RTL mirrors for free. */
  --dir: 1;
  --scroll-progress: 0;
}

/* Arabic: Cairo throughout, looser leading, no letter-spacing tricks and no
   uppercase transforms (Arabic has no case). */
[dir="rtl"]{
  --font-body:    'Cairo', 'Noto Sans Arabic', ui-sans-serif, system-ui, sans-serif;
  --font-display: 'Cairo', 'Noto Sans Arabic', ui-sans-serif, system-ui, sans-serif;
  --dir: -1;
}
[dir="rtl"] em,[dir="rtl"] i{font-style:normal;font-weight:600}

/* ------------------------------------------------------------------ reset */
*,*::before,*::after{box-sizing:border-box}
*{margin:0}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;scroll-padding-top:6rem;background:var(--bg)}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

body{
  font-family:var(--font-body);
  /* No background here on purpose. html carries it, so it paints as the page
     canvas; an opaque background on body would paint over the z-index:-1
     ambience layer (block backgrounds paint after negative-z children). */
  color:var(--ink);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
  font-feature-settings:'cv02','cv03','cv04','ss01';
}
[dir="rtl"] body{line-height:1.85;font-feature-settings:normal}

img,svg,video,canvas{display:block;max-width:100%;height:auto}
input,button,textarea,select{font:inherit;color:inherit}
button{background:none;border:0;cursor:pointer}
a{color:inherit;text-decoration:none}
ul,ol{list-style:none;padding:0}
:target{scroll-margin-top:6rem}

::selection{background:var(--ink);color:var(--bg)}

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

/* --------------------------------------------------------------- headings */
h1,h2,h3,h4,h5{
  font-family:var(--font-display);
  font-weight:600;
  line-height:.98;
  letter-spacing:-.035em;
  color:var(--ink-strong);
  text-wrap:balance;
}
[dir="rtl"] h1,[dir="rtl"] h2,[dir="rtl"] h3,[dir="rtl"] h4{letter-spacing:0;line-height:1.28}

/* Display scale. Deliberately large — the type is the design. */
h1{font-size:clamp(2.75rem,1rem + 8.5vw,8rem)}
h2{font-size:clamp(2rem,1rem + 4.5vw,4.5rem)}
h3{font-size:clamp(1.35rem,1rem + 1.4vw,2rem)}
h4{font-size:1.05rem;letter-spacing:-.02em}
p{text-wrap:pretty}

/* ---------------------------------------------------------------- layout */
.shell{width:100%;max-width:var(--shell);margin-inline:auto;padding-inline:var(--gutter)}
.shell-narrow{max-width:78rem}
.shell-text{max-width:52rem}
.section{padding-block:clamp(5rem,11vw,11rem)}
.section-tight{padding-block:clamp(3.5rem,7vw,6rem)}
.rule{border:0;border-top:1px solid var(--line);margin:0}

.skip-link{
  position:absolute;inset-inline-start:1rem;top:-100px;z-index:200;
  background:var(--ink);color:var(--bg);padding:.7rem 1.2rem;font-weight:600;
  transition:top .2s var(--ease);
}
.skip-link:focus{top:1rem}

/* ------------------------------------------------------------ micro-label */
/* Mono, uppercase, wide-tracked. Replaces the old gradient "eyebrow" pill.
   In Arabic it drops the uppercase and the tracking and just goes small. */
.label{
  font-family:var(--font-mono);
  font-size:.7rem;font-weight:500;
  letter-spacing:.18em;text-transform:uppercase;
  color:var(--ink-muted);
  display:inline-flex;align-items:baseline;gap:.75rem;
}
[dir="rtl"] .label{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.82rem}
.label .idx{color:var(--ink-faint)}

.section-head{max-width:60rem;margin-bottom:clamp(2.5rem,5vw,4.5rem)}
.section-head .label{margin-bottom:clamp(1.5rem,3vw,2.5rem);display:block}
.section-head .lede{color:var(--ink-muted);font-size:clamp(1rem,.95rem + .35vw,1.2rem);margin-top:1.75rem;max-width:44rem}

/* Section header with the label on one side and the heading on the other. */
.head-split{
  display:grid;gap:clamp(1.5rem,4vw,3rem);
  align-items:start;margin-bottom:clamp(2.5rem,5vw,4.5rem);
}
@media (min-width:900px){.head-split{grid-template-columns:14rem 1fr}}

/* -------------------------------------------------------------- buttons */
/* Flat, square-ish, no glow. The fill wipes in from the leading edge. */
.btn{
  position:relative;isolation:isolate;overflow:hidden;
  display:inline-flex;align-items:center;justify-content:center;gap:.75rem;
  padding:1.05rem 2rem;
  font-family:var(--font-mono);font-size:.78rem;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;line-height:1;
  border:1px solid var(--ink);color:var(--ink-strong);
  transition:color .4s var(--ease),border-color .4s var(--ease);
}
[dir="rtl"] .btn{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.95rem;font-weight:600}
.btn::before{
  content:'';position:absolute;inset:0;z-index:-1;background:var(--ink);
  transform:scaleX(0);transform-origin:calc(50% - 50% * var(--dir)) 50%;
  transition:transform .5s var(--ease);
}
.btn:hover{color:var(--bg)}
.btn:hover::before{transform:scaleX(1)}
.btn svg{flex:none;transition:transform .4s var(--ease)}
.btn:hover svg{transform:translateX(calc(4px * var(--dir)))}

/* Inverted: solid to start, hollows out on hover. */
.btn-solid{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.btn-solid::before{background:var(--bg);transform:scaleX(0)}
.btn-solid:hover{color:var(--ink-strong)}

.btn-sm{padding:.7rem 1.2rem;font-size:.7rem}
.btn-block{width:100%}

/* Text link: label plus a rule that redraws on hover. */
.link{
  position:relative;display:inline-flex;align-items:center;gap:.6rem;
  font-family:var(--font-mono);font-size:.78rem;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;color:var(--ink);
  padding-block:.4rem;
}
[dir="rtl"] .link{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.95rem;font-weight:600}
.link::after{
  content:'';position:absolute;inset-block-end:0;inset-inline:0;height:1px;
  background:currentColor;transform:scaleX(1);
  transform-origin:calc(50% + 50% * var(--dir)) 50%;
  transition:transform .45s var(--ease);
}
.link:hover::after{transform:scaleX(0)}
.link svg{transition:transform .4s var(--ease)}
.link:hover svg{transform:translateX(calc(5px * var(--dir)))}
[dir="rtl"] .link svg{transform:scaleX(-1)}
[dir="rtl"] .link:hover svg{transform:scaleX(-1) translateX(5px)}

/* ================================================================= grain */
/* The one piece of ambience that survives: a single fixed noise tile. No
   auroras, no animated grid, no vignette, no particle canvas. */
.grain{
  position:fixed;inset:-50%;z-index:1;pointer-events:none;
  opacity:.028;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  animation:grain-shift 5s steps(5) infinite;
}
@keyframes grain-shift{
  0%,100%{transform:translate3d(0,0,0)}
  20%{transform:translate3d(-1%,1%,0)}
  40%{transform:translate3d(1%,-1%,0)}
  60%{transform:translate3d(-1%,-1%,0)}
  80%{transform:translate3d(1%,1%,0)}
}

/* ------------------------------------------------------- scroll progress */
.scroll-progress{
  position:fixed;inset-block-start:0;inset-inline:0;height:1px;z-index:120;
  pointer-events:none;transform-origin:0 50%;background:var(--ink);
  transform:scaleX(var(--scroll-progress));
  opacity:0;transition:opacity .3s var(--ease);
}
[dir="rtl"] .scroll-progress{transform-origin:100% 50%}
.js .scroll-progress{opacity:.45}

/* ================================================================ cursor */
/* Two elements: a small solid dot that tracks precisely, and a ring that
   lags behind and swells over anything interactive. Created by app.js and
   only on fine pointers, so touch devices never see it. */
.cursor-dot,.cursor-ring{
  position:fixed;top:0;left:0;z-index:400;pointer-events:none;
  border-radius:50%;will-change:transform;
}
.cursor-dot{
  width:6px;height:6px;margin:-3px 0 0 -3px;background:var(--ink-strong);
  transition:opacity .3s,transform .2s var(--ease);
}
.cursor-ring{
  width:40px;height:40px;margin:-20px 0 0 -20px;
  border:1px solid rgba(255,255,255,.35);
  transition:width .35s var(--ease),height .35s var(--ease),
             margin .35s var(--ease),border-color .35s var(--ease),opacity .3s;
}
body.cursor-hot .cursor-ring{
  width:74px;height:74px;margin:-37px 0 0 -37px;border-color:rgba(255,255,255,.7);
}
body.cursor-hot .cursor-dot{transform:scale(0)}
body.cursor-idle .cursor-dot,body.cursor-idle .cursor-ring{opacity:0}

/* ================================================================= header */
.site-header{
  position:fixed;inset-block-start:0;inset-inline:0;z-index:100;
  padding-block:1.75rem;
  transition:transform .5s var(--ease-io),padding .4s var(--ease-io),background-color .4s;
  will-change:transform;
}
.site-header.is-stuck{padding-block:1.1rem;background:rgba(10,10,11,.72);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
.site-header.is-hidden{transform:translate3d(0,-120%,0)}
.header-inner{display:flex;align-items:center;justify-content:space-between;gap:2rem}

.brand{
  display:inline-flex;align-items:center;gap:.65rem;
  font-family:var(--font-display);font-weight:600;font-size:1rem;
  letter-spacing:-.03em;color:var(--ink-strong);
}
.brand img{width:1.6rem;height:1.6rem}

.nav{display:none;align-items:center;gap:2rem}
@media (min-width:1024px){.nav{display:flex}}
.nav a{
  position:relative;
  font-family:var(--font-mono);font-size:.72rem;font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink-muted);
  padding-block:.35rem;transition:color .3s var(--ease);
}
[dir="rtl"] .nav a{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.95rem}
.nav a::after{
  content:'';position:absolute;inset-block-end:0;inset-inline:0;height:1px;
  background:currentColor;transform:scaleX(0);
  transform-origin:calc(50% - 50% * var(--dir)) 50%;
  transition:transform .4s var(--ease);
}
.nav a:hover{color:var(--ink-strong)}
.nav a:hover::after{transform:scaleX(1)}
.nav a[aria-current="page"]{color:var(--ink-strong)}
.nav a[aria-current="page"]::after{transform:scaleX(1)}

.header-actions{display:flex;align-items:center;gap:1.25rem}
.lang-switch{
  font-family:var(--font-mono);font-size:.72rem;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;color:var(--ink-muted);
  transition:color .3s;
}
.lang-switch:hover{color:var(--ink-strong)}

/* Hamburger drawn from two rules so it can morph into a cross. */
.nav-toggle{
  position:relative;width:2.25rem;height:2.25rem;flex:none;
}
@media (min-width:1024px){.nav-toggle{display:none}}
.nav-toggle span{
  position:absolute;inset-inline:.25rem;height:1px;background:var(--ink);
  transition:transform .4s var(--ease),opacity .25s;
}
.nav-toggle span:nth-child(1){top:.85rem}
.nav-toggle span:nth-child(2){top:1.35rem}
body.nav-open .nav-toggle span:nth-child(1){transform:translateY(.25rem) rotate(45deg)}
body.nav-open .nav-toggle span:nth-child(2){transform:translateY(-.25rem) rotate(-45deg)}

/* Full-screen menu. Links are display-size and stagger in. */
.mobile-nav{
  position:fixed;inset:0;z-index:99;background:var(--bg);
  display:flex;flex-direction:column;justify-content:center;
  padding:7rem var(--gutter) 3rem;
  clip-path:inset(0 0 100% 0);
  transition:clip-path .7s var(--ease);
  visibility:hidden;
}
.mobile-nav.is-open{clip-path:inset(0 0 0 0);visibility:visible}
.mobile-nav a{
  display:block;padding:.5rem 0;
  font-family:var(--font-display);font-weight:600;letter-spacing:-.035em;
  font-size:clamp(2rem,10vw,4rem);line-height:1.1;color:var(--ink-strong);
  transition:color .3s;
}
[dir="rtl"] .mobile-nav a{letter-spacing:0;font-size:clamp(1.75rem,8vw,3.25rem)}
.mobile-nav a:hover{color:var(--ink-muted)}
.mobile-nav .mobile-foot{margin-top:3rem;display:grid;gap:.5rem}
.mobile-nav .mobile-foot a{font-family:var(--font-mono);font-size:.8rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-muted)}
[dir="rtl"] .mobile-nav .mobile-foot a{font-family:var(--font-body);letter-spacing:0;text-transform:none}
body.nav-open{overflow:hidden}

/* Cascade, gated on JS so a no-JS drawer is simply open and readable. */
.js .mobile-nav a{opacity:0;transform:translate3d(0,40px,0)}
.js .mobile-nav.is-open a{
  opacity:1;transform:none;
  transition:opacity .6s var(--ease) var(--stagger,0ms),transform .8s var(--ease) var(--stagger,0ms);
}

/* =================================================================== hero */
.hero{
  position:relative;
  min-height:clamp(38rem,92vh,60rem);
  display:flex;flex-direction:column;justify-content:flex-end;
  padding-block:clamp(9rem,18vh,12rem) clamp(3rem,6vw,5rem);
}
.hero h1{margin-block:clamp(1.5rem,4vw,3rem)}
.hero-foot{
  display:grid;gap:2rem;align-items:end;
  padding-top:clamp(2rem,5vw,3.5rem);border-top:1px solid var(--line);
}
@media (min-width:900px){.hero-foot{grid-template-columns:1fr auto}}
.hero-lede{color:var(--ink-muted);font-size:clamp(1rem,.95rem + .4vw,1.25rem);max-width:38rem}
.hero-actions{display:flex;flex-wrap:wrap;gap:1rem}

/* The live location line. A quiet mono row, not a badge pill. */
.hero-note{
  display:inline-flex;align-items:center;gap:.7rem;
  font-family:var(--font-mono);font-size:.72rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-muted);
}
[dir="rtl"] .hero-note{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.9rem}
.hero-note .dot{
  width:.4rem;height:.4rem;border-radius:50%;background:var(--ok);flex:none;
  animation:pulse 2.8s var(--ease-io) infinite;
}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.25}}

/* Scroll hint at the bottom of the hero. */
.scroll-hint{
  position:absolute;inset-block-end:1.5rem;inset-inline-end:var(--gutter);
  font-family:var(--font-mono);font-size:.65rem;letter-spacing:.2em;
  text-transform:uppercase;color:var(--ink-faint);
  display:none;align-items:center;gap:.6rem;
}
@media (min-width:900px){.scroll-hint{display:flex}}
[dir="rtl"] .scroll-hint{font-family:var(--font-body);letter-spacing:0;text-transform:none}
.scroll-hint i{display:block;width:1px;height:2.5rem;background:var(--line);position:relative;overflow:hidden}
.scroll-hint i::after{content:'';position:absolute;inset-inline:0;height:50%;background:var(--ink);animation:hint 2.4s var(--ease-io) infinite}
@keyframes hint{0%{transform:translateY(-100%)}60%,100%{transform:translateY(200%)}}

/* ============================================================== reveals */
/* Two systems, unchanged in spirit from before: JS-gated scroll reveals and
   pure-CSS entrances above the fold. Everything is readable with JS off. */

.js [data-reveal]{opacity:0;transform:translate3d(0,32px,0);will-change:opacity,transform}
.js [data-reveal="rise"]{transform:translate3d(0,80px,0)}
.js [data-reveal="left"]{transform:translate3d(calc(-48px * var(--dir)),0,0)}
.js [data-reveal="right"]{transform:translate3d(calc(48px * var(--dir)),0,0)}
.js [data-reveal="fade"]{transform:none}
.js [data-reveal="wipe"]{opacity:1;clip-path:inset(0 0 101% 0);transform:none}
.js [data-reveal].is-visible{
  opacity:1;transform:none;
  transition:opacity .9s var(--ease),transform 1s var(--ease),clip-path 1s var(--ease);
  transition-delay:var(--reveal-delay,0ms);
}
.js [data-reveal="wipe"].is-visible{clip-path:inset(0 0 0 0)}
.js [data-reveal].is-settled{will-change:auto}

/* Media that scales down into place as it enters. */
.js [data-reveal="media"]{opacity:1;transform:none;clip-path:inset(0 0 100% 0)}
.js [data-reveal="media"] img{transform:scale(1.14);transition:transform 1.4s var(--ease)}
.js [data-reveal="media"].is-visible{clip-path:inset(0 0 0 0);transition:clip-path 1.1s var(--ease);transition-delay:var(--reveal-delay,0ms)}
.js [data-reveal="media"].is-visible img{transform:scale(1)}

/* ------------------------------------------------- masked text reveal */
/* app.js wraps each WORD in a span and groups words into line spans. Word
   level, never character level: Arabic is cursive-joined and splitting per
   glyph would destroy the ligatures. */
  /* Definitions live in the "masked text reveal (per word)" block below. */

/* ---------------------------------------------------- entrance (no gate) */
[data-enter]{animation:enter-up 1s var(--ease) both;animation-delay:var(--enter-delay,0ms)}
[data-enter="fade"]{animation-name:enter-fade}
@keyframes enter-up{from{opacity:0;transform:translate3d(0,34px,0)}to{opacity:1;transform:none}}
@keyframes enter-fade{from{opacity:0}to{opacity:1}}

/* ============================================================== marquee */
/* Display-size ticker. The track holds the list twice so -50% loops clean. */
.marquee{position:relative;overflow:hidden;padding-block:clamp(1rem,2vw,1.75rem);border-block:1px solid var(--line)}
.marquee-track{display:flex;width:max-content;align-items:center;animation:marquee-run var(--marquee-time,40s) linear infinite;will-change:transform}
.marquee:hover .marquee-track{animation-play-state:paused}
@keyframes marquee-run{from{transform:translate3d(0,0,0)}to{transform:translate3d(-50%,0,0)}}
[dir="rtl"] .marquee-track{animation-name:marquee-run-rtl}
@keyframes marquee-run-rtl{from{transform:translate3d(-50%,0,0)}to{transform:translate3d(0,0,0)}}
.marquee-item{
  display:inline-flex;align-items:center;gap:1rem;flex:none;
  padding-inline:1.75rem;
  font-family:var(--font-display);font-weight:500;letter-spacing:-.03em;
  font-size:clamp(1.5rem,3.5vw,3rem);color:var(--ink-faint);
  white-space:nowrap;transition:color .4s var(--ease);
}
[dir="rtl"] .marquee-item{letter-spacing:0}
.marquee-item:hover{color:var(--ink-strong)}
.marquee-item img{width:1.6rem;height:1.6rem;object-fit:contain;opacity:.6}
.marquee-item .glyph{font-size:1.4rem;line-height:1}
.marquee-sep{flex:none;color:var(--ink-faint);font-size:1rem;opacity:.4}

/* =========================================================== work index */
/* The centrepiece. Full-width rows of display type; the project image is not
   in the row at all on desktop — it floats under the cursor. */
.work-index{border-top:1px solid var(--line)}
.work-row{
  position:relative;display:block;
  border-bottom:1px solid var(--line);
  padding-block:clamp(1.4rem,3.2vw,2.4rem);
  transition:opacity .5s var(--ease);
}
.work-row-inner{
  display:grid;gap:.75rem 2rem;align-items:baseline;
  grid-template-columns:1fr auto;
}
@media (min-width:900px){
  .work-row-inner{grid-template-columns:4.5rem minmax(0,1fr) 16rem 5rem;gap:2rem;align-items:center}
}
.work-num{
  font-family:var(--font-mono);font-size:.72rem;letter-spacing:.1em;
  color:var(--ink-faint);transition:color .4s var(--ease);
}
.work-title{
  font-family:var(--font-display);font-weight:500;letter-spacing:-.035em;
  font-size:clamp(1.75rem,5.5vw,4.25rem);line-height:1;color:var(--ink);
  transition:transform .6s var(--ease),color .4s var(--ease);
  transform-origin:calc(50% - 50% * var(--dir)) 50%;
}
[dir="rtl"] .work-title{letter-spacing:0;line-height:1.25;font-size:clamp(1.5rem,4.5vw,3.25rem)}
.work-meta{
  font-family:var(--font-mono);font-size:.72rem;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-muted);
  display:flex;flex-wrap:wrap;gap:.5rem 1rem;
}
[dir="rtl"] .work-meta{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.88rem}
.work-year{font-family:var(--font-mono);font-size:.72rem;color:var(--ink-faint);text-align:end}
.work-arrow{display:none}
@media (min-width:900px){
  .work-arrow{
    display:grid;place-items:center;justify-self:end;color:var(--ink);
    opacity:0;transform:translate3d(calc(-14px * var(--dir)),0,0);
    transition:opacity .4s var(--ease),transform .5s var(--ease);
  }
  .work-row:hover .work-arrow{opacity:1;transform:none}
  .work-row:hover .work-title{transform:translateX(calc(18px * var(--dir)));color:var(--ink-strong)}
  .work-row:hover .work-num{color:var(--ink)}
  /* Everything the cursor is not on recedes. */
  .work-index:hover .work-row:not(:hover){opacity:.35}
}
[dir="rtl"] .work-arrow svg{transform:scaleX(-1)}



/* ------------------------------------------------------- work grid */
/* Used for "related work" and anywhere a compact set reads better than rows.
   Flat: no border, no lift, no glow. The image does the work. */
.work-grid{display:grid;gap:clamp(1.5rem,3vw,2.5rem)}
@media (min-width:700px){.work-grid{grid-template-columns:repeat(2,1fr)}}
@media (min-width:1200px){.work-grid-3{grid-template-columns:repeat(3,1fr)}}
.work-card{display:block}
.work-card .media{overflow:hidden;background:var(--bg-2);aspect-ratio:4/3}
.work-card .media img{width:100%;height:100%;object-fit:cover;filter:grayscale(1) brightness(.85);transition:transform 1s var(--ease),filter .6s var(--ease)}
.work-card:hover .media img{transform:scale(1.05);filter:grayscale(0) brightness(1)}
.work-card .caption{display:flex;justify-content:space-between;gap:1rem;align-items:baseline;padding-top:1.1rem}
.work-card h3{font-size:clamp(1.1rem,1rem + .6vw,1.5rem);font-weight:500;transition:color .3s}
.work-card .caption .y{font-family:var(--font-mono);font-size:.72rem;color:var(--ink-faint);flex:none}
.work-card .sub{font-family:var(--font-mono);font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-muted);margin-top:.4rem}
[dir="rtl"] .work-card .sub{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.85rem}

/* ============================================================= services */
/* Numbered rows that open on hover, same family as the work index. */
.service-list{border-top:1px solid var(--line)}
.service-row{
  position:relative;display:block;border-bottom:1px solid var(--line);
  padding-block:clamp(1.6rem,3.5vw,2.75rem);
  transition:opacity .5s var(--ease);
}
.service-row-inner{display:grid;gap:1rem 2rem}
@media (min-width:900px){
  .service-row-inner{grid-template-columns:4.5rem minmax(0,22rem) minmax(0,1fr) 3rem;gap:2.5rem;align-items:start}
  .service-list:hover .service-row:not(:hover){opacity:.4}
}
.service-num{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.1em;color:var(--ink-faint);padding-top:.5rem}
.service-name{
  font-family:var(--font-display);font-weight:500;letter-spacing:-.03em;
  font-size:clamp(1.5rem,3vw,2.4rem);line-height:1.05;color:var(--ink);
  transition:transform .6s var(--ease),color .4s var(--ease);
}
[dir="rtl"] .service-name{letter-spacing:0;line-height:1.3}
.service-row:hover .service-name{color:var(--ink-strong);transform:translateX(calc(10px * var(--dir)))}
.service-desc{color:var(--ink-muted);font-size:1rem;max-width:40rem}
.service-row .work-arrow{padding-top:.4rem}

/* Capability list on a single service page. */
.cap-list{display:grid;gap:0;border-top:1px solid var(--line)}
.cap-list li{
  display:grid;grid-template-columns:3rem 1fr;gap:1rem;align-items:baseline;
  padding-block:1.1rem;border-bottom:1px solid var(--line-soft);color:var(--ink);
}
.cap-list li span:first-child{font-family:var(--font-mono);font-size:.7rem;color:var(--ink-faint)}

/* ================================================================= stats */
/* Big numbers on a bare rule grid. No panel, no gradient text, no hover wash. */
.stats{display:grid;gap:0;border-top:1px solid var(--line)}
@media (min-width:700px){.stats{grid-template-columns:repeat(auto-fit,minmax(min(100%,12rem),1fr))}}
.stat{padding-block:clamp(1.75rem,4vw,2.75rem);border-bottom:1px solid var(--line)}
@media (min-width:700px){
  .stat{border-bottom:0;padding-inline-end:2rem}
  .stat + .stat{border-inline-start:1px solid var(--line);padding-inline-start:2rem}
}
.stat .n{
  font-family:var(--font-display);font-weight:500;letter-spacing:-.04em;line-height:1;
  font-size:clamp(3rem,7vw,5.5rem);color:var(--ink-strong);
}
.stat .l{
  font-family:var(--font-mono);font-size:.7rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-muted);margin-top:1rem;
}
[dir="rtl"] .stat .l{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.9rem}

/* ================================================================== about */
.split-grid{display:grid;gap:clamp(2.5rem,6vw,5rem);align-items:start}
@media (min-width:960px){.split-grid{grid-template-columns:1fr 1fr}}
.media-stack{display:grid;grid-template-columns:1fr 1fr;gap:clamp(.75rem,1.5vw,1.25rem)}
.media-stack figure{overflow:hidden;background:var(--bg-2)}
.media-stack figure:first-child{margin-block-start:clamp(2rem,5vw,4rem)}
.media-stack img{width:100%;aspect-ratio:3/4;object-fit:cover;filter:grayscale(1) brightness(.9);transition:filter .7s var(--ease),transform 1.2s var(--ease)}
.media-stack figure:hover img{filter:grayscale(0) brightness(1);transform:scale(1.04)}

/* Full-bleed image band. */
.band{position:relative;overflow:hidden;background:var(--bg-2)}
.band img{width:100%;aspect-ratio:21/9;object-fit:cover}
@media (max-width:700px){.band img{aspect-ratio:4/3}}

/* ================================================================== tech */
/* A plain wrapped list of names. The old bordered tile grid is gone. */
.tech-list{display:flex;flex-wrap:wrap;gap:.5rem 0;border-top:1px solid var(--line);padding-top:2rem}
.tech-list li{
  display:inline-flex;align-items:center;gap:.6rem;
  font-family:var(--font-display);font-weight:400;letter-spacing:-.02em;
  font-size:clamp(1.1rem,2vw,1.75rem);color:var(--ink-muted);
  transition:color .3s var(--ease);
}
[dir="rtl"] .tech-list li{letter-spacing:0}
.tech-list li:hover{color:var(--ink-strong)}
.tech-list li::after{content:'';display:inline-block;width:.3rem;height:.3rem;border-radius:50%;background:currentColor;opacity:.35;margin-inline:1.25rem}
.tech-list li:last-child::after{display:none}
.tech-list img{width:1.4rem;height:1.4rem;object-fit:contain;opacity:.7}

/* ================================================================== prose */
.prose{color:var(--ink-muted);font-size:1.05rem;line-height:1.75;max-width:46rem}
.prose>*+*{margin-top:1.25rem}
.prose h2{font-size:clamp(1.5rem,1.2rem + 1.2vw,2rem);margin-top:3rem;color:var(--ink-strong)}
.prose h3{font-size:1.25rem;margin-top:2.25rem;color:var(--ink-strong)}
.prose strong{color:var(--ink);font-weight:600}
.prose a{color:var(--ink);text-decoration:underline;text-underline-offset:4px;text-decoration-thickness:1px;transition:color .25s}
.prose a:hover{color:var(--accent)}
.prose ul,.prose ol{display:grid;gap:.6rem;padding-inline-start:1.3rem}
.prose ul li{list-style:disc}
.prose ol li{list-style:decimal}
.prose li::marker{color:var(--ink-faint)}
.prose blockquote{
  padding-inline-start:1.5rem;border-inline-start:1px solid var(--line);
  color:var(--ink);font-family:var(--font-display);font-size:1.3rem;line-height:1.4;
  letter-spacing:-.02em;
}
[dir="rtl"] .prose blockquote{letter-spacing:0;line-height:1.7}
.prose code{background:var(--bg-3);padding:.15em .45em;font-size:.9em;font-family:var(--font-mono);color:var(--ink)}
.prose pre{background:var(--bg-2);border:1px solid var(--line);padding:1.25rem;overflow-x:auto}
.prose pre code{background:none;padding:0}
.prose hr{border:0;border-top:1px solid var(--line);margin-block:2.5rem}
.prose img{width:100%}

/* Oversized intro paragraph. */
.lead{
  font-family:var(--font-display);font-weight:400;letter-spacing:-.025em;
  font-size:clamp(1.35rem,1rem + 1.6vw,2.25rem);line-height:1.25;color:var(--ink);
  max-width:44rem;
}
[dir="rtl"] .lead{letter-spacing:0;line-height:1.6}

/* ============================================================== post cards */
.post-list{display:grid;gap:0;border-top:1px solid var(--line)}
.post-row{
  display:grid;gap:.6rem 2rem;
  padding-block:clamp(1.4rem,3vw,2.25rem);border-bottom:1px solid var(--line);
  transition:opacity .5s var(--ease);
}
@media (min-width:900px){
  .post-row{grid-template-columns:9rem minmax(0,1fr) 3rem;align-items:center;gap:2.5rem}
  .post-list:hover .post-row:not(:hover){opacity:.4}
}
.post-date{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.1em;color:var(--ink-faint)}
.post-row h3{
  font-family:var(--font-display);font-weight:500;letter-spacing:-.03em;
  font-size:clamp(1.25rem,2.4vw,1.9rem);line-height:1.15;color:var(--ink);
  transition:transform .6s var(--ease),color .4s var(--ease);
}
[dir="rtl"] .post-row h3{letter-spacing:0;line-height:1.4}
.post-row:hover h3{color:var(--ink-strong);transform:translateX(calc(10px * var(--dir)))}
.post-row p{color:var(--ink-muted);font-size:.95rem;margin-top:.4rem;max-width:44rem}

.pagination{display:flex;gap:1.25rem;margin-top:4rem;flex-wrap:wrap;font-family:var(--font-mono);font-size:.78rem}
.pagination a,.pagination span{color:var(--ink-faint);transition:color .25s}
.pagination a:hover{color:var(--ink-strong)}
.pagination .is-current{color:var(--ink-strong);text-decoration:underline;text-underline-offset:5px}

/* =============================================================== page bits */
.page-hero{padding-block:clamp(9rem,20vh,14rem) clamp(2.5rem,5vw,4rem)}
.page-hero h1{font-size:clamp(2.5rem,1rem + 7vw,6.5rem)}

.breadcrumb{
  display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;
  font-family:var(--font-mono);font-size:.7rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-faint);margin-bottom:clamp(2rem,5vw,3.5rem);
}
[dir="rtl"] .breadcrumb{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.85rem}
.breadcrumb a{transition:color .25s}
.breadcrumb a:hover{color:var(--ink-strong)}
.breadcrumb .sep{opacity:.5}

/* Metadata rail: key/value pairs on rules. */
.meta-rail{display:grid;gap:0;border-top:1px solid var(--line)}
@media (min-width:800px){.meta-rail{grid-template-columns:repeat(auto-fit,minmax(min(100%,11rem),1fr))}}
.meta-item{padding-block:1.5rem;border-bottom:1px solid var(--line)}
@media (min-width:800px){
  .meta-item{border-bottom:0;padding-inline-end:1.5rem}
  .meta-item + .meta-item{border-inline-start:1px solid var(--line);padding-inline-start:1.5rem}
}
.meta-item .k{
  font-family:var(--font-mono);font-size:.68rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-faint);margin-bottom:.75rem;
}
[dir="rtl"] .meta-item .k{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.82rem}
.meta-item .v{color:var(--ink);font-size:1rem}

.chips{display:flex;flex-wrap:wrap;gap:.4rem .9rem}
.chip{font-family:var(--font-mono);font-size:.72rem;color:var(--ink-muted)}
[dir="rtl"] .chip{font-family:var(--font-body);font-size:.85rem}

.case-section{margin-bottom:clamp(2.5rem,5vw,4rem)}
.case-section .label{display:block;margin-bottom:1.25rem}

.empty-state{
  padding:clamp(3rem,8vw,6rem) 0;border-top:1px solid var(--line);
  font-family:var(--font-mono);font-size:.8rem;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-faint);
}
[dir="rtl"] .empty-state{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:1rem}

/* Filters as a mono rule-bar. */
.filters{
  display:flex;flex-wrap:wrap;gap:1.5rem;
  padding-block:1.25rem;border-block:1px solid var(--line);
  margin-bottom:clamp(2rem,4vw,3rem);
}
.filter{
  position:relative;font-family:var(--font-mono);font-size:.72rem;
  letter-spacing:.14em;text-transform:uppercase;color:var(--ink-faint);
  padding-block:.25rem;transition:color .3s;
}
[dir="rtl"] .filter{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.92rem}
.filter::after{
  content:'';position:absolute;inset-block-end:0;inset-inline:0;height:1px;background:currentColor;
  transform:scaleX(0);transform-origin:calc(50% - 50% * var(--dir)) 50%;
  transition:transform .4s var(--ease);
}
.filter:hover{color:var(--ink)}
.filter:hover::after,.filter.is-active::after{transform:scaleX(1)}
.filter.is-active{color:var(--ink-strong)}
.work-row.is-refiltered{animation:filter-in .5s var(--ease) both}
@keyframes filter-in{from{opacity:0}to{opacity:1}}

/* ============================================================== CTA band */
/* A full-width statement, not a bordered card with an orbiting light. */
.cta{padding-block:clamp(5rem,12vw,10rem);border-top:1px solid var(--line)}
.cta h2{font-size:clamp(2.25rem,1rem + 6.5vw,6rem);max-width:16ch}
.cta .cta-foot{
  display:grid;gap:2rem;align-items:end;margin-top:clamp(2.5rem,5vw,4rem);
  padding-top:2rem;border-top:1px solid var(--line);
}
@media (min-width:900px){.cta .cta-foot{grid-template-columns:1fr auto}}
.cta p{color:var(--ink-muted);max-width:34rem}
.cta-actions{display:flex;flex-wrap:wrap;gap:1rem}

/* ================================================================== form */
.form{display:grid;gap:0}
.field{display:grid;gap:.75rem;padding-block:1.5rem;border-bottom:1px solid var(--line)}
.form-grid{display:grid;gap:0}
@media (min-width:760px){
  .form-grid{grid-template-columns:1fr 1fr;column-gap:2.5rem}
  .field-full{grid-column:1/-1}
}
.field label{
  font-family:var(--font-mono);font-size:.68rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-faint);
}
[dir="rtl"] .field label{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.85rem}
.field .req{color:var(--accent)}
.input,.textarea,.select{
  width:100%;padding:.35rem 0;
  background:none;border:0;border-bottom:1px solid transparent;
  color:var(--ink-strong);font-size:1.05rem;
  transition:border-color .3s var(--ease);
}
.input::placeholder,.textarea::placeholder{color:var(--ink-faint)}
.input:focus,.textarea:focus,.select:focus{outline:none;border-bottom-color:var(--ink)}
.textarea{resize:vertical;min-height:6rem}
.select{
  appearance:none;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none' stroke='%238b8a86' stroke-width='1.5' stroke-linecap='round'%3E%3Cpath d='m3 5 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right .25rem center;padding-inline-end:2rem;
}
[dir="rtl"] .select{background-position:left .25rem center;padding-inline-end:0;padding-inline-start:2rem}
.select option{background:var(--bg-2);color:var(--ink)}
.field-error{font-size:.82rem;color:var(--danger)}
.input.has-error,.textarea.has-error,.select.has-error{border-bottom-color:var(--danger)}
.hp{position:absolute!important;left:-9999px!important;opacity:0;height:0;width:0;pointer-events:none}

.alert{
  display:flex;gap:.9rem;padding:1.1rem 0;margin-bottom:1.5rem;
  border-block:1px solid var(--line);font-size:.95rem;
}
.alert svg{flex:none;margin-top:.15rem}
.alert-success{color:var(--ok)}
.alert-error{color:var(--danger)}
.alert-info,.alert-warn{color:var(--ink)}

/* Contact rail: one line per channel, on rules. */
.contact-lines{display:grid;gap:0;border-top:1px solid var(--line)}
.contact-line{
  display:grid;grid-template-columns:1fr auto;gap:1rem;align-items:baseline;
  padding-block:1.35rem;border-bottom:1px solid var(--line);
  transition:color .3s var(--ease);
}
.contact-line .k{
  font-family:var(--font-mono);font-size:.68rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-faint);
}
[dir="rtl"] .contact-line .k{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.85rem}
.contact-line .v{color:var(--ink);font-size:1.05rem;overflow-wrap:anywhere;text-align:end;transition:color .3s}
a.contact-line:hover .v{color:var(--ink-strong)}

.socials{display:flex;flex-wrap:wrap;gap:1.5rem;margin-top:2rem}
.social{
  font-family:var(--font-mono);font-size:.72rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-muted);transition:color .25s;
}
[dir="rtl"] .social{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.9rem}
.social:hover{color:var(--ink-strong)}

/* ================================================================= footer */
.site-footer{border-top:1px solid var(--line);margin-top:auto;padding-top:clamp(3rem,7vw,6rem)}
/* The wordmark, set huge, is the footer's main event. */
.footer-mark{
  font-family:var(--font-display);font-weight:600;letter-spacing:-.045em;
  font-size:clamp(2.5rem,12vw,11rem);line-height:.88;color:var(--ink-strong);
  margin-bottom:clamp(2.5rem,6vw,4.5rem);
}
[dir="rtl"] .footer-mark{letter-spacing:0;line-height:1.1;font-size:clamp(2.5rem,11vw,9rem)}
.footer-cols{
  display:grid;gap:2.5rem;padding-bottom:clamp(2.5rem,5vw,4rem);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,12rem),1fr));
}
.footer-col h4{
  font-family:var(--font-mono);font-size:.68rem;font-weight:500;
  letter-spacing:.18em;text-transform:uppercase;color:var(--ink-faint);
  margin-bottom:1.25rem;
}
[dir="rtl"] .footer-col h4{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.85rem}
.footer-col ul{display:grid;gap:.75rem}
.footer-col a,.footer-col li{font-size:.95rem;color:var(--ink-muted);transition:color .25s}
.footer-col a:hover{color:var(--ink-strong)}
.footer-bottom{
  display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;align-items:center;
  padding-block:1.75rem;border-top:1px solid var(--line);
  font-family:var(--font-mono);font-size:.7rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-faint);
}
[dir="rtl"] .footer-bottom{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.85rem}

/* ====================================================== page transition */
/* A panel that covers the page between navigations.
 *
 * The covering state is a plain class, never an animation. An earlier version
 * had the veil covering by default and animated it away on load, which meant
 * that any failure to run that animation left an opaque panel over the entire
 * site with no way out. Now: no JS at all and the veil is parked off-screen;
 * with JS it covers until app.js adds .is-open, and if the transition never
 * paints it simply snaps open instead of trapping the page.
 */
.page-veil{
  position:fixed;inset:0;z-index:300;pointer-events:none;
  background:var(--bg);
  transform:translateY(-100%);
}
.js .page-veil{transform:translateY(0)}
.js .page-veil.is-open{transform:translateY(-100%);transition:transform .8s var(--ease) .05s}
/* On the way out app.js drops .is-open, so this slides the panel back down. */
body.is-leaving .page-veil{transform:translateY(0);transition:transform .5s var(--ease)}

/* ========================================================== parallax */
[data-parallax]{--py:0px;transform:translate3d(0,var(--py),0);will-change:transform}

/* ==================================================== reduced motion */
@media (prefers-reduced-motion:reduce){
  .js [data-reveal]{opacity:1!important;transform:none!important;clip-path:none!important;transition:none!important}
  .js [data-reveal="media"] img{transform:none!important}
    [data-enter]{animation:none!important;opacity:1!important;transform:none!important}
  .js .mobile-nav a{opacity:1!important;transform:none!important}
  .page-veil{display:none!important}
  .cursor-dot,.cursor-ring{display:none!important}
  *,*::before,*::after{
    animation-duration:.01ms!important;
    animation-iteration-count:1!important;
    transition-duration:.01ms!important;
    scroll-behavior:auto!important;
  }
  .grain,.marquee-track,.scroll-hint i::after,.hero-note .dot{animation:none!important;transform:none!important}
  [data-parallax]{--py:0px!important}
}

/* --------------------------------------------------------------- utility */
.text-center{text-align:center}
.muted{color:var(--ink-muted)}
.faint{color:var(--ink-faint)}
.mono{font-family:var(--font-mono)}
.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}
.mt-1{margin-top:.5rem}.mt-2{margin-top:1rem}.mt-3{margin-top:1.5rem}.mt-4{margin-top:2rem}.mt-6{margin-top:3rem}.mt-8{margin-top:clamp(3rem,7vw,6rem)}
.flex{display:flex}.items-center{align-items:center}.justify-between{justify-content:space-between}
.gap-1{gap:.5rem}.gap-2{gap:1rem}.wrap{flex-wrap:wrap}

/* A reveal variant that changes nothing itself — it exists only so the
   IntersectionObserver flips `is-visible` on an element whose words carry
   the animation (see .split-word above). */
.js [data-reveal="text"]{opacity:1;transform:none}

/* =========================================================== corrections */

/* Standalone oversized intro, used outside .section-head. */
.lede{color:var(--ink-muted);font-size:clamp(1rem,.95rem + .45vw,1.25rem);max-width:44rem}

/* The hover arrow is shared by all three row types, so the reveal has to be
   too — scoping it to .work-row alone left it permanently invisible on the
   service and post lists. */
@media (min-width:900px){
  .service-row:hover .work-arrow,
  .post-row:hover .work-arrow{opacity:1;transform:none}
}

/* Stacked variant of the metadata rail: one column, rules below rather than
   between, so it does not inherit the horizontal dividers. */
.meta-rail.is-stack{grid-template-columns:1fr}
.meta-rail.is-stack .meta-item{
  border-bottom:1px solid var(--line);
  border-inline-start:0;padding-inline:0;
}

/* Gallery images keep their own proportions instead of the 21:9 hero crop. */
.band-auto img{aspect-ratio:auto}

/* Centre the floating preview on the cursor with a transform rather than a
   vw-based margin, which was wrong wherever the min() clamp took over. */

/* If app.js never arrives, this class is stripped after a few seconds and
   every reveal falls back to plain visible content (see the head script). */

/* ------------------------------------------ masked text reveal (per word) */
/* Supersedes the earlier per-line grouping, which measured line breaks once
   and clipped words whenever the real layout disagreed with the measurement.
   Each word is now its own mask, so native wrapping decides the breaks and a
   resize can never strand a word behind an overflow:hidden edge.

   Still word level, never character level: Arabic is cursive-joined and a box
   per glyph would break the ligatures. */
.split-word{
  display:inline-block;overflow:hidden;vertical-align:bottom;
  /* Padding lets descenders sit inside the mask; the matching negative
     margin keeps the line box exactly where it was. */
  padding-block:.04em .16em;margin-block:-.04em -.16em;
}
[dir="rtl"] .split-word{padding-block:.1em .3em;margin-block:-.1em -.3em}
.split-inner{display:inline-block;will-change:transform}
.js .split-ready .split-inner{transform:translate3d(0,110%,0)}
.js .split-ready.is-visible .split-inner{
  transform:none;
  transition:transform 1s var(--ease);
  transition-delay:calc(var(--reveal-delay,0ms) + var(--w) * 42ms);
}
@media (prefers-reduced-motion:reduce){
  .js .split-ready .split-inner{transform:none!important;transition:none!important}
}

/* Long unbroken words must wrap rather than push the page sideways. */
h1,h2,h3,.work-title,.service-name,.footer-mark{overflow-wrap:break-word}

/* ------------------------------------------- work index: narrow screens */
/* The old `1fr auto` put the number in the wide column and squeezed the
   project title into an auto track, pushing it off the side of the screen.
   Explicit placement instead: number in a fixed gutter, everything else
   stacked in the second column. */
.work-row-inner{
  grid-template-columns:2.75rem minmax(0,1fr);
  gap:.45rem 1rem;align-items:baseline;
}
.work-row-inner > .work-num  {grid-area:1 / 1}
.work-row-inner > .work-title{grid-area:1 / 2}
.work-row-inner > .work-meta {grid-area:2 / 2}
.work-row-inner > .work-year {grid-area:3 / 2;text-align:start}
@media (min-width:900px){
  .work-row-inner{grid-template-columns:4.5rem minmax(0,1fr) 16rem 5rem;gap:2rem;align-items:center}
  /* These have to match the selectors above exactly. A `> *` reset is less
     specific than `> .work-num`, so it silently lost and every row stayed
     in the stacked mobile layout on desktop. */
  .work-row-inner > .work-num,
  .work-row-inner > .work-title,
  .work-row-inner > .work-meta,
  .work-row-inner > .work-year{grid-area:auto}
  .work-row-inner > .work-year{text-align:end}
}

/* ------------------------------------------------------ cursor, part two */
/* The custom dot and ring replace the pointer rather than trailing beside it.
   Restored while the cursor is idle (app.js adds .cursor-idle after a pause)
   and never applied on touch, where neither element exists. */
@media (hover:hover) and (pointer:fine){
  body:not(.cursor-idle){cursor:none}
  body:not(.cursor-idle) a,
  body:not(.cursor-idle) button,
  body:not(.cursor-idle) [role="button"]{cursor:none}
  /* Text entry keeps its I-beam — losing it makes a form feel broken. */
  body:not(.cursor-idle) input,
  body:not(.cursor-idle) textarea,
  body:not(.cursor-idle) select{cursor:auto}
}
@media (prefers-reduced-motion:reduce){
  body,body *{cursor:auto!important}
}


/* ==========================================================================
   STAGE LAYER
   Reference: activetheory.net — checked against its live shell and its own
   UI-layout data (assets/data/uil.*.json), not from memory. What that file
   actually specifies:

     · one typeface everywhere — NB Architekt Std, a squared monospace —
       at lineHeight 1.1–1.2 display, 1.6–2.0 body, and POSITIVE letter
       spacing (0.01–0.05). Never the tight negative tracking of a
       geometric sans.
     · ink is a grey ramp on pure black: #eeeeee, #aaaaaa, #888888.
     · the panel grey is #161616 (HomeFloorShader uBaseColor).
     · there is no brand fill. Colour is emissive — point lights and bloom
       (#93e5ff, #7dc2f0, #d600ff, #0bed90, #b4e0e3) burning through an
       otherwise black room, composited through UnrealBloom.
     · the whole site is one WebGL stage: body overflow hidden, #Stage, and
       screens named Home / Work / WorkDetail / About / Contact / Footer.

   This site cannot be a canvas: it is server-rendered, bilingual and
   indexed, and the Arabic text, the hreflang set and the JSON-LD all have to
   stay in the DOM. So the stage is rebuilt in DOM terms — black room, one
   emissive light field on a canvas behind everything, squared mono type,
   HUD chrome instead of a nav bar, and bloom as the only colour.

   The blue brand field this replaces (three drifting orbs, a conic mesh, an
   orbiting spotlight, gradient headlines) is gone entirely. It was the
   reason the page still read as a dark SaaS template.
   ========================================================================== */

/* ----------------------------------------------------------- the stage */
/* Black room. The canvas is the only thing in it; everything else is type. */
.ambience{position:fixed;inset:0;z-index:-2;pointer-events:none;overflow:hidden;background:#000}

.stage-canvas{
  position:absolute;inset:0;width:100%;height:100%;
  display:block;
  /* Additive light on black: the canvas paints the emissive pass, the page
     paints the type. Nothing about the canvas is load-bearing — it is empty
     until app.js draws into it. */
  opacity:0;transition:opacity 1.6s var(--ease-io);
}
.stage-canvas.is-lit{opacity:1}

/* Floor haze. A single low band of light, the way their home scene sits on a
   lit floor rather than in an even wash. */
.ambience::before{
  content:'';position:absolute;inset-inline:-10%;inset-block-end:-30vh;height:80vh;
  background:radial-gradient(50% 50% at 50% 100%,rgba(147,229,255,.13),transparent 70%);
  filter:blur(60px);
}
/* Vignette. Corners go to true black so the lit centre reads as a room. */
.ambience::after{
  content:'';position:absolute;inset:0;
  background:radial-gradient(125% 85% at 50% 45%,transparent 45%,rgba(0,0,0,.92) 100%);
}

/* Grain over black needs to be lighter than it was over colour. */
.grain{opacity:.035}

/* ------------------------------------------------------------- HUD chrome */
/* Fixed corner metadata. Their chrome is a mark, a trigger and a few small
   readouts pinned to the viewport — never a centred row of links. */
.hud{
  position:fixed;z-index:60;pointer-events:none;
  font-family:var(--font-mono);font-size:.62rem;letter-spacing:.22em;text-transform:uppercase;
  color:var(--ink-faint);
  line-height:1.9;
}
[dir="rtl"] .hud{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.72rem}
.hud-bl{inset-block-end:var(--gutter);inset-inline-start:var(--gutter)}
.hud-br{inset-block-end:var(--gutter);inset-inline-end:var(--gutter);text-align:end}
@media (max-width:900px){.hud{display:none}}
.hud .live{color:var(--glow);text-shadow:0 0 10px rgba(147,229,255,.7)}

/* ------------------------------------------------------------- loader */
/* Their site opens on black with a counter. Same idea, but it is removed by
   JS and never blocks: without JS the element is never rendered visible. */
.loader{
  position:fixed;inset:0;z-index:200;background:#000;
  display:flex;align-items:flex-end;justify-content:space-between;
  padding:var(--gutter);
  font-family:var(--font-mono);font-size:.7rem;letter-spacing:.24em;text-transform:uppercase;
  color:var(--ink-muted);
  transition:opacity .7s var(--ease),visibility .7s;
}
[dir="rtl"] .loader{font-family:var(--font-body);letter-spacing:0;text-transform:none}
html:not(.js) .loader,html.seen .loader{display:none}
.loader.is-done{opacity:0;visibility:hidden;pointer-events:none}
.loader .pct{font-size:clamp(3rem,12vw,9rem);letter-spacing:.02em;line-height:.9;color:var(--ink-strong)}
.loader .bar{position:absolute;inset-block-end:0;inset-inline-start:0;height:1px;width:100%;
  background:var(--glow);transform-origin:calc(50% - 50% * var(--dir)) 50%;
  transform:scaleX(var(--load,0));box-shadow:0 0 14px 1px rgba(147,229,255,.8)}

/* ================================================================== type */
/* Squared mono, uppercase, positive tracking — the single loudest difference
   from the previous pass, which used a rounded geometric sans pulled tight. */
h1,h2,h3,.work-title,.service-name,.footer-mark,.cta h2,.mobile-nav a{
  text-transform:uppercase;
  letter-spacing:.02em;
  font-weight:500;
}
h1,h2{line-height:1.06}
/* Monospace sets much wider than Sora did, so every display size comes down
   a step or the headline no longer fits its line. */
h1{font-size:clamp(2rem,.9rem + 4.6vw,4.6rem)}
h2{font-size:clamp(1.5rem,.9rem + 2.6vw,2.9rem)}
h3{font-size:clamp(1.1rem,.95rem + .9vw,1.5rem);letter-spacing:.04em}
.page-hero h1{font-size:clamp(2rem,.9rem + 4.4vw,4.4rem)}
.cta h2{font-size:clamp(1.75rem,.9rem + 3.6vw,4rem);max-width:20ch}

/* Arabic keeps Cairo and takes none of it: there is no case in Arabic, and
   tracking breaks the joins. Said plainly rather than left to leak through. */
[dir="rtl"] h1,[dir="rtl"] h2,[dir="rtl"] h3,
[dir="rtl"] .work-title,[dir="rtl"] .service-name,[dir="rtl"] .footer-mark,
[dir="rtl"] .cta h2,[dir="rtl"] .mobile-nav a{
  text-transform:none;letter-spacing:0;font-weight:600;
}
[dir="rtl"] h1,[dir="rtl"] h2{line-height:1.3}

/* Body copy takes their generous leading — the reference sets body at
   lineHeight 1.6-2.0. */
.prose,.lede,.hero-lede{line-height:1.85}

/* Short-form copy is set in the mono as well, because the reference uses one
   face for everything. Long-form prose deliberately stays in Inter: several
   hundred words of monospace is a wall, and the insights posts and case
   studies are exactly that. */
.hero-lede,.lede,.service-desc,.work-meta,.post-row p,.cta p,
.footer-col a,.footer-col li,.stat .l,.contact-line .v,.meta-item .v{
  font-family:var(--font-mono);
  letter-spacing:.01em;
}
/* Monospace runs wider, so short-form copy comes down a step to keep its
   measure. */
.hero-lede,.lede{font-size:clamp(.85rem,.8rem + .25vw,1rem)}
.service-desc,.cta p,.post-row p{font-size:.85rem}
.footer-col a,.footer-col li{font-size:.8rem}
[dir="rtl"] .hero-lede,[dir="rtl"] .lede,[dir="rtl"] .service-desc,
[dir="rtl"] .work-meta,[dir="rtl"] .post-row p,[dir="rtl"] .cta p,
[dir="rtl"] .footer-col a,[dir="rtl"] .footer-col li,[dir="rtl"] .stat .l,
[dir="rtl"] .contact-line .v,[dir="rtl"] .meta-item .v{
  font-family:var(--font-body);letter-spacing:0;font-size:.95rem;
}

::selection{background:var(--glow);color:#000}

/* ------------------------------------------------------------- accents */
/* One rule for the whole site: interactive things go white and pick up a
   bloom; nothing is ever filled with a brand colour. */
.label .idx{color:var(--glow)}
.hero-note .dot{background:var(--glow);box-shadow:0 0 14px 3px rgba(147,229,255,.85)}
.scroll-hint i::after{background:var(--glow)}
.scroll-progress{background:var(--glow);box-shadow:0 0 12px rgba(147,229,255,.8)}
.js .scroll-progress{opacity:.9}

.btn{
  border-color:rgba(255,255,255,.22);
  border-radius:0;
  font-family:var(--font-mono);letter-spacing:.2em;
}
[dir="rtl"] .btn{font-family:var(--font-body);letter-spacing:0}
.btn::before{background:#fff}
.btn:hover{color:#000;border-color:#fff;box-shadow:0 0 40px -8px rgba(255,255,255,.45)}
.btn-solid{background:#fff;color:#000;border-color:#fff;box-shadow:0 0 44px -12px rgba(255,255,255,.55)}
.btn-solid::before{background:#000}
.btn-solid:hover{color:#fff;box-shadow:0 0 60px -14px rgba(147,229,255,.6);border-color:#fff}

.link{color:var(--ink)}
.link:hover{color:var(--glow)}
.nav a:hover,.nav a[aria-current="page"]{color:var(--ink-strong)}
.lang-switch:hover,.brand:hover span{color:var(--glow)}

/* Index rows. The rule under the hovered row lights up like a filament; the
   title goes white rather than coloured. */
.work-row::after,.service-row::after,.post-row::after{
  content:'';position:absolute;inset-block-end:-1px;inset-inline:0;height:1px;
  background:var(--glow);box-shadow:0 0 12px rgba(147,229,255,.75);
  transform:scaleX(0);transform-origin:calc(50% - 50% * var(--dir)) 50%;
  transition:transform .7s var(--ease);pointer-events:none;
}
.post-row{position:relative}
@media (min-width:900px){
  .work-row:hover::after,.service-row:hover::after,.post-row:hover::after{transform:scaleX(1)}
  .work-row:hover .work-title,.service-row:hover .service-name,.post-row:hover h3{color:var(--ink-strong)}
  .work-row:hover .work-num,.service-row:hover .service-num{color:var(--glow)}
  .work-row:hover .work-arrow,.service-row:hover .work-arrow,.post-row:hover .work-arrow{color:var(--glow)}
}

/* Media. Their project plates read as lit screens in a dark room: dim and
   desaturated at rest, and they come up to full brightness under the cursor
   rather than gaining a tint. */
.work-card .media,.media-stack figure,.band{background:var(--bg-3)}
.work-card .media img,.media-stack img{filter:grayscale(1) brightness(.55) contrast(1.1)}
.work-card:hover .media img,.media-stack figure:hover img{filter:grayscale(0) brightness(1) contrast(1)}
.work-card .media::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.10);
  transition:box-shadow .5s var(--ease);
}
.work-card:hover .media::after{box-shadow:inset 0 0 0 1px rgba(147,229,255,.55),0 0 50px -12px rgba(147,229,255,.5)}
.work-card:hover h3{color:var(--ink-strong)}
.band img{filter:grayscale(1) brightness(.6) contrast(1.1)}

.tech-list li:hover,.marquee-item:hover,.filter:hover{color:var(--glow)}
.filter.is-active{color:var(--ink-strong)}
.filter.is-active::after,.filter:hover::after{background:var(--glow)}

.prose a{color:var(--ink-strong)}
.prose a:hover{color:var(--glow)}
.prose li::marker{color:var(--glow)}
.prose blockquote{border-inline-start-color:var(--glow)}
.prose code,.prose pre{background:var(--bg-3);border-radius:0}

.input:focus,.textarea:focus,.select:focus{border-bottom-color:var(--glow)}
.field .req{color:var(--glow)}
a.contact-line:hover .v,.social:hover,.footer-col a:hover,
.pagination a:hover,.breadcrumb a:hover{color:var(--glow)}

/* Cursor: a small hard ring, and a bloom point at its centre. */
.cursor-dot{background:var(--glow);box-shadow:0 0 14px 3px rgba(147,229,255,.9)}
.cursor-ring{border-color:rgba(255,255,255,.35)}
body.cursor-hot .cursor-ring{border-color:rgba(147,229,255,.9);background:rgba(147,229,255,.06)}

/* The closing CTA is the one place a light source is aimed at the type. */
.cta{position:relative;overflow:hidden;border-top:1px solid var(--line)}
.cta::before{
  content:'';position:absolute;inset-block-start:-60%;inset-inline:0;height:150%;pointer-events:none;
  background:radial-gradient(45% 45% at 50% 60%,rgba(147,229,255,.18),transparent 70%);
  filter:blur(70px);
}
.cta > *{position:relative}

/* ------------------------------------------------------------- header */
/* Mark, language, trigger. The inline link row is gone at every width — the
   menu is the navigation now, exactly as it is on the reference. */
.nav{display:none!important}
.nav-toggle{display:block!important}
.site-header{mix-blend-mode:normal}
.site-header.is-stuck{background:rgba(0,0,0,.55)}
.brand span{font-family:var(--font-mono);letter-spacing:.22em;text-transform:uppercase;font-size:.74rem}
[dir="rtl"] .brand span{font-family:var(--font-body);letter-spacing:0;text-transform:none;font-size:.9rem}
.lang-switch{font-family:var(--font-mono);letter-spacing:.22em}

/* Full-screen menu. Numbered, mono, and each line lights as it is hovered. */
.mobile-nav{background:#000}
/* The panel already carries the gutter, so the list only centres itself. */
.mobile-nav nav{display:grid;gap:.25rem;width:100%;max-width:var(--shell);margin-inline:auto}
.mobile-nav nav a{
  font-family:var(--font-display);
  font-size:clamp(1.6rem,6vw,3.6rem);
  letter-spacing:.02em;
  color:var(--ink-muted);
  display:flex;align-items:baseline;gap:1.25rem;
  padding-block:.25em;
  border-bottom:1px solid var(--line-soft);
  transition:color .35s var(--ease);
}
.mobile-nav nav a::before{
  content:counter(menu,decimal-leading-zero);counter-increment:menu;
  font-size:.6rem;letter-spacing:.2em;color:var(--ink-faint);
  transform:translateY(-.6em);flex:none;
}
.mobile-nav nav{counter-reset:menu}
[dir="rtl"] .mobile-nav nav a{font-family:var(--font-body)}
.mobile-nav nav a:hover{color:var(--ink-strong);text-shadow:0 0 30px rgba(147,229,255,.55)}
.mobile-nav nav a:hover::before{color:var(--glow)}

/* --------------------------------------------------------------- hero */
/* The rule above the lede belonged to a left-aligned, bottom-anchored hero.
   Centred in the room it reads as a stray line, so it goes. */
.hero-foot{border-top:0;padding-top:0}
/* One full viewport, type centred in the room. */
.hero{
  min-height:100svh;display:flex;flex-direction:column;justify-content:center;
  text-align:center;padding-block:clamp(7rem,18vh,11rem) clamp(5rem,12vh,8rem);
}
.hero .shell{display:grid;justify-items:center;gap:clamp(1.25rem,3vw,2rem)}
.hero h1{max-width:18ch}
.hero-note{justify-content:center}
.hero-foot{display:grid;justify-items:center;gap:clamp(1.5rem,3vw,2.25rem);grid-template-columns:1fr!important}
.hero-lede{max-width:46ch;color:var(--ink-muted)}
.hero-actions{justify-content:center}
.scroll-hint{inset-inline:0;justify-content:center;flex-direction:column;align-items:center}

/* Two leftovers still pulled tight for the old geometric sans. Monospace
   needs the opposite, and the marquee needs to come down a step now that it
   is set in a wider face. */
.stat .n{letter-spacing:.01em}
.marquee-item{letter-spacing:.06em;text-transform:uppercase;font-size:clamp(1rem,2vw,1.6rem)}
[dir="rtl"] .marquee-item{letter-spacing:0;text-transform:none}
.marquee-sep{opacity:.25}

/* The studio photographs sit a touch brighter than the project plates: they
   are the room, not a screen in it. */
.media-stack img{filter:grayscale(1) brightness(.72) contrast(1.05)}

/* Form controls take the mono too — they are UI, and the reference sets its
   UI in the same face as everything else. */
.input,.textarea,.select{font-family:var(--font-mono);font-size:.9rem;letter-spacing:.01em}
[dir="rtl"] .input,[dir="rtl"] .textarea,[dir="rtl"] .select{font-family:var(--font-body);letter-spacing:0;font-size:1rem}

/* ------------------------------------------------------------ footer */

.footer-mark{
  font-size:clamp(1.6rem,7vw,5.5rem);
  color:var(--ink-strong);
  text-align:center;
  opacity:.9;
}

/* ------------------------------------------------- reduced motion */
@media (prefers-reduced-motion:reduce){
  .stage-canvas{transition:none}
}

/* ============================================ work index: hover panel */
/* Replaces the image that used to follow the cursor. The shot is anchored to
   its own row and wipes in from the leading edge, so it reads as belonging to
   that project rather than trailing the mouse around the page.

   The base rule is the touch/narrow presentation: a plain strip under the
   row. The desktop panel is layered on top of it, and only where a fine
   pointer, the room to place it, and no reduced-motion preference all hold —
   so reduced-motion visitors keep the strip and still see the work. */
.work-shot{margin-top:1rem;overflow:hidden;background:var(--bg-2);aspect-ratio:16/10}
/* Full width is right on a phone; on a wide row it would be enormous. */
@media (min-width:700px){.work-shot{max-width:26rem}}
.work-shot img{width:100%;height:100%;object-fit:cover}

/* 1200px is the first width where the metadata column (16rem) plus the arrow
   (5rem) plus gaps leaves a run of empty title column wide enough to hold the
   panel without it landing on either the title or the metadata. */
@media (hover:hover) and (pointer:fine) and (min-width:1200px) and (prefers-reduced-motion:no-preference){
  /* Rows overlap once a panel is showing, so the hovered row has to win. */
  .work-index{position:relative}
  .work-row:hover{z-index:5}

  .work-shot{
    position:absolute;margin:0;z-index:3;pointer-events:none;
    inset-block-start:50%;
    /* Sits in the empty run of the title column, clear of the meta and year. */
    /* Must clear arrow (5rem) + gap (2rem) + meta (16rem) + gap (2rem) = 25rem. */
    inset-inline-end:26rem;
    width:15rem;aspect-ratio:4/3;
    transform:translateY(-50%) scale(.96);
    clip-path:inset(0 100% 0 0);
    opacity:0;
    box-shadow:0 0 70px -20px rgba(147,229,255,.55);
    transition:clip-path .75s var(--ease),opacity .35s var(--ease),transform .75s var(--ease);
  }
  [dir="rtl"] .work-shot{clip-path:inset(0 0 0 100%)}

  .work-row:hover .work-shot{
    clip-path:inset(0 0 0 0);
    opacity:1;
    transform:translateY(-50%) scale(1);
  }

  /* The image counter-scales as the mask opens, so the picture settles rather
     than simply appearing. */
  .work-shot img{
    transform:scale(1.28);
    transition:transform 1.15s var(--ease),filter .8s var(--ease);
    filter:grayscale(1) brightness(.5);
  }
  .work-row:hover .work-shot img{transform:scale(1);filter:grayscale(0) brightness(1)}

  /* A hairline frame, lit. No colour wash over the photograph. */
  .work-shot::after{
    content:'';position:absolute;inset:0;pointer-events:none;
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.10);
    transition:box-shadow .6s var(--ease);
  }
  .work-row:hover .work-shot::after{box-shadow:inset 0 0 0 1px rgba(147,229,255,.5)}

  /* With a panel in the row, the long meta line would collide with it. */
  .work-row:hover .work-meta{opacity:.55;transition:opacity .4s var(--ease)}
}

/* ==========================================================================
   STAGE MODE
   Everything under html.stage-on: the lit room behind the page. The class is
   added by stage.js only once a WebGL2 context, a real GPU and no
   reduced-motion preference all hold, so none of this touches a page showing
   the flat layout — and the layout is the same either way, because the
   renderer only ever lights the page, never replaces any part of it.
   It DOES run on phones; the renderer drops its budget rather than bowing out.
   ========================================================================== */

/* Below the page AND below the page's own glows, which are z-index:-1
   pseudo-elements: the canvas is the last child of <body>, so at the same
   depth it would paint over every one of them. */
.stage-gl{
  position:fixed;inset:0;width:100%;height:100%;
  z-index:-2;display:block;pointer-events:none;
}

/* The renderer paints its own dust, vignette and grain in the composite pass,
   so the CSS light field and grain layers are superseded. */
.stage-on .ambience,
.stage-on .grain{display:none}

/* Smooth scroll stays off in stage mode (gated in app.js): a transformed
   wrapper would become the containing block for the fixed rows below, and the
   pinning would land in the wrong place. The camera easing does that job. */

/* The renderer is background only. The page keeps its own layout, its own
   rows and its own hover states — lighting them is the whole of the stage's
   job. What is left here is legibility: type over a lit field needs a little
   ground under it. */
.stage-on .hero-lede,
.stage-on .hero-note,
.stage-on .work-meta,
.stage-on .service-desc{text-shadow:0 2px 22px #000, 0 0 8px rgba(0,0,0,.85)}

/* The room itself answers the pointer now, so the ring can be quieter. */
.stage-on .cursor-ring{border-color:rgba(147,229,255,.45)}
.stage-on body.cursor-hot .cursor-ring{border-color:rgba(147,229,255,.85)}

/* ---------------------------------------------------------- page swap */
/* With the stage up there is no page load between pages: the content is
   replaced in place while the renderer holds the room. The content fades, the
   light dips with it (the renderer's own fade), and both come back up. */
#main{transition:opacity .38s var(--ease)}
body.is-swapping #main{opacity:0;transition:opacity .24s var(--ease)}
body.is-swapping{cursor:progress}
@media (prefers-reduced-motion:reduce){
  #main,body.is-swapping #main{transition:none}
}



/* ==========================================================================
   LIGHT & MOTION
   The layer that makes the page feel lit rather than printed. Three ideas,
   applied everywhere:

     1. THE POINTER CARRIES A LIGHT. app.js writes the cursor's position
        inside the hovered element as --mx/--my, in percent, so a row, a card
        or a button can put a pool of light exactly under the hand instead of
        switching a flat state on. That is what makes a hover read as a lamp.
     2. NOTHING IS EVER STILL. The hero glow drifts and leans toward the
        cursor, the headline carries a slow sheen, the live dot has a halo. A
        visitor who has not scrolled yet still sees a page that is alive —
        which is the whole job of the first screen.
     3. SCROLL IS TRAVEL. --scroll-progress drives the rail at the top of the
        window, and the renderer takes the same number as depth, so moving
        down the document changes the colour of the room behind it.

   All of it is decoration. Every rule here sits inside a hover, an animation
   or a custom property with a safe fallback, and the whole section is turned
   off under prefers-reduced-motion at the end.
   ========================================================================== */

:root{--ptr-x:50%;--ptr-y:40%}

/* ------------------------------------------------- the pointer's light */

.work-row,.service-row,.post-row,.work-card,.btn,.stat,.chip{--mx:50%;--my:50%}

/* Cyan at the centre with a trace of magenta at the edge: the same two lights
   the renderer is burning behind the page, so the DOM and the canvas agree.
   Behind the row's own text (z-index:-1), so nothing is ever washed over. */
.work-row::before,.service-row::before,.post-row::before{
  content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(24rem 15rem at var(--mx) var(--my),
             rgba(147,229,255,.15), rgba(214,0,255,.06) 45%, transparent 72%);
  opacity:0;transition:opacity .45s var(--ease);
}
@media (min-width:900px){
  .work-row:hover::before,.service-row:hover::before,.post-row:hover::before{opacity:1}
  /* The title picks up the light it is standing in. */
  .work-row:hover .work-title{text-shadow:0 0 38px rgba(147,229,255,.45)}
  .service-row:hover .service-name{text-shadow:0 0 30px rgba(147,229,255,.35)}
  /* The index number becomes the brightest thing in the row. */
  .work-row:hover .work-num,
  .service-row:hover .service-num{text-shadow:0 0 18px rgba(147,229,255,.9)}
}

/* Touch has no hover to give, so the light comes up on press instead. */
.work-row:active::before,.service-row:active::before,.post-row:active::before{opacity:1}

/* ------------------------------------------------------------- buttons */
/* The fill still wipes across on hover; the glow is what says the button is
   live before it is even touched. */
.btn{transition:color .4s var(--ease),border-color .4s var(--ease),
                box-shadow .45s var(--ease),transform .12s var(--ease-io)}
.btn::after{
  content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(7rem 7rem at var(--mx) var(--my),
             rgba(147,229,255,.55), transparent 70%);
  opacity:0;transition:opacity .35s var(--ease);
}
.btn:hover::after{opacity:1}
.btn:hover{
  border-color:var(--glow);
  box-shadow:0 0 42px -10px rgba(147,229,255,.65), 0 0 0 1px rgba(147,229,255,.25);
}
.btn-solid:hover{box-shadow:0 0 56px -8px rgba(147,229,255,.75)}
.btn:active{transform:translateY(1px)}

/* A text link lights its own filament rather than gaining an underline. */
.link:hover{color:var(--ink-strong);text-shadow:0 0 20px rgba(147,229,255,.55)}
.link:hover svg{color:var(--glow)}

/* ---------------------------------------------------------------- hero */
/* The first thing anyone sees, so it carries its own light and never sits
   still: three coloured pools drifting behind the headline and leaning toward
   the cursor. Behind the type, never over it. */
.hero::before{
  content:'';position:absolute;z-index:-1;pointer-events:none;
  inset-block-start:-8vh;inset-inline-start:-12%;
  width:min(74rem,124%);height:min(74rem,110vh);
  background:
    radial-gradient(34% 34% at 32% 42%, rgba(147,229,255,.26), transparent 70%),
    radial-gradient(30% 30% at 68% 56%, rgba(214,0,255,.18), transparent 72%),
    radial-gradient(24% 24% at 54% 24%, rgba(11,237,144,.11), transparent 70%);
  transform:translate3d(calc((var(--ptr-x) - 50%) * .14), calc((var(--ptr-y) - 50%) * .10), 0);
  transition:transform 1.1s var(--ease);
  animation:hero-drift 22s var(--ease-io) infinite alternate;
}
@keyframes hero-drift{
  0%  {opacity:.75;filter:blur(26px) hue-rotate(0deg)}
  50% {opacity:1;  filter:blur(34px) hue-rotate(-18deg)}
  100%{opacity:.85;filter:blur(24px) hue-rotate(14deg)}
}

/* A slow sheen travelling across the headline. Mostly white: the colour is a
   highlight moving through the type, never a gradient the type is painted in.

   It is carried by the WORDS, not by the h1. app.js splits the headline into
   .split-word masks with a .split-inner inside each, and a transformed inner
   is painted on its own — an h1 that clipped its background to text would lose
   every word that had been split out of it. Each word therefore holds its own
   sheen, offset by its index (--w, set by app.js), so the highlight reads as
   one wave crossing the line rather than as a flash per word.

   With JavaScript off there are no words to carry it and the headline is
   simply white, which is why the rule is scoped to .js. */
.js .hero h1 .split-inner{
  background-image:linear-gradient(100deg,
    #ffffff 0%, #ffffff 32%, #bfeaff 41%, #e6b4ff 49%, #ffffff 60%, #ffffff 100%);
  background-size:900% 100%;
  -webkit-background-clip:text;background-clip:text;
  color:transparent;
  animation:sheen 9s linear infinite;
  animation-delay:calc(var(--w, 0) * -0.45s);
}
@keyframes sheen{from{background-position:100% 0}to{background-position:-800% 0}}

/* The live dot already pulses; the halo makes it a lamp. */
.hero-note .dot{box-shadow:0 0 12px rgba(11,237,144,.9),0 0 26px rgba(11,237,144,.45)}

/* The scroll hint is the one instruction on the page, so it should glow. */
.scroll-hint i::after{box-shadow:0 0 10px rgba(147,229,255,.9);background:var(--glow)}

/* --------------------------------------------------------------- rails */
/* The rail is the scroll made visible, and it changes colour on the way down
   exactly as the field behind the page does. */
.scroll-progress{
  background:linear-gradient(90deg,var(--glow),var(--glow-hot));
  box-shadow:0 0 18px rgba(147,229,255,.7);
}

/* -------------------------------------------------------------- numbers */
.stat{position:relative}
.stat .n{transition:text-shadow .5s var(--ease),color .4s var(--ease)}
.stat:hover .n{text-shadow:0 0 44px rgba(147,229,255,.55)}
.stat::after{
  content:'';position:absolute;inset-block-start:-1px;inset-inline:0;height:1px;
  background:var(--glow);box-shadow:0 0 12px rgba(147,229,255,.8);
  transform:scaleX(0);transform-origin:calc(50% - 50% * var(--dir)) 50%;
  transition:transform .7s var(--ease);
}
.stat:hover::after{transform:scaleX(1)}

/* ---------------------------------------------------------- small parts */
.chip{transition:color .35s var(--ease),border-color .35s var(--ease),box-shadow .45s var(--ease)}
.chip:hover{color:var(--ink-strong);border-color:rgba(147,229,255,.55);box-shadow:0 0 24px -8px rgba(147,229,255,.7)}

.tech-list li{transition:color .4s var(--ease),text-shadow .5s var(--ease)}
.tech-list li:hover{color:var(--ink-strong);text-shadow:0 0 26px rgba(147,229,255,.6)}

.label .idx{transition:color .5s var(--ease)}
.section:hover .label .idx{color:var(--glow)}

/* A project plate lifts toward the cursor rather than only brightening. */
.work-card .media{overflow:hidden}
.work-card .media img{transition:filter .7s var(--ease),transform 1.1s var(--ease)}
.work-card:hover .media img{transform:scale(1.045)}

/* ------------------------------------------------------ reduced motion */
/* All of the above is decoration, so it all comes off in one place. */
@media (prefers-reduced-motion:reduce){
  .hero::before{animation:none;transition:none;transform:none}
  .js .hero h1 .split-inner{
    animation:none;background-image:none;
    color:var(--ink-strong);-webkit-text-fill-color:currentColor;
  }
  .work-row::before,.service-row::before,.post-row::before,.btn::after{transition:none}
  .btn:active{transform:none}
}

/* ---------------------------------------------------------------- faq */
.faq{border-top:1px solid var(--line)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{
  list-style:none;cursor:pointer;display:flex;align-items:baseline;justify-content:space-between;
  gap:1.5rem;padding:1.35rem 0;
}
.faq summary::-webkit-details-marker{display:none}
.faq summary h3{margin:0;font-size:clamp(1.02rem,.95rem + .45vw,1.3rem);font-weight:500;color:var(--ink-strong);line-height:1.35}
.faq summary::after{
  content:"+";flex:none;font-family:var(--font-display);font-size:1.25rem;line-height:1;color:var(--ink-muted);
  transition:transform .35s var(--ease);
}
.faq details[open] summary::after{transform:rotate(45deg)}
.faq summary:focus-visible{outline:2px solid var(--ink-strong);outline-offset:4px}
.faq-a{margin:0;padding:0 0 1.5rem;max-width:44rem;color:var(--ink-muted);line-height:1.65}
@media (prefers-reduced-motion:reduce){.faq summary::after{transition:none}}

/* ------------------------------------------- landing: points and steps */
.page-actions{display:flex;flex-wrap:wrap;gap:1rem}
.point-list{display:grid;gap:0;margin:0;padding:0;list-style:none;border-top:1px solid var(--line)}
.point-list > *{display:grid;gap:.5rem;padding:1.4rem 0;border-bottom:1px solid var(--line)}
@media (min-width:760px){.point-list > *{grid-template-columns:minmax(0,18rem) 1fr;gap:2rem}}
.point-list dt,.point-list strong{margin:0;font-weight:500;color:var(--ink-strong);font-size:clamp(1rem,.95rem + .35vw,1.2rem);line-height:1.35}
.point-list dd,.point-list span{margin:0;color:var(--ink-muted);line-height:1.65;max-width:44rem}
.point-list.numbered{counter-reset:pt}
.point-list.numbered > li{counter-increment:pt}
.point-list.numbered strong::before{
  content:counter(pt,decimal-leading-zero);display:block;margin-bottom:.35rem;
  font-family:var(--font-display);font-size:.78rem;font-weight:400;color:var(--ink-faint);
}
