/* ============================================================
   WAPISO wapiv3: site footer ("the route map")
   Light and part of the site: surfaces --surface-*, text --ink-*, accents --brand / --brand-deep, lines --hairline.
   Markup + data: components/footer.php + components/footer-config.php. Behaviour: assets/js/footer.js.

   One plate, one composition:
     wave edge            a pale edge, transparent above the curve
     the big WAPI         cut into the surface across the full width, behind everything (decoration)
     the map              hub (the call to action) + four routes (badge, rail, stations); Caretta swims to the station you point at
     contact route        address, phone, email; at the end of its rail the socials and back to top as round nodes, the copyright beside them in very small type
   Layouts of the map (the markup never changes):
     A  >= 1280px         hub on the left, the spine joins four horizontal rails
     B  960 - 1279px      hub strip on top, the same horizontal rails below
     C4 700 - 959px       hub on top, four vertical routes side by side
     C2 < 700px           hub on top, vertical routes two by two
   Motion: transform and opacity only. Everything that loops runs while the footer is on screen (.is-near, set by footer.js) and not
   under prefers-reduced-motion. Without JS the map is static and complete.
   ============================================================ */

.site-footer {
  --wf-1: 8px;
  --wf-2: 16px;
  --wf-3: 24px;
  --wf-4: 40px;
  --wf-5: 64px;
  --wf-wave: clamp(36px, 4.2vw, 60px);       /* height of the surface edge */
  --wf-p: 0;                                  /* 0..1, set by footer.js while the footer scrolls into view (the wave drifts) */
  --wf-sky: var(--brand-tint);                /* back wave: pale sky, refined below where color-mix exists */
  --wf-line: var(--brand-rim);                /* the spine and its connectors */
  --wf-steel: var(--ink-3);                   /* resting strokes (rails, dashes, dots): a calm steel blue where color-mix exists */
  --wf-steel-ink: var(--brand-deep);          /* the ring of a dot, the ink of a badge: a deeper, quieter blue where color-mix exists */
  --wf-halo: var(--brand-tint);               /* the soft light around a dot */
  --wf-lift: 0 1px 2px var(--hairline);       /* a badge or a ring lifts a hair off the plate */
  --wf-slots: 6;                              /* stations per route in the horizontal layouts (set by .wf-slots-N from the PHP) */
  --wf-rail-y: 16px;                          /* centre of a rail, from the top of its route row */
  --wf-dot: 12px;                             /* the station dot (footer.js reads this to park the swimmer on it) */
  --wf-sx: 14px;                              /* the spine's x inside a route row */
  --wf-pad: 44px;                             /* room left of the badge for the spine and its elbow */
  --wf-bw: 120px;                             /* badge column */
  --wf-hub-w: clamp(272px, 26vw, 332px);
  --wf-hub-gap: 64px;                         /* between the hub text and the spine: the room Caretta rests in */
  position: relative;
  z-index: 1;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  font: 400 15px/1.6 var(--font-body);
}
.wf-slots-5 { --wf-slots: 5; }
.wf-slots-6 { --wf-slots: 6; }
.wf-slots-7 { --wf-slots: 7; }
.wf-slots-8 { --wf-slots: 8; }
@supports (color: color-mix(in srgb, red 50%, blue)) {
  .site-footer {
    --wf-sky: color-mix(in srgb, var(--brand-sky) 34%, var(--surface-base));
    --wf-line: color-mix(in srgb, var(--brand-sky) 52%, var(--surface-sunken));
    --wf-steel: color-mix(in srgb, var(--brand) 34%, var(--ink-3));
    --wf-steel-ink: color-mix(in srgb, var(--brand-deep) 62%, var(--ink-3));
    --wf-halo: color-mix(in srgb, var(--brand-sky) 26%, transparent);
    --wf-lift: 0 1px 2px color-mix(in srgb, var(--ink-1) 7%, transparent);
  }
}
.site-footer a:not(.btn) { color: inherit; text-decoration: none; }
.site-footer a:not(.btn):focus-visible,
.site-footer button:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.wf-vh { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ---------- surface: a soft wave edge, transparent above the curve so any section can sit over it ---------- */
.wf-surface { position: relative; height: var(--wf-wave); overflow: hidden; pointer-events: none; }
.wf-surface svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.wf-wave-back  { fill: var(--wf-sky); opacity: 0.8; transform: translateX(calc(var(--wf-p) * -40px)); }
.wf-wave-front { fill: var(--brand-tint); transform: translateX(calc(var(--wf-p) * 28px)); }

.wf-body {
  position: relative;
  overflow: hidden;                           /* the big WAPI is cut by the bottom edge: it is sunk into the plate */
  margin-top: -1px;
  background: linear-gradient(180deg, var(--brand-tint) 0, var(--surface-sunken) 260px, var(--surface-sunken) 100%);
}
.wf-stage { position: relative; z-index: 2; padding-top: var(--wf-3); padding-bottom: var(--wf-2); }

/* ---------- the big WAPI: engraved into the plate, full width, very quiet ---------- */
.wf-word {
  position: absolute; z-index: 0; left: 0; right: 0; bottom: -6%;
  height: clamp(120px, 21vw, 330px);          /* the letters stretch to the full width; the height is capped so wide screens do not turn it into a wall */
  pointer-events: none;
}
.wf-word svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.wf-w-lip  { fill: var(--surface-raised); }                                   /* the thin light edge under the cut */
.wf-w-face { fill: var(--brand); fill-opacity: 0.085; color: var(--ink-1); }   /* the inner shadow takes its colour from here (flood-color: currentColor) */
.wf-sheen-a { stop-color: var(--surface-raised); stop-opacity: 0.85; }
.wf-sheen-b { stop-color: var(--surface-raised); stop-opacity: 0; }
.wf-sheen { opacity: 0; transform: translate(var(--wf-mx, 250px), var(--wf-my, 90px)); transition: opacity 500ms var(--ease-entrance); }  /* footer.js moves it (user units of the 1000 wide box) and shows it while the pointer is over the footer */
.wf-word.is-lit .wf-sheen { opacity: 1; }

/* ---------- the map ---------- */
.wf-map { position: relative; display: grid; grid-template-columns: var(--wf-hub-w) minmax(0, 1fr); align-items: center; }

/* hub: the one call to action */
.wf-hub { position: relative; z-index: 1; padding-right: var(--wf-hub-gap); }
.wf-cta-title { margin: 0; font: 650 clamp(22px, 1.9vw, 27px)/1.22 var(--font-display); letter-spacing: -0.022em; color: var(--ink-1); }
.wf-line { display: block; }
.wf-line--b { color: var(--brand-deep); }
.wf-hub-act { margin: var(--wf-3) 0 0; }
.wf-hub-act .btn svg { width: 16px; height: 16px; }
.wf-logo-link { display: inline-block; border-radius: var(--r-input); }
.wf-logo { display: block; width: auto; height: 52px; margin-left: -20px; }
.wf-tagline { margin: var(--wf-1) 0 0; max-width: 26em; font-size: 14px; line-height: 1.6; color: var(--ink-2); }

/* the hub node on the spine; Caretta rests on it */
.wf-ring {
  --wf-rest: -38px;                            /* where Caretta rests, from the node's centre (footer.js reads it): beside it, not on the badges */
  position: absolute; z-index: 3;
  left: calc(100% + var(--wf-sx) - 9px); top: 50%;
  width: 18px; height: 18px; margin-top: -9px;
  box-sizing: border-box; border: 2px solid var(--wf-steel-ink); border-radius: var(--r-pill);
  background: var(--surface-raised);
  box-shadow: 0 0 0 5px var(--wf-halo);
  pointer-events: none;
}
.wf-ring::before { content: ""; position: absolute; right: 100%; top: 50%; width: calc(var(--wf-hub-gap) + var(--wf-sx) - 9px + 2px); height: 2px; margin-top: -1px; background: var(--wf-line); }   /* the stub to the call to action */

/* routes */
.wf-routes { position: relative; display: flex; flex-direction: column; min-width: 0; }
.wf-route {
  --rail-c: var(--wf-steel);
  position: relative;
  display: grid; grid-template-columns: var(--wf-bw) minmax(0, 1fr); column-gap: 14px;
  margin: 0;
  padding: 0 0 10px var(--wf-pad);
}
.wf-route:nth-child(1) { --r: 0; }
.wf-route:nth-child(2) { --r: 1; }
.wf-route:nth-child(3) { --r: 2; }
.wf-route:nth-child(4) { --r: 3; }
.wf-route:nth-child(5) { --r: 4; }
.wf-route:nth-child(6) { --r: 5; }
.wf-n-2 { --n: 2; } .wf-n-3 { --n: 3; } .wf-n-4 { --n: 4; } .wf-n-5 { --n: 5; } .wf-n-6 { --n: 6; } .wf-n-7 { --n: 7; } .wf-n-8 { --n: 8; }

/* rail styles: solid, dashed, dotted, double (the route reads apart without colour); --rail-h runs sideways, --rail-v down */
.wf-route--solid  { --rail-c: var(--wf-steel); --rail-h: linear-gradient(var(--rail-c), var(--rail-c)) 0 50% / 100% 2px no-repeat; --rail-v: var(--rail-c); }
.wf-route--dashed { --rail-c: var(--wf-steel); --rail-h: repeating-linear-gradient(90deg, var(--rail-c) 0 8px, transparent 8px 14px) 0 50% / 100% 2px no-repeat; --rail-v: repeating-linear-gradient(180deg, var(--rail-c) 0 7px, transparent 7px 12px); }
.wf-route--dotted { --rail-c: var(--wf-steel); --rail-h: radial-gradient(circle at 50% 50%, var(--rail-c) 0 1.3px, transparent 1.9px) 0 50% / 8px 4px repeat-x; --rail-v: radial-gradient(circle at 50% 50%, var(--rail-c) 0 1.3px, transparent 1.9px) 50% 0 / 4px 8px repeat-y; }
.wf-route--double { --rail-c: var(--wf-steel); }

/* the spine: every row draws its own piece, so the rows stack into one line; the first and last rows turn into elbows */
.wf-route::before, .wf-route::after { content: ""; position: absolute; pointer-events: none; }
.wf-route::before { left: calc(var(--wf-sx) - 1px); top: 0; bottom: 0; width: 2px; background: var(--wf-line); transform-origin: 50% 0; }
.wf-route::after  { left: calc(var(--wf-sx) - 1px); top: calc(var(--wf-rail-y) - 1px); width: calc(var(--wf-pad) - var(--wf-sx) + 1px); height: 2px; background: var(--wf-line); transform-origin: 0 50%; }
.wf-route:last-child::before {
  bottom: auto; width: calc(var(--wf-pad) - var(--wf-sx) + 1px); height: calc(var(--wf-rail-y) + 1px);
  background: none; border-left: 2px solid var(--wf-line); border-bottom: 2px solid var(--wf-line); border-bottom-left-radius: var(--r-card);
}
.wf-route:last-child::after { display: none; }

/* badge: the route's name, where the line number would be on a transit map */
.wf-badge {
  align-self: start; justify-self: stretch;
  display: inline-flex; align-items: center; justify-content: center;
  height: 28px; margin: 2px 0 0; padding: 0 10px;
  border: 1px solid var(--hairline); border-radius: var(--r-pill);
  background: var(--surface-raised);
  box-shadow: var(--wf-lift);
  font: 600 12px/1 var(--font-mono); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--brand-deep);
  white-space: nowrap;
}

/* track: the rail behind the stations */
.wf-track { position: relative; min-width: 0; }
.wf-rail {
  position: absolute; z-index: 0;
  left: -14px; top: var(--wf-rail-y);
  width: calc(100% / var(--wf-slots) * (var(--n) - 1) + (var(--wf-dot) / 2) + 14px);   /* from the badge to the last dot */
  height: 4px; margin-top: -2px;
  overflow: hidden; border-radius: var(--r-pill);
  background: var(--rail-h);
  transform-origin: 0 50%;
}
.wf-route--double .wf-rail { height: 6px; margin-top: -3px; box-sizing: border-box; border-block: 1px solid var(--rail-c); border-radius: 0; background: none; }
.wf-lit {                                       /* the signal: lit from the badge up to the station you point at */
  position: absolute; inset: 1px 0;
  background: linear-gradient(90deg, var(--wf-steel-ink), var(--brand));
  transform-origin: 0 50%; transform: scaleX(var(--reach, 0));
}
.wf-pulse {                                     /* a slow glint along the rail while the footer is on screen */
  position: absolute; inset: 0; opacity: 0;
  background: linear-gradient(90deg, transparent 0 42%, var(--surface-raised) 50%, transparent 58% 100%);
  transform: translateX(-100%);
}

/* stations: a dot on the rail, the label under it */
.wf-stations { position: relative; z-index: 1; display: grid; grid-template-columns: repeat(var(--wf-slots), minmax(0, 1fr)); margin: 0; padding: 0; list-style: none; }
.wf-stations li:nth-child(1) { --i: 0; } .wf-stations li:nth-child(2) { --i: 1; } .wf-stations li:nth-child(3) { --i: 2; } .wf-stations li:nth-child(4) { --i: 3; }
.wf-stations li:nth-child(5) { --i: 4; } .wf-stations li:nth-child(6) { --i: 5; } .wf-stations li:nth-child(7) { --i: 6; } .wf-stations li:nth-child(8) { --i: 7; }
.wf-stations a {
  position: relative; display: block;
  min-height: 52px; padding: 27px 12px 6px 0;                                 /* the right padding keeps a label that fills its slot off the next one (the narrowest slot is 117px from 1280px: "Documentation" needs 99 at 14px) */
  border-radius: var(--r-input);
  font: 400 14px/1.4 var(--font-body);
  overflow-wrap: break-word; hyphens: manual;
  transition: color var(--dur-button) var(--ease-ui);
}
.site-footer .wf-stations a { color: var(--ink-2); }                       /* colour only: it has to beat the footer-wide link colour, the box rules above stay easy to override per layout */
.wf-stations a::before {                       /* the dot */
  content: ""; position: absolute; left: 0; top: calc(var(--wf-rail-y) - var(--wf-dot) / 2);
  width: var(--wf-dot); height: var(--wf-dot); box-sizing: border-box;
  border: 2px solid var(--wf-steel-ink); border-radius: var(--r-pill);
  background: var(--surface-raised);
  box-shadow: 0 0 0 3px var(--wf-halo);
  transition: transform var(--dur-button) var(--ease-ui), background-color var(--dur-button) var(--ease-ui), border-color var(--dur-button) var(--ease-ui), box-shadow var(--dur-button) var(--ease-ui);
}
.wf-stations a::after {                        /* the ripple that leaves the dot when you point at it */
  content: ""; position: absolute; left: 0; top: calc(var(--wf-rail-y) - var(--wf-dot) / 2);
  width: var(--wf-dot); height: var(--wf-dot); box-sizing: border-box;
  border: 1.5px solid var(--brand); border-radius: var(--r-pill);
  opacity: 0; pointer-events: none;
}
.site-footer .wf-stations a:hover, .site-footer .wf-stations a:focus-visible { color: var(--brand-deep); }
.wf-stations a:hover::before, .wf-stations a:focus-visible::before { background: var(--brand); border-color: var(--brand); box-shadow: 0 0 0 5px var(--wf-halo); transform: scale(1.2); }
.wf-stations a:focus-visible { box-shadow: var(--focus-ring); }
.site-footer .wf-more a { color: var(--brand-deep); font-weight: 500; white-space: nowrap; }   /* the arrow never drops to a line of its own */
.wf-more svg { display: inline-block; width: 13px; height: 13px; margin-left: 4px; vertical-align: -1px; transition: transform var(--dur-button) var(--ease-ui); }
.wf-more a:hover svg { transform: translateX(3px); }

/* the swimmer */
.wf-traveler { position: absolute; z-index: 4; left: 0; top: 0; width: 0; height: 0; opacity: 0; pointer-events: none; }   /* footer.js parks it on the hub and shows it */
.wf-traveler.is-ready { opacity: 1; }
.wf-js:not(.is-live) .wf-traveler { opacity: 0; }                  /* she waits for the reveal */
.wf-trav-turn { position: absolute; left: -35px; top: -17px; width: 70px; height: 34px; transition: transform 220ms var(--ease-ui); }
.wf-trav-bob img { display: block; width: 100%; height: auto; }
.wf-bub {
  position: absolute; width: 7px; height: 7px; box-sizing: border-box;
  border: 1.5px solid var(--brand-sky); border-radius: var(--r-pill);
  background: var(--surface-raised);
  opacity: 0; pointer-events: none;
}

/* ---------- contact: one more route in the same grammar (a badge, a rail, a dot per item), on the full width so an address has room;
   the socials are the nodes at the end of the rail (the rail shows between them) ---------- */
.wf-reach {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: var(--wf-bw) minmax(0, 1fr); column-gap: 14px;
  margin-top: var(--wf-2); padding-top: var(--wf-3);
  border-top: 1px solid var(--hairline);
}
.wf-reach-track { position: relative; display: flex; align-items: flex-start; gap: var(--wf-3); min-width: 0; }
.wf-reach-rail {
  position: absolute; z-index: 0; left: -14px; right: 0; top: var(--wf-rail-y);
  height: 2px; margin-top: -1px; border-radius: var(--r-pill);
  background: var(--wf-steel); opacity: 0.8;
  transform-origin: 0 50%;
}
.wf-reach-list { position: relative; z-index: 1; display: flex; flex: 1 1 auto; flex-wrap: wrap; gap: 0 var(--wf-4); min-width: 0; margin: 0; padding: 0; list-style: none; }
.wf-reach-item { position: relative; min-width: 0; padding: 27px 0 6px; }
.wf-reach-item--address { max-width: 21em; }
.wf-reach-item::before {                          /* the dot, as on a route */
  content: ""; position: absolute; left: 0; top: calc(var(--wf-rail-y) - var(--wf-dot) / 2);
  width: var(--wf-dot); height: var(--wf-dot); box-sizing: border-box;
  border: 2px solid var(--wf-steel-ink); border-radius: var(--r-pill);
  background: var(--surface-raised);
  box-shadow: 0 0 0 3px var(--wf-halo);
}
.wf-reach-k { display: block; margin: 0 0 5px; font: 600 11px/1 var(--font-mono); letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-2); }
.wf-reach-v { display: block; font: 400 14px/1.5 var(--font-body); color: var(--ink-1); }
.wf-reach-v.mono { font-size: 13.5px; }
.site-footer a.wf-reach-v { width: fit-content; padding-block: 10px; margin-block: -10px; border-radius: var(--r-input); color: var(--ink-1); transition: color var(--dur-button) var(--ease-ui); }   /* the box grows, the line does not: a 40px target */
.site-footer a.wf-reach-v:hover { color: var(--brand-deep); text-decoration: underline; text-underline-offset: 3px; }
.wf-social { display: flex; gap: var(--wf-1); }
.wf-social a {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  border: 1px solid var(--hairline); border-radius: var(--r-pill);
  background: var(--surface-raised);
  box-shadow: var(--wf-lift);
  color: var(--ink-2);
  transition: color var(--dur-button) var(--ease-ui), border-color var(--dur-button) var(--ease-ui), transform var(--dur-button) var(--ease-ui);
}
.wf-social a:hover { color: var(--brand); border-color: var(--wf-steel-ink); transform: translateY(-2px); }
.wf-social svg { width: 18px; height: 18px; }

/* ---------- the end of the contact route: round nodes (the socials, then back to top) on the rail, the fine print under them ---------- */
.wf-reach-end { flex: none; display: flex; flex-direction: column; align-items: flex-end; margin-left: auto; }
.wf-reach-nodes { position: relative; z-index: 1; display: flex; gap: var(--wf-1); margin-top: calc(var(--wf-rail-y) - 22px); }
.wf-top {
  display: grid; place-items: center;
  width: 44px; height: 44px; padding: 0;
  border: 1px solid var(--hairline); border-radius: var(--r-pill);
  background: var(--surface-raised);
  box-shadow: var(--wf-lift);
  color: var(--wf-steel-ink);
  cursor: pointer;
  transition: border-color var(--dur-button) var(--ease-ui), color var(--dur-button) var(--ease-ui), transform var(--dur-button) var(--ease-ui);
}
.wf-top svg { width: 16px; height: 16px; }
.wf-top:hover { border-color: var(--wf-steel-ink); color: var(--brand-deep); transform: translateY(-2px); }
.wf-fine { position: relative; z-index: 1; margin: 6px 0 0; text-align: right; font: 400 10.5px/1.5 var(--font-mono); color: var(--ink-2); }   /* very small on purpose; --ink-2 keeps it 6:1 */
.wf-fine span:not(.wf-sep) { display: block; }
.wf-fine .wf-sep { display: none; }

/* ============================================================
   Motion (JS adds the hooks: .wf-js while the map waits below the fold, .is-live once it has been seen, .is-near while it is on screen)
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
  /* reveal: the spine draws down, the rails extend, the stations pop in; once */
  .wf-js .wf-route::before { transform: scaleY(0); }
  .wf-js .wf-route::after  { transform: scaleX(0); }
  .wf-js .wf-rail          { transform: scaleX(0); }
  .wf-js .wf-badge         { opacity: 0; transform: translateX(-10px); }
  .wf-js .wf-stations li   { opacity: 0; transform: translateY(8px) scale(0.96); }
  .wf-js .wf-hub > :not(.wf-ring) { opacity: 0; transform: translateY(10px); }
  .wf-js .wf-ring          { transform: scale(0); }
  .wf-js .wf-reach-item, .wf-js .wf-reach-nodes, .wf-js .wf-fine { opacity: 0; transform: translateY(8px); }
  .wf-js .wf-reach-rail { transform: scaleX(0); }

  .wf-js.is-live .wf-route::before { transform: none; transition: transform 480ms var(--ease-entrance) calc(var(--r, 0) * 130ms); }
  .wf-js.is-live .wf-route::after  { transform: none; transition: transform 340ms var(--ease-entrance) calc(var(--r, 0) * 130ms + 200ms); }
  .wf-js.is-live .wf-rail          { transform: none; transition: transform 760ms var(--ease-entrance) calc(var(--r, 0) * 130ms + 300ms); }
  .wf-js.is-live .wf-badge         { opacity: 1; transform: none; transition: opacity 420ms var(--ease-entrance) calc(var(--r, 0) * 130ms + 260ms), transform 420ms var(--ease-entrance) calc(var(--r, 0) * 130ms + 260ms); }
  .wf-js.is-live .wf-stations li   { opacity: 1; transform: none; transition: opacity 460ms var(--ease-entrance) calc(var(--r, 0) * 130ms + 440ms + var(--i, 0) * 70ms), transform 460ms var(--ease-entrance) calc(var(--r, 0) * 130ms + 440ms + var(--i, 0) * 70ms); }
  .wf-js.is-live .wf-hub > :not(.wf-ring) { opacity: 1; transform: none; transition: opacity 560ms var(--ease-entrance), transform 560ms var(--ease-entrance); }
  .wf-js.is-live .wf-hub-act       { transition-delay: 120ms; }
  .wf-js.is-live .wf-ring          { transform: none; transition: transform 420ms var(--ease-entrance) 240ms; }
  .wf-js.is-live .wf-reach-item, .wf-js.is-live .wf-reach-nodes, .wf-js.is-live .wf-fine { opacity: 1; transform: none; transition: opacity 520ms var(--ease-entrance) 900ms, transform 520ms var(--ease-entrance) 900ms; }
  .wf-js.is-live .wf-reach-rail { transform: none; transition: transform 760ms var(--ease-entrance) 800ms; }
  .wf-js .wf-traveler { transition: opacity 500ms var(--ease-entrance) 1100ms; }

  /* the signal along a rail, the ripple on a dot */
  .wf-lit { transition: transform 520ms var(--ease-entrance); }
  .wf-stations a:hover::after, .wf-stations a:focus-visible::after { animation: wf-ripple 760ms var(--ease-entrance); }
  @keyframes wf-ripple { from { opacity: 0.7; transform: scale(1); } to { opacity: 0; transform: scale(2.6); } }

  /* idle life, only while the footer is on screen */
  .is-near .wf-pulse { animation: wf-pulse 9s ease-in-out infinite; animation-delay: calc(var(--r, 0) * -2.3s); }
  @keyframes wf-pulse { 0% { opacity: 1; transform: translateX(-100%); } 55% { opacity: 1; transform: translateX(100%); } 56%, 100% { opacity: 0; transform: translateX(100%); } }
  .is-near .wf-trav-bob { animation: wf-bob 4.2s ease-in-out infinite; }
  .is-near .wf-traveler.is-swimming .wf-trav-bob { animation-duration: 0.7s; }
  @keyframes wf-bob { 0%, 100% { transform: translateY(0) rotate(-1deg); } 50% { transform: translateY(-3px) rotate(1.5deg); } }

  /* bubbles that leave Caretta when she arrives (footer.js sets their position) */
  .wf-bub { animation: wf-bub 1000ms var(--ease-entrance) forwards; animation-delay: calc(var(--b, 0) * 110ms); }
  @keyframes wf-bub { 0% { opacity: 0; transform: translate(0, 0) scale(0.6); } 20% { opacity: 0.95; } 100% { opacity: 0; transform: translate(var(--bx, 0px), -30px) scale(1.15); } }
}

/* ============================================================
   Responsive
   ============================================================ */

/* B: the hub becomes a strip above the same rails (the spine runs down from it) */
@media (max-width: 1279px) {
  .site-footer { --wf-bw: clamp(104px, 12vw, 120px); }
  .wf-map { display: block; }
  .wf-hub { display: flex; align-items: center; justify-content: space-between; gap: var(--wf-2) var(--wf-3); min-height: 72px; padding: 0 0 var(--wf-3) calc(var(--wf-pad) + 36px); }
  .wf-hub::after { content: ""; position: absolute; left: calc(var(--wf-sx) - 1px); top: calc((100% - var(--wf-3)) / 2); bottom: 0; width: 2px; background: var(--wf-line); }
  .wf-hub--brand { justify-content: flex-start; gap: var(--wf-3); }
  .wf-line { display: inline; }
  .wf-cta-title { max-width: 30em; }
  .wf-hub-act { margin: 0; flex: none; }
  .wf-ring { --wf-rest: 0px; left: calc(var(--wf-sx) - 9px); top: calc((100% - var(--wf-3)) / 2); }
  .wf-ring::before { right: auto; left: 100%; width: calc(var(--wf-pad) + 36px - var(--wf-sx) - 9px); }
}
@media (min-width: 1280px) {
  /* A: the first row is an elbow too, the spine runs between the first and the last rail (from 1280 px: the slots are then 117 px or more, and "Documentation" needs 106) */
  .wf-route:first-child::before {
    top: calc(var(--wf-rail-y) - 1px); width: calc(var(--wf-pad) - var(--wf-sx) + 1px);
    background: none; border-left: 2px solid var(--wf-line); border-top: 2px solid var(--wf-line); border-top-left-radius: var(--r-card);
  }
  .wf-route:first-child::after { display: none; }
}

/* C: vertical routes. The rail is drawn per station (from its dot down to the next one), so labels that wrap never break the line. */
@media (max-width: 959px) {
  .site-footer { --wf-wave: clamp(32px, 6vw, 44px); --wf-bw: 100%; }
  .wf-stage { padding-top: var(--wf-2); }
  .wf-hub { display: block; min-height: 0; padding: 0 72px var(--wf-3) 0; }
  .wf-hub::after, .wf-ring::before { display: none; }
  .wf-hub--brand { padding-right: 0; }
  .wf-hub-act { margin: var(--wf-2) 0 0; }
  .wf-ring { --wf-rest: 0px; left: auto; right: 4px; top: auto; bottom: calc(var(--wf-3) + 13px); margin-top: 0; }
  .wf-hub--brand .wf-ring { display: none; }
  .wf-routes { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--wf-2) var(--wf-3); }
  .wf-route { display: block; padding: 0; }
  .wf-route::before, .wf-route::after { display: none; }
  .wf-badge { justify-self: start; margin: 0 0 4px; }
  .wf-rail { display: none; }
  .wf-stations { display: block; }
  .wf-stations li { position: relative; }
  .wf-stations a { min-height: 44px; padding: 11px 4px 11px 28px; }
  .wf-stations a::before, .wf-stations a::after { left: 0; top: 15px; }
  .wf-stations li:not(:last-child)::before {          /* from this dot down to the next one */
    content: ""; position: absolute; left: 5px; top: 21px; width: 2px; height: 100%;
    background: var(--rail-v);
  }
  .wf-route--double .wf-stations li:not(:last-child)::before { box-sizing: border-box; border-inline: 1px solid var(--rail-c); background: none; width: 6px; left: 3px; }
  .wf-reach { display: block; }
  .wf-reach-track { display: block; }
  .wf-reach-rail, .wf-reach-item::before { display: none; }
  .wf-reach-list { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr) minmax(0, 1fr); gap: var(--wf-2) var(--wf-3); }
  .wf-reach-item { padding: 0; }
  .wf-reach-item--address { max-width: none; }
  .wf-reach-end { align-items: flex-start; margin: var(--wf-2) 0 0; }
  .wf-reach-nodes { margin-top: 0; }
  .wf-fine { margin-top: var(--wf-2); text-align: left; }
  .wf-fine span:not(.wf-sep), .wf-fine .wf-sep { display: inline; }
  .wf-reach .wf-badge { margin-bottom: 14px; }
}
@media (max-width: 699px) {
  .wf-routes { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--wf-3) var(--wf-2); }
  .wf-reach-list { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .wf-reach-item--address { grid-column: 1 / -1; }
  .wf-word { bottom: -4%; }
}
@media (max-width: 359px) {
  .wf-stations a { padding-left: 22px; padding-right: 0; font-size: 14px; overflow-wrap: anywhere; }
}

@media (prefers-reduced-motion: reduce) {
  .site-footer *, .site-footer *::before, .site-footer *::after { transition: none !important; animation: none !important; }
  .wf-wave-back, .wf-wave-front { transform: none; }
  .wf-pulse, .wf-bub { display: none; }
}
@media (forced-colors: active) {
  .wf-badge, .wf-social a, .wf-top, .wf-ring { border-color: CanvasText; }
  .wf-rail, .wf-lit, .wf-reach-rail, .wf-route::before, .wf-route::after, .wf-hub::after { background: CanvasText; }
  .wf-stations a::before, .wf-reach-item::before { border-color: CanvasText; background: Canvas; }
  .wf-word { display: none; }
}
@media print { .site-footer { display: none !important; } }
