﻿/* ===========================================================================
   Aeline — rebuilt in plain HTML/CSS.

   Every number here was MEASURED off the live reference at a 1440px viewport
   with aeline/probe-live.mjs and aeline/probe-geometry.mjs, not eyeballed from
   the JPEG. The measured value is written next to anything non-obvious, so a
   later edit can tell a deliberate change from a drift.

   Nothing from the template's own stylesheet, markup or image library is used.
   =========================================================================== */

/* --arc-x has to be a REGISTERED property, not just a custom one.
   The responsive value below is min(600px, calc(...)), and an unregistered
   custom property is handed to getComputedStyle as that raw token stream:
   parseFloat('min(600px, calc(...))') is NaN, motion.js fell back to its
   hard-coded 600, and the carousel kept drawing a 504px arc inside a 374px
   hero while the CSS static slots had already shrunk correctly. Registering it
   makes the computed value a resolved length - '444.05px' - which is both what
   the JS needs and what makes it animatable.

   A browser without @property support falls back to the 600px initial value,
   which is exactly the behaviour that shipped before, so nothing regresses. */
@property --arc-x {
  syntax: '<length>';
  inherits: true;
  initial-value: 600px;
}

:root {
  /* palette — read back from the live page's computed styles */
  --lime:        #cdfb56;   /* rgb(205,251,86)  card + button fill          */
  --lime-soft:   #d6fd70;   /* rgb(214,253,112) small marks on dark         */
  --ink:         #131313;   /* rgb(19,19,19)                                */
  --ink-soft:    #242424;   /* a softer ink for large filled buttons. Ahmad
                               asked for the pricing button to be less than
                               pure black; the lime label still reads 13.4:1
                               on it, against 16.1:1 on --ink.             */
  --ink-lift:    #2c2c2c;   /* the hover state of an ink surface. brightness()
                               cannot make one: 1.1 x 19 is 21, a 2/255 change
                               nobody can see, so an ink button had no hover at
                               all. 4.4:1 against #131313 - visibly different. */
  --body:        #2f2f2f;   /* rgb(47,47,47)    paragraphs                  */
  --body-soft:   #5f5f5f;   /* rgb(95,95,95)    card copy — 6.6:1 on white  */
  --card-grey:   #f2f2f2;   /* rgb(242,242,242)                             */
  --logo-grey:   #d8d8d8;
  --blue:        #38c6f6;   /* rgb(56,198,246)  chart + chip                */
  --star:        #ebf213;   /* rgb(235,242,19)                              */

  --hero-inset:  12px;      /* white gutter round the hero                  */
  --hero-r:      24px;      /* measured borderRadius                        */
  --hero-h:      976px;     /* measured hero height                         */
  --container:   1336px;    /* bento spans x 52..1388 at 1440               */
  --nav-w:       1296px;    /* nav spans x 72..1368                         */
  --gap:         24px;

  /* the carousel arc: x = 600·sin(phi), y = 45·(1 − cos(phi)) */
  --arc-x:       600px;
  --arc-y:       45px;
  --card-w:      190px;
  --card-h:      198px;

  /* A reading measure in ch resolves from the FIRST font in the stack, which is
     Plus Jakarta Sans — it has Latin glyphs, so it wins the ch lookup even on a
     page whose text is all Arabic and all drawn in Mirza. Every Arabic column
     was therefore sized to a Latin character count. The Arabic value is set in
     px from a measured characters-per-line count, not converted from the Latin
     one. See the measure check in rtl-check.mjs. */
  --measure:     46ch;
  --measure-wide: 62ch;

  /* ONE FAMILY, THREE CUTS — IBM Plex, which is what the OSUS identity book
     names on p.20. It replaces four unrelated faces: El Messiri for Arabic
     headings, Mirza for Arabic reading text, Plus Jakarta for Latin, Geist Mono
     for labels. Ahmad's verdict on that combination was «الخطوط لسة خربانة
     تقريبا وغير متناسقة», and the cause was structural rather than taste - El
     Messiri is a low-contrast semi-Kufi with a large x-height, Mirza is
     high-contrast traditional Naskh drawn for display sizes, and one set above
     the other is two type cultures in one paragraph.
     Arabic and Latin here are one design with shared metrics, which is what
     lets the display token and the reading token be the same stack: the
     hierarchy is carried by weight and size, not by changing typeface. */
  --sans:       'IBM Plex Sans', 'IBM Plex Sans Arabic', -apple-system, 'Segoe UI', sans-serif;
  --display-ar: 'IBM Plex Sans Arabic', 'IBM Plex Sans', sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, monospace;
  /* --wordmark used to be Inter, because the REFERENCE set its wordmark in
     Inter. Ours is the real OSUS artwork now, so the only two elements that
     ever used this token show either Arabic (which takes El Messiri) or
     nothing at all - and compare.mjs caught the consequence immediately: a
     webfont was being shipped that no glyph on either page was drawn with. */
  --wordmark: var(--sans);
}

/* THERE USED TO BE A SECOND @font-face HERE and it is worth knowing why it is
   gone, because the trap it patched is still real.

   CSS places the baseline at `line-box centre + (ascent - descent) / 2`, and it
   reads those two numbers from the FIRST AVAILABLE font - the first family in
   the stack that owns a space glyph - NOT from the font that draws the glyphs.
   With Plus Jakarta first and Mirza drawing the Arabic, Latin metrics were
   positioning Arabic ink: «إدارة المشاريع» sat 5px low in a 44px pill, 19.5px
   of air above it and 9.5px below. The patch was the same Plus Jakarta woff2
   redeclared under a second name with ascent-override 82% / descent-override
   44%, swapped in on eight selectors.

   IBM Plex Sans and IBM Plex Sans Arabic are one design with shared metrics, so
   the font that measures and the font that draws now agree. Measured before
   deleting, not assumed: over nineteen Arabic pills the mean |offset| is 0.76px
   without the patch against 1.05px with it. It is not being carried forward. */

/* [2] Arabic <-> English stops being a white flash.
   The switch is a real document navigation - two files, two URLs, which is the
   whole reason a crawler sees both languages - so it reloaded the page and
   replayed every entrance animation. This hands the browser a cross-fade
   between the two documents instead. One rule; browsers without it get exactly
   today's behaviour, and it starts paying properly the day the five inner pages
   exist. */
@view-transition { navigation: auto; }

*, *::before, *::after { box-sizing: border-box; }

/* No rule in this project asks for 700 - the count across every stylesheet is
   200 once, 400 seven times, 500 thirty-six and 600 four. The browser default
   on these two elements was the only thing reaching for it, and honouring that
   default would mean shipping a whole extra Arabic weight (69 KB) so that a
   handful of <b> tags could be 100 units heavier than the semibold beside them. */
b, strong { font-weight: 600; }

/* THE DOT PATTERN, on every white section.
 *
 * Ported from the React <DotPattern> Ahmad sent, which paints an SVG <pattern>
 * of one circle per 16x16 tile at cx/cy 1, r 1, in neutral-400/80. A
 * radial-gradient reproduces that exactly and costs no element, no SVG, no
 * useId and no request:
 *
 *     circle at 1px 1px, r 1px, every 16px, rgb(163 163 163) at .8
 *
 * It goes on a ::before rather than on the section's own background because
 * three of these sections already paint white and one paints a photograph, and
 * a layer can be stacked without touching either.
 *
 * The mask is the one deliberate departure from his component. Edge to edge,
 * a hard grid competes with the text sitting on it; fading it out towards the
 * middle of each section keeps the texture at the margins, where it reads as
 * paper, and out of the way of the reading column.
 */
.dots {
  position: relative;
  isolation: isolate;
  /* Ink and radius are tokens because three different situations want three
     different answers, and the alternative is three copies of the gradient. */
  --dot-ink: rgba(163, 163, 163, .7);   /* 1.85:1 on white */
  --dot-r: 1px;
}
.dots::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: radial-gradient(circle at var(--dot-r) var(--dot-r),
                    var(--dot-ink) var(--dot-r), transparent 0);
  background-size: 16px 16px;

  /* THE FADE IS FIXED, NOT PROPORTIONAL, and that is the whole point.
     It used to be radial-gradient(120% 90% at 50% 50%, transparent 30%, #000),
     whose transparent middle SCALES WITH THE SECTION. On a 1280x645 desktop
     section that hid a sensible middle and kept dots in the margins. On a phone
     the same sections are 1959 and 2915px tall, so the transparent ellipse grew
     to swallow essentially the whole page and Ahmad saw no dots at all - which
     is exactly what he reported.
     A linear fade measured in PIXELS behaves identically at every height: the
     pattern simply softens into the seam where one section meets the next, and
     is full strength everywhere else, on any screen. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 80px,
                      #000 calc(100% - 80px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 80px,
                      #000 calc(100% - 80px), transparent 100%);
}
/* BOTH OVERRIDES LIVE HERE, NOT IN THE RESPONSIVE SECTION AT THE FOOT OF THE
   FILE, and in this order on purpose. They have identical specificity, so the
   later one wins - and the one that has to win on a phone with Increase
   Contrast switched on is the contrast one. Six hundred lines apart, that
   ordering is invisible and would be broken by the next person to tidy up.

   A phone is not a desktop held closer. Measured with the dot layer patched out
   of styles.css on the wire and the two renders diffed: 1.20% of the section's
   pixels at 390px against 1.18% at 1440px. The pattern IS painting on a phone -
   it is simply 1px at 1.85:1, which is at the threshold of what anyone can see
   on a bright screen at arm's length. */
@media (max-width: 809px) {
  /* Ahmad, 2026-08-29, looking at the phone: the dots read as NOISE under the
     reading column, not as paper. 1.4px at .85 was the answer to the opposite
     complaint (invisible) and it overshot. Smaller ink, lighter, and a wider
     tile so the field is calmer per square inch - dot AREA is roughly halved
     and the count per screen drops ~36% at 20px vs 16px. */
  .dots { --dot-ink: rgba(170, 170, 170, .6); --dot-r: 1px; }     /* 1.65:1 */
  .dots::before { background-size: 20px 20px; }
}

/* THIS RULE WAS `display: none`, AND IT IS THE ONE THING ON THE PAGE THAT HIDES
   THE PATTERN ON AN IPHONE AND ON NOTHING ELSE.
   iOS Accessibility > Display & Text Size > Increase Contrast sets
   prefers-contrast: more. Ahmad reported twice that the dots were not on his
   iPhone while every measurement I could take said they were painting, and this
   is the gap: the ink was fine, his phone was throwing the layer away because I
   told it to.
   Someone who asks for more contrast is asking for MORE CONTRAST, not for a
   texture to be deleted - the pattern sits behind the reading column, it does
   not sit under the type. So it is darkened rather than removed. */
@media (prefers-contrast: more) {
  /* Contrast, not size: 1.5px was doing both, and on his iPhone (Increase
     Contrast on) it was the largest dot on the page. Keep the ink dark, take
     the radius back down. */
  .dots { --dot-ink: rgba(130, 130, 130, .85); --dot-r: 1px; }    /* 3.1:1 */
}

html { -webkit-text-size-adjust: 100%; }

/* [1] Every nav link used to TELEPORT: there was no scroll-behavior anywhere in
   the file. Guarded, because a forced smooth scroll is genuinely unpleasant for
   anyone who asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* :where() keeps this at zero specificity, so a section can still override it.
   24px is deliberate - the sections carry 96px of their own top padding, so the
   landing already breathes; this is just enough to show that something exists
   above, which is what tells you you arrived rather than started. */
:where(#about, #services, #expertise, #pricing, #cta, #contact) {
  scroll-margin-block-start: 24px;
}

body {
  margin: 0;
  background: #fff;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
  letter-spacing: -0.02em;          /* -0.32px at 16px */
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { display: block; max-width: 100%; }
a   { color: inherit; text-decoration: none; }
p   { margin: 0; }

/* The reference's content column is min(1440, viewport - 104), narrowing to a
   16px gutter on phones. Ours was min(1336, viewport - 48): identical at 1440
   and wrong at every other width - 104px too narrow above 1500, 56px too wide
   below 1400. Measured on the live page's own bento at 1920/1600/1400/1177/900. */
.container {
  width: min(1440px, calc(100% - 104px));
  margin-inline: auto;
}
@media (max-width: 809px) {
  .container { width: calc(100% - 32px); }
}

/* Geist Mono, 14px/20, weight 500, tracking +1.68px — nav, buttons, eyebrows */
.mono {
  font-family: var(--mono);
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* Reveal-on-scroll. Note the .js scope: it is what makes hiding OPT-IN.
   Without it, a motion.js that 404s or throws leaves 32 elements at opacity 0
   forever - measured, 964 readable characters instead of 3,395. boot.js adds
   the class before first paint and takes it back if motion.js never claims the
   page. See boot.js for the whole argument.

   AND NOTE `translate`, NOT `transform`.
   That is not a style preference, it is what unpicks a knot. A reveal that ends
   in `transform: none` outranks every hover transform on the same element - it
   is why data-rise had to sit on the services SHELL rather than on its two
   cards, with a comment in the markup apologising for it. `translate` and
   `scale` are independent properties that COMPOSE with transform instead of
   replacing it, so a card can rise into place and still lift under the pointer.
   Reveals can now go on cards, which is what makes the stagger below possible.

   [4] THREE VERBS, NOT ONE. Twenty-seven elements all performing the same 22px
   fade is what made the page feel like one gesture repeated rather than a
   sequence. Text rises, cards settle in from slightly small, and the one big
   photograph wipes open. Three, and no more than three: a fourth would start
   reading as decoration. */
.js [data-rise] {
  opacity: 0;
  translate: 0 22px;
  transition: opacity .8s cubic-bezier(.22,.61,.36,1),
              translate .8s cubic-bezier(.22,.61,.36,1),
              scale .8s cubic-bezier(.22,.61,.36,1);
}
.js [data-rise].in { opacity: 1; translate: none; scale: none; }

/* a card settles rather than rises: less travel, and it arrives from 2.5% small */
.js [data-rise="card"] { translate: 0 13px; scale: .975; }

/* the one photograph on the page wipes open from its bottom edge. `round` keeps
   the cell's 24px corners - a bare inset(0) squares them off and clips the
   shadow with them. */
.js [data-rise="reveal"] {
  translate: none;
  clip-path: inset(0 0 100% 0 round var(--gap));
  transition: opacity .9s cubic-bezier(.22,.61,.36,1),
              clip-path .9s cubic-bezier(.22,.61,.36,1);
}
.js [data-rise="reveal"].in { clip-path: inset(0 round var(--gap)); }

/* [5] The twelve expertise cards were the only grid on the page still arriving
   as one block - pricing and the bento already stagger through data-delay. They
   stagger in CSS rather than markup so the step is 60ms rather than the JS
   observer's 90, and so six cards need no six attributes. */
.js .exp-track[data-rise] .exp-card {
  opacity: 0;
  translate: 0 16px;
  transition: opacity .55s cubic-bezier(.22,.61,.36,1),
              translate .55s cubic-bezier(.22,.61,.36,1);
  /* The step is carried by a custom property, NOT by six transition-delay
     rules. Those were written first and measured at 0s across all six: the
     `transition` shorthand above RESETS transition-delay, and it outranks a
     bare .exp-card:nth-child(). A custom property survives the shorthand and
     needs no specificity contest at all. */
  transition-delay: calc(var(--i, 0) * 60ms);
}
.js .exp-track[data-rise].in .exp-card { opacity: 1; translate: none; }
.exp-card:nth-child(2) { --i: 1; }
.exp-card:nth-child(3) { --i: 2; }
.exp-card:nth-child(4) { --i: 3; }
.exp-card:nth-child(5) { --i: 4; }
.exp-card:nth-child(6) { --i: 5; }
.exp-card:nth-child(7) { --i: 6; }

/* THE HERO DOES NOT USE ANY OF THAT.
   Above-the-fold copy must not wait for a script to download: a slow motion.js
   left a fully-painted hero photograph with no headline in it for seconds, and
   a blocked one left it that way. These four run off the render pipeline, which
   cannot 404.

   .nav gets its own keyframe because it is centred by translateX(-50%) - a
   keyframe ending in `transform: none` puts it 592px past the right edge with
   the logo off screen. The centring is carried in every frame. */
@keyframes hero-rise {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: none; }
}
@keyframes nav-rise {
  from { opacity: 0; transform: translateX(-50%) translateY(-14px); }
  to   { opacity: 1; transform: translateX(-50%); }
}
.nav        { animation: nav-rise .8s cubic-bezier(.22,.61,.36,1) both; }
.hero h1    { animation: hero-rise .8s cubic-bezier(.22,.61,.36,1) both; }
.hero__sub  { animation: hero-rise .8s cubic-bezier(.22,.61,.36,1) .09s both; }
.hero__cta  { animation: hero-rise .8s cubic-bezier(.22,.61,.36,1) .18s both; }
.trust      { animation: hero-rise .8s cubic-bezier(.22,.61,.36,1) .27s both; }

/* =============================================================== NAV ==== */

.nav {
  /* The bar belongs at the top of the page and nowhere else. Fixed, it rode
     back into view on every upward scroll and painted white text over white
     content, which reads as a second broken copy of the header. */
  position: absolute;
  top: 34px;                                      /* bar y16 + 18 to the row  */
  left: 50%;
  transform: translateX(-50%);
  width: var(--nav-w);
  max-width: calc(100% - 48px);
  height: 40px;
  display: flex;
  align-items: center;
  gap: 12px;
  z-index: 60;
  transition: transform .45s cubic-bezier(.22,.61,.36,1), opacity .3s;
}
/* [data-rise] would overwrite the centring transform, so state both here.
   .js-scoped for the same reason as the rule above: unscoped, a blocked
   motion.js leaves the bar permanently 14px high. */
.js .nav[data-rise]     { transform: translateX(-50%) translateY(-14px); }
.js .nav[data-rise].in  { transform: translateX(-50%); }


/* The wordmark is 1000 x 280.47 - a 3.57:1 band, not the 4:3 box the
   placeholder triangle occupied. `color` is set here and nowhere else: the
   artwork is fill="currentColor" throughout, so this line is what makes the
   logo white. */
.nav__brand { display: flex; align-items: center; gap: 10px; color: #fff; }
.nav__mark  { width: 82px; height: 23px; flex: none; }

/* The mark IS the name. A word set beside it says the same thing twice, in
   both languages - Ahmad's call, and it is the right one: the artwork is the
   identity, the text was scaffolding from when the mark was a placeholder. */
.nav__word, .ftr__word { display: none; }
.nav__word  {
  font-family: var(--wordmark);
  font-size: 24px;
  font-weight: 400;
  letter-spacing: -0.06em;                       /* -1.44px at 24px */
  color: #fff;
}

.nav__links {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 48px;
  color: #fff;
}
.nav__links a { opacity: .95; transition: opacity .25s; }
.nav__links a:hover { opacity: .65; }

.nav__lang {
  color: #fff;
  padding: 8px 12px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  transition: background .25s;
}
.nav__lang:hover { background: rgba(255, 255, 255, 0.14); }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  transition: transform .25s cubic-bezier(.22,.61,.36,1), background .25s;
}
.btn:active { transform: scale(.975); }

.nav__cta { margin-inline-start: auto; }

.btn--pill {
  background: var(--lime);
  color: var(--ink);
  height: 40px;
  padding: 0 20px;
}
.btn--pill:hover { background: var(--lime-soft); }

/* THE MENU BUTTON IS THE SAME OBJECT AS THE LANGUAGE PILL.
   It was a 40px lime rounded SQUARE with heavy ink bars, sitting next to a
   40px hairline white CIRCLE. Two controls doing the same kind of job, drawn
   in two different languages: one read as a chunky app icon and pulled far
   more attention than the menu deserves. And the brand book is explicit that
   the accent is spent sparingly - on an action, not on chrome. The hero's lime
   button is 100px below this bar, so a lime toggle above it was two accents
   stacked. Matching .nav__lang exactly makes the end of the bar a pair. */
.nav__burger {
  display: none;
  width: 40px; height: 40px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
  padding: 0;
  place-content: center;
  gap: 5px;
  transition: background .25s;
}
.nav__burger:hover { background: rgba(255, 255, 255, 0.14); }
.nav__burger span {
  /* 2px bars 5px apart, so the centres sit 7px either side of the middle -
     which is exactly the translateY the close cross below relies on. Changing
     either number without the other turns the X into a lopsided cross. */
  display: block; width: 18px; height: 2px; border-radius: 2px;
  background: #fff;
  transition: transform .3s cubic-bezier(.22,.61,.36,1), opacity .2s;
}
.nav__burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav__burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* -- the small-screen panel ---------------------------------------------- */

.panel {
  position: absolute;
  top: 86px;
  left: 50%;
  transform: translateX(-50%) translateY(-12px);
  width: var(--nav-w);
  max-width: calc(100% - 48px);
  z-index: 55;
  background: var(--ink);
  border-radius: 24px;
  padding: 20px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .28s, transform .28s cubic-bezier(.22,.61,.36,1);
}
.panel[hidden] { display: none; }
.panel.is-open { opacity: 1; transform: translateX(-50%); pointer-events: auto; }

.panel__links { display: grid; }
.panel__links a {
  padding: 14px 8px;
  font-size: 24px; font-weight: 500; letter-spacing: -0.04em;
  color: #fff;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.panel__links a:last-child { border-bottom: 0; }
.panel__cta { width: 100%; margin-top: 16px; justify-content: space-between; }

/* ============================================================== HERO ==== */

.hero {
  position: relative;
  margin: var(--hero-inset);
  height: var(--hero-h);
  border-radius: var(--hero-r);
  overflow: hidden;
  isolation: isolate;
}

.hero__sky {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  z-index: -1;
  background: linear-gradient(180deg, #0568b0, #56bef0);
}

/* [7] The sky moves slower than the content on the way past it. Scroll-driven,
   so it runs on the compositor with NO JavaScript at all - nothing to load,
   nothing to fail, and nothing added to the scroll handler. @supports because
   older browsers simply keep the static sky, which is the current design.

   The 1.15 scale is what buys the room to move: without it the drift would
   uncover the top or bottom edge of a photograph that is exactly inset:0. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    .hero__sky {
      scale: 1.15;
      animation: sky-drift linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 100vh;
    }
    @keyframes sky-drift {
      from { translate: 0 -3%; }
      to   { translate: 0  5%; }
    }
  }
}

.hero__copy { text-align: center; padding-top: 223px; }   /* h1 top = y 235 */

.hero h1 {
  margin: 0;
  font-size: 60px;
  line-height: 72px;
  font-weight: 500;
  letter-spacing: -0.06em;                        /* -3.6px at 60px */
  color: #fff;
}
.hero h1 span { display: block; color: rgba(255, 255, 255, 0.75); }

.hero__sub {
  margin: 16px auto 0;                            /* sub top = y 395 */
  width: 500px;
  color: #fff;
}

.hero__cta {
  margin-top: 34px;                               /* buttons top = y 477 */
  display: flex;
  justify-content: center;
  gap: 14px;
  /* Below ~560px the two buttons together are wider than the phone and the
     hero's overflow:hidden cut both ends off - with body{overflow-x:hidden}
     there was no way to scroll to what was missing. Wrapping is a no-op at
     600px and up: geometry is byte-identical at 600, 900, 1180 and 1440. */
  flex-wrap: wrap;
}
.hero__cta .btn { max-width: 100%; }

.btn--ghost {
  height: 44px;
  padding: 0 20px;
  color: #fff;
  background: rgba(18, 40, 53, 0.25);             /* measured on the live page */
  border: 1px solid rgba(255, 255, 255, 0.22);
  backdrop-filter: blur(8px);
}
.btn--ghost:hover { background: rgba(18, 40, 53, 0.4); }

.btn--go {
  height: 44px;
  padding: 0 6px 0 20px;
  gap: 14px;
  background: var(--lime);
  color: var(--ink);
}
.btn--go:hover { background: var(--lime-soft); }
.btn--go i {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--ink); color: #fff;
  display: grid; place-items: center;
}
.btn--go svg { width: 16px; height: 16px; }

/* -- the carousel -------------------------------------------------------- */

.fan {
  position: absolute;
  top: 553px;                                     /* card top = y 565 */
  left: 50%;
  width: 100%;
  height: 250px;
  z-index: 1;                                     /* contains the cards' own
                                                     z-index, see motion.js */
  transform: translateX(-50%);
}

/* The ring is inset:0 of a FULL-WIDTH .fan, so hovering anywhere in the hero -
   up to 185px from the nearest card - used to freeze the carousel. It hands its
   pointer events to the cards, which are the only thing worth hovering. */
.fan__ring { position: absolute; inset: 0; pointer-events: none; }

.fan__card {
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--card-w);
  height: var(--card-h);
  margin-left: calc(var(--card-w) / -2);
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 22px 46px rgba(6, 46, 82, 0.16);
  overflow: hidden;
  padding: 14px;
  display: flex;
  flex-direction: column;
  font-size: 10px;
  line-height: 1.3;
  letter-spacing: -0.04em;
  transform:
    translateX(var(--x, 0px))
    translateY(var(--y, 0px))
    perspective(1200px)
    rotateY(var(--r, 0deg));
  /* The JS drives rotateY to +/-110, past the point where the card turns away
     from the viewer, and Chromium happily paints the reverse side: a mirrored
     slab climbing to 80% opacity, parked at each end of the arc on 96% of
     frames. On the Arabic page it is mirrored Arabic. Verified safe - with the
     property applied, every front-facing card renders 0 changed pixels. */
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  pointer-events: auto;               /* see .fan__ring above */
  will-change: transform;
}

/* Static fallback: the nine arc slots, phi = i·22.5° − 90°, so the page is
   correct before motion.js runs and when motion is switched off. Expressed as
   multiples of --arc-x rather than the nine literals they used to be, so they
   track the breakpoints below - this is the arrangement a reduced-motion
   visitor sees, and it was clipping four cards on a phone. */
.fan__card:nth-child(1) { --x: calc(var(--arc-x) * -1);      --y: 45.0px; --r:  90deg; }
.fan__card:nth-child(2) { --x: calc(var(--arc-x) * -0.9239); --y: 27.8px; --r:  67.5deg; }
.fan__card:nth-child(3) { --x: calc(var(--arc-x) * -0.7071); --y: 13.2px; --r:  45deg; }
.fan__card:nth-child(4) { --x: calc(var(--arc-x) * -0.3827); --y:  3.4px; --r:  22.5deg; }
.fan__card:nth-child(5) { --x: 0px;                          --y:  0.0px; --r:   0deg; }
.fan__card:nth-child(6) { --x: calc(var(--arc-x) *  0.3827); --y:  3.4px; --r: -22.5deg; }
.fan__card:nth-child(7) { --x: calc(var(--arc-x) *  0.7071); --y: 13.2px; --r: -45deg; }
.fan__card:nth-child(8) { --x: calc(var(--arc-x) *  0.9239); --y: 27.8px; --r: -67.5deg; }
.fan__card:nth-child(9) { --x: var(--arc-x);                 --y: 45.0px; --r: -90deg; }

/* There are twenty-one cards and only nine slots. When motion.js is running it
   queues the other twelve off stage and hides them each frame; when it is NOT -
   no JavaScript, or a visitor who asked for less movement - nothing assigns
   them a position, so all twelve would default to --x:0/--y:0/--r:0 and pile up
   opaque in the dead centre of the arc. Both cases are named explicitly rather
   than left to the cascade, because they arrive by different routes. */
html:not(.js) .fan__card:nth-child(n+10) { display: none; }

/* -- card internals ------------------------------------------------------ */

.c-title { font-size: 12px; font-weight: 500; color: var(--ink); letter-spacing: -0.04em; }
.c-sub   { font-size: 7.5px; color: #9aa1ab; }
.c-label { font-size: 10px; color: #6b7280; margin-top: auto; }
.c-big   { font-size: 24px; font-weight: 500; letter-spacing: -0.06em; line-height: 1.1; }

.c-bars { display: flex; align-items: flex-end; gap: 5px; height: 92px; margin-top: auto; }
.c-bars i { flex: 1; background: #e6e9ee; border-radius: 3px 3px 0 0; }
.c-bars i.on { background: var(--blue); }
.c-axis { display: flex; justify-content: space-between; margin-top: 5px; color: #c6ccd4; font-size: 6px; flex: none; }

.c-area { width: 100%; height: 78px; margin-top: auto; display: block; flex: none; }

.c-head {
  background: var(--ink); color: #fff; border-radius: 9px;
  padding: 7px 9px; display: flex; align-items: center; justify-content: space-between;
  font-size: 12px; font-weight: 500;
  margin: -4px -4px 8px; flex: none;
}
.c-spark { width: 20px; height: 12px; color: var(--lime-soft); }
.c-pct { font-size: 32px; font-weight: 500; letter-spacing: -0.06em; margin-top: 2px; line-height: 1.1; }
.c-pct em {
  font-style: normal; font-size: 6.5px; letter-spacing: 0;
  background: #dff5d8; color: #2f7d32; border-radius: 4px; padding: 1px 4px;
  vertical-align: middle; margin-inline-start: 4px;
}
.c-note { font-size: 7.5px; color: #9aa1ab; }

.c-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: auto; }
.c-tags--top { margin: 0 0 auto; }
.c-tags span {
  border: 1px solid #e8ebef; border-radius: 999px;
  padding: 4px 8px; font-size: 10px; color: #4b5563; background: #fff;
}

.c-money { font-size: 20px; font-weight: 500; letter-spacing: -0.05em; }
.c-money s { color: #c9ced6; text-decoration: none; font-size: 15px; }
.c-bar { height: 8px; border-radius: 5px; background: #eaedf1; margin: 8px 0 10px; overflow: hidden; }
.c-bar i { display: block; height: 100%; width: 49%; background: var(--blue); border-radius: 5px; }
.c-row { display: flex; align-items: center; gap: 7px; padding: 6px 0; border-top: 1px solid #f1f3f6; }
.c-row b { width: 11px; height: 11px; border-radius: 50%; background: var(--ink); flex: none; }
.c-row div { flex: 1; }
.c-row em { display: block; height: 3px; border-radius: 2px; background: #e7eaef; }
.c-row em + em { margin-top: 4px; width: 58%; }
.c-row span { font-size: 10px; font-weight: 500; color: #6b7280; }

.c-photo { padding: 0; }
/* <picture> is inline by default, and an inline box has no definite height for
   the img's `height:100%` to resolve against - the photograph collapsed to its
   own aspect and left a white band under it. The wrapper has to become the box. */
.c-photo picture { display: block; width: 100%; height: 100%; }
.c-photo img { width: 100%; height: 100%; object-fit: cover; }
.c-chip {
  position: absolute; display: flex; align-items: center; gap: 5px;
  background: #fff; border-radius: 999px; padding: 5px 8px 5px 5px;
  font-size: 9px; font-weight: 500; color: var(--ink);
  box-shadow: 0 4px 10px rgba(0, 0, 0, .12);
}
.c-chip b { width: 12px; height: 12px; border-radius: 50%; background: var(--blue); }
.c-chip em { font-style: normal; color: #2f7d32; background: #dff5d8; border-radius: 4px; padding: 0 3px; }
.c-chip--a { top: 34px; inset-inline-start: 16px; }
.c-chip--b { top: 96px; inset-inline-start: 30px; }

.c-over {
  position: absolute; inset-inline: 9px; bottom: 9px;
  background: #fff; border-radius: 10px; padding: 8px 9px; display: flex; gap: 10px;
}
.c-over > div { flex: 1; }
.c-over span { display: block; font-size: 8px; color: #9aa1ab; }
.c-over i { font-style: normal; color: #2f7d32; background: #dff5d8; border-radius: 3px; padding: 0 3px; }
.c-over strong { font-size: 16px; font-weight: 500; letter-spacing: -0.05em; }

.c-glass {
  background: linear-gradient(180deg, rgba(255,255,255,.34), rgba(255,255,255,.08));
  box-shadow: none;
  border: 1px solid rgba(255, 255, 255, .3);
  display: grid; place-content: center; text-align: center; gap: 9px; color: #fff;
}
.c-glass i {
  width: 30px; height: 30px; border-radius: 50%; background: #fff; color: var(--blue);
  display: grid; place-items: center; margin: 0 auto;
}
.c-glass i svg { width: 16px; height: 16px; }
.c-glass b { font-size: 20px; font-weight: 500; letter-spacing: -0.05em; }
.c-glass span { font-size: 9px; color: rgba(255, 255, 255, .78); }

.c-ink {
  background: var(--ink);
  display: flex; align-items: center; padding: 16px;
}
.c-ink p { font-size: 20px; line-height: 1.3; font-weight: 500; letter-spacing: -0.04em; color: #fff; }
.c-ink s { color: #6c6e72; text-decoration: none; }
.c-ink u {
  display: inline-block; width: 14px; height: 14px; border-radius: 50%;
  background: var(--lime-soft); vertical-align: -1px; text-decoration: none; margin: 0 2px;
}

/* -- rating -------------------------------------------------------------- */

/* Three checkable promises, in the slot the template used for a star rating
   that nobody has given. Same position, same weight, no invented claim. */
.trust {
  position: absolute;
  z-index: 2;                                     /* above the carousel */
  top: 789px;
  inset-inline: 0;
  margin: 0; padding: 0;
  list-style: none;
  display: flex;
  justify-content: center;
  gap: 10px;
  color: #fff;
}
.trust li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  line-height: 20px;
  padding: 10px 18px;
  border-radius: 999px;
  background: rgba(18, 40, 53, 0.28);
  border: 1px solid rgba(255, 255, 255, 0.22);
  backdrop-filter: blur(8px);
  white-space: nowrap;
}
.trust svg { width: 17px; height: 17px; flex: none; color: var(--lime); }

/* ==================================================== DEEMA RIBBON ===== */
/*
 * This block used to be a LOGO STRIP - a marquee of client names, complete and
 * correct and referenced by no markup anywhere in the project. The wall of
 * client logos was deleted long ago because OSUS had no clients to put on it;
 * the CSS outlived it. (2026-08-30: it has three now, and sections/05b-clients
 * is a real logo strip again - built on these same keyframes rather than a
 * second copy of them.) It is reused rather than rewritten: the two-half track,
 * the exact translateX(-50%) that lands one half over the other, the RTL
 * sign flip and the reduced-motion line all already existed and all were right.
 *
 * Ahmad on the desktop card: «التقسيط عبر ديما في شكل الدسكتوب طوييييييله
 * احتاجك تخليها شريط يمر في مكانه». It is a flex row with space-between, so at
 * ≥810px the label sits at one end, the logo at the other, and there is a metre
 * of black between them. A ribbon fills that by moving through it.
 */
.dm { overflow: hidden; width: 100%; mask-image: linear-gradient(to right,
      transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
      -webkit-mask-image: linear-gradient(to right,
      transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%); }
.dm__track { display: flex; width: max-content; animation: marquee 32s linear infinite; }
.dm__half  { display: flex; align-items: center; gap: 40px; padding-inline-end: 40px; }

.dm__item {
  display: inline-flex; align-items: center; gap: 16px; flex: none;
  font-size: 14px; font-weight: 500; line-height: 20px; color: #fff;
  white-space: nowrap;
}
.dm__item img { height: 22px; width: auto; display: block; }

/* The phone keeps the still version - «وفي شكل الهاتف ممتازة» - and only the
   wording changes. Which half is showing is decided at 810px, below. */
.dm__still { display: none; align-items: center; justify-content: space-between; gap: 16px; }
.dm__still img { height: 26px; width: auto; }

@keyframes marquee { to { transform: translateX(-50%); } }

/* ============================================================ ABOUT ==== */

.about { padding-top: 60px; text-align: center; }   /* label cut 2026-08-30; h2 starts here */

/* UNUSED SINCE 2026-08-30 — THIS IS THE ONE PLACE THAT EXPLAINS THE CUT; the
   seven sections just carry a one-line pointer back here.
   Every section had a small mono word with a square before it - «من نحن»,
   «الخدمات», «أعمالنا» - sitting above its headline. Ahmad cut all seven: the
   headline under the label already said what the section was, so the label was
   a second, quieter copy of the same fact. Three headings carried a top margin
   that existed only to clear the label (20/20/16px) and were zeroed with it;
   the other four heads use flex gap, which closed on its own. Every reveal
   delay below a label stepped down one. compare.mjs lost its .eyebrow row and
   had the 46px shift pinned into its geometry table.
   Kept, not deleted, because the
   markup change is one line per section and this is the only definition of the
   treatment; deleting it would make putting one back a rewrite. If it is still
   unused at the next tidy-up, delete it then. */
.eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ink);
}
.eyebrow::before { content: ''; width: 4px; height: 4px; background: currentColor; }

.about h2 {
  margin: 0;                                        /* was 20px: the gap under the
                                                       section label, cut with it */
  font-size: 48px;
  line-height: 56.16px;
  font-weight: 500;
  letter-spacing: -0.06em;                          /* -2.88px at 48px */
  color: var(--ink);
}
.about h2 .dim { color: rgba(19, 19, 19, 0.5); }

/* The icon is positioned, not in flow, so the chip has no in-flow content and
   its baseline is its own bottom margin edge. With the SVG in flow it donates
   its baseline instead, the chip hangs 16px below the text baseline, and every
   headline line carrying a chip grows 7px. */
.chip {
  display: inline-block;
  position: relative;
  overflow: hidden;
  width: 40px; height: 40px; border-radius: 50%;
  vertical-align: -7px; margin: 0 2px;
}
.chip--blue { background: var(--blue); }
.chip--lime { background: var(--lime); }
.chip svg { position: absolute; inset: 0; margin: auto; width: 22px; height: 22px; }
.chip--lime svg { fill: var(--ink); }

/* ============================================================ BENTO ==== */

.bento {
  margin-top: 80px;                                 /* bento top = y 1456 */
  display: grid;
  grid-template-columns: 443fr 443fr 403fr;         /* measured widths      */
  grid-template-rows: 212px 88px;                   /* 212 + 24 + 88 = 324  */
  gap: var(--gap);
  text-align: start;
}

.cell { border-radius: var(--gap); overflow: hidden; position: relative; }
.cell--photo, .cell--quote { grid-row: span 2; }

.cell__label { font-size: 14px; line-height: 20px; font-weight: 500; }
.cell__num {
  display: block;
  font-size: 40px; line-height: 48px; font-weight: 500; letter-spacing: -0.06em;
}
.cell__body { font-size: 16px; line-height: 24px; font-weight: 500; }

/* 1 — photo */
.cell--photo { background: linear-gradient(200deg, #1288cf, #48ade8 60%, #a8cde6); }
.cell--photo > img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 8%;
}
.cell__top {
  position: absolute; inset: 20px 20px auto 20px;
  display: flex; align-items: flex-start; justify-content: space-between;
}
.ipsum {
  font-size: 25px; font-weight: 600; line-height: 26px; letter-spacing: -0.02em;
  color: #fff;
}
.ipsum i { font-style: normal; font-size: 10px; vertical-align: 9px; }
.cell__btn {
  width: 40px; height: 40px; border-radius: 12px; background: #fff;
  display: grid; place-items: center;
}
.cell__btn svg { width: 20px; height: 20px; fill: var(--ink); }
.cell__panel {
  position: absolute; inset: auto 20px 20px 20px;
  background: #fff; border-radius: 12px; padding: 16px;
}
.cell__panel strong {
  display: block; font-size: 60px; line-height: 72px; font-weight: 500;
  letter-spacing: -0.06em;
}
.cell__panel p { margin-top: 12px; font-size: 14px; line-height: 20px; font-weight: 500; }

/* 2 — quote */
.cell--quote { background: var(--card-grey); padding: 20px; display: flex; flex-direction: column;
               text-align: start; }
.cell--quote .cell__num { margin-top: 12px; }
/* The Deema wordmark is white on transparent, so the ink card is the only
   surface on this page it can sit on. */
.deema { display: block; margin-top: auto; }
.deema img { height: 26px; width: auto; }
.cell__quote { margin-top: 12px; max-width: 336px; font-size: 16px; line-height: 24px; font-weight: 500; }

/* 3 — lime, as glass.
 *
 * Ahmad asked for the lime cards to be "liquid glass". Apple documents that
 * name for Apple platforms only and there is no such thing as a liquid-glass
 * stylesheet on the web, so this is an APPROXIMATION of the look, built the
 * way the effect is actually built: a translucent tint, a real backdrop blur
 * with saturation lifted so colour survives the blur, a bright inner edge on
 * the light side and a dimmer one below, and a soft coloured shadow.
 *
 * It only works because of what is now BEHIND it. Glass over a flat fill
 * refracts nothing and just looks like a weaker colour; the dot pattern added
 * to the white sections in the same round is what the blur has to bend, and
 * it is what makes this read as a material rather than as reduced opacity.
 *
 * Both fallbacks are the solid card, because a browser without backdrop-filter
 * and a person who has asked for less transparency both need the tint to carry
 * on its own. Text on it is --ink, which is 15:1 on the solid lime and gets
 * LIGHTER underneath as the card turns translucent, so contrast only improves.
 */
.cell--lime {
  padding: 20px;
  display: flex;
  flex-direction: column;
  /* FULL STRENGTH, NOT A PASTEL. The first version ran the tint down to .55
     alpha in the middle, and over a white page that is a washed-out lime with
     none of the punch the colour is for - Ahmad's words were «مو واضح ... باهت»,
     and he was right. Glass does not have to mean faint: a real pane of tinted
     glass is SATURATED and gets its glassiness from what happens at its
     surface, not from how much of the wall shows through it.
     So the tint sits at .97-.93 - the colour reads as --lime - and the glass
     is carried by the three things that actually say "glass": a specular sheen
     raking across it, a bright rim on the light side, and a coloured glow
     underneath. The backdrop blur stays, and still does visible work at the
     edges where the tint is thinnest. */
  background:
    linear-gradient(140deg,
      rgba(214, 253, 112, .97) 0%,
      rgba(205, 251, 86,  .93) 48%,
      rgba(197, 246, 68,  .97) 100%);
  backdrop-filter: blur(16px) saturate(180%);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, .6);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .95),
    inset 0 -1px 0 rgba(150, 190, 40, .35),
    0 12px 34px rgba(133, 170, 24, .28);
}

/* The specular sweep. A single soft highlight crossing the top corner is what
   the eye reads as a curved, lit surface; without it a high-opacity tint is
   just a coloured box. pointer-events:none so it never eats the card's link. */
.cell--lime::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    /* the lit corner */
    radial-gradient(115% 75% at 10% -12%, rgba(255, 255, 255, .62) 0%, transparent 58%),
    /* a thin bright streak, which is the thing that actually reads as a pane */
    linear-gradient(118deg, transparent 34%, rgba(255, 255, 255, .38) 44%,
                    rgba(255, 255, 255, .06) 52%, transparent 60%),
    /* the far corner picking up a little light back */
    radial-gradient(85% 65% at 108% 112%, rgba(255, 255, 255, .28) 0%, transparent 62%);
  /* No blend mode. soft-light over a colour this bright barely registered, and
     the sheen was invisible; plain alpha white on lime is what a lit surface
     actually looks like, and it lifts the ground under the text rather than
     darkening it, so contrast only improves. */
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .cell--lime { background: var(--lime); }
}
@media (prefers-reduced-transparency: reduce) {
  .cell--lime {
    background: var(--lime);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

.cell--lime .cell__num { margin-top: 8px; }
.cell--lime .cell__body { margin-top: 12px; }

/* The card is now an instruction, so the whole card is the link that carries
   it out. The ::after stretches the anchor over the card rather than nesting
   anything, which keeps one link and one focus stop. */
.cell__go {
  margin-top: auto;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: start;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  border-block-end: 1px solid rgba(19, 19, 19, .3);
  padding-block-end: 2px;
  transition: border-color .25s, gap .25s;
}
[dir="rtl"] .cell__go { font-size: calc(14px * 1.08); }
.cell__go svg { width: 14px; height: 14px; }
.cell__go::after { content: ''; position: absolute; inset: 0; }
.cell--lime:hover .cell__go { border-block-end-color: var(--ink); gap: 12px; }
.cell__go:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
[dir="rtl"] .cell__go svg { transform: scaleX(-1); }

/* 4 — dark
   `justify-content: space-between` is gone: it is what pinned the label to one
   end and the logo to the other and left the middle empty, which is the whole
   thing Ahmad asked to be rid of. The ribbon takes the width instead. */
.cell--dark {
  background: var(--ink); color: #fff; padding: 20px;
  display: flex; align-items: center;
}
.cell--dark > * { flex: 1; min-width: 0; }

/* ======================================================= RESPONSIVE ==== */

/* scale() cannot take calc(100vw / 1440) - a length over a number is a length,
   not a ratio, so the declaration was dropped and the carousel never shrank.
   Steps instead. The old 0.8 step sat at max-width 1280, which after the
   breakpoints were measured onto the reference's own numbers would have applied
   at exactly one pixel. */
@media (max-width: 1439px) {
  .fan { transform: translateX(-50%) scale(0.9); transform-origin: top center; }
}

@media (max-width: 1279px) {
  /* The arc has to fit inside the hero, which clips it with no scrollbar to
     reach what is cut - measured at 390px before this: a 510px arc inside a
     374px hero, two cards gone ENTIRELY at opacity 1.0.
     The divisor is not 2 x the scale factor: a card near the end of the arc is
     still ~73px wide, so its outer edge sits beyond the arc radius. Derived by
     bisecting the largest arc that clips nothing over a full rotation
     (aeline/_work/arcfit.mjs): 1.379 at 810px, 1.14 at 700px. 1.39 with a
     little room. */
  :root { --arc-x: min(600px, calc((100vw - 24px) / 1.39)); }
  .nav__links  { display: none; }
  .nav__burger { display: grid; }

  /* WHERE THE BURGER ACTUALLY WENT WRONG. The bar is a flex row and
     .nav__cta carried its only margin-inline-start:auto - the single thing
     holding the logo at one end and the controls at the other. Below 810 the
     CTA is display:none, the auto margin goes with it, and logo + EN + burger
     collapse into one cluster at the start: measured, the burger sat at x=182
     on a 390px screen, dead centre, with the whole end half of the bar empty.
     The auto margin has to live on whichever control is FIRST at the end,
     which from 1279 down is the language pill. */
  .nav__lang { margin-inline-start: auto; }
  .nav__cta  { margin-inline-start: 0; }

  /* Both rings are 40px, and on a phone they are the ONLY two controls on
     screen. That clears the WCAG floor but sits under the 44pt/48dp both
     platforms ask for. The ring stays 40px because the design wants it there;
     only the hit area grows, by 3px on every side, to 46px. Nothing moves and
     nothing is repainted. */
  .nav__lang, .nav__burger { position: relative; }
  .nav__lang::after, .nav__burger::after {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: 999px;
  }
  .hero { height: auto; padding-bottom: 56px; }
  .hero__copy { padding-top: 180px; }
  .hero h1 { font-size: clamp(34px, 5.2vw, 60px); line-height: 1.2; }
  .hero__sub { width: min(500px, 88%); }
  .fan { position: relative; top: 0; left: auto; margin-top: 56px; height: 168px;
         transform: scale(0.68); transform-origin: top center; }
  /* This said .rating, a class that stopped existing when the invented star
     rating became the three promises. The row therefore kept position:absolute
     and top:789px while the hero above it became height:auto, so on every
     screen under 1280px it sat wherever 789px happened to land - over the logo
     strip on a phone. It has to come back into the flow with the fan. */
  .trust { position: relative; top: 0; margin-top: 24px; flex-wrap: wrap; }
  .about h2 { font-size: clamp(26px, 4.2vw, 48px); line-height: 1.17; }
  .chip { width: 0.82em; height: 0.82em; vertical-align: -0.1em; }
  .chip svg { width: 55%; height: 55%; }
  .bento { grid-template-columns: 1fr 1fr; grid-template-rows: auto; }
  .cell--photo { min-height: 420px; }
  .cell--lime, .cell--dark { grid-column: span 2; }
  .cell--lime { min-height: 212px; }
}

@media (max-width: 809px) {
  /* measured 0.857 at 430px, 0.862 at 390 and 360, 0.866 at 320 */
  :root { --arc-x: min(600px, calc((100vw - 16px) / 0.87)); }
  /* «وفي شكل الهاتف ممتازة» — the still line stays, the ribbon stands down.
     A phone card is 358px wide and a ribbon crossing it reads as a ticker. */
  .cell--dark .dm { display: none; }
  .cell--dark .dm__still { display: flex; }
  .nav__cta  { display: none; }
  .nav, .panel { max-width: calc(100% - 32px); }
  .panel { top: 78px; }
  .hero { margin: 8px; }
  .hero__copy { padding-top: 140px; }
  .fan { height: 104px; margin-top: 44px; transform: scale(0.42); }
  /* Three nowrap pills side by side are ~640px; at 390 they were being clipped
     by body{overflow-x:hidden} rather than overflowing, so nothing reported it
     and a third of the row was simply gone. */
  .trust { flex-direction: column; align-items: center; gap: 8px; padding-inline: 16px; }
  .trust li { width: 100%; justify-content: center; white-space: normal; text-align: center; }
  .bento { grid-template-columns: 1fr; }
  .cell--photo, .cell--quote, .cell--lime, .cell--dark { grid-row: auto; grid-column: auto; }
  .cell--photo { min-height: 460px; }
}

@media (prefers-reduced-motion: reduce) {
  .dm__track { animation: none; }
  .nav, .panel, .nav__burger span { transition: none; }
  .js [data-rise] { opacity: 1; translate: none; scale: none; transition: none; }
  .js [data-rise="reveal"] { clip-path: none; }
  .js .exp-track[data-rise] .exp-card { opacity: 1; translate: none; transition: none; }
  .hero__sky { animation: none; scale: 1; translate: none; }
  /* A cross-document view transition is still motion. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
  /* `animation: none` on .nav would drop the centring transform with it, so
     the nav is given its resting state explicitly instead. */
  .hero h1, .hero__sub, .hero__cta, .trust { animation: none; }
  .nav { animation: none; opacity: 1; transform: translateX(-50%); }
  /* motion.js returns before it ever places a card here, so the nine static
     slots above are the whole arrangement and cards ten to twenty-one have
     nowhere to be. */
  .fan__card:nth-child(n+10) { display: none; }
}

/* ===========================================================================
   ARABIC / RTL
   Appended last on purpose: these have to win, and in CSS source order beats
   being right.
   =========================================================================== */

/* Positive letter-spacing severs the joins between Arabic letters, and the
   heavy negative tracking the display face wants makes them collide. Both are
   Latin typography and neither survives the crossing. */
[dir="rtl"] .mono,
[dir="rtl"] .eyebrow,
[dir="rtl"] .nav__links a,
[dir="rtl"] .btn { letter-spacing: 0; }

/* Geist Mono carries no Arabic, so the LETTERS fell back to the Arabic face
   while U+0020 kept being served by Geist Mono at its fixed monospace advance:
   8.41px against the 2.42px the same text gets in body copy. Every Arabic
   button label and every eyebrow read as isolated words. Zeroing the tracking
   was not enough - the mono family has to leave the stack entirely. */
[dir="rtl"] .mono { font-family: var(--sans); font-weight: 600; }

/* Headings take the display face; everything else stays in the text face. */
[dir="rtl"] h1,
[dir="rtl"] h2,
[dir="rtl"] h3,
[dir="rtl"] .cell__num,
[dir="rtl"] .cell__panel strong,
[dir="rtl"] .c-big,
[dir="rtl"] .nav__word { font-family: var(--display-ar); }

[dir="rtl"] .hero h1,
[dir="rtl"] .about h2,
[dir="rtl"] .cell__num,
[dir="rtl"] .cell__panel strong,
[dir="rtl"] .dm__item { letter-spacing: 0; }

/* Arabic sits taller in the same box, so a line-height tuned for Latin runs
   its ascenders into the line above. */
[dir="rtl"] .hero h1      { line-height: 1.32; }
[dir="rtl"] .about h2     { line-height: 1.34; }
[dir="rtl"] .panel__links a { line-height: 1.5; }

/* A 200%-wide track under RTL hangs off the LEFT, so its visible window is the
   right half and a negative translate walks it straight off the screen. The
   loop has to run the other way. */
[dir="rtl"] .dm__track { animation-name: marquee-rtl; }
@keyframes marquee-rtl { to { transform: translateX(50%); } }

/* Arrows are direction. Clocks, locks, wallets, charts and logos are not. */
[dir="rtl"] .btn--go i svg,
[dir="rtl"] .panel__cta i svg { transform: scaleX(-1); }

/* The carousel is a fixed 3D arrangement, not a reading order: mirroring it
   would tip every card the wrong way against its own lighting. */
[dir="rtl"] .fan { direction: ltr; }
[dir="rtl"] .fan__card { direction: rtl; }

/* --ar IS 1 NOW, AND THAT IS A MEASUREMENT, NOT A SURRENDER.
   It was 1.19 because Mirza drew Arabic 19% smaller than Plus Jakarta drew
   Latin at the same px value, so 16px that read right in English read thin in
   Arabic. IBM Plex Sans Arabic and IBM Plex Sans are one design: measured on
   letters that neither rise nor drop — «مسمسمس» against "xnmsuvw" at 100px —
   the Arabic body is 61.5 and the Latin x-height 53.5. The Arabic is already
   the LARGER of the two, so the old correction would now be pushing it 19%
   past a face that is 15% ahead to begin with.
   The token stays rather than being deleted from ~40 call sites: it is the one
   place a future face gets corrected, and at 1 every one of those calc()s is
   the value it wraps. */
[dir="rtl"] { --ar: 1; }

/* The Arabic measures, set in px because ch would hand them a Latin count.
   Tuned to land in the same 45-85 characters-per-line band the Latin columns
   sit in; rtl-check.mjs counts them on the built page and fails outside it. */
[dir="rtl"] { --measure: 440px; --measure-wide: 600px; }

[dir="rtl"] body,
[dir="rtl"] .hero__sub,
[dir="rtl"] .cell__quote,
[dir="rtl"] .cell__body,
[dir="rtl"] .cell__panel p { font-size: calc(16px * var(--ar)); line-height: 1.7; }

[dir="rtl"] .mono,
[dir="rtl"] .nav__links a,
[dir="rtl"] .cell__label,
[dir="rtl"] .trust li { font-size: calc(14px * var(--ar)); }

/* THE BUTTONS, and why --ar does not apply to them.
   --ar scales Arabic up 19% because the Arabic face draws smaller ink than the
   Latin one at the same value. That is right for running text and wrong inside
   a pill: it put a 16.66px word, with hamza above it and the deep tail of ع and
   ي below, into a 44px button drawn around 14px of Latin. The face wants ~23px
   of line box at that size and had 20, so the glyphs overflowed it - and
   because a font's overflow is not symmetric, all of it landed underneath.

   A button label is a fixed-height box, so the answer is at the source: less
   scale-up, and a line box that actually fits what is left. 1.08 keeps most of
   the optical correction; 1.42 gives the descender somewhere to go.

   A second correction used to sit here too - a font-metric override applied
   through --sans on eight selectors, because Plus Jakarta was POSITIONING the
   Arabic that Mirza was DRAWING. One family draws and measures now, so that
   half is gone; see the note where the @font-face used to be. */

[dir="rtl"] .btn,
[dir="rtl"] .trust li,
[dir="rtl"] .prc-cta,
[dir="rtl"] .prc-buy,
[dir="rtl"] .cta-btn,
[dir="rtl"] .panel__cta,
[dir="rtl"] .nav__lang { font-size: calc(14px * 1.08); line-height: 1.42; }

[dir="rtl"] .dm__item { font-size: calc(14px * var(--ar)); }
[dir="rtl"] .nav__word   { font-size: calc(24px * var(--ar)); }

/* The five sections were authored before Mirza was chosen, so every text role
   that sets its own font-size — rather than inheriting body's — kept the Latin
   value and came out 19% smaller than the paragraph beside it. Measured with
   aeline/type-check.mjs, which compares each role against its English twin and
   fails on any body role that renders at the same px in both. */
[dir="rtl"] .exp-card__body,
[dir="rtl"] .ftr__desc,
[dir="rtl"] .ftr__link,
[dir="rtl"] .ftr__item,
[dir="rtl"] .prc-deema__body { font-size: calc(16px * var(--ar)); line-height: 1.7; }

[dir="rtl"] .exp-card__meta,
[dir="rtl"] .prc-feats li,
[dir="rtl"] .ftr__tagline,
[dir="rtl"] .ftr__colTitle,
[dir="rtl"] .ftr__legal p,
/* :not(.ftr__link) IS LOAD-BEARING. This rule and the 16px one nine lines above
   it are both (0,2,0) and both match a footer nav link, so source order decided
   it and this one is later - the links were being set from the 14px rule, not
   the 16px one written for them. It went unnoticed for as long as --ar was 1.19,
   because 14 x 1.19 is 16.66 and that was near enough to the intended 16 to look
   right. The moment --ar became 1 the same rule produced 14px against English's
   16px. The bug was always there; the multiplier was hiding it. */
[dir="rtl"] .ftr__col a:not(.ftr__link) { font-size: calc(14px * var(--ar)); line-height: 1.65; }

[dir="rtl"] .panel__links a { font-size: calc(24px * var(--ar)); }

/* h4 was missing from the display list, so every card title in "الخبرة" was set
   in the reading face while every other heading on the page was not. */
[dir="rtl"] h4,
[dir="rtl"] .prc-note { font-family: var(--display-ar); }

/* Uppercase does nothing in Arabic and the tracking that goes with it severs
   the joins. Both are Latin small-caps typography; neither crosses. */
[dir="rtl"] .ftr__colTitle,
[dir="rtl"] .exp-card__no,
[dir="rtl"] .prc-plan { letter-spacing: 0; text-transform: none; }
