/* ==========================================================================
   AMIN APAC — hero-map.css
   The vector 3D plate that carries the landing-page hero.

   Loaded after base.css and immersive-hero.css. immersive-hero.css still owns the
   stage shell, the copy block, the markets rail, the market card, the scroll
   cue and the dark header; this file owns only what used to be the globe:
   the tilted plate, its ground, the map layers and the pins. The three.js
   rules in immersive-hero.css (.globe-layer, .gmark-*, .is-flat) match nothing now
   and are harmless.

   One perspective on .map-stage, one transform on .plate, everything else a
   child of it. The scroll writes --p/--tilt/--spin/--zoom/--lift onto <body>
   and the two transforms below compose them.
   ========================================================================== */

:root{
  --p:0;
  --tilt:16deg;
  --zoom:1;
  --spin:0deg;
  --lift:0px;
  --m-line-soft:rgba(200,208,220,.09);

  /* The one non-purple in the map, and it means exactly one thing: this is the
     market you have chosen. Purple is what the whole plate is made of - lit
     countries, pin dots, the selected label - so a purple "you are here" had
     nothing to be brighter than. Green is far enough around the wheel to hold
     its own against a purple label sitting on purple terrain, and reads as a
     live indicator rather than a second brand colour. */
  --live:#2FE39B;
  --live-soft:rgb(47 227 155 / .28);
  /* Label ink for the selected pill. White on --live is 1.7:1 and unreadable,
     so the text inverts; a near-black with a green cast rather than a flat one,
     so it reads as part of the same colour rather than a default black. */
  --live-ink:#04140D;
}

/* -- the stage --------------------------------------------------------- */
.map-stage{
  position:absolute;inset:0;z-index:1;
  overflow:hidden;
  /* Was 3400px. At that distance the perspective divide rendered the near
     (southern) edge of the plate 1.075x and the far (northern) edge 0.935x -
     a 15% spread across one map, all of it on the north-south axis. It
     inflated Australia and New Zealand, shrank China and Japan, and opened a
     gap over the Timor Sea that is not in the geometry: the source is honest
     plate carree and Indonesia's southern arc actually overlaps Australia's
     northern coast in latitude. 7200px cuts that spread to ~7%, which is
     enough depth to read as a table you are looking across and little enough
     to keep the spacing close to a world map. */
  perspective:7200px;
  perspective-origin:50% 38%;

  /* v7's environment, verbatim: neutral grey ink with two lighter washes, so
     purple means "an agency works here" rather than being what the page is
     made of. The v2 stage tokens are near-black and read as a different page. */
  background:
    radial-gradient(120% 80% at 50% 16%, #2A2E36 0%, transparent 62%),
    radial-gradient(80% 55% at 62% 92%, #23262D 0%, transparent 72%),
    #14161A;
}

/* ==========================================================================
   Focus turns the perspective OFF, and it is the single biggest thing keeping
   the focused country sharp.
   ==========================================================================
   A perspective on an ancestor puts the whole map into Chrome's 3D compositing
   path, where the subtree is rasterised ONCE and then texture-mapped. That
   raster is far below device resolution and, worse, barely moves with it:
   measured on a focused Philippines, interior detail was 1.67 at DPR 1 and
   1.90 at DPR 2 - a HiDPI screen bought almost nothing. With the perspective
   removed the same frame measures 3.83 and 6.90. So the map was throwing away
   most of its resolution before any raster reached it, which is why sharpening
   the relief assets kept returning so little (a plate rebuilt at 2400px
   instead of 1400px moved the rendered figure by 5%).

   It is the PRESENCE of the property, not its value: 7200px, 30000px and
   200000px all measure the same 1.9: only `none` restores it.

   Turning it off is free HERE and nowhere else. The focus tilt is 6-7deg, so
   the perspective divide has almost nothing left to do - probed at the plate's
   corners, 7200px against none moves them 2px out of 676. In the region view
   the tilt is 16deg and the same probe shows the far edge drawn 3% narrower
   than the near one, which IS the depth cue the hero is built on, so it stays.

   :not(.is-flying) is what makes the switch invisible. is-flying is on this
   element for the length of the flight, so the property survives the whole
   move and only drops once the plate has settled at focus tilt, where the
   difference is those 2px. Coming back it returns at the START of the return
   flight - also at focus tilt, the cheap end. Both edges land where the change
   costs least, and neither is a transition: perspective cannot animate to
   none, and at 2px it does not need to. */
.is-focus .map-stage:not(.is-flying){perspective:none}


/* -- the ground -------------------------------------------------------- */
.ground{
  position:absolute;left:50%;top:52%;
  width:260%;aspect-ratio:1/1;
  transform:translate(-50%,-50%) rotateX(var(--tilt)) rotateZ(var(--spin)) scale(calc(1 + var(--p) * .06));
  transform-style:preserve-3d;
  pointer-events:none;
}
.ground i{position:absolute;inset:0;display:block}
.ground-grid{
  background:
    repeating-linear-gradient(0deg,  var(--m-line-soft) 0 1px, transparent 1px 8.33%),
    repeating-linear-gradient(90deg, var(--m-line-soft) 0 1px, transparent 1px 8.33%);
  -webkit-mask-image:radial-gradient(closest-side, #000 25%, transparent 78%);
          mask-image:radial-gradient(closest-side, #000 25%, transparent 78%);
  opacity:calc(.25 + var(--p) * .75);
}
.ground-pool{
  background:radial-gradient(closest-side, rgba(126,96,190,.14), rgba(60,64,74,.10) 46%, transparent 74%);
  opacity:calc(.35 + var(--p) * .65);
}

/* -- the plate ---------------------------------------------------------
   Sized in vw and held to the viewBox aspect so a percentage inside it is a
   percentage of the SVG, which is what lets the pins be placed in per-cent.
   The rail takes ~260px on the right and the copy box sits top-left, so the
   plate is nudged right of centre to keep New Zealand clear of the rail while
   Karachi tucks under the copy - the same composition as the reference. */
/* 47/46 is measured, not chosen. Shifting to 50/44 to close the ocean gap
   between Australia and the rail was tried and rendered worse: New Zealand
   ran into the rail column, Japan crowded the hairline, and the empty water
   simply moved to the left of Karachi. The gap on the right is the map's
   framing, not a mistake in it - an atlas has ocean, and the headline block
   bottom-left is what balances it. Leave this alone. */
.plate{
  position:absolute;left:47%;top:46%;
  /* Three caps: design size, the width left once the rail column is out of
     the way, and the tilted height on a short wide window. */
  /* Three caps: design size, the width left once the rail column is out of the
     way, and the tilted height on a short wide window. The vh one binds at
     every ordinary viewport, so it - not the 1360 - is the dial that sets how
     big the map reads.
     Was min(1360px, 100vw - 300px, 118vh). 118vh stood the plate taller than
     the window it sat in (1227px of plate in a 1080px viewport) so it bled off
     the top and bottom, and the map read over-zoomed. 94vh takes about 20% off
     at every size and lets the plate finish inside the window with room over
     it: at 1920x1080 it stands ~940px tall after the tilt, against 1179 before.
     The last 6% of that is headroom rather than size - at 100vh the north edge
     of China sat hard against the floating header bar on a 768-tall window,
     which read as the map being cropped by the chrome rather than framed.
     This is as small as it should go. Below ~90vh the stage starts showing
     more empty ground than map, which is the complaint this whole sequence of
     changes began with.
     Note this scales the whole plate, so the geographic framing is unchanged -
     the same countries, drawn smaller. The other dial is HOME_PAD in
     hero-map.js, which widens the CAMERA instead and so shows more ocean
     around the same eleven markets; it is clamped at GEO.w and has only ~9%
     of travel left. Reach for this one first. */
  width:min(1130px,calc(100vw - 300px),94vh);
  /* Kept in step with GEO.w/GEO.h by hero-map.js, which writes it inline at
     start-up; this value is only what renders before that runs. If the two
     disagree the SVG letterboxes inside the plate and every pin - placed as a
     percentage of the plate - drifts off its city. */
  aspect-ratio:1600 / 1541;
  transform:
    translate(-50%,-50%)
    translateY(var(--lift))
    rotateX(var(--tilt)) rotateZ(var(--spin))
    scale(var(--zoom));
  transform-style:preserve-3d;
}
.plate-svg{position:absolute;inset:0;width:100%;height:100%}
.plate-svg svg{display:block;width:100%;height:100%;overflow:visible;isolation:isolate}

/* -- the map ------------------------------------------------------------ */
.m-wall use{fill:var(--wall,#2A2D33);stroke:none}
.m-context use{fill:url(#v7ContextFill);stroke:#7A818C;stroke-width:.55;stroke-opacity:.5;vector-effect:non-scaling-stroke}
.m-region  use{fill:url(#v7RegionFill);stroke:#99A0AB;stroke-width:.6;stroke-opacity:.55;vector-effect:non-scaling-stroke}
.m-face    use{fill:url(#v7MemberFill);stroke:none}

/* -- the country IS the button ------------------------------------------
   A pin is a 9px dot on a plate the size of the window, and the shape under
   it is the thing the eye actually goes for. So the whole svg stops taking
   the pointer and the member faces alone take it back: without that the
   coastline stroke and the wall extrusions win the hit near an edge, and a
   click on Japan two pixels inside its coast does nothing at all.
   Non-member countries stay inert - there is nothing behind them to open. */
.plate-svg svg{pointer-events:none}
.m-face use{pointer-events:auto;cursor:pointer}
/* Hover lights the country a shade and the pin standing on it: the pin is
   what names the market you are about to open, so it has to answer too. */
/* Hover is a fill, not a filter - see v7MemberFillHot for why. */
.m-face use.is-hot{fill:url(#v7MemberFillHot)}
/* The dim fades at half a second, which is right for something that happens
   once, at the top of a flight, to the whole map. It is far too slow for a
   pointer: a country that takes half a second to acknowledge a hover reads as
   the page thinking about it. The faces get their own, quicker.
   Two speeds, and the asymmetry is deliberate: it lights immediately and
   releases a little more slowly, which is what stops a hand crossing the map
   from setting off a strobe. */
#plateSvgEl .m-face use{transition:fill .26s ease}
#plateSvgEl .m-face use.is-hot{transition:fill .1s ease}
/* Not while a country is the subject - one thing is lit at a time, and it is
   not whatever the pointer happens to be over. */
.is-focus .m-face use.is-hot{fill:url(#v7MemberFillFocus)}
.is-focus .m-face use.is-hot:not(.is-on){fill:#23262D}

.m-shadow{filter:url(#v7Soft);opacity:.5;transition:opacity .5s ease}
/* Frontal, the shadow is seen at its full length instead of foreshortened -
   the same ink covers half again as much sea. */
.is-focus .m-shadow{opacity:.3}
.m-shadow use{fill:#000}
.m-tex{pointer-events:none;mix-blend-mode:overlay}
.m-tex-context{opacity:.62}
.m-tex-region {opacity:.72}
.m-tex-member {opacity:.92}

.m-edge use{
  fill:none;
  /* Was #EBDDFF at 2.1. Nine percent off white on a #7C5CFF plate made the
     coastline the brightest value in the frame, so the eye landed on the
     outline instead of on the country - and paired with v7Glow below it read
     as eleven countries wearing a fog collar rather than eleven countries.
     A separation hairline is all this needs: the plates are light purple on a
     near-black sea and were never at risk of being lost in it. Kept as an
     alpha rather than a hex so it sits ON the sea it is drawn over instead of
     being a second, lighter light source. */
  stroke:rgba(255,255,255,.2);
  /* --rim is per country, set by rimWeights(): an archipelago is nearly all
     coastline, so an unweighted rim lights the whole island. */
  stroke-width:calc(1.1 * var(--rim,1));
  vector-effect:non-scaling-stroke;
  stroke-linejoin:round;
  stroke-dasharray:1;
  stroke-dashoffset:1;
  opacity:0;
}
/* No fade. The line used to come up on opacity .3s at the same time as it drew
   itself, and those two readings fight: for the first third of the draw the
   whole outline is arriving as a ghost while a third of it is also being
   stroked on, and the eye takes the fade — so a coastline that genuinely draws
   itself was being read as a country fading in. The dash does all the work now.
   Where there is no line yet there is nothing, not a faint one. */
.m-edge use.is-drawn{
  stroke-dashoffset:0;
  opacity:1;
  transition:stroke-dashoffset 1.15s cubic-bezier(.4,0,.2,1);
}

/* The moment it arrives. One flare per country, filled rather than stroked, so
   the land lifts rather than the outline — the coastline is already busy being
   drawn by the running light on the same frame. Short, and asymmetric: up in a
   fifth of its life and down over the rest, which is how light behaves and how
   a flash reads as an event rather than a pulse. */
.m-flash{pointer-events:none;mix-blend-mode:plus-lighter}
.m-flash use{fill:#FFF3DC;opacity:0}
.m-flash use.is-flash{animation:m-flare .78s cubic-bezier(.2,.7,.3,1) both}
@keyframes m-flare{
  0%  {opacity:0}
  16% {opacity:.34}
  100%{opacity:0}
}

/* -- the light that draws it ----------------------------------------------
   A short bright segment travelling the same outline at the same rate as the
   rim beneath it, so the rim reads as its wake rather than as a line growing
   from one end. Eleven <use>, one per member, and only alive for the 1.15s of
   their own lap.

   Deliberately unfiltered. v7Glow is two Gaussian blurs and a merge; the note
   on .is-lit below explains what putting that on an element whose
   stroke-dashoffset is animating did to the entrance. The head is bright
   enough on its own — it is white against a purple slab — and a crisp light is
   the right look anyway: a blurred one on a moving line is a smear.

   pathLength="1" is set on every member path (see the builder), so the dash
   numbers are fractions of each coastline rather than user units. 0.13 is the
   same proportion of the lap on Australia as on Singapore, which is what keeps
   eleven countries of wildly different sizes reading as one gesture. */
.m-run{fill:none;pointer-events:none}
.m-run use{
  fill:none;
  stroke:#FFFFFF;
  stroke-width:calc(2.6 * var(--rim,1));
  vector-effect:non-scaling-stroke;
  stroke-linecap:round;
  stroke-dasharray:.13 .87;
  stroke-dashoffset:1;
  opacity:0;
}
/* Two mechanisms, one per property, and that split is deliberate. The offset
   is a transition so it can carry the identical curve and duration as the rim
   underneath — the two must travel as one thing or the wake detaches from the
   light. The opacity is a keyframe animation because it needs three stops the
   transition cannot express: on almost immediately, hold, and off over the last
   fifth of the lap so the light leaves rather than stopping dead at the finish.
   Different properties, so they never fight. */
.m-run use.is-running{
  stroke-dashoffset:0;
  transition:stroke-dashoffset 1.15s cubic-bezier(.4,0,.2,1);
  animation:m-run-fade 1.15s linear both;
}
@keyframes m-run-fade{
  0%   {opacity:0}
  8%   {opacity:1}
  82%  {opacity:1}
  100% {opacity:0}
}
/* The glow is applied only once the line has finished drawing itself, and this
   is worth the extra class. v7Glow is two Gaussian blurs and a merge; put it on
   the element whose stroke-dashoffset is animating and the browser re-runs both
   blurs over the whole country on every frame of a 1.15s tween - nine of those
   overlapping was 12 long tasks of 55-120ms through the entrance, which is
   exactly what a dropped frame feels like. Drawn first, lit after: the filter
   renders once, and the line is crisp while it is moving, which is better
   anyway - a glow on a line that is still arriving reads as a smear. */
/* SUPERSEDED - see the long note on v7Glow in hero-map.js, which is now a
   record of how the halo was tuned rather than of what is applied. The filter
   is a stdDeviation-7 blur merged under the line; seven is wide enough to
   leave the coast, which is what put a bloom in the water between New Delhi
   and Karachi and turned the Indonesian islands into one soft mass. It is the
   only element in the region view without a hard edge, and the eye takes the
   softest edge in a frame as the frame's resolution. Off. v7Glow and
   v7GlowTight are both left in defs, unreferenced, because they are the
   entire control surface if the region ever needs presence back - restoring
   is re-adding the filter here, nothing else. */
.m-edge use.is-lit{filter:none}

/* -- pins ---------------------------------------------------------------
   Children of .plate so they cannot slide off the coast, counter-rotated to
   face the camera, lifted 18px so the label is never cut by the plate. */
/* The container is a plane the pins stand off; it must not catch the click meant for them. */
.pins{position:absolute;inset:0;transform-style:preserve-3d;transform:translateZ(18px);pointer-events:none}
.pins>.pin{pointer-events:auto}
.pin{
  position:absolute;z-index:1;
  /* The button IS the dot, and that is the whole point. It used to be a flex
     row - dot, gap, label - and translate(-50%,-50%) centred THAT row on the
     city, which left the dot half a label-width away from its own coordinate:
     measured, 42px for Singapore, 49px for Bangkok, 44px for Melbourne, always
     in the direction away from the label. On a country the size of Australia
     nobody notices. On Singapore, which is sub-pixel at this zoom and has no
     visible landmass under it, the dot sat in open water off the Malay
     peninsula and read as a mistake in the map.
     The label is taken out of flow below and hung off this box instead, so the
     box is 9x9 and the centring lands the dot exactly on the lat/lng. That was
     always the stated intent - see the --ly note further down, which fixed the
     same bug on the vertical axis and missed the horizontal one. */
  display:block;width:9px;height:9px;
  transform:translate(-50%,-50%)
            rotateZ(calc(var(--spin) * -1)) rotateX(calc(var(--tilt) * -1));
  transform-style:preserve-3d;
  padding:0;border:0;background:none;
  color:var(--stage-text);
  font:500 12px/1 "Libre Franklin",sans-serif;
  white-space:nowrap;
  cursor:pointer;
  opacity:0;
  transition:opacity .4s ease;
}
.pin.is-in{opacity:1}
.pin:hover,.pin.is-linked{z-index:2}
.pin.is-on{z-index:3}
/* Green, not purple. The dot was --purple sitting on a country filled with
   --purple: same hue, same family, so the one mark that says "an agency is
   HERE" dissolved into the shape it was marking. Green is the only hue on this
   map that belongs to no country, which is what makes it readable as a pin
   rather than as more terrain.

   The halo is dialled back from the purple one it replaces. Green is the more
   luminous of the two at equal alpha, and eleven of them across the region
   view should read as marks on a map, not as a string of lights. */
.pin-dot{
  /* display:block is load-bearing. This is a <span>, and it used to get its
     size for free by being a flex item of .pin - flex blockifies its children.
     .pin is display:block now, so without this the span falls back to inline,
     width/height are ignored, and the dot collapses to 0x0: all that survives
     is the box-shadow halo, which still paints and makes it look like a very
     dim dot rather than a missing one. */
  display:block;
  position:relative;
  width:10px;height:10px;border-radius:50%;
  /* Lavender at rest, NOT --live. Eleven #2FE39B dots with an 11px bloom made
     green the page's de-facto accent, and green is a status colour - up,
     online, healthy - which is a claim a network of agencies is not making.
     It also spent the only second hue in the palette on a marker rather than
     on the network or the action, and on a phone, where .pin-label is
     display:none, it left the hero as eleven anonymous green blooms.
     Lavender is already the accent; the dots join it and stop competing.
     The 1.5px ink ring is load-bearing: #D3B9FF on the mid member fill
     (#8257D6) is 2.95:1, which is fine for a 9px disc with a hard edge under
     it and not fine without one. The bloom is a third of the green one's
     alpha - at rest these are marks on a map, and the ONE that is selected is
     the light. */
  background:var(--lavender);
  /* Weighted UP, not down, from the green it replaced. Green was too loud for
     what it was saying, but the dot is now the ONLY mark on the map at rest -
     the pills are gone until asked for - so it has to carry the whole "there
     are eleven of us and here is where" on its own. First pass used a 1.5px
     ink ring and a .28 bloom and the dots read as grey specks: the hard dark
     ring desaturates a 9px disc from a metre away. 1px ring, wider soft ring,
     brighter bloom. */
  box-shadow:
    0 0 0 1px rgb(6 6 10 / .5),
    0 0 0 4px rgb(211 185 255 / .16),
    0 0 14px 3px rgb(211 185 255 / .45);
  transition:scale .16s ease, background .16s ease, box-shadow .16s ease;
}
.pin-dot::after{
  content:"";position:absolute;inset:-5px;border-radius:50%;
  border:1px solid rgb(211 185 255 / .55);
  opacity:0;transition:opacity .16s ease;
}
.pin:hover .pin-dot::after,.pin:focus-visible .pin-dot::after,
.pin.is-linked .pin-dot::after{opacity:1}
/* Selected: the dot leaves the palette. White-on-purple was legible in
   isolation but vanished once the country beneath it lit up purple too. */
/* Inverted, because every dot is green now and the selected one still has to
   be the loudest thing on the plate. It takes a white core inside a green
   halo - the same two colours, swapped - so selection reads as the mark
   lighting up from within rather than as one more green dot among ten.
   The pill going solid green underneath it does the rest. */
/* Green now belongs to selection ALONE, which is what makes it worth having.
   The white core stays: on the focused country the plate under this dot goes
   light purple, and a solid green disc on it was legible but flat - a white
   centre inside a green ring reads as the mark lighting up from within, and
   it is the only marker on the map wearing either colour. */
.pin.is-on .pin-dot{
  scale:1.45;background:var(--white);
  box-shadow:0 0 0 3.5px var(--live), 0 0 22px 5px rgb(47 227 155 / .6);
}
/* The halo ring follows it, or the dot sits inside a lavender circle. */
.pin.is-on .pin-dot::after{border-color:rgb(47 227 155 / .6)}
/* --ly is what keeps eleven labels legible without lying about where eleven
   cities are. It used to sit on .pin, which moved the DOT off its coordinate
   too - Karachi 13px north of Karachi. Now the dot is exactly on its lat/lng
   and only the chip beside it steps aside, which is what an atlas does. */
.pin-label{
  /* Hung off the dot rather than sitting beside it in flow - see the note on
     .pin. The dot is 9px, so 100% + 7 keeps the old 7px gap exactly.
     translateY(-50%) is the vertical centring the flex row used to do; --ly is
     relaxLabels() stepping the chip aside, and the two compose. */
  position:absolute;top:50%;left:calc(100% + 7px);
  transform:translateY(-50%) translateY(var(--ly,0px));
  display:inline-flex;align-items:center;gap:6px;
  padding:3px 8px;border-radius:999px;
  background:rgba(18,20,24,.74);
  border:1px solid rgba(200,208,220,.2);
  -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
  letter-spacing:.02em;
  /* At rest the pill is NOT drawn. The rail on the right already names all
     eleven cities, so the map was naming them a second time 300px away in the
     highest-contrast chip treatment on the plate - and doing it badly, because
     eleven chips at this zoom collide (Tokyo/Shanghai, Melbourne/Auckland,
     Bangkok/Manila) however hard relaxLabels() works. The map's job is shape
     and spread; the rail's job is names. One system each.

     opacity, NOT display:none or visibility - three reasons, all load-bearing:
     relaxLabels() measures these boxes to solve collisions and needs them laid
     out; the city name has to stay in the accessibility tree; and a hidden box
     cannot transition. Under 900px the rule at the breakpoint below DOES use
     display:none, because there the rail is a horizontal scroller under the
     map and the pill has nothing to fight with. */
  opacity:0;
  transition:opacity .18s ease,border-color .16s ease,background .16s ease;
}
/* The four states that earn a name: the pointer is on it, the keyboard is on
   it, the rail row for it is hovered (.is-linked), or it is the selection. */
.pin:hover .pin-label,
.pin:focus-visible .pin-label,
.pin.is-linked .pin-label,
.pin.is-on .pin-label{opacity:1}
/* A counter, not a numeral. Set as a lavender word beside the city it read as
   part of the name - "Bangkok 2" - so it becomes a filled chip instead: its own
   shape, its own ground, and tabular figures so it never reflows the label. */
/* Labels that would otherwise run off the plate or collide with the rail sit
   on the other side of their dot. This replaces flex-direction:row-reverse,
   which no longer means anything now the label is absolutely positioned. */
.pin.lbl-left .pin-label{left:auto;right:calc(100% + 7px)}
.pin-label i{
  display:grid;place-items:center;
  min-width:15px;height:15px;padding:0 4px;
  border-radius:999px;
  background:rgb(211 185 255 / .22);
  font-style:normal;font-size:9.5px;font-weight:700;line-height:1;
  font-variant-numeric:tabular-nums;letter-spacing:0;
  color:var(--lavender);
}
.pin:hover .pin-label,.pin:focus-visible .pin-label,.pin.is-linked .pin-label{border-color:var(--purple);color:#fff}
/* Selected: the whole marker goes green, dot and pill as one object. The text
   inverts to --live-ink because white on this green is 1.7:1; dark ink on it is
   14:1, so the label is the most legible on the map rather than the least.
   No blur behind it - a solid fill has nothing to see through, and the glass
   treatment on the unselected labels is exactly what this one is leaving. */
.pin.is-on .pin-label{
  background:var(--live);
  border-color:var(--live);
  color:var(--live-ink);
  /* A point larger than the resting chips, and the padding with it. The pin
     labels live inside the plate.s 3D transform, so they are rasterised into
     a texture and mapped - at 1x that costs 12px type its definition, and the
     selected pill was the one place it showed, because dark ink on bright
     green has no glow to hide behind. Measured, 13px carries 43% more ink
     pixels than 12px here. At 2x the same label is already crisp, so this is
     buying back what the low-density raster takes away.
     Only the selected state: it is the one the eye is on, it is the only pin
     visible in focus mode, and so it can grow without crowding a neighbour
     or disturbing the label solver. */
  font-size:13px;
  font-weight:600;
  padding:4px 10px;
  -webkit-backdrop-filter:none;backdrop-filter:none;
  box-shadow:0 2px 18px rgb(47 227 155 / .38);
}
/* The counter inverts with the label: a white chip on green would glare, so it
   punches through in the label's own ink instead. */
.pin.is-on .pin-label i{background:rgb(4 20 13 / .16);color:var(--live-ink)}
.pin:focus-visible{outline:none}
.pin:focus-visible .pin-label{outline:var(--focus-w) solid var(--lavender);outline-offset:var(--focus-off)}

/* -- scrim and copy box ------------------------------------------------
   The plate runs under the headline, so the copy gets a panel of its own to
   sit on: a gradient fall-off from the left edge, no panel. */
.hero-globe .stage-scrim{
  z-index:2;
  background:linear-gradient(100deg, rgb(6 6 10 / .9) 0%, rgb(6 6 10 / .6) 18%, rgb(6 6 10 / .15) 32%, transparent 44%);
}
.hero-globe .hero-copy{top:clamp(72px,9vh,112px)}
.hero-globe .hero-copy .wrap{
  width:max-content;max-width:min(440px,calc(100vw - var(--s6) * 2));
  margin-left:var(--stage-gutter);
  padding:0;
}
/* Superseded by the .hero-globe h1 rule further down this file, which wins on
   source order at equal specificity. Kept in step with it so the two do not
   disagree about the slope if the order ever changes. */
.hero-globe h1{max-width:12ch;font-size:clamp(34px,4.4vw,60px)}

/* -- selected market word over the plate -------------------------------- */
.hero-globe .hero-word{
  top:auto;bottom:calc(var(--s7) + 56px);left:var(--stage-gutter);right:auto;
  transform:none;padding:0;text-align:left;
  max-width:min(46vw,15ch);
  font-size:clamp(30px,4.4vw,60px);
  letter-spacing:-.02em;text-shadow:none;z-index:4;
}
.hero-globe .hero-word.is-out{transform:translateY(-6px)}

/* -- rail ----------------------------------------------------------------
   v2's rail as it was: a hairline with stops on it, floating over the stage.
   The labels get a text-shadow instead of a column behind them. */
.hero-globe .market-rail{width:216px;padding-right:var(--s4)}
.hero-globe .rail-row{text-shadow:0 1px 6px rgb(6 6 10 / .9)}

/* -- narrow screens ------------------------------------------------------- */
@media (max-width:900px){
  :root{--tilt:34deg}
  .map-stage{order:2;position:relative;inset:auto;height:min(64svh,520px);perspective:5000px;perspective-origin:50% 42%}
  /* 130vw at left:42% put the plate's west edge off the screen and took
     Karachi and Mumbai with it - two of the eleven members were not on the map
     on a phone at all, which is a worse failure than the map being small.
     118vw at 47% fits the whole Karachi-to-Auckland diagonal inside 390px.
     Check BOTH ends after any change here: the east end is New Zealand and it
     is as close to its edge as Karachi is to the other. */
  .plate{width:118vw;left:47%;top:40%}
  .pin-label{display:none}
  .pin.is-on .pin-label{display:block}
  .hero-globe .hero-copy .wrap{width:auto;max-width:none;margin:0 var(--s6)}
  .hero-globe .market-rail{width:auto}
  .hero-globe .rail-row{text-shadow:none}
  .hero-globe .hero-word{position:static;padding:0 var(--s6);max-width:none;font-size:clamp(26px,7vw,44px);margin-top:var(--s5)}
}

@media (prefers-reduced-motion:reduce){
  .m-flash use{animation:none;opacity:0}
  .m-edge use{stroke-dashoffset:0;opacity:1;transition:none}
  .pin{opacity:1;transition:none}
  .plate,.ground{transition:none}
}


/* ==========================================================================
   Map, copy and rail
   ==========================================================================
   The plate carries the banner; the copy sits bottom-left and the markets
   rail runs down the right. The scroll cue and the big market word are the
   two pieces that go: the cue duplicates the directory below, and the word
   repeated the market the rail already lights up. */
.hero-globe .hero-cue,
.hero-globe .hero-word{display:none}

/* -- tour the network ---------------------------------------------------
   A quiet third thing under the headline. It is not competing with "Brief the
   network" in the header - that is the conversion, this is a demonstration -
   so it is a line of type with a glyph, not a pill. */
/* Promoted from a caption to a control. This is the hero's only action, and
   it was 12px letterspaced caps at 62% white with a ghost glyph, sitting under
   a 60px headline and losing outright to "Get in touch" in the header - a
   filled white pill, and the highest-contrast object on the page. The nav was
   beating the hero at the hero's own job.

   Solid --live, no border. Green came off the eleven resting pins because
   eleven of it made green the page's accent by default while saying nothing;
   ONE of it, on the one thing in the hero a visitor can press, is the use
   that colour was worth keeping for. It is now the only green in the fold,
   which is what makes it read as an action rather than as decoration.

   No border anywhere on this control - the fill IS the shape. A ring plus a
   ground draws the same pill twice and reads as a hairline outline rather
   than a button; every state below moves the background, never a border.

   #04140D on #2FE39B is 14:1. White on the same green is 1.7:1, so the ink is
   dark here for the same reason it is dark on the selected pin's pill - this
   is the one bright surface on a dark page and it inverts. */
.hero-tour{
  display:inline-flex;align-items:center;gap:10px;
  margin-top:var(--s5,22px);
  padding:11px 20px 11px 14px;
  border:0;
  border-radius:999px;
  background:var(--lavender);
  cursor:pointer;
  color:var(--plum);
  font:600 12px/1 "Libre Franklin",sans-serif;
  letter-spacing:.1em;text-transform:uppercase;
  transition:background .18s ease,color .18s ease;
}
/* The fill lifts. An explicit value rather than a brightness filter, which
   would take the glyph disc and the ink up with it. */
.hero-tour:hover,.hero-tour:focus-visible{background:#E4D3FF}
/* Running: it inverts. A solid green resting state has nowhere brighter to go
   to say "playing", so the pill hands the fill back and keeps the colour in
   the ink - and the tour's green is out on the map at that moment anyway,
   travelling from pin to pin, which is where the eye should be. */
.hero-tour.is-on{background:rgb(211 185 255 / .16);color:var(--lavender)}
/* Filled, not ringed. A 24px circle outlined in white at .28 over a dark hero
   is a ring with a triangle loose inside it; the same value as a ground makes
   one object. The disc is a fixed 24px, so nothing moves when the border goes. */
/* Punches through the fill rather than sitting on it. White at .16 was a disc
   on a dark ground; on a bright green ground the same white is invisible, so
   the disc is the label's own ink at low alpha - the same move the selected
   pin's counter chip makes. */
.hero-tour-glyph{
  display:grid;place-items:center;
  width:24px;height:24px;border-radius:50%;
  border:0;background:rgb(4 20 13 / .16);
  transition:background .18s ease;
}
.hero-tour-glyph::before{
  content:"";
  border-style:solid;
  border-width:5px 0 5px 8px;                       /* a play triangle */
  border-color:transparent transparent transparent currentColor;
  margin-left:2px;
  transition:none;
}
.hero-tour:hover .hero-tour-glyph{background:rgb(43 20 82 / .26)}
/* Running: the triangle becomes a stop square. Same element, so there is one
   control rather than a pair that can disagree about the state. */
.hero-tour.is-on .hero-tour-glyph{background:rgb(211 185 255 / .22)}
.hero-tour.is-on .hero-tour-glyph::before{
  border:0;width:8px;height:8px;margin-left:0;border-radius:1px;
  background:var(--live,#2FE39B);
}
/* Was .hero-tour.is-on{color:#fff}. It sat after the rule above and quietly
   won it, so the running pill came out white-on-dim-green. The ink stays
   --live: the running state is the one that should look like the colour, and
   #2FE39B on this plate is ~9:1. */

/* A tour spends its whole length in focus mode, where the hero copy fades out
   - which would take the button that stops it with it. The copy block stays,
   and the words inside it go instead, so the only thing left on that corner of
   the screen is the control the visitor needs. */
.hero-globe.is-focus.is-touring .hero-copy{opacity:1;transform:none;pointer-events:auto}
/* !important, and deliberately. immersive-hero.css animates these two with
   `animation: stage-in-left ... both`, and a filled animation sits in a higher
   cascade origin than any normal declaration - so the keyframe's opacity:1 wins
   against anything written here. Important is the only thing that outranks it,
   and this is the case the flag exists for rather than a shortcut around
   specificity. */
.hero-globe.is-focus.is-touring .hero-copy .eyebrow,
.hero-globe.is-focus.is-touring .hero-copy h1{opacity:0!important;transition:opacity .3s ease}

/* -- the idle nudge -----------------------------------------------------
   One ring out of three pins, spread across the region, once. It answers the
   only real objection to a map you have to click - that nobody knows they can
   - and then it stops. A pulse on a loop stops being an invitation after the
   second pass and becomes something to wait out. */
@keyframes pinPing{
  0%  {opacity:.9;inset:-4px}
  70% {opacity:0; inset:-19px}
  100%{opacity:0; inset:-19px}
}
.pin.is-nudge .pin-dot{scale:1.2}
.pin.is-nudge .pin-dot::after{
  border-color:rgb(211 185 255 / .8);
  animation:pinPing .9s cubic-bezier(.2,.7,.3,1);
}
@media (prefers-reduced-motion:reduce){
  .pin.is-nudge .pin-dot{scale:1}
  .pin.is-nudge .pin-dot::after{animation:none}
}

/* -- the rail, right ---------------------------------------------------
   A hairline with stops on it, floating over the plate - no column behind
   it. The labels take a text-shadow instead, which is enough over open
   water and does not put a dark band through New Zealand. */
.hero-globe .market-rail{
  right:var(--stage-gutter);
  width:200px;padding-right:0;
  background:none;
}
.hero-globe .rail-row{text-shadow:0 1px 8px rgb(6 6 10 / .95)}
/* The agency name is the only thing in this column a visitor cannot read off
   the map, and it was the quieter half of every row: 11px at opacity .7 under
   a 13px 600 city. Now that the map does not print city names at rest, the
   rail is the only place either name appears, so the row carries two pieces of
   information rather than one and a footnote.

   Parity by LUMINANCE, not by hue. The first pass put the agency in lavender
   (#B9A6E8) to separate the two lines by colour rather than by one being
   faded out. It separated them and it also put eleven lines of purple down the
   right edge of a page whose only accent is purple - the column read as an
   accent block instead of as a list, and the lavender was no longer saying
   anything, which is the same complaint that took green off the pins.

   Both lines are neutral now and the step between them is brightness:
   --stage-text on the city, --stage-dim on the agency. Both are existing
   tokens doing the job they were defined for (17.6:1 and 8.1:1 on the stage
   ink), so nothing new enters the system. The agency keeps 12px at full
   opacity - the point of the original change was that it is real content, not
   a footnote, and that survives the colour going grey.

   The city stays the anchor either way: it is what the dot on the map
   corresponds to. Giving the agency the larger size was tried and is wrong -
   it breaks the correspondence between this column and the map beside it. */
.hero-globe .rail-name{color:var(--stage-text)}
.hero-globe .rail-meta{
  font-size:12px;line-height:16px;
  opacity:1;color:var(--stage-dim);
}
.hero-globe .rail-row:hover .rail-meta,
.hero-globe .rail-row:focus-visible .rail-meta{color:var(--stage-text)}


@media (max-width:900px){
  .hero-globe .market-rail{width:auto;right:auto}
  .hero-globe .rail-row{text-shadow:none}
}

/* The copy, bottom-left: the one corner the region leaves empty. Compact, no
   panel, and a soft fall-off from the corner so the lede is readable over
   the ground grid without darkening any coastline. */
.hero-globe .hero-copy{
  top:auto;bottom:clamp(40px,7vh,72px);left:0;right:auto;z-index:5;
}
/* 440px was the measure for a 51.8px headline. At the paired size the same
   three lines need 509px or "one Asia-Pacific." breaks at its own hyphen and
   the headline runs to four. Sized off the type rather than left behind by it;
   the copy still sits in the corner the region leaves empty. */
.hero-globe .hero-copy .wrap{
  width:max-content;max-width:min(520px,calc(100vw - var(--s6) * 2));
  margin-left:var(--stage-gutter);padding:0;
}
/* Type. Three sizes, three jobs, and the gaps between them do the grouping:
   the eyebrow sits close to the headline it labels, the lede stands off it.
   Sizes are set against the map rather than against a text column - this is
   the only copy on a full-bleed image, so it can afford to be large. */
/* No colour here. immersive-hero.css already sets .hero-globe .eyebrow to
   --stage-dim, and this rule was overriding that token with a raw #B9A6E8 on
   a higher specificity - so the one place the eyebrow's colour was declared
   was not the place the system declares it.

   The lavender went for the same reason it left the rail: it is the page's
   only accent, and spending it on a label that names the page reduces what is
   left for the things a reader can act on. --stage-dim is 8.1:1 against the
   stage ink, better than the 6.4:1 this was, and it puts the eyebrow in the
   same neutral secondary rank as the rail's agency line - which is the rank
   it holds. Size, weight and tracking stay; only the colour declaration is
   handed back to the token. */
.hero-globe .hero-copy .eyebrow{
  margin-bottom:var(--s4);
  font-size:11px;line-height:16px;font-weight:600;
  letter-spacing:.18em;
}
/* Paired with .pf-name in css/profile-hero.css. The two headlines are the same
   rank of element on the two pages a visitor moves between, so they are sized
   off one slope: the profile's name runs 1.8x this at every width the two
   share. It used to be 3.6vw against the profile's 8.8vw, which read as a
   deliberate hierarchy at 1440 (2.4x) and as near-equals on a phone (1.2x) —
   the same two pages, two different arguments about which one is bigger.
   Change one slope and change the other. */
.hero-globe h1{
  /* 14ch, not the 13ch this held at weight 400. The measure is in ch so it
     tracks the size, but not the weight: at 600 the same characters set about
     5% wider, and "one Asia-Pacific." tipped over the old measure and broke at
     its own hyphen into a fourth line. */
  max-width:14ch;
  font-size:clamp(34px,4.4vw,60px);
  line-height:1.04;
  font-weight:600;                   /* was 400 — the lightest display type on
                                        either page, and the odd one out */
  letter-spacing:-.025em;            /* large type sets loose; pull it back */
  text-shadow:0 2px 28px rgb(10 12 16 / .85);
}
/* Same three words, same corner, on both pages. immersive-hero.css sets .06em;
   the profile's cue has always been .12em and it is the better of the two at
   this size. Overridden here rather than there — that file is shared. */
.hero-globe .hero-cue{letter-spacing:.12em}
.hero-globe .stage-scrim{
  display:block;z-index:2;
  background:radial-gradient(60% 55% at 12% 100%, rgb(6 6 10 / .82) 0%, rgb(6 6 10 / .4) 45%, transparent 75%);
}
.has-selection .hero-copy{opacity:1;transform:none;pointer-events:auto}
@media (max-width:900px){
  .hero-globe .hero-copy{position:static}
  .hero-globe .hero-copy .wrap{width:auto;max-width:none;margin:0 var(--s6)}
  .hero-globe .stage-scrim{display:none}
}

/* v2's stage dressing - nebula washes, lens flare, grain and vignette - was
   designed for a sphere in space. On a plate it darkens the ground v7 lit;
   the section takes v7's ink and the overlays are switched off. */
.hero-globe{background:#14161A}
.hero-globe::before,.hero-globe::after,.map-stage::after{display:none}
.hero-globe .stage-scrim{
  background:
    linear-gradient(20deg, rgba(20,22,26,.93) 0%, rgba(20,22,26,.62) 30%, transparent 58%),
    linear-gradient(100deg, rgba(20,22,26,.7) 0%, transparent 34%);
}


/* ==========================================================================
   The entrance
   ==========================================================================
   .is-intro is the starting state and is on the element before the first paint;
   .is-in is added a frame later and every transition below runs against it.
   .is-intro is dropped when the sequence finishes, so nothing here is left
   holding a transition on an element the rest of the page has to animate.

   The delays are the running order, and they are written here rather than in JS
   because that is where the rest of this file's timing lives - the draw-on, the
   focus fades - and one place to read the sequence is worth more than the
   convenience of stepping it from the animation loop.
   ========================================================================== */
.hero-globe.is-intro .ground,
.hero-globe.is-intro .plate-svg{opacity:0}
.hero-globe.is-intro.is-in .ground{opacity:1;transition:opacity .8s ease}
/* One fade, on the DIV that wraps the SVG - not three on the tier groups
   inside it.
   The tier-by-tier version read better and cost far too much. A trace of the
   entrance showed 2.4 seconds of RasterTask: this map is expensive to paint -
   two thousand <use>, nine blurred coastline filters, a Gaussian shadow and
   three `overlay`-blended relief images - and animating anything INSIDE it
   forces the whole picture to be re-rastered every frame. Fading the wrapper
   instead lets it be rastered once and blended by the compositor, which is
   what will-change:opacity here promises the browser it can do.
   Same for the plate: promoted, the camera move is a matrix on a finished
   texture rather than a repaint. Both hints are scoped to .is-intro, so the
   memory goes back the moment the entrance is over. */
.hero-globe.is-intro .plate-svg{will-change:opacity}
.hero-globe.is-intro .plate{will-change:transform}
.hero-globe.is-intro.is-in .plate-svg{opacity:1;transition:opacity .7s ease}

/* The copy is about the map, so it cannot arrive before it. */
/* The eyebrow and the h1 are NOT here. immersive-hero.css already animates both with
   its own stage-in-left, and a filled animation outranks anything this file
   says about their opacity - so rules for them would look like they were
   driving the entrance while doing nothing at all. Their timing is v2's. */
.hero-globe.is-intro .hero-tour,
.hero-globe.is-intro .hero-cue{opacity:0;transform:translateY(16px)}
.hero-globe.is-intro.is-in .hero-tour,
.hero-globe.is-intro.is-in .hero-cue{
  opacity:1;transform:none;
  transition:opacity .7s ease,transform .7s cubic-bezier(.22,.68,.28,1);
}
.hero-globe.is-intro.is-in .hero-tour        {transition-delay:.62s}
.hero-globe.is-intro.is-in .hero-cue         {transition-delay:.95s}

/* The rail deals itself out top to bottom - eleven markets counted off rather
   than eleven markets asserted. --i is written on each row by buildRail(). */
.hero-globe.is-intro .rail-row{opacity:0;transform:translateX(14px)}
.hero-globe.is-intro.is-in .rail-row{
  opacity:1;transform:none;
  transition:opacity .5s ease,transform .5s cubic-bezier(.22,.68,.28,1);
  transition-delay:calc(.52s + var(--i,0) * .045s);
}

@media (prefers-reduced-motion:reduce){
  .hero-globe.is-intro .ground,.hero-globe.is-intro .plate-svg,
  .hero-globe.is-intro .rail-row,
  .hero-globe.is-intro .hero-copy .eyebrow,.hero-globe.is-intro .hero-copy h1,
  .hero-globe.is-intro .hero-cue{opacity:1;transform:none}
  .hero-globe.is-intro.is-in .ground,.hero-globe.is-intro.is-in .plate-svg,
  .hero-globe.is-intro.is-in .rail-row,.hero-globe.is-intro.is-in .hero-copy .eyebrow,
  .hero-globe.is-intro.is-in .hero-copy h1,.hero-globe.is-intro.is-in .hero-cue{transition:none}
}


/* ==========================================================================
   Focus mode
   ==========================================================================
   Choosing a market flies the map into that member's country and splits the
   stage: country on the left, who is there on the right. The zoom itself is
   the SVG's viewBox, animated in JS - see the header of hero-map.js - so
   everything here is only the dressing around it.
   ========================================================================== */

/* Everything that is not the focused country steps back. Opacity on the
   individual <use> rather than on the layer, because the relief image is a
   sibling of the faces and dimming the layer would dim the terrain of the
   country we just flew to. */
/* Dimming is a fill change, not an opacity change, and the reason is the
   extrusion: a wall is eighteen stacked slices of the same country, so at
   opacity .16 each they composite back to 1-(1-.16)^18 = 96% and the country
   we meant to send to the back comes forward instead. Faces, edges and the
   shadow are single elements, so those can use opacity. */
#plateSvgEl use{transition:fill .5s ease,opacity .5s ease}
/* Stated as "everything the flight did not land on", rather than as a class
   written onto each of those elements. The old form toggled .is-off across
   every <use> carrying a country — 2,113 of them, 1,800 being wall slices — at
   the apex of the flight, which is a full style pass over the map landing in
   the middle of the move it is supposed to be invisible during. The class now
   goes on the focused country alone, about twenty-five elements, and the rest
   follow from :not(). Same picture, two orders of magnitude fewer writes, and
   the pass happens at the same moment it always did. */
.is-focus .m-face    use:not(.is-on){fill:#23262D}
.is-focus .m-context use:not(.is-on),
.is-focus .m-region  use:not(.is-on){fill:#1B1E23;stroke-opacity:.12}
.is-focus .m-wall    use:not(.is-on){fill:#15171C}
.is-focus .m-edge    use:not(.is-on){opacity:.05}
.is-focus .m-shadow  use:not(.is-on){opacity:.35}

/* -- while the camera is moving -------------------------------------------
   Every frame of a flight rewrites the SVG's viewBox, so every frame re-renders
   the map from vectors: 1,800 wall slices, the country faces, the coastlines
   and a relief image resampled to a new scale. A trace of one flight is almost
   entirely RasterTask — 13ms of Paint against several seconds of raster across
   the raster threads.

   None of that work can be skipped without the map disappearing, but a good
   deal of its cost is quality that only matters when the picture is still.
   Antialiasing 1,800 stacked silhouettes and resampling the relief smoothly
   are both bought back here for the few hundred milliseconds the camera is in
   motion, and both return the instant it stops. What it costs is slightly
   harder edges on a map that is travelling too fast to inspect them. */
.is-flying .m-walls,
.is-flying .m-walls *{shape-rendering:optimizeSpeed}
.is-flying .m-terrain image,
.is-flying image{image-rendering:optimizeSpeed}

/* The copy has said its piece; the country and the panel are the subject now. */
.is-focus .hero-copy{
  opacity:0;transform:translateY(-12px);pointer-events:none;
  transition:opacity .35s ease,transform .35s ease;
}
/* The rail is where the panel is going. It hands over rather than stacking,
   and the panel carries prev/next so the network is still walkable. */
.is-focus .market-rail{
  opacity:0;pointer-events:none;
  transition:opacity .3s ease;
}
/* The ground reads harder once the map is close: at this zoom the grid is the
   only thing saying the country is standing on a surface. */
.is-focus .ground-grid{opacity:calc(.55 + var(--p) * .45)}
.is-focus .ground-pool{opacity:calc(.6 + var(--p) * .4)}
/* The scrim paints OVER the plate, so anything it covers it dims. The first
   cut put a radial wash at 26%/52% - which is exactly where a focused country
   is framed - and took the Philippines to half the brightness of the same
   country on the whole-region map. It now darkens only the right, where the
   panel needs a ground, and leaves the left half to the map. */
.is-focus .stage-scrim{
  background:linear-gradient(270deg,
    rgb(10 12 16 / .8) 0%, rgb(10 12 16 / .34) 22%, transparent 44%);
}

/* -- the panel ---------------------------------------------------------- */
/* Two rhythms on the right: geography as free type, agency as a card. The
   wrapper itself is invisible - the header sits on the stage the way the hero
   copy does, and only the agency list is boxed. Editorial, not modal. */
.focus-panel{
  position:absolute;z-index:8;
  right:var(--stage-gutter);
  top:50%;translate:0 -50%;
  width:min(420px,34vw);
  max-height:min(82vh,760px);
  overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:thin;scrollbar-color:#3A3A46 transparent;
  padding:0;
  opacity:0;transform:translateX(18px);
  /* .45/.5 was over half a second of fade on top of a flight it was already
     waiting for. The panel now starts before the camera lands, so its own
     arrival has to be quick enough not to give the time back. */
  transition:opacity .3s ease,transform .34s cubic-bezier(.22,.68,.28,1);
}
.focus-panel.is-open{opacity:1;transform:none}
/* The dip between two markets. Quicker out than in - a panel should clear
   itself briskly and arrive at its own pace. */
.focus-panel.is-open.is-swap{
  opacity:0;transform:translateX(10px);
  transition:opacity .2s ease,transform .2s ease;
}
.focus-panel::-webkit-scrollbar{width:6px}
.focus-panel::-webkit-scrollbar-thumb{background:#3A3A46;border-radius:3px}

/* The rail dot is the same "you are here", so it takes the same colour - one
   signal, two places, rather than a green pin and a purple row disagreeing
   about which market is open. Overrides immersive-hero.css, which loads before this. */
.market-rail .rail-row.is-on .rail-dot{background:var(--live)}

.fp-close{
  position:absolute;top:0;right:0;
  display:grid;place-items:center;
  width:30px;height:30px;padding:0;
  border:0;border-radius:50%;
  background:rgb(255 255 255 / .06);
  color:var(--stage-dim);cursor:pointer;
  transition:background .16s ease,color .16s ease;
}
.fp-close svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.6}
.fp-close:hover{background:var(--purple);color:var(--white)}
.fp-close:focus-visible{outline:var(--focus-w) solid var(--lavender);outline-offset:var(--focus-off)}

/* The 40px on the right is the close button's seat - the dateline ends before
   it rather than running under it. */
.fp-head{padding:var(--s1) 40px var(--s5) 0}

/* The dateline. Two peripheral facts on one baseline with a rule under them,
   so the city name below has nothing between it and the card. */
.fp-dateline{
  display:flex;align-items:baseline;justify-content:space-between;
  gap:var(--s3);
  padding-bottom:var(--s3);
  border-bottom:1px solid rgb(255 255 255 / .14);
}
.fp-eyebrow{
  margin:0;
  font-size:11px;font-weight:600;letter-spacing:.18em;text-transform:uppercase;
  color:#B9A6E8;
}
/* Never wraps: the country name gives way first, because the hour is four
   glyphs and the country is not. The old reasoning here was that
   "AMIN WORLDWIDE - NEW ZEALAND" had slack in it; the brand half is gone, so
   the longest string this row can now carry is NEW ZEALAND or PHILIPPINES at
   eleven glyphs. Re-measured at the narrowest the panel gets, both still sit on
   one line beside the clock with room to spare. */
.fp-clock{
  flex:none;margin:0;
  font-size:10px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:var(--stage-dim);white-space:nowrap;
  text-shadow:0 1px 12px rgb(10 12 16 / .9);
}
.fp-clock b{
  font-family:var(--mono);font-size:13px;line-height:1;letter-spacing:.02em;
  font-weight:600;color:var(--white);
}
.fp-city{
  margin:var(--s4) 0 0;
  font-size:clamp(34px,3.2vw,48px);font-weight:400;line-height:1.02;
  letter-spacing:-.025em;color:var(--white);
  text-shadow:0 2px 28px rgb(10 12 16 / .85);
}

/* The card. */
.fp-list{
  display:flex;flex-direction:column;gap:var(--s5);
  padding:var(--s5) var(--s6);
  /* Lighter than the stage it sits on, which it was not. Measured against the
     ground behind it the old face came out at 19.9 to the stage's 28.3 - a
     card DARKER than its surroundings reads as a hole cut in the page, and it
     left the border as the only thing describing the shape. That is why the
     hairline could not be softened: remove it and the corners dissolved.
     Raised now, so the surface separates itself and the line is free to. */
  background:linear-gradient(180deg, rgb(32 35 43 / .95), rgb(24 27 33 / .95));
  -webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);
  /* Tinted, not neutral. Down from .07 and no longer holding the card
     together - it only keeps the curve crisp where the face and the stage
     pass close in value, so it can afford to be a colour rather than a line.
     This is --lavender at 7%, which lands near white at 4% for weight: the
     edge picks up the palette the panel is already lit with instead of
     reading as a grey rule laid over it. */
  border:1px solid rgb(211 185 255 / .07);   /* var(--lavender) */
  border-radius:var(--r-lg);
  /* Negative spread so the lift stays under the card instead of pooling on the
     stage below it - that pool was reading as a second surface behind the
     prev/next row, which is meant to sit on bare stage. */
  box-shadow:0 24px 70px -28px rgb(0 0 0 / .6);
}
/* 56px of saturated brand mark was the loudest thing on a panel whose subject
   is the city. 48 keeps it identifiable and lets the name lead. */
.fp-agency .tile-md{width:48px;height:48px;font-size:16px;flex:none}
/* Sits on the card because it counts what is on the card. Pulled tight to the
   first agency - the list's own gap is set for agency-to-agency, and a caption
   should read as attached to what it captions. */
.fp-count{
  margin:0 0 calc(var(--s5) * -1 + var(--s2));
  font-size:10px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:var(--stage-dim);opacity:.75;
}
.fp-agency + .fp-agency{padding-top:var(--s6);border-top:1px solid rgb(255 255 255 / .08)}

/* Three groups per agency, and the spacing says so: 4px inside the identity
   block, 16px to the blurb, 12px to the services, 20px to the actions. Even
   gaps throughout were what made this read as six loose lines. */
.fp-id{display:flex;align-items:center;gap:var(--s3)}
.fp-name{min-width:0}
.fp-name h3{
  margin:0;
  font-size:17px;font-weight:600;line-height:1.2;letter-spacing:-.01em;
  color:var(--white);
}
/* A credit line, not a sentence: small, tracked and uppercase so it sits
   clearly under the name instead of competing with the blurb below. */
.fp-facts{
  margin:4px 0 0;
  font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  color:var(--stage-dim);
}

/* The one piece of real writing on the card, so it gets full-strength text and
   no opacity - dimmed body copy on a dark card just reads as muddy. A point
   larger than the directory's blurb because here it is the card's second voice
   after the name, not a caption under a logo. */
.fp-pitch{
  margin:var(--s4) 0 0;
  font-size:15px;line-height:23px;letter-spacing:-.005em;
  color:var(--stage-text);
}

/* Wraps, deliberately: the fitter measures the second row to know what to
   collapse into "+N", so nowrap would leave it nothing to detect and the last
   chip would simply be sliced by the card edge. It runs before the panel is
   shown, so the two-row state is never painted. */
.focus-panel .tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:var(--s3)}
/* Face only, no stroke. A 1px border at 8% around a 5% fill was drawing the
   same shape twice and reading as a hairline outline rather than a chip, so
   the fill carries it alone - lifted to 8% to hold the edge the border was
   doing, which keeps the chip the same weight overall. */
.focus-panel .tag{
  flex:none;
  padding:3px 9px;border-radius:var(--r-pill);
  background:rgb(255 255 255 / .08);
  border:0;
  font-size:11px;line-height:16px;white-space:nowrap;
  color:var(--stage-dim);
}
.focus-panel .tag-more{background:none;opacity:.7}

/* Anchored at both ends rather than left-ragged: the primary holds the left
   edge, the secondary the right, and the card reads as resolved instead of
   trailing off into empty space. Wraps back to a left-aligned row if the two
   ever meet. */
.fp-do{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:var(--s3) var(--s4);margin-top:var(--s5);
}
/* Nothing here but the size. The action is .btn-primary now, so the purple,
   the wipe to purple-deep and the tinted lift all come from the system rather
   than being re-specified against a dark card - which is what an outlined
   ghost was working around. Only the scale is local: 16px type on a 420px
   panel was a button built for a page. */
.focus-panel .fp-do .btn{
  font-size:13px;padding:8px 16px;
  cursor:pointer;
}
/* The rule draws itself in from the V to the final e, rather than appearing
   under the whole phrase at once - the direction of the wipe is the direction
   you read, so it feels like the link answering rather than a state flipping.
   It is a pseudo-element on the words only: the arrow is a glyph, not a word,
   and a rule running under it reads as a typo. */
.fp-site{
  display:inline-flex;align-items:baseline;gap:5px;
  font-size:13px;font-weight:500;color:var(--lavender);
  text-decoration:none;
  transition:color .18s ease;
}
.fp-site span{position:relative}
.fp-site span::after{
  content:"";position:absolute;left:0;right:0;bottom:-3px;height:1px;
  background:currentColor;
  transform:scaleX(0);transform-origin:left center;
  transition:transform .32s cubic-bezier(.22,.68,.28,1);
}
.fp-site:hover,.fp-site:focus-visible{color:var(--white)}
.fp-site:hover span::after,.fp-site:focus-visible span::after{transform:scaleX(1)}
/* Out is faster and retreats the way it came, so a cursor crossing a stack of
   links does not leave a trail of rules easing out behind it. */
.fp-site:not(:hover) span::after{transform-origin:right center;transition-duration:.2s}
.fp-site i{font-style:normal}

@media (prefers-reduced-motion:reduce){
  .fp-site span::after{transition:none}
}

/* Bare stage, deliberately: the arrows are a way onward, not a third surface.
   No fill, no border, no lift - only the card above them is boxed. */
.fp-step{
  display:flex;justify-content:space-between;gap:var(--s3);
  margin-top:var(--s4);padding:0 var(--s2);
  background:none;border:0;box-shadow:none;backdrop-filter:none;
}
.fp-arrow{
  padding:0;border:0;background:none;cursor:pointer;
  font:inherit;font-size:12px;color:var(--stage-dim);
  transition:color .16s ease;
}
.fp-arrow:hover{color:var(--white)}
.fp-arrow:focus-visible{outline:var(--focus-w) solid var(--lavender);outline-offset:var(--focus-off);border-radius:2px}

/* -- narrow screens -----------------------------------------------------
   No room for a split, so the map keeps the stage and the panel becomes the
   sheet under it. The country still centres itself in the map - the JS asks
   the same media query and frames it differently. */
@media (max-width:900px){
  .is-focus .hero-copy{opacity:1;transform:none;pointer-events:auto}
  .is-focus .market-rail{opacity:1;pointer-events:auto}
  .focus-panel{
    position:static;translate:none;order:4;
    width:auto;max-height:none;overflow:visible;
    margin:var(--s5) var(--s6) 0;
    transform:translateY(14px);
  }
  .focus-panel.is-open{transform:none}
}

@media (prefers-reduced-motion:reduce){
  #plateSvgEl use,.is-focus .hero-copy,.is-focus .market-rail,.focus-panel{transition:none}
}

/* The relief is one image per tier, so dimming a country's face leaves its
   terrain at full strength - which is why an "off" China still read as lit.
   In focus mode the tier reliefs drop away and the focus-clipped copy of the
   same image carries the one country that matters. Cross-fade, not a swap. */
.m-tex{transition:opacity .5s ease}
/* There was a fourth copy of apac.jpg here, clipped to the focused country, to
   put the region texture back over it after the tier dropped to .10. It is
   gone: the country plates carry their own relief now, and layering the region
   raster on top only made the markets LESS like one another - see the note in
   paintZoom(). */
.is-focus .m-tex-context{opacity:.10}
.is-focus .m-tex-region {opacity:.10}
.is-focus .m-tex-member {opacity:.10}

/* Zoomed in on an archipelago, every island carries its own halo and they run
   together into one white mass. A thinner stroke close up keeps the outline a
   line rather than a fog, and lets the terrain inside read. */
/* And a different filter with it: the region's glow is built to give eleven
   small shapes presence, which on one large one is just a soft edge. */
/* SUPERSEDED. Both the glow and the near-white rim are off in focus now. The
   filter is one blur held at ~2 device pixels, which is a 2px ramp from sea to
   land all the way around the country - and the eye takes the softest edge in
   the frame as the frame's resolution, so a crisp relief inside a ramped
   coastline reads as a blurry map. The stroke under it was #F1E8FF, four
   percent off white on a #7C5CFF plate: bright enough to be the brightest
   thing on screen and therefore what the eye focused on, instead of the
   terrain. The plate is light purple on a near-black sea; it does not need a
   line to be separable from the water. What carries the edge instead is
   .mi-hi, which is unfiltered and clipped to the country's own inside - same
   lift, hard edge. Region view is untouched: eleven small countries do need
   the glow for presence. Put these two lines back to restore the halo. */
.is-focus .m-edge use.is-lit{filter:none}
.is-focus .m-edge use{
  stroke-width:.8;
  opacity:var(--edge-glow,1);
  stroke:rgba(255,255,255,.16);
}
.is-focus .m-edge use:not(.is-on){opacity:.04}
/* Close up, the slab reads through its walls: the top face gets a little
   more light so the country is lit from the fill, not from the outline. */
.is-focus .m-face use.is-on{fill:url(#v7MemberFillFocus)}
/* Vector shading - what carries the focused country instead of the relief
   image. Three strokes of its own coastline, clipped to its own inside:

     .mi-dome  centred, wide, soft   -> the landmass sits up out of the water
     .mi-lo    offset down-right     -> shadow on the far side of the light
     .mi-hi    offset up-left        -> lit edge on the near side

   Widths, blurs and offsets are all driven from JS in proportion to the zoom,
   so this holds one constant appearance from the whole region down to a single
   island - which an image cannot do.

   Light-handed on purpose: an archipelago's islands are narrower than the
   strokes themselves, so at full strength the shading meets in the middle and
   the island goes dark instead of domed. */
/* The country's shaded relief, built from real elevation (tools/build-relief.py).
   Overlay, so mid-grey is a no-op: the hillshade is generated with level ground
   AT mid-grey and the sea forced to it, so only actual slope lifts or drops the
   purple underneath and flat country is left exactly as the fill painted it.

   .85, not the .45 this carried while it was fractal noise. Noise had to stay
   faint to avoid reading as dirt; a real hillshade has something to say at full
   strength, and overlay on a fill this light compresses it further.

   It arrives after the country has landed - terrain that fades up while the
   camera is still moving reads as a rendering artefact. */
/* hard-light, NOT overlay, and this is the difference between a country with
   terrain on it and a country with a very faint suggestion of terrain on it.

   Overlay keys off the BASE. The focused fill sits at L~153, well above mid
   grey, so overlay takes its screen branch everywhere on land and compresses
   the shading's dark half - which is the half that carries edges. Measured by
   ablation on a Luzon crop at DPR 1: deleting .m-terrain outright cost only
   13% of the fine detail on land (b1 5.92 -> 5.15). The hillshade was being
   generated, fetched, decoded and then very nearly thrown away.

   hard-light keys off the BLEND layer instead, which is the hillshade, and a
   hillshade is built for exactly this: build-relief.py forces level ground and
   sea to mid grey, and mid grey is hard-light's no-op, so flat country and
   water come through untouched while slope gets its full range back. Same
   crop: b1 5.92 -> 6.94, and the purple holds (land mean 152.9 -> 149.5).

   Measured against the alternatives on the same crop - soft-light 5.47 (worse
   than overlay), multiply .5 and luminosity .5 both lose the fill colour
   (land mean 121 and 140 against 153). Do not swap this back to a blend that
   keys off the base without re-running that ablation. */
.m-terrain{mix-blend-mode:hard-light;opacity:0;pointer-events:none;transition:opacity .45s ease}
/* No delay. The tier texture above drops from .92 to .10 as the flight starts,
   so a quarter second of dead time here is a quarter second with neither layer
   up: measured mid-flight, the two together fell to .48 at 360ms and the
   country visibly flattened before its relief arrived. Starting them together
   holds the sum at .95 or better the whole way across. */
.is-focus .m-terrain{opacity:var(--terrain,.85);transition:opacity .45s ease}

.m-inner{opacity:0;transition:opacity .5s ease}
.is-focus .m-inner{opacity:1}
.m-inner use{fill:none;vector-effect:none}
/* The dome is a rim, not a wash: at .42 it was shading whole islands rather
   than their edges, and the country came out darker than the map it flew from. */
/* Only the dome is blurred, and it is wide and faint - a broad fall-off, not
   an edge. The two bevels are NOT filtered: a blurred stroke this close to the
   coast is what made the whole island read as soft, because the eye takes the
   softest edge in the picture as the picture's resolution. Clipping a hard
   stroke to the country's own inside gives the same bevel with a crisp edge. */
/* Halved. It is a broad fall-off inside the coast, and at .14 it was adding a
   second soft ramp immediately behind the one the glow was already making. */
.mi-dome{
  stroke:#2A1552;stroke-width:var(--inner-w,12);
  filter:url(#v7Inner);opacity:.07;
}
.mi-lo{
  stroke:#1B0E38;stroke-width:var(--emboss-w,8);
  opacity:.4;
}
/* The lit edge carries more weight than the shadow - the country should read
   as lifted into the light, not pressed into the dark. */
.mi-hi{
  stroke:#F3EBFF;stroke-width:var(--emboss-w,8);
  opacity:.55;
}

/* Only the market in frame keeps its pin; the others are off-screen at this
   zoom anyway and would sit on the stage rather than on the map. */
.is-focus .pin:not(.is-on){opacity:0;pointer-events:none}

/* The focused coastline redraws itself on arrival - the same draw-on the
   region gets on first load, now for one country. The transition is already
   on .is-drawn; this just makes the un-drawn state cheap to snap back to. */
.is-focus .m-edge use:not(.is-drawn){transition:none}


/* ==========================================================================
   The header
   ==========================================================================
   Two problems, one fix. The bar was a logo pinned to one wall and a button
   pinned to the other with eleven hundred pixels of nothing between them,
   which reads as unfinished rather than restrained; and .is-focus deliberately
   fades the rail and the copy out, so the moment the map flies to one country
   the page has no visible way through the other ten.

   So the bar becomes a contained plate floating over the plate below it - the
   emptiness inside a frame is composed, the same emptiness across the full
   viewport is not - and its middle fills, only when there is something true to
   say, with where you are and the way on. Nothing invented: the crumb is the
   selection, and its three parts are all real destinations.

   Selectors are body.v2 rather than .has-v2 on purpose: immersive-hero.css owns the
   dark header at that weight, and a lighter selector here loses to it however
   late this file loads.
   ========================================================================== */
/* The floating bar itself moved to immersive-hero.css. It was here because the
   map page was the only page that had one, and profile.html then sat under a
   full-bleed transparent header while this page sat under a pill - two pages,
   two chromes, for no reason a reader could see. It is the dark header's
   treatment, and immersive-hero.css owns the dark header, so that is where it
   lives now. What stays here is the crumb, which is genuinely this page's:
   it names the market the map has flown to. */

/* -- the contextual middle --------------------------------------------- */
/* Centred on the bar rather than placed in its flex flow: the logo and the
   action keep the edges they already own, and the crumb sits on the axis of
   the stage no matter how wide either of them gets. */
.hdr-context{
  position:absolute;left:50%;top:50%;
  translate:-50% -50%;
  display:flex;align-items:center;gap:var(--s4);
  /* visibility, not opacity alone: a crumb faded to nothing is still read out
     and still tabbed into, and at rest it names a market nobody has chosen. */
  visibility:hidden;opacity:0;
  transform:translateY(-6px);
  transition:opacity .3s ease,transform .3s ease,visibility 0s linear .3s;
}
.hdr-context.is-in{
  visibility:visible;opacity:1;transform:none;
  transition:opacity .35s ease .05s,transform .35s cubic-bezier(.22,.68,.28,1) .05s,visibility 0s;
}

.hdr-crumb{
  display:flex;align-items:center;gap:var(--s2);
  margin:0;
  font-size:12px;line-height:18px;font-weight:500;
  letter-spacing:.12em;text-transform:uppercase;
  white-space:nowrap;
  color:rgb(255 255 255 / .5);
}
/* The root is the way back to the whole region - the same thing the panel's
   close button does, offered where you are already reading. */
.hdr-root{
  font:inherit;letter-spacing:inherit;text-transform:inherit;
  color:inherit;background:none;border:0;padding:0;
  cursor:pointer;transition:color .2s ease;
}
.hdr-root:hover,.hdr-root:focus-visible{color:var(--white)}
.hdr-sep{
  width:1px;height:10px;
  background:rgb(255 255 255 / .22);
  transform:skewX(-18deg);
}
.hdr-country{color:rgb(255 255 255 / .62)}
.hdr-city{color:var(--white);font-weight:600}

.hdr-step{display:inline-flex;align-items:center;gap:var(--s1)}
.hdr-arrow{
  /* A smaller sibling of the 43px action at the other end of the bar, rather
     than a third unrelated size. */
  width:34px;height:34px;
  display:inline-grid;place-items:center;
  /* Filled, not outlined. Two of these sit either side of a step counter on a
     dark bar, and at .14 the ring was doing less to find them than a ground at
     the same value does. The disc is a fixed 34px, so the border can go without
     the pair changing size or spacing. */
  border:0;
  border-radius:50%;
  background:rgb(255 255 255 / .1);color:rgb(255 255 255 / .72);
  cursor:pointer;
  transition:background .2s ease,color .2s ease;
}
.hdr-arrow svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.6}
.hdr-arrow:hover{background:var(--purple);color:var(--white)}
.hdr-arrow:focus-visible{outline:var(--focus-w) solid var(--purple);outline-offset:var(--focus-off)}

/* THE CRUMB AND THE SECTION NAV SHARE THIS SLOT AND SWAP.

   index.html carries both now: Members / Insights / Free tools / Newsletter at
   rest, the market breadcrumb once the map has flown somewhere. They are never
   both wanted - at rest the crumb names a market nobody has chosen, and in focus
   the crumb IS the navigation, because .is-focus has faded the rail and the hero
   copy out and stepping through the other ten markets is what the reader needs.

   :has() on the .wrap rather than a class from hero-map.js, because the state
   already exists in the DOM: .hdr-context.is-in is the single source of truth
   for "a market is selected" and the script sets it in one place. A second class
   written next to it would be a second thing to keep in sync.

   The nav fades on the same 0.3s as the crumb it is trading places with, and
   goes to visibility:hidden for the reason stated on .hdr-context above - a nav
   faded to nothing is still tabbed into and still read out. */
/* The resting half of the swap. .ins-nav has no opacity of its own in base.css,
   and a transition declared only on the hidden state animates one direction and
   snaps back on the other - the nav would fade out when a market opens and
   reappear instantly when it closes. */
.site-header .wrap .ins-nav{
  opacity:1;visibility:visible;
  transition:opacity .35s ease .05s,visibility 0s;
}
.site-header .wrap:has(.hdr-context.is-in) .ins-nav{
  visibility:hidden;opacity:0;
  transition:opacity .3s ease,visibility 0s linear .3s;
}

/* Below this the plate is narrow enough that the logo, the crumb and the
   action start negotiating for the same pixels. The panel's own prev/next
   carries the network from here down - and with the crumb gone the section nav
   keeps the slot at every width down to base.css's own 720 cutoff. */
@media (max-width:1180px){
  .hdr-context{display:none}
  .site-header .wrap:has(.hdr-context.is-in) .ins-nav{visibility:visible;opacity:1}
}

@media (prefers-reduced-motion:reduce){
  .hdr-context{transition:visibility 0s}
  .hdr-context.is-in{transition:visibility 0s}
  .site-header .wrap:has(.hdr-context.is-in) .ins-nav{transition:visibility 0s}
}


/* -- the city-state case -------------------------------------------------
   Singapore is three viewBox units across. focusViewBox already refuses to
   fly two hundred times into a six-point polygon and frames the strait
   instead - but the focus dim then takes the peninsula, Sumatra and Borneo to
   near-black, and what is left is a lit speck in an empty frame.

   So when the focused member is this small the surroundings stay readable.
   The picture becomes "Singapore, in the strait", which is the honest one:
   there is no version of this where that island alone fills a screen. */
.is-micro .m-face    use:not(.is-on){fill:#2C3039}
.is-micro .m-context use:not(.is-on),
.is-micro .m-region  use:not(.is-on){fill:#22262D;stroke-opacity:.26}
.is-micro .m-wall    use:not(.is-on){fill:#1C1F25}
.is-micro .m-edge    use:not(.is-on){opacity:.14}
.is-micro .m-shadow  use:not(.is-on){opacity:.42}
/* The tier reliefs come back up too - at .10 the land around it is a flat
   silhouette, and a silhouette is what made the frame read as empty. */
.is-focus.is-micro .m-tex-context,
.is-focus.is-micro .m-tex-region,
.is-focus.is-micro .m-tex-member{opacity:.28}


/* ==========================================================================
   Directory head, controls and the service menu
   Lives here rather than in base.css because it is this page's, not the
   shared directory's. On the white half of the page, so it takes the light
   palette - the same tokens the sortbar beside it uses.
   ========================================================================== */
.directory .directory-head{display:block;text-align:center;margin-bottom:var(--s8)}
/* Measure capped well short of the 1360px grid: a deck set the full width of
   the page is a rule of text, not a paragraph. */
.directory-deck{
  max-width:60ch;margin:var(--s4) auto 0;
  font-size:17px;line-height:27px;color:var(--gray);
}

.dir-controls{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3);
  margin-bottom:var(--s7);
}
/* The count opens the right-hand group and takes the row's slack with it, so
   the row has two anchored ends instead of trailing off into empty space. It is
   also the aria-live region, and it sits immediately before the control whose
   state it completes. */
.dir-controls .count{
  margin-left:auto;margin-right:var(--s1);
  color:var(--gray);font-size:14px;white-space:nowrap;
}
/* Only the service menu pushes right. The search field carried its own
   margin-left:auto from when it lived in the heading row, and two auto margins
   in one flex row split the free space between them - which parked the field in
   the middle of the page instead of beside the sort control it belongs with. */
.dir-controls .dir-search{margin-left:0}
/* The menu opens under its own left edge now that it leads the row; anchored
   right it would spill across the search field beside it. */
.svc-menu{right:auto;left:0}

/* -- the service menu -- */
/* The pill is the wrapper; the toggle and the clear are transparent targets
   inside it.

   Filled at both ends, and the fill is what carries the state. It used to be
   outlined at rest and filled when set, which meant the control changed kind
   between states rather than changing value - and it left the one hairline in a
   row of otherwise solid pills. Neutral --mist at rest, lavender when set: the
   step is a ground getting louder, not an edge appearing.

   transparent, not `border:0` - the pill carries a 1px border, and removing it
   outright would shrink the control by 2px and nudge the search field beside
   it. The pixel stays, it just never shows. */
.svc-select{
  position:relative;display:inline-flex;align-items:center;
  border:1px solid transparent;border-radius:var(--r-pill);
  background:var(--mist);
  transition:background-color 200ms ease-in-out,box-shadow 200ms ease-in-out;
}
/* With no edge to darken, hover has to move the ground. .05 is the smallest
   step that registers on either fill without reading as a second state. */
.svc-select:hover{box-shadow:inset 0 0 0 999px rgb(58 35 100 / .05)}
/* Lavender rather than the signature purple: this is a state, and a saturated
   fill here would out-shout Brief the network further down the page. Plum on
   lavender is 7.6:1, so the label stays the strongest thing in the pill - which
   matters, because the label IS the state. */
.svc-select.is-set{
  background:var(--lavender);
}
.svc-select.is-set .svc-toggle{color:var(--plum)}
/* --slate, not --plum. These controls carried plum as their resting text, which
   is body copy in a purple that the page reserves for meaning something. The
   rule the hero states in its own comment - purple means "an agency works here",
   it is not what the page is made of - applies to a control's label as much as
   to a surface. Purple stays on the states below, where it is doing work:
   .is-set on this toggle, .is-on in the menu, and the focus rings. */
.svc-toggle{
  display:inline-flex;align-items:center;gap:var(--s2);
  padding:9px 16px;
  border:0;border-radius:var(--r-pill);background:none;
  font-family:var(--font);font-size:14px;line-height:normal;color:var(--slate);
  cursor:pointer;
}
.svc-select.is-set .svc-toggle{padding-right:6px;font-weight:500}
.svc-toggle:focus-visible,.svc-clear:focus-visible,.svc-opt:focus-visible{
  outline:var(--focus-w) solid var(--focus-c);outline-offset:var(--focus-off);
}
.svc-caret{
  width:14px;height:14px;flex:none;
  fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;
  transition:transform 200ms cubic-bezier(.2,.7,.3,1);
}
.svc-toggle[aria-expanded="true"] .svc-caret{transform:rotate(180deg)}
/* The chevron's seat is the clear button's seat. One says "there is more to
   choose", the other "you have chosen" - they are never both true. */
.svc-select.is-set .svc-caret{display:none}

.svc-clear{
  display:grid;place-items:center;
  width:20px;height:20px;margin-right:9px;padding:0;flex:none;
  border:0;border-radius:50%;
  background:var(--plum);color:var(--white);cursor:pointer;
  transition:background-color 200ms ease-in-out;
}
.svc-clear svg{width:9px;height:9px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}
.svc-clear:hover{background:var(--purple-deep)}

/* The count rides in its own well so it reads as a quantity rather than part of
   the label - "Brand & campaigns 6" would otherwise scan as a version number. */
.svc-n{
  min-width:20px;padding:1px 6px;
  border-radius:var(--r-pill);
  background:rgb(58 35 100 / .07);
  font-size:12px;font-weight:500;font-variant-numeric:tabular-nums;
  text-align:center;color:var(--gray);
}

/* Right-aligned to the toggle and above the grid. Not a modal: an outside click
   or Escape dismisses it, and the page underneath stays live. */
.svc-menu{
  position:absolute;z-index:30;top:calc(100% + 8px);right:0;
  width:min(380px,86vw);max-height:min(70vh,520px);overflow-y:auto;
  padding:6px;
  background:var(--white);
  border:1px solid rgb(58 35 100 / .1);border-radius:var(--r-md);
  box-shadow:0 18px 48px -12px rgb(28 16 54 / .28), 0 2px 8px rgb(28 16 54 / .06);
}
.svc-opt{
  display:block;width:100%;
  padding:9px 12px;
  border:0;border-radius:12px;
  background:none;text-align:left;cursor:pointer;
  font-family:var(--font);
  transition:background-color 160ms ease-in-out;
}
.svc-opt + .svc-opt{margin-top:2px}
.svc-opt:hover,.svc-opt:focus-visible{background:rgb(58 35 100 / .05)}
.svc-opt.is-on{background:var(--pale)}
.svc-opt-h{
  display:flex;align-items:center;gap:var(--s3);
  font-size:15px;line-height:22px;color:var(--slate);
}
/* The chosen one keeps the purple, on the pale ground it already had. Seven
   purple labels said nothing; one says which. */
.svc-opt.is-on .svc-opt-h{color:var(--plum)}
.svc-opt-h b{font-weight:600}
.svc-opt-h .svc-n{margin-left:auto}

@media (max-width:760px){
  /* The menu goes to the left edge of the control, which by then is the left
     edge of the page - anchored right, it would hang off the viewport. */
  .svc-select{margin-left:0;width:100%}
  .svc-toggle{flex:1;justify-content:flex-start}
  .svc-menu{right:auto;left:0;width:100%}
}

/* -- directory search ---------------------------------------------------
   Sits in the heading row because the chip row below is already full width at
   1360px. margin-left:auto on this and none on the sortbar, so the pair travels
   to the right edge together with one gap between them rather than the free
   space splitting between two auto margins. */
.directory .sortbar{margin-left:0}
.dir-search{
  position:relative;display:flex;align-items:center;align-self:center;
  margin-left:auto;
  /* 310, not 272: the placeholder measures 209px and 272 left it 200px of text
     room, so it truncated mid-word. 310 gives 238 - about 29px of slack, enough
     to absorb the metric shift when the web font swaps in. The heading row has
     over 500px spare, so the space costs nothing. A truncated placeholder reads
     as a broken field, and this one is naming what you can search by, which is
     the whole reason it says more than "Search". */
  width:min(310px,100%);
}
.dir-search-i{
  position:absolute;left:14px;
  width:16px;height:16px;pointer-events:none;
  fill:none;stroke:var(--gray);stroke-width:1.6;stroke-linecap:round;
  transition:stroke 200ms ease-in-out;
}
.dir-search input{
  width:100%;
  /* 38 clears the 16px glyph at 14 with 8px to spare; 40 stops the text 9px
     short of the clear chip. The vertical stays 9 so the field keeps the exact
     height of the service pill beside it. */
  padding:9px 40px 9px 38px;
  /* Filled, and the 1px kept transparent. The field is centred in a flex row
     against .svc-select and has to hold that pill's exact height, so the two
     pixels stay in the box even though nothing draws them. --mist is the ground
     the pill beside it now uses at rest: one row, one resting surface. */
  border:1px solid transparent;border-radius:var(--r-pill);
  background:var(--mist);
  /* The visitor's own words, so they read as body copy and not as a filter
     token. Same reason as the toggle and the menu above. */
  font-family:var(--font);font-size:14px;line-height:normal;color:var(--slate);
  transition:background-color 200ms ease-in-out,box-shadow 200ms ease-in-out;
}
.dir-search input::placeholder{color:var(--gray)}
.dir-search input:hover{box-shadow:inset 0 0 0 999px rgb(58 35 100 / .05)}
/* The keyline is gone, so the ring comes back - there is nothing else left to
   carry focus. --focus-w of --focus-c, the same ring .svc-toggle,
   .svc-clear and .svc-opt already use - but drawn INSIDE the field's edge, so
   it reads as the field's own border rather than as a halo around it, which is
   how every other input on the site now lights. So every focusable control here
   lights identically. #7A00DF reads at 5.6:1 against #904FFF's 3.9 and is the
   focus ink everywhere else on the page. */
.dir-search input:focus{
  outline:var(--focus-w) solid var(--focus-c);outline-offset:var(--focus-in);
  box-shadow:none;
}
/* The magnifier follows the field's own state, so the whole control lights
   rather than just its outline. */
.dir-search input:focus ~ .dir-search-i,
.dir-search.has-q .dir-search-i{stroke:var(--plum)}
/* There is a real clear button, and two would be one too many. */
.dir-search input::-webkit-search-cancel-button,
.dir-search input::-webkit-search-decoration{-webkit-appearance:none;appearance:none}

/* The chip is 24px in a 37px field, which leaves 6.5px above and below it. Sat
   at 6px from the right edge it read as jammed into the corner - the gap has to
   match the one it already has top and bottom, not be smaller than it. 7px is
   that gap, so the circle now sits in even space on three sides. */
.dir-search-x{
  position:absolute;right:7px;
  display:grid;place-items:center;
  width:24px;height:24px;padding:0;
  border:0;border-radius:50%;
  background:rgb(58 35 100 / .07);
  color:var(--gray);cursor:pointer;
  transition:background-color 200ms ease-in-out,color 200ms ease-in-out;
}
.dir-search-x svg{
  width:11px;height:11px;
  fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;
}
.dir-search-x:hover{background:var(--plum);color:var(--white)}
.dir-search-x:focus-visible{outline:var(--focus-w) solid var(--focus-c);outline-offset:var(--focus-off)}

@media (max-width:760px){
  /* Full width on its own line: a 272px field wedged between a wrapped headline
     and the sort control was the narrowest thing on the page and the one you
     are most likely to be aiming at. */
  .dir-search{width:100%;order:3;margin-left:0}
}


/* ==========================================================================
   The metrics strip
   ==========================================================================
   It used to be a pale lavender ribbon between the hero and the directory,
   and the page crossed three surfaces in about 140px of scroll: near-black
   map, then #F4EDFF, then white. Two hard horizontal cuts stacked on top of
   each other, the first of them a 224-level jump in a single pixel row - so
   the map was guillotined mid-Australia and the strip read as a stray band
   rather than a section, having no height to be one with.

   The numbers were never the problem. Offices and people are the only scale
   the page states anywhere, and the count-up is the one moment the figures
   earn. What was wrong is that they were staged as a third surface.

   So the strip becomes the floor of the hero instead of the lid of the
   directory: same dark field, figures in white, labels in lavender. That
   deletes one of the two cuts, gives the map something to stand on, and
   leaves a single transition into the light - at "The APAC members", where a
   heading is arriving anyway and a change of ground reads as a new chapter
   rather than a seam.

   Scoped to body.v2, which is this page. The directory runs the same strip
   on a white page above a white directory, where it has no cut to make.
   ========================================================================== */
body.v2 .statbar{
  position:relative;
  /* Top matches the hero's bottom edge, which samples at rgb(20 22 27) on the
     left and a little lighter to the right; deepening downward gives the strip
     a floor of its own without redrawing the seam it just removed. */
  background:
    /* The hero does not end on a flat colour: its own radial wash lifts the
       bottom-right to about 28 where the left edge sits at 22. A flat top
       here therefore rebuilt the seam on the right at 6 levels. This is the
       tail of that wash, carried across the join and faded out downward. */
    radial-gradient(130% 190% at 68% 0%, rgb(255 255 255 / .038), transparent 58%),
    linear-gradient(180deg, rgb(20 22 27), rgb(16 18 22));
  border-top:0;
}
/* Was `body.v2 .statbar .wrap{padding-top:44px;padding-bottom:52px}` - two raw
   pixel values, off the token scale entirely, and asymmetric with no stated
   reason. .statbar now takes --sec-sm on the section itself like every other
   band; it reads as belonging to the hero above it rather than as its own. */
body.v2 .statbar .stat{border-left-color:rgb(255 255 255 / .09)}
body.v2 .statbar .stat dd{color:var(--white)}
body.v2 .statbar .stat dt{color:rgb(211 185 255 / .72)}   /* var(--lavender) */

/* The one transition left, and it is marked rather than merely allowed to
   happen: the same west-to-east longitude ramp the footer closes on. The page
   now crosses from dark to light twice, and both crossings carry the same
   rule, which is the network's own axis - Karachi through to Auckland. */
body.v2 .statbar::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:3px;
  background:var(--ramp);
}
