/* ==========================================================================
   AIZONIQ — platform theme
   --------------------------------------------------------------------------
   The site's visual language, ported from docs/design/landing-page-v5.html and
   applied to every page rather than to the homepage alone.

   It loads last, after style.css, because style.css is the older sheet: it
   still owns layout — the nav geometry, the grids, the footer columns — while
   everything that reads as *design* is decided here. Where the two disagree
   this file wins on purpose, and the rules that exist only to win are grouped
   under "Reclaimed from style.css" so they are easy to delete once the older
   sheet is broken up.

   Three parts:
     TOKENS + BASE     the palette, the type, one black ground
     THE BACKDROP      starfield, glows, orb field, grain — injected into every
                       page by tools/normalize-pages.js, driven by backdrop.js
     COMPONENTS        what the existing markup has to look like on top of it
   ========================================================================== */

/* ==========================================================================
   TOKENS
   ========================================================================== */
:root {
  --bg: #000000;

  /* The hero's palette, copied verbatim from Landing.html. Two glows, each a
     four-stop radial from a core out to a transparent fade. Their sides are
     swapped from the source — see .bg_glow-wrap below. Nothing here is shared
     with the field under the hero; these eight values only draw the first
     screen. */
  --violet-core:  #6031AF;
  --violet-mid:   #462289;
  --violet-deep:  #29145F;
  --violet-fade:  rgba(18, 8, 42, 0.42);
  --magenta-core: #A52456;
  --magenta-mid:  #831B46;
  --magenta-deep: #561232;
  --magenta-fade: rgba(38, 6, 22, 0.38);

  --halo-fine:  drop-shadow(0 0 1px rgba(255,255,255,0.25));
  --halo-mid:   drop-shadow(0 0 2px rgba(255,255,255,0.30));
  --halo-heavy: drop-shadow(0 0 2px rgba(255,255,255,0.35));

  --color-bg: #04030e;
  --gradient-degree: -10deg;
  --gradient-colors: #06f 0%, #00d9ff 33%, #7300ff 67%, #ff00d0 100%;
  --gradient-primary: var(--gradient-degree), var(--gradient-colors);

  --c-blue:    #2F00FF;
  --c-indigo:  #1126EA;
  --c-steel:   #2234DA;
  --c-violet:  #B81DF5;
  --c-magenta: #DE09FF;
  --c-cyan:    #00d9ff;

  --panel:    rgba(10, 3, 29, .74);
  --white-10: rgba(255,255,255,.10);
  --white-20: rgba(255,255,255,.20);
  --white-40: rgba(255,255,255,.40);
  --white-50: rgba(255,255,255,.50);
  --white-70: rgba(255,255,255,.70);

  --font-sans: "Satoshi", "Tajawal", "Tajawal Fallback", sans-serif;
  --ease: cubic-bezier(.16, 1, .3, 1);

  /* The height of the bar, the size of the mark inside it, and the height the
     hero subtracts from the viewport. All three read this. */
  --nav-h: clamp(88px, 8vw, 124px);
  /* One size for every item in the nav row — links and the language control
     alike, so nothing in the row can drift out of step with the rest. */
  --nav-link-size: clamp(1.0625rem, 1.4vw, 1.375rem);

  /* Reclaimed from style.css and design-system.css. Those sheets colour text,
     borders and surfaces through these names in a few hundred places; pointing
     them at the palette above converts all of it at once, which is the only
     way "every text on the platform is white" holds without auditing 4,000
     lines of selectors. */
  --dark-bg: #04030e;
  --darker-bg: #04030e;
  --card-bg: var(--panel);
  --border: var(--white-10);
  --text: #ffffff;
  --text-primary: #ffffff;
  --text-secondary: var(--white-70);
  --text-muted: var(--white-50);
  --primary: var(--c-violet);
  --primary-color: var(--c-violet);
  --primary-light: var(--c-cyan);
  --primary-dark: var(--c-blue);
  --secondary-color: var(--c-magenta);
  --accent-color: var(--c-magenta);
  --font-primary: var(--font-sans);
}

@property --o { syntax: '<number>'; inherits: false; initial-value: 0.3; }

/* ==========================================================================
   BASE
   ========================================================================== */
html {
  background: var(--color-bg);
  scroll-behavior: smooth;
}

body {
  position: relative;   /* the backdrop is absolutely positioned against this */
  background: var(--color-bg);
  color: #fff;
  font-family: var(--font-sans);
  overflow-x: hidden;
}

/* A stand-in for Tajawal with Tajawal's line box, so the swap when the real
   face lands does not move anything. Without it the Arabic pages reflow by up
   to 656px on a phone and the h1 jumps under the reader mid-read; with it the
   same pages settle at 0-104px and the h1 does not move at all. size-adjust is
   measured against this site's own strings, not a generic ratio. */
@font-face {
  font-family: "Tajawal Fallback";
  src: local("Arial"), local("Liberation Sans"), local("Helvetica");
  size-adjust: 123.4%;
}

html[dir="rtl"] body,
html[dir="rtl"] h1, html[dir="rtl"] h2, html[dir="rtl"] h3, html[dir="rtl"] h4,
html[dir="rtl"] .hero-title, html[dir="rtl"] .section-title, html[dir="rtl"] .cta-title {
  font-family: "Satoshi", "Tajawal", "Tajawal Fallback", sans-serif;
}

/* Sixteen pages have no translated body text, so normalize-pages.js marks their
   <main> lang="ar" dir="rtl" and it stays Arabic whichever way the toggle is
   set. Every RTL rule above is keyed on html[dir], which is now the wrong
   element for those pages — the face has to follow the marked region instead. */
html[dir="ltr"] main[dir="rtl"],
html[dir="ltr"] main[dir="rtl"] h1, html[dir="ltr"] main[dir="rtl"] h2,
html[dir="ltr"] main[dir="rtl"] h3, html[dir="ltr"] main[dir="rtl"] h4 {
  font-family: "Satoshi", "Tajawal", "Tajawal Fallback", sans-serif;
}

/* ---- LTR Global Text Alignment & Subpages Formatting ---- */
html[dir="ltr"] body {
  direction: ltr;
  text-align: left;
}

html[dir="ltr"] main:not([dir="rtl"]),
html[dir="ltr"] main:not([dir="rtl"]) section,
html[dir="ltr"] main:not([dir="rtl"]) article,
html[dir="ltr"] main:not([dir="rtl"]) .panel,
html[dir="ltr"] main:not([dir="rtl"]) .block,
html[dir="ltr"] main:not([dir="rtl"]) .service-page-hero,
html[dir="ltr"] main:not([dir="rtl"]) .service-detail {
  direction: ltr;
  text-align: left;
}

html[dir="ltr"] main:not([dir="rtl"]) h1,
html[dir="ltr"] main:not([dir="rtl"]) h2,
html[dir="ltr"] main:not([dir="rtl"]) h3,
html[dir="ltr"] main:not([dir="rtl"]) h4,
html[dir="ltr"] main:not([dir="rtl"]) h5,
html[dir="ltr"] main:not([dir="rtl"]) h6,
html[dir="ltr"] main:not([dir="rtl"]) p,
html[dir="ltr"] main:not([dir="rtl"]) li {
  direction: ltr;
  text-align: left;
}

/* The hero is the one block above that is centred in Arabic, so left-aligning
   it in English is not "correcting" anything — it is inventing a second layout
   for the same screen. The rule above names the elements directly, and a direct
   rule beats the `text-align: center !important` those two inherit from
   `.hero .container` below, so the exemption has to name them directly too.
   Without it the English strapline sat 103px right of centre at 1440 while the
   Arabic one sat exactly on it. */
html[dir="ltr"] main:not([dir="rtl"]) .hero-title,
html[dir="ltr"] main:not([dir="rtl"]) .hero-description {
  text-align: center;
}

html[dir="ltr"] main:not([dir="rtl"]) ul,
html[dir="ltr"] main:not([dir="rtl"]) ol {
  direction: ltr;
  text-align: left;
  padding-inline-start: 1.5rem;
  padding-inline-end: 0;
}

/* Centered section elements in LTR */
html[dir="ltr"] .text-center,
html[dir="ltr"] .text-center *,
html[dir="ltr"] .section-header,
html[dir="ltr"] .section-header *,
html[dir="ltr"] .hero .container,
html[dir="ltr"] .cap-rail-head,
html[dir="ltr"] .showcase-head,
html[dir="ltr"] .ksa-focus h2,
html[dir="ltr"] .ksa-focus p.lede {
  text-align: center !important;
}

html[dir="ltr"] .hero-title {
  max-width: 18em;
}

/* No `max-width` on the English strapline. It carries `white-space: nowrap` and
   a vw-driven font size (see the long note on .hero-description), so a cap does
   not make the line wrap inside it — the line keeps its width and hangs out of
   the box, and `text-align: center` centres the box rather than the sentence.
   A 42rem cap here left the English line 103px right of centre at 1440 while
   the Arabic one sat exactly on it. The two sentences measure within 18px of
   each other, so the Arabic geometry fits English unchanged. */

html[dir="ltr"] .block > .wrap > h2,
html[dir="ltr"] .block > h2 {
  max-width: 32ch;
  text-wrap: balance;
}

::selection { background-color: #ff00d033; }

/* The scrollbar. It was the wrong way round — a light grey track with a
   near-black thumb, which on a black page reads as a bright stripe down the
   edge and is the first thing your eye lands on.

   Inverted: the track disappears into the page and the thumb is a soft rounded
   pill. The transparent border plus background-clip is what insets the pill
   from the edges of the track; without it the thumb is a full-width block and
   the radius has nothing to show. style.css and design-system.css each carried
   their own scrollbar block as well — all three have been collapsed into this
   one, so `height` is set here too for horizontal bars. */
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: #08061a; }
::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.26);
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover {
  background: rgba(255,255,255,.42);
  background-clip: padding-box;
}
::-webkit-scrollbar-corner { background: #08061a; }
/* Firefox has no pseudo-elements for this; it takes two values and no radius. */
html { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.30) #08061a; }

a { color: inherit; text-decoration: none; }

/* The page sits on the backdrop, never under it. The skip link is deliberately
   absent: it is positioned off-screen until focus, and giving it
   `position: relative` here would drop it back into the flow and print it at
   the top of every page. */
main, .footer, .chatbot-container, #n8n-chat {
  position: relative;
  z-index: 1;
}
/* Sticky, not fixed. It has to keep occupying its own height at the top of the
   flow — every page below the homepage begins with a section that expects the
   bar to be there — and it has to stay put once the page moves, which is the
   only thing .scrolled is for. style.css had it `fixed` inside a 1400px pill;
   both are gone. */
.navbar { position: sticky; top: 0; z-index: 1000; }
/* The whole rule, not just the z-index. checkout/ and checkout/success/
   load theme.css and nothing else, so a skip link that is only styled in
   style.css prints there as a green line of body text above the page. */
.skip-link {
  position: absolute;
  inset-inline-start: 1rem;
  top: -100%;
  z-index: 100000;
  padding: .75rem 1.25rem;
  background: #10b981;
  color: #fff;
  font-weight: 700;
  border-radius: 0 0 .5rem .5rem;
  text-decoration: none;
  transition: top .15s ease;
}
.skip-link:focus { top: 0; }

/* ==========================================================================
   THE BACKDROP
   --------------------------------------------------------------------------
   One element, injected directly after <body> on every page. It reproduces the
   two-part composition of the reference: a hero-height band of violet and
   magenta with a starfield over it, and below that an orb field that carries
   the colour down the rest of the document.

   The backdrop is absolute, not fixed, so the top band scrolls away exactly as
   it does in the reference — a fixed backdrop would pin the glow to the
   viewport and destroy the hand-off.
   ========================================================================== */
.site-backdrop {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  contain: paint;
}

/* ---- the hero band ------------------------------------------------------
   Masked to transparent over its bottom 45%. The field's blend layer reaches
   up underneath it, so the two overlap for ~45vh instead of meeting at an
   edge and there is no boundary to see. */
.backdrop-top {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom,
    #000 0%, #000 54%, rgba(0,0,0,.92) 66%, rgba(0,0,0,.62) 78%,
    rgba(0,0,0,.28) 89%, transparent 100%);
  mask-image: linear-gradient(to bottom,
    #000 0%, #000 54%, rgba(0,0,0,.92) 66%, rgba(0,0,0,.62) 78%,
    rgba(0,0,0,.28) 89%, transparent 100%);
}
.backdrop-top .base { position: absolute; inset: 0; background: var(--bg); }

/* ---- the colour field ---------------------------------------------------
   Two glows, copied from Landing.html at its exact geometry, blur radius,
   colours and stop positions. Each is most of a circle parked above the fold —
   `top: -58vh` on a 112vh box leaves only its lower arc in frame — so what the
   first screen actually shows is the bottom edge of a violet sun on one side
   and a magenta one on the other, screen-blended over black.

   This replaced three hard-edged discs under a single 250px blur. That build
   put a gold disc in the middle of the frame, which is where the warm brown
   band across the hero was coming from; there is no warm colour in this
   palette at all. */
.bg_glow-wrap {
  position: absolute; border-radius: 50%;
  filter: blur(70px); mix-blend-mode: screen;
}
/* Sides swapped, by request: magenta now opens the frame on the left and
   violet closes it on the right, the reverse of Landing.html.

   A true mirror, not a recolour. Each light keeps the size it was drawn at —
   the violet is the bigger of the two at 100vw x 112vh, the magenta the
   smaller at 95vw x 104vh — and only its side changes, so the composition is
   the same one flipped rather than a new one with the old colours in it.
   Mirroring an offset means moving it to the opposite edge at the same
   magnitude: left:-26vw becomes right:-26vw. */
.bg_glow-wrap.is-left {
  width: 95vw; height: 104vh; left: -16vw; top: -56vh;
  background: radial-gradient(circle at 50% 50%,
    var(--magenta-core) 0%, var(--magenta-mid) 28%, var(--magenta-deep) 50%,
    var(--magenta-fade) 68%, rgba(0,0,0,0) 82%);
}
.bg_glow-wrap.is-right {
  width: 100vw; height: 112vh; right: -26vw; top: -58vh;
  background: radial-gradient(circle at 50% 50%,
    var(--violet-core) 0%, var(--violet-mid) 30%, var(--violet-deep) 50%,
    var(--violet-fade) 68%, rgba(0,0,0,0) 82%);
}

/* One ramp, top to bottom, ending on solid page colour at 72%. Also verbatim.
   It reads as heavy, but .backdrop-top is masked to transparent over its own
   bottom 45% and the blend below reaches up underneath — so the solid end is
   masked away before it can become an edge, exactly as it is in the source. */
.bg_scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to bottom,
    rgba(4,3,14,0) 0%, rgba(4,3,14,.12) 16%, rgba(4,3,14,.52) 34%,
    rgba(4,3,14,.86) 50%, rgba(4,3,14,.98) 62%, #04030e 72%);
}

/* The frame the stars live in. Its own mask keeps them from surviving into
   the scrim, where they would read as dust rather than sky.

   `direction: ltr` is load-bearing, not cosmetic. Every star is a box-shadow
   offset from a 2px block, and a 2px block in an RTL container is laid out
   against the *right* edge — which puts all 300 shadows, whose offsets run
   from +34px to +2560px, off the right of the viewport. On the Arabic pages
   the entire sky was invisible until this line. */
.star-frame {
  position: absolute; inset: 0; width: 100%; height: 100vh; background: transparent;
  direction: ltr;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 52%, rgba(0,0,0,.35) 78%, transparent 96%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 52%, rgba(0,0,0,.35) 78%, transparent 96%);
}

.stars {
  width: 2px; height: 2px; border-radius: 50%; background: transparent;
  filter: var(--halo-fine);
  box-shadow: 718px 1689px #FFF , 1538px 708px #FFF , 1372px 585px #FFF , 1392px 488px #FFF , 1836px 2303px #FFF , 1309px 816px #FFF , 433px 1332px #FFF , 1979px 501px #FFF , 2078px 1983px #FFF , 2461px 557px #FFF , 1966px 316px #FFF , 1123px 1402px #FFF , 1625px 2076px #FFF , 1049px 372px #FFF , 319px 980px #FFF , 126px 1121px #FFF;
  animation: animStar 100s linear infinite;
}
.stars:after {
  content: " "; top: -600px; width: 2px; height: 2px; border-radius: 50%;
  position: absolute; background: transparent;
  box-shadow: 672px 2257px #FFF , 821px 854px #FFF , 2334px 1057px #FFF , 1499px 1912px #FFF , 1545px 22px #FFF , 941px 1452px #FFF , 1666px 168px #FFF , 577px 503px #FFF , 1167px 1107px #FFF , 2218px 2104px #FFF , 2372px 1772px #FFF , 1593px 580px #FFF , 894px 2361px #FFF , 1552px 1134px #FFF , 453px 1525px #FFF , 2256px 1631px #FFF , 1405px 394px #FFF , 201px 2149px #FFF , 34px 2213px #FFF , 2388px 246px #FFF , 392px 667px #FFF , 2405px 2410px #FFF;
}

.stars1 {
  width: 2px; height: 2px; border-radius: 50%; background: transparent;
  filter: var(--halo-mid);
  box-shadow: 735px 2395px #FFF , 742px 1559px #FFF , 1637px 383px #FFF , 483px 941px #FFF , 2028px 761px #FFF , 692px 1202px #FFF , 1620px 252px #FFF , 679px 135px #FFF , 1927px 2544px #FFF , 1717px 1031px #FFF , 2121px 517px #FFF , 2491px 1514px #FFF , 923px 1883px #FFF , 1947px 1462px #FFF , 2023px 455px #FFF , 1055px 852px #FFF , 583px 631px #FFF , 54px 859px #FFF , 1028px 256px #FFF , 450px 2504px #FFF , 1607px 2348px #FFF , 2243px 1494px #FFF;
  animation: animStar 125s linear infinite;
}
.stars1:after {
  content: " "; top: -600px; width: 2px; height: 2px; border-radius: 50%;
  position: absolute; background: transparent;
  box-shadow: 1717px 2554px #FFF , 891px 1897px #FFF , 409px 2476px #FFF , 84px 1996px #FFF , 181px 38px #FFF , 2122px 1291px #FFF , 2254px 375px #FFF , 948px 2128px #FFF , 1947px 2302px #FFF , 1237px 2532px #FFF , 2338px 2002px #FFF , 251px 1525px #FFF , 2510px 1440px #FFF , 204px 836px #FFF , 2044px 437px #FFF , 394px 548px #FFF , 696px 1741px #FFF , 2291px 499px #FFF;
}

.stars2 {
  width: 3px; height: 3px; border-radius: 50%; background: transparent;
  filter: var(--halo-heavy);
  box-shadow: 10px 1086px #FFF , 1290px 2153px #FFF , 2258px 231px #FFF , 1084px 758px #FFF , 621px 2482px #FFF , 836px 799px #FFF , 949px 292px #FFF , 16px 2265px #FFF , 1818px 1945px #FFF , 2399px 1553px #FFF , 157px 551px #FFF , 2397px 438px #FFF , 1571px 2128px #FFF , 1941px 928px #FFF , 741px 1097px #FFF , 1415px 1296px #FFF , 945px 1132px #FFF , 909px 2080px #FFF , 2198px 1039px #FFF , 1385px 912px #FFF;
  animation: animStar 175s linear infinite;
}
.stars2:after {
  content: " "; top: -600px; width: 3px; height: 3px; border-radius: 50%;
  position: absolute; background: transparent;
  box-shadow: 793px 1150px #FFF , 1434px 1247px #FFF , 1355px 1046px #FFF , 2255px 1676px #FFF , 1235px 945px #FFF , 287px 1787px #FFF , 473px 442px #FFF , 1864px 177px #FFF , 1818px 505px #FFF , 1420px 167px #FFF , 1955px 2053px #FFF , 1822px 145px #FFF , 931px 629px #FFF , 386px 668px #FFF , 1397px 2398px #FFF;
}

.stars3 {
  width: 2px; height: 2px; border-radius: 50%; background: transparent;
  filter: var(--halo-fine);
  box-shadow: 157px 356px #FFF , 194px 2284px #FFF , 245px 897px #FFF , 257px 2367px #FFF , 300px 2198px #FFF , 334px 685px #FFF , 387px 1109px #FFF , 487px 2100px #FFF , 692px 2506px #FFF , 918px 2391px #FFF , 1018px 1633px #FFF , 1181px 533px #FFF , 1289px 1397px #FFF , 1716px 679px #FFF , 1780px 1716px #FFF , 1842px 1183px #FFF , 1911px 2402px #FFF , 2342px 1267px #FFF , 2539px 847px #FFF;
  animation: animStar 140s linear infinite;
}
.stars3:after {
  content: " "; top: -600px; width: 2px; height: 2px; border-radius: 50%;
  position: absolute; background: transparent;
  box-shadow: 419px 1493px #FFF , 668px 454px #FFF , 725px 623px #FFF , 1080px 1158px #FFF , 1396px 2464px #FFF , 1426px 2470px #FFF , 1516px 2501px #FFF , 1562px 949px #FFF , 1638px 1618px #FFF , 1720px 2193px #FFF , 1874px 2294px #FFF , 1964px 665px #FFF , 1971px 1985px #FFF , 2118px 98px #FFF , 2517px 108px #FFF , 2519px 1545px #FFF , 2533px 225px #FFF;
}

.stars4 {
  width: 2px; height: 2px; border-radius: 50%; background: transparent;
  filter: var(--halo-mid);
  box-shadow: 62px 424px 1px #FFF , 116px 623px 1px #FFF , 539px 2182px 1px #FFF , 989px 2406px 1px #FFF , 1339px 1066px 1px #FFF , 1387px 841px 1px #FFF , 1414px 1835px 1px #FFF , 1453px 1880px 1px #FFF , 1595px 820px 1px #FFF , 1726px 2058px 1px #FFF , 1962px 735px 1px #FFF , 1980px 2560px 1px #FFF , 2503px 11px 1px #FFF;
  animation: animStar 150s linear infinite;
}
.stars4:after {
  content: " "; top: -600px; width: 3px; height: 3px; border-radius: 50%;
  position: absolute; background: transparent;
  box-shadow: 583px 1943px 2px #FFF , 989px 1758px 2px #FFF , 1244px 505px 2px #FFF , 1856px 2085px 2px #FFF , 1980px 438px 2px #FFF , 2083px 1018px 2px #FFF , 2095px 80px 2px #FFF , 2298px 236px 2px #FFF , 2486px 2101px 2px #FFF , 2512px 2074px 2px #FFF;
}

.stars5 {
  width: 3px; height: 3px; border-radius: 50%; background: transparent;
  filter: var(--halo-heavy);
  box-shadow: 78px 1578px 3px #FFF , 433px 348px 3px #FFF , 589px 1040px 3px #FFF , 1361px 2123px 3px #FFF , 1635px 1999px 3px #FFF , 2115px 1658px 3px #FFF;
  animation: animStar 190s linear infinite;
}

.stars6 {
  width: 2px; height: 2px; border-radius: 50%; background: transparent;
  filter: var(--halo-fine);
  box-shadow: 62px 1423px 1px #FFF , 193px 2477px 1px #FFF , 206px 1037px 1px #FFF , 309px 812px 1px #FFF , 491px 1156px 1px #FFF , 522px 1249px 1px #FFF , 580px 298px 1px #FFF , 602px 2198px 1px #FFF , 643px 1271px 1px #FFF , 655px 2035px 1px #FFF , 684px 747px 1px #FFF , 694px 272px 1px #FFF , 700px 1430px 1px #FFF , 716px 1760px 1px #FFF , 827px 1401px 1px #FFF , 829px 2256px 1px #FFF , 858px 2382px 1px #FFF , 1010px 427px 1px #FFF , 1027px 2397px 1px #FFF , 1306px 1988px 1px #FFF , 1570px 519px 1px #FFF , 1627px 2026px 1px #FFF , 1633px 2326px 1px #FFF , 1728px 1057px 1px #FFF , 1803px 1672px 1px #FFF , 1957px 1265px 1px #FFF , 2014px 2077px 1px #FFF , 2210px 1332px 1px #FFF , 2283px 2554px 1px #FFF , 2298px 1955px 1px #FFF , 2435px 1339px 1px #FFF , 2450px 1259px 1px #FFF , 2467px 1749px 1px #FFF;
  animation: animStar 115s linear infinite;
}
.stars6:after {
  content: " "; top: -600px; width: 2px; height: 2px; border-radius: 50%;
  position: absolute; background: transparent;
  box-shadow: 81px 310px 1px #FFF , 249px 2085px 1px #FFF , 375px 2270px 1px #FFF , 409px 775px 1px #FFF , 632px 1450px 1px #FFF , 667px 1284px 1px #FFF , 684px 556px 1px #FFF , 718px 1579px 1px #FFF , 722px 329px 1px #FFF , 926px 2358px 1px #FFF , 927px 106px 1px #FFF , 980px 2453px 1px #FFF , 1132px 2418px 1px #FFF , 1140px 1556px 1px #FFF , 1183px 1224px 1px #FFF , 1198px 2157px 1px #FFF , 1268px 772px 1px #FFF , 1531px 2480px 1px #FFF , 1638px 1582px 1px #FFF , 1648px 292px 1px #FFF , 1932px 2123px 1px #FFF , 2072px 2549px 1px #FFF , 2080px 822px 1px #FFF , 2341px 1774px 1px #FFF , 2440px 1330px 1px #FFF , 2488px 2197px 1px #FFF , 2493px 1811px 1px #FFF , 2541px 2484px 1px #FFF;
}

.stars7 {
  width: 2px; height: 2px; border-radius: 50%; background: transparent;
  filter: var(--halo-mid);
  box-shadow: 50px 1445px 2px #FFF , 114px 1521px 2px #FFF , 136px 430px 2px #FFF , 717px 90px 2px #FFF , 847px 149px 2px #FFF , 875px 2185px 2px #FFF , 879px 2063px 2px #FFF , 987px 1741px 2px #FFF , 1091px 169px 2px #FFF , 1380px 2406px 2px #FFF , 1513px 2026px 2px #FFF , 1602px 864px 2px #FFF , 1640px 651px 2px #FFF , 1738px 324px 2px #FFF , 1807px 1240px 2px #FFF , 1847px 1488px 2px #FFF , 1981px 229px 2px #FFF , 2030px 785px 2px #FFF , 2146px 1521px 2px #FFF , 2188px 2476px 2px #FFF , 2276px 1175px 2px #FFF , 2308px 1838px 2px #FFF , 2350px 540px 2px #FFF , 2381px 111px 2px #FFF;
  animation: animStar 160s linear infinite;
}
.stars7:after {
  content: " "; top: -600px; width: 2px; height: 2px; border-radius: 50%;
  position: absolute; background: transparent;
  box-shadow: 77px 352px 2px #FFF , 85px 1867px 2px #FFF , 90px 956px 2px #FFF , 159px 330px 2px #FFF , 511px 836px 2px #FFF , 588px 1422px 2px #FFF , 625px 46px 2px #FFF , 768px 830px 2px #FFF , 838px 1772px 2px #FFF , 910px 2407px 2px #FFF , 1229px 886px 2px #FFF , 1243px 1307px 2px #FFF , 1265px 1022px 2px #FFF , 1368px 320px 2px #FFF , 1376px 2144px 2px #FFF , 1663px 504px 2px #FFF , 2042px 2032px 2px #FFF , 2469px 2096px 2px #FFF;
}

.stars8 {
  width: 4px; height: 4px; border-radius: 50%; background: transparent;
  filter: var(--halo-heavy);
  box-shadow: 117px 2371px 3px #FFF , 133px 895px 3px #FFF , 204px 1536px 3px #FFF , 439px 1943px 3px #FFF , 615px 1281px 3px #FFF , 824px 670px 3px #FFF , 1022px 1456px 3px #FFF , 1267px 1603px 3px #FFF , 1383px 57px 3px #FFF , 1400px 1523px 3px #FFF , 1510px 601px 3px #FFF , 1514px 1970px 3px #FFF , 2297px 1559px 3px #FFF;
  animation: animStar 210s linear infinite;
}

.shooting-stars {
  width: 5px; height: 85px;
  border-top-left-radius: 50%; border-top-right-radius: 50%;
  position: absolute; bottom: 0; right: 0;
  background: linear-gradient(to top, rgba(255, 255, 255, 0), white);
  filter: drop-shadow(0 0 2px rgba(255,255,255,0.35));
  animation: animShootingStar 10s linear infinite;
}

/* The drift stops on anything phone- or tablet-sized, and it is the single
   biggest idle cost the site has there. Each sky layer is a 2px element whose
   box-shadows spread across 2560 x 2560 CSS pixels, so that is the layer's
   paint area; at the DPR 3 of a mid-range phone it is a 59-megapixel texture,
   past what the compositor will hold. Chrome therefore cannot run the
   transform on the compositor and repaints all of them on the main thread,
   every frame, forever — measured at 0.330 s of main thread per second of wall
   clock on a throttled 390px phone, against 0.189 with the drift stopped. That
   is 43% of the idle budget of every page, spent on 12 to 25 pixels a second of
   movement across a 390px screen.

   Desktop keeps it: at DPR 1 the same layers fit and the animation composites,
   where it measures 0.02 s/s — free, and the drift is what stops a static
   starfield reading as wallpaper. */
@media (max-width: 900px) {
  .stars, .stars1, .stars2, .stars3, .stars4,
  .stars5, .stars6, .stars7, .stars8 { animation: none; }
}


@keyframes animStar {
  from { transform: translateY(0px); }
  to   { transform: translateY(-2560px) translateX(-2560px); }
}
@keyframes animShootingStar {
  /* transform and opacity only. Animating height relaid out the document on
     every frame of a loop that never ends; the streak keeps its authored 85px. */
  from { transform: translateY(0px) translateX(0px) rotate(-45deg); opacity: 1; }
  to   { transform: translateY(-2560px) translateX(-2560px) rotate(-45deg); opacity: 1; }
}
.sky { will-change: transform; transition: transform 0.1s ease-out; }

/* ---- the field ----------------------------------------------------------
   Starts 95vh above where the hero band ends, i.e. deep inside it. */
.backdrop-field {
  position: absolute;
  top: 5vh; left: 0; right: 0; bottom: 0;
  z-index: 0; pointer-events: none; overflow: hidden;
}

/* THE BLEND — the single most important element on the page. 200vh tall,
   spanning the junction. Its own mask makes it ramp in from nothing, so the
   hero's colours hand off to the field's with no edge.

   The alphas are deliberately low. At full strength this element painted the
   first two content blocks a saturated blue — measurably brighter than the
   sections further down (a mean blue of 121 against 21 and 49), which read as
   two different pages stitched together rather than one continuous ground.
   They are now scaled to about a third, which lands the blocks on the same
   near-black as §05 and §06 while leaving the ramp itself untouched: the hero
   still hands off over ~400px rather than stopping at a line. */
.blend {
  position: absolute; top: 0; left: 0; right: 0; height: 200vh;
  mix-blend-mode: screen;
  background:
    radial-gradient(72vw 52vh at 20% 40%, rgba(96, 49, 175, .20), transparent 72%),
    radial-gradient(64vw 46vh at 80% 36%, rgba(165, 36, 86, .17), transparent 72%),
    radial-gradient(84vw 58vh at 34% 66%, rgba(47, 0, 255, .14), transparent 74%),
    radial-gradient(62vw 48vh at 86% 74%, rgba(184, 29, 245, .12), transparent 74%),
    radial-gradient(78vw 56vh at 28% 92%, rgba(17, 38, 234, .12), transparent 76%);
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0%, rgba(0,0,0,.08) 26%, rgba(0,0,0,.34) 42%,
    rgba(0,0,0,.70) 55%, #000 66%, #000 76%,
    rgba(0,0,0,.72) 87%, rgba(0,0,0,.30) 95%, transparent 100%);
  mask-image: linear-gradient(to bottom,
    transparent 0%, rgba(0,0,0,.08) 26%, rgba(0,0,0,.34) 42%,
    rgba(0,0,0,.70) 55%, #000 66%, #000 76%,
    rgba(0,0,0,.72) 87%, rgba(0,0,0,.30) 95%, transparent 100%);
}

.orb-wrap { position: absolute; inset: 0; will-change: transform; }
.orb {
  position: absolute;
  border-radius: 3.40282e38px;
  filter: blur(110px);
  mix-blend-mode: screen;
  opacity: var(--o);
  animation: drift var(--t, 32s) ease-in-out infinite;
  will-change: transform;
}
.orb.rev { animation-direction: reverse; }

/* Movement only. Every keyframe used to carry an opacity as well — var(--o)
   swinging up to x1.30 and down to x0.78 — and with seven orbs running that
   swing on seven different periods, from 30s to 42s, the brightness of the
   whole page below the hero never settled. Nothing ever lined up, so it read
   as the page breathing at you rather than as anything moving.

   The orbs still drift and still scale. What is gone is the part that made
   the light level itself the thing that changed. `will-change` drops opacity
   with it: naming a property that no longer animates just asks the compositor
   to keep a layer alive for nothing. */
@keyframes drift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(6%, -4%) scale(1.12); }
  66%      { transform: translate(-5%, 3%) scale(0.92); }
}

/* Orbs begin well below the junction — the blend owns that zone. */
/* The first three used to run at .40/.36/.28 — half again as strong as the
   seven below them — which is what painted §01 and §02 a saturated blue while
   §05 and §06 sat on near-black. They are now level with the rest of the
   field, so every content block shares one ground. */
.orb.o1 { width: 68vw; height: 68vw; top: 108vh; left: -18vw; background: var(--c-blue);   --o:.16; --t:30s; }
.orb.o2 { width: 58vw; height: 58vw; top: 152vh; right:-16vw; background: var(--c-indigo); --o:.16; --t:38s; }
.orb.o3 { width: 58vw; height: 58vw; top: 205vh; left:  6vw; background: var(--c-violet);  --o:.18; --t:34s; }
.orb.o4 { width: 46vw; height: 46vw; top: 46%; right:-10vw; background: var(--c-cyan);    --o:.20; --t:42s; }
.orb.o5 { width: 54vw; height: 54vw; top: 61%; left: -12vw; background: var(--c-steel);   --o:.30; --t:36s; }
.orb.o6 { width: 48vw; height: 48vw; top: 75%; right:  6vw; background: var(--c-magenta); --o:.26; --t:32s; }
.orb.o7 { width: 72vw; height: 72vw; top: 89%; left:  12vw; background: var(--c-violet);  --o:.32; --t:40s; }
/* The orbs stop there too, for a different reason.
   Their drift genuinely does composite — it is transform only, and it costs
   the main thread nothing. What it costs is the compositor: seven layers, each
   a 46-72vw square carrying filter: blur(110px) and mix-blend-mode: screen,
   re-composited at 60fps for as long as the tab is open. Measured at 497 ms of
   compositor thread per six idle seconds on a throttled phone.
   What that buys on a 360px screen is a 6% translate spread over 30 to 42
   seconds — under two pixels a second, past the point anyone can see it. The
   will-change goes with the animation: a promoted layer that never moves is a
   texture held for nothing, and on a cheap phone that memory is scarce. */
@media (max-width: 900px) {
  .orb { animation: none; will-change: auto; }
  .orb-wrap { will-change: auto; }
}

/* One scrim doing two jobs that need two different units, which is the only
   reason this is two background layers instead of one gradient.

   Job one is the light level of the ground below the hero, and it has to be
   the same whether the page under it is three screens or twelve. A percentage
   stop cannot express that: 50% of this homepage is somewhere around the
   fourth content block, so the wash was still at .08 through the sector band
   and the capability rail, and both read as brightly lit as the hero they had
   just left. The first layer is measured in vh instead — nothing at the hero's
   edge, .42 by 52vh, and then .42 for the whole rest of the document, because
   a gradient's last stop colour continues to the end.

   Job two is the settle at the foot, which genuinely is proportional to the
   page, so the second layer keeps percentages. It contributes nothing until
   the halfway mark, and the two together land at about .65 at the very bottom,
   where the old single gradient already ended. The foot is not getting darker;
   the body of the page is coming down to meet it.

   Do not fold these into one gradient with mixed vh and % stops. On a short
   page a later % stop can resolve above an earlier vh one, and CSS clamps an
   out-of-order stop to its predecessor — which collapses the whole tail onto
   one position and draws a hard edge across the middle of the page. Two
   layers, one unit each, cannot do that to themselves.

   Still nothing at the top: this element starts exactly where the hero ends,
   and any alpha at stop 0 makes its own band there. The ramp also needs more
   than its two end stops — a straight line from 0 to .42 has a kink at each
   end that the eye reads as a soft edge even though the maths is continuous,
   so the two middle stops round those corners off.

   The orbs are untouched, deliberately. They are the animation, and dimming
   them changes what moves rather than how dark the room is. Under this scrim
   the brightest of them lands about where the faintest ones sat before: deep
   coloured light in a darker room, which is what was asked for. */
.field-wash {
  position: absolute;
  top: 100vh; top: 100svh; left: 0; right: 0; bottom: 0;
  z-index: 0; pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(4,3,14,0) 0, rgba(4,3,14,.06) 9vh, rgba(4,3,14,.24) 22vh,
      rgba(4,3,14,.38) 36vh, rgba(4,3,14,.42) 52vh),
    linear-gradient(180deg,
      rgba(4,3,14,0) 0%, rgba(4,3,14,0) 50%, rgba(4,3,14,.14) 82%,
      rgba(4,3,14,.40) 100%);
}

/* Grain fades in too, so it doesn't switch on at a line.

   Static. It used to run `grainShift .9s steps(4)` — four hard jumps a second
   applied to an element that covers everything below the hero and blends in
   overlay. steps() means it snapped rather than moved, so it read as flicker,
   and because it is a blend layer every snap forced the compositor to re-blend
   that whole region against what was underneath it. Between four of those a
   second and seven orbs fading in and out on top, the bottom four-fifths of
   the page never held still. The texture is the point; the shaking was not. */
.grain {
  position: absolute;
  top: 100vh; top: 100svh; left: 0; right: 0; bottom: 0;
  z-index: 1; pointer-events: none;
  opacity: .055; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22vh);
  mask-image: linear-gradient(to bottom, transparent 0, #000 22vh);
}

/* ==========================================================================
   TYPE
   ========================================================================== */
h1, h2, h3, h4, h5, h6 {
  color: #fff;
  font-weight: 800;
  letter-spacing: -.035em;
  line-height: 1.2;
}
h1 { font-size: clamp(40px, 6.4vw, 96px); }
h2 { font-size: clamp(34px, 5vw, 72px); }
h3 { font-size: clamp(20px, 2vw, 26px); letter-spacing: -.02em; }
h4 { font-size: clamp(17px, 1.4vw, 20px); letter-spacing: -.015em; }

p { color: var(--white-70); font-weight: 200; line-height: 1.8; }
small, .text-muted { color: var(--white-50); }

.text-gradient,
.gradient-text {
  background: linear-gradient(var(--gradient-primary));
  /* No background-size either. The 220% was travel room for a sweep that no
     longer runs — see the note on .btn-primary below for why it had to go —
     and at 220% a short mark like «001» only ever showed one flat slice of the
     ramp. At the natural size the whole gradient reads across the element,
     which is what the class is for. */
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}

/* Headings are white. All of them, on every page, by request.

   The gradient stays defined above because it is not only a heading device —
   the numbered labels on cards («001», «المرحلة الأولى»), the counts and a
   handful of inline marks all use the same class and all keep it. What changes
   is that a heading never takes it, whichever of the two spellings the markup
   happens to use.

   Both properties have to be reset, not just `color`: the gradient works by
   painting a background and punching the glyphs out of it with
   `-webkit-text-fill-color: transparent`, and that fill colour wins over
   `color` in every engine that supports it. Setting one without the other
   leaves the text invisible on WebKit and correct on Firefox, which is the
   worst possible way for this to be wrong. The background and the shimmer come
   off too — there is nothing left to animate, and an animation running against
   a solid fill is a repaint per frame for no pixels. */
h1 .text-gradient, h1 .gradient-text,
h2 .text-gradient, h2 .gradient-text,
h3 .text-gradient, h3 .gradient-text,
h4 .text-gradient, h4 .gradient-text,
h5 .text-gradient, h5 .gradient-text,
h6 .text-gradient, h6 .gradient-text,
h1.text-gradient, h1.gradient-text,
h2.text-gradient, h2.gradient-text,
h3.text-gradient, h3.gradient-text {
  background: none;
  -webkit-text-fill-color: #fff;
  color: #fff;
  animation: none;
}

p.lede {
  font-size: clamp(1rem, 1.1vw, 1.25rem); font-weight: 200; line-height: 1.8;
  color: var(--white-70); max-width: 36rem;
}
/* The closing lede runs to three lines on a wide screen and only there: the
   36rem measure is fixed while the type keeps growing to its 20px ceiling, so
   past about 1800px the same sentence needs a line it did not need at 1440.
   Giving this one a wider measure settles it at two everywhere, and `balance`
   stops the second line being a single trailing word. */
.cta-section p.lede { max-width: 48rem; text-wrap: balance; }

.reveal {
  opacity: 0; transform: translateY(30px);
  transition: opacity .95s var(--ease), transform .95s var(--ease);
  transition-delay: var(--d, 0s);
}
.reveal.in { opacity: 1; transform: none; }

/* ==========================================================================
   LAYOUT
   ========================================================================== */
.wrap { width: 100%; max-width: 80rem; margin-inline: auto; padding-inline: 2rem; }

.block { padding-block: clamp(110px, 16vh, 200px); }
/* Less than it was: the proof strip sits directly above this block now and
   is doing the work the empty space used to do. */
.block.first { padding-top: clamp(90px, 14vh, 150px); }
.block.tight { padding-block: clamp(70px, 10vh, 120px); }
/* The 18ch cap is what keeps a two-word heading from stretching across the
   whole wrap, and it earns its place — but it was measured for headings of two
   or three words, and five headings on the site run to six or seven. Those
   were breaking to a second line with 350px of empty wrap beside them: the
   room was there and the cap was refusing it. «اختر نقطة البداية، لا قائمة
   المشتريات» is the widest of them at 1182px.

   75rem is the smallest ceiling that lets all five set in one line, and it is
   still 80px short of the wrap, so nothing here ever runs edge to edge. A
   heading longer than that still wraps, now balanced across a wider measure
   rather than a narrower one. Verified against all 70 section headings on the
   site at 1440px: nine went from two lines to one, «لماذا تتبنّى الشركات
   السعودية الذكاء الاصطناعي الآن؟» on the home page went from three to two,
   and not one went the other way. */
.block > .wrap > h2 { margin-top: 1.5rem; max-width: min(100%, 75rem); }
.block > .wrap > p.lede { margin-top: 2rem; }

.cards { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 1.25rem; margin-top: 3.5rem; }
.cards.two { grid-template-columns: repeat(2, minmax(0,1fr)); }

/* A grid of exactly four goes 2x2 rather than 3+1. Detected rather than
   declared, so the four-stage sections do not have to carry a modifier class
   into every page that repeats them. */
@media (min-width: 901px) {
  .cards:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, minmax(0,1fr)); }
}

/* One panel treatment, shared by the new markup and by every card the older
   sheet already draws. The 1px padding is the border: a gradient sits behind
   an opaque inner, which is the only way to get a gradient border that
   animates on hover. */
.panel,
.service-card,
.testimonial-card,
.feature-item,
.pricing-card,
.value-card,
.stat-card,
.info-card {
  border-radius: 20px;
  padding: 1px;
  background: linear-gradient(150deg, var(--white-20), rgba(255,255,255,.02));
  border: 0;
  box-shadow: none;
  transition: background .6s var(--ease), transform .6s var(--ease);
}
.panel:hover,
.service-card:hover,
.testimonial-card:hover,
.feature-item:hover,
.pricing-card:hover,
.value-card:hover,
.stat-card:hover,
.info-card:hover {
  background: linear-gradient(150deg, var(--gradient-colors));
  transform: translateY(-6px);
  box-shadow: none;
}

.panel-inner {
  height: 100%; border-radius: 19px; padding: 2.5rem 2rem 2.75rem;
  background: var(--panel);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}

/* The older cards have no inner element, so the panel fill has to go on the
   card itself. background-clip keeps the gradient border visible underneath. */
.service-card,
.testimonial-card,
.feature-item,
.pricing-card,
.value-card,
.stat-card,
.info-card {
  background-image: linear-gradient(var(--panel), var(--panel)),
                    linear-gradient(150deg, var(--white-20), rgba(255,255,255,.02));
  background-origin: border-box;
  background-clip: content-box, border-box;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}
.service-card:hover,
.testimonial-card:hover,
.feature-item:hover,
.pricing-card:hover,
.value-card:hover,
.stat-card:hover,
.info-card:hover {
  background-image: linear-gradient(var(--panel), var(--panel)),
                    linear-gradient(150deg, var(--gradient-colors));
}

/* ---- arriving at a capability -------------------------------------------
   The ten cards on the homepage no longer link to the top of a pillar page,
   they link to the one card on it that is about the thing that was clicked:
   /services/intelligence/#vision, and so on. Two rules make that land well.

   scroll-margin-top, because the bar is sticky and would otherwise be sitting
   on top of the heading the reader was sent to. It reads --nav-h, the same one
   number the bar and the hero are sized from.

   :target, because a page that jumps three cards down with nothing marked
   leaves the reader to work out whether they arrived anywhere. The border
   lights the way it does on hover, so the answer is the first thing they see. */
.panel[id] { scroll-margin-top: calc(var(--nav-h) + 1.25rem); }
.panel[id]:target {
  background: conic-gradient(from var(--edge),
    #06f, #00d9ff, #7300ff, #ff00d0, #7300ff, #00d9ff, #06f);
  /* Two turns, not forever. --edge reverts to 0deg and the first and last
     stops are both #06f, so the resting frame is the one it started on. */
  animation: edgeSpin 5s linear 2;
}

/* A card's title is an h3's size whatever its level. The two case studies on
   the portfolio page are h2 because they are top-level sections of that page,
   and at the h2 scale — up to 72px — a three-word title filled the card. The
   level is about the document outline; the size is about the box it is in. */
.panel h2 { font-size: clamp(20px, 2vw, 26px); letter-spacing: -.02em; }

.panel .num, .card-num {
  display: block; font-size: .75rem; font-weight: 800; letter-spacing: .2em; margin-bottom: 1.75rem;
}
.panel p { margin-top: .875rem; font-size: .9375rem; font-weight: 200; line-height: 1.8; color: var(--white-50); }

.marquee {
  overflow: hidden;
  direction: ltr;
  -webkit-mask-image: linear-gradient(90deg,#0000,#000 10% 90%,#0000);
  mask-image: linear-gradient(90deg,#0000,#000 10%,#000 90%,#0000);
}
@media (width>=768px) {
  .marquee {
    -webkit-mask-image: linear-gradient(90deg,#0000,#000 30% 70%,#0000);
    mask-image: linear-gradient(90deg,#0000,#000 30%,#000 70%,#0000);
  }
}
/* backdrop.js doubles each track's contents at load, so the row holds its list
   twice and the animation travels exactly one copy per cycle: the frame it
   jumps back from is identical to the frame it jumps back to, and the loop is
   invisible.

   Except it was not, and the reason is one gap. A track of N items has N-1
   gaps inside it; doubled, it has 2N items and 2N-1 gaps — one more than two
   copies' worth, because a gap also appears where the two copies meet. So the
   full width is 2C + g, half of it is C + g/2, and translateX(-50%) lands half
   a gap short of where the second copy begins. Every pass slipped 36px, and
   after a few passes the row was visibly stuttering and drifting out of the
   mask. That is the "it turns off and stops moving" this fixes: not a missing
   duplicate, a missing 2.25rem.

   Both rows start at the beginning of their list. The second runs the same
   keyframes in reverse rather than a mirrored pair of its own — reverse starts
   at the end of the travel, which on a doubled track is the same picture as
   the start.

   `gap` is now the only separator; the interpuncts that used to sit between
   the items are gone from the markup. */
/* The `direction: ltr` on .marquee above is load-bearing, exactly as it is on
   .star-frame, and it is the larger half of the "it turns off" bug.

   A translateX has no logical direction. In an RTL container a max-content
   child wider than its parent is placed with its RIGHT edge against the
   parent's right edge, overflowing to the LEFT — so `to { translateX(-50%) }`
   drags the words further off the side they are already hanging off, and the
   strip empties. Measured across one pass on the Arabic page: 1,224px of words
   in the mask at the start, 427 at the halfway point, 0 at the end. On the
   English page, where the overflow goes the other way, the identical
   declaration was flawless. That is why it looked like the row was switching
   itself off and why it only did it in Arabic.

   Forcing the strip to lay out left-to-right makes the geometry identical in
   both languages: start at the left edge, overflow right, travel left, and the
   second copy is always the thing arriving. The Arabic inside each span is
   untouched — a run of pure Arabic shapes and orders right-to-left on its own
   whatever the flex container does around it. Only the order of the five items
   changes, and a ticker has no reading order to preserve.

   Chosen over a mirrored keyframe under html[dir="rtl"] on purpose: i18n.js
   flips <html dir> at runtime, and geometry that is right by construction
   cannot get that flip wrong. */
/* --gap is read twice: once as the flex gap, once inside the keyframe. It has
   to be a variable because the rail below runs the same animation at a much
   tighter spacing, and the travel distance is a function of the gap — a track
   of cards at 1.25rem stepping by half of 4.5rem drifts a little further out
   of true on every pass, which is the same class of bug the note above
   describes, arrived at from the other direction. */
.marquee-track {
  --gap: 4.5rem;
  display: flex; width: max-content; gap: var(--gap);
  animation: slide 34s linear infinite;
  will-change: transform;
}
.marquee-track.two { animation: slide 44s linear infinite reverse; margin-top: 1.5rem; }
/* Half the doubled track, plus half of the one gap that separates the two
   copies — without it the loop drifts by a whole gap every pass. */
@keyframes slide { to { transform: translateX(calc(-50% - var(--gap) / 2)); } }

/* ==========================================================================
   THE PAGE HEADER
   --------------------------------------------------------------------------
   Used by one page — about/ — and these five rules are the entire live content
   of what used to be shared/css/animated.css: 12KB of card hovers, orbit
   spins, glitch effects and eighteen keyframes, of which about/ referenced
   three class names and nothing else. The sheet is gone; the three rules it
   was actually carrying are here, where the rest of the shared type scale
   already lives.
   ========================================================================== */
/* 60vh of minimum height with the content centred inside it, on top of the
   150px that clears the fixed nav, put about 345px of nothing above the title
   on a 900px screen — the header was reserving a screenful and then centring
   in what was left.

   The 128px that replaced it was still a nav's height too much. .navbar is
   `position: sticky`, so it keeps its own 115px at the top of the flow and
   everything here is paid on top of that: the title started 243px down the
   page, where contact/ starts at 196 and portfolio/ at 205. This is the same
   expression .contact-section uses, so the two pages that carry a centred
   title over a plain background now start it on the same line.

   `display: flex` went with it. Nothing is being centred vertically any more,
   and as a flex container it sized its own .container to the content — 832px
   where every other section on the page gets the full 1200 — so the header was
   the one block on the site not laid out in the same box as its neighbours. */
.page-header {
  position: relative;
  padding-top: clamp(72px, 9vh, 100px);
  padding-bottom: clamp(2.5rem, 6vh, 4.5rem);
  text-align: center;
}
/* Wider than the 800px it was, because the title below wants to be one line
   and «الريادة في ثورة الذكاء الاصطناعي» needs about 15em to get there. The
   lede keeps the old measure — a 1040px line of 19px body copy is too long to
   track back comfortably — so the two are now set separately. */
.page-header-content { max-width: min(100%, 1040px); margin: 0 auto; }
.contact-section .section-header h1 { font-size: clamp(2rem, 4.4vw, 3.5rem); }
/* One line, and larger for it.
   It was set to the same 56px ceiling as every other inner page and given
   `text-wrap: balance`, which is the right tool when a heading must break —
   it was breaking after «الذكاء» and leaving «الاصطناعي» alone underneath, and
   balance at least divided the measure evenly. But this heading is the first
   thing on the page and it does not have to break at all: given the width, it
   sets in one line, and one line lets it carry the extra 8px that a two-line
   title could not afford. Below 900px there is no width to be had and it wraps
   again — balanced, as before. */
.page-header h1 {
  margin-bottom: 20px;
  font-size: clamp(2rem, 5vw, 4rem);
  white-space: nowrap;
}
@media (max-width: 900px) {
  .page-header h1 { white-space: normal; text-wrap: balance; }
}
.page-header p {
  max-width: 800px; margin-inline: auto;
  font-size: 1.2rem; color: var(--white-70); line-height: 1.7;
}
/* ==========================================================================
   THE SECTOR GRID
   --------------------------------------------------------------------------
   Ten doors under the hero. The card is deliberately the smallest thing that
   can be a link: one mark, one name, and enough padding that a thumb cannot
   miss it. Everything a sector page has to say is on the sector page.

   Fixed column counts, not auto-fit. auto-fit picks the most columns that
   clear its minimum, which at a 1216px wrap and a 190px floor is six — so the
   ten came out 6 + 4, with a hole where the tenth card should have been. The
   count has to be a number the ten divide into: 5 and 5 on a desktop, 4 + 4 +
   2 on a tablet, five rows of 2 on a phone. Three is the one to avoid, and the
   only one auto-fit would have reached for on the way down: it leaves a single
   card alone on a fourth row.
   ========================================================================== */
/* Centred — and the centring needs the element in the selector to land.

   `.block > .wrap > h2` caps every section heading on the site at 18 characters,
   and it outranks a bare class. So `text-align: center` on its own was centring
   the line inside a box that was still 18ch wide and still flush against the
   inline start, which in Arabic is the right edge: a centred line inside an
   un-centred box, which reads as no centring at all. `margin-inline: auto` is
   the half that was missing, and the wider measure is what keeps «مبنيّ لقطاعك
   أنت» on one line instead of breaking after the first word. Exactly the trap
   .cap-rail-head below documents, arrived at from the other side. */
.block > .wrap > h2.sector-head {
  max-width: 24ch;
  margin-inline: auto;
  text-align: center;
  text-wrap: balance;
}
.sector-grid {
  display: grid; gap: .8rem; margin-top: 3rem;
  grid-template-columns: repeat(5, minmax(0, 1fr));
}
@media (max-width: 1023px) {
  .sector-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.sector-card {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .9rem; min-height: 128px; padding: 1.4rem 1rem;
  border-radius: 16px; text-align: center; text-decoration: none;
  background: rgba(255,255,255,.035);
  border: 1px solid rgba(255,255,255,.09);
  transition: transform .45s var(--ease), border-color .3s var(--ease), background .3s var(--ease);
}
/* No tile behind the mark, by request — the glyph sits on the card itself.
   It grows to make up for the box it lost: at 1.15rem inside a 42px well the
   well was doing the work of giving the icon presence, and the same glyph left
   on its own at that size reads as an afterthought rather than the top half of
   the card. */
.sector-card__icon {
  display: grid; place-items: center;
  transition: transform .3s var(--ease);
}
.sector-card__icon i {
  font-size: 1.7rem; color: #a06bff;
  transition: color .3s var(--ease), filter .3s var(--ease);
}
.sector-card__name {
  font-size: .9rem; font-weight: 600; line-height: 1.5; color: rgba(255,255,255,.86);
  text-wrap: balance;
}
.sector-card:hover, .sector-card:focus-visible {
  transform: translateY(-4px);
  background: rgba(255,255,255,.06);
  border-color: color-mix(in srgb, #7300ff 48%, transparent);
}
.sector-card:hover .sector-card__icon { transform: translateY(-2px); }
.sector-card:hover .sector-card__icon i {
  color: #c39bff;
  filter: drop-shadow(0 0 12px rgba(115,0,255,.55));
}
.sector-card:focus-visible { outline: 2px solid #7300ff; outline-offset: 3px; }

/* ==========================================================================
   THE CAPABILITY RAIL
   --------------------------------------------------------------------------
   The same two-track marquee, carrying cards instead of very large faint
   words. The rows still run in opposite directions, which is what stops the
   pair reading as one long list, and the mask on .marquee still fades both
   ends so a card never appears to be cut by nothing.

   The weave behind them is one SVG in a data URI — three sine paths, blurred
   by the compositor, no request and no script. It is the reason the rows read
   as one system rather than two unrelated tickers, and it is the last thing
   left of the flow diagram that used to make that point in the block above.
   ========================================================================== */
/* Both halves of the heading are inline spans separated by a space, so at the
   h2 scale the line broke wherever it ran out of room — which put «وكل» at the
   end of the first line and left «الاصطناعي» alone on a third. The cap plus
   balance gives it the two even lines it is written for: the plain half, then
   the gradient half whole. */
.cap-rail-head, .cap-rail-lede { text-align: center; margin-inline: auto; }
/* Smaller than the page's other h2s, and wider, and both matter.

   The size first: the default clamp tops out at 72px, where the whole sentence
   is about 1,100px of Arabic and cannot sit on one line. At 48px it is ~740px,
   which fits a 1,216px wrap with room to spare.

   The width second, and this is the part that is easy to miss: every section
   heading on the site is capped at 18 characters by `.block > .wrap > h2`, and
   that selector outranks a single class. A plain `.cap-rail-head { max-width }`
   loses to it silently — the heading keeps breaking into three lines and the
   rule that was supposed to stop it is sitting right there looking correct.
   The cap is a good default for a heading of four or five words; this one is a
   sentence, so it needs its own measure and the specificity to state it.

   balance is here for the widths where the line genuinely has to wrap. While
   it fits, balance does nothing. */
.block > .wrap > h2.cap-rail-head {
  font-size: clamp(26px, 3.6vw, 48px);
  max-width: 34ch;
  text-wrap: balance;
}
/* `p.lede` caps every lede on the site at 36rem, and it is one class plus one
   element — so a bare `.cap-rail-lede { max-width }` loses to it, which is why
   this sentence kept setting as three lines whatever was written here. 52rem
   is where it falls into the two it is written as. Exactly the same trap as
   the heading above: the default needs the element in the selector to beat. */
p.lede.cap-rail-lede { max-width: 52rem; text-wrap: balance; }
.cap-rail { position: relative; margin-top: 3rem; padding-block: 2.2rem; }
.cap-rail__weave {
  position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 240' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%237300ff' stroke-width='2.5'%3E%3Cpath d='M0 120C100 36 200 204 300 120S500 36 600 120 700 204 800 120'/%3E%3Cpath d='M0 152C120 92 220 212 340 142S560 72 660 152 760 192 800 152' stroke='%23b81df5'/%3E%3Cpath d='M0 88C140 158 240 28 360 98S580 168 700 88 780 58 800 88' stroke='%2306f'/%3E%3C/g%3E%3C/svg%3E")
    center / 100% 100% no-repeat;
  filter: blur(7px);
  opacity: .45;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
}
.cap-rail .marquee { position: relative; z-index: 1; }
/* The inherited mask fades everything outside the middle 40% of the strip to
   nothing. That is the right treatment for a row of very large faint words,
   where the point is texture and the ends should dissolve — and the wrong one
   for a row of cards, where four of the ten are then permanently half-erased
   and the rail reads as though it has holes at both ends. Trimmed to a 5%
   feather: enough that a card is not sliced off by a hard edge, not enough to
   hide one. */
.cap-rail .marquee {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
}
.cap-rail .marquee-track { --gap: 1.4rem; align-items: stretch; }
.cap-rail .marquee-track.two { margin-top: 1.4rem; }

/* Grid, not flex: the mark has to sit beside a two-line stack, and as three
   flex children the title and its line would sit next to each other instead
   of on top of one another. The icon spans both rows and stays centred
   against them.

   `direction: rtl` is on the chip and nothing else. .marquee above is forced
   to `ltr` — see the long note there; it has to be, because a translateX in an
   RTL container drags the strip off the side it is already hanging off and the
   row empties itself. But that forced ltr cascades into the cards, and a grid
   whose first column is `auto` puts that column on the left in ltr — so the
   mark landed on the wrong side of every card in the Arabic page while the
   text inside it stayed right-to-left. Setting the direction back on the chip
   fixes the card without touching the geometry of the strip that carries it:
   the animation still runs in ltr, the mark sits where an Arabic reader starts
   reading. */
.cap-chip {
  direction: rtl;
  display: grid; align-items: center;
  grid-template-columns: auto 1fr;
  column-gap: 1rem;
  padding: 1.15rem 1.4rem; border-radius: 18px; white-space: nowrap;
  background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.028));
  /* No backdrop-filter. Forty of these ride two infinite marquee tracks, and
     each blurred chip is its own composited layer over a moving background —
     the gradient fill and the 1px border read the same standing still. */
  border: 1px solid rgba(255,255,255,.1);
}
html[dir="ltr"] .cap-chip { direction: ltr; }
/* No tile behind the mark here either. The glyph carries its own weight at
   1.35rem with a soft violet cast under it. */
.cap-chip i {
  grid-row: 1 / span 2;
  display: grid; place-items: center;
  width: 34px; font-size: 1.55rem; color: #a06bff;
  filter: drop-shadow(0 0 10px rgba(115,0,255,.4));
}
.cap-chip b {
  display: block; font-size: 1.06rem; font-weight: 700; letter-spacing: -.01em; color: #fff;
}
.cap-chip em {
  display: block; margin-top: .26rem;
  font-style: normal; font-size: .84rem; font-weight: 200; color: var(--white-50);
}
/* And on a desktop the two lines centre on the card MINUS the mark, not on the
   card.

   An earlier pass here read it the other way round: it pulled the mark out of
   the flow, padded both sides of the card to 3.9rem and centred the text on
   the whole box. That is a true centre of the card, and it is the wrong one.
   The mark is a 34px label pinned to the reading edge, not part of the
   measure, so a line centred across it sits pushed toward the mark with a
   visibly wider void on the far side — 27.9px of it at 1440, in both
   directions.

   So the mark goes back into the grid — `auto 1fr`, the same two columns the
   card uses everywhere else — and the text centres inside the 1fr column,
   which IS the leftover region by definition. One number makes that centre
   exact rather than approximate: the column-gap is set equal to the chip's
   inline padding. The text column runs from `card edge + padding` to
   `mark edge - gap`, so its midpoint lands on the midpoint of
   `card edge .. mark edge` only when those two distances match; at the base
   1rem gap against 1.4rem of padding the line stops 3.2px short. Matching them
   puts it on zero. The 6.4px the card gains is absorbed by the marquee: the
   track is `max-content` and the travel is a percentage of itself, so a wider
   chip is a slightly slower ticker and nothing else.

   `align-content: center` stays because the track stretches every chip to the
   tallest one; without it the two auto rows stretch with it and the title and
   its line drift apart inside a taller card.

   Desktop only, per the brief. Below 900 the line under the title is
   display:none and the card is a mark plus one word, which is already the
   right shape for a phone. */
@media (min-width: 901px) {
  .cap-chip {
    column-gap: 1.4rem;
    align-content: center;
    text-align: center;
  }
}
/* ==========================================================================
   THE SHOWCASE DECK
   --------------------------------------------------------------------------
   Ten pieces of work as a coverflow: one card centred at full size with a
   violet glow on its edge, its neighbours scaled back, tilted and dimmed
   behind it, the rest crowded toward the edges of the stage. Dots under the
   deck, ghost chevrons over its sides, one quiet link under everything.

   Two things live here and only here; everything else is decoration.

   The first is that the slides are absolutely positioned and stacked on the
   centre line. They are not a flex row. A row can be made to overlap with
   negative margins, but it cannot be made to *interleave* — the third card
   from the left has to sit behind the second and in front of the fourth, and
   which of those is true changes every time the deck moves. That is a z-index
   per card, per index, which means the cards cannot be laid out by the flow.
   So the flow is told nothing: each slide is pinned to the same point and
   showcase.js writes where it actually goes.

   The second is that this file draws the FIRST card as the active one, with
   no script involved. showcase.js writes transform, opacity, z-index and
   pointer-events as inline styles, and an inline style beats any selector
   here — so the rules below are the state the section is in before the script
   runs and after it fails, and they are never fighting it. What that state
   has to be is the one card the markup already marks as reachable, sitting
   centred and legible, with the controls that need the script hidden.
   ========================================================================== */

.showcase-band {
  /* The card's own size is the deck's only unit: showcase.js steps by a
     fraction of this width, so the overlap is identical at every viewport
     without a breakpoint in the script. */
  /* One number sizes the whole deck: the card is 16:9 and showcase.js steps
     by a fraction of this width, so the overlap and the depth are identical at
     every viewport with no breakpoint in the script. 42vw is measured off the
     reference — a card a little under half the screen, with its neighbours
     running off both edges. */
  --card-w: clamp(340px, 42vw, 800px);
  --card-h: calc(var(--card-w) * 9 / 16);
}

/* Both of these need the element in the selector to land, for the reason
   written out at .cap-rail-head above: `.block > .wrap > h2` caps every
   heading on the site at 18ch and `p.lede` caps every lede at 36rem, and both
   outrank a bare class. A centred pair over a full-bleed deck wants a wider
   measure than either default gives. */
.block > .wrap > h2.showcase-head {
  max-width: 26ch;
  margin-inline: auto;
  text-align: center;
  text-wrap: balance;
}
p.lede.showcase-lede {
  max-width: 46rem;
  margin-inline: auto;
  text-align: center;
  text-wrap: balance;
}

/* ---- the stage ----------------------------------------------------------
   Deliberately not clipped. The edge cards are meant to run off both sides —
   that bleed is what says the deck continues — and an `overflow: hidden` here
   would also cut the active card's glow off square at the top and bottom.
   `body { overflow-x: hidden }` in the base already stops any of it reaching
   the scrollbar. */
.showcase-stage {
  position: relative;
  margin-top: clamp(2.4rem, 5vw, 3.4rem);
  padding-block: clamp(.75rem, 2vw, 1.5rem);
}
.showcase-stage:focus-visible { outline: 2px solid #a06bff; outline-offset: 6px; border-radius: 28px; }

/* The direction here is the same load-bearing declaration as on .marquee, for
   the same reason and with the same consequence — read the long note there.
   showcase.js positions the deck with translate3d, and a translate has no
   logical direction: it is physical pixels, always, whatever dir the document
   is in. Left as `rtl` the slides' own containing block would flip under it,
   and +180px would put a card on the side the script thought was the other
   one — a deck that reads correctly in English and inside-out in Arabic, from
   markup and script that are byte for byte the same.

   Forcing ltr makes the geometry identical in both languages: index 0 on the
   left, index 9 on the right, `index + 1` always the card to the right of the
   centre. The Arabic sets its own direction back on .showcase-meta below, the
   way .cap-chip does inside the marquee it is carried by. And it is why the
   buttons are chevrons: they point at a physical side, and a physical side is
   the only promise this layout can keep in both languages.

   perspective is on the track, not the stage, because it applies to a
   parent's children and nothing deeper — from the stage it would reach the
   track and stop, and the slides would rotate flat with no depth at all. */
.showcase-track {
  direction: ltr;
  position: relative;
  height: var(--card-h);
  /* Horizontal drags belong to the deck, vertical ones to the page. Without
     this a swipe up the middle of the stage fights the scroll. */
  touch-action: pan-y;
  cursor: grab;
}
.showcase-track:active { cursor: grabbing; }

.showcase-slide {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: var(--card-w);
  /* Centred by margin rather than by folding a -50% into the transform, so
     the string the script writes is pure geometry and can be read at a
     glance in the inspector. */
  margin-left: calc(var(--card-w) / -2);
  opacity: 0;
  pointer-events: none;
  z-index: 1;
  transform: scale(.82);
  /* Travel, and it is paired with AUTOPLAY_MS in showcase.js: the deck has to
     finish arriving well before the next step starts, or the two read as one
     continuous drift instead of six distinct cards. At 2.2s a card, 0.34s of
     travel leaves it standing still for six sevenths of every step. */
  transition: transform .34s var(--ease), opacity .34s var(--ease);
  will-change: transform, opacity;
}
/* The no-script state, and the first paint of every other one. */
.showcase-slide:first-child {
  opacity: 1;
  pointer-events: auto;
  z-index: 30;
  transform: none;
}

.showcase-card {
  position: relative;
  display: block;
  height: 100%;
  border-radius: 14px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  background: var(--panel);
  border: 1px solid var(--white-10);
  /* No backdrop-filter here, deliberately. There was one, and it was pure
     cost: .showcase-shot covers this box edge to edge with an opaque picture,
     so the blur had nothing behind it to show and nothing in front of it that
     could be seen through — six full-card backdrop filters compositing on
     every frame of every step, for no pixel. */
  transition: border-color .5s var(--ease), box-shadow .5s var(--ease);
}
/* Dimming is a veil over the card, and ONLY a veil: no card in the deck is
   ever drawn at a partial opacity. A translucent card is see-through, and what
   shows through it is the card behind it — two screenshots occupying the same
   pixels, which reads as a printing fault rather than as depth. So the script
   writes opacity 1 on every card it draws and 0 on the one it parks off screen,
   and the distance is carried here, by how much of the page's own ground colour
   is laid over the picture. A wash darkens the way distance darkens; a reduced
   alpha only makes the card thinner.

   --veil is written per slide by showcase.js, one value per depth. The default
   is the first neighbour's, which is what the section is worth looking at with
   no script at all. */
.showcase-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: rgba(4, 3, 14, .9);
  opacity: var(--veil, .4);
  pointer-events: none;
  transition: opacity .34s var(--ease);
}
.showcase-slide.is-active .showcase-card::after { opacity: 0; }
/* .is-active is written by the script, so without it the one card the reader
   can see is still wearing the veil and the border that say "this is one of
   the ones behind". The no-script state has to look like a card, not like a
   deck with nothing in front of it. */
.showcase-band:not(.is-live) .showcase-slide:first-child .showcase-card::after { opacity: 0; }

.showcase-slide.is-active .showcase-card,
.showcase-band:not(.is-live) .showcase-slide:first-child .showcase-card {
  border-color: color-mix(in srgb, #a06bff 72%, transparent);
  box-shadow:
    0 0 34px -6px rgba(160, 107, 255, .38),
    0 26px 70px -26px rgba(0, 0, 0, .85);
}
.showcase-card:focus-visible { outline: 2px solid #a06bff; outline-offset: 4px; }
/* The voice card is a <div>, so its link lives on the title. It has to look
   exactly like the nine titles that are not links — the card is the target in
   every case and a differently-coloured heading on one of ten reads as a
   mistake — so all that is added here is the focus ring it needs to be
   reachable and the hover the other nine get from .showcase-card. */

/* The picture is the card. It fills the whole box and the meta lies over its
   bottom edge, which is what the reference does and what any real screenshot
   dropped in here will want: a 16:9 photograph has nowhere else to go.

   The ground is the scenes' own panel colour rather than a tint of the page.
   The drawings are on a 420x264 viewBox — 1.59 against the card's 1.78 — and
   they are `meet`, so they fit by height and leave a sliver of this background
   down each side. Painting it #0b0720 makes that sliver the same colour as the
   panel the drawing itself starts with, so the seam does not exist. `slice`
   would have filled the box instead and taken 14px off the top and the bottom
   of every scene, which is exactly where their window chrome and their KPI row
   are. An <img> put here later covers the box outright and never sees any of
   this. */
.showcase-shot {
  display: block;
  position: absolute;
  inset: 0;
  background: #0b0720;
}
.showcase-shot svg,
.showcase-shot img {
  display: block;
  width: 100%;
  height: 100%;
}
.showcase-shot img { object-fit: cover; }

/* Every card's picture has words in it, so each is cut twice — from the
   vendor's Arabic page and from its English one — and the pair is swapped here
   rather than in i18n.js. That file translates textContent and never an
   attribute, which is the rule that keeps a src from being rewritten by a
   dictionary. display:none is also what keeps the other six cuts off the wire:
   the images are loading="lazy", and an element that is not displayed is never
   in a viewport, so the browser has no reason to fetch it until the reader
   actually switches language. */
/* .showcase-shot is in all three selectors because the rule above it — the one
   that gives every picture in a card its width and height — is `.showcase-shot
   img`, and a bare `.shot-en` loses to it on specificity and is silently
   overruled back to display:block. */
.showcase-shot .shot-en { display: none; }
html[dir="ltr"] .showcase-shot .shot-ar { display: none; }
html[dir="ltr"] .showcase-shot .shot-en { display: block; }

/* CSS pauses what
   carries the flag, so a page whose script never ran shows ten scenes
   running rather than ten frozen first frames with nothing to explain them.
   showcase.js sets it on the slides that are too far out or too faint to read
   anything moving in, and on all ten while the section is off screen. */
.showcase-slide.is-idle .showcase-shot svg * { animation-play-state: paused; }
/* The hint is only worth paying while something is actually moving. showcase.js
   marks the deck idle between steps; six pinned layers off screen are not free. */
.showcase-slide.is-idle { will-change: auto; }

/* Back to rtl, on the text and nothing else — the track above must stay ltr
   or the deck inverts. Same split as .cap-chip inside .marquee.

   It sits on the picture, and the scrim under it is a tall gradient rather
   than a bar: a bar has a top edge, and an edge across a photograph reads as a
   second element pasted on. The gradient starts fading four lines above the
   text so there is never a boundary to find, and it is dense enough at the
   bottom that white type holds over a bright photograph as well as over these
   dark drawings — which matters, because the pictures here are meant to be
   swapped for real ones. */
.showcase-meta {
  direction: rtl;
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .55rem;
  padding: 5.5rem clamp(1.1rem, 2vw, 1.75rem) clamp(1.1rem, 1.8vw, 1.6rem);
  background: linear-gradient(to top,
    rgba(4,3,14,.95) 0%, rgba(4,3,14,.86) 30%,
    rgba(4,3,14,.55) 62%, rgba(4,3,14,.16) 84%, transparent 100%);
  border-radius: 0 0 inherit inherit;
}
html[dir="ltr"] .showcase-meta { direction: ltr; }

/* The element is in both selectors to beat style.css, which still sets a
   size and a colour on bare h3 and p — the same fight .panel h2 documents. */
.showcase-card h3.showcase-title {
  margin: .1rem 0 0;
  font-size: clamp(1.15rem, 1.9vw, 1.85rem);
  font-weight: 800;
  line-height: 1.35;
  letter-spacing: -.02em;
  color: #fff;
  text-wrap: balance;
}
/* The line under the title is the sector, set the way the reference sets it:
   small, spaced, violet, and never more than one line. Two lines of it and it
   stops being a label and starts being a second sentence. */
.showcase-card p.showcase-kicker {
  margin: 0;
  font-size: clamp(.66rem, .8vw, .78rem);
  font-weight: 600;
  letter-spacing: .12em;
  line-height: 1.5;
  color: #a06bff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* ---- the controls -------------------------------------------------------
   Hidden until showcase.js adds .is-live. None of them does anything without
   it, and three arrows and ten dots that swallow every press are worse than
   an honest single card. The deck itself is not gated on this class — it is
   already correct above. */
.showcase-nav,
.showcase-dots { display: none; }
.showcase-band.is-live .showcase-nav { display: grid; }
.showcase-band.is-live .showcase-dots { display: flex; }

/* left/right, never inset-inline-start/end. These two buttons are the one
   place on the site where a logical property would be actively wrong: they
   name a physical side of the deck and they have to stay on it when i18n.js
   flips <html dir> at runtime, or the chevron pointing left starts moving the
   deck right. */
.showcase-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 40;
  width: clamp(40px, 5.5vw, 54px);
  aspect-ratio: 1;
  place-items: center;
  padding: 0;
  border-radius: 50%;
  color: #fff;
  background: rgba(10, 3, 29, .55);
  border: 1px solid var(--white-20);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  cursor: pointer;
  transition: background .3s var(--ease), border-color .3s var(--ease), color .3s var(--ease);
}
.showcase-nav--prev { left: clamp(.35rem, 3vw, 2.5rem); }
.showcase-nav--next { right: clamp(.35rem, 3vw, 2.5rem); }
.showcase-nav svg {
  width: 42%;
  height: 42%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.showcase-nav:hover {
  background: rgba(115, 0, 255, .38);
  border-color: color-mix(in srgb, #a06bff 60%, transparent);
}
.showcase-nav:focus-visible { outline: 2px solid #a06bff; outline-offset: 3px; }

/* ltr, like the track above and for the same reason. The deck is laid out in
   physical pixels — slide 0 is the leftmost card in both languages — so a row
   of dots left in the document's rtl puts the first dot at the right-hand end
   and the mark for the card you are looking at travels the opposite way to the
   card itself. The dots are a map of the deck, and a map has to face the same
   way as the thing it maps. */
.showcase-dots {
  direction: ltr;
  justify-content: center;
  align-items: center;
  gap: .62rem;
  margin-top: clamp(1.5rem, 3.2vw, 2.2rem);
}
.showcase-dot {
  position: relative;
  width: 6px;
  height: 6px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--white-20);
  cursor: pointer;
  transition: width .5s var(--ease), background .5s var(--ease);
}
/* An 8px target is a third of what a thumb can reliably hit, and growing the
   dot to fit one would make the row of them a row of buttons instead of a
   row of marks. The target grows instead of the mark. */
.showcase-dot::before { content: ""; position: absolute; inset: -13px -6px; }
.showcase-dot:hover { background: var(--white-50); }
.showcase-dot.is-on { width: 22px; background: linear-gradient(90deg, #7300ff, #b81df5); }
.showcase-dot:focus-visible { outline: 2px solid #a06bff; outline-offset: 4px; }

/* ---- the phone ----------------------------------------------------------
   One card with slivers of the two beside it. The card takes 78% of the
   screen, the script steps by 62% of that, and the arithmetic leaves about a
   tenth of the viewport of each neighbour showing past the centre card's
   shoulder — enough to say there is more, not enough to compete. The meta
   steps down with it: at this width the three-line kicker is the tallest
   thing in the card and it is the part with the least to say. */
@media (max-width: 560px) {
  .showcase-band { --card-w: min(86vw, 340px); }
  .showcase-meta { padding: 3.4rem .9rem 1rem; gap: .4rem; }
  .showcase-card h3.showcase-title { font-size: 1.02rem; }
  .showcase-card p.showcase-kicker { font-size: .62rem; letter-spacing: .1em; }
  .showcase-nav { width: 36px; }
  .showcase-stage { margin-top: 2rem; }
}

@media (prefers-reduced-motion: reduce) {
  /* The deck still moves — the arrows, the dots, the keys and the drag all
     still work and the script's autoplay is dead — but it moves in one frame
     instead of sliding. An animation switched off has to leave the finished
     state behind, which for this section is simply the card the reader asked
     for, centred. */
  .showcase-slide,
  .showcase-card,
  .showcase-card::after,
  .showcase-nav,
  .showcase-dot { transition: none; }
  .showcase-shot svg * { animation: none !important; }
}

/* The ten inline-SVG "scenes" that used to be the picture on every card lived
   here — a shared .sc-* vocabulary plus twenty-nine keyframes, 24KB of it. All
   six cards now carry a real picture from assets/images/showcase/; the four
   that still used a drawing were removed from the deck by request. Nothing in
   the tree matches `.sc-` any more, so the vocabulary went with the markup
   rather than being left behind to style nothing.
   ========================================================================== */

.stat-row .l, .stat-label {
  margin-top: .75rem; font-size: .75rem; font-weight: 600; letter-spacing: .2em;
  text-transform: uppercase; color: var(--white-50);
}

/* The rule for .foot-line, the proof strip and its .proof items lived here.
   All three are gone from the markup by request and the selectors went with
   them rather than being left behind to match nothing. */

/* The flow diagram and the four stage rings lived here — the two drawings
   that carried «01 — positioning» and «أربع مراحل». Both sections are gone
   from the homepage by request and no other page ever used either class, so
   the rules went with the markup instead of being left behind to match
   nothing. The idea survives as .cap-rail__weave above: one drawing, in CSS,
   saying that the things in front of it are one system.
   ========================================================================== */

/* ---- the live card edge -------------------------------------------------
   Every card on the site already carries a gradient border — a 1px padding
   box with a gradient behind an opaque inner. On hover that gradient stopped
   being a still image and became a rotation: one light travelling round the
   edge of the card you are pointing at.

   @property is what makes it possible at all. An angle interpolated through a
   registered custom property animates; the same angle written straight into a
   conic-gradient is an unanimatable keyword to the engine, which is why this
   effect is usually faked with a spinning pseudo-element and a clip. This is
   one declaration and one repaint of a card-sized box, and only while a
   pointer is actually on it. */
@property --edge { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
@keyframes edgeSpin { to { --edge: 360deg; } }

.panel:hover {
  background: conic-gradient(from var(--edge),
    #06f, #00d9ff, #7300ff, #ff00d0, #7300ff, #00d9ff, #06f);
  animation: edgeSpin 5s linear infinite;
}
.service-card:hover,
.testimonial-card:hover,
.feature-item:hover,
.pricing-card:hover,
.value-card:hover,
.stat-card:hover,
.info-card:hover {
  background-image: linear-gradient(var(--panel), var(--panel)),
                    conic-gradient(from var(--edge),
                      #06f, #00d9ff, #7300ff, #ff00d0, #7300ff, #00d9ff, #06f);
  animation: edgeSpin 5s linear infinite;
}

/* ---- the FAQ ------------------------------------------------------------
   A <details> snaps. The panel cannot be height-animated without script or a
   feature that is a year away from being safe, but the thing a reader notices
   is the snap of the text itself, and that is one keyframe. */
@keyframes faqOpen {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}
.faq-item[open] .faq-answer { animation: faqOpen .38s var(--ease) both; }

/* The heading that opens an FAQ sits over the middle of it, and so does the
   line under the heading. The list itself is a 52rem column centred in the
   wrap; a heading flush to the inline edge reads as the end of the section
   above rather than the start of the questions below. Scoped by the list it
   introduces, so every page with an FAQ gets it and no other heading moves. */
.wrap:has(> .faq-list) > h2,
.wrap:has(> .faq-list) > p.lede {
  text-align: center;
  margin-inline: auto;
  text-wrap: balance;
}
.wrap:has(> .faq-list) > p.lede { max-width: 46rem; }

/* Three things about the heading over an FAQ, all measured on the sixteen
   pages that have one — they came out identical on every page, because the
   markup is generated from the same shape.

   It sat 154px below the top of its block and only 40px above the first
   question, which is the wrong way round: it read as the tail of the section
   above rather than as the title of the list it introduces. The block's 100px
   of top padding comes down, and the 16px under the heading — a default h2
   margin nobody chose — goes up to meet the list.

   And it was breaking to two lines it did not need. `.block > .wrap > h2` caps
   every section heading on the site at 18 characters, which these exceed by a
   word: «ما يسأل عنه العملاء قبل التشغيل» measures 1096px against a 921px cap
   inside a 1280px wrap, so the room was there and the cap was refusing it.
   Released to the wrap's own width, it sets in one line; `balance` stays, so a
   longer heading on another page still divides evenly instead of stranding a
   word.
   The gap below the heading is declared on the list rather than on the
   heading, so there is one number rather than two adjoining margins collapsing
   into whichever happens to be larger — which is how the first attempt at this
   changed nothing at all. It is sized on the viewport's width, for the reason
   the closing button's gap is: how tall the window happens to be has nothing
   to say about how far a heading should sit from the list beneath it. The
   block's own top padding above keeps its `vh`, which is the unit every other
   section padding on this site is written in. */
.block.tight:has(.faq-list) { padding-top: clamp(40px, 5vh, 64px); }
.wrap:has(> .faq-list) > h2 { max-width: none; margin-bottom: 0; }
.wrap:has(> .faq-list) > .faq-list { margin-top: clamp(3rem, 4vw, 4.5rem); }

/* The +/− marker was #10b981 — the same stray green as the sector pills, six
   of them stacked down the last full screen of the page and the only colour on
   it that belongs to no gradient here. It now rotates instead of swapping
   glyph colour: one mark, turned a quarter turn when the answer opens, which
   also gives the row something to do besides change character. */
.faq-question::after {
  color: var(--white-40);
  transition: transform .3s var(--ease), color .3s var(--ease);
}
.faq-item[open] .faq-question::after {
  content: "+";
  color: #fff;
  transform: rotate(45deg);
}
.faq-item:hover .faq-question::after { color: #fff; }
/* The card itself, lit on hover and while open, so the open one is findable
   after scrolling past it. */
.faq-item { transition: border-color .3s var(--ease), background .3s var(--ease); }
.faq-item:hover { border-color: var(--white-20); }
.faq-item[open] {
  border-color: rgba(115,0,255,.45);
  background: rgba(115,0,255,.05);
}


/* ---- the relocated social links -----------------------------------------
   WhatsApp and LinkedIn, at the foot of the تواصل معنا column. style.css
   styles .social-links for the brand bar that no longer exists; this is its
   own class rather than a reuse, so deleting the old rules later touches
   nothing here. */
.footer-social { display: flex; gap: .625rem; margin-top: 1.25rem; }
.footer-social a {
  width: 38px; height: 38px; border-radius: 11px;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.05);
  border: 1px solid var(--white-10);
  color: var(--white-50); font-size: .95rem;
  transition: color .3s var(--ease), background .3s var(--ease),
              border-color .3s var(--ease), transform .3s var(--ease);
}
.footer-social a:hover {
  color: #fff; background: rgba(255,255,255,.10);
  border-color: var(--white-40); transform: translateY(-2px);
}
/* In their own colours. These two are not site chrome, they are other
   people's marks, and a reader scanning a footer for "is there a WhatsApp"
   finds green faster than they read a label. The tile picks the same colour up
   on hover so the whole target answers, not just the glyph — and the colour is
   on the glyph rather than the link, so the white the link takes on hover
   leaves it alone. */
.footer-social .fa-whatsapp { color: #25D366; }
.footer-social .fa-linkedin-in { color: #2D88D6; }
.footer-social a:has(.fa-whatsapp):hover {
  background: rgba(37,211,102,.15); border-color: rgba(37,211,102,.5);
}
.footer-social a:has(.fa-linkedin-in):hover {
  background: rgba(45,136,214,.18); border-color: rgba(45,136,214,.55);
}
/* The cursor.
   `* { cursor: auto }` is a reset with a side effect that reaches the whole
   site: `cursor` is an inherited property, and a universal selector re-declares
   it on every element, so the hand a link gets from the line below never
   reached the text inside that link. Every <h4> and <p> in the الحلول mega
   menu, and every word inside every card link, drew an I-beam over a target
   that opens a page — the pointer said "select this text" over something that
   is not text to select, and it changed shape as you crossed from the padding
   onto the word.

   The descendant selectors put the inheritance back explicitly. `a *` is one
   specificity point above the universal reset, which is all it takes. Form
   controls are then handed back their own cursors, because a text field inside
   a <label for> is genuinely a place to type. */
* { cursor: auto; }
a, button, [role="button"], .btn, summary, label[for],
a *, button *, [role="button"] *, .btn *, summary *, label[for] * { cursor: pointer; }
input, textarea, select { cursor: auto; }
input[type="text"], input[type="email"], input[type="tel"],
input[type="search"], input[type="url"], input[type="number"], textarea { cursor: text; }

/* A card whose only action is the link at the foot of it.
   The reader's target is the card — that is what the hover lifts, and what the
   gradient border lights up — but only the two words «اعرف المزيد» actually
   opened anything, so the pointer went to a hand for a fifth of the shape that
   was reacting to it. The stretched pseudo-element makes the whole panel the
   link's own hit area, so the hand is now telling the truth rather than
   decorating a dead surface. Anchored on :has() to panels that contain such a
   link, so a panel without one is untouched. */
.panel:has(.service-link) { position: relative; }
.panel:has(.service-link),
.panel:has(.service-link) * { cursor: pointer; }
.panel:has(.service-link) .service-link::after {
  content: ""; position: absolute; inset: 0; border-radius: 20px;
}

/* One line for «اعرف المزيد», on every card in the row.
   The cards are grid items and already stretch to a common height, but the
   link sat directly after a description that runs to two lines on one card and
   four on the next, so it landed at a different height in every one of them —
   six links on six different baselines across a row that is otherwise square.
   Making the inner a column and giving the last paragraph `margin-top: auto`
   drops it to the floor of whichever card it is in, and the floors are already
   level. */
.panel-inner:has(.service-link) { display: flex; flex-direction: column; }
.panel-inner:has(.service-link) > p:last-child { margin-top: auto; padding-top: 1.25rem; }

/* ---- the navbar, floating ----------------------------------------------
   No panel, no blur, no border: the mark and the links sit directly on the
   sky, at the reference's proportions — a tall wordmark hard against the
   outside edge, the links centred on the page as plain heavy text, nothing
   wearing a box.

   The bar has no fixed height. It is sized by the logo plus its padding, which
   is the only way the mark can grow without either clipping or leaving the
   links floating in dead space. */
.navbar {
  background: transparent !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  width: 100%;
  max-width: none;
  left: 0;
  transform: none;
  top: 0;
  /* One number owns the bar's height and the mark inside it, because the hero
     below subtracts that same number from the viewport. Let the two drift and
     the first screen is either short of the fold or past it. */
  height: var(--nav-h);
  padding-block: 0;
  padding-inline: clamp(1rem, 3vw, 3rem);
  transition: background .4s var(--ease), height .3s var(--ease);
}
.navbar.scrolled {
  background: linear-gradient(to bottom, rgba(4,3,14,.82), rgba(4,3,14,0)) !important;
  height: calc(var(--nav-h) * .78);
}
/* .navbar is a flex row from style.css, so .container is a flex item and sizes
   to its content unless told otherwise — which is why the whole bar used to
   huddle in 815px of a 1354px bar no matter what the row inside it did. */
.navbar .container { flex: 1; width: 100%; max-width: 120rem; padding: 0; height: 100%; }
/* The row rides a little above the middle of the bar. Padding on the bottom of
   the wrapper rather than an offset on it: the wrapper is border-box and its
   children are centred inside it, so shortening its content box lifts the mark
   and the links together by half the padding, and does it without giving the
   wrapper a `position` the mega menu would then resolve against.
   The mobile drawer is `position: fixed` and does not feel this at all. */
.navbar .nav-wrapper { height: 100%; padding-bottom: clamp(14px, 2vh, 24px); }
/* The mark alone, five pixels above the row it sits in.
   The padding above lifts the whole wrapper; this lifts only the logo inside
   it, which is the difference between "the bar is higher" and "the mark rides
   a little higher than the links". A transform rather than a margin so the
   grid track keeps its measured height and nothing else in the row moves. */
.navbar .logo { font-size: inherit; display: flex; margin-inline: 0; transform: translateY(-5px); }

/* The whole row is one centred group: mark, gap, links, gap, cart. That is
   what the reference actually does — measure its bar and the *content* is
   centred on the page (its midpoint lands within a pixel of the page's), with
   the mark sitting at the end of the group rather than at the edge of the bar.

   Two earlier attempts each got half of it. A `1fr auto 1fr` grid centred the
   links but left the mark pinned to the outer edge, 325px away. Anchoring the
   links a fixed fraction from the mark closed the gap but pushed the links off
   centre. Centring the group closes the gap *and* leaves the links within
   ~30px of the page centre, because the mark and the cart on either side are
   close enough in width to balance.

   One gap value for both sides, so the mark sits as far from the links as the
   cart does and the group reads as one object.

   Desktop only. Below 1024px style.css turns .nav-wrapper back into a flex row
   with the drawer and the hamburger, and that layout must win. */
@media (min-width: 1025px) {
  .navbar .nav-wrapper {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    /* The same gap the links use between themselves, so the mark reads as the
       first item in the row rather than as something parked near it. */
    column-gap: clamp(1.5rem, 2.4vw, 2.5rem);
  }
  /* `end`, not `start`. Both are the logical inside of the track, which in RTL
     is its left edge — the side the links are on. That one word is the whole
     difference between the mark hard against the outside of the bar and the
     mark sitting next to the links: the links stay centred on the page either
     way, and the empty space moves to the outside where the reference has it.
     The distance between them is then just the grid's column gap. */
  .navbar .logo { justify-self: end; }
  /* Centred, then nudged a little off centre — away from the mark, so the mark
     itself does not move. A transform rather than a margin for exactly that
     reason: it shifts the painted links without touching the grid, so the
     track the mark is anchored to stays where it is.
     `transform` takes no logical direction, so the two writing modes are
     spelled out. i18n.js flips <html dir> on language change, and the nudge
     flips with it: left in Arabic, right in English. */
  /* Centred, then nudged a little off centre — away from the mark, so the mark
     itself does not move.

     inset-inline-start rather than a transform, because it is the one offset
     that already knows which way the page reads: in Arabic the inline start is
     the right edge, so a positive value pushes the links left; in English it is
     the left edge and the same value pushes them right. Both directions from
     one declaration, and it follows i18n.js flipping <html dir> at runtime —
     which a transform did not, because a transform has no logical axis and the
     variable that would have given it one does not re-resolve on a dir change.

     Relative, so this moves the painted links without touching the grid: the
     track the mark is anchored to does not know this happened. */
  .navbar .nav-menu {
    justify-self: center;
    position: relative;
    inset-inline-start: clamp(12px, 1.6vw, 28px);
  }
  .navbar .nav-buttons { justify-self: start; margin-inline: 0; }
  /* The whole row — mark, links and the language control together — sits a
     fraction further from the outside edge than the bar's own padding puts it.

     A margin on the inline start rather than a transform on the wrapper, for
     two reasons. A transform would make .nav-wrapper a containing block for
     every absolutely-positioned descendant, which is the mega menu and the
     language popup; the note above this block already refuses to give the
     wrapper a `position` for that exact reason, and a transform is the same
     mistake spelled differently. And a margin is logical: in Arabic the inline
     start is the right edge, so it pushes the row left, and in English it pushes
     it right — the mirror image, which is what a mirrored layout wants, and it
     follows i18n.js flipping <html dir> at runtime without being told.

     The row moves by half this value, not all of it. The wrapper is
     `1fr auto 1fr`: the margin shifts its start edge by the full amount but also
     narrows it by the same amount, so each 1fr track loses half — which lands
     the mark, the centred links and the language control all on the same small
     leftward step instead of fanning them apart. */
  .navbar .nav-wrapper { margin-inline-start: clamp(20px, 2.6vw, 44px); }
}
.logo-img {
  height: calc(var(--nav-h) - 1.75rem);
  width: auto;
  transition: height .3s var(--ease);
}
.navbar.scrolled .logo-img { height: calc(var(--nav-h) * .78 - 1.5rem); }

/* The links. Solid white and heavy — they sit directly on the sky with no
   panel behind them, so weight is the only thing holding them up. No rule
   underneath and no pill around them: the reference's nav is text and nothing
   else.

   One size and one weight for every item in the row, the language control
   included; anything that reads as "the odd one out" reads as a button. */
.nav-menu { gap: clamp(1.5rem, 2.4vw, 2.5rem); }
.nav-link {
  color: #fff;
  font-weight: 700;
  font-size: var(--nav-link-size);
  letter-spacing: 0;
  padding: .5rem 0;
  transition: color .3s var(--ease), opacity .3s var(--ease);
}
.nav-link:hover { color: #fff; opacity: .62; }
.nav-link.active { color: #fff; opacity: 1; }
.nav-link::after, .nav-link::before { display: none; }

/* The الحلول panel, sized to its contents.

   style.css gives it a flat `width: 260px`, chosen when each row still carried
   a coloured icon tile and a line of description under the name. Both are
   `display: none` now, so a row is one short line of Arabic — the longest,
   «دليل الذكاء الاصطناعي», measures about 140px — and the panel was carrying
   roughly 60px of empty ground on each side of every item.

   `max-content` is the whole fix: the panel becomes exactly as wide as its
   widest row plus the padding, and it re-fits itself if an item is ever
   renamed or the font falls back — nothing here has to be re-measured by hand.
   The floor stops a two-word menu from collapsing into a sliver; the ceiling is
   a guard for a phone, where this panel goes full width anyway.

   The rows are centred with it. Left-aligned text inside a box that has been
   shrunk to fit reads as ragged rather than as a list, because there is no
   longer any column of white space on the right for the ragged edge to sit
   against. */
.dropdown-mega {
  width: max-content;
  min-width: 12rem;
  max-width: min(92vw, 20rem);
}
.dropdown-item { justify-content: center; padding-inline: 1.35rem; }
.dropdown-text { text-align: center; }

/* The chevron beside الحلول, dropped 1px below the line box's centre.
   It was lifted 2px on the theory that Arabic reads high; rendered side by side
   at six times scale against −2, 0, +1, +2 and +3, the opposite is true. The
   ink box of «الحلول» is tall because ا and ل reach for the ascender, but the
   weight of the word is the row of bowls along the baseline, and a chevron on
   the centre of that box floats above the thing it belongs to. +1px puts it on
   the join. The rotation on hover has to repeat the offset, because a transform
   replaces the whole list. */
.nav-dropdown > .nav-link .dropdown-arrow { transform: translateY(1px); }
.nav-dropdown:hover > .nav-link .dropdown-arrow { transform: translateY(1px) rotate(180deg); }

/* The mega menu keeps its own surface — it is a menu, not chrome. */
.dropdown-mega {
  background: var(--panel) !important;
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--white-10) !important;
  border-radius: 20px;
  box-shadow: 0 30px 90px rgba(0,0,0,.6);
}
/* The gaps too. Every link inside the menu now carries the hand, but the
   panel's own padding and the gutters between the rows are not links and fell
   back to the reset — so the pointer still flickered on the way from one row
   to the next. The menu is a menu the whole way across. */
.dropdown-mega, .dropdown-mega * { cursor: pointer; }
.dropdown-item { color: #fff; }
.dropdown-item:hover { background: rgba(255,255,255,.06); }
.dropdown-text h4 { color: #fff; }
.dropdown-text p { color: var(--white-50); }

/* What the pointer does inside it.

   The old hover was written for the panel this one used to be: a flat 6% wash
   edge to edge, and a 2px accent bar hard against the inline start. Neither
   survived the panel being shrunk to fit and its rows being centred — the bar
   ended up hugging a border 60px from the word it was marking, and a
   full-bleed wash inside a rounded panel squares off its own corners.

   So the accent stops being a bar and becomes the fill: the same violet that
   was in the 2px stripe, spread across a pill inset from the panel's edge, on
   the page's own left-to-right gradient. The pill is the whole answer — it
   changes nothing but colour, which matters here because the panel is
   `max-content` and any hover that moved or resized a row would resize the
   whole menu.

   A second gesture used to ride along with it: a gradient rule under the name
   that swept out from the middle on hover. It is gone at the user's request —
   one mark per row is the point, and two of them arriving together on a
   six-row menu was noise. Its 4px `padding-bottom` on .dropdown-text went with
   it: that gutter existed only to hold the rule off the descenders, and left
   behind it sat the label 2px above the pill's centre.

   Desktop only: below 1025 this panel is a section of the full-screen drawer,
   where there is no pointer to answer and the rows are already spaced for a
   thumb. */
@media (min-width: 1025px) {
  .dropdown-item {
    margin-inline: .5rem;
    padding-block: 10px;
    border-radius: 12px;
  }
  .dropdown-item:hover { background: transparent; }
  /* Both ancestors are in the selector for the specificity, not the scope.
     The rule that pins this to `right: 0` in Arabic is
     `html[dir="rtl"] .dropdown-item::before` — an attribute, a class and two
     elements — and anything shorter loses to it, leaving the pill anchored to
     one edge with `width: auto` resolving to zero. It reads as the hover
     simply not working. */
  .dropdown-mega .dropdown-grid .dropdown-item::before {
    inset: 0;
    width: auto;
    border-radius: inherit;
    /* Brightest in the middle, under the middle of the name, and the two
       brand ends falling away from it. Symmetric on purpose: a gradient with
       a strong end has a direction, and this panel is read right to left in
       Arabic and left to right in English out of the same declaration. */
    background: linear-gradient(90deg,
      rgba(115,0,255,.14), rgba(184,29,245,.30) 50%, rgba(255,0,208,.16));
    opacity: 0;
    transition: opacity .28s var(--ease);
  }
  .dropdown-mega .dropdown-grid .dropdown-item:hover::before { opacity: 1; }
}

/* ---- the language switch ------------------------------------------------
   It sits in the link row, immediately after تواصل معنا, and is styled to be
   indistinguishable from the links either side of it: no border, no fill, no
   pill. The only thing that marks it as a control is the caret, and the only
   thing that marks it as a language is that it says which one you are reading.

   It is still a <button>. Text that opens a menu has to be reachable by
   keyboard and announce its state, so the element is right even though it must
   not look like one. */
.lang-switch { position: relative; display: flex; }
.lang-switch__toggle {
  display: inline-flex; align-items: center; gap: .45rem;
  height: auto; padding: .5rem 0;
  background: none; border: 0; border-radius: 0; box-shadow: none;
  color: #fff;
  font-family: inherit;
  font-size: var(--nav-link-size);
  font-weight: 700;
  line-height: inherit;
  transition: opacity .3s var(--ease);
}
.lang-switch__toggle:hover,
.lang-switch__toggle[aria-expanded="true"] { opacity: .62; background: none; border: 0; }
/* Offset for the same reason as the chevron beside الحلول, and by the same
   amount so the two carets in the bar sit on one line as each other. */
.lang-switch__caret {
  width: 13px; height: 13px; flex: none; fill: none;
  stroke: #fff; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  transform: translateY(1px);
  transition: transform .3s var(--ease);
}
.lang-switch__toggle[aria-expanded="true"] .lang-switch__caret { transform: translateY(1px) rotate(180deg); }

.lang-switch__menu {
  /* Centred on the toggle rather than hung off its trailing edge, which put a
     10rem panel visibly off to one side of a much narrower word. `left` and a
     physical translate rather than a logical inset, because the centring is
     the same in both directions and a logical property here would flip it. */
  position: absolute; top: calc(100% + .5rem); left: 50%;
  min-width: 10rem; margin: 0; padding: .375rem;
  list-style: none;
  background: var(--panel);
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--white-10); border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0,0,0,.6);
  opacity: 0; visibility: hidden; transform: translateX(-50%) translateY(-6px);
  /* visibility is deliberately not in the transition list. Transitioning it
     leaves the computed value at `hidden` for the first frame in a way that is
     genuinely hard to reason about; flipping it instantly and fading only the
     opacity gives the same effect and is a state you can actually inspect. */
  transition: opacity .25s var(--ease), transform .25s var(--ease);
  z-index: 1100;
}
.lang-switch__toggle[aria-expanded="true"] + .lang-switch__menu {
  opacity: 1; visibility: visible; transform: translateX(-50%);
}
/* The language you are reading is a filled pill; the other is plain text. That
   is the reference's menu exactly, and it says which one is current without a
   tick mark to read — the fill is the state. */
.lang-switch__menu button {
  display: flex; align-items: center; justify-content: center;
  width: 100%; padding: .55rem .9rem;
  background: none; border: 0; border-radius: 10px;
  color: var(--white-70); font: inherit; font-size: .9375rem; font-weight: 700;
  text-align: center;
  transition: background .2s var(--ease), color .2s var(--ease);
}
.lang-switch__menu button:hover { background: rgba(255,255,255,.08); color: #fff; }
.lang-switch__menu button[aria-current="true"] { background: #fff; color: #04030e; }
.lang-switch__menu button[aria-current="true"]:hover { background: #fff; color: #04030e; }

.hamburger span { background: #fff; }
.btn-cart { color: #fff; }
.btn-cart:hover { color: var(--c-cyan); }

/* ---- buttons ------------------------------------------------------------ */
.btn {
  border-radius: 999px;
  border: 0;
  font-weight: 800;
  letter-spacing: .06em;
  transition: box-shadow .3s var(--ease), background .4s var(--ease), color .4s var(--ease);
  overflow: visible;
}
.btn::before, .btn::after { display: none !important; }   /* the old shimmer sweeps */

/* The icon inside a button.
   Icons are masks sized `height: 1em` with Font Awesome's own `-.125em`
   baseline offset (see icons.css), which is drawn for Latin type. Beside
   Arabic — which has no descender on most letters and sits optically higher —
   that offset drops the arrow below the middle of the word next to it. A
   smaller box and a shallower offset put it back on the optical centre, and
   because it is declared once here every button on the site agrees: the
   landing call to action, `ابدأ الآن` on the five solution pages, the sector
   pages, and the contact form's send button. */
.btn i, .btn .fa, .btn .fas { font-size: .875em; vertical-align: -.02em; }

/* Nothing moves on hover. This used to be `scale(1.05)`, which on the landing
   call to action is 11px of growth under the pointer, and it stacked on top of
   a 44px glow and a gradient sweep — three answers to one gesture.

   It has to say `none` rather than not be here at all: style.css and
   design-system.css both carry a legacy `.btn-primary:hover { transform:
   translateY(-2px) }`, and deleting this line would not remove the movement,
   it would swap one for another. Same specificity, theme.css loads last, so
   this is what wins. */
.btn:hover { transform: none; }

/* The gradient no longer sweeps on its own, and this was the single most
   expensive line on the site.

   `background-position` is not a property the compositor can animate. Chrome
   has to re-run style resolution and repaint the element on the main thread
   for every frame — and because a main-thread animation drives the document's
   whole rendering lifecycle, it also re-runs PrePaint, Layerize, Commit and
   every IntersectionObserver on the page, sixty times a second, forever,
   whether or not anyone is looking at the button.

   Traced on a throttled 360px phone over six idle seconds, with nothing
   touching the page: 357 full lifecycles, 977 ms of style recalculation,
   708 ms of paint — about 4,000 ms of a 6,000 ms window, two thirds of the
   main thread, spent on a decoration. Switching off this one animation and
   nothing else took the renderer's main thread to 139 ms. The orbs and the
   marquee kept running the whole time: those animate `transform`, so they
   live on the compositor and cost the main thread nothing.

   Twenty-three of the twenty-seven pages carry a .btn-primary, and the sector
   pages paired it with eleven .text-gradient sweeps, so this was not a landing
   page problem — it was every page, on every device, the entire time.

   The at-rest frame is the only frame now: the sweep that had moved to hover
   is gone too, as one of the three things the hover was saying at once. */
.btn-primary {
  color: var(--color-bg);
  background: linear-gradient(80deg, var(--gradient-colors));
  background-size: 200% 200%;
  background-position: 0% 50%;
  box-shadow: none;
}

/* One answer to the pointer, and it is the quietest of the three that used to
   arrive together. The 44px glow was a halo standing a button's height clear
   of the pill on every side; this is the same violet pulled in tight and
   dropped 6px, so the pill reads as lifted off the page rather than lit up.

   No media gate on it. A shadow fading in is a colour change, not movement —
   there is nothing here for `prefers-reduced-motion` to reduce, and unlike the
   old sweep (which was behind `hover: hover` and `no-preference`) it does not
   leave a reader who asks for less motion with no feedback at all.

   The colour is pinned through the hover because design-system.css has a bare
   `a:hover { color: var(--primary) }`, and an element plus a class outranks
   the single class on `.btn-primary` — so every button on the site that is an
   anchor flipped its label to magenta under the pointer, over a cyan fill.
   `.btn-primary:hover` is two classes and takes it back. The same is still
   true of .btn-secondary and .btn-ghost; they are not this section's to fix. */
.btn-primary:hover {
  color: var(--color-bg);
  box-shadow: 0 6px 22px rgba(115,0,255,.35);
}

.btn-secondary, .btn-ghost {
  background: transparent;
  color: #fff;
  border: 1px solid var(--white-20);
}
.btn-secondary:hover, .btn-ghost:hover { background: rgba(255,255,255,.08); border-color: var(--white-50); }

.btn-large { padding: 1.1rem 2.6rem; font-size: .9375rem; }
/* The one at the foot of a page is a size down. It is the only thing on the
   screen at that point, which is exactly why it does not need to be the widest
   thing on it — a pill that reads as a banner reads as an advertisement. */
/* Heavier, larger type in a smaller pill.
   The weight is 900 rather than 800 — Tajawal has a 900 and the build fetches
   it, so this is a real face and not a browser-synthesised smear. The type is
   17px against the 14px of every other .btn-large on the site, and the padding
   has come in to meet it: the word is what should be growing here, not the
   box around it. A pill that reads as a banner reads as an advertisement, and
   this one is the only thing on the screen at that point — it does not need
   width to be found. */
.cta-section .btn-large {
  padding: .72rem 1.15rem;
  font-size: 1.0625rem;
  font-weight: 900;
  line-height: 1.5;
}

/* The button sat directly under the closing sentence, on nothing but a
   paragraph's default margin — about 16px, which is less air than the sentence
   has above it. It read as attached to the copy rather than as the thing the
   copy is asking you to do. This is every closing block on the site: the
   markup is the same nineteen times over, a .lede followed by the paragraph
   that holds the button, so one rule moves all of them.

   Sized on the viewport's width, not its height: a `vh` term here makes the
   distance between a sentence and its button depend on how tall the window
   is, which is nothing to do with it, and on a 1000px-tall screen it lands on
   the same number the clamp's floor would have given anyway. */
.cta-section p.lede + p { margin-top: clamp(2.5rem, 3.2vw, 3.5rem); }

/* «لماذا تتبنّى الشركات السعودية الذكاء الاصطناعي الآن؟», on the same axis as
   everything above it. It was the one section on the page whose heading and
   lede were still set to the inline start while the rail, the showcase, the
   sector grid and the ask were all centred — so the eye, coming down a page
   that had settled into one axis, hit a block that started somewhere else.

   Both halves need the child chain in the selector, not a bare class:
   `.block > .wrap > h2` and `p.lede` already own the max-width on these two
   elements, and each outranks a single class. Those widths are load-bearing
   rather than decorative — at 1280 the 26ch cap holds the heading to 1183px
   inside a 1216px column, and the lede keeps its inherited 36rem — so it is
   `margin-inline: auto` that puts each narrowed box on the centre line and
   `text-align: center` that centres the lines inside it. Lose the auto margin
   and the text centres inside a box still flush against the edge, which reads
   as no centring at all. */
.ksa-focus > .block > .wrap > h2 {
  max-width: 26ch;
  margin-inline: auto;
  text-align: center;
  text-wrap: balance;
}
.ksa-focus > .block > .wrap > p.lede {
  margin-inline: auto;
  text-align: center;
}

/* ---- sections and chrome ------------------------------------------------
   Sections stop painting their own backgrounds; the backdrop is the only
   ground on the page. */
section, .hero, .footer, main,
.services-overview, .why-choose-us, .testimonials, .ksa-focus,
.faq-section, .cta-section, .page-hero, .section-padding {
  background: transparent !important;
}
section::before { display: none; }
.section-header::after { background: linear-gradient(90deg, #7300ff, #ff00d0); }

/* The foot of the page. The last block paid a full section's padding under its
   button and the footer paid its own on top of that — 257px of nothing between
   the last thing on the page you can click and the first thing in the footer,
   which read as the page having ended early.

   Both ends are now cut. The top too: the FAQ above it already ends on a
   .block.tight's own padding, so the gap between the last answer and «جاهزون
   حين تكون أنت جاهزاً» was two full section paddings stacked. One of them is
   enough to separate them. */
/* The 64px nobody could find.
 *
 * design-system.css sets `section { padding: var(--spacing-2xl) 0 }` — 32px at
 * each end of every <section> on the site. Every section here that has spacing
 * to declare declares it on the .block inside it, so that 32px is added to a
 * number that was already chosen deliberately, at both ends, invisibly. It is
 * why the gap above «جاهزون حين تكون أنت جاهزاً» survived being cut twice: the
 * two paddings being trimmed were only two of the four in the stack.
 *
 * Scoped with :has() to sections that own a .block, so a <section> elsewhere
 * that genuinely relies on the default keeps it. The sheet already depends on
 * :has() for the 2x2 card grid. */
section:has(> .block) { padding-block: 0; }

.cta-section .block {
  padding-top: clamp(24px, 3vh, 40px);
  padding-bottom: clamp(28px, 4vh, 48px);
}
/* No rules anywhere in the footer. The border here, the two .footer-divider
   elements and the one over the copyright row were four horizontal lines in
   the last screen of the page; the column headings and the spacing already
   say where each group starts. */
.footer {
  border-top: 0;
  /* Nothing under the last row. style.css gives .footer-bottom its own 25px of
     bottom padding, and the page ends there — a second helping from the footer
     itself just left a band of empty ground below the copyright with nothing
     in it. */
  padding-block: 1.25rem 0;
}
/* The brand bar used to sit above the columns and pay for the space over them.
   With it gone, the footer's own top padding and the grid's stacked into 166px
   of nothing between the last button on the page and the first heading in the
   footer. Both are cut; the columns start where the page ends. */
.footer-grid { padding-block: 1.75rem 2.25rem; }
.footer h4, .footer h3 { color: #fff; }
.footer a, .footer p, .footer li { color: var(--white-50); }
.footer a:hover { color: #fff; }
/* 25px above and below in style.css, for a row that is one line of type and
   three small marks.

   The bottom was cut to .5rem to close a gap that used to sit under the
   copyright, and that went too far: with the row itself being three groups of
   different heights — a line of type, three logo marks, two links — half a rem
   under the tallest of them reads as the page having been trimmed rather than
   ended. 2.25rem is roughly the height of the badge marks again, which is the
   amount of ground the row needs to sit on rather than teeter on. */
.footer-bottom { border-top: 0; color: var(--white-40); padding-block: 1.25rem 2.25rem; }

/* Three groups on one line, or three lines. Not two and a half.

   The row is `1fr auto 1fr`: the copyright, the three verification marks, the
   two legal links. Together they want about 1,090px, and the container is
   1,168 at its widest and shrinks with the window below 1,216 — so somewhere
   around 1,200 the middle track stops being able to hold the marks and the tax
   number drops onto a line of its own, which reads as a mistake rather than as
   a wrap. style.css already stacks this row on a phone for the same reason;
   this is the same rule, at the width the arithmetic actually runs out. */
@media (max-width: 1200px) {
  .footer-bottom {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
    gap: 1rem;
  }
  .footer-bottom > p,
  .footer-bottom .trust-badges,
  .footer-bottom .footer-links { justify-self: center; }
}

/* One row, always. The copyright is two elements — the year and the
   translatable «جميع الحقوق محفوظة» — and in the 1fr track it was given they
   wrapped between them at anything short of a wide desktop. */
.footer-bottom > p { white-space: nowrap; }

/* The verification marks, unboxed. style.css wraps them in a tinted, blurred,
   bordered pill and rules the three apart with vertical hairlines; all of that
   is off, so they sit on the page itself with nothing but space between them.
   Overriding rather than editing style.css keeps the older sheet as the one
   place the badge markup is described. */
.footer-bottom .trust-badges {
  background: none;
  border: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-radius: 0;
  padding: 0;
  /* Tighter than it was. The bottom row is three groups in `1fr auto 1fr`,
     and the two legal links now sit side by side instead of stacked — which
     took 111px out of the middle track and left the three marks 13px short of
     a single line, so the tax number dropped onto a row of its own. At this
     spacing they need 629 of the 640 they get on a 1440 desktop, and they
     also stop wrapping on the narrower ones, where 2rem never fitted either. */
  gap: clamp(.75rem, 1.4vw, 1.25rem);
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
}
.footer-bottom .trust-badge {
  padding: 0;
  border-radius: 0;
  background: none;
}
/* `border`, not `border-inline-start`. The hairline between the marks is a
   vertical rule on a wide screen and style.css swaps it for a horizontal one
   below 768px, where the three badges stack — so cancelling only the inline
   edge left the phone with two rules across the footer that the desktop no
   longer had. */
.footer-bottom .trust-badge + .trust-badge { border: 0; }
.footer-bottom .trust-badge:hover { background: none; }

input, textarea, select {
  background: rgba(255,255,255,.04);
  border: 1px solid var(--white-10);
  color: #fff;
}
input:focus, textarea:focus, select:focus {
  border-color: var(--white-40);
  background: rgba(255,255,255,.07);
  box-shadow: none;
  outline: none;
}
input::placeholder, textarea::placeholder { color: var(--white-40); }

/* ---- the hero -----------------------------------------------------------
   The hero paints nothing: the backdrop's top band is its background. Its job
   is to hold two things apart from each other — the copy at the top, the model
   through everything below it.

   That split is the whole reason the copy is not centred. The model is framed
   on the point where the two hands almost touch, and that point is the dead
   centre of the frame by construction. Centre the headline too and they occupy
   the same pixels; the previous build spent a scrim and a reduced type scale
   fighting exactly that. Pinning the copy to the top leaves the rest of the
   frame to the gesture.

   flex-start rather than space-between: the calls to action that used to hold
   the bottom of the column are gone, and space-between with one child is
   flex-start with a misleading name on it. */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  /* The bar is sticky and therefore still in the flow, so the hero's share of
     the first screen is the viewport less the bar. Padding by --nav-h *as
     well* is what put the calls to action a hundred pixels below the fold. */
  min-height: calc(100vh - var(--nav-h));
  min-height: calc(100svh - var(--nav-h));
  padding-block: clamp(1rem, 4vh, 3rem) clamp(2rem, 6vh, 4rem);
  overflow: visible;
}
/* The centrepiece. Behind the copy, in front of the backdrop, faded in by
   hero-3d.js once the model has actually decoded — a canvas that appears
   mid-composition reads as a glitch. The bottom of the frame is masked away so
   the forearms dissolve into the field rather than ending at the section edge. */
/* The portrait mask, and the portrait lift, in the model's own coordinates.

   The live canvas and the poster now frame the pair identically — see the
   poster rule below — so one set of stops describes both, and the stops are
   arithmetic off the model rather than eyeballed off a screenshot. The mesh
   bounds (marble 2.14..12.46, steel -8.23..2.02) and the framing in
   tools/hero-3d/main.js fix every landmark as a fraction of the box, and
   portrait fits the height, so these hold at every phone size:

     marble forearm's flat cut end   30.4%
     wrist, then the palm            41.6%
     fingertips almost touching      71.0%
     the steel hand                  74.0% - 85.0%
     steel forearm's cut            111.2%   (below the box, never on screen)

   Only one of the two cuts can be pushed off the frame — the mesh is 20.7
   units tall in a 25.6-unit box — and the one left inside is the marble
   forearm's, so the top of the field has to dissolve. That is the whole reason
   this mask exists, and it is also why the two ends cannot be identical: the
   steel forearm runs off the bottom and needs no dark at all, while the marble
   one ends in a cut that does.

   What can be identical is the shadow. Both ramps are ten points long with the
   same curve mirrored, and they cost each arm the same share of its visible
   length — the marble loses 30.4-44.5 out of 30.4-71.0, the steel loses 90-100
   out of 71.0-100, 35% each. And the bottom ramp holds full light two points
   further down than it used to, so the steel forearm now keeps two thirds of
   its visible length at full strength where it kept three fifths.

   What was here before was measured off the poster at a time when the poster
   was being drawn nearly a third smaller than the live canvas and a fifth of
   the frame lower, so on the canvas it landed nowhere near the arms it was cut
   for: nothing at all above 46% and full light only at 68%, which held the
   marble forearm — cut, shaft and wrist — at or near zero and started the arm
   at the fingers, while the steel one below kept its light to 88%. That is the
   asymmetry this fixes. */
:root {
  --hero-mask-portrait: linear-gradient(to bottom,
    transparent 0%, transparent 34.5%, rgba(0,0,0,.45) 39%, #000 44.5%,
    #000 90%, rgba(0,0,0,.45) 95.5%, transparent 100%);
  /* And the gesture sits six points higher in the frame. Applied to the box
     rather than to the framing inside the module, so the mask travels with the
     model and the stops above stay in the model's coordinates instead of the
     screen's — move one and the other follows. */
  --hero-lift-portrait: -6%;
  --hero-fit-portrait: 1;
}
/* The one thing the stops above cannot know: the copy is a fixed number of
   pixels tall and the frame is not. The headline and its strapline take about
   205px off the top of the box whatever the phone, so on a 568-tall screen
   they own 43% of the frame where on an 844-tall one they own 26% — and 34.5%,
   which is well clear of the type on the big screen, lands behind it on the
   small one. White marble under white type, which is the one thing the mask
   exists to prevent.

   So on a short frame the model is drawn smaller, anchored to the floor. That
   is exactly what `contain` used to do for the poster by accident, made
   deliberate and extended to the canvas, and because the box is scaled rather
   than the model inside it, the mask scales with it: the composition is
   identical, just smaller, and the arms keep the symmetry the stops give them.
   The lift comes off at the same time — there is nowhere to lift to. */
@media (max-height: 700px) {
  :root { --hero-lift-portrait: 0%; --hero-fit-portrait: .8; }
}
.hero-model {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.6s var(--ease);
  -webkit-mask-image: linear-gradient(to bottom,
    #000 0%, #000 72%, rgba(0,0,0,.55) 88%, transparent 100%);
  mask-image: linear-gradient(to bottom,
    #000 0%, #000 72%, rgba(0,0,0,.55) 88%, transparent 100%);
}
.hero-model.is-ready { opacity: 1; }
/* The still. Some machines answer a WebGL request and then rasterise it on the
   CPU, and some have no WebGL at all; on both, hero-3d.js gives up rather than
   run a scene at two frames a second. Without this the hero below the strapline
   is simply empty, which is worse than a picture of the thing it is about. The
   class is set by the script on exactly those two paths, so the file is
   requested only by the visitors who need it. Same framing as the live render,
   under the same mask, so nothing else about the hero changes. */
.hero-model.is-poster {
  background: url("/assets/images/hero-model-poster.webp") center bottom / contain no-repeat;
}
/* And the upright framing, on the same 0.85 the module switches at. The live
   model reframes itself for a portrait container — the pair stands up and the
   human forearm enters from the top — so a poster cut from the landscape view
   lies across the bottom of a phone as a thin strip with the frame empty above
   it. Two files, because it is two compositions, and the phone one is the
   smaller of them. The mask is the portrait mask for the same reason it exists
   on the live canvas: the forearm has to rise out of the dark rather than stop
   in it.

   `auto 100%`, and anchored to the top, rather than `contain` from the bottom.
   This file is not a picture of the model, it is a render of the portrait
   frame — 760x1050 is the frame, cropped at its own edges — so the only
   placement that reproduces the live shot is the one that maps the file's
   height onto the box's height. `contain` fits the width instead, which on a
   390-wide phone drew it 538 tall in a 756 box and then sat it on the floor:
   a quarter smaller than the canvas and a fifth lower, with the arms landing
   where the mask was not. It overflows sideways now, by design — the pair is
   6 units wide in a frame 25.6 tall, so the arms occupy the middle third and
   the crop takes empty field off both sides. Measured 320 through 768: it
   never reaches them, and by 768 the file is narrower than the box and simply
   centres in it. */
@media (max-aspect-ratio: 85/100) {
  .hero-model.is-poster {
    background-image: url("/assets/images/hero-model-poster-portrait.webp");
    background-size: auto 100%;
    background-position: center top;
    -webkit-mask-image: var(--hero-mask-portrait);
    mask-image: var(--hero-mask-portrait);
    transform: translateY(var(--hero-lift-portrait)) scale(var(--hero-fit-portrait));
    transform-origin: 50% 100%;
  }
}
.hero-model canvas { display: block; width: 100%; height: 100%; }
/* Portrait stands the pair upright, and there the human forearm's cut end
   falls inside the frame — always, at every phone size, roughly a third of the
   way down. It cannot be pushed off the top the way the landscape ones are
   pushed off the sides: to reach the top edge the arm has to pass through the
   headline, and white marble under white type costs more than the cut does.
   So the top of the field dissolves exactly as the bottom already does, and
   the arm rises out of the dark instead of stopping in it. The class comes
   from hero-3d.js, which is the only thing that knows which framing is live. */
.hero-model.is-portrait {
  -webkit-mask-image: var(--hero-mask-portrait);
  mask-image: var(--hero-mask-portrait);
  transform: translateY(var(--hero-lift-portrait)) scale(var(--hero-fit-portrait));
  transform-origin: 50% 100%;
}

/* Inner pages get the same band of sky, but only enough of it to hold a
   headline — a full viewport of hero on a service page is a full viewport
   between the visitor and what they came for. */
.hero--page {
  min-height: auto;
  justify-content: center;
  padding-block: clamp(3rem, 12vh, 7rem) clamp(40px, 8vh, 90px);
}
.hero--page .hero-title { font-size: clamp(2rem, 4.4vw, 3.5rem); }

/* The line under the headline is a different animal on an inner page. On the
   homepage it is a one-line promise, which is what `white-space: nowrap` and
   weight 800 below are for. Here it is a paragraph — two clauses and about two
   hundred characters — and nowrap turned it into a single 1,900px line that
   overflowed the viewport at both ends. A centred line wider than its own box
   overflows both sides equally, which is exactly why it read as off-centre
   rather than as too long: the fault looked like alignment and was measure.

   So on an inner page it wraps, into a measure a paragraph can be read at, and
   it is set like the lede it actually is rather than like a headline's second
   half. */
.hero--page .hero-description {
  white-space: normal;
  max-width: 46rem;
  margin-inline: auto;
  font-size: clamp(1rem, 1.2vw, 1.18rem);
  font-weight: 300;
  line-height: 1.85;
  letter-spacing: 0;
  color: var(--white-70);
  text-wrap: pretty;
}

.hero .container { position: relative; z-index: 3; text-align: center; }
.hero-content { position: relative; max-width: none; margin-inline: auto; }

/* The headline. 14em rather than the reference's 12: the second line is now a
   full clause — «يُبنى لمؤسستك، لا يُباع لها» — and at 12em it broke after
   «يُباع», stranding «لها» alone on a third line. 14em holds it on two. */
.hero-title {
  max-width: 14em;
  margin: 0 auto .75em;
  font-size: clamp(2.25rem, 5.6vw, 4.8rem);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -.01em;
  color: #fff;
}
/* The second line carried a hand-drawn marker stroke under it, ported from the
   reference. It is gone by request — markup, stroke and animation — and the
   span is kept only because it is what the two title fragments hang on. */
.hero-title .highlight { display: inline-block; position: relative; }

/* The strapline. Set to the headline's own style — same weight, same tracking,
   same solid white — so the two read as one block of type in two sizes rather
   than a title with a caption under it. Only the size differs.

   One line, by request: the max-width that used to wrap it is gone and nowrap
   holds it. That is safe above 900px because .hero is a column flex with
   align-items: center, which makes .container shrink-to-fit — so the container
   widens to the line rather than the line overflowing the container — and
   because the size is driven by vw: the sentence measures 45.6 times its own
   font size, and 1.55vw keeps that at 71% of the viewport. Below 900 the clamp
   floors out and the ratio stops holding, so it wraps again; see RESPONSIVE.

   The dark well that used to sit behind it is gone with it. It existed to buy
   contrast back over the forearms; at weight 800 in solid white the type holds
   its own, and the model now sits low enough to be clear of this line. */
.hero-description {
  margin-inline: auto;
  max-width: none;
  white-space: nowrap;
  font-size: clamp(1.0625rem, 1.55vw, 1.4rem);
  font-weight: 800;
  letter-spacing: -.01em;
  line-height: 1.2;
  color: #fff;
}

/* ==========================================================================
   THE PHOTOGRAPHIC INNER PAGES — the sectors, and الحلول
   --------------------------------------------------------------------------
   Ten sector pages and five under الحلول that were entirely type in boxes now
   carry photographs: the room the reader works in, behind the headline; and one
   beside each of two headings in the middle of the page. The classes are named
   for the sectors because that is where they were written; they are the layout
   for a photographic inner page of either family now, and are shared rather
   than duplicated under a second name.

   The sources are what decided the shapes, not the other way round. They run
   from 236px wide to 1408px and from 1.83 landscape to 0.56 portrait, which
   rules out the obvious layout — a full-bleed 21:9 band would have meant
   scaling a 736px portrait to 2,400px and cropping four fifths of it away. So
   the wide one is a BACKGROUND, where `object-fit: cover` takes any aspect and
   a scrim hides the upscale; and the two that are looked at directly are held
   at about 500px, which every source can carry sharply.
   ========================================================================== */

/* The hero photograph. It sits above .site-backdrop — which is a page-level
   element at z-index 0 — and below .hero .container at 3, so the starfield is
   simply not seen on these pages and the headline is never over bare
   photograph. */
/* The photograph starts at the top of the DOCUMENT, not under the bar. The
   navbar is sticky, so it keeps its own height at the top of the flow and the
   hero used to begin below it — which left a band of the site's own violet sky
   above every sector photograph, and the bar read as sitting on a coloured
   panel rather than on the picture. Pulling the section up by exactly the bar's
   height and giving that height straight back as padding puts the photograph
   behind the bar without moving the headline a pixel; the min-height grows by
   the same number so the band below the bar stays the size it was. The bar
   itself is already transparent — it is this that makes it look it. */
.hero--photo {
  /* The band is the first screen, not a strip at the top of it. At 56vh the
     photograph stopped about 630px down and left a dead third between it and
     the first heading — read as the picture running out rather than as space.
     `svh` so the phone's collapsing address bar cannot make it jump, `vh` first
     for the browsers that do not know the unit.

     No nav-h in the sum: the section starts at the top of the document now, so
     100svh IS the first screen, bar included. */
  min-height: 100vh;
  min-height: 100svh;
  margin-top: calc(var(--nav-h) * -1);
  /* The bar's height plus the hero's own top space, and a deeper bottom to
     answer it: the box is centred on its content box, so equal padding would
     sit the headline half the bar's height below the middle of the screen. */
  padding-top: calc(var(--nav-h) + clamp(2rem, 5vh, 3.5rem));
  padding-bottom: clamp(5rem, 14vh, 9rem);
}
/* Over a photograph the type needs a little more than it needs over the
   page's own ground: the headline gets a shadow that is invisible on the dark
   parts and holds it together over the bright ones, and the line under it goes
   from the site's 70% white to nearly full, because 70% over a photograph is
   not quiet, it is faint. */
.hero--photo .hero-title { text-shadow: 0 2px 34px rgba(0,0,0,.75); }
.hero--photo .hero-description {
  color: rgba(255,255,255,.88);
  text-shadow: 0 1px 22px rgba(0,0,0,.85);
}
.hero-photo {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  /* The bottom edge is masked away, and this is the whole reason the band no
     longer ends in a line. The scrim used to close on solid #04030e while the
     page under it carries the orb field at about rgb(8,3,19) — measured, the
     same +4R/+5B step on every one of the ten, in a single row, straight across
     1,900px. Small numbers, but the eye finds a perfectly straight edge no
     matter how small the step is, and darkening one side to match only moves
     the line.

     So the element stops painting instead. Alpha runs out over the last 38% and
     is flat zero for the final 8%, which is the part that matters: measured,
     this mask does not behave like a clean ramp near its own end — hold a
     little alpha at the last row and Chrome paints it close to full strength,
     so the fade has to be over and done with before the edge arrives rather
     than arriving at nothing exactly there. Eight per cent of dead mask is what
     makes the boundary read as zero on the meter instead of one.

     The scrim below finishes the job: it closes on the page's own ground colour
     while there is still alpha to see it, so what the mask switches off is
     already the same colour as what it reveals. */
  -webkit-mask-image: linear-gradient(to bottom,
    #000 0%, #000 62%, rgba(0,0,0,.70) 78%, rgba(0,0,0,.30) 86%,
    rgba(0,0,0,0) 92%, rgba(0,0,0,0) 100%);
  mask-image: linear-gradient(to bottom,
    #000 0%, #000 62%, rgba(0,0,0,.70) 78%, rgba(0,0,0,.30) 86%,
    rgba(0,0,0,0) 92%, rgba(0,0,0,0) 100%);
}
.hero-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Pulled down and desaturated before the scrim even lands. Several of these
     rooms are lit white — a showroom, a supermarket aisle, a clinic corridor —
     and a scrim alone could not get them under white type without turning into
     a grey sheet with a photograph faintly behind it. Only far enough down to
     hold the type, though: the first pass took them to .62 with a scrim to
     match and the rooms stopped being visible at all. Measured across all ten,
     the strip the nav links sit in now averages 17–45 of 255, which is dark
     ground for white text with the picture still legible under it. */
  filter: saturate(.82) contrast(1.02) brightness(.82);
}
/* Three layers, and each has a job. The vertical gradient is what makes white
   type readable at the top — the navigation's strip included, since the bar now
   sits on the picture — and what dissolves the picture into the page at the
   bottom, so the band has no edge to find. The radial is a pool under the type
   and nothing else: it used to run the other way — clear in the middle, dark at
   the edges — which was the right shape for a 600px strip and the wrong one for
   a full screen, where it put the weight in the corners and took the picture
   away everywhere except behind the words. Inverted, the showroom and the
   dining room stay open to their edges and the headline still has ground under
   it. The last one is the site's own violet laid over the photograph, and it is
   the reason ten photographs from ten different sources read as one site rather
   than as a stock folder. */
.hero-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg,
      rgba(4,3,14,.78) 0%, rgba(4,3,14,.24) 34%,
      rgba(4,3,14,.34) 62%, rgba(9,4,21,.90) 80%, rgb(9,4,21) 86%,
      rgb(9,4,21) 100%),
    radial-gradient(56% 40% at 50% 44%, rgba(4,3,14,.52), rgba(4,3,14,.20) 64%, rgba(4,3,14,0) 100%),
    linear-gradient(118deg, rgba(115,0,255,.26), transparent 48%, rgba(255,0,208,.15));
}

/* The first block sits closer under a photographic hero than under a band of
   sky. The hero already ends in a long quiet tail — the last eighth of it is
   the picture dissolving into the page — and that tail is doing the work the
   block's own top padding used to do, so the two together read as a gap rather
   than as a join. Adjacent-sibling, so it is the block that actually follows a
   photograph that moves up: the landing page's first block is not one, and
   keeps its own spacing. */
.hero--photo + .block.first { padding-top: clamp(60px, 9vh, 100px); }

/* The rest of a sector page, taken down to the photograph's key.
   `.field-wash` already starts at exactly 100svh — the hero's own bottom — and
   already starts at zero alpha there, which is why deepening it cannot put a
   line back at the junction: it is the one layer on the page whose top edge is
   guaranteed to be invisible. Everything it does happens further down.

   Scoped by the hero photograph rather than by a class on <body>, so a page
   gets the darker ground by having a photograph and there is nothing to keep in
   sync. A browser without `:has()` drops the rule and keeps the ordinary
   ground; nothing breaks. */
body:has(.hero--photo) .field-wash {
  background:
    linear-gradient(180deg,
      rgba(4,3,14,0) 0, rgba(4,3,14,.14) 9vh, rgba(4,3,14,.40) 22vh,
      rgba(4,3,14,.56) 36vh, rgba(4,3,14,.62) 52vh),
    linear-gradient(180deg,
      rgba(4,3,14,0) 0%, rgba(4,3,14,0) 50%, rgba(4,3,14,.24) 82%,
      rgba(4,3,14,.55) 100%);
}

/* The heading of a block and a picture of what it is describing, side by side.
   The block's own .cards grid is untouched and still spans the full width
   below — putting the picture beside the cards instead would have squeezed a
   three-column grid into 58% of the page. */
.sector-lead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, .82fr);
  gap: clamp(1.75rem, 4.5vw, 4rem);
  align-items: center;
}
/* The second one takes the other side. Two identical blocks in a row read as a
   template; alternating them reads as a page.

   The template is swapped as well as the order, and that pair is the whole
   trick: `order` alone moves the figure into grid column one, which is the 1fr
   column — so the mirrored block came out with a wider picture and a narrower
   heading than the block above it. Swapping the two tracks puts the .82fr back
   under the figure, and the two blocks are then mirrors rather than variants. */
.sector-lead--flip { grid-template-columns: minmax(0, .82fr) minmax(0, 1fr); }
.sector-lead--flip .sector-lead__figure { order: -1; }

/* `.block > .wrap > h2` no longer matches, because the heading is a level
   deeper now. Only its measure and its top margin came from there — the size
   is on the bare h2 in style.css and is unaffected. */
.sector-lead__text > h2 { margin-top: 0; max-width: 20ch; }
.sector-lead__text > p.lede { margin-top: 1.75rem; }

.sector-lead__figure {
  position: relative;
  margin: 0;
  border-radius: 18px;
  overflow: hidden;
  border: 1px solid var(--white-10);
  box-shadow: 0 34px 80px -34px rgba(0,0,0,.95);
}
.sector-lead__figure img { display: block; width: 100%; height: auto; }
/* The same violet the hero photograph gets, and for the same reason: one of
   these twenty is a white architect's drawing and the rest are dark
   photographs, and without a tint over all of them that one is a hole in the
   page. */
.sector-lead__figure::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(152deg,
    rgba(115,0,255,.30) 0%, rgba(115,0,255,.12) 38%,
    rgba(4,3,14,.34) 72%, rgba(4,3,14,.62) 100%);
}

@media (max-width: 900px) {
  /* One column, and the picture goes under the words on both blocks — an
     alternating layout has nothing to alternate in a single column. */
  .sector-lead { grid-template-columns: 1fr; gap: 1.6rem; }
  .sector-lead--flip .sector-lead__figure { order: 0; }
  .sector-lead__text > h2 { max-width: 18ch; }
  .sector-lead__text > p.lede { margin-top: 1.25rem; }
  /* A hand's width of the next section left showing under the fold, so a full
     screen of photograph does not look like the whole page. */
  .hero--photo {
    min-height: calc(100vh - 4rem);
    min-height: calc(100svh - 4rem);
  }
}

/* The call-to-action row on the inner pages. The homepage's own row —
   .hero-actions — is gone from the markup, so the class is gone from here
   too rather than being left as a selector nothing can match. */
.hero-buttons {
  position: relative;
  z-index: 3;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
}

/* The one badge left in the tree, on the cart hero. The inner pages used to
   open on an eyebrow above the headline; those were removed by request, so
   every other page now leads with the headline itself. */
.hero-badge {
  background: rgba(255,255,255,.05);
  border: 1px solid var(--white-20);
  color: #fff;
  animation: none;
  box-shadow: none;
}

/* ==========================================================================
   THE DROPDOWN
   --------------------------------------------------------------------------
   A <select>'s open list is drawn by the operating system and cannot be
   styled: `option { background; font-family }` in contact.css is the whole of
   what any browser honours, and Safari honours none of it. So on a page whose
   every other surface is a translucent panel with a blur behind it, choosing a
   service opened a grey rectangle with square corners and system type.

   shared/js/select.js builds the visible control out of the <option> elements
   and leaves the <select> in the form to be the value. What follows is the
   panel idiom the mega menu and the language switch already use, at the size
   and the radius of the fields either side of it — so the closed control is
   indistinguishable from an <input> and the open one from the rest of the
   site's chrome.

   Every rule here is inert until the script runs. A page with no JavaScript
   never gets a .aq-select and keeps the native control, styled as before.
   ========================================================================== */
.aq-select { position: relative; }

/* Still in the form, still the value, still what `required` is checked
   against — just not drawn. It keeps a box so a click on the <label> has
   somewhere to land and so the browser's validation bubble has something to
   point at; `display: none` would make it unfocusable, and Chrome answers an
   unfocusable invalid control by refusing to submit with nothing on screen.
   Two classes so this beats `.form-group select` in contact.css and
   `select.form-input` on checkout, whichever loads last. */
.aq-select .aq-select__native {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  opacity: 0;
  pointer-events: none;
  -webkit-appearance: none;
  appearance: none;
}

/* The closed control. The numbers are the input's, to the pixel — 16px/20px
   of padding, a 12px radius, the same 3% fill and 10% border — because the
   whole point is that the column of fields reads as one column. The 22px on
   the end is where the old background-image chevron was drawn. */
.aq-select__trigger {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: .75rem;
  width: 100%;
  padding: 16px 20px;
  padding-inline-end: 22px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 1rem;
  font-weight: inherit;
  line-height: 1.5;
  text-align: start;
  cursor: pointer;
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.aq-select__trigger:hover { border-color: var(--white-20); }
.aq-select__trigger:focus { outline: none; }
/* The fields around it drop their outline and show focus as a border colour.
   A button has to do better than that for anyone arriving by keyboard, so it
   keeps the border change and adds a ring — on :focus-visible only, which a
   pointer never triggers. */
.aq-select__trigger:focus-visible,
.aq-select.is-open .aq-select__trigger {
  border-color: var(--primary-color);
  background: rgba(99, 102, 241, 0.05);
}
.aq-select__trigger:focus-visible { box-shadow: 0 0 0 3px rgba(184, 29, 245, .28); }
/* The browser hangs its validation message off the <select>, and focuses it to
   do so. That element has no ink, so without this the ring would be on nothing
   at all while the bubble points at the field. */
.aq-select:has(.aq-select__native:focus) .aq-select__trigger {
  border-color: var(--primary-color);
  background: rgba(99, 102, 241, 0.05);
  box-shadow: 0 0 0 3px rgba(184, 29, 245, .28);
}

.aq-select__value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Nothing chosen yet: the prompt is set like the placeholder it is, so a
   filled field and an empty one are told apart at a glance down the column. */
.aq-select.is-empty .aq-select__value { color: var(--white-70); }

.aq-select__caret {
  flex: none;
  width: 14px;
  height: 9px;
  fill: none;
  stroke: #a78bfa;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform .25s var(--ease);
}
.aq-select.is-open .aq-select__caret { transform: rotate(180deg); }

/* The panel. The border, the radius, the blur and the shadow are
   .dropdown-mega's and .lang-switch__menu's — this is the site's menu, not a
   second design for one — but not the fill. Those two open over the page,
   where var(--panel) at 74% reads as a surface; this one opens inside
   .contact-form-wrapper, whose background IS var(--panel), and panel over
   panel is invisible: the first build put seven legible options over a card
   with no plate under them at all. So the fill is a step lighter and nearly
   opaque, which is what makes it read as lifted off the card rather than
   printed on it. visibility is deliberately outside the transition, for the
   reason set out over .lang-switch__menu. */
.aq-select__menu {
  position: absolute;
  z-index: 40;
  inset-inline: 0;
  top: calc(100% + 8px);
  max-height: min(21rem, 60vh);
  padding: .375rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: rgba(26, 18, 48, .97);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--white-10);
  border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .6);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity .22s var(--ease), transform .22s var(--ease);
  scrollbar-width: thin;
  scrollbar-color: var(--white-20) transparent;
}
/* Flipped by the script when the last field on a short window has no room
   under it. */
.aq-select--up .aq-select__menu {
  top: auto;
  bottom: calc(100% + 8px);
  transform: translateY(6px);
}
.aq-select.is-open .aq-select__menu {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.aq-select__list { margin: 0; padding: 0; list-style: none; }

/* Seven rows have to fit inside the panel without it scrolling — a list that
   clips its last option is a list that hides one — so the row is as tall as
   the type needs and no taller. */
.aq-select__option {
  position: relative;
  padding: .58rem .9rem;
  padding-inline-end: 2.25rem;
  border-radius: 10px;
  color: var(--white-70);
  font-size: .9375rem;
  line-height: 1.5;
  cursor: pointer;
  transition: background .18s var(--ease), color .18s var(--ease);
}
/* One highlight, driven by the script, so the pointer and the arrow keys
   cannot light up two different rows at once — which is what :hover alongside
   a keyboard state does. */
.aq-select__option.is-active { background: rgba(255, 255, 255, .07); color: #fff; }
.aq-select__option.is-selected { color: #fff; background: rgba(184, 29, 245, .14); }
.aq-select__option.is-selected.is-active { background: rgba(184, 29, 245, .24); }
.aq-select__option.is-placeholder { color: rgba(255, 255, 255, .45); }
.aq-select__option.is-disabled { opacity: .4; cursor: default; }

/* The tick on the chosen row, drawn rather than set, so it needs no icon font
   and no second network request: a box with two of its four borders, turned
   45°. The two borders are named physically on purpose. Logical ones flip with
   the writing direction, and a mirrored tick is not a tick — in Arabic it came
   out as a chevron pointing at the text. */
.aq-select__option.is-selected::after {
  content: "";
  position: absolute;
  inset-inline-end: .95rem;
  top: 50%;
  width: 6px;
  height: 10px;
  border: 2px solid #fff;
  border-top: 0;
  border-left: 0;
  transform: translateY(-60%) rotate(45deg);
}

@media (prefers-reduced-motion: reduce) {
  .aq-select__menu,
  .aq-select__caret,
  .aq-select__option,
  .aq-select__trigger { transition: none; }
}

/* ==========================================================================
   RESPONSIVE + A11Y
   ========================================================================== */
@media (max-width: 900px) {
  .wrap { padding-inline: 1.375rem; }
  /* The strapline is one line on desktop only. Here the font size has hit the
     bottom of its clamp, so the sentence no longer shrinks with the viewport
     and holding it on one line would run it off the side of the screen. */
  .hero-description { white-space: normal; max-width: min(32em, 88vw); }
  /* And it needs air above it. .75em is measured against the headline's own
     size, which on a phone is the floor of the clamp — so the gap shrank with
     the type at exactly the moment the headline gained a third line and the
     strapline gained a second. The two ran together as one paragraph. */
  .hero-title { margin-bottom: 1.5em; }

  /* ------------------------------------------------------------------
     One axis on a phone.

     Every heading on the site is centred and every paragraph under it was
     set to the inline start, which on a 390px column reads as two different
     pages stacked — the eye starts each block in a different place. There is
     no second column here for a ragged edge to sit against, so the alignment
     was doing nothing but breaking the rhythm.

     Enumerated rather than `main { text-align: center }`, because two things
     on this site are user interfaces and not prose: the أبعاد and طاولة
     screens on the portfolio page reproduce real apps line for line, and the
     contact form is a form. Both would be wrecked by a blanket rule, and
     neither is listed here.
     ------------------------------------------------------------------ */
  .block > .wrap > h2,
  .block > .wrap > h3,
  .block > .wrap > p,
  .block > .wrap > .lede,
  .section-header,
  .panel-inner,
  .card,
  .card-inner,
  .showcase-copy,
  .faq-answer { text-align: center; }
  /* The number is an inline span; centring its line box is what moves it. */
  .panel-inner > .num, .card .card-num { display: block; }
  /* The feature lists under each panel are plain <ul>s with no marker, so
     they only need the padding the UA reserves for one taking back. */
  .panel-inner ul, .card ul { padding-inline: 0; list-style: none; }
  .panel-inner ul li, .card ul li { text-align: center; }
  /* Anything the panel puts at its end — «اقرأ المزيد» and its arrow — goes
     to the middle with the rest of the card. */
  .panel-inner .service-link { justify-content: center; }

  /* The mega menu is a full-width sheet inside the drawer, not a panel
     hanging off a link: theme's desktop `width: max-content` was still
     shrink-wrapping it on a phone, which parked the six solutions against
     the inline edge while every other item sat on the centre. */
  .dropdown-mega { width: 100%; min-width: 0; max-width: none; }
  /* Same for the language control at the foot of the drawer. */
  .nav-menu .nav-lang { display: flex; justify-content: center; }
  .nav-menu .nav-lang .lang-switch { justify-content: center; }
  .cards, .cards.two { grid-template-columns: minmax(0,1fr); }
  /* The backdrop, thinned for a phone.
     Nine star layers and seven orbs is a composition built for a 1440px frame.
     On a 390px one the same nine layers are nine full-screen composited
     textures and the same seven orbs are seven 120vw discs each carrying an
     80px blur — the most expensive thing on the page, on the least capable
     hardware, for a density nobody can see at that size. Four sky layers and
     four orbs read identically here and cost a little over half as much. */
  .stars5, .stars6, .stars7, .stars8, .shooting-stars { display: none; }
  .orb.o2, .orb.o4, .orb.o6 { display: none; }
  .orb { filter: blur(80px); width: 120vw !important; height: 120vw !important; }
  /* Both glows widened to 150vw on a phone, verbatim from Landing.html: at
     100vw the two arcs stop overlapping in the middle and the hero shows a
     black column straight down its centre. */
  .bg_glow-wrap.is-left  { width: 150vw; left: -50vw; }
  .bg_glow-wrap.is-right { width: 150vw; right: -50vw; }
  .navbar { padding-block: .625rem; }
}

@media (max-width: 560px) {

  /* Two sector doors per row, for the same reason the capability grid keeps
     two: one column turns ten cards into a screen and a half of scrolling
     before the page says anything else, and the card is a mark and a name —
     there is no detail to lose at half the width. */
  .sector-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; }
  .sector-card { min-height: 112px; padding: 1.1rem .7rem; gap: .7rem; }
  .sector-card__icon i { font-size: 1.25rem; }
  .sector-card__name { font-size: .8rem; }

  /* The chips carry a title and a line of explanation, and the line is the
     first thing to go: at this width it wraps or forces a chip wider than the
     phone. The title alone still names the capability, which is what the rail
     is for. */
  .cap-chip { padding: .95rem 1.1rem; column-gap: .8rem; }
  .cap-chip i { width: 26px; font-size: 1.05rem; grid-row: auto; }
  .cap-chip b { font-size: .95rem; }
  .cap-chip em { display: none; }
  .cap-rail { padding-block: 1.4rem; }
  .cap-rail__weave { filter: blur(5px); opacity: .35; }
}

@media (prefers-reduced-motion: reduce) {
  html, body { scroll-behavior: auto; }
  .stars, .stars1, .stars2, .stars3, .stars4, .stars5,
  .stars6, .stars7, .stars8, .shooting-stars,
  .orb, .grain, .marquee-track { animation: none; }
  .sky { transition: none; }
  .reveal { opacity: 1; transform: none; transition: none; }
  /* The weave is a still drawing already — it has no animation to stop — so
     the rail simply parks with both rows at the start of their travel and
     every card legible. An animation switched off must leave the finished
     state behind, never the first frame. */
  .faq-item[open] .faq-answer { animation: none; }
  .panel:hover, .panel[id]:target,
  .service-card:hover, .testimonial-card:hover, .feature-item:hover,
  .pricing-card:hover, .value-card:hover, .stat-card:hover, .info-card:hover { animation: none; }
  html { scroll-behavior: auto; }
  /* Without this the canvas never leaves opacity 0: the renderer stops after
     its settle frames and the transition has nothing left to run on. */
  .hero-model { transition: none; }
}
