/* =========================================================================
 * DARWESH HOME -- ONE ROOM, ONE CAMERA
 * =========================================================================
 * Replaces css/home-luxury.css entirely. That file's failure was structural,
 * not decorative: it built the page out of ten sections, each painting its
 * own opaque background, which produced 5 hard seams, 2 flat colour bands
 * and 688px of dead gap. Measured, exactly 1 element in the whole page ever
 * crossed a section boundary -- so there was nothing to carry a visitor
 * across one.
 *
 * THE INVERSION. The page is no longer MADE OF sections. It is one
 * continuous environment, and the movements are content travelling through
 * it. Movements paint no background at all. There is therefore no y
 * coordinate at which a background can change: the seam count is zero by
 * construction rather than by tuning.
 *
 * Scoped to `body.world`. Nothing here applies to another page.
 * ========================================================================= */

body.world {
  /* --- DARK LUXURY REFINEMENT (color-only restyle, current pass) --------
     Replaces the light-luxury migration this comment used to describe.
     The world is dark-dominant again, but unlike the pre-light version,
     BOTH gradient recipes in .world-field below (.wf-navy / .wf-ivory)
     now come from the SAME dark family -- there is no true light/white
     zone left on Home at all. .wf-ivory keeps its old class name (it's
     an internal hook, not a rendered color) but now means "the lighter
     of two dark tiers" -- see the ramp comment further down for how that
     still produces real tonal separation (deep navy/obsidian vs. an
     elevated, slightly warmer dark) without ever going bright.
     Because neither tier is ever light, the --on-stone text-flip this
     file used to drive (ivory ink on navy vs. dark ink on white) has no
     light state left to flip to. It is pinned to a constant below (see
     --on-stone) rather than reworking every color-mix() call site that
     reads it -- each of those keeps its old two-branch shape, but the
     "on light" branch is now permanently unreachable, called out at each
     site rather than deleted, so this stays a color/token change and not
     a structural rewrite of section 2's rules.

     DARK LUXURY CORRECTION (follow-up pass): the dark family above was
     initially a navy one (#071D33/#0D263B/#132E45), which read as
     corporate blue at full-page scale -- flagged directly against the
     Intro overlay, which never used those values and reads correctly.
     Every hex below is now a genuinely neutral obsidian/charcoal instead
     (see css/cinematic.css's header comment for the full palette and
     its git-history/Intro provenance); "--w-navy" keeps its name only
     because renaming it would touch every consumer in this file for no
     visual gain -- the value itself is obsidian, never blue. */
  --w-navy:     #0B0E12;   /* Main Obsidian -- Hero / Services / Departure. Named --w-navy for
                              historical reasons only; the value is neutral obsidian, never navy
                              (see the DARK LUXURY CORRECTION note at the top of this file). */
  --w-navy-hi:  #111315;   /* Deep Charcoal -- soft/raised tier, gradients only */
  --w-ivory:    #1C1F21;   /* Card Surface Dark -- lightest dark tier (elevated-zone gradient top); NOT a light background any more */
  --w-ivory-lo: #17191B;   /* Elevated Dark -- elevated-zone gradient's deep stop */

  --w-gold:      #C69A4B;  /* Darwesh Gold */
  --w-gold-hi:   #D4AF60;  /* Soft Gold */
  --w-gold-deep: #8A6A38;  /* derived, darkened gold for gradient depth only -- not a brand value */

  /* Type reads against the world. Both tiers are dark now, so text stays
     in one family (warm ivory on dark) for the whole page -- see
     --on-stone below for how the old two-family flip is retired without
     touching every consumer of it. */
  --w-ink:      #F4EFE7;   /* Warm Ivory -- primary heading/label text */
  --w-ink-soft: #B8B0A5;   /* body copy on dark */
  --w-ink-dark: #0B0E12;   /* unreachable now (see --on-stone) -- kept only as color-mix()'s other argument */
  --w-ink-dark-mute: #111315; /* unreachable now (see --on-stone) -- same reason */

  --w-hair:     rgba(244, 239, 231, 0.16);
  --w-hair-warm:rgba(198, 154, 75, 0.28);

  --w-ease:     cubic-bezier(0.22, 0.61, 0.36, 1);
  --w-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --w-fast: 160ms;
  --w-mid: 340ms;
  --w-slow: 680ms;

  /* Depth. Three planes, assigned by MEANING (see §3), never by position. */
  --w-z-fore: 0px;
  --w-z-mid: -46px;
  --w-z-back: -130px;

  /* One spacing scale for the whole journey. */
  --w-air-s: clamp(1.5rem, 3vw, 2.5rem);
  --w-air-m: clamp(3rem, 6vw, 5.5rem);
  --w-air-l: clamp(5rem, 10vw, 9rem);

  background: var(--w-navy);
  color: var(--w-ink);
}
/* --journey is written by js/cine-scroll-3d.js onto the ROOT element: 0 at
   the top of the document, 1 at the bottom. Everything atmospheric derives
   from it. Declared here (not on body.world) so the JS value inherits down
   instead of being shadowed by a body-level declaration. */
:root { --journey: 0; }

/* =========================================================================
 * 1. THE PERSISTENT WORLD
 * =========================================================================
 * One fixed element. It never scrolls, never repeats, and has no edge
 * anywhere in the document, so no scroll position can reveal a boundary.
 *
 * DARK LUXURY REFINEMENT. Both recipes below, .wf-navy and .wf-ivory, are
 * now from the same dark family -- there is no light station left on this
 * page at all, so the failure mode the previous two rebuilds each fought
 * (a text-color flip has to cross a mid-grey-on-mid-grey band somewhere in
 * a light<->dark crossfade -- measured once at 1.19:1, far below AA) can't
 * occur here: headings and body copy stay Warm Ivory for the whole journey
 * (see --on-stone below, now a constant), so nothing about this ramp has
 * to protect a flip that no longer exists.
 *
 * That frees the ramp to go back to FIVE alternating zones purely for
 * background DEPTH -- "main background -> slightly lighter section ->
 * dark card -> photographic section -> deep navy CTA", per the dark-
 * luxury brief's tonal-separation requirement, rather than the single
 * end-loaded ramp the light-luxury pass used. Only two actual background
 * recipes exist, .wf-navy (deep navy/obsidian) and .wf-ivory (an
 * elevated, slightly warmer dark card tone -- see the token block above
 * for why that name is now misleading but kept), cross-added below via
 * the same "running difference of cumulative ramps" technique as before:
 * the four ramps t1..t4 are each a narrow, independent crossfade window,
 * and because every zone boundary uses the same width, opacity_navy +
 * opacity_ivory telescopes to exactly 1 at every point in the journey by
 * construction, so there is still no seam and no darken/bloom at a
 * handover -- only which dark tier is momentarily dominant changes. */
/* --t1..--t4 / --navy-mix live on body.world. They no longer strictly have
   to (--on-stone is a constant now -- see below -- so nothing on this same
   element reads --navy-mix back), but declaring them here still means
   every descendant that DOES read them (.world-field's children, several
   levels down) inherits the value with zero extra wiring, so there is no
   reason to move them. */
body.world {
  /* DARK LUXURY REFINEMENT: back to five alternating zones (as the
     original pre-light-luxury version used), retuned so the DEEP navy/
     obsidian tier dominates rather than the elevated one -- per the
     brief's "55-65% deep navy/obsidian, 20-25% elevated dark/navy
     surfaces" balance. Zones, by --journey position:
       navy    [0,     0.12]  Hero
       elevated[0.20,  0.32]  Intent
       navy    [0.40,  0.64]  City Discovery + Properties (dark-card zone)
       elevated[0.72,  0.84]  Installments / Trust
       navy    [0.92,  1.0 ]  Departure / Footer close
     with an 0.08-wide crossfade at each boundary (t1..t4). Pure-navy time
     (.44) plus half of the four transition bands (.16) is ~60% of the
     journey; pure-elevated (.24) plus its half-share (.16) is ~40% --
     comfortably inside the target range once real photography (Hero
     image, city/property cards) supplies its own share of warm-ivory
     brightness on top. */
  --t1: clamp(0, calc((var(--journey) - 0.12) / 0.08), 1);
  --t2: clamp(0, calc((var(--journey) - 0.32) / 0.08), 1);
  --t3: clamp(0, calc((var(--journey) - 0.64) / 0.08), 1);
  --t4: clamp(0, calc((var(--journey) - 0.84) / 0.08), 1);
  --navy-mix: calc(1 - var(--t1) + var(--t2) - var(--t3) + var(--t4));
}
.world-field {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--w-navy);
}
.world-field > i {
  position: absolute;
  inset: 0;
  display: block;
  will-change: opacity;
}

/* DEEP -- Hero, City Discovery + Properties, Departure/Footer close. The
   dominant recipe, ~60% of the journey per the token block's comment. */
.world-field .wf-navy {
  opacity: var(--navy-mix);
  background:
    radial-gradient(120% 80% at 76% 6%, rgba(198, 154, 75, 0.14), transparent 56%),
    radial-gradient(90% 70% at 8% 88%, rgba(11, 14, 18, 0.30), transparent 60%),
    linear-gradient(172deg, #17191B 0%, #111315 55%, #0B0E12 100%);
}
/* ELEVATED -- Intent, Installments/Trust. Still dark -- a lighter, warmer
   dark-card tone than .wf-navy, not a light background -- that's what
   gives the "slightly lighter section" tonal step the brief asks for. */
.world-field .wf-ivory {
  opacity: calc(1 - var(--navy-mix));
  background:
    radial-gradient(120% 80% at 76% 6%, rgba(198, 154, 75, 0.10), transparent 56%),
    radial-gradient(90% 70% at 8% 88%, rgba(11, 14, 18, 0.35), transparent 60%),
    linear-gradient(172deg, #1C1F21 0%, #17191B 55%, #141619 100%);
}
/* A single vignette over the whole world, so the frame is always held and
   the extremes always read as further away. Both tiers are dark now, so
   this no longer needs to hide during one of them -- it stays on
   throughout at a restrained baseline and only deepens a little further
   during the .wf-navy zones, which is the subtle "edge light" depth cue
   the brief asks to keep, not a giant vignette bloom. */
.world-field .wf-vignette {
  background: radial-gradient(118% 82% at 50% 44%, transparent 42%, rgba(4, 8, 14, 0.6) 100%);
  opacity: calc(0.3 + var(--navy-mix) * 0.3);
}

/* Everything the visitor reads sits above the world. */
body.world main { position: relative; z-index: 1; }

/* THE OLD SCENE SURFACES ARE SWITCHED OFF HERE.
   css/cinematic.css still gives every [data-cine3d] scene a lit surface
   plane and an atmosphere layer -- the mechanism from the previous two
   passes, which is exactly the bordered rounded rectangle per section that
   this redesign exists to remove. They are neutralised rather than deleted
   from that file because cinematic.css is shared with login/signup and the
   other cinematic pages, which still use them correctly. */
body.world [data-cine3d="rail"]::before,
body.world [data-cine3d="rail"]::after,
body.world [data-cine3d="cards"]::before,
body.world [data-cine3d="cards"]::after,
body.world [data-cine3d="hero"]::after { content: none; display: none; }

/* AND THE SECTION MOTION ITSELF -- the actual source of the shutter.
   css/cinematic.css gives every [data-cine3d] scene a scroll-driven
   transform on the SECTION:

     [data-cine3d="rail"], [data-cine3d="cards"] {
       transform: perspective(1400px)
         translate3d(0, calc(var(--e) * 90px - var(--l) * 70px),
                        calc(var(--d) * -220px))
         scale(calc(1 - var(--e) * 0.06 - var(--l) * 0.08));
       opacity: calc(1 - var(--e) * 0.25 - var(--l) * 0.5); }

   90px of vertical travel, 220px of Z and a 0.92 scale, applied to a whole
   <section>. That is a rectangular panel moving as one object, and it is
   the largest movement on the page by an order of magnitude -- the
   `.w-arrive` block motion this pass removed was only ever riding on top of
   it. Measured before this rule existed: a .mv still carried y=-26.6px
   z=-41.7px at scrollY 5562 even after every Home-side transform was gone.

   Neutralised here rather than deleted from css/cinematic.css, which is
   loaded by all 33 pages for its palette and is left untouched by this
   pass. (index.html is in fact the only page carrying a data-cine3d
   attribute, so these rules are now inert everywhere -- but removing them
   is a wider change than fixing Home's scroll, and this pass does not make
   it.) Home's motion comes from the flow system in section 3 and nowhere
   else: one mechanism, not two competing ones. */
body.world [data-cine3d],
body.world [data-cine3d="hero"],
body.world [data-cine3d="hero-media"],
body.world [data-cine3d="rail"],
body.world [data-cine3d="cards"],
body.world [data-cine3d="promo"] {
  transform: none;
  opacity: 1;
  isolation: auto;
}
/* Note on ordering: .w-hero-plane carries BOTH data-cine3d="hero-media" and
   data-flow="visual", and the two selectors have identical specificity, so
   the flow rule wins only because section 3 comes later in this file. Do not
   move this block below it. Nothing here touches `position`: the hero plane
   is absolutely positioned and resetting that would drop it out of its
   composition entirely. */

/* =========================================================================
 * 2. MOVEMENTS
 * =========================================================================
 * A movement has NO background, NO border and NO fill. It is a region of
 * the journey, not a box. It carries only its own scroll progress (--mp,
 * 0 entering -> 1 leaving, written by the shared controller) which its
 * contents use to arrive and recede.
 *
 * Movements deliberately OVERLAP: each pulls up into the previous one so
 * the outgoing composition is still on screen while the next arrives. That
 * overlap is what replaces the old 688px of dead gap. ==================== */
body.world .mv {
  /* A movement is a LAYOUT container and nothing else. It has no progress
     value, no transform, no opacity and no z-index -- deliberately, because
     every one of those turns it into an animated rectangle. The old --mp
     alias (of --section-progress) lived here and drove `.w-arrive` /
     `.w-recede` / `.w-carry`; all three now read their own element's --fp
     instead, so nothing on this page is animated per-section any more. */
  position: relative;
  padding-block: var(--w-air-l);
  /* THE OVERLAP. Each movement pulls up into the previous one so the
     outgoing composition is still on screen while the next arrives -- this
     is what replaced the old 688px of dead gap between sections.
     Eased from -2.1x to -1.4x. At -2.1x the overlap (185px at desktop
     width) exceeded the movement's own top padding (144px), so two content
     boxes genuinely intersected. That was survivable only while the
     controller was writing a z-index to decide which won -- which is
     exactly the mechanism that made sections look like panels sliding over
     each other, and it is gone. At -1.4x the AIR overlaps and the CONTENT
     never does, so there is still no gap and nothing to stack. */
  margin-block-start: calc(var(--w-air-m) * -1.4);
}
body.world .mv:first-of-type { margin-block-start: 0; }
body.world .mv-in {
  width: 100%;
  max-width: 1340px;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 4.5rem);
  position: relative;
}

/* Text tone no longer follows the world. --on-stone used to flip between
   "dark ink on a light ivory ground" (1) and "light ink on navy" (0) as
   --navy-mix crossed 0.5 -- see section 1's DARK LUXURY REFINEMENT comment
   for why that ground no longer exists: both .wf-navy and .wf-ivory are
   dark now, so there is nothing left to flip to. Pinned to a constant 0
   instead of deleting every color-mix() call site below: each keeps
   resolving to its var(--w-ink)/var(--w-ink-soft) ("light ink") branch
   for the whole journey, and the var(--w-ink-dark)/var(--w-ink-dark-mute)
   branch is unreachable dead code (documented at its declaration in the
   token block above), not a bug. */
body.world { --on-stone: 0; }
body.world .w-display,
body.world .w-h2,
body.world .w-h3 {
  color: color-mix(in oklab, var(--w-ink) calc((1 - var(--on-stone)) * 100%), var(--w-ink-dark));
}
body.world .w-body {
  color: color-mix(in oklab, var(--w-ink-soft) calc((1 - var(--on-stone)) * 100%), var(--w-ink-dark-mute));
}

/* The shadow exists for type sitting over photography (Hero), where it is
   still needed regardless of the world's own tone -- but a dark shadow
   under dark navy text on an ivory ground reads as a smudge, not depth.
   Scaled by (1 - on-stone) so it fades to nothing exactly where the text
   itself has flipped dark. */
body.world .w-display,
body.world .w-h2,
body.world .w-h3,
body.world .w-body {
  text-shadow: 0 1px 20px rgba(6, 6, 9, calc((1 - var(--on-stone)) * 0.72));
}
/* The eyebrow is the smallest, lightest-weight type on the page and often
   sits over a carried photograph, so it carries the strongest halo of
   anything here -- measured, not assumed. */
body.world .w-label {
  color: var(--w-gold-hi);
  /* Scaled by (1 - on-stone), same treatment as headings/body above: full
     halo over Departure's photography-on-navy, none over the light zones
     where a heavy black halo behind small gold caps would read as a smudge. */
  text-shadow:
    0 1px 3px rgba(6, 6, 9, calc((1 - var(--on-stone)) * 0.95)),
    0 2px 22px rgba(6, 6, 9, calc((1 - var(--on-stone)) * 0.85));
}

/* =========================================================================
 * 3. FLOW -- the scroll choreography. Elements, never sections.
 * =========================================================================
 * WHAT THIS REPLACED, AND WHY.
 *
 * The previous system animated whole compositions: `.w-arrive` moved an
 * entire copy block 34px up and 140px back in Z as one object, `.w-recede`
 * sank the outgoing half the same way, and js/cine-scroll-3d.js gave each
 * section a scroll-driven z-index so the centred one painted over its
 * neighbours. Every part of that was deliberate and every part of it was
 * wrong: a section is a rectangle, so animating one animates a rectangle,
 * and rectangles sliding over rectangles read as a shop shutter no matter
 * how gently they are eased. It is not a tuning problem. A block that moves
 * is a block you can see moving.
 *
 * So nothing here animates a `.mv`. Every value below is applied to a
 * single ELEMENT -- one headline, one photograph, one paragraph -- and each
 * element derives its progress from ITS OWN box (js/cine-scroll-3d.js's
 * flowProgress). Two elements in the same movement therefore hold different
 * progress at the same instant and drift apart by themselves. That is the
 * mechanism: no shared value exists for a block to move by.
 *
 * THE WINDOW. Each element's travel is split into three, and the middle one
 * is the point of the whole design:
 *
 *   0.06 -> 0.30   ENTER   rising in, becoming legible
 *   0.30 -> 0.74   HOLD    nothing moves at all
 *   0.74 -> 0.98   EXIT    going quiet as it leaves the top
 *
 * The hold spans the entire comfortable reading band, so text is never
 * moving or fading while it is being read -- the difference between motion
 * that supports content and motion that competes with it. The exit is what
 * keeps the promise that old content is gone before new content is
 * readable: an element only starts leaving once it is ~190px from the top
 * of the viewport, by which point the next one is still deep in its hold.
 *
 * --fp DEFAULTS TO 0.5, deliberately: mid-hold, fully settled, fully
 * opaque. If the controller never runs -- JS disabled, a script error, a
 * blocked file -- every element resolves to its finished state and the page
 * reads perfectly. An entrance animation must never be the thing that
 * decides whether content exists.
 *
 * The per-role windows below are overridden by setting --in-start/--in-len/
 * --out-start/--out-len rather than by redeclaring --in/--out. Custom
 * property substitution is lazy, so --in and --out resolve against whatever
 * the element's own window values are, and a role rule cannot lose a
 * specificity race with the base rule. ================================== */
body.world [data-flow] {
  --fp: 0.5;
  --fs: 0;            /* per-element stagger, in progress units (0 - 0.10) */
  --in-start: 0.06;
  --in-len: 0.18;
  --out-start: 0.74;
  --out-len: 0.24;
  --in:  clamp(0, calc((var(--fp) - var(--in-start) - var(--fs)) / var(--in-len)), 1);
  --out: clamp(0, calc((var(--fp) - var(--out-start)) / var(--out-len)), 1);
}

/* PRIMARY HEADLINE. Typography enters and leaves on its own account, with
   no scale and no depth -- letterforms distort under either and the eye
   reads it as the page zooming rather than as the line arriving. */
body.world [data-flow="lead"] {
  /* The 0.55 FLOOR on the entering half. A headline is content, not an
     effect: it must be legible from the moment it is on screen, and an
     entrance is a flourish on top of that. Measured on a 430px phone
     before this floor existed, "Verified & Recommended" sat at opacity
     0.887 while 70% of the way down the viewport -- already being read,
     still fading in. The floor applies only to the enter term, so the exit
     still takes it all the way down to 0.06 and old content really does
     leave. */
  opacity: calc((0.55 + var(--in) * 0.45) * (1 - var(--out) * 0.94));
  transform: translate3d(0, calc((1 - var(--in)) * 16px + var(--out) * -20px), 0);
}

/* PRIMARY VISUAL. The image changes crop and weight rather than travelling:
   a small scale either side of 1 plus a fade, which reads as the camera
   settling on it and then losing interest, not as a panel sliding past. */
body.world [data-flow="visual"] {
  opacity: calc((0.18 + var(--in) * 0.82) * (1 - var(--out) * 0.8));
  transform: translate3d(0, calc(var(--out) * -12px), 0)
             scale(calc(1.035 - var(--in) * 0.035 - var(--out) * 0.055));
}

/* SUPPORTING CONTENT. Leaves EARLIER than the headline it belongs to, so a
   composition empties in a considered order instead of all at once. */
body.world [data-flow="support"] {
  --out-start: 0.68;
  --out-len: 0.22;
  opacity: calc(var(--in) * (1 - var(--out)));
  transform: translate3d(0, calc((1 - var(--in)) * 12px + var(--out) * -14px), 0);
}

/* QUIET. Eyebrows, labels, fine print. Opacity carries almost all of it --
   these are the smallest type on the page and any real movement on them
   registers as flicker. */
body.world [data-flow="quiet"] {
  --in-len: 0.18;
  opacity: calc(var(--in) * (1 - var(--out)));
  transform: translate3d(0, calc((1 - var(--in)) * 8px), 0);
}

/* =========================================================================
 * 3b. DEPTH -- three planes, assigned by meaning
 * =========================================================================
 * FOREGROUND  the one thing to read or do now      -- full contrast, crisp
 * MIDGROUND   supporting content                   -- smaller, quieter
 * BACKGROUND  the world                            -- always soft
 * An element's plane is a property of WHAT IT IS, so depth encodes
 * hierarchy rather than decorating it.
 *
 * The Z distances are a third of what they were. At -140/-420px the
 * midground and background were far enough back to be read as separate
 * layers sitting behind the page -- literally "content stacked behind
 * content". Kept shallow, the same cue now reads as one composition with
 * soft and crisp parts, which is what depth is for here. ================ */
body.world .w-stage { perspective: 2600px; perspective-origin: 50% 46%; }
body.world .w-fore { transform: translateZ(var(--w-z-fore)); }
body.world .w-mid  { transform: translateZ(var(--w-z-mid)); opacity: 0.86; }
body.world .w-back { transform: translateZ(var(--w-z-back)); opacity: 0.62; filter: blur(1.1px); }

/* =========================================================================
 * 4. TYPOGRAPHY
 * ========================================================================= */
body.world .w-display {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: clamp(2.6rem, 6.6vw, 5.6rem);
  line-height: 1.01;
  letter-spacing: -0.032em;
  text-wrap: balance;
  margin: 0;
}
body.world .w-h2 {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: clamp(1.85rem, 3.6vw, 3.1rem);
  line-height: 1.08;
  letter-spacing: -0.024em;
  text-wrap: balance;
  margin: 0;
}
body.world .w-h3 {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: clamp(1.05rem, 1.6vw, 1.4rem);
  line-height: 1.22;
  letter-spacing: -0.01em;
  margin: 0;
}
body.world .w-body {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: clamp(0.975rem, 1.1vw, 1.1rem);
  line-height: 1.64;
  max-width: 58ch;
  margin: 0;
}
body.world .w-label {
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  margin: 0;
}
/* Sorani and Arabic are not English with other words in it: joined script
   sits lower, needs more leading, and is actively damaged by the tight
   negative tracking that makes the Latin display face architectural.
   js/i18n.js sets `dir` on <html>, never on <body> (see the note near
   .w-plane below) -- these rules were written as `body.world[dir="rtl"]`,
   which can never match, so they silently never applied: Kurdish/Arabic
   rendered at the Latin-tuned size/tracking/leading instead, which is
   what let the headline balloon past its intended box. Rescoped to
   `html[dir="rtl"] body.world`, the one selector shape that actually
   matches. */
html[dir="rtl"] body.world .w-display { letter-spacing: 0; line-height: 1.22; font-size: clamp(2.1rem, 5.4vw, 4.5rem); }
html[dir="rtl"] body.world .w-h2 { letter-spacing: 0; line-height: 1.34; }
html[dir="rtl"] body.world .w-h3 { letter-spacing: 0; line-height: 1.42; }
html[dir="rtl"] body.world .w-body { line-height: 1.9; }
/* Property Rails section headings -- a calmer marketplace scale than the
   shared .w-h2 (which is tuned for hero-adjacent sections like City
   Discovery/Services and stays untouched here). Scoped to its own class so
   only Premium/Affordable/Rent pick it up. Desktop ~30-36px, mobile
   ~24-28px, weight 600 -- a listing rail heading, not a hero headline. */
body.world .w-rail-title {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-weight: 600;
  font-size: clamp(1.75rem, 2.2vw, 2.25rem);
  line-height: 1.15;
  letter-spacing: -0.012em;
  text-wrap: balance;
  margin: 0;
}
@media (max-width: 767px) {
  body.world .w-rail-title { font-size: clamp(1.5rem, 6vw, 1.75rem); }
}
html[dir="rtl"] body.world .w-rail-title { letter-spacing: 0; line-height: 1.3; }
html[dir="rtl"] body.world .w-label { letter-spacing: 0.12em; }

/* =========================================================================
 * 5. CONTROLS
 * =========================================================================
 * The button system from the previous pass tested well and is kept -- the
 * objection this round was composition, not controls. What changes is
 * PLACEMENT (one action per movement) and that every variant now reads
 * against a world that changes tone underneath it. ===================== */
body.world .w-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.6em;
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.9375rem; font-weight: 700; line-height: 1;
  padding: 0.95rem 1.6rem; min-height: 48px;
  border-radius: 14px; border: 1px solid transparent;
  cursor: pointer; text-decoration: none;
  transition: background-color var(--w-fast) var(--w-ease), border-color var(--w-fast) var(--w-ease),
              color var(--w-fast) var(--w-ease), transform var(--w-fast) var(--w-ease);
}
body.world .w-btn-arrow { flex: none; transition: transform var(--w-mid) var(--w-ease-out); }
body.world .w-btn:hover .w-btn-arrow { transform: translateX(3px); }
html[dir="rtl"] body.world .w-btn-arrow { transform: scaleX(-1); }
html[dir="rtl"] body.world .w-btn:hover .w-btn-arrow { transform: scaleX(-1) translateX(3px); }

/* Primary inverts across the tonal journey so it is always the highest
   contrast object in the frame, on night and on stone alike. */
body.world .w-btn--primary {
  background: color-mix(in oklab, var(--w-gold) calc((1 - var(--on-stone)) * 100%), var(--w-ink-dark));
  color: color-mix(in oklab, #0B0E12 calc((1 - var(--on-stone)) * 100%), #F4EFE7);
}
body.world .w-btn--primary:hover { transform: translateY(-1px); filter: brightness(1.08); }
body.world .w-btn--primary:active { transform: translateY(0) scale(0.985); }
body.world .w-btn--ghost {
  background: transparent;
  color: color-mix(in oklab, var(--w-ink) calc((1 - var(--on-stone)) * 100%), var(--w-ink-dark));
  border-color: color-mix(in oklab, var(--w-hair) calc((1 - var(--on-stone)) * 100%), rgba(11,14,18,0.22));
}
body.world .w-btn--ghost:hover { border-color: var(--w-gold); }

body.world .w-action {
  display: inline-flex; align-items: center; gap: 0.5em; position: relative;
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: color-mix(in oklab, var(--w-gold) calc((1 - var(--on-stone)) * 100%), var(--w-gold-deep));
  text-decoration: none; padding: 0.6rem 0; min-height: 44px;
}
body.world .w-action::after {
  content: ''; position: absolute; inset-inline-start: 0; bottom: 0.4rem;
  width: 100%; height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: inline-start;
  transition: transform var(--w-mid) var(--w-ease-out);
}
body.world .w-action:hover::after { transform: scaleX(1); }
body.world .w-action svg { flex: none; transition: transform var(--w-mid) var(--w-ease-out); }
body.world .w-action:hover svg { transform: translateX(3px); }
html[dir="rtl"] body.world .w-action svg { transform: scaleX(-1); }

body.world .w-icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: none; border-radius: 50%;
  border: 1px solid color-mix(in oklab, var(--w-hair-warm) calc((1 - var(--on-stone)) * 100%), rgba(11,14,18,0.24));
  background: transparent;
  color: color-mix(in oklab, var(--w-ink) calc((1 - var(--on-stone)) * 100%), var(--w-ink-dark));
  cursor: pointer;
  transition: background-color var(--w-fast) var(--w-ease), border-color var(--w-fast) var(--w-ease), transform var(--w-fast) var(--w-ease);
}
body.world .w-icon-btn:hover { border-color: var(--w-gold); }
body.world .w-icon-btn:active { transform: scale(0.94); }
body.world .w-icon-btn svg { width: 18px; height: 18px; }
html[dir="rtl"] body.world .w-icon-btn svg { transform: scaleX(-1); }

body.world .w-btn:focus-visible,
body.world .w-action:focus-visible,
body.world .w-icon-btn:focus-visible,
body.world .w-intent-card:focus-visible,
body.world .w-plane:focus-visible,
body.world .w-svc-plane:focus-visible {
  outline: 2px solid var(--w-gold-hi); outline-offset: 3px;
}

/* =========================================================================
 * 6. HANDOFFS -- carry, answer, aperture, recede
 * =========================================================================
 * The four moves, defined once and reused. Every one of them is an element
 * that physically spans two movements, which is the mechanism by which the
 * "elements crossing a boundary" count rises from 1 to one-per-handoff. */

/* CARRY -- an image plane that begins in one movement and is still on
   screen, deeper and dimmer, in the next. Absolutely positioned so it can
   extend past its own movement's box without affecting layout. */
body.world .w-carry {
  position: absolute;
  pointer-events: none;
  z-index: -1;
  background-size: cover;
  background-position: center;
  border-radius: 20px;
  /* Soft parallax, which the brief allows, rather than a moving plane,
     which it does not. This used to sit between -260px and -420px in Z and
     travel -60px: at that distance it was a large perspective plane parked
     behind the page, exactly the "content stacked behind content" read.
     Now it drifts +-11px against the scroll with no Z at all -- the
     background stays visually connected across the two movements it spans,
     which is the whole point of a carry, without ever becoming an object
     you can see moving. Driven by the element's own --fp like everything
     else, so there is one motion system on this page, not two. */
  opacity: calc(0.10 + var(--in) * 0.10 - var(--out) * 0.14);
  /* Translate only, no scale. A scale of 1.02-1.04 on this plate pushed it
     16px past the document's right edge (measured) -- it is absolutely
     positioned and nothing clips it, and the -260px Z it used to sit at was
     what had been hiding that by shrinking it perspectively. Vertical
     parallax is the whole effect anyway; the scale added nothing. */
  transform: translate3d(0, calc((var(--fp) - 0.5) * -22px), 0);
  filter: blur(2px);
  -webkit-mask-image: radial-gradient(72% 72% at 50% 50%, #000 38%, transparent 78%);
          mask-image: radial-gradient(72% 72% at 50% 50%, #000 38%, transparent 78%);
}

/* ANSWER -- a hairline plus a line of type that leaves one movement and is
   picked up by the next. The rule itself spans the boundary. */
body.world .w-answer {
  position: absolute;
  inset-inline-start: clamp(1.25rem, 4vw, 4.5rem);
  bottom: calc(var(--w-air-m) * -1);
  height: calc(var(--w-air-m) * 2);
  width: 1px;
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--w-hair-warm) calc((1 - var(--on-stone)) * 100%), rgba(11,14,18,0.22)),
    transparent);
  z-index: -1;
  pointer-events: none;
}

/* APERTURE -- REMOVED, and the class kept only as a plain clipping box.
   It went through two versions and both were the same mistake twice. The
   first was a pair of opaque shutters that slid apart. The second replaced
   them with a scroll-driven radial mask, on the theory that a mask has no
   edges of its own so it could not read as a shutter. It still did: an
   opening that widens as you scroll is an iris, an iris is a door, and the
   content behind it is revealed rather than composed. "No visible edges" is
   not the same property as "does not behave like a shutter".
   What is left is a clip, so the gallery's own overflow stays contained on
   narrow viewports. Nothing about it is animated and no JS writes to it. */
/* min-width:0 undoes the grid item default (min-width:auto), which would
   otherwise size .w-city-layout's track -- and the whole page -- to fit
   .w-aperture's descendants' un-wrapped intrinsic content width (the classic
   CSS Grid/flex "blowout"). Harmless on the desktop cascade (every .w-plane
   there is position:absolute and contributes no intrinsic width at all);
   load-bearing on the mobile carousel, where .w-plane is a real in-flow flex
   item and this is what lets overflow-x:auto actually clip/scroll instead of
   the grid track stretching to fit all eight cards side by side. */
body.world .w-aperture { overflow: hidden; min-width: 0; }

/* =========================================================================
 * 7. ARRIVAL
 * ========================================================================= */
/* =========================================================================
 * VISUAL REBUILD (design-system Phase 2): centered full-bleed composition,
 * replacing the split dark-zone/image-plane layout above. Rebuilt to match
 * the approved Home reference -- a single continuous cinematic photograph
 * with a dark scrim for legibility, centered copy, no LTR/RTL mirroring
 * needed since a centered composition is symmetric by construction. The
 * photograph itself (URL, subject, licensing) is untouched -- only the
 * crop/position/overlay treatment around it changed. Real content below
 * the fold (Social Universe, Property Intent rail, Service Universe) is
 * out of scope for this pass and unchanged.
 * ========================================================================= */
/* .mv's own padding-block (var(--w-air-l), ~96-144px) is the spacing
   between ordinary movements -- correct for every section EXCEPT this
   first one, where it was leaving a dead gap of solid body background
   between the floating transparent navbar and the hero photograph (the
   navbar is `position:fixed` and genuinely transparent -- js/site-header.js
   -- so nothing was wrong there; the gap was this section starting 144px
   too low). Zeroing only the top half here lets .w-hero-plane's
   background-image reach true document y=0 and sit directly behind the
   glass rail, while padding-block-end (the gap before Quickstart) is
   untouched. */
body.world .mv[data-cine3d="hero"] { padding-block-start: 0; }
/* .mv-in (the shared content gutter every movement uses) was stacking its
   own padding-inline on TOP of .w-hero's own -- 40px effective margin on
   mobile instead of the intended 20px, squeezing the search input so
   narrow its placeholder text got clipped. .w-hero already establishes
   the hero's own gutter (its decorative elements are positioned relative
   to it), so .mv-in's copy of that padding is pure double-counting here. */
body.world .mv[data-cine3d="hero"] .mv-in { padding-inline: 0; }
body.world .w-hero {
  position: relative;
  min-height: clamp(640px, 92vh, 960px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: clamp(1.5rem, 6.5vw, 6.5rem);
  padding-block: clamp(6rem, 14vh, 9rem) clamp(3.25rem, 9vh, 6rem);
  overflow: hidden;
}
/* The hero photograph is a full-bleed plane, softly vignetted at the very
   edges only (not carved into a centered oval) so the whole frame reads as
   one continuous cinematic photograph behind the bottom-left copy block,
   matching the locked reference composition. */
body.world .w-hero-plane {
  position: absolute;
  inset: 0;
  z-index: -2;
  background-size: cover;
  background-position: center 40%;
  -webkit-mask-image: radial-gradient(ellipse 130% 120% at 50% 46%, #000 76%, transparent 100%);
          mask-image: radial-gradient(ellipse 130% 120% at 50% 46%, #000 76%, transparent 100%);
}
/* Dark scrim: a champagne-neutral obsidian gradient (never a flat black
   overlay). Symmetric now that the copy is centered rather than
   bottom-left -- a soft radial vignette darkens the middle where the text
   sits, plus a vertical gradient anchoring the base, with no left/right
   bias so it reads identically under LTR and RTL. */
body.world .w-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(ellipse 60% 56% at 50% 46%, rgba(9,11,14,.46) 0%, rgba(9,11,14,.22) 52%, rgba(9,11,14,0) 100%),
    linear-gradient(180deg, rgba(9,11,14,.24) 0%, rgba(9,11,14,.04) 24%, rgba(9,11,14,.1) 54%, rgba(9,11,14,.62) 100%);
  pointer-events: none;
}
/* Centered composition: one direction-agnostic anchor (align-items/
   justify-content: center) so a language switch (LTR <-> RTL) never
   shifts this block toward either edge -- flex-start/flex-end would
   flip automatically under dir="rtl" and move the whole hero sideways,
   which is exactly what this centered redesign must not do. */
body.world .w-hero-copy {
  position: relative;
  z-index: 1;
  max-width: 40rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  align-items: center;
  text-align: center;
  margin-inline: auto;
  /* Refinement-pass palette, local to the Hero only -- a softer, less
     yellow/mustard champagne than the site-wide --w-gold-hi token used
     elsewhere (chips, other section eyebrows, footer links), which stays
     untouched everywhere outside this block. */
  --hero-ivory: #F3EEE3;
  --hero-champagne: #D2B27A;
}
/* At the old 46rem measure "Building the Future." wrapped to a third
   line, turning a two-line brand statement into three and crowding the
   frame. The description/value-statement keep their own narrower reading
   widths (58ch / 38rem) below, so widening this outer box only gives the
   headline room -- it doesn't widen the body copy. */
/* Locked-reference headline: bold two-line sans, not the earlier serif
   pass -- the reference image is explicit and supersedes the prior
   editorial-serif decision for this one element. Scoped tightly to the
   hero headline only (other .w-h2/.w-h3 section headings elsewhere on
   this page are unaffected). Each line is a real, separately-colored
   child <span> already in the markup (data-i18n="index.heroLine1"/
   "index.heroLine2") holding plain semantic sentences -- no markup or
   presentation logic inside the translation strings. Wrapping is decided
   entirely by CSS below: one line per span on desktop where the content
   fits (nowrap), a balanced natural wrap on mobile. */
/* Inter at 500 (Medium) is what's actually loaded and rendering here --
   the earlier "Geist" mention in this file was aspirational, not true;
   Geist isn't served by the site's existing Google-Fonts-only pipeline
   (fonts.googleapis.com/fonts.gstatic.com) and no local Geist asset
   exists in the repo, so adding it would mean a new font host, out of
   scope for a typography-only pass. This is Inter, honestly. */
body.world .w-hero-copy .w-display {
  font-family: 'Inter', system-ui, sans-serif;
  font-weight: 500;
  font-size: clamp(2.4rem, 4.05vw, 4rem);
  line-height: 1;
  letter-spacing: -0.01em;
}
/* Desktop/tablet: each line stays on one visual line where space allows
   (verified to fit down to the 768px breakpoint for every shipped
   language's copy at this font size) -- "More Than Property." /
   "A Brighter Tomorrow.", not four lines. The span is free to grow wider
   than the centered column below it since flex align-items:center never
   clips a child to the container's own max-width. */
body.world .w-hero-copy .w-display span { white-space: nowrap; }
body.world .w-hero-copy .w-display [data-i18n="index.heroLine1"] { color: var(--hero-ivory); }
body.world .w-hero-copy .w-display [data-i18n="index.heroLine2"] { color: var(--hero-champagne); }
/* Kurdish/Arabic script reads better at zero tracking than the Latin
   negative letter-spacing above; weight matches the Latin treatment. */
html[dir="rtl"] body.world .w-hero-copy .w-display {
  font-weight: 500;
  letter-spacing: 0;
}
/* Description reads quieter than the headline -- lighter weight and a
   muted ivory/gray, never brighter than the headline -- with a measure
   narrower than .w-body's site-wide 58ch. Scoped to the hero only;
   .w-body itself (used by other movements on this page) is unchanged. */
body.world .w-hero-copy .w-body {
  max-width: 37rem;
  font-weight: 400;
  color: rgba(234, 229, 219, 0.68);
  font-size: clamp(0.9375rem, 0.85vw, 1rem);
  text-shadow: 0 1px 8px rgba(0,0,0,.35);
}

/* Eyebrow: softened to the same local champagne as the headline's second
   line, at reduced opacity so it supports the headline instead of
   competing with it -- scoped to the Hero only, the site-wide .w-label
   (used by other section eyebrows) is untouched. */
body.world .w-hero-copy .w-label {
  color: var(--hero-champagne);
  opacity: 0.75;
}

/* ---- Brand-forward hero content: CTA row, values row, supporting
   statement. These sit inside .w-hero-copy, below the headline/description,
   and are deliberately quiet -- the headline is still the loudest thing in
   the frame; this content earns its place through restraint, not size. ---- */
body.world .w-hero-cta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.85rem;
}
/* Narrower and slightly shorter than the site-wide .w-btn default (48px)
   so the CTAs read as supporting actions under the headline, not the
   loudest objects in the frame. */
body.world .w-hero-cta-row .w-btn { min-height: 45px; padding-inline: 1.35rem; font-size: 0.875rem; }
/* True-glass primary CTA -- near-zero fill, real blur, champagne carried
   only in the thin border/glow accent, never as an opaque colored fill --
   so the button reads as "this glass surface's action," not a separate
   solid gold disc dropped on top of it. */
body.world .w-hero-copy .w-btn--primary {
  background-color: transparent;
  background-image: linear-gradient(180deg, rgba(224,190,130,.14), rgba(224,190,130,.05));
  color: #F5EFE0;
  border: 1px solid rgba(224,190,130,.4);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08), 0 0 16px -8px rgba(224,190,130,.35);
  backdrop-filter: blur(20px) saturate(108%);
  -webkit-backdrop-filter: blur(20px) saturate(108%);
}
body.world .w-hero-copy .w-btn--primary:hover {
  filter: none;
  border-color: rgba(224,190,130,.62);
  box-shadow: 0 1px 1px rgba(0,0,0,.25), 0 12px 30px -10px rgba(0,0,0,.6), 0 0 28px -8px rgba(224,190,130,.42);
}
body.world .w-hero-copy .w-btn--ghost {
  background: rgba(255,255,255,.02);
  border-color: rgba(237,231,220,.22);
  backdrop-filter: blur(6px);
}
body.world .w-hero-copy .w-btn--ghost:hover { border-color: rgba(224,190,130,.55); background: rgba(224,190,130,.05); }
/* Quickstart's own Search button gets the same smoked-glass treatment as
   the hero's CTAs (not the site-wide solid .w-btn--primary) so the two
   sections read as one continuous premium system. */
body.world .w-quickstart .w-btn--primary {
  background:
    linear-gradient(180deg, rgba(224,190,130,.26), rgba(224,190,130,.09)),
    rgba(15,17,20,.78);
  color: #F5EFE0;
  border: 1px solid rgba(224,190,130,.42);
  box-shadow: 0 1px 1px rgba(0,0,0,.25), 0 8px 20px -10px rgba(0,0,0,.5), 0 0 18px -10px rgba(224,190,130,.3);
}
body.world .w-quickstart .w-btn--primary:hover { filter: none; border-color: rgba(224,190,130,.6); }
/* Thin divider lines flank the values row, per the reference composition
   -- kept as ::before/::after (not real child <span> elements) since
   js/i18n.js swaps this element's textContent directly on language
   change; a pseudo-element line can never be wiped out by that swap the
   way a child element would be. Fixed width (not flex-grow) so the
   lines read as a restrained flourish, not a stretched full rule. */
body.world .w-hero-values {
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--hero-champagne);
  opacity: 0.6;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.85rem;
}
body.world .w-hero-values::before,
body.world .w-hero-values::after {
  content: '';
  width: 2.25rem;
  height: 1px;
  flex: none;
  background: linear-gradient(90deg, transparent, rgba(224,190,130,.24));
}
body.world .w-hero-values::after { background: linear-gradient(90deg, rgba(224,190,130,.24), transparent); }
body.world .w-hero-value-statement {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 0.88rem;
  line-height: 1.7;
  color: #C9C2B6;
  opacity: 0.86;
  max-width: 44rem;
  margin: 0;
}
html[dir="rtl"] body.world .w-hero-values { letter-spacing: 0.1em; }
html[dir="rtl"] body.world .w-hero-value-statement { line-height: 1.9; }

/* ---- Quickstart: the stat strip + trust statement, relocated out of the
   hero into their own section directly beneath it so the hero's first
   three seconds stay about the brand, not property search. Deliberately
   NOT a panel: no background, border, blur or shadow -- the numbers,
   labels and paragraph float directly on the page's own graphite
   background, the same surface the hero sits on, not a card dropped on
   top of it. (A rounded glass panel lived here before; every property
   that built it -- background, border, backdrop-filter, box-shadow,
   border-radius, the tall padding that inflated this section's height --
   was removed outright rather than overridden, so nothing is hidden
   underneath.) */
body.world .w-quickstart {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  margin-top: calc(var(--w-air-s) * -0.6);
  padding-block: 0.5rem 1rem;
  padding-inline: var(--w-air-s);
}

/* Stats -> Social -> Intent rhythm. AUDIT (measured live via
   getBoundingClientRect(), not section boxes):
   Gap A (Stats -> Social) was 126px desktop / 94px mobile, made of two
   pieces: (1) the Social <section>'s own Tailwind `mt-10 md:mt-16` utility
   (40px/64px, a real adjacent-section margin -- Social is a plain
   <section>, not a `.mv` sibling, so it never adopted the shared overlap
   formula) and (2) ~54px/62px of orbit self-centering INSIDE .so-stage
   (stage height minus tile height, halved -- carousel geometry, never
   touched here, only measured to separate it from (1)). Only (1) is
   adjustable within this task's scope.
   Gap B (Social -> Intent) was actually NEGATIVE on desktop (-35px, real
   overlap) and undersized on mobile (24px against a 44-60px target).
   Root cause: .w-intent-section never got its own adjacent-pair override
   for this boundary (unlike its own City Discovery boundary, fixed
   earlier) -- it still uses the generic, unscoped `.mv` formula
   (margin-block-start: calc(var(--w-air-m) * -1.4) desktop /
   calc(var(--w-air-s) * -1) mobile, in the shared rule + the max-width:
   767px block later in this file). That generic pull-up assumes every
   sibling donates the same padding-based overlap room every other `.mv`
   pair does -- Social isn't a `.mv` section and doesn't, so the formula
   overshoots straight into Intent's own visible eyebrow text.
   Fix: two scoped adjacent-sibling overrides, layered on the EXISTING
   formulas via calc() (never replacing them with new ones), targeting
   only this one Stats->Social->Intent chain -- no other section's
   spacing changes. */
body.world .w-quickstart + section {
  margin-top: 1.375rem; /* 22px -- was the plain 4rem (64px) Tailwind utility */
}
body.world .w-quickstart + section + .w-intent-section {
  margin-block-start: calc(var(--w-air-m) * -1.4 + clamp(6.5rem, 7.7vw, 7.5rem));
}
@media (max-width: 767px) {
  body.world .w-quickstart + section {
    margin-top: 0.375rem; /* 6px -- was the plain 2.5rem (40px) Tailwind utility */
  }
  body.world .w-quickstart + section + .w-intent-section {
    margin-block-start: calc(var(--w-air-s) * -1 + clamp(1.5rem, 7vw, 2rem));
  }
}

/* ---- Property-type chips: quick, functional entry points into the real
   map.html filter (?types=house etc., the same URL param map.html's own
   readUrlStateIntoFilters() already reads for its Home Type drawer) --
   not decorative, every chip is a real working link. ---- */
body.world .w-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; margin-top: 0; }
/* Refinement pass: lighter, smaller compact filters -- near-transparent
   glass with a thin champagne hairline, not the earlier heavier tinted
   pill. Selected/hover state stays a subtle champagne emphasis, never a
   solid gold fill. */
body.world .w-chip {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 40px; padding: 0 14px; border-radius: 999px;
  background: rgba(255,255,255,.045);
  border: 1px solid rgba(224,190,130,.22);
  color: rgba(240,232,214,.88); font-family: 'Inter', system-ui, sans-serif; font-size: 0.875rem; font-weight: 500;
  text-decoration: none; backdrop-filter: blur(10px) saturate(120%);
  transition: border-color var(--w-fast) var(--w-ease), background-color var(--w-fast) var(--w-ease), transform var(--w-fast) var(--w-ease);
}
body.world .w-chip svg { width: 14px; height: 14px; flex: none; }
body.world .w-chip:hover { border-color: rgba(224,190,130,.42); background: rgba(224,190,130,.06); transform: translateY(-1px); }
body.world .w-chip:focus-visible { outline: 2px solid var(--w-gold); outline-offset: 2px; }

/* ---- Stat strip: real, honestly-stated counts only -- rendered by
   js loadHeroStats() below, three-state (loading/ready/error) exactly
   like this page's existing loadCityApartmentCounts(); never a
   fabricated number. Lives in the centered Quickstart band beneath the
   hero, so it's centered to match that band, not the (now removed)
   bottom-left hero composition. ---- */
body.world .w-stats { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(2rem, 5vw, 3.4rem); margin-top: 0.5rem; }
body.world .w-stat { text-align: center; min-width: 0; }
body.world .w-stat-num { display: block; font-family: 'Inter', 'IBM Plex Sans', system-ui, sans-serif; font-weight: 700; font-size: clamp(2.1rem, 2.6vw, 2.75rem); color: #F4EFE7; font-variant-numeric: tabular-nums; line-height: 1.05; }
body.world .w-stat-label { display: block; margin-top: 5px; font-family: 'Inter', system-ui, sans-serif; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: rgba(244,239,231,.62); }
body.world .w-stat-num.is-loading { color: #8F887C; }
/* The 2 stats with no real backend counter yet (Unique Visitors, Total
   Views -- see the markup comment above heroStats) get a dim numeral and
   a small champagne-tinted note instead of a fabricated number. Quiet by
   design: a whisper of the brand accent on one line of text, never a
   gold fill. */
body.world .w-stat--unavailable .w-stat-num { color: rgba(244,239,231,.26); }
body.world .w-stat-note { display: block; margin-top: 2px; font-family: 'Inter', system-ui, sans-serif; font-size: 0.62rem; font-weight: 500; letter-spacing: 0.03em; color: rgba(224,190,130,.55); }
/* Mobile-only alternates (see the mobile media query below): quiet
   utility rows in place of the big .w-stat--unavailable tiles, and a
   short trust statement in place of the full paragraph. Hidden here so
   desktop -- which keeps the original 5-stat row and full paragraph,
   completely untouched -- never renders them. */
body.world .w-stats-mini { display: none; }
body.world .w-hero-value-statement-short { display: none; }

/* =========================================================================
 * 8. INTENT -- a stable row of five compact cards
 * =========================================================================
 * Replaces the old "one wall, five panels" design (full-bleed vertical
 * strips that dramatically expanded on hover, collapsing their siblings).
 * That interaction and that height are both explicitly rejected by the
 * approved brief -- this section must read as a calm, premium selector,
 * not a giant gallery. Five equal-width landscape cards, gap between them
 * (not a hairline seam), each with its own radius/shadow/glass edge. The
 * row NEVER changes shape on hover/focus -- only the interacted card lifts
 * a few pixels, its own image zooms slightly, and its own arrow nudges --
 * every sibling stays exactly where it is. */
body.world .w-intent-row {
  display: flex;
  align-items: stretch;
  width: 100%;
  gap: clamp(12px, 1.4vw, 18px);
}
body.world .w-intent-card {
  position: relative;
  flex: 1 1 0%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  height: clamp(210px, 13vw, 230px);
  border-radius: 18px;
  overflow: hidden;
  text-decoration: none;
  padding: 1.1rem;
  border: 1px solid rgba(224, 190, 130, 0.22);
  box-shadow: 0 14px 30px -18px rgba(0,0,0,.6);
  transition: transform 200ms cubic-bezier(.2,.8,.2,1), border-color 200ms cubic-bezier(.2,.8,.2,1), box-shadow 200ms cubic-bezier(.2,.8,.2,1);
}
body.world .w-intent-media {
  position: absolute; inset: 0; z-index: 0;
  background-image: var(--img); background-size: cover; background-position: center;
  transform: scale(1.02);
  filter: saturate(0.82) contrast(1.03);
  transition: transform 220ms cubic-bezier(.2,.8,.2,1), filter 220ms cubic-bezier(.2,.8,.2,1);
}
body.world .w-intent-scrim {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(0deg, rgba(8,8,11,0.72) 0%, rgba(8,8,11,0.32) 38%, rgba(8,8,11,0) 68%);
}
body.world .w-intent-icon {
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; margin-bottom: 0.6rem;
  border-radius: 10px;
  border: 1px solid rgba(224,190,130,.32);
  background: rgba(224,190,130,.08);
  backdrop-filter: blur(6px);
  color: var(--w-gold-hi);
}
body.world .w-intent-icon svg { width: 17px; height: 17px; }
body.world .w-intent-body {
  position: relative; z-index: 2;
  display: flex; flex-direction: column; gap: 0.2rem;
}
body.world .w-intent-name {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-weight: 600; font-size: clamp(1.0625rem, 1.2vw, 1.25rem);
  letter-spacing: -0.01em; color: var(--w-ink);
}
body.world .w-intent-desc {
  display: flex; align-items: center; gap: 0.35rem;
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: clamp(0.8125rem, 0.9vw, 0.875rem); font-weight: 500; color: rgba(244,239,231,.72);
  transition: color 200ms cubic-bezier(.2,.8,.2,1);
}
body.world .w-intent-arrow {
  flex: none;
  transition: transform 200ms cubic-bezier(.2,.8,.2,1);
}
html[dir="rtl"] body.world .w-intent-arrow { transform: scaleX(-1); }
@media (hover: hover) {
  body.world .w-intent-card:hover,
  body.world .w-intent-card:focus-visible {
    transform: translateY(-4px);
    border-color: rgba(224,190,130,.5);
    box-shadow: 0 20px 38px -16px rgba(0,0,0,.66);
  }
  body.world .w-intent-card:hover .w-intent-media,
  body.world .w-intent-card:focus-visible .w-intent-media {
    transform: scale(1.08); filter: saturate(0.95) contrast(1.02);
  }
  body.world .w-intent-card:hover .w-intent-desc,
  body.world .w-intent-card:focus-visible .w-intent-desc {
    color: rgba(244,239,231,.92);
  }
  body.world .w-intent-card:hover .w-intent-arrow,
  body.world .w-intent-card:focus-visible .w-intent-arrow {
    transform: translateX(3px);
  }
  html[dir="rtl"] body.world .w-intent-card:hover .w-intent-arrow,
  html[dir="rtl"] body.world .w-intent-card:focus-visible .w-intent-arrow {
    transform: scaleX(-1) translateX(3px);
  }
}

/* Section-scoped overrides: this section must stay visibly more compact
   than the shared .mv/.w-h2 defaults (which are tuned for taller Home
   sections) -- an additive class on the element, rather than editing the
   shared rules, keeps every other Home section untouched. */
body.world .mv.w-intent-section { padding-block: clamp(4.5rem, 6vw, 5.5rem); }
body.world .w-intent-heading { font-size: clamp(2.1rem, 3vw, 2.5rem); font-weight: 600; }

/* =========================================================================
 * 9. CITY DISCOVERY -- compact editorial city cards
 * =========================================================================
 * Replaces the wide, eight-deep cascade (fanning out to thin, near-invisible
 * slivers, and a section tall enough to feel like a separate microsite) with
 * a tighter system: one large active card plus exactly one neighbour on
 * each side -- js/city-gallery.js's paint() writes a data-near="0|1" flag
 * per plane (near = |offset| <= 1) and everything past that is fully
 * hidden here, not just dim. Split composition unchanged: copy on one side,
 * cards on the other; arrows/dots/keyboard/click and the real
 * projects.html?city=X routes are the same existing controller, untouched.
 *
 * ON IMAGERY -- still the honest part, unchanged in substance. No file in
 * this repository is a real, licensed photograph of Kirkuk, Erbil, or any
 * of the other six cities -- there is no per-city photography asset to
 * draw from at all (confirmed: images/ holds only brand marks and flag
 * icons). Labelling a stock or placeholder image as a specific city would
 * be a false claim about a place, so a plane's face is still NOT a
 * photograph pretending to be that city -- per the brief's own fallback
 * ("use an honest neutral architectural/city placeholder rather than
 * misrepresenting another city"), it is a brighter, warmer composed
 * sky-and-skyline light-field, deterministic per city via --h/--s/--l, with
 * the city NAME as the dominant readable element. `--img` remains an
 * optional first layer: the day a real, licensed per-city photograph
 * exists, dropping it into that one custom property adopts it with no
 * other redesign. ========================================================= */
body.world .w-city-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr);
  gap: clamp(1.5rem, 3vw, 3rem);
  align-items: center;
}
body.world .w-city-copy { display: flex; flex-direction: column; gap: 0.6rem; }
body.world .w-city-copy .w-h2 { font-size: clamp(2.375rem, 3vw, 2.75rem); }
body.world .w-city-copy .w-body { font-size: clamp(0.9375rem, 1vw, 1rem); }
@media (max-width: 1023px) {
  body.world .w-city-layout { grid-template-columns: 1fr; }
  body.world .w-city-copy { margin-bottom: 1.25rem; }
  /* One column means the wall has no side column to fan into, so it goes
     back to fanning symmetrically from the centre. */
  body.world .w-gallery { justify-content: center; }
}

/* BUG FIX (not a redesign): flex-start anchored the active card (--o:0,
   translateX 0) to this container's own left edge, leaving zero room for
   the previous neighbour (negative --o, translateX negative) to peek --
   it always cascaded straight off the container's left boundary, fully
   hidden, at every index (not just index 0 -- confirmed live). Centering
   gives both the previous and next neighbour's translateX equal room
   inside this SAME container -- card width/height, step distance, scale,
   opacity, section spacing and the copy column are all untouched, so
   "previous + active + next" now shows the way the design already
   intended once neither side is starved of room. */
body.world .w-gallery {
  position: relative;
  min-height: clamp(430px, 30vw, 470px);
  display: flex; align-items: center; justify-content: center;
  touch-action: pan-y;
}
body.world .w-wall {
  position: relative;
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
}
body.world .w-plane {
  position: absolute;
  width: clamp(340px, 22vw, 380px);
  height: clamp(420px, 30vw, 460px);
  border-radius: 22px;
  overflow: hidden;
  text-decoration: none;
  box-shadow: 0 14px 30px -18px rgba(11, 14, 18, 0.3);
  /* --o is the plane's signed offset from the focused city, written by the
     gallery controller. Position is a pure function of it: a horizontal
     cascade, not a rotated wall. z-index falls with distance so the focused
     card always reads as the one in front. Anything past one step away is
     cut to opacity 0 by the data-near rule below -- editorial and compact,
     not a wall of cards disappearing into the dark.
     --dir flips the cascade's physical direction for RTL (below): raw
     translateX is never direction-aware on its own, and the gallery sits on
     the LEFT of its copy column in RTL (the grid's columns mirror with the
     page), so without this the cascade would shift cards rightward -- into
     the copy text -- instead of mirroring away from it as it does in LTR. */
  --dir: 1;
  --step: clamp(150px, 12vw, 220px);
  transform:
    translateX(calc(var(--o) * var(--step) * var(--dir)))
    translateY(calc(min(abs(var(--o)), 1) * 12px))
    scale(calc(1 - min(abs(var(--o)), 1) * 0.1));
  z-index: calc(30 - abs(var(--o)));
  opacity: calc(1 - min(abs(var(--o)), 1) * 0.42);
  transition: transform 240ms cubic-bezier(.2,.8,.2,1), opacity 240ms cubic-bezier(.2,.8,.2,1), box-shadow 240ms cubic-bezier(.2,.8,.2,1);
}
/* The hard cutoff: js/city-gallery.js's paint() sets data-near="0" once a
   plane is more than one step from focus. Past that point it must not just
   dim -- three cards read as "editorial and compact"; six or seven fading
   sliver still reads as the rejected wall. */
body.world .w-plane[data-near="0"] { opacity: 0; pointer-events: none; }
/* html[dir="rtl"], not body[dir="rtl"]: js/i18n.js only ever sets the dir
   attribute on document.documentElement, never on body (confirmed live --
   document.body.getAttribute('dir') is null even while its computed
   direction is rtl), so a body[dir="rtl"] selector here would silently
   never match. */
html[dir="rtl"] body.world .w-plane { --dir: -1; }
/* The face: a real, verified photograph of the city's own named landmark
   (--img, set per city in js/city-gallery.js), covering and centered like
   an <img object-fit:cover> would. The radial/linear gradient layers behind
   it are the pre-photo abstract fallback -- kept only so a plane with no
   `img` (should one ever be removed) still renders something coherent
   instead of a transparent hole; they're fully hidden under any real,
   opaque photo. */
body.world .w-plane-face {
  position: absolute; inset: 0; z-index: 0;
  background:
    var(--img, none),
    radial-gradient(48% 32% at 74% 16%, hsla(44, 78%, 91%, 0.6), transparent 72%),
    linear-gradient(180deg,
      hsl(var(--h) calc(var(--s) * 1%) calc(var(--l) * 1% + 50%)) 0%,
      hsl(calc(var(--h) - 6) calc(var(--s) * 1%) calc(var(--l) * 1% + 34%)) 48%,
      hsl(calc(var(--h) - 12) calc(var(--s) * 1%) calc(var(--l) * 1% + 16%)) 100%);
  background-size: cover;
  background-position: center;
}
/* Deep-graphite material, not a dark wash: a shorter, lighter bottom-only
   gradient so the photograph stays visible across the top two-thirds of the
   card -- "do NOT make cards overly dark; the city image must remain
   visible", per the brief -- while the name/CTA text over the bottom still
   has enough contrast to read. */
body.world .w-plane-scrim {
  position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(0deg, rgba(8, 10, 14, 0.74) 0%, rgba(8, 10, 14, 0.3) 36%, transparent 64%);
}
body.world .w-plane-body {
  position: absolute; inset-inline: 0; bottom: 0; z-index: 1;
  padding: 1.2rem 1.3rem 1.35rem;
  display: flex; flex-direction: column; gap: 0.35rem;
}
body.world .w-plane-name {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-weight: 600; font-size: 1.25rem; letter-spacing: -0.015em; color: #F4EFE7;
  text-shadow: 0 1px 12px rgba(4,4,7,0.9);
  transition: font-size 240ms cubic-bezier(.2,.8,.2,1);
}
body.world .w-plane-note {
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.625rem; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase;
  color: rgba(244,239,231,0.7);
}
/* Every visible card (active + the one neighbour on each side) shows its
   own CTA now -- with only 3 cards ever on screen, per-card clarity reads
   better than a reveal-on-select mechanic built for a wall of eight. */
body.world .w-plane-go {
  margin-top: 0.5rem; display: inline-flex; align-items: center; gap: 0.45rem;
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.04em;
  color: var(--w-gold-hi);
}
/* The focused card still carries the "obviously selected" language --
   forward (no offset/scale-down), a restrained shadow and a soft champagne
   ring -- just lighter than the old double-shadow + solid gold ring. */
body.world .w-plane[data-focus="1"] {
  opacity: 1;
  box-shadow: 0 22px 40px -18px rgba(11, 14, 18, 0.36), 0 0 0 1.5px rgba(224,190,130,.55) inset;
}
body.world .w-plane[data-focus="1"] .w-plane-name { font-size: 1.5rem; }
html[dir="rtl"] body.world .w-plane-go svg { transform: scaleX(-1); }

body.world .w-gallery-nav {
  display: flex; align-items: center; justify-content: center; gap: 0.9rem;
  margin-top: 1.25rem;
}
/* Replaces the old one-dot-per-city row (busy at 8 cities) with a compact
   "01 / 08" counter over a thin progress line -- still says exactly where
   you are and how many cities there are, without eight small targets. */
body.world .w-gallery-progress {
  display: flex; align-items: center; gap: 0.6rem;
  min-width: 120px;
}
body.world .w-gallery-count {
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.03em;
  font-variant-numeric: tabular-nums;
  color: color-mix(in oklab, var(--w-ink) calc((1 - var(--on-stone)) * 100%), var(--w-ink-dark));
  white-space: nowrap;
}
body.world .w-gallery-count [data-city-current] { color: var(--w-gold-hi); }
body.world .w-gallery-count-sep {
  margin-inline: 0.2em;
  color: color-mix(in oklab, var(--w-hair) calc((1 - var(--on-stone)) * 100%), rgba(11,14,18,0.32));
}
body.world .w-gallery-track {
  position: relative;
  width: 84px; height: 2px; border-radius: 2px;
  background: color-mix(in oklab, var(--w-hair) calc((1 - var(--on-stone)) * 100%), rgba(11,14,18,0.18));
  overflow: hidden;
}
/* inset-inline-start is a logical property -- it already resolves to the
   fill's growth-from-start edge in either direction (left in LTR, right in
   RTL) with no separate RTL rule needed. */
body.world .w-gallery-fill {
  position: absolute; inset-block: 0; inset-inline-start: 0;
  width: 0%; border-radius: 2px;
  background: var(--w-gold);
  transition: width 240ms cubic-bezier(.2,.8,.2,1);
}

/* Section-scoped padding: content-driven, not the taller shared .mv default
   -- an additive class on the element, rather than editing the shared rule,
   keeps every other Home section untouched. Tightened further than the
   Intent Selector above it (60-72px desktop / 48-52px mobile) per final
   polish -- no min-height/100vh anywhere in this section, so this padding
   plus the cards' own height is the section's entire vertical footprint. */
body.world .mv.w-city-section { padding-block: clamp(3.75rem, 5vw, 4.5rem); }
/* BUG FIX (not a spacing change): the shared .mv negative-margin overlap
   system (section 2 above, margin-block-start: calc(var(--w-air-m) * -1.4))
   assumes a section's own padding-block absorbs that pull-up so only AIR
   overlaps the next section, never content. This section's padding-block is
   intentionally shorter than the shared default (comment above), so the
   next .mv genuinely overlaps ~49px into this section's own box in normal
   layout -- verified live via getBoundingClientRect(). Being the later DOM
   sibling, it was painting over this section's Next/Prev arrows, silently
   eating real pointer clicks (the focus/counter logic itself was always
   correct -- programmatic .click() proved that). Giving this section a
   z-index (its .mv parent rule already sets position:relative) is a
   stacking-order-only fix: no padding, margin, size or position value
   changes, so nothing moves on screen -- it only decides which layer wins
   the pre-existing overlap band. */
body.world .mv.w-city-section { z-index: 2; }

/* Home section-spacing refinement: Intent Selector -> City Discovery sat at
   only ~37px of actual visible air on desktop (Intent's own narrowed 86px
   bottom padding + City's 72px top padding, minus the shared -1.4x overlap
   pull-up above) -- both sections read as visually correct on their own,
   but the boundary between them read as compressed/attached rather than as
   two distinct movements. Fixed the ONLY way that's safe here: adding to
   this ONE adjacent-sibling pair's margin-block-start (still ordinary
   margin flow, never absolute positioning), never touching either
   section's own approved internal layout, padding, or any other section's
   boundary. Every other Home section boundary was audited against this one
   and already lands in one of two consistent existing tiers (~95px where
   one side uses the narrower content-driven padding, ~167px where both
   sides use the shared default) -- this was the one real outlier.

   Micro-refinement pass: the first pass's delta (+72-96px desktop,
   +52-68px mobile) measured correct but slightly too generous once seen
   live next to the rest of the rhythm -- tuned down to a smaller clamp()
   so the two sections read as one continuous experience while keeping
   real breathing room (target ~100-108px desktop / ~88-96px mobile
   content gap, still layered on the existing overlap formula via calc(),
   never a from-scratch value). */
body.world .w-intent-section + .w-city-section {
  margin-block-start: calc(var(--w-air-m) * -1.4 + clamp(4rem, 4.5vw, 4.5rem));
  /* Nearly-invisible atmosphere, not a transition band -- a very faint,
     tightly-contained champagne/graphite glow that fades out well before
     it could read as a separate rectangular block or a hard edge. */
  background: radial-gradient(90% 38% at 50% 0%, rgba(198, 154, 75, 0.022), transparent 55%);
}
@media (max-width: 767px) {
  body.world .w-intent-section + .w-city-section {
    margin-block-start: calc(var(--w-air-s) * -1 + clamp(1.25rem, 4vw, 1.75rem));
  }
}

/* =========================================================================
 * 10. PROPERTIES, SERVICES, OFFER, DEPARTURE
 * =========================================================================
 * All open compositions. A property card keeps an edge because a listing IS
 * a discrete object; nothing else does. */

/* Quick Sale section atmosphere -- purely decorative, non-spacing (no
   margin/padding here, so every already-approved Stats/Social/Intent/
   City/Services/Property-Rails boundary in this file is untouched). A
   faint warm glow anchored behind the heading, same "atmosphere, not a
   rectangular block" language as the Intent->City transition above, plus
   a small pulse dot (aria-hidden, no new copy) next to the heading text
   as the section's only "live/urgent" cue -- no fabricated countdown or
   claim, since no such data exists on a listing. */
body.world .w-quicksale-section { position: relative; }
body.world .w-quicksale-section::before {
  content: ''; position: absolute; inset-block-start: 0; inset-inline: 10%; height: 60%;
  background: radial-gradient(60% 100% at 20% 0%, rgba(198, 154, 75, 0.05), transparent 70%);
  pointer-events: none; z-index: 0;
}
body.world .w-quicksale-section .mv-in { position: relative; z-index: 1; }
body.world .w-quicksale-heading { display: flex; align-items: center; gap: 0.6rem; }
body.world .w-quicksale-pulse {
  flex: none; width: 8px; height: 8px; border-radius: 999px;
  background: var(--w-gold-hi); box-shadow: 0 0 0 0 rgba(212, 175, 96, 0.55);
  animation: w-quicksale-pulse-ring 2.2s ease-out infinite;
}
@keyframes w-quicksale-pulse-ring {
  0% { box-shadow: 0 0 0 0 rgba(212, 175, 96, 0.55); }
  70% { box-shadow: 0 0 0 8px rgba(212, 175, 96, 0); }
  100% { box-shadow: 0 0 0 0 rgba(212, 175, 96, 0); }
}
@media (prefers-reduced-motion: reduce) {
  body.world .w-quicksale-pulse { animation: none; box-shadow: 0 0 0 3px rgba(212, 175, 96, 0.22); }
}

/* Desktop-only: tighten the Quick Sale -> Premium boundary specifically.
   Wrapped in min-width:768px because this selector's specificity (0,2,2)
   would otherwise beat the generic mobile ".mv" override (0,2,1) and leak
   into mobile spacing, which must stay untouched. */
@media (min-width: 768px) {
  body.world .w-quicksale-section + section {
    margin-block-start: calc(var(--w-air-m) * -1.4 - 24px);
  }
}

body.world .w-rail {
  display: flex; gap: clamp(1rem, 1.6vw, 1.5rem);
  overflow-x: auto; scroll-snap-type: x mandatory;
  /* Programmatic scrolls (the prev/next buttons, keyboard) glide instead of
     teleporting. Does not affect wheel/trackpad/touch input, which the
     browser drives natively. */
  scroll-behavior: smooth;
  /* A swipe that runs past the last card stays in the rail: it must not
     chain to the page (sideways page rubber-banding on iOS, back/forward
     navigation on some trackpads). */
  overscroll-behavior-x: contain;
  padding-block: 0.25rem 1rem; scrollbar-width: none;
}
body.world .w-rail::-webkit-scrollbar { display: none; }
body.world .w-rail > * { scroll-snap-align: start; }
/* The buttons already pass behavior:'auto' under reduced motion; this keeps
   the CSS layer agreeing, since 'auto' defers to scroll-behavior. */
@media (prefers-reduced-motion: reduce) {
  body.world .w-rail { scroll-behavior: auto; }
}

/* Desktop prev/next rail controls. Absolutely positioned against .mv-in
   (already position:relative) so they sit beside the section heading
   without taking part in its box model -- the heading's own layout and
   spacing are exactly what they were before these exist. Two PHYSICAL
   buttons (chevron always points the way it sits on screen); which scroll
   direction each performs is resolved live from text direction in
   js/carousel.js, so RTL is a behavior swap, never a mirrored icon.
   Mobile relies on swipe alone, per spec -- hidden below the same
   min-width:1024px cutoff already used elsewhere in this file (see the
   max-width:1023px rules above) for the tablet/desktop split. */
body.world .w-rail-nav { display: none; }
@media (min-width: 1024px) {
  body.world .w-rail-nav {
    display: flex; gap: 0.5rem;
    position: absolute; top: 0.2rem; inset-inline-end: 0;
  }
}
body.world .w-rail-nav .w-icon-btn:disabled {
  opacity: 0.35; cursor: default;
}
body.world .w-rail-nav .w-icon-btn:disabled:hover { border-color: inherit; }
/* Property Rails only: sit the prev/next controls INLINE next to "View
   All" in the same header row instead of absolutely-pinned top-right of
   the section (the default .w-rail-nav behavior above, kept as-is for
   Quick Sale so it is not touched). Modifier class, not a rule change --
   .w-rail-nav's own default stays exactly what it was. */
@media (min-width: 1024px) {
  body.world .w-rail-nav.is-inline { position: static; top: auto; inset-inline-end: auto; }
}

/* Empty state: no panel, no fill. Type and one action, standing in the
   world -- so zero listings looks composed rather than broken. */
body.world .w-empty {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1.1rem;
  padding-block: var(--w-air-s);
  max-width: 34rem;
}
body.world .w-empty-actions { display: flex; flex-wrap: wrap; gap: 0.8rem; }
body.world .w-rail:has(.w-empty) { display: block; overflow: visible; }
/* Property Rails' compact empty state (see renderPropertyRail's
   compactEmpty option) -- a small inline message, never the full .w-empty
   panel three times over. */
body.world .w-rail-empty-compact { padding-block: 1.1rem; max-width: 34rem; }
body.world .w-rail:has(.w-rail-empty-compact) { display: block; overflow: visible; }

/* Services: an editorial column of rules, not a grid of boxes. */
body.world .w-list { display: flex; flex-direction: column; }
body.world .w-row {
  display: flex; align-items: center; gap: 1.1rem;
  padding: 1.15rem 0;
  border-block-end: 1px solid color-mix(in oklab, var(--w-hair) calc((1 - var(--on-stone)) * 100%), rgba(11,14,18,0.14));
  text-decoration: none;
  transition: padding-inline-start var(--w-mid) var(--w-ease-out);
}
body.world .w-row:first-child { border-block-start: 1px solid color-mix(in oklab, var(--w-hair) calc((1 - var(--on-stone)) * 100%), rgba(11,14,18,0.14)); }
body.world .w-row:hover { padding-inline-start: 0.6rem; }
body.world .w-row svg:first-child { flex: none; color: var(--w-gold); }
body.world .w-row-text { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
body.world .w-row-cue { margin-inline-start: auto; flex: none; opacity: 0.5; transition: transform var(--w-mid) var(--w-ease-out), opacity var(--w-fast) var(--w-ease); }
body.world .w-row:hover .w-row-cue { opacity: 1; transform: translateX(4px); }
html[dir="rtl"] body.world .w-row-cue { transform: scaleX(-1); }
html[dir="rtl"] body.world .w-row:hover .w-row-cue { transform: scaleX(-1) translateX(4px); }

/* The offer's own design now lives in css/offer-banner.css, shared by
   Home, offer.html and the Admin preview. Home only has to give the
   component room. */
body.world [data-offer-section] { margin-bottom: var(--w-air-m); }

/* Trust: a typographic list. No hairline grid, no cells, no boxes. */
body.world .w-trust { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--w-air-s) clamp(1.5rem,4vw,3.5rem); }
body.world .w-trust-item { display: flex; flex-direction: column; gap: 0.5rem; max-width: 34ch; }
body.world .w-trust-item svg { color: var(--w-gold); }

/* Departure. Quiet: type and two actions standing in the dusk. */
body.world .w-departure { display: flex; flex-direction: column; align-items: flex-start; gap: 1.3rem; padding-block: var(--w-air-m); }
body.world .w-departure-actions { display: flex; flex-wrap: wrap; gap: 0.8rem; }

/* =========================================================================
 * SERVICES DISCOVERY -- cover-flow layered card system
 * =========================================================================
 * Card DATA/content is unchanged (real SERVICE_CATALOG, real routes, real
 * i18n) -- this pass changed only HOW the cards are presented. Desktop is
 * a true cover-flow: one fully-visible active card, its immediate
 * neighbours ~60% visible sitting partly behind it, and a further ring of
 * cards showing only a narrow sliver -- js/home-services.js's paint()
 * computes each card's --tx/--scale/opacity/filter/z-index per tier
 * (three discrete tiers, not one continuous falloff) directly in JS, read
 * against the card's own rendered width so the cascade stays
 * proportionally correct across its clamp() range.
 * A fully separate component from css/services-carousel.css (the shared
 * carousel services.html's own hero still uses, untouched) -- .w-svc-*
 * exists only here, on Home.
 * Card content lives entirely inside the card (image, small category
 * icon, title, one-line description, "Explore Service ->"), per the
 * original brief -- no duplicate title/description block below the
 * carousel. ============================================================ */
body.world .w-services-section .w-aperture { overflow: visible; }
body.world .w-svc-copy {
  max-width: 34rem; margin-inline: auto; margin-bottom: var(--w-air-s);
  display: flex; flex-direction: column; align-items: center; gap: 0.6rem;
  text-align: center;
}
body.world .w-svc-copy .w-h2 { font-size: clamp(2.375rem, 3vw, 2.75rem); }
body.world .w-svc-copy .w-body { font-size: clamp(0.9375rem, 1vw, 1rem); max-width: 44ch; margin-inline: auto; }

/* Stage wrap: positioning context for the two flanking arrows (desktop
   only -- see .w-svc-arrow below) and a width roughly matched to the
   cover-flow cluster's own footprint so the arrows sit just outside the
   outer sliver cards, not off at the far edges of the whole section. */
body.world .w-svc-stage-wrap { position: relative; max-width: 54rem; margin-inline: auto; }
body.world .w-svc-gallery {
  position: relative;
  min-height: clamp(410px, 28vw, 450px);
  display: flex; align-items: center; justify-content: center;
  touch-action: pan-y;
}
body.world .w-svc-wall {
  position: relative; width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  /* Gives the children's rotateY real foreshortening -- without a
     perspective on the shared parent, rotateY just skews the flat card
     rather than reading as depth along a curved path. */
  perspective: 1400px;
}
/* Rotating cover-flow: card content/data/routes are unchanged (see
   js/home-services.js) -- only HOW each card is placed changed. Position
   is a pure function of four JS-computed custom properties -- --tx
   (translateX), --ty (a small downward arc as cards move outward), --ry
   (rotateY, angling each card to face the centre) and --scale -- all
   already RTL-aware and pixel/degree-exact against the rendered card's
   own width, rather than a single --o formula:
     tier 0 (active):    --tx 0,   --ty 0,   --ry 0deg,        --scale 1    -- fully visible, facing forward
     tier 1 (neighbour):  ~60% of card width,  ~10px,  ~14deg (8deg mobile), --scale ~0.92 -- ~55-65% visible, curved inward
     tier 2 (outer):      a further ~25% beyond that, ~16px, ~22deg (14deg mobile), --scale ~0.85 -- narrow sliver
   opacity/z-index/filter are also set directly per card in paint() (not
   derived here), since three distinct tiers -- not one continuous
   falloff -- is what "far-left sliver -> partial -> ACTIVE -> partial ->
   far-right sliver" actually means. */
body.world .w-svc-plane {
  position: absolute;
  width: clamp(300px, 20vw, 330px);
  height: clamp(400px, 28vw, 440px);
  border-radius: 22px;
  overflow: hidden;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 14px 30px -18px rgba(11, 14, 18, 0.3);
  transform-style: preserve-3d;
  backface-visibility: hidden;
  transform: translateX(var(--tx, 0px)) translateY(var(--ty, 0px)) rotateY(var(--ry, 0deg)) scale(var(--scale, 1));
  transition: transform 300ms cubic-bezier(.2,.8,.2,1), opacity 300ms cubic-bezier(.2,.8,.2,1), filter 300ms cubic-bezier(.2,.8,.2,1), box-shadow 300ms cubic-bezier(.2,.8,.2,1);
}
/* A partially-visible card reading slightly clearer on hover/focus signals
   it's interactive (clicking it recentres it) without touching its
   JS-driven base opacity value, which stylesheet specificity can't safely
   override without !important against an inline style. */
body.world .w-svc-plane:not([data-focus="1"]):hover,
body.world .w-svc-plane:not([data-focus="1"]):focus-visible {
  filter: saturate(1) brightness(1.08) !important;
}
/* Photo-first: real service photography (--img, service-catalog.js's
   `photo` field) covers a muted, deterministic fallback gradient -- only
   ever visible on the two catalog entries with no photo yet (maintenance,
   installment). */
body.world .w-svc-plane-face {
  position: absolute; inset: 0; z-index: 0;
  background:
    var(--img, none),
    radial-gradient(48% 32% at 74% 16%, hsla(44, 78%, 91%, 0.55), transparent 72%),
    linear-gradient(180deg,
      hsl(var(--h) calc(var(--s) * 1%) calc(var(--l) * 1% + 46%)) 0%,
      hsl(calc(var(--h) - 6) calc(var(--s) * 1%) calc(var(--l) * 1% + 30%)) 48%,
      hsl(calc(var(--h) - 12) calc(var(--s) * 1%) calc(var(--l) * 1% + 14%)) 100%);
  background-size: cover;
  background-position: center;
}
/* Shorter, lighter bottom-only gradient (matches City Discovery's own
   correction): the photograph stays visible, not overly darkened, while
   the title/description/CTA over the bottom still read clearly. */
body.world .w-svc-plane-scrim {
  position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(0deg, rgba(8, 10, 14, 0.76) 0%, rgba(8, 10, 14, 0.32) 40%, transparent 66%);
}
/* Small category icon -- a restrained glass chip over the image, never a
   large centered emblem competing with the photograph. */
body.world .w-svc-plane-icon {
  position: absolute; inset-block-start: 0.9rem; inset-inline-start: 0.9rem; z-index: 1;
  width: 34px; height: 34px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(11,14,18,0.42);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(244,239,231,0.16);
}
body.world .w-svc-plane-icon .material-symbols-outlined { font-size: 18px; color: #F4EFE7; }
body.world .w-svc-plane-body {
  position: absolute; inset-inline: 0; bottom: 0; z-index: 1;
  padding: 1.2rem 1.3rem 1.35rem;
  display: flex; flex-direction: column; gap: 0.35rem;
}
body.world .w-svc-plane-title {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-weight: 600; font-size: 1.2rem; letter-spacing: -0.015em; color: #F4EFE7;
  text-shadow: 0 1px 12px rgba(4,4,7,0.9);
  transition: font-size 240ms cubic-bezier(.2,.8,.2,1);
}
body.world .w-svc-plane-desc {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 0.8rem; line-height: 1.42;
  color: rgba(244,239,231,0.78);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
body.world .w-svc-plane-go {
  margin-top: 0.35rem; display: inline-flex; align-items: center; gap: 0.45rem;
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.04em;
  color: var(--w-gold-hi);
}
body.world .w-svc-plane[data-focus="1"] {
  opacity: 1;
  box-shadow: 0 22px 40px -18px rgba(11, 14, 18, 0.36), 0 0 0 1.5px rgba(224,190,130,.55) inset;
}
body.world .w-svc-plane[data-focus="1"] .w-svc-plane-title { font-size: 1.4rem; }
html[dir="rtl"] body.world .w-svc-plane-go svg { transform: scaleX(-1); }
/* .w-svc-arrow-prev/-next swap physical sides under RTL via their own
   inset-inline-start/end (logical, no override needed) -- but each
   button's chevron SVG path is a fixed shape, so without this it stays
   pointing the same screen direction after the swap and both arrows end
   up pointing inward at each other instead of outward, away from the
   card cluster, same fix already applied to .w-svc-plane-go above. */
html[dir="rtl"] body.world .w-svc-arrow svg { transform: scaleX(-1); }

body.world .w-svc-gallery-nav {
  display: flex; align-items: center; justify-content: center; gap: 0.9rem;
  margin-top: 1.25rem;
}
/* "01 / 07" counter + thin progress line -- same cleaner interaction
   language as City Discovery's pagination, not a row of tiny dots. */
body.world .w-svc-gallery-progress { display: flex; align-items: center; gap: 0.6rem; min-width: 120px; }
body.world .w-svc-gallery-count {
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.03em;
  font-variant-numeric: tabular-nums;
  color: color-mix(in oklab, var(--w-ink) calc((1 - var(--on-stone)) * 100%), var(--w-ink-dark));
  white-space: nowrap;
}
body.world .w-svc-gallery-count [data-svc-current] { color: var(--w-gold-hi); }
body.world .w-svc-gallery-count-sep { margin-inline: 0.2em; color: color-mix(in oklab, var(--w-hair) calc((1 - var(--on-stone)) * 100%), rgba(11,14,18,0.32)); }
body.world .w-svc-gallery-track {
  position: relative; width: 84px; height: 2px; border-radius: 2px;
  background: color-mix(in oklab, var(--w-hair) calc((1 - var(--on-stone)) * 100%), rgba(11,14,18,0.18));
  overflow: hidden;
}
/* inset-inline-start is a logical property -- already resolves to the
   fill's growth-from-start edge in either direction, no separate RTL
   rule needed (see City Discovery's identical .w-gallery-fill comment). */
body.world .w-svc-gallery-fill {
  position: absolute; inset-block: 0; inset-inline-start: 0;
  width: 0%; border-radius: 2px; background: var(--w-gold);
  transition: width 240ms cubic-bezier(.2,.8,.2,1);
}

/* Flanking arrows -- outside the left/right edge of the card cluster,
   never on top of the active card. Desktop/tablet only (mobile hides
   these below and relies on native swipe, per spec); reuses the existing
   .w-icon-btn circular chrome, just repositioned via these two classes. */
body.world .w-svc-arrow { display: none; }
@media (min-width: 768px) {
  body.world .w-svc-arrow {
    display: inline-flex;
    position: absolute; top: 50%; transform: translateY(-50%);
    z-index: 40;
  }
  body.world .w-svc-arrow-prev { inset-inline-start: 0; }
  body.world .w-svc-arrow-next { inset-inline-end: 0; }
}

/* Section-scoped padding: content-driven, same 60-72px desktop / 48-52px
   mobile rhythm as City Discovery immediately above it -- no min-height,
   no 100vh, so this padding plus the cards' own height is the section's
   entire vertical footprint. */
body.world .mv.w-services-section { padding-block: clamp(3.75rem, 5vw, 4.5rem); }

/* Services -> Trust boundary: after removing the Home-only MAM AI CTA row,
   the visible gap here measured ~180-250px -- not from this pair's own
   margin formula (same shared -1.4x overlap every .mv pair uses) but from
   the Trust section's OWN .w-trust inline margin-top:var(--w-air-m),
   which exists to separate Trust from the live offer banner ABOVE it
   inside that same section -- when no offer is currently published
   (data-offer-section stays hidden, the common case), that margin has
   nothing above it to separate from and just stacks onto this section
   boundary's own padding/overlap instead. Never touching that inline
   margin (Trust's own internal spacing, out of scope) -- this ONE
   adjacent-pair override pulls the next section up by exactly that much
   extra, landing the real visible content gap back in the intended
   ~80-110px desktop / ~56-80px mobile range. */
body.world .w-services-section + section {
  margin-block-start: calc(var(--w-air-m) * -1.4 - clamp(3.25rem, 6vw, 5.25rem));
}
@media (max-width: 767px) {
  /* Mobile's own base .mv formula here is a DIFFERENT one (--w-air-s * -1,
     not --w-air-m * -1.4 -- see the max-width:767px block later in this
     file) -- building the same desktop pattern on the wrong base term is
     what overshot the reduction on the first pass (measured 33px against
     a 56-80px target). Layering on the correct mobile base instead. */
  body.world .w-services-section + section {
    margin-block-start: calc(var(--w-air-s) * -1 - clamp(3rem, 10vw, 3.75rem));
  }
}

/* =========================================================================
 * 11. HEADER
 * ========================================================================= */
/* DARK LUXURY CORRECTION: js/site-header.js's own header is neutral
   obsidian (#0B0E12) on every page including Home, so this file no
   longer needs to override it at all. The bottom nav (mobile only) gets
   the same treatment here -- a translucent Elevated Dark instead of the
   original white bar (or the navy this file briefly used), so Home's
   mobile chrome matches the rest of the obsidian site. */
body.world .home-bottomnav {
  background: rgba(17, 25, 27, 0.92);
  backdrop-filter: blur(16px);
  border-top: 1px solid #2B2C2A;
}

/* =========================================================================
 * 12. MOBILE -- the same world, fewer planes
 * =========================================================================
 * Not a stacked desktop. Continuity, atmosphere, hierarchy and every
 * handoff survive; what reduces is plane count (2 not 3), travel distance,
 * and blur cost. The world becomes ABSOLUTE rather than FIXED: a fixed
 * full-viewport painted layer is the classic mobile scroll-jank trap, and
 * the continuity requirement is satisfied either way because the element
 * still has no edge inside the document. ============================== */
@media (max-width: 767px) {
  body.world .world-field { position: absolute; height: 100%; }
  body.world .world-field > i { position: fixed; }
  body.world .wf-vignette { display: none; }

  body.world .mv { padding-block: var(--w-air-m); margin-block-start: calc(var(--w-air-s) * -1); }
  body.world .w-hero {
    min-height: 92svh;
    padding-inline: 1.25rem;
    padding-block: 6.5rem calc(3.5rem + env(safe-area-inset-bottom, 0px));
  }
  /* Centered composition, same taller/wider mask as before so the
     photograph still reads clearly with no side column to fall back on. */
  body.world .w-hero-plane {
    -webkit-mask-image: radial-gradient(ellipse 160% 100% at 50% 42%, #000 70%, transparent 100%);
            mask-image: radial-gradient(ellipse 160% 100% at 50% 42%, #000 70%, transparent 100%);
  }
  /* Mobile hero refinements: the headline was reading too tall/crowded --
     a smaller clamp ceiling + tighter line-height keeps the two-line
     headline from wrapping into a third, and every element below gets
     its own considered spacing rather than one flat gap, so nothing
     feels stacked on top of its neighbour. */
  /* Final polish: even tighter inter-group rhythm than the previous
     pass -- the whole block reads noticeably smaller now, so 12-18px
     compounded was starting to feel loose again. */
  body.world .w-hero-copy { gap: 0.7rem; max-width: 100%; }
  body.world .w-hero-copy .w-display { font-size: clamp(2.125rem, 7.4vw, 2.375rem); line-height: 1.03; }
  /* Mobile: let each line wrap again (desktop's nowrap is undone here),
     and balance the break instead of the browser's default greedy wrap
     -- "More Than" / "Property." rather than an uneven "More" / "Than
     Property.". text-wrap:balance degrades gracefully to a normal wrap
     in browsers that don't support it yet. */
  body.world .w-hero-copy .w-display span { white-space: normal; text-wrap: balance; }
  /* Slightly warmer + higher-contrast than the desktop dimming without
     going bright white -- still clearly secondary to the headline. */
  body.world .w-hero-copy .w-body { max-width: 20.5rem; font-size: 0.85rem; line-height: 1.56; color: rgba(236,231,221,.72); }
  /* Property-type chips: a 2x2 grid reads calmer at phone width than a
     4-up row that would wrap unevenly -- sized as compact filter controls,
     not full-width CTAs. */
  body.world .w-hero-copy .w-chips { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.45rem; width: 100%; max-width: 19rem; }
  body.world .w-hero-copy .w-chip { justify-content: center; min-height: 35px; padding-inline: 10px; font-size: 0.8rem; }
  /* CTAs stack full-width so both read as equally reachable, one-thumb
     targets rather than a cramped side-by-side pair. */
  body.world .w-hero-cta-row { flex-direction: column; width: 100%; max-width: 19rem; gap: 0.55rem; }
  body.world .w-hero-cta-row .w-btn { width: 100%; justify-content: center; padding-inline: 1.2rem; min-height: 43px; font-size: 0.85rem; }
  body.world .w-hero-values { font-size: 0.625rem; letter-spacing: 0.18em; }
  body.world .w-quickstart { margin-top: calc(var(--w-air-s) * -0.35); padding-block: 0.35rem 0.9rem; padding-inline: 1rem; }
  /* Calm mobile summary, not a mini dashboard: single row of only the 3
     REAL stats (unavailable tiles hidden entirely, not just restyled --
     see .w-stat--unavailable below), smaller numbers than desktop,
     more horizontal room, less vertical stacking than the old 2/2/1
     five-stat grid. */
  body.world .w-quickstart .w-stats {
    display: grid; grid-template-columns: repeat(3, 1fr);
    column-gap: 0.6rem; row-gap: 0;
    justify-items: center; width: 100%; max-width: 24rem; margin-inline: auto;
  }
  body.world .w-quickstart .w-stats .w-stat--unavailable { display: none; }
  body.world .w-quickstart .w-stat-num { font-size: 2rem; }
  body.world .w-quickstart .w-stat-label { font-size: 0.66rem; }
  /* Visitor/Views analytics: two quiet utility rows, not stat tiles --
     no big number, no loading state, just a label and a "Soon" note. */
  body.world .w-quickstart .w-stats-mini {
    display: flex; flex-direction: column; align-items: center; gap: 4px;
    width: 100%; max-width: 20rem; margin-inline: auto;
  }
  body.world .w-quickstart .w-stats-mini-row { display: flex; align-items: center; justify-content: center; gap: 6px; }
  body.world .w-quickstart .w-stats-mini-label {
    font-family: 'Inter', system-ui, sans-serif; font-size: 10px; font-weight: 500;
    letter-spacing: 0.02em; color: rgba(244,239,231,.4);
  }
  body.world .w-quickstart .w-stats-mini-sep { font-size: 10px; color: rgba(244,239,231,.22); }
  body.world .w-quickstart .w-stats-mini-soon {
    font-family: 'Inter', system-ui, sans-serif; font-size: 9.5px; font-weight: 600;
    letter-spacing: 0.03em; text-transform: uppercase; color: rgba(224,190,130,.5);
  }
  /* Short trust statement takes over from the fuller paragraph on
     mobile only -- desktop's .w-hero-value-statement is untouched. */
  body.world .w-quickstart .w-hero-value-statement { display: none; }
  body.world .w-quickstart .w-hero-value-statement-short {
    display: block; font-family: 'Inter', system-ui, sans-serif;
    font-size: 0.82rem; line-height: 1.6; color: #C9C2B6; opacity: 0.86;
    max-width: 30ch; margin-inline: auto;
  }
}

/* Short mobile screens (iPhone SE and similar, <=700px tall): the fixed
   86px bottom nav budget above doesn't shrink with the viewport, so on a
   short screen the stat row's own bottom edge was landing right against
   it with almost no breathing room. Compresses the space ABOVE the stats
   (hero top padding, inter-element gaps) rather than touching the fixed
   safe-area clearance itself, so the stats/dots/scroll-cue keep the same
   guaranteed distance from the bottom nav regardless of screen height. */
@media (max-width: 767px) and (max-height: 700px) {
  body.world .w-hero { padding-block-start: 3.5rem; }
  body.world .w-hero-copy { gap: 0.6rem; }
  body.world .w-hero-copy .w-display { font-size: clamp(1.9rem, 7.5vw, 2.25rem); }
  body.world .w-hero-copy .w-body { -webkit-line-clamp: 2; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
}

@media (max-width: 767px) {
  /* Intent: a dedicated mobile design, not a shrunk desktop row -- a
     horizontal snap carousel where each card shows enough of its neighbour
     that the row visibly continues off-screen, inviting a swipe. Every
     card keeps its full content (image, icon, title, sentence, arrow)
     inside itself; nothing is tracked or toggled by JS. */
  /* Real fix for the fixed .dmnav-bar visually sitting over the first
     card's text/CTA on ordinary touch scroll (the scroll-margin rule
     below only ever helped assisted/keyboard scrolling, not this).
     ROOT CAUSE, measured live via getBoundingClientRect() at 390x844:
     at the natural resting point where the row has just fully scrolled
     into view (row's own bottom edge flush with the viewport bottom --
     not a contrived edge case, the first moment a user sees the whole
     row), the first card's .w-intent-body sat ~49px INTO the nav's
     reserved zone (10px offset + 76px height = 86px, from
     css/cinematic.css .dmnav-bar).
     A first pass also added trailing space after the row and shrank the
     heading-to-row gap; measurement showed the heading-gap shrink is a
     dead end for this metric (it shifts the row AND everything after it,
     including City, up together -- net zero for the row's position
     relative to the viewport), and the row's own trailing padding, while
     it DOES help clearance, does so only by pushing City's negative
     margin-pull-up target later -- i.e. it is the SAME thing as growing
     the Intent->City gap, 1px for 1px. Since that gap must stay exactly
     at its approved ~88-96px, that lever is unusable and has been
     reverted. The ONLY lever left that moves the card's actual text
     without touching City's position, the heading's position, or the
     card's own outer size is the CARD's own padding-bottom: it reserves
     more of the card's FIXED height below the text (justify-content:
     flex-end packs the icon/title/description/arrow group against this
     edge), so the group shifts up inside the same-size card -- image,
     border-radius, hover lift, everything else about the card is
     unchanged, only where the text sits inside it moves. */
  body.world .w-intent-row {
    --intent-gutter: clamp(1.25rem, 4vw, 4.5rem);
    overflow-x: auto; scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--intent-gutter); scrollbar-width: none;
    padding-inline: var(--intent-gutter);
    margin-inline: calc(var(--intent-gutter) * -1);
    padding-block: 2px;
    gap: 10px;
  }
  body.world .w-intent-row::-webkit-scrollbar { display: none; }
  body.world .w-intent-card {
    flex: 0 0 73vw; scroll-snap-align: center;
    height: clamp(215px, 50vw, 235px);
    padding-bottom: calc(1.1rem + 72px); /* was 1.1rem -- +72px, see note above */
    /* Bottom-nav safety clearance: guarantees that any ASSISTED scroll
       landing on a card (Tab/keyboard focus, in-page anchor jump,
       assistive-tech scrollIntoView) never rests with the card's own
       text/CTA behind the fixed .dmnav-bar. Secondary/additive guard --
       does not by itself fix ordinary free-scroll/touch overlap, which
       the padding-bottom above does. */
    scroll-margin-block-end: calc(86px + env(safe-area-inset-bottom, 0px) + 14px);
  }
  body.world .mv.w-intent-section { padding-block: clamp(3rem, 12vw, 3.5rem); }
  body.world .w-intent-heading { font-size: clamp(1.75rem, 7vw, 2.125rem); }

  /* City gallery: a completely different mobile composition, not the
     desktop cascade squeezed down. A real horizontally-scrolling,
     snap-to-center carousel -- #cityWall (the '.w-gallery' div) becomes the
     scroll container itself, so native touch drag, arrows, dots and
     keyboard (js/city-gallery.js) all move the same one source of truth.
     Every plane is a normal in-flow flex item here, not the absolutely
     positioned cascade card -- the data-near cutoff and --o transform from
     the desktop rules are switched off below rather than fought. */
  body.world .mv.w-city-section { padding-block: clamp(3rem, 12vw, 3.25rem); }
  body.world .w-city-copy .w-h2 { font-size: clamp(1.75rem, 7vw, 2rem); }
  body.world .w-city-copy .w-body { font-size: 0.875rem; }
  body.world .w-aperture { overflow: visible; }
  body.world .w-gallery {
    --city-gutter: clamp(1.25rem, 4vw, 4.5rem);
    display: block;
    min-height: 0;
    overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none;
    /* touch-action:pan-y (desktop/tablet cascade, above) would block the
       browser's own horizontal scroll-snap drag here -- auto lets it
       decide per-axis, exactly what a real horizontal scroller needs while
       still passing vertical drags up to scroll the page. */
    touch-action: auto;
    padding-inline: var(--city-gutter);
    margin-inline: calc(var(--city-gutter) * -1);
    scroll-padding-inline: var(--city-gutter);
  }
  body.world .w-gallery::-webkit-scrollbar { display: none; }
  body.world .w-wall { display: flex; width: max-content; height: auto; gap: 12px; }
  body.world .w-plane {
    position: relative;
    width: auto;
    height: clamp(300px, 42vw, 330px);
    flex: 0 0 80vw;
    scroll-snap-align: center;
    transform: none !important;
    opacity: 1 !important;
    z-index: auto;
  }
  body.world .w-plane-name { font-size: 1.125rem; }
  body.world .w-plane[data-focus="1"] .w-plane-name { font-size: 1.25rem; }
  body.world .w-gallery-nav { margin-top: 0.75rem; }

  /* Services: the SAME rotating cover-flow as desktop (js/home-services.js
     computes one transform model on every breakpoint) -- only sized down
     and lighter-rotated for portrait. Only one card is fully readable;
     left/right show a small curved portion, outer cards are mostly
     hidden. .w-svc-gallery breaks out to the full viewport width (its
     parent .mv-in carries side padding that would otherwise squeeze the
     sliver's room) and clips there, so a peeking card is cropped cleanly
     at the screen edge instead of ever forcing page-level horizontal
     scroll -- there is no native scroll container to rely on any more. */
  body.world .mv.w-services-section { padding-block: clamp(3rem, 12vw, 3.25rem); }
  body.world .w-svc-copy { max-width: 100%; padding-inline: 0.5rem; }
  body.world .w-svc-copy .w-h2 { font-size: clamp(1.75rem, 7vw, 2rem); }
  body.world .w-svc-copy .w-body { font-size: 0.875rem; }
  body.world .w-svc-gallery {
    /* Logical margin-inline (not left/margin-left + position:relative --
       verified live: that physical-property trick anchors a block wider
       than its container to the CONTAINING BLOCK'S opposite physical edge
       under dir:rtl, landing the whole gallery off-centre instead of
       full-bleed) -- equal negative margin on both logical sides expands
       symmetrically to the viewport regardless of writing direction. */
    margin-inline: calc(50% - 50vw);
    min-height: clamp(285px, 40vw, 305px);
    overflow: hidden;
  }
  /* Narrower than the old 80vw -- at 80vw the active card left almost no
     room on either side, so a neighbour's peek was either near-zero or, once
     scaled/rotated, wildly uneven between the two sides. At 74vw the fixed
     (100vw - 74vw)/2 = 13vw side margin this full-bleed layout leaves on
     each edge is what actually determines how much of a neighbour peeks out
     (clipped by the viewport edge, not by translateX) -- same amount on
     both sides by construction, verified live. */
  body.world .w-svc-plane { width: 74vw; height: clamp(285px, 40vw, 305px); }
  body.world .w-svc-plane-title { font-size: 1.05rem; }
  body.world .w-svc-plane[data-focus="1"] .w-svc-plane-title { font-size: 1.15rem; }
  body.world .w-svc-gallery-nav { margin-top: 0.75rem; }

  body.world .w-trust { grid-template-columns: 1fr; }
  body.world .w-empty-actions { width: 100%; }
  body.world .w-empty-actions .w-btn { flex: 1 1 auto; }
  body.world .w-carry { display: none; }   /* cost without benefit at this size */
  body.world { padding-bottom: 4.5rem; }   /* clear of the fixed bottom nav */
}

@media (min-width: 768px) and (max-width: 1023px) {
  /* No override needed for .w-intent-row here: the base five-equal-card
     flex row (section 8) already fits this width without hiding any
     card. */
}

/* =========================================================================
 * 12. FOOTER -- Deep Navy close
 * =========================================================================
 * The footer sits AFTER the journey (--journey has already reached 1 by
 * the time it's on screen, so the world-field is already fully navy
 * behind it) but it is its own opaque element, not part of the fixed
 * field -- css/cinematic.css's `.cine-scope .home-footer` rule gives it a
 * light ivory surface (correct for every OTHER page that reuses the same
 * component), which this overrides specifically for Home, per the
 * approved HOME COLOR FLOW ("Footer = Deep Navy"). Text/link colors
 * inside it get their own explicit on-navy values rather than reading
 * --on-stone, since the footer never sits anywhere but the end of the
 * journey -- it has no tonal ambiguity to resolve. ======================= */
body.world .home-footer {
  background: var(--w-navy);
  border-top: 1px solid rgba(244, 239, 231, 0.12);
}
body.world .home-footer-word { color: var(--w-gold-hi); }
body.world .home-footer-tagline,
body.world .home-footer-link,
body.world .home-footer-bottom { color: var(--w-ink-soft); }
body.world .home-footer-heading { color: var(--w-ink); }
body.world .home-footer-link:hover { color: var(--w-gold); }
body.world .home-footer-bottom { border-top-color: rgba(244, 239, 231, 0.12); }

/* =========================================================================
 * 13. REDUCED MOTION
 * =========================================================================
 * The world, the tonal journey and every composition remain. Only the
 * movement stops: planes rest at their designed positions, and the gallery
 * still changes focus -- instantly instead of gliding. ================== */
@media (prefers-reduced-motion: reduce) {
  body.world .w-plane,
  body.world .w-svc-plane,
  body.world .w-intent-card,
  body.world .w-intent-media,
  body.world .w-intent-arrow,
  body.world .w-intent-desc,
  body.world .w-btn,
  body.world .w-btn-arrow,
  body.world .w-action::after,
  body.world .w-row,
  body.world .w-row-cue,
  body.world .w-icon-btn { transition: none; }
  /* Every choreographed element rests at its finished state. The controller
     also parks --fp at 0.5 and never attaches a scroll listener, so this is
     belt and braces rather than the only defence. */
  body.world [data-flow] { transform: none !important; opacity: 1 !important; }
  body.world .w-carry { filter: none; }
  /* Belt and braces for the mobile city carousel's native scroll-snap: JS
     already asks for instant (not smooth) scrollIntoView when this media
     query matches (js/city-gallery.js's prefersReducedMotion()), this just
     covers any native browser-driven snap settling too. */
  body.world .w-gallery { scroll-behavior: auto; }
}
