/* ============================================================
   AD HOLIC — base, chrome, and the shared primitives
   ============================================================ */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scrollbar-width: thin;
  scrollbar-color: var(--plum-high) var(--void);
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--void);
  color: var(--ice);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video, svg { display: block; max-width: 100%; }
img, video { height: auto; }

/* Class-based display rules (.btn, .field …) out-specify the UA's
   [hidden] rule, so state it once with intent. */
[hidden] { display: none !important; }

h1, h2, h3, h4 { margin: 0; font-weight: 600; line-height: var(--lh-snug); }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--magenta-soft);
  outline-offset: 3px;
  border-radius: var(--r-1);
}

::selection { background: var(--magenta); color: var(--ice); }

*::-webkit-scrollbar { width: 10px; }
*::-webkit-scrollbar-track { background: var(--void); }
*::-webkit-scrollbar-thumb { background: var(--plum-high); border-radius: 8px; }

/* ---------- Shared primitives ---------- */

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { position: relative; padding-block: var(--section-y); }

/* The ribbon as a text treatment — the logo's own gradient. */
.ribbon-text {
  background: var(--ribbon);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* Fraunces' italic is the accent voice in Latin. Arabic has no
   italic, so there it is carried by the gradient alone. */
:root[data-lang="en"] .ribbon-text { font-style: italic; }

/* A 1px gradient border, drawn with two backgrounds so the fill
   stays flat plum and only the edge carries the ribbon. */
.ribbon-edge {
  border: 1px solid transparent;
  background:
    linear-gradient(var(--plum), var(--plum)) padding-box,
    var(--ribbon-120) border-box;
}

/* Section eyebrow: a short ribbon rule, then a tracked micro-label. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--tr-eyebrow);
  text-transform: uppercase;
  color: var(--magenta-soft);
}
.eyebrow::before {
  content: "";
  width: 32px;
  height: 1px;
  flex: none;
  background: linear-gradient(90deg, var(--magenta), transparent);
}
:root[dir="rtl"] .eyebrow::before {
  background: linear-gradient(270deg, var(--magenta), transparent);
}
/* Arabic is uppercase-less; forcing it does nothing but it also
   must not inherit the Latin transform on mixed strings. */
:root[data-lang="ar"] .eyebrow { text-transform: none; }

.h-display {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-3xl);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
}
.h-section {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-2xl);
  line-height: var(--lh-snug);
  letter-spacing: var(--tr-tight);
}
.lede {
  color: var(--lilac);
  font-size: var(--fs-lg);
  max-width: var(--maxw-text);
  line-height: var(--lh-body);
}
.h-display + .lede, .h-section + .lede { margin-top: var(--s-5); }

/* ---------- Buttons ---------- */

.btn {
  --btn-bg: transparent;
  --btn-fg: var(--ice);
  --btn-bd: var(--plum-high);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;
  padding: 0 var(--s-6);
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-bd);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: 700;
  overflow: hidden;   /* clips the press ripple */
  transition:
    background var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out),
    color var(--dur-2) var(--ease-out),
    transform var(--dur-1) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out);
}
.btn:hover { transform: scale(1.035); }
.btn:active { transform: scale(.985); }
.btn[disabled] { opacity: .45; pointer-events: none; transform: none; }

.btn--primary {
  --btn-bd: transparent;
  background: var(--ribbon);
  color: var(--ice);
}
.btn--primary:hover { box-shadow: var(--glow-magenta); }

.btn--ghost:hover { --btn-bd: var(--lilac); }

.btn__ico { width: 16px; height: 16px; flex: none; }
:root[dir="rtl"] .btn__ico--arrow { transform: scaleX(-1); }

/* Press ripple — spawned by main.js at the pointer. */
@keyframes ripple { to { transform: scale(3.4); opacity: 0; } }
.ripple {
  position: absolute;
  border-radius: var(--r-pill);
  background: rgba(246, 241, 248, .35);
  transform: scale(0);
  animation: ripple 550ms ease-out forwards;
  pointer-events: none;
}

/* ---------- Film grain ----------
   A static SVG noise tile at 5% — it sits over everything and takes
   no pointer events, which is what keeps the neon from looking flat. */
.grain::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- Header ---------- */

.header {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--z-header);
  height: var(--header-h);
  display: flex;
  align-items: center;
  border-bottom: 1px solid transparent;
  transition: background var(--dur-3) var(--ease-out),
              border-color var(--dur-3) var(--ease-out);
}
.header[data-stuck="true"] {
  background: rgba(19, 7, 20, .72);
  backdrop-filter: blur(14px) saturate(1.4);
  border-bottom-color: rgba(43, 17, 54, .6);
}
.header__in {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  gap: var(--s-5);
}

.brand { display: flex; align-items: center; gap: var(--s-3); flex: none; }
/* One image, not a mark plus a typeface approximation of the wordmark.
   The header lockup is cut from the brand artwork itself (see
   _dev/make-lockup.py), so the lettering on screen is the real thing. */
.brand__lockup { height: 34px; width: auto; display: block; }
@media (max-width: 860px) { .brand__lockup { height: 30px; } }

.nav { display: flex; align-items: center; gap: var(--s-2); margin-inline-start: auto; }
.nav__link {
  position: relative;
  padding: var(--s-2) var(--s-3);
  font-size: var(--fs-sm);
  color: var(--lilac);
  transition: color var(--dur-2) var(--ease-out);
}
/* Underline draws in from the leading edge. */
.nav__link::after {
  content: "";
  position: absolute;
  inset-inline: var(--s-3);
  bottom: 2px;
  height: 1px;
  background: linear-gradient(90deg, var(--magenta), var(--royal));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-2) var(--ease-out);
}
:root[dir="rtl"] .nav__link::after { transform-origin: right; }
.nav__link:hover { color: var(--ice); }
/* Two things set this: the scrollspy writes aria-current="true" for the
   section you are looking at, and a link to the page you are already on
   carries aria-current="page". Both mean "you are here". */
.nav__link:hover::after,
.nav__link[aria-current]::after { transform: scaleX(1); }
.nav__link[aria-current] { color: var(--ice); }

.header__act { display: flex; align-items: center; gap: var(--s-3); flex: none; }

/* Language toggle — one pill, two states */
.lang {
  display: inline-flex;
  border: 1px solid var(--plum-high);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.lang__b {
  min-width: 44px;
  min-height: 36px;
  padding: 0 var(--s-3);
  background: transparent;
  border: 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--lilac);
  transition: background var(--dur-2), color var(--dur-2);
}
.lang__b:hover { color: var(--ice); }
.lang__b[aria-pressed="true"] { background: var(--plum-high); color: var(--ice); }

.burger {
  display: none;
  width: 44px; height: 44px;
  align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid var(--plum-high);
  border-radius: var(--r-pill);
}
.burger__bars { width: 18px; height: 12px; position: relative; }
.burger__bars i {
  position: absolute; inset-inline: 0; height: 1.5px; background: var(--ice);
  transition: transform var(--dur-2) var(--ease-out), opacity var(--dur-1);
}
.burger__bars i:nth-child(1) { top: 0; }
.burger__bars i:nth-child(2) { top: 5px; }
.burger__bars i:nth-child(3) { top: 10px; }
.burger[aria-expanded="true"] i:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.burger[aria-expanded="true"] i:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] i:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

/* ---------- Mobile drawer ---------- */

.drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  background: rgba(19, 7, 20, .97);
  backdrop-filter: blur(20px);
  padding: calc(var(--header-h) + var(--s-6)) var(--gutter) var(--s-7);
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: opacity var(--dur-3) var(--ease-out),
              transform var(--dur-3) var(--ease-out),
              visibility var(--dur-3);
}
.drawer[data-open="true"] { opacity: 1; visibility: visible; transform: none; }
.drawer__link {
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--plum-high);
  font-family: var(--font-display);
  font-size: var(--fs-xl);
}
.drawer__link:hover { color: var(--magenta-soft); }
.drawer .btn { margin-top: var(--s-5); }
.drawer__lang { margin-top: var(--s-5); align-self: flex-start; }
.drawer__lang .lang__b { min-height: 44px; min-width: 60px; }

@media (max-width: 1000px) {
  /* .nav normally pushes the actions right; with it gone the actions
     have to claim the free space themselves. */
  .nav { display: none; }
  .header__act { margin-inline-start: auto; }
  .burger { display: inline-flex; }
  .header__act .lang { display: none; }
}
/* Below this the brand + CTA + burger stop fitting on one row and the
   burger gets pushed off-screen. The drawer carries the CTA. */
@media (max-width: 660px) {
  .header__cta { display: none; }
}
@media (min-width: 1001px) { .drawer { display: none; } }

/* ============================================================
   REVEALS — progressive enhancement.
   Content is visible by default and only hides once .js-anim is
   set, which happens pre-paint and only when IntersectionObserver
   exists and reduced motion is off. No JS, no IO, or reduced
   motion all degrade to plain readable content.
   ============================================================ */

:root.js-anim .rise {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 700ms var(--ease-out), transform 700ms var(--ease-out);
  transition-delay: var(--d, 0ms);
}
:root.js-anim .rise.is-in { opacity: 1; transform: none; }

/* The approach nodes pop rather than rise — a back-eased scale that
   overshoots slightly, so they land like beats on the spine. */
:root.js-anim .pop {
  opacity: 0;
  transform: scale(.5);
  transition: opacity 550ms var(--ease-out), transform 550ms var(--ease-back);
  transition-delay: var(--d, 0ms);
}
:root.js-anim .pop.is-in { opacity: 1; transform: none; }

/* Word-by-word headline stagger. Each word carries its own --d. */
:root.js-anim .word {
  display: inline-block;
  opacity: 0;
  transform: translateY(.5em);
  transition: opacity 520ms var(--ease-out), transform 520ms var(--ease-out);
  transition-delay: var(--d, 0ms);
}
:root.js-anim .word.is-in { opacity: 1; transform: none; }

/* A line that draws itself — used for the approach spine. */
:root.js-anim .draw-x {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 1.4s var(--ease-out);
}
:root[dir="rtl"].js-anim .draw-x { transform-origin: right; }
:root.js-anim .draw-x.is-in { transform: scaleX(1); }
:root.js-anim .draw-y {
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 1.4s var(--ease-out);
}
:root.js-anim .draw-y.is-in { transform: scaleY(1); }

/* ---------- The hero entrance ----------
   Deliberately CSS keyframes, not JS. These elements are the page's
   LCP candidates; driven by script they would sit at opacity 0 until
   the bundle arrives. As keyframes they animate from first paint. */
@keyframes hero-rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
@keyframes hero-word {
  from { opacity: 0; transform: translateY(.6em); }
  to   { opacity: 1; transform: none; }
}
@keyframes hero-fade { from { opacity: 0; } to { opacity: 1; } }

.hero-rise, .hero-word, .hero-fade {
  opacity: 0;
  animation-delay: var(--d, 0s);
  animation-fill-mode: both;
  animation-timing-function: var(--ease-out);
}
.hero-rise { animation-name: hero-rise; animation-duration: .6s; }
.hero-word { animation-name: hero-word; animation-duration: .5s; }
.hero-fade { animation-name: hero-fade; animation-duration: .8s; }

/* Low-capability devices skip the opening curtain entirely (see the
   probe in the document head), so the hero must not sit waiting
   behind delays that were budgeted for the intro. */
.no-intro .hero-rise,
.no-intro .hero-word,
.no-intro .hero-fade { animation-delay: calc(var(--d, 0s) * .12); }
.no-intro .boot { display: none; }

/* ---------- The curtain ----------
   Particles fly in and assemble the mark, which crossfades to the real
   artwork; then the whole panel lifts away upward. */

.boot {
  position: fixed;
  inset: 0;
  z-index: var(--z-boot);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-6);
  overflow: hidden;
  background: var(--void);
  transition: transform 500ms cubic-bezier(.76, 0, .24, 1);
}
/* The curtain leaves upward rather than fading, so the page is revealed
   from behind it. pointer-events drops immediately: a transition that
   never completes — throttled tab, backgrounded load, compositor
   asleep — must not leave a full-screen overlay eating every tap. */
.boot[data-done="true"] { transform: translateY(-100%); pointer-events: none; }

.boot__stage { position: relative; width: 220px; height: 220px; }
.boot__canvas {
  width: 220px; height: 220px;
  transition: opacity 350ms linear;
}
.boot__solid {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  opacity: 0;
  transform: scale(.92);
  transition: opacity 400ms var(--ease-out), transform 400ms var(--ease-out);
}
.boot__solid[style*="opacity: 1"] { transform: scale(1); }
.boot__solid img { width: 160px; height: auto; }

.boot__eq { height: 22px; }

.boot__read { display: flex; flex-direction: column; align-items: center; gap: var(--s-3); }
.boot__words {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  letter-spacing: .5em;
  /* The tracking pushes the last letter off-centre; pull it back. */
  text-indent: .5em;
  color: var(--lilac);
}
.boot__pct {
  font-family: var(--font-display);
  font-size: 2rem;
  font-variant-numeric: tabular-nums;
  color: rgba(246, 241, 248, .9);
}
.boot__pct i { font-size: 1.125rem; font-style: normal; color: var(--lilac); }

/* The progress rule runs the full width along the bottom edge. */
.boot__bar {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 2px;
  transform: scaleX(0);
  transform-origin: left;
  background: var(--ribbon);
}
:root[dir="rtl"] .boot__bar { transform-origin: right; }

/* ---------- Ambient hue ----------
   Fixed-colour washes, crossfaded by scroll as the page tunes from
   magenta through indigo to royal.

   These were originally solid discs carrying filter: blur(120px), on
   the assumption that driving opacity alone would let the compositor
   reuse the blurred raster. That is false: changing opacity on a
   FILTERED layer re-rasterises the filter, and seven overlapping 120px
   blurs across the viewport is an enormous surface to re-blur every
   frame. Measured on the live site: 3 fps, 317ms frames, 132 long
   tasks — hiding this one element alone restored 59 fps.

   A radial-gradient is soft by construction, so no filter is needed to
   get the same glow. It rasterises once, and with its own compositor
   layer an opacity change is a composite rather than a repaint. */
.ambient {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  /* Nothing inside ever affects layout or the rest of the page's paint. */
  contain: strict;
}
.ambient span {
  position: absolute;
  display: block;
  opacity: 0;
  /* Own layer: opacity changes never touch paint. */
  will-change: opacity;
}
.ambient__a {
  top: -25%; left: -25%;
  width: 70vw; height: 70vh;
}
.ambient__b {
  top: 33%; right: -25%;
  width: 60vw; height: 60vh;
}

/* ---------- Cursor companion ----------
   A dot on the pointer and a ring trailing it on a spring. The native
   cursor is left visible — this is an accent, not a replacement. */
.cursor { position: fixed; inset: 0; z-index: 90; pointer-events: none; }
.cursor__dot,
.cursor__ring {
  position: absolute;
  top: 0; left: 0;
  border-radius: 50%;
  /* Difference blending keeps it legible over both the void and the
     one light card on the page without any state tracking. */
  mix-blend-mode: difference;
  will-change: transform;
}
.cursor__dot { width: 6px; height: 6px; background: var(--ice); }
.cursor__ring {
  width: 30px; height: 30px;
  border: 1px solid rgba(246, 241, 248, .7);
  opacity: .6;
  transition: width 200ms var(--ease-out), height 200ms var(--ease-out),
              opacity 200ms var(--ease-out), margin 200ms var(--ease-out);
}
.cursor__ring[data-over="true"] {
  width: 52px; height: 52px;
  opacity: 1;
}

/* Present to screen readers, absent on screen. Not display:none —
   that would drop it from the accessibility tree as well. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- Skip link ---------- */
.skip {
  position: absolute;
  inset-inline-start: var(--s-4);
  top: -80px;
  z-index: var(--z-toast);
  padding: var(--s-3) var(--s-5);
  background: var(--plum);
  color: var(--ice);
  font-size: var(--fs-sm);
  font-weight: 700;
  border-radius: var(--r-pill);
  transition: top var(--dur-2) var(--ease-out);
}
.skip:focus { top: var(--s-4); }

/* ---------- Language visibility ----------
   Both languages ship in the DOM; CSS shows the active one. ---------- */
:root[data-lang="en"] .lang-ar,
:root[data-lang="ar"] .lang-en { display: none !important; }

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .rise, .word { opacity: 1 !important; transform: none !important; transition: none; }
  .draw-x, .draw-y { transform: none !important; transition: none; }
  /* Present immediately, never animated in. */
  .hero-rise, .hero-word, .hero-fade {
    animation: none; opacity: 1; transform: none;
  }
  .grain::after { content: none; }
  .btn:hover { transform: none; }
  /* The motion layer never installs under reduced motion, so these
     would otherwise sit at their initial values forever. */
  .ambient, .cursor { display: none; }
  .boot { display: none; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}
