/* ==========================================================================
   H7G.com Ver 2 — Section styles
   One block per section, appended in page order as each section is designed.
   ========================================================================== */

/* --- 01 · Cinematic Opening -----------------------------------------------
   The visitor arrives inside a dark, dormant industrial world. No nav, no
   headline, no numbers, no buttons — nothing is lit.

   Scrolling does not move the page. `.opening` is five viewports tall and
   `.opening__stage` sticks to the top, so the frame holds still while
   opening.js publishes scroll position as --p (0→1) plus one cumulative
   progress value per stage (--p1 … --p5).

   Three pixel-aligned night photographs are stacked:

     dark    everything switched off
     lit     everything switched on, same overcast sky
     finale  everything switched on under an open, starlit sky

   Stages 01–04 reveal the lit layer through four radial masks, one centred on
   each subject; a mask that has opened never closes, so the world lights up in
   sequence and stays lit. Stage 05 is the city, and because the sky has to
   open at the same moment the city does, it is not a mask at all — the whole
   finale layer simply crossfades in over the top.

   Mask geometry and opacity are driven straight off --p, so they cost nothing
   but a repaint. `object-position` is the one expensive property here: it
   animates only on frames narrower than 3:2, where the camera has to travel,
   and it is off entirely on wider screens.
   -------------------------------------------------------------------------- */

.opening {
  --p: 0;
  --p1: 0; --p2: 0; --p3: 0; --p4: 0; --p5: 0;

  /* Where each subject sits in the frame, measured off the artwork. These five
     pairs drive both the reveal masks and the mobile framing — one source of
     truth, so the two can never drift apart.

     Ellipses are kept tight. The city sits low on the far shore at y 36–49%,
     directly behind the platform and above the tankers, so a generous radius
     here reaches the skyline and lights the city several stages early. Stage
     05 has no radii — it is the finale crossfade, and its pair only aims the
     mobile camera. */
  --e1-x: 26%; --e1-y: 52%; --e1-rx: 14%; --e1-ry: 12%;  /* 01 offshore platform */
  --e2-x:  8%; --e2-y: 65%; --e2-rx:  9%; --e2-ry:  9%;  /* 02 oil pumpjack      */
  --e3-x: 57%; --e3-y: 64%; --e3-rx: 19%; --e3-ry: 14%;  /* 03 tanker fleet      */
  --e4-x: 86%; --e4-y: 42%; --e4-rx: 18%; --e4-ry: 20%;  /* 04 refinery          */
  --e5-x: 53%; --e5-y: 43%;                              /* 05 city skyline      */

  /* The subject the camera is currently attending to. */
  --focus-x: 50%;
  --focus-y: 55%;
  --zoom: 1;

  position: relative;
  height: 500dvh;
  background: var(--c-void);
}

.opening__stage {
  position: sticky;
  top: 0;
  height: 100dvh;
  overflow: clip;
}

/* top/left below are deliberately physical, not logical. The artwork has a
   real geography and must not mirror when the document flips to RTL. This is
   the one place in the stylesheet where left/right is the correct choice. */
.opening__world {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.opening__layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus-x) var(--focus-y);
  transform: scale(var(--zoom));
  transform-origin: var(--focus-x) var(--focus-y);
  transition:
    object-position 900ms var(--ease-out),
    transform 900ms var(--ease-out);
}

/* The lit world, revealed one subject at a time. Mask layers compose with
   `add` by default, so the four ellipses union into a growing lit region. */
.opening__layer--lit {
  --m1: radial-gradient(ellipse calc(var(--p1) * var(--e1-rx)) calc(var(--p1) * var(--e1-ry)) at var(--e1-x) var(--e1-y), #000 50%, transparent 100%);
  --m2: radial-gradient(ellipse calc(var(--p2) * var(--e2-rx)) calc(var(--p2) * var(--e2-ry)) at var(--e2-x) var(--e2-y), #000 50%, transparent 100%);
  --m3: radial-gradient(ellipse calc(var(--p3) * var(--e3-rx)) calc(var(--p3) * var(--e3-ry)) at var(--e3-x) var(--e3-y), #000 50%, transparent 100%);
  --m4: radial-gradient(ellipse calc(var(--p4) * var(--e4-rx)) calc(var(--p4) * var(--e4-ry)) at var(--e4-x) var(--e4-y), #000 50%, transparent 100%);

  -webkit-mask-image: var(--m1), var(--m2), var(--m3), var(--m4);
  mask-image: var(--m1), var(--m2), var(--m3), var(--m4);
}

/* Stage 05. Held back until the stage is a quarter under way so the city and
   the opening sky land as one event rather than creeping in from the start. */
.opening__layer--finale {
  opacity: clamp(0, calc((var(--p5) - 0.25) * 1.6), 1);
}

/* --- Grade: vignette and grain. Static, costs nothing to composite. ------- */
.opening__grade {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(120% 95% at 50% 55%, transparent 42%, rgb(4 7 11 / 0.66) 100%),
    linear-gradient(to bottom, rgb(4 7 11 / 0.6) 0%, transparent 26%, transparent 68%, rgb(4 7 11 / 0.8) 100%);
}

.opening__grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.14;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --- Arrival: the dormant world surfaces out of pure black, once. --------- */
/* Sits above the rail and the scroll cue so the whole frame — world, rail and
   cue together — surfaces out of black as one thing. */
.opening__veil {
  position: absolute;
  inset: 0;
  z-index: 6;
  background: var(--c-void);
  pointer-events: none;
  animation: opening-emerge 4s cubic-bezier(0.33, 0, 0.15, 1) forwards;
}

@keyframes opening-emerge {
  0%   { opacity: 1; }
  60%  { opacity: 0.5; }
  100% { opacity: 0; }
}

/* --- Brand mark ----------------------------------------------------------
   Present from the first dark frame. It follows the reading direction, unlike
   the rail — a brand mark belongs where the eye starts, and at the top corner
   it never meets the rail. */
.opening__brand {
  position: absolute;
  top: var(--space-8);
  inset-inline-start: var(--gutter);
  z-index: 4;
  display: block;
  width: 104px;
}

/* --- Tagline -------------------------------------------------------------
   Whole from the first frame, but unlit — the words behave like everything
   else in this section. They start barely there and gain their light as the
   visitor scrolls, reaching full strength exactly when the world does. */
.opening__words {
  position: absolute;
  top: 15%;
  left: 50%;
  z-index: 4;
  translate: -50% 0;
  width: min(90%, 26ch);
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.4vw, 1.9rem);
  font-weight: var(--weight-medium);
  line-height: 1.3;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-text);
  text-wrap: balance;
  pointer-events: none;

  /* Never fully dark, so the phrase can always be read, and never brightening
     before the first subject lights. */
  opacity: clamp(0.26, calc(0.26 + var(--p) * 0.74), 1);
}

.opening__words span { display: block; }

.opening__words b {
  font-weight: inherit;
  color: var(--c-gold-soft);
}

/* --- Stage rail ----------------------------------------------------------
   Present from the first dark frame, so the visitor can see where the journey
   goes before any of it is lit.

   The rail carries muted text over photography, and the artwork under it is not
   uniform — platform floodlights and the Shanghai skyline both fall in this
   band and peak at pure white. Measured against the finale layer the inactive
   rows came out at 4.0:1, under the 4.5 AA floor, and far worse locally. The
   scrim below fixes that at the source instead of brightening the text, which
   would destroy the point of "not reached yet". */
.opening__rail {
  position: absolute;
  top: 50%;
  /* Physical `left`, not logical: the rail is pinned to the artwork, and the
     artwork does not mirror. Flipping it in RTL would drop it on top of the
     refinery — the busiest, brightest corner of the frame. */
  left: 0;
  z-index: 3;
  translate: 0 -50%;
  margin: 0;
  padding: 0;
  list-style: none;
  /* The rail is laid out physically because it is anchored to the artwork:
     number on the left, label after it, gold wash running left→right — the
     same in all three languages. Each Arabic label still shapes and orders as
     a proper RTL run inside its own box. */
  direction: ltr;
}

/* Soft scrim so the rail always has something dark to sit on, whatever the
   artwork is doing behind it. Bleeds well past the text so no edge is visible. */
.opening__rail::before {
  content: "";
  position: absolute;
  inset: -12% -45% -12% -100%;
  z-index: -1;
  background: radial-gradient(closest-side at 22% 50%, rgb(4 7 11 / 0.82), rgb(4 7 11 / 0) 78%);
  pointer-events: none;
}

.opening__step {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  min-height: 44px;
  padding-block: var(--space-2);
  padding-left: var(--gutter);
  padding-right: var(--space-16);
  font-size: var(--text-sm);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-text-muted);
  white-space: nowrap;
  transition: color var(--dur-base) var(--ease-out);
}

.opening__step .num {
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-bold);
  font-size: var(--text-base);
  opacity: 0.75;
}

.opening__step.is-active {
  color: var(--c-text);
  /* Gold wash marking the live stage, running out from the frame edge so it
     never reads as a hard block. */
  background: linear-gradient(
    to right,
    rgb(201 162 39 / 0.62) 0%,
    rgb(201 162 39 / 0.14) 58%,
    transparent 100%
  );
}
.opening__step.is-active .num { opacity: 1; }

/* --- Scroll cue ----------------------------------------------------------
   The opening has no other affordance, so without this the visitor has no way
   to know the world responds to anything. It stays for the whole journey —
   there are five stages to get through, and it retires only at the very end,
   once the world is fully lit and there is nothing left to reveal. */
.opening__cue {
  position: absolute;
  bottom: var(--space-8);
  left: 50%;
  z-index: 4;
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  color: var(--c-text-muted);
  font-size: var(--text-xs);
  letter-spacing: 0.3em;
  text-transform: uppercase;
  pointer-events: none;
  /* Full strength until the last stage, then out over the final tenth. */
  opacity: clamp(0, calc((1 - var(--p)) * 10), 1);
  transition: opacity var(--dur-base) var(--ease-out);
}

/* Same reasoning as the rail: the cue sits over water that carries the tanker's
   reflected deck lights. */
.opening__cue::before {
  content: "";
  position: absolute;
  inset: -60% -180%;
  z-index: -1;
  background: radial-gradient(closest-side, rgb(4 7 11 / 0.7), rgb(4 7 11 / 0) 76%);
  pointer-events: none;
}

.opening__cue svg {
  width: 22px;
  height: 22px;
  stroke: var(--c-gold-soft);
  stroke-width: 1.5;
  fill: none;
  animation: opening-cue 2.4s var(--ease-out) infinite;
}

@keyframes opening-cue {
  0%, 65%, 100% { transform: translateY(0); opacity: 0.85; }
  35%           { transform: translateY(6px); opacity: 1; }
}

/* --- Responsive ----------------------------------------------------------
   Whether the camera travels is decided by the frame's ASPECT RATIO, not its
   width. The artwork is 16:9, so any viewport at least that wide shows the
   whole world and the frame can stay perfectly still — panning it as well made
   the image drift under the cursor on every scroll tick.

   Below 3:2 the crop bites hard enough to matter: a 1024×768 tablet in
   landscape only shows the middle 75% of the width, which loses the pumpjack
   off the left edge completely and lights a subject nobody can see. Those
   viewports travel between the five subjects instead. Keying this to width
   was the bug — 1024px is not a narrow screen, but 1024×768 is a narrow
   frame. */
@media (max-aspect-ratio: 3/2) {
  /* Only hinted where the camera actually moves — a permanent will-change on
     three full-screen images costs memory on every device for nothing. */
  .opening__layer { will-change: object-position; }

  .opening[data-stage="1"] { --focus-x: var(--e1-x); --focus-y: var(--e1-y); }
  .opening[data-stage="2"] { --focus-x: var(--e2-x); --focus-y: var(--e2-y); }
  .opening[data-stage="3"] { --focus-x: var(--e3-x); --focus-y: var(--e3-y); }
  .opening[data-stage="4"] { --focus-x: var(--e4-x); --focus-y: var(--e4-y); }
  .opening[data-stage="5"] { --focus-x: var(--e5-x); --focus-y: var(--e5-y); }
}

/* 3:2 and wider: one still frame, start to finish. */
@media (min-aspect-ratio: 3/2) {
  .opening { --zoom: 1; --focus-x: 50%; --focus-y: 55%; }
  .opening__layer { transition: none; }
}

/* Tablet and small-screen chrome. */
@media (max-width: 1023px) {
  .opening__step { font-size: var(--text-xs); }
  .opening__brand { width: 72px; }
  .opening__words { top: 13%; }
}

/* Portrait tablets have room to spare, so the live subject gets a little
   push-in. Landscape ones are already cropped by the aspect and do not. */
@media (orientation: portrait) and (min-width: 768px) and (max-width: 1023px) {
  .opening { --zoom: 1.3; }
}

/* Portrait phones ----------------------------------------------------------
   A 16:9 frame in a tall window already shows only about a quarter of the
   artwork's width, which is a good portrait crop on its own. Magnifying on top
   of that turned the frame into an unreadable close-up of steelwork, so there
   is no extra zoom here — the travel between subjects comes from
   object-position alone.

   The rail also changes shape. Five stacked rows with labels is a desktop
   pattern; on a phone it ate half the screen and fought the scroll cue for the
   foot of the frame. Here it collapses into one row of five ticks, and only
   the live stage says its name. */
@media (orientation: portrait) and (max-width: 767px) {
  .opening { --zoom: 1; }

  /* One still frame, like the wide screens get. Travelling between subjects is
     right on a tablet and wrong on a phone: the window is only about a third of
     the artwork wide, so each move is a large jump for a small screen, and the
     picture slides sideways under the reader on every scroll tick.

     57% is the framing of stage 03 — city, tanker fleet and the near edge of the
     refinery — which is the one crop that composes on its own.

     The cost, and it is a real one: the pumpjack (8%) and the offshore platform
     (26%) fall outside this window, so stages 01 and 02 light subjects a phone
     cannot see. The rail still names them and the sky and city still turn on.
     Showing all five without moving would mean letterboxing the artwork to about
     a third of the screen's height. */
  .opening,
  .opening[data-stage] { --focus-x: 57%; }

  .opening__layer {
    transition: none;
    will-change: auto;
  }

  .opening__brand { width: 52px; top: var(--space-6); }

  /* Clears the mark, which reaches ~13% of a short phone's height. */
  .opening__words {
    top: 17%;
    width: min(88%, 18ch);
    font-size: var(--text-base);
    letter-spacing: 0.05em;
  }

  .opening__rail {
    top: auto;
    bottom: calc(var(--space-16) + var(--space-8));
    left: var(--gutter);
    right: var(--gutter);
    translate: none;
    display: flex;
    justify-content: center;
    gap: var(--space-3);
  }

  /* The row runs across the frame here, so the scrim has to as well. */
  .opening__rail::before {
    inset: -180% -30%;
    background: radial-gradient(closest-side, rgb(4 7 11 / 0.72), rgb(4 7 11 / 0) 80%);
  }

  .opening__step {
    position: relative;
    flex: 0 1 52px;
    min-height: 0;
    display: block;
    padding: 0;
    text-align: center;
    background: none;
    letter-spacing: 0.1em;
  }

  /* The tick under each number: the whole chain is visible at a glance, and
     the passed ones are gold. */
  .opening__step::after {
    content: "";
    display: block;
    margin-top: var(--space-2);
    height: 2px;
    border-radius: 1px;
    background: var(--c-border);
    transition: background var(--dur-base) var(--ease-out);
  }
  .opening__step.is-lit::after { background: rgb(201 162 39 / 0.55); }
  .opening__step.is-active::after { background: var(--c-gold); }

  .opening__step .num { font-size: var(--text-xs); opacity: 0.45; }
  .opening__step.is-lit .num { opacity: 0.8; }
  .opening__step.is-active .num { opacity: 1; color: var(--c-gold-soft); }

  /* Only the live stage is named, floated above its own tick. */
  .opening__step .label {
    position: absolute;
    bottom: calc(100% + var(--space-3));
    left: 50%;
    width: max-content;
    max-width: 40vw;
    transform: translateX(-50%);
    font-size: var(--text-xs);
    white-space: nowrap;
    opacity: 0;
    transition: opacity var(--dur-base) var(--ease-out);
  }
  .opening__step.is-active .label { opacity: 1; }
}

/* Very short landscape (phones turned sideways): shrink the rail so it
   doesn't run off the top and bottom of the frame. */
@media (max-height: 480px) {
  .opening__step {
    min-height: 32px;
    padding-block: 0;
    font-size: var(--text-xs);
  }
  .opening__step .num { font-size: var(--text-sm); }
  .opening__brand { width: 56px; top: var(--space-3); }
  .opening__words { top: 7%; font-size: var(--text-sm); }
}

/* Narrow landscape (≤700px wide phones turned sideways): the centered tagline
   and the language group share the top band, and the 26ch box reaches under
   the pill — drop the words below the group's row instead. */
@media (max-height: 480px) and (max-width: 700px) {
  .opening__words { top: 26%; }
}

/* Only Latin has letter case, and Arabic needs room for its marks, so the
   uppercasing and tight leading apply where they mean something. */
[dir="rtl"] .opening__words {
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.5;
}

/* CJK keeps the tracking — it suits a set tagline — but needs looser leading. */
:lang(zh) .opening__words {
  text-transform: none;
  line-height: 1.6;
}

/* --- Motion off: no pin, no travel. One fully lit frame. ------------------ */
@media (prefers-reduced-motion: reduce) {
  .opening { height: 100dvh; --zoom: 1; }
  .opening__stage { position: relative; }
  .opening__layer {
    object-position: 50% 55%;
    transition: none;
  }
  .opening__layer--lit {
    -webkit-mask-image: none;
    mask-image: none;
  }
  .opening__layer--finale { opacity: 1; }
  .opening__veil { animation: none; opacity: 0; }
  .opening__cue { display: none; }
  .opening__cue svg { animation: none; }
  .opening__words { opacity: 1; }
}


/* --- 02 · Our Journey -----------------------------------------------------
   A horizontal timeline of seven milestones, one open at a time. journey.js
   sets `.is-active` and `data-side`; every size and easing decision is here.

   Two layouts, and the difference is not cosmetic:

   Wide frames fit all seven cards, so nothing scrolls. Opening a year only
   redistributes `flex-grow` — the open card takes about a quarter of the row
   and its neighbours give up a little each, which is the brief's "the
   surrounding years get slightly smaller" for free, with no scroll position to
   keep in sync and no scrollbar to hide.

   Narrow frames can't fit seven of anything, so there the strip becomes a snap
   scroller of fixed-width cards.

   The open card grows in height as well, because it has four more blocks of
   content to show than a closed one. Height is the only layout property
   animated per card; everything else that moves is scale, opacity or a
   gradient, so a frame costs a composite rather than a reflow.
   -------------------------------------------------------------------------- */

/* The opening's name-plate (2026-09-07, user's 1.jpeg): company name and a
   one-line descriptor, centred on the top band between the brand mark and
   the language group. Below 1100px it drops under the chrome band (the
   centred sub-line would meet the language pill); very short landscape
   frames hide it rather than let it collide with the tagline. */
.opening__lockup {
  position: absolute;
  inset-inline: 0;
  inset-block-start: clamp(30px, 4.6vh, 54px);
  z-index: 5;
  margin: 0;
  display: grid;
  justify-items: center;
  gap: 9px;
  text-align: center;
  pointer-events: none;
}

/* Set like the brand mark's own wordmark (user 2026-09-07): the logo's
   "GROUP" is outlined geometric caps under the maze, so the plate borrows
   its voice — Inter caps, wide tracking, and the SVG's exact gold gradient
   clipped onto the glyphs. English on all three pages by the same call. */
.opening__lockup-name {
  font-family: 'Inter', sans-serif;
  font-weight: var(--weight-medium);
  font-size: clamp(1.05rem, 1.8vw, 1.45rem);
  line-height: 1;
  letter-spacing: 0.34em;
  text-indent: 0.34em; /* recentres tracked caps */
  text-transform: uppercase;
  background: linear-gradient(180deg, #e0c268 0%, #c9a227 35%, #a5851f 80%, #8c6f1a 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 1px 10px rgb(4 7 11 / 0.7));
}

.opening__lockup-sub {
  /* The descriptor is localized (only the name stays English), so it rides
     each page's own body face. floor 12px — the sweep's minimum-font rule
     caught 0.62rem at 9.9px. */
  font-family: var(--font-body);
  font-size: clamp(0.75rem, 0.85vw, 0.8rem);
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  color: var(--c-text-muted);
  text-shadow: 0 1px 10px rgb(4 7 11 / 0.9);
}

@media (max-width: 1099px) {
  .opening__lockup {
    inset-block-start: 112px;
    padding-inline: var(--space-4);
  }
}

/* With the name-plate under the chrome band on narrow frames, the tagline
   gives it room. Short frames are excluded — there the plate hides and the
   words keep their own landscape rules. */
@media (max-width: 1099px) and (min-height: 541px) {
  .opening__words { top: clamp(200px, 24%, 264px); }
}

@media (max-height: 540px) {
  .opening__lockup { display: none; }
}

/* Only the NAME stays English everywhere (user's call); the descriptor is
   localized, so it takes the usual script rules. */
[dir="rtl"] .opening__lockup-sub {
  letter-spacing: 0;
  text-indent: 0;
  text-transform: none;
}

:lang(zh) .opening__lockup-sub {
  text-transform: none;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
}

/* Section thresholds (2026-09-01, user's call): the footer's centred gold
   hairline now marks every section boundary — one line at each section's
   block-start, identical to .footer::before. scale/commit gain position:
   relative below solely to anchor it. Journey deliberately has none: its
   block-start sits flush under the sticky header, whose 32px blurred
   under-fade smears a 1px line invisible — and the opening's artwork edge
   already draws that boundary. */
.pillars::before,
.network::before,
.scale::before,
.commit::before,
.services::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 14%;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--c-gold-soft), transparent);
  opacity: 0.55;
  z-index: 2;
}

.scale,
.commit {
  position: relative;
}

.journey {
  /* Closed and open card heights. Open has to hold the achievements list and
     the two meta rows on top of what a closed card shows. */
  /* THE ROW HAS ONE HEIGHT, AND IT DOES NOT DEPEND ON WHICH CARD IS OPEN.
     This is the whole reason the section stopped shaking. Letting the open
     card's content set the row meant its text re-wrapped on every frame of the
     width animation, so the row's height wobbled frame by frame and dragged the
     rail, the hint and the rest of the page with it.

     The number is measured, not guessed: the tallest open card across three
     languages and eight frame sizes needed 651px on a wide frame and 696px in
     the scroller. Re-measured 2026-08-26 with the wide mode's NARROW end
     included (1200–1366px, a range the first pass missed): the 2025/2026
     English cards wrap to 689px at 1200w, so the floor is 692 — anything
     short of that and the row height depends on which card is open again. */
  --row-h: clamp(692px, 74vh, 720px);

  /* Floor for a single card, only ever reached if --row-h somehow is not. */
  --card-h: clamp(400px, 52vh, 500px);

  /* Row shares. Seven cards: at 2.2 the open one takes ~24% of the row and the
     other six ~12.7% each. Past ~2.6 the closed cards get too narrow for a
     two-word title to sit on two lines. */
  --grow-open: 2.2;

  /* Hover is a transform, so this is how much bigger the card *looks*, not how
     much room it takes. Nothing reflows. */
  --hover-scale: 1.055;

  position: relative;
  isolation: isolate;
  overflow: clip;
  padding-block: var(--section-block);
  /* The sticky header sits above this section in the flow, so it already
     provides the top clearance. What it does not provide is room for itself
     once it is pinned — hence the scroll margin, so landing here from the menu
     puts the eyebrow below the bar rather than behind it. */
  padding-block-start: var(--space-12);
  scroll-margin-block-start: var(--header-h);
  background: var(--c-bg);
}

/* --- Background: the night earth -----------------------------------------
   A texture, not a subject. It carries dense city-light clusters that peak
   near white, which is exactly the range the muted body text lives in, so it
   is held well back and the scrim below does the rest. */
/* Anchored to the far side of the frame rather than stretched across it, so the
   globe is an object in the composition instead of a wash behind everything.
   `inset-inline-end` means it also swaps sides with the reading direction: it
   always sits opposite the heading, never under it.

   The master is cropped square around the globe (`_source/journey/earth-globe`)
   precisely so this anchor is predictable. Cropping it out of the original 16:9
   frame with object-position was guesswork, and the version that used the full
   plate put the globe in the middle of the section, behind the cards. */
.journey__earth {
  position: absolute;
  /* High in the section: its top limb reads against the heading band rather
     than behind the rail, which is where it was sitting. */
  inset-block-start: 34%;
  inset-inline-end: -9%;
  z-index: -2;
  width: min(62%, 780px);
  height: auto;
  max-width: none;      /* base.css caps every img at 100%; this one is bled */
  translate: 0 -50%;
  /* Lifted rather than un-scrimmed. The globe stays under .journey__scrim so a
     phone, where it is wide enough to reach behind the heading, still has its
     contrast guaranteed; brightness buys back what the scrim takes. */
  opacity: 1;
  filter: brightness(1.55) saturate(1.05);

  /* The plate's "black" is a hazy near-black, not the page's, so at this
     brightness its square edge read as a panel sitting behind the cards.
     Screening it out was tried first and cannot work — screen only ever
     lightens, so a non-zero black lifts the whole box. This fades the frame
     away instead; the sphere fills the crop, so the falloff lands on its own
     rim and reads as atmosphere. */
  -webkit-mask-image: radial-gradient(closest-side, #000 68%, transparent 99%);
  mask-image: radial-gradient(closest-side, #000 68%, transparent 99%);
}

.journey__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    /* Heaviest over the heading block, which is the one place body text sits
       directly on the artwork with no card behind it. */
    radial-gradient(85% 62% at 18% 26%, rgb(10 14 20 / 0.93), rgb(10 14 20 / 0) 72%),
    linear-gradient(
      to bottom,
      var(--c-bg) 0%,
      rgb(10 14 20 / 0.62) 18%,
      rgb(10 14 20 / 0.62) 74%,
      var(--c-bg) 100%
    );
}

/* One measure for every band in the section, so the heading, the first card and
   the arrow all start on the same line at any width. Not `.container`: that
   centres itself once the frame is wider than 1200px, which would leave the
   heading indented from a card strip that keeps running to the gutter. */
.journey__head,
.journey__strip,
.journey__nav,
.journey__hint {
  max-width: 1600px;
  margin-inline: auto;
}

/* --- Heading -------------------------------------------------------------- */
.journey__head { padding-inline: var(--gutter); }

.journey__intro,
.journey__eyebrow { position: relative; }

.journey__eyebrow {
  margin: 0 0 var(--space-4);
  color: var(--c-gold);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.26em;
  text-transform: uppercase;
}

.journey__title {
  /* Regular weight, not the h2 default. At this size the display face's thin
     strokes are the point; bold turns the word into a slab. */
  font-weight: var(--weight-regular);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.journey__intro {
  margin-top: var(--space-6);
  /* min() because 44ch of Inter is about 400px, which is wider than a 390px
     phone's content box. */
  max-width: min(44ch, 100%);
  color: var(--c-text-muted);
}

/* --- The strip ------------------------------------------------------------ */
.journey__strip {
  position: relative;
  margin-top: var(--space-8);
  display: flex;
  align-items: stretch;
  min-height: var(--row-h);
  padding-inline: var(--gutter);
}

/* Tops aligned, not centres. Every year then sits on one line directly under
   the intro paragraph, which is what makes the row read as a timeline — and it
   closes the gap that centring opened up, since the open card now grows
   downward instead of pushing its own top up out of the block. */
.journey__list {
  display: flex;
  align-items: stretch;
  gap: clamp(5px, 0.85vw, 14px);
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A flex container with one child, so the card fills the height the row ended
   up with. The row's height still comes from the open card's content — flex
   sizes every item from its own content first, takes the tallest, and stretches
   the rest to match — but now the six closed cards use that height instead of
   leaving a block of dead space under themselves. */
.journey__item {
  display: flex;
  flex: 1 1 0;
  /* Without this a long title sets the flex base size and the row stops
     honouring the shares above. */
  min-width: 0;
  transition: flex-grow 480ms var(--ease-out);
}

.journey__item.is-active { flex-grow: var(--grow-open); }

/* --- The card ------------------------------------------------------------- */
/* The two card heights are floors, not fixed sizes. An open card has to hold
   four more blocks than a closed one, and how many lines those take depends on
   the language and on how wide the row has made the card — Chinese at 1024×768
   needed 504px of text where English needed 390. Pinning the height meant the
   media band was squeezed to nothing and the milestone was still clipped.
   Growing past the floor costs nothing here: the cards are centred in the row,
   so a taller one simply pushes the row's height up.

   Nothing animates height, either. The open card's growth is carried by the
   detail panel's own 0fr → 1fr transition, and the card, being auto-height,
   follows it for free — in every engine, with no interpolate-size needed. */
.journey__card {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  min-height: var(--card-h);
  overflow: hidden;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-md);
  background: rgb(18 24 33 / 0.62);
  transition:
    border-color var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out),
    /* Softer and shorter than the rest: this one answers the cursor, and
       --ease-out's near-instant start reads as a snap at hover speed. */
    scale 340ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity var(--dur-slow) var(--ease-out);
}

/* No height of its own — the row already has one, and a second source would be
   free to disagree with it and put the wobble back. */
.journey__item.is-active > .journey__card {
  border-color: rgb(201 162 39 / 0.62);
  box-shadow:
    0 0 0 1px rgb(201 162 39 / 0.22),
    0 30px 70px rgb(0 0 0 / 0.6),
    0 0 60px -12px rgb(201 162 39 / 0.35);
}

/* Filament along the open card's top edge. */
.journey__card::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 14%;
  z-index: 3;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--c-gold-soft), transparent);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
}

.journey__item.is-active > .journey__card::before {
  opacity: 1;
  box-shadow: 0 0 18px 2px rgb(227 199 122 / 0.45);
}

/* --- Year + title, and the card's hit area -------------------------------- */
.journey__year {
  margin: 0;
  font: inherit;
  font-family: var(--font-body);
}

.journey__pick {
  display: block;
  width: 100%;
  padding: var(--space-4) var(--space-4) var(--space-3);
  background: none;
  border: 0;
  text-align: start;
}

/* The whole card is clickable while the button's accessible name stays the
   year and its title. Above the media and the text, below nothing — there is
   no other interactive element inside a card. */
.journey__pick::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
}

/* The ring is drawn on that hit area, not on the button box — the button is
   only the top strip of the card, and a ring around the year alone reads as if
   the year were the target rather than the milestone. Outlining the pseudo-
   element puts it around the whole card without needing :has(). */
.journey__pick:focus-visible { outline: none; }

.journey__pick:focus-visible::after {
  outline: 3px solid var(--c-focus);
  outline-offset: -3px;
  border-radius: var(--radius-md);
}

.journey__num {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.45vw, 1.4rem);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  line-height: 1.1;
  color: var(--c-gold-soft);
  transition: font-size 620ms var(--ease-out);
}

.journey__name {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  line-height: 1.45;
  /* 0.13em looked better and cost too much: at 1280px a closed card gives a
     title 103px of measure, and "DEVELOPMENT" wanted 104. It broke the word and
     left a lone T on the next line. */
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--c-text-muted);
  /* A closed card is about 140px wide. Nothing in the approved copy is wider
     than that now, but the card clips its overflow, so a longer word in a
     future language must break rather than disappear — and break at a hyphen
     if the language has rules for it. */
  hyphens: auto;
  overflow-wrap: break-word;
  transition:
    font-size 620ms var(--ease-out),
    color var(--dur-slow) var(--ease-out);
}

/* The short gold rule under the title. Drawn from the title rather than the
   button, so it lands after the words instead of before them. */
.journey__name::after {
  content: "";
  display: block;
  width: 26px;
  height: 2px;
  margin-top: var(--space-3);
  background: var(--c-gold);
  opacity: 0.75;
  transition: width var(--dur-slow) var(--ease-out), opacity var(--dur-slow) var(--ease-out);
}

.journey__item.is-active .journey__num { font-size: clamp(1.6rem, 2.1vw, 2.05rem); }

.journey__item.is-active .journey__name {
  font-size: var(--text-sm);
  color: var(--c-text);
}

.journey__item.is-active .journey__name::after { width: 40px; opacity: 1; }

/* --- Media ---------------------------------------------------------------- */
/* The floor keeps a photograph looking like a photograph. Without it the media
   band was the only flexible row in the card, so on a short viewport it gave up
   all its height to the text and the card became a wall of words. */
.journey__media {
  position: relative;
  flex: 1 1 auto;
  min-height: clamp(110px, 15vh, 200px);
  overflow: hidden;
}

.journey__item.is-active .journey__media { min-height: clamp(160px, 22vh, 280px); }

/* Taken out of flow on purpose. In flow, `height: 100%` against an auto-height
   card resolves to auto, so each 3:4 photograph set its own band height — 455px
   on the open card — and dragged the card to 923px. Filling the box absolutely
   means the band is sized by the flex rules above and the photograph crops to
   whatever that turns out to be. */
.journey__shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Closed cards sit back, and they do it with opacity alone. A `filter` here
     was re-rasterising six photographs on every frame of the open animation,
     while their boxes were being resized — the single most expensive thing in
     the section, and the reason opening a card stuttered. */
  opacity: 0.72;
  transition:
    opacity var(--dur-slow) var(--ease-out),
    scale 900ms var(--ease-out);
}

.journey__item.is-active .journey__shot {
  opacity: 1;
  scale: 1.02;
}

/* The last card is about the group rather than a place, so the brand mark sits
   over its photograph. Both children occupy the same grid cell. */
.journey__media--mark { display: grid; }
.journey__media--mark > * { grid-area: 1 / 1; }

/* The photograph fills its box absolutely, and a positioned element paints over
   an in-flow sibling, so the mark needs a position of its own or it disappears
   behind the earth. */
.journey__mark {
  position: relative;
  z-index: 1;
  place-self: center;
  width: 46%;
  max-width: 96px;
  height: auto;
  opacity: 0.55;
  transition: opacity var(--dur-slow) var(--ease-out);
}

/* Keeps the glowing limb at the foot of the band so the gold mark sits on empty
   space rather than on the one bright thing in the frame. */
.journey__media--mark .journey__shot { object-position: 50% 100%; }

.journey__item.is-active .journey__mark { opacity: 1; }

/* --- Text ---------------------------------------------------------------- */
.journey__body { padding: var(--space-4); }

.journey__lede {
  margin: 0;
  max-width: 42ch;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--c-text-muted);
  /* A closed card gives the narrative three lines. The clamp is what keeps a
     long line of Arabic from pushing the card's own layout around. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  transition: color var(--dur-slow) var(--ease-out);
}

.journey__item.is-active .journey__lede {
  -webkit-line-clamp: 3;
  color: var(--c-text);
}

/* Collapsed to nothing rather than display:none, so opening it is a real
   transition. 0fr → 1fr needs the inner wrapper to do the clipping. */
.journey__detail {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition:
    grid-template-rows 480ms var(--ease-out),
    opacity 380ms var(--ease-out),
    visibility 0s linear 480ms;
}

.journey__detail > * { overflow: hidden; }

.journey__item.is-active .journey__detail {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
  /* The text arrives just behind the box that holds it. */
  transition-delay: 0s, 180ms, 0s;
}

.journey__label {
  margin: var(--space-3) 0 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--c-gold);
}

.journey__wins {
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--c-text-muted);
}

.journey__wins li {
  position: relative;
  padding-inline-start: var(--space-4);
}

.journey__wins li + li { margin-top: var(--space-2); }

.journey__wins li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.6em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--c-gold);
}

.journey__meta {
  margin: var(--space-3) 0 0;
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-xs);
}

.journey__meta dt {
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--c-gold);
}

.journey__meta dd {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--c-text);
}

/* --- Hover: the year under the cursor swells, the rest give way -----------
   Pointer devices only. On a touch screen :hover sticks after a tap and the
   row would stay in a half-open state nobody asked for.

   This is deliberately `scale` and not `flex-grow`. Growing the hovered card
   re-laid out the whole row, which moved every other card sideways under a
   stationary cursor — so the pointer kept landing on a different card, which
   grew, which moved the row again. The result was a shake, not an animation.
   A transform changes nothing about the layout: the hit boxes stay exactly
   where they were, the cursor stays on the card it is pointing at, and the
   whole effect is one composited property.

   Every rule here is also held off while the row is opening a card
   (`.is-shifting`, set by journey.js). Widths are changing then, so the cards
   slide under a stationary cursor and :hover walks across three or four of them
   in half a second — the same shake, arriving from the other direction. */
@media (hover: hover) and (min-width: 1200px) {
  .journey:not(.is-shifting) .journey__item:hover {
    position: relative;
    z-index: 2;
  }

  .journey:not(.is-shifting) .journey__item:hover > .journey__card {
    scale: var(--hover-scale);
    border-color: rgb(201 162 39 / 0.38);
  }

  .journey:not(.is-shifting) .journey__item:hover .journey__name { color: var(--c-text); }
  .journey:not(.is-shifting) .journey__item:hover .journey__shot { opacity: 0.92; }

  .journey:not(.is-shifting) .journey__list:hover .journey__item:not(:hover):not(.is-active) > .journey__card {
    scale: 0.965;
    opacity: 0.72;
  }
}

/* --- Rail and arrows ----------------------------------------------------- */
.journey__nav {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-8);
  padding-inline: var(--gutter);
}

.journey__arrow {
  flex: none;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  border-radius: 50%;
  color: var(--c-text-muted);
  transition:
    color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out),
    opacity var(--dur-fast) var(--ease-out);
}

.journey__arrow:hover:not(:disabled),
.journey__arrow:focus-visible {
  color: var(--c-gold-soft);
  background-color: rgb(201 162 39 / 0.1);
}

.journey__arrow:disabled {
  opacity: 0.28;
  cursor: default;
}

.journey__arrow svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* One chevron drawn once. The forward arrow is the same glyph flipped, and in
   RTL both flip again — "next" is always the direction the reader is going. */
.journey__arrow--next svg { scale: -1 1; }
[dir="rtl"] .journey__arrow--prev svg { scale: -1 1; }
[dir="rtl"] .journey__arrow--next svg { scale: 1 1; }

.journey__rail {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Flat, not a gradient: a gradient has a direction and this line is mirrored
   between the two scripts. */
.journey__rail::before {
  content: "";
  position: absolute;
  inset-inline: 22px;
  inset-block-start: 50%;
  height: 1px;
  background: rgb(201 162 39 / 0.3);
}

.journey__rail li {
  position: relative;
  z-index: 1;
}

/* 44px of finger, 9px of dot. These duplicate what the cards already do, so
   they are hidden from assistive tech rather than announced twice — the cards
   and the arrows are the accessible controls.

   Spans, not buttons, and that is the point: a focusable element inside an
   `aria-hidden` region is a rule violation even with `tabindex="-1"`, because
   clicking one still moves focus into a subtree assistive tech has been told to
   ignore. As spans they are a mouse affordance and nothing else — every move
   they offer is on the cards and the arrows too. */
.journey__dot {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}

.journey__dot::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--c-bg);
  box-shadow: 0 0 0 1.5px rgb(201 162 39 / 0.55);
  transition:
    width var(--dur-base) var(--ease-out),
    height var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}

.journey__dot:hover::before { background: rgb(201 162 39 / 0.4); }

.journey__dot.is-active::before {
  width: 13px;
  height: 13px;
  background: var(--c-gold);
  box-shadow:
    0 0 0 3px rgb(201 162 39 / 0.2),
    0 0 22px 4px rgb(201 162 39 / 0.5);
}

.journey__hint {
  margin: var(--space-6) auto 0;
  max-width: none;
  text-align: center;
  color: var(--c-text-muted);
  font-size: var(--text-xs);
  letter-spacing: 0.28em;
  text-transform: uppercase;
}

/* --- Narrow frames -------------------------------------------------------
   Seven cards will not fit, so the row becomes a snap scroller and the cards
   take a fixed width. Opening one changes no width at all here, which is what
   keeps the centring in journey.js exact.

   The row is taller than on a wide frame for the opposite reason to the one you
   would expect: the cards are narrower, so the same words take more lines. A
   tall narrow window measured 696px against 651px on a desktop frame. */
@media (max-width: 1199px) {
  .journey {
    --card-w: min(78vw, 340px);
    --card-h: 400px;
    --row-h: clamp(700px, 64vh, 780px);
  }

  .journey__strip {
    padding-inline: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .journey__strip::-webkit-scrollbar { display: none; }

  .journey__list {
    width: max-content;
    gap: var(--space-4);
    /* Half a frame of padding at each end, so the first and last cards can
       reach the middle like every other one. */
    padding-inline: max(var(--gutter), calc(50% - 0.5 * var(--card-w)));
  }

  .journey__item,
  .journey__item.is-active {
    flex: 0 0 var(--card-w);
    scroll-snap-align: center;
  }

  /* The strip is swiped here, so nothing needs to shrink to signal which card
     is open — the open one is the one in front of you. */
  .journey__item.is-active .journey__num { font-size: 1.9rem; }
}

@media (max-width: 1199px) {
  .journey__strip { margin-top: var(--space-12); }
  .journey__nav { gap: var(--space-2); }
  .journey__hint { letter-spacing: 0.2em; }
}

/* Below 480px the two arrows and seven 44px dots no longer fit the width
   together, and the arrows are the pair that has a replacement: the strip is
   swiped, and the cards themselves still answer to the arrow keys. Shrinking
   the dots instead would have put every tap target under 44px. */
@media (max-width: 479px) {
  .journey__arrow { display: none; }
  .journey__nav { padding-inline: var(--space-2); }
}

/* The scrim's dark pool is aimed at the heading, and the heading changes corner
   with the reading direction. Without this the Arabic page puts its title over
   the brightest part of the globe. */
[dir="rtl"] .journey__scrim {
  background:
    radial-gradient(85% 62% at 82% 26%, rgb(10 14 20 / 0.93), rgb(10 14 20 / 0) 72%),
    linear-gradient(
      to bottom,
      var(--c-bg) 0%,
      rgb(10 14 20 / 0.62) 18%,
      rgb(10 14 20 / 0.62) 74%,
      var(--c-bg) 100%
    );
}

/* --- Script differences --------------------------------------------------
   Uppercasing and wide tracking are Latin devices. Arabic has no letter case
   and its joined script only suffers from added tracking. */
[dir="rtl"] .journey__eyebrow,
[dir="rtl"] .journey__title,
[dir="rtl"] .journey__name,
[dir="rtl"] .journey__label,
[dir="rtl"] .journey__meta dt,
[dir="rtl"] .journey__hint {
  letter-spacing: 0;
  text-transform: none;
}

/* The year stays Latin numerals in every language, so it keeps its tracking. */
:lang(zh) .journey__eyebrow,
:lang(zh) .journey__title,
:lang(zh) .journey__name,
:lang(zh) .journey__label,
:lang(zh) .journey__meta dt,
:lang(zh) .journey__hint {
  text-transform: none;
}

:lang(zh) .journey__lede,
:lang(zh) .journey__wins { line-height: 1.75; }

/* --- Motion off ----------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .journey__strip { scroll-snap-type: none; }
  .journey__shot { scale: 1; }
  .journey__list:hover .journey__item:not(:hover):not(.is-active) > .journey__card {
    scale: 1;
    opacity: 1;
  }
}

/* --- 03 · Our Business ----------------------------------------------------
   Four pillars — Global Trading, Shipping, Terminals & Storage, Refining —
   under "One Chain. Four Capabilities."

   No scroll mechanic (the user's call, 2026-08-24, replacing the first,
   pinned build): on wide frames the row simply stands, and the cursor wakes
   one industry at a time — gold frame, full-brightness photograph, and an
   ambient video loop fading in over the still (ships steaming, the flare
   burning, light on the water, routes pulsing). business.js owns only
   playback and, below 1200px, which card is `.is-active`; every visual
   decision is here.

   Two layouts, and the JS never asks a media query which one is live:

   row      ≥1200px. All four cards share the row; hover is the mechanic.
            Hover emphasis is scale, never flex-grow — section 02's shake
            lesson — so the row's layout never moves under the cursor.
   scroller <1200px. Section 02's snap scroller; a swipe makes the nearest
            card the active one, and the active card plays while the section
            is on screen. `.is-active` styling lives only in this layout.

   Every card shows all of its content in both layouts: activation is
   emphasis, never disclosure.
   -------------------------------------------------------------------------- */

.pillars {
  position: relative;
  isolation: isolate;
  background: var(--c-bg);
  /* The header is pinned above whatever passes beneath it, so a menu jump
     needs the margin to land the heading clear of the bar. */
  scroll-margin-block-start: var(--header-h);
}

.pillars__stage {
  position: relative;
  overflow: clip;
  padding-block: var(--section-block);
  padding-block-start: var(--space-12);
}

/* --- Background: golden strands --------------------------------------------
   Not a globe — section 02 already has one, and two night earths in a row
   read as a repeat (the user flagged exactly that). This plate is abstract:
   fine golden filaments and lit nodes on near-black, the section's "one
   chain" drawn as light. Same grammar as before: an object anchored
   opposite the heading, masked to its own soft edge, held back by the scrim. */
.pillars__net {
  position: absolute;
  inset-block-start: -6%;
  inset-inline-end: -8%;
  z-index: -2;
  width: min(64%, 980px);
  height: auto;
  max-width: none;      /* base.css caps every img at 100%; this one is bled */
  opacity: 0.55;
  -webkit-mask-image: radial-gradient(ellipse closest-side, #000 55%, transparent 98%);
  mask-image: radial-gradient(ellipse closest-side, #000 55%, transparent 98%);
}

.pillars__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    /* Heaviest over the heading block — the one place text meets the artwork
       with no card behind it. */
    radial-gradient(85% 62% at 18% 22%, rgb(10 14 20 / 0.9), rgb(10 14 20 / 0) 72%),
    linear-gradient(
      to bottom,
      var(--c-bg) 0%,
      rgb(10 14 20 / 0.55) 16%,
      rgb(10 14 20 / 0.55) 78%,
      var(--c-bg) 100%
    );
}

[dir="rtl"] .pillars__scrim {
  background:
    radial-gradient(85% 62% at 82% 22%, rgb(10 14 20 / 0.9), rgb(10 14 20 / 0) 72%),
    linear-gradient(
      to bottom,
      var(--c-bg) 0%,
      rgb(10 14 20 / 0.55) 16%,
      rgb(10 14 20 / 0.55) 78%,
      var(--c-bg) 100%
    );
}

/* One measure for every band — section 02's rule, for section 02's reason. */
.pillars__head,
.pillars__strip,
.pillars__nav,
.pillars__hint {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
}

/* --- Heading --------------------------------------------------------------- */
.pillars__head { padding-inline: var(--gutter); }

.pillars__eyebrow {
  margin: 0 0 var(--space-4);
  color: var(--c-gold);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.26em;
  text-transform: uppercase;
}

.pillars__title {
  font-weight: var(--weight-regular);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

/* Gold at display size: 5.6:1 on the page ground, far past the 3:1 large-text
   floor, and the scrim above holds the artwork out of this band. */
.pillars__accent { color: var(--c-gold); }

.pillars__intro {
  margin-top: var(--space-4);
  max-width: min(46ch, 100%);
  color: var(--c-text-muted);
}

/* --- The strip -------------------------------------------------------------- */
.pillars__strip {
  position: relative;
  margin-top: var(--space-8);
  padding-inline: var(--gutter);
}

.pillars__list {
  display: flex;
  align-items: stretch;
  gap: clamp(12px, 1.4vw, 24px);
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pillars__item {
  display: flex;
  flex: 1 1 0;
  min-width: 0;
}

/* --- The card ---------------------------------------------------------------
   Column of three bands: words, photograph, doorway. The photograph fills its
   box absolutely for section 02's reason: in flow, `height: 100%` against an
   auto-height card resolves to auto and the 3:4 plate would set its own band
   height. The row's height comes from the tallest card and flex stretches the
   rest to match — nothing here animates layout, so it cannot wobble. */
.pillars__card {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-md);
  background: rgb(18 24 33 / 0.62);
  transition:
    border-color var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out),
    /* Answers the cursor, so it is softer and shorter than the rest —
       --ease-out's near-instant start reads as a snap at hover speed. */
    scale 340ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity var(--dur-slow) var(--ease-out);
}

/* Filament along the woken card's top edge — section 02's signature. */
.pillars__card::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 14%;
  z-index: 3;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--c-gold-soft), transparent);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
}

.pillars__top { padding: var(--space-6) var(--space-6) var(--space-4); }

.pillars__num {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 1.7vw, 1.75rem);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  line-height: 1.1;
  color: var(--c-gold-soft);
}

.pillars__name {
  margin: var(--space-3) 0 0;
  font-size: clamp(1.15rem, 1.5vw, 1.55rem);
  font-weight: var(--weight-regular);
  letter-spacing: 0.05em;
  line-height: 1.25;
  text-transform: uppercase;
  color: var(--c-text);
  overflow-wrap: break-word;
  hyphens: auto;
}

.pillars__desc {
  margin: var(--space-3) 0 0;
  max-width: 38ch;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--c-text-muted);
  transition: color var(--dur-slow) var(--ease-out);
}

/* --- Media: a still, and the loop that wakes over it ------------------------ */
.pillars__media {
  position: relative;
  flex: 1 1 auto;
  min-height: clamp(260px, 34vh, 460px);
  overflow: hidden;
}

.pillars__shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Recession is opacity alone — a filter here re-rasterises on every frame,
     which is section 02's stutter. */
  opacity: 0.58;
  transition:
    opacity var(--dur-slow) var(--ease-out),
    scale 900ms var(--ease-out);
}

/* The ambient loop sits over the still, invisible until it truly has frames:
   business.js flips `.is-playing` on the media band off the video's own
   `playing` event, so a file still arriving never shows as a black flash —
   the still simply holds until the motion is ready. */
.pillars__motion {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
  transition: opacity 500ms var(--ease-out);
}

.pillars__media.is-playing .pillars__motion { opacity: 1; }

/* --- The doorway -------------------------------------------------------------
   Each pillar will have its own page one day. The <a> below ships without an
   href on purpose: it is not focusable and not announced as a link until the
   page it names exists, at which point the href alone brings it to life —
   the same contract as the header menu. Interactive styling is scoped to
   [href] so nothing looks clickable before it is. */
.pillars__cta {
  margin: 0;
  padding: var(--space-4) var(--space-6) var(--space-6);
}

.pillars__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-gold-soft);
  text-decoration: none;
}

.pillars__link svg {
  flex: none;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: translate var(--dur-base) var(--ease-out);
}

/* The arrow points where the reader is going. */
[dir="rtl"] .pillars__link svg { scale: -1 1; }

.pillars__link[href]:hover,
.pillars__link[href]:focus-visible { color: var(--c-gold); }
.pillars__link[href]:hover svg { translate: 4px 0; }
[dir="rtl"] .pillars__link[href]:hover svg { translate: -4px 0; }

/* --- Rail and hint ------------------------------------------------------------
   The dots are a position indicator for the scroller, so they exist only
   there — on a wide frame there is no position to indicate. Spans, not
   buttons, for section 02's reason: they duplicate what a swipe already
   does, so they stay a mouse convenience outside the accessibility tree. */
.pillars__nav {
  margin-top: var(--space-6);
  padding-inline: clamp(2rem, 14vw, 16rem);
}

.pillars__rail {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pillars__rail::before {
  content: "";
  position: absolute;
  inset-inline: 22px;
  inset-block-start: 21px;
  height: 1px;
  background: rgb(201 162 39 / 0.3);
}

.pillars__rail li {
  position: relative;
  z-index: 1;
}

.pillars__dot {
  display: grid;
  justify-items: center;
  width: 44px;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
  color: var(--c-text-muted);
  transition: color var(--dur-base) var(--ease-out);
}

.pillars__dot::before {
  content: "";
  display: block;
  width: 9px;
  height: 9px;
  margin-block: 17.5px 10px;
  border-radius: 50%;
  background: var(--c-bg);
  box-shadow: 0 0 0 1.5px rgb(201 162 39 / 0.55);
  transition:
    width var(--dur-base) var(--ease-out),
    height var(--dur-base) var(--ease-out),
    margin var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}

.pillars__dot:hover::before { background: rgb(201 162 39 / 0.4); }

.pillars__dot.is-active { color: var(--c-gold-soft); }

.pillars__dot.is-active::before {
  width: 13px;
  height: 13px;
  margin-block: 15.5px 8px;
  background: var(--c-gold);
  box-shadow:
    0 0 0 3px rgb(201 162 39 / 0.2),
    0 0 22px 4px rgb(201 162 39 / 0.5);
}

.pillars__hint {
  margin: var(--space-4) auto 0;
  max-width: none;
  text-align: center;
  color: var(--c-text-muted);
  font-size: var(--text-xs);
  letter-spacing: 0.28em;
  text-transform: uppercase;
}

/* --- Wide row: the cursor is the mechanic ------------------------------------ */
@media (min-width: 1200px) {
  /* No position to indicate and nothing to swipe. */
  .pillars__nav,
  .pillars__hint { display: none; }

  /* The row at rest is calmer than the scroller's inactive cards — nothing
     is "selected" here, so nothing should look switched off. */
  .pillars__shot { opacity: 0.78; }

  /* A wide frame without a cursor (a large tablet) gets no hover and no
     loops, so the stills simply stand at full strength. */
  @media (hover: none) {
    .pillars__shot { opacity: 1; }
  }
}

@media (hover: hover) and (min-width: 1200px) {
  .pillars__item:hover,
  .pillars__item:focus-within {
    position: relative;
    z-index: 2;
  }

  /* Scale, never flex-grow: the hit boxes stay exactly where they are, so
     the cursor cannot land on a moving card — section 02's shake, avoided
     the same way. :focus-within is for the day the Explore links get their
     hrefs; it costs nothing today. */
  .pillars__item:hover > .pillars__card,
  .pillars__item:focus-within > .pillars__card {
    scale: 1.03;
    border-color: rgb(201 162 39 / 0.62);
    box-shadow:
      0 0 0 1px rgb(201 162 39 / 0.22),
      0 30px 70px rgb(0 0 0 / 0.6),
      0 0 60px -12px rgb(201 162 39 / 0.35);
  }

  .pillars__item:hover > .pillars__card::before,
  .pillars__item:focus-within > .pillars__card::before {
    opacity: 1;
    box-shadow: 0 0 18px 2px rgb(227 199 122 / 0.45);
  }

  /* The still wakes first — full light plus a slow drift while the loop is
     still arriving — and the video then fades in over it. */
  .pillars__item:hover .pillars__shot,
  .pillars__item:focus-within .pillars__shot {
    opacity: 1;
    scale: 1.06;
    transition:
      opacity var(--dur-slow) var(--ease-out),
      scale 7s linear;
  }

  .pillars__item:hover .pillars__desc,
  .pillars__item:focus-within .pillars__desc { color: var(--c-text); }

  /* The rest of the row steps back a little, without moving. */
  .pillars__list:hover .pillars__item:not(:hover) > .pillars__card {
    opacity: 0.82;
  }
}

/* --- Scroller -------------------------------------------------------------------
   Below 1200px four cards will not sit in a row. Section 02's snap scroller,
   with the same centring contract in business.js. `.is-active` means
   anything only here. */
@media (max-width: 1199px) {
  .pillars {
    --card-w: min(80vw, 360px);
  }

  /* At 64% of a phone the strands were a thread; behind the heading they
     need to read as an object. The scrim still owns the contrast. */
  .pillars__net {
    width: min(96%, 640px);
    inset-block-start: -2%;
    opacity: 0.5;
  }

  .pillars__strip {
    padding-inline: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .pillars__strip::-webkit-scrollbar { display: none; }

  .pillars__list {
    width: max-content;
    gap: var(--space-4);
    padding-inline: max(var(--gutter), calc(50% - 0.5 * var(--card-w)));
  }

  .pillars__item,
  .pillars__item.is-active {
    flex: 0 0 var(--card-w);
    scroll-snap-align: center;
  }

  /* The photograph needs a real height here — the card is content-sized, and
     an absolutely filled band contributes none of its own. */
  .pillars__media { min-height: clamp(240px, 36vh, 380px); }

  .pillars__item.is-active > .pillars__card {
    border-color: rgb(201 162 39 / 0.62);
    box-shadow:
      0 0 0 1px rgb(201 162 39 / 0.22),
      0 30px 70px rgb(0 0 0 / 0.6),
      0 0 60px -12px rgb(201 162 39 / 0.35);
  }

  .pillars__item.is-active > .pillars__card::before {
    opacity: 1;
    box-shadow: 0 0 18px 2px rgb(227 199 122 / 0.45);
  }

  .pillars__item.is-active .pillars__shot { opacity: 1; }
  .pillars__item.is-active .pillars__desc { color: var(--c-text); }

  .pillars__nav { padding-inline: var(--space-8); }
  .pillars__hint { letter-spacing: 0.2em; }
}

/* --- Script differences ----------------------------------------------------- */
[dir="rtl"] .pillars__eyebrow,
[dir="rtl"] .pillars__title,
[dir="rtl"] .pillars__name,
[dir="rtl"] .pillars__link,
[dir="rtl"] .pillars__hint {
  letter-spacing: 0;
  text-transform: none;
}

:lang(zh) .pillars__eyebrow,
:lang(zh) .pillars__title,
:lang(zh) .pillars__name,
:lang(zh) .pillars__link,
:lang(zh) .pillars__hint {
  text-transform: none;
}

:lang(zh) .pillars__desc { line-height: 1.75; }

/* --- Motion off --------------------------------------------------------------
   business.js never calls play() under reduced motion; hiding the element as
   well means even a paused first frame cannot appear. */
@media (prefers-reduced-motion: reduce) {
  .pillars__strip { scroll-snap-type: none; }
  .pillars__motion { display: none; }
  .pillars__card { transition: border-color var(--dur-slow) var(--ease-out); scale: 1 !important; }
  .pillars__shot { scale: 1 !important; }
  .pillars__list:hover .pillars__item:not(:hover) > .pillars__card { opacity: 1; }
}

/* --- 04 · Global Network ---------------------------------------------------
   The doc's centrepiece: a large rotating globe, golden routes between the
   operating hubs, ships tracing them, and one region's story at a time.

   The globe is a canvas (network.js — a hand-rolled dotted orthographic
   projection; no 3D library) and is pure decoration: the chips are real
   buttons, the card is aria-live, and a visually-hidden list carries every
   region's text for assistive tech and for no-JS. The section deliberately
   has no background plate — the globe IS the artwork, and the page ground
   behind it keeps it weightless.

   Layout: heading start-aligned like every section; the globe large ("the
   globe must be the focus"). On wide frames the stage is two columns — the
   card in its own start-side column, fully clear of the sphere at every
   width — and on narrow ones the card drops into the flow beneath the globe;
   chips row under everything. The canvas allows vertical panning through it
   on touch (touch-action: pan-y), so spinning the globe never traps the
   page scroll.
   -------------------------------------------------------------------------- */

.network {
  position: relative;
  isolation: isolate;
  overflow: clip;
  padding-block: var(--section-block);
  background: var(--c-bg);
  scroll-margin-block-start: var(--header-h);
}

.network__head,
.network__stage {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
}

/* --- Heading (the section family's voice) ---------------------------------- */
.network__head { padding-inline: var(--gutter); }

.network__eyebrow {
  margin: 0 0 var(--space-4);
  color: var(--c-gold);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.26em;
  text-transform: uppercase;
}

.network__title {
  font-weight: var(--weight-regular);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.network__accent { color: var(--c-gold); }

.network__intro {
  margin-top: var(--space-4);
  max-width: min(46ch, 100%);
  color: var(--c-text-muted);
}

/* --- The stage -------------------------------------------------------------- */
.network__stage {
  position: relative;
  margin-top: var(--space-8);
  padding-inline: var(--gutter);
  display: grid;
  justify-items: center;
}

.network__globe {
  position: relative;
  width: min(84vmin, 682px, 100%);
  aspect-ratio: 1;
}

/* Wide frames: the card gets its own start-side column so it can never sit
   over the sphere, whatever the width. The grid mirrors by itself in RTL.
   Chips + hint live INSIDE the stage since 2026-09-02 (user's call: they sat
   centred on the whole container, visibly off the sphere's axis) — rows 2/3
   of the GLOBE column, so they sit exactly under the sphere. */
@media (min-width: 900px) {
  .network__stage {
    grid-template-columns: minmax(240px, 340px) minmax(0, 1fr);
    align-items: center;
    column-gap: var(--space-8);
  }
  .network__card { grid-column: 1; grid-row: 1; }
  .network__globe { grid-column: 2; grid-row: 1; }
  .network__chips { grid-column: 2; grid-row: 2; justify-self: center; margin-block-start: var(--space-4); }
  .network__hint  { grid-column: 2; grid-row: 3; justify-self: center; }
}

/* A quiet halo so the sphere sits in space rather than on the page. */
.network__globe::before {
  content: "";
  position: absolute;
  inset: -14%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle, rgb(201 162 39 / 0.10) 0%, rgb(201 162 39 / 0.04) 42%, transparent 68%);
}

.network__globe canvas {
  display: block;
  touch-action: pan-y;
}

/* Hub names ride the globe. Decorative — the chips carry the semantics — and
   physically positioned: the geography must not mirror in RTL. */
.network__tag {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  white-space: nowrap;
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  color: var(--c-text-muted);
  text-shadow: 0 1px 8px rgb(4 7 11 / 0.95), 0 0 2px rgb(4 7 11 / 0.9);
  transition: opacity 220ms var(--ease-out), color 320ms var(--ease-out);
}

/* The selected country's name turns gold while the others step back —
   network.js drives the per-frame opacity, this carries the colour half of
   the spotlight (scenarios 2+3, 2026-09-08). */
.network__tag.is-active {
  color: var(--c-gold-soft);
  text-shadow: 0 1px 8px rgb(4 7 11 / 0.95), 0 0 10px rgb(201 162 39 / 0.45);
}

/* --- The region card --------------------------------------------------------
   In flow: its own start-side column on wide screens (see the stage grid),
   below the globe on narrow ones. aria-live, so a chip click reads its
   story out. */
.network__card {
  width: min(420px, 100%);
  padding: var(--space-6);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-md);
  background: rgb(18 24 33 / 0.72);
  backdrop-filter: blur(8px);
}

.network__place {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: var(--weight-regular);
  letter-spacing: 0.04em;
  color: var(--c-text);
}

.network__place::after {
  content: "";
  display: block;
  width: 26px;
  height: 2px;
  margin-top: var(--space-3);
  background: var(--c-gold);
  opacity: 0.75;
}

.network__role {
  margin: var(--space-3) 0 0;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--c-text-muted);
}

/* --- Chips ------------------------------------------------------------------ */
.network__chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin-block: var(--space-6) 0;
  padding: 0; /* the stage provides the gutter now */
  list-style: none;
}

.network__chip {
  min-height: 44px;
  min-inline-size: 44px;
  padding-inline: var(--space-4);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-pill);
  background: none;
  color: var(--c-text-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition:
    color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

.network__chip:hover {
  color: var(--c-text);
  border-color: rgb(201 162 39 / 0.45);
}

.network__chip:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
}

.network__chip.is-active {
  color: var(--c-gold-soft);
  border-color: rgb(201 162 39 / 0.62);
  background: rgb(201 162 39 / 0.12);
}

.network__hint {
  margin: var(--space-4) auto 0;
  max-width: none;
  text-align: center;
  color: var(--c-text-muted);
  font-size: var(--text-xs);
  letter-spacing: 0.28em;
  text-transform: uppercase;
}

/* --- Narrow frames ---------------------------------------------------------- */
@media (max-width: 899px) {
  .network__globe { width: min(88vw, 528px); }
  .network__card { margin-top: var(--space-6); }
}

/* --- Script differences ----------------------------------------------------- */
[dir="rtl"] .network__eyebrow,
[dir="rtl"] .network__title,
[dir="rtl"] .network__chip,
[dir="rtl"] .network__hint {
  letter-spacing: 0;
  text-transform: none;
}

:lang(zh) .network__eyebrow,
:lang(zh) .network__title,
:lang(zh) .network__chip,
:lang(zh) .network__hint {
  text-transform: none;
}

:lang(zh) .network__role { line-height: 1.75; }

/* --- Motion off -------------------------------------------------------------
   network.js already stops auto-rotation and ships; this quiets the chrome. */
@media (prefers-reduced-motion: reduce) {
  .network__tag { transition: none; }
  .network__chip { transition: none; }
}

/* --- 05 · Scale & Capabilities ---------------------------------------------
   The doc's ask, verbatim: fully minimal, big numbers counting up as they
   enter the view (scale.js does the counting). So: type on the page ground —
   no plates, no cards. Each stat is a thin rule with a short gold segment,
   an ambient media tile (added 2026-08-26 at the user's request), a small
   label, the figure in the display face, and its unit. The grid is
   deliberate rather than auto-fit: five across on wide frames, 3+2 on
   middling ones, two-up on phones — auto-fit would strand the fifth stat
   alone on its own row at tablet widths. */

.scale {
  padding-block: var(--section-block);
  background: var(--c-bg);
  scroll-margin-block-start: var(--header-h);
}

.scale__head,
.scale__grid {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.scale__eyebrow {
  margin: 0 0 var(--space-4);
  color: var(--c-gold);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.26em;
  text-transform: uppercase;
}

.scale__title {
  font-weight: var(--weight-regular);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.scale__accent { color: var(--c-gold); }

.scale__intro {
  margin-top: var(--space-4);
  max-width: min(46ch, 100%);
  color: var(--c-text-muted);
}

.scale__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-12) var(--space-8);
  margin-block: var(--space-16) 0;
}

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

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

.scale__stat {
  position: relative;
  border-block-start: 1px solid var(--c-border);
  padding-block-start: var(--space-6);
}

/* The short gold segment on the rule — the same accent register as the
   card's underline in 04. Logical inset, so it mirrors in RTL. */
.scale__stat::before {
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline-start: 0;
  width: 34px;
  height: 2px;
  background: var(--c-gold);
  opacity: 0.75;
}

/* The ambient tile above each label — a still that its 5s loop fades in
   over once real frames exist (`.is-playing` comes from the video's own
   playing/pause events, the same contract as 03's pillars). Decorative:
   the whole band is aria-hidden and the poster's alt is empty. */
.scale__media {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9; /* the clips deliver 1280×720; posters are cut from
                           their own first frame so the fade never jumps */
  margin-block-end: var(--space-6);
  overflow: hidden;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-md);
  background: var(--c-surface);
}

.scale__media img,
.scale__media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.scale__media video {
  opacity: 0;
  transition: opacity 600ms var(--ease-out);
}

.scale__media.is-playing video { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .scale__media video { display: none; }
}

.scale__label {
  margin: 0 0 var(--space-4);
  color: var(--c-text-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.scale__figure {
  margin: 0;
  font-family: var(--font-display);
  line-height: 1;
  color: var(--c-text);
}

.scale__value {
  font-size: clamp(2.75rem, 4.6vw, 4.5rem);
  font-variant-numeric: tabular-nums; /* digits keep their width mid-count */
}

.scale__suffix {
  font-size: clamp(1.4rem, 2.3vw, 2.2rem);
  color: var(--c-gold-soft);
}

.scale__unit {
  display: block;
  margin-block-start: var(--space-3);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--c-text-muted);
}

/* --- Script differences ----------------------------------------------------- */
[dir="rtl"] .scale__eyebrow,
[dir="rtl"] .scale__title,
[dir="rtl"] .scale__label {
  letter-spacing: 0;
  text-transform: none;
}

:lang(zh) .scale__eyebrow,
:lang(zh) .scale__title,
:lang(zh) .scale__label {
  text-transform: none;
}

:lang(zh) .scale__unit { line-height: 1.75; }


/* --- 06 · Our Commitment ---------------------------------------------------
   People · Planet · Reliability as three full-viewport photographs, in flow —
   deliberately not pinned (03's lesson: pinned stages fight the header band).
   Each panel: a cover photo, a vertical scrim heavy where the text sits (the
   section-01 lesson: contrast is measured against the artwork, so the text
   band is near-opaque page colour), and a statement that lights on entry.
   commit.js adds .is-armed before hiding anything — no-JS reads it all lit. */

.commit {
  background: var(--c-bg);
  padding-block-start: var(--section-block);
  scroll-margin-block-start: var(--header-h);
}

.commit__head {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block-end: var(--space-16);
}

.commit__eyebrow {
  margin: 0 0 var(--space-4);
  color: var(--c-gold);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.26em;
  text-transform: uppercase;
}

.commit__title {
  font-weight: var(--weight-regular);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.commit__accent { color: var(--c-gold); }

.commit__intro {
  margin-top: var(--space-4);
  max-width: min(46ch, 100%);
  color: var(--c-text-muted);
}

.commit__panel {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-items: end;
  overflow: hidden;
}

/* The picture wrapper leaves the grid flow (it would otherwise sit in the
   panel as an empty grid item); the img fills it edge to edge. */
.commit__panel picture {
  position: absolute;
  inset: 0;
}

.commit__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Ambient loop between the photo and the scrim; faded in only once real
   frames are playing (03's contract — no black flash while the file
   arrives). */
.commit__motion {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 900ms var(--ease-out);
}

.commit__motion.is-playing { opacity: 1; }

/* Vertical, so it needs no RTL variant. Near-opaque page colour at the text
   band, a light wash at the top so the header's band never sits on raw
   highlights. */
.commit__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgb(10 14 20 / 0.92) 0%,
    rgb(10 14 20 / 0.55) 34%,
    rgb(10 14 20 / 0.12) 62%,
    rgb(10 14 20 / 0.30) 100%
  );
}

.commit__content {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block-end: clamp(3rem, 9vh, 6rem);
}

.commit__name {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 4.5vw, 3.6rem);
  font-weight: var(--weight-regular);
  line-height: 1.1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--c-text);
}

/* One very short line per panel — the doc's ask; no lists, no paragraphs. */
.commit__lede {
  margin: 0;
  max-width: min(44ch, 100%);
  color: var(--c-text);
  font-size: var(--text-lg);
}

/* The reveal exists only once JS armed the section. */
.commit.is-armed .commit__content {
  opacity: 0;
  translate: 0 24px;
  transition:
    opacity 700ms var(--ease-out),
    translate 700ms var(--ease-out);
}

.commit.is-armed .commit__panel.is-lit .commit__content {
  opacity: 1;
  translate: 0 0;
}

@media (prefers-reduced-motion: reduce) {
  .commit.is-armed .commit__content {
    opacity: 1;
    translate: none;
    transition: none;
  }

  /* Second belt: commit.js never plays these under reduced motion. */
  .commit__motion { display: none; }
}

/* --- Script differences ----------------------------------------------------- */
[dir="rtl"] .commit__eyebrow,
[dir="rtl"] .commit__title,
[dir="rtl"] .commit__name {
  letter-spacing: 0;
  text-transform: none;
}

:lang(zh) .commit__eyebrow,
:lang(zh) .commit__title,
:lang(zh) .commit__name {
  text-transform: none;
}

:lang(zh) .commit__lede { line-height: 1.75; }

/* -- 07 · H7 Services -------------------------------------------------------
   The platform as THE APP ITSELF: a floating phone boots the H7 Services
   icon, opens into the six real pages, and every page softly projects
   copies of its own content out beside the device (the page on the glass
   never empties; the copies dissolve where they float — they never fly
   back in). Choreography locked against the Higgsfield keyframe videos in
   _source/services/. Sub-namespaces on this one section: .mob__* (stage),
   .phone* (device), .boot* (app-icon birth), .ap__* (the six page UIs,
   English/LTR product chrome), .fly* (the projected copies). */
.services {
  /* Small flow padding only (2026-09-01: the old self-pad of a full
     --header-h left a ~176px void between commit and this section in
     normal scrolling). Anchor jumps are handled the sitewide way now —
     scroll-margin — and the one-viewport contract moved into .mob's
     min-height: calc(100vh - var(--header-h)), so a jump still lands the
     dot nav exactly on the fold, never under it. */
  padding-block: var(--space-4) 0;
  background: var(--c-bg);
  scroll-margin-block-start: var(--header-h);
}

.services__head {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block-end: var(--space-8);
  /* Start-aligned like every other section head (2026-09-01, user's call —
     the centred title read as the odd one out against the left eyebrow). */
}

.services__eyebrow {
  margin: 0 0 var(--space-4);
  color: var(--c-gold);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.26em;
  text-transform: uppercase;
}

.services__title {
  font-weight: var(--weight-regular);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.services__accent { color: var(--c-gold); }

.services__intro {
  margin-top: var(--space-4);
  max-width: min(52ch, 100%);
  color: var(--c-text-muted);
}

/* Clears the header band's 32px blurred under-fade: an anchor jump to
   #services lands the section top flush with the header, and without this
   the eyebrow sat inside that fade and smeared to a gold smudge. Kept small
   — this section is one viewport and the dot nav must stay above the fold. */
.services__head { position: relative; z-index: 5; padding-block-start: clamp(32px, 5vh, 48px); padding-block-end: 0; }

[dir="rtl"] .services__eyebrow,
[dir="rtl"] .services__title {
  letter-spacing: 0;
  text-transform: none;
}

:lang(zh) .services__title {
  text-transform: none;
}

    /* Scenario 5 — the MOBILE APP, page by page. A floating phone carries
       live recreations of the six real screens; beside it, each page's
       services animate in. Everything moves: the phone floats, screens
       swipe like the app, numbers count, bars fill, vessels pulse.
       DEMO ONLY. */

    .mob {
      position: relative;
      /* One viewport MINUS the pinned header: with scroll-margin landing the
         section top at --header-h, this keeps the dot nav on the fold. */
      min-height: calc(100vh - var(--header-h));
      overflow: hidden;
      background: var(--c-bg);
      display: flex;
      flex-direction: column;
    }

    /* ---- ambient world ---- */
    .mob__world {
      position: absolute;
      inset: -4%;
      background: url("../img/services-map-dim.webp") center 38% / cover no-repeat;
      opacity: 0.14;
      filter: blur(2px) saturate(0.8);
      animation: world-drift 60s ease-in-out infinite alternate;
    }

    @keyframes world-drift {
      from { translate: 0 0; }
      to { translate: -2.5% 1.5%; }
    }

    .mob__vignette {
      position: absolute;
      inset: 0;
      background:
        radial-gradient(ellipse 80% 70% at 62% 52%, transparent 30%, rgb(4 7 11 / 0.9) 100%),
        linear-gradient(to bottom, rgb(4 7 11 / 0.55), transparent 24%);
    }

    .mob__dust i {
      position: absolute;
      width: 3px;
      height: 3px;
      border-radius: 50%;
      background: rgb(227 199 122 / 0.5);
      animation: dust var(--d, 16s) linear infinite;
      animation-delay: var(--dl, 0s);
      left: var(--x);
      top: 110%;
      opacity: 0;
    }

    @keyframes dust {
      0% { translate: 0 0; opacity: 0; }
      12% { opacity: 0.55; }
      88% { opacity: 0.25; }
      100% { translate: var(--dx, 3vw) -120vh; opacity: 0; }
    }

    /* ---- layout ---- */
    .mob__head { position: relative; z-index: 5; padding-top: clamp(20px, 4.5vh, 52px); }

    /* phone dead-centre; the sides belong to the page's flown-out content */
    .mob__grid {
      position: relative;
      z-index: 5;
      flex: 1;
      display: grid;
      place-items: center;
      width: 100%;
      padding: 0 var(--gutter) clamp(24px, 4vh, 44px);
    }

    /* the page strip: number · title · one line. Narrow screens keep it as
       a centred band above the phone; from 900px it moves to the LEFT
       column (above the note cards) so the phone rises and the whole
       section fits one viewport. */
    .mob__pagebar {
      position: relative;
      z-index: 6;
      height: 112px;
      margin-top: clamp(12px, 2.4vh, 28px);
    }

    .mob__pagebar .mob__slide { align-items: center; text-align: center; }
    .mob__pagebar .mob__title { font-size: clamp(1.3rem, 2.4vw, 2rem); margin-bottom: 6px; }
    .mob__pagebar .mob__num { margin-bottom: 8px; }
    .mob__pagebar .mob__num::after { display: none; }
    .mob__pagebar .mob__line { font-size: var(--text-sm); }

    @media (min-width: 900px) {
      .mob { position: relative; }

      .mob__pagebar {
        position: absolute;
        right: calc(50% + 12.5vw);
        width: min(20vw, 300px);
        /* Above BOTH parked note rows (2026-09-08, user's report: the fly
           cards were landing over the page line on every page) — the left
           column reads top-down: head · this strip · note row 1 · note
           row 2. Keep in step with .fly--l1/.fly--l2 below. */
        top: clamp(210px, 24vh, 270px);
        height: 180px;
        margin: 0;
      }

      /* the section intro mirrors it on the RIGHT column — same top as the
         page strip opposite (2026-09-08, user: the two must ALIGN) */
      .mob .services__intro {
        position: absolute;
        left: calc(50% + 12.5vw);
        width: min(20vw, 300px);
        max-width: none;
        /* the strip is section-relative, this box is head-relative — the
           head sits 16px (--space-4) below the section top, hence the
           offset in each stop */
        top: clamp(194px, calc(24vh - 16px), 254px);
        margin: 0;
        text-align: start;
      }

      .mob__pagebar .mob__slide { align-items: flex-start; text-align: start; justify-content: flex-start; }
      .mob__pagebar .mob__title { font-size: clamp(1.25rem, 1.7vw, 1.7rem); }
      .mob__pagebar .mob__line { font-size: var(--text-sm); }
    }

    .mob__slide {
      position: absolute;
      inset: 0;
      display: flex;
      flex-direction: column;
      justify-content: center;
      pointer-events: none;
    }

    .mob__line { margin-bottom: 0; }

    .mob__slide > * {
      opacity: 0;
      translate: 0 22px;
      transition: opacity 550ms var(--ease-out), translate 550ms var(--ease-out);
    }

    .mob__slide.is-on > * { opacity: 1; translate: 0 0; }
    .mob__slide.is-on > *:nth-child(1) { transition-delay: 60ms; }
    .mob__slide.is-on > *:nth-child(2) { transition-delay: 160ms; }
    .mob__slide.is-on > *:nth-child(3) { transition-delay: 260ms; }
    .mob__slide.is-on > *:nth-child(4) { transition-delay: 360ms; }

    .mob__num {
      margin: 0 0 14px;
      color: var(--c-gold);
      font-family: var(--font-body);
      font-size: var(--text-xs);
      font-weight: var(--weight-medium);
      letter-spacing: 0.3em;
    }

    .mob__num::after {
      content: "";
      display: inline-block;
      vertical-align: middle;
      width: clamp(40px, 5vw, 90px);
      height: 1px;
      margin-inline-start: 14px;
      background: linear-gradient(to right, rgb(201 162 39 / 0.6), transparent);
    }

    .mob__title {
      margin: 0 0 12px;
      font-family: var(--font-display);
      font-weight: var(--weight-regular);
      font-size: clamp(1.7rem, 3.4vw, 2.9rem);
      line-height: 1.12;
      letter-spacing: 0.02em;
      color: var(--c-text);
    }

    .mob__title em { font-style: normal; color: var(--c-gold-soft); }

    .mob__line { margin: 0 0 26px; color: var(--c-text-muted); font-size: var(--text-base); max-width: 40ch; }

    .mob__feats { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }

    .mob__feats li {
      position: relative;
      padding-inline-start: 26px;
      color: var(--c-text-muted);
      font-size: var(--text-sm);
      letter-spacing: 0.02em;
    }

    .mob__feats li::before {
      content: "";
      position: absolute;
      inset-inline-start: 0;
      top: 0.62em;
      width: 14px;
      height: 1px;
      background: var(--c-gold);
    }

    .mob__feats b { color: var(--c-text); font-weight: var(--weight-medium); }

    /* dots */
    .mob__nav {
      position: absolute;
      bottom: clamp(8px, 2vh, 24px);
      inset-inline: 0;
      z-index: 9;
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 2px;
    }

    .mob__nav button {
      width: 44px;
      height: 44px;
      padding: 0;
      border: none;
      background: none;
      cursor: pointer;
      display: grid;
      place-content: center;
    }

    .mob__nav button i {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: var(--c-border);
      transition: background 300ms, scale 300ms, box-shadow 300ms;
    }

    .mob__nav button.is-on i {
      background: var(--c-gold-soft);
      scale: 1.25;
      box-shadow: 0 0 12px rgb(227 199 122 / 0.7);
    }

    /* ---- right: THE PHONE ---- */
    .mob__stage {
      position: relative;
      display: grid;
      place-items: center;
      perspective: 1400px;
      min-height: 60vh;
    }

    .mob__halo {
      position: absolute;
      width: 130%;
      aspect-ratio: 1;
      border-radius: 50%;
      background: radial-gradient(circle, rgb(201 162 39 / 0.16) 0%, rgb(201 162 39 / 0.05) 38%, transparent 62%);
      animation: halo-breathe 7s ease-in-out infinite;
    }

    @keyframes halo-breathe {
      0%, 100% { opacity: 0.8; scale: 1; }
      50% { opacity: 1; scale: 1.05; }
    }

    .phone-tilt { transform-style: preserve-3d; will-change: transform; }

    .phone {
      position: relative;
      /* sized from the viewport HEIGHT so head + phone + dots always fit
         one browser page (phone height ≈ 2.11 × width) */
      width: clamp(220px, calc((100vh - 360px) / 2.11), 330px);
      aspect-ratio: 9 / 19;
      border-radius: 13.5% / 6.4%;
      background: linear-gradient(150deg, #23262c, #0d0f13 40%, #16181d 70%, #2a2d33);
      padding: 2.6%;
      box-shadow:
        inset 0 0 0 1px rgb(255 255 255 / 0.14),
        inset 0 0 0 4px #08090c,
        0 40px 90px rgb(0 0 0 / 0.7),
        0 0 80px rgb(201 162 39 / 0.12);
      animation: phone-float 8s ease-in-out infinite;
    }

    @keyframes phone-float {
      0%, 100% { translate: 0 0; }
      50% { translate: 0 -14px; }
    }

    /* side keys */
    .phone::before, .phone::after {
      content: "";
      position: absolute;
      background: #35383f;
      border-radius: 2px;
    }

    .phone::before { right: -2px; top: 26%; width: 3px; height: 9%; }
    .phone::after { left: -2px; top: 20%; width: 3px; height: 5%; box-shadow: 0 5.2vh 0 #35383f; }

    .phone__screen {
      position: absolute;
      inset: 1.9%;
      border-radius: 11.4% / 5.4%;
      background: #0a0e15;
      overflow: hidden;
      container-type: size;
    }

    .phone__island {
      position: absolute;
      top: 2.6%;
      left: 50%;
      translate: -50% 0;
      width: 27%;
      height: 2.6%;
      border-radius: 999px;
      background: #050608;
      z-index: 40;
    }

    .phone__reflect {
      position: absolute;
      top: 104%;
      width: 74%;
      height: 10%;
      border-radius: 50%;
      background: radial-gradient(ellipse, rgb(201 162 39 / 0.14), transparent 65%);
      filter: blur(6px);
    }

    /* screen glass sheen */
    .phone__screen::after {
      content: "";
      position: absolute;
      inset: 0;
      z-index: 50;
      pointer-events: none;
      background: linear-gradient(118deg, rgb(255 255 255 / 0.055) 0%, transparent 30%);
    }

    /* =========================================================
       THE REVEAL — 2s after a page lands, the services and
       figures it offers drift softly OUT of the phone and rest
       beside it; then they slip back in before the next page
       ========================================================= */
    /* THE PROJECTION: the page on the glass stays COMPLETE the whole time.
       A soft copy of each block rises from its exact spot on the page,
       glides out past the glass and grows to rest beside the phone. When
       its moment is over it does NOT fly back into the device — it simply
       dissolves where it floats, drifting up a breath. */
    .mob__fly { position: absolute; inset: 0; z-index: 7; pointer-events: none; }
    .fly-group { position: absolute; inset: 0; }

    .fly {
      position: absolute;
      width: min(20vw, 300px);
      container-type: inline-size;
      font-family: var(--font-en);
      color: #e8ebf0;
      opacity: 0;
      /* the parked (inside-the-screen) pose is written INLINE by JS, so the
         start state is concrete and the outward flight can never be skipped */
      transition:
        transform 560ms cubic-bezier(0.55, 0, 0.6, 1),
        opacity 220ms ease 300ms;
    }

    .fly.is-primed { opacity: 1; transition-duration: 560ms, 140ms; transition-delay: 0ms, 0ms; }

    .fly-group.is-out .fly {
      opacity: 1;
      transition: transform 1400ms var(--ease-out), opacity 140ms linear;
    }

    /* the travelling glow: bright as it leaves the glass, settling soft */
    .fly-group.is-out .fly__in { animation: fly-glow 1.8s var(--ease-out) forwards; }
    .fly-group.is-out .fly:nth-of-type(2) .fly__in { animation-delay: 180ms; }
    .fly-group.is-out .fly:nth-of-type(3) .fly__in { animation-delay: 360ms; }
    .fly-group.is-out .fly:nth-of-type(4) .fly__in { animation-delay: 540ms; }

    @keyframes fly-glow {
      0% { filter: drop-shadow(0 0 34px rgb(227 199 122 / 0.65)); }
      100% { filter: drop-shadow(0 0 14px rgb(227 199 122 / 0.3)); }
    }

    .fly-group.is-out .fly:nth-of-type(2) { transition-delay: 180ms; }
    .fly-group.is-out .fly:nth-of-type(3) { transition-delay: 360ms; }
    .fly-group.is-out .fly:nth-of-type(4) { transition-delay: 540ms; }

    /* the goodbye: dissolve in place, never back into the device */
    .fly-group.is-fading .fly {
      opacity: 0;
      translate: 0 -16px;
      transition: opacity 650ms ease, translate 650ms ease, transform 0ms 650ms;
    }

    .fly-group.is-fading .fly:nth-of-type(2) { transition-delay: 90ms; }
    .fly-group.is-fading .fly:nth-of-type(3) { transition-delay: 180ms; }
    .fly-group.is-fading .fly:nth-of-type(4) { transition-delay: 270ms; }

    /* flown cards carry their own body and glow */
    .fly .ap__card,
    .fly .ap__cards .ap__stat,
    .fly .fly__panel,
    .fly .fly__map,
    .fly__note {
      background: rgb(15 21 32 / 0.96);
      border: 1px solid rgb(201 162 39 / 0.42);
      box-shadow: 0 24px 60px rgb(0 0 0 / 0.55), 0 0 44px rgb(201 162 39 / 0.18);
    }

    /* the note: one service category, with its explanation */
    .fly__note {
      border-radius: 14px;
      padding: 18px 22px;
      min-height: 122px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      text-align: start;
    }

    .fly__note b {
      display: block;
      font-family: var(--font-body);
      font-weight: 600;
      font-size: 1.02rem;
      letter-spacing: 0.02em;
      line-height: 1.4;
      color: var(--c-text);
      margin-bottom: 7px;
    }

    .fly__note b em { font-style: normal; color: var(--c-gold-soft); }

    .fly__note span {
      display: block;
      color: var(--c-text-muted);
      font-size: 0.86rem;
      line-height: 1.65;
    }

    .fly__note span em { font-style: normal; color: var(--c-gold-soft); }

    .fly .ap__cards { gap: 2.4cqw; }
    .fly .ap__fill { transition: width 900ms var(--ease-out) 700ms; }
    .fly-group.is-out .ap__fill { width: var(--w); }
    .fly-group.is-out .ap__ring .fgc { stroke-dashoffset: 37.2; }

    .fly__panel { border-radius: 3.4cqw; padding: 2.2cqw 3.6cqw; }
    .fly__panel :is(.ap__row, .ap__alert, .ap__row-bar, .ap__set):last-child { border-bottom: none; }

    .fly__map { position: relative; border-radius: 3.4cqw; overflow: hidden; aspect-ratio: 16 / 10; }
    .fly__map img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 60% 42%; }

    .fly__dot {
      position: absolute;
      width: 2.6cqw;
      height: 2.6cqw;
      border-radius: 50%;
      background: #e3c77a;
      box-shadow: 0 0 3cqw #e3c77a;
      animation: bell-blink 2.2s ease-in-out infinite;
    }

    /* a tidy two-column grid: both columns the same distance from the
       phone, both rows on the same lines, every card the same size. The
       rows sit low enough to clear the page strip in the left column. */
    /* Parked rows sit BELOW the page strip (left) / intro (right) — moved
       down from 42%/72% when the strip rose above them (2026-09-08). */
    .fly--l1 { right: calc(50% + 12.5vw); top: 52%; }
    .fly--l2 { right: calc(50% + 12.5vw); top: 75%; }
    .fly--r1 { left: calc(50% + 12.5vw); top: 52%; }
    .fly--r2 { left: calc(50% + 12.5vw); top: 75%; }

    /* Short desktop windows squeeze the rows into each other — drop the
       second row further; a card edge below the fold beats an overlap. */
    @media (min-width: 900px) and (max-height: 849px) {
      .fly--l2, .fly--r2 { top: 82%; }
    }

    @media (max-width: 900px) {
      .mob__fly { display: none; }
    }

    /* =========================================================
       BOOT — the H7 Services icon is born on the glass, then
       opens into the app (iOS launch language)
       ========================================================= */
    .boot {
      position: absolute;
      inset: 0;
      z-index: 30;
      display: grid;
      place-items: center;
      background: radial-gradient(ellipse 90% 60% at 50% 42%, #0c1119 0%, #07090d 70%);
      transition: opacity 500ms var(--ease-out) 250ms;
    }

    .boot__glow {
      position: absolute;
      top: 50%;
      left: 50%;
      translate: -50% -50%;
      width: 70cqw;
      aspect-ratio: 1;
      border-radius: 50%;
      background: radial-gradient(circle, rgb(201 162 39 / 0.22), transparent 60%);
      opacity: 0;
      transition: opacity 900ms var(--ease-out);
    }

    .boot.is-on .boot__glow { opacity: 1; }

    .boot__ring {
      position: absolute;
      top: 50%;
      left: 50%;
      width: 30cqw;
      aspect-ratio: 1;
      margin: -15cqw 0 0 -15cqw;
      border: 1px solid rgb(227 199 122 / 0.7);
      border-radius: 50%;
      opacity: 0;
    }

    .boot.is-on .boot__ring { animation: boot-ring 1.4s var(--ease-out) 150ms; }
    .boot.is-on .boot__ring--b { animation-delay: 420ms; }

    @keyframes boot-ring {
      0% { scale: 0.25; opacity: 0.9; }
      100% { scale: 2.6; opacity: 0; }
    }

    .appicon {
      position: relative;
      width: 42cqw;
      aspect-ratio: 1;
      border-radius: 24%;
      background: linear-gradient(155deg, #3b3c37 0%, #131414 52%, #1b1c19 100%);
      box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 0.14),
        inset 0 0 0 1px rgb(255 255 255 / 0.05),
        0 6cqw 12cqw rgb(0 0 0 / 0.6),
        0 0 14cqw rgb(201 162 39 / 0.18);
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 2.6cqw;
      overflow: hidden;
      opacity: 0;
      scale: 0.3;
    }

    .appicon img { width: 52%; height: auto; }

    .appicon span {
      color: #c9a227;
      font-family: var(--font-en);
      font-weight: 700;
      font-size: 4.2cqw;
      letter-spacing: 0.16em;
    }

    .boot.is-on .appicon { animation: icon-pop 1s cubic-bezier(0.2, 1.4, 0.35, 1) 350ms forwards; }

    @keyframes icon-pop {
      0% { opacity: 0; scale: 0.3; rotate: -8deg; filter: blur(10px); }
      55% { opacity: 1; filter: blur(0); }
      100% { opacity: 1; scale: 1; rotate: 0deg; filter: blur(0); }
    }

    /* the shine that licks across the icon once it lands */
    .appicon::after {
      content: "";
      position: absolute;
      inset: -20%;
      background: linear-gradient(115deg, transparent 36%, rgb(255 255 255 / 0.30) 48%, transparent 60%);
      translate: -130% 0;
    }

    .boot.is-on .appicon::after { animation: icon-shine 1s ease-in-out 1.35s; }

    @keyframes icon-shine {
      to { translate: 130% 0; }
    }

    /* launch: the icon opens into the app */
    .boot.is-launch { opacity: 0; }
    .boot.is-launch .appicon {
      animation: none;
      opacity: 0;
      scale: 2.4;
      transition: scale 650ms var(--ease-in), opacity 520ms var(--ease-in) 80ms;
    }

    .boot.is-gone { display: none; }

    /* stage entrance + nav gating. The stage hides only once JS has ARMED
       the section (site contract: no-JS readers always see it lit). */
    .mob__stage { transition: opacity 900ms var(--ease-out), translate 900ms var(--ease-out); }
    .mob.is-armed .mob__stage { opacity: 0; translate: 0 46px; }
    .mob.is-armed.is-here .mob__stage { opacity: 1; translate: 0 0; }
    .mob__nav { opacity: 0; pointer-events: none; transition: opacity 600ms var(--ease-out); }
    .mob.is-ready .mob__nav { opacity: 1; pointer-events: auto; }

    /* =========================================================
       THE SIX APP PAGES — portrait, faithful to the real app
       ========================================================= */
    .ap {
      position: absolute;
      inset: 0;
      display: flex;
      flex-direction: column;
      font-family: var(--font-en);
      color: #e8ebf0;
      background: #0a0e15;
      opacity: 0;
      translate: 0 9%;
      transition: opacity 600ms var(--ease-out), translate 600ms var(--ease-out);
      z-index: 1;
    }

    .ap.is-on { opacity: 1; translate: 0 0; z-index: 2; }
    .ap.is-off { opacity: 0; translate: 0 -7%; }

    /* interior stagger on arrival */
    .ap__body > * {
      opacity: 0;
      translate: 0 14px;
      transition: opacity 480ms var(--ease-out), translate 480ms var(--ease-out);
    }

    .ap.is-on .ap__body > * { opacity: 1; translate: 0 0; }
    .ap.is-on .ap__body > *:nth-child(1) { transition-delay: 180ms; }
    .ap.is-on .ap__body > *:nth-child(2) { transition-delay: 280ms; }
    .ap.is-on .ap__body > *:nth-child(3) { transition-delay: 380ms; }
    .ap.is-on .ap__body > *:nth-child(4) { transition-delay: 480ms; }
    .ap.is-on .ap__body > *:nth-child(5) { transition-delay: 580ms; }
    .ap.is-on .ap__body > *:nth-child(6) { transition-delay: 680ms; }

    .ap__status {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 4.5cqw 7cqw 1cqw;
      font-size: 3.6cqw;
      font-weight: 600;
      letter-spacing: 0.02em;
    }

    .ap__sig { display: flex; gap: 1.4cqw; align-items: center; }
    .ap__sig i { display: block; width: 4cqw; height: 2.6cqw; border-radius: 0.8cqw; background: #cfd5dd; }
    .ap__sig i:nth-child(1) { width: 3cqw; opacity: 0.9; }
    .ap__sig i:nth-child(3) { width: 5.6cqw; border: 0.35cqw solid #cfd5dd; background: linear-gradient(to right, #cfd5dd 70%, transparent 70%); }

    .ap__title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 1.5cqw 6cqw 2cqw;
      font-size: 7.2cqw;
      font-weight: 700;
      letter-spacing: 0.01em;
    }

    .ap__bell { position: relative; width: 6cqw; height: 6cqw; color: var(--c-gold); }
    .ap__bell svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 1.8; }
    .ap__bell::after {
      content: "";
      position: absolute;
      top: -0.5cqw;
      right: -0.5cqw;
      width: 2.2cqw;
      height: 2.2cqw;
      border-radius: 50%;
      background: var(--c-gold);
      animation: bell-blink 2.4s ease-in-out infinite;
    }

    @keyframes bell-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

    .ap__tabs {
      display: flex;
      gap: 5cqw;
      padding: 0 6cqw 2.4cqw;
      font-size: 3.4cqw;
      color: #8b94a3;
      border-bottom: 1px solid rgb(255 255 255 / 0.07);
    }

    .ap__tabs .on { color: var(--c-gold); box-shadow: 0 1.6cqw 0 -1cqw var(--c-gold); }

    .ap__body {
      flex: 1;
      min-height: 0;
      padding: 3cqw 5cqw;
      display: flex;
      flex-direction: column;
      gap: 2.6cqw;
      overflow: hidden;
    }

    .ap__body--flush { padding: 0; gap: 0; }

    /* tab bar */
    .ap__bar {
      display: flex;
      justify-content: space-around;
      align-items: center;
      padding: 2.2cqw 2cqw 5cqw;
      border-top: 1px solid rgb(255 255 255 / 0.08);
      background: rgb(8 11 17 / 0.92);
    }

    .ap__bar span {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 1cqw;
      font-size: 2.6cqw;
      color: #6d7684;
    }

    .ap__bar svg { width: 5.6cqw; height: 5.6cqw; stroke: currentColor; fill: none; stroke-width: 1.7; }
    .ap__bar .on { color: var(--c-gold); }

    /* shared atoms */
    .ap__cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2cqw; }

    .ap__stat {
      background: rgb(255 255 255 / 0.045);
      border: 1px solid rgb(255 255 255 / 0.07);
      border-radius: 3cqw;
      padding: 2.6cqw 1cqw;
      text-align: center;
    }

    .ap__stat b { display: block; font-family: var(--font-en-display); font-weight: 400; font-size: 6.4cqw; line-height: 1.15; }
    .ap__stat span { font-size: 2.5cqw; color: #8b94a3; letter-spacing: 0.06em; }
    .ap__stat--gold b { color: var(--c-gold-soft); }
    .ap__stat--teal b { color: #4ec8bd; }
    .ap__stat--green b { color: #63c186; }

    .ap__card {
      background: rgb(255 255 255 / 0.045);
      border: 1px solid rgb(255 255 255 / 0.07);
      border-radius: 3.4cqw;
      padding: 3cqw 3.6cqw;
    }

    .ap__card-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 1cqw; }
    .ap__card-top b { font-size: 4cqw; font-weight: 600; }

    .ap__route { color: #8b94a3; font-size: 3cqw; margin-bottom: 2.2cqw; }
    .ap__route em { font-style: normal; color: #aeb6c2; }

    .ap__pill {
      padding: 0.9cqw 2.6cqw;
      border-radius: 999px;
      font-size: 2.7cqw;
      white-space: nowrap;
      color: var(--pc, #8b94a3);
      background: color-mix(in srgb, var(--pc, #8b94a3) 16%, transparent);
    }

    .ap__pill--teal { --pc: #4ec8bd; }
    .ap__pill--amber { --pc: #e0a13f; }
    .ap__pill--green { --pc: #63c186; }
    .ap__pill--red { --pc: #e0655f; }
    .ap__pill--gold { --pc: #e3c77a; }
    .ap__pill--blue { --pc: #6b93c7; }

    .ap__track { position: relative; height: 1.4cqw; border-radius: 999px; background: rgb(255 255 255 / 0.09); overflow: hidden; }

    .ap__fill {
      position: absolute;
      inset: 0 auto 0 0;
      width: 0;
      border-radius: inherit;
      background: var(--pc, var(--c-gold));
      transition: width 1100ms var(--ease-out) 500ms;
    }

    .ap.is-on .ap__fill { width: var(--w); }

    .ap__meta { display: flex; justify-content: space-between; font-size: 2.8cqw; color: #8b94a3; margin-top: 1.6cqw; }
    .ap__meta b { color: #d8dde5; font-weight: 500; }

    .ap__kicker { font-size: 3cqw; letter-spacing: 0.14em; text-transform: uppercase; color: #8b94a3; }

    /* rows (tanks / lists / settings) */
    .ap__row {
      display: grid;
      grid-template-columns: 1fr auto;
      align-items: center;
      gap: 2.6cqw;
      padding: 2.4cqw 0;
      border-bottom: 1px solid rgb(255 255 255 / 0.06);
      font-size: 3.4cqw;
    }

    .ap__row small { display: block; color: #8b94a3; font-size: 2.8cqw; margin-top: 0.6cqw; }

    .ap__row-bar { display: grid; grid-template-columns: 8.2em 1fr auto; align-items: center; gap: 2.4cqw; padding: 2.5cqw 0; border-bottom: 1px solid rgb(255 255 255 / 0.06); font-size: 3.2cqw; }
    .ap__row-bar i { font-style: normal; color: #8b94a3; font-size: 2.9cqw; min-width: 6cqw; text-align: right; }

    /* map page */
    .ap__map { position: relative; flex: 1; min-height: 0; overflow: hidden; }
    .ap__map img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 62% 40%; opacity: 0.9; }

    .ap__map svg { position: absolute; inset: 0; width: 100%; height: 100%; }

    .ap-ship { fill: #e3c77a; }
    .ap-pulse { fill: none; stroke: #e3c77a; stroke-width: 0.5; opacity: 0; animation: ap-pulse 3s var(--ease-out) infinite; }
    .ap-pulse--b { animation-delay: 1s; }
    .ap-pulse--c { animation-delay: 2s; }

    @keyframes ap-pulse {
      0% { r: 1.5; opacity: 0.8; }
      75%, 100% { r: 8; opacity: 0; }
    }

    .ap__mapstats {
      display: flex;
      border-top: 1px solid rgb(255 255 255 / 0.08);
      background: rgb(8 11 17 / 0.9);
    }

    .ap__mapstats div { flex: 1; text-align: center; padding: 2.6cqw 0; }
    .ap__mapstats b { display: block; font-family: var(--font-en-display); font-weight: 400; font-size: 5.8cqw; color: var(--c-gold-soft); }
    .ap__mapstats span { font-size: 2.3cqw; letter-spacing: 0.1em; color: #8b94a3; text-transform: uppercase; }
    .ap__mapstats div + div { border-left: 1px solid rgb(255 255 255 / 0.07); }

    /* --- the real Map View (mirrors the client's app screenshot) --- */
    .ap__seg2 {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 1.5cqw 5cqw 2.4cqw;
      border-bottom: 1px solid rgb(255 255 255 / 0.07);
    }

    .ap__seg2-tabs { display: flex; gap: 5cqw; font-size: 3.6cqw; color: #98a1af; }
    .ap__seg2-tabs .on { color: var(--c-gold); box-shadow: 0 1.8cqw 0 -1.2cqw var(--c-gold); }

    .ap__seg2-ic { display: flex; gap: 3.4cqw; align-items: center; position: relative; }
    .ap__seg2-ic svg { width: 5cqw; height: 5cqw; stroke: #cfd5dd; fill: none; stroke-width: 1.7; }
    .ap__seg2-ic::after {
      content: "";
      position: absolute;
      right: -0.6cqw;
      top: -0.6cqw;
      width: 2cqw;
      height: 2cqw;
      border-radius: 50%;
      background: var(--c-gold);
    }

    .ap__mapctl {
      display: flex;
      gap: 2cqw;
      padding: 2.2cqw 5cqw;
      border-bottom: 1px solid rgb(255 255 255 / 0.06);
    }

    .ap__ctl {
      flex: 1;
      display: flex;
      flex-direction: column;
      gap: 0.6cqw;
      padding: 1.6cqw 2.4cqw;
      border: 1px solid rgb(255 255 255 / 0.12);
      border-radius: 2cqw;
    }

    .ap__ctl small { font-size: 2.2cqw; color: #8b94a3; }
    .ap__ctl b { font-size: 3cqw; font-weight: 600; color: #e8ebf0; }

    .ap__ctl--out {
      flex: 0 0 auto;
      flex-direction: row;
      align-items: center;
      justify-content: center;
      gap: 1.6cqw;
      padding-inline: 3cqw;
      color: var(--c-gold);
      font-size: 3cqw;
      font-weight: 600;
    }

    .ap__ctl--out svg { width: 4.4cqw; height: 4.4cqw; stroke: var(--c-gold); fill: none; stroke-width: 1.8; }

    .ap__map img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

    .ap__mlabel {
      position: absolute;
      translate: -50% -50%;
      text-align: center;
      color: #aeb6c2;
      text-shadow: 0 1px 4px rgb(0 0 0 / 0.8);
    }

    .ap__mlabel b { display: block; font-weight: 500; font-size: 3cqw; letter-spacing: 0.05em; line-height: 1.35; }
    .ap__mlabel i { display: block; font-style: normal; font-size: 2.3cqw; opacity: 0.78; line-height: 1.3; }
    .ap__mlabel--cn { color: #8fd7a8; }

    .ap__shipb {
      position: absolute;
      translate: -50% -50%;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 0.7cqw;
    }

    .ap__shipb i {
      display: grid;
      place-content: center;
      width: 5.4cqw;
      height: 5.4cqw;
      border-radius: 50%;
      border: 1px solid rgb(227 199 122 / 0.9);
      background: rgb(10 14 20 / 0.6);
    }

    .ap__shipb i svg { width: 3.4cqw; height: 3.4cqw; stroke: #e3c77a; fill: none; stroke-width: 2.2; }

    .ap__shipb em {
      font-style: normal;
      font-size: 1.9cqw;
      letter-spacing: 0.06em;
      color: #e3c77a;
      background: rgb(10 14 20 / 0.6);
      border: 1px solid rgb(227 199 122 / 0.4);
      border-radius: 1cqw;
      padding: 0.2cqw 1cqw;
    }

    .ap__mapzoom {
      position: absolute;
      left: 3.5cqw;
      bottom: 4cqw;
      display: flex;
      flex-direction: column;
      gap: 1.6cqw;
    }

    .ap__mapzoom u {
      text-decoration: none;
      display: grid;
      place-content: center;
      width: 7cqw;
      height: 7cqw;
      border-radius: 1.8cqw;
      border: 1px solid rgb(255 255 255 / 0.2);
      background: rgb(10 14 20 / 0.6);
      color: #e8ebf0;
      font-size: 4cqw;
    }

    .ap__mapzoom svg { width: 4cqw; height: 4cqw; stroke: #e8ebf0; fill: none; stroke-width: 1.6; }

    /* icon stat bar */
    .ap__mapstats div {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 0.8cqw;
      padding: 2.2cqw 0 2.6cqw;
    }

    .ap__mapstats .ap__msl {
      display: flex;
      align-items: center;
      gap: 1.4cqw;
      color: #aeb6c2;
      font-size: 2.4cqw;
      letter-spacing: 0.02em;
      text-transform: none;
    }

    .ap__msl svg { width: 4.2cqw; height: 4.2cqw; stroke: var(--c-gold); fill: none; stroke-width: 1.6; }

    .ap__mapstats small { font-size: 2.2cqw; color: #8b94a3; }

    .ap__seg {
      position: absolute;
      top: 3cqw;
      left: 5cqw;
      right: 5cqw;
      display: flex;
      gap: 4cqw;
      font-size: 3.6cqw;
      z-index: 3;
    }

    .ap__seg .on { color: var(--c-gold); box-shadow: 0 1.8cqw 0 -1.2cqw var(--c-gold); }
    .ap__seg span:not(.on) { color: #98a1af; }

    /* utilization ring (operations) */
    .ap__ring { display: flex; align-items: center; gap: 3cqw; }
    .ap__ring svg { width: 15cqw; height: 15cqw; }
    .ap__ring circle { fill: none; stroke-width: 3.2; }
    .ap__ring .bgc { stroke: rgb(255 255 255 / 0.09); }
    .ap__ring .fgc {
      stroke: var(--c-gold);
      stroke-linecap: round;
      stroke-dasharray: 100.5;
      stroke-dashoffset: 100.5;
      transform: rotate(-90deg);
      transform-origin: 50% 50%;
      transition: stroke-dashoffset 1300ms var(--ease-out) 500ms;
    }

    .ap.is-on .ap__ring .fgc { stroke-dashoffset: 37.2; } /* 63% */

    /* financial */
    .ap__fin { display: grid; grid-template-columns: 1fr 1fr; gap: 2.2cqw; }
    .ap__fin .ap__card b.n { display: block; font-family: var(--font-en-display); font-weight: 400; font-size: 6.6cqw; line-height: 1.2; }
    .ap__up { color: #63c186; font-size: 2.8cqw; }
    .ap__dn { color: #e0655f; font-size: 2.8cqw; }

    /* alerts */
    .ap__sev { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2cqw; }

    .ap__alert { display: grid; grid-template-columns: auto 1fr auto; gap: 2.6cqw; align-items: center; padding: 2.6cqw 0; border-bottom: 1px solid rgb(255 255 255 / 0.06); }
    .ap__alert i {
      width: 2.6cqw; height: 2.6cqw; border-radius: 50%;
      background: var(--pc, #8b94a3);
      box-shadow: 0 0 3cqw var(--pc, transparent);
      animation: bell-blink 2s ease-in-out infinite;
    }
    .ap__alert b { display: block; font-size: 3.3cqw; font-weight: 600; }
    .ap__alert small { display: block; color: #8b94a3; font-size: 2.7cqw; margin-top: 0.5cqw; }

    /* profile */
    .ap__me { display: flex; align-items: center; gap: 3.4cqw; }
    .ap__avatar {
      width: 13cqw; height: 13cqw; border-radius: 50%;
      background: radial-gradient(circle at 35% 30%, #2c3646, #131a26);
      border: 1px solid rgb(255 255 255 / 0.12);
      display: grid; place-content: center;
      color: #9fb0c8;
    }
    .ap__avatar svg { width: 7cqw; height: 7cqw; stroke: currentColor; fill: none; stroke-width: 1.6; }
    .ap__me b { display: block; font-size: 4.4cqw; }
    .ap__me small { color: #8b94a3; font-size: 3cqw; }

    .ap__set { display: flex; align-items: center; gap: 3cqw; padding: 2.8cqw 0; border-bottom: 1px solid rgb(255 255 255 / 0.06); font-size: 3.5cqw; }
    .ap__set svg { width: 5cqw; height: 5cqw; stroke: var(--c-gold); fill: none; stroke-width: 1.7; }
    .ap__set span:last-child { margin-left: auto; color: #6d7684; }
    .ap__set--out { color: #e0655f; border-bottom: none; }
    .ap__set--out svg { stroke: #e0655f; }

    /* responsive */
    @media (max-width: 900px) {
      .mob__grid { grid-template-columns: 1fr; gap: 10px; }
      .mob__text { min-height: 300px; order: 2; text-align: center; }
      .mob__text--l .mob__slide { align-items: center; }
      .mob__text--r { min-height: 160px; order: 3; }
      .mob__text--r .mob__slide { align-items: center; }
      .mob__text--r .mob__feats { max-width: none; }
      .mob__slide { align-items: center; }
      .mob__feats li { padding-inline-start: 0; }
      .mob__feats li::before { display: none; }
      .mob__stage { min-height: 52vh; order: 1; }
      .phone { width: clamp(220px, 52vw, 280px); }
    }

    @media (prefers-reduced-motion: reduce) {
      .phone, .mob__halo, .mob__world, .ap__bell::after, .ap__alert i, .ap-pulse { animation: none; }
      .mob__stage { opacity: 1; translate: none; transition: none; }
    }

/* ==========================================================================
   08 · Footer — the last section. Per the structure doc: completely simple,
   elegant, professional — logo, one nav, the reach block (site · email ·
   refinery · socials, NO phone number), a localized © bar. The site closes
   on --c-void, the same deepest black the opening rose out of. A canvas of
   very gentle drifting gold motes (footer.js) gives it life; no-JS and
   reduced motion both get the still, fully lit footer.
   ========================================================================== */
.footer {
  position: relative;
  overflow: hidden;
  background: var(--c-void);
  padding-block: var(--space-12) 0;
}

/* the same centred gold hairline the inner sections use as a threshold */
.footer::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 14%;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--c-gold-soft), transparent);
  opacity: 0.55;
}

.footer__dust {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.footer__inner {
  position: relative; /* above the canvas */
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  gap: var(--space-12);
  justify-items: center;
  text-align: center;
}

/* The brand emblem — the mark set like a jewel: two hairline gold rings,
   five quiet indices on the band (the 01–05 value chain, no words), a glint
   travelling the bezel, a breathing halo, and an occasional sheen crossing
   the mark itself. Pure CSS; reduced-motion keeps the still emblem. */
.footer__emblem {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 150px;
  aspect-ratio: 1;
}

.footer__emblem-glow {
  position: absolute;
  inset: -70px;
  background: radial-gradient(circle, rgb(201 162 39 / 0.15), transparent 62%);
  animation: footer-breathe 8s ease-in-out infinite alternate;
}

.footer__emblem-ring {
  position: absolute;
  inset: 0;
  border: 1px solid rgb(201 162 39 / 0.3);
  border-radius: 50%;
  /* five indices at 72° — the chain's five stages, wordless */
  background:
    radial-gradient(circle 2.5px at 50% 3%, rgb(227 199 122 / 0.7) 99%, transparent),
    radial-gradient(circle 2.5px at 94.7% 35.5%, rgb(227 199 122 / 0.7) 99%, transparent),
    radial-gradient(circle 2.5px at 77.6% 88%, rgb(227 199 122 / 0.7) 99%, transparent),
    radial-gradient(circle 2.5px at 22.4% 88%, rgb(227 199 122 / 0.7) 99%, transparent),
    radial-gradient(circle 2.5px at 5.3% 35.5%, rgb(227 199 122 / 0.7) 99%, transparent);
}

.footer__emblem-ring--inner {
  inset: 12px;
  border-color: rgb(201 162 39 / 0.16);
  background: none;
}

.footer__emblem-glint {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
    transparent 0 76%, rgb(227 199 122 / 0.85) 88%, transparent 96%);
  mask: radial-gradient(closest-side,
    transparent calc(100% - 14px), #000 calc(100% - 12px) calc(100% - 1px), transparent);
  -webkit-mask: radial-gradient(closest-side,
    transparent calc(100% - 14px), #000 calc(100% - 12px) calc(100% - 1px), transparent);
  animation: footer-glint 16s linear infinite;
}

.footer__brand {
  position: relative;
  display: inline-block;
  line-height: 0;
}

.footer__brand img {
  width: 84px;
  height: 84px;
}

/* sheen sweep, clipped to the mark itself; rests off-canvas between passes */
.footer__brand::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(115deg,
    transparent 38%, rgb(255 243 214 / 0.5) 50%, transparent 62%) no-repeat;
  background-size: 300% 100%;
  background-position: 130% 0;
  mask: url("../img/h7-logo.svg") center / contain no-repeat;
  -webkit-mask: url("../img/h7-logo.svg") center / contain no-repeat;
  animation: footer-sheen 7s linear infinite;
}

@keyframes footer-breathe {
  from { opacity: 0.55; }
  to   { opacity: 1; }
}

@keyframes footer-glint {
  to { rotate: 360deg; }
}

@keyframes footer-sheen {
  0%   { background-position: 130% 0; }
  26%  { background-position: -30% 0; }
  100% { background-position: -30% 0; }
}

@keyframes footer-thread {
  0%, 12%    { inset-block-start: -64px; opacity: 0; }
  20%        { opacity: 1; }
  54%        { opacity: 1; }
  62%, 100%  { inset-block-start: 100%; opacity: 0; }
}

.footer__nav ul,
.footer__reach ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.footer__nav ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  column-gap: var(--space-6);
}

.footer__nav a,
.footer__reach a {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;  /* tap target — both axes: أعمالنا is only 33px wide */
  min-inline-size: 44px;
  color: var(--c-text-muted);
  text-decoration: none;
  font-size: var(--text-sm);
  letter-spacing: 0.02em;
  transition: color var(--dur-base) var(--ease-out);
}

.footer__nav a[href]:hover,
.footer__nav a[href]:focus-visible,
.footer__reach a[href]:hover,
.footer__reach a[href]:focus-visible {
  color: var(--c-gold-soft);
}

/* Terms & Privacy have no pages yet — inert until their hrefs exist, the
   same contract as section 03's Explore links. Socials wait on the company's
   profile URLs the same way. */
.footer a:not([href]) {
  cursor: default;
  opacity: 0.55;
}

/* Per the brief every reach item is named — official website · official
   email · refinery website · social media. Quiet caps over each link; the
   44px link box supplies the intra-pair air, li spacing separates groups. */
.footer__reach > ul:not(.footer__social) > li + li {
  margin-block-start: var(--space-3);
}

/* Group thresholds — the site's hairline motif structures the reach column:
   a centred fade between groups on mobile; from 900 up the rule anchors to
   the inline start and carries a short gold segment (the scale section's
   signature), so RTL mirrors by itself via the logical inset. */
.footer__reach > ul:not(.footer__social) > li + li,
.footer__intlbox {
  position: relative;
  padding-block-start: var(--space-3);
}

.footer__reach > ul:not(.footer__social) > li + li::before,
.footer__intlbox::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--c-border), transparent);
}

.footer__reach > ul:not(.footer__social) > li + li::after,
.footer__intlbox::after {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 26px;
  height: 1px;
  background: var(--c-gold);
  opacity: 0.6;
  display: none;
}

.footer__label {
  display: block;
  margin: 0;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-text-muted);
  opacity: 0.72;
}

[dir="rtl"] .footer__label {
  letter-spacing: 0; /* joined Arabic never gets tracking — sitewide rule */
}

/* H7 International — the socials' slot (2026-09-06, user's call): a gold
   wordmark with real depth that opens h7-intl.com, its e-mail beneath.
   The gradient fill needs drop-shadow (not text-shadow, which shows through
   the transparent fill); the stacked shadows are the "3D". */
.footer__intlbox {
  margin-block-start: var(--space-4);
  display: grid;
  justify-items: center; /* the stacked mobile footer centres everything */
}

.footer__reach .footer__intl {
  display: inline-block;
  min-block-size: 0;
  padding-block: var(--space-2);
  /* The site's own body face — the Playfair display cut was rejected
     (2026-09-06, user: «همون فونت خود سایت باشد»). */
  font-weight: 700;
  font-size: 1.3rem;
  line-height: 1.3;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: linear-gradient(168deg, #f4e3ab 0%, var(--c-gold-soft) 32%, var(--c-gold) 60%, #7e6414 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 1px 0 rgb(56 42 6 / 0.95))
          drop-shadow(0 2px 1px rgb(0 0 0 / 0.6))
          drop-shadow(0 7px 16px rgb(201 162 39 / 0.3));
  transition: filter var(--dur-base) var(--ease-out), translate var(--dur-base) var(--ease-out);
}

.footer__reach .footer__intl span {
  display: block;
  font-size: 0.78em;
  letter-spacing: 0.14em;
}

.footer__reach .footer__intl:hover,
.footer__reach .footer__intl:focus-visible {
  translate: 0 -2px;
  filter: drop-shadow(0 1px 0 rgb(56 42 6 / 0.95))
          drop-shadow(0 3px 2px rgb(0 0 0 / 0.6))
          drop-shadow(0 10px 22px rgb(227 199 122 / 0.55));
}

.footer__reach .footer__intl-mail {
  margin-block-start: var(--space-1);
}

.footer__bar {
  position: relative;
  margin-block-start: var(--space-12);
  padding-block: var(--space-6);
  border-block-start: 1px solid var(--c-border);
  text-align: center;
}

.footer__bar small {
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
  color: var(--c-text-muted);
}

@media (min-width: 900px) {
  .footer__inner {
    grid-template-columns: auto 1fr auto;
    gap: var(--space-16);
    align-items: start;
    justify-items: start;
    text-align: start;
  }

  /* the emblem owns its column — larger, and centred on the column's height */
  .footer__emblem {
    inline-size: 190px;
    align-self: center;
  }

  .footer__nav {
    justify-self: center;
  }

  .footer__nav ul {
    display: grid;
    column-gap: 0;
    justify-content: start;
    position: relative;
    padding-inline-start: var(--space-6);
  }

  /* the golden thread — a hairline rail beside the menu (::before) and a
     soft light that slides down it every few seconds (::after), the brief's
     «خطوط روان بسیار ملایم» made literal */
  .footer__nav ul::before {
    content: "";
    position: absolute;
    inset-block: 6px;
    inset-inline-start: 0;
    width: 1px;
    background: linear-gradient(to bottom,
      transparent, rgb(201 162 39 / 0.3) 15% 85%, transparent);
  }

  .footer__nav ul::after {
    content: "";
    position: absolute;
    inset-inline-start: -1px;
    inset-block-start: -64px;
    width: 3px;
    height: 64px;
    border-radius: 999px;
    background: linear-gradient(to bottom,
      transparent, rgb(227 199 122 / 0.75), transparent);
    filter: drop-shadow(0 0 6px rgb(227 199 122 / 0.45));
    opacity: 0;
    animation: footer-thread 8s ease-in-out infinite;
  }

  .footer__intlbox {
    justify-items: start;
  }

  /* The lockup box spans the column (it is the widest item); its glyphs run
     LTR from the left edge. On RTL pages the column's text edge is the
     right, so align the lockup's own text there — physical right, because
     the anchor itself is dir="ltr". */
  [dir="rtl"] .footer__reach .footer__intl {
    text-align: right;
  }

  /* reach thresholds gain their start-anchored form and the gold segment */
  .footer__reach > ul:not(.footer__social) > li + li::before,
  .footer__intlbox::before {
    background: var(--c-border);
    opacity: 0.6;
  }

  .footer__reach > ul:not(.footer__social) > li + li::after,
  .footer__intlbox::after {
    display: block;
  }
}

/* Entry reveal — armed by JS, so no-JS readers get the footer already lit. */
.footer.is-armed .footer__inner,
.footer.is-armed .footer__bar {
  opacity: 0;
  translate: 0 16px;
  transition: opacity 900ms var(--ease-out), translate 900ms var(--ease-out);
}

.footer.is-armed .footer__bar {
  transition-delay: 140ms;
}

.footer.is-lit .footer__inner,
.footer.is-lit .footer__bar {
  opacity: 1;
  translate: 0 0;
}

@media (prefers-reduced-motion: reduce) {
  .footer.is-armed .footer__inner,
  .footer.is-armed .footer__bar {
    opacity: 1;
    translate: none;
    transition: none;
  }

  .footer__dust {
    display: none;
  }

  /* the emblem holds still: rings + halo stay, the travelling light goes,
     the sheen's resting position is off-canvas so it never shows */
  .footer__emblem-glow,
  .footer__brand::after {
    animation: none;
  }

  .footer__emblem-glint,
  .footer__nav ul::after {
    display: none; /* the nav keeps its still hairline rail */
  }
}
  