/* ==========================================================================
   AMAN X — MOTION SYSTEM
   --------------------------------------------------------------------------
   Motion depicts a system observing, measuring, and correcting human behaviour
   — calmly and continuously. Governing rule: motion arrives and settles; it
   does not perform.

   Three levels (see review/MOTION-DIRECTION.md):
     L1 Essential interaction — never disabled, it is feedback
     L2 Content storytelling  — explains structure, scroll-triggered
     L3 Ambient brand motion  — atmosphere, must never compete with reading

   Architecture note: reveal elements are authored VISIBLE. They are only hidden
   once JS confirms it can animate them (html.js-motion). A JS failure therefore
   leaves content readable rather than invisible — the difference between
   progressive enhancement and a broken page.
   ========================================================================== */

/* ==========================================================================
   L2 — SCROLL REVEALS
   ========================================================================== */

/* Baseline: fully visible. No JS, no CSS animation support -> content shows. */
[data-reveal] {
  opacity: 1;
  transform: none;
}

/* Only once JS is present do we take control and hide, ready to animate in. */
.js-motion [data-reveal] {
  opacity: 0;
  transition:
    opacity   var(--motion-slow) var(--ease-enter),
    transform var(--motion-slow) var(--ease-enter);
  transition-delay: calc(var(--i, 0) * var(--stagger-base));
  will-change: opacity, transform;
}

/* Direction-aware entrances. Logical properties mean the Arabic build mirrors
   automatically: content enters from the reading origin (right in AR, left in
   EN) so it arrives "with" the reader rather than against them. */
.js-motion [data-reveal="up"]     { transform: translateY(var(--move-lg)); }
.js-motion [data-reveal="down"]   { transform: translateY(calc(var(--move-lg) * -1)); }
/* Inline-axis entrances read their direction from the token, so RTL needs no
   override rule here — see --motion-inline-start-x in tokens.css. */
.js-motion [data-reveal="start"]  { transform: translateX(var(--motion-inline-start-x)); }
.js-motion [data-reveal="end"]    { transform: translateX(var(--motion-inline-end-x)); }
.js-motion [data-reveal="scale"]  { transform: scale(0.96); }
/* Decorative rails. Purely ornamental hairlines, so they may animate freely —
   they carry no reading value and never gate content. The rail grows from the
   reading origin; its transform-origin is set per-rail below. */
.js-motion [data-reveal="rail"]   { opacity: 1; transform: scaleX(0); }
.js-motion [data-reveal="fade"]   { transform: none; }

/* ---- Pattern D — depth resolve -------------------------------------------
   For panels and important visuals: the element resolves toward the reader
   rather than sliding. Scale only, no blur — blur is a paint-bound property
   and the research budget is transform/opacity. */
.js-motion [data-reveal="depth"] {
  transform: scale(var(--motion-depth-subtle));
  transform-origin: center;
}

/* ---- Mobile drawer link sequence ------------------------------------------
   The drawer opened as one block. Its links now settle top-to-bottom in both
   locales — the list is vertical, so reading order is the same either way, and
   the Arabic text inside each row stays RTL untouched.

   Keyed to the drawer's own open state rather than to an observer: the drawer
   is not scrolled into view, it is summoned, so an IntersectionObserver would
   be the wrong instrument. --i is already set by the template. */
.js-motion .drawer__list li {
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity   var(--motion-standard) var(--ease-enter),
    transform var(--motion-standard) var(--ease-enter);
}
.js-motion .drawer[data-open="true"] .drawer__list li {
  opacity: 1;
  transform: none;
  /* Starts after the panel itself has begun arriving, so the links do not race
     the surface they sit on. */
  transition-delay: calc(120ms + var(--i, 0) * var(--stagger-tight));
}
/* Reduced motion: the drawer is a navigation surface. It appears complete. */
@media (prefers-reduced-motion: reduce) {
  .js-motion .drawer__list li {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* ==========================================================================
   POP-OUT PATTERNS

   Three variants of one idea: the element resolves forward as its section
   arrives. They differ only in how much, because a 6-card grid and a
   full-width form panel need different amounts of the same gesture.

   Each is transform + opacity, plus border-color where the element has a
   frame. Nothing here touches layout.
   ========================================================================== */

/* Card pop — grids of services, sectors, solutions, values, certifications. */
.js-motion [data-reveal="pop"] {
  transform: translateY(var(--pop-lift)) scale(var(--pop-scale-card));
  transform-origin: center;
  transition-duration: var(--pop-duration-card);
  transition-timing-function: var(--ease-enter);
}

/* Panel pop — larger blocks: intro panels, the contact form, solution panels.
   Less scale than a card: the same proportional change on a bigger box reads
   as a zoom rather than as depth. */
.js-motion [data-reveal="panel"] {
  transform: translateY(var(--pop-lift-panel)) scale(var(--pop-scale-panel));
  transform-origin: center;
  transition-duration: var(--pop-duration-panel);
  transition-timing-function: var(--ease-enter);
}

/* List row — warning rows, feature lists, capability bullets. Deliberately the
   smallest gesture on the site; a list of eight rows each performing a visible
   entrance is a list nobody finishes reading. */
.js-motion [data-reveal="row"] {
  transform: translateY(var(--pop-lift-row)) scale(var(--pop-scale-row));
  transition-duration: var(--pop-duration-row);
  transition-timing-function: var(--ease-enter);
}

/* The frame leads the content.
   Applies to any popped element that has a visible border: the border resolves
   first, the content follows. This is the detail that makes the gesture read as
   deliberate rather than as a fade. */
.js-motion [data-reveal="pop"],
.js-motion [data-reveal="panel"] {
  transition-property: opacity, transform, border-color, box-shadow;
}
.js-motion [data-reveal="pop"]:not(.is-revealed),
.js-motion [data-reveal="panel"]:not(.is-revealed) {
  border-color: transparent;
}
.js-motion [data-reveal="pop"].is-revealed,
.js-motion [data-reveal="panel"].is-revealed {
  transition-delay: calc(var(--i, 0) * var(--section-stagger-tight));
}
/* Content inside a popped card follows its frame. One shared delay rather than
   a per-child observer: the children are already inside a revealed parent, so
   this is a transition delay, not extra machinery. */
.js-motion [data-reveal="pop"] > *,
.js-motion [data-reveal="panel"] > * {
  transition: opacity var(--pop-duration-card) var(--ease-enter) var(--pop-frame-lead);
}
.js-motion [data-reveal="pop"]:not(.is-revealed) > *,
.js-motion [data-reveal="panel"]:not(.is-revealed) > * { opacity: 0; }

/* One-time icon emphasis inside a popped card. Not a pulse — it settles once
   and stops. An icon that keeps moving is an icon nobody stops noticing. */
.js-motion [data-reveal="pop"] .card__icon,
.js-motion [data-reveal="row"] svg {
  transition: transform var(--pop-duration-card) var(--ease-emphasized) var(--pop-frame-lead);
}
.js-motion [data-reveal="pop"]:not(.is-revealed) .card__icon,
.js-motion [data-reveal="row"]:not(.is-revealed) svg {
  transform: scale(0.9);
}

/* Mobile dialect: shorter travel, quicker, and no frame-lead — on a phone the
   sequence is perceived as latency rather than as choreography. */
@media (max-width: 719px) {
  .js-motion [data-reveal="pop"] {
    transform: translateY(var(--pop-lift-sm)) scale(var(--pop-scale-card));
    transition-duration: var(--pop-duration-card-sm);
  }
  .js-motion [data-reveal="panel"] {
    transform: translateY(var(--pop-lift-sm)) scale(var(--pop-scale-panel));
    transition-duration: var(--pop-duration-card-sm);
  }
  .js-motion [data-reveal="row"] { transform: translateY(4px); }
  .js-motion [data-reveal="pop"] > *,
  .js-motion [data-reveal="panel"] > * { transition-delay: 0ms; }
}

/* ---- Pattern C — signal draw ---------------------------------------------
   For connectors and relationship lines. stroke-dashoffset changes no layout
   and no paint region, so a long draw is cheap. The dash length is supplied
   per element as --len, because it depends on the path. */
.js-motion [data-reveal="draw"] {
  opacity: 1;
  stroke-dasharray: var(--len, 200);
  stroke-dashoffset: var(--len, 200);
  transition: stroke-dashoffset var(--connector-draw-duration) var(--ease-emphasized);
  transition-delay: calc(var(--i, 0) * var(--section-stagger));
}
.js-motion [data-reveal="draw"].is-revealed { stroke-dashoffset: 0; }

.js-motion [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* Mask reveal — the safest possible Arabic text animation: the glyphs are never
   touched, only a clipping window moves across them. */
.js-motion [data-reveal="mask"] {
  opacity: 1;
  /* Opens from the reading origin: left edge in EN, right edge in AR. The
     glyphs themselves are never touched — only the clipping window moves —
     which is what makes this safe for Arabic cursive shaping. */
  clip-path: var(--motion-mask-closed);
  transition: clip-path var(--motion-deliberate) var(--ease-emphasized);
  transition-delay: calc(var(--i, 0) * var(--stagger-loose));
}
.js-motion [data-reveal="mask"].is-revealed { clip-path: inset(0 0 0 0); }

/* CSS scroll-driven reveals where supported. Progressive: the IntersectionObserver
   path stays as the fallback and both produce the same end state. */
/* Scroll-driven reveals where supported.
   `forwards` rather than `both`: with `both`, an unresolved timeline would pin
   the element at the from-state (opacity 0) — the same failure that made the
   timeline rail freeze. With `forwards` an unresolved timeline leaves the
   element at its authored state and the IntersectionObserver still reveals it,
   so the worst case is "no scroll animation", never "invisible content". */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* The hero is excluded: it runs its own timed entrance, and a scroll-driven
       animation would override that transition. Measured — with the hero
       included, badge/sub/CTA/topology all snapped 0 -> 1 at t=143ms while only
       the clip-path headline animated, because an animation beats a transition.
       Mask reveals are excluded for the same reason. */
    .js-motion [data-reveal]:not(.is-revealed):not([data-reveal="mask"]):not(.hero [data-reveal]) {
      animation: ax-reveal-in var(--motion-slow) var(--ease-enter) forwards;
      animation-timeline: view();
      animation-range: entry 10% cover 28%;
    }
  }
}
@keyframes ax-reveal-in {
  from { opacity: 0; transform: var(--reveal-from, translateY(var(--move-lg))); }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   SECTION CHAPTERS

   A section is a chapter: it establishes itself once, then sequences its
   content. The order is atmosphere -> eyebrow -> heading -> copy -> visual,
   and the whole sequence is budgeted to land inside ~900ms. Past that a
   reader stops perceiving choreography and starts perceiving delay.

   The stagger index --i is written by JS from VISUAL position, not DOM order,
   so a card cascade follows the reading direction in both locales without the
   markup being reordered. See initCascade() in motion.js.
   ========================================================================== */
.js-motion [data-chapter] [data-reveal] {
  transition-duration: var(--section-enter-duration);
  transition-timing-function: var(--ease-enter);
}

/* The heading gets its own, slower beat so it leads rather than competes. */
.js-motion [data-chapter] h2[data-reveal],
.js-motion [data-chapter] [data-reveal="mask"] {
  transition-duration: var(--section-title-duration);
}

/* Cascade members are ordered by --i, which JS sets geometrically. */
.js-motion [data-cascade] > * {
  transition-delay: calc(var(--i, 0) * var(--section-stagger));
}
.js-motion [data-cascade="tight"] > * {
  transition-delay: calc(var(--i, 0) * var(--section-stagger-tight));
}

/* Section atmosphere: one very low-contrast wash that resolves as the chapter
   opens. Opacity only — the layer itself never moves, so it costs no layout
   and no repaint beyond its own compositing. */
.section[data-chapter]::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background:
    radial-gradient(60% 50% at var(--motion-origin-inline-start) 0%,
                    color-mix(in srgb, var(--accent-line) 7%, transparent), transparent 70%);
  transition: opacity var(--section-enter-duration) var(--ease-enter);
}
.js-motion .section[data-chapter].is-entered::before { opacity: 1; }
.section[data-chapter] { position: relative; isolation: isolate; }
.section[data-chapter] > * { position: relative; z-index: 1; }

/* Mobile dialect: shorter travel, quicker, less overlap. Motion is not
   removed — it is spoken more briefly. */
@media (max-width: 719px) {
  .js-motion [data-chapter] [data-reveal] {
    transition-duration: var(--section-enter-duration-sm);
  }
  .js-motion [data-cascade] > *,
  .js-motion [data-cascade="tight"] > * {
    transition-delay: calc(var(--i, 0) * var(--section-stagger-sm));
  }
  .js-motion [data-reveal="up"]    { transform: translateY(var(--section-distance-block-sm)); }
  .js-motion [data-reveal="start"] { transform: translateX(calc(var(--section-distance-inline-sm) * var(--motion-inline-dir) * -1)); }
  .js-motion [data-reveal="end"]   { transform: translateX(calc(var(--section-distance-inline-sm) * var(--motion-inline-dir))); }
  /* No section atmosphere on small screens: it is the least legible layer and
     the most expensive per pixel on a phone GPU. */
  .section[data-chapter]::before { display: none; }
}

/* ==========================================================================
   HERO ENTRANCE
   The hero gets its own beat. Without this the elements are marked revealed in
   the same frame they are initialised, so nothing animates — measured, the hero
   was fully settled at 120ms. The stagger below is slower and wider than a
   scroll reveal because this is the opening statement, not a section arriving.
   ========================================================================== */
/* Hero sequence timing.
   Only decorative elements remain in the sequence — the badge, the two accent
   rails, and the topology. Semantic text carries no data-reveal at all, so the
   entrance can be as deliberate as it likes without touching LCP. */
.js-motion .hero [data-reveal] {
  transition-duration: 520ms;
  transition-timing-function: var(--ease-emphasized);
  transition-delay: calc(var(--i, 0) * 110ms);
}

/* ---- Decorative hero rails ---------------------------------------------- */
.hero__rail,
.hero__stats-rule {
  display: block;
  block-size: 2px;
  background: linear-gradient(to var(--motion-origin-inline-end),
              var(--accent-line), transparent);
  transform-origin: var(--motion-origin-inline-start) center;
  transition: transform var(--motion-slow) var(--ease-emphasized);
}

.hero__rail { inline-size: clamp(64px, 12vw, 132px); margin-block-end: var(--sp-5); }
.hero__stats-rule { inline-size: 100%; opacity: 0.45; margin-block-end: var(--sp-8); }

.js-motion [data-reveal="rail"].is-revealed { transform: scaleX(1); }
/* The topology resolves last and slowest — it is the payoff, not the opener.
   It is never the LCP element (the sub-headline is), so a longer settle here
   costs nothing measurable. */
.js-motion .hero .topology-wrap {
  transition-duration: 760ms;
  transition-delay: 320ms;
}

/* ==========================================================================
   L2 — SECTION HEADER RULE
   The "rule + index" signature motif: the hairline draws itself in.
   ========================================================================== */
.js-motion .eyebrow::after {
  transform: scaleX(0);
  /* Draws away from the eyebrow text, so it follows the reading direction. */
  transform-origin: var(--motion-origin-inline-start) center;
  transition: transform var(--motion-slow) var(--ease-emphasized) 120ms;
}
.js-motion .eyebrow.is-revealed::after,
.js-motion .is-revealed .eyebrow::after { transform: scaleX(1); }

/* ==========================================================================
   L3 — AMBIENT SYSTEM
   Atmosphere only. transform/opacity exclusively, pointer-events none, paused
   when the tab is hidden (WCAG 2.2.2 + battery).
   ========================================================================== */
.ambient {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  contain: strict;
}
.ambient > * { position: absolute; will-change: transform; }

/* ---- Technical grid ------------------------------------------------------
   Two hairline gradients, translated diagonally. The whole layer is one
   composited element. */
.ambient__grid {
  inset: -30%;
  opacity: var(--ambient-min);
  background-image:
    linear-gradient(to right,  var(--accent-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--accent-line) 1px, transparent 1px);
  background-size: 64px 64px;
  animation: ax-grid-drift var(--motion-ambient) var(--ease-linear) infinite;
  animation-play-state: var(--ambient-play);
}
/* Measured at 3px of travel over 2.4s — below any threshold of notice. The
   cycle is now shorter so the same 64px tile step is actually perceivable as
   drift, while staying far below the level that competes with reading. */
@keyframes ax-grid-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(64px, 64px, 0); }
}

/* ---- Gradient light ------------------------------------------------------
   The blur is baked into the radial gradient, never animated. This is the
   visual result of the original initAurora without its cost. */
.ambient__glow {
  width: 62vmax; height: 62vmax;
  border-radius: 50%;
  opacity: var(--ambient-mid);
  background: radial-gradient(circle closest-side,
              var(--ax-cyan-500) 0%, transparent 70%);
  animation: ax-glow-orbit var(--motion-ambient) var(--ease-linear) infinite;
  animation-play-state: var(--ambient-play);
}
.ambient__glow--a { inset-block-start: -22vmax; inset-inline-start: -14vmax; }
.ambient__glow--b {
  inset-block-end: -26vmax; inset-inline-end: -18vmax;
  background: radial-gradient(circle closest-side, var(--ax-green) 0%, transparent 70%);
  animation-duration: var(--motion-ambient-slow);
  animation-direction: reverse;
  opacity: var(--ambient-min);
}
/* Measured at 10px of travel over 2.4s. Widened so the light genuinely moves
   across the hero rather than hovering in place. */
@keyframes ax-glow-orbit {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(16%, 10%, 0) scale(1.16); }
  100% { transform: translate3d(0, 0, 0) scale(1); }
}

/* ---- Scan sweep ----------------------------------------------------------
   A single slow pass. Carried over from the original `laserSweep`, which was
   one of its more distinctive touches. */
.ambient__scan {
  inset-inline: 0;
  block-size: 40vh;
  background: linear-gradient(to bottom, transparent, var(--accent-line), transparent);
  /* Measured at 0.035 — contributed nothing. Either meaningful or gone; this is
     the sweep the original site used and it is worth keeping, so: meaningful. */
  opacity: 0.09;
  animation: ax-scan-sweep var(--motion-ambient) var(--ease-linear) infinite;
  animation-play-state: var(--ambient-play);
}
@keyframes ax-scan-sweep {
  0%, 100% { transform: translate3d(0, -60vh, 0); }
  50%      { transform: translate3d(0, 140vh, 0); }
}

/* Ambient behind body copy is capped harder than anywhere else. */
.section .ambient__grid { opacity: var(--ambient-text); }

/* ---- Internal-page ambient ----------------------------------------------
   Deliberately lighter than the homepage hero: internal pages should share the
   atmosphere, not compete with the hero for attention. One grid, one light. */
.page-header { position: relative; isolation: isolate; overflow: hidden; }
.page-header > .container { position: relative; z-index: 1; }
.ambient--page .ambient__grid { opacity: var(--ambient-text); }
.ambient--page .ambient__glow--a {
  width: 46vmax; height: 46vmax;
  opacity: var(--ambient-min);
  inset-block-start: -26vmax;
  inset-inline-end: -12vmax;
  inset-inline-start: auto;
}

/* ==========================================================================
   HERO TOPOLOGY — the human-risk diagram
   A capability visualisation, NOT a monitoring console. No counters, no
   timestamps, no event feed, nothing implying observed data.
   ========================================================================== */
.topology {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  /* The SVG coordinate system is geometric, not linguistic. Without this, an
     RTL document flips `text-anchor: start|end`, throwing the outboard node
     labels to the wrong side of their nodes. The Arabic strings inside still
     shape and order correctly — bidi resolves the run itself; only the anchor
     is forced to resolve geometrically. */
  direction: ltr;
}
/* Each label re-declares its own direction so mixed Arabic/Latin names
   (e.g. "أمان إكس فيش") order correctly within the anchored box. */
/* Label bidi now lives on .topology__tag (HTML layer). */

.topology__ring {
  fill: none;
  stroke: var(--accent-line);
  stroke-width: 1;
  opacity: 0.4;
  transform-origin: center;
  animation: ax-ring-pulse 8s var(--ease-linear) infinite;
  animation-play-state: var(--ambient-play);
}
.topology__ring--2 { animation-delay: -2.6s; }
.topology__ring--3 { animation-delay: -5.2s; }
/* Measured at opacity 0.049 in the audit — effectively invisible. The pulse now
   holds a legible peak before fading, so the perimeter reads as a boundary
   rather than as nothing. */
@keyframes ax-ring-pulse {
  0%   { transform: scale(0.88); opacity: 0; }
  18%  { transform: scale(0.96); opacity: 0.42; }
  70%  { transform: scale(1.14); opacity: 0.10; }
  100% { transform: scale(1.2);  opacity: 0; }
}

/* Connecting paths draw themselves in on load. */
.topology__path {
  fill: none;
  stroke: var(--accent-line);
  stroke-width: 1.75;
  opacity: 0.55;
  stroke-dasharray: var(--len, 200);
  stroke-dashoffset: var(--len, 200);
  animation: ax-path-draw var(--motion-deliberate) var(--ease-emphasized) forwards;
  animation-delay: calc(360ms + var(--i, 0) * var(--stagger-loose));
}
@keyframes ax-path-draw { to { stroke-dashoffset: 0; } }

/* Signals.
   Two distinct kinds, because the direction IS the message:

     observation — travels INWARD, node to human. This is measurement:
                   awareness engagement, simulation results, policy compliance
                   flowing back as signal.
     intervention — travels OUTWARD, human to node. This is the response:
                   training, simulation, remediation being applied.

   Making them identical (as they were) reduced the diagram to decoration. With
   direction differentiated, the cause-and-effect loop is legible without
   reading a single label — which is what the audit found missing. */
.topology__signal {
  offset-path: var(--path);
  offset-rotate: 0deg;
  animation-timing-function: var(--ease-linear);
  animation-iteration-count: infinite;
  animation-play-state: var(--ambient-play);
}

/* Inward: measurement returning to the human. Cooler, smaller, steady. */
.topology__signal--in {
  fill: var(--accent-line);
  animation-name: ax-signal-in;
  animation-duration: 5.5s;
  animation-delay: calc(var(--i, 0) * -1.3s);
}
@keyframes ax-signal-in {
  0%   { offset-distance: 100%; opacity: 0; }
  15%  { opacity: 0.85; }
  85%  { opacity: 0.85; }
  100% { offset-distance: 0%;   opacity: 0; }
}

/* Outward: intervention being applied. Warmer, larger, slightly slower. */
.topology__signal--out {
  fill: var(--ax-green);
  animation-name: ax-signal-out;
  animation-duration: 6.5s;
  animation-delay: calc(var(--i, 0) * -1.7s - 2.2s);
}
@keyframes ax-signal-out {
  0%   { offset-distance: 0%;   opacity: 0; }
  15%  { opacity: 0.9; }
  85%  { opacity: 0.9; }
  100% { offset-distance: 100%; opacity: 0; }
}

/* Capability nodes. */
.topology__node { transform-box: fill-box; transform-origin: center; }
.topology__node-dot {
  fill: var(--surface-1);
  stroke: var(--accent-line);
  stroke-width: 2.25;
  transition: fill var(--motion-standard) var(--ease-enter),
              stroke-width var(--motion-standard) var(--ease-enter);
}
.topology__node-halo {
  fill: var(--accent-line);
  opacity: 0.3;
  transform-box: fill-box;
  transform-origin: center;
  animation: ax-node-breathe 5s var(--ease-linear) infinite;
  animation-delay: calc(var(--i, 0) * -1.1s);
  animation-play-state: var(--ambient-play);
}
/* Measured at opacity 0.057 — invisible. Raised so each capability node reads
   as active rather than as a dot on a wireframe. */
@keyframes ax-node-breathe {
  0%, 100% { transform: scale(1);    opacity: 0.34; }
  50%      { transform: scale(1.45); opacity: 0.10; }
}

/* The centre: human behaviour. Larger, slower, steadier — it is the subject. */
.topology__core-ring {
  fill: none;
  stroke: var(--accent-text);
  stroke-width: 2;
  opacity: 0.7;
  transform-box: fill-box;
  transform-origin: center;
  animation: ax-core-pulse 6s var(--ease-linear) infinite;
  animation-play-state: var(--ambient-play);
}
@keyframes ax-core-pulse {
  0%, 100% { transform: scale(1); opacity: 0.5; }
  50%      { transform: scale(1.04); opacity: 0.75; }
}
/* ==========================================================================
   TOPOLOGY LABELS

   HTML, not SVG <text>. SVG font-size is expressed in user units and then
   scaled by the viewBox-to-viewport ratio, so the previous `font-size: 9px`
   rendered at 6.2-8.2px depending on breakpoint (measured), and no single
   declared value could give a stable readable size. In HTML the authored
   pixel size is the delivered pixel size.

   The layer sits in the same grid cell as the SVG; the wrapper's aspect-ratio
   matches the viewBox exactly, so the per-label percentages map onto the same
   coordinates the nodes are drawn at.
   ========================================================================== */
.topology-wrap > * { grid-area: 1 / 1; }

.topology__labels {
  position: relative;
  inline-size: 100%;
  block-size: 100%;
  /* Decorative overlay: never intercept a click meant for the page. */
  pointer-events: none;
}

.topology__tag {
  position: absolute;
  /* Physical, not logical: the radial geometry does not mirror for RTL, only
     the text inside each label does. */
  left: var(--lx);
  top: var(--ly);

  font-family: var(--font-display);
  font-weight: 550;
  line-height: 1.25;
  color: var(--text-2);
  text-wrap: balance;

  /* Mixed Arabic/Latin names ("أمان إكس فيش") order correctly within the box
     without forcing a direction on the label. */
  unicode-bidi: plaintext;
}

/* Anchoring — mirrors what text-anchor used to do, in the box model. */
/* Wide enough that the longer Arabic names stay on one line: a second
   line here grows the box toward the node halo. */
.topology__tag--middle { transform: translate(-50%, -50%); text-align: center; max-inline-size: 50%; }
.topology__tag--start  { transform: translate(0, -50%);    text-align: start;  max-inline-size: 24%; }
.topology__tag--end    { transform: translate(-100%, -50%); text-align: end;   max-inline-size: 24%; }
.topology__tag--core   {
  transform: translate(-50%, -50%);
  text-align: center;
  max-inline-size: 44%;
  color: var(--text-1);
  font-weight: 650;
}

/* Size.
   Scoped by breakpoint rather than by modifier, because the two compositions
   both ship on every page and their rules would otherwise compete: the RTL
   desktop selector carries higher specificity than the mobile modifier, so it
   silently won and pinned the Arabic mobile core label to the outer size.
   Splitting on the same 719px boundary that swaps the composition means only
   one set is ever live.

   Arabic runs slightly larger: at the same nominal size its skeleton reads
   smaller than Latin, and it has no capitals to anchor the eye. */
@media (min-width: 720px) {
  .topology__tag       { font-size: clamp(16px, 1.25vw, 19px); }
  .topology__tag--core { font-size: clamp(21px, 1.60vw, 24px); }

  :root[dir="rtl"] .topology__tag       { font-size: clamp(18px, 1.40vw, 21px); line-height: 1.5; }
  :root[dir="rtl"] .topology__tag--core { font-size: clamp(23px, 1.80vw, 26px); }
}

/* Arabic is cursive: tracking pulls the joins apart and word-spacing widens
   the gaps that already separate words. Both are Latin display conventions,
   and the previous rule applied 0.08em tracking plus uppercase — inherited
   from a monospace treatment — to Arabic as well. */
:root[dir="rtl"] .topology__tag {
  letter-spacing: normal;
  word-spacing: normal;
  text-transform: none;
}

/* Hover/focus a node -> its path and label strengthen. Desktop refinement only;
   no information is exposed on hover alone. */
@media (hover: hover) and (pointer: fine) {
  .topology__node:hover .topology__node-dot,
  .topology__node:focus-visible .topology__node-dot {
    fill: var(--accent-line);
    stroke-width: 2.5;
  }
}

/* ==========================================================================
   L1 — ESSENTIAL INTERACTION
   Never disabled: this is feedback, not decoration.
   ========================================================================== */
.btn {
  transition:
    background-color var(--motion-standard) var(--ease-enter),
    border-color     var(--motion-standard) var(--ease-enter),
    color            var(--motion-standard) var(--ease-enter),
    transform        var(--motion-fast)     var(--ease-emphasized);
}
.btn:active { transform: scale(var(--scale-press)); }

/* Arrow advances in the reading direction. */
.btn__icon--dir {
  transition: transform var(--motion-fast) var(--ease-emphasized);
}
.btn:hover .btn__icon--dir { transform: translateX(var(--move-xs)); }
:root[dir="rtl"] .btn:hover .btn__icon--dir { transform: scaleX(-1) translateX(var(--move-xs)); }

/* Magnetic buttons — desktop, pointer-fine only. --mx/--my are written by JS
   and clamped to --move-sm (8px), so the target never escapes the pointer. */
@media (hover: hover) and (pointer: fine) {
  .btn[data-magnetic] {
    transform: translate3d(var(--mx, 0), var(--my, 0), 0);
    transition: transform var(--motion-standard) var(--ease-enter),
                background-color var(--motion-standard) var(--ease-enter);
  }
  .btn[data-magnetic]:active {
    transform: translate3d(var(--mx, 0), var(--my, 0), 0) scale(var(--scale-press));
  }
}

/* Cards */
.card, .cert-card, .price-card, .timeline__item {
  transition:
    transform    var(--motion-standard) var(--ease-enter),
    border-color var(--motion-standard) var(--ease-enter),
    box-shadow   var(--motion-standard) var(--ease-enter);
}
.card:hover, .card:focus-within { transform: translateY(calc(var(--move-xs) * -1)); }

.card__icon { transition: transform var(--motion-standard) var(--ease-emphasized),
                          background-color var(--motion-standard) var(--ease-enter); }
.card:hover .card__icon, .card:focus-within .card__icon { transform: scale(1.06); }

/* Card light-tracking. --px/--py come from JS on desktop; the default keeps a
   sensible static highlight so no-JS still looks intentional. */
@media (hover: hover) and (pointer: fine) {
  .card[data-track] { position: relative; }
  .card[data-track]::before {
    content: "";
    position: absolute; inset: 0;
    border-radius: inherit;
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--motion-standard) var(--ease-enter);
    background: radial-gradient(
      420px circle at var(--px, 50%) var(--py, 0%),
      color-mix(in srgb, var(--accent-line) 12%, transparent), transparent 60%);
  }
  .card[data-track]:hover::before,
  .card[data-track]:focus-within::before { opacity: 1; }
}

/* Nav active indicator */
.nav__link { position: relative; }
.nav__link::after {
  content: "";
  position: absolute;
  inset-inline: var(--sp-3);
  inset-block-end: 6px;
  block-size: 2px;
  background: var(--accent-line);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--motion-standard) var(--ease-emphasized);
}
.nav__link:hover::after                { transform: scaleX(0.45); }
.nav__link[aria-current="page"]::after { transform: scaleX(1); }

/* Header condenses after scroll (class set by JS). */
.header {
  transition: background-color var(--motion-standard) var(--ease-enter),
              box-shadow       var(--motion-standard) var(--ease-enter),
              border-color     var(--motion-standard) var(--ease-enter);
}
.header.is-scrolled { box-shadow: var(--shadow-2); }

/* Mobile drawer links stagger in. */
.drawer[data-open="true"] .drawer__link {
  animation: ax-drawer-in var(--motion-standard) var(--ease-enter) both;
  animation-delay: calc(60ms + var(--i, 0) * var(--stagger-tight));
}
@keyframes ax-drawer-in {
  from { opacity: 0; transform: translateY(var(--move-md)); }
  to   { opacity: 1; transform: none; }
}

/* Form fields */
.input, .textarea {
  transition: border-color var(--motion-fast) var(--ease-enter),
              box-shadow   var(--motion-fast) var(--ease-enter),
              background-color var(--motion-fast) var(--ease-enter);
}
.field__error[data-visible="true"] {
  animation: ax-error-in var(--motion-standard) var(--ease-enter) both;
}
@keyframes ax-error-in {
  from { opacity: 0; transform: translateY(calc(var(--move-xs) * -1)); }
  to   { opacity: 1; transform: none; }
}
.alert--success:not([hidden]) {
  animation: ax-success-in var(--motion-standard) var(--ease-emphasized) both;
}
@keyframes ax-success-in {
  from { opacity: 0; transform: scale(0.98); }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   L2 — METHODOLOGY TIMELINE
   Progress follows the scroll and the reading direction.
   ========================================================================== */
/* The rail needs real height: with absolutely-positioned children it collapsed
   to 0px, and a ViewTimeline over a zero-height box never resolves (currentTime
   stayed null and the fill sat at scaleX(0)). */
.timeline-track {
  position: relative;
  block-size: 2px;
  margin-block-end: var(--sp-6);
}
.timeline-track::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: 100%;
  block-size: 2px;
  background: var(--border);
}
.timeline-track__fill {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  block-size: 2px;
  inline-size: 100%;
  background: var(--accent-line);
  transform: scaleX(var(--progress, 0));
  /* In Arabic the fill grows from the right. Mirroring the container alone
     would leave it growing left-to-right, which reads backwards. */
  transform-origin: var(--motion-origin-inline-start) center;
  transition: transform var(--motion-slow) var(--ease-linear);
}

/* Scroll-linked fill — driven by JavaScript, deliberately.
   CSS scroll-driven animation was implemented and tested here first: a named
   view-timeline on the step list, scoped from the section. Every part resolved
   correctly (timeline-scope, view-timeline-name, animation-timeline all
   computed as authored, subject inside scope) yet ViewTimeline.currentTime
   stayed null, and because the animation carries fill-mode:both an unresolved
   timeline PINS the rail at scaleX(0) — worse than no animation at all.
   Rather than ship a decorative feature that can silently freeze the element it
   decorates, the rail is driven by the IntersectionObserver in motion.js, which
   is deterministic and covered by tests. Scroll-driven CSS is still used for
   reveals, where an unresolved timeline degrades harmlessly.
   See review/MOTION-TECHNICAL-DECISION.md. */

.timeline__item { position: relative; }
.timeline__item .timeline__week { transition: color var(--motion-standard) var(--ease-enter); }
.timeline__item.is-active { border-block-start-color: var(--accent-text); }
.timeline__item.is-active .timeline__week { color: var(--accent-text); }

/* Methodology phase numerals */
.phase::before { transition: color var(--motion-standard) var(--ease-enter),
                             transform var(--motion-standard) var(--ease-emphasized); }
.phase:hover::before { color: var(--accent-text); transform: scale(1.08); }

/* ==========================================================================
   L2 — CERTIFICATIONS
   Authoritative, not promotional. Nothing glows, nothing spins.
   ========================================================================== */
.cert-card { position: relative; }
.cert-card .card__icon { transition: transform var(--motion-slow) var(--ease-emphasized); }
.cert-card:hover .card__icon, .cert-card:focus-within .card__icon { transform: scale(1.05); }

/* A hairline connects governance -> continuity -> quality, drawn on reveal. */
.cert-link {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: calc(var(--sp-6) * -1);
  inline-size: var(--sp-6);
  block-size: 1px;
  background: var(--border-strong);
  transform: scaleX(0);
  transform-origin: var(--motion-origin-inline-start) center;
  transition: transform var(--motion-slow) var(--ease-emphasized) 200ms;
}
.is-revealed .cert-link { transform: scaleX(1); }
@media (max-width: 899px) { .cert-link { display: none; } }

/* ==========================================================================
   PAGE TRANSITIONS — View Transition API, progressive
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) {
    animation: ax-vt-out var(--motion-page) var(--ease-exit) both;
  }
  ::view-transition-new(root) {
    animation: ax-vt-in var(--motion-page) var(--ease-enter) both;
  }
  /* Navigating forward carries you in the reading direction: the outgoing page
     leaves toward inline-start, the incoming page arrives from inline-end. In
     Arabic that is leftward, in English rightward — --motion-inline-dir carries
     the sign. The vertical component is unchanged; the inline offset is
     deliberately smaller so the transition stays a settle, not a slide. */
  @keyframes ax-vt-out {
    to {
      opacity: 0;
      transform: translate3d(calc(var(--move-xs) * var(--motion-inline-dir) * -1),
                             calc(var(--move-sm) * -1), 0);
    }
  }
  @keyframes ax-vt-in {
    from {
      opacity: 0;
      transform: translate3d(calc(var(--move-xs) * var(--motion-inline-dir)),
                             var(--move-sm), 0);
    }
  }
}
/* The header and brand persist across the transition so the frame stays stable. */
.header { view-transition-name: ax-header; }
.brand  { view-transition-name: ax-brand; }

/* ==========================================================================
   RESPONSIVE INTENSITY
   Gated by capability, not viewport width alone.
   ========================================================================== */

/* Coarse pointer / touch: drop pointer-dependent effects entirely. */
@media (hover: none), (pointer: coarse) {
  .btn[data-magnetic] { transform: none; }
  .card[data-track]::before { display: none; }
  .ambient__scan { display: none; }
}

/* ==========================================================================
   MOBILE TOPOLOGY
   A purpose-built vertical composition, not a shrunken radial one. The desktop
   diagram's 9px orbiting labels are unreadable at 320px; this sets them beside
   the nodes at body size instead.
   ========================================================================== */
.topology-wrap--mobile { display: none; }

.topology__spine {
  stroke: var(--accent-line);
  stroke-width: 2;
  opacity: 0.45;
}
.topology__ring--m { opacity: 0.3; }
/* Mobile labels sit beside the spine, so they get the full remaining width. */
.topology__tag--m {
  transform: translate(0, -50%);
  text-align: start;
  max-inline-size: 70%;
  font-weight: 550;
  color: var(--text-1);
}
/* In RTL the spine moves to the reading side and labels extend the other way. */
:root[dir="rtl"] .topology__tag--m { transform: translate(-100%, -50%); }

.topology__tag--core-m {
  font-weight: 700;
  color: var(--accent-text);
  max-inline-size: 66%;
}
:root[dir="rtl"] .topology__tag--m,
:root[dir="rtl"] .topology__tag--core-m { letter-spacing: normal; word-spacing: normal; }

/* Sizes live in the breakpoint that actually shows this composition — see the
   note on the desktop block above. */
@media (max-width: 719px) {
  .topology__tag--m      { font-size: clamp(16px, 4.4vw, 18px); }
  .topology__tag--core-m { font-size: clamp(19px, 5.2vw, 22px); }

  :root[dir="rtl"] .topology__tag--m      { font-size: clamp(16px, 4.6vw, 18px); line-height: 1.5; }
  :root[dir="rtl"] .topology__tag--core-m { font-size: clamp(19px, 5.4vw, 22px); }
}
/* Below 720px the radial diagram is replaced outright, not merely scaled. */
@media (max-width: 719px) {
  .topology-wrap:not(.topology-wrap--mobile) { display: none; }
  .topology-wrap--mobile { display: grid; }
}

/* Mobile: one intentional ambient gesture, not zero.
   The audit found mobile had NO ambient motion at all — the grid animation was
   switched off and two of three layers hidden. That is over-simplification: the
   brief asks for a simplified but intentional mobile motion language. The glow
   is the cheapest layer to animate (one composited element, no repaint) so it
   is the one that survives; the grid stays static and the scan is dropped. */
@media (max-width: 767px) {
  :root { --move-lg: 16px; --move-md: 12px; }
  .ambient__glow--b,
  .ambient__scan { display: none; }
  .ambient__grid { opacity: var(--ambient-min); animation: none; }
  .ambient__glow--a { animation-duration: var(--motion-ambient-slow); }
  /* Two signals on the spine rather than eight radial ones — enough to show
     the flow runs both ways, few enough to stay calm and battery-cheap. */
  .topology__signal { animation-duration: 7s; }
  .topology__signal--out { animation-duration: 8.5s; }
}

/* Save-data / low-end: ambient becomes static. */
@media (prefers-reduced-data: reduce) {
  .ambient > * { animation: none !important; }
}

/* ==========================================================================
   REDUCED MOTION — a designed static composition, not a suppression layer
   The test: a reduced-motion visitor should not be able to tell anything is
   missing. Every element resolves to its intended final state.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  /* Content is simply present. */
  .js-motion [data-reveal],
  .js-motion [data-reveal].is-revealed {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    transition: none !important;
    animation: none !important;
  }

  /* Ambient holds a composed position rather than freezing mid-drift. */
  .ambient > *,
  .topology__ring,
  .topology__signal,
  .topology__node-halo,
  .topology__core-ring {
    animation: none !important;
  }
  .topology__ring   { opacity: 0.14; transform: scale(1); }
  .topology__signal { opacity: 0.65; offset-distance: 62%; }
  .topology__node-halo { opacity: 0.12; transform: scale(1.1); }
  .topology__core-ring { opacity: 0.6; transform: scale(1); }

  /* Paths stay drawn — the diagram must read complete. */
  .topology__path { stroke-dashoffset: 0 !important; animation: none !important; }

  /* Section rules and timelines rest at full extent. */
  .js-motion .eyebrow::after { transform: scaleX(1) !important; transition: none !important; }
  .timeline-track__fill { transform: scaleX(1) !important; animation: none !important; transition: none !important; }
  .cert-link { transform: scaleX(1) !important; transition: none !important; }

  /* L1 keeps colour feedback — removing it would remove feedback itself —
     but loses all spatial movement. */
  .btn, .card, .cert-card, .price-card, .timeline__item, .input, .textarea {
    transition-property: background-color, border-color, color, box-shadow !important;
    transition-duration: var(--motion-fast) !important;
  }
  .btn:active, .card:hover, .card:focus-within,
  .card:hover .card__icon, .card:focus-within .card__icon,
  .btn:hover .btn__icon--dir, .btn[data-magnetic] {
    transform: none !important;
  }
  .card[data-track]::before { display: none; }

  .drawer[data-open="true"] .drawer__link { animation: none !important; }
  .field__error[data-visible="true"],
  .alert--success:not([hidden]) { animation: none !important; }

  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none !important; }
}

/* ==========================================================================
   HUMAN RISK JOURNEY
   Explains the transformation, distinct from the methodology timeline which
   explains the operating approach. The visual progression carries the meaning:
   the rail shifts from risk to resilience along the reading direction, so the
   direction of travel is legible before a word is read.
   ========================================================================== */
.journey { position: relative; }

/* ---- Rail --------------------------------------------------------------- */
.journey__rail {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 21px;
  block-size: 2px;
  background: var(--border);
  border-radius: 2px;
  overflow: hidden;
}
.journey__rail-fill {
  position: absolute;
  inset: 0;
  /* Risk to resilience, in the reading direction. */
  background: linear-gradient(to var(--motion-origin-inline-end),
              var(--ax-amber-ink) 0%,
              var(--accent-line) 46%,
              var(--ax-green) 100%);
  transform: scaleX(var(--journey-progress, 0));
  transform-origin: var(--motion-origin-inline-start) center;
  transition: transform var(--motion-slow) var(--ease-linear);
}

/* ---- Stages ------------------------------------------------------------- */
.journey__stages {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--sp-6);
  grid-template-columns: 1fr;
}
@media (min-width: 720px)  { .journey__stages { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1100px) { .journey__stages { grid-template-columns: repeat(6, 1fr); } }

.journey__stage { position: relative; padding-block-start: var(--sp-12); }

.journey__marker {
  position: absolute;
  inset-block-start: 12px;
  inset-inline-start: 0;
  inline-size: 20px;
  block-size: 20px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface-0);
}
.journey__dot {
  inline-size: 12px;
  block-size: 12px;
  border-radius: 50%;
  background: var(--surface-1);
  border: 2px solid var(--border-strong);
  transition: background-color var(--motion-standard) var(--ease-enter),
              border-color     var(--motion-standard) var(--ease-enter),
              transform        var(--motion-standard) var(--ease-emphasized);
}

/* Tone carries the progression. Applied on activation, so the colour shift is
   part of the storytelling rather than a static decoration. */
.journey__stage.is-active .journey__dot { transform: scale(1.25); }
.journey__stage--risk.is-active   .journey__dot { background: var(--ax-amber-ink); border-color: var(--ax-amber-ink); }
.journey__stage--active.is-active .journey__dot { background: var(--accent-line);  border-color: var(--accent-line); }
.journey__stage--gain.is-active   .journey__dot { background: var(--ax-green);     border-color: var(--ax-green); }

.journey__index {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  color: var(--text-3);
  margin-block-end: var(--sp-2);
  transition: color var(--motion-standard) var(--ease-enter);
}
.journey__stage--risk.is-active   .journey__index { color: var(--ax-amber-ink); }
.journey__stage--active.is-active .journey__index { color: var(--accent-text); }
.journey__stage--gain.is-active   .journey__index { color: var(--ax-green-ink); }

.journey__title {
  font-size: var(--fs-lg);
  margin-block-end: var(--sp-2);
}
.journey__body {
  font-size: var(--fs-sm);
  color: var(--text-3);
  line-height: var(--lh-body);

  /* Backstop, not a layout mechanism. content.json is regenerated by the CMS,
     so an editor can lengthen any of these at any time; six columns only read
     as one row while the cells stay comparable. The sources chosen in
     journey.php run 3-4 lines, so this never engages on current content — it
     only stops one long paste from turning the row ragged. Full detail lives
     on the services and solutions pages. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 6;
  line-clamp: 6;
  overflow: hidden;
}
:root[dir="rtl"] .journey__body { line-height: var(--lh-body-ar); }

/* One column below the breakpoint: nothing to keep level, so no clamp. */
@media (max-width: 719px) {
  .journey__body {
    display: block;
    -webkit-line-clamp: none;
    line-clamp: none;
    overflow: visible;
  }
}

.journey__actions { margin-block-start: var(--sp-12); }

/* ---- Stage entrance -----------------------------------------------------
   A distinct pattern, not another fade-up: each stage rises slightly while its
   marker scales in, so the sequence reads as progression along the rail. */
.js-motion [data-reveal="journey"] {
  opacity: 0;
  transform: translateY(var(--move-md));
}
.js-motion [data-reveal="journey"] .journey__marker { transform: scale(0.4); }
.js-motion [data-reveal="journey"] {
  transition:
    opacity   var(--motion-slow) var(--ease-enter),
    transform var(--motion-slow) var(--ease-enter);
  transition-delay: calc(var(--i, 0) * var(--stagger-base));
}
.js-motion [data-reveal="journey"] .journey__marker {
  transition: transform var(--motion-standard) var(--ease-emphasized);
  transition-delay: calc(120ms + var(--i, 0) * var(--stagger-base));
}
.js-motion [data-reveal="journey"].is-revealed { opacity: 1; transform: none; }
.js-motion [data-reveal="journey"].is-revealed .journey__marker { transform: scale(1); }

/* On a narrow screen the rail becomes vertical, running down the reading edge. */
@media (max-width: 719px) {
  .journey__rail {
    inset-block: 8px 8px;
    inset-inline: 9px auto;
    inline-size: 2px;
    block-size: auto;
  }
  .journey__rail-fill {
    background: linear-gradient(to bottom,
                var(--ax-amber-ink) 0%, var(--accent-line) 46%, var(--ax-green) 100%);
    transform: scaleY(var(--journey-progress, 0));
    transform-origin: top center;
  }
  :root[dir="rtl"] .journey__rail-fill { transform-origin: top center; }
  .journey__stage { padding-block-start: 0; padding-inline-start: var(--sp-10); }
  .journey__marker { inset-block-start: 2px; }
}

/* Reduced motion: the whole journey is present and complete. */
@media (prefers-reduced-motion: reduce) {
  .journey__rail-fill {
    transform: scaleX(1) !important;
    transition: none !important;
  }
  .js-motion [data-reveal="journey"],
  .js-motion [data-reveal="journey"].is-revealed {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .js-motion [data-reveal="journey"] .journey__marker,
  .js-motion [data-reveal="journey"].is-revealed .journey__marker {
    transform: none !important;
    transition: none !important;
  }
  .journey__stage .journey__dot { transform: none !important; }
}
@media (prefers-reduced-motion: reduce) and (max-width: 719px) {
  .journey__rail-fill { transform: scaleY(1) !important; }
}
