/* =============================================================================
   Sdeira Design System - tokens
   -----------------------------------------------------------------------------
   Single source of truth for every colour, surface, radius, duration and scale
   step used across the Sdeira sites. Brand values are transcribed from
   "Sdeira Group - Guidelines V4.0.pdf" and corrected against V9 (July 2026).

   LIWA LEADS. V9 p28 publishes the proportions: Light Gray #d4d4d4 at 50%,
   Liwa #910811 at 30% and Grey 70 #6d6e71 at 20% are the PRIMARY palette;
   Black, Al Khatim, Razeen, Sweihan and Jais are secondary. So Liwa carries
   emphasis (headings, kickers, figures, links, icons) and action (primary
   fills, the CTA band) alike, and the page ground is White or #d4d4d4 - two
   canvases, never a third (V9 p71). The file used to say the opposite:
   Sweihan was --accent and --surface-brand, and the page floor was a warm
   tint derived from Razeen. Every value below that changed is traceable to
   V9, to GLMA's Website Brand Compliance Review of 20 Aug 2026, or to the
   signed-off prototype docs/prototypes/a-liwa/ - nothing here is invented.

   HOW TO USE
   - Link this file in <head> BEFORE the page's own <style> block.
   - Do not redeclare any of these custom properties inside a page. A page-level
     :root would win on source order and silently detach that page from the
     system.
   - To change a brand value, change it here once. Every page and the reference
     page at docs/sdeira-design-system.html read from this same file, so they
     cannot drift apart.

   THE TWO CANVASES. There are exactly two page grounds and every number below
   is measured against both: White #FFFFFF and Light Gray #d4d4d4. A third
   ground is not a tuning decision, it is a brand decision (V9 p71).

   CONTRAST RULES THIS PALETTE FORCES (each number measured, not estimated)
   1. Liwa is BOTH the emphasis text colour and the action fill, and one value
      serves both because it is the strongest pairing in the palette: 9.35 on
      White, 6.30 on #d4d4d4, and 9.35 again for white text sitting ON it.
      Nothing is darkened to make it work - there is no second Liwa, and
      deriving one would be a ninth colour.
   2. Razeen, Al Khatim and Sweihan are fill-only on light surfaces. Razeen
      scores 1.98 on White, Al Khatim 2.38, Sweihan 3.35. Nothing is ever
      written in them there, and none of them carries a functional role: V9
      p28 gives each 15% as a secondary.
   3. On dark surfaces the logic inverts: Razeen scores 5.22 on Black, so the
      warm tone becomes a legitimate text colour exactly where it was forbidden
      on light. Liwa scores 1.1 on Black and disappears, which is why the
      on-dark accent stays Razeen. V9 is silent on the accent inverting on a
      dark ground - this is a technical necessity, recorded as a question for
      the identity owner rather than presented as compliance.
   4. Body copy on White is Jais, not Black (V9 p71's Contrast Rule, stated for
      the White canvas). Jais measures 6.98 on White and 4.71 on #d4d4d4, so it
      clears AA on both canvases. Headings stay on Black (10.31 / 6.96); p71
      governs body copy.
   5. Grey 70 clears 5.10 on White but only 3.44 on #d4d4d4. It keeps its 20%
      primary role and its own token, and on the grey canvas it is LARGE TEXT
      ONLY - a figure or a marker, never body-size copy. --muted uses Jais for
      that reason and clears both canvases.

   TYPE RULES
   1. Every font-size on the site comes from one of the ten roles below. No
      literal, no local value, no exceptions. A component that needs a size the
      scale does not have is a change to the scale, made here.
   2. NOTHING RENDERS BELOW --type-caption (.875rem). The scale is in rem, so
      that is the reader's setting times .875 - 14px on a default browser, 13.1
      on one set to 15. A role that cannot survive being scaled by the reader
      is the wrong role.
   3. Every text node clears AA against its own ground: 4.5 normal, 3.0 large.
      Large means 24px here and only 24px. That used to be forced: one
      licensed face plus font-synthesis: none meant nothing rendered heavier.
      Since the licensed Bold arrived (18 Aug 2026) text really does render
      heavier, so the 18.66px bold allowance is now technically available -
      but every measurement in this file was computed against the 24px floor
      and none has been recomputed. 24px stays the working floor until they
      are.
   4. AAA (7.0) is met where the palette can meet it and is NOT a site-wide
      target. Liwa reaches it on White (9.35) and does not on #d4d4d4 (6.30);
      --muted reaches it on White (6.98, at the line) and does not on #d4d4d4
      (4.71). Reaching it on both canvases everywhere would mean no secondary
      grey and no brand accent in text, which is a different design, not a fix.
   5. Text over photography is NOT scored by ratio. It is governed by the scrim
      rule in section 11 of this file and is reviewed separately rather than
      guessed from a background it cannot resolve.

   OPEN ITEM: the brand guide contradicts itself on Razeen. The printed HEX is
   #D0B176 while the printed RGB (221, 177, 118) resolves to #DDB176. The RGB
   value is used below because RGB is the more reliable source in identity
   files, and every contrast figure here was computed from it. One line changes
   if the brand owner rules the other way.
   ========================================================================== */

/* =============================================================================
   TYPEFACE
   -----------------------------------------------------------------------------
   DIN Next LT Arabic is the identity typeface (brand guide, pages 30 to 33).
   The guide specifies four weights: Light, Regular, Medium, Bold. ALL FOUR ARE
   NOW PRESENT - supplied by the client on 18 Aug 2026 in the official brand
   pack ("Website Project with Digital Next - Marketing files", Sdeira/Fonts).
   The Regular file in that pack is byte-identical to the one already here,
   which is how we know it is the same source.

   WHY A BLOCK PER WEIGHT
   The pages request 400, 500, 600, 700, 800 and 900. A weight with no face
   declared is SYNTHESISED by the browser, which thickens the outlines
   algorithmically. On a joined Arabic script that is not an acceptable
   fallback: it thickens strokes unevenly, distorts the joins between letters,
   and fattens the dots until they merge with the letterforms.

   Every slot the pages ask for therefore names a real file:

     300 -> Light
     400 -> Regular
     500 -> Medium
     600 -> Medium     (Medium is the licensed weight nearest 600)
     700 -> Bold
     800 -> Bold       (Bold is the heaviest licensed weight)
     900 -> Bold       (900 was being synthesised before these files arrived)

   Until 18 Aug 2026 every one of these blocks pointed at Regular, so the whole
   site rendered at one weight. It no longer does: headings, buttons and labels
   now carry the Medium and Bold outlines the guide specifies. That is a
   visible, site-wide change, and it is the change this comment has been
   describing as pending since the file was written.

   FORMAT NOTE
   These are TTFs, as delivered. The same outlines as WOFF2 run roughly 60 to
   70 percent smaller on the render-blocking path. Worth doing before launch;
   it needs fontTools, which is not installed here.

   LICENCE NOTE
   DIN Next LT Arabic is a commercial Monotype family. Self-hosting it for the
   web requires a webfont licence separate from a desktop licence. The file was
   supplied by the client, so the licence is theirs to confirm before launch.
   ========================================================================== */

@font-face {
  font-family: "DIN Next LT Arabic";
  src: url("../fonts/din-next-lt-arabic-light.ttf?v=c0dbf35118ff693e") format("truetype");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DIN Next LT Arabic";
  src: url("../fonts/din-next-lt-arabic-regular.ttf?v=a96ab4c40a249f58") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DIN Next LT Arabic";
  src: url("../fonts/din-next-lt-arabic-medium.ttf?v=ce169ecc0ffb9a9e") format("truetype");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DIN Next LT Arabic";
  src: url("../fonts/din-next-lt-arabic-medium.ttf?v=ce169ecc0ffb9a9e") format("truetype");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DIN Next LT Arabic";
  src: url("../fonts/din-next-lt-arabic-bold.ttf?v=59ea6825837e8042") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DIN Next LT Arabic";
  src: url("../fonts/din-next-lt-arabic-bold.ttf?v=59ea6825837e8042") format("truetype");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DIN Next LT Arabic";
  src: url("../fonts/din-next-lt-arabic-bold.ttf?v=59ea6825837e8042") format("truetype");
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ===== 0. TYPE =====
     One family. The guide names DIN Next LT Arabic for both Arabic and Latin,
     and the file carries both scripts, so there is no second family to switch
     to mid-sentence. Tajawal and Cairo remain only as loading fallbacks. */
  --font-brand: "DIN Next LT Arabic", "Tajawal", "Cairo", system-ui, sans-serif;
  /* Kept as a separate token so headings can be moved to a display cut later
     without touching every heading rule. Same family for now. */
  --font-display: "DIN Next LT Arabic", "Cairo", "Tajawal", system-ui, sans-serif;

  /* ===== THE TYPE SCALE =====
     ELEVEN ROLES, NINE DISTINCT SIZES, ONE PLACE. This is the whole scale;
     nothing outside it may be authored. scripts/check_type.py fails the build
     on any font-size literal, and every exception carries a `scale-exempt:`
     comment saying what it is and why no role can hold it. There are four, all
     wordmark lockups and one status numeral.

     MEASURED at a 15px root - multiply by 16/15 for a default browser:

       role      360px   1180px   1600px   step*   what it is
       caption    13.1     13.1     13.1     -      fine print, the floor
       meta       13.8     13.8     13.8    1.05    kickers, dates, categories
       control    13.8     13.8     13.8    1.00    nav links, buttons in chrome
       action     14.3     14.3     14.3    1.04    page buttons, inline actions
       body         15       15       15    1.05    running copy
       lead       15.8     16.5       18    1.20    section intros
       label      16.9     16.9     20.6    1.14    supporting headings
       card       22.5     26.5     33.8    1.64    every repeated-card title
       section    37.5     50.6     68.7    2.03    every H2
       display    41.3     58.8       75    1.09    the page H1, once per page
       figure       30       40     54.3     -      numerals: years, stats, steps

     * step is each role against the one above it at 1600.

     IT IS A ROLE SCALE, NOT A MODULAR ONE, and saying so is the point. A 1.25
     ladder would be prettier and wrong for this site: the bottom four rungs sit
     inside 2.2px on purpose, because a nav link, a button label and a date are
     three different jobs at the same reading distance and a visible step
     between them reads as inconsistency rather than as hierarchy. The
     separation there is colour and weight. The steps only open up once the
     element stops being a control and starts being content - lead 1.20, card
     1.64, section 2.03 - which is where a reader actually needs the jump.

     control and meta resolve to the same .92rem and stay two names. They are
     the same size for the same reason, not by coincidence, and either could
     move without the other.

     FLUID, AND THE CROSSOVER IS SHARED. The six roles that scale use
     clamp(min, vw, max) with the vw term crossing between about 1100 and 1200,
     so the whole upper half of the scale reaches its cap together rather than
     at six different widths. The five UI roles are fixed on purpose: a control
     that changes size with the viewport changes the size of the box around it.

     MIN AND MAX. The floor is --type-caption, .875rem, and NOTHING renders
     below it - rule 2 of the TYPE RULES above. The ceiling is --type-display at
     5rem. Both are in rem, so both move with the reader's own setting; a role
     that cannot survive being scaled by the reader is the wrong role.

     WHERE THE VALUES CAME FROM, since it is not a formula: each was the
     measured dominant of what the pre-system homepage already rendered for
     that job, so adopting a role snapped 300-odd local declarations onto the
     scale without resizing the page. Three have since moved for reasons
     recorded at each: control and meta up to .92rem and caption to .875rem
     (the floor), after the site owner read 12px navigation off the screen. */

  /* Canonical responsive type roles. Use the role, not a local font-size:
     display is reserved for the page H1, section is every H2, card is every
     repeated-card title, and label is supporting heading metadata. */
  --type-display: clamp(2.75rem, 5vw, 5rem);
  --type-section: clamp(2.5rem, 4.3vw, 5rem);
  --type-card: var(--type-label);
  --type-label: clamp(1.125rem, 1.4vw, 1.375rem);
  /* 1.08 until 11 Aug 2026. It was the measured dominant of the pre-system
     homepage, tuned for a single-line hero title - the moment a --type-display
     heading wraps to two lines (a two-line Arabic hero H1, the common case
     off the homepage), the ascenders/descenders and diacritics of one line sit
     almost on top of the next. Site owner instruction, given against
     business/land-lease's wrapped H1: raise it, as the token, not a local
     override on that one page - it is --type-display's own leading, so every
     H1 that wraps anywhere on the site was carrying the same defect. */
  --leading-display: 1.15;
  --leading-section: 1.12;
  --leading-card: 1.2;
  --leading-label: 1.35;

  /* FIFTH HEADING ROLE, ADDED 2026-08-12. The four roles above left a real gap:
     --type-card/--type-label tops out at 1.375rem (a repeated-card title) and
     --type-section starts at 2.5rem (a full H2) - nothing between them. That
     gap was where the homepage's sector hover-preview panel
     (.sector-hover-copy h3, a single-item spotlight headline shown above the
     "Business" sector strip on hover) had been living as an unrolled literal,
     clamp(1.7rem, 2.7vw, 2.6rem), since before the type-role system existed.
     Site owner asked for that headline bigger; --type-section would have
     nearly doubled it at the low end and nearly tripled it at the high end -
     correct for a page H2, wrong for a compact 5-across preview panel - so
     per the token-file-first rule this is a new role, not a snap onto
     --type-section. Values are a deliberate ~15-18% raise over the old
     literal (1.7->2rem, 2.6->3rem), not a measured drift transcription: this
     role never existed, so there was nothing to measure. Use it for a
     single-item feature/spotlight headline; a REPEATED card keeps
     --type-card. */
  --type-feature: clamp(2rem, 3vw, 3rem);
  --leading-feature: 1.15;

  /* SIXTH HEADING ROLE, ADDED 2026-08-13. --type-feature closed the gap above
     for a spotlight headline, but at its low end (2rem) it reads as
     essentially --type-section's own low end (2.5rem) - correct for a
     hover-preview panel's single headline, wrong for a short caption
     beside its own dedicated media (a name label beside a portrait figure),
     which needs one clear step above --type-card/--type-label (tops at
     1.375rem) without borrowing a page-heading size. Use it for that case
     only - a caption headline that names the one thing its own figure shows.
     A repeated card keeps --type-card; a spotlight/hover headline keeps
     --type-feature; a page H2 keeps --type-section. */
  --type-headline: clamp(1.375rem, 1.8vw, 1.75rem);
  --leading-headline: 1.2;

  /* ----- Non-heading text roles -----
     The four roles above cover headings. Everything else on a page - running
     copy, a section intro, a button label, a kicker, fine print - had no role
     at all, so it was expressed as a local font-size: 303 declarations on the
     homepage alone. The values below are not invented. Each one is the measured
     dominant of what the homepage already renders for that job, so adopting a
     role snaps the drift to the scale instead of resizing the page.

     WHAT WAS ACTUALLY THERE, per role, measured at a 1345px viewport:
       lead     16.14 to 20.85px across 13 selectors (1.04rem to a 1.42rem cap)
       body     14.40 to 16.64px across 12 selectors (.9 / .94 / .95 / .96 /
                .98 / 1 / 1.04rem: the same intent written seven ways)
       action   14.08 to 16.82px (.88 / .92 / .95 / 1rem and a 1.18rem cap)
       control  12.80 to 13.76px (.8 / .82rem and a .86-.95rem clamp)
       meta     12.48 to 13.76px (.78 / .8 / .82 / .84 / .85 / .86rem)
       caption  12.48 to 13.60px (.78 / .85rem)
     Every one of those declarations was unconditional, with no @media around
     it, which is what identifies the spread as drift rather than a deliberate
     responsive fit. A size that a narrow viewport genuinely needed would have
     been inside a media query.

     WHY SIX ROLES AND NOT FIVE
     action and control are separate because the page genuinely has two tiers,
     not because a sixth name was wanted. Page-content buttons and inline
     action links cluster near .95rem; compact chrome (nav links, the header
     CTA, the action label inside a dense sector tile) clusters near .82rem.
     Collapsing both onto one value would have grown the header controls by 16
     to 25 percent and put the nav at risk of wrapping. control pairs with the
     control sizing roles in section 9b. */
  --type-lead: clamp(1.05rem, 1.4vw, 1.2rem);
  --type-body: 1rem;
  --type-action: .95rem;
  /* RAISED FROM .82rem ON 2026-08-09, and the reason the old value existed is
     the reason it could move. .82 was never chosen: it is the measured
     dominant of what the pre-system homepage already rendered for compact
     chrome (the 12.80-13.76px spread listed above), transcribed so that
     adopting the role would not resize the page. That was the right call for
     the migration and it is not an argument for the value.

     What it renders is the problem. rem is the reader's own setting, so a
     browser whose default is 15px - not unusual, and correct to respect -
     put the site's PRIMARY NAVIGATION at 12.3px. The site owner read it off
     the screen and asked whether 12 could possibly be right.

     THE OLD COMMENT'S OBJECTION IS TESTED, NOT ASSUMED. It says collapsing
     control onto action would grow the header controls 16-25% and "put the
     nav at risk of wrapping". Measured on the live bar at 1281/1400/1600 in
     both languages: .95rem begins to bind on English at 1600, 1rem overflows
     by 4-19px, and .92rem clears every width in both languages with room to
     spare. So the objection is real and it lands between .92 and .95, not at
     .92. English is the binding case - its nav labels are the longest - and
     the drawer breakpoint moved to 1280, which is where the headroom came
     from.

     Still a separate role from action, for the reason the block below gives.
     The two tiers remain; the gap between them is now .03rem rather than
     .13rem. */
  --type-control: .92rem;
  /* NOTE ON THE RENAME: --type-meta used to mean "h5 heading size" (1rem) and
     --type-caption "h6" (.875rem). Neither was referenced anywhere, in any page
     or in the reference doc, so both are repurposed here for the body-side jobs
     they name. --type-meta now covers kickers, eyebrows, category labels and
     meta rows: the same grouping the --label colour role already uses further
     down this file, so size and colour describe one thing. */
  /* THE SCALE HAS A FLOOR NOW, AND IT IS .875rem. Both of these were set the
     same way --type-control was - transcribed from measured drift on the
     pre-system homepage - and both landed under it: .82rem renders 12.3px and
     .78rem renders 11.7px on a browser whose default is 15px, which is not an
     unusual setting and is one the site must respect. A kicker, a category
     label, a news date and the footer copyright are all visitor-facing text,
     not decoration, and none of them had a reason to be the smallest thing on
     the page.

     meta joins control at .92rem because they are one tier: the labels in the
     bar and the labels on a card do the same job at the same distance. caption
     is fine print and stays one step below, at the floor itself. Nothing on
     this site may now declare a text size under .875rem - see the RULES block
     at the top of this file. */
  --type-meta: .92rem;
  --type-caption: .875rem;

  /* Leading is set for the two running-copy roles only. Buttons, kickers and
     meta rows carry leading that is tuned to the shape of the control they sit
     in, and overriding that from a text role would tighten the footer link list
     and the button labels for no typographic gain. */
  --leading-lead: 1.8;
  --leading-body: 1.7;

  /* Semantic aliases keep existing pages readable while they are migrated to
     the role names above. New CSS must use the role names. The h5 and h6
     aliases are gone: they pointed at --type-meta and --type-caption, which no
     longer carry a heading meaning, and nothing referenced them. */
  --type-h1: var(--type-display);
  --type-h2: var(--type-section);
  --type-h3: var(--type-card);
  --type-h4: var(--type-label);
  --leading-h1: var(--leading-display);
  --leading-h2: var(--leading-section);
  --leading-h3: var(--leading-card);
  --leading-h4: var(--leading-label);

  /* ===== 1. BRAND PALETTE, RAW =====
     The official values. These are the only colours in the system; everything
     below assigns one of them to a job and nothing derives a new shade.

     NINE NAMES, AND LIGHT GRAY IS NOT A NEW COLOUR. V9 p28 publishes the
     palette in two tiers and Light Gray #d4d4d4 sits in the PRIMARY tier, at
     the largest share of the three (50%). It had no token here because the
     page ground used to be a warm tint derived from Razeen, so the one colour
     the guide gives the most space to was the one the system could not name.
     It is transcribed, not derived.

       PRIMARY    Light Gray 50%   Liwa 30%   Grey 70 20%
       SECONDARY  Black 40%   Al Khatim 15%   Razeen 15%   Sweihan 15%
                  Jais 15%

     The proportions are a design instruction, not a CSS one: no token can
     enforce how much of a page a colour covers. What the tokens can do is stop
     a secondary from carrying a primary's job, and that is what section 4
     below does. */
  --brand-light-grey: #d4d4d4;
  --brand-liwa: #910811;
  --brand-sweihan: #D8702C;
  --brand-al-khatim: #DF9A34;
  --brand-razeen: #DDB176;
  --brand-jais: #59595c;
  --brand-grey-70: #6D6E71;
  --brand-black: #414042;
  --brand-white: #FFFFFF;

  /* Subsidiary logo primitives. These values are read from the supplied marks,
     not invented from a nearby Sdeira colour. They are primitives only: pages
     consume the semantic aliases below, never these names directly. */
  /* Khadamat Brand Guidelines V1 (digital, transcribed 24 Aug 2026): Deep
     Forest is the brand's primary accent (30% of the hierarchy); Olive Green,
     Basil Green and Soft Sage are the secondary greens (15% each). Light Gray,
     Grey 70, Black and Jais above are shared Sdeira primitives the guide
     reuses at 50/20/40/15% respectively, unchanged from the group values. */
  --brand-khadamat-green-deep: #29523D; /* Deep Forest */
  --brand-khadamat-green: #547035; /* Olive Green */
  --brand-khadamat-green-soft: #809C50; /* Basil Green */
  --brand-khadamat-lime: #BABF84; /* Soft Sage */
  /* Eskan Brand Guidelines V1, transcribed 3 Sep 2026 from the identity
     owner's own PDF, which reached this project in the OneDrive pack of
     18 Aug. Every one of the three values that existed before that date was
     WRONG, by one or two units each: #006A80 for #016b80, #077985 for
     #0a7985, #A3C9CA for #a3c8ca. They were sampled off the supplied logo
     files - the comment above still says "read from the supplied marks" -
     and a PNG carries its own colour management, so sampling a mark is not
     transcription. #79ACA4 is the fourth colour, absent entirely until now.
     Corrected values only; nothing here is rounded or adjusted. */
  --brand-eskan-teal-deep: #016B80; /* primary */
  --brand-eskan-teal: #0A7985;
  --brand-eskan-mid: #79ACA4;
  --brand-eskan-sage: #A3C8CA;

  /* Zoad Brand Guidelines V4 page 19, transcribed 3 Sep 2026. The guide names
     its colours and states their shares, which the two values that existed
     before did not capture: Blueberry 50% is the dominant colour, Flour
     (white, already --brand-white) 40% is the clean ground, and Masonry Oven
     is a 10% warm accent for "distinction and emphasis". The three
     secondaries carry a third each of the secondary palette.

     ONE VALUE IS OURS, NOT THE GUIDE'S, AND DELIBERATELY SO. Page 19 prints
     #28374A under Masonry Oven - the same hex AND the same CMYK it prints
     under Blueberry - while the swatch beside it is plainly brick red. It is
     a copy-paste error in the guide. Sampling that swatch out of the PDF at
     200dpi gives #7D4132, one unit from the #7E4233 this file already
     carried, so whoever transcribed it measured the swatch rather than
     reading the caption and was right to. The value stands and the error is
     reported to the identity owner. */
  --brand-zoad-blueberry: #28374A; /* primary, 50% */
  --brand-zoad-brown: #7E4233; /* Masonry Oven, 10% accent */
  --brand-zoad-mackerel: #51616B; /* Spanish Mackerel */
  --brand-zoad-cardamom: #6C6751; /* Cardamom */
  --brand-zoad-yeast: #FAF1EA; /* Yeast */

  /* NO GUIDELINE EXISTS FOR YOUR LAUNDRY. The OneDrive pack of 18 Aug carries
     a brand guide for Sdeira, Khadamat, Eskan, Zoad and 68 Living, and none
     for this company. Both values below were sampled off the supplied mark,
     which is exactly what produced four wrong Eskan values above. They are
     kept because a site has to render, and they are recorded here as
     UNVERIFIED: no Your Laundry page ships against them until the company
     supplies a guide or confirms them in writing. */
  --brand-laundry-blue: #7EB5D8; /* UNVERIFIED - sampled, not transcribed */
  --brand-laundry-beige: #BFA894; /* UNVERIFIED - sampled, not transcribed */
  /* Your Laundry "Deep Tide" (V3 change-request value, #26347C): the deep
     fill used for the brand wipe on Your-Laundry-themed blocks. Kept in the
     primitives block like the other brand fills so block modifiers reference
     the token, never a literal. */
  --brand-laundry-deep-tide: #89B3D3;

  /* Owner-approved promotional treatment for the Your Laundry company card:
     the primary accent fill with white copy and the light wave. Transcribed
     from the reference token file (--laundry-promo-*); the group homepage
     does not mount the Laundry brand mode, so these are declared here for the
     card's hover state rather than inherited. */
  --laundry-promo-fill: var(--brand-laundry-blue);
  --laundry-promo-ink: var(--brand-white);
  --laundry-promo-wave: url("../images/patterns/your-laundry-wave-light.svg");
  --laundry-promo-wave-opacity: 1;

  /* Contrast veil for photography only. It is brand Black, NOT pure black:
     pure black is not one of the palette values, and an overlay drawn in it
     was GLMA's finding p2-04 ("the black overlay does not align with the
     approved image treatment or with the colour palette"). The alphas that
     use it are unchanged - they are a legibility floor, and section 11 sets
     them. What changed is the three numbers that made it black. */
  --image-overlay: var(--brand-black);
  --image-overlay-rgb: 65, 64, 66;

  /* ===== 2. SURFACES =====
     TWO CANVASES, AND THAT IS THE WHOLE LADDER. V9 p71: a page ground is
     White or Light Gray #d4d4d4, and no file carries more than two. There is
     no third tone to reach for, so a component that needs to separate from
     its ground alternates against it - a card on White takes the grey, a card
     on grey takes White - rather than asking for a rung that does not exist.

     THE HONEY FLOOR IS GONE. --surface-base was #FAF4EC, --surface-sunken
     #F6E9D6 and --surface-sunken-strong #EAD3B5: three warm tints derived
     from Razeen, which GLMA's finding p4-03 names as "the pale orange
     treatment ... outside the primary colour gradient". The four names below
     survive because pages read them by name. The resting ground is Light Gray;
     the state pair steps to White, keeping the two approved canvases distinct
     without inventing a third tone. Do not reintroduce a warm value for any
     of them. */
  --surface-base: var(--brand-white);
  --surface-raised: var(--brand-white);
  /* SUNKEN IS A GROUND, NOT A FILL. A SECTION ON IT ALWAYS CARRIES A TEXTURE
     TOO. Binding, and the reason is in section 5b. As a card or a strip it is
     still just a colour; as the ground of a section it always carries the
     p.43 mark, and .pad--sunken in assets/css/sdeira-sections.css draws it so
     that a record cannot ask for the colour and forget the pattern. */
  --surface-sunken: var(--brand-light-grey);
  --surface-sunken-strong: var(--surface-raised);
  --surface-warm: var(--surface-raised);
  --surface-neutral-soft: var(--brand-light-grey);
  /* Liwa is the brand surface, not Sweihan. White on it measures 9.35. */
  --surface-brand: var(--brand-liwa);
  /* THE DEEP BRAND SURFACE IS THE SANCTIONED LIWA TONE STEP.

     This used to be a darkened Sweihan (#A34D12), invented because Sweihan
     fails as a surface under white text (3.35) and the palette had no dark
     orange to give. Liwa does not have that problem: white on it is 9.35.
     The resting role is --accent-fill; the second state is the approved tone
     step --accent-hover, brand Black mixed into Liwa at 30%. It is derived,
     not a darker Liwa primitive.

     WHERE THE SECOND RUNG COMES FROM INSTEAD. A hover or a wipe that used to
     step from #B85D22 to #A34D12 now steps by TONE, not by hue: brand Black
     mixed into the action fill at the sanctioned 30%. That is --accent-hover
     below. One colour, two states, no new primitive. */
  --surface-brand-deep: var(--accent-hover);
  --surface-inverse: var(--brand-black);
  --surface-inverse-soft: var(--brand-jais);
  --shadow-floating: 0 14px 40px color-mix(in srgb, var(--brand-black) 16%, transparent);
  /* Shared glass treatment: the same blur used by the floating top bar, with
     a dark translucent fill for cards placed over photography. */
  --glass-blur: 18px;
  --glass-card-fill: color-mix(in srgb, var(--card-dark) 64%, transparent);

  /* Legacy aliases kept so existing rules resolve to the two-canvas ladder.
     Their names still say "warm"; their values no longer are. */
  --offwhite: var(--surface-base);
  --warm-grey: var(--surface-sunken);

  --charcoal: var(--brand-black);
  --charcoal-soft: var(--brand-jais);
  --charcoal-raised: var(--brand-grey-70);

  /* ===== 3. TEXT ===== */
  /* HEADINGS AND STRONG TEXT. Black on White = 10.31, on #d4d4d4 = 6.96.
     V9 p71's Contrast Rule governs BODY copy and says nothing about headings,
     so they stay on Black. */
  --ink: var(--brand-black);
  /* RUNNING COPY, and it is Jais by instruction rather than by preference.
     V9 p71: "On White Canvas: Body copy must be Jais; titles can use Liwa for
     emphasis." Jais measures 6.98 on White and 4.71 on #d4d4d4, so the rule
     costs nothing on either canvas. It is a separate name from --ink because
     the two are separate instructions: move one and the other does not move. */
  --body-ink: var(--brand-jais);
  /* Jais, not Grey 70. Grey 70 drops to 3.44 on #d4d4d4 and fails body text
     there; Jais clears both canvases. Same value as --body-ink today, and
     still its own role: --muted is secondary text, --body-ink is primary. */
  --muted: var(--brand-jais);
  /* White at 78% composited over Black. Solid so it stays correct when it lands
     on a Jais card instead of the section floor. 7.02 on Black. */
  --muted-on-dark: #D5D5D5;

  /* ===== 4. BRAND ROLES =====
     ONE COLOUR CARRIES EMPHASIS AND ACTION, AND IT IS LIWA.

     Emphasis is headings, kickers, figures, links, icons and rings. Action is
     the primary button, the CTA band and every filled control. They used to be
     two colours and three values - Sweihan for the lines, #B85D22 for the
     fill, #A34D12 for small orange text - because no orange in the palette
     clears AA as text or under white text, so each role needed its own
     workaround. Liwa clears both at once (9.35 as text on White, 9.35 for
     white text on it), so the workarounds are gone and every role below
     resolves to the same published value.

     The 2px focus ring on a light-surface control is part of this: it used to
     be Razeen at 1.98 on white, under the 3.0 that WCAG 1.4.11 asks of a focus
     indicator. It is Liwa now, at the same width and offset. On a DARK ground
     the answer inverts with everything else: Liwa is 1.10 there, so the ring
     takes --accent-on-dark at 5.22. */
  --accent: var(--brand-liwa);

  /* SMALL ACCENT TEXT. Same value as --accent, and it stays a separate name.
     It used to be a THIRD orange (#A34D12), the deepest of three, because a
     13.8px orange mark failed AA on every light ground. Liwa needs no such
     step: 9.35 on White and 6.30 on #d4d4d4, at any size. Deriving a darker
     Liwa to "strengthen" this role would be a ninth colour - do not. */
  --accent-strong: var(--brand-liwa);

  /* THE ACTION FILL. Also Liwa, and for the first time it is the same value as
     the accent: white on it measures 9.35, so a fill under white text needs no
     darkening at all. Two rungs used to exist here (#B85D22 resting, #A34D12
     hovered) purely to work around an orange that could not carry white text.
     Both are gone. */
  --accent-fill: var(--brand-liwa);

  /* THE INK THAT SITS ON THE ACTION FILL. White, at 9.35 - the strongest
     pairing available on any value in the palette. Stated as its own role
     rather than assumed, because a brand mode may need the opposite answer:
     Your Laundry's blue cannot carry white and takes black instead. */
  --on-action: var(--brand-white);

  /* THE SECOND STATE OF THE ACTION FILL, AND IT IS NOW THE SAME FILL.
     V3 change request (23 Sept): the filled button keeps its resting colour on
     hover instead of darkening; the icon moves 4px toward reading instead.
     --accent-hover previously mixed Brand Black into the fill at 30%
     (compositing to #791920); that step is retired and the name now maps
     straight to the resting fill.

     NEVER write a darker Liwa hex to get this. There is no darker Liwa. */
  --accent-hover: var(--accent-fill);
  --accent-text: var(--brand-black);
  /* -deep is a FILL that carries white text - the drawer's contact button, the
     closing band's primary. It follows --accent-fill, which is now the same
     published Liwa, so the name survives and the second value it used to hold
     does not. */
  --accent-deep: var(--accent-fill);
  /* Liwa on Black scores 1.1 and is invisible there. On dark surfaces the warm
     tone carries the accent instead: Razeen on Black = 5.22. This is also the
     focus-ring role for form controls on a dark surface; their active ground
     stays on the same --card-dark-hover rung as pointer hover.

     V9 DOES NOT AUTHORISE THIS and it is not presented as compliance. The
     guide is silent on the accent inverting on a dark ground; the alternative
     is an accent no one can see. It is an open question for the identity
     owner, listed as such rather than resolved here. */
  --accent-on-dark: var(--brand-razeen);
  --accent-wash: color-mix(in srgb, var(--brand-liwa) 12%, transparent);

  /* ----- 4a. THE HOVER COLOUR OF A LINK -----
     One role for the whole page: on hover and on keyboard focus, a text link
     and any icon inside it move to the accent together. Before this role
     existed, 55 hover rules across the homepage declared a colour or a fill
     using seven different values, and several defined the same selector two or
     three times with different answers.

     Two values, because one cannot serve both surface families. Measured with
     Liwa as the hover colour:

       White   #FFFFFF   9.35        Black #414042   1.10
       #d4d4d4           6.30

     The light canvases clear AA with room. Black does not - Liwa disappears
     there - so dark surfaces take Razeen at 5.22 instead.

     The whole table used to fail: Sweihan measured 3.35 / 3.06 / 2.80 / 3.08
     on the four grounds of the day, none of them clearing 4.5, and the comment
     here proposed a darkened orange as the fix. The colour change makes that
     workaround unnecessary rather than accepting it.

     Not for the filled buttons: .btn-primary, .nav-cta and .cta-action sit ON
     the accent fill, where accent-coloured text measures 1.00 and disappears.
     Those keep the fill plus --accent-hover's tone step. */
  --link-hover: var(--accent);
  --link-hover-on-dark: var(--accent-on-dark);

  /* ----- 4c. THE WATERMARK STRIPE TINTS -----
     The diagonal brand-line composition inside .company-card separates its
     stripes by STRENGTH, not by hue: three hues at 7% opacity read as one warm
     band, one pink band and one grey band, which is a stain rather than a
     watermark. Same geometry, same stripe widths, same 7% element opacity,
     same hover step - only the tint of each stop changes.

     They read --identity-mark, so a brand mode's own decoration follows it,
     and at :root that is Liwa. color-mix against transparent, not a new hex:
     one approved value at three dilutions, never three colours. */
  --watermark-1: color-mix(in srgb, var(--identity-mark) 100%, transparent);
  --watermark-2: color-mix(in srgb, var(--identity-mark) 68%, transparent);
  --watermark-3: color-mix(in srgb, var(--identity-mark) 44%, transparent);

  /* THE "GOLD" ROLES ARE NOT GOLD ANY MORE, and the names are kept only
     because pages read them. Razeen is a 15% secondary in V9 p28 and stops
     carrying a functional role on a light surface; what these marked - a
     highlight, a decorative fill - is emphasis, and emphasis is Liwa. */
  --gold: var(--accent);
  --gold-fill: var(--accent);
  /* A warm tone on light is a fill, never a word: Razeen 1.98, Al Khatim 2.38,
     Sweihan 3.35. This role resolves to the neutral label tone. */
  --gold-text: var(--label);
  /* On dark the inversion holds: Razeen on Black = 5.22, on Jais = 3.55, which
     is large text only. */
  --gold-on-dark: var(--brand-razeen);

  /* ===== 4b. WHERE COLOUR IS ALLOWED TO GO =====
     V9 p28 sets the proportions and this table is how they land in tokens:

       Liwa      -> emphasis AND action: headings' accent, kickers, figures,
                    links, feature icons, primary fills, the CTA band, rings.
                    9.35 on White, 6.30 on #d4d4d4
       Jais      -> body copy and inline icons on light   6.98 / 4.71
       Grey 70   -> awaiting-data markers                 5.10 on White,
                    3.44 on #d4d4d4 - LARGE TEXT ONLY there
       Razeen    -> icons and accents on dark             5.22 on Black
       Sweihan
       Al Khatim -> 15% secondaries. Fill only, never text on light, and never
                    a functional role that the primary palette owns

     THE OLD TABLE SAID THE OPPOSITE. It read "Red now marks ACTION and the
     brand band. Every other job that used to borrow it gets a neutral or a
     warm tone", on the reasoning that Liwa at high density reads as an error
     state. V9 p28 answers that directly: Liwa is the 30% emphasis colour and
     is meant to carry exactly those jobs. What keeps it from reading as an
     error is its share of the page, which is a design decision measured on a
     screenshot, not a token that can be tuned here.

     An INLINE icon beside copy is still Jais. It is punctuation for the
     sentence it sits in and takes the text colour, not the accent. A FEATURE
     icon - the visual lead of its own card - takes the accent. */

  /* Inline pictograms. Not the accent: they belong to the text. */
  --icon: var(--brand-jais);
  --icon-on-dark: var(--brand-razeen);
  /* Unboxed feature icons are the visual lead of their card, not inline
     pictograms. They take the brand's strong accent on light surfaces and its
     approved on-dark accent counterpart, while titles and body copy remain
     neutral. */
  --icon-feature-color: var(--accent-strong);
  --icon-feature-color-on-dark: var(--accent-on-dark);
  /* The tinted square behind an icon. Neutral, so it does not read as a state. */
  --icon-wash: color-mix(in srgb, var(--brand-jais) 10%, transparent);
  --icon-wash-strong: color-mix(in srgb, var(--brand-jais) 18%, transparent);

  /* Solid icon-tile fill for repeated markers (why-ic, story-icon,
     about-value-icon). It was Al Khatim, a 15% secondary carrying a repeated
     functional tile, with black glyphs at 4.33 because white failed on it
     (2.38). It is the accent now: white on Liwa is 9.35, so the tile reads at
     twice the separation and stops spending a secondary's share on chrome. */
  --icon-warm-fill: var(--accent-fill);
  --icon-warm-text: var(--on-action);
  /* Institutional value markers need stronger separation than the warm fill.
     Razeen on brand Black measures 5.22:1 and is the approved on-dark pairing. */
  --icon-value-fill: var(--brand-black);
  --icon-value-text: var(--brand-razeen);
  /* The hover wash behind an icon tile. A 14% tint of the accent, not of a
     secondary: the tile itself is the accent now, so its hover has to be the
     same colour or the two disagree about what the tile is. */
  --icon-warm-wash: color-mix(in srgb, var(--accent) 14%, transparent);

  /* A quieter 10% tint behind a Jais glyph. The name still says razeen; the
     value does not. At 10% the ground is close enough to white that the glyph
     clears every floor on both canvases. */
  --icon-razeen-wash: color-mix(in srgb, var(--accent) 10%, transparent);

  /* Kickers, eyebrows, category labels, meta rows.
     JAIS, NOT GREY 70. Grey 70 clears 5.10 on White but only 3.44 on #d4d4d4,
     so the role would fail AA on one of the two canvases and the answer would
     have to be a caveat telling each grey section to redeclare it by hand. A
     default that is wrong on one canvas, corrected by remembering, is how
     drift starts. Jais measures 6.98 on White and 4.71 on #d4d4d4 and clears
     both. */
  --label: var(--brand-jais);
  --label-on-dark: var(--muted-on-dark);

  /* Card badges take the accent on light surfaces: 9.35 on White, 6.30 on
     #d4d4d4. Dark cards use Razeen instead. */
  --badge-text: var(--accent-strong);
  --badge-text-on-dark: var(--brand-razeen);

  /* SIZE, and it is part of the role now.
     The badge had a colour token and a weight token but no size, so every
     badge took whatever font-size its own selector happened to carry - in
     practice --type-meta, 13.12px. Against 16px body copy and a 15.2px action
     label that is the smallest text on the page wearing the loudest colour,
     and it read as fine print rather than as a card's category.

     --type-action is the value, not a new number: it is the role directly
     above meta on the same scale, the one page-content buttons and inline
     action links already use, and a badge is closer to that tier than to the
     caption tier it had drifted into. Nothing here invents a size.

     The colour role above clears AA at this size. */
  --badge-size: var(--type-action);

  /* A badge is a word, not a chip. No fill, no padding, no border: the colour
     and the weight are the whole treatment. Stated as a token so the rule is
     visible in the system rather than living only in a page comment. */
  --badge-fill: transparent;
  /* Top of the weight scale. Since 18 Aug 2026 this renders as the real Bold
     outline: the licensed Light, Medium and Bold files arrived and every
     @font-face slot names one of them. It used to render identically to
     Regular (measured: the same string was 65.96px wide at every weight from
     300 to 900, because all five slots pointed at the one licensed file), and
     the declaration was kept precisely so these badges would thicken on their
     own the day the file landed. They have. html still carries
     font-synthesis: none, so an undeclared weight is never faked. */
  --badge-weight: 700;

  /* Large numerals take the accent on light surfaces and Razeen on dark. */
  --figure: var(--accent-strong);
  --figure-on-dark: var(--brand-razeen);

  /* SIZE, for the same reason --badge-size exists a few lines up: the figure
     had a colour role and no size, so every numeral took whatever its own
     selector happened to carry. Measured on the two homepages before this
     line: 97.8px on the about badge, 55.9 on a timeline year, 53.1 on a
     verified figure, 39.8 on a process step number, 30 on a partner stat -
     five components rendering the same thing at five sizes, none of them on
     the scale.

     A NUMERAL IS NOT A HEADING, which is why this is its own role rather than
     a nearest-fit onto --type-card. Mapping them onto heading roles is what
     produced the spread in the first place: a step number is not a card title
     and stops matching one the moment either is retuned.

     The step is the ladder's own: 2rem to 4rem is exactly the --type-card
     rung doubled at both ends (1.5-2.25 -> 2-4 is x1.33 / x1.78), and 3.4vw
     puts the fluid crossover at the same 1180px the rest of the scale uses.
     Nothing here is a new idea, only a rung that was missing. */
  --figure-size: clamp(2rem, 3.4vw, 4rem);

  /* Awaiting-data marker. Deliberately neutral: a missing value is not an
     error, and colouring it in the accent tells the visitor something has gone
     wrong. Grey 70 is a 20% PRIMARY in V9 p28, so this is the palette's own
     answer rather than a quiet neutral borrowed from elsewhere.

     ITS LIMIT IS MEASURED AND IT MATTERS ON THE GREY CANVAS: Grey 70 is 5.10
     on White but 3.44 on #d4d4d4, which clears the 3.0 large-text allowance
     and not the 4.5 body floor. So a pending marker on the grey canvas is a
     figure or a large label, never body-size copy. Body-size awaiting-data
     copy on grey takes --muted. */
  --pending: var(--brand-grey-70);
  --pending-on-dark: var(--muted-on-dark);
  /* Review navigation, approved by the site owner on 8 Sep 2026. */
  --review-disabled-opacity: .5;

  /* Caution text and its wash. Both were Sweihan-derived; both take the accent
     now, for the same reason every other functional role did - a 15% secondary
     does not carry a state. */
  --warn-text: var(--accent-strong);
  --warn-wash: color-mix(in srgb, var(--accent) 10%, transparent);

  /* ===== 5. LINES ===== */
  /* The separator is Light Gray itself. It used to be #EAD3B5, the deepest
     rung of the honey ladder, and there is no such rung any more. */
  --line: var(--brand-light-grey);
  --line-on-dark: color-mix(in srgb, var(--brand-white) 18%, transparent);
  /* The group's own diagonal signature (identity bands, corner stickers,
     divider ticks, timeline dots) reads this indirection rather than a
     primitive directly, so a brand mode below can swap what its own decorative
     marks draw in without touching what any palette value means - see BRAND
     MODES. At :root it is Liwa: V9 p43 derives the pattern from the mark, and
     the mark's colour is the identity's own. It was Sweihan, which put a 15%
     secondary on every decorative element on the site. */
  --identity-mark: var(--brand-liwa);
  /* Shared short separator geometry used by divided steps and chapter cards.
     Tone and placement belong to each variant; the mark's proportions do not. */
  --step-separator-thickness: 2px;
  --step-separator-length: 72px;
  /* ONE ANGLE, AND IT IS 45 DEGREES.
     V9 p10, p11 and p43: the mark is built on 45deg and every pattern is
     derived from it. This file used to publish 22deg here and 112deg below,
     with a third value (108deg) written by hand inside sdeira-v2.css and a
     fourth (43deg) for the card wipe - four angles in a system that has one.
     GLMA's finding p2-03 is exactly this. Language may move a stripe group to
     the opposite edge; it must never mirror or change the angle. */
  --brand-stripe-angle: 45deg;
  --brand-stripe-gradient-angle: 315deg;
  /* A hover wipe is directional interaction, unlike the static identity
     stripe. It begins at the reading start edge; the RTL counterpart below
     mirrors the travel only, never the mark or its decorative bands. */
  --motion-wipe-gradient-angle: var(--brand-stripe-gradient-angle);
  /* Row-list wipes enter from the same corner as the route cards they sit
     beside (25 Sept change request): the angle now follows
     --route-card-wipe-angle instead of a fixed 45deg. */
  --motion-wipe-gradient-angle-arrow: var(--route-card-wipe-angle-en);
  /* Route-card panels grow from the chevron corner. Same 45deg as everything
     else; LTR mirrors that exact geometry below at 315deg. This is interaction
     motion, never an identity stripe angle - the mirror stays, the two degrees
     of drift in it are gone. */
  --route-card-wipe-angle: 45deg;
  --route-card-wipe-angle-en: 45deg;
  /* Shared geometry for outlined identity bands on section photography and
     image frames. The vertical overfill is clipped by each host, so the
     visible band reaches both edges even after rotating.

     THE OVERFILL FOLLOWS THE ANGLE, and it had to grow when the angle moved
     to 45deg. A band of height h rotated by theta inside a host of height H
     covers that host only while h >= H / cos(theta) + w * tan(theta). At the
     old 22deg, cos is .927 and tan is .404, so 140% and a -20% offset were
     enough. At 45deg both terms jump - cos is .707 and tan is 1 - and the same
     140% band stops short of two corners and reads as a streak laid across the
     picture instead of a band framing it. 200% at -50% clears the widest band
     (144px) on the tallest host this is used on, with margin.

     Change one of these three and re-derive the other two. They are not taste. */
  --identity-band-width: clamp(96px, 7.5vw, 144px);
  --identity-band-border: 2px;
  --identity-band-top: -50%;
  --identity-band-height: 200%;
  --identity-band-fill: 12%;
  /* Image ribbons keep the same 2px geometry but sit behind content as a
     low-contrast photographic watermark. */
  --image-ribbon-line-strength: 44%;
  --image-ribbon-fill-strength: 5%;
  /* P.43 corner artwork: one named photographic mark, with its own ratio so
     it cannot be confused with the identity-band component above.

     RETUNED 28 Aug 2026, site owner: the prior fixed content scale read too
     small on the first featured-project card and on its companion cards.
     The content mark now scales with its own photograph, with a minimum that
     keeps it present on narrow cards and a cap that keeps it secondary on
     wide figures. This is one responsive component tier, not separate sizes
     for normal versus mirrored artwork:

     - --photo-mark-width: every mark on a content-scale photograph (a split
       figure, the feature project card, the About photo).
     - --photo-mark-width-hero: the homepage hero and the closing CTA band,
       the two full-bleed section-scale photographs - owner asked for these
       two kept in step with each other but sized a step above the content
       tier, matching how much bigger a full-bleed section already reads
       next to a card.

     Every current caller was re-pointed to the tier its own composition
     actually matches, not to whichever of the two old token names it
     happened to already reference - .about-visual moves from the old
     p43-width (which is now the hero/CTA tier) to the content tier, because
     it is a photo the size of a split figure, not a full-bleed section. */
  /* Owner, 28 Aug 2026: raise the cap only (same 34% / 16vw share of the
     frame), so the mark reads larger on the wide viewports that were
     already hitting the ceiling, without changing when it starts scaling
     on narrower ones. RETUNED same day, second pass: the share itself was
     also too small wherever the host box is narrower than a full section -
     .split-media in a half-width copy/photo layout (About page and its
     siblings) renders this photo at ~580px, so 34% of it is only ~197px,
     a mark barely a fifth the width of the same mark on a full-bleed
     figure. RETUNED a third time: 60% - owner wants it bigger still after
     seeing 44%. */
  --photo-mark-width: clamp(160px, 60%, 520px);
  --photo-mark-width-hero: clamp(160px, 16vw, 340px);
  --photo-mark-p43-ratio: 434 / 281;

  /* ONE TONE FOR A BAND ON A PHOTOGRAPH. BINDING.
     The geometry above is shared by every identity band on the site. The TONE
     is not a free choice on top of it - it follows the surface, and there are
     exactly two answers:

       - on a flat section surface: --identity-band-fill (12%) with a solid
         --identity-mark outline. The band is a graphic element on a plain
         ground and is meant to be seen as one.
       - on a photograph: the two values below. A photograph is already busy,
         and at full strength the outlines stop being a watermark and start
         competing with whatever text the panel carries. Measured on the news
         hero, which is where this rule was finally written down; the same
         complaint had already produced the image-ribbon strengths, so this is
         not a second answer to the same question - it IS those strengths,
         given the name of the role so a band does not have to borrow a
         ribbon's token to find them.

     A band that sits UNDER its host's scrim, or that carries its own opacity,
     already lands at or below this tone and needs no further reduction - the
     homepage hero (scrim paints after the bands, leaving .28-.56 of them) and
     the closing CTA (opacity .28) are both in that position. Anything drawn
     directly on a photo at full strength is the case this rule governs.
     A surface that needs something else is a question for the identity owner.
     One such question was asked and answered - see the ambient case below.
     That is the whole list; do not add a fourth. */
  --identity-band-line-on-photo: var(--image-ribbon-line-strength);
  --identity-band-fill-on-photo: var(--image-ribbon-fill-strength);

  /* THE AMBIENT CASE. Directed by the site owner on 2026-08-04.
     A band drawn behind a section's OWN CONTENT - text and cards sitting
     directly on top of it, with no scrim and no photograph in between - is
     doing a third job. It is not a graphic element the visitor is meant to
     look at (the flat-surface case) and it is not a watermark on a busy photo
     (the case above). It is texture on an otherwise empty ground, and the
     brief for it was explicit: visible enough to add depth, quiet enough that
     it never competes with the words in front of it.

     These are NOT new numbers. They are the values the homepage news section
     has carried since it was built, promoted here and given the name of the
     role so the rest of the page can reach the same tone instead of each
     section guessing at its own. The line is the fill's counterpart, not an
     outline: at 6% the band reads as an edge in the ground rather than as a
     drawn rectangle, which is exactly the difference between this case and
     the flat-surface one.

     THE FILL IS ZERO NOW, AND THAT IS THE POINT. V9 p43 draws the pattern as
     thin outlines taken from the mark, and p63 applies it "along the outer
     edges ... to frame the composition without competing" with the content. A
     filled band is not an outline. GLMA's finding p4-02 is that the patterns
     do not follow the approved applications, and a 1% fill behind a section's
     own content was the largest instance of it.

     So the ambient case is a LINE and nothing else: 5% of the identity mark,
     no fill. Never raise either number to make the pattern "show up" - if it
     needs to be seen it is at the wrong strength for the job, and the answer
     is where it is drawn, not how strong it is. Never put text on one. */
  --identity-band-line-ambient: 5%;
  --identity-band-fill-ambient: 0%;

  /* ===== 5b. THE PATTERNED GROUND =====
     A section on --surface-sunken always carries a texture with it: the
     colour and the pattern are one ground, and a section takes both or
     neither.

     RETIRED, 31 Aug 2026: the diagonal 45deg hairline hatch that used to
     live here as --ground-sand (a repeating-linear-gradient, one token
     holding a hairline/gap/angle so no caller reassembled the numbers
     itself). Site owner: this diagonal-line language must never reappear,
     anywhere in the system - it had already been deleted once from
     .pad--field/.pad--edge (27 Aug 2026, "delete the old patterns") and from
     both homepages' own sections (21 Aug 2026, PATTERN CLEAN-UP PASS 2 in
     sdeira-liwa.css), but a plain .pad--sunken with no other ground modifier
     was missed in both passes and went on drawing it - caught on
     /about/certifications's "Recognition published in 2026" strip.

     WHAT REPLACED IT is not a token: every ground that used to read
     --ground-sand now paints the same asset directly - images/patterns/
     solid-a.svg, no-repeat, centred, cover, at 5% opacity - the p.43 mark,
     V9's own approved pattern, already the production value on both
     homepages' .needs/.value-sections and on .pad--field/.pad--edge in
     assets/css/sdeira-sections.css. A shared image asset does not drift the
     way four reassembled gradient numbers can, so this does not need a
     second CSS custom property to stay one source of truth.

     WHERE IT MAY BE DRAWN. On --surface-sunken, as a section's own ground.
     --surface-base is the page's own canvas and stays plain, or every
     section on the site is patterned and none of them reads as one;
     --surface-raised is a card, and a card is a surface in front of the
     ground, not another ground (.step-list in sdeira-sections.css used to
     get this wrong, painting the ground pattern a second time inside a
     card sitting on top of it). On a dark section the tint is not a step at
     all, so the dark ground restates it in white - see .pad--inverse in the
     sections file. */

  /* ===== 6. RADIUS =====
     ONE near-sharp value. A formal wordmark and a pill button do not belong in
     the same system. */
  --r: 4px;
  /* Owner-approved compact homepage sector grid. */
  --hero-sector-gap: 4px;
  --hero-sector-mobile-radius: 0px;

  /* ===== 7. MOTION =====
     3 durations, 1 easing. --dur-fast is colour only, --dur-base is surface and
     size, --dur-slow is image zoom and the entrance stagger. */
  --dur-fast: 120ms;
  --dur-base: 240ms;
  --dur-slow: 480ms;
  --ease-out: cubic-bezier(.2, .7, .3, 1);

  /* A fourth duration, and the reason it is not a fourth interaction duration:
     the three above are RESPONSES - something the visitor did, answered inside
     half a second. --dur-ambient is the opposite kind of motion. Nothing
     triggers it, nothing waits for it, and it must never be caught happening:
     it is the drift of the background identity bands (the ambient case in
     section 5), slow enough that the page looks still while it is running.
     Anything the visitor triggers still uses one of the three above. */
  --dur-ambient: 19s;

  /* ===== 8. LAYOUT ===== */
  --max: 1280px;
  --nav-submenu-min: 260px;
  /* The floating header pill's own inset, and the inline padding of the
     contact button inside it. Matches the reference nav-controls rules. */
  --nav-pill-inset: 20px;
  /* Single shared header logo height, rest and scrolled, all widths (V3
     change request). The header sheets wire their --logo-h to this. */
  --header-logo-height: 72px;
  --header-logo-rest: clamp(40px, 4.4vw, 54px);
  --header-logo-scrolled: clamp(30px, 3.2vw, 38px);
  --header-logo-drawer: 42px;
  --header-nav-gap: 18px;
  --header-menu-gap: clamp(12px, 1.55vw, 20px);
  --header-disclosure-padding: 6px;
  --header-nav-gap-small: 10px;
  --header-nav-mobile-padding: 0 20px;
  --sec-pad: clamp(64px, 6.4vw, 96px);
  --sec-head-gap: clamp(34px, 3.4vw, 52px);
  --cta-mobile-panel-padding: 24px 16px;
  --cta-mobile-action-padding: 12px;
  --cta-mobile-action-gap: 12px;
  --cta-mobile-heading-gap: 24px;
  /* C13 wide photo-band copy. Long, sourced project narratives need a wider
     reading measure while retaining the standard photo-band rhythm. (It used
     to say "scrim" here; that band stopped darkening its photograph on
     26 Aug 2026 - the copy sits in the shared glass block instead.) */
  --photo-band-copy-measure: 72ch;
  /* Portrait lockups in the shared logo-wide split use a compact landscape
     slot so the mark supports the copy instead of setting the section height. */
  --logo-wide-portrait-inline: 180px;
  /* A selected body paragraph in a logo-wide split is measured to two lines at
     the desktop type scale without introducing a hard line break. */
  --logo-wide-copy-measure: 62ch;
  --photo-band-wide-min-block: clamp(520px, 48vw, 680px);
  --photo-band-wide-gap: clamp(48px, 5vw, 72px);

  /* C13 international phone control. The country code is its own searchable
     control at wide widths, then becomes a full row before either value clips.
     The closed control sizes to the code alone - the flag stays inside the
     open list only - so the local number keeps the width it needs to type
     into. */
  --phone-control-gap: 0px;
  --phone-field-gap: 8px;
  --phone-country-min: 92px;
  --phone-picker-max: 320px;
  --phone-picker-list-max: 220px;
  --phone-field-collapse: 620px;

  /* C13 service-request card. It occupies one half of the shared 1280px
     content measure, then yields to the available inline size responsively. */
  --service-form-max: calc(var(--max) * .5);

  /* C17 click-to-load Google Landbank map. The viewport-relative block sizes
     are shared by the lightweight poster and live canvas, so activation never
     shifts the page and overlays remain touch-safe at every breakpoint.

     THE MAP IS INSIDE THE PAGE'S READING COLUMN, not full-bleed (site owner
     instruction, 12 Aug 2026). It shares that column with a list panel that
     carries every location in text, because a pin on a satellite tile is not
     always legible on its own, and the list also reads with no JavaScript and
     before the visitor activates Google. Both sit at the same block size so
     the pair reads as one object. --land-map-collapse (980px) is the width
     below which the pair stacks; it is a media query, so the number is
     repeated there by hand rather than read from here. */
  --land-map-block: clamp(540px, 65svh, 720px);
  --land-map-block-mobile: clamp(420px, 58svh, 500px);
  --land-map-list-inline: 340px;
  /* THE SHEET NEVER TAKES MORE THAN HALF THE MAP (site owner, 14 Aug 2026).
     This was clamp(320px, 46svh, 420px) - a length written independently of
     the map's own, so on a short viewport the two clamps met at 320px of
     sheet over a 420px map and the open list covered the map almost
     completely. Stated as a fraction of the map instead, the relationship
     holds at every height: the visitor reading the list can still see where
     the plots are, which is the whole reason the pair exists. */
  --land-map-list-block-mobile: calc(var(--land-map-block-mobile) / 2);
  --land-map-collapse: 980px;
  --land-map-panel-padding: 16px;
  --land-map-inset: 16px;
  --land-map-gap: 12px;
  --land-map-control-target: 40px;
  --land-map-filter-height: 38px;
  --land-map-index-target: 26px;
  --land-map-pin-target: 44px;
  --land-map-pin-mark: 28px;
  --land-map-pin-ring: 2px;
  --land-map-pin-radius: 50%;
  --land-map-tooltip-inline: 260px;
  --land-map-fullscreen-layer: 1200;
  --land-map-overlay-layer: 500;
  /* Above --land-map-overlay-layer, not equal to it: the filter bar and the
     list panel float over the facade and the live canvas both, and with an
     equal z-index the facade would win the stack on DOM order alone (it
     paints after the bar) and hide it completely. */
  --land-map-panel-layer: 600;
  --land-map-camera-padding: 48px;
  --land-map-camera-padding-selected: 72px;
  /* The camera covers the whole United Arab Emirates, not Abu Dhabi alone:
     every emirate matters to the visitor even when no plot sits in it (site
     owner instruction, 12 Aug 2026), so the overview zoom and the minimum
     zoom both frame the country rather than the plot cluster. */
  --land-map-zoom-min: 6;
  --land-map-zoom-max: 18;
  --land-map-zoom-overview: 7;
  --land-map-zoom-cluster: 11;
  --land-map-zoom-cluster-step: 2;
  --land-map-zoom-selected: 15;
  --land-map-parcel-line: 2;
  --land-map-parcel-line-active: 3;
  --land-map-parcel-fill-opacity: .14;
  --land-map-parcel-fill-opacity-active: .28;
  /* The facade's own map is drawn, not photographed: an inline SVG built from
     the same lat/lng and polygon records as the live Google layer, so the two
     never disagree about where a plot sits (site owner instruction, 12 Aug
     2026 - replacing the supplied raster poster, which plotted its pins
     outside the real cluster). Its camera is a unitless scale factor on an
     SVG <g>, not a Google zoom level, so it needs its own small range rather
     than reusing --land-map-zoom-*: 1 is "every visible plot fit in view",
     the facade's own resting state. */
  --land-map-svg-scale-min: .6;
  --land-map-svg-scale-max: 10;
  --land-map-svg-scale-step: 1.35;
  --land-map-svg-scale-selected: 5.5;

  /* ===== 8b. RESERVED TEXT LINES =====
     A row of repeated cards only reads as a row if the same element sits at the
     same height in every card. A one-line description beside a two-line one
     pushes its title, its icon and its action link out of alignment, and the
     strip stops reading as one system even when the cards are the same height.

     So a repeated-card description RESERVES a fixed number of lines rather than
     taking its natural height. The reservation is written in `lh`, the element's
     own computed line-height:

       min-block-size: calc(var(--card-sub-lines) * 1lh);

     That is the whole point of using `lh` instead of an em value. The homepage
     had this same reservation written five times as 3.1em, 3.2em, 3em and 0,
     because the line-height differs per breakpoint (1.6 / 1.55 / 1.45) and each
     pass hand-converted it and got a slightly different number. `lh` tracks the
     type role automatically, so the reservation cannot drift out of step with a
     line-height change again.

     RESERVE, DO NOT CLAMP. There is deliberately no line-clamp here. Clamping
     truncates and appends an ellipsis, which hides real copy from the visitor
     and would collide with this project's rule that a visitor is never shown
     incomplete content. Measured across fifteen widths from 360 to 1920, no
     sector description wraps past two lines, so the reservation alone is
     enough and nothing is ever cut. If a description genuinely needs three
     lines later, raise this count. Do not start truncating.

     A hard <br> in the markup is not an alternative to this. One card used to
     carry one to force a second line, which then broke the copy at a fixed
     point even at widths where it fitted on a single line. */
  --card-sub-lines: 4;

  /* The reading measure that MAKES the description wrap to --card-sub-lines.
     Reserving the height alone is not enough: a short description still renders
     on one line inside its reserved box, so the card keeps a visible gap where
     the second line should be and the strip still reads as uneven. Constraining
     the measure is what actually produces the second line.

     WHY 17.5ch AND NOT A PERCENTAGE
     ch is the width of one "0" in the element's own font, so the measure holds
     its character count when the font-size steps down at a breakpoint. A
     percentage would not: the sector card is 372px wide at 1920 and 168px at
     360, and the same percentage means a different number of characters at each.

     HOW THE VALUE WAS CHOSEN, measured not guessed. The five sector
     descriptions are 27.6, 26.4, 24.0, 23.7 and 18.8ch of text. For every one of
     them to land on exactly two lines the measure has to be:

       BELOW  18.8ch  or the shortest description stays on one line
       AT OR ABOVE 17ch or the longest needs a third line, because Arabic breaks
                        on word boundaries and 27.6ch does not split evenly

     Tested at 14 / 15 / 16 / 16.5 / 17 / 17.5 / 18ch across eleven widths from
     360 to 1920: 14 through 16.5 produced three-line descriptions, and 17, 17.5
     and 18 produced exactly two lines on all five cards at every width. 17.5ch
     is the middle of that verified window, so it keeps margin on both sides
     rather than sitting on an edge.

     IF THE COPY CHANGES, re-measure. A description longer than about 35ch will
     need a third line, and the fix is to raise --card-sub-lines, never to
     truncate. */
  --card-sub-measure: 17.5ch;

  /* ===== 8c. MEDIA RATIO =====
     ONE delivery ratio for the whole site, and four slots that are all CROPS of
     it. This exists because the people who supply the photography are not the
     people who build the pages: MarCom shoots and exports, and every extra
     ratio in the brief is another chance for a file to arrive at the wrong
     shape and get stretched, letterboxed or silently re-cropped by whoever
     pastes it in.

     THE MASTER IS 16:9, AND IT WAS MEASURED, NOT PICKED.
     The built homepage already settled on it without anyone writing it down.
     Read off the live page at 1280, `.needs-route-media`, `.sector-hover-visual`
     and the news card image all render at 1.778 exactly. Several of the source
     files behind them are 1122x1402 - a 4:5 portrait - already being centre-cropped
     into that 1.778 box by `object-fit: cover` with no visible damage. So the
     crop-from-one-master approach is not a proposal here; it is what the site
     has been doing, promoted to a rule.

     THE MASTER WAS 16:9, MEASURED OFF THE HOMEPAGE, UNTIL 15 AUG 2026.
     Site owner instruction that date: move the master to 4:3. The homepage's
     own `.about-visual` / `.needs-route-media` / `.sector-hover-visual` /
     news-card image were NOT wired to this token (they predate it, in
     `sdeira-v2.css` / `sdeira-v2-en.css`, each hardcoding its own fixed
     height or a literal `16 / 9`) and are ported onto `var(--media-wide)` in
     the same change, in the unlayered SYSTEM LAYER of both files so the
     override holds at every viewport regardless of which `@layer legacy`
     rule would otherwise win. The one exception, deliberately: the news
     grid's feature card keeps its own height-stretch treatment
     (`block-size` matched to its text column), never a fixed ratio - it
     never used var(--media-wide) and still doesn't.

     DELIVERY: 2560x1920, the master ratio at 2x the widest slot the site renders.

     THE FOUR SLOTS, all centre-crops of that one file:
       --media-panorama  21:9  full-bleed page hero
       --media-wide       4:3  THE MASTER, UNCROPPED - cards, news, galleries,
                               needs routes, sector panels. The default. Prefer it.
       --media-card       3:2  standard card media
       --media-square     1:1  a person, a logo tile

     THE SAFE AREA, which is the only thing a photographer actually has to
     remember. Cropping 4:3 down to 1:1 keeps 75% of the width; cropping it out
     to 21:9 keeps 57.1% of the height. Both crops are centred, so the region
     that survives EVERY slot is the central 75% x 57.1% of the frame - 1920x1097
     on a 2560x1920 master. That is the same 42.9% of total frame area the 16:9
     master gave (the fraction is set by the narrowest and widest slots, 1:1 and
     21:9, not by where the master sits between them) - moving the master to 4:3
     only trades width budget for height budget, it does not shrink the budget.
     Keep the subject inside that and the same file works in all four slots.
     Outside it, the subject is composed for one slot only.

     WHY THERE IS NO PORTRAIT SLOT. A portrait crop is what breaks this system:
     4:5 keeps only 60% of a 4:3 master's width (was 45% of 16:9), still too
     narrow to compose a portrait subject in. New page components are therefore
     landscape-or-square only. The homepage's own `.about-visual` was 4:5 and
     predated this rule; it is retrofitted onto var(--media-wide) as of the
     same 15 Aug 2026 change that moved the master to 4:3 (see
     assets/css/sdeira-v2.css / sdeira-v2-en.css, "About visual" comment).

     A slot is applied as `aspect-ratio: var(--media-wide)` plus
     `object-fit: cover` - never by hard width/height attributes, which would
     letterbox a correctly-supplied file. */
  --media-master: 4 / 3;
  --media-panorama: 21 / 9;
  --media-wide: 4 / 3;
  --media-card: 3 / 2;
  --media-square: 1 / 1;

  /* ===== 9. ICON SCALE =====
     Icons follow semantic roles, never a local pixel value. Inline icons use
     1em so an arrow beside copy stays optically matched to that copy; controls
     and markers use the responsive roles below rather than inheriting text
     size accidentally. */
  --icon-inline: 1em;
  --icon-control: clamp(1rem, 1.2vw, 1.25rem);
  --icon-default: clamp(1.25rem, 1.5vw, 1.5rem);
  --icon-feature: clamp(1.5rem, 2.1vw, 2rem);
  --icon-display: clamp(2rem, 3vw, 2.5rem);

  /* Legacy aliases keep existing sites on the shared responsive scale. */
  --ic-xs: clamp(.875rem, 1vw, 1rem);
  --ic-sm: var(--icon-control);
  --ic-md: var(--icon-default);
  --ic-lg: var(--icon-feature);
  --ic-xl: var(--icon-display);

  /* One hover zoom for every card photo. */
  --zoom: 1.045;

  /* ===== 10. ELEVATION =====
     A card reads as a separate surface through tone alone, no border and no
     shadow. On a white section a card needs the section under it to be sunken;
     on a dark section the card steps up from Black to Jais. */
  --band: var(--surface-sunken);
  --card: var(--surface-raised);
  --card-hover: var(--surface-sunken);
  --band-dark: var(--surface-inverse);
  --card-dark: var(--surface-inverse-soft);
  --card-dark-hover: var(--brand-grey-70);

  /* ===== 10b. SDEIRA ASSISTANT CONTRACT =====
     The assistant is the only persistent floating utility on the site. Its
     geometry and its non-interaction timing live here so the shared component,
     the design-system preview and both languages cannot drift apart.

     Colour, radius, type, icon roles, focus colour and interaction motion do
     NOT get assistant copies: they continue to read the system roles above.
     These tokens are only the values that have no meaning outside this one
     component. */
  --assistant-panel-inline: 400px;
  --assistant-panel-block: 580px;
  --assistant-panel-block-mobile: 600px;
  --assistant-panel-inline-clearance: 40px;
  --assistant-panel-inline-clearance-mobile: 32px;
  --assistant-panel-block-clearance: 140px;
  --assistant-panel-block-clearance-mobile: 120px;
  --assistant-panel-offset-inline: 24px;
  --assistant-panel-offset-inline-mobile: 16px;
  --assistant-panel-offset-block: 100px;
  --assistant-panel-offset-block-mobile: 84px;
  --assistant-launcher-target: 44px;
  --assistant-launcher-target-mobile: 44px;
  --assistant-launcher-field: 2rem;
  --assistant-launcher-field-mobile: 2rem;
  --assistant-nudge-inline: 280px;
  --assistant-nudge-gap: 12px;
  --assistant-nudge-padding-block: 14px;
  --assistant-nudge-padding-inline: 16px;
  --assistant-launcher-hover-scale: 1.06;
  /* --assistant-mark-hover-scale/-rotation and --assistant-spark-* RETIRED,
     31 Aug 2026: the launcher's five orbiting sparks and the mark's own
     hover scale/rotate are gone with them - Sdeira Group Guidelines p3-01/
     p3-02, "decorative stars, sparkles and additional embellishments are not
     part of the approved graphic system." See sdeira-assistant.css. */
  --assistant-typing-dot: 5px;
  --assistant-typing-lift: 3px;
  --assistant-choice-target: 46px;
  --assistant-icon-target: 40px;
  --assistant-logo-block: 34px;
  --assistant-avatar-target: 26px;
  --assistant-input-max-block: 168px;
  /* RETIRED 28 Aug 2026: was the composer's backdrop-filter blur radius: the
     strip it applied to no longer exists (assets/css/sdeira-assistant.css,
     ".ai-chat-composer floats ON the conversation"). Replaced by the token
     below for the fade that took its place, not left as a second orphaned
     value alongside it. */
  --assistant-composer-fade: 28px;
  --assistant-focus-outline: 3px;
  --assistant-focus-offset: 3px;
  --assistant-panel-enter-distance: 8px;
  --assistant-launcher-enter-distance: 12px;
  --assistant-welcome-delay: 800ms;
  --assistant-type-cadence: 18ms;
  --assistant-reply-delay: 280ms;

  /* ===== 11. TRANSLUCENT DARK LAYERS OVER PHOTOGRAPHY =====
     These alphas are not decoration, they are the contrast floor for any text
     sitting on a photo. Raising the base from near-black to brand Black made
     every existing layer weaker, so each one was recomputed against a fully
     white photo region, the worst case.

     At alpha .78 the composite is #6B6A6C: white text clears at 5.38 but Razeen
     collapses to 2.72 and fails even large text. At .90 the composite is
     #545355: white text 7.65, Razeen 3.87, which clears large text.

     THE FLOOR MOVED, 26 Aug 2026, BY THE SITE OWNER'S DECISION.
     It was .86. The owner asked for the hero's brand copy block to read as
     actual glass rather than as a slab, was shown that .86 and .90 are
     indistinguishable on a photograph, and chose to change this rule for the
     whole site rather than to carry the block as a local exception - the
     correct half of the choice, since an exception is how a system ends up
     with two answers to one question.

     They asked for .70. The floor is .74, and the four points are not a
     softening of their instruction, they are what this section's own method
     returns. Recomputed against a fully white photo region, the worst case,
     exactly as every number above it was:

       alpha  composite      white text
       .90    rgb(84,83,85)     7.65
       .86    rgb(92,91,92)     6.76
       .82    rgb(99,98,100)    6.07
       .78    rgb(107,106,108)  5.38
       .74    rgb(114,114,115)  4.81   <- lowest that still clears body text
       .70    rgb(122,121,123)  4.33   FAILS the 4.5 body floor
       .66    rgb(130,129,130)  3.88   FAILS

     .74 is therefore not a preference, it is the last value at which the
     second binding rule below is still true. Going to .70 is available and is
     the owner's to take, but it costs white BODY text on a bright photograph
     and the honest way to buy it is to stop putting body text on this layer,
     not to leave both rules standing and contradicting each other.

     LOWERING THE FLOOR PERMITS, IT DOES NOT REQUIRE. Every --scrim-* value
     below keeps the alpha it was measured at; each one is its own decision
     about its own surface, and nothing here asks the header, the footer or
     the sector bar to become see-through. Only a role that is declared at the
     new floor gets it - currently --scrim-glass alone.

     BINDING RULES
     - Never drop a dark layer over photography below .74.
     - Body-size text on these layers is white only.
     - Razeen on these layers is permitted for large text only, never body. */
  /* Existing Khadamat carousel geometry promoted unchanged.
     Breakpoints: 640px mobile, 981px wide copy. The interval matches
     the existing 6000ms controller timer in sdeira-chrome.js. */
  --hero-carousel-height: 100vh;
  --hero-carousel-height-mobile: 92vh;
  --hero-carousel-padding: clamp(140px, 22vh, 220px) clamp(48px, 7vh, 72px);
  --hero-carousel-copy-width: min(720px, 100%);
  --hero-carousel-copy-width-wide: min(960px, 100%);
  --hero-carousel-mark-padding: 0 .3em;
  --hero-carousel-heading-leading: normal;
  --hero-carousel-heading-tail: 0;
  --hero-carousel-lead-gap: 12px;
  --button-photo-fill: var(--brand-black);
  --button-photo-ink: var(--brand-white);
  --hero-carousel-lead-leading: normal;
  --hero-carousel-rail-gap: 10px;
  --hero-carousel-rail-margin: 28px;
  --hero-carousel-rail-width: clamp(220px, 28vw, 320px);
  --hero-carousel-rail-height: 3px;
  --hero-carousel-rail-rest: 28%;
  --hero-carousel-interval: 6s;
  --hero-carousel-focus-width: 3px;
  --hero-carousel-focus-offset: 3px;

  --scrim-nav: rgba(65, 64, 66, .90);
  --scrim-hero: rgba(65, 64, 66, .88);
  --scrim-control: rgba(65, 64, 66, .92);

  /* THE FOOTER SCRIM IS BRAND BLACK, NOT PURE BLACK.

     It was rgba(0, 0, 0, .90) under an exception the identity owner granted on
     30 July 2026, on the reasoning that brand Black at .90 over the warm page
     canvas (#FAF4EC) composited to a mid grey rather than to a closing band.
     Both halves of that reasoning are gone: the warm canvas no longer exists,
     and GLMA's finding p2-04 objects to a pure-black layer by name. It is the
     approved Black at the same .90 now, identical to --scrim-bar directly
     below, and the alpha is untouched.

     THIS REVERSES A SIGNED-OFF EXCEPTION AND NEEDS THE IDENTITY OWNER'S
     CONFIRMATION. The earlier approval was given against a page that no longer
     looks like this; V9 does not authorise a ninth colour and the review asks
     for the eight. If the owner wants the deeper close back, the answer is a
     decision from them, not a literal restored here.

     Over White the composite is #545355, so white footer text measures 7.64 -
     above every floor in this file. */
  --scrim-footer: rgba(65, 64, 66, .90);

  /* Brand Black at the same .90, for bars that sit over photography rather
     than closing the page. This is what the footer used before the owner's
     black decision, and it exists so that decision cannot leak: the hero
     sector bar briefly turned black because it was reading --scrim-footer,
     and the owner asked for it back. Over a dark photograph this measures
     around rgb(60, 59, 61), 11.2 against white text. */
  --scrim-bar: rgba(65, 64, 66, .90);

  /* THE GLASS COPY BLOCK. Site owner, 26 Aug 2026.
     A panel that carries a page's own copy over a photograph, and is meant to
     be seen THROUGH rather than to cut the photograph out. It is the only role
     declared at the new .74 floor; the bars above it stay where they were
     measured. Pair it with --glass-blur, the same blur the top bar takes once
     it scrolls - the glass reading comes from the blur, and this alpha only
     lets enough of the photograph past for the blur to have something to show.

     --glass-fill-alpha is the same number as a percentage, for a surface that
     is not Brand Black. A subsidiary direction mixes its OWN brand colour to
     this alpha rather than borrowing the Black value:
       background: color-mix(in srgb, var(--brand-x) var(--glass-fill-alpha), transparent);
     Any colour other than Black at this alpha is a contrast question in its
     own right - the table above is Black's - and the answer belongs to the
     identity owner, not to the page reaching for it. */
  --scrim-glass: rgba(65, 64, 66, .74);
  --glass-fill-alpha: 74%;
}

/* The site is authored Arabic-first, but interaction follows the reader. The
   physical angle is needed because CSS gradients do not accept a logical
   inline-start direction. Reflecting the axis moves the first colour stop to
   the right in RTL; identity stripes intentionally keep their own angle. */
:root[dir="rtl"] {
  --motion-wipe-gradient-angle: calc(360deg - var(--brand-stripe-gradient-angle));
}

:root[dir="ltr"] {
  --route-card-wipe-angle: 315deg;
}
:root[dir="rtl"] {
   --route-card-wipe-angle-en: 315deg; /* أو الزاوية المعكوسة المناسبة لحركتك */
}
/* =============================================================================
   BRAND MODES
   -----------------------------------------------------------------------------
   One group system, many logo-led accents. A page opts in with `data-brand`
   on <html>; it must never redeclare this contract in its own CSS. Surfaces,
   spacing, radius, motion and focus geometry remain shared always - a
   subsidiary does not get its own corner radius or its own easing curve.

   Colour is wider than accent alone, since 15 Aug 2026: a brand mode may also
   set --ink (body/heading text), --line (borders and dividers), --on-action
   (the ink that sits on its own fill) and --identity-mark (the diagonal
   stripe/band/dot decoration the group draws in Liwa). What it may NEVER touch
   is one of the --brand-* primitives themselves - --identity-mark is read by
   name everywhere a decorative mark would otherwise read a primitive directly,
   precisely so a brand choosing its own accent still cannot redefine what a
   group colour means anywhere else on the site.

   --accent-hover needs no per-brand line: it is derived from --accent-fill, so
   each mode's second state is its own colour stepped by the same 30% of brand
   Black. Never write a darker hex for a brand's hover.

   Contrast determines the fill ink and the text ink alike, and the two are
   measured separately. Khadamat, Eskan, and Zoad use white on a dark logo
   colour for fills, and their own deep tone clears AA (8.87 / 6.23 / 12.60 on
   white) for body text too. Your Laundry's blue cannot carry white as a fill
   and cannot carry itself as text either (2.21 on white) - it keeps the group
   ink for --ink while --line and --identity-mark, which carry no text-
   contrast floor, still take its own blue. The lighter logo colours stay
   on-dark accents, never body text.
   ========================================================================== */
html[data-brand="khadamat"] {
  --accent: var(--brand-khadamat-green);
  --accent-fill: var(--brand-khadamat-green-deep);
  --accent-strong: var(--brand-khadamat-green-deep);
  --accent-text: var(--brand-white);
  --accent-deep: var(--brand-khadamat-green-deep);
  --accent-on-dark: var(--brand-khadamat-lime);
  --accent-wash: color-mix(in srgb, var(--brand-khadamat-green) 12%, transparent);
  --surface-brand: var(--brand-khadamat-green);
  /* The wipe surface must read DARKER than --accent-fill, or the shared
     .nav-cta hover wipe (sdeira-nav-controls.css) is invisible: it draws
     --surface-brand-deep over a --accent-fill button. It was mapped to the
     same Deep Forest as the fill (identical rgb(41,82,61), verified with
     getComputedStyle on the live page) so hovering produced zero visual
     change. Site owner, 3 Sep 2026: use the brand's own Olive Green
     darkened, the same 30%-black mix the group's default --accent-hover
     uses, rather than another primitive. */
  --surface-brand-deep: color-mix(in srgb, var(--brand-black) 30%, var(--brand-khadamat-green));
  --link-hover: var(--brand-khadamat-green-deep);
  --link-hover-on-dark: var(--brand-khadamat-lime);
  --badge-text: var(--brand-khadamat-green-deep);
  --badge-text-on-dark: var(--brand-khadamat-lime);
  --figure: var(--brand-khadamat-green-deep);
  --figure-on-dark: var(--brand-khadamat-lime);
  /* Moved here from khadamat-theme.css's `:root` on 4 Sep 2026, with that
     whole block. The group's own --icon-on-dark is Razeen, Sdeira's orange,
     and it is read directly by the footer's inline icons. One brand, one
     on-dark accent: the same Soft Sage every other on-dark role in this
     block already takes, rather than a second colour for icons alone. */
  --icon-on-dark: var(--brand-khadamat-lime);
  /* REVERSED, site owner, 31 Aug 2026: the 15 Aug 2026 decision below this
     comment used to give Khadamat's own green as --ink, reading through
     every heading and card title on the site (h1/h2/h3, .icon-item titles,
     the nav bar). Reviewed live and rejected: an accent colour carrying
     that much running text - every section heading, every card title - is
     not a pattern the design system recognises anywhere else on the site,
     and it reads as an unapproved deviation from both the group system and
     Khadamat's own brand guide (accent is 30% of the hierarchy, a highlight
     colour, not a body-text role). --ink now takes the group's own neutral,
     the same primitive Your Laundry already uses for the identical reason
     two brand blocks below (its own accent measuring 2.21:1 fails white
     text - Khadamat's measured contrast was fine at 8.87:1, but "reads
     AA/AAA" was never the objection here, "reads as green body copy" was).
     --accent/--accent-fill/--accent-strong above are UNCHANGED: the green
     stays the functional highlight - buttons, the active nav underline,
     hover states, icons - exactly where the brand guide's own 30% accent
     share says it belongs. */
  --ink: var(--brand-black);
  /* A light tint of the brand's own accent, not the group's warm neutral -
     30% mixed with white lands within .1 of the group's own --line contrast
     against white (1.45 group vs 1.67 here), so the border reads the same
     quiet weight it always has, just in the brand's hue. */
  --line: color-mix(in srgb, var(--accent) 30%, var(--brand-white));
  --identity-mark: var(--brand-khadamat-green);
}

html[data-brand="eskan"] {
  --accent: var(--brand-eskan-teal);
  --accent-fill: var(--brand-eskan-teal-deep);
  --accent-strong: var(--brand-eskan-teal-deep);
  --accent-text: var(--brand-white);
  --accent-deep: var(--brand-eskan-teal-deep);
  --accent-on-dark: var(--brand-eskan-sage);
  --accent-wash: color-mix(in srgb, var(--brand-eskan-teal) 12%, transparent);
  --surface-brand: var(--brand-eskan-teal);
  --surface-brand-deep: var(--brand-eskan-teal-deep);
  --link-hover: var(--brand-eskan-teal-deep);
  --link-hover-on-dark: var(--brand-eskan-sage);
  --badge-text: var(--brand-eskan-teal-deep);
  --badge-text-on-dark: var(--brand-eskan-sage);
  --figure: var(--brand-eskan-teal-deep);
  --figure-on-dark: var(--brand-eskan-sage);
  /* NOT the brand teal, corrected 3 Sep 2026, and this is the same decision
     the site owner already made on Khadamat on 31 August.

     Eskan's teal used to be --ink, so every heading, card title and nav item
     on a future Eskan site would have been set in it. Brand Guidelines V1
     p.17 says what the teal is for: Dark Cyan is "the main brand accent" at
     30% of the hierarchy, against Light Gray's 50% foundation. And p.60,
     writing the rule out directly for internal communications, says body
     copy on a white canvas must be the grey and titles "may use Dark Cyan
     for emphasis". Emphasis, not running text.

     Khadamat's block above records the owner's reasoning at length: an
     accent colour carrying every heading and card title is not a pattern
     this design system recognises, whatever it measures. Eskan's teal
     measured 6.15:1, comfortably legible, and legibility was never the
     objection. --accent and --accent-fill are unchanged: the teal stays the
     functional highlight, which is exactly the 30% share its own guide
     gives it. */
  --ink: var(--brand-black);
  --line: color-mix(in srgb, var(--accent) 30%, var(--brand-white));
  --identity-mark: var(--brand-eskan-teal);
}

/* Rebuilt 3 Sep 2026 from Brand Guidelines V4 p.19 and its applications
   chapter. Before this the whole mode was one colour - Blueberry in all
   sixteen roles - because only two Zoad primitives existed and one of them
   was unused. Every role below now follows a rule the guide states.

   THE ACCENT IS MASONRY OVEN, NOT BLUEBERRY, AND THE GUIDE SAYS SO TWICE.
   p.19: Masonry Oven is the 10% share, "a warm accent that adds distinction
   and emphasis", against Blueberry's dominant 50%. The applications chapter
   then pairs them consistently: business card, NFC card and letterhead all
   set Bold + Masonry Oven for the name and the subject line, and Regular +
   Blueberry for the body (p.35, p.36, p.38). Emphasis is warm; running text
   is navy.

   That same evidence answers --ink differently here than on Khadamat. The
   site owner ruled on 31 Aug that Khadamat's green must not carry running
   text, and the reasoning was that its guide calls green a 30% highlight.
   Zoad's guide does the opposite: Blueberry is the dominant colour and the
   stated colour of body copy. So Blueberry is --ink, and it is not the same
   decision reversed - it is the same rule, read off a different document. */
html[data-brand="zoad"] {
  /* Zoad V4 pp.19-23: its own palette and Arial in both languages. */
  --font-brand: Arial, sans-serif;
  --font-display: Arial, sans-serif;
  --brand-black: var(--brand-zoad-blueberry);
  --brand-jais: var(--brand-zoad-mackerel);
  --brand-light-grey: var(--brand-white);
  --brand-liwa: var(--brand-zoad-brown);
  --brand-razeen: var(--brand-zoad-yeast);
  --brand-sweihan: var(--brand-zoad-mackerel);
  --brand-al-khatim: var(--brand-zoad-cardamom);
  --surface-base: var(--brand-white);
  --surface-raised: var(--brand-white);
  --surface-neutral-soft: var(--brand-white);
  --surface-inverse: var(--brand-zoad-blueberry);
  --surface-inverse-soft: var(--brand-zoad-mackerel);
  --body-ink: var(--brand-zoad-blueberry);
  --muted: var(--brand-zoad-mackerel);
  --muted-on-dark: var(--brand-white);
  --scrim-nav: var(--brand-zoad-blueberry);
  --scrim-hero: var(--brand-zoad-blueberry);
  --scrim-control: var(--brand-zoad-blueberry);
  --scrim-footer: var(--brand-zoad-blueberry);
  --scrim-bar: var(--brand-zoad-blueberry);
  --scrim-glass: var(--brand-zoad-blueberry);
  --zoad-pattern-dots: url("../images/patterns/zoad-dots.svg?v=99e97e7b1b9b5032");
  --zoad-pattern-outline: url("../images/patterns/zoad-outline.svg?v=2e808a42b30d45f0");
  --zoad-pattern-mark: url("../images/patterns/zoad-corner.svg?v=3300614d291aee17");
  --zoad-pattern-mark-on-light: url("../images/patterns/zoad-corner-light.svg?v=ad4b266d5b118b57");
  --zoad-pattern-opacity: .12;
  --zoad-corner-opacity: .35;
  --zoad-card-corner-opacity: .16;
  --zoad-focus-on-light: var(--brand-zoad-blueberry);
  --zoad-focus-on-dark: var(--brand-white);
  --zoad-corner-width: 44%;
  --zoad-logo-height: 75px;
  --zoad-logo-height-mobile: 54px;
  --zoad-footer-logo-width: 100px;
  --zoad-dots-size: 480px 380px;
  --zoad-sector-card-surface: var(--brand-zoad-yeast);
  --zoad-assistant-emblem: url("../images/brand/zoad-assistant-emblem.svg?v=df85305ffc9480db");
  --zoad-logo-ratio: 1400 / 1627;
  /* Original halftone artwork, Brand Guidelines V4 p.30. */
  --brand-pattern-art: url("../images/patterns/zoad-dots.svg?v=99e97e7b1b9b5032");
  --brand-pattern-ratio: 480 / 380;
  /* Masonry Oven: white on it measures 7.74:1, clears AAA, so a filled
     button needs no darker variant. */
  --accent: var(--brand-zoad-brown);
  --accent-fill: var(--brand-zoad-brown);
  --accent-strong: var(--brand-zoad-brown);
  --accent-text: var(--brand-white);
  --accent-deep: var(--brand-zoad-brown);
  /* NOT Masonry Oven: it measures 1.56:1 on the Blueberry ground and is
     unreadable there. Yeast is the guide's own warm light tone, 10.85:1. */
  --accent-on-dark: var(--brand-zoad-yeast);
  --accent-wash: color-mix(in srgb, var(--brand-zoad-brown) 12%, transparent);
  /* The dominant 50%. Deep is the same 30%-black mix the group's default
     hover uses, so the shared .nav-cta wipe has something to draw. */
  --surface-brand: var(--brand-zoad-blueberry);
  --surface-brand-deep: var(--brand-zoad-blueberry);
  /* Hover steps from the warm accent to the dominant navy: a tone step, not
     a ninth colour, which is what the group's motion vocabulary allows. */
  --link-hover: var(--brand-zoad-blueberry);
  --link-hover-on-dark: var(--brand-white);
  --badge-text: var(--brand-zoad-brown);
  --badge-text-on-dark: var(--brand-zoad-yeast);
  /* A figure is a large quiet number, not an emphasis mark, so it takes the
     dominant colour rather than the 10% accent. 12.10:1. */
  --figure: var(--brand-zoad-blueberry);
  --figure-on-dark: var(--brand-zoad-yeast);
  /* Flour is the neutral ground. Secondary tones belong to card groups. */
  --surface-sunken: var(--brand-white);
  /* 12.10:1 on white - re-measured 3 Sep 2026 against the guideline's
     Blueberry. The 12.60 recorded here before was measured against #23344C,
     a value the guide does not contain. Still the most room of any brand. */
  --ink: var(--brand-zoad-blueberry);
  --line: color-mix(in srgb, var(--accent) 30%, var(--brand-white));
  --identity-mark: var(--brand-zoad-blueberry);
}

html[data-brand="laundry"] {
  --accent: var(--brand-laundry-blue);
  --accent-fill: var(--brand-laundry-blue);
  --accent-strong: var(--brand-laundry-blue);
  --accent-text: var(--brand-black);
  /* The one brand whose fill cannot carry white: 2.21 on the supplied blue,
     against 4.66 for black. Change the text, never darken a supplied colour. */
  --on-action: var(--brand-black);
  --accent-deep: var(--brand-laundry-blue);
  --accent-on-dark: var(--brand-laundry-beige);
  --accent-wash: color-mix(in srgb, var(--brand-laundry-blue) 12%, transparent);
  --surface-brand: var(--brand-laundry-blue);
  --surface-brand-deep: var(--brand-laundry-blue);
  --link-hover: var(--brand-black);
  --link-hover-on-dark: var(--brand-laundry-beige);
  --badge-text: var(--brand-black);
  --badge-text-on-dark: var(--brand-laundry-beige);
  /* Laundry blue is 2.21:1 on a white card; its feature glyphs use the same
     approved black role as figures so non-text contrast stays above 3:1. */
  --icon-feature-color: var(--brand-black);
  --figure: var(--brand-black);
  --figure-on-dark: var(--brand-laundry-beige);
  /* No --ink override here, on purpose: the supplied blue measures 2.21:1 on
     white, well under AA's 4.5 floor for body text - the same failure that
     already keeps this brand off white --accent-text above. Body copy stays
     the group's --brand-black rather than an illegible blue. Border and
     decorative-mark tone have no text-contrast floor, so those still take the
     brand's own colour. */
  --line: color-mix(in srgb, var(--accent) 30%, var(--brand-white));
  --identity-mark: var(--brand-laundry-blue);
}

/* =============================================================================
   SURFACE UTILITIES
   One class per rung of the ladder, so a section declares which surface it is
   instead of carrying a colour literal. Assigning backgrounds this way is what
   makes "no two adjacent sections are the same" checkable rather than hoped for.
   ========================================================================== */

.surface-base {
  background: var(--surface-base);
  color: var(--ink);
}

.surface-sunken {
  background: var(--surface-sunken);
  color: var(--ink);
}

.surface-sunken-strong {
  background: var(--surface-sunken-strong);
  color: var(--ink);
}

/* Kept as an alias of the grey canvas. The name says warm; there is no warm
   ground left. Do not give it a value of its own. */
.surface-warm {
  background: var(--surface-warm);
  color: var(--ink);
}

/* Liwa band. White on Liwa = 9.35. Razeen on Liwa = 4.73, which is the pairing
   the brand guide itself uses on pages 30 and 32. */
.surface-brand {
  background: var(--surface-brand);
  color: var(--on-action);
}

/* Black band. White = 10.31, Razeen = 5.22. */
.surface-inverse {
  background: var(--surface-inverse);
  color: var(--brand-white);
}

/* =============================================================================
   TYPE BASE
   Applied here rather than per page so all five documents inherit the same
   rules and none of them can quietly opt out.
   ========================================================================== */

html {
  /* Belt and braces alongside the weight-slot @font-face blocks above. If a
     weight is ever requested that has no matching face, this makes the browser
     fall back to the nearest real face instead of inventing one. Faux bold on a
     joined Arabic script distorts the letter joins. */
  font-synthesis: none;
  -webkit-font-synthesis: none;
}

body {
  font-family: var(--font-brand);
  /* Arabic is a joined cursive script. Positive tracking breaks the joins
     between letters, so it stays at zero everywhere, always. */
  letter-spacing: 0;
  /* DIN Next LT Arabic ships kerning and full GSUB/GPOS shaping. Both are on by
     default, but stating it means a later reset cannot silently disable them. */
  font-kerning: normal;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--font-display);
  letter-spacing: 0;
}

/* Reusable heading roles for every page that links this token file. */
.type-display { font-family: var(--font-display); font-size: var(--type-display); line-height: var(--leading-display); }
.type-section { font-family: var(--font-display); font-size: var(--type-section); line-height: var(--leading-section); }
.type-card { font-family: var(--font-display); font-size: var(--type-card); line-height: var(--leading-card); }
.type-label { font-family: var(--font-display); font-size: var(--type-label); line-height: var(--leading-label); }
.type-feature { font-family: var(--font-display); font-size: var(--type-feature); line-height: var(--leading-feature); }

/* Non-heading text roles. No font-family here: these are body copy and controls,
   which already inherit the brand face from the body rule above. */
.type-lead { font-size: var(--type-lead); line-height: var(--leading-lead); }
.type-body { font-size: var(--type-body); line-height: var(--leading-body); }
.type-action { font-size: var(--type-action); }
.type-control { font-size: var(--type-control); }
.type-meta { font-size: var(--type-meta); }
.type-caption { font-size: var(--type-caption); }

/* Role classes must beat the legacy `i.ph` page rules. The important flag is
   deliberately limited to an explicit system role, never a blanket icon rule. */
:is(i.ph, svg).icon-inline { inline-size: 1em; block-size: 1em; font-size: var(--icon-inline) !important; }
:is(i.ph, svg).icon-control { inline-size: 1em; block-size: 1em; font-size: var(--icon-control) !important; }
:is(i.ph, svg).icon-default { inline-size: 1em; block-size: 1em; font-size: var(--icon-default) !important; }
:is(i.ph, svg).icon-feature { inline-size: 1em; block-size: 1em; font-size: var(--icon-feature) !important; }
:is(i.ph, svg).icon-display { inline-size: 1em; block-size: 1em; font-size: var(--icon-display) !important; }

/* Footer contact rows are shared chrome. The icon is centered against the
   contact line so email, phone and address rows share one optical alignment. */
.foot-col .foot-contact {
  display: flex;
  align-items: center;
  gap: 10px;
  width: fit-content;
}

.foot-contact > .icon-inline {
  flex: 0 0 auto;
  color: var(--icon-on-dark);
}
