/* ============================================================================
   Dawam landing page — the entire stylesheet for "/".
   Deliberately standalone: this page never loads main.css (~205 KB) or
   i18n.js (~317 KB). Token VALUES below are copied verbatim from main.css so
   the page and the app are the same brand by construction; nothing is imported.

   Light-only by decision (D1): no prefers-color-scheme block and no
   [data-theme] hook anywhere in this file, because the cream brand surface has
   no ratified dark counterpart, and `color-scheme: light` is what stops a
   phone in dark mode auto-darkening the ground behind a late-painting
   background.

   RTL is carried entirely by logical properties: no physical left/right
   anywhere below, and probe-layout.js greps this file to keep it that way.
   ========================================================================= */

/* ── Fonts (D3) ───────────────────────────────────────────────────────────
   Self-hosted Vazirmatn, one VARIABLE file per subset covering 400–700.
   NO ?v= on either src — this string must be byte-identical to the
   <link rel="preload"> href in index.html. deploy.sh rewrites ?v= only inside
   *.html, so a buster here would freeze at its authored value while the HTML's
   moved every deploy: two URLs for one file, a wasted preload and a double
   download. Fonts are versioned by FILENAME. The unicode-range values are
   load-bearing and copied verbatim from frontend/fonts/PROVENANCE.md — without
   them every visitor downloads BOTH subsets and the saving is gone. */
@font-face {
  font-family: 'Vazirmatn';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/vazirmatn-arabic.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}
@font-face {
  font-family: 'Vazirmatn';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/vazirmatn-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── Tokens ────────────────────────────────────────────────────────────── */
:root {
  color-scheme: light;

  /* Colour primitives — the ratified Dawam palette (DECISIONS.md D3/D4).
     Every text pair used on this page is in the plan's measured contrast
     table and clears 4.5:1. --navy-600 and --teal-600 are deliberately ABSENT:
     they measure 4.10 and 3.68 on cream and must never be used for text here. */
  --navy-950: #14253a;
  --navy-900: #203650;
  --navy-700: #415d7f;
  --navy-100: #e3ebf4;
  --teal-800: #185250;
  --teal-700: #2a6664;
  --teal-300: #a7cac8;
  --teal-200: #c6dedd;
  --teal-100: #dfedec;
  --teal-50:  #f0f7f7;
  --cream:    #F3EFE8;
  /* The palette exception (D6): white on WhatsApp green is 1.98:1 and fails
     outright, so anything on the platform green takes navy ink (6.21:1). Since
     2026-08-30 (owner's call) it paints the floating button AND .btn-primary,
     so the hero/closing CTA and the float read as the same action; nothing
     else may use it. */
  --wa-green: #25D366;
  /* Named, because #closing's block-end clearance is defined AS this. */
  --wa-float-h: 48px;
  /* Its phone form is a full-bleed bar, not a pill, so it is taller. */
  --wa-bar-h: 56px;
  /* The ONE clearance #closing and .site-footer take, redefined once in the
     phone block. Nothing reads --wa-float-h for clearance directly. */
  --wa-clear: calc(var(--wa-float-h) + var(--sp-5));

  --hairline: 1px solid rgba(32, 54, 80, 0.12);
  --radius: 10px;
  --radius-lg: 14px;

  /* Landing-specific type scale. NOT the app's --fs-* ladder: the app tops out
     at 36px for a KPI value and has no display sizes. 16px is the body floor
     on this page (the app's 14px is for dense tables read all day; this is a
     marketing page read once by a stranger on a phone). */
  --ls-1: clamp(1.75rem, 7.2vw, 2.75rem);
  --ls-2: clamp(1.375rem, 5.2vw, 1.875rem);
  --ls-3: 1.125rem;
  --ls-body-lg: 1.0625rem;
  --ls-body: 1rem;
  --ls-small: 0.875rem;

  /* One 4px base unit. Every margin and padding on this page is one of these. */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4rem;
  --sp-9: 6rem;

  --gutter: var(--sp-4);
  --measure: 34rem;
}
@media (min-width: 431px) { :root { --gutter: var(--sp-5); } }
@media (min-width: 900px) { :root { --measure: 56rem; } }

/* ── Reset ─────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, h3, p, dl, dd, figure, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg, video { display: block; max-inline-size: 100%; }
img { block-size: auto; }
a { color: inherit; }
button { font: inherit; }
/* §4 and the hero's video CTA ship [hidden] and landing.js unhides them, so
   this has to beat any author display — .hero-cta2 is inline-flex, and the UA
   [hidden] rule loses to every author rule. */
[hidden] { display: none !important; }

/* ── Base typography ───────────────────────────────────────────────────── */
/* The hero's phone crop runs past the inline-end viewport edge and THIS is the
   rule that cuts it — the image carries no clipping of its own, so the cut
   lands on the screen edge in both directions.

   The value pair is deliberate: `hidden` makes the element a SCROLL CONTAINER,
   so it hides the scrollbar while documentElement.scrollWidth keeps reporting
   the overflow (392 against a 360px viewport — the probe's own assertion
   failing on a page that looked perfectly clipped); `clip` creates no scroll
   container, so the number agrees with the picture, and `hidden` stays first
   as the fallback for Safari < 16, where the result looks identical.

   BOTH elements: overflow on <html> propagates to the VIEWPORT and leaves the
   html box itself `visible`, so that rule alone clipped nothing. <body> does
   the clipping; <html> stops body's value being propagated instead. */
html,
body {
  overflow-x: hidden;
  overflow-x: clip;
}

/* The hero's secondary CTA jumps to #video, and #offer / #faq are the ad
   deep-link anchors — all three read better as a scroll than as a cut. The
   reduced-motion block at the foot of this file forces scroll-behavior: auto
   !important, so this is opt-out by preference rather than unconditional. */
html { scroll-behavior: smooth; }

body {
  background: var(--cream);
  color: var(--navy-900);
  font-family: 'Vazirmatn', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: var(--ls-body);
  -webkit-font-smoothing: antialiased;
}

/* Line height is set PER SCRIPT; font size never is. Vazirmatn's Arabic line
   box is ~1.57em against the Latin fallback's ~1.15, and at display sizes the
   Arabic needs to breathe more. Do NOT extend this to font-size — that is what
   drags rem paddings against fixed-px constraints in the app. */
body { line-height: 1.6; }
html[lang="ar"] body { line-height: 1.85; }
h1 { line-height: 1.18; }
html[lang="ar"] h1 { line-height: 1.42; }

/* ── D2: both languages live in the DOM, CSS shows one ─────────────────────
   Keyed off the CLASS, not the bare lang attribute, because the toggle's own
   label is English text that must be visible in Arabic mode
   (class="i18n-ar" lang="en") — the class says WHEN to show it, the lang says
   WHAT it is, and a screen reader needs both. */
html[lang="ar"] .i18n-en,
html[lang="en"] .i18n-ar { display: none; }

/* ── Focus ─────────────────────────────────────────────────────────────────
   Never `outline: none` anywhere in this file. Cream ring on the two dark
   bands and on the green floating button; navy everywhere else. */
:focus-visible {
  outline: 3px solid var(--navy-700);
  outline-offset: 3px;
}
#pain :focus-visible,
#offer :focus-visible,
.wa-float:focus-visible { outline-color: var(--cream); }

/* ── Layout primitives ─────────────────────────────────────────────────── */
.wrap {
  inline-size: 100%;
  max-inline-size: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ── Header — not sticky (D7) ──────────────────────────────────────────────
   A 56px sticky bar costs 6.6% of an 844px phone viewport permanently, and the
   persistent CTA is already the floating button. This scrolls away. */
.site-header {
  border-block-end: var(--hairline);
  background: var(--cream);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-block-size: 56px;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: 44px;
  color: var(--navy-900);
}
.brand__mark { flex: none; inline-size: 32px; block-size: 32px; }
.brand__name {
  font-size: var(--ls-3);
  font-weight: 700;
}
.site-header__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.lang-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 44px;
  min-inline-size: 44px;
  padding-block: var(--sp-2);
  padding-inline: var(--sp-3);
  border: 1px solid rgba(32, 54, 80, 0.25);
  border-radius: var(--radius);
  background: transparent;
  color: var(--navy-900);
  font-size: var(--ls-small);
  cursor: pointer;
}
.lang-toggle:hover { border-color: var(--navy-900); }


/* ── §1 Hero — asymmetric, not centred ─────────────────────────────────────
   Order: badge → H1 → sub → primary CTA → secondary CTA → visual, all set to
   the inline start. Centred hero text is the loudest template tell on the
   charter's list, so the asymmetry is the point rather than a side effect.
   Zero perspective, zero rotation, zero glow, zero gradient anywhere below. */
.hero-badge {
  display: inline-block;
  padding-block: var(--sp-1);
  padding-inline: var(--sp-3);
  border-radius: var(--radius);
  background: var(--teal-100);
  color: var(--teal-800);            /* 7.39:1 */
  font-size: var(--ls-small);
}
#hero h1 { margin-block-start: var(--sp-4); }
.hero-sub {
  margin-block-start: var(--sp-3);
  max-inline-size: 26rem;
  color: var(--navy-700);            /* 5.92:1 on cream */
  font-size: var(--ls-body-lg);
}

/* ── Buttons ───────────────────────────────────────────────────────────────
   Two variants and no bare `.btn` base: an unmatched class name renders in the
   UA grey buttonface and reads as disabled, so every button on this page names
   a class that exists. .btn-onteal is consumed by §6 and §9, whose ground IS
   teal-800 — the same pair inverted. */
.btn-primary,
.btn-onteal {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-block-size: 52px;
  max-inline-size: 26rem;
  margin-block-start: var(--sp-5);
  padding-block: var(--sp-2);
  padding-inline: var(--sp-5);
  border-radius: var(--radius);
  font-weight: 700;
  text-decoration: none;
}
.btn-primary { background: var(--wa-green); color: var(--navy-900); }   /* 6.21:1, the float's pair */
.btn-onteal  { background: var(--cream); color: var(--teal-800); }   /* 7.76:1 */
.btn-glyph { flex: none; }

/* A text link with a chevron, not a second outlined button — two stacked
   full-width buttons is the template shape. The chevron points DOWN the page,
   so it needs no mirroring: '<'/'>' class glyphs are Bidi_Mirrored and a
   scaleX(-1) on one double-mirrors. */
.hero-cta2 {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: 44px;
  margin-block-start: var(--sp-2);
  color: var(--teal-700);            /* 5.76:1 on cream */
  text-decoration: none;
}
.hero-cta2:hover { text-decoration: underline; }
.hero-cta2__chevron { flex: none; }

/* ── The hero visual (Q2, amended 2026-08-25) ──────────────────────────────
   ONE crop: the payslip's money block, straight on — no perspective, no glow.
   A second layer here was removed because at the width the composition gave it
   nothing on it could be read; do not add one back without checking it against
   the 300px rendered floor probe-layout asserts on .hero-slip.
   justify-content follows `direction`: flex-start under 900 lines the crop up
   with the H1, flex-end from 900 up hugs the column's outer edge. */
.hero-visual {
  margin-block-start: var(--sp-6);
  display: flex;
  justify-content: flex-start;
}
/* One of exactly TWO box-shadows on the whole page; the floating button is the
   other. Hairline border and --radius-lg, verbatim from the app's card. */
.hero-slip {
  inline-size: 100%;
  max-inline-size: 26rem;
  border: var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 32px rgba(32, 54, 80, 0.16);
}
/* PHONE ONLY: the same crop, ~13% shorter (2026-08-26) — it stood 472px of an
   844px viewport. Two parts, because the constraint is legibility not height;
   probe-layout holds this to a 300px rendered floor at 360px, which a
   scale-only cut in the 10-15% band breaks (328 x .88 = 289).
   1. aspect-ratio + cover drop 66 of the source's 924 rows, ALL dead space.
      DERIVATION, and it is the record — dark ink runs y=35..865 on the 700px
      asset (canvas row scan), leaving 35 above and 59 below; cover overflows by
      66/700 of the rendered width and 21/66 = 32% of that sits above, which is
      where object-position's 32% comes from. -8%.
   2. inline-size 94%. -6%.
   Net 472->413 at 390, 433->378 at 360; rendered width 308-336, above the floor.
   NOT whitespace below: the pale net-pay band (#edf2fe) runs to y=899, so the
   visible bottom edge deliberately cuts inside it — there is no spare room.
   A RECAPTURE INVALIDATES PART 1: re-run the row scan and re-derive both
   numbers, or drop them for a plain image at 94% (-6%, still legible).
   Desktop untouched: from 900px this fills a 26rem column. */
@media (max-width: 639px) {
  .hero-slip {
    inline-size: 94%;
    /* Auto margins absorb the 6% part 2 gives up. Without this the flex
       parent's flex-start spent all of it on one side and the crop read as a
       misaligned card. Why: DESIGN-2026-08-28-landing-hero-cta-yield.md. */
    margin-inline: auto;
    aspect-ratio: 700 / 858;
    object-fit: cover;
    object-position: 50% 32%;
  }
}
/* Desktop is two columns, and the split is set by what the H1 NEEDS rather than
   by a tidy fraction: at --ls-1's 44px cap the English headline wants ~26rem to
   stay inside three lines, and a 5fr/7fr split gave it 333px and four ragged
   ones. The visual column is then 26rem too at --measure, which is exactly what
   the crop's own cap asks for — so it fills its side instead of floating in it. */
@media (min-width: 900px) {
  #hero .wrap {
    display: grid;
    grid-template-columns: minmax(0, 26rem) minmax(0, 1fr);
    align-items: center;
    column-gap: var(--sp-6);
  }
  .hero-visual {
    margin-block-start: 0;
    justify-content: flex-end;
  }
}

/* ── Section rhythm ────────────────────────────────────────────────────────
   Deliberately uneven — that unevenness IS the hierarchy. §3 dominates and
   §7 steps down. §3 takes 64px on the phone rather than 96: 96 top and
   bottom is 192px of air around one section on a 360px viewport and reads as
   an unfinished page. 96 returns at >=900px where there is room for it. */
main > section { padding-block: var(--sp-7); }
#hero,
#how,
#offer { padding-block: var(--sp-8); }
#trust { padding-block: var(--sp-6); }
/* §1b is the hero's last line, not a subject of its own: no top padding, so the
   hero's own block padding is the whole gap above the flow. */
#highlights { padding-block-start: 0; padding-block-end: var(--sp-7); }
@media (min-width: 900px) { #how { padding-block: var(--sp-9); } }
/* The hero's own air, halved on the phone (2026-08-26): --sp-8 put the card's
   صافي المستحق للدفع band 27px under the fixed CTA bar at 390x844 — the figure
   the hero exists to show, absent at rest. --sp-6 lands the card at 783 against
   a bar starting at 788. English runs a taller H1 and still loses ~20px. */
@media (max-width: 639px) { #hero { padding-block: var(--sp-6); } }
/* Phone compaction (2026-08-30, owner's call: "more compact vertically").
   Every step of the rhythm above drops one token below 640px — the hierarchy
   is preserved because it is RELATIVE (§3 still 48 against §7's 24, and the
   --sp-9 desktop air is untouched). The probe's §3 phone floor moved 64 → 48
   with it. */
@media (max-width: 639px) {
  main > section { padding-block: var(--sp-6); }
  #how,
  #offer { padding-block: var(--sp-7); }
  #trust { padding-block: var(--sp-5); }
  #highlights { padding-block-end: var(--sp-6); }
  .pain-list { gap: var(--sp-3); }
  .spine { gap: var(--sp-6); margin-block-start: var(--sp-5); }
  .faq,
  .trust-roster,
  .trust-quotes { margin-block-start: var(--sp-4); }
}

/* Four grounds only, in this order down the page: cream → navy-950 (§2) →
   cream (§3–§5) → teal-800 (§6) → cream (§7–§9). The two inversions are the
   page's structural rhythm; do not add a third. */
#pain {
  background: var(--navy-950);
  color: var(--navy-100);
}
#offer {
  background: var(--teal-800);
  color: var(--cream);
}

/* ── Headings ──────────────────────────────────────────────────────────── */
#hero h1 {
  font-size: var(--ls-1);
  font-weight: 700;
  text-wrap: balance;
}
#how h2,
#closing h2,
.offer-l1 {
  font-size: var(--ls-2);
  font-weight: 700;
}
#video h2,
#trust h2,
#faq h2 {
  font-size: var(--ls-3);
  font-weight: 700;
}

/* §9 is the one centred block on the page — it earns it by being the last
   thing. Its block-end padding clears the floating button so that button can
   never cover this CTA. */
#closing {
  text-align: center;
  padding-block-end: var(--wa-clear);
}

/* ── §2 Pain strip — the tonal inversion ───────────────────────────────────
   Three lines on the page's only dark band (ground and ink set with the other
   section grounds above). No heading, no icon, no card, and nothing here should
   ever grow one: the moment it does it has become §5 and the rhythm break —
   half this section's job — is gone. The 2x20px rule at each line's inline
   start is a ::before rather than a fourth element, so the markup stays a bare
   list; --teal-300 is decorative and carries no text, which is why it may sit
   outside the contrast table. */
.pain-list {
  display: grid;
  gap: var(--sp-4);
}
.pain-line {
  position: relative;
  padding-inline-start: var(--sp-4);
  font-size: var(--ls-body-lg);
}
.pain-line::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  /* Centre the 20px rule on the FIRST line box, whose height differs by script
     (Arabic 1.85, Latin 1.6). The em fallback lands close enough everywhere;
     `lh` makes it exact and is simply dropped by engines that do not know it. */
  inset-block-start: 0.35em;
  inset-block-start: calc((1lh - 20px) / 2);
  inline-size: 2px;
  block-size: 20px;
  background: var(--teal-300);
}

/* ── §3 How it works — the spine ───────────────────────────────────────────
   The page's dominant section and its ONLY graphic motif: a 2px teal line
   running down the inline-start edge with a ringed numeral on it at each step.
   A spine and not three cards, because it literally draws the journey the copy
   describes and because a grid of identical icon cards is a charter defect.

   Dominance is carried by the heading step (--ls-2 against §5's --ls-3), the
   two screenshots and the total rendered height — NOT by air; the block
   padding that carries the rest of it is set in the section rhythm above.

   Step 1 carries NO image, and that is the shipped state, not a hole: the
   ZKTeco device photograph does not exist and Q3's ruling is that an honest gap
   beats a placeholder. Nothing below is conditional on a step having media —
   the spine and the numerals hold the rhythm across a step that ends after its
   one line, which is why this motif suits three deliberately unequal steps. */
.spine {
  display: grid;
  gap: var(--sp-7);
  margin-block-start: var(--sp-6);
}

/* --num-top centres the 28px numeral on the heading's FIRST line box, and the
   spine segment then starts from that same anchor + 14px (the circle's centre).
   Computed rather than eyeballed because the heading's line height is set per
   script, so a single hardcoded offset is wrong in one of the two languages. */
.step {
  --step-h-lh: 1.35;
  --num-top: calc((var(--ls-3) * var(--step-h-lh) - 28px) / 2);
  position: relative;
  padding-inline-start: 44px;
}
html[lang="ar"] .step { --step-h-lh: 1.6; }

/* The segment runs from THIS numeral's centre to the NEXT one's, so the spine
   terminates exactly at the last numeral instead of trailing past it down the
   payslip. block-size resolves against .step's own box, so `100% + the grid
   gap` lands precisely on the next step's --num-top + 14px. The last step has
   no segment, which is what makes the line stop where the journey does. */
.step:not(:last-child)::before {
  content: '';
  position: absolute;
  inset-inline-start: 13px;
  inset-block-start: calc(var(--num-top) + 14px);
  inline-size: 2px;
  block-size: calc(100% + var(--sp-7));
  background: var(--teal-700);
}
.step-num {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: var(--num-top);
  z-index: 1;                        /* sits ON the line, not under it */
  display: grid;
  place-items: center;
  inline-size: 28px;
  block-size: 28px;
  border: 2px solid var(--teal-700);
  border-radius: 50%;
  background: var(--cream);
  color: var(--teal-800);
  font-size: var(--ls-small);
  font-weight: 700;
  line-height: 1;
}
.step-h {
  font-size: var(--ls-3);
  font-weight: 700;
  line-height: var(--step-h-lh);
}
.step-p {
  margin-block-start: var(--sp-2);
  color: var(--navy-700);            /* 5.92:1 on cream */
  font-size: var(--ls-body);
}

/* The two screenshots are full-page phone captures at 2x — 780x1800 and
   780x2568 — so shown whole they are a screenful of scroll each. They are
   framed by a CSS aspect-ratio and cropped with object-fit instead, which
   costs ZERO bytes and leaves both at the quality floor of their byte budgets.
   The width/height ATTRIBUTES stay the true intrinsic pixels; this stylesheet
   decides the box and is loaded before layout, so CLS is still structurally
   zero. */
.step-media {
  margin-block-start: var(--sp-4);
  inline-size: 100%;
  border: var(--hairline);
  border-radius: var(--radius);
  object-fit: cover;
}
/* Each window is calibrated against REAL SOURCE PIXELS, never eyeballed —
   object-position is a fraction of the OVERFLOW, so a guessed percentage lands
   somewhere different in each image and cuts a row in half.
     step 2 — source y 760..1512 of 1800 (72.5%). step2-attendance-en.webp is
              captured to the SAME geometry, so one window serves both
              languages. It must keep all three flags (متأخر, خروج مفقود,
              مغادرة مبكرة): cropping past them deletes the evidence the
              section's copy promises and leaves a screenshot of some app.
     step 4 — source y 1468..2548 of 2568 (98.7%): pay lines through the print
              button, closing on the card's own bottom edge. Largest box of the
              three — it is the payoff the H1 promised.
   RE-DERIVE BOTH WHENEVER THE SHOTS ARE RECAPTURED. */
.step-media--attendance {
  aspect-ratio: 780 / 752;
  object-position: 50% 72.5%;
}
.step-media--slip {
  aspect-ratio: 780 / 1080;
  object-position: 50% 98.7%;
}
/* The Add Punch dialog is shown WHOLE — it is one screen, not a crop of one —
   so the box carries the asset's own ratio and object-fit has nothing to cut. */
.step-media--punch { aspect-ratio: 742 / 1124; }

/* Desktop: one step per ROW, text at the inline start and its screenshot at
   the inline end. Three across gave each 780px screenshot ~200px of column, so
   the rows and the payslip lines fell to about 5px — the section whose whole
   argument is "look at the real thing" had nothing to look at — and left step
   1, which ships with no image by design, holding an empty cream column the
   height of its neighbours, which reads as a failed image load.

   Stacking answers both, and the spine STAYS VERTICAL so none of the phone
   rules above is re-stated with the axes swapped: same segment maths, same
   --sp-7 gap its calc() depends on. A step with no media is a short row.
   The media track is a FIXED 22rem so the three text columns line up, and the
   steps stay unequal by HEIGHT: step 4's payslip is the tallest box. */
@media (min-width: 900px) {
  .step {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 22rem;
    /* auto 1fr, not two autos: a row-spanning item is skipped when an auto
       track is sized against a FLEXIBLE sibling track, so row 1 is exactly the
       heading and the body line sits under it. Two auto rows split the tall
       screenshot's height between them and left the body line floating in the
       middle of the step. */
    grid-template-rows: auto 1fr;
    column-gap: var(--sp-6);
    align-items: start;
  }
  /* .step-num is absolutely positioned, so it is not a grid item and keeps
     sitting on the spine. */
  .step-h,
  .step-p { grid-column: 1; }
  .step-media {
    grid-column: 2;
    grid-row: 1 / span 2;
    margin-block-start: 0;
  }
}

/* ── §4 Video — click-to-play, never autoplay ──────────────────────────────
   The frame carries aspect-ratio, so the block reserves its exact height before
   the poster decodes and §4 contributes zero CLS.

   It is a one-cell GRID, not an absolutely positioned stack: centring an
   absolute child needs inset-inline-start: 50% plus a PHYSICAL translateX(-50%),
   which moves the same way in both directions and lands the button off-centre
   in RTL; place-items: center is direction-agnostic. `overflow: hidden` here
   clips a 14px radius, not a scroll, so it does not repeat the html/body trap
   above. */
.video-frame {
  position: relative;
  display: grid;
  place-items: center;
  margin-block-start: var(--sp-5);
  aspect-ratio: 16 / 9;
  border: var(--hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--navy-950);
}
/* Both the poster and the <video> landing.js swaps in take the single cell. */
.video-poster,
.video-frame video {
  grid-area: 1 / 1;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}
/* 64px circle, --cream glyph on --navy-900 (10.75:1). A real button, so it is
   already a 64x64 tap target and takes the page's focus ring unchanged. */
.video-play {
  grid-area: 1 / 1;
  z-index: 1;
  display: grid;
  place-items: center;
  inline-size: 64px;
  block-size: 64px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--navy-900);
  color: var(--cream);
  cursor: pointer;
}
/* No mirroring. The triangle points inline-end in LTR and would read as
   "rewind" if flipped for RTL — transport controls are a universal
   non-mirrored idiom, and the native <video controls> bar does not mirror
   either. Hence data-probe-mirror="skip" on the glyph rather than a
   scaleX(-1) here. */
.video-duration {
  position: absolute;
  inset-block-end: var(--sp-3);
  inset-inline-end: var(--sp-3);
  padding-block: var(--sp-1);
  padding-inline: var(--sp-2);
  border-radius: var(--radius);
  background: var(--navy-950);
  color: var(--cream);
  font-size: var(--ls-small);
}
.video-caption {
  margin-block-start: var(--sp-3);
  color: var(--navy-700);            /* 5.92:1 on cream */
  font-size: var(--ls-body);
}

/* ── §7 Trust ──────────────────────────────────────────────────────────────
   The page's LOW-rank section (--sp-6 block padding, --ls-3 heading, no icons)
   and §8, sharing one rhythm: every sub-block starts --sp-5 below its
   heading. §5 shared it until it was removed on 2026-08-30. */
.faq,
.trust-roster,
.trust-quotes { margin-block-start: var(--sp-5); }
/* The one exception, on hierarchy rather than rhythm: §7's stat line is not a
   sub-block, it is the heading's second line. At --sp-5 the two read as
   unrelated statements with the proof floating between them. */
.trust-stats { margin-block-start: var(--sp-2); }

/* ── §1b The flow — the punch-to-payslip chain ─────────────────────────────
   Six stages, an arrow between each, one filled tile at the end: the H1 made
   literal (2026-08-27). Reasoning and every number: DESIGN-2026-08-27-landing-flow.md.
   Three things must not be undone:
     1. It may never read as §3's spine, which also runs vertically on a phone:
        that is a ringed CIRCLE with a numeral on a CONTINUOUS rail.
     2. The arrow is a 2px shaft (::before) + a border-triangle head (::after),
        not a glyph and not an SVG. A coloured `border-inline-start` against
        transparent `border-block` aims the apex at the INLINE-END edge, so the
        writing mode mirrors it and there is no flip to get backwards.
     3. Per-row icons are a charter AMENDMENT enforced in probe-layout.js: the
        ban is on a REPEATED marker, and these six differ. */
.flow {
  display: grid;
  row-gap: var(--sp-4);
}
.flow-stage {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.flow-mark {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 36px;
  block-size: 36px;
  border-radius: var(--radius);
  background: var(--teal-100);
  color: var(--teal-800);
}
.flow-label {
  color: var(--navy-900);
  font-size: var(--ls-body);
  /* 200 % zoom lays a 360px phone out in 180 CSS px, where the label column is
     ~100px and `automatically` is wider than that. Without this the word pushes
     <body> past its own clip box and the reflow gate fails. */
  overflow-wrap: break-word;
}
/* The destination. One filled tile and one heavier label is the whole of the
   "you arrive here" signal — cheaper than a seventh mark and it cannot be
   mistaken for a sixth ordinary stage. */
.flow-stage--end .flow-mark {
  background: var(--teal-700);
  color: var(--cream);
}
.flow-stage--end .flow-label {
  color: var(--navy-950);
  font-weight: 700;
}
/* A 12px arrow — 5px shaft + 7px head — centred on the 36px tile (centre 18)
   and in the 16px row gap, 2px clear each end. Was a bare 8x5 --teal-300 caret
   and invisible on a phone: --teal-300 on cream is 1.54:1, which no size could
   rescue; --teal-700 is 5.76:1. `100%`, not a fixed offset: a wrapped label
   makes the row taller than the tile. */
.flow-stage:not(:last-child)::before,
.flow-stage:not(:last-child)::after {
  content: '';
  position: absolute;
}
.flow-stage:not(:last-child)::before {
  inset-inline-start: 17px;
  inset-block-start: calc(100% + 2px);
  inline-size: 2px;
  block-size: 5px;
  background: var(--teal-700);
}
.flow-stage:not(:last-child)::after {
  inset-inline-start: 12px;
  inset-block-start: calc(100% + 7px);
  inline-size: 0;
  block-size: 0;
  border-inline: 6px solid transparent;
  border-block-start: 7px solid var(--teal-700);
}
/* Desktop: the chain turns and the whole pipeline is one glance. Six equal
   columns, the tile above its label, and the caret moves into the column gap at
   the tile's own centre line (18px − half the caret's 8px) so it reads as
   pointing FROM one tile TO the next rather than floating between two labels.
   -14.5px = half the --sp-5 gap plus half the caret's 5px width. */
@media (min-width: 900px) {
  .flow {
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    column-gap: var(--sp-5);
    align-items: start;
  }
  .flow-stage {
    display: block;
  }
  .flow-mark { margin-block-end: var(--sp-3); }
  /* Same arrow, turned — and anchored to the TILE, not the column (2026-08-30).
     The stage is a full 1fr column with a 36px tile at its inline-start, so an
     arrow parked at the column's far edge sat beside the NEXT tile and read as
     attached to it. Now a SHORT arrow (21px shaft + 7px head) is centred in
     the space between this tile's end (36px) and the next tile's start
     (100% + the --sp-5 gap): the midpoint is 50% + 30px, so the shaft starts
     14px before it and the head ends 14px after. A tile-to-tile shaft was
     tried first (2026-08-30) and read as a ruler, not a pointer. */
  .flow-stage:not(:last-child)::before {
    inset-inline-start: calc(50% + 16px);
    inset-block-start: 17px;
    inline-size: 21px;
    block-size: 2px;
  }
  .flow-stage:not(:last-child)::after {
    inset-inline-start: calc(50% + 37px);
    inset-inline-end: auto;
    inset-block-start: 12px;
    border-inline: 0;
    border-block: 6px solid transparent;
    border-inline-start: 7px solid var(--teal-700);
  }
}

/* §7 roster. Why, at length: DESIGN-2026-08-27-landing-customer-roster.md.
   Rendered only from cfg.trust, so nothing here can paint anything invented.

   ONE TILE, ONE THING (2026-08-28): the mark, or — for the one customer with
   no mark — the name in the page's own type, never both.

   The CONTAIN box is 140x64, not 64x64, and the extra width is measured: in a
   SQUARE box the tall Arabic lockups are height-bound at 64px while the wide
   Latin ones are width-bound at 31-35px TALL, so half the roster rendered at
   half the weight of the other half. At 140x64 all six are height-bound (the
   widest, DMT at 2.03:1, needs 130px), so they share one cap height and differ
   only in width — the difference between two lockups rather than an accident
   of the frame. The box predates the image, so CLS stays zero.

   A RAIL below 900px, a centred wrapping WALL above it, one flex container: the
   media query changes only wrapping, gaps and tile width. 900px is the seam
   .benefits already uses. Four rules keep the rail from being
   the template it resembles, all four load-bearing: it never ANIMATES (the
   auto-scrolling strip is the tell, not horizontal scrolling), its items are
   NOT CARDS (no border, fill or radius — that shape is this page's one
   interactive affordance), it scrolls in pure CSS with NO SCRIPT (RTL flips
   the scroll origin and scrollLeft disagrees across engines), and its
   scrollbar is HIDDEN, which is why .trust-swipe exists to say what it took
   away. The next tile still peeks; that is the cue, the words only name it. */
.trust-roster {
  display: flex;
  gap: var(--sp-5);
  overflow-x: auto;
  scroll-snap-type: inline mandatory;
  overscroll-behavior-inline: contain;
  scrollbar-width: none;
}
.trust-roster::-webkit-scrollbar { display: none; }
.trust-customer {
  flex: none;
  /* 120 on the rail: two tiles plus 70px of the third fit a 358px phone. */
  inline-size: 120px;
  min-block-size: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  scroll-snap-align: start;
}
@media (min-width: 900px) {
  .trust-roster {
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-7);
    overflow-x: visible;
    scroll-snap-type: none;
  }
  /* 848px of content, 140px tiles, 48px gaps: five tiles need 892px and four
     need 704, so the seven wrap 4 + 3 and both rows centre. Nothing here
     assumes the count — an eighth customer re-balances to 4 + 4 on its own. */
  .trust-customer { inline-size: 140px; }
  .trust-swipe { display: none; }
}
.trust-customer__mark {
  inline-size: 100%;
  block-size: 64px;
  object-fit: contain;
}
.trust-customer__name {
  color: var(--navy-900);
  font-size: var(--ls-body);
  font-weight: 700;
}
/* Inline END: the direction it asks for and the edge the next tile is cut at.
   The one line on this page that is not start-aligned — it points, not reads. */
.trust-swipe {
  margin-block-start: var(--sp-3);
  color: var(--navy-700);
  font-size: var(--ls-small);
  text-align: end;
}
.trust-quotes {
  display: grid;
  gap: var(--sp-4);
}
.trust-quote {
  padding-block-start: var(--sp-4);
  border-block-start: var(--hairline);
}
.trust-quote__by {
  margin-block-start: var(--sp-2);
  color: var(--navy-700);
  font-size: var(--ls-small);
}
/* A sentence, not a KPI, so it takes the quiet lead treatment (--navy-700,
   5.92:1) rather than the bold it wore as a bare "+395": bold under a bold
   heading was two headlines stacked. */
.trust-stats {
  color: var(--navy-700);
  font-size: var(--ls-body-lg);
}

/* ── §6 offer · §8 FAQ · §9 closing ────────────────────────────────────────
   §6's h2 is a one-word LABEL, not the big type: the design system gives this
   section one --ls-2 element and the binding promise (.offer-l1) is what earns
   it. --teal-200 on --teal-800 is 6.31:1, quiet but never disabled-looking, and
   it is the same tone the small print takes. */
#offer h2 {
  font-size: var(--ls-small);
  font-weight: 700;
  color: var(--teal-200);
}
.offer-l1 { margin-block-start: var(--sp-3); }
.offer-l2,
.offer-l3 {
  margin-block-start: var(--sp-4);
  font-size: var(--ls-body-lg);
}

/* An OPEN definition list: no <details>, no chevrons, no JS. Seven identical
   disclosure rows is the rhythm the charter bans, and open is scannable in one
   phone scroll. The hairline goes on dd + dt, so it separates pairs and never
   floats above the first question. */
.faq dt { font-weight: 600; }
.faq dd {
  margin-block-start: var(--sp-2);
  color: var(--navy-700);            /* 5.92:1 on cream */
}
.faq dd + dt {
  margin-block-start: var(--sp-5);
  padding-block-start: var(--sp-5);
  border-block-start: var(--hairline);
}

/* #closing is centred by the section rhythm block above; the CTA keeps its
   26rem cap, so it needs the auto margins to sit on the centre line. */
#closing .btn-primary { margin-inline: auto; }

/* ── Footer ────────────────────────────────────────────────────────────── */
.site-footer {
  border-block-start: var(--hairline);
  padding-block: var(--sp-4);
  /* Same clearance rule as #closing, for the same reason: at the terminal
     scroll position the fixed button sat on the © line in Arabic and on the
     privacy LINK in English, i.e. a dead tap target at the page bottom. */
  padding-block-end: var(--wa-clear);
  font-size: var(--ls-small);
  color: var(--navy-700);
}
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
}
.site-footer a {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  min-inline-size: 44px;
  color: var(--teal-700);
  text-decoration: none;
}
.site-footer a:hover { text-decoration: underline; }

/* ── Floating WhatsApp button — all viewports ──────────────────────────────
   Logical insets, so it sits bottom-END: left in RTL, right in LTR, verified
   by probe-layout.js's mirroring check. No pulse, no bounce, no entrance
   animation; one of exactly two box-shadows on the page. max-inline-size keeps
   the long Arabic label from widening the document at 360px. */
.wa-float {
  position: fixed;
  inset-block-end: calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  inset-inline-end: var(--sp-4);
  z-index: 20;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: var(--wa-float-h);
  max-inline-size: calc(100% - var(--sp-8));
  padding-block: var(--sp-2);
  padding-inline: var(--sp-4);
  border-radius: var(--radius-lg);
  background: var(--wa-green);
  color: var(--navy-900);
  font-size: var(--ls-body);
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 6px 20px rgba(20, 37, 58, 0.22);
}
.wa-float__glyph { flex: none; }

/* PHONE: the pill becomes a full-bleed bottom bar (2026-08-26). Fixed means it
   passes over scrolling content whatever shape it is; what changes is what the
   overlap MEANS. A rounded pill in the corner read as something come loose and
   covering text by accident, and at rest it sat on the hero payslip's closing
   figure. Edge to edge, square, opaque, it is the viewport's own bottom chrome
   — and identical in both directions, which the corner pill was not. No
   reveal-on-scroll: it must not need JS to exist. --wa-clear is redefined in
   the SAME block so the bar's height and the room left for it cannot drift.
   Rationale: DESIGN-2026-08-26-landing-mobile-pass.md. */
@media (max-width: 639px) {
  :root {
    /* --sp-6, not --sp-5: at 200% zoom the bar's label WRAPS and the bar grows
       to ~84px against a 56px --wa-bar-h, which clipped the last footer line.
       The extra 8px absorbs that without moving anything at 100%. */
    --wa-clear: calc(var(--wa-bar-h) + var(--sp-6) + env(safe-area-inset-bottom, 0px));
  }
  .wa-float {
    /* position: fixed blockifies inline-flex, so inset-inline: 0 stretches it
       edge to edge rather than shrink-wrapping the label. */
    inset-block-end: 0;
    inset-inline: 0;
    justify-content: center;
    min-block-size: var(--wa-bar-h);
    max-inline-size: none;
    padding-block: var(--sp-3);
    padding-block-end: calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
    border-radius: 0;
    border-block-start: 1px solid rgba(20, 37, 58, 0.22);
    box-shadow: 0 -6px 20px rgba(20, 37, 58, 0.16);
  }
  /* Set by landing.js while the hero's own CTA is on screen, so the first
     screen shows the offer once instead of twice. Phone only.
     NO transform, deliberately: probe-layout's [cta]/[closing] checks measure
     this element's RECT at page top, where it is yielded — opacity+visibility
     leave the box in place, a translate would make them assert nothing.
     visibility also drops it from the tab order.
     Why: DESIGN-2026-08-28-landing-hero-cta-yield.md. */
  .wa-float.is-yielded { opacity: 0; visibility: hidden; }
}

/* ── Reduced motion ────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
