/* ===============================================================
   WINGS — home.html only: fullpage slide experience
   Loaded after wings.css (reuses --plum/--cyan/--serif/--sans/--pad
   tokens). Scoped to #slides / .slide* classes so it never touches
   the shared header, footer, or other pages' layout rules.
   =============================================================== */

/* this is a permanent fullpage experience — no footer to scroll ---
   into, so the viewport stays locked for good. */
html, body { height:100%; overflow:hidden; }

body.has-slides { background:#1a1014; }

/* header stays pinned above every slide --------------------------- */
.site-header--fixed { position:fixed; }

/* slide stack ------------------------------------------------------ */
#slides { position:fixed; inset:0; width:100%; height:100vh; overflow:hidden; z-index:5; background:#1a1014; }

.slide { position:absolute; inset:0; width:100%; height:100%; display:none; }
.slide.is-visible { display:block; }

/* Slide 1 only, first paint: stays invisible until home-slides.js
   confirms both photo halves have loaded and are correctly
   positioned, then removes this class outright (no fade) -- see the
   .slide[0] reveal logic at the top of home-slides.js. Prevents a
   flash of the plain 50/50 .slide__half fallback (mismatched crops
   for this "lr" angled split) while the real images are still
   downloading. */
.slide:first-child.is-loading { opacity:0; }

/* image halves — home-slides.js sizes a shared .slide__canvas box in
   exact pixels so the two halves' baked-in seam stays lined up; these
   rules are just the 50/50 fallback before that measurement runs. */
.slide__media { position:absolute; inset:0; overflow:hidden; }
.slide__canvas { position:absolute; top:0; left:0; width:100%; height:100%; }
.slide__half { position:absolute; display:block; }
.slide__half.is-broken { display:none; }

.slide[data-media="lr"] .slide__half--a { left:0; top:0; width:50%; height:100%; }
.slide[data-media="lr"] .slide__half--b { left:50%; top:0; width:50%; height:100%; }
.slide[data-media="tb"] .slide__half--a { left:0; top:0; width:100%; height:50%; }
.slide[data-media="tb"] .slide__half--b { left:0; top:50%; width:100%; height:50%; }

/* faint brand grid texture, on every slide -- same grid.png image as
   .hero__grid (style.css), same stacking order too: BELOW the blur,
   not above it. The grid.png artwork is itself very low-contrast
   (near-white lines on transparent), so on its own -- as a plain
   layer on top -- it barely reads against a photo. On the Hero it
   only becomes visible because the multiply-blend scrim sits ON TOP
   of it: multiply darkens the photo everywhere except where the
   grid's lighter lines are, which come through as a subtly lighter
   texture in the dark tint. Putting the grid above the scrim (a
   previous attempt here) lost that interaction entirely. JS still
   drives this layer's opacity (tl.to(...opacity:0.5) in goToIndex) --
   an inline style from GSAP always wins over any CSS value here
   regardless of specificity, so the opacity below is only a no-JS
   fallback. No scaleX entrance animation either: slides toggle
   .is-visible repeatedly as the visitor scrolls back and forth, and
   a CSS keyframe animation would replay every time -- the same
   problem slide 1's text reveal hit, solved there with a one-off JS
   tween instead (see home-slides.js). */
/* NOT var(--hero-grid) here: that variable's url("img/grid.png")
   (style.css) resolves relative to whichever stylesheet actually uses
   it, not to style.css's own folder -- and this file lives one
   directory deeper (css/home.css), so var(--hero-grid) here silently
   404'd at .../wings/css/img/grid.png instead of .../wings/img/grid.png.
   No opacity/z-index/blend-mode value could ever have made that
   visible, since the image was never loading at all. Spelled out with
   the correct ../ relative path instead. */
.slide__grid { position:absolute; inset:0; z-index:1; pointer-events:none; opacity:.1; background:url("../img/grid.png") center/cover no-repeat; }

/* legibility blur/scrim -- same radial-gradient + multiply treatment
   as .hero__blur (style.css), sitting on top of the grid texture so
   the multiply blend can pick out its lighter lines against the
   darkened photo (see .slide__grid comment above). */
.slide__scrim {
    position:absolute; inset:0; z-index:2;
    /* background: radial-gradient(50% 50% at 50% 50%, rgba(70, 21, 44, 0.40) 24.04%, rgba(70, 21, 44, 0.90) 100%); */
    background: radial-gradient(50% 50% at 50% 50%, rgba(2, 124, 151, 0.40) 24.04%, rgba(2, 124, 151, 0.90) 100%);
    mix-blend-mode: multiply;
}
/* slide 7's cyan wash — dormant, superseded by [data-slide-bg-full]'s
   pre-baked wash. Left in place for a possible future blend. */
.slide__map-tint { position:absolute; inset:0; z-index:1; pointer-events:none; opacity:0; background:linear-gradient(180deg, rgba(15,158,189,.7) 0%, rgba(15,158,189,.7) 100%); }

/* content layer ---------------------------------------------------- */
/* .slide__content has no width constraint of its own, so its empty
   right-hand area sits above slide 7's map and would otherwise eat
   hover/click before it reaches the map. pointer-events:none here,
   :auto restored on .slide__exit-content, lets real content stay
   clickable while empty space falls through. */
.slide__content { position:relative; z-index:3; height:100%; display:flex; align-items:center; padding-top:64px; pointer-events:none; }
/* lines slide body copy up with the header/footer's wider measure
   (var(--wrap-header), 90rem) instead of the site-wide default .wrap
   (var(--wrap), 1180px) — home.html only, so no other page is affected. */
.slide__content .wrap { width:100%; max-width:var(--wrap-header); }
.slide__exit-content { pointer-events:auto; }
/* slide 7: .slide__exit-content fades out on FULL map reveal but still
   catches clicks meant for the map underneath until pointer-events is
   turned off too, gated on the same .is-map-active class. */
.slide[data-map-slide].is-map-active .slide__exit-content { pointer-events:none; }
.slide__content--center .wrap { text-align:center; color:#fff; }
.slide__content--center h2 { color:#fff; margin-bottom:26px; }
.slide__content--center p { max-width:900px; margin:0 auto .8em; color:rgba(255,255,255,.9); font-size:1.02rem; }
.slide__content--center .accent { font-weight:700; }
.slide__content--center .feature-row { color:#fff; }

/* left-aligned slide copy (1–4) — same serif/sans rhythm as the ---
   old hero, reused across every photo-plus-live-text slide -------- */
.slide__text { max-width:640px; }
.slide__text h1,
.slide__text h2 { color:#fff; font-size:clamp(2.3rem,5.4vw,64px); line-height:.98; font-weight:600; margin-bottom:28px; }
.slide__lead { max-width:600px; }
.slide__lead p { color:#fff; font-size:16px; line-height:1.4; font-weight:400; opacity:.94; margin-bottom:1em; }
.slide__lead p.accent { font-weight:700; opacity:1; }
.slide__chev { margin:6px 0 24px; }
ul.slide__chev li { color:#fff; font-size:16px; line-height:1.4; margin-bottom:8px; }
ul.slide__chev li::before { color:#fff; }
.slide__chev strong { font-weight:700; }

/* stacked stat heading (slide 7 — "260+ organisations / 60+ countries
   / 6 regions / 25+ years" as one block, not a feature-row grid) ----- */
.slide__stats { margin-bottom:26px; }
.slide__stats span { display:block; color:#fff; font-family:var(--serif); font-weight:600; font-size:clamp(2.3rem,5.4vw,64px); line-height:.98; }

/* pull-quote / testimonial slide (slide 6) --------------------------- */
.slide__quote { max-width:890px; }
.slide__quote .quote-text { font-family:var(--serif); font-weight:600; font-size:clamp(2.3rem,5.4vw,64px); line-height:.98; color:#fff; margin-bottom:18px; }

.slide__quote cite { display:block; font-style:normal; font-weight:700; font-size:14px; color:#fff; opacity:.85; margin-bottom:22px; }
.slide__quote .quote-context { font-size:15px; line-height:1.4; color:rgba(255,255,255,.85); max-width:600px; }

/* four-card CTA row (slide 8) — sized and formatted to match the
   standard card treatment used across the rest of the site: fixed
   256px width, 45px gaps, and the same .card border/padding/hover/
   typography rules from style.css (nothing card-specific overridden
   here any more). The only thing that stays slide-specific is the
   translucent dark background -- these cards sit over a photo
   backdrop rather than a plain page background, so they still need
   a scrim for legibility; everything else now just inherits the
   shared .card rules like every other card on the site. */
/* Grid, not flex, matching the same fixed-256px-column pattern as
   .wings-cards__grid (style.css): explicit 256px columns so cards
   never wrap early just because the slide's own padding narrows
   things -- flex-wrap was breaking to 2 rows well before the row was
   actually full. auto-fit (not a hardcoded repeat(5,...)) sizes the
   track list to however many cards actually exist -- a fixed 5-track
   grid left 4 cards sitting in the first 4 tracks, visibly off-centre
   since the empty 5th track still counted toward centering the row.
   width:max-content + the max-width cap below make the grid's own box
   shrink to fit its real card count (so justify-content:center has no
   leftover empty track to center around) while still capping out at
   "5 cards wide" before a 6th is forced onto its own row. */
.slide__cards { display:grid; grid-template-columns:repeat(auto-fit,256px); gap:45px; margin:36px auto 0; width:max-content; max-width:calc(5*256px + 4*45px); justify-content:center; text-align:left; }
.slide__cards .card { width:256px; }
/* Hover is the standard site-wide .card:hover (style.css: white fill,
   plum text) -- :not(:hover) here matters because this translucent
   resting background and that white hover fill are equal-specificity
   selectors (two classes each) both setting `background`, and this
   rule loads later (home.css after style.css), so without :not(:hover)
   it would always win and the card would never actually show the
   hover fill other cards on the site get. */
.slide__cards .card:not(:hover) { background:rgba(12,7,10,.15); }

/* slide 7's static backdrop — bg-7.png, a plain object-fit:cover <img>,
   faded out on exit. [data-slide-bg-full] (bg-7-1/bg-7-2, a top/bottom
   pair with its own dark wash baked in) sits on top at opacity:0 and
   fades in during revealMapFull() (home-slides.js), swapping the
   visible backdrop instead of layering a runtime gradient. Uses its own
   class names (not .slide__canvas/.slide__half) so it stays outside the
   normal tear-apart/rewind machinery — see setupBgFullLayout(). */
.slide__bg { position:absolute; inset:0; overflow:hidden; }
.slide__bg img { width:100%; height:100%; object-fit:cover; display:block; }
.slide__bg-full { position:absolute; inset:0; overflow:hidden; opacity:0; }
.slide__bg-full-canvas { position:absolute; top:0; left:0; width:100%; height:100%; }
.slide__bg-full .slide__bg-full-half { position:absolute; display:block; }
/* 50/50 fallback before setupBgFullLayout() measures the real images. */
.slide__bg-full .slide__bg-full-half--a { top:0; left:0; width:100%; height:50%; }
.slide__bg-full .slide__bg-full-half--b { top:50%; left:0; width:100%; height:50%; }

/* world map (slide 7) — [data-map-image] (img/map/map.svg) sits inside
   [data-map-canvas], sized once by getMapSize() in home-slides.js and
   never resized again. HALF (resting, on arrival) positions the map so
   only its left portion is on screen next to the text; FULL (played by
   revealMapFull() on a further scroll) recenters the same fixed-size
   map — see .slide__map-canvas in home-slides.js for the full sequence.
   Six region overlays sit on top, gated to only respond once
   .is-map-active lands (never during the HALF-state teaser) — see
   .map-region below and setMapRegionsTabbable() in home-slides.js.
   [data-map-reveal] slides in via a plain transform (xPercent 100->0,
   with overshoot — see goToIndex in home-slides.js); its right edge
   extends 20% past .slide__media as overshoot buffer so the overshoot
   never exposes a gap. Stacking: backdrop photo at base, scrim/grid
   above it, [data-map-reveal] and .slide__content both z-index:3
   (content wins ties by markup order). */
.slide__map-canvas { position:absolute; top:0; left:0; }
.slide__media-reveal { position:absolute; top:0; bottom:0; left:0; right:-20%; overflow:hidden; z-index:3; }
.slide__map-image { position:absolute; top:0; left:0; width:100%; height:100%; display:block; }
/* region overlays sit inset:0 over the map canvas so they scale/
   reposition with it automatically — see [data-map-regions] in
   home.html for how each one's placement was derived. opacity:0 until
   the map reaches FULL (revealMapFull(), home-slides.js). */
.slide__map-regions { position:absolute; inset:0; pointer-events:none; opacity:0; }
/* .map-region stays pointer-events:none even once active — mouse
   interaction comes from .map-region-hit below, not this rectangular
   wrapper (six overlapping bounding boxes made box-based hover feel
   broken; see [data-map-regions] in home.html). */
.map-region { position:absolute; pointer-events:none; z-index:1; }
/* asia-pacific's box is the largest (it crops mainland Asia AND
   Australia/Oceania into one image — see [data-map-regions] in
   home.html) and overlaps every other region's box by a wide margin.
   pointer-events:visiblePainted is supposed to let clicks fall through
   its transparent areas to whatever's underneath regardless of paint
   order, but in practice it was still winning those overlaps — so every
   other region is explicitly stacked above it here, belt-and-suspenders
   on top of the alpha-based hit-testing: in any overlapping area the
   OTHER region's .map-region-hit is what's actually on top and gets
   tested first. */
.map-region[data-region="asia-pacific"] { z-index:0; }
/* .map-region-hit is a third, invisible layer per region (an inline
   <svg><image>, home.html) used purely for hit-testing:
   pointer-events:visiblePainted tests the cursor against the image's
   real alpha channel rather than a bounding box, so hover/click only
   fire on the actual silhouette. Listeners attach to its <image> in
   home-slides.js. cursor:pointer lives here (not on .map-region) so
   the pointer only changes over the real shape too. */
.map-region-hit { position:absolute; inset:0; width:100%; height:100%; opacity:0; pointer-events:none; display:block; }
.map-region-hit image { pointer-events:none; }
.slide[data-map-slide].is-map-active .map-region-hit image { pointer-events:visiblePainted; cursor:pointer; }
/* .map-region-shape: a single silhouette per region. Two techniques:

   1) north-america + latin-america (raster-in-svg assets, img/map/*-white.svg
      set as --region-mask inline by template-home-slides.php) still use
      a CSS mask-image, which has always worked fine for them.
   2) europe / mena / sub-saharan-africa / asia-pacific (true vector
      <path> assets) are now inlined directly as <svg> markup instead
      of used as a mask-image source -- see
      wings_map_inline_region_shape() in inc/blocks.php. CSS
      mask-image on an *external* SVG resource turned out to be
      unreliable across browsers for these specific files
      (blank/partial first paint), almost certainly a mask +
      async-resource-load repaint bug -- inlining the shape removes
      that external fetch/decode step entirely. These use plain
      `color` + the SVG's own fill="currentColor".

   Both variants: white at rest, background/colour transitions to gold
   on hover/active/focus -- no second "gold" layer cross-fading any
   more. mask-size:100% 100% (variant 1) / the inlined <svg>'s own
   100%/100% sizing (variant 2) both keep the shape stretched to its
   percentage box regardless of the source asset's own intrinsic size. */
.map-region-shape {
  position:absolute; inset:0; width:100%; height:100%; display:block; pointer-events:none;
  background-color: #FFFFFF; /* solid equivalent of the base map.svg's own silhouette colour (white at .65 opacity over --plum, matched exactly -- see img/map/map.svg's fill-opacity) -- matching it exactly means the region overlay and the base map's independently-traced coastline blend into one seamless colour instead of showing a visible double-edge where their tracings don't pixel-for-pixel coincide. It's also NOT an alpha colour, so two adjacent regions' rectangles overlapping (they legitimately do, e.g. mena/sub-saharan-africa, europe/asia-pacific) can never double up and brighten at the seam the way a translucent rgba() would. */
  -webkit-mask-image: var(--region-mask);
  mask-image: var(--region-mask);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  transition: background-color .6s ease-out;
}
/* :hover is deliberately not a trigger here — mouseenter/mouseleave on
   .map-region-hit (home-slides.js) toggles .is-active instead, so the
   highlight stays pixel-accurate. :focus-visible stays for keyboard. */
.map-region:focus-visible .map-region-shape,
.map-region.is-active .map-region-shape { background-color: #EFB935; }

/* inline-SVG variant: no mask, just colour + fill="currentColor". Same
   #FFFFFF/#EFB935 values as above, kept in sync deliberately. */
.map-region-shape--inline {
  background-color: transparent;
  -webkit-mask-image: none;
  mask-image: none;
  color: #FFFFFF;
  transition: color .6s ease-out;
}
.map-region-shape--inline svg { display:block; width:100%; height:100%; }
.map-region:focus-visible .map-region-shape--inline,
.map-region.is-active .map-region-shape--inline { color: #EFB935; background-color: transparent; }
/* Now nested inside .slide__map-canvas (template-home-slides.php),
   sharing .map-region's own 0-100% box, so each label's left/top can
   sit just past the map's edge (negative or >100%) and still track
   the map's real size/position on resize -- previously this container
   matched the much bigger .slide__media-reveal box instead, which is
   why the old percentages had to land ON the map artwork to line up
   with a region at all. */
.map-interactive__labels { position:absolute; inset:0; pointer-events:none; z-index:3; }
.map-label { position:absolute; width:220px; opacity:0; transform:translateY(6px); transition:opacity .25s ease, transform .25s ease; }
.map-label.is-active { opacity:1; transform:translateY(0); }
.map-label h3 { font-family:var(--serif); font-weight:600; line-height:1.25; color:#fff; margin:0 0 8px; padding-bottom:8px; border-bottom:1px solid rgba(255,255,255,.6); }
.map-label ul.chev { margin:0; }
.map-label ul.chev li { color:#fff; line-height:1.4; margin-bottom:3px; font-size: .9rem; }
.map-label ul.chev li::before { color:#EFB935; }

/* stands in for .slide__cue once the map reaches FULL — opacity is
   driven entirely from JS (revealMapFull()/ensureAssembled(), both in
   home-slides.js), same as .slide__cue's own resting opacity, since an
   inline style from gsap.set() always wins over a CSS class rule here
   regardless of specificity. */
.map-hint { position:absolute; left:50%; bottom:40px; transform:translateX(-50%); z-index:4; margin:0; color:#fff; font-family:var(--serif); letter-spacing:.02em; opacity:0; pointer-events:none; white-space:nowrap; }

/* footer content reused as the final slide's overlay — mirrors
   our-work.html's real site footer markup/layout. */
.slide__content--bottom { align-items:flex-end; padding-bottom:56px; }
.slide__footer { color:#fff; width:100%; }
.slide__footer .footer-logo .brand-logo { filter:brightness(0) invert(1); height:auto; }
/* matches the site header/nav's wider max-width instead of the default
   .wrap every other slide uses. */
.wrap.wrap--header-width { max-width:var(--wrap-header); }
/* slide 9: "next" restarts from slide 1 (playRewind()), so no cue. */
.slide[data-footer-slide] .slide__cue { display:none; }

/* slide 1's initial text reveal used to be a CSS keyframe animation
   here, triggered by .slide:first-child.is-visible — but that selector
   matches every time slide 1 becomes .is-visible again, not just on the
   genuine first page load, so it replayed (instantly, on its own fixed
   timing) every time playRewind() looped back to slide 1, showing the
   text well before the GSAP-gated reveal in home-slides.js. Handled in
   JS instead now — see the one-off tween near slides[0] in
   home-slides.js — so nothing here re-triggers it. */

/* scroll cue -------------------------------------------------------- */
.slide__cue { position:absolute; left:50%; bottom:10px; transform:translateX(-50%); z-index:4; display:flex; flex-direction:column; align-items:center; gap:8px; background:none; border:0; color:#fff; cursor:pointer; opacity:.85; padding:6px; }
.slide__cue:hover { opacity:1; }
.slide__cue svg { width:15px; height:15px; animation:slideCueBounce 1.8s ease-in-out infinite; }
.slide__cue span { font-size:.68rem; letter-spacing:.18em; text-transform:uppercase; }
@keyframes slideCueBounce { 0%,100% { transform:translateY(0); } 50% { transform:translateY(8px); } }

/* dot navigation ------------------------------------------------------ */
.slide__dots { position:fixed; right:28px; top:50%; transform:translateY(-50%); z-index:20; display:flex; flex-direction:column; gap:12px; }
.slide__dots button { width:9px; height:9px; border-radius:50%; border:1.5px solid rgba(255,255,255,.75); background:transparent; padding:0; cursor:pointer; transition:background .2s,transform .2s; }
.slide__dots button:hover { transform:scale(1.2); }
.slide__dots button.is-active { background:#fff; }

/* responsive -------------------------------------------------------- */
@media(max-width:860px) {
  .slide__dots { right:14px; }
  .slide__text { max-width:520px; }
}

/* .slide__cards breakpoints match style.css's .wings-cards__grid
   (repeat(2,256px) under 1024px, single column under 600px), so this
   slide's cards behave the same as every other fixed-256px card grid
   on the site rather than inventing their own thresholds. */
@media(max-width:1024px) {
  .slide__cards { max-width:calc(2*256px + 45px); }
}
@media(max-width:600px) {
  .slide__cards { max-width:256px; }
}

@media(prefers-reduced-motion:reduce) {
  .slide__cue svg { animation:none; }
}
