/**
 * VIPER base layer.
 *
 * LOGICAL PROPERTIES ONLY. No `left`, `right`, `margin-left`, `text-align: left`.
 * The RTL hook blocks physical properties at write time, and Arabic is the
 * majority experience — a mirroring bug is not cosmetic here.
 */

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

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

body {
  background: var(--ink);
  color: var(--text);
  font-family: var(--font-text);
  font-size: 16px;
  line-height: calc(1.6 * var(--leading-adjust, 1));
  /* NO -webkit-font-smoothing: antialiased.
     Light-on-dark type already reads optically bolder and more diffuse
     because light bleeds into the dark around it. `antialiased` is the usual
     "fix" and makes it strictly worse by thinning strokes further — and on
     Arabic the dots and joins are the first things to disappear. */
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }

::selection { background: var(--lime); color: var(--on-lime); }

/* Visible focus on every interactive element — WCAG 2.2 AA. */
:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: absolute;
  inset-block-start: -100px;
  inset-inline-start: var(--space-4);
  z-index: 100;
  padding: var(--space-3) var(--space-5);
  background: var(--lime);
  color: var(--on-lime);
  font-weight: 600;
  transition: inset-block-start var(--dur-2) var(--ease-standard);
}
.skip:focus { inset-block-start: var(--space-4); }

.wrap {
  max-width: 1320px;
  margin-inline: auto;
  padding-inline: clamp(var(--space-4), 5vw, var(--space-10));
}

/* ------------------------------------------------------------------- nav */
.nav {
  position: sticky;
  inset-block-start: 0;
  z-index: 50;
  background: color-mix(in oklab, var(--ink) 82%, transparent);
  backdrop-filter: blur(14px);
  border-block-end: 1px solid var(--line);
}
.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
  min-height: 68px;
}
.nav__brand {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.35rem;
  letter-spacing: var(--tracking-display, 0);
  color: var(--text-hi);
}
 /* Base state is VISIBLE. Hiding lives in `.nav--js .nav__links` further
    down, so the panel only disappears once a script exists to reopen it. */
.nav__links { gap: var(--space-6); }
.nav__links a {
  font-size: .95rem;
  color: var(--text-mid);
  transition: color var(--dur-2) var(--ease-standard);
}
.nav__links a:hover { color: var(--text-hi); }

.nav__actions { display: flex; align-items: center; gap: var(--space-4); }
.lang {
  font-size: .9rem;
  color: var(--text-lo);
  padding-inline: var(--space-2);
}
.lang:hover { color: var(--text-hi); }

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-6);
  border: 1px solid transparent;
  border-radius: 2px;
  font-weight: 600;
  font-size: .95rem;
  cursor: pointer;
  /* These four exist for <button class="btn">, not for anchors. Without them a
     real button keeps the UA's grey background and system font, which is how
     the gallery filter shipped as an almost-invisible light box on a dark
     page. `font: inherit` also matters for Arabic — the UA font has no Arabic
     shaping worth the name. */
  background: transparent;
  font-family: inherit;
  line-height: inherit;
  -webkit-appearance: none;
  appearance: none;
  transition: background var(--dur-2) var(--ease-standard),
              color var(--dur-2) var(--ease-standard),
              border-color var(--dur-2) var(--ease-standard);
}

/* Near-black on lime = 10.11:1. White on lime would be 1.90:1 — a WCAG AA
   failure by more than double, and the defect in the original prototype.
   Enforced by tests/Feature/DesignTokenTest.php. */
.btn--primary { background: var(--lime); color: var(--on-lime); }
.btn--primary:hover { background: var(--lime-400); }

.btn--ghost { border-color: var(--line-control); color: var(--text-hi); }
.btn--ghost:hover { border-color: var(--text-hi); }

/* ------------------------------------------------------------------ hero
   ONE hero implementation, in the `.hero`/`.hero__*` block further down. The
   prototype's duplicate `.hero h1` rule used to live here and quietly beat the
   block's `.hero__h` on specificity, pinning Arabic leading at 1.02 and making
   the headline lines collide. Do not add a second one. */
/* NOT LIME. The seeded hero carried three lime elements — this label, the rule
   before it, and `.hero__accent` — so the accent stopped signalling anything in
   the one viewport where it matters most (rule 04 §21). The eyebrow is a label;
   labels are quiet. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--fs-caption);
  font-weight: 600;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--text-lo);
  margin-block-end: var(--space-5);
}
.eyebrow::before {
  content: "";
  inline-size: 28px;
  block-size: var(--bw-1);
  /* `--line-control` is the border colour solved for 3:1, so this hairline is
     still a visible drawn mark rather than a suggestion of one. */
  background: var(--line-control);
  flex: none;
}

/* --------------------------------------------------------------- sections */
.sec { padding-block: var(--section-air); }
.sec__head { max-inline-size: 62ch; margin-block-end: var(--space-9); }
.sec h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-h2);
  line-height: var(--lh-head);
  color: var(--text-hi);
  text-wrap: balance;
}
.sec p.lede { margin-block-start: var(--space-4); color: var(--text-mid); }

.grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
}
.card {
  padding: var(--space-7);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 3px;
  transition: border-color var(--dur-3) var(--ease-standard),
              transform var(--dur-3) var(--ease-rake);
}
.card:hover { border-color: var(--line-2); transform: translateY(-2px); }
.card h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--text-hi);
  margin-block-end: var(--space-3);
}
.card p { color: var(--text-mid); font-size: .95rem; }
.card__num {
  display: block;
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .12em;
  color: var(--text-lo);
  margin-block-end: var(--space-4);
}

/* ----------------------------------------------------------------- footer */
.foot {
  padding-block: var(--space-8);
  border-block-start: 1px solid var(--line);
  color: var(--text-lo);
  font-size: .9rem;
}

/* -------------------------------------------------------------------- fab
   inset-inline-end puts this bottom-left in Arabic and bottom-right in
   English automatically. The glyph itself never mirrors. */
.fab {
  position: fixed;
  inset-block-end: var(--space-6);
  inset-inline-end: var(--space-6);
  z-index: 60;
  display: grid;
  place-items: center;
  inline-size: 54px;
  block-size: 54px;
  border-radius: 50%;
  background: #25D366;
  color: #fff;
  box-shadow: 0 6px 24px rgb(0 0 0 / .4);
  transition: transform var(--dur-2) var(--ease-rake);
}
.fab:hover { transform: scale(1.06); }

/* ------------------------------------------------------------ breakpoints */
@media (min-width: 768px) {
  .grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  .grid { grid-template-columns: repeat(3, 1fr); }
}

/* On mobile the floating action would sit over the thumb zone and compete
   with the primary CTA, so it collapses into the fixed bottom bar instead.
   docs/15-wireframes.md */
@media (max-width: 640px) {
  .fab { inset-block-end: var(--space-4); inline-size: 48px; block-size: 48px; }
}

/* ==========================================================================
   P1 — brand mark, service hub, service page, footer
   Logical properties only. `/* rtl-ok *​/` marks anything deliberately physical.
   ========================================================================== */

/* ------------------------------------------------------------- brand mark
   The logo NEVER mirrors (rule 01 §4). Height is fixed and width follows,
   so the intrinsic 265x164 ratio holds and the header never shifts (CLS). */
.nav__logo {
  block-size: 34px;
  inline-size: auto;
  display: block;
}
.nav__brand { display: inline-flex; align-items: center; }

/* ------------------------------------------------------------------- hub */
.hub {
  padding-block: var(--space-11) var(--space-8);
  border-block-end: 1px solid var(--line);
}
.eyebrow {
  color: var(--lime-500);
  font-size: .78rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  margin-block-end: var(--space-3);
}
.hub__h {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3.4rem);
  color: var(--text-hi);
  line-height: 1.1;
}
.hub__lede {
  max-inline-size: 62ch;
  margin-block-start: var(--space-4);
  color: var(--text-mid);
  font-size: 1.05rem;
  line-height: calc(1.6 * var(--leading-adjust));
}

/* ----------------------------------------------------------------- cards */
.cards {
  list-style: none;
  display: grid;
  gap: var(--space-5);
  padding: 0;
  margin-block: var(--space-9);
}
.card {
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surface-1);
  transition: border-color var(--dur-2) var(--ease-standard);
}
.card:hover { border-color: var(--line-2); }
.card__h { font-family: var(--font-display); font-size: 1.3rem; }
.card__h a { color: var(--text-hi); text-decoration: none; }
.card__h a:hover { text-decoration: underline; }
.card__body {
  margin-block-start: var(--space-3);
  color: var(--text-mid);
  line-height: calc(1.65 * var(--leading-adjust));
}
.card__more {
  display: inline-block;
  margin-block-start: var(--space-4);
  color: var(--lime-500);
  font-size: .85rem;
}

/* ---------------------------------------------------------- service page */
.svc__head { padding-block: var(--space-9) var(--space-8); }
.svc__h {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5.5vw, 3.6rem);
  color: var(--text-hi);
  line-height: 1.08;
  margin-block-start: var(--space-4);
}
.svc__lede {
  max-inline-size: 62ch;
  margin-block-start: var(--space-4);
  color: var(--text-mid);
  font-size: 1.05rem;
  line-height: calc(1.65 * var(--leading-adjust));
}
.svc__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-6);
}
.svc__note {
  margin-block-start: var(--space-3);
  color: var(--text-lo);
  font-size: .88rem;
}
.svc__body { padding-block: var(--space-7); max-inline-size: 70ch; }
.svc__body h2 { font-family: var(--font-display); font-size: 1.5rem; color: var(--text-hi); }
.svc__body p { margin-block-start: var(--space-4); color: var(--text); line-height: calc(1.75 * var(--leading-adjust)); }
.svc__meta { padding-block-end: var(--space-10); }
.svc__meta dt { color: var(--text-lo); font-size: .82rem; }
.svc__meta dd { color: var(--text-hi); margin-block-start: var(--space-1); }

/* --------------------------------------------------------------- crumbs
   The separator is a pseudo-element so `content` flips with direction: in
   Arabic the trail must read right-to-left with the chevron reversed. */
.crumbs { display: flex; flex-wrap: wrap; gap: var(--space-2); font-size: .85rem; }
.crumbs a { color: var(--text-lo); }
.crumbs a:hover { color: var(--text); }
.crumbs > * + *::before {
  content: '›';
  margin-inline-end: var(--space-2);
  color: var(--text-faint);
}
[dir='rtl'] .crumbs > * + *::before { content: '‹'; }
.crumbs [aria-current='page'] { color: var(--text-mid); }

/* ---------------------------------------------------------------- footer */
.foot__grid {
  display: grid;
  gap: var(--space-7);
  padding-block: var(--space-10) var(--space-7);
}
.foot__logo { block-size: 42px; inline-size: auto; opacity: .9; }
.foot__tag { margin-block-start: var(--space-3); color: var(--text-lo); font-size: .9rem; }
.foot__h {
  font-size: .78rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-lo);
  margin-block-end: var(--space-3);
}
.foot__col p { color: var(--text-mid); line-height: calc(1.7 * var(--leading-adjust)); }
.foot__col a { color: var(--text-mid); }
.foot__col a:hover { color: var(--lime-500); }
.foot__addr { max-inline-size: 34ch; }
.foot__social { list-style: none; padding: 0; display: grid; gap: var(--space-2); }
.foot__legal {
  padding-block: var(--space-5);
  border-block-start: 1px solid var(--line);
  color: var(--text-lo);
  font-size: .85rem;
}

/* ------------------------------------------------------------ breakpoints */
@media (min-width: 640px) {
  .cards { grid-template-columns: repeat(2, 1fr); }
  .foot__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .cards { grid-template-columns: repeat(3, 1fr); }
  .foot__grid { grid-template-columns: 1.5fr repeat(4, 1fr); }
}

/* ==========================================================================
   P1-3 — enquiry form
   ========================================================================== */

.form__layout { display: grid; gap: var(--space-9); padding-block: var(--space-9) var(--space-12); }
.form { display: grid; gap: var(--space-6); max-inline-size: 46rem; }

.field { display: grid; gap: var(--space-2); }
.field label { color: var(--text); font-size: .92rem; }
.field .opt { color: var(--text-faint); font-size: .82rem; margin-inline-start: var(--space-2); }

.field input,
.field select,
.field textarea {
  inline-size: 100%;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-1);
  /* line-control is solved for 3:1 — it is the SOLE indicator of the
     control's bounds, so it must not be softened (design-system.md). */
  border: 1px solid var(--line-control);
  border-radius: 10px;
  color: var(--text-hi);
  font: inherit;
  /* text-align: start, never left — the caret must begin at the reading edge. */
  text-align: start;
}

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible,
.btn:focus-visible,
a:focus-visible {
  outline: 2px solid var(--lime-500);
  outline-offset: 2px;
}

.field [aria-invalid='true'] { border-color: #F2555A; }

.field--check {
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: var(--space-3);
}
.field--check input {
  inline-size: 20px;
  block-size: 20px;
  margin-block-start: 2px;
}

.hint { color: var(--text-lo); font-size: .82rem; }
.err  { color: #F2555A; font-size: .85rem; }

/* Honeypot. Hidden from sight without display:none — some bots skip fields
   that are display:none, and this one is meant to be irresistible. */
.hp {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.note {
  padding: var(--space-5);
  border-radius: 12px;
  border: 1px solid var(--line-2);
  margin-block-end: var(--space-7);
  /* border-inline-start: the accent bar sits on the reading edge and swaps
     sides with direction automatically. */
  border-inline-start-width: 3px;
}
.note--ok  { border-inline-start-color: var(--lime-500); background: var(--surface-1); }
.note--bad { border-inline-start-color: #F2555A; background: var(--surface-1); }
.note__h   { font-family: var(--font-display); font-size: 1.15rem; color: var(--text-hi); margin-block-end: var(--space-2); }
.note p    { color: var(--text-mid); line-height: calc(1.6 * var(--leading-adjust)); }
.note__ref { margin-block-start: var(--space-3); color: var(--text-hi); }

.form__aside { color: var(--text-mid); }
.form__aside p { margin-block-end: var(--space-3); }
.form__aside a { color: var(--text-mid); }
.form__aside a:hover { color: var(--lime-500); }

@media (min-width: 900px) {
  .form__layout { grid-template-columns: 1.6fr .8fr; gap: var(--space-11); }
}

/* ==========================================================================
   Mobile / tablet navigation
   Below 1024px the link row was `display:none` with nothing in its place, so
   phones and tablets had NO navigation at all — the majority of this
   business's traffic, with no way to reach a service page.
   ========================================================================== */

.nav__toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /* 44px is the WCAG 2.2 target minimum, not a round number. */
  min-inline-size: 44px;
  min-block-size: 44px;
  padding-inline: var(--space-3);
  background: transparent;
  border: 1px solid var(--line-control);
  border-radius: 10px;
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.nav__toggle-label {
  /* Visually hidden, still announced. The icon alone has no accessible name. */
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The panel is VISIBLE by default and hidden only once the script has run —
   so a blocked or failed bundle degrades to a longer header, never to a site
   with no navigation. */
.nav--js .nav__links { display: none; }

.nav--js.nav--open .nav__links {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  position: absolute;
  inset-inline: 0;
  inset-block-start: 100%;
  padding: var(--space-4) clamp(var(--space-4), 5vw, var(--space-10)) var(--space-6);
  background: var(--ink);
  border-block-end: 1px solid var(--line);
}
.nav--js.nav--open .nav__links a {
  padding-block: var(--space-3);
  font-size: 1.05rem;
  color: var(--text);
  border-block-end: 1px solid var(--line);
}

.nav__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}

@media (min-width: 1024px) {
  /* Desktop: the toggle disappears and the row is always open, whatever the
     script did. `display: flex !important` is deliberate — it must beat
     `.nav--js .nav__links { display: none }` regardless of toggle state, so a
     narrow window widened to desktop can never strand the nav closed. */
  .nav__toggle { display: none; }
  .nav--js .nav__links,
  .nav--js.nav--open .nav__links {
    display: flex !important;
    flex-direction: row;
    position: static;
    padding: 0;
    background: none;
    border: 0;
  }
  .nav--js.nav--open .nav__links a { border: 0; padding-block: 0; font-size: .95rem; }
}

/* ==========================================================================
   P1-4 — pricing tables and package tiers
   ========================================================================== */

/* Visually hidden, still announced — for table captions. */
.vh {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.price { padding-block: var(--space-9); }
.price__group { margin-block-end: var(--space-10); }
.price__h { font-family: var(--font-display); font-size: 1.5rem; color: var(--text-hi); margin-block-end: var(--space-4); }
.price__h a { color: inherit; }
.price__h a:hover { color: var(--lime-500); }

/* A wide table must scroll inside its own box — the page body must never
   scroll horizontally on a phone. */
.price__table {
  inline-size: 100%;
  border-collapse: collapse;
  display: block;
  overflow-x: auto;
}
.price__table th,
.price__table td {
  /* text-align: start, never left: the reading edge flips with direction. */
  text-align: start;
  padding: var(--space-3) var(--space-4);
  border-block-end: 1px solid var(--line);
  vertical-align: top;
}
.price__table thead th {
  color: var(--text-lo);
  font-size: .78rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-weight: 500;
}
.price__table tbody th { color: var(--text-hi); font-weight: 500; }
.price__table td { color: var(--text); }
.price__note { display: block; color: var(--text-lo); font-size: .8rem; margin-block-start: var(--space-1); }
.price__eg { color: var(--text-lo); font-size: .88rem; }
.price__classes { padding-block-end: var(--space-10); }
.price__cta { padding-block-end: var(--space-12); }

/* ------------------------------------------------------------------ tiers */
.tiers {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-6);
  margin-block: var(--space-9) var(--space-6);
  /* Stretch so all three cards match height regardless of content length —
     a comparison table with ragged bottoms is harder to compare. */
  align-items: stretch;
}
.tier {
  display: flex;
  flex-direction: column;
  padding: var(--space-7);
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--surface-1);
}
.tier--featured { border-color: var(--lime-600); }
.tier__flag {
  align-self: start;
  display: inline-flex;
  align-items: center;
  /* Fixed height, matched by the spacer below, so the badge does not push the
     featured card's price out of line with its siblings. Three prices at three
     different heights are much harder to compare, which is the one job a
     three-tier table has. */
  block-size: 26px;
  padding-inline: var(--space-3);
  border-radius: 999px;
  background: var(--lime-500);
  /* Near-black on lime: 10.68:1. NEVER white — that is 1.85:1. */
  color: var(--on-lime);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .06em;
  margin-block-end: var(--space-4);
}

/* The reserved slot on unbadged cards. Decorative and empty, so it is hidden
   from the accessibility tree rather than announced as a blank item. */
.tier:not(.tier--featured)::before {
  content: '';
  display: block;
  block-size: 26px;
  margin-block-end: var(--space-4);
}
.tier__h { font-family: var(--font-display); font-size: 1.6rem; color: var(--text-hi); }
.tier__h a { color: inherit; }
.tier__h a:hover { color: var(--lime-500); }
.tier__price { margin-block-start: var(--space-4); color: var(--text-hi); }
.tier__price strong { font-size: 1.8rem; font-family: var(--font-display); }
.tier__from,
.tier__vat { display: block; color: var(--text-lo); font-size: .8rem; }
.tier__saving { margin-block-start: var(--space-2); color: var(--lime-500); font-size: .9rem; }
.tier__body {
  margin-block-start: var(--space-4);
  color: var(--text-mid);
  line-height: calc(1.65 * var(--leading-adjust));
}
.tier__sub {
  margin-block-start: var(--space-6);
  font-size: .78rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-lo);
}
.tier__list { list-style: none; padding: 0; margin-block-start: var(--space-3); display: grid; gap: var(--space-2); }
.tier__list li {
  color: var(--text);
  padding-inline-start: var(--space-5);
  position: relative;
}
/* The tick sits on the reading edge and swaps sides with direction. */
.tier__list li::before {
  content: '✓';
  position: absolute;
  inset-inline-start: 0;
  color: var(--lime-600);
}
.tier__tier { display: block; color: var(--text-lo); font-size: .82rem; }
/* Pushes the CTA to the card's bottom so all three line up. */
.tier .btn { margin-block-start: auto; align-self: start; }
.tier__list + .btn { margin-block-start: var(--space-6); }

@media (min-width: 768px) {
  .tiers { grid-template-columns: repeat(3, 1fr); }
  .price__table { display: table; }
}


/* Footer navigation — carries the pages the six-item header cannot hold. */
.foot__nav { list-style: none; padding: 0; display: grid; gap: var(--space-2); }
.foot__nav a { color: var(--text-mid); }
.foot__nav a:hover { color: var(--lime-500); }

/* ==========================================================================
   P1-4b — gallery facets and project/article meta
   ========================================================================== */

.facets {
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surface-1);
  margin-block: var(--space-8) var(--space-5);
}
.facets__row {
  display: grid;
  gap: var(--space-4);
  margin-block-start: var(--space-4);
}
.facets__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  /* Aligns the button with the bottom of the selects beside it. */
  align-self: end;
}

.card__meta {
  margin-block-start: var(--space-2);
  color: var(--text-lo);
  font-size: .85rem;
}

/* Definition list used for project facts. Each dt/dd pair is wrapped in a div
   so the grid rows stay paired — a bare dl grid separates a term from its
   definition the moment one of them wraps. */
.meta { display: grid; gap: var(--space-5); padding-block-end: var(--space-10); }
.meta dt {
  color: var(--text-lo);
  font-size: .78rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  margin-block-end: var(--space-2);
}
.meta dd { color: var(--text-hi); }
.meta dd a { color: var(--text-hi); }
.meta dd a:hover { color: var(--lime-500); }

@media (min-width: 768px) {
  .facets__row { grid-template-columns: 1fr 1fr auto; align-items: end; }
  .meta { grid-template-columns: repeat(3, 1fr); }
}

/* ==========================================================================
   P1-5b — CMS blocks
   ========================================================================== */

/* ------------------------------------------------------------------ hero
   The poster is the LCP element and is positioned absolutely BEHIND the
   copy, never as a CSS background-image: a background cannot carry alt text
   and browsers deprioritise it, which is exactly wrong for the one image
   the performance budget depends on. */
.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  isolation: isolate;
}
.hero--viewport { min-block-size: min(88vh, 780px); }
.hero--tall     { min-block-size: min(70vh, 620px); }
.hero--medium   { min-block-size: min(52vh, 460px); }

/* The <picture> element is what gets positioned, not the <img> inside it.
   A bare `.hero__poster { position: absolute }` stopped working the moment
   the img was wrapped for AVIF/WebP art direction: <picture> is a real box
   that lays out in normal flow, so the absolutely-positioned img inside it
   resolved against the picture's zero-height box and the hero rendered flat
   black with a real photograph loaded and invisible. */
/* NO NEGATIVE z-index anywhere in this stack.
   `.hero` sets `isolation: isolate`, which creates a stacking context — and a
   z-index:-2 child inside one paints behind the section's own background
   rather than behind its text. The image loaded, decoded and sat at exactly
   the right rectangle, invisible. Layering upward (image 0, scrim 1, content
   2) does not depend on how any ancestor happens to be stacked. */
.hero > picture {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
}
.hero__poster {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
}

/* Scrim so text stays legible over any poster. `to top` is a physical
   direction on purpose: the scrim follows gravity, not reading order, and
   must NOT flip in RTL. */
.hero::after {
  content: '';
  z-index: 1;
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, /* rtl-ok */
              rgb(11 12 14 / .88) 0%,
              rgb(11 12 14 / .55) 42%,
              rgb(11 12 14 / .18) 100%);
}
.hero[data-overlay='none']::after   { opacity: .35; }
.hero[data-overlay='strong']::after { opacity: 1; }

.hero__inner {
  position: relative;
  z-index: 2;
  padding-block: var(--space-12) var(--space-11);
}
.hero__h {
  font-family: var(--font-display);
  font-size: var(--fs-display-1);
  /* Explicit per-direction values, NOT calc(1.04 * var(--leading-adjust)).
     That calc measurably did not resolve — a literal calc(1.04 * 1.3) computed
     to 116px while the var form computed to 88px on the same element, with the
     variable inheriting correctly as 1.3. Two numbers a reader can see beat one
     clever expression that has to be trusted.

     The Arabic value is much larger because Arabic display type needs it: the
     hamza on أ rises well above the line and the kasra under ـِ drops below it,
     so a leading tuned for Latin caps had "اضرب أولاً." colliding with
     "احمِ للأبد." at every viewport width. */
  line-height: 1.06;
  letter-spacing: var(--tracking-display, 0);
  color: var(--text-hi);
  max-inline-size: 18ch;
}
[dir='rtl'] .hero__h { line-height: 1.42; }

/* The one lime element in this viewport. */
.hero__accent { color: var(--lime-500); display: block; }
.hero__lede {
  margin-block-start: var(--space-5);
  max-inline-size: 56ch;
  color: var(--text-mid);
  font-size: clamp(1rem, 1.6vw, 1.15rem);
  line-height: calc(1.65 * var(--leading-adjust));
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-7);
}

/* -------------------------------------------------------------- rich text */
.rt { padding-block: var(--space-11); }
.rt--narrow .rt__body,
.rt--narrow .rt__h { max-inline-size: 62ch; }
.rt--wide .rt__body { max-inline-size: 90ch; }
.rt__h {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 2.2rem);
  color: var(--text-hi);
  margin-block-end: var(--space-5);
}
.rt__body {
  color: var(--text);
  font-size: 1.05rem;
  line-height: calc(1.8 * var(--leading-adjust));
}

/* ------------------------------------------------------------ service grid */
.grid-block { padding-block: var(--space-11); }
.grid-block .cards { margin-block: var(--space-8) var(--space-6); }

@media (min-width: 640px) {
  .cards--2 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .cards--3 { grid-template-columns: repeat(3, 1fr); }
  .cards--4 { grid-template-columns: repeat(4, 1fr); }
}

/* ------------------------------------------------------------- the portal */
/* Logical properties only — this whole area mirrors under `dir="rtl"` without
   a single physical value (rule 01 §3). */
.portal { padding-block: var(--space-9) var(--space-12); }
.portal--narrow { max-inline-size: 32rem; }
.portal__h {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.4vw, 2.4rem);
  color: var(--text-hi);
  margin-block-end: var(--space-4);
}
.portal__h2 {
  font-family: var(--font-display);
  font-size: 1.25rem;
  color: var(--text-hi);
  margin-block-end: var(--space-4);
}
.portal__lede { color: var(--text-lo); margin-block-end: var(--space-7); }
.portal__help { color: var(--text-faint); font-size: .88rem; margin-block-start: var(--space-5); }
.portal__empty { color: var(--text-faint); padding-block: var(--space-7); }
.portal__section { margin-block-start: var(--space-10); }
.portal__list { display: grid; gap: var(--space-2); color: var(--text); }

/* A six-digit code reads far better spaced out, and `text-align: center`
   is direction-neutral so it needs no RTL variant. */
.code-input {
  letter-spacing: .5em;
  text-align: center;
  font-size: 1.4rem;
}

.portal-nav { margin-block-end: var(--space-9); border-block-end: 1px solid var(--line); }
.portal-nav__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  align-items: center;
  list-style: none;
  padding: 0;
  margin: 0;
}
.portal-nav__link {
  display: inline-block;
  padding-block: var(--space-3);
  color: var(--text-lo);
  text-decoration: none;
  border-block-end: 2px solid transparent;
}
.portal-nav__link:hover { color: var(--text-hi); }
.portal-nav__link.is-current {
  color: var(--text-hi);
  border-block-end-color: var(--lime);
}
/* Pushed to the far edge in BOTH directions — `margin-inline-start: auto`
   resolves to left in LTR and right in RTL on its own. */
.portal-nav__out { margin-inline-start: auto; }

.portal__facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3) var(--space-6);
  margin-block: var(--space-7);
}
.portal__facts dt { color: var(--text-faint); font-size: .9rem; }
.portal__facts dd { color: var(--text-hi); margin: 0; }

/* Wide tables scroll inside their own box. The page body must never scroll
   horizontally, in either direction. */
.table-scroll { overflow-x: auto; margin-block: var(--space-6); }
.table { inline-size: 100%; border-collapse: collapse; }
.table th,
.table td {
  /* `start`, not `left` — the whole table flips with the document. */
  text-align: start;
  padding: var(--space-3) var(--space-4);
  border-block-end: 1px solid var(--line);
  white-space: nowrap;
}
.table thead th { color: var(--text-faint); font-size: .85rem; font-weight: 500; }
.table tbody td { color: var(--text); }
.table tfoot th, .table tfoot td { color: var(--text-hi); }

.btn--sm { padding: var(--space-2) var(--space-4); font-size: .88rem; }

/* A link inside a table must be identifiable as a link WITHOUT relying on
   colour — the numbers here render in the same tone as the surrounding text,
   so colour alone identified nothing at all (WCAG 1.4.1). */
.table a {
  color: var(--text-hi);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.table a:hover { color: var(--lime); }
.table a:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 2px;
}

/* ---------------------------------------------------------- chat widget */
/* `inset-inline-*` throughout, so the whole widget moves to the other side
   under dir="rtl" without a single physical value. The WhatsApp FAB already
   occupies the trailing corner, so chat sits above it rather than beside —
   two circles side by side in a narrow viewport crowd the thumb zone. */
.chat {
  position: fixed;
  inset-block-end: calc(var(--space-9) + 4.25rem);
  inset-inline-end: var(--space-7);
  z-index: 60;
}
.chat__toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--line-control);
  border-radius: 999px;
  background: var(--surface-1, #14150F);
  color: var(--text-hi);
  font-family: inherit;
  font-size: .9rem;
  cursor: pointer;
}
.chat__toggle:hover { border-color: var(--text-hi); }
.chat__toggle:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }

.chat__panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  inline-size: min(22rem, calc(100vw - 2 * var(--space-7)));
  /* Bounded so the panel cannot grow up past the header. It is anchored to
     the bottom, so an unbounded log pushes the top of the panel off-screen
     and the title becomes unreachable. */
  max-block-size: min(26rem, calc(100vh - 12rem));
  margin-block-end: var(--space-4);
  padding: var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg, 14px);
  background: var(--surface-1, #14150F);
}
.chat__panel[hidden] { display: none; }

.chat__h { font-family: var(--font-display); font-size: 1.05rem; color: var(--text-hi); }
.chat__status { color: var(--text-faint); font-size: .82rem; }

.chat__log {
  flex: 1;
  overflow-y: auto;
  display: grid;
  gap: var(--space-3);
  list-style: none;
  padding: 0;
  margin: 0;
}
.chat__msg {
  max-inline-size: 85%;
  padding: var(--space-3) var(--space-4);
  border-radius: 12px;
  font-size: .9rem;
  line-height: calc(1.55 * var(--leading-adjust));
  /* `overflow-wrap` so a pasted URL cannot widen the panel past the viewport. */
  overflow-wrap: anywhere;
}
/* The visitor's own messages hug the trailing edge, staff replies the leading
   one — and both flip with the document, because `margin-inline-start: auto`
   resolves per direction. */
.chat__msg--visitor {
  margin-inline-start: auto;
  /* A lime EDGE, not a lime fill.
     Filled lime here put three lime elements in one viewport alongside the
     page's own CTA and the hero headline, and an accent used three times has
     stopped being an accent (rule 04 §21). The edge still distinguishes the
     visitor's own messages at a glance. */
  background: var(--surface-2, #1B1C15);
  color: var(--text-hi);
  border: 1px solid var(--line);
  border-inline-end: 3px solid var(--lime);
}
.chat__msg--staff,
.chat__msg--ai {
  margin-inline-end: auto;
  background: var(--surface-2, #1B1C15);
  color: var(--text);
  border: 1px solid var(--line);
}

.chat__form { display: grid; gap: var(--space-3); }
.chat__label {
  /* Visually hidden, not removed — the textarea still needs a name. */
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}
.chat__input {
  inline-size: 100%;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line-control);
  border-radius: 10px;
  background: transparent;
  color: var(--text-hi);
  font-family: inherit;
  font-size: .9rem;
  resize: vertical;
}
.chat__input:focus-visible { outline: 2px solid var(--lime); outline-offset: 1px; }
.chat__note { color: var(--text-faint); font-size: .75rem; }

@media (prefers-reduced-motion: reduce) {
  .chat__panel { transition: none; }
}

/* ==========================================================================
   Editorial sections — split, stats, process, statement
   Logical properties throughout; the whole page mirrors on dir alone.
   ========================================================================== */

/* Every image reserves its ratio before it decodes. CLS budget is 0.05 and
   images are the easiest way to blow it (rule 04 §9). */
/* Was `var(--radius-lg, 14px)` against a token that HAS NEVER EXISTED, so
   every editorial image on the site fell back to 14px — the point at which
   a surface reads as a consumer app rather than an instrument. */
.pic { position: relative; overflow: hidden; border-radius: var(--r-3); background: var(--surface-2); }
.pic img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }

.sec__h {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 3.6vw, 2.6rem);
  line-height: 1.18;
  color: var(--text-hi);
  max-inline-size: 24ch;
  margin-block-end: var(--space-4);
}
[dir='rtl'] .sec__h { line-height: 1.5; }
.sec__lede {
  color: var(--text-mid);
  max-inline-size: 62ch;
  font-size: 1.05rem;
  line-height: calc(1.7 * var(--leading-adjust));
}

/* ----------------------------------------------------------- stat band */
.stats { border-block: 1px solid var(--line); background: var(--surface-1, #14150F); }
.stats__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-7) var(--space-5);
  padding-block: var(--space-9);
}
/* Four lime numbers plus a lime eyebrow made this band five accents. Numbers
   do not need colour to read as important — size and a tabular face do it, and
   spending the accent here leaves none for the thing a visitor should act on. */
.stat__n {
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  line-height: var(--lh-display);
  color: var(--text-hi);
  margin-block-end: var(--space-2);
}
.stat__l { color: var(--text-lo); font-size: .9rem; line-height: 1.5; max-inline-size: 24ch; }

/* --------------------------------------------------------------- split */
.split { padding-block: var(--section-air); }
.split__grid { display: grid; gap: var(--space-8); align-items: center; }
.split__h {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.4vw, 2.4rem);
  line-height: 1.18;
  color: var(--text-hi);
  margin-block-end: var(--space-4);
}
[dir='rtl'] .split__h { line-height: 1.5; }
.split__lede { color: var(--text-mid); line-height: calc(1.7 * var(--leading-adjust)); }
.split__points { list-style: none; padding: 0; margin-block-start: var(--space-6); display: grid; gap: var(--space-3); }
.split__points li {
  color: var(--text);
  /* The rule sits on the INLINE-START edge, which flips with direction. */
  padding-inline-start: var(--space-5);
  border-inline-start: 2px solid var(--lime-700, #6E7A12);
  line-height: calc(1.6 * var(--leading-adjust));
}

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-7);
  color: var(--text-hi);
  text-decoration: none;
  border-block-end: 1px solid var(--line-control);
  padding-block-end: var(--space-2);
}
.link-arrow:hover { border-block-end-color: var(--lime); }
.link-arrow:focus-visible { outline: 2px solid var(--lime); outline-offset: 3px; }
/* The arrow MIRRORS — rule 01 §5. Rotated, never swapped for another glyph. */
[dir='rtl'] .link-arrow__i { transform: scaleX(-1); display: inline-block; }

/* ------------------------------------------------------------- process */
.process { padding-block: var(--space-11); border-block-start: 1px solid var(--line); }
.steps {
  list-style: none;
  padding: 0;
  margin-block-start: var(--space-9);
  display: grid;
  gap: var(--space-7);
  counter-reset: step;
}
.step { padding-block-start: var(--space-5); border-block-start: 1px solid var(--line); }
.step__n {
  font-family: var(--font-display);
  font-size: .85rem;
  color: var(--lime);
  letter-spacing: .1em;
  display: block;
  margin-block-end: var(--space-3);
}
.step__h { font-size: 1.05rem; color: var(--text-hi); margin-block-end: var(--space-2); }
.step__b { color: var(--text-lo); font-size: .95rem; line-height: calc(1.65 * var(--leading-adjust)); }

/* ----------------------------------------------------------- statement */
.statement { position: relative; isolation: isolate; }
.statement__bg { border-radius: 0; }
.statement__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  /* Was a flat 72% wash, which is why the page's one full-vehicle photograph
     reads as a grey rectangle. A directional gradient lets the image survive
     where there is no text over it, and stays dense where there is. */
  background: linear-gradient(to var(--grad-end), rgb(11 12 14 / .94) 0%, rgb(11 12 14 / .72) 46%, rgb(11 12 14 / .10) 100%);
}
.statement__inner {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  padding-block: var(--space-10);
}
.statement__q {
  font-family: var(--font-display);
  font-size: var(--fs-display-2);
  line-height: var(--lh-head);
  color: var(--text-hi);
  max-inline-size: 30ch;
}
[dir='rtl'] .statement__q { line-height: 1.6; }
.statement__a { margin-block-start: var(--space-4); color: var(--lime); font-size: .85rem; letter-spacing: .08em; }

@media (min-width: 720px) {
  .stats__grid { grid-template-columns: repeat(4, 1fr); }
  .steps { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
}
@media (min-width: 900px) {
  .split__grid { grid-template-columns: 1fr 1fr; gap: var(--space-10); }
  /* Order, not direction — the grid already mirrors under dir="rtl". */
  .split--flip .split__media { order: 2; }
}

/* Service cards with imagery. The picture link is aria-hidden and
   tabindex="-1" — the heading link beside it goes to the same place, and two
   tab stops to one destination is noise for a keyboard user. */
.card--media { padding: 0; overflow: hidden; }
.card--media .card__media { display: block; }
.card--media .card__media .pic { border-radius: 0; }
.card--media .card__h { padding-inline: var(--space-6); padding-block-start: var(--space-5); }
.card--media .card__body { padding-inline: var(--space-6); padding-block-end: var(--space-6); }
/* --m-long, NOT --dur-sweep. That token is 2600ms — the ambient rake
   sweep — and it was giving every service card a two-and-a-half-second
   image zoom on hover. */
.card--media img { transition: transform var(--m-long) var(--m-ease-out); }
.card--media:hover img { transform: scale(1.03); }
@media (prefers-reduced-motion: reduce) {
  .card--media img, .card--media:hover img { transition: none; transform: none; }
}

/* Hub page headers with imagery behind them.
   Every hub previously opened on bare text against black, which read as an
   unfinished page rather than a considered one. */
.hub--media { position: relative; overflow: hidden; }
/* Layered with POSITIVE z-index on the content, not a negative one on the
   image. `isolation: isolate` on the section creates a stacking context, and a
   z-index:-1 child inside one paints behind the section's own background —
   the image loads, decodes and is never seen. Raising the content is the
   version that does not depend on how the ancestor is stacked. */
.hub__bg {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: 0;
}
.hub--media > .wrap { position: relative; z-index: 2; }
/* The scrim follows GRAVITY, not reading order — `to top` is physical on
   purpose and must not flip in RTL. */
.hub__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  /* Tuned against a DARK photograph. The first pass ran .95 → .62 and the
     image was loaded, decoded, correctly positioned — and completely
     invisible. A scrim built for a bright image erases a dark one.

     Tuned twice: the first pass was too heavy and the second, strengthened
     for one bright photograph, erased the dark ones — and most of this
     library is dark. These values hold body text legible on the darkest
     asset in the set without flattening the brightest. */
  background: linear-gradient(to top, /* rtl-ok */
              rgb(11 12 14 / .92) 0%,
              rgb(11 12 14 / .70) 50%,
              rgb(11 12 14 / .42) 100%);
}
.hub--media { padding-block: var(--space-12) var(--space-10); }

/* ------------------------------------------------------- hero ambient video */
/* Sits between the poster (0) and the scrim (1). It fades IN over the poster,
   so if it never loads — reduced motion, Save-Data, slow connection, refused
   autoplay — the poster simply remains, which is the designed state anyway. */
.hero__motion {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.hero__video {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity var(--dur-sweep, .9s) var(--ease-standard, ease);
}
.hero__video.is-visible { opacity: 1; }

/* Belt and braces. The script already refuses to attach a video under reduced
   motion; this makes it true even if a video reaches the DOM some other way. */
@media (prefers-reduced-motion: reduce) {
  .hero__video { display: none; }
}

/* A moving frame needs more scrim than a still.
   The hero gradient is tuned for a dark photograph; a bright video playing
   under the same values leaves the lede fighting the footage. Applied only
   once a video is actually visible, so the still-only hero keeps its lighter,
   better-looking treatment. */
.has-hero-video .hero::after {
  background: linear-gradient(to top, /* rtl-ok */
              rgb(11 12 14 / .93) 0%,
              rgb(11 12 14 / .74) 45%,
              rgb(11 12 14 / .45) 100%);
}

/* ==========================================================================
   SCROLL REVEALS
   The base state is VISIBLE. The rule that HIDES lives behind
   `html.js-motion`, and that class is added by the same script that contains
   the observer which removes it. A failed script costs the animation, never
   the content — which on a booking site is the only failure mode that matters.
   ========================================================================== */

.js-motion .reveal {
  opacity: 0;
  /* translate3d, not translateY: its own compositor layer, so a reveal never
     triggers layout or paint. transform also cannot cause CLS — it is not a
     layout property — so this costs nothing against the 0.05 budget. */
  transform: translate3d(0, var(--m-travel), 0);
  transition: opacity var(--m-moderate) var(--m-ease-out),
              transform var(--m-moderate) var(--m-ease-out);
}
.js-motion .reveal.is-in,
.js-motion .reveal-group.is-in > .reveal { opacity: 1; transform: none; }

/* VERTICAL ONLY. A horizontal reveal is banned twice over: it drags the eye
   sideways, and in a bidi document it is a trap — sliding in from the left is
   correct in English and backwards in Arabic. Content arrives from below,
   which is where the scroll came from, in every language. */

/* Stagger, enumerated because CSS cannot multiply by an element index.
   Capped at 5: 4 x 60ms = 240ms. Staggering twelve cards would take 660ms and
   stop reading as choreography.
   RTL NEEDS NO SPECIAL CASE — the grid itself mirrors, so DOM child 1 is
   visually first in both scripts and the cascade runs in reading order. */
.js-motion .reveal-group.is-in > .reveal:nth-child(1) { transition-delay: 0ms; }
.js-motion .reveal-group.is-in > .reveal:nth-child(2) { transition-delay: var(--m-stagger); }
.js-motion .reveal-group.is-in > .reveal:nth-child(3) { transition-delay: calc(var(--m-stagger) * 2); }
.js-motion .reveal-group.is-in > .reveal:nth-child(4) { transition-delay: calc(var(--m-stagger) * 3); }
.js-motion .reveal-group.is-in > .reveal:nth-child(5) { transition-delay: calc(var(--m-stagger) * 4); }

.js-motion .reveal--near { transform: translate3d(0, var(--m-travel-sm), 0); }
.js-motion .reveal--near.is-in { transform: none; }

/* THE HERO IS EXCLUDED, EXPLICITLY.
   The LCP element must never start at opacity 0 — that is the commonest way
   a nice scroll animation destroys a Core Web Vitals score. This wins even if
   someone adds .reveal to a hero child from the CMS. */
.js-motion .hero .reveal,
.js-motion .hero.reveal { opacity: 1 !important; transform: none !important; }

/* ------------------------------------------------------------ hover states
   Small travel, short duration, GPU-composited properties only. Enumerated
   rather than `transition: all`, which would also animate the focus ring's
   box-shadow and delay the one thing that must never be delayed. */
.btn {
  transition: background-color var(--m-short) var(--m-ease),
              border-color var(--m-short) var(--m-ease),
              color var(--m-short) var(--m-ease);
}
.card {
  transition: background-color var(--m-short) var(--m-ease),
              border-color var(--m-short) var(--m-ease);
}
/* focus-within, not hover alone: a keyboard user tabbing to the link inside
   the card gets the same feedback a mouse user gets. */
.card:hover, .card:focus-within { border-color: var(--line-strong, var(--line)); }

.link-arrow { transition: border-color var(--m-short) var(--m-ease); }
/* scaleX(var(--flow)) mirrors the glyph AND inverts the local X axis, so the
   translate that follows moves in reading direction with no second rule.
   One expression, correct in both scripts. */
.link-arrow__i {
  display: inline-block;
  transform: scaleX(var(--flow)) translateX(0);
  transition: transform var(--m-short) var(--m-ease);
}
.link-arrow:hover .link-arrow__i,
.link-arrow:focus-visible .link-arrow__i { transform: scaleX(var(--flow)) translateX(3px); }

/* The sticky header gains density on scroll rather than changing height.
   Shrinking a sticky header moves everything below it and books a layout
   shift on a scroll the user did not ask for; the CLS budget is 0.05. */
.nav { transition: background-color var(--m-moderate) var(--m-ease); }

/* The WCAG 2.2 SC 2.2.2 pause control.
   `inset-inline-end` so it mirrors with direction. Sits above the scrim and
   the copy, because it must remain reachable whatever is behind it. */
.hero__pause {
  position: absolute;
  z-index: 3;
  inset-block-end: var(--space-6);
  /* inline-START, not end: the chat widget and the WhatsApp button already
     occupy the trailing corner, and in RTL all three collided. Mirrors with
     direction either way. */
  inset-inline-start: var(--space-6);
  /* 44x44 minimum target — WCAG 2.2 SC 2.5.8. */
  min-block-size: 44px;
  background: rgb(11 12 14 / .62);
  backdrop-filter: blur(8px);
}
.hero__pause[hidden] { display: none; }

/* Some users disable transparency at OS level for exactly this kind of
   overlay; a blurred panel becomes unreadable rather than merely plainer. */
@media (prefers-reduced-transparency: reduce) {
  .hero__pause { background: var(--surface-2, #1B1C15); backdrop-filter: none; }
}

/* ==========================================================================
   SEARCH
   ========================================================================== */
.search { padding-block: var(--space-9) var(--space-12); }
.search__form { display: flex; align-items: end; gap: var(--space-4); max-inline-size: 40rem; }
.search__form .field { flex: 1; }
.search__prompt, .search__empty { color: var(--text-lo); margin-block: var(--space-6); }
.search__count { color: var(--text-faint); font-size: .88rem; margin-block-end: var(--space-6); }

/* Filter chips. `flex-wrap` rather than a scroller: at four sections a
   horizontal scroller hides options behind a gesture nobody performs. */
.search__filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block: var(--space-7);
}
.chip {
  display: inline-block;
  padding: var(--space-2) var(--space-5);
  border: 1px solid var(--line-control);
  border-radius: 999px;
  color: var(--text-lo);
  font-size: .88rem;
  text-decoration: none;
  transition: border-color var(--m-short) var(--m-ease),
              color var(--m-short) var(--m-ease);
}
.chip:hover { border-color: var(--text-hi); color: var(--text-hi); }
.chip:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
/* The active chip is the ONE lime element on this page (rule 04 §21), and it
   is near-black ON lime rather than the reverse — white on lime is 1.85:1. */
.chip.is-current {
  background: var(--lime);
  border-color: var(--lime);
  color: var(--on-lime);
}

.search__list { list-style: none; padding: 0; display: grid; gap: var(--space-8); }
.search__hit { padding-block-end: var(--space-7); border-block-end: 1px solid var(--line); }
.search__section {
  color: var(--text-faint);
  font-size: .75rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  margin-block-end: var(--space-2);
}
.search__h { font-family: var(--font-display); font-size: 1.15rem; margin-block-end: var(--space-2); }
.search__h a { color: var(--text-hi); text-decoration: none; }
.search__h a:hover { color: var(--lime); }
.search__h a:focus-visible { outline: 2px solid var(--lime); outline-offset: 3px; }
.search__excerpt {
  color: var(--text-lo);
  font-size: .95rem;
  line-height: calc(1.65 * var(--leading-adjust));
  max-inline-size: 72ch;
}

/* The degraded-search notice. Distinct from --ok and --bad: this is neither
   a success nor an error, it is a caveat about result quality. */
.note--warn { border-color: var(--lime-700, #6E7A12); }

/* Header search. Icon-only, so it needs an explicit target size — the
   accessible name lives on aria-label, not on visible text. */
.nav__search {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 40px;
  block-size: 40px;
  color: var(--text-lo);
  border-radius: 999px;
  transition: color var(--m-short) var(--m-ease), background-color var(--m-short) var(--m-ease);
}
.nav__search:hover { color: var(--text-hi); background: rgb(255 255 255 / .06); }
.nav__search:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }

/* ==========================================================================
   REVIEWS
   ========================================================================== */

/* The rating input is a real radio group, not a star widget. Native controls
   are keyboard-complete and announce their own state; a div-based star rail
   is the control most likely to leave a screen-reader user unable to answer. */
.rating__scale { display: flex; gap: var(--space-3); margin-block: var(--space-3); }
.rating__opt {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 48px;
  block-size: 48px;          /* WCAG 2.2 SC 2.5.8 target size */
  border: 1px solid var(--line-control);
  border-radius: 999px;
  color: var(--text-lo);
  cursor: pointer;
  transition: border-color var(--m-short) var(--m-ease), color var(--m-short) var(--m-ease);
}
.rating__opt:hover { border-color: var(--text-hi); color: var(--text-hi); }
/* The input is visually hidden but still focusable and still the thing that
   receives the click — never `display: none`, which removes it from the tab
   order and from the accessibility tree entirely. */
.rating__opt input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  margin: 0;
}
.rating__opt:has(input:checked) {
  background: var(--lime);
  border-color: var(--lime);
  color: var(--on-lime);      /* near-black on lime, 10.68:1 */
}
.rating__opt:has(input:focus-visible) { outline: 2px solid var(--lime); outline-offset: 3px; }

/* Honeypot. Off-screen rather than display:none — a bot that reads computed
   styles skips hidden fields, and skipping it is what we are trying to detect. */
.hp {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* Visually hidden but announced. The star glyphs are aria-hidden decoration;
   this carries the actual rating. */
.sr-only {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.reviews { padding-block: var(--space-11); }
.reviews__list {
  list-style: none;
  padding: 0;
  margin-block-start: var(--space-9);
  display: grid;
  gap: var(--space-6);
}
.review {
  padding: var(--space-7);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg, 14px);
  background: var(--surface-1, #14150F);
}
.review__stars { color: var(--lime); letter-spacing: .12em; }
.review__body {
  margin-block: var(--space-4);
  color: var(--text);
  line-height: calc(1.7 * var(--leading-adjust));
  /* A blockquote's default indent is a physical margin in most UA sheets. */
  margin-inline: 0;
}
.review__who { color: var(--text-faint); font-size: .88rem; }

@media (min-width: 900px) {
  .reviews__list { grid-template-columns: repeat(3, 1fr); }
}

/* ---------------------------------------------------------------------------
   Admin-defined forms (blocks.form)
   Reuses .form / .field / .note; only the radio group is new.
   --------------------------------------------------------------------------- */
.form-block { padding-block: var(--space-12); }
.radios { display: grid; gap: var(--space-2); }
.radio { display: flex; align-items: center; gap: var(--space-3); color: var(--text-mid); cursor: pointer; }
.radio input { inline-size: 1.05rem; block-size: 1.05rem; accent-color: var(--lime); flex: none; }
.radio input:focus-visible { outline: 2px solid var(--lime); outline-offset: 3px; }

/* ---------------------------------------------------------------------------
   Social embeds (blocks.social_embed)
   The frame's height is reserved before anything loads — a feed that arrives
   and pushes the page down is exactly the CLS the 0.05 budget forbids.
   --------------------------------------------------------------------------- */
.social-embed { padding-block: var(--space-12); }

.social-embed__frame {
  position: relative;
  block-size: var(--embed-h, 560px);
  margin-block-start: var(--space-6);
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--surface-1);
  overflow: hidden;
}

.social-embed__frame iframe {
  inline-size: 100%;
  block-size: 100%;
  border: 0;
  display: block;
}

.social-embed__gate {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--space-4);
  block-size: 100%;
  padding: var(--space-6);
  text-align: center;
}

.social-embed__note {
  color: var(--text-mid);
  max-inline-size: 46ch;
  line-height: calc(1.6 * var(--leading-adjust));
}

.social-embed__alt a { color: var(--text-lo); font-size: .88rem; }
.social-embed__alt a:hover { color: var(--lime-500); }

/* Once the frame is in, the gate has done its job and must not sit over it. */
.social-embed__frame.is-loaded .social-embed__gate { display: none; }

/* ---------------------------------------------------------------------------
   Loyalty points (portal.loyalty)
   --------------------------------------------------------------------------- */
.points { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: var(--space-4); margin-block: var(--space-8); }

.points__card {
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--surface-1);
}

.points__label { color: var(--text-lo); font-size: .85rem; }
.points__value { font-family: var(--font-display); font-size: 2.4rem; color: var(--text-hi); line-height: 1.1; }
.points__worth { color: var(--text-mid); font-size: .9rem; }

.points__referral { margin-block: var(--space-9); }

/* The one lime element in this viewport (rule 04 §21). Near-black text on
   lime — never white, which measures 1.85:1 (rule 04 §20). */
.points__code {
  display: inline-block;
  margin-block: var(--space-4);
  padding: var(--space-3) var(--space-6);
  background: var(--lime);
  color: var(--on-lime);
  font-family: var(--font-display);
  font-size: 1.6rem;
  letter-spacing: .12em;
  border-radius: 2px;
}

.points__referrals { display: grid; gap: var(--space-2); color: var(--text-mid); font-size: .9rem; }

/* ---------------------------------------------------------------------------
   Programmatic landing pages (landing.show)
   --------------------------------------------------------------------------- */
.hub__eyebrow {
  color: var(--lime-500);
  font-size: .82rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  margin-block-end: var(--space-3);
}

.prose {
  max-inline-size: 68ch;
  color: var(--text-mid);
  line-height: calc(1.7 * var(--leading-adjust));
  padding-block: var(--space-9);
}

.landing__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding-block-end: var(--space-12);
}

/* ==========================================================================
   THE MOTION LAYER
   Everything above this line is the base reveal — a 16px fade that keeps a
   page from arriving all at once. Everything below is the cinematic pass the
   brief asked for, and it is built to three constraints that do not bend:

     1. THE LCP ELEMENT NEVER ANIMATES. The hero poster paints once, opaque,
        at its final size (rule 04 §1). Every rule here that touches the hero
        touches its TEXT children, all of which are smaller than a full-bleed
        poster and so cannot take LCP from it.
     2. TRANSFORM AND OPACITY ONLY. Neither is a layout property, so none of
        this can contribute to the 0.05 CLS budget. No width, height, top or
        margin is ever animated.
     3. NOTHING HERE IS LOAD-BEARING. Reduced motion collapses every duration
        and distance token to zero, and each effect is written so that the
        zero case is the finished state rather than a broken one.
   ========================================================================== */

/* ------------------------------------------------------------ hero entrance
   The hero is already in view, so it arrives on LOAD rather than on scroll —
   a scroll reveal at scrollTop 0 fires in the same frame anyway and just
   reads as a slow paint.

   Pure CSS and NOT gated on `.js-motion`, unlike the scroll reveals. An
   animation always reaches its final frame, so there is no path where a
   script failure strands the hero at opacity 0. That is precisely why the
   scroll reveals need the class and this does not.

   THE POSTER IS ABSENT FROM THIS LIST ON PURPOSE. */
.hero__inner > .eyebrow,
.hero__inner > .hero__h,
.hero__inner > .hero__lede,
.hero__inner > .hero__actions {
  animation: hero-rise var(--m-hero) var(--m-ease-hero) both;
}
.hero__inner > .hero__h       { animation-delay: 80ms; }
.hero__inner > .hero__lede    { animation-delay: 180ms; }
.hero__inner > .hero__actions { animation-delay: 260ms; }

/* The accent is the one lime element in the viewport, and arriving a beat
   after the line it completes is the whole point of splitting the headline
   in two. Nested inside an animating h1: both opacities end at 1, so the
   compounding is invisible. */
.hero__accent {
  animation: hero-rise var(--m-hero) var(--m-ease-hero) both;
  animation-delay: 220ms;
}

@keyframes hero-rise {
  from { opacity: 0; transform: translate3d(0, var(--m-travel), 0); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------------------- section heads
   A heading, its eyebrow and its lede are one thought, so they travel
   further than a card and settle over a longer curve. --m-travel-lg is the
   24px ceiling the token file names, not a new licence to slide. */
.js-motion .reveal--head {
  transform: translate3d(0, var(--m-travel-lg), 0);
  transition-duration: var(--m-long);
  transition-timing-function: var(--m-ease-hero);
}
.js-motion .reveal--head.is-in,
.js-motion .reveal-group.is-in > .reveal--head { transform: none; }

/* The lime rule beside an eyebrow draws itself in reading direction.
   `calc(50% - 50% * var(--flow))` resolves to 0% in LTR and 100% in RTL, so
   one expression puts the origin on the start edge in both scripts — the
   same trick `.link-arrow__i` uses, and the reason there is no RTL override
   sheet anywhere in this file. */
.js-motion .eyebrow.reveal::before {
  transform: scaleX(0);
  transform-origin: calc(50% - 50% * var(--flow)) 50%;
  transition: transform var(--m-long) var(--m-ease-out) var(--m-stagger);
}
/* The eyebrow IS the reveal, not a wrapper around one — so it is revealed
   either by its own `.is-in` or by the group it sits directly inside. */
.js-motion .eyebrow.reveal.is-in::before,
.js-motion .reveal-group.is-in > .eyebrow.reveal::before { transform: scaleX(1); }

/* ------------------------------------------------------------- more stagger
   The cap moves from 5 to 8. Six service cards were the case that exposed it:
   cards 6+ all landed together on the beat of card 5, which reads as a bug
   rather than as choreography. 7 x 60ms = 420ms is still inside the window
   where a sequence is perceived as one gesture. Past 8 the delay is dropped
   entirely rather than growing without bound. */
.js-motion .reveal-group.is-in > .reveal:nth-child(6) { transition-delay: calc(var(--m-stagger) * 5); }
.js-motion .reveal-group.is-in > .reveal:nth-child(7) { transition-delay: calc(var(--m-stagger) * 6); }
.js-motion .reveal-group.is-in > .reveal:nth-child(8) { transition-delay: calc(var(--m-stagger) * 7); }

/* ------------------------------------------------------ scroll-linked drift
   A slow parallax on the two full-width editorial images, driven by
   `animation-timeline: view()` — the scroll position IS the timeline, so this
   costs zero JavaScript, never runs off-screen, and cannot jank the main
   thread the way a scroll listener does.

   Chrome and Edge have it; Safari and Firefox do not, and get a settle on
   reveal instead (below). Both are correct endings — neither browser shows a
   still where the other shows motion.

   The image is scaled by roughly twice the drift so the frame never empties
   at either end of the range. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .split__media .pic img,
    .statement__bg img {
      animation: media-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
      /* `will-change` was left in the stylesheet, permanently promoting three
         full-bleed images to their own compositor layers on every page that
         carries one — the exact thing the design system says never to do. The
         view-timeline already composites this. */
    }
    @keyframes media-drift {
      from { transform: scale(1.1) translate3d(0, calc(var(--m-drift) * -1), 0); }
      to   { transform: scale(1.1) translate3d(0, var(--m-drift), 0); }
    }
  }
}

/* The fallback, and the reason it is written as `not (…)` rather than left to
   the cascade: the drift above sets `transform` from a filling animation,
   which outranks a transition on the same property. Running both would mean
   the settle silently never happening in Chrome, which is the kind of dead
   rule that survives for years. */
@supports not (animation-timeline: view()) {
  .js-motion .split__media .pic img,
  .js-motion .statement__inner ~ .statement__bg img {
    transform: scale(1.06);
    transition: transform var(--m-hero) var(--m-ease-hero);
  }
  .js-motion .split__media.is-in .pic img { transform: none; }
}

/* ------------------------------------------------------------ button lift
   One pixel. The colour change is the feedback; the lift is what makes it
   feel like a physical control rather than a rectangle changing colour.
   The full transition list is restated because `transition` is not additive —
   a second declaration replaces the first rather than extending it. */
.btn {
  transition: background-color var(--m-short) var(--m-ease),
              border-color var(--m-short) var(--m-ease),
              color var(--m-short) var(--m-ease),
              transform var(--m-short) var(--m-ease);
}
.btn:hover { transform: translate3d(0, -1px, 0); }
/* Returns to rest on press, so the control reads as being pushed down. */
.btn:active { transform: none; }

/* Same gesture on a card, replacing the base rule's colour-only transition.
   `focus-within`, so a keyboard user tabbing into the card gets it too. */
.card {
  transition: background-color var(--m-short) var(--m-ease),
              border-color var(--m-short) var(--m-ease),
              transform var(--m-short) var(--m-ease-out);
}
.card:hover, .card:focus-within { transform: translate3d(0, -2px, 0); }

/* --------------------------------------------------------- page transitions
   Cross-document view transitions: the browser cross-fades between two pages
   of the same origin with no router, no SPA and no JavaScript. Progressive by
   construction — a browser without support simply navigates.

   Kept SHORT. A page transition longer than the navigation it covers makes a
   fast site feel slow, which is the opposite of the intent. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }

  ::view-transition-old(root) {
    animation-duration: var(--m-short);
    animation-timing-function: var(--m-ease-in);
  }
  ::view-transition-new(root) {
    animation-duration: var(--m-moderate);
    animation-timing-function: var(--m-ease-out);
  }
}

/* ------------------------------------------------------------ stat counters
   The figure counts up when its band arrives. `tabular-nums` and the width
   the script reserves are what keep a counting number from resizing its own
   column — an animation that causes layout shift would fail the 0.05 budget
   on a decorative flourish, which is never a trade worth making. */
.stat__n { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------ belt & braces
   Every effect above is already zero-valued under reduced motion via the
   tokens. This kills the two that are structural rather than token-driven —
   an animation-timeline does not read a duration token, and a view
   transition is negotiated by the browser before any of our CSS applies. */
@media (prefers-reduced-motion: reduce) {
  .split__media .pic img,
  .statement__bg img {
    animation: none;
    transform: none;
  }
  .hero__inner > .eyebrow,
  .hero__inner > .hero__h,
  .hero__inner > .hero__lede,
  .hero__inner > .hero__actions,
  .hero__accent { animation: none; opacity: 1; transform: none; }
  .btn:hover, .card:hover, .card:focus-within { transform: none; }
}

/* ------------------------------------------------- reveal meets hover
   The one place the two systems collide. `transition` is a single property,
   not an additive list, so whichever rule wins replaces the other entirely —
   and `.js-motion .reveal` (two classes) outranks a bare `.card` (one). Left
   alone, giving a card a reveal would silently delete its hover transition
   and the colour change would snap.

   Both lists are therefore restated together on the elements that are
   revealable AND interactive. Transform runs at --m-short on a card because
   one duration has to serve both the entrance and the hover lift, and a
   400ms lift under the cursor feels broken in a way a 250ms entrance does
   not. */
.js-motion .card.reveal {
  transition: opacity var(--m-moderate) var(--m-ease-out),
              transform var(--m-short) var(--m-ease-out),
              background-color var(--m-short) var(--m-ease),
              border-color var(--m-short) var(--m-ease);
}
.js-motion .link-arrow.reveal {
  transition: opacity var(--m-moderate) var(--m-ease-out),
              transform var(--m-moderate) var(--m-ease-out),
              border-color var(--m-short) var(--m-ease);
}

/* ==========================================================================
   3D VEHICLE VIEWER
   The poster is the section. Everything here layers over an image that is
   already correct, so every give-up path — no WebGL, reduced motion, a failed
   fetch — leaves a finished-looking block rather than a hole.
   ========================================================================== */

.viewer { padding-block: clamp(var(--space-10), 9vw, var(--space-13)); }

/* The stage reserves its own aspect ratio, so swapping a poster for a canvas
   moves nothing. CLS budget is 0.05 and a viewer that resizes on load is the
   easiest way to spend all of it. */
.viewer__stage {
  position: relative;
  margin-block-start: var(--space-8);
  aspect-ratio: 16 / 9;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg, 14px);
  overflow: hidden;
  background: var(--surface-2);
}
.viewer__poster,
.viewer__canvas {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
}
.viewer__poster { border-radius: 0; }
.viewer__canvas canvas { display: block; inline-size: 100%; block-size: 100%; }

/* The poster does not disappear when the model arrives — it fades under it.
   A transparent GLB over an empty box looks broken for the frame or two
   before the first render lands. */
.viewer__stage.is-live .viewer__poster {
  opacity: 0;
  transition: opacity var(--m-long) var(--m-ease-out);
}

/* ------------------------------------------------------------- hotspots
   DOM overlays, never shapes inside the canvas (rule 04 §23): Arabic does not
   shape reliably in a WebGL texture, and a screen reader cannot see one.
   Positioned by percentage so they track the image at any width — and because
   percentages of the diagram's own viewBox mirror correctly under RTL without
   a second rule. */
.viewer__spots { position: absolute; inset: 0; margin: 0; padding: 0; list-style: none; }
.viewer__spot {
  position: absolute;
  inset-inline-start: var(--spot-x);
  inset-block-start: var(--spot-y);
}

.viewer__pin {
  /* Translated by half its own size so the PIN CENTRE sits on the anchor,
     not its corner. `--flow` flips the X half under RTL, which is what keeps
     the pin on the panel when the whole stage mirrors. */
  transform: translate(calc(-50% * var(--flow)), -50%);
  inline-size: 32px;
  block-size: 32px;
  border-radius: var(--r-pill, 999px);
  border: 1px solid var(--lime);
  /* Near-black on lime: 10.11:1. White on lime is 1.90:1 and fails AA by more
     than double (rule 04 §20). */
  background: var(--lime);
  color: var(--on-lime);
  font-weight: 700;
  font-size: .82rem;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: transform var(--m-short) var(--m-ease-out);
}
.viewer__pin:hover { transform: translate(calc(-50% * var(--flow)), -50%) scale(1.12); }
.viewer__pin:focus-visible { outline: 2px solid var(--lime-300); outline-offset: 3px; }

.viewer__note {
  position: absolute;
  inset-inline-start: 44px;
  inset-block-start: -8px;
  inline-size: max(220px, 22vw);
  max-inline-size: 300px;
  padding: var(--space-5);
  background: var(--surface-3);
  border: 1px solid var(--line-2);
  border-radius: 3px;
  z-index: 3;
}
.viewer__note[hidden] { display: none; }
.viewer__note-zone { color: var(--lime); font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; }
.viewer__note-title { color: var(--text-hi); font-weight: 700; margin-block-start: var(--space-2); }
.viewer__note-body { color: var(--text-mid); font-size: .9rem; margin-block-start: var(--space-2); }

/* Near the trailing edge the note would run off the stage. Flipping it to the
   other side of the pin is direction-agnostic because both offsets are
   logical. */
.viewer__spot:nth-last-child(-n+3) .viewer__note {
  inset-inline-start: auto;
  inset-inline-end: 44px;
}

.viewer__load {
  position: absolute;
  inset-block-end: var(--space-5);
  inset-inline-end: var(--space-5);
  z-index: 4;
}
.viewer__load[hidden] { display: none; }

.viewer__status {
  margin-block-start: var(--space-4);
  color: var(--text-lo);
  font-size: .88rem;
}

/* On a narrow screen the notes cannot float over a 16:9 stage without
   covering the car. They stack under it instead, always open, which is the
   readable arrangement rather than a shrunken version of the wide one. */
@media (max-width: 640px) {
  .viewer__note {
    position: static;
    inline-size: auto;
    max-inline-size: none;
    margin-block-start: var(--space-3);
  }
  .viewer__spots { position: static; display: grid; gap: var(--space-4); padding-block-start: var(--space-5); }
  .viewer__spot { position: static; display: flex; gap: var(--space-4); align-items: flex-start; }
  .viewer__pin { transform: none; flex: none; }
  .viewer__pin:hover { transform: scale(1.12); }
}

@media (prefers-reduced-motion: reduce) {
  .viewer__stage.is-live .viewer__poster { transition: none; }
  .viewer__pin, .viewer__pin:hover { transition: none; }
}

/* ==========================================================================
   CONFIGURATOR — /صمم-سيارتك, /configure
   ==========================================================================

   Three things here are load-bearing rather than decorative:

   1. THE CAR IS NEVER MIRRORED. Everything else in this block uses logical
      properties and flips with direction (rule 01 §3), but the silhouette is
      an SVG with its own coordinate system and is deliberately left alone —
      a mirrored car is a right-hand-drive car, which is a different vehicle
      rather than the same one laid out the other way (rule 01 §4).

   2. ONE LIME ELEMENT PER VIEWPORT (rule 04 §21). The accent is spent on the
      total, because that is the number the whole page exists to produce.
      Selected states therefore signal with border and surface, never fill —
      which is also why a chosen tier is legible to anyone who cannot separate
      the accent from the ground.

   3. NOTHING HERE DEPENDS ON SCRIPT. The submit button is present and works;
      `is-live` removes it once the script is driving, so a control that would
      do nothing never sits on screen.
   ------------------------------------------------------------------------ */

.cfg { padding-block: var(--space-11) var(--space-12); }
.cfg__h {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: var(--tracking-display);
  font-size: clamp(2rem, 5vw, 3.25rem);
  color: var(--text-hi);
  margin-block: var(--space-2) var(--space-4);
}
.cfg__lede { color: var(--text-mid); max-inline-size: 46ch; margin-block-end: var(--space-9); }

.cfg__layout { display: grid; gap: var(--space-9); }
@media (min-width: 1024px) {
  .cfg__layout { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); align-items: start; }
  /* Sticky so the car stays in view while the controls scroll past it —
     the diagram is the feedback for every choice below it. */
  .cfg__stage { position: sticky; inset-block-start: var(--space-8); }
}

/* -------------------------------------------------------- coverage map */

.cfg__map { margin: 0; }
.cfg__svg {
  inline-size: 100%;
  block-size: auto;
  /* Reserved before anything paints. The CLS budget is 0.05 and a diagram
     that resizes on first render spends most of it on its own (rule 04 §9). */
  aspect-ratio: 1200 / 520;
  display: block;
}
.cfg__body { fill: var(--surface-2); }
.cfg__outline { fill: none; stroke: var(--line-2); stroke-width: 3; }
.cfg__zone {
  fill: var(--surface-3);
  stroke: var(--surface-1);
  stroke-width: 2;
  transition: fill var(--dur-2) var(--ease-standard);
}
/* Covered panels read as a lifted surface, not as the accent — see note 2.
   `lime-900` is dark enough to sit under the outline without becoming the
   brightest thing on the page. */
.cfg__zone.is-covered { fill: var(--lime-900); stroke: var(--lime-700); }

.cfg__map-note {
  color: var(--text-lo);
  font-size: .875rem;
  margin-block-start: var(--space-3);
  max-inline-size: 44ch;
}

.cfg__panels {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  padding: 0;
  margin-block-start: var(--space-4);
}
.cfg__panels li {
  font-size: .8125rem;
  color: var(--text-mid);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: var(--space-1) var(--space-3);
}
.cfg__panels:empty { display: none; }

/* --------------------------------------------------------- the controls */

.cfg__group { border: 0; padding: 0; margin: 0 0 var(--space-9); }
.cfg__legend {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.125rem;
  color: var(--text-hi);
  padding: 0;
  margin-block-end: var(--space-4);
}
.cfg__label { display: block; font-size: .875rem; color: var(--text-mid); margin-block-end: var(--space-2); }
.cfg__input {
  inline-size: 100%;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-1);
  border: 1px solid var(--line-control);
  border-radius: 3px;
  color: var(--text-hi);
  font: inherit;
  /* `start`, not `left` — this field takes Arabic and English in the same
     box and must align to whichever direction is running. */
  text-align: start;
}
.cfg__input:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
.cfg__help { font-size: .8125rem; color: var(--text-lo); margin-block-start: var(--space-2); }
.cfg__matched { font-size: .9375rem; color: var(--text); margin-block-start: var(--space-3); min-block-size: 1.5em; }
.cfg__or { font-size: .8125rem; color: var(--text-lo); margin-block: var(--space-6) var(--space-3); }

.cfg__shapes,
.cfg__tiers,
.cfg__finishes { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-2); }
.cfg__shapes { grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); }

.cfg__shape {
  display: block;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--surface-1);
  text-decoration: none;
  block-size: 100%;
  transition: border-color var(--dur-2) var(--ease-standard);
}
.cfg__shape:hover { border-color: var(--line-2); }
.cfg__shape.is-on { border-color: var(--lime-600); background: var(--surface-2); }
.cfg__shape:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
.cfg__shape-code {
  font-family: var(--font-display);
  font-size: .6875rem;
  letter-spacing: .12em;
  color: var(--text-faint);
  display: block;
}
.cfg__shape-label { display: block; color: var(--text-hi); font-weight: 600; font-size: .9375rem; }
.cfg__shape-eg { display: block; color: var(--text-lo); font-size: .75rem; margin-block-start: var(--space-1); }

/* ------------------------------------------------------------ the tiers */

.cfg__tier {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--surface-1);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-standard);
}
.cfg__tier:hover { border-color: var(--line-2); }
.cfg__tier.is-on { border-color: var(--lime-600); background: var(--surface-2); }
/* The radio stays in the document and stays focusable — it is what makes the
   whole control keyboard-operable and what a screen reader announces. It is
   only visually replaced by the card around it. */
.cfg__tier input { accent-color: var(--lime-600); flex: none; }
.cfg__tier:has(input:focus-visible) { outline: 2px solid var(--lime); outline-offset: 2px; }
.cfg__tier-name { color: var(--text-hi); font-weight: 600; font-size: .9375rem; flex: 1; }
.cfg__tier-price {
  color: var(--text-mid);
  font-size: .875rem;
  /* Prices line up in a column, so the figures must be tabular or the
     decimal drifts row to row. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cfg__tier--none .cfg__tier-name { color: var(--text-lo); font-weight: 400; }

.cfg__note {
  font-size: .8125rem;
  color: var(--text-lo);
  border-inline-start: 2px solid var(--line-2);
  padding-inline-start: var(--space-3);
  margin-block-end: var(--space-3);
  max-inline-size: 52ch;
}
.cfg__note--stop { border-inline-start-color: var(--line-control); color: var(--text-mid); }

/* ---------------------------------------------------------- the finishes
   A RENDERED SAMPLE, NOT A COLOUR CHIP. The same colour in gloss, satin and
   matte is the same hex — the entire difference is how a highlight rolls off
   a curved panel, which a flat swatch cannot express. Each sample is a
   cylinder lit by one hard source at the house rake angle; the width and
   contrast of the specular band IS the information.

   Photographed samples replace these once the production shoot happens; the
   markup does not change when they do. */

.cfg__finishes { grid-template-columns: repeat(3, 1fr); }
.cfg__finish {
  display: block;
  text-decoration: none;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: var(--space-2);
  background: var(--surface-1);
}
.cfg__finish.is-on { border-color: var(--lime-600); }
.cfg__finish:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
.cfg__finish-sample {
  display: block;
  block-size: 3.5rem;
  border-radius: 2px;
  background-color: var(--surface-4);
}
.cfg__finish--gloss .cfg__finish-sample {
  background-image: linear-gradient(61deg,
    var(--surface-2) 0%, var(--surface-3) 34%,
    var(--text-hi) 44%, var(--text-mid) 48%,
    var(--surface-3) 56%, var(--ink) 100%);
}
.cfg__finish--satin .cfg__finish-sample {
  background-image: linear-gradient(61deg,
    var(--surface-2) 0%, var(--surface-3) 26%,
    var(--text-lo) 46%, var(--surface-4) 62%,
    var(--surface-2) 100%);
}
.cfg__finish--matte .cfg__finish-sample {
  background-image: linear-gradient(61deg,
    var(--surface-2) 0%, var(--surface-3) 40%,
    var(--surface-4) 55%, var(--surface-2) 100%);
}
.cfg__finish-name { display: block; text-align: center; font-size: .8125rem; color: var(--text-mid); margin-block-start: var(--space-2); }

/* ------------------------------------------------------------ the price */

.cfg__price {
  margin-block-start: var(--space-9);
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--surface-1);
  padding: var(--space-6);
}
.cfg__price-h { font-family: var(--font-display); font-size: 1.125rem; color: var(--text-hi); margin: 0 0 var(--space-4); }
.cfg__price-empty { color: var(--text-lo); }
.cfg__lines { list-style: none; padding: 0; margin: 0 0 var(--space-4); display: grid; gap: var(--space-2); }
.cfg__lines li { display: flex; justify-content: space-between; gap: var(--space-4); font-size: .9375rem; color: var(--text); }
.cfg__num { font-variant-numeric: tabular-nums; color: var(--text-mid); white-space: nowrap; }

.cfg__total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  border-block-start: 1px solid var(--line);
  padding-block-start: var(--space-4);
  margin: 0;
}
.cfg__total-label { color: var(--text-mid); font-size: .9375rem; }
/* THE one lime element in this viewport. */
.cfg__total-num {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.5rem, 4vw, 2rem);
  color: var(--lime);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cfg__vat { font-size: .8125rem; color: var(--text-lo); margin-block-start: var(--space-2); }
.cfg__provisional { font-size: .875rem; color: var(--text-mid); margin-block-start: var(--space-3); }

/* Adjacent to the number, never a footnote — under E-Commerce Law Art. 10(1)
   a displayed price is a binding representation, and only a notice visible
   before contracting discharges it. Do not move this into a tooltip. */
.cfg__disclaimer {
  font-size: .75rem;
  line-height: 1.6;
  color: var(--text-lo);
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--line);
  max-inline-size: 62ch;
}

.cfg__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-block-start: var(--space-6); }
/* Present and working until the script takes over; removed the moment it
   does, so no control on screen is a no-op. */
.cfg__form.is-live .cfg__submit { display: none; }

@media (prefers-reduced-motion: reduce) {
  .cfg__zone, .cfg__shape, .cfg__tier, .cfg__finish { transition: none; }
}

/* ==========================================================================
   SCROLL SEQUENCE — the 3D block that turns as you read
   ==========================================================================

   The layout IS the fallback. Below 1024px and under reduced motion there is
   no sticky stage and no canvas: the poster sits above the chapters and the
   whole thing is an ordinary illustrated section, which is a complete design
   rather than a degraded one. Everything the pinned version adds is additive.

   The car is never mirrored (rule 01 §4) — the poster is a photograph and the
   canvas has its own coordinate system, so neither flips. Only the text column
   swaps sides, which is what `grid-template-columns` and logical properties do
   for free.
   ------------------------------------------------------------------------ */

.seq { padding-block: var(--space-11); }

.seq__scroller { position: relative; margin-block-start: var(--space-9); }

/* Base: no pinning. Stage first, chapters after, in document order. */
.seq__sticky { margin-block-end: var(--space-8); }

.seq__stage {
  position: relative;
  /* Reserved before anything paints. The CLS budget is 0.05 and a stage that
     resizes on first render spends most of it on its own (rule 04 §9). */
  aspect-ratio: 16 / 9;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow: hidden;
}
.seq__poster,
.seq__stage img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
}
.seq__canvas {
  position: absolute;
  inset: 0;
  /* Opacity, not `display`. A lost WebGL context restores to a painted image
     rather than to an empty box, and the swap can be a fade rather than a jump. */
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-standard);
}
.seq__canvas canvas { inline-size: 100%; block-size: 100%; display: block; }
/* Only once the model is actually on screen does the poster hand over. */
.seq__scroller.is-live .seq__canvas { opacity: 1; }
.seq__scroller.is-live .seq__poster { opacity: 0; transition: opacity var(--dur-3) var(--ease-standard); }

.seq__chapters { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-9); }

.seq__card {
  border-inline-start: 2px solid var(--line-2);
  padding-inline-start: var(--space-5);
  max-inline-size: 46ch;
}
.seq__n {
  font-family: var(--font-display);
  font-size: .75rem;
  letter-spacing: .14em;
  color: var(--text-faint);
  margin: 0 0 var(--space-2);
}
.seq__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--text-hi);
  margin: 0 0 var(--space-2);
}
.seq__body { color: var(--text-mid); margin: 0; }
.seq__angle {
  font-size: .75rem;
  letter-spacing: .06em;
  color: var(--text-faint);
  margin-block-start: var(--space-3);
}

/* ------------------------------------------------------------- the pinned
   version. Two conditions, both required: enough width for a two-column
   layout, and no stated objection to motion. */
@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
  .seq__scroller {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: var(--space-10);
    align-items: start;
  }

  .seq__sticky {
    position: sticky;
    /* Not zero: a stage flush against the top of the viewport reads as a
       takeover. Leaving the header visible keeps it a section on a page. */
    inset-block-start: var(--space-8);
    margin-block-end: 0;
  }

  /* THE SCROLL DISTANCE IS PROPORTIONAL TO THE READING.
     One viewport per chapter, set from the chapter count rather than fixed, so
     a two-chapter sequence does not make the reader scroll through five
     screens of nothing to finish it. */
  .seq__chapters {
    gap: 0;
    min-block-size: calc(var(--seq-chapters, 3) * 100vh);
  }
  .seq__chapter {
    min-block-size: 100vh;
    display: flex;
    align-items: center;
  }
}

@media (prefers-reduced-motion: reduce) {
  .seq__canvas,
  .seq__poster { transition: none; }
}

/* ------------------------------------------------- configurator: advisor
   and the remembered-car offer. Both are quiet by design — they sit above
   the real controls and must not compete with them for attention. */

.cfg__advisor {
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--surface-1);
  padding: var(--space-4);
  margin-block-end: var(--space-9);
}
.cfg__advisor-summary {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--text-hi);
  cursor: pointer;
  /* The marker is a disclosure affordance, not decoration — left to the
     browser so it points the right way in RTL for free. */
  list-style-position: inside;
}
.cfg__advisor-summary:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
.cfg__advisor[open] .cfg__advisor-summary { margin-block-end: var(--space-3); }
.cfg__group--tight { margin-block-end: var(--space-5); }
.cfg__group--tight .cfg__label { margin-block-end: var(--space-3); }

/* `display: flex` beats the UA stylesheet's `[hidden] { display: none }`, so
   without this the bar renders — empty — on every page that has nothing to
   restore. An author `display` on an element that is ever `hidden` has to say
   so explicitly; this is the classic version of that bug. */
.cfg__resume[hidden] { display: none; }
.cfg__resume {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  border: 1px solid var(--line-2);
  border-radius: 3px;
  background: var(--surface-2);
  padding: var(--space-3) var(--space-4);
  margin-block-end: var(--space-6);
}
.cfg__resume-text { margin: 0; flex: 1; font-size: .9375rem; color: var(--text); min-inline-size: 12ch; }
.cfg__forget {
  background: none;
  border: 0;
  padding: var(--space-1) var(--space-2);
  color: var(--text-lo);
  font: inherit;
  font-size: .8125rem;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.cfg__forget:hover { color: var(--text); }
.cfg__forget:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }

/* ==========================================================================
   THE CAR MOTIF — the geometric identity the site was specified to have
   ==========================================================================

   `docs/16-ui-direction.md` §1.3 calls the rake "the signature, the thing the
   site is remembered by". Only its easing curve was ever built, so the site
   shipped as rounded rectangles on black — correct, and anonymous.

   Three devices, drawn from the subject: a panel shut line, a cut edge, and
   measurement typography. No photography needed for any of them, which matters
   because there is none yet (blocker B7).

   COMPOSITION RULE: at most two of these visible at once. A motif applied
   everywhere is a texture, and a texture is not a signature.
   ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------- D1: seam
   A panel shut line. A hairline that BREAKS at the gutter and ends in a raked
   tick — the join between two panels, which is the exact thing this studio
   spends its day on. Mirrors for free through --rake-skew. */
.seam {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  border: 0;
  margin: 0;
  padding: 0;
  block-size: var(--bw-1);
}
.seam::before {
  content: '';
  flex: 1;
  block-size: var(--bw-1);
  background: var(--line);
}
.seam::after {
  content: '';
  inline-size: var(--space-7);
  block-size: var(--bw-3);
  background: var(--line-2);
  transform: skewX(var(--rake-skew));
}

/* ------------------------------------------------------------ D2: rake cut
   A cut edge on full-bleed surfaces only. ~30 bytes a use and the single
   biggest step away from "ten grey bands" — it gives the page a direction.
   Capped at five surfaces site-wide; beyond that it is wallpaper. */
.rake-cut { clip-path: var(--rake-cut); }
.rake-cut--top {
  clip-path: polygon(0 0, 100% var(--rake-drop), 100% 100%, 0 100%);
}

/* ------------------------------------------- D3: measurement typography
   For facts a customer compares: a price, a duration, a warranty term, a
   coverage figure. Its own quiet register, so a number can read as important
   without spending the accent — which is what frees the lime budget
   everywhere else. Tabular and slashed-zero because these line up in columns
   and get read against each other. */
.spec {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums slashed-zero;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--text-lo);
  margin: 0;
}
.spec > * + * {
  /* A rule, never a middot. A divider is a drawn line on an instrument. */
  border-inline-start: var(--bw-1) solid var(--line-2);
  padding-inline-start: var(--space-4);
}
.spec b {
  font-weight: 600;
  color: var(--inst-300);
  /* Latin-and-digits inside Arabic: the value is a measurement, and bidi
     would otherwise reorder "3 years" or "SAR 1,290" (rule 01 §6). */
  unicode-bidi: isolate;
}

/* --------------------------------------------------------- section rhythm
   One air token for every band. Ten sections at an identical fixed padding is
   literally what "evenly-spaced grey bands" means; scaling with the viewport
   gives a desktop page room and does not spend a phone screen on margin. */
.seq,
.cfg,
.reviews,
.social-embed { padding-block: var(--section-air); }

@media (prefers-reduced-motion: reduce) {
  .seam::after { transform: none; }
}

/* The rake applied, to full-bleed surfaces only — the two on the homepage
   plus the section hub. Five site-wide is the cap; these are three of them. */
.hero,
.statement__bg,
.hub--media { clip-path: var(--rake-cut); }

/* The seam sits between bands, so a section ENDS rather than merely stopping. */
.split + .sec::before,
.sec + .split::before,
.process + .sec::before {
  content: '';
  display: block;
  block-size: var(--bw-1);
  background: var(--line);
  margin-block-end: var(--section-air);
}
