/* ═══════════════════════════════════════════════════════════════════════════
   services.css - the Services page only.

   css/site.css is loaded first and unmodified: the header, the type scale, the
   palette, .section, .wrap, .card, .tag, .btn, .rule and the reveal/scrub
   machinery all come from there. This file adds the five things the homepage
   has no equivalent for:

     1. a masthead for a page that has no hero (.svm)
     2. the sticky pillar rail (.svrail)
     3. framework panels with their document shelves (.svfw)
     4. the directories - facets, search, result rows (.svdir)
     5. the listing routes and the review pipeline (.svroute, .svform)

   Everything is built out of the existing tokens. No new colour is introduced
   except one: --sv-gold-line, the hairline gold the header already uses, so the
   masthead's rules match the bar above them.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --sv-gold-line: #daa520;
  --sv-rail-h: 3.25rem;
  /* The bar is fixed and its height changes with the viewport, so js/services.js
     measures it and rewrites this. The value here is the desktop measurement, so
     the sticky rail and the masthead are already correct on first paint. */
  --nav-h: 3.4rem;
  /* And the bar with the lockup grown, which since 2026-09-04 is what the reader
     sees for the whole masthead on the wide header. --nav-h is still the resting
     height, because the sticky rail hangs off that and the rail is only ever
     seen past the masthead; only the masthead's own top pad has to clear the
     tall state. Both are rewritten by js/services.js and js/knowledge.js.
     The stand-in is the measured desktop value, so first paint is right and a
     browser that never fires the observer stays right. */
  --nav-h-top: 8rem;
  --nav-clear: max(var(--nav-h-top), var(--nav-h));
  /* the header is fixed; anchors have to clear it and the rail under it */
  --sv-anchor-pad: 8.5rem;

  /* Elevation. Three steps, each one two shadows: a tight contact shadow that
     seats the edge and a wide ambient one that lifts it. One blurred shadow is
     what makes a card read as printed on the page rather than resting on it,
     and the page had four separately invented one-shadow values in it. */
  --lift-1: 0 1px 2px rgba(20, 30, 38, 0.05), 0 4px 14px -8px rgba(20, 30, 38, 0.10);
  --lift-2: 0 2px 4px rgba(20, 30, 38, 0.06), 0 14px 34px -18px rgba(20, 30, 38, 0.22);
  --lift-3: 0 4px 8px rgba(20, 30, 38, 0.07), 0 28px 60px -28px rgba(20, 30, 38, 0.30);
}
/* Screen-reader-only label. site.css has no equivalent because the homepage
   labels everything visibly; the three search inputs here carry a visible
   placeholder but need a real label for a screen reader. */
.u-sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* Smooth anchor travel, but never against the reader's wishes. */
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body { background: var(--surface); }

/* ────────────────────────────── header ──────────────────────────────
   The dark bar itself is in css/hero-spider.css, because both pages use it.
   All this page adds is what the bar looks like before it flips. `is-dark` is
   toggled in js/services.js and js/knowledge.js, at the scroll position where
   the masthead's last pixel goes under the bar.

   Transparent, on the client's instruction, 2026-08-29. It was pure white,
   which made the bar the one light plane on the opening screen and put the
   reader through two changes at the masthead's edge - the bar going white to
   navy, and the lockup swapping under it. Transparent is what index.php does
   over its own dark hero: the masthead stays whole, the bar is a set of links
   floating on it, and the only change is the navy arriving past it.

   The consequence to keep in mind is that the bar is now dark-ground in both
   of its states on these two pages, exactly as it is on index.php. That is
   why the ink tokens are set here rather than left to flip with the class -
   there is no light state left for the light defaults in css/site.css to
   serve - and it is why both pages now carry index.php's single
   assets/logo-for-header.svg instead of the old logo-for-black /
   logo-colored pair. */
.nav {
  background: transparent;
  backdrop-filter: none;
  border-bottom-color: transparent;

  --nav-ink: #ffffff;
  --nav-ink-2: rgba(255, 255, 255, 0.84);
  --nav-ink-3: rgba(255, 255, 255, 0.62);
  --nav-line: rgba(255, 255, 255, 0.26);
  --nav-accent: #ffffff;
}

/* Transparent is right for the masthead at rest and wrong as soon as it moves:
   what climbs under the bar is the masthead's own eyebrow and h1, so the
   wordmark ends up sitting on the page title. The homepage answered this with
   `is-scrolled` (see css/hero-spider.css) and this is that answer brought
   across - same navy, same blur, so the two pages arrive at the same bar.
   js/services.js and js/knowledge.js now write the class. Client, 2026-09-11.

   No media query, where the homepage's version is capped at 1080px: a hero
   keeps an empty band at the top of its art, a masthead carries its title
   directly under the bar, so this one is needed at every width. */
.nav.is-scrolled:not(.is-dark) {
  background: color-mix(in srgb, var(--blue-900) 92%, transparent);
  backdrop-filter: blur(14px);
  border-bottom-color: rgba(255, 255, 255, 0.12);
}

/* css/hero-spider.css gives every bar without `is-over-hero` a blue lift, and
   that is a shadow drawn for white type on a white plane. There is no plane
   here until the flip, so it hangs in the masthead as a halo. Two classes deep
   to beat it, and it returns with `is-dark`, which brings a black one. */
.nav:not(.is-dark) { box-shadow: none; }

/* Navy on navy is not a button - the same inversion .nav.is-dark already makes
   past the masthead. The ground is dark on both sides of the flip now, so the
   button cannot go back to being blue in between. */
.nav:not(.is-dark) .btn--primary {
  background: #ffffff;
  color: var(--blue-900);
}
.nav:not(.is-dark) .btn--primary:hover { background: var(--blue-100); }

/* ────────────────────────────── masthead ──────────────────────────────
   The homepage earns its height with the hero. A services page should not:
   the reader arrived to find something, so the masthead states what the three
   routes are and how large each one is, and then gets out of the way. It is
   the page's one full-dark plane, which is why the numbers can be this large
   without shouting. */

.svm {
  position: relative;
  isolation: isolate;
  /* Both pads raised 2026-08-29 with the alignment fix. Pulled out to the
     viewport the copy had the whole left gutter as breathing room; sitting on
     the wrap's content edge it needs that room vertically instead, and the
     bottom pad was less than half the top, which cramped the map's legend
     against the section below. */
  padding-top: calc(var(--nav-clear) + clamp(3.5rem, 9vh, 6.5rem));
  padding-bottom: clamp(3.5rem, 7.5vh, 5.5rem);
  /* Lightened and taken toward blue 2026-09-04 on the client's instruction.
     --blue-900, which this was, is the site's dark ink - hsl(207 31% 11%), a
     near-neutral slate that reads as black behind a map of blue points. This is
     the same hue with the saturation roughly doubled and four points of
     lightness added, hsl(208 58% 15%): still a dark plane, white type still at
     15:1 on it, but a navy the map belongs to rather than a grey it sits on.
     Written as a literal rather than a token because it is this masthead's
     ground, not a new step on the blue scale. */
  background: #10283D;
  color: #fff;
  overflow: hidden;
}
/* Two washes, both very low contrast: a cool one behind the type so the copy
   sits on a slightly lifted ground, and a warm one at the far corner so the
   plane is not flat. Anything stronger and the numerals lose their edge. */
.svm::before {
  content: '';
  position: absolute;
  inset: -40% -10% auto -35%;
  height: 150%;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(50% 55% at 22% 30%, rgba(75, 126, 153, 0.28), transparent 70%),
    radial-gradient(38% 45% at 92% 8%, rgba(218, 165, 32, 0.13), transparent 72%);
}
/* `.svm::after` was a second hexagon texture - the identity's cell tiled in
   white at 5.5% and masked back to the map's corner. Removed on request, with
   the homepage's hex mesh: the map is already a field of dots and lines, and a
   lattice behind it was competing with the one thing on this plane that has to
   be read at a glance. The washes above stay; they are what keeps the plane
   from going flat. */

.svm__grid {
  display: grid;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}
/* The copy takes the narrower half. It is four short lines of title and a
   paragraph, and it does not need more than that; the map does, because it is
   the thing on this plane that has to be read at a glance rather than sentence
   by sentence. The column ratio and the gap together are what keep the type
   clear of the map's western dots - the map grows to the right, not into the
   copy. */
@media (min-width: 1024px) {
  .svm__grid { grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); }

  /* The copy used to follow the map out to the viewport: a negative start margin
     of the centring gutter, so the title stood the wrap's padding from the left
     edge rather than on the wrap's content edge. Removed 2026-08-29 on the
     client's instruction, and the reason it had to go is measurable rather than
     a matter of taste. At 1440px it put the h1 at x=72 while every section head
     below it starts at x=172 - the masthead was the only plane on the page not
     lining up with the page, which is exactly what reads as a full-width block
     that forgot its container. One left edge for the whole document now.

     The map keeps its own bleed to the right. That is not the same move: it
     runs *out* of the column into empty sea, and nothing below it shares an
     edge with it to disagree with. */
}

.svm .eyebrow { color: var(--gold-300); }
.svm__title {
  margin: 0.9rem 0 0;
  font-size: clamp(2.3rem, 5vw, 4.15rem);
  font-weight: 550;
  line-height: 1.02;
  letter-spacing: -0.022em;
  text-wrap: balance;
}
.svm__title em {
  font-style: normal;
  color: var(--blue-300);
}
.svm__lead {
  max-width: 54ch;
  margin: 1.35rem 0 0;
  color: rgba(255, 255, 255, 0.74);
  font-size: var(--fs-lead);
  line-height: 1.65;
}
.svm__actions { display: flex; flex-wrap: wrap; gap: 0.7rem; margin-top: 2rem; }
.svm__actions .btn--ghost {
  border-color: rgba(255, 255, 255, 0.3);
  color: #fff;
}
.svm__actions .btn--ghost:hover {
  border-color: var(--gold-300);
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
}

/* ─────────────────────────── the masthead map ───────────────────────────
   The region, plotted. js/gcc-network.js draws the points, the chords and the
   moving parts to the canvas; everything here is the box it draws into and the
   six labels standing on top of it.

   The labels are markup, not canvas text. Six country names are content - they
   have to be selectable, translatable, readable by a screen reader and legible
   without the script - so they sit in the DOM as a plain wrapped legend and the
   script promotes them onto the map by adding .is-mapped and writing --x/--y.
   Below the breakpoint they fall back to that legend on purpose: four of the six
   are within a few hundred kilometres of each other around the Gulf, and at
   phone width their labels would overlap into a pile. */

.svnet {
  position: relative;
  /* The frame tools/build-gcc-map.py projects into. Declared as a ratio so the
     panel holds its space before the module has measured anything. */
  aspect-ratio: 1000 / 792;
  width: 100%;
}
/* The map runs off the page rather than sitting in a box on it. Inside the wrap
   it would be about a third narrower, and at that size the peninsula is a
   diagram; carried out to the viewport edge it is a region that continues past
   the screen, which is the more honest thing for a map of six countries to say.
   The section clips it, and the mask above already fades the far side, so what
   leaves the frame is empty sea rather than land.

   The distance from the wrap's content edge to the viewport edge is the wrap
   padding plus whatever centring gutter is left once the page is wider than
   --maxw. max() is there for the narrow case, where that gutter is negative. */
@media (min-width: 1024px) {
  .svnet {
    /* auto, not the 100% above: 100% resolves against the grid track, so the
       panel would overflow the bleed instead of growing into it. */
    width: auto;
    margin-inline-end: calc(-1 * max(var(--pad), (100vw - var(--maxw)) / 2 + var(--pad)));
  }
}
.svnet__canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* The sampled frame is a rectangle and the region inside it is not, so a hard
     edge would read as a crop of a bigger map. Faded at the corners it reads as
     a field that happens to be densest where the peninsula is. */
  -webkit-mask-image: radial-gradient(118% 112% at 54% 46%, #000 52%, transparent 93%);
          mask-image: radial-gradient(118% 112% at 54% 46%, #000 52%, transparent 93%);
}

.svnet__labels {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem 0.5rem;
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
}
.svnet__pill {
  display: inline-block;
  margin: 0;
  border: 0;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  appearance: none;
  padding: 0.34rem 0.62rem;
  /* One hairline, the same on all four edges and the same on all six pills.
     Between 2026-08-29 and 2026-09-04 this was the state's own colour, written
     as --sv-state by js/gcc-network.js, with a 3px bar on the inline start
     carrying it; the colours came off the map on instruction and the bar went
     with them - it was the key to a legend that no longer has anything to key,
     and an asymmetric pill sits badly on labels flush to two columns. */
  border: 1px solid rgba(164, 223, 245, 0.3);
  border-radius: var(--corner-chip);
  background: rgba(6, 26, 41, 0.72);
  color: rgba(255, 255, 255, 0.82);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: border-color 0.3s var(--ease-out), background 0.3s var(--ease-out),
              color 0.3s var(--ease-out);
}
.svnet__pill:focus-visible {
  outline: 2px solid var(--sv-gold-line);
  outline-offset: 3px;
}
/* Pointing at a label lights the state's points on the map, and pointing at the
   state lights the label - the label is much the easier target, and Bahrain at
   this scale is four pixels of coastline. */
.svnet__pill.is-lit {
  /* The gold hairline, which is what this was before the six states had colours
     of their own and is what it is again: with one tone on the map there is no
     state colour left to light the pill in, and gold is the page's accent and
     the one hue on this plane that means "this one". The ground goes darker
     under it too, so the lift is contrast as well as colour. */
  border-color: var(--sv-gold-line);
  background: rgba(4, 22, 35, 0.94);
  color: #fff;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--sv-gold-line) 40%, transparent),
              0 6px 18px -10px rgba(0, 0, 0, 0.8);
}

/* ── the map's instruction ──
   A map that answers the pointer and never says so is a map most readers will
   read as a picture. One quiet line says it, and the gold cue is what carries
   it: a still dot in a field where everything else is moving would be missed,
   so it breathes on the same slow beat as the traffic between the capitals.

   Written as a plain sentence rather than as an icon and a tooltip, because the
   instruction is one clause long and a tooltip would need the very affordance
   it was there to announce. */
.svnet__hint {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.9rem 0 0;
  color: rgba(255, 255, 255, 0.58);
  font-size: var(--fs-tiny);
  letter-spacing: 0.01em;
  transition: opacity 0.6s var(--ease-out), translate 0.6s var(--ease-out);
}
/* One or the other, never both: `hover: none` is also the test
   js/gcc-network.js uses to decide whether to follow the pointer at all, so the
   two strings and the two behaviours cannot drift apart. */
.svnet__hint--touch { display: none; }
@media (hover: none) {
  .svnet__hint--hover { display: none; }
  .svnet__hint--touch { display: inline; }
}
.svnet__cue {
  flex: none;
  width: 0.44rem;
  height: 0.44rem;
  border-radius: 50%;
  background: var(--sv-gold-line);
  animation: svnet-cue 2.6s var(--ease-out) infinite;
}
@keyframes svnet-cue {
  0%,  100% { box-shadow: 0 0 0 0 rgba(218, 165, 32, 0.5); opacity: 0.85; }
  55%       { box-shadow: 0 0 0 6px rgba(218, 165, 32, 0); opacity: 1; }
}
/* Where the labels leave the flow and go onto the map, the line goes with them,
   to the bottom-left corner - the one part of the frame that is open sea at
   every viewport, and the corner the mask has already faded. */
@media (min-width: 700px) {
  .svnet.is-mapped .svnet__hint {
    position: absolute;
    inset-inline-start: 0;
    inset-block-end: 0.15rem;
    margin: 0;
  }
}
/* Said once. The reader who has lit a state has been told by the map itself,
   and a standing instruction for something already done is clutter. */
.svnet.is-probed .svnet__hint {
  opacity: 0;
  translate: 0 5px;
  pointer-events: none;
}

/* Below the breakpoint the labels leave the map and become a wrapped row of
   chips under it, and that row is the only way in on a phone - there is no
   pointer to move over the drawing. So the pill stops being a caption and has
   to be a target: 0.34rem of padding gave a 32px control, eleven short of the
   44px a thumb needs, and the six of them sat close enough that missing one
   meant hitting its neighbour. Above 700px they go back over the map, where
   they are read far more often than they are pressed and the tighter set is
   the better one. */
@media (max-width: 699px) {
  .svnet__labels {
    gap: 0.55rem;
  }
  .svnet__pill {
    padding: 0.7rem 0.9rem;
  }
}

@media (min-width: 700px) {
  .svnet.is-mapped .svnet__labels {
    position: absolute;
    inset: 0;
    display: block;
    margin: 0;
  }
  /* --x/--y are the anchor the leader line arrives at, written by the module
     from the same table it draws the line from. The 10px gap is where the
     canvas puts the connector dot, so the label never sits on its own anchor. */
  .svnet.is-mapped .svnet__pill {
    position: absolute;
    left: calc(var(--x, 0.5) * 100%);
    top: calc(var(--y, 0.5) * 100%);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    transform: translate(10px, -50%);
  }
  .svnet.is-mapped .svnet__pill[data-align="end"] {
    transform: translate(calc(-100% - 10px), -50%);
  }
  /* The map is drawn once, in one direction, and the labels are placed against
     its geography - so this is one of the few blocks on the page that must not
     mirror under RTL. A flipped Gulf is a wrong map. */
  [dir="rtl"] .svnet.is-mapped .svnet__pill { transform: translate(10px, -50%); }
  [dir="rtl"] .svnet.is-mapped .svnet__pill[data-align="end"] {
    transform: translate(calc(-100% - 10px), -50%);
  }
}

/* ─────────────────────────── the pillar rail ───────────────────────────
   Sticky rather than fixed, so it belongs to the document rather than floating
   over it, and it sits directly under the bar. Scroll-spy is in js/services.js. */

.svrail {
  position: sticky;
  top: var(--nav-h);
  z-index: 30;
  background: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line-soft);
}

/* The bar retreats on the way down (site.css:303, translateY(-100%)) but the
   rail went on reserving its full height, so a scroll down left the rail stuck
   112px below the top of the viewport with an empty band above it - the bar's
   footprint with no bar in it.

   The offset follows the bar instead. A sticky `top` only takes effect once the
   element would otherwise scroll past it, so there is no need to know whether
   the rail is stuck: while it is still in flow this changes nothing, and while
   it is stuck it puts it exactly under whatever is left of the bar. It snaps
   rather than slides because top is not a property this page animates - and the
   bar is sliding out from over it at that moment, which is what the eye follows.
*/
body:has(.nav.is-hidden) .svrail { top: 0; }

.svrail__inner {
  display: flex;
  align-items: center;
  gap: clamp(0.4rem, 1.6vw, 1.6rem);
  min-height: var(--sv-rail-h);
  overflow-x: auto;
  scrollbar-width: none;
}
.svrail__inner::-webkit-scrollbar { display: none; }
.svrail a {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.9rem 0.15rem;
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.3s var(--ease-out);
}
.svrail a i {
  font-style: normal;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  transition: color 0.3s var(--ease-out);
}
.svrail a::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 2px;
  background: var(--blue-700);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.35s var(--ease-out);
}
[dir="rtl"] .svrail a::after { transform-origin: right center; }
.svrail a:hover { color: var(--ink); }
.svrail a.is-active { color: var(--blue-700); }
.svrail a.is-active i { color: var(--gold-600); }
.svrail a.is-active::after { transform: scaleX(1); }
.svrail__cta { margin-inline-start: auto; flex: none; }
.svrail__cta .btn { padding: 0.5rem 1rem; font-size: var(--fs-xs); }
@media (max-width: 820px) { .svrail__cta { display: none; } }

/* ───────────────────────────── section heads ─────────────────────────────
   A pillar head is a section head with a figure and a lead statistic, so the
   reader knows the size of the thing they are about to scroll through. */

.svhead { display: grid; gap: 0.4rem 3.5rem; margin-bottom: clamp(2rem, 4.5vh, 3.25rem); }
@media (min-width: 900px) {
  .svhead { grid-template-columns: minmax(0, 1fr) minmax(0, 26rem); align-items: end; }
}
.svhead h2 {
  margin: 0.7rem 0 0;
  font-size: clamp(1.9rem, 3.4vw, 2.9rem);
  font-weight: 550;
  line-height: 1.06;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.svhead__lead { margin: 0; color: var(--ink-2); font-size: var(--fs-lead); line-height: 1.68; }
@media (min-width: 900px) { .svhead__lead { padding-bottom: 0.35rem; } }
.svhead .rule { margin-top: 1.4rem; grid-column: 1 / -1; color: var(--gold-500); }

/* ── the centered head ──
   Set by the client 2026-08-29, to match the homepage's .section__head--center.
   The two pages had two different section heads - this one asymmetric, heading
   in the wide column with its lede beside the baseline, the homepage's centered
   - and the instruction is that they agree.

   Same arithmetic as the homepage rule, deliberately, including the two
   max-widths in ch rather than px: a centered heading that runs the full
   measure is not centered, it is full width with a short last line. 24ch breaks
   these headings into two or three lines of roughly even length and the lede
   sits under as a narrower block, which is the shape a centered head has to
   make to read as one thing rather than as a heading with a paragraph after it.

   Everything except the arrangement is kept. The type scale is .svhead's own -
   it runs larger than the homepage's and that is this page's voice, not an
   inconsistency - and so are the numeral, the gold rule and the scrub. */
.svhead--center { justify-items: center; text-align: center; }
.svhead--center h2 { max-width: 24ch; margin-inline: auto; }
.svhead--center .svhead__lead { max-width: 46ch; margin-top: 0.9rem; }
@media (min-width: 900px) {
  /* one column, so the lede drops under the heading instead of beside it */
  .svhead--center { grid-template-columns: minmax(0, 1fr); }
  .svhead--center .svhead__lead { padding-bottom: 0; }
}
/* The rule draws from the middle out. Left-origin under a centered head reads
   as an underline that missed, and at full bleed it is a section divider rather
   than the last beat of the head landing - so it is capped to the lede's own
   measure and centered on it. */
/* .svhead.svhead--center and not .svhead--center on its own: the origin this
   overrides is set by `.svhead .rule` about 900 lines further down, which is
   the same (0,2,0) specificity and later in the file, so a single class here
   loses the cascade and the rule keeps drawing from the left edge. The RTL pair
   has the same problem against `[dir="rtl"] .svhead .rule` and takes the same
   treatment. Centered is centered in both directions, so RTL restates rather
   than mirrors - there is no left edge to flip to. */
.svhead.svhead--center .rule {
  width: min(100%, 46ch);
  margin-inline: auto;
  transform-origin: 50% 50%;
}
[dir="rtl"] .svhead.svhead--center .rule { transform-origin: 50% 50%; }

.svsection { position: relative; padding-block: clamp(3.5rem, 8vh, 6.5rem); }
.svsection--tint { background: var(--surface-2); }
.svsection[id], .svlist[id] { scroll-margin-top: var(--sv-anchor-pad); }

/* ───────────────────────── the dark bands ─────────────────────────
   The page used to run light the whole way down after the masthead, which made
   four sections read as one long sheet. The homepage alternates instead, and
   this is that rhythm brought over: masthead dark, assessment light, providers
   dark, training light, listing dark. The eye gets a boundary at every section
   without a rule being drawn.

   The homepage does it with .section--dark, which sets a background and a colour
   and then repairs each component that assumed a white ground underneath it -
   card titles, the numeral watermark, the eyebrow. That works there because the
   homepage has few components per band. It does not scale here: this page's
   sections are built from about thirty pieces that each read --surface, --ink or
   --line directly, and repairing them one at a time is how you end up with a
   panel nobody noticed was still white.

   So the band re-points the tokens instead and every descendant follows. That is
   also why the class goes on the <section> and not on a wrapper: the custom
   properties have to be in scope for everything inside.

   One inversion is deliberate and worth stating, because it looks like a mistake
   in the numbers. On light ground --surface is the plate and --surface-2 and -3
   step *darker* into recesses. Reversing that literally would put the recesses
   at black, so here all three step *lighter* than the band, in the same order.
   A plate still sits above its ground and a recess still sits behind its plate;
   only the direction of "away from the ground" changes. */
.svsection--dark,
.svlist--dark {
  background: var(--blue-900);
  color: #fff;

  --surface:   color-mix(in srgb, var(--c-navy) 88%, var(--c-slate));
  --surface-2: color-mix(in srgb, var(--c-navy) 80%, var(--c-slate));
  --surface-3: color-mix(in srgb, var(--c-navy) 70%, var(--c-slate));

  --ink:   #fff;
  --ink-2: rgba(255, 255, 255, 0.76);
  --ink-3: rgba(255, 255, 255, 0.54);

  --line:      rgba(255, 255, 255, 0.17);
  --line-soft: rgba(255, 255, 255, 0.09);

  /* The mid and deep blues were picked to carry weight against white. On navy
     they are the ground, so anything that used them to stand out has to come up
     the scale rather than down it. --blue-300 is the logo's own sky and is the
     top of that scale, so it is where all three land. */
  --blue-800: var(--blue-300);
  --blue-700: var(--blue-300);
  --blue-600: color-mix(in srgb, var(--c-sky) 82%, #fff);
  --blue-500: color-mix(in srgb, var(--c-sky) 62%, var(--c-navy));
  --blue-50:  color-mix(in srgb, var(--c-navy) 74%, var(--c-slate));

  /* Same for the gold: --gold-700 is a near-brown against white and disappears
     here, and --gold-100 is a wash that only exists to tint a white plate. */
  --gold-700: var(--gold-300);
  --gold-600: var(--gold-500);
  --gold-100: color-mix(in srgb, var(--c-gold) 22%, var(--c-navy));

  --sv-on-fill: var(--blue-900);
}

/* The supplied plane, used as the providers band's ground instead of flat navy.
   Same artwork the homepage news band uses, so the two pages share a ground
   rather than each inventing one.

   Not site.css's .imgband, for two reasons that are both about this section
   rather than about the art. Its scrim runs left to right and bottoms out at
   0.42 alpha on the right-hand side, which puts --ink-3 (white at 54%) at
   4.0:1 over the brightest facet - this band is a register full of small muted
   labels, and they have to hold 4.5:1 across the whole width. And .imgband
   drifts its art on a 34s loop, which is decoration on a page whose motion
   otherwise only answers the reader.

   So: a vertical scrim, no loop. 0.64 at the head down to 0.82 under the
   directory, which is where the type gets small and dense. Measured against
   the brightest facet in each band, --ink-3 holds 5.0:1 at the top and 5.3:1
   at the bottom, against 5.8:1 on the flat navy it replaces, and white body
   type is 13:1 or better throughout. */
.svsection--art { isolation: isolate; }
.svsection--art > .wrap { position: relative; z-index: 2; }
.svsection--art::before,
.svsection--art::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.svsection--art::before {
  z-index: 0;
  background: url(../assets/pattern.png) center / cover no-repeat;
}
.svsection--art::after {
  z-index: 1;
  background: linear-gradient(to bottom,
    rgba(20, 30, 38, 0.64) 0%,
    rgba(20, 30, 38, 0.74) 45%,
    rgba(20, 30, 38, 0.82) 100%);
}

/* ──────────────────────── the two frameworks ────────────────────────
   Two panels, deliberately not cards: a card is a link and these are not. The
   left rule carries the framework's colour - blue for SIRI, gold for ITTI -
   which is the same pairing the homepage's framework section uses.

   They shipped as two brochure columns: a paragraph, three facts, a shelf of
   PDFs, and no way to act on any of it. The head above them promises "one
   comparable result" and the panels did nothing with the comparison - they were
   two lists that happened to be side by side.

   Three things changed, in order of how much they matter:

   1. Each panel is now the way into the register. The button under the facts
      presses the register's own framework chip, so the 221 in "accredited
      assessors in the register" stops being a number and becomes the filter
      that produces it. The panel presses the chip rather than holding a filter
      of its own, which is why the panel also lights up when the reader sets
      that filter from the register instead - one source of truth, read both
      ways. js/services.js owns the binding; the is-active class is all it
      writes here.

   2. The two panels align to each other. They were a flex column each, with
      the shelf pushed to the bottom by margin-top:auto, so the shorter panel
      opened a hole in its middle exactly as tall as the other panel's extra
      PDF - the one thing about the pair a reader was guaranteed to notice.
      Six subgrid rows put tag against tag and shelf against shelf, and the
      slack that is left goes to the bottom of the shorter shelf where it
      reads as padding. Guarded on @supports; the flex column is the fallback
      and its only fault is the hole it always had.

   3. The facts arrive as measurements. Each row fades and its rule draws in
      turn off the page's own --p (js/scrub.js), the same scroll position the
      route and every other block arrives on, and the two register counts now
      carry data-count so they run up to their value like the stat strip
      ninety lines below already did. Nothing here loops.

   The tag also stops being a full-width bar. It is declared inline-block in
   css/site.css and was being stretched by the flex column it sat in, which is
   a defect and not a decision. */

.svfw { display: grid; gap: clamp(1.25rem, 2.5vw, 1.75rem); }
@media (min-width: 940px) { .svfw { grid-template-columns: 1fr 1fr; } }

.svfw__item {
  display: flex;
  flex-direction: column;
  padding: clamp(1.5rem, 2.6vw, 2.1rem);
  border: 1px solid var(--line);
  border-inline-start: 2px solid var(--fw, var(--blue-600));
  border-radius: var(--corner-box);
  background: var(--surface);
  transition: border-color 0.35s var(--ease-out), box-shadow 0.35s var(--ease-out);
}
.svfw__item--itti { --fw: var(--gold-500); }
.svfw__item:hover { box-shadow: 0 10px 40px -22px rgba(9, 74, 117, 0.35); }

/* Six rows: tag, title, description, facts, the register button, the shelf.
   The children are in that order and there are exactly six of them, so the
   subgrid needs no line names to stay honest - but it does mean anything added
   to a panel has to be added to both, or the pair stops aligning. */
@supports (grid-template-rows: subgrid) {
  @media (min-width: 940px) {
    .svfw { grid-template-rows: repeat(6, auto); }
    .svfw__item {
      display: grid;
      grid-row: 1 / -1;
      grid-template-rows: subgrid;
    }
  }
}

/* Declared inline-block in css/site.css; the flex column was stretching it. */
.svfw__item .tag { align-self: start; justify-self: start; }

.svfw__item h3 {
  margin: 0.85rem 0 0;
  font-size: clamp(1.15rem, 1.7vw, 1.45rem);
  font-weight: 550;
  letter-spacing: -0.012em;
  line-height: 1.22;
}
.svfw__item > p { margin: 0.7rem 0 0; color: var(--ink-2); font-size: var(--fs-base); line-height: 1.65; }

/* ── the facts ──
   The rows arrive in turn off --p rather than all at once: three facts landing
   together is one event, three landing in sequence is a reading order. --p
   falls back to 1, so with js/scrub.js absent all three are simply there. */
.svfw__facts {
  display: grid;
  gap: 0;
  margin: 1.35rem 0 0;
  align-content: start;
  border-top: 1px solid var(--line-soft);
}
.svfw__facts div {
  position: relative;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.62rem 0;
  font-size: var(--fs-sm);
  --lit: clamp(0, calc(var(--p, 1) * 4.4 - var(--r)), 1);
  opacity: calc(0.25 + var(--lit) * 0.75);
}
.svfw__facts div:nth-child(1) { --r: 0; }
.svfw__facts div:nth-child(2) { --r: 1; }
.svfw__facts div:nth-child(3) { --r: 2; }
/* The rule under each row draws with it, which is what makes the sequence read
   as a sheet being filled in rather than three paragraphs fading. */
.svfw__facts div::after {
  content: '';
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: 0;
  width: 100%;
  height: 1px;
  background: var(--line-soft);
  transform: scaleX(var(--lit));
  transform-origin: left center;
}
[dir="rtl"] .svfw__facts div::after { transform-origin: right center; }
.svfw__facts dt, .svfw__facts span:first-child { color: var(--ink-3); }
.svfw__facts dd, .svfw__facts span:last-child {
  margin: 0;
  color: var(--ink);
  font-weight: 500;
  text-align: end;
  font-variant-numeric: tabular-nums;
  translate: 0 calc((1 - var(--lit)) * 4px);
}

/* ── into the register ──
   Quiet at rest: this is the panel's one action and it should not out-shout the
   shelf under it. Pressed, it fills with the framework's own colour, because
   the state it reports is a filter the reader can see the effect of four
   hundred pixels down the page and needs to be able to find their way back to. */
.svfw__filter {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  margin: 1.1rem 0 0;
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: var(--corner-btn);
  background: none;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 500;
  text-align: start;
  cursor: pointer;
  transition: border-color 0.3s var(--ease-out), background 0.3s var(--ease-out),
              color 0.3s var(--ease-out);
}
.svfw__filter svg { width: 15px; height: 15px; flex: none; transition: translate 0.3s var(--ease-out); }
.svfw__filter:hover {
  border-color: var(--fw, var(--blue-600));
  background: color-mix(in srgb, var(--fw, var(--blue-600)) 7%, transparent);
  color: var(--ink);
}
.svfw__filter:hover svg { translate: 0 2px; }
.svfw__filter:focus-visible { outline: 2px solid var(--blue-600); outline-offset: 2px; }
/* Pressed: the arrow turns back up the page, because that is now where the
   button takes the reader. */
.svfw__filter[aria-pressed="true"] {
  border-color: var(--fw, var(--blue-600));
  background: var(--fw, var(--blue-600));
  color: var(--fw-on, #fff);
}
.svfw__filter[aria-pressed="true"] svg { rotate: 180deg; }
.svfw__filter[aria-pressed="true"]:hover svg { translate: 0 -2px; }
/* Gold is a light fill: white on it fails, navy on it does not. */
.svfw__item--itti { --fw-on: var(--blue-900); }

/* The panel whose index the register is showing. A ring rather than a heavier
   left rule, because border-width cannot be transitioned without reflowing the
   panel's contents. */
.svfw__item.is-active {
  border-color: color-mix(in srgb, var(--fw, var(--blue-600)) 45%, var(--line));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fw, var(--blue-600)) 30%, transparent);
}
.svfw__item.is-active .tag {
  background: var(--fw, var(--blue-600));
  color: var(--fw-on, #fff);
}

/* Document shelf. Tiles, not rows: the sheet is what the reader is reaching
   for, so it gets the space, and the index colour on its badge says which
   library the file came out of. A download is still a commitment, so the
   format and the weight stay on the face of the tile.
   auto-fill, not auto-fit: the empty track is kept when a card holds fewer
   files than fit, so a SIRI tile and an ITTI tile are the same size. */
/* The shelf fills its subgrid row and hands the height to the tiles, so the
   weight line sits on the same baseline in both cards even though one card's
   titles wrap onto more lines than the other's. */
.svdocs { margin: 1.45rem 0 0; display: grid; grid-template-rows: auto minmax(0, 1fr); }
.svdocs__label {
  margin: 0 0 0.55rem;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.svdocs__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 8.25rem), 1fr));
  grid-auto-rows: minmax(min-content, 1fr);
  gap: 0.6rem;
}
.svdoc {
  position: relative;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: 0.55rem;
  justify-items: center;
  padding: 1.05rem 0.7rem 0.9rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--corner-box);
  background: var(--surface-2);
  color: var(--ink);
  text-align: center;
  text-decoration: none;
}
.svdoc:hover {
  border-color: color-mix(in srgb, var(--fw, var(--blue-600)) 45%, var(--line));
  background: color-mix(in srgb, var(--fw, var(--blue-600)) 7%, var(--surface));
}
/* The glyph. Sheet, folded corner, badge - the three marks that make a file
   read as a PDF at a glance, at a size worth glancing at. */
.svdoc__ico { display: block; }
.svdoc__ico svg { display: block; width: clamp(40px, 3.2vw, 48px); height: auto; }
.svdoc__sheet, .svdoc__fold {
  fill: none;
  stroke: color-mix(in srgb, var(--fw, var(--blue-600)) 40%, var(--line));
  stroke-width: 1.5;
  stroke-linejoin: round;
}
.svdoc__sheet { fill: var(--surface); }
.svdoc__band { fill: var(--fw, var(--blue-600)); }
.svdoc__pdf {
  fill: var(--fw-on, #fff);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-anchor: middle;
}
.svdoc__line { stroke: color-mix(in srgb, var(--ink-3) 32%, transparent); stroke-width: 1.5; stroke-linecap: round; }
/* The pointer is already on the tile; the arrow only confirms what a click
   does. It is not there at rest, so it never competes with the title. */
.svdoc__arrow {
  position: absolute;
  inset-block-start: 0.55rem;
  inset-inline-end: 0.55rem;
  width: 14px;
  height: 14px;
  color: var(--fw, var(--blue-600));
  opacity: 0;
  /* The reports open in a tab now rather than downloading, so the glyph is an
     open-in-new box and it leaves toward the top-right rather than downward. */
  translate: -3px 3px;
}
.svdoc:hover .svdoc__arrow, .svdoc:focus-visible .svdoc__arrow { opacity: 1; translate: 0; }
.svdoc b {
  align-self: start;
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: 1.3;
  text-wrap: balance;
}
.svdoc small {
  color: var(--ink-3);
  font-size: var(--fs-tiny);
  font-weight: 600;
  letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* The shelf was pushed to the bottom of the panel, which put the pair's height
   difference in the middle of the shorter one. Subgrid aligns them now; the
   slack goes under the last PDF. */
.svfw__item .svdocs { padding-top: 1.45rem; }

/* ───────────────────────────── stat strip ─────────────────────────────
   Tabular figures throughout: these numbers animate up on reveal, and
   proportional digits make the whole row twitch while they do. */

.svstats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1px;
  margin-bottom: clamp(1.75rem, 4vh, 2.75rem);
  background: var(--line-soft);
  border: 1px solid var(--line-soft);
  border-radius: var(--corner-box);
  overflow: hidden;
}
.svstats > div { padding: 1.15rem 1.25rem; background: var(--surface); }
.svsection--tint .svstats > div { background: var(--surface-2); }
.svstats b {
  display: block;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: 450;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--blue-800);
  font-variant-numeric: tabular-nums;
}
.svstats span {
  display: block;
  margin-top: 0.45rem;
  color: var(--ink-3);
  font-size: var(--fs-tiny);
  font-weight: 500;
  letter-spacing: 0.06em;
  line-height: 1.4;
}
.svstats > div:first-child b { color: var(--gold-700); }

/* ─────────────────────── country distribution bars ─────────────────────── */

.svbars { display: grid; gap: 0.55rem; margin: 0 0 clamp(1.75rem, 4vh, 2.5rem); }
.svbars__row {
  display: grid;
  grid-template-columns: minmax(6.5rem, 11rem) minmax(0, 1fr) 3.2rem;
  gap: 0.9rem;
  align-items: center;
  font-size: var(--fs-xs);
}
.svbars__row span:first-child { color: var(--ink-2); }
.svbars__track { height: 6px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.svbars__fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--blue-700), var(--blue-500));
  transform: scaleX(var(--v, 0));
  transform-origin: left center;
  transition: transform 1.1s var(--ease-out) var(--d, 0s);
}
[dir="rtl"] .svbars__fill { transform-origin: right center; }
/* A state with no rows in this register. It keeps its full row - the point is
   that a nil return is reported, not omitted - so the track has to read as
   deliberately empty rather than as a bar that failed to draw: a hairline at
   the origin, and the count in the muted ink instead of the reading ink. */
.svbars__row--nil span:first-child { color: var(--ink-3); }
.svbars__row--nil b { color: var(--ink-3); font-weight: 400; }
.svbars__row--nil .svbars__track {
  background: var(--line-soft);
  box-shadow: inset 2px 0 0 0 var(--surface-3);
}
.svbars__row b {
  text-align: end;
  color: var(--ink);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* ───────────────────────────── directories ─────────────────────────────
   One component for all three. Facets are buttons with aria-pressed, the
   result count is a live region, and the list renders in pages of 24 rather
   than dropping 298 rows into the document at once. */

.svdir { margin-top: 0.5rem; }
.svdir__bar {
  display: grid;
  gap: 0.85rem;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--surface);
}
.svsection--tint .svdir__bar { background: var(--surface); }
@media (min-width: 760px) {
  .svdir__bar { grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); align-items: start; }
}

.svsearch { position: relative; display: flex; align-items: center; }
.svsearch svg {
  position: absolute;
  inset-inline-start: 0.75rem;
  width: 16px; height: 16px;
  color: var(--ink-3);
  pointer-events: none;
}
.svsearch input {
  width: 100%;
  /* 44px minimum, and 16px type so iOS does not zoom the page on focus */
  min-height: 44px;
  padding: 0.6rem 0.85rem 0.6rem 2.35rem;
  border: 1px solid var(--line);
  border-radius: var(--corner-btn);
  background: var(--surface-2);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-base);
  transition: border-color 0.3s var(--ease-out), background 0.3s var(--ease-out);
}
[dir="rtl"] .svsearch input { padding: 0.6rem 2.35rem 0.6rem 0.85rem; }
.svsearch input::placeholder { color: var(--ink-3); }
.svsearch input:focus-visible {
  outline: 2px solid var(--blue-500);
  outline-offset: 1px;
  border-color: var(--blue-500);
  background: var(--surface);
}
.svsearch__clear {
  position: absolute;
  inset-inline-end: 0.35rem;
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border: 0;
  border-radius: var(--corner-chip);
  background: none;
  color: var(--ink-3);
  cursor: pointer;
  transition: color 0.25s var(--ease-out), background 0.25s var(--ease-out);
}
.svsearch__clear:hover { color: var(--ink); background: var(--surface-3); }
.svsearch__clear[hidden] { display: none; }

.svfacets { display: grid; gap: 0.6rem; }
/* One line per set, scrolled rather than wrapped. A wrapped set changes height
   as the reader filters - chips carry live counts and drop out when a filter
   makes them empty - so the search box and the first result row move under the
   pointer between clicks. One row that scrolls is a fixed height whatever the
   set contains, and the assessor register's nine countries were never going to
   fit on a line at any width. */
.svfacets__set {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.4rem;
  align-items: center;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  /* the scrollbar is drawn inside the box on every platform that reserves one,
     so the chips need the room under them */
  padding-bottom: 0.15rem;
}
.svfacets__set::-webkit-scrollbar { height: 4px; }
.svfacets__set::-webkit-scrollbar-thumb { background: var(--line); border-radius: 999px; }
.svfacets__set::-webkit-scrollbar-track { background: transparent; }
.svchip { flex: none; }
/* The set label holds its place while the chips run under it, so a scrolled row
   still says what it is filtering by. */
.svfacets__label {
  position: sticky;
  inset-inline-start: 0;
  z-index: 1;
  flex: none;
  margin-inline-end: 0.2rem;
  padding-inline-end: 0.35rem;
  background: var(--surface);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}
/* Placed on the bar's own grid rather than stacked in a column of their own.
   Every bar carries two sets: a short one (index, or country) and a long one
   (country, capability family, center). The short set sits beside the search,
   which otherwise has half a card of white space next to it, and the long set
   takes the full width so it wraps into two rows instead of four.
   This has to follow the two rules above, not sit in the media query further
   up the file: same specificity, and the later declaration wins. */
@media (min-width: 760px) {
  .svfacets { display: contents; }
  .svfacets__set:first-child { grid-column: 2; grid-row: 1; }
  .svfacets__set:last-child { grid-column: 1 / -1; grid-row: 2; }
}
.svchip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0.5rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 500;
  cursor: pointer;
  transition: border-color 0.25s var(--ease-out), background 0.25s var(--ease-out),
              color 0.25s var(--ease-out);
}
.svchip:hover { border-color: var(--blue-300); color: var(--ink); }
.svchip:focus-visible { outline: 2px solid var(--blue-500); outline-offset: 2px; }
.svchip i {
  font-style: normal;
  font-size: var(--fs-tiny);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  transition: color 0.25s var(--ease-out);
}
.svchip[aria-pressed="true"] {
  border-color: var(--blue-800);
  background: var(--blue-900);
  color: #fff;
}
.svchip[aria-pressed="true"] i { color: rgba(255, 255, 255, 0.62); }
.svchip--gold[aria-pressed="true"] { border-color: var(--gold-700); background: var(--gold-700); }
/* Touch: the hover tint never lands, so pressed state has to carry it alone. */
@media (hover: none) { .svchip:hover { border-color: var(--line); color: var(--ink-2); } }

.svdir__status {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  align-items: baseline;
  padding: 1.1rem 0 0.9rem;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}
.svdir__status b { color: var(--ink); font-weight: 550; font-variant-numeric: tabular-nums; }
.svdir__reset {
  border: 0;
  background: none;
  padding: 0.35rem 0;
  color: var(--blue-700);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.svdir__reset[hidden] { display: none; }
.svdir__note {
  margin: 0;
  padding: 0.8rem 0.95rem;
  border-inline-start: 2px solid var(--gold-500);
  background: var(--gold-100);
  color: var(--gold-700);
  font-size: var(--fs-xs);
  line-height: 1.55;
}

/* Assessor rows. A list, not a table: three fields, and a table would only add
   a header row the reader has to keep looking back at. */
/* The register is one table, not a stack of independent rows. The country and
   index tracks have to be sized by the widest value in the whole column - with
   per-row `auto` tracks, "United Arab Emirates" and "Bahrain" produced two
   different column layouts and nothing lined up. `subgrid` puts every row on
   the parent's tracks. */
/* Five tracks since the Enquire button arrived, and the fifth is `max-content`
   for the same reason the other two are: the button is the same width in every
   row, so sizing it per row would put the column edge in a different place on
   each one. */
.svrows {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.3fr) max-content max-content max-content;
  gap: 0;
  border-top: 1px solid var(--line-soft);
}
.svrow {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  gap: 0.25rem 1rem;
  align-items: center;
  padding: 0.85rem 0.25rem;
  border-bottom: 1px solid var(--line-soft);
  transition: background 0.25s var(--ease-out);
}
.svrow:hover { background: var(--surface-2); }
.svsection--tint .svrow:hover { background: var(--surface-3); }
.svrow__name { font-size: var(--fs-sm); font-weight: 500; color: var(--ink); }
.svrow__org { font-size: var(--fs-xs); color: var(--ink-2); }
.svrow__country {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.45rem;
  font-size: var(--fs-tiny);
  color: var(--ink-3);
  white-space: nowrap;
}
@media (max-width: 700px) { .svrow__country { justify-content: flex-start; } }
.svrow__fw {
  flex: none;
  padding: 0.15rem 0.5rem;
  border-radius: var(--corner-chip);
  background: var(--blue-50);
  color: var(--blue-700);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.08em;
}
.svrow__fw--itti { background: var(--gold-100); color: var(--gold-700); }
.svrow__act { justify-self: end; }
@media (max-width: 700px) {
  .svrows { grid-template-columns: minmax(0, 1fr) max-content; }
  .svrow__org { grid-column: 1; }
  .svrow__country { grid-column: 1; }
  /* justify-self matters as much as align-self here, and its absence was a bug.
     This column is `max-content` and the Enquire button below shares it, so the
     column is the button's width - 69px. A grid item defaults to
     `justify-self: stretch`, so the badge was stretched to all 69px while its
     text stayed at `text-align: start`: "SIRI" sat 8px from the left edge with
     32px of empty pill after it, reading as a mis-centred label rather than as
     a badge. `end` shrinks the pill back to its own text, where the symmetric
     padding centres it, and lines its right edge up with the button. */
  .svrow__fw { grid-row: 1; grid-column: 2; align-self: start; justify-self: end; }
  /* The button drops to the bottom of the stack rather than keeping a column of
     its own: at this width a column for it would take a third of the row and
     leave the name wrapping over three lines to pay for it. */
  .svrow__act { grid-column: 2; align-self: end; }
}

/* Provider cards. Two facets at once - country and capability - so the card
   has to show both without becoming a spec sheet. */
.svcards {
  display: grid;
  gap: 0.85rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17.5rem), 1fr));
}
.svcard {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1.05rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: var(--corner-box);
  background: var(--surface);
  transition: border-color 0.3s var(--ease-out), transform 0.3s var(--ease-out),
              box-shadow 0.3s var(--ease-out);
}
.svcard:hover {
  border-color: var(--blue-300);
  transform: translateY(-2px);
  box-shadow: 0 12px 34px -22px rgba(9, 74, 117, 0.4);
}
.svcard__name {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 550;
  line-height: 1.32;
  color: var(--ink);
  letter-spacing: -0.008em;
}
.svcard__where {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--ink-3);
  font-size: var(--fs-tiny);
  font-weight: 500;
  letter-spacing: 0.04em;
}
.svcard__iso {
  display: inline-grid;
  place-items: center;
  min-width: 1.95rem;
  padding: 0.1rem 0.32rem;
  border: 1px solid var(--line);
  border-radius: var(--corner-chip);
  color: var(--blue-700);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.06em;
}
.svcard__caps { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: auto; padding-top: 0.35rem; }
/* The provider's own site, where the record carries one. Only ever an absolute
   http(s) URL - js/services.js drops anything else rather than render it. */
.svcard__link {
  align-self: flex-start;
  color: var(--blue-700);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
}
.svcard__link:hover { color: var(--blue-900, var(--blue-700)); }
/* The website link and the Enquire button share the last line of the card, and
   `margin-top: auto` is what keeps that line on the floor: the cards in a row
   are stretched to the tallest, and without it a card with one capability chip
   would put its button halfway up the empty space. */
.svcard__foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.6rem;
}
.svcard__foot:empty { display: none; }
.svcard__foot .svcard__link { align-self: center; }
.svcap {
  padding: 0.16rem 0.48rem;
  border-radius: var(--corner-chip);
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 0.03em;
}
.svcap--none { background: transparent; border: 1px dashed var(--line); color: var(--ink-3); }
.svcard__scope {
  align-self: flex-start;
  color: var(--gold-700);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Training programmes. Bilingual by construction: the Arabic title is the
   record, the English is the translation, and both are shown because half the
   audience reads each. */
.svprog {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.35rem 1.5rem;
  padding: 1.1rem 0.25rem;
  border-bottom: 1px solid var(--line-soft);
  transition: background 0.25s var(--ease-out);
}
.svprog:first-of-type { border-top: 1px solid var(--line-soft); }
.svprog:hover { background: var(--surface-2); }
@media (min-width: 860px) {
  .svprog { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) 8rem; align-items: center; }
}
.svprog__title { margin: 0; font-size: var(--fs-base); font-weight: 550; color: var(--ink); line-height: 1.32; }
/* The second line of a programme card is whichever language the heading is not:
   Arabic under an English heading, English under an Arabic one. Hence `alt` and
   not `ar` - the class used to name a language it only holds on one of the two
   builds of the page, which is a comment that lies in the markup.

   It cannot pin a direction for the same reason. js/services.js writes dir on
   the element and the browser's own rule for that attribute is what should win;
   setting `direction` here would beat the attribute and lay the English out
   backwards on an Arabic page. `text-align: start` then resolves against
   whichever direction the element ended up with. */
.svprog__alt {
  margin: 0.25rem 0 0;
  font-family: 'Noto Kufi Arabic', 'IBM Plex Sans', system-ui, sans-serif;
  font-size: var(--fs-sm);
  font-weight: 400;
  line-height: 1.6;
  color: var(--ink-2);
  text-align: start;
}
/* The syllabus and the programme's own page, where the applicant supplied them.
   Under the two titles rather than in the meta column on the right: that column
   is 8rem of tags at the wide breakpoint and would wrap a two-word link onto
   three lines. Same treatment as .svcard__link on a provider card, so the two
   registers offer an outbound link that looks like one thing.

   `gap` and `start` rather than a margin on one side: the row mirrors on the
   Arabic build, and neither the order of the two links nor which edge they sit
   against is something this rule should pin. */
.svprog__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  margin: 0.45rem 0 0;
  text-align: start;
}
.svprog__link {
  color: var(--blue-700);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
}
.svprog__link:hover { color: var(--blue-900, var(--blue-700)); }

.svprog__center { font-size: var(--fs-xs); color: var(--ink-2); }
.svprog__center small { display: block; margin-top: 0.15rem; color: var(--ink-3); font-size: var(--fs-tiny); }
.svprog__meta { display: flex; flex-wrap: wrap; gap: 0.35rem; }

/* ── the Enquire button, and the panel it opens ─────────────────────────────
   The client's ask, 2026-09-14: a button at the end of each row that slides a
   short form in, remembers what was typed, and batches what was asked about.
   The markup is goic_enquiry_listing_render_panel() and the behaviour is
   js/enquire.js; this is only how the two look.

   Quiet by default and a real button on hover. It appears 10 times on a page of
   assessors beside names that are the actual content, so a filled button in
   every row would turn the register into a column of calls to action. */
.enqbtn {
  flex: none;
  min-height: 2rem;
  padding: 0.28rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--corner-btn);
  background: var(--surface);
  color: var(--blue-700);
  font: inherit;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.25s var(--ease-out), border-color 0.25s var(--ease-out),
              color 0.25s var(--ease-out);
}
.enqbtn:not(:disabled):hover { background: var(--blue-50); border-color: var(--blue-700); }
.enqbtn:focus-visible { outline: 2px solid var(--blue-700); outline-offset: 2px; }
/* 32px is a deliberate size in a dense register read with a mouse, and far too
   small to hit with a thumb - measured 69x32 at 390px, against the 44px minimum.
   This is the page's primary action and it repeats once per row, so it is the
   most-repeated tap target on the site. Raised on touch and at phone widths
   only; the desktop register keeps its compact row. */
@media (pointer: coarse), (max-width: 30rem) {
  .enqbtn { min-height: 2.75rem; padding-block: 0.5rem; }
}
/* SUPERSEDED 2026-09-16. Nothing emits .enqbtn--none any more: every row now
   gets an enabled Enquire button, and a row with no contact address is taken
   as an enquiry the Center introduces by hand (see inc/enquiry.php's
   goic_enquiry_listing_needs_intro). The rule is kept, unused, because the
   reasoning below is the record of what the disabled state had to solve and
   why it looked the way it did - if a "cannot enquire" state is ever needed
   again, this is where it starts. The first paragraph's figures were true on
   2026-09-14 and are what made the old rule untenable.

   Original note follows, unchanged:

   Disabled is the ordinary state for two of the three registers - 157 of 164
   providers and all 19 training programmes have no contact address on file - so
   it has to read as "nothing to press here", not as an error.

   It has to say so without being pressed, hovered or compared. The old version
   differed from the enabled button by a border tint and a grey label, which at
   --fs-micro was no difference at all, and the reason lived in a title attribute
   that a phone never shows. So it stops looking like a button entirely: the
   label changes (see enquireButton in js/services.js), the border goes, and the
   weight drops to normal. What is left is a line of muted text next to rows that
   have an outlined blue button, which is a difference that survives greyscale, a
   colourblind reader and a glance, and needs no enabled row nearby to compare
   against. The title keeps the fuller sentence for a mouse user who lingers.

   Not a dashed outline, which was the first attempt: .svcap--none already uses
   one for "Domain not stated", and a provider card with both showed two dashed
   pills stacked that read as the same kind of thing.

   Keyed on the modifier rather than :disabled because .is-sent is disabled too,
   and it is a different state with its own look - it must keep its fill and its
   weight. */
.enqbtn--none {
  border-color: transparent;
  background: transparent;
  color: var(--ink-3);
  font-weight: 400;
  cursor: not-allowed;
  /* No side padding, so the label sits flush with the country and the links
     above it rather than indented like a button that lost its outline. It also
     keeps the text inside the fixed 8rem track on .svprog, where all 19
     programmes are in this state and the label would otherwise measure out at
     almost exactly the full column; white-space is reset for the same reason,
     so a narrow track wraps instead of spilling. */
  padding-inline: 0;
  white-space: normal;
  line-height: 1.25;
}
/* Sent, set by js/enquire.js on the button that was pressed. The one piece of
   state the page keeps after the panel closes, and it is worth keeping: a
   reader working down 298 rows has no other way to see which ones they have
   already written to. */
.enqbtn.is-sent {
  border-color: transparent;
  background: var(--blue-50);
  color: var(--blue-700);
  cursor: default;
}

.enq {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  justify-content: flex-end;
}
/* Said again, and not belt and braces: `[hidden] { display: none }` is in the
   browser's own stylesheet, and the user-agent origin loses to every author
   rule - so the `display: flex` above would defeat it and the panel would
   render open from first paint. The attribute in the selector is what puts this
   rule above the class rule it has to beat. Same reason as .navdrop__menu in
   css/site.css, which shipped without it. */
.enq[hidden] { display: none; }
.enq__scrim {
  position: absolute;
  inset: 0;
  background: rgba(11, 20, 38, 0.55);
  opacity: 0;
  transition: opacity 0.25s var(--ease-out);
}
.enq.is-open .enq__scrim { opacity: 1; }
.enq__panel {
  position: relative;
  width: min(30rem, 100%);
  max-height: 100%;
  overflow-y: auto;
  padding: 1.75rem clamp(1.1rem, 3vw, 1.85rem) 2rem;
  background: var(--surface);
  box-shadow: -18px 0 40px rgba(11, 20, 38, 0.16);
  /* 100%, not a fixed distance: the panel is the full width of its own box, so
     this parks it exactly off-screen at any viewport. --enq-out flips for
     Arabic, where off-screen is the other way. */
  --enq-out: 100%;
  transform: translateX(var(--enq-out));
  transition: transform 0.25s var(--ease-out);
}
/* Arabic: the panel comes in from the other side, so both the parked position
   and the shadow it casts over the page have to turn round with it. */
[dir="rtl"] .enq__panel {
  --enq-out: -100%;
  box-shadow: 18px 0 40px rgba(11, 20, 38, 0.16);
}
.enq.is-open .enq__panel { transform: translateX(0); }
@media (prefers-reduced-motion: reduce) {
  .enq__panel, .enq__scrim { transition: none; }
}
/* The page behind stops scrolling while the panel is open. Set on the root by
   js/enquire.js rather than on body, because the sticky rail measures against
   the document and a body-level overflow lock moves it. */
.enq-open { overflow: hidden; }

/* 2.75rem, not the 2.25rem it was: this is the only control that dismisses the
   panel apart from the scrim behind it, and 36px was under the 44px a thumb
   needs. Sized the same at every width - a close button has no reason to be
   smaller with a mouse, and the glyph inside it is unchanged. */
.enq__close {
  position: absolute;
  top: 0.5rem;
  inset-inline-end: 0.5rem;
  width: 2.75rem;
  height: 2.75rem;
  border: 0;
  background: transparent;
  color: var(--ink-3);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
.enq__close:hover { color: var(--ink); }
/* The gap keeps the heading clear of the close button, so it tracks that
   button's size - 2.75rem wide sitting 0.5rem in. It is margin-inline-end
   rather than a physical right margin: the close button is placed with
   inset-inline-end, so in Arabic it sits top-left and a right margin would
   leave the gap on the side with nothing in it and run the heading under the
   button on the other. */
.enq__title { margin: 0 0 0.5rem; margin-inline-end: 3.25rem; font-size: var(--fs-md); font-weight: 600; color: var(--ink); }
.enq__about { margin: 0 0 1.25rem; font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.6; }

.enq__form { display: grid; gap: 0.9rem; }
.enq__form[hidden] { display: none; }
.enq__row { display: grid; gap: 0.9rem; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 30rem) { .enq__row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.enq__field { display: grid; gap: 0.3rem; }
.enq__label { font-size: var(--fs-micro); font-weight: 600; color: var(--ink-2); letter-spacing: 0.02em; }
.enq__req { color: var(--gold-700); }
.enq__field input,
.enq__field select {
  min-height: 2.75rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--corner-btn);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-xs);
}
.enq__field input:focus-visible,
.enq__field select:focus-visible { outline: 2px solid var(--blue-700); outline-offset: 1px; }

.enq__consent {
  display: grid;
  grid-template-columns: 1.15rem 1fr;
  gap: 0.6rem;
  align-items: start;
  font-size: var(--fs-micro);
  color: var(--ink-2);
  line-height: 1.6;
}
.enq__consent input { margin-top: 0.2rem; }

.enq__error {
  margin: 0;
  padding: 0.7rem 0.85rem;
  border-inline-start: 2px solid var(--gold-500);
  background: var(--gold-100);
  color: var(--gold-700);
  font-size: var(--fs-micro);
  line-height: 1.55;
}
.enq__error[hidden] { display: none; }

.enq__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem 1rem; margin-top: 0.25rem; }
.enq__remembered {
  border: 0;
  background: none;
  padding: 0;
  color: var(--ink-3);
  font: inherit;
  font-size: var(--fs-micro);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.enq__remembered:hover { color: var(--ink); }
.enq__remembered[hidden] { display: none; }

.enq__done { display: grid; gap: 1rem; justify-items: start; }
.enq__done[hidden] { display: none; }
.enq__donemsg { margin: 0; font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.6; }

/* Close and Undo. Close is the primary act here and keeps the button styling;
   Undo is the quiet one, because the overwhelmingly common outcome is that the
   visitor meant to send the enquiry. Same reasoning as .enq__remembered above,
   and the same treatment, so the two quiet actions in this panel match. */
.enq__doneact { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem 1rem; }
.enq__undo {
  border: 0;
  background: none;
  padding: 0;
  color: var(--ink-3);
  font: inherit;
  font-size: var(--fs-micro);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.enq__undo:hover { color: var(--ink); }
.enq__undo[hidden] { display: none; }
.enq__undoerr {
  margin: 0;
  font-size: var(--fs-micro);
  color: var(--ink-2);
  line-height: 1.5;
}
.enq__undoerr[hidden] { display: none; }

/* ── the standing list of enquiries not yet sent ───────────────────────────
   Fixed to the bottom of the viewport, opposite the panel's own edge, and
   deliberately small: it is a safety net for a misclick, not a basket the
   visitor is meant to be working in. Most people will send an enquiry and never
   look at it.

   It sits above the register but below the panel's scrim (z-index 60), so
   opening an enquiry covers it rather than leaving a second interactive thing
   floating over a modal dialog. */
.enqbox {
  position: fixed;
  inset-inline-start: 1rem;
  inset-block-end: 1rem;
  z-index: 50;
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-start;
  gap: 0.5rem;
  max-width: min(22rem, calc(100vw - 2rem));
}
.enqbox[hidden] { display: none; }
.enqbox__pill {
  border: 1px solid var(--blue-700);
  border-radius: 2rem;
  background: var(--surface);
  box-shadow: 0 2px 10px rgba(11, 20, 38, 0.15);
  padding: 0.5rem 1rem;
  min-height: 2.5rem;
  color: var(--blue-700);
  font: inherit;
  font-size: var(--fs-micro);
  cursor: pointer;
}
.enqbox__pill:focus-visible { outline: 2px solid var(--blue-700); outline-offset: 2px; }
.enqbox__list {
  border: 1px solid var(--line);
  border-radius: 0.5rem;
  background: var(--surface);
  box-shadow: 0 6px 24px rgba(11, 20, 38, 0.18);
  padding: 0.9rem 1rem;
  width: 100%;
}
.enqbox__list[hidden] { display: none; }
.enqbox__title {
  margin: 0 0 0.6rem;
  font-size: var(--fs-micro);
  font-weight: 600;
  color: var(--ink);
}
.enqbox__items { margin: 0; padding: 0; list-style: none; display: grid; gap: 0.5rem; }
.enqbox__item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25rem 0.75rem;
}
.enqbox__name { font-size: var(--fs-micro); color: var(--ink-2); line-height: 1.4; }
.enqbox__pull {
  border: 0;
  background: none;
  padding: 0;
  color: var(--blue-700);
  font: inherit;
  font-size: var(--fs-micro);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  white-space: nowrap;
}
.enqbox__pull:disabled { color: var(--ink-3); cursor: default; }
.enqbox__msg,
.enqbox__note {
  margin: 0.7rem 0 0;
  font-size: var(--fs-micro);
  color: var(--ink-3);
  line-height: 1.5;
}
.enqbox__msg[hidden] { display: none; }

/* ── pager ─────────────────────────────────────────────────────────────────
   The register used to grow: 24 rows, then 48, then 72, and the section ran
   past three screens before the reader reached the next pillar. It pages
   instead, so a section is a fixed height whatever the filter returns.

   Prev and Next hold the two ends, the numbers sit centered between them, and
   the range caption goes underneath rather than beside - on a narrow screen the
   numbers drop out and the caption takes the middle, which keeps the row to
   three items at every width. */
.svpager {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.6rem 1rem;
  margin-top: 1.35rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line-soft);
}
.svpager[hidden] { display: none; }
.svpager__step {
  grid-row: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  position: relative;
  min-height: 2.75rem;
  padding: 0.5rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: var(--corner-btn);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 500;
  cursor: pointer;
  touch-action: manipulation;
  transition: border-color 0.2s var(--ease-out), color 0.2s var(--ease-out),
              box-shadow 0.25s var(--ease-out), transform 0.12s var(--ease-out);
}
.svpager__step:first-of-type { justify-self: start; grid-column: 1; }
.svpager__step:last-of-type { justify-self: end; grid-column: 3; }
.svpager__step svg { width: 1rem; height: 1rem; flex: none; }
/* The chevrons point along the reading direction, so they mirror with it. */
[dir="rtl"] .svpager__step svg { transform: scaleX(-1); }
.svpager__step:hover:not(:disabled) {
  border-color: var(--blue-600);
  color: var(--blue-700);
  box-shadow: var(--lift-1);
}
.svpager__step:active:not(:disabled) { transform: scale(0.97); }
/* Disabled rather than removed: an end of the list that keeps its place tells
   the reader they are at the end, where a vanished button just moves things. */
.svpager__step:disabled { opacity: 0.38; cursor: default; }

.svpager__pages {
  grid-row: 1;
  grid-column: 2;
  display: flex;
  align-items: center;
  gap: 0.2rem;
}
.svpager__no {
  min-width: 2.4rem;
  min-height: 2.4rem;
  padding: 0 0.4rem;
  border: 1px solid transparent;
  border-radius: var(--corner-btn);
  background: none;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.18s var(--ease-out), color 0.18s var(--ease-out),
              border-color 0.18s var(--ease-out);
}
.svpager__no:hover { background: var(--surface-3); color: var(--ink); }
/* The one filled plate on the page that carries text of its own, so it is the
   one place a dark band cannot inherit its way to a readable result: --blue-800
   comes up to the logo's sky there, and white on sky is a 1.5 contrast ratio.
   The label reads off a token instead, and the band sets it to navy - a light
   chip with dark type, which is what the inversion should give you anyway. */
.svpager__no[aria-current="true"] {
  background: var(--blue-800);
  border-color: var(--blue-800);
  color: var(--sv-on-fill, #fff);
  font-weight: 600;
}
.svpager__gap {
  padding-inline: 0.15rem;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}
.svpager__at {
  grid-row: 2;
  grid-column: 2;
  margin: 0;
  color: var(--ink-3);
  font-size: var(--fs-tiny);
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

@media (max-width: 640px) {
  /* Numbers off, caption into the middle. Prev and Next are the whole control
     at this width, and they are the two a thumb can reach. */
  .svpager { grid-template-columns: auto 1fr auto; }
  .svpager__pages { display: none; }
  .svpager__at { grid-row: 1; grid-column: 2; }
  .svpager__step span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  /* Icon-only now, so the button has to hold the 44px target on its own. */
  .svpager__step { min-width: 2.75rem; justify-content: center; padding: 0.5rem; }
}
.svempty {
  padding: 2.5rem 1rem;
  border: 1px dashed var(--line);
  border-radius: var(--corner-box);
  text-align: center;
  color: var(--ink-2);
}
.svempty[hidden] { display: none; }
.svempty b { display: block; margin-bottom: 0.4rem; color: var(--ink); font-weight: 550; }

/* ───────────────────────── the request route ─────────────────────────
   Five steps on one line. It shipped as a flat strip: five circles, five
   hairlines, five paragraphs, all at one weight, and nothing about it said
   which end was the start or that a reader was meant to follow it. A route
   that cannot be walked is a list with decoration on it.

   Two layers, and they are deliberately independent:

   1. Arrival. The rail draws left to right as the block scrolls into place,
      and each numeral fills as the rail reaches it. This is the page's own
      --p (js/scrub.js) and nothing else - the same scroll position the rest
      of the section arrives on, so the route is drawn by the reader getting
      to it rather than by a timer. --p falls back to 1, so with the module
      absent the whole rail is simply already drawn.

   2. Inspection. Pointer or keyboard promotes one step and recedes the other
      four. js/services.js owns which one through data-active; the CSS here
      only reacts to it. Nothing is hidden in the unpromoted state - every
      word is legible at rest - so a reader who never touches it loses no
      content, and a reader who does gets the strip to hold still on the step
      they are reading.

   The lit fraction is computed per step rather than by JS writing five
   values: --i is the step's index and --p * 5.6 is where the drawing head
   has got to, so clamp(0, p*5.6 - i, 1) is how far this step has come up.
   The 5.6 rather than 5 is so the last numeral finishes before --p does,
   which stops the fifth step lagging visibly behind the other four.

   Nothing here animates width, height, top or left: the rail fill is a
   scaleX, the promotion is a translate, and both directions are logical, so
   the RTL override at the foot of this block is the whole of the RTL work. */

.svroute {
  position: relative;
  display: grid;
  gap: 1.25rem 1rem;
  margin: clamp(2rem, 4.5vh, 3rem) 0 0;
  padding: 2.25rem 0 0;
  border-top: 1px solid var(--line-soft);
  list-style: none;
}
@media (min-width: 860px) { .svroute { grid-template-columns: repeat(5, 1fr); } }

.svroute__step {
  position: relative;
  /* How far the drawing head has come past this step, and past the one after
     it. The second is what fills this step's outgoing connector. */
  --lit:  clamp(0, calc(var(--p, 1) * 5.6 - var(--i)), 1);
  --litn: clamp(0, calc(var(--p, 1) * 5.6 - var(--i) - 1), 1);
}
.svroute__step:nth-child(1) { --i: 0; }
.svroute__step:nth-child(2) { --i: 1; }
.svroute__step:nth-child(3) { --i: 2; }
.svroute__step:nth-child(4) { --i: 3; }
.svroute__step:nth-child(5) { --i: 4; }

/* The hit area is the whole step, so the target is a card and not a 30px
   circle. Reset rather than restyle - it is a button for the keyboard and the
   touch tap, not because anything about it should look like one. */
.svroute__hit {
  display: block;
  width: 100%;
  margin: 0;
  padding: 2.4rem 0.9rem 0.9rem 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
  transition: transform 0.4s var(--ease-out), background-color 0.3s var(--ease-out);
}
[dir="rtl"] .svroute__hit { padding-inline: 0 0.9rem; }
.svroute__hit:focus-visible {
  outline: 2px solid var(--blue-600);
  outline-offset: 3px;
}

/* ── the numeral ──
   Border and figure both interpolate from the resting hairline to the brand
   blue on --lit, so the chip does not appear, it fills. The scale is small on
   purpose: 0.9 to 1 is enough to read as arriving and not enough to shift the
   line the numerals sit on. */
.svroute__no {
  position: absolute;
  top: 0;
  inset-inline-start: 0;
  display: grid;
  place-items: center;
  width: 1.85rem; height: 1.85rem;
  border: 1px solid color-mix(in srgb, var(--blue-300) calc(var(--lit) * 100%), var(--line-soft));
  border-radius: 50%;
  background: var(--surface);
  color: color-mix(in srgb, var(--blue-700) calc(var(--lit) * 100%), var(--ink-3));
  font-size: var(--fs-micro);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  scale: calc(0.9 + var(--lit) * 0.1);
  z-index: 2;
  transition: background-color 0.35s var(--ease-out), border-color 0.35s var(--ease-out),
              color 0.35s var(--ease-out), box-shadow 0.35s var(--ease-out);
}
.svsection--tint .svroute__no { background: var(--surface-2); }

/* ── the connector ──
   Track and fill are two layers on the same line rather than one line whose
   colour changes, because the fill has to be able to sit part-drawn. The
   track is the hairline the strip already had; the fill scales over it. */
@media (min-width: 860px) {
  .svroute__step::before,
  .svroute__step::after {
    content: '';
    position: absolute;
    top: 0.92rem;
    inset-inline-start: 1.85rem;
    width: calc(100% - 1.85rem);
    height: 1px;
    z-index: 1;
  }
  .svroute__step::before { background: var(--line-soft); }
  .svroute__step::after {
    background: linear-gradient(to right, var(--blue-300), var(--blue-600));
    transform: scaleX(var(--litn));
    transform-origin: left center;
  }
  .svroute__step:last-child::before,
  .svroute__step:last-child::after { content: none; }
}

.svroute__name {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--ink);
}

/* ── the acting party ──
   The one thing the strip did not say and the thing a process diagram is read
   for. Set as a rule under the label rather than a filled chip: five filled
   chips in a row would out-shout the five names above them, which are what a
   reader is scanning. */
.svroute__who {
  display: inline-block;
  margin-top: 0.3rem;
  padding-bottom: 0.15rem;
  border-bottom: 1px solid color-mix(in srgb, var(--blue-300) calc(var(--lit) * 100%), transparent);
  color: color-mix(in srgb, var(--blue-700) calc(var(--lit) * 100%), var(--ink-3));
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transition: color 0.3s var(--ease-out), border-color 0.3s var(--ease-out);
}

.svroute__txt {
  display: block;
  margin-top: 0.5rem;
  color: var(--ink-2);
  font-size: var(--fs-sm);
  line-height: 1.6;
  transition: color 0.3s var(--ease-out);
}

/* ── promotion ──
   data-active is the index js/services.js is holding: pointer, focus or tap.
   The promoted step lifts and fills its numeral; the rest drop their body copy
   to --ink-3, which is the difference between one step being read and five
   being scanned. The attribute is absent until the module runs, so the resting
   page has no promoted step and every step is at full ink. */
[data-route][data-active] .svroute__txt { color: var(--ink-3); }
[data-route][data-active] .svroute__name { color: var(--ink-2); }

.svroute__step.is-on .svroute__hit {
  background: color-mix(in srgb, var(--blue-50) 60%, transparent);
  transform: translateY(-3px);
}
.svroute__step.is-on .svroute__name { color: var(--ink); }
.svroute__step.is-on .svroute__txt { color: var(--ink-2); }
.svroute__step.is-on .svroute__no {
  border-color: var(--blue-700);
  background: var(--blue-700);
  color: #fff;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--blue-300) 34%, transparent);
}
.svroute__step.is-on .svroute__who {
  border-bottom-color: var(--gold-500);
  color: var(--gold-700);
}

/* The fill runs to the end of a promoted step's own connector, so the rail
   reads as "you are here" and not just as an arrival that already finished. */
@media (min-width: 860px) {
  .svroute__step.is-before::after,
  .svroute__step.is-on::after { transform: scaleX(1); }
}

[dir="rtl"] .svroute__step::after { transform-origin: right center; }

/* Stacked, the rail is vertical: the connector runs down the gutter the
   numerals sit in rather than across, which is the only honest reading of a
   sequence in one column. */
@media (max-width: 859px) {
  .svroute { gap: 0; }
  .svroute__hit { padding: 0.55rem 0 1.1rem 2.85rem; }
  [dir="rtl"] .svroute__hit { padding-inline: 2.85rem 0; }
  .svroute__no { top: 0.5rem; }
  .svroute__step:not(:last-child)::before,
  .svroute__step:not(:last-child)::after {
    content: '';
    position: absolute;
    top: 2.5rem;
    inset-inline-start: 0.92rem;
    width: 1px;
    height: calc(100% - 2.5rem);
    z-index: 1;
  }
  .svroute__step:not(:last-child)::before { background: var(--line-soft); }
  .svroute__step:not(:last-child)::after {
    background: var(--blue-600);
    transform: scaleY(var(--litn));
    transform-origin: top center;
  }
  .svroute__step.is-before::after,
  .svroute__step.is-on::after { transform: scaleY(1); }
}

/* ───────────────────────── get listed (dark close) ───────────────────────── */

.svlist {
  position: relative;
  isolation: isolate;
  padding-block: clamp(3.5rem, 8vh, 6.5rem);
  background: var(--blue-900);
  color: #fff;
  overflow: hidden;
}
.svlist::before {
  content: '';
  position: absolute;
  inset: auto -10% -30% -20%;
  height: 90%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(45% 60% at 30% 90%, rgba(75, 126, 153, 0.24), transparent 72%);
}
.svlist .eyebrow { color: var(--gold-300); }
/* .svlist h2 and .svlist__lead were here, and both are gone with the head that
   needed them. This section's head used to be a bare .svrise with no component
   of its own, so it carried a private copy of .svhead's type treatment; it is
   .section__head--wide now, like every other head on the page, and that brings
   its own. The copy had to go rather than simply stop being used: .svlist h2 and
   .section__head--wide h2 have equal specificity and services.css loads after
   site.css, so the dead rule was still winning and holding the heading at 46.4px
   while the other four sat at 65.6px. It was the only h2 inside .svlist. */

.svform__grid {
  display: grid;
  gap: 1rem;
  margin-top: clamp(2rem, 4.5vh, 3rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.svform {
  display: flex;
  flex-direction: column;
  padding: 1.5rem 1.4rem 1.4rem;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--corner-box);
  background: rgba(255, 255, 255, 0.035);
  transition: border-color 0.35s var(--ease-out), background 0.35s var(--ease-out);
}
.svform:hover { border-color: rgba(218, 165, 32, 0.55); background: rgba(255, 255, 255, 0.06); }
.svform__num {
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--gold-300);
  font-variant-numeric: tabular-nums;
}
.svform h3 { margin: 0.7rem 0 0; font-size: var(--fs-md); font-weight: 550; letter-spacing: -0.01em; }
.svform > p { margin: 0.55rem 0 0; color: rgba(255, 255, 255, 0.68); font-size: var(--fs-sm); line-height: 1.6; }
.svform ol {
  margin: 1.1rem 0 1.35rem;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.3rem;
  counter-reset: fld;
}
.svform li {
  position: relative;
  padding-inline-start: 1.5rem;
  color: rgba(255, 255, 255, 0.75);
  font-size: var(--fs-sm);
  line-height: 1.55;
}
.svform li::before {
  counter-increment: fld;
  content: counter(fld);
  position: absolute;
  inset-inline-start: 0;
  top: 0.05rem;
  color: rgba(255, 255, 255, 0.4);
  font-size: var(--fs-micro);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.svform .btn { align-self: flex-start; }
.svform .btn--ghost { border-color: rgba(255, 255, 255, 0.32); color: #fff; }
.svform .btn--ghost:hover { border-color: var(--gold-300); background: rgba(255, 255, 255, 0.08); }

/* Two routes into each register: the online form, and the bilingual document
   for anyone who would rather fill it in and email it. The solid button is the
   online one because that is the route the Center wants used - the download
   sits beside it as the alternative, not as the equal.

   Wrapped rather than left as two siblings because the card is a flex column
   and the margin-top:auto that pinned one button to the bottom would, with two,
   push only the first and leave a gap between them. */
.svform__cta {
  margin-top: auto;
  padding-top: 1.4rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

/* Review pipeline. The forms all end in the same three-state admin block, so it
   is stated once here rather than three times in the cards. */
.svpipe {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.35rem;
  align-items: center;
  margin-top: clamp(2rem, 4vh, 2.75rem);
  padding-top: 1.5rem;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  color: rgba(255, 255, 255, 0.7);
  font-size: var(--fs-xs);
}
.svpipe__label {
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45);
}
.svpipe span.svpipe__state { display: inline-flex; align-items: center; gap: 0.45rem; }
.svpipe__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.svpipe__state--pending { color: var(--gold-300); }
.svpipe__state--ok { color: #7fd4a4; }
.svpipe__state--no { color: #eba0a0; }
.svpipe__arrow { color: rgba(255, 255, 255, 0.3); }
[dir="rtl"] .svpipe__arrow { transform: scaleX(-1); }

/* ═══════════════════════════ motion and depth ═══════════════════════════
   The page shipped with one gesture - fade up 18px, once, on a threshold - and
   it read as inert. Everything below replaces that with motion that is either
   tied to the reader's own input or reporting a real value:

     .svm__*      entrance, on load, in CSS - the masthead is above the fold, so
                  there is no scroll to drive it and no observer to wait for
     .svrise      arrival driven by scroll position through --p (js/scrub.js)
     .svhead__num the section's index, drifting against its own head
     .svrail__*   where the reader is, and how much page is left
     [data-sheen] a highlight that follows the pointer across a surface
     --lift-*     one elevation scale instead of four invented shadows

   The obligations are the same three css/site.css takes on: under reduced motion
   kill the transition, pin the resting state, and leave the content readable.
   That block is at the end of this file and covers every selector here.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── the masthead map ─────────────────────────────────────────────────────
   Painted by js/gcc-network.js. The opening sweep, the rings, the pings and the
   couriers are all in that module; the labels are DOM, so their entrance is
   here with the rest of the masthead's and on the same 90ms rhythm. It starts
   after the sweep has left the Gulf rather than with the copy - a label that
   arrives before the coastline under it has nothing to point at. */
.svnet__pill {
  animation: sv-in 0.7s var(--ease-out) both;
  animation-delay: calc(520ms + var(--i, 0) * 90ms);
}
.svnet__labels li:nth-child(1) .svnet__pill { --i: 0; }
.svnet__labels li:nth-child(2) .svnet__pill { --i: 1; }
.svnet__labels li:nth-child(3) .svnet__pill { --i: 2; }
.svnet__labels li:nth-child(4) .svnet__pill { --i: 3; }
.svnet__labels li:nth-child(5) .svnet__pill { --i: 4; }
.svnet__labels li:nth-child(6) .svnet__pill { --i: 5; }

/* ── the masthead entrance ─────────────────────────────────────────────────
   Six elements, one rhythm, 90ms apart. In CSS rather than on an observer for
   two reasons: content at the top of the page must never depend on a module
   loading to become visible, and an entrance that has to wait for an
   IntersectionObserver callback starts a frame or two late, which is exactly
   long enough to read as a stutter. */
@keyframes sv-in   { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }
@keyframes sv-lift { from { translate: 0 108%; }             to { translate: 0 0; } }
@keyframes sv-rule { from { transform: scaleX(0); }          to { transform: scaleX(1); } }

.svm__intro .eyebrow,
.svm__lead,
.svm__actions {
  animation: sv-in 0.75s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 90ms);
}
.svm__lead    { --i: 2; }
.svm__actions { --i: 3; }

/* The lift is clipped by a box the height of one line. A single overflow on the
   h1 would clip the descenders of the last line while it arrived, which is why
   each line carries its own - and why the clip box is a fraction taller than the
   line and pulled back up by the same amount. */
.svm__mask {
  display: block;
  overflow: clip;
  padding-bottom: 0.08em;
  margin-bottom: -0.08em;
}
.svm__mask > span {
  display: block;
  animation: sv-lift 0.95s var(--ease-out) both;
}
.svm__mask:nth-of-type(1) > span { animation-delay: 90ms; }
.svm__mask:nth-of-type(2) > span { animation-delay: 190ms; }

/* ── arrival off scroll position ───────────────────────────────────────────
   --p is written per frame by js/scrub.js and nothing else. Opacity is run at
   1.5x so a block is legible well before it has finished settling: a paragraph
   held at half opacity for half a screen of scroll is worse than no animation.
   The 1 fallback is what the page looks like if the module never runs. */
.svrise {
  opacity: clamp(0, calc(var(--p, 1) * 1.5), 1);
  translate: 0 calc((1 - var(--p, 1)) * 26px);
}

/* ── the section's index ───────────────────────────────────────────────────
   An outlined numeral above each section head. It was set inline in the eyebrow
   as "01 / Factory assessment", where it was the same 0.7rem as the words and
   did nothing: a reader four screens down could not tell which pillar they were
   in without reading. At display size it is a landmark.

   Outline rather than fill because a solid numeral this size competes with the
   heading under it. It reads --p from the scrubbed head it sits in - an
   inherited custom property, so it needs no data-scrub of its own - and drifts
   against the head rather than with it, which is what makes the head feel like
   it has depth instead of being one flat layer arriving. */
.svhead__lede { position: relative; }
.svhead__num {
  display: block;
  margin-bottom: 0.15rem;
  font-size: clamp(3.4rem, 7vw, 5.6rem);
  font-weight: 600;
  line-height: 0.78;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(45, 103, 137, 0.26);
  translate: 0 calc((1 - var(--p, 1)) * -18px);
}
.svsection--tint .svhead__num { -webkit-text-stroke-color: rgba(45, 103, 137, 0.3); }
.svhead__num--dark { -webkit-text-stroke-color: rgba(231, 196, 108, 0.34); }

/* The gold rule under a section head draws itself as the head arrives. It is
   already a hairline the reader is not asked to look at; drawing it is what
   turns it from a divider into the last beat of the head landing. */
.svhead .rule {
  transform: scaleX(var(--p, 1));
  transform-origin: 0 50%;
}
[dir="rtl"] .svhead .rule { transform-origin: 100% 50%; }

/* ── the rail ──────────────────────────────────────────────────────────────
   Two additions. The mark is one element moved between links rather than four
   underlines blinking on and off - one event instead of two, and it shows the
   reader which direction they travelled. The hairline above it is the only
   purely orientational thing on the page: the rail says which pillar, that says
   how much is left. */
.svrail__inner { position: relative; }
.svrail__prog {
  position: absolute;
  inset-inline: 0;
  bottom: -1px;
  height: 2px;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--blue-600), var(--sv-gold-line));
  pointer-events: none;
}
[dir="rtl"] .svrail__prog { transform-origin: 100% 50%; }
.svrail__ind {
  position: absolute;
  bottom: 0;
  inset-inline-start: 0;
  width: 1px;
  height: 2px;
  transform-origin: 0 50%;
  background: var(--gold-600);
  pointer-events: none;
  transition: transform 0.45s var(--ease-out);
}
.svrail__ind[hidden] { display: none; }
/* The per-link underline stays as the fallback and is only superseded once the
   mark is really there - the module may not have run, and a rail with no active
   state at all is worse than one that blinks. */
.svrail:has(.svrail__ind:not([hidden])) a.is-active::after { transform: scaleX(0); }

/* ── pointer sheen ─────────────────────────────────────────────────────────
   A highlight at the cursor's actual position inside the surface. The cheapest
   honest depth cue there is: a rectangle that answers where the pointer is
   stops reading as printed, and unlike a hover shadow it says the surface is
   live before the reader commits to clicking it. Two custom properties come
   from JS; everything the surface does with them is here.

   z-index -1 over an isolated stacking context, so it sits above the panel's
   own background and below every word in it. */
[data-sheen] { position: relative; isolation: isolate; }
[data-sheen]::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s var(--ease-out);
  background: radial-gradient(18rem 18rem at var(--mx, 50%) var(--my, 0%),
                              rgba(45, 103, 137, 0.10), transparent 68%);
}
[data-sheen]:hover::after { opacity: 1; }
/* On the dark close the same gesture has to be light, not blue. */
.svform[data-sheen]::after {
  background: radial-gradient(18rem 18rem at var(--mx, 50%) var(--my, 0%),
                              rgba(231, 196, 108, 0.14), transparent 68%);
}

/* ── depth ─────────────────────────────────────────────────────────────────
   Every panel on the page was a 1px border on a flat fill, which is why it read
   as a printed document rather than an interface. One elevation scale, applied
   at rest and one step up on hover, and the same 0.28s ease-out everywhere so
   the whole page has one reflex. */
.svfw__item {
  box-shadow: var(--lift-1);
  transition: box-shadow 0.28s var(--ease-out), transform 0.28s var(--ease-out),
              border-color 0.28s var(--ease-out);
}
.svfw__item:hover { transform: translateY(-3px); box-shadow: var(--lift-3); }

.svcard {
  box-shadow: var(--lift-1);
  transition: box-shadow 0.28s var(--ease-out), transform 0.28s var(--ease-out),
              border-color 0.28s var(--ease-out);
}
.svcard:hover { transform: translateY(-3px); box-shadow: var(--lift-2); }

.svdir__bar { box-shadow: var(--lift-1); }

.svdoc { transition: border-color 0.28s var(--ease-out), background 0.28s var(--ease-out),
                     box-shadow 0.28s var(--ease-out), transform 0.28s var(--ease-out); }
.svdoc__arrow { transition: opacity 0.28s var(--ease-out), translate 0.28s var(--ease-out); }
.svdoc__ico svg { transition: scale 0.28s var(--ease-out); }
/* A tile is a bigger object than the row it replaced, so it takes the bigger
   step off the page. */
.svdoc:hover { transform: translateY(-2px); box-shadow: var(--lift-2); }
.svdoc:hover .svdoc__ico svg { scale: 1.05; }

/* ── the distribution bars ─────────────────────────────────────────────────
   The fill was a flat blue growing from zero. It now has a light edge, and a
   sheen crosses it once as it lands - the same reading a measuring instrument
   gives when it settles. It runs once, on the fill's own transition, so it
   cannot become an idle loop the reader has to ignore. */
.svbars__track { box-shadow: inset 0 0 0 1px rgba(20, 30, 38, 0.05); }
.svbars__fill {
  position: relative;
  overflow: hidden;
  background: linear-gradient(90deg, var(--blue-700), var(--blue-500));
}
/* Held until the host is released. The markup is written on load and the bars
   are not filled until the row is on screen, so an animation declared here
   unconditionally would run against a bar still at scaleX(0) and be over before
   anyone saw the bar at all. The stagger is the fill's own --d, so the sheen
   crosses each bar as that bar lands rather than all six at once. */
.svbars__fill::after {
  content: '';
  position: absolute;
  inset: 0;
  translate: -100% 0;
  background: linear-gradient(100deg, transparent 35%, rgba(255, 255, 255, 0.55) 50%, transparent 65%);
}
.is-filled .svbars__fill::after {
  animation: sv-sheen 1.05s var(--ease-out) both;
  animation-delay: calc(0.3s + var(--d, 0s));
}
@keyframes sv-sheen { to { translate: 100% 0; } }

/* ── press states ──────────────────────────────────────────────────────────
   A filter chip is the most-clicked thing on the page and it had no press
   state at all - only a hover and a pressed colour, which on a phone means the
   first feedback a reader gets is the result list changing under their thumb. */
.svchip { touch-action: manipulation; }
.svchip:active { transform: scale(0.96); }
.svchip { transition: background 0.2s var(--ease-out), border-color 0.2s var(--ease-out),
                      color 0.2s var(--ease-out), transform 0.12s var(--ease-out); }
.btn:active { transform: scale(0.98); }

/* Figures are counted up, so they change width as they run; tabular figures are
   what stop the label beside them jittering while they do. */
.svstats b { font-variant-numeric: tabular-nums; }


/* ─────────────────────────── reduced motion ───────────────────────────
   Three obligations for every animated thing on this page, the same three
   css/site.css takes on: kill the transition, reset the resting state, and
   leave the content readable. The scrubbed elements are the important case -
   an element left at --p 0 would be invisible, not merely still - and
   js/scrub.js pins --p to 1 for exactly that reason. This pins it again in CSS
   so the page is still readable if the module never runs at all. */
@media (prefers-reduced-motion: reduce) {
  .svrail a, .svrail a::after, .svrail__ind, .svnet__pill,
  .svfw__item, .svdoc, .svchip, .svrow, .svcard, .svprog, .svform,
  .svfw__filter, .svfw__filter svg, .svdoc__arrow, .svdoc__ico svg,
  .svbars__fill, .svsearch input, .svsearch__clear,
  .svpager__step, .svpager__no,
  [data-sheen]::after {
    transition: none !important;
  }
  .svm__intro .eyebrow, .svm__lead, .svm__actions, .svnet__pill,
  .svm__mask > span, .is-filled .svbars__fill::after, .svnet__cue {
    animation: none !important;
  }
  /* The cue stops breathing, so it has to hold the eye standing still. */
  .svnet__cue { box-shadow: 0 0 0 3px rgba(218, 165, 32, 0.22); }
  .svnet__hint { transition: none; }
  .svrise { --p: 1; opacity: 1; translate: 0 0; }
  .svhead__num { translate: 0 0; }
  .svhead .rule { transform: none; }
  .svbars__fill { transform: scaleX(var(--v, 0)); }
  .svbars__fill::after { display: none; }
  .svfw__item:hover, .svcard:hover, .svdoc:hover,
  .svpager__step:active:not(:disabled) { transform: none; }
  /* The route keeps its promotion - which step is selected is information, not
     motion - and loses only the lift that carried it. --p is pinned at 1 above,
     so the rail and every numeral are simply already drawn. */
  .svroute__step.is-on .svroute__hit { transform: none; }
  /* The framework button keeps the arrow's direction, which says where pressing
     it takes the reader, and loses the nudge that pointed at it. */
  .svfw__filter:hover svg, .svfw__filter[aria-pressed="true"]:hover svg { translate: 0; }
  /* The tile arrow still appears on hover - it says what a click does - but it
     arrives in place rather than dropping in, and the glyph holds its size. */
  .svdoc__arrow { translate: 0; }
  .svdoc:hover .svdoc__ico svg { scale: 1; }
  [data-sheen]:hover::after { opacity: 0; }
}

/* ─────────────────────────── small screens ─────────────────────────── */

@media (max-width: 640px) {
  .svm { padding-top: calc(var(--nav-h) + 2.25rem); }
  .svbars__row { grid-template-columns: minmax(5.5rem, 1fr) minmax(0, 1.4fr) 2.6rem; gap: 0.6rem; font-size: var(--fs-tiny); }
}

/* ── country flags ─────────────────────────────────────────────────────────
   Drawn into an inline sprite by tools/build-flags.py, so the page still makes
   no external requests. The inset ring is structural, not decoration: the hoist
   band on Bahrain's and Qatar's flags and the middle band on Uzbekistan's are
   white, and without it those three lose their shape against the surface. */
.svflag {
  flex: none;
  width: 1.35rem;
  height: 0.9rem;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px rgba(20, 30, 38, 0.16);
}
.svchip[aria-pressed="true"] .svflag { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3); }
/* The bar chart's label cell and the register's country cell both hold a flag
   and a name now, so they line the two up rather than setting text alone. */
.svbars__row span:first-child { display: flex; align-items: center; gap: 0.45rem; }

/* ── ask the Center ────────────────────────────────────────────────────────
   The call to action that closes each of the three directories, and the one
   form section all three of them point at.

   No colours of its own. The band on the providers section re-points --surface,
   --ink and --line for everything inside it, so stating the row in tokens is
   what lets the same component sit on the light planes and the dark one without
   a modifier class - the only thing the markup varies is the button, which is
   site.css's own .btn--on-dark. */
.svask {
  margin-top: clamp(2rem, 4vh, 2.75rem);
  padding: clamp(1.1rem, 2.2vh, 1.5rem) clamp(1.2rem, 2.4vw, 1.75rem);
  border: 1px solid var(--line);
  border-radius: var(--radius, 10px);
  background: var(--surface-2);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.9rem clamp(1rem, 2vw, 1.75rem);
}
/* Reads as a note rather than as a heading: the register above it is the
   section's subject and this is the aside at the end of it. */
.svask__txt {
  margin: 0;
  flex: 1 1 24ch;
  max-width: 62ch;
  color: var(--ink-2);
  font-size: var(--fs-sm, 0.95rem);
}
.svask .btn { flex: none; }

/* The form's own section. The width is the form's, not the page's - a text
   input stretched to a directory's full measure is harder to read and invites
   the eye to travel further than the field is worth. */
.svask-sec__body {
  max-width: 46rem;
  margin-inline: auto;
}
