/* ============================================================================
   home.css — ورقة أنماط الصفحة الرئيسية وحدها (تصميم «مسار العُقد»، المعتمد).

   منشؤها: `apps/landing/preview/f/f.css` + `apps/landing/preview/tokens.css`
   من جولة مقارنة نسخ الرئيسية (A…F). اعتمد المالك النسخة F في 2026-08-02،
   فرُقّيت إلى `/` وحُذفت شجرة `/preview/` كلها. الملفان مدموجان هنا بترتيب
   وصلهما الأصلي (الرموز أوّلًا ثم المكوّنات) — طلب شبكي واحد بدل اثنين،
   والشلّال كما رُوجع حرفيًّا.

   ⚠️ هذه الورقة **بديلة** عن `landing.css` لا مضافة إليها: الرئيسية وحدها
   تحمّلها، وبقية الصفحات تحمّل `landing.css` وحدها. تحميل الاثنتين معًا يصطدم
   على أسماء مشتركة (.btn/.wrap/.section/.nav/.foot).

   ── الجزء ١: الرموز (tokens) — مصدر الحقيقة الوحيد لكل لون ومقاس هنا ──────
   القاعدة الأصلية: لا لون خام ولا طول px خارج القائمة المسموحة داخل الجزء ٢؛
   كل شيء يأتي من هنا. الكتلة ١ أدناه منصوصة حرفيًّا في المرجع البصري ولا
   تُعدَّل؛ ما بعدها مشتقّ واشتقاقه مبرَّر في موضعه.

   (كان فاحص الرموز `tools/check-tokens.mjs` في `apps/home-variants` يفرض هذه
   القاعدة آليًّا على النسخ الست؛ بقيت الشجرة على فرع `home-variants` وحدها،
   فالقاعدة هنا التزام تحريري: لا لون خام ولا طول px في الجزء ٢.)
   ========================================================================= */

:root {
  /* ---- 1. MANDATED. Exact values from the brief. Do not change. ---------- */
  --bg-deep: #030B1A;
  --bg-mid: #072049;
  --bg-glow: #0E3A6B;
  --cyan-500: #00A3FF;
  --cyan-400: #22D3EE;
  --cyan-300: #7DE3FF;
  --teal-500: #00BFA5;
  --teal-400: #00C48C;
  --success: #22C55E;
  --danger: #EF4444;
  --wa: #25D366;
  --ink-0: #FFFFFF;
  --ink-1: #B8CCE0;
  --ink-2: #7C93AE;
  --glass-bg: rgba(12, 40, 80, 0.38);
  --glass-brd: rgba(0, 163, 255, 0.45);
  --r-card: 20px;
  --r-chip: 12px;
  --z-back: -600px;
  --z-mid: -250px;
  --z-front: 0px;

  /* ---- 2. Typography ----------------------------------------------------
     The brand identity file (docs/DESIGN-SYSTEM.md:13) specifies Alexandria,
     and the live site loads it. The brief says to check the identity file
     first and only fall back to Tajawal/Almarai if none exists — one does,
     so Alexandria leads and Tajawal is the fallback.

     --font-num deliberately does NOT lead with Inter. Every numeral in this
     page is Arabic-Indic (٠-٩ / U+0660-0669) and Google's Inter ships no
     Arabic subset, so an Inter-first stack drops every digit to whatever the
     OS resolves for sans-serif — a real defect on the live site
     (DESIGN-PLAN.md:487). Alexandria covers Arabic-Indic digits, so it leads
     here and Inter is kept only for Latin runs (--font-en). */
  --font-ar: "Alexandria", "Tajawal", "Segoe UI", system-ui, sans-serif;
  --font-num: "Alexandria", "Tajawal", system-ui, sans-serif;
  --font-en: "Inter", "Segoe UI", system-ui, sans-serif;

  /* clamp() only — no breakpoint ladders, per the brief.
     Ratio ~1.22 (major third, tightened) so the scale holds at 320px. */
  /* Max reduced from 4.5rem after measuring the real fold: at 1440 the
     headline wrapped to four lines and pushed the CTA under the fold, and at
     390 it took five. A hero headline that outgrows its own viewport is not
     impact, it is a layout bug. */
  --t-display: clamp(1.875rem, 1.2rem + 2.9vw, 3.5rem);
  --t-h1: clamp(1.875rem, 1.25rem + 3.1vw, 3.5rem);
  --t-h2: clamp(1.5rem, 1.15rem + 1.75vw, 2.375rem);
  --t-h3: clamp(1.125rem, 1rem + 0.6vw, 1.4375rem);
  --t-lead: clamp(1.0625rem, 1rem + 0.5vw, 1.3125rem);
  --t-body: clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);
  --t-small: clamp(0.875rem, 0.86rem + 0.08vw, 0.9375rem);
  --t-micro: 0.8125rem;
  /* Stat/metric numerals get their own ramp — they carry the argument. */
  --t-metric: clamp(2rem, 1.4rem + 3vw, 3.25rem);

  /* 1.18 was too tight for Arabic: the hamza on أ and the descender of ج
     overflow the em box, so the two halves of the hero headline physically
     collided — white glyphs landing on the cyan accent line below, measured at
     2.1:1. Latin-derived tight leading does not transfer to Arabic. */
  --lh-tight: 1.32;
  --lh-head: 1.3;
  --lh-body: 1.75;
  /* Arabic must never receive positive letter-spacing: it breaks the
     joining behaviour of the script (DESIGN-PLAN.md:489). Zero, explicitly,
     so no component is tempted to invent a value. */
  --tracking-ar: 0;

  /* ---- 3. Spacing — strict 4px scale ------------------------------------ */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-16: 64px;
  --s-20: 80px;
  --s-24: 96px;
  --s-32: 128px;
  /* Section rhythm: fluid but still landing on the 4px grid at both ends. */
  --sec-y: clamp(64px, 9vw, 128px);
  /* Tighter rhythm for the product-first treatment. A token rather than a
     literal in b.css so the two rhythms stay comparable and adjustable
     together — and so the token checker keeps its teeth. */
  --sec-y-tight: clamp(40px, 4.6vw, 72px);
  --maxw: 1240px;
  --maxw-prose: 68ch;

  /* ---- 4. Radii (extends the two mandated values) ----------------------- */
  --r-sm: 8px;
  --r-pill: 999px;
  --r-media: 22px;

  /* Max width for an inline product-shot card placed in a text column. A token
     rather than a literal so the token checker stays meaningful. */
  --media-card-max: 340px;

  /* ---- 5. Depth / 3D ---------------------------------------------------
     The brief fixes perspective at 1200px desktop / 900px mobile, and the
     tablet is its own case at "half the rotation and reduced perspective".
     Rotation angles live here so the tablet halving is one override, not a
     hunt through component files. */
  --persp: 1200px;
  --rot-card: 8deg;
  --rot-soft: 4deg;
  --z-lift: 40px;

  /* ---- 6. Glow -------------------------------------------------------
     Blur radii are CONSTANTS. The brief forbids animating blur, so these are
     never tweened — a glow moves by transforming its own layer. */
  --glow-sm: 40px;
  --glow-md: 90px;
  --glow-lg: 180px;
  --glow-cyan: rgba(0, 163, 255, 0.45);
  --glow-cyan-soft: rgba(34, 211, 238, 0.28);
  --glow-teal: rgba(0, 191, 165, 0.32);

  /* ---- 7. Scrims — explicit, never guessed -----------------------------
     WCAG AA over a photographic/glowing background cannot be asserted by
     eye. Text sits on one of these, and the checker measures the composite
     against the worst-case (brightest) pixel underneath.
     --scrim-text is the floor for body copy over any generated background. */
  --scrim-text: rgba(3, 11, 26, 0.72);
  --scrim-strong: rgba(3, 11, 26, 0.86);
  --scrim-veil: rgba(3, 11, 26, 0.45);

  /* ---- 8. Surfaces ------------------------------------------------------ */
  --surface-1: #061530;
  --surface-2: #08203f;
  --line: rgba(125, 227, 255, 0.16);
  --line-strong: rgba(125, 227, 255, 0.3);

  /* Gradients (declared once; components reference the token) */
  --grad-cta: linear-gradient(135deg, var(--cyan-500), var(--teal-500));
  --grad-deep: linear-gradient(180deg, var(--bg-deep), var(--bg-mid));

  /* ---- 9. Motion -------------------------------------------------------- */
  --dur-fast: 0.18s;
  --dur: 0.4s;
  --dur-slow: 0.7s;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

  /* ---- 10. Controls ----------------------------------------------------
     44px is the accessibility floor from the brief; 16px input text is the
     iOS auto-zoom threshold. Both are tokens so no component can undercut
     them by accident. */
  --tap-min: 44px;
  --input-size: 16px;
  --focus-ring: 0 0 0 3px rgba(125, 227, 255, 0.7);

  --shadow-card: 0 2px 8px rgba(3, 11, 26, 0.4), 0 24px 60px -28px rgba(3, 11, 26, 0.75);
  --shadow-lift: 0 40px 90px -34px rgba(3, 11, 26, 0.9);
}

/* Tablet is a distinct case, not a scaled phone: half the rotation, reduced
   perspective. Declared here so all three variants inherit it identically. */
@media (min-width: 768px) and (max-width: 1023px) {
  :root {
    --persp: 1000px;
    --rot-card: 4deg;
    --rot-soft: 2deg;
  }
}

@media (max-width: 767px) {
  :root {
    --persp: 900px;
    --rot-card: 0deg;
    --rot-soft: 0deg;
  }
}
/* ============================================================================
   ── الجزء ٢: مكوّنات الرئيسية — «مسار العُقد» ─────────────────────────────
   خيط D + سرد A المثبَّت + مدارات الهيرو الحية فوق مشهد E. أربع لحظات مثبَّتة
   (القصة، القطاعات، سكّة الأجنحة، لفّ الباقات)، كلها معطّلة دون 768px وتحت
   prefers-reduced-motion.

   Below 768px the page now has a THIRD motion tier — `motion-lite`, GSAP only,
   no WebGL and no pins — so the phone gets the thread, the stations, the FAQ
   knots and the plan-hug. Every rule for it is scoped to `html.motion-lite`,
   which means the no-JS and reduced-motion pages still render exactly the
   settled composition that was approved.

   القواعد الصارمة كما رُوجعت:
   · كل لون/طول رمز (0/1px/2px/12px وحدها مستثناة)
   · CSS alone never hides content — hiding lives under
     .motion-on/.motion-lite/.io-reveal, all three JS-only, and everything JS
     rewinds is fully visible in the settled default
   · glow blurs constant; scroll-linked motion is transform/opacity (the two
     drawn moments — pricing wrap, final burst — are dashoffset on SHORT
     dedicated strokes, and the burst is a one-shot, not a scrub)
   ========================================================================= */

/* ── base ─────────────────────────────────────────────────────────────── */
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* الجذر يقصّ الفيض الأفقي بنفسه، لا body وحده. القصّ على body يكفي كروم —
   وسفاري iOS يظلّ يسمح بجرّ إطار العرض المرئي جانبيًّا متى تجاوز أي عنصر حافة
   المستند، وهو «توسّع الصفحة يسارًا» الذي أبلغ عنه المالك عند «لماذا عُقد؟».
   القصّ على الجذر يحسم الجرّ لكل السلالة مهما كان العنصر الجاني.
   `clip` تحديدًا لا `hidden`: لا يُنشئ حاوية تمرير، فلا يمسّ الشريط اللاصق
   ولا scroll-behavior ولا تثبيتات ScrollTrigger. */
html { overflow-x: clip; }

body.home {
  background: var(--bg-deep);
  color: var(--ink-0);
  font-family: var(--font-ar);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tracking-ar);
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
  /* Mobile Safari and Chrome inflate the text of block containers they judge
     too wide for the screen, unless this is pinned. Every size on this page
     comes off a deliberate clamp() scale, so any inflation is not a courtesy —
     it is a layout error arriving on one device and not the others. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
.en { font-family: var(--font-en); }
.num { font-family: var(--font-num); font-feature-settings: "tnum"; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

/* SAFE AREA — the document ships `viewport-fit=cover`, so the layout viewport
   deliberately runs UNDER the sensor housing and the home indicator. Nothing
   in this file ever accounted for the second half of that bargain: cover means
   YOU owe the insets back on anything that must stay readable. Landscape on a
   notched iPhone is where the debt shows — Safari reports ~50px of inset on
   the housing side and 0 on the other, so the 24px of inline padding put one
   edge of every line behind the housing while the opposite edge kept its 24px.
   The eye reads that as a wide blank strip down one side of the page.

   PHYSICAL left/right, not logical inline-start/end: the insets Safari reports
   are physical, and this page is RTL, so `padding-inline: <start> <end>` would
   hand the LEFT inset to the RIGHT edge — the exact `inset-inline-end is
   physical-left in RTL` trap this project has already paid for once. max()
   keeps the design's 24px wherever the inset is smaller, which is every device
   in portrait and every desktop, so nothing moves except where it must. */
.wrap {
  max-width: var(--maxw); margin-inline: auto; padding-inline: var(--s-6);
  padding-left: max(var(--s-6), env(safe-area-inset-left));
  padding-right: max(var(--s-6), env(safe-area-inset-right));
}
.wrap.narrow { max-width: var(--maxw-prose); }

::selection { background: var(--cyan-500); color: var(--bg-deep); }
:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--r-sm); }

.skip {
  position: fixed; inset-inline-start: var(--s-4); top: var(--s-4); z-index: 60;
  display: inline-flex; align-items: center; min-height: var(--tap-min);
  padding: var(--s-2) var(--s-5); border-radius: var(--r-pill);
  background: var(--cyan-500); color: var(--bg-deep); text-decoration: none;
  transform: translateY(-200%); transition: transform var(--dur-fast) var(--ease-out);
  /* Both physical margins, because only ONE of them can bite: the box sets
     inset-inline-start and leaves the other inset auto, so the margin on the
     un-positioned side is inert in either writing direction. Cheaper than
     restating the rule per direction. */
  margin-left: env(safe-area-inset-left); margin-right: env(safe-area-inset-right);
}
.skip:focus-visible { transform: none; }

/* (شارة النسخة `.variant-badge` كانت أداة مقارنة وحدها — أُزيلت مع الشجرة.) */

/* ── reveal states (JS-scoped) ────────────────────────────────────────── */
/* THREE tiers now, and every hiding rule below is scoped behind a class only
   JavaScript can add: `motion-on` (desktop, Three+GSAP), `motion-lite` (phone,
   GSAP only — no WebGL), `io-reveal` (the CSS-only fallback). The loader's
   fail() removes motion-on AND motion-lite and calls __oqdReveal(), so nothing
   here can hide content that JS is unable to reveal. */
html.motion-on .reveal,
html.motion-lite .reveal { opacity: 0; }
html.io-reveal .reveal {
  opacity: 0; transform: translateY(var(--s-6));
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}
html.io-reveal .reveal.in { opacity: 1; transform: none; }
html.motion-on:not(.hero-in) [data-hero],
html.motion-lite:not(.hero-in) [data-hero] { opacity: 0; }

/* ── AMBIENCE YIELDS TO THE SCROLL ────────────────────────────────────────
   Measured, not assumed. Under the ×4-throttle gate at 1440, over a fixed
   1800px trajectory, this page ran mean 47.0 fps with every decoration alive
   and 58.6 with all CSS animation killed — and the loss was superlinear across
   the eleven rotating hero layers: killing the three rings alone bought 5.6,
   the eight counter-spinning chips alone 1.7, both together 10.5. As long as
   ANY of them ticks, the whole hero stack re-composites every frame.
   (Not layerisation: `will-change: transform` on all eleven changed nothing —
   47.4 vs 47.0. Not the WebGL scene either: removing the canvas bought 0.9.)

   So while the visitor is actually scrolling, the time-driven decorations hold
   still — a 60-second orbit and a 2.6s traveller are imperceptible during a
   flick, and they resume ~200ms after the scroll stops (ScrollTrigger's own
   scrollStart/scrollEnd, so no second scroll listener exists). Scroll-DRIVEN
   choreography is untouched: it is the scroll, it cannot yield to it.

   PAUSED, never removed: `animation: none` would drop each ring back to 0deg
   and snap it again on release. And the `!important` is load-bearing, which is
   the trap this comment exists for — every animation on this page is declared
   with the `animation` SHORTHAND, and a shorthand rewrites animation-play-state
   to `running`. The declarations that own these elements are more specific than
   any sane pause selector (`:is(html.motion-on,…) .orbits.in-view .orbit.o1`),
   so the first cut of this rule lost the cascade in silence: the class toggled
   correctly, the computed play state stayed «running», and the measurement was
   unchanged. Importance is the only override that does not require restating
   eight compound selectors.

   ONE list, one owner — same rule as the .in-view registry below. */
html.scroll-quiet .orbit,
html.scroll-quiet .counter-spin,
html.scroll-quiet .scroll-cue,
html.scroll-quiet .pdot,
html.scroll-quiet .tline::after,
html.scroll-quiet .step-knot .kg,
html.scroll-quiet .rail-rider .kg,
html.scroll-quiet .cta-band::before { animation-play-state: paused !important; }

/* ── nav ──────────────────────────────────────────────────────────────── */
.nav {
  position: sticky; top: 0; z-index: 50;
  background: var(--glass-bg);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
.nav-inner { display: flex; align-items: center; gap: var(--s-6); min-height: var(--s-16); }
.brand { text-decoration: none; display: inline-flex; align-items: center; justify-content: center; min-height: var(--tap-min); min-width: var(--tap-min); }
.brand-word { font-size: var(--t-h3); font-weight: 700; color: var(--ink-0); }
.nav-links { display: none; gap: var(--s-2); }
.nav-links a {
  display: inline-flex; align-items: center; min-height: var(--tap-min);
  padding-inline: var(--s-4); text-decoration: none; color: var(--ink-1);
  border-radius: var(--r-sm); transition: color var(--dur-fast) var(--ease-out);
}
.nav-links a:hover { color: var(--ink-0); }
.nav-cta { display: none; align-items: center; gap: var(--s-4); margin-inline-start: auto; }
.nav-menu { margin-inline-start: auto; position: relative; }
.nav-menu summary {
  list-style: none; cursor: pointer; color: var(--ink-1);
  display: grid; place-items: center;
  width: var(--tap-min); height: var(--tap-min); border-radius: var(--r-sm);
}
.nav-menu summary::-webkit-details-marker { display: none; }
.nav-panel {
  position: absolute; inset-inline-end: 0; top: calc(100% + var(--s-2));
  min-width: calc(var(--s-32) * 2);
  display: flex; flex-direction: column;
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s-3);
  box-shadow: var(--shadow-card);
}
.nav-panel a {
  display: flex; align-items: center; min-height: var(--tap-min);
  padding-inline: var(--s-4); text-decoration: none; color: var(--ink-1);
  border-radius: var(--r-sm);
}
.nav-panel a:hover { background: var(--surface-2); color: var(--ink-0); }
@media (min-width: 1024px) {
  .nav-links { display: flex; }
  .nav-cta { display: flex; }
  .nav-menu { display: none; }
}

/* ── buttons ──────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: var(--tap-min); padding: var(--s-3) var(--s-8);
  border-radius: var(--r-pill); text-decoration: none; font-weight: 600;
  font-size: var(--t-body);
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.btn:hover { transform: translateY(calc(-1 * var(--s-1))); }
.btn-primary { background: var(--grad-cta); color: var(--bg-deep); box-shadow: 0 var(--s-2) var(--glow-sm) var(--glow-cyan-soft); }
.btn-primary:hover { box-shadow: 0 var(--s-3) var(--glow-md) var(--glow-cyan); }
.btn-ghost { background: transparent; color: var(--ink-0); border: 1px solid var(--line-strong); }
.btn-ghost:hover { border-color: var(--cyan-400); }
.btn-sm { padding: var(--s-2) var(--s-5); }
.lnk {
  color: var(--cyan-300); text-decoration: none;
  display: inline-flex; align-items: center; min-height: var(--tap-min);
}
.lnk:hover { text-decoration: underline; }
.nav-cta .lnk { min-width: var(--tap-min); justify-content: center; }

/* ── thread primitives — upgraded to the WRAP line's quality ──────────
   The owner's bar: «الخط مثل خط الباقات». The wrap strokes glow because they
   carry an SVG blur filter; these CSS lines get the same bloom from a
   constant box-shadow halo, a brighter three-stop gradient, and a small
   travelling light that rides each connector (transform-only, time-driven,
   under motion-on). Default = drawn, lit, calm. */
/* The connector is this page's signature — and at a flat 96px it was also its
   single biggest source of dead air on a phone: 96px of 2px-wide line between
   every pair of sections, stacked on top of two section paddings. The height
   now scales with the viewport (48px on a phone, 96px unchanged on a desktop)
   and the negative margin that lets it overlap into the next section scales
   WITH it, so the overlap stays a third of the line at every size instead of
   swallowing it whole on mobile.
   --tseg-h is read by the traveller keyframe too — it inherits down to
   .tline::after. A hard-coded 96px there would have sent the light sliding
   well past the end of a 48px line.

   NO NEGATIVE BOTTOM MARGIN. It used to pull the following content up by a
   third of the line, and the owner caught what that actually did: `.tseg` is
   the FIRST CHILD of the next section, not a spacer between two, so the
   margin did not "let the line reach toward the heading" — it dragged the
   HEADING UP INTO THE LINE. Measured at the FAQ: the terminal knot finished
   32px BELOW the heading's top edge, i.e. the thread ran through the middle
   of «أسئلة قد تدور في ذهنك» and out the other side.
   Removed, the connector sits in normal flow with one `--sec-y` above it and
   one below, which is the centred position between the two sections that it
   was always supposed to hold. */
.tseg {
  --tseg-h: clamp(48px, 7vw, 96px);
  position: relative; height: var(--tseg-h); width: 2px; margin-inline: auto;
  margin-bottom: var(--sec-y);
}
/* …and the other half of "centred": with the margin above removed, the band
   read 88px of air ABOVE the line and 0 below it, because the section's own
   top padding sits above `.tseg` while the heading follows it immediately.
   The connector is the thing that spans the two sections, so it — not the
   section — owns that gap: the section gives up its top padding wherever a
   connector is present, and the connector carries one `--sec-y` on each side.
   The band becomes sec-y | line | sec-y, symmetrical by construction at every
   breakpoint instead of by a hand-tuned number.
   Without :has() support the section keeps its padding and the line simply
   sits high in a larger gap — never overlapping, which is the part that
   mattered. */
.section:has(> .tseg) { padding-top: 0; }
.tline {
  position: absolute; inset: 0; overflow: visible;
  background: linear-gradient(180deg, var(--cyan-300), var(--cyan-500), var(--teal-400));
  border-radius: var(--r-pill);
  box-shadow: 0 0 var(--s-3) var(--glow-cyan);
}
/* the travelling light: a short bright segment sliding down the drawn line */
.tline::after {
  content: ""; position: absolute; inset-inline: calc(-1 * 1px); top: 0;
  height: var(--s-6); border-radius: var(--r-pill);
  background: linear-gradient(180deg, transparent, var(--cyan-300), transparent);
  opacity: 0;
}
@keyframes f-thread-travel {
  from { transform: translateY(calc(-1 * var(--s-6))); opacity: 0; }
  12% { opacity: 1; }
  88% { opacity: 1; }
  to { transform: translateY(var(--tseg-h)); opacity: 0; }
}
/* .in-view is stamped by an IntersectionObserver in f.js: ten always-running
   travellers measured as a real FPS regression under the ×4-throttle gate
   (mean 57.5→50.4, worst frame 29.9). Only the connector actually on screen
   animates — at most two at a time. The lite tier runs the SAME observer, so
   widening the selector here cannot smuggle in an always-on animation. */
:is(html.motion-on, html.motion-lite) .tseg.in-view .tline::after { animation: f-thread-travel 2.6s linear infinite; }
.tknot {
  position: absolute; bottom: calc(-1 * var(--s-1)); inset-inline-start: 50%;
  width: var(--s-3); height: var(--s-3);
  transform: translateX(50%);
  border-radius: var(--r-pill); background: var(--cyan-300);
}
.kg {
  position: absolute; inset: calc(-1 * var(--s-1));
  border-radius: var(--r-pill);
  box-shadow: 0 0 var(--s-3) var(--cyan-400), 0 0 var(--glow-sm) var(--glow-cyan);
}
.pdot {
  display: inline-block; width: var(--s-2); height: var(--s-2);
  border-radius: var(--r-pill); background: var(--teal-400);
  box-shadow: 0 0 var(--s-3) var(--glow-teal);
}
@keyframes f-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.6); opacity: 0.55; }
}
:is(html.motion-on, html.motion-lite) .pdot.in-view { animation: f-pulse 2.2s var(--ease-in-out) infinite; }

/* ── hero: orbit paths over the living field ──────────────────────────── */
.hero {
  position: relative; overflow: clip;
  padding-block: var(--s-20) var(--s-12);
  min-height: 100dvh; display: flex; flex-direction: column; justify-content: center;
}
.layer { position: absolute; inset: 0; pointer-events: none; }
.layer-bg { z-index: 0; }
.layer-bg .bg-plate, .layer-bg img { width: 100%; height: 100%; object-fit: cover; }
.layer-bg .scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, var(--scrim-veil), var(--scrim-veil) 55%, var(--bg-deep) 96%);
}
/* `not all and (min-width: 768px)` — the EXACT complement of the motion tier's
   own query, not `(max-width: 767px)`. Widths are fractional: at browser zoom
   this page measures 767.27px, which is neither ≥768 nor ≤767, and the two
   would disagree about which page the visitor is on. Same expression as the
   lite matchMedia context in f.js, for the same reason. */
@media not all and (min-width: 768px) {
  .layer-bg .scrim {
    background: linear-gradient(180deg, var(--scrim-text), var(--scrim-text) 60%, var(--bg-deep) 96%);
  }
}
.layer-live { z-index: 1; width: 100%; height: 100%; }
/* THE SCENE ARRIVES — it does not switch on.
   The canvas starts transparent and f.js adds `is-awake` only AFTER the first
   frame has actually been rendered, so what fades in is a picture rather than a
   blank element that later fills. Scoped to `html.motion-on` for the same
   reason every hiding rule on this page is: that class is JS-only, so the no-JS
   page, the reduced-motion page and the lite tier all get the canvas at its
   natural opacity — where it is an empty, transparent element anyway. Nothing
   the page SAYS is behind this class; the canvas is decorative and aria-hidden,
   and the hero is complete beneath it (plate + scrim + orbits).

   THE TRANSITION BELONGS TO THE WAKE STATE, NOT THE RESTING ONE, and that is
   not tidiness — it was measured. With `transition` declared on the base rule,
   the resting state is animatable in BOTH directions, and the moment this
   stylesheet lands late (it is the third of three <link>s, behind a cross-origin
   font sheet) the canvas transitions 1 → 0 instead of simply starting at 0. Add
   `is-awake` while that is still in flight and CSS reverses it — and a reversal
   is deliberately shortened in proportion to how far the outgoing transition
   got, so the «fade» collapsed to ~12ms: sampled per frame over two cold loads,
   one ran the full 0 → 1 over ~670ms and the other snapped 0.13 → 0.97 in one
   frame. Declared here instead, the resting state cannot animate at all — a
   late stylesheet just puts the (still empty) canvas at 0 with no transition to
   interrupt, and the only animation this element can ever run is the one
   arriving with `is-awake`. */
html.motion-on .layer-live { opacity: 0; }
html.motion-on .layer-live.is-awake { opacity: 1; transition: opacity var(--dur-slow) var(--ease-out); }
.hero > .wrap { position: relative; z-index: 2; }

.hero-grid { display: grid; gap: var(--s-12); align-items: center; }
.hero-copy { max-width: 60ch; }
.kicker { color: var(--cyan-300); font-weight: 600; font-size: var(--t-small); margin-bottom: var(--s-4); }
h1 { font-size: var(--t-h1); line-height: var(--lh-tight); font-weight: 700; }
.h1-a, .h1-b { display: block; }
.h1-b.accent {
  background: linear-gradient(90deg, var(--cyan-300), var(--teal-400));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.sub { color: var(--ink-1); font-size: var(--t-lead); margin-top: var(--s-5); max-width: 52ch; }
.cta-row { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-top: var(--s-8); }
.note { display: flex; align-items: center; gap: var(--s-2); color: var(--ink-1); font-size: var(--t-small); margin-top: var(--s-5); }
.note svg { color: var(--teal-400); flex: none; }

/* «المسارات» — the orbit system, tokenized from the live hero.
   Static (rings + chips in place) without JS; the spin lives under motion-on,
   so reduced-motion and no-JS get a complete, calm composition. */
.hero-visual { position: relative; aspect-ratio: 1; max-width: 30rem; margin-inline: auto; width: 100%; }
.orbits { position: absolute; inset: 0; }
.orbit {
  position: absolute; top: 50%; left: 50%; translate: -50% -50%;
  border-radius: var(--r-pill);
}
.orbit::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  border: 1px dashed var(--line-strong);
}
.orbit.o1 { width: 100%; height: 100%; }
.orbit.o2 { width: 78%; height: 78%; }
.orbit.o3 { width: 55%; height: 55%; }
@keyframes f-spinCW { to { transform: rotate(360deg); } }
@keyframes f-spinCCW { to { transform: rotate(-360deg); } }
/* Eleven layers turn here, and until this round they turned FOREVER — including
   the ~17000px of document where the hero is nowhere near the screen. Measured
   below the hero (a fixed 2400→4200px trajectory, ×4 throttle): 48.9 fps with
   them ticking, 54.3 with them stopped. They now hang off `.orbits.in-view`
   like every other ambient animation on this page, which is the registry the
   hero orbit system was simply never added to. */
:is(html.motion-on, html.motion-lite) .orbits.in-view .orbit.o1 { animation: f-spinCCW 60s linear infinite; }
:is(html.motion-on, html.motion-lite) .orbits.in-view .orbit.o2 { animation: f-spinCW 48s linear infinite; }
:is(html.motion-on, html.motion-lite) .orbits.in-view .orbit.o3 { animation: f-spinCCW 34s linear infinite; }
.sat { position: absolute; transform: translate(-50%, -50%); }
:is(html.motion-on, html.motion-lite) .orbits.in-view .cs-1 { animation: f-spinCW 60s linear infinite; }
:is(html.motion-on, html.motion-lite) .orbits.in-view .cs-2 { animation: f-spinCCW 48s linear infinite; }
:is(html.motion-on, html.motion-lite) .orbits.in-view .cs-3 { animation: f-spinCW 34s linear infinite; }
.sat-chip {
  display: grid; place-items: center;
  width: var(--s-12); height: var(--s-12); border-radius: var(--r-pill);
  background: var(--glass-bg); border: 1px solid var(--glass-brd);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  box-shadow: var(--shadow-card);
}
.sat-chip svg { width: var(--s-6); height: var(--s-6); }
.sat-word { font-size: var(--t-small); font-weight: 700; color: var(--ink-0); }
/* The 24/7 core sits on its own opaque disc: the plate's bokeh orbs drift
   under the orbit centre, and worst-pixel contrast must be structural. */
.orbit-core {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  text-align: center; padding: var(--s-5) var(--s-8);
  background: var(--scrim-strong); border: 1px solid var(--line);
  border-radius: var(--r-card);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}
.orbit-core b { display: block; font-size: var(--t-metric); font-weight: 700; color: var(--cyan-300); line-height: var(--lh-tight); }
.orbit-core span { display: block; font-size: var(--t-small); color: var(--ink-1); margin-top: var(--s-2); }

@media (min-width: 1024px) {
  .hero-grid { grid-template-columns: 1.05fr 0.95fr; }
  .hero { padding-block: var(--s-24) var(--s-16); }
}
@media (max-width: 1023px) {
  .hero { min-height: auto; }
}

/* An invitation to travel: the cue bounces gently at the hero's foot. */
.scroll-cue {
  position: absolute; bottom: var(--s-5); left: 50%; transform: translateX(-50%);
  z-index: 2; color: var(--cyan-300); opacity: 0.8; pointer-events: none;
}
@keyframes f-cue {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50% { transform: translateX(-50%) translateY(var(--s-2)); }
}
:is(html.motion-on, html.motion-lite) .scroll-cue.in-view { animation: f-cue 2s var(--ease-in-out) infinite; }
@media (max-width: 1023px) { .scroll-cue { display: none; } }

.ticker-wrap { margin-top: var(--s-16); }
.ticker-note { color: var(--ink-2); font-size: var(--t-small); margin-bottom: var(--s-4); }
.bead-string { display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-4); align-items: center; }
.bead {
  display: inline-flex; align-items: center;
  padding: var(--s-2) var(--s-4); border-radius: var(--r-pill);
  background: var(--glass-bg); border: 1px solid var(--line);
  color: var(--ink-1); font-size: var(--t-small);
}

/* ── sections ─────────────────────────────────────────────────────────── */
/* ── SECTION RHYTHM — measured, not taste ─────────────────────────────────
   The shipped scale put this much EMPTY between every pair of sections, with
   nothing inside it but a 2px line:

       393px phone   192–233px   = 26–32% of a 730px screen
       834px tablet  214–255px
       1440px desk   320px flat

   That figure is `--sec-y` twice plus the connector, and the phone was the
   worst hit for a reason worth writing down: `clamp(64px, 9vw, 128px)` never
   reaches its 9vw middle term on a narrow screen — 9vw is 35px at 393px — so
   a phone always sat on the 64px FLOOR, the one term that does not scale
   down. The floor was doing all the damage, and it was invisible to anyone
   reading the clamp as "responsive".

   Lower floor, gentler slope, lower ceiling. The rhythm still reads generous
   on a desktop and stops being a scroll tax on a phone.

   Scoped to this variant's own body rather than edited in tokens.css, which
   is shared by all six preview variants and would move A–E with it. */
body.home { --sec-y: clamp(44px, 6vw, 96px); }

.section { position: relative; padding-block: var(--sec-y); }
.eyebrow {
  display: inline-flex; align-items: center; gap: var(--s-2);
  color: var(--teal-400); font-weight: 600; font-size: var(--t-small);
  margin-bottom: var(--s-3); padding: var(--s-1) var(--s-4);
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--glass-bg);
}
.eyebrow::before {
  content: ""; width: var(--s-2); height: var(--s-2); border-radius: var(--r-pill);
  background: var(--teal-400); box-shadow: 0 0 var(--s-3) var(--glow-teal);
}
.sec { font-size: var(--t-h2); line-height: var(--lh-head); font-weight: 700; max-width: 30ch; }
.lead { color: var(--ink-1); font-size: var(--t-lead); margin-top: var(--s-4); max-width: 62ch; }
.section-cta { margin-top: var(--s-10); }
.more { color: var(--ink-1); margin-top: var(--s-8); }
.more a { color: var(--cyan-300); }

/* WHY — the journey's manifesto: centred, with a lit thread-mark beneath.
   Same words; the treatment alone declares «هنا تبدأ الرحلة». */
.why .wrap { text-align: center; }
.why .sec { margin-inline: auto; }
.why .sec::after {
  content: ""; display: block; width: var(--s-16); height: 2px;
  margin: var(--s-6) auto 0; border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--cyan-300), var(--teal-400));
  box-shadow: 0 0 var(--s-3) var(--glow-cyan);
}
.why .lead { margin-inline: auto; }

.grid { display: grid; gap: var(--s-5); margin-top: var(--s-10); }
/* minmax(0, 1fr), never bare 1fr. `1fr` is minmax(auto, 1fr), and the AUTO
   MINIMUM is the trap: a track may not shrink below its item's min-content,
   and Safari takes a sector card's min-content from the 1200px-wide photograph
   inside it — the track inflates, the card renders wider than the phone, and
   the page grows a leftward overflow (RTL). Chrome resolves the same card to
   the container width, which is why every desktop-side measurement of this
   page read «no overflow» while the owner's iPhone photographed one. A zero
   minimum makes the track's width the CONTAINER's business, not the image's,
   on every engine alike. */
.grid-3 { grid-template-columns: minmax(0, 1fr); }
.grid-4 { grid-template-columns: minmax(0, 1fr); }
/* …and the items give up their auto minimum too. The track fix above and this
   line close the same hole from both ends, deliberately: desktop WebKit caps
   the card at its slot even with the image uncapped (measured — track held
   313px with max-width:none injected), so whatever sizing path iOS Safari
   takes to inflate it CANNOT be reproduced or regression-tested from here.
   Belt and braces is the honest posture when the failing engine is
   unreachable: min-width:0 costs nothing on engines that already cap. */
.grid > * { min-width: 0; }
@media (min-width: 768px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) {
  .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.card {
  display: block; padding: var(--s-6); border-radius: var(--r-card);
  background: var(--surface-1); border: 1px solid var(--line);
  text-decoration: none; list-style: none;
  transition: transform var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
a.card:hover { transform: translateY(calc(-1 * var(--s-1))); border-color: var(--cyan-400); box-shadow: 0 var(--s-3) var(--glow-sm) var(--glow-cyan-soft); }
.card h3 { font-size: var(--t-h3); margin-bottom: var(--s-2); }
.card p { color: var(--ink-1); font-size: var(--t-small); }
/* shared icon chip for sector cards and integration pillars */
.card-ico {
  display: grid; place-items: center;
  width: var(--s-12); height: var(--s-12);
  border-radius: var(--r-pill); margin-bottom: var(--s-4);
  background: var(--glass-bg); border: 1px solid var(--glass-brd);
  color: var(--cyan-300);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.card-ico svg { width: var(--s-6); height: var(--s-6); }
a.card:hover .card-ico { color: var(--teal-400); border-color: var(--cyan-400); }

/* ── HOW: stations on the spine, revealed line → number → card ────────── */
.journey { position: relative; list-style: none; margin-top: var(--s-16); padding-block: var(--s-4); }
.jline {
  position: absolute; top: 0; bottom: 0; inset-inline-start: var(--s-3); width: 2px;
  margin-inline-start: calc(-1px);
  background: linear-gradient(180deg, var(--cyan-300), var(--cyan-500), var(--teal-400));
  border-radius: var(--r-pill);
  box-shadow: 0 0 var(--s-3) var(--glow-cyan);
  transform-origin: top center;
}
.station { position: relative; padding-inline-start: var(--s-12); padding-block: var(--s-5); }
.step-knot {
  position: absolute; inset-inline-start: var(--s-3); top: var(--s-8);
  transform: translateX(50%);
  width: var(--s-10); height: var(--s-10); border-radius: var(--r-pill);
  display: grid; place-items: center;
  background: var(--surface-2); border: 2px solid var(--cyan-300);
  color: var(--cyan-300); font-weight: 700;
}
.step-knot .kg { inset: calc(-1 * var(--s-2)); }
/* the station the visitor is AT breathes (class from ScrollTrigger) */
@keyframes f-knot-live {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.25); opacity: 0.7; }
}
:is(html.motion-on, html.motion-lite) .step-knot.is-live .kg { animation: f-knot-live 1.8s var(--ease-in-out) infinite; }
.station-card {
  position: relative;
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s-6);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.station-card:hover { border-color: var(--line-strong); }
/* a lit stub ties each card back to its knot on the spine (desktop) */
.station-card::before {
  content: ""; position: absolute; top: var(--s-10); height: 2px; width: var(--s-6);
  background: linear-gradient(90deg, var(--cyan-400), var(--teal-400));
  border-radius: var(--r-pill); box-shadow: 0 0 var(--s-3) var(--glow-cyan);
  display: none;
}
.station-card h3 { font-size: var(--t-h3); margin-bottom: var(--s-2); }
.station-card p { color: var(--ink-1); font-size: var(--t-small); }
@media (min-width: 1024px) {
  .jline { inset-inline-start: 50%; }
  .station { width: 50%; padding-inline: 0 var(--s-16); padding-block: var(--s-6); }
  .station .step-knot { inset-inline-start: auto; inset-inline-end: 0; transform: translateX(-50%); }
  .station.alt { margin-inline-start: auto; padding-inline: var(--s-16) 0; }
  .station.alt .step-knot { inset-inline-end: auto; inset-inline-start: 0; transform: translateX(50%); }
  /* card→knot stubs: default stations reach toward the spine on their
     inline-END edge; alternate ones on their inline-START */
  .station .station-card::before { display: block; inset-inline-end: calc(-1 * var(--s-6)); }
  .station.alt .station-card::before { inset-inline-end: auto; inset-inline-start: calc(-1 * var(--s-6)); }
}

/* ── STORY — A's pin, compact enough to always fit the pinned viewport ── */
/* The 100vh box belongs to the PIN, and the pin is registered from 768px up
   (f.js, `if (FULL) mm.add('(min-width: 768px)'…)`). Below that it was exactly
   the dead weight the note over .sect-pin warns about — that note drew the
   right conclusion and then only applied it to the reel; .story-pin and
   .price-pin kept theirs unscoped.
   On iOS it is worse than dead: `vh` resolves against the LARGE viewport, the
   one with the URL bar hidden, so the box is ~80px TALLER than the screen it
   is centring content inside — a phone-only band of empty that no desktop
   width can reproduce. `svh` is the honest unit for a pinned stage (on desktop
   it is identical to vh; on a phone it is the height actually on screen), with
   vh left in front of it as the fallback for anything older than Safari 15.4. */
.story-pin { display: flex; flex-direction: column; justify-content: center; padding-block: var(--s-6); }
@media (min-width: 768px) { .story-pin { min-height: 100vh; min-height: 100svh; } }
.story-head .sec { max-width: none; }
.story-head .lead { margin-top: var(--s-2); }
.story-stage { display: grid; gap: var(--s-6); margin-top: var(--s-8); align-items: start; }
/* Two columns from 768 (not 1024): the story pins from 768, and a stacked
   chat+flow column measures ~1000px against a 900px pinned viewport — the
   pin-fit gate rightly fails that. Side by side, the tallest column fits. */
@media (min-width: 768px) { .story-stage { grid-template-columns: 1.1fr 0.9fr; } }

.chat {
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s-5);
  max-width: 30rem;
}
.chat-head { display: flex; align-items: center; gap: var(--s-3); padding-bottom: var(--s-3); border-bottom: 1px solid var(--line); }
.av {
  width: var(--s-10); height: var(--s-10); border-radius: var(--r-pill);
  display: grid; place-items: center; background: var(--grad-cta); color: var(--bg-deep); font-weight: 700;
}
.who { display: flex; flex-direction: column; }
.who small { color: var(--teal-400); font-size: var(--t-micro); }
.day { text-align: center; color: var(--ink-2); font-size: var(--t-micro); margin-block: var(--s-3); }
.beat { display: flex; margin-block: var(--s-2); }
.beat p { padding: var(--s-3) var(--s-4); border-radius: var(--r-chip); max-width: 85%; font-size: var(--t-small); }
.beat-in { justify-content: flex-start; }
.beat-in p { background: var(--surface-2); border: 1px solid var(--line); }
.beat-out { justify-content: flex-end; }
.beat-out p { background: var(--bg-glow); border: 1px solid var(--glass-brd); }
.beat-card { justify-content: flex-start; }
.mini-card {
  display: flex; align-items: center; gap: var(--s-4);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-chip); padding: var(--s-3) var(--s-4);
}
.mini-card .price { color: var(--cyan-300); font-weight: 700; }
.beat-paid { justify-content: center; }
.beat-paid .paid { color: var(--success); font-weight: 600; }
.wa-input {
  margin-top: var(--s-3); padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line); border-radius: var(--r-pill);
  color: var(--ink-2); font-size: var(--t-small);
}

.flowcard {
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s-5);
}
.flowcard .hd { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s-4); }
.flowcard .live { display: inline-flex; align-items: center; gap: var(--s-2); color: var(--teal-400); font-size: var(--t-micro); font-weight: 600; }
.fthread { position: relative; padding-inline-start: var(--s-8); }
.fline {
  position: absolute; inset-block: var(--s-2); inset-inline-start: var(--s-2); width: 2px;
  background: linear-gradient(180deg, var(--cyan-300), var(--cyan-500), var(--teal-400));
  border-radius: var(--r-pill); transform-origin: top center;
  box-shadow: 0 0 var(--s-3) var(--glow-cyan);
}
.fnode { position: relative; padding-block: var(--s-3); display: flex; align-items: center; gap: var(--s-3); }
/* «الرحلة خلف المحادثة» — same defect, own arithmetic. `.fline` sits at --s-2
   and is 2px wide, so its centre is 9px in from the thread's edge, while this
   knot was landing at 12: THREE pixels off, the widest miss of the three.
   Measured by rect, not derived: line centre 626.9, knot centre 623.9. +3px
   (−20 → −23) puts the thread through the middle of the circle. */
.fknot {
  position: absolute; inset-inline-start: calc(-1 * var(--s-6) + 1px); top: 50%;
  width: var(--s-3); height: var(--s-3); border-radius: var(--r-pill);
  background: var(--cyan-300); transform: translateX(50%) translateY(-50%);
}
.fico {
  display: grid; place-items: center; flex: none;
  width: var(--s-8); height: var(--s-8); border-radius: var(--r-pill);
  background: var(--glass-bg); border: 1px solid var(--glass-brd);
  color: var(--cyan-300);
}
.fico svg { width: var(--s-4); height: var(--s-4); }
.ftxt { display: flex; flex-direction: column; }
.ftxt small { color: var(--ink-2); font-size: var(--t-micro); }
.ftxt b { color: var(--ink-0); font-size: var(--t-small); }
.story-after { padding-top: var(--s-10); }
.flow-shot { max-width: var(--media-card-max); }
.disclaimer { color: var(--ink-2); font-size: var(--t-micro); margin-top: var(--s-6); }

/* ══ SECTORS — «محطّات الحلول»: a six-chapter reel on the thread ══════════
   BASE = an ordinary responsive grid of six complete chapter cards, each with
   its real photograph. That is what renders with JS off, under reduced motion,
   below 768px, and if the CDN never answers. Every rule that stacks or
   overlaps anything is scoped under `html.motion-on .sect-pin.deck-on` — TWO
   classes, both JS-only, and `deck-on` is added only after f.js measures that
   the deck fits this viewport.

   NOTE the min-height is NOT here: an unscoped `min-height:100vh` would wrap a
   plain grid in a 100vh box in exactly the modes that have no reel, which is
   dead weight in the document that matters most. It lives in deck mode only. */
.sect-pin { padding-block: var(--s-6); }
.sect-head .sec { max-width: 34ch; }
.sect-deck { position: relative; margin-top: var(--s-8); }
.sect-stage { margin-top: 0; }
.sect-after { padding-top: var(--s-12); }

/* ── the chapter card (base and deck share these) ─────────────────────── */
.sect-card { padding: 0; overflow: clip; display: flex; flex-direction: column; }
.chap-media {
  position: relative; display: block;
  aspect-ratio: 4 / 3; overflow: clip;
  background: var(--surface-2);
}
.chap-shot { display: block; width: 100%; height: 100%; }
.chap-shot img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform var(--dur-slow) var(--ease-out);
}
a.sect-card:hover .chap-shot img { transform: scale(1.04); }
/* The photograph is a real room in real light; the page is midnight navy.
   This veil is what marries them — and it is also why text never sits on the
   photo: the copy has its own solid surface below. */
.chap-veil {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 28%, var(--scrim-text));
}
/* The medallion: the sector's glyph on a soft disc, sitting ON the photo's
   dark foot — decorative in full, and outside every measured text box. */
.chap-medallion {
  position: absolute; inset-inline-start: var(--s-5); bottom: var(--s-5);
  display: grid; place-items: center;
  width: var(--s-16); aspect-ratio: 1; border-radius: var(--r-pill);
  background: var(--glass-bg); border: 1px solid var(--glass-brd);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}
.chap-glyph {
  display: block; width: 56%; height: 56%; color: var(--cyan-300);
  transition: color var(--dur) var(--ease-out);
}
.chap-glyph svg { width: 100%; height: 100%; display: block; }
a.sect-card:hover .chap-glyph { color: var(--teal-400); }

.chap-txt { display: block; padding: var(--s-6); }
.chap-txt h3 { font-size: var(--t-h3); margin-bottom: var(--s-2); }
.chap-txt p { color: var(--ink-1); font-size: var(--t-small); }
.chap-go {
  display: inline-grid; place-items: center; margin-top: var(--s-4);
  width: var(--s-10); height: var(--s-10); border-radius: var(--r-pill);
  border: 1px solid var(--line-strong); color: var(--cyan-300);
  transition: transform var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.chap-go svg { width: var(--s-5); height: var(--s-5); }
/* RTL: «forward» is toward the inline start — physically LEFT. */
a.sect-card:hover .chap-go { border-color: var(--cyan-400); transform: translateX(calc(-1 * var(--s-1))); }

/* deck-only chrome: nothing here carries a word. .sect-fill joins the list from
   the other end — it is MOBILE-only chrome, hidden everywhere else. */
.deck-in, .deck-out, .sect-rail, .sect-fill { display: none; }

/* Mobile: the six businesses hang as knots on the page's own thread. The rail
   and the knots are pure CSS and complete without JavaScript — every knot sits
   OUTSIDE the text boxes so it can never poison a contrast sample.
   On the `motion-lite` tier the thread also MOVES: a real fill element scrubs
   down the list and each knot lights as the visitor reaches it. Both of those
   live behind `html.motion-lite`, so the no-JS and reduced-motion pages keep
   exactly the settled composition that was approved. */
@media not all and (min-width: 768px) {
  .sect-deck { padding-inline-start: var(--s-8); }
  .sect-deck::before {
    content: ""; position: absolute; inset-block: var(--s-10);
    inset-inline-start: var(--s-3); width: 2px; border-radius: var(--r-pill);
    background: linear-gradient(180deg, var(--cyan-300), var(--teal-400));
    opacity: 0.6;
  }
  /* THE SIX KNOTS WERE NEVER ON SCREEN. The knot hangs on the thread OUTSIDE
     the card, and the card is `overflow: clip` (that is what rounds the
     photograph's corners) — so all six were being clipped away, in every
     mobile render since the deck was written. Verified in the browser: the
     pseudo-element resolves to background rgb(125,227,255) at
     inset-inline-start:-23px, and nothing appears in the gutter.

     The first fix was `overflow-clip-margin`, and it was WRONG for half the
     phones on earth: WebKit does not implement it, so the property is dropped
     and the clip stays exactly as it was. Proven rather than assumed — the same
     page in Playwright's WebKit still clipped the knot while Chromium showed
     it. Since this block is mobile-only, the portable answer is simply not to
     clip: the card's background and border still paint to its rounded corners,
     and .chap-media carries the top radius itself so the photograph's corners
     are rounded by the media box rather than borrowed from the card's clip. */
  .sect-card { position: relative; overflow: visible; }
  .chap-media { border-start-start-radius: var(--r-card); border-start-end-radius: var(--r-card); }
  /* −1px, not +1px — the same two-pixel error the FAQ knot carried, and the
     same correction: 12px knot centred on a rail whose centre is 13px in.
     Measured off by exactly 2px before the change, 0 after. */
  .sect-card::before {
    content: ""; position: absolute; top: var(--s-16);
    inset-inline-start: calc(-1 * var(--s-6) - 3px);
    width: var(--s-3); height: var(--s-3); border-radius: var(--r-pill);
    background: var(--cyan-300); box-shadow: 0 0 var(--s-3) var(--glow-cyan);
  }
  /* the lit thread, scrubbed by GSAP (scaleY) — one transform, no paint */
  html.motion-lite .sect-fill {
    display: block; position: absolute; inset-block: var(--s-10);
    inset-inline-start: var(--s-3); width: 2px; border-radius: var(--r-pill);
    background: linear-gradient(180deg, var(--cyan-300), var(--teal-400));
    box-shadow: 0 0 var(--s-3) var(--glow-cyan);
    transform-origin: 50% 0%;
  }
  /* unlit until the thread arrives; toggleClass un-lights it on the way back
     up for free, exactly like the FAQ knots */
  html.motion-lite .sect-card::before {
    background: var(--bg-deep); border: 1px solid var(--line-strong); box-shadow: none;
    transition: background var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
  }
  html.motion-lite .sect-card.is-lit::before {
    background: var(--cyan-300); border-color: var(--cyan-300);
    box-shadow: 0 0 var(--s-3) var(--cyan-400), 0 0 var(--glow-sm) var(--glow-cyan);
  }
}

/* ══ DECK MODE ═══════════════════════════════════════════════════════════ */
html.motion-on .sect-pin.deck-on {
  min-height: 100vh; display: flex; flex-direction: column; justify-content: center;
}
html.motion-on .sect-pin.deck-on .sect-head { text-align: center; }
/* The chapter is the star: the section heading steps down to a series title so
   the biggest line on screen is the SECTOR. Also buys back ~90px of the 900px
   pinned budget. */
html.motion-on .sect-pin.deck-on .sect-head .sec {
  font-size: var(--t-h3); max-width: none; margin-inline: auto;
}
html.motion-on .sect-pin.deck-on .sect-head .lead {
  font-size: var(--t-small); max-width: 74ch; margin-inline: auto; margin-top: var(--s-3);
}

html.motion-on .sect-pin.deck-on .deck-in,
html.motion-on .sect-pin.deck-on .deck-out {
  display: block; width: 2px; height: var(--s-6);
  margin: var(--s-3) auto; border-radius: var(--r-pill);
  background: linear-gradient(180deg, var(--cyan-300), var(--cyan-500));
  box-shadow: 0 0 var(--s-3) var(--glow-cyan);
}
html.motion-on .sect-pin.deck-on .deck-out {
  background: linear-gradient(180deg, var(--cyan-500), var(--teal-400));
}

/* The stage. `--stage-h` is measured and written by f.js from the tallest
   chapter, because absolutely-positioned cards contribute no height.
   overflow-x: clip (legal beside overflow-y: visible) lets a chapter wipe off
   stage without ever touching document width. */
html.motion-on .sect-pin.deck-on .sect-stage {
  display: block; position: relative; margin: 0;
  min-height: var(--stage-h, var(--s-32));
  overflow-x: clip; overflow-y: visible;
}
html.motion-on .sect-pin.deck-on .sect-card {
  position: absolute; inset-inline: 0; top: 0;
  max-width: calc(var(--s-32) * 7); margin-inline: auto;
  flex-direction: row-reverse; align-items: stretch;
  background: var(--surface-1); border-color: var(--line-strong);
  box-shadow: var(--shadow-card);
  /* Nothing GSAP scrubs may appear in a CSS transition: the deck scrubs
     transform and opacity on these six every frame, and an inherited
     transform transition turns each scrub frame into a chase. */
  transition: border-color var(--dur) var(--ease-out);
}
html.motion-on .sect-pin.deck-on a.sect-card:hover { transform: none; }
html.motion-on .sect-pin.deck-on .chap-media {
  flex: 1 1 46%; aspect-ratio: auto; min-height: min(42vh, calc(var(--s-32) * 3));
}
/* Deck mode: row-reverse in RTL puts the photo on the PHYSICAL LEFT and the
   copy on the right, so the veil darkens toward the right where the two meet.
   Gradient angles are physical and do not flip with direction. */
html.motion-on .sect-pin.deck-on .chap-veil {
  background: linear-gradient(90deg, transparent 35%, var(--scrim-veil));
}
html.motion-on .sect-pin.deck-on .chap-txt {
  flex: 1 1 54%; align-self: center; padding: var(--s-10);
}
html.motion-on .sect-pin.deck-on .chap-txt h3 {
  font-size: var(--t-h2); line-height: var(--lh-head); margin-bottom: var(--s-3);
}
html.motion-on .sect-pin.deck-on .chap-txt p { font-size: var(--t-lead); max-width: 46ch; }
html.motion-on .sect-pin.deck-on .sect-more {
  text-align: center; margin-top: var(--s-5); font-size: var(--t-small);
}

/* ── the station rail: six knots, a rider, and a thread that fills ────── */
html.motion-on .sect-pin.deck-on .sect-rail {
  display: block; position: relative;
  max-width: calc(var(--s-32) * 4); margin-inline: auto;
  padding-block: var(--s-3);
}
/* No CSS transform on track/fill — GSAP owns transform on .rail-fill, so the
   vertical centring is done with margin instead of translateY. */
.rail-track, .rail-fill {
  position: absolute; top: 50%; margin-top: calc(-1 * 1px);
  inset-inline: 0; height: 2px; border-radius: var(--r-pill);
}
.rail-track { background: var(--line-strong); }
.rail-fill {
  background: linear-gradient(270deg, var(--cyan-300), var(--teal-400));
  box-shadow: 0 0 var(--s-3) var(--glow-cyan);
}
.rail-knots { position: relative; display: flex; justify-content: space-between; align-items: center; }
/* dir=rtl + space-between ⇒ knot 0 is the RIGHTMOST: the thread arrives from
   the right and travels left, which is the reading direction. */
.sect-knot {
  position: relative; width: var(--s-3); height: var(--s-3);
  border-radius: var(--r-pill);
  background: var(--bg-deep); border: 1px solid var(--line-strong);
}
.kcore {
  position: absolute; inset: calc(-1 * 1px); border-radius: var(--r-pill);
  background: var(--cyan-300);
  box-shadow: 0 0 var(--s-3) var(--cyan-400), 0 0 var(--glow-sm) var(--glow-cyan);
}
/* THE RIDER STARTS ON KNOT 0, which in RTL is the RIGHTMOST — so it is anchored
   inset-inline-START. It was anchored inline-END (physical LEFT), i.e. parked on
   the LAST knot, and f.js then tweens it x: -(n/last)·span() — further left
   still. Measured: knot centres ran 970…470, the rider sat at 470 and travelled
   to 170, roughly 294px off the end of the rail it is supposed to ride.
   The offset centres the 16px rider on the 12px knot: (12 − 16) / 2 = −2px. */
.rail-rider {
  position: absolute; top: calc(50% - var(--s-2));
  inset-inline-start: calc((var(--s-3) - var(--s-4)) / 2);
  width: var(--s-4); height: var(--s-4); border-radius: var(--r-pill);
  background: var(--cyan-300);
}
/* Gated to .in-view by the shared IntersectionObserver in f.js. */
html.motion-on .sect-deck.in-view .rail-rider .kg {
  animation: f-pulse 2.2s var(--ease-in-out) infinite;
}
/* «the string does not stop at six» — so the fading beads belong PAST THE LAST
   knot, which is the inline-END side (physical left in RTL), in the direction
   the thread is still travelling. They used to sit at inline-start, i.e. behind
   knot 0, where the thread has already been. */
.rail-more {
  position: absolute; top: 50%; margin-top: calc(-1 * 1px);
  inset-inline-end: calc(-1 * var(--s-8));
  display: flex; align-items: center; gap: var(--s-2);
}
.rail-more i { width: 2px; height: 2px; border-radius: var(--r-pill); background: var(--line-strong); }
.rail-more i:nth-child(2) { opacity: 0.66; }
.rail-more i:nth-child(3) { opacity: 0.36; }

/* ── the whatsapp-sellers band ────────────────────────────────────────── */
.band {
  margin-top: var(--s-8); padding: var(--s-10);
  border-radius: var(--r-card); background: var(--surface-2);
  border: 1px solid var(--line-strong);
}
.band h3 { font-size: var(--t-h3); }
.band p { color: var(--ink-1); margin-block: var(--s-3) var(--s-6); max-width: 68ch; }
/* The band stops being a slab: copy in one column, the two actions stacked
   beside it, so the eye meets the offer and the action together. */
@media (min-width: 768px) {
  .band { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--s-10); align-items: center; }
  .band h3, .band p { grid-column: 1; }
  .band p { margin-block: var(--s-3) 0; }
  .band .cta-row { grid-column: 2; grid-row: 1 / span 2; flex-direction: column; margin-top: 0; }
}

/* ── INTEGRATIONS chips on the thread ─────────────────────────────────── */
.chip-string {
  position: relative; display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-5);
  align-items: center; margin-top: var(--s-12); padding-block: var(--s-3);
}
/* The chips are beads on a thread: `.cline` runs at top:50% and they sit ON
   it. That reading holds only while they are ONE row. The moment they wrap —
   which they do on every phone — top:50% lands in the GAP BETWEEN the two
   rows, and the thread stops being a thread and becomes a rule slicing the
   group in half. It reads as a stray horizontal line, which is exactly how
   the owner reported it.
   CSS has no "did this flex container wrap" query, so the line is tied to the
   width at which the row still fits. Below that the chips are simply a
   wrapped group, which needs no line to be legible. */
.cline { display: none; }
@media (min-width: 768px) {
  .cline {
    display: block;
    position: absolute; inset-inline: 0; top: 50%; height: 2px;
    background: linear-gradient(90deg, var(--cyan-300), var(--cyan-500), var(--teal-400));
    border-radius: var(--r-pill); opacity: 0.7;
    box-shadow: 0 0 var(--s-3) var(--glow-cyan);
  }
}
.chip {
  position: relative; display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-3) var(--s-5); border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--line-strong);
  color: var(--ink-0); font-size: var(--t-small);
}
.chip small { color: var(--ink-2); }

/* ── SUITES: the platform rail ────────────────────────────────────────
   BASE = a normal wrapping grid (all eight visible: the no-JS, mobile and
   reduced-motion truth). The sideways rail exists only while JS holds the
   `rail-on` class inside matchMedia(≥768px), and reverts cleanly. */
.suite-ico {
  display: grid; place-items: center;
  width: var(--s-12); height: var(--s-12);
  border-radius: var(--r-pill); margin-bottom: var(--s-4);
  background: var(--glass-bg); border: 1px solid var(--glass-brd);
  color: var(--cyan-300);
}
.suite-ico svg { width: var(--s-6); height: var(--s-6); }
/* Base (grid) mode: the clip behaves like a .wrap so the eight cards keep
   their gutters. Rail mode strips that so the sideways track can bleed. */
.rail-clip { overflow: hidden; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--s-6); }
/* `width: 100%` is the whole rail. Without it THE RAIL NEVER EXISTED — not
   once, at any width, since the section was written.
   `.suites-pin.rail-on` is a column flex container, so `.rail-clip` is a flex
   item, and dropping its max-width let it take its CONTENT width instead of its
   container's: measured at 1440, clip.clientWidth came back 2722px — exactly the
   track's scrollWidth. f.js computes `shift() = track.scrollWidth -
   clip.clientWidth`, so shift was 0, the guard concluded there was nothing to
   slide, `rail-on` came straight back off, and the section quietly rendered as
   the base grid. A defect that reverts to a valid layout is a defect nobody
   reports. Pinning the clip to its container's width restores the overflow the
   slide is made of: 2722 vs 1440 ⇒ 1282px of travel. */
.suites-pin.rail-on .rail-clip { max-width: none; padding-inline: 0; width: 100%; }
.suites-pin.rail-on { min-height: 100vh; display: flex; flex-direction: column; justify-content: center; }
/* progress beads under the rail — knots on the platform's own string.
   Rail chrome only: meaningless without the slide, so it exists only there. */
.rail-dots { display: none; }
.suites-pin.rail-on .rail-dots {
  display: flex; justify-content: center; align-items: center;
  gap: var(--s-3); margin-top: var(--s-8);
}
.rail-dot {
  width: var(--s-2); height: var(--s-2); border-radius: var(--r-pill);
  background: var(--line-strong);
  transition: background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.rail-dot.is-on {
  background: var(--cyan-300);
  box-shadow: 0 0 var(--s-3) var(--glow-cyan);
  transform: scale(1.4);
}
.suites-pin.rail-on .suites-track {
  display: flex; flex-wrap: nowrap; width: max-content;
  gap: var(--s-5); padding-inline: var(--s-6); margin-top: var(--s-10);
}
.suites-pin.rail-on .suite-card { width: clamp(16rem, 22vw, 21rem); flex: none; }

/* ── PRICING: the wrap stage ──────────────────────────────────────────── */
/* Same scoping as .story-pin above, same reason. */
.price-pin { display: flex; flex-direction: column; justify-content: center; padding-block: var(--s-6); }
@media (min-width: 768px) { .price-pin { min-height: 100vh; min-height: 100svh; } }
.plans-stage { position: relative; margin-top: var(--s-10); }
.wrap-svg {
  position: absolute; inset: calc(-1 * var(--s-10)) calc(-1 * var(--s-6));
  width: calc(100% + var(--s-12)); height: calc(100% + var(--s-20));
  overflow: visible; pointer-events: none; z-index: 1;
}
.plans { display: grid; gap: var(--s-5); }
/* minmax(0,1fr) كبقية شبكات الملف — لا 1fr عارية بعد اليوم (انظر تعليق .grid-3). */
@media (min-width: 1024px) { .plans { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; } }
.plan {
  position: relative; padding: var(--s-6); border-radius: var(--r-card);
  background: var(--surface-1); border: 1px solid var(--line);
  display: flex; flex-direction: column; gap: var(--s-3);
  transition: border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
.plan[data-featured] { background: var(--surface-2); box-shadow: var(--shadow-card); }
/* The thread's touch. The card does NOT lift: the wrap path is built against
   its un-transformed rect, so an 8px hop would peel the card off the very
   thread that is gripping it. It holds still and takes the light instead.
   The `motion-lite` tier reuses this exact state for its cheap pricing moment:
   no SVG, no wrap, no pin — just the plan nearest mid-viewport taking the same
   light as the visitor scrolls the stack, so the phone gets the gesture the
   desktop gets, at the cost of one class toggle. */
.plan.is-hugged {
  border-color: var(--cyan-300);
  box-shadow: var(--shadow-card), 0 0 var(--glow-md) var(--glow-cyan-soft);
}
/* Already knotted and passed: a quiet trace, so the visitor can see how far
   the thread has come without three cards shouting at once. */
.plan.is-tied { border-color: var(--line-strong); }
.plan-badge {
  position: absolute; top: calc(-1 * var(--s-4)); inset-inline-end: var(--s-6);
  background: var(--teal-400); color: var(--bg-deep);
  font-size: var(--t-micro); font-weight: 700;
  padding: var(--s-1) var(--s-3); border-radius: var(--r-pill); z-index: 2;
}
.pname { font-size: var(--t-h3); }
.price .num { font-size: var(--t-metric); font-weight: 700; color: var(--cyan-300); }
.price small { color: var(--ink-2); }
.price-live { color: var(--ink-2); font-size: var(--t-micro); }
.plan ul { list-style: none; display: flex; flex-direction: column; gap: var(--s-2); }
.plan li { color: var(--ink-1); font-size: var(--t-small); padding-inline-start: var(--s-5); position: relative; }
.plan li::before {
  content: ""; position: absolute; inset-inline-start: 0; top: var(--s-2);
  width: var(--s-2); height: var(--s-2); border-radius: var(--r-pill); background: var(--teal-400);
}
.plan-cta { margin-top: auto; }
.price-note { color: var(--ink-2); font-size: var(--t-small); margin-top: var(--s-8); }

/* ── faq ──────────────────────────────────────────────────────────────── */
.qa {
  position: relative;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--surface-1); margin-top: var(--s-4);
}
/* The FAQ runs on the page's own thread: a dim rail is the whole list, the
   lit fill is how far the thread has come, and each question wears a knot on
   it. Both rail and fill are decorative and live in the gutter. */
.faq-thread { position: relative; padding-inline-start: var(--s-8); margin-top: var(--s-6); }
.fq-rail, .fq-fill {
  position: absolute; inset-block: var(--s-6); inset-inline-start: var(--s-3);
  width: 2px; border-radius: var(--r-pill);
}
.fq-rail { background: var(--line-strong); }
.fq-fill {
  background: linear-gradient(180deg, var(--cyan-300), var(--teal-400));
  box-shadow: 0 0 var(--s-3) var(--glow-cyan);
  transform-origin: 50% 0%;
}
/* the knot: in the gutter, outside every measured text box */
/* CONCENTRIC WITH THE RAIL — MEASURED, never derived. Deriving this from the
   --s-* tokens produced a wrong answer twice, for two compounding reasons that
   are invisible in the cascade:

     · the knot's containing block is the card's PADDING box, so the card's 1px
       border shifts every figure by one; and
     · this pseudo-element is `content-box`. The reset gives `border-box` to
       elements but does not reach ::before/::after, so `width: var(--s-3)`
       (12px) plus its own 1px border RENDERS AT 14px, not 12 — and a 14px
       circle centred as though it were 12px sits a pixel off.

   Worse, the obvious check is circular: getBoundingClientRect cannot see a
   pseudo-element, so reading `inset-inline-start` back out of the cascade only
   re-states the assumption it was meant to test — which is exactly how the
   previous pass reported a confident "0" while the owner was still looking at
   the defect.
   The honest measurement clones the pseudo's full computed box — width, height,
   border widths, box-sizing, transform — into a real element in the same
   containing block and reads ITS rect. Measured that way: rail centre 1084.1,
   knot centre 1082.1 → two pixels short, so the thread cut a chord through the
   circle rather than passing through its middle. −3px lands it on the line.
   RETUNE THE SAME WAY. Do not recompute it from the tokens. */
.qa::before {
  content: ""; position: absolute; top: var(--s-6);
  inset-inline-start: calc(-1 * var(--s-6) - 3px);
  width: var(--s-3); height: var(--s-3); border-radius: var(--r-pill);
  background: var(--bg-deep); border: 1px solid var(--line-strong);
  transition: background var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
/* lit as the thread reaches it — and un-lit on the way back up, because the
   class is toggled by a ScrollTrigger that toggles in both directions */
.qa.is-lit::before {
  background: var(--cyan-300); border-color: var(--cyan-300);
  box-shadow: 0 0 var(--s-3) var(--cyan-400), 0 0 var(--glow-sm) var(--glow-cyan);
}
.qa[open]::before { background: var(--teal-400); border-color: var(--teal-400); }

.qa summary {
  list-style: none; cursor: pointer; display: flex; align-items: center;
  gap: var(--s-4); min-height: var(--tap-min); padding: var(--s-4) var(--s-6);
  font-weight: 600;
}
.qa summary::after { margin-inline-start: auto; }
.qa summary::-webkit-details-marker { display: none; }
.qa summary::after {
  content: ""; flex: none; width: var(--s-3); height: var(--s-3);
  border-inline-end: 2px solid var(--cyan-300); border-bottom: 2px solid var(--cyan-300);
  transform: rotate(45deg); transition: transform var(--dur-fast) var(--ease-out);
}
.qa[open] summary::after { transform: rotate(225deg); }
.qa .ans { padding: 0 var(--s-6) var(--s-6); color: var(--ink-1); }

/* ── FINAL: the last knot + the burst ─────────────────────────────────── */
.final .cta-band { position: relative; text-align: center; padding-block: var(--s-12); }
/* a faint echo of the hero's orbit paths behind the closing action */
.final .cta-band::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: min(calc(var(--s-32) * 4), 90vw); aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border: 1px dashed var(--line); border-radius: var(--r-pill);
  pointer-events: none;
}
@keyframes f-final-orbit { to { transform: translate(-50%, -50%) rotate(360deg); } }
:is(html.motion-on, html.motion-lite) .final .cta-band.in-view::before { animation: f-final-orbit 80s linear infinite; }
.final .cta-band > * { position: relative; }
.final h2 { font-size: var(--t-h2); }
.final p { color: var(--ink-1); margin-block: var(--s-4) var(--s-8); }
.knot-cta { position: relative; display: inline-block; }
.knot-cta::before, .knot-cta::after {
  content: ""; position: absolute; inset: calc(-1 * var(--s-3)) calc(-1 * var(--s-6));
  border: 1px solid var(--glass-brd); border-radius: var(--r-pill);
  pointer-events: none;
}
.knot-cta::after { inset: calc(-1 * var(--s-6)) calc(-1 * var(--s-12)); opacity: 0.45; }
.burst-svg {
  position: absolute; left: 50%; top: 50%;
  /* Capped by viewport: a fixed 640px box centred on the button measured as
     exactly 160px/125px of horizontal overflow at 320/390 — an empty svg
     still owns its box. */
  width: min(calc(var(--s-32) * 5), 94vw); height: min(calc(var(--s-32) * 3.5), 94vw);
  transform: translate(-50%, -50%);
  overflow: visible; pointer-events: none;
}
.knot-cta .btn { position: relative; z-index: 1; }

/* ── footer ───────────────────────────────────────────────────────────── */
/* The last thing on the page is the one thing guaranteed to meet the home
   indicator, and under viewport-fit=cover it does so with nothing in between. */
.foot {
  border-top: 1px solid var(--line);
  padding-block: var(--s-16) calc(var(--s-8) + env(safe-area-inset-bottom));
  background: var(--surface-1);
}
.foot-top { display: grid; gap: var(--s-10); }
@media (min-width: 768px) { .foot-top { grid-template-columns: 1.4fr repeat(3, 1fr); } }
.foot-pitch { color: var(--ink-1); font-size: var(--t-small); margin-block: var(--s-3) var(--s-6); max-width: 38ch; }
.foot-col h4 { font-size: var(--t-small); color: var(--ink-2); margin-bottom: var(--s-3); font-weight: 600; }
.foot-col ul { list-style: none; }
.foot-col a {
  display: flex; align-items: center; min-height: var(--tap-min);
  color: var(--ink-1); text-decoration: none; font-size: var(--t-small);
}
.foot-col a:hover { color: var(--cyan-300); }
.foot-copy { color: var(--ink-2); font-size: var(--t-micro); margin-top: var(--s-12); padding-top: var(--s-6); border-top: 1px solid var(--line); }
.foot-legal { color: var(--ink-2); font-size: var(--t-micro); margin-top: var(--s-2); }

/* ══ THE PHONE PAYS FOR GLASS IN A CURRENCY THE DESKTOP GATE NEVER BILLED ══
   LAST BLOCK IN THE FILE, DELIBERATELY. These selectors are plain classes, no
   more specific than the `.nav` / `.sat-chip` / `.orbit-core` /
   `.chap-medallion` rules they undo, so at equal specificity only source ORDER
   decides. Written up beside the scroll-quiet measurement — where it belongs
   by subject — it lost to all four of them in silence: the media query matched,
   the cascade ignored it, and the computed backdrop-filter never changed.
   If this block moves back up the file, it stops working.

   The measurement over scroll-quiet is a DESKTOP measurement (1440, ×4 CPU
   throttle) and it costed the eleven rotating hero layers as *animation*. It
   could not see the other half of the bill, because on a desktop GPU that half
   is nearly free: eight of those rotating layers are `.sat-chip`, and every one
   carries `backdrop-filter: blur(12px)`. A backdrop-filter on a TRANSFORMING
   element cannot be rasterised once and reused — the backdrop beneath it is a
   different set of pixels every frame, so the blur is re-sampled and re-run
   every frame, per chip. iOS Safari is the browser where that is ruinous
   rather than merely wasteful, which is why a page measured at 59.8fps on this
   desktop can still stutter in the hand.

   Sixteen blurred layers were live at 390px: the sticky nav (full-bleed, and
   the one element whose backdrop genuinely changes on every scroll frame),
   eight orbit chips, the orbit core, six chapter medallions. scroll-quiet
   cannot help with any of it — it pauses ANIMATION, while the nav's blur is
   driven by the scroll itself, so the quiet window is exactly when the nav
   costs the most.

   The effect is also worth the least here: at 390px the nav is a 65px bar of
   very dark glass and the chips are 48px discs. What the blur buys at that
   size is a few pixels of softness nobody is looking for. So below the motion
   tier's own breakpoint the blur comes off and the translucency is replaced
   with the colour it was already resolving to over this page's near-black
   ground — same bar, same discs, no per-frame re-blur. Desktop is untouched.

   `not all and (min-width: 768px)`, not `(max-width: 767px)`, for the
   fractional-width reason spelled out over the hero scrim rule. */
@media not all and (min-width: 768px) {
  .nav, .sat-chip, .orbit-core, .chap-medallion {
    backdrop-filter: none; -webkit-backdrop-filter: none;
  }
  /* Fully opaque, not 94%. With the blur gone the background is the ONLY
     thing separating the bar from the page, and the owner's screenshots show
     why the last 6% matters: a white card sliding under a translucent bar
     reads as a smear across the logo. On a 65px phone bar there is nothing
     for glass to be glass against — legibility takes the argument. */
  .nav { background: #061530; }
  .sat-chip, .chap-medallion { background: rgba(12, 40, 80, 0.88); }
  .orbit-core { background: rgba(3, 11, 26, 0.94); }
}
