/* =============================================================================
   Ageshio redesign — component & layout stylesheet
   -----------------------------------------------------------------------------
   Architecture: Elementor owns STRUCTURE + CONTENT; this file owns LAYOUT.
   Containers carry semantic BEM classes (assigned by scripts/build_homepage.py);
   the rules below own their geometry precisely. All magic values are tokens in
   :root so there is a single source of truth. No !important — specificity comes
   from scoping under the component root (e.g. .ags-hero ...).

   Naming: block .ags-hero  ·  element .ags-hero__mid  ·  standalone .ags-textlink
   ========================================================================== */

:root {
  /* ---- Palette (measured EXACT from Figma) ---- */
  --ags-red:      #8E1E00;
  --ags-black:    #111010;
  --ags-cream:    #F7F4F2;
  --ags-white:    #FFFFFF;
  --ags-hero-bg:  #151414;
  --ags-blue:     #3A78C1;

  /* ---- Spacing scale (px) ---- */
  --ags-space-1: 4px;
  --ags-space-2: 8px;
  --ags-space-3: 16px;
  --ags-space-4: 24px;
  --ags-space-5: 32px;
  --ags-space-6: 48px;
  --ags-space-7: 64px;
  --ags-space-8: 96px;

  /* ---- Content frame ---- */
  --ags-container: 1120px;
  --ags-gutter:    112px;   /* side margin at 1280 viewport */

  /* ---- SECTION SPACING (Mika's Ageshio-Web-Notes.png feedback, 2026-09-08) ----
     Two knobs, both measured off her annotated capture as RATIOS of the frame, so they
     are independent of that capture's (unrecoverable) DPR:

       outer guide  x=66 of a 3600 frame           -> 1.83% of frame width
       pad swatch   205px vs the 1916px hero band  -> 10.7% of viewport height

     --ags-edge     how far out elements sit from the section's left/right edge. This is
                    where the interior ags-c-* pages and .ags-masters ALREADY sit, which
                    is why they are the sections she did NOT mark.
     --ags-sec-pad  breathing room above AND below each section's content. Per-section,
                    so between two sections you read the sum — the "spare space between
                    sections" she asked for.

     Scope is exactly her annotations: pad on mission/grouptour/vip/exp, edge on
     grouptour/vip/exp/masters. VALUES IS DELIBERATELY UNTOUCHED — it carries zero
     annotations, and it is the only section whose design aspect (1.957) is already wider
     than a laptop viewport (1.879), which is precisely why she left it alone. */

  /* ---- REFERENCE FRAME (ultrawide pass, 2026-09-08) ---------------------------------
     THE PROBLEM. Above ~1440 every value that SIZES content froze (px caps on the cqw
     fonts, the root rem's 22px ceiling, min(14cqw,200px) on the GT photo) while section
     heights kept growing with the viewport. At 3440x1440 that left the 1440-sized
     composition sitting in a 1440-tall section: VIP's copy marooned over a ~275px void,
     the GT small photo tiny beside a 547px-tall neighbour, Values' middle sparse. It is
     ONE cause, so it gets ONE fix — patching gap-by-gap did not converge (3 attempts).

     THE MODEL (Simon's call: "keep our layout similar to how it is on 1440, we are happy
     to have more space on the sides but keep the sections filling the viewport"). Treat
     1440x766 as the design frame — itself derived from Mika's capture, whose hero is
     exactly one viewport at 3600:1916 — and make ultrawide a literal ENLARGEMENT of it.
     Every gap, photo and font then holds its 1440 proportion, sections still fill, and
     the pad stays consistent by construction.

     TWO scales, deliberately separate, because welding them is what broke earlier passes:

       --ags-fsc    FRAME scale. How wide the layout is. Tracks --ags-frame, which is
                    100vw until the viewport is BOTH wide and tall. The max(1920px, ...)
                    floor is what keeps every <=1920 viewport byte-identical to before:
                    the frame never gets NARROWER than it used to be, so no viewport
                    suddenly grows side margins it didn't have.

       --ags-scale  CONTENT scale. How big the content is. A contain-fit of 1440x766, so
                    it is bounded by viewport HEIGHT as well as frame width and the
                    composition can therefore never overflow a short viewport. This is
                    the one that must never be floored: flooring it would let content
                    grow past the frame on a 1024-wide screen.

       --ags-tsc    scale / fsc — how much the content is shrunk RELATIVE to its frame.
                    1 whenever width is the binding constraint. cqw tracks the FRAME, so
                    any cqw-sized font must multiply by this to track CONTENT instead;
                    without it a wide-but-short viewport (1920x900, 2560x1080) oversizes
                    the type and collides. It reduces to the OLD --gt-tsc exactly at the
                    heights that were already verified (0.914 at 1440x700, 1 at >=766
                    when width-bound), which is why the laptop range does not move.

       --ags-px     ONE REFERENCE PIXEL: 1px at 1440x766, growing with --ags-scale. Every
                    frozen px literal below becomes calc(N * var(--ags-px)) where N is
                    that value's measured 1440 size — so the number in the CSS still
                    reads as the design's own px value.

     Worked examples (frame / content scale / side space per edge):
       1280x700   1280 / 0.889 / 0        1440x766   1440 / 1.000 / 0
       1440x700   1440 / 0.914 / 0        1920x1080  1920 / 1.333 / 0
       2560x1080  2030 / 1.410 / 265      2560x1440  2560 / 1.778 / 0
       3440x900   1920 / 1.175 / 760      3440x1440  2706 / 1.880 / 367
     Type at 3440x1440 is ~1.88x its 1440 size — the deliberate freeze in docs/TYPE-SPEC.md
     is what this pass replaces, and Simon accepted that trade-off explicitly. --- */
  --ags-frame:   min(100vw, max(1920px, calc(1.8799 * 100lvh)));
  --ags-fsc:     tan(atan2(var(--ags-frame), 1440px));
  --ags-scale:   min(var(--ags-fsc), tan(atan2(100lvh, 766px)));
  --ags-tsc:     calc(var(--ags-scale) / var(--ags-fsc));
  --ags-px:      calc(var(--ags-scale) * 1px);
  --ags-edge:    calc(0.0183 * var(--ags-frame));
  /* Frame letterbox + the content inset that lands on the frame GUIDE at any width. --ags-well is 0
     below ~1920 (frame == 100vw) and opens as side margin past it, so --ags-edge-guide == --ags-edge
     at/below the reference and caps content to the frame guide on ultrawide (keeps the layout
     faithful with side space — Simon 2026-09-21). Use for HORIZONTAL insets, NOT gaps/widths. */
  --ags-well:       max(0px, calc((100vw - var(--ags-frame)) / 2));
  --ags-edge-guide: calc(var(--ags-well) + var(--ags-edge));
  /* DAMPENED HEADING SCALE (Simon 2026-09-21): display titles scaled 1.88x at 3440 read too big.
     --ags-hsc tracks --ags-scale up to ~1920 (scale 1.333) then grows at only 35% of the rate, so
     ≤1920 is UNCHANGED and ultrawide titles are ~1.52x instead of 1.88x. --ags-hpx is the matching
     "reference pixel" for TITLE font-sizes (swap --ags-px -> --ags-hpx on display headings only). */
  --ags-hsc:     calc(min(var(--ags-scale), 1.333) + max(0, var(--ags-scale) - 1.333) * 0.35);
  --ags-hpx:     calc(var(--ags-hsc) * 1px);

  /* ---- CTA PILL — ONE definition, consumed by the homepage §10 and every interior §10 ----
     Simon 2026-09-09: "take the same consistent button as the home page". They had drifted badly —
     interior measured 233x53 against the homepage's 300x32 (font 15.1 vs 14.63, vertical padding
     15.9 vs 6, and no min-width at all). Tokens rather than two copies of the numbers, so the two
     consumers cannot drift apart again; the ONLY place to change the CTA pill is here.
     The font is the Kit button global's own 0.8125rem — the interior had been overriding it. */
  --ags-cta-fs:   0.8125rem;
  --ags-cta-pad:  calc(6 * var(--ags-px)) calc(64 * var(--ags-px));
  --ags-cta-minw: calc(300 * var(--ags-px));
  /* Heading + the heading→pill gap, added 2026-09-09 when Simon asked to match the heading too. The
     interior had 51.8px/1.12 against the homepage's 33.75px/1.0 and no gap rule at all (22px vs 46).
     Same principle: one definition, two consumers. Mobile is EXCLUDED by design — both CTAs have
     their own treatment inside the ≤1023 block (the homepage's is a text link, not a pill). */
  --ags-cta-h-fs: 1.875rem;
  --ags-cta-h-lh: 1;
  --ags-cta-gap:  calc(19.15 * var(--ags-px));
  /* The heading→pill distance is TWO things stacked: the flex container's own row-gap PLUS the button
     widget's margin-top. Only the second was shared, so the total still differed (46 vs 41) — the
     interior inner sat on Elementor's default 22px against the homepage's 28. Both live here now. */
  --ags-cta-igap: 1.5556rem;
  /* The BAND itself (Simon 2026-09-09: "match the padding too"). These differed in BEHAVIOUR, not
     just value: the homepage carried a flat 90px from Elementor's per-element setting (so it never
     scaled) and got its height from min-height:48svh, while the interior used a scaled 158.4 and had
     no min-height at all — so its height was padding+content. Matching the padding alone would still
     have left the bands 313 vs 368 tall, so the min-height is shared too and both bands are now
     identical. 90 is the homepage's own value at the 1440 reference, now scaling like everything
     else — which also un-freezes it on ultrawide (it was stuck at 90 there). */
  --ags-cta-band-pad:  calc(90 * var(--ags-px));
  --ags-cta-band-minh: 48svh;
  --ags-sec-pad: 10.7svh;
  /* the box a full-screen section's content must land inside */
  --ags-stage-box: calc(100lvh - 2 * var(--ags-sec-pad));

  /* ---- Radii ---- */
  --ags-radius-sm: 8px;
  --ags-radius-btn: 6px;    /* (unused — all CTAs are pills; kept for any future rounded-rect) */
  --ags-radius-pill: 999px;

  /* Type lives in the Elementor Global Kit now (UI-editable, migrates natively) —
     sizes there are in REM. This is the ONE knob that scales the whole scale as a
     unit: 1rem resolves to 1px at the 1280 reference (16px root × rem where rem=px/16)
     and grows to ~1.375× on ultrawide. See docs/TYPE-SPEC.md + create-redesign-kit.php. */
}

/* =============================================================================
   OLD-SAFARI / iOS-12 FALLBACK for the scale engine + viewport-unit tokens (Simon 2026-09-24).
   Safari <15.4 (e.g. the 2013 iPad Air on iOS 12) lacks CSS trig (tan/atan2 — which powers
   --ags-fsc/--ags-scale) AND svh/lvh, so the raw definitions above are INVALID there → --ags-frame,
   --ags-fsc, --ags-scale and the whole --ags-px cascade collapse → "layout ruined". When trig is
   unsupported we route those roots to JS-computed equivalents that use NO trig (functions.php
   `ageshio_stable_vh` publishes --ags-frame-fb / --ags-fsc-fb / --ags-scale-fb from innerWidth/
   innerHeight, refreshed on width/orientation) and swap the svh/lvh tokens to vh. tan(atan2(a,b)) == a/b,
   so the JS scale is mathematically IDENTICAL to the CSS one. MODERN browsers support trig → they SKIP
   this whole block = zero change. Every derived token (--ags-tsc/px/edge/well/hsc/hpx/edge-guide)
   cascades from these roots, so no others need overriding here.
   ============================================================================= */
@supports not (width: tan(1deg)) {
  :root {
    --ags-frame: var(--ags-frame-fb, 100vw);
    --ags-fsc:   var(--ags-fsc-fb, 1);
    --ags-scale: var(--ags-scale-fb, 1);
    --ags-cta-band-minh: 48vh;
    --ags-sec-pad: 10.7vh;
    --ags-stage-box: calc(100vh - 2 * 10.7vh);
  }
}
html {
  /* 1.25vw == 16px at 1280px viewport → Kit rem sizes hit exact Figma px @1280;
     clamped 16→22px so type stays put below laptop and caps on ultrawide. */
  font-size: max(16px, min(1.25vw, 22px));
  font-size: clamp(16px, 1.25vw, 22px);
}
/* THE REM KNOB — reference-frame driven, site-wide (2026-09-09). Every Kit/rem-sized thing (VIP's
   whole composition, mission, values, --gt-rgap, and now the interior ags-c-* pages' type) enlarges
   in proportion instead of freezing at the old 22px ceiling. 18px is the knob's value at the 1440
   reference, so this is IDENTICAL to the clamp above wherever the frame is 100vw and >=1280
   (1512 → 18.9px, 1728 → 21.6px); it only differs where the old clamp was capping (1920: 22 → 24)
   or where content must shrink to fit a short viewport.
   The 16px floor is load-bearing: <body> shares this element with the MOBILE layout, where the frame
   scale is ~0.27 and an unfloored value would render 5px type — the floor is also why mobile came out
   byte-identical on both the homepage and the interior pages.
   (Was scoped `html:has(body.ags-redesign)` while only the homepage had been converted.) */
html {
  font-size: max(16px, calc(18 * var(--ags-px)));
}

/* =============================================================================
   SMOOTH SCROLL (Lenis) — the recommended helper CSS.
   -----------------------------------------------------------------------------
   Lenis adds `.lenis` to <html> when it's driving the scroll (see
   js/ageshio-smooth-scroll.js, enqueued front-page-only). These rules let the page
   size correctly under JS-driven scrolling and stop native CSS smooth-scroll from
   fighting Lenis on anchor jumps. Absent Lenis (reduced-motion / other pages / no-JS),
   `.lenis` is never added, so none of this applies and native scroll is untouched. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }

/* =============================================================================
   BACK TO TOP button (ageshio-totop.js) — faithful to Simon's ref (2026-09-22): a
   SLIM VERTICAL pill, bottom-right, up-arrow over "TO TOP" reading upward. Light/
   outlined (cream + maroon), fills maroon on hover; fades in past ~60% scroll. Site-wide. */
.ags-totop {
  position: fixed; right: max(16px, min(2vw, 30px)); right: clamp(16px, 2vw, 30px); bottom: max(16px, min(2vw, 30px)); bottom: clamp(16px, 2vw, 30px); z-index: 900;
  display: inline-flex; flex-direction: column; align-items: center; gap: 8px;
  width: 42px; padding: 13px 0 15px; border-radius: 999px; cursor: pointer;
  background: #F7F4F2; color: var(--e-global-color-primary, #8E1E00);
  border: 1px solid rgba(142, 30, 0, .5);
  box-shadow: 0 6px 20px rgba(20, 16, 16, .16);
  opacity: 0; visibility: hidden; transform: translateY(12px);
  transition: opacity .3s ease, transform .3s ease, visibility .3s, background .2s ease, color .2s ease, border-color .2s ease, bottom .45s cubic-bezier(.4,0,.2,1);
}
.ags-totop.is-shown { opacity: 1; visibility: visible; transform: none; }
/* HOMEPAGE only (body.ags-redesign): the mobile action bar (ENG·BOOK·☰) is pinned to the BOTTOM, so
   lift the back-to-top button clear of it — its normal corner gap ABOVE the bar (bar height = 2u pad +
   pill-h). When the bar slides away on scroll-down (body.ags-nav-hidden, set by ageshio-nav.js), drop
   the button back to the normal corner offset, timed to the bar's own .45s slide so they move together.
   Interior pages put the bar at the TOP → no collision, so this is homepage-scoped. */
@media (max-width: 1023px) {
  body.ags-redesign .ags-totop {
    /* bar height = 3.55 × the mnav knob (2u top+bottom padding + 1.55u pill height). The knob
       min(4.6vw,18px) is RE-STATED here because --mnav-u is scoped to .ags-mnav and this button is a
       body-level sibling (not a descendant), so it can't inherit that var. Lift = bar height + the
       button's normal corner gap. */
    --ags-bar-h: calc(3.55 * min(4.6vw, 18px));
    bottom: calc(var(--ags-bar-h) + max(16px, min(2vw, 30px)));
    bottom: calc(var(--ags-bar-h) + clamp(16px, 2vw, 30px));
  }
  body.ags-redesign.ags-nav-hidden .ags-totop {
    bottom: max(16px, min(2vw, 30px));
    bottom: clamp(16px, 2vw, 30px);
  }
}
.ags-totop:hover { background: var(--e-global-color-primary, #8E1E00); color: #F7F4F2; border-color: transparent; }
.ags-totop:focus-visible { outline: 2px solid var(--e-global-color-primary, #8E1E00); outline-offset: 2px; }
.ags-totop svg { display: block; }
.ags-totop__label { writing-mode: vertical-rl; transform: rotate(180deg); font-size: 9px; letter-spacing: .18em; font-weight: 700; line-height: 1; }
@media (prefers-reduced-motion: reduce) { .ags-totop { transition: opacity .01s, visibility .01s; } }

/* =============================================================================
   MOTION — reduced-motion accessibility guard (the ONE thing Elementor Pro's
   native Motion Effects don't handle). Entrance animations add `.elementor-invisible`
   (opacity:0) then reveal via JS; scrolling motion_fx uses transforms. If the user
   prefers reduced motion, force everything to its final, visible, untransformed state
   so content is NEVER stuck hidden and nothing drifts. Belt-and-suspenders: also
   neutralise the animation classes themselves. Motion authored via _animation /
   motion_fx in scripts/build_homepage.py; rationale in functions.php + the spec. --- */
/* SAFETY NET — never let a CONTAINER stay stuck hidden. Elementor's entrance-animation
   JS reliably reveals WIDGETS but is flaky on inner flex containers (it adds
   `.elementor-invisible` at render but doesn't always remove it), which once blanked the
   blue Values columns + Masters cards. So we only ever tag WIDGETS for entrance anim
   (see anim_stagger in build_homepage.py); this rule is belt-and-suspenders: any .e-con
   that still carries .elementor-invisible is forced visible so content can't vanish. */
.ags-redesign [data-elementor-type="wp-page"] .e-con.elementor-invisible {
  visibility: visible !important;
  opacity: 1 !important;
}
/* OLD SAFARI (<15, e.g. the iOS-12 iPad): Elementor's scroll-triggered entrance animations don't
   reliably remove `.elementor-invisible` from WIDGETS there, so animated headings (the Mission
   statement, etc.) stayed hidden = "blank red section". Force ALL invisible content visible with no
   motion — content over animation. Gated on aspect-ratio (Safari 15+) so MODERN browsers skip this and
   keep their fade-ins. (Mirrors the reduced-motion fallback below.) */
@supports not (aspect-ratio: 1 / 1) {
  .elementor-invisible {
    visibility: visible !important;
    opacity: 1 !important;
    animation: none !important;
    transform: none !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .elementor-invisible {
    visibility: visible !important;
    opacity: 1 !important;
    animation: none !important;
  }
  .elementor-element {
    transition: none !important;
  }
  /* kill any Elementor scrolling-effects transform on the redesign page */
  .ags-redesign [data-elementor-type="wp-page"] .elementor-motion-effects-element,
  .ags-redesign [data-elementor-type="wp-page"] .elementor-motion-effects-container {
    transform: none !important;
  }
  /* wipe: never hide the image behind the panel */
  .ags-wipe::after { display: none !important; }
}

/* =============================================================================
   IMAGE WIPE (§8 About) — the ONE effect Elementor Pro can't do natively, so it
   gets a tiny scoped bit of custom CSS + a dedicated observer in the theme footer
   (see functions.php ageshio_wipe_script). A colored panel covers the media panel,
   then sweeps off to the right when the section scrolls in — the single dramatic
   brand moment (spec M3). Panel is red (brand); scaleX 1→0 from the right edge.
   Reduced-motion / no-JS safe: the panel starts hidden and only APPEARS once JS adds
   `.ags-wipe--armed` (so if JS is off, the image is just visible, never covered). --- */
.ags-wipe { position: relative; overflow: hidden; }
/* item 3 (Simon 2026-09-22): REMOVE the homepage About slide-reveal — the red wipe panel never
   covers, so the photo simply shows on scroll-in. Scoped to the About media (the only .ags-wipe
   on the site); the ::after panel is what animated, so hiding it removes the reveal entirely. */
body.ags-redesign .ags-about__media.ags-wipe::after { display: none !important; }
.ags-wipe::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: var(--ags-red);
  transform: scaleX(0);          /* default OFF — no cover unless JS arms it */
  transform-origin: right center;
  z-index: 4;
  pointer-events: none;
}
/* JS armed the wipe (and we're not reduced-motion): panel covers the image, ready. */
.ags-wipe.ags-wipe--armed::after {
  transform: scaleX(1);
  transform-origin: left center;
}
/* Triggered on scroll-in: sweep the panel off to the right. */
.ags-wipe.ags-wipe--armed.is-wiped::after {
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform 1.15s cubic-bezier(0.76, 0, 0.24, 1);
}

/* =============================================================================
   RESPONSIVE SCOPE — desktop ≥1024 (locked) · mobile ≤1023 (real single-column layout)
   -----------------------------------------------------------------------------
   The fluid clamp()/vw/grid layout is tuned to hold from ~1024px up through ultrawide
   (3440), so it stays LOCKED to min-width:1024 — the boundary it's designed to hold to.
   The instant the viewport drops BELOW that (i.e. the desktop layout can no longer fit),
   the mobile layout takes over: a real single-column reflow covering the ENTIRE <1024 range
   (phones AND small tablets). See the MOBILE block far below +
   docs/superpowers/specs/2026-07-31-mobile-homepage-design.md.
   The old 768–1023 "coming soon" placeholder gate is RETIRED — mobile now covers that band.
   The base min-width:1024 below still applies at ≥1024 (unchanged desktop); the mobile media
   block releases it. --- */
body.elementor-page {
  min-width: 1024px;
}

/* The branded placeholder gate (ageshio_mobile_gate() in functions.php) is now DORMANT — the
   real mobile layout covers the whole <1024 range, so the gate never shows. Kept hidden (and the
   PHP kept) as a harmless fallback; safe to delete the markup + this line together later. */
.ags-mobile-gate { display: none !important; }

/* =============================================================================
   BUTTONS — single source of truth (Figma).
   -----------------------------------------------------------------------------
   btn() emits NO inline background/radius/padding in the widget JSON, so Elementor
   generates no competing per-element rule — these plain component classes win with
   NORMAL specificity (no compounding tricks). Variants: solid · invert · bar · link.
   Font is the accent global (Red Rose 700). --- */
.ags-btn .elementor-button {
  border-radius: var(--ags-radius-pill);   /* pill — all filled CTAs are pills in the reference */
  padding: 8px 34px;                        /* thin pill (~36px tall) — matches the reference */
  line-height: 1.2;
  border: none;
  transition: background-color .15s ease, color .15s ease;
}
/* All filled section CTAs are PILLS in the Figma reference (verified against s05/s06/s07/s09
   crops), so every variant keeps the base pill radius — no 6px rounded-rect. --solid = a
   content-width red pill (§3/§5/§6); --bar = a full-width red pill (§7/§9); --invert = cream
   pill (§10). */
.ags-btn--solid  .elementor-button { background-color: var(--ags-red);  color: var(--ags-cream); }
.ags-btn--solid  .elementor-button:hover { background-color: #7a1900; }
.ags-btn--invert .elementor-button { background-color: var(--ags-cream); color: var(--ags-red); }
.ags-btn--invert .elementor-button:hover { background-color: #ffffff; }
/* Full-width red pill (Experiences §7, Masters §9 CTAs span their column). */
.ags-btn--bar .elementor-button {
  background-color: var(--ags-red); color: var(--ags-cream);
  justify-content: center;
  /* Padding in `em`, not the base pill's flat 8px/34px: the label is a Kit rem font, so on the
     homepage it now grows with the reference frame while a px pad froze — the bar measured 36px
     tall at 1440 but only 54 where 68 was proportional, i.e. visibly squat on ultrawide. The
     coefficients ARE the old px values at the 1440 reference (8/14.625, 34/14.625), so nothing
     moves at any verified viewport, and this now holds on interior pages and mobile too. */
  padding: 0.547em 2.325em;
}
.ags-btn--bar .elementor-button:hover { background-color: #7a1900; }
/* Borderless underlined text link (READ OUR REVIEWS §3, MEET THE GRAND MASTERS §9). */
.ags-textlink .elementor-button {
  background-color: transparent;
  border: none;
  border-radius: 0;
  padding: 4px 0;
  color: var(--ags-red);
  -webkit-text-decoration: underline;
  text-decoration: underline;
  text-underline-offset: 4px;
}
.ags-textlink .elementor-button:hover { background-color: transparent; color: #7a1900; }

/* =============================================================================
   SITE NAVBAR — Theme Builder header (.elementor-location-header)
   -----------------------------------------------------------------------------
   FAITHFUL PORT of the old hero nav. build_header_v2.py emits the SAME markup + classes
   the hero nav used (.ags-hero__nav row · .ags-nav-logo · .ags-nav-group ×3 ·
   .ags-nav-spacer · .ags-nav-lang · .ags-nav-cta), so the original hero-nav rules below
   style it identically. Only difference from the hero: the gutter/top-offset that the
   hero STAGE used to supply is applied here on the header's own inner container (the nav
   is no longer wrapped by the hero stage). Position/hide-show is the block further down. */

/* Frame: full-bleed row, symmetric gutter. The TOP/side spacing is applied on the header's
   own wrapper container below (not here) — Elementor emits a per-element padding on
   .ags-hero__nav that zeroes any padding set on this rule, so the offset must live on the
   header wrapper which we control cleanly. */
.elementor-location-header .ags-hero__nav.e-con {
  flex-direction: row;
  align-items: flex-start;
  gap: max(24px, min(3vw, 64px));
  gap: clamp(24px, 3vw, 64px);
  width: 100%;
  box-sizing: border-box;
  padding: 0 var(--ags-hero-gutter);
}
/* Whole-navbar top spacing: push the entire bar (logo + links + CTA together) down off
   the top edge. Applied on the header's outer wrapper — the reliable hook (Elementor's
   default 10px here is replaced with a comfortable, uniform offset). */
.elementor-location-header > .e-con {
  padding-top: 20px;
  padding-bottom: max(8px, min(1vh, 14px));
  padding-bottom: clamp(8px, 1vh, 14px);   /* trimmed ~half — tighter bar, less clash with content */
}

/* Brand crest (top-left) — the red emblem SVG. Sized RELATIVE to the nav link type, not a
   fixed px: in the Figma reference the logo is ~1.76× the 3-line link-stack height, so we
   express it in `em` off the link font. Pin the wrapper's font-size to the link size (14px)
   so `em` resolves against the NAV type (the wrapper otherwise inherits 16px) — now if the
   nav font scales, the logo scales with it. 7.65em ≈ 107px @14px = 1.76× the link stack. */
.elementor-location-header .ags-nav-logo {
  flex: 0 0 auto;
  margin-right: max(24px, min(2.5vw, 56px));
  margin-right: clamp(24px, 2.5vw, 56px);
  font-size: 14px;
  /* Centre the (taller) logo on the link stack so the cloud rises ABOVE the first link and
     the base sits below it (the reference balance) instead of top-aligning + overhanging
     downward. align-self:center centres it against the row; negative block margins cancel
     its overhang so it does NOT inflate the nav-row height (which was pushing the bottom
     padding down). */
  align-self: center;
  margin-top: calc((7.65em - 3.9em) / -2);
  margin-bottom: calc((7.65em - 3.9em) / -2);   /* -(logo − link-stack)/2 ≈ -26px each side */
}
.elementor-location-header .ags-nav-logo img { width: auto; height: 7.65em; max-width: none; display: block; }

/* Nav link type is Kit-owned (nav_link: Red Rose Reg 14/-3%). Here: only layout — zero the
   <p> margin so link tops align (Events line == English+ line), and make the <a> inherit the
   widget's Kit type instead of the theme's default anchor styling. */
.elementor-location-header .ags-nav-group p,
.elementor-location-header .ags-nav-lang p { margin: 0; }
.elementor-location-header .ags-nav-group p a,
.elementor-location-header .ags-nav-lang p a { font: inherit; letter-spacing: inherit; }
.elementor-location-header .ags-nav-group .elementor-widget-container,
.elementor-location-header .ags-nav-lang .elementor-widget-container { padding: 0; }
/* CAP the nav font growth. The Kit nav_link global is 0.875rem, so it scales with the "scales
   as one unit" root knob (14px@1280 → 19px@1920) — but the navbar is CHROME/UI, not editorial
   content, so it shouldn't balloon on wide screens. clamp() lets it track the rem a little then
   freezes at 15px. Scoped under the header (0-2-1) to beat the Kit global; the <a> font:inherit
   above picks it up from the <p>. Same cap on the BOOK WITH US pill. */
.elementor-location-header .ags-nav-group p,
.elementor-location-header .ags-nav-lang p,
.elementor-location-header .ags-nav-cta .elementor-button {
  font-size: max(14px, min(0.875rem, 15px));
  font-size: clamp(14px, 0.875rem, 15px);
}
/* Large gap between the TWO LEFT columns (Figma ≈84px); base row gap stays tight elsewhere. */
.elementor-location-header .ags-nav-group + .ags-nav-group { margin-left: max(48px, min(5.5vw, 120px)); margin-left: clamp(48px, 5.5vw, 120px); }
/* Same large lead before the English+ link, so the Events/Shop column separates from the
   right cluster (English+ · BOOK WITH US) the way the two left column-pairs do. */
.elementor-location-header .ags-nav-lang { margin-left: max(48px, min(5.5vw, 120px)); margin-left: clamp(48px, 5.5vw, 120px); }
.elementor-location-header .ags-nav-group,
.elementor-location-header .ags-nav-lang { justify-content: flex-start; }

/* Each link group: tight vertical stack sized to content so nowrap links never overflow.
   width:auto (not Elementor's forced 100%) so flex-basis sizes to content; 3px row gap. */
.elementor-location-header .ags-nav-group.e-con {
  flex: 0 0 auto;
  width: auto;
  min-width: -webkit-max-content;
  min-width: -moz-max-content;
  min-width: max-content;
  gap: 3px;
  padding: 0;
}

/* Flexible spacer that pushes the right cluster (Events/Shop · English+ · BOOK WITH US)
   to the RIGHT edge — the piece the boxed template lacked (pill was clipping off-screen). */
.elementor-location-header .ags-nav-spacer.e-con { flex: 1 1 auto; min-width: var(--ags-space-4); }

/* English + : plain nav link, inline with the right cluster. */
.elementor-location-header > .ags-hero__nav .ags-nav-lang,
.elementor-location-header .ags-nav-lang { flex: 0 0 auto; align-self: flex-start; }
.elementor-location-header .ags-nav-lang p a { color: var(--ags-cream); -webkit-text-decoration: none; text-decoration: none; }

/* BOOK WITH US: full thin-long PILL. Scoped under the header (0-3-1) to beat the later
   .ags-btn--solid section-CTA rule (6px rounded-rect) on source order. */
.elementor-location-header .ags-nav-cta { flex: 0 0 auto; align-self: flex-start; }
.elementor-location-header .ags-nav-cta .elementor-button {
  padding: 3px 38px;
  line-height: 1.2;
  white-space: nowrap;
  border-radius: var(--ags-radius-pill);
}

/* =============================================================================
   NAVBAR v2 (.ags-nav2) — NEW design. Reference: figma/frames/navbar-v2/navbar-desktop.png.
   STRUCTURE (matches the reference):
     .ags-nav2 (transparent, fixed via .elementor-location-header) = a row of:
       ├─ .ags-nav2__logo  — crest + AGESHIO wordmark, OUTSIDE the pill, on the dark page bg (far left)
       └─ .ags-nav2__pill  — the floating CREAM pill, contains ONLY the nav menu:
            left group  = Tour · Retreat · Experiences▾ · Discover▾   (+ Events etc. when re-added)
            right group = English▾ · [BOOK YOUR TRIP▾ red pill]   (split anchored to the item before .ags-nav-book)
   There is NO separate button widget and NO crest inside the pill — BOOK YOUR TRIP is the
   menu item .ags-nav-book (keeps its dropdown), styled as the red pill.
   COLOURS use the Elementor GLOBAL palette vars (single source of truth — Site Settings →
   Global Colors), with the local --ags-* mirror as fallback. Loaded after Elementor's widget
   CSS so these win on source order (no !important). Supersedes the inert .ags-hero__nav block above.
   ============================================================================= */

/* --- ANIMATED MEGA-MENU (two states) -------------------------------------------------------
   DEFAULT (no hover): a floating rounded CREAM PILL holding only the links; the crest sits
   OUTSIDE it, on the page/hero background to the left (crest is NOT on cream).
   EXPANDED (hovering a dropdown parent, via :has()): the pill animates out to the PAGE EDGES,
   squaring into a full-width rectangle that now supports BOTH the crest and the links (crest
   becomes part of the cream), and the dropdown WINDOW drops below it as a connected cream panel.
   Reference: figma/frames/navbar-v2/navbar-dropdown-open.png.
   --------------------------------------------------------------------------------------------- */
.ags-nav2 {
  --ags-nav-crest: 112px;                     /* emblem size */
  --ags-nav-gutter: max(10px, min(1.4vw, 20px));
  --ags-nav-gutter: clamp(10px, 1.4vw, 20px); /* side gutter → pill reaches close to both edges */
  --ags-crest-gutter: max(-8px, min(0.1vw, 0px));
  --ags-crest-gutter: clamp(-8px, 0.1vw, 0px);/* crest shifted further LEFT (near/at the viewport edge) */
  --ags-nav-gap: max(16px, min(1.8vw, 30px));
  --ags-nav-gap: clamp(16px, 1.8vw, 30px);   /* gap crest→pill: a little room so the pill isn't crowded by the logo */
  --ags-nav-top: 20px;                        /* rest gap: viewport top → crest & pill top */
  --ags-nav-photo-w: 108px;                   /* dropdown photo size (square) */
  --ags-nav-photo-gap: 12px;                  /* gap between the two dropdown photos */
  --ags-nav-photos-w: calc(2 * var(--ags-nav-photo-w) + var(--ags-nav-photo-gap));  /* the 2-photo cluster width — the BOOK pill matches this so the photos line up directly under it (per feedback) */
  --ags-anim: .40s cubic-bezier(.22,1,.36,1);/* ONE shared timing for the whole morph (open + close) */
  /* easeOutQuad — a GENTLE decelerate. NOT an extreme ease-out (e.g. .2,.8,.2,1): those fling ~80% of
     the distance out in the first ~20% of time then crawl the final bit for the rest, which read as a
     "hesitation before going all the way out". easeOutQuad keeps a more even velocity to the edge. */
  --ags-nav-anim: .42s cubic-bezier(.25, .46, .45, .94); /* the DROPDOWN reveal (window height). */
  --ags-nav-geo: .34s cubic-bezier(.25, .46, .45, .94);  /* PILL GEOMETRY, SHORT edges (top ~20px, right ~20px) + corners — easeOutQuad. */
  --ags-nav-geo-x: .34s cubic-bezier(.25, 1, .5, 1);     /* PILL GEOMETRY, LONG left edge (margin-left+padding-left travel ~138px, the crest zone, vs 20px for the others). SAME .34s duration as the short edges so ALL edges reach the corners at the EXACT SAME instant; a more front-loaded curve (easeOutQuart) covers its 7× distance so it's visually arrived at the same moment the short edges are — no lag, no early finish. (margin-left+padding-left share this var so links stay pinned.) */
  --ags-nav-lead: .1s;                                /* stagger: on OPEN the pill geometry goes FIRST and the dropdown drops after this lead; on CLOSE the dropdown retracts first and the geometry rounds back after it. */
  /* Expanded padding = the rest insets, added back so content (links) never moves as the pill
     fills to the edges. The window uses the NEGATIVES of these to span the full viewport. */
  --ags-pad-l-open: calc(var(--ags-crest-gutter) + var(--ags-nav-crest) + var(--ags-nav-gap));
  --ags-pad-r-open: var(--ags-nav-gutter);
}

/* Header row — holds the absolute crest overlay + the pill. position:relative anchors the crest;
   the pill is the single flow child that fills the width. NO cream on the row itself — the PILL is
   the one cream shape (it morphs into the full panel), so there's no crossfade/two-shape flash. */
.ags-nav2.e-con {
  background-color: transparent;
  padding: 0;
  width: 100%;
  position: relative;
  flex-direction: row;
  align-items: stretch;
}

/* CREST — an ABSOLUTE OVERLAY (not in flow). This is REQUIRED for the Model-B morph: the pill
   must be able to fill edge-to-edge UNDER the crest on expand. The crest floats on top at the
   far left, its own fixed position, and never moves when the pill morphs. Decorative → no pointer
   events. Kill the Elementor image-widget inner inset so the crest box == the crest image. */
.ags-nav2__logo.e-con {
  position: absolute;
  left: var(--ags-crest-gutter);
  top: -10px;                              /* lifted UP a little → more space between crest & the pill */
  z-index: 5;
  width: auto;
  align-items: flex-start;
  pointer-events: none;
}
/* Home link (2026-08-16): the crest <img> is wrapped in an <a href="/">; keep the <a> layout-
   transparent (display:contents). The crest is sized DETERMINISTICALLY to --ags-nav-crest — the exact
   width the pill's left-padding already reserves for it. The old width:auto chain collapsed to 48px
   once the lazy SVG sat inside the link (flex item → widget-container → img all width:auto, no
   intrinsic width propagated), so we pin the box and let object-fit keep the ~square SVG's aspect. */
/* The crest is a home link, but its parent .ags-nav2__logo is pointer-events:none (it overlays the
   hero; the logo was decorative). Re-enable clicks on the crest itself so the <a href="/"> fires. */
.ags-nav2__crest { width: var(--ags-nav-crest); flex: 0 0 auto; pointer-events: auto; cursor: pointer; }
.ags-nav2__crest .elementor-widget-container { width: var(--ags-nav-crest); padding: 0; }
.ags-nav2__crest .elementor-widget-container > a { display: contents; }
.ags-nav2__crest img { height: var(--ags-nav-crest); width: var(--ags-nav-crest) !important; max-width: none !important; -o-object-fit: contain; object-fit: contain; display: block; }

/* The CREAM PILL — SECOND in flow, fills the remaining width. Floating rounded bar at rest (short,
   vertically centred in the taller row via the row's align-items:center). On expand it dissolves
   INTO the row's cream: bg transparent + radius 0 + right margin → 0, so the whole bar reads as one
   cream rectangle filling to both edges & the top. Timed on the shared --ags-anim. */
/* THE PILL is the single cream shape that MORPHS. At REST: a short rounded floating bar, inset by
   the top gap, the crest zone on the left, and the gutter on the right. On EXPAND (.is-mega-open):
   margins → 0 so it grows to fill the viewport top + both edges, corners square. The removed
   margins are added back as PADDING so the links never move. Its child .ags-nav2__window unfolds
   downward inside it (grid reveal) — because the window is a CHILD, its width == the pill's width
   at EVERY frame, so pill + panel morph as ONE shape. Reverse (mouse-leave removes the class) runs
   all these transitions backwards → shrinks back into the pill. ONE shared timing (--ags-anim). */
.ags-nav2__pill.e-con {
  position: relative;                        /* anchors the window child */
  background-color: var(--e-global-color-accent, var(--ags-cream));
  /* ELEVATION (review fix, 2026-09-20): the pill is cream, same as the page background on light
     interior pages (Articles, About) — with no shadow there it had zero contrast, so the nav links
     looked like bare text floating on the page instead of a floating pill. A soft, low-opacity drop
     shadow reads as elevation on ANY background: near-invisible extra lift over the maroon
     homepage hero (already high-contrast), and the thing that actually separates the pill from a
     same-colour page on Articles/About/every interior page. Two stops (tight + soft) for a natural
     "resting on the page" shadow rather than a single hard blur ring. */
  box-shadow: 0 2px 6px rgba(17,16,16,.10), 0 8px 20px rgba(17,16,16,.14);
  /* = half the ~44px pill height → an IDENTICAL fully-rounded pill at rest, but it animates COHESIVELY.
     The global --ags-radius-pill (999px) kept the corners visibly round until ~98% of the morph, so they
     squared off ~100ms AFTER the edges reached the corners — that's the "corners aren't part of the same
     animation" issue. A 22px start squares the top corners in step with the edges reaching the viewport. */
  border-radius: 22px;
  flex: 1 1 auto;
  min-width: 0;
  margin: var(--ags-nav-top) var(--ags-nav-gutter) 0 var(--ags-pad-l-open);  /* left margin reserves crest zone */
  padding: 6px 10px 6px 20px;
  align-items: center;
  /* UNIFIED BLOOM: keep the links on one row, but let the dropdown WINDOW wrap onto its OWN full-
     width line BELOW them (flex-wrap) so it's IN-FLOW — the pill's OWN height then includes it. The
     window reveals via its grid 0fr→1fr; as it grows the pill grows with it, on the SAME transition
     as the pill's margin/padding/radius → the whole cream shape morphs as ONE motion. At rest the
     window is 0-height, so this second line adds nothing and the pill looks EXACTLY as before. */
  flex-wrap: wrap;
  row-gap: 0;                /* the wrapped dropdown line must NOT inherit the pill's 20px gap: at rest that gap
                                inflated the pill (44→64px); at 0 the rest pill stays thin AND the open dropdown
                                connects directly under the links (its own padding gives the spacing). */
  /* CLOSE: everything reverts TOGETHER (no lead) — the pill geometry and the dropdown collapse on the
     same clock, so the close reads as ONE cohesive motion, not a staged one-by-one revert. */
  transition: margin-top var(--ags-nav-geo), margin-right var(--ags-nav-geo), margin-left var(--ags-nav-geo-x), padding-top var(--ags-nav-geo), padding-right var(--ags-nav-geo), padding-left var(--ags-nav-geo-x), border-radius var(--ags-nav-geo);
}
.ags-nav2.is-mega-open .ags-nav2__pill.e-con {
  margin: 0;                                 /* fills top + both edges */
  /* add the removed insets back as padding so the links + panel anchor DON'T move: margin-top
     collapses 20→0 (the white fills UP to the viewport top) while padding-top grows by the same
     amount, so the content baseline and the pill's BOTTOM edge stay fixed → no shift-up, and the
     panel (top:100%) unfolds from a stationary line in sync with the fill. */
  padding-top: calc(6px + var(--ags-nav-top));
  padding-left: calc(20px + var(--ags-pad-l-open));
  padding-right: calc(10px + var(--ags-pad-r-open));
  border-radius: 0 0 18px 18px;              /* square top (flush to viewport top/edges), rounded bottom — as before */
  box-shadow: 0 22px 34px rgba(17, 16, 16, .10);  /* the depth that was on the window, now on the whole shape */
  /* OPEN ordering: the geometry (edges + corners) goes FIRST — no delay — so the pill forms its shape
     before the dropdown drops into it (the window height below is delayed by --ags-nav-lead). The long
     left edge uses the faster --ags-nav-geo-x so it reaches the corner in step with the short edges. */
  transition: margin-top var(--ags-nav-geo), margin-right var(--ags-nav-geo), margin-left var(--ags-nav-geo-x), padding-top var(--ags-nav-geo), padding-right var(--ags-nav-geo), padding-left var(--ags-nav-geo-x), border-radius var(--ags-nav-geo);
}

/* Nav menu widget fills the pill; lay links out horizontally (the Pro widget's wrapper chain
   collapses to min-content otherwise, stacking links vertically). Force the widget + its inner
   wrappers to position:static so the full-width dropdown window resolves against .ags-nav2
   (x=0, full viewport width) — NOT the narrower widget wrapper (which offset the panel left). */
.ags-nav2__menu { flex: 1 1 auto; min-width: 0; width: 100%; position: static; }
/* ALL wrappers between the sub-menu and .ags-nav2 must be static, so the full-width dropdown
   window's containing block is .ags-nav2 (only positioned ancestor = x:0, full 1280 width). The
   nav <ul> (.elementor-nav-menu) is the sneaky one — Elementor makes it position:relative, which
   was the sub-menu's offsetParent, anchoring the panel narrow + offset. */
.ags-nav2__menu .elementor-widget-container,
.ags-nav2__menu .elementor-nav-menu--main,
.ags-nav2__menu .elementor-nav-menu--main > .elementor-nav-menu,
.ags-nav2__pill.e-con { position: static; }
.ags-nav2__menu .elementor-widget-container { width: 100%; }
.ags-nav2__menu .elementor-nav-menu--main { width: 100%; display: flex; }
.ags-nav2__menu .elementor-nav-menu--main > .elementor-nav-menu {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  gap: max(12px, min(1.6vw, 26px));
  gap: clamp(12px, 1.6vw, 26px);
  width: 100%;
  margin: 0;
  padding: 0;
}
/* Main-menu li stays STATIC so the full-width dropdown window resolves against .ags-nav2 (the
   nearest positioned ancestor = full viewport width), not the narrow <li>. */
.ags-nav2__menu .elementor-nav-menu--main > .elementor-nav-menu > li.menu-item { position: static; }

/* LEFT vs RIGHT split — the RIGHT cluster is the language switcher + BOOK pill; everything before it
   (Tour · Retreat · Experiences · Discover — plus any not-yet-ready items like Events when they get added
   back) is the LEFT cluster. Anchor the split to the item IMMEDIATELY BEFORE the permanent BOOK pill
   (:has) instead of a hand-placed .ags-nav-right-start marker. The old marker rode on "Events", so
   REMOVING Events killed the split (right cluster collapsed left) and re-adding it could double it up
   (two margin-left:autos share the free space). BOOK is always the last item and the language switcher
   always sits just before it, so this self-heals across ANY add/remove of left items — nothing to move.
   (.ags-nav-right-start on menu items is now inert/harmless; the menu scripts may still stamp it.) */
.ags-nav2__menu .elementor-nav-menu > li:has(+ li.ags-nav-book) { margin-left: auto; }
/* BOOK sticky to the pill's RIGHT edge, so its right gap matches the 10px top/bottom (per feedback). The
   flex `gap` + the margin-left:auto split leaves a phantom gap-worth of trailing space after the last
   item; pull BOOK back by exactly one gap so it sits flush (right gap → the pill's 10px padding-right). */
.ags-nav2__menu .elementor-nav-menu > li.ags-nav-book { margin-right: calc(-1 * max(12px, min(1.6vw, 26px)) + 10px); margin-right: calc(-1 * clamp(12px, 1.6vw, 26px) + 10px); margin-left: 34px; }
/* margin-right: +10px off flush → BOOK's right space is 20px, matching "Tour"'s 20px left space so the
   pill's horizontal padding reads balanced (BOOK + the photos anchored to it shift left with it).
   margin-left adds space BEFORE the BOOK pill: the flex auto-margin (on Events) absorbs it, so Events +
   English+ shift LEFT while BOOK holds its (now 20px-inset) right edge. The language dropdown list aligns
   under English+, so it shifts left too → more room between the language options and the photos. */

/* Top-level links: Red Rose BOLD (matches the reference weight — the Kit's link_bold/accent
   weight 700), RED (global 'primary') on cream. Darkens slightly on hover. */
.ags-nav2__menu .elementor-nav-menu > li.menu-item > a.elementor-item {
  font-family: "Red Rose", serif;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
  color: var(--e-global-color-primary, var(--ags-red));
  padding: 8px 2px;
  background: transparent;
  white-space: nowrap;
}
/* Darker red on HOVER only — the ACTIVE page is indicated by the underline below, NOT a colour
   change (a colour "highlight" on current-menu-item also turned the BOOK pill's cream text dark-red
   → it blended into the red pill). */
.ags-nav2__menu .elementor-nav-menu > li.menu-item:not(.ags-nav-book) > a.elementor-item:hover,
.ags-nav2__menu .elementor-nav-menu > li.menu-item:not(.ags-nav-book) > a.elementor-item:focus,
.ags-nav2__menu .elementor-nav-menu > li.menu-item:not(.ags-nav-book) > a.elementor-item:focus-visible {
  color: #7a1900;
  background: none;
}

/* ACTIVE / HOVER INDICATOR on top-level links — a red underline that grows from the centre.
   Shows on hover, and STAYS while the item's dropdown is open (li.is-open, set by ageshio-nav.js).
   Uses a box-shadow-free inline-block underline via a pseudo so it doesn't shift layout. The
   BOOK YOUR TRIP pill is excluded (it's a solid button, not a text link). */
.ags-nav2__menu .elementor-nav-menu > li.menu-item:not(.ags-nav-book) > a.elementor-item {
  position: relative;
}
.ags-nav2__menu .elementor-nav-menu > li.menu-item:not(.ags-nav-book) > a.elementor-item::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 6px;   /* close under the text (the <a> has 8px vertical padding) */
  height: 2px;
  background: var(--e-global-color-primary, var(--ags-red));
  transform: scaleX(0);
  transform-origin: center;
  transition: transform .25s cubic-bezier(.25,1,.5,1);
}
.ags-nav2__menu .elementor-nav-menu > li.menu-item:not(.ags-nav-book) > a.elementor-item:hover::before,
.ags-nav2__menu .elementor-nav-menu > li.menu-item.is-open > a.elementor-item::before,
.ags-nav2__menu .elementor-nav-menu > li.menu-item.current-menu-item:not(.ags-nav-book) > a.elementor-item::before,
.ags-nav2__menu .elementor-nav-menu > li.menu-item.current-menu-ancestor:not(.ags-nav-book) > a.elementor-item::before {
  transform: scaleX(1);   /* underline: on hover, while the dropdown is open, AND for the active page/section */
}

/* Dropdown indicator = a "+" FLUSH against the label (reference shows "Experiences+"). Elementor
   renders its caret inside a separate .sub-arrow inline box that carries its own width/spacing —
   fully HIDE it and append the "+" directly to the link (::after on the <a>) so there's no gap. */
/* Dropdown indicator: the "+" is baked INTO the menu label text (e.g. "Experiences +") by the
   menu script — the most reliable approach (Elementor's caret lives in a .sub-arrow flex child
   whose ::after replacement kept dropping to its own line). Just hide Elementor's caret icon. */
.ags-nav2__menu .elementor-nav-menu > li.menu-item-has-children > a .sub-arrow { display: none; }

/* BOOK YOUR TRIP — the .ags-nav-book menu item rendered as a THIN solid RED PILL (global
   primary bg, cream text). SINGLE LINK to the contact-forms page (no dropdown). */
.ags-nav2__menu .elementor-nav-menu > li.ags-nav-book > a.elementor-item {
  background: var(--e-global-color-primary, var(--ags-red));
  color: var(--e-global-color-accent, var(--ags-cream));
  border-radius: var(--ags-radius-pill);
  padding: 3px 30px;          /* thinner pill (reduced so the label fits the fixed width below) */
  /* Width == the dropdown photo pair, so the two photos line up directly beneath the button (per
     feedback: "extend the button length to match the pictures"). Centred label. */
  display: inline-flex; align-items: center; justify-content: center;
  box-sizing: border-box; width: var(--ags-nav-photos-w); text-align: center;
  margin-left: 6px;
  line-height: 1.25;
}
/* BOOK YOUR TRIP is a plain link (no dropdown) → no "+" after it. */
.ags-nav2__menu .elementor-nav-menu > li.ags-nav-book > a.elementor-item::after { content: none; }
.ags-nav2__menu .elementor-nav-menu > li.ags-nav-book > a.elementor-item:hover { color: var(--e-global-color-accent, var(--ags-cream)); background: #7a1900; }

/* Elementor's OWN sub-menus are HIDDEN on desktop — we render our own full-width window
   (.ags-nav2__window) via ageshio-nav.js instead (the widget JS-positions sub-menus as narrow
   per-item dropdowns that can't be forced full-width). The widget's sub-menus still drive the
   MOBILE burger menu, so only suppress them at desktop width.
   DESKTOP boundary = ≥1024 (matches body{min-width:1024} + the whole desktop layout). Below that
   the mobile burger takes over — so this desktop-only suppression must start at 1024, NOT 768,
   or the 768–1023 tablet band gets desktop treatment while the rest of the page is mobile. */
@media (min-width: 1024px) {
  .ags-nav2__menu .elementor-nav-menu--main .sub-menu { display: none !important; }
  /* Elementor's Nav Menu widget shows its burger toggle at ≤1024 (its default "tablet"
     breakpoint) — but our desktop starts AT 1024, so the burger doubled up with the horizontal
     menu across 768–1024. Hide the burger on desktop so the nav flips cleanly at the SAME
     1024/1023 line as the rest of the site. */
  .ags-nav2__menu .elementor-menu-toggle { display: none !important; }
}

/* Pill must NOT clip the window (which escapes its padding via negative offsets) at rest, but the
   window is height-collapsed then so nothing shows regardless. */
.ags-nav2__pill.e-con { overflow: visible; }

/* MEGA-MENU WINDOW — a CHILD OF THE PILL. It drops from the pill's bottom (top:100%) and spans the
   FULL viewport by using NEGATIVE left/right that cancel the pill's expanded padding (so it reaches
   x:0 → x:100vw). Because it's a pill child, its box tracks the pill as the pill morphs → they grow
   & shrink as ONE shape. GRID 0fr→1fr height-reveal (frame-accurate) on the SAME --ags-anim as the
   pill, so open AND close (reverse) are perfectly synced. Same cream as the pill = seamless. */
/* MEGA-MENU WINDOW — an IN-FLOW full-width line at the bottom of the pill (via the pill's flex-wrap),
   so the pill's OWN height includes it → pill + dropdown grow/shrink as ONE shape. Its HEIGHT animates
   (JS sets the open height to the content's natural height; rest is 0) under overflow:hidden.
   We animate HEIGHT, not grid 0fr↔1fr, because grid-fr animates 0fr→1fr (open) but SNAPS 1fr→0fr on
   close in this engine — which made the dropdown vanish abruptly. height animates BOTH ways. Content
   sits in the pill's stationary content box → revealed in place, no slide. */
.ags-nav2__window {
  --ags-win-pad-left: var(--ags-pad-l-open);          /* fallback; JS sets the per-parent value */
  flex: 0 0 100%;                                     /* full-width line below the links */
  overflow: hidden;                                   /* clip content while collapsed / collapsing */
  height: 0;                                          /* collapsed at rest → pill unaffected, no sliver */
  /* CLOSE: retracts immediately (no lead) so it collapses TOGETHER with the pill geometry → cohesive close. */
  transition: height var(--ags-nav-anim);
}
.ags-nav2.is-mega-open .ags-nav2__window {
  /* OPEN: drops AFTER the pill forms its shape (lead delay). Height is set inline by JS to the content height. */
  transition: height var(--ags-nav-anim) var(--ags-nav-lead);
}
/* The collapsing grid item — BARE (no padding/border) so grid-row 0fr shrinks it to a true 0. */
.ags-nav2__window-inner {
  overflow: hidden;
  min-height: 0;
}
/* The padded content row (list left, photos right). Padding lives HERE, not on the grid item above,
   so the item can collapse fully. Right padding aligns the photos with the BOOK YOUR TRIP button edge. */
.ags-nav2__window-pad {
  padding: 6px var(--ags-nav-window-r, 53px) 26px var(--ags-win-pad-left);
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;   /* min list↔photos gap; small so the narrow language dropdown (list under English+) doesn't force the photos to overflow past the button */
}
.ags-nav2__window-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
/* Dropdown photos: a right-aligned PAIR, matching the mobile nav corner images. Placeholder fill
   until real images are set — swap the background-image on --photo-1 / --photo-2 (also drives the
   mobile corners so both stay in sync). */
.ags-nav2__window-photos { flex: 0 0 auto; display: flex; gap: var(--ags-nav-photo-gap); align-self: flex-start; margin-top: 20px; }   /* dropped so the button→photos gap EQUALS the space above the BOOK button (~30px open): pad content top 64 + 20 → photo top 84 = book bottom 54 + 30. Consistent in every dropdown; bottom falls naturally. Right edge aligns to the BOOK button (JS sets --ags-nav-window-r). */
.ags-nav2__window-photo {
  width: var(--ags-nav-photo-w); height: var(--ags-nav-photo-w); border-radius: 10px;   /* SQUARE, matching the mobile nav corner photos */
  background: #d8d0cb center/cover no-repeat;         /* neutral placeholder (real images TBD) */
}
/* Real nav photos (shared desktop dropdown + mobile corners): tea-ceremony LEFT/TR, wood-texture
   RIGHT/BL — matches figma/frames/navbar-v2/navbar-dropdown-open (WebP under uploads redesign dir). */
.ags-nav2__window-photo--1 { background-image: url("/wp-content/uploads/redesign/nav-tea.jpg"); background-image: url("/wp-content/uploads/redesign/nav-tea.webp"); }
.ags-nav2__window-photo--2 { background-image: url("/wp-content/uploads/redesign/nav-wood.jpg"); background-image: url("/wp-content/uploads/redesign/nav-wood.webp"); }
.ags-nav2__window-list a {
  position: relative;
  display: inline-block;
  font-family: "Red Rose", serif;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
  color: var(--e-global-color-primary, var(--ags-red));
  padding: 7px 0;
  white-space: nowrap;
  -webkit-text-decoration: none;
  text-decoration: none;
  transition: color .2s ease;
}
/* HOVER/ACTIVE INDICATOR on dropdown items — an underline grows in under the label. The link does
   NOT move (no padding shift) — the underline is a pseudo positioned under it. */
.ags-nav2__window-list a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 2px;
  height: 2px;
  background: var(--e-global-color-primary, var(--ags-red));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .2s cubic-bezier(.25,1,.5,1);
}
/* Kill Elementor's default sub-item hover skin (a dark-grey rgb(63,68,75) box) — hover is indicated
   by the colour shift + the growing underline only, NO highlight box (Simon 2026-08-18). */
.ags-nav2__window-list a:hover, .ags-nav2__window-list a:focus-visible { color: #7a1900; background: none; }
.ags-nav2__window-list a.elementor-sub-item:hover,
.ags-nav2__window-list a.elementor-sub-item:focus,
.ags-nav2__window-list a.elementor-sub-item:focus-visible { background: transparent; }
.ags-nav2__window-list a:hover::after, .ags-nav2__window-list a:focus-visible::after { transform: scaleX(1); }
/* ACTIVE dropdown item = the current page under this parent. Elementor tags the cloned sub-link
   .elementor-item-active + aria-current="page" and its default skin paints a dark-grey box with
   white text. Override to the SAME red text as the other items + a persistent underline (the
   selected item is indicated by the underline, NOT a highlight box). */
.ags-nav2__window-list a.elementor-item-active,
.ags-nav2__window-list a[aria-current="page"] {
  background: none;
  color: var(--e-global-color-primary, var(--ags-red));
}
.ags-nav2__window-list a.elementor-item-active::after,
.ags-nav2__window-list a[aria-current="page"]::after { transform: scaleX(1); }
@media (max-width: 1023px) { .ags-nav2__window { display: none; } }   /* mobile (≤1023) uses the burger */

/* NOTE: hide/show on scroll (.is-hidden / .is-solid) is owned by the canonical
   .elementor-location-header block further below. No duplicate here. */

/* =============================================================================
   MOBILE NAV (≤1023) — custom .ags-mnav overlay REPLACES Elementor's default
   mobile UI. Elementor renders one Nav Menu widget (native "Primary Nav" menu);
   ageshio-nav.js reads that hidden <ul> and builds .ags-mnav. So here we HIDE
   Elementor's mobile toggle + dropdown AND the desktop pill/crest — .ags-mnav is
   the only visible nav ≤1023. The menu <ul> stays in the DOM (display:none is
   still queryable) as the data source. Desktop ≥1024 is untouched.
   Sizing = one-knob scale-as-one-unit (memory: wp-redesign-mobile-scaling-pattern):
   ONE capped-vw knob on .ags-mnav, every child dimension = calc(N * var(--knob)).
   Calibrated ONCE at the 390px reference; never per-width tweaked. --- */
@media (max-width: 1023px) {
  /* Elementor's own mobile UI + the desktop pill/crest are all hidden. */
  .ags-nav2__menu .elementor-nav-menu--main { display: none; }
  .ags-nav2__menu .elementor-menu-toggle { display: none !important; }
  .ags-nav2__menu .elementor-nav-menu--dropdown { display: none !important; }
  .ags-nav2__logo, .ags-nav2__pill { display: none; }

  /* MOBILE SAFETY — never allow the page to scroll horizontally. Translated interior pages occasionally
     picked up a stray horizontal scrollbar (an element ended up slightly wider than the viewport in some
     languages, shifting the whole page + crest). `overflow-x: clip` removes it WITHOUT creating a scroll
     container — so the fixed nav, the overlay's scroll-lock, and normal vertical scrolling all keep
     working (unlike `overflow: hidden`, and `clip` leaves overflow-y: visible untouched). Must be on
     BOTH html AND body — in Chromium a single one doesn't clip the viewport's horizontal scroll. */
  html, body { overflow-x: hidden; overflow-x: clip; }

  .ags-mnav {
    --mnav-u: min(4.6vw, 18px);          /* the ONE knob. 390px → 17.9px ≈ 18px cap */
    --mnav-pad: calc(1.1 * var(--mnav-u));
    /* Shared pill height for the three bottom-bar buttons (ENG · BOOK · burger) so they match
       EXACTLY — declared on the common ancestor so all three inherit it (memory:
       wp-redesign-mobile-scaling-pattern → shared sub-knob on the common ancestor). */
    --mnav-pill-h: calc(1.55 * var(--mnav-u));  /* ~28px @390 — thin pills, per reference */
    display: block;
    font-family: "Red Rose", serif;
  }

  /* PERSISTENT CREST — fixed top-left, ALWAYS visible: over the maroon page when the menu is
     closed, over the cream panel when open (z above the overlay so it's never covered). The menu
     has NO top bar — the only chrome is this crest + the bottom action bar. Decorative. */
  .ags-mnav__crest {
    position: fixed;
    /* MATCH the desktop crest position: desktop img sits flush at top:0, left:10px. */
    top: 0; left: calc(0.56 * var(--mnav-u));   /* ~10px @390 */
    z-index: 960;
    height: calc(6.25 * var(--mnav-u));   /* ~112px @390 — MATCHES the desktop crest size (.ags-nav2 --ags-nav-crest:112px); knob-scaled so it shrinks on smaller phones */
    width: auto; display: block;
    pointer-events: auto;   /* the crest is now a HOME link (like desktop) — tappable */
    cursor: pointer;
    transition: transform .45s cubic-bezier(.4,0,.2,1);
  }
  .ags-mnav__crest img { display: block; height: 100%; width: auto; }   /* fill the linked crest anchor */
  /* HIDE ON SCROLL (in lockstep with the desktop header, toggled by ageshio-nav.js): crest slides
     UP off the top, action bar slides DOWN off the bottom. Reveals on scroll-up. */
  .ags-mnav.is-hidden .ags-mnav__crest { transform: translateY(calc(-100% - 6px)); }
  /* THE THREE BOTTOM-BAR BUTTONS (ENG · BOOK YOUR TRIP · burger) share ONE pill treatment so they
     match exactly: same height (--mnav-pill-h), pill radius, 700 Red Rose. COLOURS FLIP with menu
     state (set in the .ags-mnav__actions block below): CLOSED = cream pill + red content; OPEN
     (.is-open) = red pill + cream content. Base geometry only here. */
  .ags-mnav__eng,
  .ags-mnav__actions .ags-mnav__lang-btn,
  .ags-mnav__actions .ags-mnav__book,
  .ags-mnav__actions .ags-mnav__burger {
    flex: 0 0 auto;
    box-sizing: border-box;
    height: var(--mnav-pill-h);
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--ags-radius-pill);
    font-family: "Red Rose", serif; font-weight: 700;
    font-size: calc(0.82 * var(--mnav-u));
    letter-spacing: 0.02em;
    -webkit-text-decoration: none;
    text-decoration: none; white-space: nowrap;
  }
  /* Widths tuned so ENG + BOOK + burger + gaps fit inside the bar's content box at 390 (they were
     overflowing the right edge under space-between). All knob-based → constant viewport fraction,
     so fitting at 390 fits every width. */
  .ags-mnav__eng { padding-left: calc(0.9 * var(--mnav-u)); padding-right: calc(0.9 * var(--mnav-u)); }
  .ags-mnav__actions .ags-mnav__book { padding-left: calc(1.5 * var(--mnav-u)); padding-right: calc(1.5 * var(--mnav-u)); }
  .ags-mnav__actions .ags-mnav__burger { width: calc(2.8 * var(--mnav-u)); padding: 0; border: 0; cursor: pointer; }

  /* Burger glyph: 3 bars via the button's <span> + its ::before/::after. currentColor so the
     bars flip with the pill's text colour (red when closed, cream when open). */
  .ags-mnav__burger { position: relative; }
  .ags-mnav__burger > span { position: relative; display: block; width: calc(1.15 * var(--mnav-u)); }
  .ags-mnav__burger > span,
  .ags-mnav__burger > span::before,
  .ags-mnav__burger > span::after {
    content: ""; height: calc(0.12 * var(--mnav-u)); min-height: 1.5px;
    background: currentColor; border-radius: 2px;
  }
  .ags-mnav__burger > span::before,
  .ags-mnav__burger > span::after {
    position: absolute; left: 0; right: 0;
  }
  /* Lines closer together (smaller glyph inside the slim pill). */
  .ags-mnav__burger > span::before { top: calc(-0.32 * var(--mnav-u)); }
  .ags-mnav__burger > span::after  { top: calc(0.32 * var(--mnav-u)); }

  /* THE FADING CREAM PANEL — full-screen, holds the corner photos + the sliding list stage. This
     is the ONLY part that shows/hides; the crest + action bar are persistent chrome ON TOP of it.
     Fade IN/OUT via opacity, gated by visibility (mirrors .ags-nav2__window): kept display:flex
     always so the opacity transition has a rendered "from" state — a display:none↔flex toggle would
     pop instead of fade. visibility snaps OFF after the fade completes (0s delay = --mnav-anim) so
     the closed panel is non-interactive and invisible; pointer-events guards taps at rest. Padding
     leaves room for the fixed crest (top) and the fixed action bar (bottom) so the centred list
     never tucks under them. Under reduced-motion the guard below zeroes the transition → instant
     show/hide. (≥1024 the whole .ags-mnav is display:none via the min-width rule.) */
  .ags-mnav__overlay {
    display: flex;
    position: fixed; top: 0; right: 0; bottom: 0; left: 0;
    z-index: 940;
    background: var(--e-global-color-accent, var(--ags-cream));
    flex-direction: column;
    /* Top/bottom padding reserve room for the fixed crest (top) and action bar (bottom). The bottom
       must also account for the ~var(--mnav-bar-h) action bar so the centred list sits in the TRUE
       optical centre of the open space (Simon: lift the links to centred) — the list was ~27px low
       because the bar height wasn't included in the bottom reserve. */
    padding: calc(5.6 * var(--mnav-u)) var(--mnav-pad) calc(4.4 * var(--mnav-u) + var(--mnav-bar-h, 4.2 * var(--mnav-u)));
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--mnav-anim), visibility 0s linear var(--mnav-anim);
  }
  .ags-mnav { --mnav-anim: .28s cubic-bezier(.22,1,.36,1); }
  .ags-mnav.is-open .ags-mnav__overlay {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity var(--mnav-anim), visibility 0s;   /* visible immediately on open */
  }
  /* Corner photos sit BEHIND the menu text (z-index:0), as decorative accents — a tall
     list (e.g. Experiences' 6 wrapped items) can reach a corner, and text must stay readable
     over the placeholder rather than being painted over by it. */
  .ags-mnav__photo {
    position: absolute; z-index: 0;
    width: calc(8.2 * var(--mnav-u)); height: calc(8.2 * var(--mnav-u));
    background: #d8d0cb center/cover no-repeat;   /* neutral placeholder fill (Simon supplies photos) */
    border-radius: calc(0.5 * var(--mnav-u));
    /* iOS WebKit: pin the corner photos to their OWN compositing layer so the panels' translateX
       slide (below) can't repaint/reposition them mid-animation — the "photo jumps out of its
       top-right spot when clicking into a submenu" on interior. translateZ(0) promotes the layer
       WITHOUT changing the top/right placement; backface-visibility:hidden keeps the layer stable. */
    transform: translateZ(0);
    -webkit-backface-visibility: hidden; backface-visibility: hidden;
  }
  /* Corner photos hug the corners (Simon ref #3/#4): TR flush top-right; BL bottom-left but it MUST
     CLEAR the fixed action bar (ENG · BOOK · burger). The bar is ~var(--mnav-bar-h) tall; place the
     BL photo's bottom just above it (bar height + small gap) so it never sits over the button.
     (Was 2.4u → photo bottom overlapped the bar by ~20px.) */
  .ags-mnav__photo--tr { top: var(--mnav-pad); right: var(--mnav-pad); background-image: url("/wp-content/uploads/redesign/nav-tea.jpg"); background-image: url("/wp-content/uploads/redesign/nav-tea.webp"); }
  .ags-mnav__photo--bl { bottom: calc(var(--mnav-bar-h, 4.2 * var(--mnav-u)) + 0.1 * var(--mnav-u)); left: var(--mnav-pad); background-image: url("/wp-content/uploads/redesign/nav-wood.jpg"); background-image: url("/wp-content/uploads/redesign/nav-wood.webp"); }

  .ags-mnav__stage {
    flex: 1 1 auto; position: relative; overflow: hidden;
    display: flex;
    z-index: 1;                                   /* menu column above the corner photos */
  }
  /* Each panel scrolls INTERNALLY when its content is taller than the stage (the tallest case
     is Experiences: 6 items, several wrapping to 2 lines, on a short phone). `safe center`
     centres when it fits but falls back to top-aligned + scrollable when it overflows, instead
     of clipping the top unreachably (the classic flex-centering overflow trap). */
  .ags-mnav__panel {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    justify-content: safe center;
    overflow-y: auto;
    gap: calc(0.9 * var(--mnav-u));
    padding: calc(3.2 * var(--mnav-u)) var(--mnav-pad) calc(1.4 * var(--mnav-u));  /* top room for the pinned < Back */
    transition: transform var(--mnav-anim);
    /* iOS WebKit: keep each panel on its OWN persistent compositing layer. The sub-panel is populated
       while off-screen (translateX(100%)); without a live layer WebKit slides in a stale/blank snapshot
       (the missing sub-links) — the JS reflow-then-next-frame in ageshio-nav.js commits the new links,
       and will-change keeps the layer around so they actually paint. Also isolates each panel's repaint
       from the corner photos. */
    -webkit-backface-visibility: hidden; backface-visibility: hidden;
    will-change: transform;
  }
  /* Centre the MAIN list in the band BETWEEN the two corner photos (TR-bottom ↔ BL-top), not in the
     raw stage — the fixed action bar made the stage's optical centre ~17px low. A small bottom pad
     bump lifts the centred column up into the photo band. (Main only; the sub-panel band differs.) */
  .ags-mnav__panel--main { transform: translateX(0); padding-bottom: calc(3.3 * var(--mnav-u)); }
  .ags-mnav__panel--sub  { transform: translateX(100%); }
  .ags-mnav.is-sub .ags-mnav__panel--main { transform: translateX(-100%); }
  .ags-mnav.is-sub .ags-mnav__panel--sub  { transform: translateX(0); }
  /* < Back sits WITH the links — in the normal flex flow at the top of the centred sub-list column
     (Simon: "back button still not with the links"). Was position:absolute pinned to the panel top
     (~148px above the list). Now it's a flex child so it centres together with the list; a small
     margin separates it from the first item. */
  .ags-mnav__back {
    align-self: center;
    margin-bottom: calc(0.8 * var(--mnav-u));
    border: 0; background: transparent; cursor: pointer;
    font-family: "Red Rose", serif; font-weight: 700;
    font-size: calc(0.95 * var(--mnav-u));
    color: var(--e-global-color-primary, var(--ags-red));
  }
  .ags-mnav__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; align-items: center; gap: calc(0.9 * var(--mnav-u)); }
  .ags-mnav__list a {
    display: inline-flex; align-items: baseline;
    font-family: "Red Rose", serif; font-weight: 700;
    font-size: calc(1.7 * var(--mnav-u));
    color: var(--e-global-color-primary, var(--ags-red));
    -webkit-text-decoration: none;
    text-decoration: none; white-space: nowrap;
    line-height: 1.0;
  }
  /* Sub-panel children can be long ("Traditional Dojo Training Experience") — let them
     wrap, centered, instead of clipping the 390px stage. Main list stays nowrap (short
     labels). Width capped in knob units so short items don't stretch; tune at 390 ref.
     TIGHT line-height so a label that wraps to 2 lines reads clearly as ONE option — the
     inter-line gap must stay well below the inter-item gap (.ags-mnav__list gap). */
  .ags-mnav__panel--sub .ags-mnav__list a {
    display: block;
    white-space: normal;
    text-align: center;
    max-width: calc(17 * var(--mnav-u));   /* ~305px @390 → long items wrap to 2 lines */
    line-height: 0.95;
  }
  .ags-mnav__plus { margin-right: calc(0.2 * var(--mnav-u)); }   /* + PREFIXES the label ("+ Experiences") on mobile */

  /* PERSISTENT ACTION BAR — fixed at the BOTTOM, ALWAYS visible (over the maroon page when closed,
     over the cream panel when open). z above the overlay so it's never covered; it carries the ONE
     burger that toggles the menu, so it must stay tappable in both states. */
  .ags-mnav__actions {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 960;
    display: flex; align-items: center; justify-content: space-between;
    gap: calc(0.6 * var(--mnav-u));
    padding: calc(1.0 * var(--mnav-u)) var(--mnav-pad);
    transition: transform .45s cubic-bezier(.4,0,.2,1);
  }
  /* HIDE ON SCROLL — the bar slides DOWN off the bottom edge (crest slides up; see above). */
  .ags-mnav.is-hidden .ags-mnav__actions { transform: translateY(calc(100% + 6px)); }
  /* COLOUR STATE — all three bottom buttons flip together.
     CLOSED (default): cream pill, red content (over the maroon page). */
  .ags-mnav__actions .ags-mnav__eng,
  .ags-mnav__actions .ags-mnav__lang-btn,
  .ags-mnav__actions .ags-mnav__book,
  .ags-mnav__actions .ags-mnav__burger {
    background: var(--e-global-color-accent, var(--ags-cream));
    color: var(--e-global-color-primary, var(--ags-red));
  }
  /* OPEN: red pill, cream content (over the cream panel). */
  .ags-mnav.is-open .ags-mnav__actions .ags-mnav__eng,
  .ags-mnav.is-open .ags-mnav__actions .ags-mnav__lang-btn,
  .ags-mnav.is-open .ags-mnav__actions .ags-mnav__book,
  .ags-mnav.is-open .ags-mnav__actions .ags-mnav__burger {
    background: var(--e-global-color-primary, var(--ags-red));
    color: var(--e-global-color-accent, var(--ags-cream));
  }
  /* Mobile language switcher: current-code button (pill above) + a stack of the other languages that
     pops UP above the bar on tap. List items are always cream pills / red text (they sit over the
     maroon page or the cream panel edge). */
  .ags-mnav__lang { position: relative; flex: 0 0 auto; display: inline-flex; }
  .ags-mnav__actions .ags-mnav__lang-btn { padding-left: calc(0.9 * var(--mnav-u)); padding-right: calc(0.9 * var(--mnav-u)); gap: calc(0.32 * var(--mnav-u)); border: 0; cursor: pointer; }
  .ags-mnav__lang-caret { font-size: 0.8em; line-height: 1; }
  .ags-mnav__lang-list {
    position: absolute; bottom: calc(100% + (0.5 * var(--mnav-u))); bottom: calc(100% + calc(0.5 * var(--mnav-u))); left: 0;
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-direction: column; gap: calc(0.4 * var(--mnav-u));
    opacity: 0; visibility: hidden; transform: translateY(6px);
    transition: opacity .2s ease, transform .2s ease, visibility .2s;
  }
  .ags-mnav__lang.is-open .ags-mnav__lang-list { opacity: 1; visibility: visible; transform: none; }
  /* Interior pages pin the action bar to the TOP, so the list must open DOWNWARD (JS adds --down when
     the button is in the top half of the viewport); otherwise it pops up off-screen. */
  .ags-mnav__lang--down .ags-mnav__lang-list { top: calc(100% + (0.5 * var(--mnav-u))); top: calc(100% + calc(0.5 * var(--mnav-u))); bottom: auto; transform: translateY(-6px); }
  .ags-mnav__lang-list a {
    display: inline-flex; align-items: center; justify-content: center;
    height: var(--mnav-pill-h); padding-left: calc(0.9 * var(--mnav-u)); padding-right: calc(0.9 * var(--mnav-u));
    border-radius: var(--ags-radius-pill);
    background: var(--e-global-color-accent, var(--ags-cream));
    color: var(--e-global-color-primary, var(--ags-red));
    font-family: "Red Rose", serif; font-weight: 700; font-size: calc(0.82 * var(--mnav-u));
    letter-spacing: 0.02em; -webkit-text-decoration: none; text-decoration: none; white-space: nowrap;
  }
}
@media (max-width: 1023px) and (prefers-reduced-motion: reduce) {
  /* Kill ALL overlay/panel transitions under reduced-motion. The .is-open rule re-declares the
     overlay's opacity transition at higher specificity (0,3,0), so the guard must match that
     specificity too or the open-fade leaks through — hence the .ags-mnav[.is-open] selectors. */
  .ags-mnav .ags-mnav__overlay,
  .ags-mnav.is-open .ags-mnav__overlay,
  .ags-mnav .ags-mnav__panel { transition: none; }
}
/* .ags-mnav is desktop-hidden so it never shows ≥1024 (built on body always). Scoped to a
   min-width query — NOT a bare `.ags-mnav{display:none}` — because a bare rule after the ≤1023
   block has equal specificity and (being later in source) would win there too, hiding the bar at
   every width. The min-width gate is mutually exclusive with the ≤1023 block, so no cascade fight. */
@media (min-width: 1024px) { .ags-mnav { display: none; } }

/* =============================================================================
   INTERIOR MOBILE NAV — TOP BAR (Simon 2026-08-17, ref Image #7). Interior pages ONLY:
   body:not(.ags-redesign) = every non-homepage page (the homepage keeps its approved BOTTOM bar).
   The shared .ags-mnav__actions bar is relocated to a transparent TOP bar with the crest on the LEFT and
   the three buttons clustered on the RIGHT in the order BOOK · burger · ENG. Closed: BOOK = cream pill
   (red text), burger + ENG = dark pills (cream content). Open (cream panel): all three flip to red
   pills + cream so the cream BOOK pill isn't invisible on the cream panel. --- */
@media (max-width: 1023px) {
  /* Bar → maroon strip pinned at the TOP; pills cluster right; left space reserved for the crest. */
  body:not(.ags-redesign) .ags-mnav__actions {
    top: 0; bottom: auto;
    justify-content: flex-end; align-items: center;
    gap: calc(0.3 * var(--mnav-u) * var(--mnav-fit, 1));
    /* NO background — the pills float over the page (Simon 2026-08-17: remove the red bar). Same as the homepage bar. */
    /* tighter side padding shifts the whole cluster right → BOOK clears the full-size crest. */
    padding: calc(1.0 * var(--mnav-u)) calc(0.6 * var(--mnav-u));
  }
  /* The pills (BOOK · burger · ENG) pack to the right and must clear the FIXED left crest. Their base size
     scales with the --mnav-u (vw) knob, but that can't know the LABEL length — a longer translation (e.g.
     German/Spanish BOOK text) widens the cluster into the crest. So --mnav-fit (set by ageshio-nav.js,
     default 1) uniformly scales the whole cluster DOWN just enough to keep it clear of the crest at any
     width AND any translation. Type + BOOK/ENG padding + burger width + gap all ride the same multiplier. */
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__eng,
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__lang-btn,
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__book,
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__burger { font-size: calc(0.72 * var(--mnav-u) * var(--mnav-fit, 1)); }
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__book { padding-left: calc(0.6 * var(--mnav-u) * var(--mnav-fit, 1)); padding-right: calc(0.6 * var(--mnav-u) * var(--mnav-fit, 1)); }
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__eng,
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__lang-btn { padding-left: calc(0.6 * var(--mnav-u) * var(--mnav-fit, 1)); padding-right: calc(0.6 * var(--mnav-u) * var(--mnav-fit, 1)); }
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__burger { width: calc(2.5 * var(--mnav-u) * var(--mnav-fit, 1)); }
  /* DOM order is ENG · BOOK · burger; the mockup reads BOOK · burger · ENG left→right → reorder via flex. */
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__book   { order: 1; }
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__burger { order: 2; }
  body:not(.ags-redesign) .ags-mnav__actions .ags-mnav__eng    { order: 3; }
  /* COLOURS: keep the shared pill scheme — all three are CREAM (white) pills + RED content when closed
     (Simon 2026-08-17: NOT black; keep white+red). The shared .ags-mnav__actions rules already do this
     (closed cream/red · open red/cream), so NO per-button override here. The bar has NO background in
     either state (transparent), so the pills float over the page and the red-flipped pills pop on the
     open cream panel. */
  /* CREST → keep the FULL homepage size (Simon 2026-08-17: it must NOT shrink). Inherit the shared
     112px crest (top:0, left:~10px); only lift it above the maroon bar so it's never covered. */
  body:not(.ags-redesign) .ags-mnav__crest { z-index: 962; }
  /* HIDE-ON-SCROLL: the bar is at the top now → slide UP off the top (0,4,1 beats the shared slide-down). */
  body:not(.ags-redesign) .ags-mnav.is-hidden .ags-mnav__actions { transform: translateY(calc(-100% - 6px)); }
  /* OPEN PANEL padding: crest + bar now both sit at the top → reserve room up top, release the old
     bottom-bar reserve so the centred link list sits true-centre. */
  body:not(.ags-redesign) .ags-mnav__overlay {
    padding-top: calc(6.6 * var(--mnav-u)); padding-bottom: calc(4.4 * var(--mnav-u));
  }
  /* Corner photos on interior: ALL the chrome (crest + ENG·BOOK·☰ bar) is at the TOP, so ONLY the TR
     photo needs to dodge it — drop TR just below the bar (--mnav-photo-y ≈ 68px, clears the ~64px bar /
     ~46px pills). The BL photo has NOTHING beneath it (the bottom bar is a HOMEPAGE-only thing), so it
     HUGS the bottom-left corner at the standard --mnav-pad edge inset. Lifting BL made it look like it
     was clearing a phantom bottom bar (Simon 2026-09-24: "not far down enough — buttons are top-right").
     So NOT a symmetric pair here: each photo just clears its own real chrome. Was: TR top 6.6u (dodged
     the CREST height even though the crest is top-LEFT and never overlaps the top-RIGHT photo). */
  body:not(.ags-redesign) .ags-mnav { --mnav-photo-y: calc(3.8 * var(--mnav-u)); }
  body:not(.ags-redesign) .ags-mnav__photo--tr { top: var(--mnav-photo-y); }
  body:not(.ags-redesign) .ags-mnav__photo--bl { bottom: var(--mnav-pad); }
}

/* =============================================================================
   LINK HOVER — underline sweep (in-from-left on enter, out-to-right on leave).
   On the nav text links (.ags-nav-group / .ags-nav-lang) + borderless .ags-textlink.
   currentColor adapts to dark (nav) and light (sections). NOT on the pill. */
.elementor-location-header .ags-nav-group p a,
.elementor-location-header .ags-nav-lang p a,
.ags-textlink .elementor-button {
  position: relative;
}
.elementor-location-header .ags-nav-group p a::after,
.elementor-location-header .ags-nav-lang p a::after,
.ags-textlink .elementor-button::after {
  content: "";
  position: absolute;
  left: 0; bottom: -2px;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform .4s cubic-bezier(.25,1,.5,1);
}
.elementor-location-header .ags-nav-group p a:hover::after,
.elementor-location-header .ags-nav-lang p a:hover::after,
.ags-textlink .elementor-button:hover::after {
  transform: scaleX(1);
  transform-origin: left center;
}
/* .ags-textlink renders its own static underline — drop it so only the swept ::after shows. */
.ags-textlink .elementor-button { -webkit-text-decoration: none; text-decoration: none; }
@media (prefers-reduced-motion: reduce) {
  .elementor-location-header .ags-nav-group p a::after,
  .elementor-location-header .ags-nav-lang p a::after,
  .ags-textlink .elementor-button::after { transition: none; }
}

/* Navbar hide/show + transparent→solid states (toggled by ageshio-nav.js). */
.elementor-location-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 900;
  background: transparent;
  transition: transform .45s cubic-bezier(.4,0,.2,1), background-color .35s ease, box-shadow .35s ease;
  will-change: transform;
}
/* NO dark background on scroll — the navbar is a floating cream pill on a transparent header, so
   it stays transparent whether at rest or scrolled (dropped the old .is-solid dark fill per Simon).
   The hide-on-scroll (transform) behaviour is kept; only the colour swap is removed. */
.elementor-location-header.is-solid { background: transparent; }
/* Hide fully: the CREST overhangs the header bottom (it's taller than the 64px header + lifted up),
   so translateY(-100%) alone leaves its lower half visible. Move up by the header height PLUS a
   crest-clearance so the whole crest clears the top edge. NOTE: --ags-nav-crest lives on the child
   .ags-nav2 and can't be read here (custom props inherit DOWN only), so use a literal clearance
   (≥ the 112px crest) with the var as a hint fallback. */
.elementor-location-header.is-hidden { transform: translateY(calc(-100% - 120px)); }
@media (prefers-reduced-motion: reduce) {
  .elementor-location-header { transition: background-color .35s ease; }
  .elementor-location-header.is-hidden { transform: none; }   /* never hide under reduced-motion */
}

/* =============================================================================
   FOOTER (§11) — Theme Builder footer (build_footer_v2.py). The contact + social
   text widgets carried the theme's default `<p> { margin: ~16px 0 20px }`. The old rule
   reset only margin-BOTTOM, leaving a ~16px margin-TOP on every line → label sat far above
   its value (Phone ⇕ 098-…, Email ⇕ address) and the socials were loosely spaced, vs the
   reference where each label hugs its value + only a small gap divides the Phone/Email
   groups. Reset BOTH top+bottom to 0 so label/value pairs + socials are COMPACT (both
   desktop AND mobile — this base rule isn't media-gated). --- */
.ags-c-footer .elementor-widget-text-editor p { margin-top: 0; margin-bottom: 0; }
.ags-c-footer .elementor-widget-text-editor { line-height: 1.3; }
/* Small deliberate gap ABOVE the "Email" group so Phone-group and Email-group stay visually
   separate without the old loose spacing. The gap is an INLINE margin-top:24px on the Email
   widget's <p> (element ffb27bf8) — inline beats a plain rule, so override with !important and
   trim to 10px. Target by element id so it can't leak to the socials (an earlier :nth-of-type
   rule wrongly hit Facebook). All 7 contact lines are actually ONE wrap-column in the DOM. */
.ags-c-footer .elementor-element-ffb27bf8 p { margin-top: 10px !important; }
/* Bottom row (§11 base, both desktop + mobile): the BUDO badge column (element 5ef6d1e9) was
   ~199px wide with 10px inner padding, leaving dead space so the copyright text floated far from
   the logo. Shrink the column to hug the badge + drop its padding so copyright sits right next to
   it. (The row flex gap is already small; the width was the real spacer.) */
.ags-c-footer .elementor-element-5ef6d1e9,
.ags-c-footer .elementor-element-5ef6d1e9 > .e-con-inner {
  width: auto; max-width: none; flex: 0 0 auto;
  padding-left: 0 !important; padding-right: 0 !important;   /* beat Elementor's .e-con-inner padding */
}
/* Badge column hugs the badge image (no extra width), and the row gap is tight so the copyright
   text sits right beside the logo (Simon: "even closer"). The badge image widget carries an inline
   118px width but the visible mark is narrower — the padding removal + tight gap close the space. */
.ags-c-footer .elementor-element-5ef6d1e9 .elementor-widget-image { width: auto; }
.ags-c-footer > .e-con:nth-child(3) > .e-con-inner { gap: 2px; }

/* Footer links: ANIMATED hover underline (Simon — make it obvious they're links). The underline is
   a gradient line drawn via background-image whose width transitions 0 → 100% on hover, so it grows
   in from the left rather than snapping on. Uses background-image (NOT text-decoration) so the
   anchors' inline `text-decoration:none` can't block it; we also neutralise that inline underline on
   the email link so all footer links behave identically. Covers legal + phone + email + socials
   (every <a> inside the footer). Desktop + mobile (base rule, not media-gated). */
.ags-c-footer a {
  -webkit-text-decoration: none !important;
  text-decoration: none !important;            /* normalise — email carries an INLINE underline; !important beats it */
  background-image: linear-gradient(currentColor, currentColor);
  background-position: 0 100%;                  /* sit the line on the text baseline */
  background-repeat: no-repeat;
  background-size: 0% 1px;                       /* hidden until hover */
  transition: background-size .3s ease;
  padding-bottom: 1px;                           /* small gap between text + underline */
}
.ags-c-footer a:hover,
.ags-c-footer a:focus-visible { background-size: 100% 1px; }   /* grow the underline in */
@media (prefers-reduced-motion: reduce) {       /* no grow animation → instant underline */
  .ags-c-footer a { transition: none; }
}
/* Crest sized in the build (larger); no CSS cap. */
/* ONE capped, CENTRED content block for the whole footer (all rows share it) → on ultrawide the
   footer scales as a unit and centres (margins grow both sides), never sticking to one side.
   Simple 2-col rows (links LEFT, crest RIGHT via space-between) — no absolute positioning. */
.ags-c-footer > .e-con-inner { align-items: center; }
.ags-c-footer .e-con-boxed > .e-con-inner {
  /* 92vw binds below ~1740px viewport (small/laptop screens unchanged); the 1600 cap only
     bites on ultrawide, where it spreads the two sides (links ↔ crest) into the extra real
     estate instead of clustering them mid-canvas. */
  max-width: min(92vw, 1600px);
  margin-left: auto;
  margin-right: auto;
}

/* =============================================================================
   DESKTOP LAYOUT (≥1024) — wrapped ONCE.
   -----------------------------------------------------------------------------
   Everything below applies ONLY at desktop width. Below 1024 none of it exists, so the
   MOBILE region (further down) builds on a blank canvas — no !important, no specificity
   fights. Section background COLOURS live in Elementor's post-<id>.css (band() generator,
   no media query) so mobile keeps its colours even though these layout blocks are gated.
   ========================================================================== */

/* ---- Injected looping background <video>s (ageshio-video.js) — GLOBAL (both breakpoints) ----
   Each <video> sits inside a STATIC .ags-vid-frame. The rounded corners + overflow clip live on the
   FRAME, NEVER on the <video>: Chromium composites a border-radius'd <video> onto its own GPU layer
   and drops that layer's PAINT after the first frame (video keeps playing, but the compositor shows
   BLANK until a forced repaint) — the exact failure the sliding photo carousels hit. A static
   ancestor's clip reliably applies to composited descendants, so the frame clips and the video keeps
   painting. The frame carries per-target geometry/z-index below; the video just FILLS it. z-index:0
   keeps videos below overlays/stage/kanji unless a target lifts them (e.g. the hero card → z:2).

   filter: brightness(1) is LOAD-BEARING — do NOT remove it as a "no-op". A border-radius'd <video>
   composites onto its own GPU layer whose rounded-clip paint the compositor DROPS after the first
   frame (video keeps playing, shows blank). A `filter` forces the subtree to render into a stable
   offscreen SURFACE, so the video is rasterised INTO it (clip baked in) and can't be layer-evicted.
   This is exactly why the About video (whose parent .ags-about__photo already has a grayscale filter)
   never dropped while the hero card — with no filter in its ancestry — did. brightness(1) is visually
   identity; a filter (unlike opacity) always creates the surface and can't be optimised away. */
.ags-vid-frame { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 0; overflow: hidden; pointer-events: none; filter: brightness(1); }
.ags-vid { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; pointer-events: none; }

/* Remove the wpfront-scroll-top plugin's floating karateka "Page Top" back-to-top button site-wide —
   it's not part of the redesign (per feedback). The plugin sets display:block INLINE via JS, so an ID
   selector + !important is the only way an external stylesheet can hide it. Global (all theme pages,
   both breakpoints); was previously mobile-only. */
#wpfront-scroll-top-container { display: none !important; }

@media (min-width: 1024px) {

/* =============================================================================
   SECTION STACK — sticky "slide over" transition
   (§4 Values → §5 Group Tour → §6 VIP → §7 Experiences), CSS-only, no JS.
   -----------------------------------------------------------------------------
   The contiguous run of .ags-stack sections is wrapped in .ags-stack-wrap (built in
   scripts/build_homepage.py). Each stacked section STICKS to the top of the viewport;
   the NEXT one scrolls up and covers it (higher z-index) — sections layer like a deck
   of cards, so the seams feel seamless instead of block-after-block.

   ⚑ WHY THE WRAPPER (the fix for the old "§7 repeats" bug): position:sticky only
   releases when its CONTAINING BLOCK ends. Previously all sections shared one parent
   (the page wrapper), so the LAST sticky section (Experiences) never released — it
   stayed pinned and painted OVER Masters/CTA (the duplication you saw). Scoping sticky
   to .ags-stack-wrap makes every stacked section release together the moment the
   wrapper scrolls out, so Masters/CTA/footer render normally beneath it.

   Values keeps its SHORT Figma height (78svh) — it's the COVERED section, so Group Tour
   simply slides up over it from its bottom edge (blue→cream blends seamlessly, no gap,
   NOT enlarged). The covering sections are viewport-tall so each fully hides the one
   beneath. Requirements: no ancestor has overflow/transform (verified), and every
   stacked section is OPAQUE (solid bg). --- */
.ags-redesign [data-elementor-type="wp-page"] > .ags-stack-wrap {
  /* override the centered `> .e-con` rhythm default: this is a tall scroll region
     (~sum of its sections), not a single centered band. */
  justify-content: flex-start;
  /* Kill Elementor's default .e-con inline padding (10px, from a var() fallback in the
     plugin's frontend.min.css — not in our JSON, so it can only be overridden here, not
     removed at source). Left in, it insets every stacked section 10px and the white body
     bg shows through as thin borders down the deck. Same fix .ags-gallery already uses. */
  padding: 0;
  /* CRITICAL for the slide-over: the sticky cards MUST butt seam-to-seam. Elementor's
     container defaults to a flex-gap (20px here) which, in a deck of pinned cards, opens a
     20px strip between each card — so as one card pins and the next slides up you see "a
     little bit of the card above/below". Zero the gap so cards cover each other with no seam. */
  gap: 0;
  row-gap: 0;
}
.ags-stack-wrap > .ags-stack {
  position: static;
  /* stacked sections are no longer direct children of the page wrapper, so re-apply the
     vertical centering the `> .e-con` rhythm rule used to give them. */
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* Every deck section is viewport-tall so each fully hides the previous as it slides up
   (uniform full-screen reads best for the deck effect). The deck now runs §2→§7
   (mission→gallery→values→grouptour→vip→exp); since EVERY section is covered by the next,
   they are ALL 100svh — including mission (this is also what makes mission "fill the page"
   while pinned) and values (previously 78svh as the covered-only first section; now it
   also covers gallery, so it goes full-height too). NOTE: these heights must live HERE on
   `.ags-stack-wrap > …`, not on `[wp-page] > .ags-mission/.ags-gallery` — once wrapped,
   those sections are no longer direct children of the page wrapper so the old rules stop
   matching. */
.ags-stack-wrap > .ags-mission,
.ags-stack-wrap > .ags-gallery,
.ags-stack-wrap > .ags-values,
.ags-stack-wrap > .ags-film,
.ags-stack-wrap > .ags-grouptour,
.ags-stack-wrap > .ags-vip,
.ags-stack-wrap > .ags-exp {
  /* LARGE viewport height (lvh) — NOT dvh, NOT svh (Simon 2026-09-23, "fix for good" on iPad). A pinned
     deck card must always be AT LEAST as tall as the viewport so the next card can't peek at its bottom.
     `svh` (smallest, URL-bar-shown) leaves the card short when the bar retracts → next card peeks. `dvh`
     (dynamic) fixes the peek but RESIZES the card every time the phone/iPad URL bar shows/hides on scroll
     → the whole sticky deck "breathes"/jitters (this is what looked "completely broken" on iPad Safari).
     `lvh` is the LARGEST viewport (URL bar retracted) and is FIXED: the card is always ≥ the viewport (no
     peek) AND never resizes (no jitter). On desktop there's no retracting bar so lvh==svh==dvh → this is a
     no-op there; it only stabilizes touch devices. (Mobile ≤1023 flows normally on `svh`, see that block.) */
  min-height: 100vh;
  min-height: 100lvh;
  height: 100vh;
  height: 100lvh;
  /* Each deck section is an OPAQUE covering card pinned at top:0. It must CLIP its own
     content: if the viewport shrinks (esp. shorter) so a section's content is taller than
     its box, without this the overflow spills past the bottom edge and bleeds over the next
     section in the deck (visible when resizing mid-scroll). Clip so a card never shows
     anything outside its own frame. */
  overflow: hidden;
}
/* REST SPACERS — a SHORT (40lvh) empty block after each card except the last (inserted by
   build_homepage.py). Fixes "next card peeks when settled": each sticky card fills exactly 1
   viewport of scroll, so without this the next card starts sliding up the instant you pass the
   single flush pixel-position → wherever you stop, the next card peeks. The 40lvh spacer scrolls
   past while the pinned card (sticky top:0, opaque, higher z) keeps covering the viewport = a
   ~40%-viewport flush REST, then the next card slides over. NOT 100lvh — a full-viewport spacer
   creates a dead zone ("scroll twice to move it"); 40lvh gives rest without the dead scroll.
   The spacer is transparent + unpositioned (no z-index) so the pinned card paints above it. */
.ags-stack-wrap > .ags-stack-spacer {
  display: none;
  flex: none;
  height: 40vh;
  height: 40lvh;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  /* No sticky theatrics under reduced-motion (sections flow top-to-bottom) → collapse the rest
     spacers so there are no empty gaps between sections. */
  .ags-stack-wrap > .ags-stack-spacer { display: none; }
}
/* z-index ladder: each later section paints over the previous as it slides up. */
.ags-stack-wrap > .ags-mission   { z-index: 1; }
.ags-stack-wrap > .ags-gallery   { z-index: 2; }
.ags-stack-wrap > .ags-values    { z-index: 3; }
.ags-stack-wrap > .ags-film      { z-index: 4; }
.ags-stack-wrap > .ags-grouptour { z-index: 5; }
.ags-stack-wrap > .ags-vip       { z-index: 6; }
.ags-stack-wrap > .ags-exp       { z-index: 7; }
/* Reduced-motion: no sticky theatrics — sections flow normally, top to bottom. */
@media (prefers-reduced-motion: reduce) {
  .ags-stack-wrap > .ags-stack { position: relative; height: auto; }
}

/* =============================================================================
   §4.5 "A FILM" — deck card between Values and Group Tour (z-index 4, 100lvh group above).
   Dark editorial section: a 5-column intro row (4 paragraphs + a bold statement), a centred
   click-to-play video (poster facade → YouTube iframe via js/ageshio-film.js) flanked by
   遊ぶ/Play + 動画/Video, and a giant "A film by Ageshio Japan" title + crest at the foot.
   Reference-frame model: body type in --ags-px; the big title dampened via --ags-hpx; the video
   frame CAPPED so it stays compact (not ballooned) on ultrawide. Values are a starting cut —
   TUNE to the mockup by screenshot at integration (no runtime in this code-only pass).
   Base rule uses `.ags-stack-wrap > .ags-film` so it beats the deck's `> .ags-stack` defaults
   (display/flex/justify) on specificity+order. Desktop-only (mobile treatment = follow-up). --- */
.ags-stack-wrap > .ags-film {
  background: #0e0d0d;
  color: var(--ags-cream, #F7F4F2);
  justify-content: space-between;            /* override the deck default `center`; the three rows
                                                spread across the full 100lvh like .ags-values__card */
  padding-top: calc(var(--ags-sec-pad) * 0.6);
  padding-bottom: calc(var(--ags-sec-pad) * 0.6);  /* reduced rhythm for this section (Simon) — kept
                                                      as a fraction of the shared token so it scales */
  padding-left: var(--ags-edge-guide);
  padding-right: var(--ags-edge-guide);
  /* no explicit gap — justify-content:space-between distributes the slack between the 3 rows */
}
/* inner containers: kill Elementor's default 10px .e-con padding so bands sit on the guide */
.ags-film__intro, .ags-film__stage, .ags-film__foot { padding: 0; }
/* Band 1 — intro row: 4 paragraphs + bold statement, top-aligned. */
/* intro LAYOUT (row / top-align) is in the DB container settings; CSS sizes the 5 columns. */
.ags-film__intro { width: 100%; }
/* Narrow fixed-width columns (scaled with the font, per §4 "wrap-critical box width scaled by the
   same factor as its font") so the copy wraps like the reference (~5-6 lines) instead of 3; the
   intro's DB justify=space-between spreads them with the statement far right. */
.ags-film__p   { flex: 0 0 auto; width: calc(155 * var(--ags-px)); }
.ags-film__stmt { flex: 0 0 auto; width: calc(191 * var(--ags-px)); }
/* Kill the text-editor <p>'s default top margin so the paragraph text top aligns with the statement
   heading's top (the heading has no such margin) — "top aligned with the top-right". */
.ags-film__p .elementor-widget-container p { margin: 0; }
/* Per-language (doc §5 body.ags-lang-<code>): German/Dutch copy runs longer and wrapped to more
   lines at 165px, overflowing the 100lvh card by ~21px — widen those columns so they wrap fewer
   lines and fit. English/Japanese keep the reference wrap. */
body.ags-lang-de .ags-film__p,
body.ags-lang-nl .ags-film__p { width: calc(198 * var(--ags-px)); }
/* Band 2 — stage: a centred row [遊ぶ/Play] [video] [動画/Video] with the kanji beside the photo.
   Stage LAYOUT (row / centre / centre) is set in the Elementor container settings (DB), the way the
   rest of the site does it. The video has an EXPLICIT lvh-capped height (heights keep a lvh basis
   in the composition model) so it does NOT depend on the flex-grow chain; its WIDTH derives from
   that height via aspect-ratio. */
.ags-film__stage { position: relative; width: 100%; }   /* label↔video gap is the DB flex_gap */
.ags-film__playerwrap { flex: 0 0 auto; }
.ags-film__player {
  /* lvh basis (fits within 100lvh after 2×sec-pad + intro + foot) + scaled px cap (per §1:
     heights keep a lvh basis, only the cap is scaled). ~430 is the video's reference-frame share. */
  /* px cap is the BINDING constraint at normal viewports → the video scales with --ags-px, the same
     content scale as the columns/overlap, so the composition holds as ONE unit to ultrawide (was
     55lvh-bound → the video grew ~1.67x while text grew 1.88x, drifting). 62lvh = short-vp backstop. */
  height: min(62vh, calc(460 * var(--ags-px)));
  height: min(62lvh, calc(460 * var(--ags-px))); aspect-ratio: 4 / 3; width: auto;
  position: relative; border-radius: 14px; overflow: hidden; cursor: pointer; background: #000;
}
.ags-film__poster { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; }
/* a 16:9 video cover-fills the 4:3 frame (min-* 100% + 16:9 aspect scales it up; frame clips) */
.ags-film__iframe {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  min-width: 100%; min-height: 100%; aspect-ratio: 16 / 9; border: 0;
}
/* Play button: hidden until the video is hovered / keyboard-focused (Simon 2026-09-23). */
.ags-film__play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: calc(84 * var(--ags-px)); height: calc(84 * var(--ags-px));
  border-radius: 50%; background: rgba(0,0,0,.42); border: 2px solid rgba(255,255,255,.9);
  cursor: pointer; opacity: 0; transition: opacity .25s, background .2s;
}
.ags-film__player:hover .ags-film__play,
.ags-film__play:focus-visible { opacity: 1; }
.ags-film__player:hover .ags-film__play { background: rgba(0,0,0,.55); }
.ags-film__play-icon {
  position: absolute; left: 54%; top: 50%; transform: translate(-50%, -50%);
  width: 0; height: 0; border-style: solid;
  border-width: calc(11 * var(--ags-px)) 0 calc(11 * var(--ags-px)) calc(18 * var(--ags-px));
  border-color: transparent transparent transparent #fff;
}
.ags-film__player.is-playing .ags-film__poster,
.ags-film__player.is-playing .ags-film__play { display: none; }
/* Labels flank the photo (in-flow flex items, vertically centred via the stage's align:center).
   width:max-content shrinks the Elementor container to its text (same idiom as .ags-gt-eyebrow) so
   the kanji sit right beside the video instead of flex-filling the space and centring far out. */
.ags-film__label { flex: 0 0 auto; width: -webkit-max-content; width: -moz-max-content; width: max-content; text-align: center; position: relative; z-index: 2; }
/* Overlap a fraction of each kanji onto the video edges (the stage's DB flex_gap is 0); z-index
   above lifts them over the video. Negative margins keep the group centred (they shrink total width). */
.ags-film__label--l { margin-right: calc(-16 * var(--ags-px)); }
.ags-film__label--r { margin-left: calc(-16 * var(--ags-px)); }
/* Band 3 — foot: giant title + crest, baseline-aligned. */
.ags-film__foot { width: 100%; gap: calc(24 * var(--ags-px)); }
.ags-film__crest img { height: calc(72 * var(--ags-px)); width: auto; display: block; }
/* All film headings + paragraphs are cream on the dark bg (override the Kit's red heading colour). */
body.ags-redesign [data-elementor-type="wp-page"] .ags-film .elementor-heading-title,
body.ags-redesign [data-elementor-type="wp-page"] .ags-film .ags-film__p .elementor-widget-container { color: var(--ags-cream, #F7F4F2); }
/* Globally-typed heading/text SIZES render on .elementor-*; scope to page-type to beat the Kit
   per-element global (0,4,0) — homepage headings convention (see composition doc pre-flight). */
body.ags-redesign [data-elementor-type="wp-page"] .ags-film__p .elementor-widget-container { font-size: calc(14 * var(--ags-px)); line-height: 1.42; }
/* Statement is the Bounded display face at weight 400 — the same Bounded weight every other section
   uses (statement labels / numgrid / overlay all 400); 700 read too bold (Simon 2026-09-23). */
body.ags-redesign [data-elementor-type="wp-page"] .ags-film__stmt .elementor-heading-title { font-family: 'Bounded', sans-serif; font-weight: 400; text-align: right; line-height: 1.28; font-size: calc(18 * var(--ags-px)); }
/* Kanji + romaji follow the site's eyebrow convention (same as the map/Tour/Retreat labels):
   kanji = Shippori Mincho 600, romaji = Bounded 500, cream, no tracking/transform. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-film__kanji .elementor-heading-title { font-family: "Shippori Mincho", serif; font-weight: 600; font-size: calc(56 * var(--ags-px)); line-height: 1; }
body.ags-redesign [data-elementor-type="wp-page"] .ags-film__romaji .elementor-heading-title { font-family: var(--e-global-typography-primary-font-family, "Bounded"), sans-serif; font-weight: 500; letter-spacing: normal; text-transform: none; font-size: calc(15 * var(--ags-px)); line-height: 1.2; margin-top: calc(2 * var(--ags-px)); }
body.ags-redesign [data-elementor-type="wp-page"] .ags-film__title .elementor-heading-title { font-size: calc(68 * var(--ags-hpx)); line-height: .96; margin: 0; white-space: nowrap; text-align: left; font-family: var(--e-global-typography-primary-font-family, inherit); }

/* SPACING SCALE — reference-frame scaled (ultrawide pass). The :root values are flat px, so
   every gap built on them froze while the sections grew. The numbers are unchanged (each is the
   token's own measured value at the 1440 reference); they just travel with --ags-scale now.
   Scoped to body.ags-redesign INSIDE the desktop block so interior ags-c-* pages and the whole
   mobile layout keep the flat px until their own pass. */
body.ags-redesign {
  --ags-space-1: calc(4 * var(--ags-px));
  --ags-space-2: calc(8 * var(--ags-px));
  --ags-space-3: calc(16 * var(--ags-px));
  --ags-space-4: calc(24 * var(--ags-px));
  --ags-space-5: calc(32 * var(--ags-px));
  --ags-space-6: calc(48 * var(--ags-px));
  --ags-space-7: calc(64 * var(--ags-px));
  --ags-space-8: calc(96 * var(--ags-px));
}

/* =============================================================================
   SECTION RHYTHM — per-section heights that HOLD THE FIGMA PROPORTIONS
   -----------------------------------------------------------------------------
   Earlier every section was forced to a flat 100svh so only one showed on load.
   But Figma's sections are intentionally DIFFERENT heights (Desktop.png @1280:
   hero 838 · mission 730 · gallery 900 · values 654 · grouptour 946 · vip 1018 ·
   exp 932 · about 654 · masters 798 · cta 400). Flattening them created dead bands
   (worst: the 400px CTA blown up to a full screen) and lost the intended rhythm the
   scroll animations will key off.

   So: the HERO stays 100svh (the full-viewport landing + animation canvas); every
   other section's min-height = its Figma ratio × the hero (ratio = figma_h / 838),
   expressed in svh so it scales with viewport height and holds the proportion at any
   size. min-height (not height) so a section whose content runs taller than its
   Figma band still grows and scrolls — nothing ever clips. Footer is content-driven.
   --- */
.ags-redesign [data-elementor-type="wp-page"] > .e-con {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* ratio × 100svh, keyed to hero=838px @1280. Comment = Figma height that sets it.
   NOTE: the four stacked sections (values/grouptour/vip/exp) are NOT listed here — they
   live inside .ags-stack-wrap and get their heights from the SECTION STACK block above
   (Values 78svh; the covering three 100svh). Only the normal-flow sections are here. */
.ags-redesign [data-elementor-type="wp-page"] > .ags-hero      { min-height: 100vh; min-height: 100lvh; }  /* 838 — full viewport (lvh: flush as browser UI shows/hides, same as the deck cards) */
.ags-redesign [data-elementor-type="wp-page"] > .ags-mission   { min-height: 87vh; min-height: 87svh;  }  /* 730 */
.ags-redesign [data-elementor-type="wp-page"] > .ags-gallery   { min-height: 107vh; min-height: 107svh; }  /* 900 */
.ags-redesign [data-elementor-type="wp-page"] > .ags-about     { min-height: 78vh; min-height: 78svh;  }  /* 654 */
/* §8 About: consistent --ags-sec-pad breathing above+below on the content wrapper (matches the
   other sections per the composition doc; replaces the ~46px incidental pad — Simon 2026-09-22). */
.ags-redesign .ags-about__inner.e-con { padding-top: var(--ags-sec-pad); padding-bottom: var(--ags-sec-pad); box-sizing: border-box; }
.ags-redesign [data-elementor-type="wp-page"] > .ags-masters   { min-height: 100vh; min-height: 100svh; }  /* Authentic Lineage — fill exactly one viewport (Simon 2026-09-22; was 95svh Figma-proportion) */
.ags-redesign [data-elementor-type="wp-page"] > .ags-cta     { min-height: var(--ags-cta-band-minh); }  /* 400 */
/* Overrides Elementor's per-element flat 90px, which is emitted as a custom property consumed by
   `.e-con` (0,1,0) — so this page-type scope wins. Visually a no-op at <=1920 (the band is
   min-height-driven and its content is centred); it stops the pad freezing on ultrawide. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-cta { padding-top: var(--ags-cta-band-pad); padding-bottom: var(--ags-cta-band-pad); }
/* …and clear Elementor's stray per-level padding inside it, so the band's own padding is the only
   vertical one — the same cleanup the interior sections got. The homepage CTA has an extra wrapper
   layer the interior one does not (.ags-cta > .e-con-boxed > .e-con-inner) which carried 10px a side.
   NOTE this is a consistency cleanup, not a visible fix: a rect-based reading suggested the content
   sat ~26px below centre, but that was the fadeInUp entrance animation perturbing the widget rects
   (conventions §1). Measured in PAINTED pixels both bands are symmetric — 129/128 home, 130/128
   interior at 1440x766. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-cta .e-con,
body.ags-redesign [data-elementor-type="wp-page"] .ags-cta .e-con-inner { padding-top: 0; padding-bottom: 0; }

/* CTA band headline: the Bounded DISPLAY global is 60px (right for section titles) but
   the CTA headline is smaller in Figma (~30px, one line). Kit globals ignore inline
   widget size, so scope the smaller size here (in rem so it still scales as one unit).
   --fs-cta-head is unconfirmed in TYPE-SPEC — revisit with exact value. */
/* Match Elementor's per-element depth (0-4-1) with real section→block→element scoping
   (not a padding class) so this overrides the kit's display size for the CTA head. */
.ags-redesign [data-elementor-type="wp-page"] .ags-cta .ags-cta__title .elementor-heading-title {
  font-size: var(--ags-cta-h-fs);      /* ≈30px @1280 */
  line-height: var(--ags-cta-h-lh);    /* was implicit 1.0 from the Kit — stated so both CTAs match */
}
/* The CTA band's boxed inner is Elementor's flat 1120px content width. Once the headline started
   scaling that box stayed put, so "Ready to experience Okinawa" wrapped onto a second line at
   ultrawide (measured 2 lines at 3440 vs 1 at 1440). Scale the box with the type and the single
   reference line holds. 1120 is its own measured width at the 1440 frame. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-cta > .e-con > .e-con-inner {
  width: calc(1120 * var(--ags-px));
  max-width: calc(1120 * var(--ags-px));
  gap: var(--ags-cta-igap);   /* was 28px — now shared with the interior CTA */
}
/* CTA pill: THINNER (less vertical padding) + LONGER (more horizontal padding / min-width) than
   the shared .ags-btn pill. And more BREATHING SPACE between the heading and the button. */
.ags-cta .ags-btn--invert .elementor-button {
  padding: var(--ags-cta-pad);        /* thinner (6px vs 8px) + longer (64px vs 34px sides) */
  min-width: var(--ags-cta-minw);     /* a long pill even for the short label */
}
.ags-cta .ags-btn--invert { margin-top: var(--ags-cta-gap); }   /* space below the heading (19.15 = the old 2.5vh at 766) */

/* HOMEPAGE MAP BAND — its own section directly BELOW the §10 CTA, before the footer. FULL-BLEED 50/50
   (Simon 2026-09-23, Figma): the MAP fills the LEFT half edge-to-edge, a solid BLUE panel fills the RIGHT
   half with the editorial (沖縄 + "Okinawa" + line + GET DIRECTIONS), cream on blue. No grey band, no
   rounding, no gap — the two halves meet at the centre. Contact details live in the footer, so this
   column stays editorial. BASE (mobile ≤1023) stacks: map on top, blue panel below. */
.ags-home-mapband.e-con {
  padding: 0;                              /* full-bleed */
  background-color: var(--ags-blue, #3A78C1);
  box-sizing: border-box;
  overflow: hidden;                        /* safety: clip either half from spilling into the next section */
}
.ags-home-map__row.e-con {
  display: flex; flex-direction: column;   /* mobile: map over panel */
  gap: 0; width: 100%; box-sizing: border-box;
  padding: 0;   /* kill Elementor's stray .e-con 10px padding — it inset the map/panel and let the blue band show at the top/left/right */
}
/* Map half — fills its side, no rounding (full-bleed). Mobile: a 3:2 strip; desktop: fills the row height. */
.ags-home-mapframe,
.ags-home-mapframe .elementor-widget-container { width: 100%; }
.ags-home-mapframe__inner {
  position: relative; width: 100%; aspect-ratio: 3 / 2; overflow: hidden;
}
.ags-home-mapframe__inner iframe {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; border: 0; display: block;
  z-index: 1; pointer-events: auto;
}
/* Blue editorial panel — centred stack, cream on blue. */
.ags-home-map__text.e-con {
  background-color: var(--ags-blue, #3A78C1);
  text-align: center; align-items: center; justify-content: center; --flex-gap: 0px;
  padding: max(28px, min(5vw, 64px));
  padding: clamp(28px, 5vw, 64px);
}
.ags-home-map__text .elementor-widget { margin: 0; }
/* Kanji 沖縄 — Shippori Mincho, cream on blue. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-home-map__kanji .elementor-heading-title {
  font-family: "Shippori Mincho", serif; font-weight: 600;
  font-size: calc(64 * var(--ags-hpx)); line-height: 1;
  color: var(--ags-cream, #F7F4F2);
}
/* "Okinawa" label — cream, TITLE case (reference shows "Okinawa", not uppercase), clean body face. */
/* "Okinawa" label — EXACT same treatment as the Tour/Retreat/etc. romaji labels on the other pages:
   the Bounded display face, weight 500, cream, no tracking/transform (measured off the GT "Tour" label). */
body.ags-redesign [data-elementor-type="wp-page"] .ags-home-map__label .elementor-heading-title,
.ags-home-map__label .elementor-heading-title {
  font-family: var(--e-global-typography-primary-font-family, "Bounded"), sans-serif;
  font-weight: 500; letter-spacing: normal; text-transform: none;
  font-size: calc(17 * var(--ags-hpx)); line-height: 1.2;
  color: var(--ags-cream, #F7F4F2);
}
/* (The "Train where karate began" heading was removed — it isn't in the reference.) */
/* Intro paragraph — the same BODY face at LIGHT weight (300), matching the label + the reference. */
.ags-home-map__intro, .ags-home-map__intro p {
  font-family: "Red Hat Display", sans-serif !important;
  font-weight: 300; line-height: 1.6;
  font-size: calc(18 * var(--ags-hpx));
  color: var(--ags-cream, #F7F4F2);
}
/* GET DIRECTIONS — a cream PILL with blue text (reference), NOT a text link (ags-textlink dropped in DB). */
.ags-home-map__link .elementor-button {
  display: inline-flex; align-items: center; justify-content: center;
  background-color: var(--ags-cream, #F7F4F2); color: var(--ags-blue, #3A78C1);
  border: 0; border-radius: 999px;
  padding: calc(6 * var(--ags-px)) calc(44 * var(--ags-px));   /* thin, like the §10 CTA pill above */
  font-family: var(--e-global-typography-accent-font-family, "Red Rose"), sans-serif;
  font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  font-size: calc(14 * var(--ags-px)); line-height: 1; -webkit-text-decoration: none; text-decoration: none;
  transition: background-color .2s ease, color .2s ease;
}
.ags-home-map__link .elementor-button:hover { background-color: #ffffff; color: var(--ags-blue, #3A78C1); }
.ags-home-map__link .elementor-button::before,
.ags-home-map__link .elementor-button::after { content: none; }   /* no swept underline / arrow */

@media (min-width: 1024px) {
  /* Full-bleed 50/50 via CSS GRID — two equal tracks at a DEFINITE height. Grid guarantees both columns
     are exactly the same height (the row track), so no flex-stretch/percentage ambiguity. The map cell
     is filled by the iframe via absolute positioning (relative widget → absolute chain). */
  .ags-home-map__row.e-con {
    display: grid; grid-template-columns: 1fr 1fr; grid-gap: 0; gap: 0;
    height: max(500px, min(56vh, 640px));
    height: max(500px, min(56svh, 640px));
    height: max(500px, min(56vh, 640px));
    height: clamp(500px, 56vh, 640px);
    height: clamp(500px, 56svh, 640px);
  }
  /* Map cell: the widget is position:relative and its whole chain absolute-fills it → the iframe covers
     the ENTIRE left cell (top, bottom, left, right), no blue showing through. */
  .ags-home-mapframe { position: relative; min-width: 0; width: auto; margin: 0; align-self: stretch; justify-self: stretch; }
  .ags-home-mapframe .elementor-widget-container { position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: 0; padding: 0; }
  .ags-home-mapframe__inner { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: auto; height: auto; aspect-ratio: auto; }
  .ags-home-map__text.e-con { min-width: 0; align-self: stretch; }
  /* Vertical rhythm inside the editorial stack (kanji is first, no top margin). */
  .ags-home-map__label { margin-top: calc(8 * var(--ags-px)); }
  .ags-home-map__intro { margin-top: calc(22 * var(--ags-px)); width: 20em; max-width: 90%; }
  .ags-home-map__link { margin-top: calc(26 * var(--ags-px)); }
}

/* =============================================================================
   HERO (section 1)
   ========================================================================== */

/* Split two-photo background (left: water/rocks · right: cliffs), seam at viewport
   centre so the seal always sits on it. Each half is painted as its own ::before/
   ::after layer clipped to its side — that's the only way `cover` can crop two images
   to two halves (a single multi-layer background can't cover-crop per-half). */
.ags-hero {
  position: relative;
  isolation: isolate;   /* own stacking context for the bg layer */
  background-color: var(--ags-hero-bg);
  /* Full viewport height so the next (cream) section isn't visible on load — see the
     section-fills-viewport rule below. lvh (not svh) so it stays flush as the browser UI
     shows/hides, matching the deck cards. */
  --ags-hero-h: calc(var(--vh, 1vh) * 100);
  /* Symmetric edge inset. Grows a little with the viewport so the FULL-BLEED nav/H1/
     card keep a proportional margin off the edges (Figma ≈16px at 1280). */
  --ags-hero-gutter: calc(23.04 * var(--ags-px));   /* 23.04 = the old 1.6vw at the 1440 frame */
}
/* --- Background: two REAL photo-half divs (not pseudo-elements — Elementor owns
   ::before/::after on every .e-con and overrides them, which blanked the left half).
   Absolutely positioned behind the content; cover => never distorts. Halves are
   AI-upscaled to ~2560px wide (Real-ESRGAN x2) so they stay sharp to 4K. --- */
.ags-hero__bg.e-con {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 0;
  flex-direction: row; gap: 0; padding: 0;
  pointer-events: none;
}
.ags-hero__half.e-con {
  flex: 1 1 50%; min-width: 0; padding: 0;
  position: relative;                 /* containing block for the injected .ags-vid + overlay */
  background-size: cover; background-repeat: no-repeat;
  background-image: linear-gradient(rgba(17,16,16,0.30), rgba(17,16,16,0.30)), var(--half-src);
}
.ags-hero__half--left  { --half-src: url('/wp-content/uploads/redesign/hero-left-2x.jpg');  background-position: right center; }
.ags-hero__half--right { --half-src: url('/wp-content/uploads/redesign/hero-right-2x.jpg'); background-position: left center; }
/* LEFT half runs a looping water video (ageshio-video.js) at .ags-vid z-index:0 — a child
   covers the container's background paint, so the baked-in dark gradient no longer darkens it.
   Re-create the SAME dark gradient as an overlay ABOVE the video (z-index:1). The RIGHT half
   keeps its baked gradient (no video). --half-src stays as the no-JS / poster fallback. */
.ags-hero__half--left::after {
  content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  background: linear-gradient(rgba(17,16,16,0.30), rgba(17,16,16,0.30));
  z-index: 1; pointer-events: none;
}
/* content sits above the bg layer */
.ags-hero__stage.e-con { position: relative; z-index: 1; }

/* Injected looping background videos: geometry lives on .ags-vid-frame (see the GLOBAL block above
   the desktop @media). Per-target frame overrides follow each target's rules below. */

/* --- Stage: FULL-BLEED flex column (no max-width cap). Fills the hero height and
   distributes two bands: middle (seal + rotate) / bottom (H1 + card). The site navbar
   now lives in the Theme Builder header (.elementor-location-header) and floats over
   the hero — the hero no longer bakes in its own nav row. Symmetric gutter is the single
   source of the edge inset, so left-aligned groups hug the left and right-aligned
   groups hug the right at every width. --- */
.ags-hero__stage.e-con {
  width: 100%;
  box-sizing: border-box;
  min-height: var(--ags-hero-h);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;   /* H1 + card hug the bottom */
  gap: var(--ags-space-4);
  /* H1/card sit near the BOTTOM (Figma 838px: ≈20px off the bottom edge). The mid band
     (seal + rotate) is NOT distributed by this flex — it's absolutely centred in the stage
     (see .ags-hero__mid below) so it holds the SAME vertical centre it had when the hero
     still baked in its own nav (3-band space-between). Removing the nav had collapsed the
     flow to 2 bands and dropped the seal ~70px; absolute-centring the mid band restores it
     independent of how many flow bands remain. */
  padding: calc(12.26 * var(--ags-px)) var(--ags-hero-gutter) calc(18.38 * var(--ags-px));   /* the old 1.6vh / 2.4vh at 766 */
}
.ags-hero__bottom.e-con { padding: 0; }
/* Mid band (seal + rotating words): absolutely centred in the stage so its vertical centre
   is independent of the (now nav-less) flow — matching the old 3-band centre exactly.
   FULL stage width (left:0/right:0, NOT inset by the gutter) so the 3-column grid's middle
   column lands on the viewport centre (the photo seam, x640) — insetting by the gutter
   shifted the seal ~20px right. The stage padding still owns the H1/card edge inset; this
   band spans edge-to-edge like it did in flow before it was absolutely positioned. */
.ags-hero__mid.e-con {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  margin: 0;
  padding-left: var(--ags-hero-gutter);
  padding-right: var(--ags-hero-gutter);
}
/* Both bands span the full stage width; their internal alignment (below) pins
   groups to the left/right edges. */
.ags-hero__mid.e-con,
.ags-hero__bottom.e-con {
  width: 100%;
}

/* --- Middle band: 3 equal columns via CSS GRID (Elementor flex-basis %s get
   dropped; grid honours the thirds deterministically). Seal sits dead-centre on
   the photo seam; rotating text centred in the right third. --- */
.ags-hero__mid.e-con {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  align-items: center;
  grid-gap: var(--ags-space-4);
  gap: var(--ags-space-4);
}
.ags-hero__seal.e-con { align-items: center; }
.ags-hero__seal img { width: calc(120 * var(--ags-px)); height: auto; }   /* slightly larger centre seal (Figma) */

/* --- Bottom band: H1 left, card right; aligned to the stage-frame edges (the stage
   gutter owns the edge inset, so H1/card stay aligned with the nav at every width). */
.ags-hero__bottom.e-con {
  flex-direction: row;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--ags-space-6);
  width: 100%;
}
/* The card lives in .ags-hero__frame2, a SIBLING of the stage (so mobile can make it an
   independent full-screen frame). On DESKTOP, restore the original composition: pin frame2 to the
   hero's bottom-right, aligned to the same gutter the stage uses, so the card sits exactly where it
   did when it was the right item of the __bottom flex row. The H1 stays in __bottom (bottom-left).
   frame2 has no box of its own here beyond positioning the card. */
.ags-hero__frame2 {
  position: absolute;
  /* Sit the card CLOSER to the bottom-right corner (Simon): smaller inset than the stage gutter. */
  right: calc(0.5 * var(--ags-hero-gutter));
  bottom: calc(0.5 * var(--ags-hero-gutter));
  z-index: 2;   /* above the stage content, same layer the card had */
  width: auto;   /* shrink-wrap the card (override Elementor's .e-con width:100%) so `right` hugs
                    the card to the gutter instead of stretching a full-width box off-screen */
  max-width: none;
}
/* The hero frame-spacer is a mobile-only rest buffer between the two hero frames — inert on
   desktop (the desktop hero is a single screen). */
.ags-hero__frame-spacer { display: none; }
/* H1 "Okinawan Karate": width:min-content puts each word on its own line
   ("Okinawan" / "Karate") deterministically — no magic max-width. Font family/size/
   weight/tracking now come from the Global Kit "primary" style (Bounded Regular 60px
   @1280, scaling via the root knob) — this rule owns ONLY layout, not type. */
.ags-hero__h1 { flex: 0 1 auto; }
.ags-hero__h1 .elementor-heading-title {
  margin: 0;
  width: -webkit-min-content;
  width: -moz-min-content;
  width: min-content;
}
/* Desktop: shrink H1 to 3.0rem (overrides Kit global "primary" with deep scope) */
.elementor-page [data-elementor-type="wp-page"] .ags-hero__h1 .elementor-heading-title {
  font-size: 3.0rem;
}

/* --- Rotating intro text (4 lines fade in/out one at a time) --- */
/* Force the widget chain to full column width; absolute-positioned lines have no
   intrinsic width and would otherwise collapse the whole stack to 0. */
.ags-hero__rotate-col.e-con,
.ags-hero__rotate-col .elementor-widget-text-editor,
.ags-hero__rotate-col .elementor-widget-container { width: 100%; }
/* Narrow block, centred in the right third, so each message wraps to ~2 lines like
   the Figma reference (block ≈ 200px wide, y-centred with the seal). */
.ags-hero__rotate {
  position: relative;
  min-height: 4.2em;
  width: 100%;
  margin: 0;
  display: block;
}
/* DESKTOP: centre the rotating tagline on the RIGHT-HALF background photo (75vw), not the
   right grid-third (~83vw). .ags-hero__rotate-col is a direct child of the full-width,
   absolutely-centred .ags-hero__mid, so left:75% = 75% of the viewport = the right photo's
   horizontal centre; top:50% aligns it to the photo's vertical centre. Pulling the col out of
   the grid leaves the col-1 spacer + col-2 seal in place, so the seal keeps the 50vw seam. */
@media (min-width: 1024px) {
  .ags-hero__rotate-col.e-con {
    position: absolute;
    left: 75%;
    top: 50%;
    transform: translate(-50%, -50%);
    /* 180 + padding:0, not 200 with Elementor's stray 10px each side: that padding stayed frozen
       while the font scaled, so the box/font ratio drifted 5% and flipped the tagline from 3 lines
       to 2 on ultrawide. 180 is the CONTENT width this had at the 1440 reference, so the wrap is
       now identical at every width. */
    width: calc(180 * var(--ags-px));
    padding-left: 0;
    padding-right: 0;
  }
}
.ags-hero__rotate span {
  position: absolute;
  top: 0;
  right: 0;
  bottom: auto;
  left: 0;
  width: 100%;
  opacity: 0;
  text-align: center;
  animation: agsRotate 12s infinite;
}
/* Desktop tagline (overrides the Kit global with deep scope).
   Mika's feedback: it was the only piece of the page on a THIRD typeface — Red Hat Display Light,
   which nothing else uses — reading thin and shouty. Now on the brand face that the navbar and
   buttons use, at regular weight, in sentence case:
     · "Red Rose" — the navbar/button family (titles are Bounded, but that is the big display face;
       this is 14px UI-scale text, so it matches the nav, not the headings).
     · weight 400 — her "regular instead of thin" (nav/buttons sit at 700; that would be too heavy
       for four rotating lines).
     · text-transform: none — she wrote the lines in sentence case, and the DB content ALREADY is
       ("Learn directly from authentic Okinawan dojos"); the shouting was purely this transform.
     · letter-spacing: normal — the 0.06em was caps tracking, which looks loose in sentence case. */
.elementor-page [data-elementor-type="wp-page"] .ags-hero__rotate span {
  font-family: "Red Rose", serif;
  font-weight: 400;
  font-size: 0.82rem;
  line-height: 1.25;
  text-transform: none;
  letter-spacing: normal;
}
@keyframes agsRotate {
  0%, 2%    { opacity: 0; transform: translateY(8px); }
  6%, 22%   { opacity: 1; transform: translateY(0); }
  26%, 100% { opacity: 0; transform: translateY(-8px); }
}

/* =============================================================================
   HERO — AGESHIO card (bottom-right)
   -----------------------------------------------------------------------------
   A 2-region card: a text region (logo above a link cluster) beside an inset,
   rounded photo. Built as a 2-column CSS grid and sized in CONTAINER-QUERY UNITS
   (1cqw = 1% of the card's width) so the whole card scales as ONE unit and holds
   its Figma proportions at every viewport — not just at 1280.

   Figma ref @1280: card 408×210 · photo ≈49% wide · logo top-centre · links a tight
   cluster lower-centre · links Red Rose Bold 10 / lh 11 / -1%.

   DOM (emitted by scripts/build_homepage.py; wrappers are Elementor's):
     .ags-hero__card                          grid container
     ├─ .ags-hero__card-txt                   col 1 · flex column
     │  ├─ .ags-hero__card-logo   (image widget → container → img)
     │  └─ .ags-hero__card-links  (3 text-editor widgets → p > a)
     └─ .ags-hero__card-photo                 col 2 · image widget → container → img
   ========================================================================== */

/* Card. Height comes from the Figma aspect-ratio on the fluid width. The single grid
   row is `minmax(0, 1fr)` so it equals the card height AND lets its children shrink to
   fit — without this the photo's intrinsic height grew the row past the card, pushing
   the photo's rounded bottom below the clip edge. */
.ags-hero__card.e-con {
  container-type: inline-size;
  flex: 0 0 auto;
  width: calc(374.4 * var(--ags-px));   /* 374.4 = the old 26vw at the 1440 frame; the card is cqw-internal, so this scales it whole */
  aspect-ratio: 408 / 210;
  display: grid;
  grid-template-columns: 1fr 48.8%;       /* text | photo (Figma: photo ≈49%) */
  grid-template-rows: minmax(0, 1fr);     /* row = card height; children may shrink */
  padding: 0;                             /* clear Elementor's default .e-con 10px pad so
                                             the grid cells reach the card edges */
  overflow: hidden;
  border-radius: max(8px, min(0.79vw, 15px));
  border-radius: clamp(8px, 0.79vw, 15px);
}
/* iOS 12 / Safari <15 has no aspect-ratio: this card sets WIDTH and derives HEIGHT from aspect-ratio,
   so without it the height goes to content = "way too big" on the desktop layout (iPad landscape). Give
   it the explicit height (width 374.4 × 210/408 ≈ 192.7, in --ags-px). Desktop only; the ≤1023 card has
   its own @supports fallback. Modern browsers support aspect-ratio → skip this. */
@media (min-width: 1024px) {
  @supports not (aspect-ratio: 1 / 1) {
    body.ags-redesign .ags-hero__card { height: calc(192.7 * var(--ags-px)); }
  }
}

/* Let every grid/flex descendant shrink below its content size. `min-*: auto` (the
   default) is what let the photo overflow; setting it once here is the clean root fix. */
.ags-hero__card *,
.ags-hero__card { min-width: 0; min-height: 0; }

/* Elementor injects a default 10px padding on every `.e-con` widget — it was silently
   stacking on our own spacing (thick photo margin, offset links). Zero it once here; our
   own padding/gap below own all spacing. */
.ags-hero__card .e-con { padding: 0; }

/* Elementor ships `transition: transform .4s` on every container + widget wrapper. During
   the ~300ms window around first paint that overlaps page load, the card eased into place
   and the photo zoomed on reload. Nothing here animates on hover → disable transitions
   across the whole (self-contained) card subtree. */
.ags-hero__card * { transition: none !important; }
/* Re-enable transition for card links and add hover fade */
.ags-hero__card-links a { transition: opacity .25s ease !important; }
.ags-hero__card-links a:hover { opacity: .7; }

/* --- Text region (col 1): logo top, link cluster bottom. --- */
/* The logo + links are grouped with `gap`, and the whole group is centred vertically with
   auto margins — a fixed gap is predictable, whereas `space-between` gave the two items
   only a few px of slack (logo/links nearly overlapped). Both children full-width so
   centring is uniform; each centres its own contents. */
.ags-hero__card-txt {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5cqw;                              /* space between logo and links */
  padding: 7cqw 5cqw 9cqw;
  /* The visible RED space runs from the card's inner-left to the photo IMAGE edge, which
     extends past this grid cell — so centring within the cell lands ~13px left of the
     red-space centre. A small right shift re-centres the logo+links in the space the eye
     sees. transform (not padding) so the column keeps its width — narrowing it wraps the
     links and caps the logo. */
  transform: translateX(3.2cqw);          /* ~13px right → red-space centre */
}

/* Logo. The image is intentionally wider than the text column, so the whole logo chain
   shrink-wraps to the image (`width: fit-content`) and is centred by the parent's
   `align-items: center`. Because the widget box == the image box, the logo's centre lands
   exactly on the column centre (= the links' centre). max-width:none beats Elementor's
   default `max-width:100%` that would otherwise clamp the image to the column width. */
.ags-hero__card-logo.elementor-widget-image,
.ags-hero__card-logo .elementor-widget-container,
.ags-hero__card-logo figure { width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; }
.ags-hero__card-logo img {
  display: block;
  width: 44cqw;                           /* ~180px @408; scales with the card */
  max-width: none;
  height: auto;
}

/* Link cluster: Red Rose Bold (accent global), Figma 10 / lh 11 / -1%, tight centred
   stack. line-height on the <p> (not just the inline <a>) sets each link's box height. */
.ags-hero__card-links.e-con {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.2cqw;                            /* ~5px between links */
}
.ags-hero__card-links p {
  margin: 0;
  text-align: center;
  font-size: 2.45cqw;                     /* ~10px @408 */
  line-height: 2.7cqw;                    /* ~11px @408 */
  letter-spacing: -0.01em;                /* -1% */
}
.ags-hero__card-links a {
  font: inherit;
  color: var(--ags-cream);
  -webkit-text-decoration: underline;
  text-decoration: underline;
  text-underline-offset: 0.5cqw;
}

/* --- Photo region (col 2): inset from the card edges, own rounded corners. --- */
.ags-hero__card-photo.e-con {
  display: grid;                          /* single cell; the image fills it */
  position: relative;                     /* containing block for the injected .ags-vid */
  padding: 1.6cqw;                        /* ~6px red margin around the photo */
  overflow: hidden;
}
/* The card photo is a plain Elementor Image widget (editable media — image or animated GIF/WebP —
   in the Navigator). No injected video here; the <img> IS the content. */
.ags-hero__card-photo .elementor-widget-image,
.ags-hero__card-photo .elementor-widget-container { height: 100%; }
.ags-hero__card-photo img {
  width: 100%;
  height: 100%;
  -o-object-fit: cover;
     object-fit: cover;                      /* crop to fill, never distort or tile */
  border-radius: 2.5cqw;                  /* photo's own soft corners (~10px @408) */
  display: block;
}

/* =============================================================================
   MISSION (section 2) — FULL-BLEED red band (Figma s02, latest design)
   -----------------------------------------------------------------------------
   The latest design is edge-to-edge red (NOT the earlier card-on-cream). The band's
   own red background fills the viewport-tall section; the centred stack sits in a
   constrained column with generous side padding. --- */
.ags-mission { background-color: var(--ags-red); }
/* SCALE-AS-ONE-UNIT: every dimension here is in `rem`, so the whole composition grows in
   lockstep with the root knob (html { font-size: clamp(16px, 1.25vw, 22px) }). The
   headline is already rem (Kit `ags_h2b` = 4rem = 64px @1280), so the column width, kanji,
   gaps and padding must be rem too — otherwise the font scales on one curve while a fixed-px
   column / vh-based gaps scale on another, and the line-wrap + rhythm DRIFT as the window
   resizes (the "shape changes with screen size" bug). With all-rem, the column/font ratio
   is constant at every width → the 4-line taper and vertical rhythm hold identically from
   laptop to ultrawide (root clamps 16→22px over 1280→1760, then freezes together). */
.ags-mission__inner.e-con {
  width: 100%;
  /* The correct 4-line taper (line 2 ends "…birthplace", last line "original Blue Zones.")
     only holds while the TEXT column is 1140–1230px. Land it dead-centre of that window at
     ~1185px so it's ROBUST — an earlier build sat the column at ~1146px, 6px above the
     1140 floor, so any sub-pixel font-rendering difference between browsers flipped it back
     to the old "Japan—the" wrap (why the live site looked unchanged). max-width 77.06rem
     (1233px @1280) − 2×1.5rem (48px) padding = 1185px text column, ~45px clear either edge. */
  max-width: 77.06rem;
  margin-left: auto;
  margin-right: auto;
  padding: 5rem 1.5rem;
  /* ~4.75rem ≈ 76px @1280: 沖縄 high, 空手 low, generous air around the centred headline. */
  gap: 4.75rem;
  /* SCALE-TO-FIT (keeps the card locked at 100lvh for one-screen scrolling): the whole padded stack
     is ~50.6rem tall and CONSTANT (all-rem), but it's sized off viewport WIDTH while the card is
     100lvh (HEIGHT) — so on wide-SHORT viewports the content grew taller than the card and got
     clipped (沖縄/空手 cut, "lost padding"). Uniformly scale the stack to fit the card height, so it
     holds the SAME proportions and the full padded composition shows at every screen size. */
  /* --mission-nat MUST equal the real unscaled content height, per language. The scale makes the
     visual height = nat * (lvh/nat) = lvh for ANY nat, so a wrong nat doesn't shrink the content to
     fit — it just leaves it overflowing. es/de wrap the statement to one MORE line than en/nl
     (4 vs 3 at 1440, 5 vs 4 at 1728), so with en's 52rem they overflowed the padded box and, being
     centred, split the excess evenly across BOTH pads: 90/77 in en became 49/35 in es/de. Value is
     in rem and the statement's font is rem-based, so one extra line is a constant ~4.4rem. */
  --mission-nat: 52rem;
  /* ULTRAWIDE FILL. The old `min(1, 100lvh/nat)` capped at 1, so on a 1440-tall viewport the
     rem-capped content (1115px) sat in a 1440px section and the leftover 325px split into the pads:
     282/263 against a 154 target, i.e. nearly double every other section. Filling a taller box with
     frozen type is impossible, so the cap has to go.
     Solved for the scale that lands the ink exactly --ags-sec-pad from both edges:
        padTop = (secH - nat*s)/2 + innerPad*s = P   ->   s = (secH/2 - P) / (nat/2 - innerPad)
     One expression covers the whole range: ~0.80 at 1440x766 (a slight shrink, which also pulls the
     laptop pad from 90 onto 82) and ~1.23 at 3440x1440 (the scale-UP that lets it fill). Denominator
     is nat/2 - 5rem, always positive since nat >= 52rem. */
  /* 5.6rem, not the declared 5rem of padding: solved from measurement, the ink actually sits 110px
     below the inner's top (padding + the 沖縄 kanji's leading) and 92px above its bottom. Using the
     declared 5rem over-shrank and pushed the laptop pad from 90 to 99. 5.6rem is the AVERAGE of the
     two, so the MIDPOINT of top/bottom lands on --ags-sec-pad. The residual ~±14px top-vs-bottom
     asymmetry is the composition's own (kanji leading) — pre-existing and unchanged. */
  transform: scale(tan(atan2(calc(50vh - var(--ags-sec-pad)), calc(0.5 * var(--mission-nat) - 5.6rem))));
  transform: scale(tan(atan2(calc(50lvh - var(--ags-sec-pad)), calc(0.5 * var(--mission-nat) - 5.6rem))));
  transform-origin: center center;
}

/* =============================================================================
   GALLERY + REVIEWS (section 3) — SCATTERED EDITORIAL COLLAGE on CREAM (Figma s03)
   -----------------------------------------------------------------------------
   The reference is NOT a grid: 6 photos scattered at irregular positions (two bleed
   off the left/right edges) around a centred red reviews block. We build a
   position:relative STAGE with the reference's 1280×880 aspect ratio, then place each
   photo absolutely by its MEASURED Figma % (measure via the s03 detector). The stage
   scales as ONE unit — sized to fit the viewport height on desktop/ultrawide, or full
   width on laptop — so the whole composition holds its proportions at every width. --- */
.ags-gallery { background-color: var(--ags-cream); overflow: hidden; }
.ags-gallery.e-con > .e-con-inner,
.ags-gallery.e-con { padding-left: 0; padding-right: 0; }
.ags-gallery__stage.e-con {
  position: relative;
  /* Stage WIDTH tracks the viewport (photos' horizontal GAPS stretch to fill the space, so the
     collage spreads edge-to-edge at every width instead of marooning mid-canvas past 1280) while
     HEIGHT is bounded (it lives in a 100lvh deck card). Because the two axes are decoupled the
     photos are sized by height+aspect-ratio below — they hold a fixed physical size as the stage
     widens; only the space BETWEEN them grows. Was aspect-locked 1280/900 + capped 1440px, which
     is what clustered it on ultrawide. */
  width: calc(0.94 * var(--ags-frame));   /* was min(94vw, 2400px) — 94% OF THE FRAME, so it enlarges instead of freezing */
  height: min(86vh, calc(820 * var(--ags-px)));
  height: min(86lvh, calc(820 * var(--ags-px)));
  margin-left: auto;
  margin-right: auto;
  padding: 0;
  display: block;
}
/* Each collage photo is absolutely positioned by its reference left%/top%; its SIZE is set by a
   bounded height (% of the bounded stage height) + a per-photo aspect-ratio → width auto-computes,
   so photos never balloon as the stage widens. Elementor wraps <img> in figure/container chains,
   so force the whole chain to fill the box. */
.ags-gallery__stage .ags-gal.elementor-widget-image {
  position: absolute;
  margin: 0;
  height: 24%;                 /* bounded by the stage height → fixed physical size */
  aspect-ratio: 5 / 4;         /* per-slot ratios override below; width computes from height */
}
.ags-gallery__stage .ags-gal .elementor-widget-container,
.ags-gallery__stage .ags-gal .elementor-image,
.ags-gallery__stage .ags-gal figure,
.ags-gallery__stage .ags-gal a { display: block; height: 100%; width: 100%; margin: 0; }
.ags-gallery__stage .ags-gal img {
  width: 100%; height: 100%;
  -o-object-fit: cover;
     object-fit: cover;
  display: block;
  border-radius: 4px;
}
/* Positions from the s03 reference (frame 1280×900). TOP% is preserved as measured (the
   top row sits high, the bottom row low → a clear middle BAND for the centred text, exactly
   how the reference avoids clash). HORIZONTALLY each photo is anchored to its NEAREST edge:
   left-biased photos use `left`, right-biased use `right` → they HUG the frame at every width
   and the central clear zone (and the gaps between photos) grows on ultrawide instead of the
   whole collage clustering mid-canvas. Per-slot `aspect-ratio` preserves each reference crop.
   Slots keyed to the ACTUAL content of gal-N.jpg (docs/PHOTO-MAP.md):
     gal-1 banner → top-left    gal-2 sword-kata → top-centre-left  gal-3 bo-staff → top-right
     gal-4 portrait → bot-left  gal-5 tea → bot-centre-right         gal-6 kumite → bot-right */
.ags-gal--1 { left:  1.6%; top: 20.7%; aspect-ratio: 209 / 206; }  /* banner group — top left        */
.ags-gal--2 { left: 28.8%; top: -16%;  aspect-ratio: 251 / 200; }  /* sword-kata rock — top centre (raised HIGH — it sits over the text column, so push it up into the whitespace ABOVE the stage to clear "5 Star" at every height) */
.ags-gal--3 { right:10.2%; top: 15.7%; aspect-ratio: 251 / 200; }  /* bo-staff pair — top right      */
.ags-gal--4 { left: 10.2%; top: 60.6%; aspect-ratio: 251 / 200; }  /* portrait pair — bottom left    */
/* gal-4 is a tall portrait (675×900) cover-cropped into a landscape slot → default centre crop cut
   off the two people's HEADS. Anchor the crop near the top so the heads stay in frame. */
.ags-gallery__stage .ags-gal--4 img { -o-object-position: center 22%; object-position: center 22%; }
.ags-gal--5 { right:23.6%; top: 62%; aspect-ratio: 270 / 203; }  /* tea — bottom centre-right (raised from 68.9% → it was pushed too low on wide-short desktops and fell past the section fold; now aligns with the gal-4/6 bottom row) */
.ags-gal--6 { right: 1.6%; top: 53.9%; aspect-ratio: 203 / 200; }  /* kumite — bottom right          */
/* Centred reviews block, overlaid on the collage (Figma: dead-centre, ~47% down). */
.ags-gallery__reviews.e-con {
  position: absolute;
  left: 50%; top: 47%;
  transform: translate(-50%, -50%);
  width: -webkit-max-content;
  width: -moz-max-content;
  width: max-content;
  max-width: 70%;   /* room for "Google Review & TripAdvisor" on ONE line at every width */
  padding: 0;
  z-index: 2;
  text-align: center;
}
/* Each review line is its own heading widget; keep line 2 unbroken so the "&" never
   orphans onto a third line as the type grows on ultrawide (Figma: always 2 lines). */
.ags-gallery__reviews .elementor-heading-title { margin: 0; line-height: 1.1; white-space: nowrap; }
/* Tighten the two review lines as a pair (JSON sets a uniform block gap), then give the
   button its own top margin so it isn't cramped against the text. Type is Kit-owned. */
.ags-gallery__reviews.e-con { gap: calc(6 * var(--ags-px)); }
.ags-gallery__reviews .elementor-widget-button { margin-top: calc(26 * var(--ags-px)); }
/* Item 5 (desktop): the Google button + the TripAdvisor link are rendered as TWO IDENTICAL maroon pills
   (per feedback — they were a filled pill + a plain text link). Same rule styles both so they can't drift.
   (Mobile keeps its own stacked-links treatment in the ≤1023 block — this is desktop-scoped.) */
.ags-gallery__reviews .ags-btn .elementor-button,
.ags-gallery__reviews > .elementor-widget-text-editor a {
  display: inline-block;
  background: var(--ags-red); color: var(--e-global-color-accent, var(--ags-cream)) !important;   /* !important beats the TA link's inline maroon color (else maroon-on-maroon) */
  border: none; border-radius: 999px;
  padding: calc(6 * var(--ags-px)) calc(30 * var(--ags-px));
  font-family: "Red Rose", serif; font-weight: 700; font-size: calc(14 * var(--ags-px)); letter-spacing: 0.02em;
  /* IDENTICAL PILL SIZE + SHAPE (Simon). They were both pills but not the same pill: Google
     measured 202x32 and TripAdvisor 237x29 — different on BOTH axes.
       height: the shared `font-size: 14px` above never reached the Google button, because its label
               lives in a span carrying the Kit button typography (14.625px) — see the
               `font-size: inherit` fix below. That 0.625px drove the 3px height difference.
       width:  each pill was sized by its own label. min-width in `em` (resolves against the pill's
               own 14px) fits the longer "TRIPADVISOR REVIEWS" and makes both pills that wide, with
               the shorter label centred inside. */
  min-width: 17.5em;
  text-align: center;
  text-transform: uppercase !important; -webkit-text-decoration: none !important; text-decoration: none !important; white-space: nowrap; line-height: 1.2;   /* !important beats the kit button-typography transform + the TA link's original underline */
}
/* the button label lives in a span with its own kit typography → force it to inherit the pill's colour + caps */
.ags-gallery__reviews .ags-btn .elementor-button .elementor-button-text {
  color: inherit !important; text-transform: uppercase !important;
  font-size: inherit !important;   /* the Kit button typography (14.625px) was overriding the pill's 14px */
}
/* The pill font needs the PAGE-TYPE scope or the Kit button global (0.8125rem) wins — that is why
   the shared `font-size: 14px` above reached the TripAdvisor link but not the Google button
   (14.625px @1440, 17.55px @1728), leaving them 3-8px different in height. Matching the Kit's own
   0.8125rem rather than fighting it keeps both pills identical AND lets them scale with the page;
   min-width is in `em` so the pair stays equal-width at every size. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-gallery__reviews .ags-btn .elementor-button,
body.ags-redesign [data-elementor-type="wp-page"] .ags-gallery__reviews .ags-btn .elementor-button .elementor-button-text,
body.ags-redesign [data-elementor-type="wp-page"] .ags-gallery__reviews > .elementor-widget-text-editor a {
  font-size: 0.8125rem;
  /* line-height too: with size and padding already equal, the Google pill still measured 2-4px
     taller because its label span carried the Kit's own line-height. Pinning both to 1.2 makes the
     two pills byte-identical in height at every viewport. */
  line-height: 1.2;
}

.ags-gallery__reviews .ags-btn .elementor-button:hover,
.ags-gallery__reviews > .elementor-widget-text-editor a:hover { background: #7a1900; }
.ags-gallery__reviews > .elementor-widget-text-editor { margin-top: calc(12 * var(--ags-px)); text-align: center; }
.ags-gallery__reviews > .elementor-widget-text-editor p { margin: 0; }
/* 地域社会 section marker — absolutely placed bottom-left of the collage stage (new design).
   CENTRED (Mika: "Center align kanji + text"): the romaji "(Chiiki Shakai)" was left-aligned under
   the kanji (both starting at the same x, computed text-align: start). The box is width:auto so it
   hugs 地域社会 — centring the children puts the shorter romaji on the kanji's centre line, the same
   treatment the GT/VIP eyebrows needed. */
.ags-gallery__kanji.e-con {
  position: absolute;
  /* Align under the bottom-left portrait (gal-4 @ left:10.2%) instead of the far stage edge — on
     ULTRAWIDE the photos hug the edges, so left:1.6% stranded the kanji alone in the left margin,
     disconnected from the collage. Sitting it under gal-4 keeps it visually grouped at every width. */
  left: 10.2%;
  bottom: 4%;
  gap: 0.1111rem;   /* was 2px */
  z-index: 2;
  width: auto;
}
.ags-gallery__kanji .elementor-heading-title { line-height: 1.05; }
.ags-gallery__kanji.e-con,
.ags-gallery__kanji > .e-con,
.ags-gallery__kanji .elementor-heading-title { align-items: center; text-align: center; }

/* =============================================================================
   VALUES (section 4) — FULL-BLEED BLUE band (Figma s04, latest design)
   -----------------------------------------------------------------------------
   Same reversed-assumption as §2 mission: the latest Desktop.png shows the blue
   running edge-to-edge (NOT a rounded card inset on cream). The section's own blue
   fills the viewport-tall band; the eyebrow row hugs the frame edges and the 4 kanji
   columns span the full width, with generous side padding. --- */
/* "The Ageshio Way" (§4) — B&W hands photo behind the kanji (per feedback). A dark gradient overlay
   (baked into the same background-image, the hero-half pattern) keeps the white kanji + eyebrows legible
   on the photo. background-color stays as the no-image fallback. Root-relative URL → apply_staging_delta.php
   prefixes it for the /mystaging subdir, same as the hero halves. */
.ags-values {
  background-color: var(--ags-blue);
  background-image:
    linear-gradient(rgba(17, 16, 16, 0.42), rgba(17, 16, 16, 0.42)),
    url('/wp-content/uploads/redesign/ageshio-way-bg.jpg');
  background-image:
    linear-gradient(rgba(17, 16, 16, 0.42), rgba(17, 16, 16, 0.42)),
    url('/wp-content/uploads/redesign/ageshio-way-bg.webp');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.ags-values.e-con > .e-con-inner,
.ags-values.e-con { padding-left: 0; padding-right: 0; }
.ags-values__card.e-con {
  /* Full-bleed: span the section width with symmetric side padding (Figma eyebrows
     sit ~62px off each edge at 1280). No radius, no cream margin, no own background
     (the section is already blue — the inline card bg just matches it). */
  /* Spans the FRAME, not 100vw (ultrawide pass). The blue band itself stays full-bleed — that's
     the SECTION's background — but the eyebrow row and the 4 kanji columns are content, and letting
     them keep 100vw put their ink ~100px off a 3440 edge while grouptour/vip/exp pulled in to the
     frame's 416px. Identical at every width <=1920, where the frame IS 100vw. */
  width: min(100%, var(--ags-frame));
  margin-left: auto;
  margin-right: auto;
  background: transparent;
  border-radius: 0;
  /* Vertical padding is now the shared section pad, and the card FILLS the section height, so its
     existing space-between spreads the three rows across the whole band instead of across a
     content-sized 1037px box that the section then centred. Before: the card sat 202px in from each
     edge and added its own 104px, giving an ink pad of ~309/316 against a 154 target — and that was
     ~22% of the section height at EVERY width, i.e. double every neighbour on laptops too, not just
     ultrawide. Scaling the card up instead was NOT an option: it needs ~1.42x, and scaling up widens
     it straight back through the outer guide (measured -46px/1558px on a 1512 frame). */
  padding: var(--ags-sec-pad) calc(72 * var(--ags-px));   /* 72 = the old 5vw at the 1440 frame */
  height: 100%;
  box-sizing: border-box;
  /* Distribute the three rows vertically (space-between): eyebrows toward the top, kanji
     columns centred, "Read about our origins" toward the bottom. The spread is set by the
     card's min-height — tuned so there's breathing room around the kanji without the rows
     flying to the section's extreme edges (space-between over the full 100svh was too much). */
  min-height: 72vh;
  min-height: 72svh;
  justify-content: space-between;
}
/* Kanji columns span the card's full inner width; eyebrow row is pulled IN so "The Ageshio
   way" ends near the first kanji's left edge and "Pillars…" starts near the last kanji's right
   edge (reference), instead of hugging the frame. ~7% side inset aligns them to the kanji
   block (first char ~12%, last char ~88% of the section). */
/* gap in rem, not Elementor's DB px: the px froze while the type scaled, so every stacked
   composition came out 9-17% tighter than a true enlargement. Each container's font is
   exactly 1rem at the 1440 reference, so N/18 rem reproduces the old px value there and
   then tracks the type. (Elementor emits its gap as `--gap: Npx Npx`, a two-value token
   that calc() cannot rescale, which is why it is restated rather than multiplied.) */
.ags-values__cols.e-con { width: 100%; padding: 0; gap: 1.3333rem; }   /* was 24px */
.ags-values__eyebrows.e-con { width: 100%; padding: 0 3.5%; box-sizing: border-box; }
/* Keep the 4 columns as equal quarters (Elementor drops flex-basis %; pin them). */
.ags-values__cols.e-con > .e-con { flex: 1 1 0; min-width: 0; gap: 0.7778rem; }   /* was 14px */
/* "Read about our origins" — uppercase to match the reference (READ ABOUT OUR ORIGINS). */
.ags-values__card > .elementor-widget-text-editor a { text-transform: uppercase; }

/* =============================================================================
   ABOUT (section 8) — big AGESHIO lockup + copy left, photo panel w/ 約 right (s08)
   -----------------------------------------------------------------------------
   Figma: the AGESHIO wordmark reads LARGE (cap ~90px, lockup ~520px wide) at the top
   of the left column, copy + link beneath it top-anchored; the right photo panel runs
   nearly the full section height. The build had a small (~345px) logo and a
   vertically-centred left column floating in an over-tall red band. --- */
/* Inner composition FILLS the section up to an ultrawide cap. `min(94vw, 1640px)` → at laptop/
   desktop the row spans ~94% of the viewport (photo reaches near the frame edge, logo reads big
   like the reference); past ~1745px it caps and centres, leaving side margins on ultrawide. Row
   items are CENTRED vertically (the photo is a fixed ~square, not a full-height stretch). */
.ags-about__inner.e-con {
  width: calc(0.97 * var(--ags-frame));   /* was min(97vw, 1760px) — 97% OF THE FRAME so it keeps growing on ultrawide */
  margin-left: auto;
  margin-right: auto;
  min-height: 78vh;
  min-height: 78svh;
  align-items: center;
  padding-top: max(2.5rem, min(6vh, 5rem));
  padding-bottom: max(2.5rem, min(6vh, 5rem));
  padding-top: max(2.5rem, min(6vh, 5rem));
  padding-top: clamp(2.5rem, 6vh, 5rem);
  padding-bottom: max(2.5rem, min(6vh, 5rem));
  padding-bottom: clamp(2.5rem, 6vh, 5rem);   /* maroon breathing room ABOVE/BELOW the photo panel (was ~10px, crowded) */
  justify-content: space-between;
}
/* Left column: ~44% (logo + copy + link), centred text like the reference. */
/* margin-inline:auto MIRRORS the media (below): both 44% columns auto-centre in the row's free space,
   so the logo's LEFT inset equals the media's RIGHT inset (symmetric section). Without it the left
   column sat flush-left while the auto-centred media kept a ~6% right margin — unequal (Simon 2026-09-23). */
.ags-about__left.e-con { flex: 0 0 44%; margin-left: auto; margin-right: auto; justify-content: center; align-items: center; gap: 1.5556rem; }   /* gap was 28px */
/* AGESHIO lockup reads LARGE — fills the left column (Figma AGESHIO word ≈40% of frame). Bigger
   cap so it grows on wider screens instead of freezing small. */
.ags-about__logo.elementor-widget-image { width: 100%; max-width: calc(860 * var(--ags-px)); }
.ags-about__logo .elementor-widget-container,
.ags-about__logo figure,
.ags-about__logo img { width: 100%; height: auto; }
/* About Ageshio link: Red Rose Bold −1% (Kit link_bold) — UPPERCASE to match the reference
   ("ABOUT AGESHIO"), dropped a little lower beneath the copy. */
.ags-about__left .elementor-widget-text-editor a { text-transform: uppercase; }
.ags-about__left > .elementor-widget-text-editor:last-child { margin-top: calc(22.98 * var(--ags-px)); }
/* Right media panel: a ~square photo panel (Figma s08). The panel is a positioning WRAPPER with
   NO filter; the photo lives on an inner .ags-about__photo layer that carries the greyscale/darken
   filter, and the 約/(About) kanji is a SEPARATE sibling stacked on top — so the filter (which
   applies to its whole subtree) never touches the text, keeping it clean cream. */
.ags-about__media.e-con {
  flex: 0 0 44%;              /* ~match the left column's footprint (Simon) — was 50%, too dominant */
  /* Auto side-margins CENTRE the gif in the free space right of the text → visually equal maroon on
     its left and right at smaller widths (this overrides the inner's space-between). Reset to 0 on
     ultrawide (below), where the capped+centred inner already gives it a balanced right margin. */
  margin-left: auto;
  margin-right: auto;
  aspect-ratio: 589 / 611;    /* Figma s08 panel is ~SQUARE (0.96), NOT a tall full-height stretch */
  align-self: center;
  position: relative;
  overflow: hidden;
  border-radius: 8px;
  padding: 0;
}
/* Ultrawide: the inner caps at 1760px and centres, so keep the space-between (right-hugging) layout
   Simon approved there — the large centred inner margin already balances the gif's right side. */
@media (min-width: 2000px) {
  .ags-about__media.e-con,
  .ags-about__left.e-con { margin-left: 0; margin-right: 0; }   /* both flush to the capped inner → stays symmetric */
}
/* Photo layer — fills the panel, greyscaled + darkened (deeper than the hero card, per Figma s08).
   The bg-image is set inline by build_homepage.py. */
.ags-about__photo.e-con {
  position: absolute; top: calc(16 * var(--ags-px)); right: calc(16 * var(--ags-px)); bottom: calc(16 * var(--ags-px)); left: calc(16 * var(--ags-px));
  /* Let inset:16px control the size — override Elementor's .e-con width:100%, which
     forced the layer to full panel width so it overflowed the panel's right edge and
     its rounded right corners got clipped off. */
  width: auto; height: auto;
  overflow: hidden;                   /* clip the video to the 6px rounded box on ALL sides */
  border-radius: 6px;
  filter: grayscale(1) brightness(0.62);
  background-size: cover !important;
  background-repeat: no-repeat !important;
  background-position: center !important;
}
/* The karate video (ageshio-video.js) fills this photo layer via its .ags-vid-frame at z-index:0 → it
   inherits the layer's grayscale/darken filter (source is already B&W) and its 16px inset. The frame
   matches the layer's 6px corner (the layer's own overflow:hidden also clips it). The 約 kanji is a
   SEPARATE sibling at z-index:1 → stays clean cream, above the video. */
.ags-about__photo > .ags-vid-frame { border-radius: 6px; }
/* Kanji layer — 約 + (About), centred and stacked ABOVE the photo, unfiltered → stays clean cream. */
.ags-about__kanji.e-con {
  position: absolute; top: calc(16 * var(--ags-px)); right: calc(16 * var(--ags-px)); bottom: calc(16 * var(--ags-px)); left: calc(16 * var(--ags-px));
  z-index: 1;
  align-items: center;
  justify-content: center;
}
/* Reveal the 約 kanji overlay only while the media panel is hovered (Simon 2026-09-23). Desktop only
   (hover isn't a touch concern — mobile keeps it shown via the ≤1023 block). It's already centred on
   the box (absolute inset + centre above); we just fade it in/out. focus-within keeps it keyboard-
   reachable (the MEET THE MASTERS button lives inside the overlay). */
@media (min-width: 1024px) {
  .ags-about__kanji { opacity: 0; transition: opacity .4s ease; }
  .ags-about__media:hover .ags-about__kanji,
  .ags-about__media:focus-within .ags-about__kanji { opacity: 1; }
}
@media (min-width: 1024px) and (prefers-reduced-motion: reduce) {
  .ags-about__kanji { transition: none; }
}
/* The 約 heading inside the overlay carries an Elementor ENTRANCE animation (server-rendered class
   `elementor-invisible` → visibility:hidden/opacity:0 until a scroll-triggered animation fires). That
   fights the container-level hover reveal: the container can fade in on hover while the widget inside
   stays hidden, so NOTHING appears. Neutralise the entrance state on the overlay's widgets so the kanji
   content is ALWAYS painted — the sole reveal gate is then the .ags-about__kanji CONTAINER opacity
   (hover-only on desktop above; always-on for mobile via the ≤1023 block). Applies at all widths. */
.ags-about__kanji .elementor-widget {
  visibility: visible !important;
  opacity: 1 !important;
  animation-name: none !important;
  transform: none !important;
}
/* Copy paragraph: width in `em` so the wrap is LOCKED to the reference regardless of the root-rem
   knob (the body Kit font is a floorless vw clamp — see §6 wrap-lock). 25em ≈ 400px @16px → the
   6-line reference wrap (L1 ends "company", L2 "karate.", L3 "masters,", L4 "rare", L5 "have"). */
.ags-about__left .elementor-widget-text-editor { width: 25em; max-width: 100%; margin-left: auto; margin-right: auto; }
/* -----------------------------------------------------------------------------
   The reference uses TALL PORTRAIT photos in these sections (the build had short
   landscape boxes). Force a consistent portrait aspect on any .ags-portrait image,
   filled via object-fit:cover so the subject is framed, not distorted. --- */
.ags-portrait.elementor-widget-image,
.ags-portrait .elementor-widget-container,
.ags-portrait .elementor-image,
.ags-portrait figure { height: 100%; margin: 0; }
.ags-portrait img {
  width: 100%;
  aspect-ratio: 3 / 4;
  height: auto;
  -o-object-fit: cover;
     object-fit: cover;
  display: block;
}

/* =============================================================================
   VIP RETREAT (§6) — scale-as-ONE-unit on a container-query stage (SAME architecture as
   §5 Group Tour). §6 is an editorial free-composition — elements DISTRIBUTED around each
   other with deliberate negative space, hugging the frame edges — so it uses the cqw stage,
   not flex (see the flex-vs-cqw principle memo). Positions in %, fonts min(cqw,px): the
   composition spreads outward on ultrawide while text stays bounded and wraps hold. Coords
   measured off the s06 reference as % of its 1280×1018 frame. --- */
/* ONE background image, painted on the SECTION at full width so it reaches both viewport edges
   edge-to-edge on every screen (cover → on ultrawide it crops a little top/bottom of the scene
   but never stretches and never leaves a margin/second image). The elements live on the STAGE
   (a capped inner frame) so they stay put and correctly scaled regardless of the bg. */
.ags-vip {
  position: relative; overflow: hidden;
  background-color: #050505 !important;   /* black fills wherever the stage photo doesn't reach */
}
/* Greyscale upscaled dojo photo painted on the STAGE (not the section) so it shares the stage's
   coordinate space with the kanji — the photo's TOP then lands at a consistent point relative to the
   後退 kanji (which is at top:17% of the stage) at EVERY laptop width, instead of drifting with the
   viewport aspect. Photo spans the stage width, sits from ~13% (a little ABOVE the kanji top → slight
   overflow, not dead-aligned) down to the stage bottom; the section's black shows above it.
   A DARKER veil (::after) dims the photo more (Simon) so it stays a subtle backdrop behind the copy. */
.ags-vip-stage::after {
  content: "";
  position: absolute;
  /* SEAMLESS BACKDROP — the element IS the photo box. Its HEIGHT comes from top/bottom and its WIDTH
     from aspect-ratio, so the photo renders at a CONSISTENT physical size at every width (its size
     tracks viewport HEIGHT only, never width → no zoom laptop→ultrawide, which cover caused). It's
     centred on the stage (= viewport centre). On narrow screens it overflows to the viewport edges
     (clipped by .ags-vip overflow:hidden); on ultrawide it's NARROWER than the viewport — and the
     edge-fade mask below dissolves its sides into the section's black, so there are NO hard bars or
     seams at any width. The photo emerges from / melts into the darkness, matching the reference. */
  left: 50%;
  transform: translateX(-50%);
  aspect-ratio: 2568 / 1268;                  /* == vip-bg.webp → element is the photo box exactly */
  top: 12%; bottom: 0;                        /* element top; the mask's top-fade (below) softens the
                                                 real photo edge to ~kanji level (17%) — not a hard line */
  z-index: -1;                                /* behind the stage content, above the section bg */
  background-image:
    linear-gradient(rgba(5,5,5,0.55), rgba(5,5,5,0.55)),   
    url("/wp-content/uploads/redesign/vip-bg.jpg");
  background-image:
    linear-gradient(rgba(5,5,5,0.55), rgba(5,5,5,0.55)),   /* extra dim over the photo */
    url("/wp-content/uploads/redesign/vip-bg.webp");
  /* cover on an element that already HAS the photo's aspect-ratio = an exact fit: no crop, no
     distortion, no zoom. (The gradient veil just paints the same box.) */
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* EDGE FADE anchored to the PHOTO: mask % is relative to THIS element (= the photo box), so the
     dissolve is always exactly at the photo's own edges regardless of viewport width or height.
     Two gradients intersected → soft fade on all four sides: top melts in under the 後退 kanji,
     sides + bottom melt into the section black. This is what makes it seamless on ultrawide. */
  -webkit-mask-image:
    linear-gradient(to right,  transparent 0, #000 10%, #000 90%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 13%, #000 91%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right,  transparent 0, #000 10%, #000 90%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 13%, #000 91%, transparent 100%);
  -webkit-mask-composite: source-in, xor;
          mask-composite: intersect;
  pointer-events: none;
}
.ags-vip.e-con { align-items: center; justify-content: center; }   /* centre the stage */
/* Stage: the positioning "frame" that holds the elements. Grows with the viewport up to a
   2200px cap, height bounded to the 100svh deck card; reference frame 1280×1018 sets the aspect.
   The background image is painted on the SECTION (full-width, single image — see .ags-vip),
   NOT here, so the bg reaches the viewport edges while the elements stay on this capped frame. */
/* .e-con so this wins over Elementor's .e-con.e-flex --flex-shrink default (see gt-stage note). */
.ags-vip-stage.e-con {
  container-type: inline-size;
  position: relative;
  /* UNWELDED — same change as .ags-gt-stage; see the long note there. The --vip-floor + whole-stage
     scale welded width to height, so filling the width forced a height the viewport didn't have and
     the stage shrank, pulling every element off Mika's outer guide (measured 224px off at 1440).
     Width is now the frame inset by the guide, height is the full deck card, nothing scales, and
     the PHOTOS absorb the height difference by getting smaller rather than cropping. */
  width: calc(var(--ags-frame) - 2 * var(--ags-edge));
  height: 100%;
  --flex-shrink: 0;
  margin-left: auto;
  margin-right: auto;

  /* Photo pair. Keeps the reference 298:493 portrait, but derives WIDTH from the available HEIGHT —
     deriving height from width (aspect-ratio) is exactly what overflowed the stage. Capped at 20cqw
     so the pair never exceeds its design width on tall screens. cqw/lvh only: a `%` inside a shared
     var resolves against whichever axis the consuming property uses (see the GT note). */
  /* LEFT-COLUMN TYPE SCALE. The title/pill/copy fonts are Kit rem values, so they track viewport
     WIDTH, while the space they sit in is a % of viewport HEIGHT. On a wide-but-short screen that
     mismatch made the copy clip into the BOOK YOUR VIP RETREAT pill — worst at 1680x894 (-47px),
     and non-monotonic (fine at 1512x860, broken at 1440x766). Shrinking the type to fit keeps
     figma's POSITIONS untouched, unlike moving the title group up. No-op at >=1000px tall, so
     desktop is unchanged. Same pattern as --gt-tsc. */
  /* FLOORED at 0.84: the bare ratio over-shrank the short end (11.2px body copy at 1280x700 for a
     collision that only needed ~0.91). 0.84 still clears the pill everywhere while keeping the copy
     legible — the required scale is ~0.87-0.93, so this has margin without being punitive. */
  /* CONSTANT now (was clamp(0.84, 100svh/1000px, 1)). VIP's type is Kit/rem-driven and the rem knob
     is itself reference-frame scaled, so the height response already lives there — a second height
     term here would double-count it and oversize the type on a tall viewport. 0.84 is what the old
     clamp resolved to at the 1440x766 reference (it floored below 840 tall), so every verified
     viewport is unchanged. */
  --vip-tsc: 0.84;
  --vip-fs-title: calc(var(--vip-tsc) * 3.75rem);   /* shared by the title font AND the pill gap */
  --vip-photo-h: min(calc(calc(var(--vh, 1vh) * 67) - var(--ags-sec-pad)), calc(1.6544 * 20cqw));   /* 493/298 */
  --vip-photo-w: calc(0.6045 * var(--vip-photo-h));                             /* 298/493 */
  /* NO background here — the section paints the single full-width bg image (above). The stage is
     just the positioning frame that holds the elements in place. */
}
.ags-vip-stage > .e-con { position: absolute; margin: 0; padding: 0; }
/* Elementor's default 10px sits on the outer .e-con, on NESTED .e-con-full AND on .e-con-inner;
   clear all three or `left:0`/`right:0` land the ink 10-20px inside the guide. */
.ags-vip-stage .e-con,
.ags-vip-stage .e-con-inner { padding: 0; }
.ags-vip-stage > .e-con { z-index: 1; }   /* content above the wash */
/* 後退 / Retreat — top-left, hugging the edge (ref x3% y18%). Kanji renders as a <div>,
   the "Retreat" marker as an <h6> — target by tag so there's no specificity tangle. */
/* On the guide, and width:max-content so the box hugs 後退 — which also CENTRES "Retreat" beneath
   it, matching figma (Retreat x60-135 centre 97.5 vs 後退 x40-157 centre 98.5). The build had both
   left-aligned at the same x; that is Mika's "Center align kanji + title". A 16% box plus
   text-align would NOT do it — the offset is flex-item centring (see the GT eyebrow note). */
.ags-vip-eyebrow { left: 0; top: 17%; width: -webkit-max-content; width: -moz-max-content; width: max-content; }
/* max-content alone was NOT enough here (it was for GT): VIP's eyebrow container aligns its items
   to flex-start, so "Retreat" stayed left-aligned at the same x as 後退. Centre explicitly.
   NOTE: the sibling rule `.ags-vip-eyebrow h6.elementor-heading-title` is DEAD — both headings
   render as <div>, so that font-size never applied and "Retreat" is sized by the Kit global. */
.ags-vip-eyebrow,
.ags-vip-eyebrow > .e-con { align-items: center; text-align: center; }
.ags-vip-eyebrow .elementor-heading-title { line-height: 1.1; white-space: nowrap; margin: 0; }
.ags-vip-eyebrow div.elementor-heading-title { font-size: min(4.6cqw, calc(66 * var(--ags-hpx))); }
.ags-vip-eyebrow h6.elementor-heading-title { font-size: min(1.1cqw, calc(16 * var(--ags-px))); }
/* Intro — top-right, right-aligned, 3 lines (ref x43–97% y6–16%). NOTE: fonts scale via the
   root-rem knob (Kit global wins over cqw here — see type-scale memo), so wrap-critical boxes
   must scale in % TOO (font ↑ and box ↑ together → wrap holds). A restrictive px cap strangles
   the box on ultrawide and forces an extra line — so keep % dominant with only a very high cap. */
/* WRAP-LOCK: box width in REM (the same unit the Kit fonts scale by), NOT % of the stage.
   Fonts scale with the root-rem knob; a %-of-stage box scales at a DIFFERENT rate, so the
   box÷font ratio drifts and wraps change between widths. In rem, box÷font is constant → the
   wrap is IDENTICAL at every width; position stays in % so the component still moves as the
   viewport narrows. 42.5rem gives the reference 3-line wrap — L1 "…journeys" / L2 "offering…
   Okinawa's" / L3 "masters…" (probed window 42–43rem; <42 → 4 lines, wider → "offering" creeps
   onto L1). Locked identical at 1280 and 3440. */
/* -1.06rem lifts the line-box leading that sits above the first painted line (measured 19px at
   rem 18 and 23px at rem 21.6 — constant in rem, because VIP's type is Kit/rem-driven not cqw).
   Without it the visible gap read 98 against a wanted 82. */
.ags-vip-intro { right: 0; top: calc(var(--ags-sec-pad) - 1.06rem); width: 42.5rem; }
.ags-vip-intro .elementor-widget-container { text-align: right; }
.ags-vip-intro .elementor-heading-title { line-height: 1.22; margin: 0; }
/* Title + pill unit "VIP Karate Retreat" — mid-left, hugs left edge (ref title x3% y33–45%,
   pill y49%). The pill rides a fixed 28px under the title (flex column) so it stays tight as
   the title grows — a fixed pill `top` would drift because fonts scale with the rem knob while
   positions are svh-bound. Title box uses min(%, px): the 640px cap is the wrap ceiling — wide
   enough for "VIP Karate" at frozen 82px, narrow enough "Retreat" always drops to line 2
   (probed: fits >470px, wraps <702px); pure % → 1 line on ultrawide. Width in REM so the
   2-line wrap ("VIP Karate" / "Retreat") is locked identical at every viewport (see intro). */
/* width scales WITH --vip-tsc: the 2-line "VIP Karate" / "Retreat" wrap is locked by box/font
   ratio, so scaling the font without the box would re-wrap it (the GT lesson). */
.ags-vip-titlegrp {
  left: 0; top: 31%; width: calc(var(--vip-tsc) * 30rem);
  /* Gap TRACKS THE TITLE FONT. It was a flat 28px while the title grows 56.7px -> 82.5px from
     laptop to ultrawide, so the visible title-to-pill gap shrank from 12px to -3px — the pill
     actually overlapped the title. Half the title's font size reproduces the current 1440 value
     (0.5 * 0.84 * 67.5 = 28) and then scales with it. */
  gap: calc(0.5 * var(--vip-fs-title)) !important;
}
.ags-vip-titlegrp .elementor-heading-title { line-height: 1.05; margin: 0; }
.ags-vip-titlegrp .elementor-button { font-size: min(1cqw, calc(14 * var(--ags-px))); padding: 0.6em 2.2em; }
/* VIP CTA sits on the DARK stage → cream pill (--invert) but with BLACK text, not the
   default red (reference s06: "BOOK YOUR VIP RETREAT" reads near-black on the cream pill). */
.ags-vip-titlegrp .ags-btn--invert .elementor-button,
.ags-vip-titlegrp .ags-btn--invert .elementor-button:hover { color: var(--ags-black); }
/* Body copy — bottom-left (ref x3% y68–90%). WRAP-LOCK via `em` on the <p> itself: the body
   Kit font scales on a ~1.25vw clamp with NO 16px floor (12.7px@1024 · 16px@1280 · 19px@1920),
   so it does NOT track the root `rem` (which floors at 16px) — a rem/% box drifts against it.
   `em` on the <p> resolves against the <p>'s OWN font-size, so box÷font is constant at every
   width → the wrap is identical everywhere (ref breaks: L1 "our VIP" · L2 "journey" ·
   L3 "private"). 20em ≈ 320px @16px → L1 ends "our VIP", L2 ends "journey" (ref). Wrapper
   shrink-wraps to it. */
/* +1.95rem because the copy's last painted line sits BELOW its own box (measured -33px at rem 18,
   -45px at rem 21.6), so anchoring the box to the pad left a visible gap of only 51. */
.ags-vip-copy { left: 0; top: auto; bottom: calc(var(--ags-sec-pad) + 1.95rem); width: auto; max-width: 40%; }
.ags-vip-copy p { width: 20em; line-height: 1.5; margin: 0; }
/* TRANSLATIONS get a wider copy column. 20em is the figma-matched English wrap ("L1 ends 'our VIP',
   L2 'journey'"), but the same paragraph runs 10-11 lines in es/de/nl instead of 8-9, pushing it up
   into the BOOK YOUR VIP RETREAT pill (0px at 1440x766, -21px at 1728x1080). Widening costs nothing
   — the container allows 40% of the stage and 26em sits well inside it — and buys back ~2 lines
   WITHOUT shrinking type, which lowering --vip-tsc would have done. English is untouched. */
body:not(.ags-lang-en) .ags-vip-copy p { width: 26em; }
/* Group Tour: sized PER LANGUAGE, because the two variables differ independently —
     en  title 2 lines, copy 5 lines (161px)  -> 0     (figma-exact, untouched)
     de  title 2 lines, copy 6 lines (185px)  -> 2.6rem
     es  title 3 lines, copy 7 lines (209px)  -> 5.4rem
     nl  title 3 lines, copy 7 lines (209px)  -> 5.4rem
   A blanket value could not serve all three: 4.4rem cleared 1440 and 1728 but still grazed at
   1512/1680 (-2..-5px), while raising it for everyone would have lifted German's title needlessly.
   Widening the title box was NOT an option — it is already 482px against 487px of room before the
   copy, so a 3-line title in es/nl is unavoidable and the vertical bound has to absorb it. */
body.ags-lang-de .ags-gt-stage { --gt-copy-w: 646; }   /* 495 eff -> 6 lines (window 460-530) */
/* PER-LANGUAGE COPY WIDTH replaces the old per-language --gt-copy-extra title lift (es/nl 6.4rem,
   de 2.6rem). That allowance was treating the symptom: the translated copy ran 7 rendered lines
   against English's 5, so it climbed into the small photo, and the fudge lifted the TITLE out of
   figma's relationship to the photo to keep the two apart. Widening the box fixes the cause — the
   copy loses 1-2 lines, its top drops ~24-48px clear of the photo, and --gt-copy-extra can go back
   to 0 for EVERY language, which is what restores the title's position on the translated pages.
   German keeps 6 lines deliberately: its 5-line window is only 540-550 effective px wide, so
   targeting it would leave the wrap 5px from a flip. */
body.ags-lang-es .ags-gt-stage { --gt-copy-w: 757; }   /* 580 eff -> 5 lines (window 520-640) */
body.ags-lang-nl .ags-gt-stage { --gt-copy-w: 698; }   /* 535 eff -> 5 lines (window 460-610) */
/* No per-language line count any more: the wider translated intro box above wraps es/de/nl to
   THREE lines, the same as English, so --gt-photo-top is the same in every language and the right
   stack no longer starts 31px lower in translations. (Was `--gt-intro-lines: 4` here.) Verified
   rendered introLines == 3 for all four languages by spec_verify_i18n. */

/* Mission statement: es/de take one extra line, so their natural height is one line greater. */
body.ags-lang-es .ags-mission__inner,
body.ags-lang-de .ags-mission__inner { --mission-nat: 58.7rem; }
/* 58.7rem, not the 56.4rem a one-extra-line estimate suggests. Solved from the measured geometry
   instead: padTop = (sectionH - nat*scale)/2 + innerPad*scale, so matching en's pad needs the
   inner's visual height to land ~734px at 1440 (es real inner height is 1013px unscaled vs en's
   912px). 56.4rem left es/de 16px tighter than en; this lands them within a few px. */
/* Page-type scope or the Kit globals win (title measured exactly 3.75rem, copy 1rem, pill
   0.8125rem — i.e. the Kit values, ignoring any unscoped rule). The copy's `width: 20em` above is
   in em so its wrap-lock tracks the scaled font automatically. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-vip-titlegrp .elementor-heading-title {
  font-size: var(--vip-fs-title); line-height: 1.05;
}
body.ags-redesign [data-elementor-type="wp-page"] .ags-vip-copy p {
  font-size: calc(var(--vip-tsc) * 1rem);
}
body.ags-redesign [data-elementor-type="wp-page"] .ags-vip-titlegrp .elementor-button {
  font-size: calc(var(--vip-tsc) * 0.8125rem);
}
/* Two EQUAL tall portraits, right. Measured off the s06 reference: each card is 23.3% wide,
   L at x48.4%, R at x73.2% (≈1.5% gap), portrait aspect 298×493 (≈3/5). CRITICAL: the card
   HEIGHT is driven by a fixed `aspect-ratio` on the WIDTH, NOT a `%` of the stage. The stage is
   height-bounded (94svh) so it's SHORTER/WIDER than the 1018px reference frame (aspect 1.42 vs
   1.26); sizing height as a %-of-stage squashed the cards to the wrong proportion (too stubby).
   Width% + fixed aspect-ratio keeps the true reference portrait shape at every viewport. Top
   nudged to 30% so the aspect-tall card sits centred in the stage band and never clips. */
/* Photos smaller than the ref measurement. Width drives the size; aspect-ratio (below) keeps the
   portrait proportion, so shrinking width shrinks both dimensions in step. Pair re-centred on the
   right (was 23.3% each; now 20% + 1.5% gap). */
/* Pair shifted right so the RIGHT photo's right edge (77% + 20% = 97%) lines up with the intro
   text's right edge above it (intro is right:3% → right edge at 97%). Gap between cards ~1.5%. */
/* RIGHT-anchored (was left:55.5%/77%, putting the right edge at 97% — 3% inside the guide).
   Anchoring from the right guarantees the pair's right edge sits ON the guide at every size. */
.ags-vip-photoR { right: 0; top: 33%; width: var(--vip-photo-w); height: var(--vip-photo-h); }
.ags-vip-photoL { right: calc(var(--vip-photo-w) + 1.5%); top: 33%; width: var(--vip-photo-w); height: var(--vip-photo-h); }
.ags-vip-photo,
.ags-vip-photo .elementor-widget-container,
.ags-vip-photo figure,
.ags-vip-photo .elementor-image { margin: 0; }
.ags-vip-photo img { width: 100%; aspect-ratio: auto; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; border-radius: 8px; }
/* The WIDGET needs the height too, not just the inner chain, or the img fills the widget instead of
   the positioned box (the GT small photo read 1.5 instead of square for exactly this reason). */
.ags-vip-stage .elementor-widget-image,
.ags-vip-photo,
.ags-vip-photo .elementor-widget-container,
.ags-vip-photo figure,
.ags-vip-photo .elementor-image { height: 100%; }
/* Right photo (meditating monk): the subject sits in the LEFT ~40% of the source frame, so the
   default centre-crop cuts him off. Pin the crop to the left so the monk stays in frame. */
.ags-vip-photoR .ags-vip-photo img { -o-object-position: 25% center; object-position: 25% center; }

/* =============================================================================
   EXPERIENCES (section 7) — two bands on CREAM (Figma s07)
   -----------------------------------------------------------------------------
   TOP: 経験 eyebrow centred; a centred text stack flanked by 4 portrait photos — a
   PAIR on each outer edge, side-by-side (not stacked), bleeding off the frame edges.
   BOTTOM: big "Karate Experiences" title left · right-aligned intro + wide CTA bar. */
.ags-exp { background-color: var(--ags-cream); overflow: hidden; justify-content: center; }
.ags-exp.e-con > .e-con-inner,
.ags-exp.e-con { padding-left: 0; padding-right: 0; }
/* Elementor's stray 10px on the stage and the row wrappers held the photo pairs 9-62px inside the
   guide; clear it horizontally (the stage keeps its vertical pad, set above). */
.ags-exp-stage.e-con { padding-left: 0; padding-right: 0; }
.ags-exp-stage .e-con-inner,
.ags-exp-stage .e-con { padding-left: 0; padding-right: 0; }   /* the lede's own 10px held the right ink 9px inside the guide */
.ags-exp__eyebrow.e-con { padding-top: 0; margin-top: 0; }   /* was margin-top:clamp(-40px,-3vh,-16px) — the negative pull hoisted 経験 above the 100lvh card and clipped it on short viewports */
/* §7 SCALE-TO-FIT — .ags-exp-stage is a real stage container (added to the page structure: it wraps the
   eyebrow + carousel + bottom bands), so the whole section scales through it exactly like §5 .ags-gt-stage
   / §6 .ags-vip-stage. §7's content is a flex-FLOW stack (not a cqw aspect composition), and below ~615lvh
   the fixed-height carousel dominates the top band (photos are capped at 52lvh), so the content has a
   stable MINIMUM height of ~730px → a FIXED --exp-nat (unlike gt/vip's width×ratio). Keep that natural
   height and, when the 100lvh card is shorter, scale the whole stage DOWN to fit — positions/proportions
   preserved. scale(1) = NO-OP whenever the card is tall enough, so every currently-clean height is
   byte-identical (only short/awkward viewports engage the scale). */
.ags-exp-stage.e-con {
  /* UNWELDED (2026-09-08). The fixed --exp-nat + scale-to-fit shrank the whole stage on short
     viewports, which pulled the photo pairs and the title in from Mika's outer guide. §7 is a
     flex-FLOW column of three rows (eyebrow / photos+carousel / title+CTA), so it needs no scale
     at all: fill the card, pad top and bottom, and let space-between push the first row to the top
     pad and the last row to the bottom pad. That makes both gaps exactly --ags-sec-pad. */
  height: 100%;
  /* The extra 1.44rem on the BOTTOM is a tail compensation, not spacing: the bottom row's last
     painted line sits 1.44rem BELOW its own box (measured -26px at rem 18, -31px at rem 21.6 —
     constant in rem). Without it the visible bottom gap read 54 against a wanted 82 and looked
     like an overflow; it wasn't. */
  padding: var(--ags-sec-pad) 0 calc(var(--ags-sec-pad) + 1.44rem);
  justify-content: space-between;
  --flex-shrink: 0;
}
/* Top band spans full width; the middle text column is centred, edge pairs hug the
   viewport edges (and slightly overrun so the outer photo bleeds off). */
/* Both §7 rows share ONE capped width, centred, so on ULTRAWIDE the photos + carousel (top row)
   AND the title + intro + button (bottom row) all pull IN by the same amount and stay aligned
   edge-to-edge with each other. space-between over the full viewport spread the gaps
   43px@1440 → 368px@2560. Capping holds tidy spacing and pulls the edges in on wide screens;
   the SAME cap on both rows keeps the photo edges lined up over the title/button edges. */
.ags-exp { --exp-row: calc(var(--ags-frame) - 2 * var(--ags-edge)); }   /* both rows span to Mika's outer guide */
.ags-exp__top.e-con {
  width: 100%;
  max-width: var(--exp-row);
  margin-left: auto;
  margin-right: auto;
  /* 0 (was clamp(16px,2.5vh,32px)): redundant now the stage owns the outer spacing via
     --ags-sec-pad and distributes the slack with space-between. Together the two rows' own
     vertical padding was 84px at 766 and 119px at 1080, which was exactly the overflow that
     pushed the last ink past the bottom pad (-60px at 766). */
  padding: 0;
  align-items: center;
}
/* --- Middle VERTICAL CAROUSEL (js/ageshio-exp.js) ---
   .ags-exp__mid is the MASK: a FIXED 5-slot window (overflow clipped) so only the centred item
   + two above/below show. .ags-exp__track slides vertically inside it (JS transform + ×3 clones
   for a seamless loop). The active item is CENTRED + red + bigger; the others are grey + smaller.
   --slot is the uniform per-line height the JS centres on — each item is forced to ONE line
   (nowrap) so every slot is exactly --slot tall and the centring math is exact. The middle
   column stays a FIXED width across screen sizes; the edge
   PHOTOS grow on larger screens to compact the middle (see .ags-exp__edge). */
.ags-exp {
  /* 1.875rem, not 3rem — Mika's "less spaced out". figma's pitch is ~30px against ~27px text at
     1280 (a ~3px gap); 3rem gave 54px against 24px text, a 30px gap. 1.875rem == 30px at the 1280
     reference and tracks the rem knob from there. */
  --slot: 1.875rem;
  --slots: 3;                        /* VISIBLE slots: 3 (active + one above + one below). More
                                        showed a 6th faded sliver that duplicated an item — with
                                        only 5 unique items, keep the window ≤ (items-1). */
}
/* HOMEPAGE §7 (Simon 2026-08-14): show ALL 5 experience types at once, still rotating (active
   still slides to centre + red + bigger). Window = 5 slots (active + 2 above + 2 below → all 5
   unique items visible, no wrap sliver since window == item count and the JS keeps N items each
   side via the ×3 clone band). Drop the top/bottom mask fade to full-opacity so no line is clipped
   or dimmed by the gradient. Homepage-scoped so interior "C · Experience List" keeps the 3-slot
   masked window. */
body.ags-redesign .ags-exp { --slots: 5; }
/* Experience List (Simon 2026-09-22): VIP + Karate Experiences adopt the homepage's 5-slot, UNMASKED
   look — all five titles visible, no fade mask. Finer title/kanji/photo-size parity + the per-experience
   photo swap + the VIP-clean hover follow in the JS/Elementor pass. */
body.ags-page-vip-karate-retreat .ags-exp,
body.ags-page-karate-experiences .ags-exp { --slots: 5; }
body.ags-page-vip-karate-retreat .ags-exp__mid.e-con,
body.ags-page-karate-experiences .ags-exp__mid.e-con { -webkit-mask-image: none; mask-image: none; }
body.ags-redesign .ags-exp__mid.e-con {
  -webkit-mask-image: none;
          mask-image: none;
}
.ags-exp__mid.e-con {
  flex: 0 0 auto;
  width: 24rem;                      /* compact FIXED width so the edge photos fit the row. The
                                        item font is sized so the longest line ("4 Styles Okinawan
                                        Karate") fits this width even at the ×1.1 active scale. */
  padding: 0;
  height: calc(var(--slot) * var(--slots));
  overflow: hidden;                  /* clip to the slot window (both axes) */
  position: relative;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 30%, #000 70%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0, #000 30%, #000 70%, transparent 100%);
}
.ags-exp__track.e-con { width: 100%; padding: 0; gap: 0; will-change: transform; }
/* Each carousel line = one FIXED-height slot (uniform → centring exact), single line. Font sized
   DOWN from the ags_h3 global so the longest name fits the fixed width comfortably. */
/* The widget row spans the full mask width, but its hover HIT-AREA must be only the WORD, not the
   whole row (else the slightest move into the empty space beside a word switches focus). So the
   row is a flex box that centres an inline-block text span sized to the word; the hover/click
   listener + cursor live on that inner span (.ags-exp__hit, set by the JS), not the row. */
.ags-exp__item.elementor-widget-heading {
  width: 100%; margin: 0;
  height: var(--slot);
  display: flex; align-items: center; justify-content: center;
}
.ags-exp__item .elementor-heading-title {
  margin: 0;
  display: inline-block;             /* shrink to the word → hit-area = the word only */
  width: auto; max-width: 100%;
  white-space: nowrap;               /* one line per slot → uniform slot height */
  cursor: pointer;
  color: #8d8681;                    /* muted grey, inactive */
  opacity: 0.45;
  transform: scale(0.8);             /* out-of-focus lines are SMALLER */
  transition: color .4s ease, opacity .4s ease, transform .4s ease;
}
.ags-exp__item.is-active .elementor-heading-title {
  color: var(--ags-red);             /* focused = red — STAYS red while focused */
  opacity: 1;
  transform: scale(1.1);             /* focused = bigger */
}
/* Font-size scoped under the page root to match the Kit typography global's specificity depth
   (0-4-1) and win on source order — a plain `.ags-exp__item …` selector LOSES to the ags_h3
   Kit global (which kept the items at 33.75px, overflowing the compact mask). ≈22px @1280 so the
   longest line fits the 24rem mask even at ×1.1 active scale. See the type-scale memo gotcha.
   SCOPE: leading class is `.elementor-page` (NOT `.ags-redesign`) so this CORE carousel rule
   applies on BOTH the homepage §7 AND interior "C · Experience List" pages — the homepage <body>
   carries both classes (see body.ags-redesign.elementor-page below), so specificity + source
   order are UNCHANGED for the homepage; interior pages (no .ags-redesign) now get it too. */
.elementor-page [data-elementor-type="wp-page"] .ags-exp__item .elementor-heading-title {
  font-size: 1.375rem;
}
/* Per-experience WYSIWYG cross-fade (2026-09-22): each swap region is a 1-cell grid; all sets
   stack in cell 1/1, only .is-active is opaque. Region auto-sizes to the tallest set (no jump).
   Replaces the old JS-driven .is-swapping opacity flip. Shared across homepage §7 + interior
   .ags-c-explist (base ags-exp* hooks; scope stays under .elementor-page so §7 is unaffected). */
.ags-exp__edge.e-con:has(.ags-exp__photoset) { display: grid; }
.ags-exp__lede.e-con:has(.ags-exp__ledeset) { display: grid; }
/* Untransformed (translated es/de/nl/ja) explist pages have the OLD DOM — the edge holds two
   .ags-portrait photos directly (no .ags-exp__photoset), so the grid-stack above is scoped off them
   with :has(). Restore the side-by-side pair row that (for transformed pages) now lives on
   .ags-exp__photoset. The old lede reverts to its own container flex-column automatically. Remove
   once the translated pages are transformed in the translation pass. */
.elementor-page [data-elementor-type="wp-page"] .ags-exp__edge.e-con:not(:has(.ags-exp__photoset)) {
  display: flex; flex-direction: row; align-items: stretch; gap: calc(8 * var(--ags-px));
}
.ags-exp__photoset, .ags-exp__ledeset {
  grid-area: 1 / 1;
  /* visibility (not just opacity) — an opacity-only hide left a paint-layer "ghost" of the
     inactive stacked sets bleeding through in Chromium (grid-stacked siblings + opacity
     transition): hit-testing/computed-style were correct but the rasterised frame showed
     overlapping text from more than one set. visibility:hidden means the layer is never
     painted at all. Delay visibility on the OUT transition so it still cross-fades (matches
     the .ags-mnav__lang-list idiom already used in this file). */
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .4s ease, visibility 0s linear .4s;
}
.ags-exp__photoset.is-active, .ags-exp__ledeset.is-active {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity .4s ease, visibility 0s;
}
/* the photoset reproduces the old edge PAIR layout (2 portraits in a row) */
.elementor-page [data-elementor-type="wp-page"] .ags-exp__photoset.e-con {
  display: flex; flex-direction: row; align-items: stretch; gap: calc(8 * var(--ags-px)); height: 100%;
}
.ags-exp__edge--left  .ags-exp__photoset { justify-content: flex-start; }
.ags-exp__edge--right .ags-exp__photoset { justify-content: flex-end; }
/* the ledeset reproduces the old lede column (intro over CTA, right-aligned) */
.ags-exp__ledeset.e-con {
  display: flex; flex-direction: column; align-items: flex-end; gap: 1rem;
}
/* Each edge = a PAIR of portraits side-by-side; the outer one bleeds off the edge.
   Scope under [data-elementor-type="wp-page"] to beat Elementor's per-element flex-direction:column.
   SCOPE: leading class is `.elementor-page` (NOT `.ags-redesign`) so the edge-photo columns lay
   out as a row on BOTH homepage §7 AND interior "C · Experience List" (specificity/source order
   unchanged for the homepage — its <body> carries both classes). */
.elementor-page [data-elementor-type="wp-page"] .ags-exp__edge.e-con {
  flex: 0 0 auto;
  /* Photos GROW on larger screens to compact the middle. The
     middle carousel is a FIXED width, so as each edge pair widens with the viewport the middle is
     squeezed inward. Coefficient tuned so the pair never overruns the viewport at the narrow end
     (was 34vw → clipped ~40px at 1440); 30vw keeps both pairs fully on-screen through the range. */
  /* Width-driven (30vw) but ALSO capped by viewport HEIGHT (55lvh): the tall portraits — and thus
     the whole §7 middle row + the section content — shrink on SHORT viewports so §7 fits the 100lvh
     card and the 経験 kanji no longer clips. On normal/tall viewports the height cap is inert (30vw wins). */
  /* 36lvh, was 52lvh. The photos are the only flexible element in the top band, so they absorb the
     height the pad now claims (content ran 41-99px past the bottom pad across 766-1440). This moves
     TOWARD figma, not away: the build's photos were 38.8% of the section height at 1080 where the
     reference is 31.8% (296 of 932); 36lvh lands ~29%. Over-shrinking is harmless — the stage's
     space-between absorbs any slack, so the bottom gap stays exactly --ags-sec-pad. */
  width: min(max(280px, min(30vw, 600px)), 36vh);
  width: min(max(280px, min(30vw, 600px)), 36lvh);
  width: min(max(280px, min(30vw, 600px)), 36vh);
  width: min(clamp(280px, 30vw, 600px), 36vh);
  width: min(clamp(280px, 30vw, 600px), 36lvh);
}
.ags-exp__edge .ags-portrait { flex: 1 1 0; min-width: 0; }
/* Figma s07: all 4 photos are fully visible, inset from the frame edge (they do NOT
   bleed/clip off-screen). Pin each pair to its edge inside the content frame gutter. */
/* No side margin: the outer photo edge sits exactly at the row edge (--exp-row), so the LEFT
   photo's left edge lines up with the title's left, and the RIGHT photo's right edge lines up
   with the button/paragraph's right (both rows share --exp-row + space-between). */
.ags-exp__edge--left  { margin-left: 0; justify-content: flex-start; }
.ags-exp__edge--right { margin-right: 0; justify-content: flex-end; }
.ags-exp__edge .ags-portrait img { aspect-ratio: 180 / 296; }   /* Figma portrait ratio */
/* §7 edge photos are GREYSCALE (matches the reference). Homepage-scoped (body.ags-redesign) so
   interior "C · Experience List" pages are unaffected. */
body.ags-redesign .ags-exp__edge .ags-portrait img { filter: grayscale(1); }
/* Bottom band: big title left, intro + CTA right. Uses the SAME capped+centred width as the top
   row (--exp-row) so its left edge (title) and right edge (button) line up exactly under the
   left and right photo pairs, and it pulls in by the same amount on ultrawide. */
.ags-exp__bottom.e-con {
  width: 100%;
  max-width: var(--exp-row);
  margin-left: auto;
  margin-right: auto;
  padding: 0;   /* see .ags-exp__top — the stage's pad + space-between replace it */
  gap: var(--ags-space-6);
  align-items: flex-end;
}
/* Title wraps to TWO stacked lines (Figma: "Karate" / "Experiences"). The width cap must
   sit on the heading element itself (the widget wrapper ignores max-width in this flex row),
   so force each word onto its own line via width:min-content on the <h2>. */
.ags-exp__title.elementor-widget-heading { flex: 0 1 auto; }
.ags-exp__title .elementor-heading-title { margin: 0; line-height: 0.85; width: -webkit-min-content; width: -moz-min-content; width: min-content; }
.ags-exp__lede.e-con { flex: 0 0 auto; width: max(calc(300 * var(--ags-px)), min(32%, calc(400 * var(--ags-px)))); width: clamp(calc(300 * var(--ags-px)), 32%, calc(400 * var(--ags-px))); }   /* gap moved to .ags-exp__ledeset */
.ags-exp__lede .ags-btn--bar { width: 100%; }
/* INTERIOR explist CTA (KE + VIP) uses the slimmer, letter-spaced pill from the Figma (Group 9.png,
   Simon 2026-09-16): ~30px tall vs the base --bar 36px, ~28px side pad, +letter-spacing. Scoped to
   .ags-c-explist so the homepage §7 --bar is untouched. Same maroon fill / white text / pill radius. */
.elementor-page [data-elementor-type="wp-page"] .ags-c-explist .ags-exp__cta .elementor-button { padding: 0.42em 1.9em; letter-spacing: 0.045em; line-height: 1.2; white-space: nowrap; }
/* Bottom-align the H2 title and the lede (whose last child is the CTA) in the grid's bottom row, so
   the button's bottom edge meets the title's bottom edge (Simon 2026-09-16). align-self beats the
   grid's align-items:start for these two items only. Interior explist (KE + VIP). */
.ags-c-explist .ags-c-explist__h, .ags-c-explist .ags-exp__lede { align-self: end; }
/* Experience-List parity (Simon 2026-09-22, Task 5): interior .ags-c-explist matches homepage §7's
   kanji-eyebrow size and edge-photo treatment. Base ags-exp* hooks stay nested under .ags-c-explist
   so homepage §7 is never touched (it's reached only via body.ags-redesign elsewhere in this file).
   H2 size/line-height parity lives with the existing per-page .ags-c-explist__h rules (see the base
   rule ~5684 and the KE/VIP overrides ~7335/7340) — not duplicated here. Item font (1.375rem, line
   2643) and edge-photo width (min(clamp(280px,30vw,600px),36lvh), line 2696) already matched and are
   untouched. */
/* Kanji-eyebrow glyph: interior's widget carries a flat Elementor-authored 40px (never scaled) —
   homepage's 経験 uses calc(56 * --ags-hpx) (line 3360ish), so interior ran flat while homepage grew
   ~1.49x by 2560. Match homepage's own formula so both scale identically 1024→ultrawide. Specificity
   (0,7,0) beats the per-widget `.elementor-{id} .elementor-element.elementor-element-77c7564a
   .elementor-heading-title{font-size:40px}` (0,4,0) regardless of stylesheet load order. */
.elementor-page [data-elementor-type="wp-page"] .ags-c-explist .ags-c-explist__eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
  font-size: calc(56 * var(--ags-hpx));
}
/* Interior explist photos stay in COLOUR (Simon 2026-09-22): unlike homepage §7 (greyscale), the
   VIP/KE pages carry real colour photos of their own content, so no grayscale filter here. */

/* =============================================================================
   MASTERS (section 9) — name caption OVERLAID on the portrait bottom (Figma s09)
   ========================================================================== */
/* §9 fills the section nearly edge-to-edge (reference: title at x2%, intro/CTA/kanji at x99%,
   cards x15→99%). Widen the OUTER box to ~96vw so the space-between top row throws the title to
   the left edge and the intro+CTA to the right edge. Scoped to the DIRECT boxed child (ms-bx),
   NOT the inner card row (which is also .e-con-boxed — widening it there broke the card span). */
.ags-masters > .e-con-boxed > .e-con-inner {
  width: calc(0.96 * var(--ags-frame)) !important;   /* 96% OF THE FRAME (was min(96vw, 2600px)): the
                                            title/intro/CTA columns keep spreading OUT toward the frame
                                            edges, and now enlarge with it instead of capping. */
  max-width: calc(0.96 * var(--ags-frame)) !important;
}
/* Push the photo row toward the section BOTTOM (per feedback — bottom-aligned like the reference):
   trim the DB's 100px bottom band padding; top trimmed a touch so the section stays ~95svh. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-masters { padding-top: calc(52 * var(--ags-px)); padding-bottom: calc(52 * var(--ags-px)); }  /* §9 SYMMETRIC top/bottom = the existing TOP (52), so the bottom MATCHES it (was 52/6). Cards keep their position; the section simply extends past 100svh rather than pushing the cards up (Simon 2026-09-23 — "don't ruin the layout"). min-height:100svh stays as a floor. */
/* Align the top row's items to the BOTTOM so the title's bottom lines up with the CTA's bottom
   (title left, intro+CTA stacked right; the CTA is the lowest element). The title's 2-line h2
   overflows its own column box downward by ~the line descent, so nudge the whole title column UP
   so the title's VISIBLE bottom matches the CTA bottom. */
/* --- MASTERS edge -> Mika's outer guide (2026-09-08). Measured 39px in from the guide, made of
   TWO parts: Elementor's boxed content-width centred the .e-con-inner to 1382px (29px inset), then
   each nested .e-con added its default 10px. Widen the inner to the frame-minus-guide and clear the
   nested padding. This is the only masters change — she marked it for the EDGE, not the pad. --- */
.ags-masters__bx.e-con { padding-left: 0; padding-right: 0; }
.ags-masters__bx.e-con > .e-con-inner {
  width: calc(var(--ags-frame) - 2 * var(--ags-edge)) !important;   /* max-width alone lost on
     ultrawide (the inner measured 2600px, not 1850) — force width and centre it. */
  max-width: none !important;
  margin-left: auto;
  margin-right: auto;
}
.ags-masters__bx .e-con { padding-left: 0; padding-right: 0; }

/* --- MISSION pad. Mika marked mission for the PAD only (no edge arrow — its text column is
   deliberately centred). Its ink already measured 599px tall at 1440x766 against a 602px padded
   box, so simply reserving the pad lands the gaps on it without touching the composition. --- */
.ags-redesign .ags-mission.e-con { padding-top: var(--ags-sec-pad); padding-bottom: var(--ags-sec-pad); }

.ags-masters__top.e-con { align-items: flex-end !important; gap: 2.2222rem; padding-top: 0; padding-bottom: 0; }   /* gap was 40px; padding-block:0 clears a stray uncleared Elementor default 10px/10px (ONE-VIEWPORT PASS, 2026-09-17) */
/* The 2-line h2 line-box overflows its column bottom (the last line's leading/descent), so its
   VISIBLE bottom sits below the CTA. Pull the TITLE up by ~0.5× its own font-size (em resolves
   against the title's 60px here) so its bottom meets the CTA bottom. Holds as the type scales. */
.ags-masters__top .elementor-heading-title {
  transform: translateY(-0.6em);   /* aligns the title's VISIBLE glyph bottom with the CTA bottom (swept) */
  /* Always wrap to 2 lines ("Authentic" / "Lineage") at every width. Cap the title box in `em`
     (relative to its own font, so it scales with the type): ~9em fits "Authentic" (~5.2em) but
     NOT "Authentic Lineage" (~10.4em) → "Lineage" always drops to line 2. */
  width: 9em;
  max-width: 9em;
}
/* Card row: 4 cards CENTRED on the screen. The row grows with the viewport (photos get a little
   wider/longer on bigger screens) up to a cap, and is centred so it's not shifted to either side.
   `!important` width beats the .e-con-boxed 1120px default this row also carries. */
/* The masters inner box is the positioning context for the absolutely-placed rank legend, AND it
   fills the section height so the content distributes top→bottom: title/intro/button pinned to the
   TOP, the card group + legend pushed to the BOTTOM (no dead space below). */
/* The masters box is .e-con-BOXED: the flex COLUMN that holds the 3 children (top row, card row,
   legend) is its INNER (.e-con-inner), NOT the outer .e-con. So the fill-height + space-between
   MUST go on the inner, or the cards never drop to the bottom (the outer has one child = the inner
   and space-between there is a no-op). position:relative stays on the outer for the absolute legend. */
/* position:relative on the INNER (not the outer) so the absolute legend is measured against the
   content column, whose right edge matches the button/intro — not the wider full-bleed outer box. */
.ags-masters__bx.e-con > .e-con-inner {
  position: relative;
  /* ONE-VIEWPORT PASS (2026-09-17): measured this section running OVER 100svh at every reference
     size (e.g. 842px at 1440x766, an overflow of 76px). Three fixes together close it WITHOUT
     touching the kanji font-size or the card row height (still the same tall portrait boxes):
     (1) the title→kanji + kanji→row gaps were the named lever (Simon), tightened 16.85→4;
     (2) padding-block:0 below (this element + __top + __kanji) clears THREE uncleared Elementor
     default 10px/10px paddings (60px total, flat, every viewport) — dead space that was never a
     visible "gap", just stray box model, same "clear it at every level" rule the interior pages
     already use; (3) once (1)+(2) dropped real content under this min-height FLOOR, the floor
     itself (was 90svh) became the actual binding constraint — trimmed to 88svh so floor+pad
     lands under 100svh with a small margin at every reference size. */
  min-height: 88vh;
  min-height: 88svh;                 /* was 90svh — see note above */
  /* flex-start + `margin-top:auto` on the photo row (below) replaces space-between. Needed because
     the 師範/師 legend is now a FLOW item rather than absolutely positioned — with space-between and
     three children the legend would float to the middle instead of sitting under the title. */
  justify-content: flex-start;
  gap: calc(4 * var(--ags-px));   /* was 16.85 (the old 2.2vh at 766) */
  padding-top: 0;
  padding-bottom: 0;
  /* lvh cap added: the row was vw-only with a 380px floor, so on a short viewport it stayed tall and
     squeezed the title+legend, which is what pushed the absolutely-positioned legend up into the
     heading. Capping by height keeps the whole section proportionate on a 13" laptop. */
  --masters-row-h: min(max(calc(380 * var(--ags-px)), min(34vw, calc(560 * var(--ags-px)))), calc(var(--vh, 1vh) * 46));
  --masters-row-h: min(max(calc(380 * var(--ags-px)), min(34vw, calc(560 * var(--ags-px)))), calc(var(--vh, 1vh) * 46));
  --masters-row-h: min(clamp(calc(380 * var(--ags-px)), 34vw, calc(560 * var(--ags-px))), calc(var(--vh, 1vh) * 46));
}
.ags-masters__row.e-con {
  width: 100% !important;           /* FULL-WIDTH photo row (per feedback — the images take up much more
                                       space); the 師範/師 legend now sits above it on the left, not beside it */
  max-width: 100% !important;
  margin-left: 0 !important;
  margin-right: 0 !important;      /* left-aligned, NOT centred */
  gap: calc(7.2 * var(--ags-px));     /* tight gaps between cards; 7.2 = the old 0.5vw at the 1440 frame */
  justify-content: flex-start;
  align-items: stretch;             /* every card fills the row height */
  order: 3;                         /* DOM order is top -> row -> kanji, so the legend has to be
                                       re-ordered between them (see .ags-masters__kanji). */
  /* FIXED row height — the accordion animates card WIDTH only (flex-grow). Height must NOT be
     derived from each card's width/aspect-ratio, or a card briefly balloons to its image's
     intrinsic height at the instant it becomes active (before flex-grow widens it). A fixed row
     height keeps every card exactly this tall at all times → only width transitions. Scales a
     little with the viewport, capped, so it stays a tall portrait strip like the reference. */
  height: var(--masters-row-h);
}
/* §9 FILL-ONE-VIEWPORT (Simon 2026-09-22, ref pasted): the card row GROWS to absorb the viewport
   slack instead of capping at 46lvh — otherwise on tall viewports the leftover space opened as a
   ballooning legend→cards void (was 31px @766 → 58 @900 → 141 @1080, the "spacing between top and
   bottom" Simon flagged). Mirrors the About page's FILL model. 411 = the space above the row at the
   1440 reference (top pad 52 + title/intro/pill/legend block + legend→cards gap) + 6px bottom pad;
   in --ags-px so it tracks the frame scale. Floor guards short viewports below the verified range.
   Homepage-scoped so the About/History masters (own row-height rules) are untouched. */
/* Card row DEFINITE height (a fixed calc, NOT flex-grow): the master-card images use a percentage
   height chain (`height:100%`), which collapses to 0 if the row height is `auto`/flex-grown — flex-fill
   made the photos disappear. So fill via an explicit calc = 100svh minus the space above the row.
   Per-language constants because the top-block (title+intro+legend) is taller in es/de/nl (longer copy). */
.ags-redesign [data-elementor-type="wp-page"] .ags-masters__row.e-con {
  height: max(calc(300 * var(--ags-px)), calc(0.9 * (100vh - var(--ags-masters-above, 424px) * var(--ags-scale))));
  height: max(calc(300 * var(--ags-px)), calc(0.9 * (100svh - var(--ags-masters-above, 424px) * var(--ags-scale))));
}
/* Photos ~1/10 shorter (Simon 2026-09-22 round 2 — was 1/5, too much): card row = 0.9 of the fill; the
   freed 0.1 + the tightened legend→cards gap (40→16, "kanji closer to the images") become a top margin
   on the 師範/師 legend, bringing the kanji DOWN while keeping it near the cards. Still fills one viewport. */
.ags-redesign [data-elementor-type="wp-page"] .ags-masters__kanji {
  margin-top: calc(0.1 * (100vh - var(--ags-masters-above, 424px) * var(--ags-scale)) + 20 * var(--ags-px));
  margin-top: calc(0.1 * (100svh - var(--ags-masters-above, 424px) * var(--ags-scale)) + 20 * var(--ags-px));
}
/* es/de/nl top-block (title + intro + legend) runs ~27px taller than EN (longer copy), so it needs to
   subtract more or §9 overflows one viewport by 27px. Per-language token (EN/ja keep the 424 default).
   Definite calc — keeps the photo height-chain intact (unlike flex-grow, which collapsed the images). */
body.ags-lang-es, body.ags-lang-de, body.ags-lang-nl { --ags-masters-above: 451px; }
/* The row is .e-con-boxed, so its inner (which holds the cards) is capped at 1120px by default —
   that's what limited the cards to ~205px. Match the inner to the row width so the 5 flex cards
   fill it and grow on ultrawide. */
.ags-masters__row.e-con > .e-con-inner {
  width: 100% !important;
  max-width: 100% !important;
  gap: calc(8 * var(--ags-px)) !important;   /* the cards' ACTUAL flex parent — tighten the whitespace between photos
                            (was 24px from the boxed row's flex_gap; the .ags-masters__row gap
                            never reached the cards because they live in this inner). */
}
/* Keep the 4 master columns equal quarters. Kill Elementor's default 10px .e-con padding so the
   column box == the photo box exactly (else there's a 10px margin around the photo that still
   triggers hover — the hover hit-area must be filled by the photo). */
/* MASTERS ACCORDION (new design). Cards are flex items in .ags-masters__row. Exactly one is
   .is-active (expanded, wide); the rest collapse to slim slivers. JS (ageshio-masters.js, added
   later) only toggles .is-active + auto-rotates; ALL motion is these CSS flex transitions.
   Reduced-motion / no-JS: the server-rendered featured card stays .is-active (static accordion). */
.ags-masters .ags-master.e-con {
  flex: 1 1 0;                 /* collapsed sliver share */
  min-width: 0;
  height: 100%;                /* == the fixed row height (NO aspect-ratio → height never
                                  depends on width, so widening never changes height). */
  padding: 0;
  position: relative;
  overflow: hidden;
  border-radius: 10px;
  transition: flex-grow .5s cubic-bezier(.4,0,.2,1);
  cursor: pointer;
}
/* ROOT-CAUSE FIX for the "photo zooms when the card expands" (Simon 2026-09-21, debugged): the
   photos are 3:2 (1.50). The active card's aspect ran 1.68→2.28 as the viewport widened (row height
   caps at 46lvh while the row widens), so object-fit:cover switched from height-fill to width-fill
   and scaled the photo UP. Capping the active card to the photo's aspect (max-width = 1.5×height)
   keeps cover height-constrained across the whole expand → constant scale, NO zoom, still fills. */
.ags-masters .ags-master.is-active { flex-grow: 3.4; }
/* Cap the active card to the photo's aspect (~1.5) so object-fit:cover stays HEIGHT-driven — the
   photo then fills at the SAME scale whether the card is closed (narrow) or expanded (wide), so
   selecting a card REVEALS width instead of ZOOMING the image (Simon 2026-09-22). The comment above
   intended `1.5 × --masters-row-h`, but the rendered card is ~5% shorter than that var so the calc
   left aspect at 1.58–1.72 (measured zoom 1.02→1.07 @1440, 1.66→1.90 @2560). A lvh cap tracks the
   real card height (~44lvh) directly: 64lvh → aspect ≤1.46, cover stays height-driven, zero zoom.
   About already caps via its own 68lvh (its cards are taller). Desktop only — the ≤1023 carousel
   variant uses full-width cards. */
@media (min-width: 1024px) {
  .ags-redesign .ags-masters .ags-master.is-active { max-width: 64vh; max-width: 64lvh; }
}
/* Photo fills the card; cover-crop keeps the face centred as the card narrows/widens. The whole
   Elementor image chain AND the img are pinned absolute inset:0 so height is locked to the card
   (100% of the fixed row height) and can't fall back to the image's intrinsic aspect. This must
   beat the base `.ags-portrait img { aspect-ratio:3/4; height:auto }` rule — hence the extra
   .ags-master scope + explicit aspect-ratio:auto reset. */
.ags-master .ags-portrait.elementor-widget-image,
.ags-master .ags-portrait .elementor-widget-container,
.ags-master .ags-portrait .elementor-image,
.ags-master .ags-portrait figure { position: absolute; top: 0; right: 0; bottom: 0; left: 0; height: 100%; width: 100%; margin: 0; }
/* Photo FILLS the card (cover) — Simon 2026-09-21: the originals should fill, no black bars. (The
   natural-aspect "window" attempt letterboxed portraits; the grand-master shots are landscape and
   fill cleanly, revealing width as the card widens rather than zooming.) */
.ags-master .ags-portrait img {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  width: 100%; height: 100%;
  aspect-ratio: auto;          /* override base .ags-portrait img aspect-ratio:3/4 */
  -o-object-fit: cover;
     object-fit: cover;
  border-radius: 10px;
  display: block;
}
/* About masters card 3 uses about-master-wide — a LANDSCAPE shot. In a narrow (non-active) slot
   object-fit:cover shows a full-height vertical strip; at the default 50% that's a distant figure +
   ceiling ("mostly ceiling"). Shift the crop to ~32%, where the prominent foreground karateka (with
   the bo) sits, so the narrow slot shows a person. Card 3 only — portrait cards 1/2/4 stay centred.
   Interim until the client's portrait Kenjiro Ueda photo lands. */
body.ags-page-about .ags-masters__row .ags-master:nth-child(3) .ags-portrait img { -o-object-position: 32% center; object-position: 32% center; }
/* round 16 (Simon 2026-09-21, systematically debugged): the "photo zooms when the card expands" on
   the About masters. Root cause: the About body has NO .ags-redesign class, so the homepage's
   --masters-row-h never applies → the shared active-card cap was an invalid calc (ignored), the card
   flex-grew to aspect 1.68→2.28 (>the photos' 1.50), and object-fit:cover scaled the photo UP. Cap
   the active card to the photo aspect with a VALID viewport value so cover stays height-constrained
   (constant scale = no zoom, still fills). 68lvh keeps aspect ≤~1.5 across the desktop range. */
body.ags-page-about .ags-masters__row .ags-master.is-active { max-width: 68vh; max-width: 68lvh; }
/* round 15 (Simon 2026-09-21): About "Connection to Grand Masters" now uses the DB grand-master
   photos (Onaga/Akamine/Kakazu/Uema). The shared data-rank-len caption pad wasn't clearing the
   2-char 師範 rank here (name clashed with the badge), so re-assert the kanji-clearing pad per rank
   length, About-scoped, with !important. */
body.ags-page-about .ags-master[data-rank-len="2"] .ags-master__caption { padding-left: calc(72 * var(--ags-px)) !important; }
body.ags-page-about .ags-master[data-rank-len="1"] .ags-master__caption { padding-left: calc(44 * var(--ags-px)) !important; }
/* Dark scrim at the bottom for caption/kanji legibility. */
.ags-master::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0; height: 45%;
  background: linear-gradient(to top, rgba(17,16,16,0.85), rgba(17,16,16,0));
  pointer-events: none; z-index: 1;
}
/* Rank kanji — visible when COLLAPSED, fades out when active. Bottom-centred on the sliver. */
.ags-master__rank.elementor-widget-heading {
  position: absolute; left: 0; right: 0; bottom: calc(14 * var(--ags-px));
  margin: 0; text-align: center; z-index: 2;
  opacity: 1; transition: opacity .3s ease;
  pointer-events: none;
}
/* Active card — reference layout: the rank kanji moves to the bottom-LEFT and turns RED; the name sits
   to its right and the short role floats to the bottom-RIGHT, all on one baseline: [師] Name ...... Role. */
.ags-master.is-active .ags-master__rank {
  opacity: 1;
  left: calc(16 * var(--ags-px)); right: auto;
  text-align: left;
}   /* kanji stays cream (matches the slivers + reference) — no colour override */
/* Caption becomes a baseline flex ROW: name (left, cleared past the kanji) + role (pushed right). Hidden
   when collapsed, fades in when active. */
.ags-master__caption.e-con {
  position: absolute; left: calc(16 * var(--ags-px)); right: calc(16 * var(--ags-px)); bottom: calc(14 * var(--ags-px));
  width: auto; z-index: 2;
  padding: 0;   /* kill Elementor's default .e-con padding — it lifted the flex-end name/pill ~10px off the
                   caption's bottom (misaligning them from the kanji) AND ate width (pill overflowed). */
  /* align-items:flex-end → the name box, the pill box and the kanji (matching bottoms + line-height:1) all
     sit on ONE bottom line. padding-left (per data-rank-len below) clears the kanji so the name never clashes. */
  flex-direction: row; align-items: flex-end; justify-content: space-between; gap: calc(8 * var(--ags-px));
  opacity: 0; transform: translateY(calc(8 * var(--ags-px)));
  transition: opacity .35s ease .1s, transform .35s ease .1s;
  pointer-events: none;
}
.ags-master.is-active .ags-master__caption { opacity: 1; transform: none; }
/* Clear the bottom-left kanji with a CONSISTENT gap to the name: pad by the kanji's own width (30px/char)
   + a fixed gap, keyed off data-rank-len (set in ageshio-masters.js: 1 = 師, 2 = 師範). A single fixed pad
   left 師 cards a big gap while 師範 clashed. */
.ags-master[data-rank-len="1"] .ags-master__caption.e-con { padding-left: calc(42 * var(--ags-px)); }
.ags-master[data-rank-len="2"] .ags-master__caption.e-con { padding-left: calc(70 * var(--ags-px)); }
.ags-master__name  { flex: 0 1 auto; min-width: 0; }
.ags-master__title { flex: 0 0 auto; }
.ags-master__rank .elementor-heading-title  { line-height: 1; }   /* glyph bottom == box bottom → bottom-aligns with the name/pill */
.ags-master__name .elementor-heading-title  { line-height: 1; white-space: nowrap; margin: 0; }
/* Role = a small coloured PILL bottom-right (reference): maroon badge, cream text, rounded. */
.ags-master__title .elementor-heading-title {
  display: inline-block; margin: 0; line-height: 1; white-space: nowrap;
  padding: calc(4 * var(--ags-px)) calc(12 * var(--ags-px)); border-radius: 999px;
  background: var(--ags-red); color: var(--ags-cream);
}
/* Name + role keep the Kit font (Red Rose) — bumped up a touch; sizes beat the Kit heading global via the
   deep scope. (Kanji stays Shippori Mincho.) */
body.ags-redesign [data-elementor-type="wp-page"] .ags-master__name .elementor-heading-title  { font-size: calc(18 * var(--ags-px)); }
body.ags-redesign [data-elementor-type="wp-page"] .ags-master__title .elementor-heading-title { font-size: calc(13 * var(--ags-px)); }
/* Intro paragraph: width in `em` (body Kit font is a floorless vw clamp — locks the wrap across
   widths, see §6/§8). ~24.4em ≈ 390px @16px → the 4-line reference wrap (L1 ends "Okinawa's",
   L2 "…are", L3 "…authenticity in", L4 "…session."). Right-aligned block, pinned to the right. */
/* Intro copy wraps to exactly 3 lines (reference): "…guided by Okinawa's Grand Masters" /
   "…real karate lineages, ensuring authenticity in" / "every training session." ~30em at the
   body font gives that 3-line measure; right-aligned, pinned to the right of its column. */
.ags-masters__intro.elementor-widget-text-editor { width: 30em; max-width: 100%; margin-left: auto; }
/* Masters 師範/師 legend → absolutely pinned to the BOTTOM-RIGHT of the section inner box. Its
   right edge aligns with the right-side elements (title/intro/button) and its bottom aligns with
   the card row's bottom (the row ends ~10px above the box bottom due to the space-between fill,
   so offset by that). Removed from flow → never affects the card row's flex sizing. */
.ags-masters__kanji.e-con {
  /* IN THE FLOW, not absolute (2026-09-08). It used to be `position:absolute; bottom:
     calc(--masters-row-h + 28px)` — anchored UP from the photo row. Since the row's height was
     vw-driven with a 380px floor, a short viewport pushed the legend straight up THROUGH the
     "Authentic Lineage" heading (measured -132px of overlap at 1280x700 and still -15px at
     1728x1080). Absolute positioning meant it could never push anything out of the way, so the
     clash was unavoidable at some aspect. As a flow item directly after the top row it sits
     UNDER the title by construction and simply cannot collide — at any size. */
  position: static;
  /* The DOM order is top -> row -> LEGEND, which is why this was absolute in the first place. Flex
     `order` slots it between the heading and the photo row instead. `margin-bottom: auto` sends all
     the free space BELOW the legend, so it hugs the title (Simon: "underneath Authentic Lineage
     Title") and the row still lands at the bottom — without space-between, which would have
     floated the legend to the middle of the leftover space. */
  order: 2;
  margin-bottom: auto;
  align-self: flex-start;            /* LEFT-aligned with the heading (per mockup) */
  gap: 1.1111rem;                    /* was 20px */
  width: auto !important;
  align-items: flex-end;             /* the two legend items baseline-align to each other */
  padding-top: 0;
  padding-bottom: 0;                  /* clears a stray uncleared Elementor default 10px/10px (ONE-VIEWPORT PASS, 2026-09-17) */
}
/* Each legend item = kanji (big serif) + romanization label beneath, kept to ONE line so both
   items baseline-align. The label column must not wrap "(Grand Master)". */
.ags-masters__legenditem.e-con { width: auto !important; flex: 0 0 auto; gap: 0.1111rem; }   /* was 2px */
/* item 4 (Simon 2026-09-22): more breathing room between the 師範/師 legend and the cards row (the
   "pink line" gap). The legend's margin-bottom:auto floats the free space below it; this adds a floor
   so the gap reads more generous and never collapses tight on shorter viewports. Homepage only. */
.ags-redesign .ags-masters__row { margin-top: calc(16 * var(--ags-px)); }  /* legend→cards gap: tight ~16px so the kanji sits CLOSE to the images (Simon 2026-09-22 r2); was 40. Stable --ags-px (NOT svh, which ballooned the gap on tall viewports); the card-row fill + kanji margin absorb the slack */
.ags-masters__legenditem .elementor-heading-title { white-space: nowrap; }

/* =============================================================================
   GROUP TOUR (§5) — scale-as-ONE-unit on a container-query stage. The stage keeps the
   reference's aspect-ratio and grows to fill the viewport: `width: min(96vw, height-cap)`.
   As the browser narrows the stage shrinks and elements pull IN; as it WIDENS the stage
   grows and every element — placed by `%`, sized in `cqw` — spreads OUTWARD with it (the
   left photo/eyebrow move left, the right column moves right). The only ceiling is the
   height cap (96svh × aspect): GT is a 100svh deck card, so the stage can't get wider than
   the screen height allows without overflowing — that's the natural "max spread" (no
   arbitrary px cap, which was freezing it small and centred on ultrawide). font + box are
   both cqw so the wrap never breaks. Coords = measured s05 reference as % of its 1280
   frame. --- */
/* .e-con.e-flex (0,2,0) so this wins over Elementor's own .e-con.e-flex defaults (same
   specificity, but this rule is later in source order) — needed for --flex-shrink below. */
.ags-gt-stage.e-con {
  container-type: inline-size;
  position: relative;
  /* Width GROWS with the viewport (up to a generous cap) so elements spread outward on
     ultrawide; height is bounded to the 100svh deck card. Because width is no longer tied to
     a fixed aspect-ratio, the stage gets WIDER (not just taller) on big screens → the left
     photo/eyebrow move further left and the right column further right. Fonts + wrap-critical
     box widths below are capped with min(cqw, px) so text stays controlled while POSITIONS
     (%) keep spreading. */
  /* ---- UNWELDED (2026-09-08, Mika's spacing feedback) --------------------------------
     Width and height are now INDEPENDENT. Before, the stage was one rigid picture: a
     floored natural height (--gt-floor: 1000px) plus a uniform scale-to-fit. That welded
     the axes together — spanning the full width forced a height of width/1.353, which does
     not fit a laptop viewport, so the whole stage shrank. And shrinking a picture pulls its
     edges inward: measured, the ink sat 233px from the outer guide when scaled vs 35px at
     scale 1. THAT inward pull was the "align box to outer guide" complaint, so the scale
     had to go — not the composition, which is already faithful to figma/Desktop.png.

     Now: width = frame inset by the outer guide, height = the full deck card. Nothing
     scales, so the edges hold. The height difference is absorbed by the PHOTOS cropping
     (explicit height + object-fit:cover below) rather than by forcing the stage taller.
     --gt-floor is gone: the crush it guarded against came from %-positioned boxes over
     fixed-height text, which the pad-anchored positions below clear directly.

     The pad is not a padding property here. The topmost ink anchors to
     `top: var(--ags-sec-pad)` and the bottommost to `bottom: var(--ags-sec-pad)` — real
     lengths on absolutely-positioned boxes — so the visible gap IS the knob, exactly,
     at every viewport height. */
  width: calc(var(--ags-frame) - 2 * var(--ags-edge));
  height: 100%;
  /* Elementor flex children shrink via its --flex-shrink custom prop
     (flex: 0 var(--flex-shrink) auto). 0 so the deck card can't shrink the stage.  */
  --flex-shrink: 0;
  margin-left: auto;
  margin-right: auto;

  /* RIGHT-COLUMN FLOW. The intro/button/photo stack cannot use `top: N%` any more: the intro's
     font is cqw-capped, so its height tracks WIDTH while a % top tracks HEIGHT. On a 766px
     viewport pad + 3 lines already reaches 204px, so the old `top: 31%` (=237) put the small
     photo ON TOP of the BOOK A GROUP TOUR pill. Chaining each item off the one above is
     collision-proof at any aspect. Both photos share --gt-photo-top so their top edges stay
     locked together (Simon's invariant / figma y287-y294). */
  /* TYPE scales with viewport HEIGHT; the LAYOUT does not. This is the decoupling that lets all
     four asks hold at once. The old whole-stage scale() shrank the layout too, which is what
     pulled the edges off the guide. Shrinking only the type keeps the box full-bleed.
     Why it's needed: at 766 the pad + 3-line intro + button stack is 306px, which pushes the
     photo's CENTRE (495) BELOW the copy's top (466) — inverting figma, where the copy top (640)
     sits below the photo centre (534). So the title, centred on the photo, ran into the copy.
     min(1, 100svh/1000px) is a NO-OP at >=1000px tall (desktop type is byte-identical) and
     trims ~23% at 766, which is exactly enough to restore figma's stacking order. */
  /* REFERENCE-FRAME TYPE SCALE. Was min(1, 100svh/1000px) — a scale that GREW with viewport
     height, which on ultrawide stacks on top of the (now unfrozen) cqw growth and oversized the
     type ~1.3x. 0.766 is exactly what the old expression resolved to at the 1440x766 reference,
     and --ags-tsc reproduces the old height response below it (0.914 at 700 tall → 0.766*0.914
     = 0.700, i.e. the old min(1, 0.7) to 3dp). So the laptop range is byte-identical and
     ultrawide becomes a clean enlargement. cqw tracks the FRAME, not the content, so this is
     also the term that stops a wide-but-SHORT viewport (1920x900) oversizing the type. */
  --gt-tsc: calc(0.766 * var(--ags-tsc));
  --gt-fs-intro: calc(var(--gt-tsc) * min(2.35cqw, calc(33 * var(--ags-px))));
  --gt-fs-btn: calc(var(--gt-tsc) * min(1cqw, calc(14 * var(--ags-px))));
  /* Line count is a VARIABLE, not a constant: en wraps the intro to 3 lines, es/de/nl to 4. With 3
     hard-coded the button sat one line too high and landed ON the Spanish intro (-2px at 1440,
     -13px at ultrawide). Same class of bug as the old 2-line title assumption. */
  --gt-intro-lines: 3;
  --gt-intro-h: calc(1.25 * var(--gt-intro-lines) * var(--gt-fs-intro));
  /* 3.6x, measured: the pill renders 35px tall at 766 where 2.4x predicted 25.5px, and that 10px
     shortfall left the small photo only 2px below the button. */
  --gt-btn-h: calc(3.6 * var(--gt-fs-btn));
  /* ONE gap used twice, so intro->button and button->photo read identically (Simon). Equalising
     them also LIFTS the button + small photo, and the big photo follows because it hangs off
     --gt-photo-top too. */
  --gt-rgap: 1.6rem;
  --gt-photo-top: calc(var(--ags-sec-pad) + var(--gt-intro-h) + var(--gt-rgap) + var(--gt-btn-h) + var(--gt-rgap));

  /* PHOTO SHAPES ARE PRESERVED. The unweld must not reshape them: the big photo stays a vertical
     rectangle (392:493) and the small one a square (179:176). The trick is direction — deriving
     HEIGHT from width is what forced the stage taller than the viewport in the first place, so
     instead derive WIDTH from the available height. Same ratio, no distortion, and it fits by
     construction; on a short viewport the photo just gets smaller rather than cropped.
     cqw (not %) for the width cap so it stays a WIDTH measure even inside a height calc. */
  --gt-large-wcap: min(30.6cqw, calc(440 * var(--ags-px)));
  --gt-large-h: min(var(--gt-photo-h), calc(1.2577 * var(--gt-large-wcap)));   /* 493/392 */
  /* Photos run to the bottom pad, so they absorb ALL the leftover height by cropping. */
  /* lvh, NOT 100%. A `%` inside a custom property resolves against whatever axis the property
     USING it cares about — so this var read as 100% of the stage WIDTH inside
     `width: calc(0.7951 * var(--gt-large-h))` and the big photo came out 418x414 (ratio 1.009)
     instead of 0.795. lvh is axis-independent, and equals the stage height because the stage is
     height:100% of a 100lvh deck card. */
  --gt-photo-h: calc(calc(var(--vh, 1vh) * 100) - var(--gt-photo-top) - var(--ags-sec-pad));

  /* TITLE WINDOW. The title cannot simply centre on the photo: the photo runs to the bottom pad
     while the copy is bottom-anchored, so the photo's centre can sit BELOW the copy's top and the
     two collide — and non-monotonically (fine at 766 and 1080, -28px at 860). So bound the title
     by the copy's actual top, derived from its own font metrics the same way --gt-intro-h is.
     0.612 = figma's bias inside that window (title centre 503 in a 287..640 window), which keeps
     the title below the window centre and above the photo centre exactly as the reference has it. */
  --gt-fs-copy: calc(var(--gt-tsc) * min(1.5cqw, calc(21 * var(--ags-px))));
  --gt-fs-link: calc(var(--gt-tsc) * min(1.05cqw, calc(15 * var(--ags-px))));
  --gt-fs-title: calc(var(--gt-tsc) * min(5.4cqw, calc(76 * var(--ags-px))));
  --gt-fs-kanji: calc(var(--gt-tsc) * min(5cqw, calc(72 * var(--ags-px))));
  --gt-title-h: calc(2.1 * var(--gt-fs-title));            /* 2 lines x 1.05 */
  /* +1.5rem safety: the bare metric estimate ran 2-6px short at 860/894 and the title grazed the
     copy. Erring tall costs nothing (the title just sits a few px higher). */
  /* --gt-copy-extra: per-language allowance. This estimate is derived from the ENGLISH copy (5
     rendered lines, 161px). The same paragraph runs 7 lines in es/nl (209px) and 6 in de (185px), so
     the estimate fell ~26-48px short and the title's collision bound was computed against a copy top
     that didn't exist — the title then overlapped the real copy (-38px on /es). Note the estimate
     ONLY feeds the title's bound; the copy itself is bottom-anchored and content-driven, so
     over-estimating is safe (it just lifts the title). Set per language below. */
  --gt-copy-extra: 0px;
  /* 540 reference px == 414 effective at 1440x766 -> the figma 5-line wrap. Translations override
     this below; measured per language by sweeping the box width against the rendered line count
     (scripts/qa, 10px steps) and taking the CENTRE of each language's window, not a value sitting
     next to a wrap flip. */
  --gt-copy-w: 540;
  --gt-copy-h: calc(6 * var(--gt-fs-copy) + 1.4 * var(--gt-fs-link) + 3.2cqw + 1.5rem
                    + var(--gt-copy-extra));
  --gt-copy-top: calc(calc(var(--vh, 1vh) * 100) - var(--ags-sec-pad) - var(--gt-copy-h));   /* lvh for the same reason */
  --gt-title-window: calc(var(--gt-copy-top) - var(--gt-photo-top));
}
/* padding:0 kills Elementor's default 10px .e-con padding, which inset EVERY element 10px from
   its own box — so `left: 0` / `right: 0` landed the ink 10px inside the outer guide instead of on
   it. (Same stray-10px gotcha as the card work.) */
.ags-gt-stage > .e-con { position: absolute; margin: 0; padding: 0; }
/* ...and on NESTED containers + inner wrappers. The eyebrow is .e-con-full wrapping ANOTHER
   .e-con-full that carried the 10px, so zeroing only the direct children still left 旅行 10px off
   the guide (box at 26, glyphs at 36) and inflated its max-content width to 129 for a 109px
   title. Elementor sprinkles this default at every container level, so clear it at every level. */
.ags-gt-stage .e-con,
.ags-gt-stage .e-con-inner { padding: 0; }
/* Positions are % (spread with the stage); font-sizes are min(cqw, px) so they scale up to
   the ~1400px design size then FREEZE (no ballooning as the stage widens further on ultrawide).
   Wrap-critical box widths are likewise min(%, px) so the wrap holds once past the cap. */
/* 旅行/Tour eyebrow — far left; kanji big. Its box centre sits at the big photo's left edge. */
/* 旅行 ink sits ON the outer guide (Mika: "align circle to outer guide"). left:0 + a LEFT
   text-align, not centred-in-a-12%-box — centring left the ink 61px inside the guide. It sits
   a touch below the intro's top line, matching figma/Desktop.png (kanji y90 vs intro y78). */
/* width:max-content, NOT 12% + text-align. The 旅行 glyphs were 109px inside a 166px box and
   sat 29px off the guide — but the cause was flex-item centring, not text-align (the title
   computes text-align:left and still didn't move). Shrinking the box to hug the glyphs puts the
   ink ON the guide at left:0, AND keeps "Tour" centred under 旅行 as figma/Desktop.png has it
   (Tour x72 vs 旅行 x40 = a 32px inset, which centring in a 109px box reproduces exactly). */
/* Kanji PAINTED top lines up with the intro's painted top line (Simon). Aligning the element BOXES
   was not enough and read as extra padding above 旅行: Shippori Mincho's CJK glyphs sit lower in
   their line box than a Latin cap-height, so equal box tops left the ink 25px low (measured off the
   rendered pixels, not the rects). Each font needs its own coefficient because the two leadings
   scale independently: solving painted-kanji == painted-intro gives
   top = pad - 0.285*fs-intro - 0.134*fs-kanji. */
.ags-gt-eyebrow { left: 0; top: calc(var(--ags-sec-pad) - 0.285 * var(--gt-fs-intro) - 0.134 * var(--gt-fs-kanji)); width: -webkit-max-content; width: -moz-max-content; width: max-content; }
/* Centre the label ("Tour") under the kanji regardless of the kanji's width — the box is max-content
   (= the wider kanji), so align-items:center + text-align:center puts the shorter label on the kanji's
   centre line. (Kept robust after the 旅行→旅 kanji change, which halved the kanji width, 2026-09-23.) */
.ags-gt-eyebrow.e-con { align-items: center; }
.ags-gt-eyebrow .elementor-heading-title { text-align: center; }
/* nowrap so the kanji never stacks vertically when the eyebrow box gets narrow (small widths). */
.ags-gt-eyebrow div.elementor-heading-title { font-size: calc(var(--gt-tsc) * min(5cqw, calc(72 * var(--ags-px)))); line-height: 1.1; white-space: nowrap; }
.ags-gt-eyebrow .elementor-heading-title:last-child { font-size: calc(var(--gt-tsc) * min(1.15cqw, calc(17 * var(--ags-px)))); }   /* "Tour" */
/* Intro — top-right, 3 lines, right-aligned. Box width capped in px so the 3-line wrap holds
   once the font freezes. */
/* Intro raised (7%→5%) so the 3-line lede clears the button below it. The lede height grows
   with width (font min(2.35cqw,33px) caps ~1440), so at a fixed button top the gap collapsed to
   ~3px past 1600 — raising the intro (costs nothing below it) restores a consistent gap. */
/* TOPMOST ink in the section -> anchors the pad. The -0.5*font term removes the line box's
   leading + ascender-to-cap gap, which sits ABOVE the visible glyphs: without it the box was at
   82 but the ink read 99 (+17). Compensating in the font's own unit keeps it exact as the font
   scales, so the measured gap equals --ags-sec-pad rather than pad-plus-leading. */
.ags-gt-intro { right: 0; top: calc(var(--ags-sec-pad) - 0.5 * var(--gt-fs-intro)); width: calc(var(--gt-tsc) * min(44%, calc(620 * var(--ags-px)))); }
.ags-gt-intro .elementor-widget-container { text-align: right; }
/* The <p> keeps the browser default `margin: 1em 0` — 32px top AND bottom at this font size,
   which pushed the ink 62px below the box and broke the `top: var(--ags-sec-pad)` anchor
   (ink landed at 153 instead of 82). .ags-gt-copy p already zeroes it; the intro never did. */
.ags-gt-intro p { margin: 0; }
.ags-gt-intro p { font-size: var(--gt-fs-intro); line-height: 1.25; }
/* TRANSLATIONS get a WIDER intro box. es/de/nl wrap this lede to FOUR lines against English's
   three, and --gt-intro-lines feeds --gt-photo-top, so the whole right-hand stack (button, both
   photos, and the title that tracks them) was pushed ~31px further down in every translation —
   which is half of why the body copy then ran into the small photo. Widening costs nothing: the
   intro is right-anchored at the top of the stage with only the far-left 旅行 eyebrow beside it.
   Both terms of the min() have to rise or the % term keeps capping it at the old width. */
body:not(.ags-lang-en) .ags-gt-intro { width: calc(var(--gt-tsc) * min(58%, calc(830 * var(--ags-px)))); }
/* BOOK A GROUP TOUR — small pill below the intro, right. */
/* Chained off the intro rather than a % of height: the intro is 3 lines of a cqw-capped font,
   so its height tracks WIDTH, not viewport height. A % top collided with it on short
   viewports (intro bottom 204 vs button top 184 at 766). pad + 3 lines + a gap can't collide. */
.ags-gt-btn { right: 0; top: calc(var(--ags-sec-pad) + var(--gt-intro-h) + var(--gt-rgap)); }
.ags-gt-btn .elementor-button { font-size: var(--gt-fs-btn); padding: 0.5em 2.2em; }
/* Small dojo photo — below the button, right (square). Size capped so it doesn't grow huge. */
/* PHOTOS ABSORB THE HEIGHT. Explicit height in % of the stage + object-fit:cover, instead of
   `aspect-ratio` + `height:auto`. aspect-ratio meant "however wide you are, be THIS tall", which
   is what forced the stage past the viewport and triggered the scale-down. Now a short viewport
   crops the image instead of shrinking the section — the box keeps its position and its width,
   so the shared top edge and the title/photo-centre relationship both survive. */
/* height ratio 0.358 = figma's 177/494 small-to-large. */
/* Square (179:176). Height derived from the width in cqw rather than via aspect-ratio — the
   box is a flex container and aspect-ratio lost to the img chain's height:100%, giving a 1.5
   ratio. cqw is a WIDTH measure usable in a height property, so this is exact. */
.ags-gt-small { right: 0; top: var(--gt-photo-top); width: min(14cqw, calc(200 * var(--ags-px))); height: calc(0.9832 * min(14cqw, calc(200 * var(--ags-px)))); }
.ags-gt-small img { aspect-ratio: auto; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; }
/* The WIDGET element needs the height too, not just the inner chain. The box measured a correct
   191x188 but the widget inside stayed 191x128, so the img filled the widget rather than the box
   and the square read 1.5. The big photo escaped this only because it carries .ags-portrait, whose
   global rule includes `.ags-portrait.elementor-widget-image` — i.e. the widget itself. */
.ags-gt-stage .elementor-widget-image { height: 100%; }
/* Large portrait — LEFT edge aligned with the 旅行 kanji's middle (eyebrow left:2% + 6% =
   8%). Top aligned with the small photo. Width capped so it stays a portrait, not a slab. */
/* left 8%->6%, tracking the eyebrow's 2%->0 move so the photo keeps its old relationship to
   the kanji. Same top as the small photo (Simon's invariant, and figma y287/y294). */
.ags-gt-large { left: 6%; top: var(--gt-photo-top); height: var(--gt-large-h); width: calc(0.7951 * var(--gt-large-h)); }   /* 392/493 */
.ags-gt-large img { aspect-ratio: auto; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; }
/* Title "Okinawan Group Tour" — positioned closer to the big photo so it reads as visually
   grouped with the image rather than floating mid-canvas. Box width (capped px) → always breaks
   "Okinawan" / "Group Tour" = 2 lines. NO transform on the positioned box (fadeInUp animates
   the inner widget's transform; the box just holds position, flex-centres the 2-line title). */
.ags-gt-title {
  /* Anchor the title's LEFT to the big photo's ACTUAL right edge (8% + min(30.6%,440px)) plus a
     small constant 2.5% gap, using the SAME expression as .ags-gt-large's geometry. Because the
     photo's width has a px cap, its right edge (as a % of the stage) MOVES with width — ~38.6% at
     laptop (30.6% < 440px) shrinking to ~28% on ultrawide (440px caps at ~20%). A fixed `left`
     therefore can't work: it overlapped the photo at narrow widths yet detached on ultrawide.
     Tracking the edge keeps a consistent hug at every width. */
  /* 8%->6% tracks .ags-gt-large's new left. Vertically the title now TRACKS the photo instead of
     using a fixed %, so Simon's invariant survives the photo becoming calc-driven: box spans the
     photo exactly, content centred, then lifted 3.2% of the photo height — the same offset
     figma/Desktop.png has (title centre 503 vs photo centre 534 of a 494-tall photo). */
  /* Anchored to the photo's ACTUAL right edge. This tracked min(30.6%,440px) — the OLD width — but
     the photo's width is now derived from its height, so the anchor was stale and left a 129px gap
     (Simon: "the main photo needs to be closer to the title"). 3.3% is figma's own gap (42px of
     1280: photo right 505, title left 547). */
  left: calc(6% + 0.7951 * var(--gt-large-h) + 3.3%); width: calc(var(--gt-tsc) * min(40%, calc(560 * var(--ags-px))));
  /* Whichever of the two constraints sits HIGHER wins (min = smaller `top`):
       0.437 * photo-h        = figma's relationship, photo centre lifted 6.3% of the photo height
       0.612 * title-window   = the collision-safe bound against the copy's top
     Neither alone works. Pure photo-tracking collided at 860/894; pure window-bias put the title
     BELOW the photo centre on ultrawide (-17px), because a tall photo runs to the bottom pad and
     drags its centre down. min() takes the reference relationship when there is room and the safe
     bound when there isn't. */
  /* LINE-COUNT AGNOSTIC (fixes /es and /nl). Subtracting `0.5 * --gt-title-h` hard-coded a TWO-line
     title (--gt-title-h = 2.1 * font). Spanish and Dutch wrap to THREE lines, so the box was
     under-measured and the title spilled into the body copy (-52px on /es, -50px on /nl).
     translateY(-50%) centres the box on the same point WITHOUT needing its height, so any line
     count in any language lands correctly. Bias trimmed 0.612 -> 0.55 of the band to leave a
     3-line title room to clear the copy; that lifts the English title ~15px, which is within
     figma tolerance and worth it for the robustness. */
  top: calc(var(--gt-photo-top)
            + min(0.437 * var(--gt-large-h), 0.55 * var(--gt-title-window)));
  height: auto;
  transform: translateY(-50%);
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start;
}
/* TRANSLATIONS get a wider title box so the 2-line figma wrap holds in every language. es and nl
   wrapped "Tour en / grupo por / Okinawa" to THREE lines in the 425px reference box, and because the
   box is centred with translateY(-50%) a third line pushed the title's first line UP past the big
   photo's top edge — Simon's "the title loses its positioning". Measured: es/nl flip to 2 lines at
   >=440 effective px, so 470 sits 30px clear of that boundary while the widest painted line is only
   435-440px. en (ink 352) and de (2 lines already at 340) are unaffected by the value; English keeps
   its figma box regardless. Both min() terms rise together or the % keeps capping it. */
body:not(.ags-lang-en) .ags-gt-title { width: calc(var(--gt-tsc) * min(45%, calc(614 * var(--ags-px)))); }
.ags-gt-title .elementor-heading-title { font-size: calc(var(--gt-tsc) * min(5.4cqw, calc(76 * var(--ags-px)))); line-height: 1.05; }
/* HEADINGS need the page-type scope or the Elementor Kit global wins. Measured proof: the title
   rendered at exactly 3.75rem (67.5px @1440, 81px @1728) — i.e. the Kit value — so --gt-tsc never
   reached it and the title kept colliding with the copy. The <p>-based intro/copy are not
   Kit-governed and did scale. Same depth trick the interior pages need. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-gt-title .elementor-heading-title {
  font-size: calc(var(--gt-tsc) * min(5.4cqw, calc(76 * var(--ags-px)))); line-height: 1.05;
}
/* Split on the WIDGET, not on .elementor-heading-title:last-child — that selector matches BOTH
   headings, because each title is the last child of its own .elementor-widget-container. The
   17px "Tour" cap was therefore also hitting 旅行 (rendered 12px instead of 53px). It only
   surfaced now: before these page-scoped rules the Kit global outranked both. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-gt-eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
  font-size: calc(var(--gt-tsc) * min(5cqw, calc(72 * var(--ags-px))));
}
body.ags-redesign [data-elementor-type="wp-page"] .ags-gt-eyebrow .elementor-widget-heading:last-child .elementor-heading-title {
  font-size: calc(var(--gt-tsc) * min(1.15cqw, calc(17 * var(--ags-px))));
}
/* Body copy — right-aligned, sits BELOW the title (top:70% clears the title band). Box ~490px
   at 1280 → main paragraph wraps to 3 lines and "We take care…" sits on ONE line (reference). */
/* BOTTOMMOST ink -> anchored to the bottom pad, not a % top. A % top could not hold the gap:
   this block's height is content-driven (3 wrapped lines + 2 more), so a fixed top left the
   bottom gap drifting with viewport height (44px at 766 vs 82 wanted). bottom: pad is exact. */
/* WIDTH IS PER LANGUAGE (--gt-copy-w, in reference px; set below). Same lever as VIP's copy —
   widen the box rather than shrink the type — but here it also buys back vertical room: this block
   is bottom-anchored, so every line it loses drops its top away from the small photo above it.
   The 60% term never binds (40% of the stage is always > 540 reference px at every width); it is
   only a sanity ceiling so the box can't outrun the stage. */
.ags-gt-copy { right: 0; top: auto; bottom: var(--ags-sec-pad); width: calc(var(--gt-tsc) * min(60%, calc(var(--gt-copy-w) * var(--ags-px)))); display: flex; flex-direction: column; align-items: flex-end; gap: 1.6cqw; }
.ags-gt-copy p { font-size: calc(var(--gt-tsc) * min(1.5cqw, calc(21 * var(--ags-px)))); line-height: 1.5; margin: 0; }
.ags-gt-copy a { font-size: calc(var(--gt-tsc) * min(1.05cqw, calc(15 * var(--ags-px)))); line-height: 1.4; }
/* Image widget chain fills its positioned box. height:100% (was auto) so the explicit box
   heights above actually reach the <img> — with `auto` anywhere in the chain the image goes
   back to its intrinsic aspect and the crop never happens. Overrides the global
   `.ags-portrait img { aspect-ratio: 3/4 }` by source order (same specificity, later). */
.ags-gt-stage .elementor-widget-image .elementor-widget-container,
.ags-gt-stage .elementor-widget-image figure,
.ags-gt-stage .elementor-widget-image .elementor-image { width: 100%; height: 100%; }
.ags-gt-stage .ags-portrait,
.ags-gt-stage .ags-portrait .elementor-widget-container,
.ags-gt-stage .ags-portrait figure { height: 100%; }
.ags-gt-stage .ags-portrait img { aspect-ratio: auto; height: 100%; }
/* Centre the capped stage in the deck card (the deck rule makes .ags-grouptour a flex col). */
.ags-grouptour.e-con { align-items: center; justify-content: center; }

/* =============================================================================
   PER-WIDGET px TYPE → reference-frame scaled (ultrawide pass, 2026-09-08)
   -----------------------------------------------------------------------------
   These headings carry a font-size set on the WIDGET in Elementor, so it lands in
   uploads/elementor/css/post-<id>.css as a flat px value under
   `.elementor-1813 .elementor-element.elementor-element-<hash> .elementor-heading-title`
   — specificity (0,4,0). They were the only type on the page frozen at EXACTLY 1.000x
   between 1440x766 and 3440x1440 (every other value at least tracked the rem knob), which
   is why the section kanji looked shrunken once their neighbours started scaling.

   Fixed here rather than in the DB on purpose: this block is desktop-scoped, so the mobile
   layout's own type caps (in the ≤1023 block) are untouched — editing the widget values
   would have changed both. The page-type scope is what wins over Elementor: (0,4,2) vs its
   (0,4,0). Each number is that heading's measured px at the 1440 reference.
   ⚠️ The element hashes are shared by all four language homepages (Polylang duplicated the
   structure), so the ags-* class hooks below cover en/es/de/nl in one go. */
body.ags-redesign [data-elementor-type="wp-page"] .ags-gallery__kanji .elementor-widget-heading:first-child .elementor-heading-title {
  font-size: calc(48 * var(--ags-hpx));    /* 地域社会 */
}
body.ags-redesign [data-elementor-type="wp-page"] .ags-values__cols .elementor-widget-heading:nth-child(2) .elementor-heading-title {
  font-size: calc(88 * var(--ags-hpx));    /* 和 · 礼 · 誠 · 心 — the pillar hero kanji */
}
body.ags-redesign [data-elementor-type="wp-page"] .ags-about__kanji .elementor-widget-heading:first-child .elementor-heading-title {
  font-size: calc(64 * var(--ags-hpx));    /* 約 */
}
body.ags-redesign [data-elementor-type="wp-page"] .ags-vip-eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
  font-size: calc(56 * var(--ags-hpx));    /* 後退 — NOTE the min(4.6cqw, …) rule in the VIP block
                                             never applied: (0,2,1) loses to Elementor's (0,4,0) */
}
body.ags-redesign [data-elementor-type="wp-page"] .ags-exp__eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
  font-size: calc(56 * var(--ags-hpx));    /* 経験 */
}
body.ags-redesign [data-elementor-type="wp-page"] .ags-masters__legenditem .elementor-widget-heading:first-child .elementor-heading-title {
  font-size: calc(72 * var(--ags-hpx));    /* 師範 / 師 — the rank legend under the title */
}
body.ags-redesign [data-elementor-type="wp-page"] .ags-master__rank .elementor-heading-title {
  font-size: calc(30 * var(--ags-px));    /* 師範 / 師 — the rank on each portrait card */
}

} /* END DESKTOP LAYOUT (≥1024) */
/* ---- FILM section — MOBILE/TABLET (≤1023, Simon 2026-09-24). IMPORTANT: the ENTIRE desktop film
   composition lives in the min-width:1024 block above, so mobile inherits NONE of it — this block must
   re-declare the section's look (dark bg + cream text), the click-to-play VIDEO FACADE structure, and a
   fresh single-column layout sized off ONE capped-vw knob (scales 390→tablet). Layout: full-width
   stacked paragraphs, left statement, full-width video, kanji ABOVE-LEFT (遊ぶ/Play) + BELOW-RIGHT
   (動画/Video), wrapping title + crest. Bands capped + centred so tablet is a column, not edge-to-edge. */
@media (max-width: 1023px) {
  .ags-stack-wrap > .ags-film {
    --film-u: min(4.3vw, 19px);              /* one knob: ~16.8px @390 → 19px cap (~442px+) */
    background: #0e0d0d; color: var(--ags-cream, #F7F4F2);   /* desktop bg is min-width-gated → set here */
    justify-content: flex-start;             /* natural top-down stack, not the 100lvh space-between */
    height: auto; min-height: 0;             /* flowing section, not a pinned deck card */
    padding-top: calc(3 * var(--film-u));
    padding-bottom: calc(3 * var(--film-u));
    padding-left: calc(1.6 * var(--film-u));
    padding-right: calc(1.6 * var(--film-u));
    gap: calc(2.2 * var(--film-u));          /* rhythm between intro / stage / foot */
  }
  /* kill Elementor's stray inner padding; keep the content a centred column on wide tablets */
  .ags-film__intro, .ags-film__stage, .ags-film__foot { padding: 0; width: 100%; max-width: calc(34 * var(--film-u)); margin-left: auto; margin-right: auto; }
  /* cream text (the min-width block's cream rule doesn't reach mobile → beat the Kit's maroon heading) */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-film .elementor-heading-title,
  body.ags-redesign [data-elementor-type="wp-page"] .ags-film .ags-film__p .elementor-widget-container { color: var(--ags-cream, #F7F4F2); }

  /* Band 1 — intro: full-width stacked paragraphs, then the statement (left-aligned on mobile). */
  .ags-film__intro { align-items: stretch; gap: calc(1 * var(--film-u)); }
  .ags-film__p, .ags-film__stmt,
  body.ags-lang-de .ags-film__p, body.ags-lang-nl .ags-film__p { width: 100%; }
  .ags-film__p .elementor-widget-container p { margin: 0; }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-film__p .elementor-widget-container {
    font-size: calc(0.92 * var(--film-u)); line-height: 1.55;
  }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-film__stmt .elementor-heading-title {
    text-align: left; font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(1.6 * var(--film-u)); line-height: 1.24; margin-top: calc(0.4 * var(--film-u));
  }

  /* Band 2 — stage + video FACADE (re-declared: the desktop structure is min-width-gated). */
  .ags-film__stage { position: relative; align-items: stretch; gap: calc(0.6 * var(--film-u)); }
  .ags-film__playerwrap { width: 100%; }
  .ags-film__player { position: relative; width: 100%; height: auto; aspect-ratio: 16 / 9; border-radius: 12px; overflow: hidden; cursor: pointer; background: #000; }
  .ags-film__poster { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; }
  .ags-film__iframe { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); min-width: 100%; min-height: 100%; aspect-ratio: 16 / 9; border: 0; }
  .ags-film__play { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); width: calc(4 * var(--film-u)); height: calc(4 * var(--film-u)); border-radius: 50%; background: rgba(0,0,0,.45); border: 2px solid rgba(255,255,255,.9); opacity: 1; }  /* touch: no hover → always shown */
  .ags-film__play-icon { position: absolute; left: 54%; top: 50%; transform: translate(-50%,-50%); width: 0; height: 0; border-style: solid; border-width: calc(0.62 * var(--film-u)) 0 calc(0.62 * var(--film-u)) calc(1 * var(--film-u)); border-color: transparent transparent transparent #fff; }
  .ags-film__player.is-playing .ags-film__poster, .ags-film__player.is-playing .ags-film__play { display: none; }
  .ags-film__label { width: -webkit-max-content; width: -moz-max-content; width: max-content; text-align: center; position: relative; z-index: 2; flex: 0 0 auto; }  /* flex:0 0 auto → never shrink below max-content so 遊ぶ/動画 stay on ONE line in the tablet row */
  /* PHONE (≤767, stage wraps to a column): auto side-margins pull the lone label on each wrapped line to
     the LEFT (遊ぶ/Play) / RIGHT (動画/Video). Tablet (768–1023) resets these — see the block below. */
  .ags-film__label--l { margin: 0 auto 0 0; }
  .ags-film__label--r { margin: 0 0 0 auto; text-align: right; }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-film__kanji .elementor-heading-title { font-family: "Shippori Mincho", serif; font-weight: 600; font-size: calc(2.5 * var(--film-u)); line-height: 1; white-space: nowrap; }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-film__romaji .elementor-heading-title { font-family: var(--e-global-typography-primary-font-family, "Bounded"), sans-serif; font-weight: 500; letter-spacing: normal; text-transform: none; font-size: calc(0.9 * var(--film-u)); line-height: 1.2; }

  /* Band 3 — foot: wrapping title, crest beneath. */
  .ags-film__foot { flex-direction: column; align-items: flex-start; gap: calc(1 * var(--film-u)); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-film__title .elementor-heading-title {
    font-size: calc(2.7 * var(--film-u)); line-height: 1.02; white-space: normal; text-align: left;
  }
  .ags-film__crest img { height: calc(3.2 * var(--film-u)); width: auto; display: block; }
}
/* FILM — TABLET (768–1023): here Elementor keeps the intro + stage as ROWS (video flanked by the kanji,
   like a scaled desktop). Drop the phone auto-margins and CENTRE the [遊ぶ · video · 動画] group tight
   around the video (not spread to the stage edges), cap the video so the flank stays balanced, and give
   the intro columns a floor width so the paragraphs + statement don't wrap into skinny ribbons. */
@media (min-width: 768px) and (max-width: 1023px) {
  /* give the flanked layout more room than the phone column cap */
  .ags-film__intro, .ags-film__stage, .ags-film__foot { max-width: min(92vw, 900px); }
  /* stage: centre the group tight around the video (not spread to the edges), cap the video */
  .ags-film__stage { justify-content: center; gap: calc(1.4 * var(--film-u)); }
  .ags-film__label--l, .ags-film__label--r { margin: 0; }
  .ags-film__playerwrap { flex: 0 1 auto; width: auto; }
  .ags-film__player { width: calc(30 * var(--film-u)); max-width: 100%; }
  /* intro: distribute the 5 columns by flex ratio (basis:0 → equal-ish; statement a touch wider) so the
     copy fills its column instead of wrapping into skinny ribbons; top-aligned. */
  .ags-film__intro { justify-content: space-between; gap: calc(1.4 * var(--film-u)); align-items: flex-start; }
  .ags-film__p { flex: 1 1 0; width: auto; }
  .ags-film__stmt { flex: 1.3 1 0; width: auto; }
}

/* ---- HOME MAP band — MOBILE/TABLET (≤1023, Simon 2026-09-24). Same story as the film: the map's
   desktop look (solid BLUE panel + cream text + the GET DIRECTIONS pill) lives in the min-width:1024
   block, so mobile got NONE of it (transparent band, Kit-maroon 沖縄/Okinawa, ghost button). Re-declare
   it here: map strip on top, blue editorial panel below, sized off one capped-vw knob. --- */
@media (max-width: 1023px) {
  .ags-home-mapband.e-con {
    --map-u: min(4.3vw, 19px);
    background-color: var(--ags-blue, #3A78C1); padding: 0; overflow: hidden;
  }
  .ags-home-map__row.e-con { display: flex; flex-direction: column; gap: 0; padding: 0; width: 100%; }
  .ags-home-mapframe, .ags-home-mapframe .elementor-widget-container { width: 100%; }
  .ags-home-mapframe__inner { position: relative; width: 100%; aspect-ratio: 3 / 2; overflow: hidden; }
  .ags-home-mapframe__inner iframe { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; border: 0; display: block; }
  /* blue editorial panel */
  .ags-home-map__text.e-con {
    background-color: var(--ags-blue, #3A78C1);
    text-align: center; align-items: center; justify-content: center;
    padding: calc(3 * var(--map-u)) calc(2 * var(--map-u)) calc(3.4 * var(--map-u));
    gap: calc(0.6 * var(--map-u));
  }
  .ags-home-map__text .elementor-widget { margin: 0; }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-home-map__kanji .elementor-heading-title {
    font-family: "Shippori Mincho", serif; font-weight: 600; font-size: calc(3 * var(--map-u)); line-height: 1; color: var(--ags-cream, #F7F4F2);
  }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-home-map__label .elementor-heading-title,
  .ags-home-map__label .elementor-heading-title {
    font-family: var(--e-global-typography-primary-font-family, "Bounded"), sans-serif; font-weight: 500; letter-spacing: normal; text-transform: none;
    font-size: calc(0.95 * var(--map-u)); line-height: 1.2; color: var(--ags-cream, #F7F4F2); margin-top: calc(0.4 * var(--map-u));
  }
  .ags-home-map__intro, .ags-home-map__intro p {
    font-family: "Red Hat Display", sans-serif !important; font-weight: 300; line-height: 1.6;
    font-size: calc(0.92 * var(--map-u)); color: var(--ags-cream, #F7F4F2);
  }
  .ags-home-map__intro { margin-top: calc(1.2 * var(--map-u)); max-width: calc(26 * var(--map-u)); }
  .ags-home-map__link { margin-top: calc(1.6 * var(--map-u)); }
  .ags-home-map__link .elementor-button {
    display: inline-flex; align-items: center; justify-content: center;
    background-color: var(--ags-cream, #F7F4F2); color: var(--ags-blue, #3A78C1);
    border: 0; border-radius: 999px;
    padding: calc(0.72 * var(--map-u)) calc(2.4 * var(--map-u));
    font-family: var(--e-global-typography-accent-font-family, "Red Rose"), sans-serif;
    font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
    font-size: calc(0.82 * var(--map-u)); line-height: 1; -webkit-text-decoration: none; text-decoration: none;
  }
  .ags-home-map__link .elementor-button::before, .ags-home-map__link .elementor-button::after { content: none; }
}
/* MAP — tablet (768–1023): shorter map strip so the section isn't overly tall at width. */
@media (min-width: 768px) and (max-width: 1023px) {
  .ags-home-mapframe__inner { aspect-ratio: 2 / 1; }
  .ags-home-map__text.e-con { padding-top: calc(4 * var(--map-u)); padding-bottom: calc(4 * var(--map-u)); }
}

/* =============================================================================
   MOBILE (≤1023px) — SINGLE-COLUMN LAYOUT (self-contained)
   =============================================================================
   The desktop LAYOUT is gated to ≥1024 (see the DESKTOP LAYOUT region above), so below 1024
   there is NO sticky deck, NO container-query stage, NO absolute positioning to fight — the
   DOM simply stacks in source order. This block builds the mobile design on that blank canvas:
   plain, low-specificity rules, no !important, no specificity tricks.

   Section background COLOURS come from Elementor's post-<id>.css (band() generator, no media
   query), so they persist here automatically. We only lay out geometry.

   RHYTHM: every section up to & including Group Tour = ONE screen (100svh, content centred),
   so the next section arrives only on scroll. Authentic Lineage (masters) onward = natural
   long scroll (Simon's spec). `svh` (small viewport) is used everywhere so a section is never
   taller than the guaranteed-visible height → no next-section "bleed" under the mobile URL bar.

   DOM recap (source order): hero · [stack-wrap: mission·gallery·values·grouptour·vip·exp] ·
   about · masters · cta · footer(template).
   ========================================================================== */
@media (max-width: 1023px) {

  /* ---- Scroll stability (Simon 2026-09-24): Firefox for Android reveals its toolbar by RESIZING the
     layout viewport on scroll-UP, and the browser's scroll-anchoring then re-picks an anchor mid-gesture
     → the view "jumps a bit" as the search bar appears (the sections themselves no longer resize — that's
     the frozen --vh). Turn scroll-anchoring OFF on the phone so the scroll offset stays put while the
     toolbar animates. Safe here: full-height sections are frozen (--vh) and images sit in fixed-ratio
     boxes, so nothing above the fold reflows to need anchoring. NOTE: some toolbar-reveal motion is
     inherent to Firefox's dynamic toolbar (it resizes the layout viewport) and can't be fully removed
     page-side. ---- */
  html, body { overflow-anchor: none; }

  /* ---- 0. Frame: release the desktop min-width lock; no horizontal scroll ---------------- */
  body.ags-redesign.elementor-page { min-width: 0; }
  body.ags-redesign,
  body.ags-redesign [data-elementor-type="wp-page"] {
    max-width: 100vw;
    /* overflow-x:CLIP, not hidden. `hidden` on one axis forces the other axis to `auto` (CSS spec),
       which turns body + wp-page into SCROLL CONTAINERS — and position:sticky sticks to its nearest
       scroll container, so the mobile deck (below) would stick inside these boxes instead of the
       viewport and the slide-over would break. `clip` prevents horizontal scroll WITHOUT creating a
       scroll container (overflow-y stays visible), so the sticky deck pins to the viewport correctly. */
    overflow-x: hidden;
    overflow-x: clip;
  }

  /* ---- Type caps: shrink the big display headings for a phone column -----------------------
     Elementor emits each heading's size as a per-element REM rule in post-<id>.css
     (`.elementor-<id> .elementor-element-XXX .elementor-heading-title { font-size: N rem }`,
     specificity 0,3,1, loaded AFTER this file). A plain `.ags-x .elementor-heading-title`
     (0,2,0) loses on source order, so we match that depth ONCE here with the page-root scope
     `[data-elementor-type="wp-page"]` (0,3,1) and set the mobile display sizes per section. This
     is the single, documented place that override lives — the per-section blocks below only do
     LAYOUT, never re-fight type. (Body copy is separate — it stays at the 16px rem floor.) */
  /* Mission §2: statement (h2) is the DOMINANT line; the 沖縄 / 空手 kanji (rendered as div titles)
     are LARGER serif accents above/below it (matching the desktop reference where all three read big).
     Split by tag so they scale on their own curves — both use vw so they grow on tablet, not freeze. */
  /* Caps lowered (Simon 2026): on TABLET widths (≳540px) the vw term hit these maxes, and the body
     statement (8 wrapping lines) + both kanji overflowed the one-screen section → 沖縄/空手 clipped
     top/bottom. Lower caps shrink the type on tablet so it fits; MOBILE is below the cap (8vw<42 at
     ≤525px, 10vw<54 at ≤540px) so its look is unchanged. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-mission   h2.elementor-heading-title  { font-size: max(28px, min(8vw, 42px)); font-size: clamp(28px, 8vw, 42px); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-mission   div.elementor-heading-title { font-size: max(34px, min(10vw, 54px)); font-size: clamp(34px, 10vw, 54px); }
  /* Gallery §3: "5 Star Rating" is the big display line (top-left); "Google Review & TripAdvisor"
     is the small bottom-row caption. They share the .ags-gallery__reviews heading widget, so scope
     each by child position. The 地域社会 kanji is separately capped (48 mockup → phone column). */
  /* Title + bottom-row caption/link are sized OFF the photo knob (--gal-photo-w), like the kanji — so
     they grow WITH the section on tablet instead of freezing at a px cap (the title froze at 52px and
     the caption at 16px while the photo/kanji kept growing, so the text looked small on iPad). The
     multipliers reproduce the tuned phone sizes (at 390 the knob≈324 → title≈41px, caption≈16px), and
     clamp floors keep them legible on the narrowest phones. The 2-line wraps are forced in JS, so they
     stay consistent regardless of size. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-gallery__reviews > .elementor-widget-heading:nth-child(1) .elementor-heading-title { font-size: max(36px, min(calc(0.127 * var(--gal-photo-w)), 84px)); font-size: clamp(36px, calc(0.127 * var(--gal-photo-w)), 84px); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-gallery__reviews > .elementor-widget-heading:nth-child(2) .elementor-heading-title { font-size: max(15px, min(calc(0.037 * var(--gal-photo-w)), 19px)); font-size: clamp(15px, calc(0.037 * var(--gal-photo-w)), 19px); }  /* small caption — grows only gently on tablet (the mockup keeps this line small vs the title) */
  /* Kanji is sized OFF the same --gal-photo-w knob (inherited from .ags-gallery__stage), so it scales
     with the photo — including shrinking on short viewports where the knob's svh cap bites — and the
     kanji + bottom row can never collide. 0.18·w ≈ 58px at the 390 reference (mockup ≈ 54–60). */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-gallery__kanji > .elementor-element:nth-child(1) .elementor-heading-title { font-size: calc(0.18 * var(--gal-photo-w)); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-gallery__kanji > .elementor-element:nth-child(2) .elementor-heading-title { font-size: calc(0.048 * var(--gal-photo-w)); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-hero__h1  .elementor-heading-title { font-size: max(38px, min(12vw, 52px)); font-size: clamp(38px, 12vw, 52px); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-gt-eyebrow  .elementor-heading-title { font-size: max(40px, min(12vw, 56px)); font-size: clamp(40px, 12vw, 56px); }
  /* Program slide titles: sized off --pu (NO px cap) so they scale as one unit with their reference-
     width blocks → the 2-line wrap ("Okinawan / Group Tour") is frozen from 320→1023 instead of
     drifting once a px cap froze the font while the block kept widening. 12.8·--pu ≈ 50px @390 (the
     reference). See [[wp-redesign-mobile-scaling-pattern]]. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-gt-title    .elementor-heading-title { font-size: calc(12.8 * var(--pu)); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-vip-eyebrow  .elementor-heading-title { font-size: max(40px, min(12vw, 56px)); font-size: clamp(40px, 12vw, 56px); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-vip-titlegrp .elementor-heading-title { font-size: calc(12.8 * var(--pu)); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-exp.ags-programs__slide .ags-exp__title  .elementor-heading-title { font-size: calc(12.8 * var(--pu)); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-masters__top .elementor-heading-title { font-size: max(38px, min(12vw, 52px)); font-size: clamp(38px, 12vw, 52px); }
  /* CTA heading — reference is 2 lines ("Ready to experience" / "Okinawan karate?"). Line 1 is the
     long one; at the CTA box width (~330px @390, Elementor's boxed inner + .e-con padding cap it) it
     must stay ≤ that width or "experience" drops to a 3rd line. 7.2vw ≈ 28px @390 keeps line 1 ≈316px
     → natural 2-line wrap. Grows to 34px on tablet where the box is wide enough. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-cta .ags-cta__title .elementor-heading-title { font-size: max(20px, min(6.9vw, 34px)); font-size: clamp(20px, 6.9vw, 34px); }
  /* Program pull-quotes (bottom-left block): GT intro is a <p>, VIP intro is a heading — size both the
     same so they match, and scale on tablet. Reference: a medium bottom-left block, ~6-8 lines. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-gt-intro p,
  body.ags-redesign [data-elementor-type="wp-page"] .ags-vip-intro .elementor-heading-title { font-size: calc(4.9 * var(--pu)); line-height: 1.16; }
  /* Values §4 (blue): eyebrows small, 'Harmony' (pillar name, heading:1) medium, 和 (big kanji,
     heading:2) is the hero — all vw-driven so they scale on tablet instead of freezing (were 14/22/88px
     flat at every width). Reference: 和 dominates (~21% of width); name + reading sit above/below. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-values__eyebrows .elementor-heading-title { font-size: max(14px, min(3.8vw, 22px)); font-size: clamp(14px, 3.8vw, 22px); }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-values__cols .elementor-widget-heading:nth-child(1) .elementor-heading-title { font-size: max(22px, min(6.5vw, 42px)); font-size: clamp(22px, 6.5vw, 42px); }  /* pillar name (Harmony) */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-values__cols .elementor-widget-heading:nth-child(2) .elementor-heading-title { font-size: max(84px, min(24vw, 150px)); font-size: clamp(84px, 24vw, 150px); }  /* the 和 hero kanji */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-values__cols .elementor-widget-text-editor p { font-size: max(18px, min(5vw, 32px)); font-size: clamp(18px, 5vw, 32px); }  /* 調和 reading */

  /* ---- 1. DECK SECTIONS (sticky-deck DISABLED on mobile — see "NO STICKY DECK ON MOBILE" at end) ----
     WHY: earlier the mobile sections were plain flow blocks (each min-height:100svh) and relied
     on scroll-snap to rest on boundaries. But flow blocks ABUT edge-to-edge — a real seam — so any
     rest that isn't exactly on a boundary shows the next section (the "red peeking in" bug). Snap
     was a band-aid over a seam. Desktop never has this because it uses a sticky DECK: each section
     pins at top:0, is opaque, sits on a z-index ladder, and the next section slides UP and COVERS
     it. Sections OVERLAP instead of abutting → there is no seam to peek through, by construction.

     This ports that exact mechanism to ≤1023. The deck sections already live in .ags-stack-wrap
     with 40svh rest-spacers between them (built by build_homepage.py) — desktop's deck is just
     gated ≥1024, so here we light up the same structure below 1024 instead of disabling it. */

  /* Deck region: tall scroll container, no centering rhythm, no seams. */
  body.ags-redesign .ags-stack-wrap { display: block; padding: 0; gap: 0; }
  body.ags-redesign .ags-stack-wrap > .e-con-inner { gap: 0; }

  /* Deck cards: flex-centred, one-screen-tall, opaque. NOTE: the `position:sticky` here is OVERRIDDEN to
     `relative` by the "NO STICKY DECK ON MOBILE" block at the END of this media query (Simon 2026-09-23) —
     so these rules now only supply the flex layout + sizing, not the pinning. Height is `svh` (the STABLE
     small-viewport unit), NOT `dvh`: `dvh` recomputes as the phone's URL bar shows/hides mid-scroll, so
     dvh-sized sections + their `background-size:cover` images resized on every scroll = the "sections zoom
     for a sec then come back" jitter. `svh` is fixed → nothing resizes. .ags-programs isn't tagged
     .ags-stack, so it's listed explicitly. */
  body.ags-redesign .ags-stack-wrap > .ags-mission,
  body.ags-redesign .ags-stack-wrap > .ags-gallery,
  body.ags-redesign .ags-stack-wrap > .ags-values,
  body.ags-redesign .ags-stack-wrap > .ags-programs {
    position: -webkit-sticky;
    position: sticky;
    top: 0;
    height: calc(var(--vh, 1vh) * 100);
    min-height: calc(var(--vh, 1vh) * 100);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;   /* Elementor .e-con defaults to flex-wrap:wrap; in a fixed-height column
                            that breaks overflowing content into an off-screen 2nd column. */
    justify-content: center;
  }
  /* z-index ladder: each later card paints OVER the previous as it slides up (the slide-over). */
  body.ags-redesign .ags-stack-wrap > .ags-mission  { z-index: 1; }
  body.ags-redesign .ags-stack-wrap > .ags-gallery  { z-index: 2; }
  body.ags-redesign .ags-stack-wrap > .ags-values   { z-index: 3; }
  body.ags-redesign .ags-stack-wrap > .ags-programs { z-index: 4; }
  /* Rest spacers: a 40svh empty block after each card gives a flush REST (the pinned card keeps
     covering the viewport while the spacer scrolls past) before the next card slides over. Without
     them the next card starts covering the instant you pass the single flush pixel. */
  body.ags-redesign .ags-stack-spacer { display: block; height: calc(var(--vh, 1vh) * 40); pointer-events: none; }

  /* About is the top-level "tail" section (NOT in the deck) — plain flow, fills a screen but can
     grow taller if its content doesn't fit a short viewport (it's the tallest section). */
  body.ags-redesign .ags-about {
    min-height: calc(var(--vh, 1vh) * 100);
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    justify-content: center;
    padding-left: 0;
    padding-right: 0;   /* zero the Elementor .e-con band inset so the copy block + photo can reach
                            near the section edges via their own max-widths (matches the reference) */
  }

  /* Reduced-motion (accessibility) + the QA screenshot tool both run prefers-reduce: no sticky
     theatrics. Un-stick every deck card + hero frame and collapse all spacers so sections read
     top-to-bottom with no empty gaps — same fallback desktop uses. NOTE: this override lives at the
     END of the mobile block (search "REDUCED-MOTION FALLBACK") so it comes AFTER the hero rules it
     overrides — same-specificity rules need later source order to win. */

  /* =====================  HERO (two-frame mini-deck)  =====================
     The hero is TWO full-screen frames, and — like the section deck above — they're sticky cards
     that overlap, not flow blocks that abut. So there is no seam to peek through:
       FRAME 1 = .ags-hero__stage (photo + title + seal + tagline), sticky top:0, 100svh, z:1
       FRAME 2 = .ags-hero__frame2 (AGESHIO card centred on cream),  sticky top:0, 100svh, z:2
     Frame 2 slides up and fully covers frame 1; then the first section-deck card (Mission, z:1 in
     its own wrapper) slides over frame 2. .ags-hero__frame2 is a REAL element (added in
     build_homepage.py; display:contents on desktop so the desktop hero is unchanged) — that's what
     lets the card be a clean 100svh frame here instead of an absolute-positioned hack. */
  body.ags-redesign .ags-hero {
    display: block;   /* just a container for the two sticky frames; height = sum of frames */
    padding: 0;       /* zero Elementor's .e-con default 20px inline padding — it offset frame2's
                         origin 20px right (dark bar on the left). Frames own their own edges. */
    overflow: visible;   /* CRITICAL: the builder sets overflow:hidden on the hero band, which makes
                            it a clipping context and BREAKS position:sticky on the child frames
                            (stage/frame2 scroll away instead of pinning → the dark spacer shows and
                            the frames don't cover). Sticky needs a non-clipping containing block.
                            The photo bg is capped at 100svh so nothing overflows the hero anyway. */
  }
  /* Photo = frame 1's background only. The bg is a sibling of the stage (direct hero child) and is
     the separate .ags-hero__bg layer is HIDDEN on mobile — the photo is painted directly on the
     sticky stage instead (see below), so it pins WITH the title/seal/tagline. A separate absolute
     bg scrolled independently of the pinned content (title stayed, photo slid up → "content scrolls
     wrong") and left a dark band below the receding photo before frame 2's cream arrived. */
  body.ags-redesign .ags-hero__bg { display: none; }
  /* FRAME 1 — a normal one-screen section that SCROLLS AWAY (NOT pinned). Per Simon: the hero
     should scroll off like §2→§3 (the card→mission handoff), not do the sticky "stack card"
     slide-over where the content freezes while the next slides over the top. So the stage is
     position:relative (z:1, below frame2's z:2) — it scrolls up and off, and frame 2 slides up
     flush right behind it. The PHOTO is the stage's own background so it moves WITH the title/
     seal/tagline (they scroll away as one unit); the dark scrim keeps the cream text legible. */
  body.ags-redesign .ags-hero__stage {
    position: relative;
    height: calc(var(--vh, 1vh) * 100);
    z-index: 1;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: max(24px, min(calc(var(--vh, 1vh) * 6), 56px));
    gap: max(24px, min(calc(var(--vh, 1vh) * 6), 56px));
    gap: clamp(24px, calc(var(--vh, 1vh) * 6), 56px);
    padding: 0 24px;
    background-image: linear-gradient(rgba(17,16,16,0.30), rgba(17,16,16,0.30)), url('/wp-content/uploads/redesign/hero-left-2x.jpg');
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center;
  }
  /* __mid flattens so seal + tagline sit directly in the stage flex; __bottom must NOT flatten
     anymore — it's the row holding h1 (frame 1) and frame2 (frame 2). Flatten it but pull frame2
     out as its own sticky frame; keep h1 in the stage via order. */
  body.ags-redesign .ags-hero__mid,
  body.ags-redesign .ags-hero__bottom { display: contents; }
  body.ags-redesign .ags-hero__h1 { order: 1; }
  body.ags-redesign .ags-hero__seal { order: 2; }
  body.ags-redesign .ags-hero__rotate-col { order: 3; }
  /* width:min-content keeps the deterministic 2-line wrap ("Okinawan" / "Karate", one word per
     line) at EVERY mobile/tablet width — same technique as desktop. With plain width:auto the title
     collapsed to one line once the viewport was wide enough (~600px+), so tablets lost the 2-line
     look. The stage is a centred column flex (align-items:center), so the min-content box stays centred. */
  body.ags-redesign .ags-hero__h1 .elementor-heading-title { width: -webkit-min-content; width: -moz-min-content; width: min-content; text-align: center; }
  body.ags-redesign .ags-hero__seal { align-items: center; }
  body.ags-redesign .ags-hero__seal img { width: 128px; height: auto; }
  body.ags-redesign .ags-hero__rotate { position: relative; width: min(72vw, 300px); min-height: 0; margin-left: auto; margin-right: auto; padding-left: 18px; padding-right: 18px; }
  body.ags-redesign .ags-hero__rotate span { position: static; opacity: 1; animation: none; }
  body.ags-redesign .ags-hero__rotate span ~ span { display: none; }

  /* Rest-spacer AFTER frame 2 (before Mission). Frame 2 IS sticky, so this buffer gives it a flush
     pinned rest before Mission slides up — without it the card releases the instant the hero ends and
     Mission's red peeks at the card's bottom. Same device as the section-deck spacers. (There are two
     .ags-hero__frame-spacer siblings; the FIRST one — between the stage and frame 2 — is collapsed
     below, because frame 1 now scrolls away normally and needs no pre-transition buffer.) */
  body.ags-redesign .ags-hero__frame-spacer {
    display: block;   /* override the desktop display:none — this buffer is mobile-only */
    height: calc(var(--vh, 1vh) * 40);
    margin: 0; padding: 0;
    pointer-events: none;
  }
  /* Collapse the FIRST spacer (immediately after the stage): frame 1 is no longer sticky, so it
     scrolls off naturally and frame 2 should follow it directly — no pre-transition rest gap. */
  body.ags-redesign .ags-hero__stage + .ags-hero__frame-spacer { display: none; }

  /* FRAME 2 — sticky card: full cream screen that CENTRES the AGESHIO card. It's a real element
     (sibling of the stage; display:contents on desktop so the desktop hero is unchanged). z:2 so it
     slides up over frame 1 (z:1) and, once pinned at top:0, fully covers it. */
  body.ags-redesign .ags-hero__frame2 {
    display: flex;
    position: -webkit-sticky;
    position: sticky;
    top: 0;
    width: 100vw;   /* full viewport width — Elementor caps .e-con width via --content-width
                       (min(100%,--container-max-width)) which resolved to 350 and left 20px dark
                       bars. 100vw + the box-sizing below make the cream reach both edges. */
    max-width: 100vw;
    left: 0;
    margin: 0;
    height: calc(var(--vh, 1vh) * 100);
    z-index: 2;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* NO horizontal padding: the card sizes its own gutter via width:90vw (5vw each side). With the
       old padding:0 24px the frame's content box was 100vw−48px, but the card is 90vw — WIDER than
       that content box on narrow phones (90vw=288 > 272 @320), so as an overflowing flex item it
       centred on the content box and sat OFF-CENTRE (left gap 24 ≠ right gap 8 at 320). Removing the
       inline padding lets the 90vw card centre cleanly in the full-width frame at every size. */
    padding: 0;
    box-sizing: border-box;
    background-color: var(--ags-cream);   /* opaque, FULL-WIDTH so it fully covers frame 1 (no dark
                                             bars from the hero's #151414 showing at the edges) */
  }
  /* The card: sized to FILL frame 2 (mockup: ~83% wide × ~85% tall — a big card, not a small
     floating one). It's a flex column that fills its height; the photo flex-GROWS to absorb the
     slack so the internal rhythm matches the mockup (wordmark near top · photo in the middle ·
     links clustered lower) instead of a fixed-size card leaving big empty cream above/below. */
  /* The card is LOCKED to the mockup's aspect ratio (measured off Mobile.png, card ≈ 313×463pt ≈ 0.68
     portrait) and everything inside is sized as a % of the card — so it SCALES AS ONE UNIT: it grows
     wider AND taller together, keeping the narrow-tall shape + the exact wordmark/photo/links rhythm.

     WHY % OF THE FRAME, NOT svh: the card sits INSIDE .ags-hero__frame2, which is already height:100svh.
     Sizing the card with `svh` re-computed the viewport height a SECOND time, independently of the
     frame — and on Firefox iPad (sticky context + URL-bar dynamic viewport) those two svh computations
     disagreed, so the card stopped matching its frame and filled inconsistently. Fix: the card's HEIGHT
     is a % of the parent frame (height:84%), so it's always 84% of whatever the frame actually resolves
     to — they cannot diverge. Width DERIVES from that height via the aspect-ratio, but is also capped at
     90vw so on a narrow PHONE the card doesn't get too wide (there width binds and height just follows). */
  body.ags-redesign .ags-hero__card {
    position: static;
    /* HEIGHT is the governing dimension (width derives from it via aspect-ratio), and it's a % of the
       parent frame so it tracks the real frame height exactly — no svh re-computation to diverge on
       Firefox iPad. The min() also caps it by WIDTH on phones: 90vw of card WIDTH == 90vw·463/313 of
       card HEIGHT ≈ 133vw, so on a narrow phone that term is the smaller one → height shrinks and the
       aspect keeps width ≤ 90vw (card stays the right shape, not a skinny sliver). On a tablet 84%
       binds → the card is a constant 84% of the frame on every iPad. */
    height: min(84%, 133vw);
    width: auto;               /* derived from height via aspect-ratio */
    aspect-ratio: 313 / 463;   /* the mockup card shape */
    min-height: 0;
    margin-left: auto;
    margin-right: auto;
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;   /* Elementor .e-con default is flex-wrap:wrap — would break the stack sideways */
    align-items: center;
    justify-content: space-between;   /* wordmark→top · links→bottom · photo centred; gaps auto-fill */
    /* vertical insets as % (of card WIDTH per CSS) — top smaller, bottom larger to LIFT the links off
       the card's bottom edge (mockup has clear red below the links). Kept modest so wordmark+photo+
       links fit within the aspect-locked height (else content would overflow the card shape). */
    padding: 5% 0 9%;
    border-radius: 12px;
  }
  /* iOS 12 / Safari <15 has NO aspect-ratio, and the polyfill only derives HEIGHT-from-width — this card
     derives WIDTH-from-height, so there it'd fall back to width:auto = full frame width ("card expands to
     the sides"). Give it an explicit width so it stays a contained, centred card (height:min(84%,133vw)
     above still works — %/vw/min are fine on iOS 12). Modern browsers support aspect-ratio → skip this. */
  @supports not (aspect-ratio: 1 / 1) {
    body.ags-redesign .ags-hero__card { width: 83vw; max-width: 380px; }
  }
  body.ags-redesign .ags-hero__card-txt { display: contents; }
  /* Wordmark+JAPAN: a share of the card WIDTH (scales with the card). */
  body.ags-redesign .ags-hero__card-logo { order: 1; width: 66%; flex: 0 0 auto; }
  body.ags-redesign .ags-hero__card-logo img { width: 100%; height: auto; }
  /* Photo: 84% of the card width with the mockup's ≈1.15 landscape aspect → its height lands at ~49%
     of the card automatically (0.84 / 1.15 × 463/313). Because BOTH the card and the photo are locked
     to mockup ratios, the photo stays big and correctly-shaped at every size — no min()/svmin juggling,
     no distortion, no overflow (the content sums to < card height, space-between adds the slack). */
  body.ags-redesign .ags-hero__card-photo {
    order: 2; flex: 0 0 auto;
    align-self: center;
    width: 80%;   /* slightly under the mockup's 84% so wordmark+links fit within the aspect-locked
                     card height at phone widths without clipping (84% left the links 18px short). */
    aspect-ratio: 264 / 229;   /* ≈1.15, the mockup's slightly-landscape shot */
    height: auto;
    display: flex; flex-direction: column;
  }
  /* iPhone-SE-class (≤340px): the aspect-locked card is so short that even an 80% photo leaves the
     3rd link ~10px clipped. Trim the photo a touch more here ONLY, so nothing clips on the smallest
     phone without shrinking it on the 360+ devices that already fit. */
  @media (max-width: 340px) {
    body.ags-redesign .ags-hero__card-photo { width: 72%; }
  }
  body.ags-redesign .ags-hero__card-photo > .elementor-element {
    flex: 1 1 auto; min-height: 0; width: 100%;
    display: flex; flex-direction: column;
  }
  body.ags-redesign .ags-hero__card-photo .elementor-widget-container {
    flex: 1 1 auto; min-height: 0; width: 100%; margin: 0;
    display: flex;
  }
  body.ags-redesign .ags-hero__card-photo figure { height: 100%; width: 100%; margin: 0; }
  body.ags-redesign .ags-hero__card-photo img {
    display: block; width: 100%; height: 100%; min-height: 0;
    -o-object-fit: cover;
       object-fit: cover; border-radius: 6px;
  }
  body.ags-redesign .ags-hero__card-links { order: 3; flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: max(2px, min(calc(var(--vh, 1vh) * 0.6), 7px)); gap: max(2px, min(calc(var(--vh, 1vh) * 0.6), 7px)); gap: clamp(2px, calc(var(--vh, 1vh) * 0.6), 7px); }
  body.ags-redesign .ags-hero__card-links p { margin: 0; text-align: center; font-size: 14px; line-height: 1.45; }
  body.ags-redesign .ags-hero__card-links a { color: var(--ags-cream); -webkit-text-decoration: underline; text-decoration: underline; text-underline-offset: 3px; }

  /* =====================  NAV (header)  =====================
     Mockup: crest left · BOOK WITH US pill + hamburger right, ONE row. Hide the link columns.
     Behaviour matches desktop: the header stays position:FIXED (inherited from the base rule) so it
     rides along, goes solid past the hero, hides on scroll-down and slides back on a deliberate
     scroll-up (ageshio-nav.js + the base .is-hidden transform). We DON'T override position or
     .is-hidden here anymore — the old `position:absolute` made the bar scroll away with the hero and
     never return, and `is-hidden{transform:none}` disabled the hide/show entirely on mobile. */
  body.ags-redesign .elementor-location-header { background: transparent; }
  /* ---- MOBILE NAVBAR — scale-as-one-unit (see memory: mobile-scaling-pattern) ----------------
     ONE knob: --nav-u, a capped-vw font-size on the row. Below the cap it's pure vw, so every
     element is a CONSTANT FRACTION of the viewport → the row keeps its proportions at every width
     and can't overlap (calibrated once at the 390 reference; the ratio also clears 320). Above the
     cap it stops growing on large phones. To rescale the WHOLE navbar, change --nav-u alone.
     NOTE: children are sized `calc(N * var(--nav-u))`, NOT `Nem` — `em` resolves against each
     element's OWN font-size (which doesn't inherit --nav-u), so it wouldn't scale. calc() off the
     custom property scales regardless of font context. */
  body.ags-redesign .ags-hero__nav {
    position: relative;   /* containing block for the absolute crest */
    --nav-u: min(4.6vw, 18px);   /* the one knob (≈17.9px @390). Below the cap = pure vw. */
    /* button-text knob — declared HERE (on the common ancestor) so BOTH the pill AND the hamburger
       ::after inherit it; a custom property set on the button element itself wouldn't reach the
       ::after (different subtree), which left the hamburger at the inherited 16px and taller. */
    --pill-fs: min(3.4vw, 15px);
    flex-wrap: nowrap;
    align-items: flex-start;   /* buttons top-align; crest is absolute */
    gap: calc(0.6 * var(--nav-u));
    /* padding-left here is ignored (Elementor zeroes it — see .ags-hero__nav.e-con note ~line 205);
       edge gutters are knob-based on the children instead. */
    padding: calc(0.85 * var(--nav-u)) calc(1.35 * var(--nav-u));
  }
  body.ags-redesign .ags-nav-group,
  body.ags-redesign .ags-nav-lang,
  body.ags-redesign .ags-nav-spacer { display: none; }
  /* Crest: absolute (size independent of row height), anchored top-left. ~6em off the knob
     (≈107px @390). Enlarged per the mockup; tight to the corner. */
  body.ags-redesign .ags-nav-logo {
    position: absolute;
    left: calc(0.6 * var(--nav-u));
    top: calc(-0.5 * var(--nav-u));
    transform: none;
    margin: 0;
    z-index: 1;
  }
  body.ags-redesign .ags-nav-logo img { height: calc(6 * var(--nav-u)); width: auto; max-width: none; display: block; }
  /* HIDE FULLY on mobile. The base .is-hidden uses translateY(-100%) = only the header's OWN height
     (~60px), but the crest is a 108px-tall absolute element anchored at top:-0.5u that hangs ~58px
     BELOW the header box — so -100% left it peeking. Translate up by the crest's full bottom reach
     instead: top(-0.5·nav-u) + img height(6·nav-u) = 5.5·nav-u below the header top, + a small margin.
     --nav-u lives on .ags-hero__nav (the header's child), so recompute the same capped-vw here rather
     than relying on inheritance onto the header. Clears the crest at every width (the unit is capped). */
  body.ags-redesign .elementor-location-header.is-hidden {
    transform: translateY(calc(-100% - 6 * min(4.6vw, 18px)));
  }
  /* Buttons top-right (margin-left:auto). Crest is absolute-left so nothing collides. */
  body.ags-redesign .ags-nav-cta {
    flex: 0 0 auto;
    align-self: flex-start;
    margin-left: auto;
  }
  /* BOOK WITH US = a long pill. Its own font is a capped-vw (--pill-fs) so on narrow phones the WHOLE
     pill shrinks in proportion — SAME SHAPE, just smaller (padding is in em → relative to this font),
     which is what buys back the width the enlarged crest needs at 320/360. At 390+ it's at full size.
     Pill height = 1.4(line-height)·fs + 2·0.5em·fs = 2.4em → the hamburger below matches this height. */
  /* Scoped under .elementor-location-header to match the desktop base rule's depth (line ~259 sets
     font-size clamp(14,.875rem,15px) at (0,3,0)); this (0,4,1) wins cleanly so the mobile pill font
     actually responds to --pill-fs instead of the frozen desktop clamp. */
  body.ags-redesign .elementor-location-header .ags-nav-cta .elementor-button {
    font-size: var(--pill-fs);   /* --pill-fs is inherited from .ags-hero__nav (see the knob there) */
    padding: 0.5em 1.5em;
    line-height: 1.4;
    white-space: nowrap;
  }
  /* Hamburger — 3 cream bars in a red pill: THINNER (shorter) than BOOK WITH US but the EXACT SAME
     HEIGHT, and a WIDER icon (per Simon). Height is tied to the pill's font (2.4 × --pill-fs) so the
     two pills always match. Bars 1.7em wide (wider icon); side padding 0.7em; vertical padding derived
     so total height == 2.4em. Bar positions in em track the padding. */
  body.ags-redesign .ags-hero__nav::after {
    content: ""; flex: 0 0 auto; align-self: flex-start;
    box-sizing: content-box;
    font-size: var(--pill-fs);   /* same knob as the pill (inherited from .ags-hero__nav) → same height */
    width: 1.7em; height: 0.9em;
    padding: 0.75em 0.7em;   /* 0.75·2 + 0.9 = 2.4em → matches the pill height */
    border-radius: 999px;
    background:
      linear-gradient(var(--ags-cream) 0, var(--ags-cream) 0) 0.7em 0.75em / 1.7em 0.14em no-repeat,
      linear-gradient(var(--ags-cream) 0, var(--ags-cream) 0) 0.7em 1.13em / 1.7em 0.14em no-repeat,
      linear-gradient(var(--ags-cream) 0, var(--ags-cream) 0) 0.7em 1.51em / 1.7em 0.14em no-repeat,
      var(--ags-red);
  }

@supports (background: linear-gradient(red 0%, red 0% 1%, red 2%)) {
body.ags-redesign .ags-hero__nav::after {
    background:
      linear-gradient(var(--ags-cream) 0 0) 0.7em 0.75em / 1.7em 0.14em no-repeat,
      linear-gradient(var(--ags-cream) 0 0) 0.7em 1.13em / 1.7em 0.14em no-repeat,
      linear-gradient(var(--ags-cream) 0 0) 0.7em 1.51em / 1.7em 0.14em no-repeat,
      var(--ags-red);
  }
}

  /* =====================  MISSION  =====================
     沖縄 · statement · 空手, centred (one screen). The gap scales with the viewport height so the
     vertical rhythm holds on phones AND tablets (like the desktop rem rhythm), and it's generous
     enough that the fadeInUp entrance (which slides the statement down a few % before settling) never
     laps onto 空手 mid-animation. Column caps at a readable measure so the statement doesn't sprawl on
     wide tablets. */
  body.ags-redesign .ags-mission__inner {
    max-width: min(92vw, 620px);
    margin-left: auto;
    margin-right: auto;
    padding: 3rem 28px;
    gap: max(2rem, min(calc(var(--vh, 1vh) * 6), 5rem));
    gap: max(2rem, min(calc(var(--vh, 1vh) * 6), 5rem));
    gap: clamp(2rem, calc(var(--vh, 1vh) * 6), 5rem);
    justify-content: center;
    transform: none;   /* desktop scale-to-fit is desktop-only; mobile has its own one-screen layout */
  }
  /* Entrance-animation overlap fix: Elementor's fadeInUp keyframe starts at translate3d(0,100%,0) =
     100% of the ELEMENT'S OWN HEIGHT. The statement is a tall multi-line block, so it slid up ~250px
     mid-animation — sweeping across 空手 below it (faint, since opacity is also ramping, but visible
     with the enlarged type). Swap the statement's entrance to a plain fadeIn (opacity only, NO
     translate) on mobile so it fades in place and can't lap onto the kanji. The short kanji keep
     fadeInUp (their 100%-height travel is tiny). */
  body.ags-redesign .ags-mission .elementor-widget-heading.fadeInUp:has(h2.elementor-heading-title) {
    animation-name: fadeIn;
  }

  /* =====================  GALLERY + REVIEWS  =====================
     Mockup (§3): "5 Star Rating" big top-LEFT · ★★★★★ top-RIGHT · ONE near-square featured photo
     (gal-1) centred in the frame · 地域社会 / (Chiiki Shakai) straddling the photo's LOWER edge ·
     a bottom split-row "Google Review & TripAdvisor" (left) · READ (OUR) REVIEWS link (right).

     The three review bits live in ONE .ags-gallery__reviews container in the DOM, but the mockup
     puts "5 Star" at the TOP and the Google-Review row at the BOTTOM with the photo between — a
     split that `order` alone can't do (they're siblings in one box). So we `display:contents` the
     wrapper to promote its children directly into the stage, then anchor each to the frame edges
     (absolute top/bottom) with the photo as the single centred IN-FLOW item. One knob: --gal-photo-w
     drives the photo and the kanji straddle. Desktop is gated ≥1024, so it's untouched. */
  body.ags-redesign .ags-gallery__stage {
    position: relative;              /* anchor for the absolute top/bottom rows, stars, and kanji */
    --gal-photo-w: min(83vw, calc(var(--vh, 1vh) * 44), 620px); /* the one knob — photo width; everything (title/stars/
                                       kanji/gutters/bottom row) derives from it, so this single value
                                       sets the whole section's ratio.
                                       · 83vw governs on PHONES → photo ≈83% of width, matching the
                                         mockup ratio (the 5-Star/Google-Review wraps are preserved).
                                       · 44svh governs on TABLETS → the photo keeps GROWING with the
                                         frame instead of freezing at a fixed px (the old 360px cap froze
                                         it at ~47% of an iPad, so the section looked small). 44svh keeps
                                         the near-square photo + title + kanji + bottom row inside the
                                         100svh deck card. Now ~59–63% on iPad portrait (was 47%).
                                       · 620px is a final ceiling for very large viewports.
                                       The vw↔svh crossover sits at tablet widths (well clear of the
                                       phone range, where vw always wins), so there's no mid-phone
                                       size-jump like the earlier min(…,40svh) had. */
    /* Text gutter for the title/stars/caption/link. They should sit CLOSER to the screen edge than
       the photo — but still SCALE with it, or we get the "jumps to the edge on tablet, comes back on
       mobile" bug (that happened when the gutter was a fixed 18px while the photo centred + capped at
       360px, so the two diverged as the window widened).
       So the gutter is a FRACTION of the photo's own side-inset: half-way between the screen edge and
       the photo edge. Because it's a constant proportion of the (50% − photo/2) inset, it tracks the
       photo continuously at every width — no jump — yet always lands outside the photo (closer to the
       edge). On a phone that inset ≈ 33px → gutter ≈ 16px, the value we tuned; on tablet the inset is
       large so the gutter grows too but stays well inside the photo's margin. */
    --gal-edge: calc((50% - var(--gal-photo-w) / 2) * 0.3);
    /* Photo-relative VERTICAL anchors. The photo is centred (top:50%) and near-square, so its half-
       height ≈ 0.52·width. Title/stars and the bottom row hang off the photo's edges (not the stage's
       top/bottom) so they stay a CONSTANT gap from the photo at any stage height — on a tall tablet
       stage they no longer fly to the section's top/bottom (that was the bug: title→photo gap grew
       92→230px as the stage got taller). max()-floored so short stages can't push them off-screen. */
    --gal-photo-half: calc(0.52 * var(--gal-photo-w));
    --gal-title-lh: calc(1.02 * max(36px, min(10.5vw, 52px)));
    --gal-title-lh: calc(1.02 * clamp(36px, 10.5vw, 52px));   /* the title's line-height (shared by the stars) */
    /* ONE knob for the vertical separation between the photo and the text above (title/stars) and below
       (review row). It's a fixed px, so the distance is IDENTICAL on a thin phone and a wider tablet
       (the anchors are photo-relative → height-independent). The caption is forced to a constant 2-line
       wrap below so the row's height doesn't change between phone and tablet either → truly consistent. */
    --gal-vgap: 92px;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;   /* kill Elementor's wrap default so nothing breaks into an off-screen column */
    align-items: center;
    justify-content: center;   /* the photo (the ONLY in-flow item) sits dead-centre in the frame */
    padding: 0;
  }
  /* FEATURED PHOTO SLOT.
     Without JS (or if the carousel bails): show ONLY gal-1 as the single featured photo.
     With JS (.is-carousel added by ageshio-gallery.js): the slot becomes a carousel — all 6
     photos share ONE near-square box, stacked and cross-fading; a segmented progress bar rides
     the photo's top edge (Services-style). CSS owns the motion; JS only toggles .is-active. */
  body.ags-redesign .ags-gal { display: none; }               /* hide collage photos 2–6… */
  /* Neutralise Elementor Pro's scrolling PARALLAX on the gallery photos at ALL times on mobile — not
     just in carousel mode. The photos ship with motion_fx translateY in their data-settings (see raw
     HTML), so on first load Elementor's handler positions the featured photo BY SCROLL, offset from
     its final spot; then ageshio-gallery.js strips the settings and it snaps into place. That gap is
     the "photo is in the wrong place until I scroll in, then it jumps" bug. This blanket transform:none
     !important beats Elementor's inline transform BEFORE the JS runs (and if the JS is slow/absent), so
     the featured photo never drifts. The carousel rule below re-pins with translate(-50%,-50%)
     !important — it's later in source order + more specific, so it wins once .is-carousel is on. */
  body.ags-redesign .ags-gal { transform: none !important; }
  body.ags-redesign .ags-gal--1 {                              /* …except the featured group shot */
    display: block;
    position: static;
    width: var(--gal-photo-w);
    aspect-ratio: 24 / 25;   /* near-square, measured off the mockup (photo ≈ 312 × 325) */
    height: auto;
    order: 2;
  }
  body.ags-redesign .ags-gal--1 .elementor-widget-container,
  body.ags-redesign .ags-gal--1 figure,
  body.ags-redesign .ags-gal--1 a { display: block; height: 100%; width: 100%; margin: 0; }
  body.ags-redesign .ags-gal--1 img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; border-radius: 8px; }

  /* ---- Carousel mode (JS present) --------------------------------------------------------- */
  /* The stage carries a fixed-size photo frame so the stacked (absolute) photos have a box to
     fill. One knob (--gal-photo-w) still drives the size; the frame is centred by the stage's
     justify/align. */
  body.ags-redesign .ags-gallery__stage.is-carousel {
    /* the photo frame's height is derived from the knob (aspect 24/25) so absolute children fill it */
    --gal-photo-h: calc(var(--gal-photo-w) * 25 / 24);
  }
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gal {
    display: block;
    position: absolute;
    left: 50%; top: 50%;
    /* Centre with MARGINS, not a transform, + transform:none!important. WHY: a transform promotes .ags-gal
       to a composited layer, and while that layer animates `left` Chromium drops the photo's border-radius
       (square corners mid-slide). With no transform the `left` slide runs on the MAIN THREAD, repainting the
       rounded corners every frame. transform:none!important ALSO defeats Elementor Pro's motion-fx parallax
       (it keeps writing an inline `transform: matrix(…)` on scroll; our !important overrides it) so the photo
       stays pinned centred — the same guarantee the old translate(-50%,-50%) gave, timing-independent. */
    transform: none !important;
    width: var(--gal-photo-w);
    height: var(--gal-photo-h);
    margin-left: calc(var(--gal-photo-w) / -2);
    margin-top: calc(var(--gal-photo-h) / -2);
    z-index: 1;
    opacity: 0;
    visibility: hidden;
    transition: opacity .6s ease, visibility .6s ease;
  }
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gal.is-active {
    opacity: 1; visibility: visible; z-index: 2;
  }
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gal .elementor-widget-container,
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gal figure,
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gal a { display: block; height: 100%; width: 100%; margin: 0; }
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gal img {
    width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; border-radius: 8px;
  }
  /* KILL Elementor's default `transition: transform .4s` (+ background/border/box-shadow .3s) that it
     puts on EVERY .e-con / .elementor-widget-* in the stage. WHY: on the desktop→mobile layout switch,
     the transform-POSITIONED pieces (photo container, 地域社会 kanji overlay, the review row — all placed
     with translate()) EASE 0.4s into their new mobile positions, while the progress bar (transition:
     none) snaps instantly → the photo/overlay visibly "scroll into position" and the bar looks left
     behind. Verified via transitionrun events firing `transform` on __kanji/__reviews on resize (plain
     geometry sampling can't see a transform-transition — see the card-css-architecture note, gotcha #1).
     FIX: blanket transition:none across the carousel stage so the whole composition snaps together.
     The ONE motion we want — the slide cross-fade — is re-declared right after (opacity/visibility). */
  body.ags-redesign .ags-gallery__stage.is-carousel,
  body.ags-redesign .ags-gallery__stage.is-carousel * { transition: none !important; }
  /* FILMSTRIP slide (photo carousels only — the Services carousel keeps its fade). Every photo sits at its
     shortest circular offset from the active one: centred (off 0), one slot right (+1 → left:150%), one slot
     left (−1 → left:−50%), further slots further out. On each step ALL offsets change by 1, so the whole strip
     glides one slot in a single direction — smooth forwards AND backwards, and interrupting a slide mid-glide
     just retargets the CSS transition (no queue, no snap). The one photo that wraps around the far edge is
     repositioned by JS with transition:none while it's off-screen (invisible). `left` slides; the
     transform stays as the parallax-proof centring. AgsCarousel.positionSlides sets --ags-off. The
     !important transition beats the blanket transition:none above (higher specificity wins). */
  body.ags-redesign .ags-gallery__stage.is-carousel { position: relative; overflow: hidden; touch-action: pan-y; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
  /* Static rounded VIEWPORT for the photo carousel (a <div class="ags-gallery__frame"> injected by
     ageshio-gallery.js, sized to the photo + centred in the stage). The photos slide INSIDE it, so its
     overflow:hidden + border-radius reliably clips them to rounded corners throughout — a rounded clip on
     the MOVING photo drops on its composited layer (the homepage promotes it via ancestor transforms /
     Lenis), which is why rounding the photo itself failed while masters — rounding its STATIC slot — worked. */
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gallery__frame {
    position: absolute;
    left: 50%; top: 50%;
    width: var(--gal-photo-w); height: var(--gal-photo-h);
    margin-left: calc(var(--gal-photo-w) / -2);
    margin-top: calc(var(--gal-photo-h) / -2);
    overflow: hidden;
    border-radius: 8px;
    z-index: 1;
  }
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gal {
    opacity: 1 !important; visibility: visible !important;
    left: calc(50% + var(--ags-off, 3) * 100%);   /* now relative to the photo-sized frame → slides one photo-width */
    transition: left .5s ease !important;
  }
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gal.is-active { z-index: 2; }
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-gal img { -webkit-user-drag: none; }
  /* No in-flow spacer needed: in carousel mode every anchored element (photos, kanji, bar) is
     positioned off the stage's 50% centre, which is stage-height/2 regardless of flow content —
     so the photo lands exactly where the single in-flow gal-1 sat in non-carousel mode. */

  /* Progress bar (shared filling-line indicator) riding the photo's TOP edge. Positioned
     relative to the stage centre so it tracks the photo frame at any size. */
  body.ags-redesign .ags-gallery__bar {
    position: absolute;
    left: 50%;
    top: calc(50% - var(--gal-photo-h) / 2 + 14px);
    transform: translateX(-50%);
    width: calc(var(--gal-photo-w) - 28px);
    z-index: 3;
  }

  /* Dissolve the reviews wrapper → its 3 children (5-Star heading · Google-Review heading · button)
     become stage children we can anchor separately to the top and bottom of the frame. */
  body.ags-redesign .ags-gallery__reviews { display: contents; }

  /* "5 Star Rating" — big, top-LEFT (child 1 of the dissolved reviews). Size lives in Type-caps.
     Anchored ABOVE the photo: its 2-line block (≈2·title-lh) ends ~34px above the photo's top edge
     (50% − photo-half). max(3%, …) floors it so a short stage doesn't push it off the top. This keeps
     the title a constant gap from the photo instead of pinned to the stage top — so on a tall tablet
     it no longer floats up to the section top. */
  body.ags-redesign .ags-gallery__reviews > .elementor-widget-heading:nth-child(1) {
    position: absolute; z-index: 3;
    top: max(3%, calc(50% - var(--gal-photo-half) - var(--gal-vgap) - 2 * var(--gal-title-lh)));
    left: var(--gal-edge);   /* rides the text gutter on the left */
    right: calc(var(--gal-edge) + 70px);   /* right stop leaves the stars their corner */
    text-align: left;
  }
  body.ags-redesign .ags-gallery__reviews > .elementor-widget-heading:nth-child(1) .elementor-heading-title {
    text-align: left; line-height: 1.02; white-space: normal;
  }
  /* ★★★★★ top-RIGHT, aligned with the first line of "5 Star Rating". */
  body.ags-redesign .ags-gallery__stage::after {
    content: "\2605\2605\2605\2605\2605";
    position: absolute; z-index: 3;
    top: max(3%, calc(50% - var(--gal-photo-half) - var(--gal-vgap) - 2 * var(--gal-title-lh)));   /* SAME top as the title */
    right: var(--gal-edge);   /* rides the text gutter on the right */
    /* Vertically CENTRE the stars on the title's FIRST line: share the title's top and give the star
       box the title's own line-height (--gal-title-lh). A single line of stars sits at the centre of
       its line-height (equal half-leading top/bottom), so its centre lands exactly on the title's
       first-line centre regardless of the star vs title font-size difference. */
    font-size: 22px;
    line-height: var(--gal-title-lh);
    letter-spacing: 0.14em; color: var(--ags-red);
    pointer-events: none;
  }

  /* 地域社会 / (Chiiki Shakai) — straddling the photo's LOWER edge, horizontally centred. Anchored
     from the stage centre: the photo's half-height is 0.5·(w · 25/24) ≈ 0.52·w, so a +0.5·w vertical
     translate drops the kanji's centre right onto the photo's bottom edge (it straddles). Absolute →
     zero flow footprint, so the photo itself stays frame-centred. */
  /* Extend the gallery section DOWNWARD (Simon 2026-09-24: OK if it's taller than one viewport) so the
     kanji + review links have room to sit lower with a bigger gap between them. Overrides the deck rule's
     100·--vh for the gallery only. */
  body.ags-redesign .ags-stack-wrap > .ags-gallery {
    height: calc(var(--vh, 1vh) * 118);
    min-height: calc(var(--vh, 1vh) * 118);
  }
  body.ags-redesign .ags-gallery__kanji {
    position: absolute; z-index: 2;
    left: 50%; top: 50%;
    /* The photo is the only in-flow item, so its centre == the stage centre (top:50%). Its bottom
       edge is at +0.5·height = +0.5·(w·25/24) ≈ +0.52·w below centre. Translate the kanji box up by
       its own half-height, then down to the photo bottom, then up a touch so the big glyphs STRADDLE
       the edge (mockup: kanji half over the grass, half spilling below onto the cream). */
    transform: translate(-50%, calc(0.52 * var(--gal-photo-w) - 45% + 14px));   /* +14px: drop the kanji slightly below the photo (Simon 2026-09-24) */
    align-items: center; text-align: center;
    pointer-events: none;
  }
  body.ags-redesign .ags-gallery__kanji .elementor-heading-title {
    color: var(--ags-red); text-align: center; line-height: 1.02;
    /* shadow removed (Simon 2026-09-24) */
  }

  /* Bottom-LEFT review links (mobile): the "Google Review & TripAdvisor" caption is REPLACED by the two
     ACTUAL review links, stacked and left-aligned on the text gutter — "Read our Google reviews" on top,
     "Read our TripAdvisor reviews" underneath (per Simon). The Google link is the old bottom-right CTA
     button (relabelled mobile-only in ageshio-gallery.js); the TripAdvisor link is the text-editor
     (child 4) that USED to sit unpositioned BEHIND the photo — before, only 3 of the 4 reviews children
     were placed, so it fell into flow under the carousel photo. Both are anchored a constant gap below
     the photo's bottom edge (photo-relative, so on a tall tablet they sit just under the photo, not
     pinned to the section bottom); max() floors them near the stage bottom on short stages.
     Desktop keeps the centred caption + CTA — untouched (this is all inside the ≤1023 media block). */
  body.ags-redesign .ags-gallery__reviews > .elementor-widget-heading:nth-child(2) { display: none; }  /* caption replaced by the links below */

  body.ags-redesign .ags-gallery__reviews > .elementor-widget-button,
  body.ags-redesign .ags-gallery__reviews > .elementor-widget-text-editor {
    position: absolute; z-index: 3;
    left: 0; right: 0;   /* full stage width → the links CENTRE (no right-side element to balance against) */
    margin: 0; text-align: center;
    display: flex; justify-content: center; align-items: flex-start;
  }
  /* TripAdvisor = BOTTOM of the stack (the shared photo-relative baseline used before). */
  body.ags-redesign .ags-gallery__reviews > .elementor-widget-text-editor {
    bottom: max(3%, calc(50% - var(--gal-photo-half) - var(--gal-vgap) - 0.5 * var(--gal-title-lh) - 90px));
  }
  /* Google = one line ABOVE the TripAdvisor link (so it reads Google-on-top, TripAdvisor-underneath). */
  body.ags-redesign .ags-gallery__reviews > .elementor-widget-button {
    bottom: calc(max(3%, calc(50% - var(--gal-photo-half) - var(--gal-vgap) - 0.5 * var(--gal-title-lh) - 90px)) + 28px);
  }
  /* Both links look identical: small underlined red text (the old CTA link treatment). */
  body.ags-redesign .ags-gallery__reviews .ags-btn .elementor-button,
  body.ags-redesign .ags-gallery__reviews > .elementor-widget-text-editor p,
  body.ags-redesign .ags-gallery__reviews > .elementor-widget-text-editor a {
    background: transparent; color: var(--ags-red) !important;   /* !important beats the TA link's inline color → both links match */
    border: none; border-radius: 0;
    margin: 0; padding: 0;
    -webkit-text-decoration: underline;
    text-decoration: underline; text-underline-offset: 4px;
    text-transform: uppercase;   /* all-caps, like the original "READ OUR REVIEWS" link */
    font-size: max(14px, min(calc(0.035 * var(--gal-photo-w)), 18px));
    font-size: clamp(14px, calc(0.035 * var(--gal-photo-w)), 18px);   /* small link — gentle tablet growth */
    line-height: 1.15;
    white-space: nowrap;
  }
  body.ags-redesign .ags-gallery__reviews .ags-btn .elementor-button { padding: 0 0 3px; }

  /* =====================  VALUES (blue)  =====================
     Mockup: ONE pillar at a time (name · 和 kanji · reading) that ROTATES through the 4 pillars
     (Harmony/和 · Respect/礼 · Sincerity/誠 · Heart/心), "fade out / fade in". ageshio-values.js adds
     .is-rotating to .ags-values__cols and toggles .is-active on the active column; CSS owns the fade. */
  /* Background: the SAME B&W hands photo + dark scrim as desktop §4. The desktop .ags-values bg rule
     (~2380) is gated to >=1024, so mobile fell back to the Elementor Global-Color blue band — re-declare
     the photo treatment here. Reuses the existing high-res uploads/redesign/ageshio-way-bg.webp (the same
     file desktop uses → crisp on retina, one asset): cover/center frames the hands, the baked 0.42
     gradient keeps the cream eyebrows/kanji legible. The card carries an inline blue Elementor bg (desktop
     neutralises it in its own >=1024 block); neutralise it here too or it would cover the section photo. */
  body.ags-redesign .ags-values {
    background-image:
      linear-gradient(rgba(17, 16, 16, 0.42), rgba(17, 16, 16, 0.42)),
      url('/wp-content/uploads/redesign/ageshio-way-bg.jpg');
    background-image:
      linear-gradient(rgba(17, 16, 16, 0.42), rgba(17, 16, 16, 0.42)),
      url('/wp-content/uploads/redesign/ageshio-way-bg.webp');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }
  body.ags-redesign .ags-values__card { background: transparent; }
  body.ags-redesign .ags-values__card {
    /* FILL the 100svh deck card (was content-height → centred in the middle 56% band, so everything
       was squished into the centre; the reference spreads across the FULL frame: eyebrows ~11%, link
       ~87%). Filling the height lets space-between pin eyebrows→top and link→bottom edge to edge. */
    height: 100%;
    flex: 1;
    min-height: 0;
    padding: calc(var(--vh, 1vh) * 8) 28px calc(var(--vh, 1vh) * 7);
    gap: 40px;
    justify-content: space-between;
  }
  /* Eyebrow row inset = a PROPORTIONAL gutter (~7.6% of width, matching the reference mockup) instead
     of a fixed px. The SHARED rule `.e-con { padding: 0 3.5% }` (0,2,0) is matched here to override it.
     The card itself is inset on phone (20px margin) but FULL-BLEED on tablet, so a FIXED gutter looked
     right on phone yet jammed the labels to ~28px from the edge on a wide iPad. A vw-based inset scales
     up with the screen (≈30px @390 · ≈53px @768 · ≈63px @834) so the labels keep a comfortable,
     reference-matched margin at every width. min() caps it so it can't over-inset and crowd the two
     labels together on huge viewports. The row spans the full card width (margin cancels card padding). */
  body.ags-redesign .ags-values__eyebrows.e-con {
    gap: 12px;
    margin-left: -28px;
    margin-right: -28px;               /* cancel the card's 28px side padding so the row spans full width */
    width: calc(100% + 56px);
    max-width: none;
    box-sizing: border-box;
  }
  /* PHONE (≤700): tight inset so the two labels sit near the edges and get the biggest possible gap
     (they're wide relative to a narrow screen). */
  @media (max-width: 700px) {
    body.ags-redesign .ags-values__eyebrows.e-con { padding: 0 16px; }
  }
  /* TABLET (>700): the card is full-bleed, so a tight inset jams the labels to the screen edge. Use a
     proportional gutter (~7.6% of width, reference-matched) so they keep a comfortable margin that
     grows with the screen. */
  @media (min-width: 701px) {
    body.ags-redesign .ags-values__eyebrows.e-con { padding: 0 min(7.6vw, 96px); }
  }
  body.ags-redesign .ags-values__eyebrows .elementor-heading-title { white-space: nowrap; }
  /* READ ABOUT OUR ORIGINS — all-caps (mockup); the reduced card bottom padding above puts it lower. */
  body.ags-redesign .ags-values__card > .elementor-widget-text-editor a { text-transform: uppercase; }
  body.ags-redesign .ags-values__cols {
    flex-direction: column;
    align-items: center;
    /* Centre the pillar cluster vertically in the gap between the eyebrows (top) and the READ link
       (bottom) — Simon 2026-09-24, over the new B&W photo bg. (The old flex-start upper-anchor suited the
       flat-blue reference; the photo composition reads better with the kanji dead-centre.) */
    justify-content: center;
    flex: 1;
  }
  /* Fallback (no JS / carousel bails): show ONLY the first pillar, hide the rest. */
  body.ags-redesign .ags-values__cols > .e-con { display: none; }
  body.ags-redesign .ags-values__cols > .e-con:first-child { display: flex; width: 100%; align-items: center; }
  /* Rotation mode (JS present): STACK all 4 pillars in one spot and cross-fade the active one.
     GRID stacking (all columns in the same 1×1 cell) — unlike position:absolute this keeps the wrapper
     sized to the tallest pillar automatically, so it doesn't collapse. Each pillar is a centred
     vertical column (name · 和 · reading); .is-active fades in. */
  body.ags-redesign .ags-values__cols.is-rotating {
    display: grid;
    grid-template-columns: 1fr;   /* one FULL-WIDTH cell (was auto → cell shrank to content width and
                                     parked left, so the kanji sat off-centre). */
    grid-template-areas: "pillar";
    align-items: center;
    align-content: center;        /* centre the single pillar row vertically instead of stretching it to
                                     fill — puts the cluster dead-centre between eyebrows and READ link
                                     (Safari doesn't reliably centre a stretched auto row). */
    justify-items: stretch;       /* columns fill the full-width cell → their centred content is
                                     horizontally centred in the frame */
  }
  body.ags-redesign .ags-values__cols.is-rotating > .e-con {
    display: flex;               /* override the fallback display:none — all pillars participate */
    grid-area: pillar;           /* every column occupies the SAME cell → they stack */
    flex-direction: column;      /* name · 和 · reading vertically (Elementor default is row) */
    align-items: center;
    justify-content: center;
    width: 100%;
    opacity: 0;
    visibility: hidden;
    transition: opacity .7s ease, visibility .7s ease;
    pointer-events: none;
  }
  body.ags-redesign .ags-values__cols.is-rotating > .e-con.is-active {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }

  /* =====================  PROGRAMS CAROUSEL (Group Tour · VIP · Experiences)  =====================
     Design (Screenshot 2026-07-31): the three program sections become ONE pinned screen — a dark
     card that auto-rotates between them, with a 3-segment progress bar + shared 計画/Services
     eyebrow across the top. ageshio-programs.js wraps the 3 sections into `.ags-programs` (each
     becomes a `.ags-programs__slide`) and toggles `.is-active`; CSS owns all motion + layout.
     Per slide (mockup): title upper-RIGHT · body copy mid-RIGHT · pull-quote bottom-LEFT ·
     BOOK link bottom-RIGHT, over a full-bleed dark photo. */

  /* Shell: one pinned screen; slides stack absolutely and cross-fade. */
  body.ags-redesign .ags-programs {
    position: relative;
    height: calc(var(--vh, 1vh) * 100);   /* pinned one-screen carousel — svh so it aligns with the sections around it */
    overflow: hidden;
    background: var(--ags-hero-bg);
  }
  /* ONE KNOB, scale-as-one-unit (see [[wp-redesign-mobile-scaling-pattern]]). The whole program-slide
     composition scales off a single unit --pu: every font is `calc(N * var(--pu))` and every text
     block is anchored on ONE side with its WIDTH also `calc(N * var(--pu))` — so blockwidth/fontsize
     is CONSTANT for any value of --pu, which freezes the reference line-wrapping from 320→1023 (it no
     longer drifts the way a `clamp(px,vw,px)` font + viewport-% block did).
       --pu = min(1vw, 0.46svh). The 0.46 is the mockup's aspect ratio (390/844): at the 390 phone
     reference both terms equal 1% of width, so the composition matches the mockup exactly; on a
     PROPORTIONALLY SHORTER (squarer) viewport like iPad Pro 11 the 0.46svh term wins, so the whole
     composition scales down to FIT the height instead of ballooning off the width and overlapping.
     Defined on the sections too so it resolves even before the carousel JS wraps them. */
  body.ags-redesign .ags-programs,
  body.ags-redesign .ags-grouptour,
  body.ags-redesign .ags-vip,
  body.ags-redesign .ags-exp { --pu: min(1vw, calc(var(--vh, 1vh) * 0.46)); }
  body.ags-redesign .ags-programs__slide {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    height: calc(var(--vh, 1vh) * 100);   /* fills the svh carousel shell (matches .ags-programs) */
    min-height: 0;
    margin: 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity .6s ease;
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* dark photo bg + scrim per section */
    background-size: cover;
    background-position: center;
  }
  body.ags-redesign .ags-programs__slide.is-active { opacity: 1; visibility: visible; z-index: 1; }
  @media (prefers-reduced-motion: reduce) {
    body.ags-redesign .ags-programs__slide { transition: none; }
  }
  /* Per-section photo backgrounds — GREYSCALE (reference is B&W) behind a dark scrim for legibility.
     `background-blend-mode: saturation` with a fully-desaturated (grey) gradient strips the photo's
     colour without a `filter` (which would also grey the cream text). The scrim keeps text legible;
     VIP's photo is dim so its scrim is lighter than gt/exp. */
  body.ags-redesign .ags-programs__slide {
    background-blend-mode: saturation, normal;
  }
  body.ags-redesign .ags-grouptour.ags-programs__slide {
    background-image:
      linear-gradient(rgba(20,20,20,1), rgba(20,20,20,1)),
      linear-gradient(rgba(17,16,16,0.62), rgba(17,16,16,0.74)),
      url("/wp-content/uploads/redesign/gt-large.jpg");
    background-image:
      linear-gradient(rgba(20,20,20,1), rgba(20,20,20,1)),
      linear-gradient(rgba(17,16,16,0.62), rgba(17,16,16,0.74)),
      url("/wp-content/uploads/redesign/gt-large.webp");
    background-blend-mode: saturation, normal, normal;
  }
  /* MOBILE-ONLY photo: the standing-karateka dojo shot from the reference card 2. Kept as its own
     file (vip-retreat.webp) — NOT vip-bg.webp, which the DESKTOP VIP stage bg still uses (see the
     ~line 1080 rule); overwriting that would change desktop too. */
  body.ags-redesign .ags-vip.ags-programs__slide {
    background-image:
      linear-gradient(rgba(20,20,20,1), rgba(20,20,20,1)),
      linear-gradient(rgba(17,16,16,0.55), rgba(17,16,16,0.68)),
      url("/wp-content/uploads/redesign/vip-retreat.jpg");
    background-image:
      linear-gradient(rgba(20,20,20,1), rgba(20,20,20,1)),
      linear-gradient(rgba(17,16,16,0.55), rgba(17,16,16,0.68)),
      url("/wp-content/uploads/redesign/vip-retreat.webp");   /* reference card 2: standing karateka, dim dojo, reflective floor */
    background-blend-mode: saturation, normal, normal;
  }
  body.ags-redesign .ags-exp.ags-programs__slide {
    background-image:
      linear-gradient(rgba(20,20,20,1), rgba(20,20,20,1)),
      linear-gradient(rgba(17,16,16,0.62), rgba(17,16,16,0.74)),
      url("/wp-content/uploads/redesign/exp-3.jpg");
    background-image:
      linear-gradient(rgba(20,20,20,1), rgba(20,20,20,1)),
      linear-gradient(rgba(17,16,16,0.62), rgba(17,16,16,0.74)),
      url("/wp-content/uploads/redesign/exp-3.webp");   /* reference card 3: staff training at the stone tablets */
    background-blend-mode: saturation, normal, normal;
  }

  /* Chrome — progress bar (one segment per slide) + shared Services eyebrow, pinned top. */
  body.ags-redesign .ags-programs__chrome {
    position: absolute;
    top: 0; left: 0; right: 0;
    z-index: 3;
    padding: calc(5 * var(--pu)) 4% 0;   /* insets in the same units as the slide bodies so the
                                            chrome tracks the composition (was fixed 20/28px). */
    pointer-events: none;
  }
  body.ags-redesign .ags-programs__bar { display: flex; gap: calc(2 * var(--pu)); }
  body.ags-redesign .ags-programs__eyebrow { margin-top: calc(6 * var(--pu)); color: var(--ags-cream); }
  /* Kanji is the dominant eyebrow mark (reference: 計画 ≈ 34px tall @390, ~9% of width). Sized off
     --pu (was frozen 40px) so it grows with the viewport instead of shrinking to a fraction on the
     wide iPad — bigger, and consistent at every width. */
  body.ags-redesign .ags-programs__kanji {
    display: block;
    font-family: "Shippori Mincho", serif; font-weight: 600;
    font-size: calc(12 * var(--pu)); line-height: 1.1;
  }
  body.ags-redesign .ags-programs__label {
    display: block;
    font-family: "Red Rose", sans-serif; font-weight: 700; letter-spacing: -0.02em;
    font-size: calc(4.2 * var(--pu)); margin-top: calc(0.5 * var(--pu));
  }

  /* Common slide chrome: hide each section's own eyebrow (the shared Services eyebrow lives in
     the top chrome), recolour text cream on the dark card, drop desktop-only decorative photos. */
  body.ags-redesign .ags-gt-eyebrow,
  body.ags-redesign .ags-vip-eyebrow,
  body.ags-redesign .ags-exp__eyebrow { display: none; }
  /* Cream text on the dark card. Headings carry a per-element title_color (red/cream) in
     post-<id>.css at depth (0,3,1) that loads after this file, so match the page-root depth to
     win — same idiom as the Type-caps block. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-programs__slide .elementor-heading-title,
  body.ags-redesign .ags-programs__slide p,
  body.ags-redesign .ags-programs__slide a { color: var(--ags-cream); }
  body.ags-redesign .ags-gt-large, body.ags-redesign .ags-gt-small,
  body.ags-redesign .ags-vip-photoL, body.ags-redesign .ags-vip-photoR,
  body.ags-redesign .ags-exp__edge, body.ags-redesign .ags-exp__mid { display: none; }
  body.ags-redesign .ags-gt-copy > .elementor-widget-text-editor:last-child { display: none; }  /* stray review link */
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__top { display: none; }               /* carousel + edge photos dropped */
  /* Per-experience sets: the desktop grid-stack cross-fade is defeated on mobile where .ags-exp__lede
     is flattened to display:contents (programs-carousel slide) and where the JS that toggles .is-active
     bails (<1024). Force ONLY the DOM-default active set (index 1) to show, so the mobile card / interior
     explist never renders all 5 intros stacked. Unscoped → covers homepage §7 slide AND interior .ags-c-explist. */
  .ags-exp__ledeset:not(.is-active),
  .ags-exp__photoset:not(.is-active) { display: none; }
  /* Inner Elementor wrappers back to normal flow (the desktop cqw stage positioned them) + let the
     copy blocks + their inner text containers FILL their grid cell (Elementor's flex align-items
     otherwise shrinks the <p> to its longest word → one-word-per-line). */
  body.ags-redesign .ags-gt-stage > .e-con,
  body.ags-redesign .ags-vip-stage > .e-con { position: static; width: 100%; max-width: 100%; height: auto; margin: 0; align-items: stretch; }
  body.ags-redesign .ags-gt-copy > .e-con-inner,
  body.ags-redesign .ags-vip-copy > .e-con-inner,
  body.ags-redesign .ags-gt-copy p,
  body.ags-redesign .ags-vip-copy p,
  body.ags-redesign .ags-exp__lede p { width: 100%; }
  /* Pull-quote must FILL its grid cell too, or Elementor's flex shrinks its inner container/text to
     the longest word (→ one-word-per-line, overflow). Force the widget, its container and text full-
     width so the quote wraps naturally across the wide col-1 cell. */
  body.ags-redesign .ags-gt-intro,
  body.ags-redesign .ags-vip-intro { width: 100%; }
  body.ags-redesign .ags-gt-intro .elementor-widget-container,
  body.ags-redesign .ags-vip-intro .elementor-widget-container,
  body.ags-redesign .ags-gt-intro p,
  body.ags-redesign .ags-vip-intro .elementor-heading-title { width: 100%; }

  /* FIXED EDITORIAL COMPOSITION (measured off the reference, as % of the section — so it grows
     proportionally on phone AND tablet, no content-driven bunching). The stage is a positioning
     context; each element is position:absolute at a set % top/bottom with a horizontal gutter:
       TITLE      top 30%, right-aligned block on the right ~62% of the width
       BODY copy  top 49%, right-aligned block on the right ~58%
       PULL-QUOTE bottom ~4%, left, ~62% wide (bottom-left)
       BOOK link  bottom ~4%, right (same baseline as the quote's last line)
     Percentages come straight from the reference (title 31%, body 49%, quote 76-96%, book 95%). */
  /* Full-bleed the slide cards so the stage is a positioning context that spans the WHOLE card at
     every width. Elementor's default .e-con inline padding is ~20px at ≤700 but 0 above it, which
     inset the stage on phones only — so the kanji (pinned to the card's 4%) and the stage-anchored
     pull-quote drifted apart, and phone gutters didn't match tablet. Zeroing it makes stage = card,
     so a pull-quote `left: 4%` lines up exactly under the 計画 kanji at all widths. */
  body.ags-redesign .ags-grouptour.ags-programs__slide,
  body.ags-redesign .ags-vip.ags-programs__slide,
  body.ags-redesign .ags-exp.ags-programs__slide { padding-left: 0; padding-right: 0; }
  body.ags-redesign .ags-gt-stage,
  body.ags-redesign .ags-vip-stage,
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__bottom {
    position: relative;
    transform: none;
    display: block;
    width: 100%;
    height: calc(var(--vh, 1vh) * 100);
    box-sizing: border-box;
    padding: 0;
    /* The slide is a centred flex column; Elementor's --flex-shrink let this stage shrink ~112px
       below the card (chrome ate the space), so bottom:% resolved against a short box and left a
       ~60px dead gap under the pull-quote/BOOK link. Force the stage to FILL the card so bottom:%
       measures from the true card edge. (See [[wp-redesign-responsive-shrink]] --flex-shrink note.) */
    flex: 1 0 auto;
    align-self: stretch;
    min-height: 0;
  }
  /* VIP title + BOOK button are wrapped in .ags-vip-titlegrp → display:contents so both become
     absolute children of the stage directly. */
  body.ags-redesign .ags-vip-titlegrp { display: contents; }

  /* NOTE: the reset rule above (`.ags-*-stage > .e-con { position:static; width:100% }`, spec 0,3,1)
     would override plain absolute positioning here, so each composition rule is scoped UNDER its
     stage (`.ags-gt-stage .ags-gt-title` = 0,3,1 + later source order → wins) and re-declares width. */

  /* TITLE — top 30%, right-aligned. Block edges are reference-% (left 22% → right gutter 4%): the
     title font is calc(12.8·--pu), so blockwidth/fontsize is constant → the 2-line wrap is frozen at
     every width. (Was left:38% + right:28px — the fixed px gutter shrank as a fraction on wide
     tablets while the font hit its px cap, so more chars fit per line and "Group Tour" rewrapped.) */
  body.ags-redesign .ags-gt-stage .ags-gt-title,
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__title,
  body.ags-redesign .ags-vip-stage .ags-vip-titlegrp > .elementor-widget-heading {
    position: absolute; top: 30%; right: calc(4 * var(--pu)); left: auto; width: calc(88 * var(--pu)); height: auto; z-index: 2; text-align: right;
  }
  body.ags-redesign .ags-gt-stage .ags-gt-title .elementor-heading-title,
  body.ags-redesign .ags-exp__title .elementor-heading-title,
  body.ags-redesign .ags-vip-titlegrp > .elementor-widget-heading .elementor-heading-title { width: auto; text-align: right; }

  /* BODY copy — top 49%, right side (a little under the title), right-aligned, wraps. Left edge 50%
     + right gutter 3% (reference), font calc(3.6·--pu) — same constant-fraction so its wrap is
     frozen too. */
  body.ags-redesign .ags-gt-stage .ags-gt-copy,
  body.ags-redesign .ags-vip-stage .ags-vip-copy,
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__lede {
    /* width calibrated so the greedy line-wrap lands EXACTLY on the reference breaks: the widest
       body line ("goals, our VIP Karate Retreats offer") measures 55·--pu, and the next word won't
       fit until 56·--pu, so 55.4·--pu reproduces the reference wrap at every viewport. padding:0
       kills Elementor's stray 10px inner gutter (a FIXED px that wouldn't scale with --pu → would
       break the constant width/font ratio the frozen wrap depends on). */
    position: absolute; top: 49%; right: calc(3 * var(--pu)); left: auto; width: calc(55.4 * var(--pu)); height: auto; padding: 0; z-index: 2; text-align: right;
  }
  body.ags-redesign .ags-gt-copy p, body.ags-redesign .ags-vip-copy p, body.ags-redesign .ags-exp__lede p {
    text-align: right; font-size: calc(3.6 * var(--pu)); line-height: 1.5; margin: 0; width: 100%;
  }
  /* PHONE (≤700): the title-to-body span is a fixed 19% of card height (title top 30% → body 49%).
     With the smaller phone title that reads as too loose a gap, so pull the body up a touch here only.
     Tablet (701–1023) keeps 49% (its larger title fills the space — looks right as-is). GT/VIP here;
     the EXP introtext override sits after its base rule below (source order). */
  @media (max-width: 700px) {
    body.ags-redesign .ags-gt-stage .ags-gt-copy,
    body.ags-redesign .ags-vip-stage .ags-vip-copy { top: 44%; }
  }

  /* PULL-QUOTE — bottom-left, ~47% wide (reference widest line ends ~45%), wraps as a block.
     .ags-exp__quote is the Experiences slide's equivalent, injected mobile-only by ageshio-programs.js
     (the desktop §7 layout has no pull-quote), so its bottom-left statement matches GT/VIP. */
  body.ags-redesign .ags-gt-stage .ags-gt-intro,
  body.ags-redesign .ags-vip-stage .ags-vip-intro,
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__quote {
    /* width 50.6·--pu: fits "journeys through" (50·--pu) but not "Okinawa's" + "martial" (51·--pu),
       so the pull-quote wraps exactly like the reference. padding:0 removes the stray inner gutter.
       left: 4% matches the 計画 kanji's 4% inset (chrome padding) — cards are full-bleed now, so the
       pull-quote's left edge lines up exactly under the kanji. bottom: 5% (same ratio at every width):
       0.5% jammed it against the very edge on small phones — a proportional margin reads balanced. */
    position: absolute; bottom: 5%; left: 4%; right: auto; width: calc(50.6 * var(--pu)); height: auto; padding: 0; z-index: 2; text-align: left;
  }
  /* EXP quote isn't an Elementor heading (no ags_h3 wrapper), so give it the Bounded face + cream +
     the shared pull-quote size/leading directly, matching GT/VIP. */
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__quote {
    font-family: "Bounded", sans-serif; font-weight: 400; color: var(--ags-cream);
    font-size: calc(4.9 * var(--pu)); line-height: 1.16;
    /* Lift the bottom-left quote clear of the bottom-right BOOK link on this slide (they sat level at
       the shared bottom:5%). Later source order → wins over the shared rule; GT/VIP keep 5%.
       Simon 2026-09-24. */
    bottom: 10%;
  }
  body.ags-redesign .ags-gt-intro .elementor-widget-container,
  body.ags-redesign .ags-vip-intro .elementor-widget-container,
  body.ags-redesign .ags-gt-intro p,
  body.ags-redesign .ags-vip-intro .elementor-heading-title { text-align: left; width: 100%; }

  /* BOOK link — bottom-right, on the pull-quote's baseline. */
  body.ags-redesign .ags-gt-stage .ags-gt-btn,
  body.ags-redesign .ags-vip-stage .ags-vip-titlegrp > .elementor-widget-button,
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__cta {
    /* bottom: 4% — SAME ratio at every width (phone + tablet). 0.5% jammed the link against the very
       edge on small phones; a proportional margin sits it consistently, on the pull-quote's baseline. */
    position: absolute; bottom: 4%; right: 3%; left: auto; width: auto; z-index: 2;
    /* Normalise the button-widget box: the GT link widget carries Elementor's default 10px .e-con
       padding + display:flex (47px tall), while VIP/EXP are 0-padding block (27px). With all anchored
       to the same bottom:%, GT's 10px bottom padding pushed its text ~10px HIGHER than VIP/EXP, so the
       three links didn't sit at the same level across the cross-fading slides. Zero it so every link
       box is identical and lands on the same baseline. */
    padding: 0; display: block;
  }
  body.ags-redesign .ags-gt-btn .elementor-widget-container,
  body.ags-redesign .ags-vip-titlegrp > .elementor-widget-button .elementor-widget-container,
  body.ags-redesign .ags-exp__cta .elementor-widget-container { padding: 0; margin: 0; }

  /* BOOK link style — small underlined cream text-link (reference), overriding the pill variants
     (GT --solid red pill, VIP --invert cream pill that rendered as an invisible white box). Scope
     under [data-elementor-type="wp-page"] so the font-size wins over Elementor's per-element size
     rule (which otherwise pinned GT's link to the 13px size-sm default while VIP got 3.2·--pu → the
     two links rendered at slightly different sizes / baselines). */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-gt-btn .elementor-button,
  body.ags-redesign [data-elementor-type="wp-page"] .ags-vip-titlegrp > .elementor-widget-button .elementor-button,
  body.ags-redesign [data-elementor-type="wp-page"] .ags-programs__slide .ags-exp__cta .elementor-button {
    background: transparent; border: none; border-radius: 0; padding: 0 0 3px;
    color: var(--ags-cream);
    -webkit-text-decoration: underline;
    text-decoration: underline; text-underline-offset: 4px;
    font-size: calc(3.2 * var(--pu)); letter-spacing: 0.04em; white-space: nowrap;
  }

  /* EXP slide fix: the intro copy + BOOK link are wrapped in .ags-exp__lede > .ags-exp__ledeset
     (per-experience sets, added by the Experience-List unification), unlike GT/VIP where copy and button
     are separate stage children. BOTH wrapper layers are Elementor .e-con → position:relative by default,
     so the absolutely-positioned introtext/CTA resolved against the (collapsed, ~0-height) ledeset instead
     of the stage — the paragraph floated to the very top (into the progress bar) and the CTA followed.
     Flatten the lede (display:contents) AND drop the ledeset as a containing block (position:static) so
     introtext + CTA both resolve against the .ags-exp__bottom stage (CTA lands bottom:4%, from the grouped
     rule). */
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__lede { display: contents; position: static; }
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__ledeset { position: static; }
  /* Intro copy BELOW the title, matching GT/VIP + the interior pages (Simon 2026-09-24): top 49%
     (phone 44%), same as .ags-gt-copy / .ags-vip-copy. The position:static on the lede + ledeset above is
     what lets this % resolve against the full .ags-exp__bottom stage — both are Elementor .e-con →
     position:relative by default, and the collapsed ledeset had otherwise floated this to the very top. */
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__introtext {
    position: absolute; top: 49%; right: calc(3 * var(--pu)); left: auto; width: calc(55.4 * var(--pu)); height: auto; padding: 0; z-index: 2; text-align: right;
  }
  body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__introtext p {
    text-align: right; font-size: calc(3.6 * var(--pu)); line-height: 1.5; margin: 0; width: 100%;
  }
  /* PHONE (≤700): match GT/VIP — pull the intro up a touch so the title→body gap isn't too loose. */
  @media (max-width: 700px) {
    body.ags-redesign .ags-exp.ags-programs__slide .ags-exp__introtext { top: 44%; }
  }

  /* =====================  ABOUT (red)  =====================
     Mockup order: wordmark → 約 photo → copy → ABOUT AGESHIO. Flatten __left so its widgets
     interleave with the media panel, then order them. */
  body.ags-redesign .ags-about__inner {
    width: 100%;
    flex-direction: column;
    align-items: center;
    gap: 28px;
    padding: calc(var(--vh, 1vh) * 8) 4px;   /* minimal side inset so the copy block can widen enough to match the
                            reference line-wrap (the photo has its own max-width, unaffected) */
  }
  body.ags-redesign .ags-about__left { display: contents; }
  body.ags-redesign .ags-about__logo { order: 1; width: min(70vw, 300px); margin-left: auto; margin-right: auto; }
  /* Media panel: the desktop aspect-ratio/position rule is gated to ≥1024, so re-establish the
     panel box here (square, relative) or the absolutely-positioned photo layer collapses. Reaches
     close to the section edges (92vw, capped 480) — keeping its 1:1 ratio — instead of a small
     centred 340px box that left wide red margins. The section inner padding is trimmed below so the
     photo genuinely nears the edge. */
  body.ags-redesign .ags-about__media {
    order: 2;
    width: 100%; max-width: min(92vw, 480px);
    aspect-ratio: 1 / 1;
    position: relative;
    overflow: hidden;
    border-radius: 18px;   /* generously rounded corners, matching the mobile reference (was 8px) */
    align-self: center; margin-left: auto; margin-right: auto;
  }
  /* Darken the photo to match the desktop variant (grayscale + brightness 0.62 → deep charcoal so
     the red band reads through and the cream 約/(About) + kanji sit legibly on top). The desktop
     filter rule is gated ≥1024, so re-declare it here. Filter lives on the PHOTO layer only, never
     the kanji sibling. */
  body.ags-redesign .ags-about__photo {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0;
    background-size: cover; background-position: center;
    filter: grayscale(1) brightness(0.62);
  }
  /* MOBILE About video: the FILL + FRAME geometry are global (above the desktop @media); here the
     frame is lifted above the static bg (z:2). The About video is inside .ags-about__photo, so it
     inherits that layer's grayscale/darken filter. (The hero card is a plain Image widget — no video
     — so its <img> shows as normal, editable content.) */
  body.ags-redesign .ags-about__photo > .ags-vid-frame { z-index: 2; }
  body.ags-redesign .ags-about__photo { background-image: none !important; }
  body.ags-redesign .ags-about__kanji { position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
  /* Copy: the MOBILE reference (figma/mobile/Mobile.png §About) shows a small, compact ~5-line block
     at ~77% width — NOT the large 16px Kit body that rendered ~7 lines. Cap the width at ~77vw and
     size the paragraph text down to the reference (~13px @390) so the wrap + weight match. */
  body.ags-redesign .ags-about__left > .elementor-widget-text-editor { order: 3; width: min(94vw, 385px); max-width: 100%; margin-left: auto; margin-right: auto; text-align: center; margin-top: 18px; }
  /* Body copy sized + widened to match the MOBILE reference's line-for-line wrap. The reference uses a
     more condensed face (~4.8 CSS px/char); our Red Hat Display is wider (~5.35), so at 12px the same
     ~60-char lines ("…travel company creating" on L1 → 5 lines) need a ~86vw block (321px @375) rather
     than the reference's 79%. CENTER-aligned — the lines wrap near-full-width so the block still reads
     as a rectangle, with the softer centred edges (not hard-justified). */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-about__left > .elementor-widget-text-editor:not(:last-child) p {
    font-size: min(3.25vw, 13.5px); line-height: 1.5; text-align: center;
  }
  body.ags-redesign .ags-about__left > .elementor-widget-text-editor:last-child { order: 4; margin-top: -12px; }
  /* "About Ageshio" → ALL CAPS (matches desktop "ABOUT AGESHIO"; the desktop uppercase rule is
     gated ≥1024). */
  body.ags-redesign .ags-about__left > .elementor-widget-text-editor:last-child a { text-transform: uppercase; }

  /* =====================  MASTERS (natural long scroll)  =====================
     Mockup (figma/mobile/Mobile.png): LEFT-aligned "Authentic Lineage" title → a near-square
     featured card that CAROUSELS through the 5 masters (segmented progress bar on the top edge,
     mirroring the §3 gallery) → 師範/師 legend SIDE-BY-SIDE → centred intro → a long, thin,
     letter-spaced "MEET THE MASTERS" pill. Flatten the boxed inner + top-row so the pieces
     reorder into that single column.

     ONE KNOB (--m-card-w) sizes the whole section as a unit (see [[wp-redesign-mobile-scaling-
     pattern]]): the card, its frame, the progress bar width, and the kanji all derive from it. */
  body.ags-redesign .ags-masters {
    padding-top: calc(var(--vh, 1vh) * 10);
    padding-bottom: calc(var(--vh, 1vh) * 10);
    --m-card-w: min(83vw, calc(var(--vh, 1vh) * 60), 560px);          /* card width — 83vw on phones (reference ratio),
                                                     svh-capped so it grows-not-explodes on tablet */
    --m-card-h: calc(var(--m-card-w) * 1.05);     /* near-square (reference card ≈ 623 × 658) */
  }
  body.ags-redesign .ags-masters > .e-con-boxed > .e-con-inner { width: 100%; max-width: 100%; }
  body.ags-redesign .ags-masters__bx > .e-con-inner {
    min-height: 0;
    display: flex; flex-direction: column;
    gap: 28px;
  }
  body.ags-redesign .ags-masters__top { display: contents; }
  /* Title column — LEFT-aligned, hugging the gutter (reference sits it slightly LEFT of the card).
     Kill the column's own e-con padding so the title reads from the band edge, not indented. */
  body.ags-redesign .ags-masters__top > .e-con:first-child {
    order: 1; width: 100%; align-items: flex-start; padding-left: 0; padding-right: 0;
    /* Gap below the title before the card. Reduced 0.4→0.2·card-width (Simon 2026-09-24: title sat too
       far from the carousel). Derived from the card knob so it scales as one unit, minus the row's own
       28px flex gap to the card. */
    margin-bottom: calc(0.2 * var(--m-card-w) - 28px);
  }
  body.ags-redesign .ags-masters__row { order: 2; }
  body.ags-redesign .ags-masters__kanji { order: 3; margin-top: 20px; }   /* +20px over the 28px flex gap → a bit more room between the image and the kanji (Simon 2026-09-24) */
  body.ags-redesign .ags-masters__top > .e-con:last-child { order: 4; width: 100%; align-items: center; }
  body.ags-redesign .ags-masters__top .elementor-heading-title {
    transform: none;
    text-align: left;     /* "Authentic Lineage" reads from the left edge */
    /* Always wrap to 2 lines ("Authentic" / "Lineage") at EVERY width — the reference is always
       2 lines, but once the column widens past ~600px "Authentic Lineage" fits on one line and the
       font caps at 52px. Cap the title box in `em` (relative to its own font, so it scales with the
       type): ~9em fits "Authentic" (~5.2em) but NOT "Authentic Lineage" (~10.4em) → "Lineage" always
       drops to line 2. Same trick as the desktop rule (~components.css:1342). */
    width: 9em; max-width: 9em;
    line-height: 1.02;
  }
  /* Intro — CENTRED (reference), overriding Elementor's baked-in align="right" (per-element rule,
     0,1,0, loads after this file). Match its depth with the page-root scope so we win. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-masters__intro,
  body.ags-redesign [data-elementor-type="wp-page"] .ags-masters__intro .elementor-widget-container,
  body.ags-redesign [data-elementor-type="wp-page"] .ags-masters__intro p { text-align: center; }
  body.ags-redesign .ags-masters__intro { width: 100%; margin-left: 0; }

  /* ---- Featured card / carousel frame -----------------------------------------------------
     The row's INNER becomes the card FRAME: a centred near-square box. Without JS it holds the
     single server-featured (.is-active) card in flow; with JS (.is-carousel) all 5 cards stack
     absolute inside it and cross-fade. */
  body.ags-redesign .ags-masters__row,
  body.ags-redesign .ags-masters__row > .e-con-inner {
    width: 100%; max-width: 100%;
    flex-direction: column;
    height: auto;
    gap: 0;
  }
  body.ags-redesign .ags-masters__row > .e-con-inner {
    width: var(--m-card-w);
    margin-left: auto;
    margin-right: auto;      /* centre the card frame in the column */
    position: relative;       /* anchor for the progress bar */
  }
  /* Portrait fill + scrim + caption apply to EVERY mobile card (in carousel mode any of the 5 can
     be the visible one). The desktop rules that pin the image are gated ≥1024, so re-establish. */
  body.ags-redesign .ags-master { display: none; }
  body.ags-redesign .ags-master .ags-portrait,
  body.ags-redesign .ags-master .ags-portrait .elementor-widget-container,
  body.ags-redesign .ags-master .ags-portrait figure,
  body.ags-redesign .ags-master .ags-portrait img { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; margin: 0; }
  body.ags-redesign .ags-master .ags-portrait img { -o-object-fit: cover; object-fit: cover; aspect-ratio: auto; }
  body.ags-redesign .ags-master::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 45%;
    background: linear-gradient(to top, rgba(17,16,16,0.85), rgba(17,16,16,0));
    z-index: 1; pointer-events: none;
  }
  /* Mobile featured card — SAME reference layout as desktop: [師 (red, left)] Name ...... Role (right). */
  body.ags-redesign .ags-master .ags-master__caption {
    position: absolute; left: 16px; right: 16px; bottom: 16px;
    width: auto;   /* the Elementor container defaults to width:100% → left/right wouldn't constrain it and
                      the row overflowed the card; width:auto lets left:16+right:16 set the real width. */
    opacity: 1; transform: none; z-index: 2;
    padding: 0;   /* kill Elementor's default .e-con padding (was misaligning bottoms + overflowing the pill) */
    flex-direction: row; align-items: flex-end; justify-content: space-between; gap: 8px;
  }
  /* consistent kanji→name gap on mobile too (per kanji length; see desktop note) */
  body.ags-redesign .ags-master[data-rank-len="1"] .ags-master__caption { padding-left: 40px; }
  body.ags-redesign .ags-master[data-rank-len="2"] .ags-master__caption { padding-left: 68px; }
  body.ags-redesign .ags-master .ags-master__rank {
    position: absolute; z-index: 2;   /* the base absolute/bottom rule is gated ≥1024 → re-establish on mobile */
    left: 16px; right: auto; bottom: 16px;
    text-align: left;
    opacity: 1; pointer-events: none;
  }
  body.ags-redesign .ags-master .ags-master__rank .elementor-heading-title { line-height: 1; }   /* glyph bottom == box bottom → bottom-aligns with name/pill */
  body.ags-redesign .ags-master__name .elementor-heading-title { white-space: nowrap; line-height: 1; }
  /* Prominent name + small role PILL on the mobile card too (the desktop rules are gated ≥1024, so the
     per-element Elementor CSS otherwise forces both to the nav_link size → they overlap). (0,4,1) beats it. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-master__name .elementor-heading-title  { font-size: 15px; }
  body.ags-redesign [data-elementor-type="wp-page"] .ags-master__title .elementor-heading-title {
    font-size: 11px; white-space: nowrap;
    display: inline-block; line-height: 1; margin: 0;
    padding: 3px 9px; border-radius: 999px;
    background: var(--ags-red); color: var(--ags-cream);
  }

  /* Fallback (JS off / carousel bailed): the single featured card fills the frame in flow. */
  body.ags-redesign .ags-master.is-active {
    display: block;
    position: relative;
    width: 100%;
    height: var(--m-card-h);
    overflow: hidden;
    border-radius: 10px;
  }

  /* Carousel mode (JS present) — all 5 cards stacked absolute in the frame, cross-fading. */
  body.ags-redesign .ags-masters__row.is-carousel > .e-con-inner { height: var(--m-card-h); }
  body.ags-redesign .ags-masters__row.is-carousel .ags-master {
    display: block;
    position: absolute; top: 0; right: 0; bottom: 0; left: 0;
    width: 100%; height: 100%;
    overflow: hidden;
    border-radius: 10px;
    z-index: 1;
    opacity: 0; visibility: hidden;
    transition: opacity .6s ease, visibility .6s ease;
  }
  body.ags-redesign .ags-masters__row.is-carousel .ags-master.is-active {
    opacity: 1; visibility: visible; z-index: 2;
  }
  /* KILL Elementor's default `transition: transform .4s` across the whole masters section — same fix +
     reason as the gallery stage above. On the desktop→mobile switch the transform-positioned pieces
     (photo container, caption, and the 師範/師 legend) would otherwise EASE 0.4s into their new mobile
     spots while the progress bar (transition:none) snaps instantly → "photo scrolls into position, bar
     left behind". Scoped to the SECTION (not just .ags-masters__row) because the kanji legend is a
     SIBLING of the row, not a descendant — a row-scoped rule can't reach it. Blanket snap; the slide
     cross-fade is re-declared right after. Guard on the carousel-active row so it's mobile-only. */
  body.ags-redesign .ags-masters:has(.ags-masters__row.is-carousel),
  body.ags-redesign .ags-masters:has(.ags-masters__row.is-carousel) * { transition: none !important; }
  /* FILMSTRIP slide for the masters PHOTO carousel — cards fill the slot (inset:0) and move via
     transform:translateX(off·100%); every card at its shortest circular offset from active (see the gallery
     note above — smooth both ways, the far-wrapping card jumps off-screen via JS). !important beats any
     Elementor motion transform + the section-wide transition:none kill above. */
  /* The STATIC card slot is the rounded viewport: its overflow:hidden + border-radius clips the sliding
     (transform-composited) cards to rounded corners throughout. Rounding the moving card itself doesn't
     survive — Chromium drops a rounded clip on the layer it's animating; a NON-animating ancestor's clip
     is reliably applied to composited descendants. position:relative also = containing block for the arrows. */
  body.ags-redesign .ags-masters__row.is-carousel > .e-con-inner { position: relative; overflow: hidden; border-radius: 10px; touch-action: pan-y; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
  body.ags-redesign .ags-masters__row.is-carousel .ags-master {
    opacity: 1 !important; visibility: visible !important;
    transform: translateX(calc(var(--ags-off, 2) * 100%)) !important;
    transition: transform .5s ease !important;
  }
  body.ags-redesign .ags-masters__row.is-carousel .ags-master.is-active { z-index: 2; }
  body.ags-redesign .ags-masters__row.is-carousel .ags-master img { -webkit-user-drag: none; }
  /* Progress bar: filling-line indicator riding the card's TOP edge. Sized off the same knob
     so it tracks the card at any width. */
  body.ags-redesign .ags-masters__bar {
    position: absolute; z-index: 3;
    left: 50%; top: 14px;
    transform: translateX(-50%);
    width: calc(var(--m-card-w) - 28px);
  }

  /* Shared carousel indicator — one filling line, reused by every carousel (B4). */
  .ags-ind__track { position: relative; width: 100%; height: 2px; border-radius: 2px;
    background: rgba(247,244,242,0.28); overflow: hidden; cursor: pointer; }
  .ags-ind__fill { position: absolute; top: 0; right: auto; bottom: 0; left: 0; width: calc(var(--ags-ind-p, 0) * 100%);
    background: var(--ags-cream, #F7F4F2); transition: width .6s cubic-bezier(.5,0,.15,1); }
  @media (prefers-reduced-motion: reduce) { .ags-ind__fill { transition: none; } }

  /* Shared carousel TAP arrows (prev/next) — overlaid on each side of a photo carousel as a visible
     tap-to-move affordance alongside swipe. Look is shared here; each carousel positions them at ITS
     photo's edges below. They sit ABOVE the photos (z:4) but are SIBLINGS of the photo anchors, so a tap
     on an arrow never triggers the photo's link (e.g. the gallery photos → TripAdvisor reviews stay live). */
  .ags-arrow {
    /* Removed on mobile (Simon 2026-09-24): the prev/next tap arrows are gone on ALL mobile carousels
       (this is the shared class) — users rely on swipe + the progress sliders + the auto-animation. */
    display: none !important;
    position: absolute; top: 50%; transform: translateY(-50%);
    z-index: 4;
    width: 36px; height: 36px; padding: 0;
    align-items: center; justify-content: center;
    border: none; border-radius: 50%;
    background: rgba(17,16,16,0.34);
    color: #F7F4F2;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .ags-arrow:hover, .ags-arrow:focus-visible { background: rgba(17,16,16,0.55); outline: none; }
  .ags-arrow:active { transform: translateY(-50%) scale(0.92); }
  .ags-arrow svg { display: block; }
  .ags-arrow--prev { left: 8px; right: auto; }
  .ags-arrow--next { right: 8px; left: auto; }
  /* Gallery: the photo is CENTRED and narrower than the full stage, so pull the arrows in to the photo's
     own left/right edges (not the stage margins). --gal-photo-w is set on the stage. */
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-arrow--prev { left: calc(50% - var(--gal-photo-w) / 2 + 6px); right: auto; }
  body.ags-redesign .ags-gallery__stage.is-carousel .ags-arrow--next { right: calc(50% - var(--gal-photo-w) / 2 + 6px); left: auto; }

  /* ---- 師範 (Grand Master) · 師 (Master) legend — SIDE BY SIDE (reference), not stacked ---- */
  body.ags-redesign .ags-masters__kanji {
    position: static;
    align-self: center; justify-content: center;
    width: 100%;
  }
  body.ags-redesign .ags-masters__kanji > .e-con-inner {
    flex-direction: row; flex-wrap: nowrap;
    justify-content: center; align-items: flex-start;
    gap: max(20px, min(8vw, 48px));
    gap: clamp(20px, 8vw, 48px);   /* space between the two glyph groups */
    width: 100%;
  }
  body.ags-redesign .ags-masters__legenditem {
    width: auto !important; flex: 0 0 auto;   /* shrink each column to its glyph (was full-width → wrapped) */
  }
  /* Kanji glyph size for the phone column (reference ≈ 57px @390). Off vw so it scales on tablet.
     Target ONLY the first heading widget in each legend item (the glyph) — the 2nd is the small
     "(Grand Master)"/"(Master)" label, which keeps its own size. */
  body.ags-redesign [data-elementor-type="wp-page"] .ags-masters__legenditem > .elementor-widget-heading:first-child .elementor-heading-title { font-size: max(48px, min(14.5vw, 84px)); font-size: clamp(48px, 14.5vw, 84px); }

  /* ---- MEET THE MASTERS button — LONG, THIN, NON-BOLD, letter-spaced (reference) ----------
     Reference pill ≈ 79% of the viewport wide and ~24px tall, with roomy tracking and a regular
     (not bold) weight. Override the shared .ags-btn--bar (full-width red pill) treatment. */
  body.ags-redesign .ags-masters__top .ags-btn--bar { width: 100%; }
  body.ags-redesign .ags-masters__top .ags-btn--bar .elementor-button {
    display: flex;
    width: min(79vw, 460px);          /* long — spans most of the column */
    margin-left: auto;
    margin-right: auto;              /* centred in the column */
    padding: 7px 24px;                /* thin — ~24px text box → ~38px pill */
    font-weight: 400;                 /* NOT bold */
    letter-spacing: 0.12em;           /* airy tracking like the reference */
  }

  /* =====================  CTA (red band)  =====================
     Reference (figma/mobile/Mobile.png): a tall, airy red band — heading "Ready to experience /
     Okinawan karate?" (2 lines, centred) with the BOOK WITH US pill below (the pill is absent from
     the mobile mockup but present on desktop Desktop(1).png — it's the primary conversion CTA, so
     it stays). Generous vertical padding gives the reference's open feel (~419px band @390). */
  body.ags-redesign .ags-cta { padding-top: calc(var(--vh, 1vh) * 15); padding-bottom: calc(var(--vh, 1vh) * 15); }
  /* Let the heading use the band's FULL inner width (the boxed CTA inner otherwise caps it ~330px,
     forcing "Ready to experience" onto a 3rd line). The boxed wrapper (cta-bx) is nested inside the
     band, so target its inner directly (not a direct child of .ags-cta). Full width → the long first
     line fits and it wraps to the reference's 2 lines. */
  body.ags-redesign .ags-cta .e-con-boxed > .e-con-inner { width: 100%; max-width: 100%; }
  body.ags-redesign .ags-cta .ags-cta__title,
  body.ags-redesign .ags-cta .ags-cta__title .elementor-widget-container { width: 100%; max-width: 100%; }
  body.ags-redesign .ags-cta .ags-btn--invert { margin-top: max(20px, min(calc(var(--vh, 1vh) * 4), 36px)); margin-top: max(20px, min(calc(var(--vh, 1vh) * 4), 36px)); margin-top: clamp(20px, calc(var(--vh, 1vh) * 4), 36px); }   /* gap heading→link */
  /* MOBILE: BOOK WITH US is a LINK, not a pill (Simon 2026-08-07). Strip the cream-pill styling →
     a cream underlined text link on the red band. Overrides the shared .ags-btn--invert pill. */
  body.ags-redesign .ags-cta .ags-btn--invert .elementor-button {
    background-color: transparent !important;
    color: var(--ags-cream);
    border: none; border-radius: 0;
    padding: 4px 0; min-width: 0;
    -webkit-text-decoration: underline;
    text-decoration: underline; text-underline-offset: 4px;
    font-weight: 400; letter-spacing: 0.1em;
  }
  body.ags-redesign .ags-cta .ags-btn--invert .elementor-button:hover { background-color: transparent !important; color: #ffffff; }

  /* =====================  FOOTER  =====================
     Reference (figma/mobile/Mobile.png): numbered links top-LEFT · big crest CENTRED below them ·
     a Phone/Email(left) + socials(right) TWO-COLUMN row · a BUDO badge + copyright row. The desktop
     footer is 3 flex-rows (toprow: links|crest · mid: empty|contact-cluster · bottom: budo|copy);
     on mobile we keep the ROW structure (don't collapse everything to one centred stack — that made
     the footer ~2× too tall) but retarget each row to the mockup. Boxed inners go full-width. */
  .ags-c-footer { padding: 40px 24px 32px; gap: 22px; }   /* tighter than desktop — reference footer is compact */
  .ags-c-footer .e-con-boxed > .e-con-inner { width: 100%; max-width: 100%; }

  /* Row 1 (toprow): links LEFT (left-aligned), crest to the RIGHT below them (reference sits the crest
     in the right ~half of the footer, its cloud centred ~75% of width — NOT centred). Stack the two
     columns; links flush-left, crest column right-aligns its image. */
  .ags-c-footer__toprow,
  .ags-c-footer__toprow > .e-con-inner {
    flex-direction: column; align-items: stretch; gap: 20px;
  }
  .ags-c-footer__toprow > .e-con-inner > .e-con { width: 100% !important; padding: 0; }
  .ags-c-footer__toprow > .e-con-inner > .e-con:first-child { align-items: flex-start; }  /* links col — left */
  .ags-c-footer__toprow > .e-con-inner > .e-con:last-child { align-items: flex-end; }     /* crest col — RIGHT (reference) */
  .ags-c-footer__toprow .elementor-widget-text-editor:first-child p { font-size: max(19px, min(6.1vw, 26px)); font-size: clamp(19px, 6.1vw, 26px); }  /* reference numbered links ≈24px @390 (were ~30px, too big) */
  .ags-c-footer__toprow img { max-width: min(58vw, 240px); height: auto; }  /* crest — reference proportion (was 273px→266px tall, dominating the footer height) */
  .ags-c-footer__toprow > .e-con-inner > .e-con:first-child { padding-top: 0 !important; }  /* drop the desktop 24px link-col nudge */

  /* Rows 2 & 3 (contact + bottom) are DIRECT children of .ags-c-footer (no .e-con-inner wrapper on the
     footer itself — each ROW is a .e-con-boxed with its own inner). Keep them as ROWS (side-by-side),
     left-aligned to the gutter, instead of collapsing to a tall centred stack. */
  .ags-c-footer > .e-con:nth-child(2) > .e-con-inner,
  .ags-c-footer > .e-con:nth-child(3) > .e-con-inner { align-items: flex-start; }
  /* MID row (contact): drop the empty desktop spacer column so the cluster sits at the gutter, and
     let the right column go full width. */
  .ags-c-footer > .e-con:nth-child(2) > .e-con-inner > .e-con:first-child:empty { display: none; }
  .ags-c-footer > .e-con:nth-child(2) > .e-con-inner > .e-con:last-child {
    width: 100% !important; align-items: flex-start !important;
  }
  /* contact + socials CLUSTER: a 2-column row (Phone/Email left · socials right). Its 2 child columns
     carry fixed desktop widths (250px / 120px) that overflow a phone → they wrapped (the whole reason
     the footer was ~2× too tall). Force the cluster full-width + space-between; each child shrinks to
     content so they sit at the two gutters like the reference. */
  .ags-c-footer > .e-con:nth-child(2) > .e-con-inner > .e-con:last-child > .e-con {
    width: 100% !important; max-width: 100% !important;
    flex-direction: row !important; flex-wrap: nowrap !important;   /* never let socials wrap under contact */
    justify-content: space-between !important; align-items: flex-start !important;
    gap: 16px !important;
  }
  .ags-c-footer > .e-con:nth-child(2) > .e-con-inner > .e-con:last-child > .e-con > .e-con {
    width: auto !important; min-width: 0 !important; flex: 0 1 auto !important;
  }
  /* Socials must stay 1-word-per-line, never break mid-word ("Instagra/m"). The 2-column row is held
     by flex-wrap:nowrap above; the email is the only long token — allow IT alone to wrap between the
     @ / domain parts so the contact column can stay narrow without hyphenating "Instagram". */
  .ags-c-footer > .e-con:nth-child(2) a { white-space: nowrap; }
  .ags-c-footer > .e-con:nth-child(2) a[href^="mailto"] { white-space: normal; word-wrap: anywhere; overflow-wrap: anywhere; }
  /* BOTTOM row (BUDO badge + copyright): keep side-by-side, left-aligned. */
  .ags-c-footer > .e-con:nth-child(3) > .e-con-inner {
    flex-direction: row; align-items: center; justify-content: flex-start; gap: 20px;
  }
  .ags-c-footer > .e-con:nth-child(3) > .e-con-inner > .e-con { width: auto !important; }
  /* BUG FIX (i18n mobile overflow, 2026-09-20): the 4 numbered legal-link <p>s (toprow) carry an
     INLINE `white-space:nowrap` baked into the migrated Text Editor widget content itself (not a
     stylesheet rule — visible via el.getAttribute('style')), so this plain declaration below can't
     beat it without !important. Harmless on EN/ES/NL (each longest link — "Terms & Conditions" /
     "Términos y condiciones" / "Algemene voorwaarden" — still fits one line ≤390), but German's
     "Allgemeine Geschäftsbedingungen" (497px) doesn't, and since it's ONE unbreakable inline element
     with no local overflow container, it pushed <body> to 531px (real page-wide horizontal overflow,
     only masked from a visible scrollbar by the site's `html,body{overflow-x:clip}` safety net two
     rules above — the content was still off-screen/clipped, not actually contained). Allowing wrap
     (matches the desktop treatment, which never forced nowrap) is the fix — same idiom as
     COMPOSITION-METHODOLOGY's "widen the box / allow wrap instead of a hard-coded EN-tuned constraint". */
  .ags-c-footer .elementor-widget-text-editor p { white-space: normal !important; }

  /* =====================  NO STICKY DECK ON MOBILE (Simon 2026-09-23)  =====================
     The sticky "slide-over" stacking fights touch scroll + momentum on phones and tablets, so on
     mobile the deck cards AND both hero frames flow top-to-bottom as normal one-screen sections
     (un-pinned), with every rest-spacer collapsed → clean, continuous native scroll. This used to be
     gated to prefers-reduced-motion (the a11y/QA fallback); it's now the DEFAULT at ≤1023. LAST in the
     mobile media query on purpose — the sticky/spacer rules above are same-specificity, so these must
     come later in source order to win. */
  body.ags-redesign .ags-stack-wrap > .ags-mission,
  body.ags-redesign .ags-stack-wrap > .ags-gallery,
  body.ags-redesign .ags-stack-wrap > .ags-values,
  body.ags-redesign .ags-stack-wrap > .ags-programs,
  body.ags-redesign .ags-hero__stage,
  body.ags-redesign .ags-hero__frame2 {
    position: relative;   /* un-pinned; each still one screen tall, flows normally */
    height: calc(var(--vh, 1vh) * 100);
  }
  /* (photo is the stage's own background now — no separate .ags-hero__bg layer to reposition) */
  body.ags-redesign .ags-stack-spacer,
  body.ags-redesign .ags-hero__frame-spacer { display: none; }   /* collapse buffers → clean flow */
  /* Nav hide-on-scroll still works on mobile (that's not "stacking") — only neutralise its full-hide
     transform under reduced-motion (matches the shared motion guard, keeps the crest on-screen). */
  @media (prefers-reduced-motion: reduce) {
    body.ags-redesign .elementor-location-header.is-hidden { transform: none; }
  }
}

/* =============================================================================
   INTERIOR COMPONENTS (ags-c-*) — reusable, Navigator-named, template-saved
   components for the ~30 interior pages (sub-project C). First page: Okinawan
   Group Tours. Same CSS-ownership model as the homepage: Elementor owns structure
   + content + Navigator names; THIS owns design/layout/animation, keyed to the
   ags-c-* classes. Colours via Global Colors, type via Kit globals.
   Responsive: shared here; desktop ≥1024 + mobile ≤1023 blocks per component.

   CONVENTION — interior-page heading/text SIZE overrides MUST target the rendered
   element with page-type scope to beat Elementor's per-element global rule (0,4,0):
     .elementor-page [data-elementor-type="wp-page"] .ags-c-X .elementor-heading-title { font-size: ... }
   (text-editor globals sit on the widget → target .elementor-widget-container instead.)
   Do NOT use body.ags-redesign here — that class is FRONT-PAGE-only (functions.php),
   absent on interior pages, so it silently fails. Tasks 4–6 reuse this pattern.
   ============================================================================= */

/* Neutral placeholder box — stands in for an unsourced photo (no broken-img glyph). The slot is a
   REAL Elementor image widget pointing at Elementor's placeholder.png (so it's editable from
   Navigator/Media Library); these rules paint the neutral #d8d0cb box over it and HIDE the
   placeholder pixels until a real image is set. Once an editor picks an image, its src no longer
   matches [src*="placeholder.png"], so the real photo shows and the mask lifts automatically. */
:root { --ags-c-ph: #d8d0cb; }
.ags-c-ph { background: var(--ags-c-ph); aspect-ratio: 4 / 3; display: block; }
/* Hide ONLY Elementor's placeholder image; a real chosen image (any other src) renders normally. */
.ags-c-ph img[src*="/elementor/assets/images/placeholder"] { opacity: 0; }
.ags-c-ph img { display: block; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; }

/* Karate History Tour — blue "What's included" band. Blue bg comes from the Kit global color
   ref (band(BLUE) → globals/colors?id=agsblue). Defensive: the heading/number/label widgets
   already emit the CREAM 'accent' global (same as the maroon variant, which needs no color
   rule), so this only PINS cream in case that band's foreground global is ever re-pointed.
   Width-agnostic (NOT inside the ≥1024 block) so it holds at mobile widths too. */
.ags-c-titleband--blue .elementor-heading-title,
.ags-c-numgrid--blue .ags-c-numgrid__num .elementor-heading-title,
.ags-c-numgrid--blue .ags-c-numgrid__label { color: var(--ags-cream, #F7F4F2); }

/* READ MORE on the History Tour Locations section is DROPPED for now — it links nowhere yet (Simon
   2026-09-24). Hidden at all widths (desktop + mobile); un-hide when it has a destination. */
body.ags-page-karate-history-tour .ags-c-locations__btn { display: none; }

/* ── "What's included" HOVER-REVEAL — floating preview near the hovered item (Simon 2026-09-23) ──────
   Show a photo on hover for each of the 8 inclusions. DESKTOP: a compact preview card floats in the void
   just LEFT of the grid and VERTICALLY TRACKS whichever item you hover — it slides to that item's level
   and cross-fades to its photo, so the image reads as belonging to the item you're on (ageshio-numgrid.js
   reads the live layout → the position + size stay right across desktop widths). Photos are per-ITEM
   Elementor Image widgets (.ags-c-numgrid__img--N, client-editable — one per inclusion); the JS uses each
   one's src, so they're the single editable source. A GREY card + cream number badge stand in until real
   photos are set. Scoped to Group Tour for now (the feedback); History's "What's included" can inherit it
   later. MOBILE (≤1023) is deferred — the per-item photos will render inline in the mobile pass. */
.ags-c-numgrid__img { display: none; }   /* the per-item widget is the editable SOURCE; the preview shows it */

@media (min-width: 1024px) {
  /* .ags-has-photo-preview is added by ageshio-numgrid.js to numgrids that have per-item photo slots
     (Group Tour + VIP), so these rules never touch numgrids without the feature. */
  .ags-c-numgrid.ags-has-photo-preview { position: relative; }
  /* grid + kanji stay above the floating preview so a photo can never cover the numbers/labels/旅行 */
  .ags-c-numgrid.ags-has-photo-preview .ags-c-numgrid__grid { position: relative; z-index: 3; }
  .ags-c-numgrid.ags-has-photo-preview .ags-c-numgrid__eyebrow { position: relative; z-index: 4; }

  /* Preview card — left edge on the section guide, top+height matched to the grid rows (set live by
     ageshio-numgrid.js). Flat (no shadow, per Simon); same spot for every item → hover cross-fades. */
  .ags-c-numgrid__preview {
    position: absolute; z-index: 2; pointer-events: none;   /* left/width/top/height set live by JS */
    border-radius: 8px; overflow: hidden;
    background: #D9D5D0;                        /* grey placeholder until a photo loads */
    opacity: 0; transform: translateY(8px);
    transition: opacity .28s ease, transform .28s ease;
  }
  .ags-c-numgrid__preview.is-visible { opacity: 1; transform: none; }
  /* two stacked panes cross-fade between items */
  .ags-c-numgrid__pane {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; background-size: cover; background-position: center;
    opacity: 0; transition: opacity .28s ease;
  }
  .ags-c-numgrid__pane.is-active { opacity: 1; }
  /* cream number badge — placeholder legibility while the slots are grey (drop/restyle once photos land) */
  .ags-c-numgrid__prevnum {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; display: flex; align-items: center; justify-content: center;
    font-family: var(--e-global-typography-accent-font-family, "Red Rose"), sans-serif;
    font-weight: 700; font-size: calc(46 * var(--ags-hpx)); line-height: 1;
    color: var(--ags-cream, #F7F4F2); opacity: .5; text-shadow: 0 2px 12px rgba(0, 0, 0, .35);
  }
  /* gentle focus cue: dim the other items while hovering the list, so the active one reads */
  .ags-c-numgrid.ags-has-photo-preview .ags-c-numgrid__item { transition: opacity .2s ease; }
  .ags-c-numgrid.ags-has-photo-preview .ags-c-numgrid__grid:hover .ags-c-numgrid__item:not(:hover) { opacity: .55; }
}
@media (min-width: 1024px) and (prefers-reduced-motion: reduce) {
  .ags-c-numgrid__preview { transition: opacity .01s linear; }
  .ags-c-numgrid__pane { transition: opacity .01s linear; }
}

/* GROUP TOURS — per-slot greyscale (reference: kumite band, black-belt detail, overlay forearm
   are B&W; group/master/beach/hero are colour). CSS filter — reversible, NOT baked into the webp.
   No-op on the placeholder box; only bites once a real photo lands. */
body.ags-page-okinawan-group-tours .ags-c-photo__img img { filter: grayscale(1); }                                      /* §3 kumite band */
body.ags-page-okinawan-group-tours .ags-c-photorow__col:nth-child(2) .ags-c-photorow__img img { filter: grayscale(1); } /* §5 belt (middle) */
/* overlay (§8) forearm bg is already greyscale via the existing .ags-c-overlay__photo img rule. */

@media (min-width: 1024px) {
  /* INTERIOR PACING — one long scroll, but a few sections fill the viewport to punctuate it.
     min-height:100svh (not height) → fills at any desktop width, grows if content needs more,
     never clips. Mobile (≤1023) is untouched → stays a continuous scroll. */
  /* §1 hero content sits in the upper band (just below the floating navbar), then §2 marquee is a
     COMPACT text band directly under it, then §3 feature photo follows IMMEDIATELY (reference).
     The hero sizes to its content (no forced tall band → no empty maroon strip), anchored near the
     top; the marquee gets no tall min-height and the feature photo pulls up to fill the rest of the
     first screen. Top padding clears the floating navbar responsively. */
  /* §1 hero + §2 marquee = top ~55svh of the first screen; the §3 feature photo fills the bottom
     ~45% (measured off the reference: photo top at 55% of the viewport). Hero sizes to a fixed svh
     so hero + the ~161px marquee band land at ~55svh, leaving the feature photo visible below. */
  /* SHARED interior §1 hero (GT is the reference; VIP/History/Karate-Exp inherit this
     IDENTICAL geometry + type — the per-page blocks below only tune the photo crop). */
  .ags-c-hero {
    min-height: 40vh;
    min-height: 40svh; justify-content: center;
    /* Bottom gap (hero content → marquee) scales with viewport WIDTH (vw), matching how the photo/
       type/marquee scale — an svh gap stayed ~fixed in px while the content grew wider, so the gap
       looked to shrink on ultrawide. vw keeps it proportionally consistent laptop→ultrawide. */
    padding-top: calc(120 * var(--ags-px)); padding-bottom: calc(49 * var(--ags-px));
  }
  body.ags-page-okinawan-group-tours .ags-c-overlay { min-height: 100vh; min-height: 100svh; }
  /* §9 Accordion fills one viewport. The list sits TOP-LEFT and the photo BOTTOM-RIGHT (reference
     #16) — so the inner row spans the full section height (its ::before divider region), the list
     top-aligns, and the photo is absolutely pinned to the bottom-right of this box (see aside rule)
     so it never moves when an item opens. Vertical padding gives the top-left list breathing room. */
  /* Fixed 100svh box + position:relative → the absolutely-pinned bottom-right photo references THIS
     stable box (not the list), so opening an item never moves it. The list flows top-left inside. */
  /* ULTRAWIDE = CONTENT WELL: cap the content to a centred laptop-width column (--ags-acc-well is the
     symmetric margin that grows past 1440 so the inner column stays ~1440 wide). On ultrawide it
     looks EXACTLY like laptop — heading top-left, photo bottom-right, same spacing — with big empty
     margins either side (accepted). Below 1440 the well is 0 and a small edge padding applies. */
  /* Content well: column caps at ~2040 so ultrawide gets a wider, more spread-out layout (not a tight
     laptop column), centred with symmetric margins that grow past 2040. Below 2040 the well is 0. */
  /* SHARED interior §9 accordion — GT is the reference; VIP + History inherit this IDENTICAL
     geometry (fixed 100svh box, list top-left, photo DECOUPLED + pinned bottom-right, fixed-width
     maroon divider rules). The per-page blocks below only tune the photo CROP. Content-well caps
     the column to ~2040 centred on ultrawide; --ags-acc-photo-w / --ags-acc-rule-w scale with it. */
  .ags-c-accordion {
    /* The well is now the REFERENCE FRAME's own letterbox rather than an arbitrary 2040px cap, so
       the accordion column lands on exactly the same guide as every other section (measured ink was
       770px from the left at 3440 against the frame guide's 416). 0 below 1440, where frame = 100vw. */
    --ags-acc-well: max(0px, calc((100vw - var(--ags-frame)) / 2));
    /* 489.6 = the clamp's value at the 1440 reference; the svh term stays as the height guard that
       keeps the photo inside a short section. round 12 (Simon 2026-09-21): reduced 489.6->400 and
       78svh->64svh — the accordion photo read too large at every size. */
    --ags-acc-photo-w: min(calc(400 * var(--ags-px)), calc(calc(var(--vh, 1vh) * 64) / 1.0925));
    --ags-acc-rule-w: calc(413.6 * var(--ags-px));
    /* Named so the padding-top compensation below is DERIVED from them, not a magic number: the
       visible top gap is the section pad PLUS the first header's own padding PLUS the leading above
       the glyph caps. */
    --ags-acc-hdr-pad: calc(10 * var(--ags-px));
    --ags-acc-h-fs: calc(72.8 * var(--ags-px));
    position: relative; height: 100vh; height: 100svh; min-height: 100vh; min-height: 100svh;
    display: flex; flex-direction: column; justify-content: flex-start;
    padding-left: calc(max(24px, min(3vw, 40px)) + var(--ags-acc-well));
    padding-right: calc(max(24px, min(3vw, 40px)) + var(--ags-acc-well));
    padding-left: calc(max(24px, min(3vw, 40px)) + var(--ags-acc-well));
    padding-left: calc(clamp(24px, 3vw, 40px) + var(--ags-acc-well));
    padding-right: calc(max(24px, min(3vw, 40px)) + var(--ags-acc-well));
    padding-right: calc(clamp(24px, 3vw, 40px) + var(--ags-acc-well));   /* padding-block → shared --ags-sec-pad */
    box-sizing: border-box;
  }
  .ags-c-accordion__inner { flex: 0 0 auto; }

  /* ---- SHARED VERTICAL RHYTHM (Mika's pad on the interior pages, 2026-09-09) -------------
     The interior pages had NO shared vertical token — measured ink pads at 1440x766 ran
     statement 68/168, titleband 72/134, numgrid -33/78, accordion 48/29, explist 50/58. Same knob
     as the homepage now governs all of them: --ags-sec-pad (10.7% of viewport height = 82px at
     Mika's 1440x766 reference), top AND bottom.

     Page-type scope, not a bare class: these are Elementor containers whose own per-element padding
     is emitted at (0,4,0), and several pages carried a (0,2,0) `body.ags-page-* .ags-c-statement
     { padding-bottom }` override. Those per-page declarations are deleted below rather than left to
     lose silently (the parity rule: edit the ONE shared rule).

     "What's included" is titleband + numgrid = ONE composition split across two sections, so the
     pad goes on the OUTSIDE of the pair only — top of the titleband, bottom of the numgrid — or the
     sum would open a 164px gulf through the middle of it.

     DELIBERATELY EXCLUDED: §1 hero + §2 marquee (the red block is height-equalised across all four
     pages to 0px spread — a hard-won invariant, and its pads are part of that); §3 feature photo and
     §5 photo row (full-bleed imagery, pad 0 by design, same as the homepage gallery); §10 CTA (its
     large padding is the deliberate site-wide enlargement Simon approved in the parity pass); and the
     §8 overlay, whose headings are corner-anchored poster-style at top:3%/bottom:5% — Mika's note on
     that section was about SIZE only, so its composition is left alone. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-statement,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist {
    padding-top: var(--ags-sec-pad);
    padding-bottom: var(--ags-sec-pad);
  }
  /* The accordion needs a DERIVED top pad, not the raw token, because its two edges are different
     KINDS of thing. Bottom edge = an IMAGE, whose painted edge is its box edge, so bottom = the pad
     exactly. Top edge = TEXT inside the first header, so two terms stack under the pad: the header's
     own padding (the click target — legitimate, keep it) and the leading above the glyph caps inside
     a line-height:1 box, measured at 0.096em of the heading (7px at 72.8, 14px at 136.9). Both of
     those SCALE, which is exactly why a flat-px compensation would drift — subtract them as
     expressions and both painted edges land on --ags-sec-pad at every viewport. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-accordion {
    padding-top: calc(var(--ags-sec-pad) - var(--ags-acc-hdr-pad) - 0.096 * var(--ags-acc-h-fs));
    padding-bottom: var(--ags-sec-pad);
  }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-titleband { padding-top: var(--ags-sec-pad); }
  /* ⚠️ Elementor puts a default 10px padding on EVERY container level, so --ags-sec-pad was being
     ADDED to three or four of them and the sections came out top-heavy: the accordion's first
     heading landed 122px down (82 + 10 inner + 10 list + 10 item + 10 header) while its bottom —
     set by the absolutely-pinned photo — sat at 82. Measured by painted pixels, 130 vs 89.
     Clear the pure WRAPPER levels' vertical padding (inline is left alone — it is load-bearing for
     the numgrid gutter and the accordion well) so the section's own pad is the only vertical one.
     The accordion __header keeps its own padding: that is the click target, not stray. This is the
     same "clear it at every level" rule the GT/VIP stages already follow. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-accordion__inner,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-accordion__list,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-accordion__item,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid > .e-con,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__grid,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__item,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-titleband > .e-con,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-statement > .e-con {
    padding-top: 0;
    padding-bottom: 0;
  }
  /* A text-editor <p> keeps the browser default `margin: 1em 0`, which hung ~17px of dead space
     under the last grid row. Trailing margin only — the leading one is the number→text gap. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__label p:last-child { margin-bottom: 0; }
  /* Same trap on the statement's eyebrow labels: a text-editor <p> keeps `margin: 1em 0`, which at
     18px put 18px of dead space above the first glyphs — the section read 104/89 painted. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-statement__labels p { margin: 0; }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-titleband + .ags-c-numgrid {
    padding-bottom: var(--ags-sec-pad);
  }
  /* "History Tour Locations" is a PAIR — the title band + the location card row (.ags-masters) below
     it — that reads as ONE section and should fill ONE viewport, exactly like "What's included"
     (titleband + numgrid). So the pad goes on the OUTSIDE of the pair only: the title band keeps its
     TOP --ags-sec-pad (the shared rule), the card row keeps its BOTTOM pad, and the internal boundary
     is collapsed (padding-bottom:0) — otherwise the title's bottom pad + the card row's top pad stack
     into a ~125px gulf through the middle and the pair overruns the viewport (was 983 vs 766).
     (Earlier this was mislabelled "standalone" and given a bottom pad — it is not standalone.) */
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-titleband.ags-c-locations-title {
    padding-bottom: 0;
    /* FILL, don't just fit: give the pair min-heights that SUM to 100svh (like "What's included" =
       40svh+60svh), so the section fills the viewport at EVERY size, not only at the 1440 reference
       where the content happened to add up to one screen. 45/55 split: the title block needs ~45svh
       (10.7svh top pad + ~34svh title), the card row ~55svh (gap + 290px row + bottom pad). Title
       pinned to the TOP of its band; card row pinned to the BOTTOM of its band (justify-content),
       whitespace distributes between — the established one-screen-pair behaviour. */
    min-height: 40vh;
    min-height: 40svh; display: flex; flex-direction: column; justify-content: flex-start;   /* title block ~40svh (title + caption, stray padding cleared) */
  }
  /* "What's included" = two sibling sections (titleband + numgrid). Split one screen across the
     pair: the heading pins to the TOP (reference), the kanji+grid fill the rest below it. `+`
     scopes to this instance only. Both share the same small left gutter (var --gt-inc-gutter) so
     the heading, kanji and grid all read off one left edge. */
  /* Shared left edge for the top-left title AND the bottom-left kanji, so their left sides align.
     --gt-inc-inset: symmetric side inset that STAYS 0 up to ~1920 then grows GENTLY on ultrawide, so
     the whole section (title left-pinned, grid right-pinned) comes in from the edges together without
     changing the internal composition. Both titleband + numgrid use the same inset → title-left and
     kanji-left both = inset + gutter, so they stay aligned. Capped so 4K doesn't over-inset. */
  body.ags-page-okinawan-group-tours {
    --gt-inc-gutter: calc(43.2 * var(--ags-px));
    --gt-inc-inset: max(0px, calc((100vw - var(--ags-frame)) / 2));   /* the frame's letterbox, replacing a hand-rolled ultrawide pull-in */
  }
  body.ags-page-okinawan-group-tours .ags-c-titleband,
  body.ags-page-okinawan-group-tours .ags-c-numgrid { padding-left: var(--gt-inc-inset); padding-right: var(--gt-inc-inset); box-sizing: border-box; }
  body.ags-page-okinawan-group-tours .ags-c-titleband { min-height: 40vh; min-height: 40svh; display: flex; flex-direction: column; justify-content: flex-start; }   /* padding-top → shared --ags-sec-pad */
  body.ags-page-okinawan-group-tours .ags-c-titleband__h { padding-left: var(--gt-inc-gutter); }
  /* Title is top-LEFT; the grid sits BOTTOM-RIGHT of the pair for a diagonal composition (reference).
     Push the grid down (justify-content:flex-end + bottom pad) and further right (see grid indent). */
  body.ags-page-okinawan-group-tours .ags-c-titleband + .ags-c-numgrid { min-height: 60vh; min-height: 60svh; display: flex; flex-direction: column; justify-content: flex-end; }   /* padding-bottom → shared --ags-sec-pad */
  /* Kanji block (旅行 + Tour) shares the title's left edge. The title's gutter is a padding-left on
     .ags-c-titleband__h; the eyebrow column carries its OWN stray left pad (~10px) + the gutter →
     zero the stray pad and set the same gutter so kanji-left == title-left exactly. */
  /* The eyebrow is nested TWO .e-con levels deep; the OUTER wrapper (parent of .ags-c-numgrid__eyebrow)
     carries Elementor's stray 10px left pad, which added on top of the gutter → kanji sat 10px right
     of the title. Zero that outer pad, put the gutter on the eyebrow, and clear inner pads/margins. */
  body.ags-page-okinawan-group-tours .ags-c-numgrid__eyebrow { padding-left: var(--gt-inc-gutter); }
  /* Vertical: the kanji block's BOTTOM (旅行 + Tour) should sit just ABOVE the grid's first-row "1"
     (reference). The eyebrow + grid are flex-row children top-aligned, so the taller kanji block
     hangs BELOW the "1"; lift it with a negative top margin (≈1.3× the kanji size, tuned so the Tour
     baseline lands a hair above row 1 at both laptop and ultrawide). Scales with --gt-inc-kanji. */
  body.ags-page-okinawan-group-tours .ags-c-numgrid__eyebrow { margin-top: calc(var(--gt-inc-kanji) * -1.95); }   /* lifted higher (was -1.35) so 旅/Tour clears the hover photo below it */
  /* Kill the body-wrapper's stray 10px left pad so the eyebrow's gutter is the SOLE left offset →
     kanji-left == title-left (title uses the identical gutter). Shifts the whole row 10px left; fine. */
  body.ags-page-okinawan-group-tours .ags-c-numgrid > .e-con { padding-left: 0; }
  body.ags-page-okinawan-group-tours .ags-c-numgrid__eyebrow > .elementor-element,
  body.ags-page-okinawan-group-tours .ags-c-numgrid__eyebrow .elementor-widget-container { padding-left: 0; margin-left: 0; }
  /* Bigger kanji. "Tour" centred UNDER the kanji glyph: the Tour widget is shrink-to-text (~39px),
     so centring its own text ≠ centring under the wider kanji. Give the Tour widget a width equal
     to the kanji's measure (2 CJK glyphs ≈ 2× the kanji font-size) then centre the text within it.
     One var drives both so they stay locked as type scales. */
  body.ags-page-okinawan-group-tours { --gt-inc-kanji: calc(72 * var(--ags-px)); }
  body.ags-page-okinawan-group-tours [data-elementor-type="wp-page"] .ags-c-numgrid__eyebrow .elementor-widget-heading:first-child .elementor-heading-title { font-size: var(--gt-inc-kanji); line-height: 1; }
  body.ags-page-okinawan-group-tours .ags-c-numgrid__eyebrow .elementor-widget-heading:last-child { width: calc(var(--gt-inc-kanji) * 1); }   /* 旅 is 1 char now (was 旅行, 2) → match its width so "Tour" centres under it */
  body.ags-page-okinawan-group-tours .ags-c-numgrid__eyebrow .elementor-widget-heading:last-child .elementor-heading-title { text-align: center; }

  /* §1 PAGE HERO — maroon band: eyebrow · photo · intro+CTA in a 3-col row. */
  .ags-c-hero__eyebrow-col { flex: 0 0 auto; }
  /* Hero photo is SMALL in the reference (~179px ≈ 14% of the 1280 container) — cap the column so
     it can't inflate under the tall band. SHARED. Page-type scope (0,3,0) beats Elementor's
     per-element --flex-* consumer (0,1,0) that would otherwise resolve the column to its 30% basis
     (same trap the numgrid documents) → an uncapped photo overflows the row and wraps the hero. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__photo-col { flex: 0 0 calc(244.8 * var(--ags-px)); }
  /* Reference: the TOPS of the kanji cluster, photo and intro paragraph all line up, kanji+photo
     on the LEFT, intro+CTA on the RIGHT. CONSISTENT laptop↔ultrawide: use the SAME side gutters at
     every width (not a max-width cap that only bites past 1600px → that made 1280 edge-to-edge but
     2560 a centred block). Type scales via the root-rem knob, so the whole cluster grows uniformly.
     align-items:flex-start so the three column TOPS line up. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__row {
    align-items: flex-start;
    /* round 11 (Simon 2026-09-21, ultrawide faithfulness): + the frame letterbox well so the hero
       kanji/intro cap to the frame guide instead of the screen edge on ultrawide (well=0 ≤1920, so
       the 1440 reference inset is unchanged). */
    width: 100%; padding-left: calc(max(0px, (100vw - var(--ags-frame)) / 2) + max(8px, min(1vw, 28px))); padding-right: calc(max(0px, (100vw - var(--ags-frame)) / 2) + max(8px, min(1vw, 28px))); padding-left: calc(max(0px, (100vw - var(--ags-frame)) / 2) + max(8px, min(1vw, 28px))); padding-left: calc(max(0px, (100vw - var(--ags-frame)) / 2) + clamp(8px, 1vw, 28px)); padding-right: calc(max(0px, (100vw - var(--ags-frame)) / 2) + max(8px, min(1vw, 28px))); padding-right: calc(max(0px, (100vw - var(--ags-frame)) / 2) + clamp(8px, 1vw, 28px)); box-sizing: border-box;
    gap: calc(14.4 * var(--ags-px)) !important;   /* tighter kanji↔photo gap (builder set 32px inline) */
  }
  /* Photo must FILL its box cleanly (no stretch/letterbox): cover + a tall-ish box like the
     reference where the photo is the tallest element in the row. */
  .ags-c-hero__photo img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; border-radius: 8px; }
  .ags-c-hero__photo { aspect-ratio: 179 / 176; }
  /* Rounded corners come from the widget's native Elementor `image_border_radius` (set in the page
     data, same as the homepage photos) — nothing to do here. No container clip. */
  .ags-c-hero__intro-col   { flex: 1 1 0; min-width: 0; align-items: flex-start; }
  .ags-c-hero__intro       { max-width: 34em; }

  /* GT §1 layout to match the reference: LARGE 旅行 kanji + "Tour" label sit directly LEFT of the
     photo (kanji & photo side-by-side, vertically centred); the intro paragraph + CTA are pushed
     to the far RIGHT, right-aligned, wrapping to ~4 lines.
     The eyebrow column + its inner .ags-c-eyebrow are full-width Elementor .e-con containers (--width
     100%). Left flexible they take an equal ~508px split (big gap before the photo); pinned to
     `flex:0 0 auto` alone they instead demand the WHOLE row (100% child) and shove the photo off
     screen. Fix both ends: col = shrink-to-fit, inner eyebrow = max-content so it hugs the kanji. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__eyebrow-col { flex: 0 0 auto; width: -webkit-max-content; width: -moz-max-content; width: max-content; }
  .ags-c-hero__eyebrow-col .ags-c-eyebrow { width: -webkit-max-content; width: -moz-max-content; width: max-content; max-width: none; }
  /* Intro column takes the remaining middle space so its right-aligned content sits at the far
     right (0,3,0 beats Elementor's .e-con.e-flex flex var at 0,2,0). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__intro-col { flex: 1 1 0; }
  /* Big kanji: the 40px comes from a per-element rule (.elementor-19532 .elementor-element-… ,
     0,3,0). Beat it with the page + wp-page + rendered-element chain, targeting the FIRST heading
     in the eyebrow (旅行); the SECOND heading ("Tour") is left small. */
  /* Kanji + Tour sized in REM so they scale with the root-rem knob in lockstep with the intro —
     consistent laptop↔ultrawide (a fixed clamp would drift against the rem-scaled intro). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__eyebrow-col .ags-c-eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: 4.4rem; line-height: 1.0; margin-top: -0.12em;   /* pull the kanji glyph up to the photo top */
  }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__eyebrow-col .ags-c-eyebrow .elementor-widget-heading:last-child .elementor-heading-title {
    font-size: 1.1rem; line-height: 1.2; text-align: center;
  }
  /* label (Tour/Retreat/Experience) sits CENTRED under the kanji (reference). The eyebrow is a
     max-content column, so centre the label widget's own box across the kanji width. */
  .ags-c-hero__eyebrow-col .ags-c-eyebrow .elementor-widget-heading:last-child .elementor-widget-container { text-align: center; }
  .ags-c-hero__eyebrow-col .ags-c-eyebrow .elementor-widget-heading:last-child { width: 100%; }
  /* LAYOUT ONLY — type family/weight/tracking come from the Kit globals the widgets carry (intro =
     `ags_h3` Bounded −3%, the §5/§6-intro global; button = `accent` Red Rose Bold 0%). Do NOT
     re-declare font here (CSS-ownership model). Push the intro paragraph + CTA to the far right,
     right-aligned, inset from the right edge by the SAME amount so they stay aligned.
     WRAP-LOCK: the four line breaks are HARD <br>s in the intro editor HTML (matching the reference
     wrap exactly) → the wrap is identical at EVERY width, immune to sub-pixel rounding that made a
     soft em-box drift a word between laptop↔ultrawide. The box just needs to be WIDE ENOUGH that no
     hard-break line soft-wraps again; sized in em so it tracks the root-rem knob (scales as one
     unit). white-space guards the intended breaks. */
  .ags-c-hero__intro-col { align-items: flex-end; text-align: right; }
  /* The intro's first line sits below its box top (Bounded's line-box leading), so its cap lands
     lower than the photo/kanji tops. Pull it up so all three tops align. Use REM (not em): em here
     resolves against the intro WIDGET's own font-size, which differs by page (GT's intro carries the
     ags_h3 global ≈41px, the others carry the lighter `text` global ≈22px) → an em pull was −43px on
     GT but only −23px on the others, leaving their intro column ~20px taller (a height mismatch).
     −1.95rem reproduces GT's exact pull at every width (rem tracks the same root knob) AND is now
     identical on every page. */
  .ags-c-hero__intro { margin-top: -1.95rem; }
  /* SHARED measure + RESERVED HEIGHT so every interior hero is the SAME height at a given viewport
     regardless of copy length (Simon: heroes must match Group Tours). `max-width:24em` lets the
     soft-wrapping intros settle at a GT-like 4-line block; `min-height:4.2em` (= 4 lines × 1.05 lh,
     in em of the intro TEXT size so it scales as one unit) RESERVES a 4-line block even when the
     copy is shorter (e.g. History's 1-liner) → the intro column occupies identical space on every
     page → identical section height, no per-page padding/min-height needed. GT re-overrides
     max-width:none + white-space:nowrap below (its wrap is pinned by hard <br>s; the 4-line reserve
     is a no-op for its already-4-line intro). */
  .ags-c-hero__intro .elementor-widget-container,
  .ags-c-hero__intro p { max-width: 24em; min-height: 4.2em; margin-left: auto; text-align: right; }
  /* Intro font SIZE scales continuously with the viewport (a scoped layout size — sanctioned for
     editorial scaling, see cqw-stage-pattern memo — NOT a type-fight; family/weight/tracking stay
     the `ags_h3` Kit global). A continuous vw scale (no 16px floor) shrinks the text below 1280 so
     the fixed hard-break lines always fit the narrowing column → wrap identical AND nothing soft-
     wraps at 1024. Page-type scope (0,5,0) beats Elementor's per-element global size. */
  /* SHARED intro TYPE — forces the ags_h3 look (Bounded 400, −3% tracking) on EVERY interior hero.
     The interior intros are text-editor widgets carrying the lighter `text` global (Red Hat Display
     300); GT's carries `ags_h3` (Bounded). Pinning family/weight/tracking here makes them identical
     (same established fix as the §4 statement labels / numgrid / overlay). Size scales with vw.
     NO white-space:nowrap here — the interior intros are single <p>s that must soft-wrap. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__intro .elementor-heading-title,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__intro .elementor-widget-container,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__intro p {
    font-family: 'Bounded', sans-serif; font-weight: 400; letter-spacing: -0.03em;
    font-size: calc(29.2 * var(--ags-px)); line-height: 1.05;
  }
  /* GT ONLY: its intro wrap is pinned by hard <br>s → keep nowrap + release the shared measure so
     the reference-exact 4-line wrap is immune to sub-pixel drift (see the WRAP-LOCK note above). */
  body.ags-page-okinawan-group-tours [data-elementor-type="wp-page"] .ags-c-hero__intro .elementor-heading-title,
  body.ags-page-okinawan-group-tours [data-elementor-type="wp-page"] .ags-c-hero__intro .elementor-widget-container,
  body.ags-page-okinawan-group-tours [data-elementor-type="wp-page"] .ags-c-hero__intro p {
    max-width: none; white-space: nowrap;
  }
  /* MULTILINGUAL: the pinned English wrap above (nowrap + hard <br>s) can't fit longer translated
     copy → horizontal overflow. On TRANSLATED pages only, let the intro soft-wrap by width.
     `[class*="ags-lang-"]:not(.ags-lang-en)` matches es/de/nl/… but NEVER English or a no-plugin
     install, so the main page is untouched. Higher specificity + later source order → wins. */
  body[class*="ags-lang-"]:not(.ags-lang-en) [data-elementor-type="wp-page"] .ags-c-hero__intro .elementor-heading-title,
  body[class*="ags-lang-"]:not(.ags-lang-en) [data-elementor-type="wp-page"] .ags-c-hero__intro .elementor-widget-container,
  body[class*="ags-lang-"]:not(.ags-lang-en) [data-elementor-type="wp-page"] .ags-c-hero__intro p {
    white-space: normal; word-wrap: break-word; overflow-wrap: break-word;
  }
  .ags-c-hero__cta { align-self: flex-end; }
  /* BOOK A GROUP TOUR — only the SIZE is owned here, in REM so it scales with the root knob like
     everything else (10px @1280 where root=16px → 0.625rem; grows to ~0.86rem @2560). Red Rose
     Bold 0% comes from the button's `accent` Kit global — do NOT re-declare family/weight/tracking.
     Page-type scope (0,5,0) beats Elementor's per-element global font-size (0,4,0) → else 13px. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__cta .elementor-button {
    font-size: 0.625rem; padding: 0.55em 4.6em;
  }
  /* (Marquee band padding is set ONCE, shared, in the §2 MARQUEE block below — identical on every
     interior page. No per-page rule here.) */

  /* Rounded photos (hero, photo-row, beach) get their corners from each widget's NATIVE Elementor
     `image_border_radius` (8px), set in the page data exactly like the homepage photo widgets — the
     clean, editable way. No CSS radius/clip here (a rounded overflow:hidden CONTAINER clipping a
     same-size raster child revealed the photo's dark edge pixels as a dark corner hairline; the
     native image radius has no such artifact). Feature (§3) + overlay (§8) stay square by design. */

  /* §3 FEATURE PHOTO — full-bleed (base: sibling interior pages keep the full image). */
  .ags-c-photo__img img { width: 100%; height: auto; display: block; border-radius: 0; }
  /* GT ONLY: cinematic letterbox band (reference crops the kumite to ~0.35 of width, NOT the full
     image height). Fixed viewport-relative height + cover crop. Page-scoped (0,2,2) beats the base
     rule above (0,1,1) here, so siblings are unaffected. */
  body.ags-page-okinawan-group-tours .ags-c-photo__img,
  body.ags-page-okinawan-group-tours .ags-c-photo__img figure { margin: 0; }
  /* Height = max(width-scaled crop, viewport-fill remainder). The width term (34vw) keeps the
     cinematic-band proportion as the viewport widens; the 48svh term makes the feature GROW on TALL
     viewports so hero(~40svh) + marquee(~13svh) + feature reaches 100svh and §4 never peeks into the
     first screen. max() → whichever is larger wins: short/wide screens get the crop, tall screens get
     the fill. cover keeps the fighters framed at any height. (Was clamp(...,500px) → capped, so it
     couldn't fill a tall viewport and the next section showed through — the bug.) */
  body.ags-page-okinawan-group-tours .ags-c-photo__img img {
    width: 100%; height: max(calc(489.6 * var(--ags-px)), 48vh); height: max(calc(489.6 * var(--ags-px)), 48svh);
    -o-object-fit: cover;
       object-fit: cover; -o-object-position: center 28%; object-position: center 28%; display: block; border-radius: 0;
  }

  /* §4 STATEMENT — corner labels + big centred headline + link. */
  .ags-c-statement { text-align: center; }
  /* Group Tours: generous, FIXED reference spacing (NOT a viewport-fill — the photos are meant to
     sit below the fold; the section is a long scroll). Space opens (a) between the eyebrow labels
     and the headline, and (b) below the link before the photo row. Values track the viewport width
     a little (vw) so the rhythm holds laptop→ultrawide, clamped so it stays sane. Scoped to GT. */
  body.ags-page-okinawan-group-tours .ags-c-statement__headline { margin-top: calc(86.4 * var(--ags-px)); }
  /* Generous gap below the link before the photos (reference). The section's own padding-bottom is
     the reliable lever — the link is the last flex child of the inner body, so a big pad here opens
     the space cleanly without fighting the flex gap. */
  /* statement padding-bottom (was clamp(90px, 11vw, 200px)) now comes from the shared --ags-sec-pad rhythm. */
  /* round 11 (Simon 2026-09-21, ultrawide faithfulness): the corner labels used to inset by the
     edge guide from the SCREEN edge, so on ultrawide they flung out to ~81px while the rest of the
     content (numgrid, photorow, homepage) caps to the frame guide (~366+). Add the frame letterbox
     well so the labels land on the SAME guide — faithful layout, consistent side padding. well=0
     ≤1920 so the 1440 reference is unchanged. */
  .ags-c-statement__labels { display: flex; justify-content: space-between; align-items: flex-start; padding-left: calc(max(0px, (100vw - var(--ags-frame)) / 2) + 43.2 * var(--ags-px)); padding-right: calc(max(0px, (100vw - var(--ags-frame)) / 2) + 43.2 * var(--ags-px)); }
  /* Eyebrow labels are black BOUNDED to MATCH the headline (were Red Hat Display Light — the wrong
     font). Text-editor widgets carry the body global on the container → scope there to override
     family/weight (these widgets have no per-element global, so 0,2,0 wins). */
  .ags-c-statement__labels .elementor-widget-container { font-family: 'Bounded', sans-serif; font-weight: 400; font-size: calc(18 * var(--ags-px)); color: #111010; line-height: 1.25; max-width: 14em; }
  /* max-width on the CONTAINER (em tracks the 18px set here) so each label wraps to 2 lines like the
     reference. Left label hugs left, right label hugs right. */
  .ags-c-statement__label--l { text-align: left; }
  .ags-c-statement__label--r { text-align: right; }
  .ags-c-statement__label--r .elementor-widget-container { margin-left: auto; }
  /* Headline is a big 4-line display in the reference; it inherits the Kit global (too small) with
     no desktop size rule → scope to the rendered element + page type to beat the per-element global
     (0,4,0). max-width (in em, so it tracks the headline size) is set ON THE HEADING-TITLE: the
     wrapper's max-width loses to Elementor's `.e-con > .elementor-widget{max-width:100%}` (0,4,0),
     and em on the 16px wrapper would resolve wrong — the constraint must sit on the sized element
     to wrap to ~4 lines like the reference, not 3. */
  .ags-c-statement__headline { margin-left: auto; margin-right: auto; line-height: 1.12; }
  /* max-width tuned so the headline breaks like the reference: L1 "We handle everything — so your",
     L2 "group can focus entirely on", L3 "training, learning, and", L4 "experiencing Okinawa".
     em tracks the headline size so the wrap holds as type scales. Sits on the heading-title. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-statement__headline .elementor-heading-title {
    font-size: calc(70 * var(--ags-hpx)); line-height: 1.12; max-width: 18.5em; margin-left: auto; margin-right: auto;
  }
  /* §4 LINK — "SEE WHAT'S INCLUDED": reference is BLACK with a static underline (not the maroon
     swept-underline treatment .ags-textlink gives elsewhere). Override both here; later source
     order beats the shared .ags-textlink rules (same 0,2,0 specificity). */
  .ags-c-statement__link { margin-top: calc(8 * var(--ags-px)); }
  .ags-c-statement__link .elementor-button { color: #111010; -webkit-text-decoration: underline; text-decoration: underline; text-underline-offset: 4px; }
  .ags-c-statement__link .elementor-button:hover { color: #111010; }
  /* Trim the label — the Red Rose global size sits on the inner text span, so set it there. */
  .ags-c-statement__link .elementor-button-text { font-size: calc(11 * var(--ags-px)); }
  /* This link also carries .ags-textlink → kill its swept ::after so only the static underline shows. */
  .ags-c-statement__link .elementor-button::after { display: none; }

  /* §5 PHOTO ROW — equal columns (shared base). */
  .ags-c-photorow__row { display: flex; }
  .ags-c-photorow__col { flex: 1 1 0; min-width: 0; }
  .ags-c-photorow__img img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; }

  /* Group Tours: capped, centred band. At laptop it's near-full-width + large (reference look);
     on ULTRAWIDE the max-width holds so the photos DON'T inflate — margins appear left/right
     instead (Simon: "keep them large but not too big, accept space on the sides"). Small gaps
     between the three, top-aligned so each keeps its own aspect (kids/belt/master heights differ,
     like the reference). Scoped so the other interior pages' shared photo rows are untouched. */
  body.ags-page-okinawan-group-tours .ags-c-photorow__row {
    max-width: calc(1440 * var(--ags-px)); margin-left: auto; margin-right: auto; gap: calc(13 * var(--ags-px));
    align-items: stretch; padding-left: 18px; padding-right: 18px; box-sizing: border-box;
  }
  /* Reference: all three photos are EQUAL-HEIGHT near-square tiles. Source images differ in aspect
     (kids 4:3, belt 1:1, master 3:2) → square each COLUMN and cover-crop, so the left (kids) and
     right (master) match the middle instead of rendering as short landscape strips (the bug).
     object-position keeps the subjects framed. Large + fixed — they live below the fold. */
  body.ags-page-okinawan-group-tours .ags-c-photorow__col { aspect-ratio: 1 / 1; }
  body.ags-page-okinawan-group-tours .ags-c-photorow__img,
  body.ags-page-okinawan-group-tours .ags-c-photorow__img figure { height: 100%; }
  body.ags-page-okinawan-group-tours .ags-c-photorow__img img { height: 100%; -o-object-fit: cover; object-fit: cover; }
  body.ags-page-okinawan-group-tours .ags-c-photorow__col:first-child img { -o-object-position: center 30%; object-position: center 30%; }
  body.ags-page-okinawan-group-tours .ags-c-photorow__col:last-child img  { -o-object-position: center 20%; object-position: center 20%; }

  /* §6 SECTION TITLE — giant heading on maroon. Scope to the rendered element + page type
     to beat Elementor's per-element global font-size (0,4,0); see region-header convention. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-titleband__h .elementor-heading-title {
    font-size: calc(100.8 * var(--ags-hpx)); line-height: 1; margin: 0;
  }

  /* §7 NUMBERED GRID — 4 columns × 2 rows on maroon. The grid is emitted as a flex-row .e-con;
     override to a REAL CSS grid (plain class at 0,1,0 beats Elementor's .e-flex by source order,
     same as the marquee track). Eyebrow sits above the grid (body colw). */
  /* FIXED-width columns (not 1fr) so each item stays a tight ~3-line block that wraps the SAME way
     at every width — 1fr let the columns stretch on ultrawide, spreading the cluster wide and
     collapsing items to 1–2 lines (off-reference). Column width ≈ the reference item measure. */
  /* Columns are width-CAPPED and packed to the RIGHT end of the grid area (justify-content:end), so
     the block clusters bottom-RIGHT on the page. On LAPTOP the cap exceeds the available space, so
     columns shrink to fill (fills to the edge — unchanged, Simon likes it). On ULTRAWIDE the columns
     cap out and pack against the right; the extra room opens as a gap between the kanji and the grid
     (grid stays bottom-right, not spread across the middle). Column cap + fonts scale with vw so the
     grid looks bigger on ultrawide (SCALE AS ONE UNIT), text wraps the same at every width. */
  /* Column MIN is 184px (not 0) so on small desktop widths the columns can't compress below the
     width the hard-wrapped copy needs — otherwise the pinned <br> lines overflow/rewrap (the bug at
     1024–1152). row-gap tightened. */
  .ags-c-numgrid__grid { display: grid; grid-template-columns: repeat(4, minmax(calc(190 * var(--ags-px)), calc(216 * var(--ags-px)))); justify-content: end; grid-gap: calc(23 * var(--ags-px)) calc(49 * var(--ags-px)); gap: calc(23 * var(--ags-px)) calc(49 * var(--ags-px)); box-sizing: border-box; }
  /* Reference: eyebrow is a LEFT GUTTER; the 4×2 grid is indented to its right (first number lands
     ~24%). Flip the eyebrow+grid wrapper to a row on desktop only (mobile keeps the column stack).
     NOTE: the builder Navigator-names this wrapper "Numbered Grid — Body" but never puts a
     `.ags-c-numgrid__body` class on it in the DOM (verified live: it's the sole .e-con child of
     .ags-c-numgrid) — so target it structurally. Page-type scope (0,4,0) beats Elementor's
     flex-direction consumer `.e-con-full.e-flex` (0,2,0) that resolves the per-element column. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid > .e-con {
    flex-direction: row; align-items: flex-start; gap: calc(43.2 * var(--ags-px));
    padding-right: max(16px, min(1.5vw, 28px));
    padding-right: clamp(16px, 1.5vw, 28px); box-sizing: border-box; /* tighter right gutter → grid sits closer to the page's right edge */
  }
  /* Elementor resolves flex-grow/shrink/basis from --flex-* (its .elementor-element consumer is
     0,1,0 and wins the source-order tie vs a plain .ags-c-* class → flex-basis stayed `auto`,
     verified live). Page-type scope (0,4,0) beats it so the gutter basis actually applies. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__eyebrow { flex: 0 1 22%; min-width: 0; } /* left gutter → grid sits RIGHT; shrinkable (0 1) so on narrow desktop the columns keep their min width and the wrap holds, gutter yields instead */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__grid    { flex: 1 1 0; min-width: 0; }
  .ags-c-numgrid__item { display: flex; flex-direction: column; gap: calc(3 * var(--ags-px)); } /* number↔text gap halved (6→3) */
  /* Number = globally-typed heading (gstyle=marker → per-element global 0,4,0); scope to the
     rendered element + page type to win (see region-header convention). NOT body.ags-redesign. */
  .ags-c-numgrid__num { opacity: .8; }
  /* Number scales with viewport (holds ~15px at laptop, grows on ultrawide in lockstep with the label). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__num .elementor-heading-title {
    font-size: calc(17 * var(--ags-px));
  }
  /* Label = globally-typed text-editor (global type sits on the widget) → scope to the widget
     container to win (0,4,0). Reference: BOUNDED (was Red Hat Display Light — the wrong font),
     regular weight with bold lead phrases via the <b> in the editor HTML. max-width in em (tracks
     the 15px here) so each item wraps to ~3 lines like the reference. */
  /* Copy carries hard <br> line breaks (set in the page data) → let it size to content, don't cap
     with an em max-width that would re-wrap the pinned lines. */
  .ags-c-numgrid__label { max-width: none; }
  /* Label scales with viewport width (SCALE AS ONE UNIT) — holds ~15px at laptop, grows on ultrawide
     so the grid looks bigger there instead of leaving wide gaps; hard <br> wraps hold at every size. */
  .ags-c-numgrid__label .elementor-widget-container { font-family: 'Bounded', sans-serif; font-weight: 400; font-size: calc(17 * var(--ags-px)); line-height: 1.35; }
  /* Lead phrases: Medium (500) — a light, restrained emphasis (was 900 Black → then 600; Simon wants
     lighter). A real weight via the variable Bounded face (the old static set only had 200/400/900). */
  .ags-c-numgrid__label b { font-weight: 500; }

  /* §8 OVERLAY SPLIT — cqw editorial stage: full-bleed photo fills; the text clusters are
     absolutely placed in % and typed in min(cqw,px), so the whole composition scales as ONE
     unit 1024→ultrawide (no reflow, no overlap). THE STAGE is the container-query context, so
     cqw resolves against the stage width (≈ viewport, content_width:full). aspect-ratio gives
     the stage a real box for the absolute children. Fonts are SCOPED to the rendered element
     + page type to beat Elementor's per-element global font-size (0,4,0); see region header. */
  .ags-c-overlay__stage {
    position: relative; container-type: inline-size; overflow: hidden;
    aspect-ratio: 1280 / 720;
  }
  /* GT page: the stage is exactly ONE VIEWPORT tall (height:100svh, NOT aspect-ratio). The old
     aspect-ratio:1280/720 forced height = width×0.5625, so on wide screens (width > ~1.78×height)
     the section grew TALLER than the viewport (1440px at 2560×1080) and overflowed the browser —
     that's the "too massive on ultrawide" bug. Pinning height to 100svh makes the section fit the
     browser at every desktop size; the cover-photo (inset:0) and %-positioned text fill the box. */
  body.ags-page-okinawan-group-tours .ags-c-overlay,
  body.ags-page-okinawan-group-tours .ags-c-overlay > .e-con-inner { min-height: 100vh; min-height: 100svh; }
  body.ags-page-okinawan-group-tours .ags-c-overlay .ags-c-overlay__stage {
    aspect-ratio: auto; height: 100vh; height: 100svh; min-height: 0;
  }
  /* Photo fills the stage (absolute inset:0). It's a placeholder BOX until a real asset lands
     (photo() → .ags-c-ph): drop its 4/3 aspect so inset:0 sizing wins; grayscale is a no-op on
     the box and only bites the real <img> later. */
  .ags-c-overlay__photo { position: absolute; top: 0; right: 0; bottom: 0; left: 0; }
  .ags-c-overlay__photo.ags-c-ph { aspect-ratio: auto; width: 100%; height: 100%; }
  /* Real Image widget nests the img in .elementor-widget-container > figure.elementor-image;
     neither has a height, so img{height:100%} alone wouldn't fill the stage (masked now by the
     placeholder box). Give the wrappers full height so a real gt-overlay.jpg fills vertically —
     same fix pattern as elsewhere in this file (figure{height:100%}). No-op on the .ags-c-ph box. */
  .ags-c-overlay__photo .elementor-widget-container,
  .ags-c-overlay__photo figure { height: 100%; width: 100%; margin: 0; }
  .ags-c-overlay__photo img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; filter: grayscale(1); }
  /* Every non-photo child is absolutely placed on the stage. */
  .ags-c-overlay__stage > :not(.ags-c-overlay__photo) { position: absolute; }
  /* Thin decorative center connector line (purely CSS — no DOM, no Navigator entry). WHITE per the
     reference — was `var(--e-global-color-secondary)` which resolves to the near-black text colour on
     this page (not cream); set an explicit cream/white so the line reads over the dark photo. */
  .ags-c-overlay__stage::before {
    content: ""; position: absolute; left: 50%; top: 16%; height: 66%; width: 1px;
    background: #f7f4f2; opacity: .7; z-index: 1;
  }
  /* Two overlaid headings at opposite corners (globally-typed → scope to win 0,4,0).
     6.5cqw, WAS 9cqw (Mika 2026-09-09: "Who is this for / Why a ___ is too large — a bit smaller so
     they don't look too demanding"). At the 1440 reference that is 128px -> 92px. 6.5 is not a new
     guess: Karate Experiences already carried it as a per-page override because its longer headings
     ("Book as a Group Tour" / "Book as an individual") overflowed at 9cqw — that page has read at 92px
     all along, so this makes the four pages consistent AND removes the override.
     It also fixes a real collision it was causing on GT/VIP: at 9cqw the bottom-right heading grew up
     into the top-right list and clipped "…we handle the rest."
     CAP is now reference-frame scaled rather than a flat 200px. The stage is full-bleed (100vw), so
     cqw over-grows on ultrawide — 6.5cqw would reach 224px at 3440. The cap pins it to the 1440
     value x --ags-scale (92.3 x 1.88 = 173px at 3440x1440), i.e. a true enlargement. */
  .ags-c-overlay__h { margin: 0; z-index: 2; }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__h .elementor-heading-title {
    font-size: min(6.5cqw, calc(92.3 * var(--ags-hpx))); line-height: .98; margin: 0;
  }
  /* Mika 2026-09-09: this section gets the shared pad as well. The four corner clusters were
     anchored in % of the stage (3% / 4% / 5% = 23 / 31 / 38px at the 1440 reference), i.e. tighter
     than every other section and inconsistent with each other. All four now sit on --ags-sec-pad.
     HORIZONTAL stays at 3% — Simon confirmed the side margins read correctly. */
  /* round 11 (Simon 2026-09-21, ultrawide faithfulness): the corner headings/lists inset from the
     SCREEN edge (3%), so on ultrawide they drift out to ~103px while the rest of the content caps to
     the frame guide. Add the frame letterbox well + a 3%-of-FRAME inset so they land on the same
     guide (identical ≤1920 where well=0, so the poster composition is unchanged at the reference). */
  .ags-c-overlay__h--tl { top: var(--ags-sec-pad);  left: calc(max(0px, (100vw - var(--ags-frame)) / 2) + 0.03 * var(--ags-frame)); }
  .ags-c-overlay__h--br { bottom: var(--ags-sec-pad); right: calc(max(0px, (100vw - var(--ags-frame)) / 2) + 0.03 * var(--ags-frame)); }
  .ags-c-overlay__h--br .elementor-heading-title { text-align: right; }
  /* Constrain each heading to a corner block so it WRAPS like the reference
     ("Who is / this for" 2 lines TL; "Why a / group / tour" 3 lines BR) instead of one long
     line. Width in cqw keeps the wrap proportional as the stage scales. Scoped to (0,4,0) so it
     beats Elementor's `.elementor.elementor .e-con > .elementor-widget{max-width:100%}` (ties,
     wins on source order — same mechanism as the font-size overrides). */
  /* 40cqw is 568px at the 1440 reference, but this stage is FULL-BLEED (100vw) so cqw over-grows —
     1376px at 3440, which loosened the wrap. The scaled cap pins the box to a true enlargement. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__h--tl { max-width: min(40cqw, calc(568 * var(--ags-px))); }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__h--br { max-width: min(40cqw, calc(568 * var(--ags-px))); }
  /* Thin caption lines flanking the center divider (small, text-editor globals → scope to widget). */
  .ags-c-overlay__cap { z-index: 2; opacity: .9; }
  .ags-c-overlay__cap .elementor-widget-container { font-family: 'Bounded', sans-serif; font-weight: 400; font-size: min(1.3cqw, calc(15 * var(--ags-px))); line-height: 1.3; }
  .ags-c-overlay__cap--l { top: 50%; right: 54%; }
  .ags-c-overlay__cap--l .elementor-widget-container { text-align: right; }
  .ags-c-overlay__cap--r { top: 28%; left: 53%; }
  /* Two bullet lists (globally-typed text-editor → scope to widget). */
  .ags-c-overlay__list { z-index: 2; }
  .ags-c-overlay__list .elementor-widget-container { font-family: 'Bounded', sans-serif; font-weight: 400; font-size: min(1.7cqw, calc(21 * var(--ags-px))); line-height: 1.25; }
  .ags-c-overlay__list ul { margin: 0; padding-left: 1.1em; }
  .ags-c-overlay__list li { margin: 0 0 .35em; }
  .ags-c-overlay__list--l { bottom: var(--ags-sec-pad); left: calc(max(0px, (100vw - var(--ags-frame)) / 2) + 0.03 * var(--ags-frame)); }
  .ags-c-overlay__list--r { top: var(--ags-sec-pad); right: calc(max(0px, (100vw - var(--ags-frame)) / 2) + 0.03 * var(--ags-frame)); }
  .ags-c-overlay__list--r .elementor-widget-container { text-align: right; }
  .ags-c-overlay__list--r ul { list-style-position: inside; padding-left: 0; }
  .ags-c-overlay__list--r p { margin: .6em 0 0; }
  /* VIP-ONLY bullet shrink. VIP's right list has SIX bullets vs the four on GT/KE (one on History),
     so at the shared 21px it ran to 546 and overlapped the bottom-right "Why a VIP Retreat" heading
     by 42px (measured; the others clear by +46). Both lists are absolutely positioned and can't push
     each other, so the fix is to shrink the content — VIP-scoped so the pages that already clear are
     untouched. Same min(cqw, px·--ags-px) form as the shared rule (1.4/17 = 1.7/21 × 0.81) so it
     still scales with the frame. Page hook per the conventions "per-page allowance, don't compromise
     the pages that are fine". */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-overlay__list .elementor-widget-container {
    font-size: min(1.4cqw, calc(17 * var(--ags-px)));
  }
  /* max-width on caption/list clusters is (0,1,0) and LOSES to Elementor's
     `.elementor.elementor .e-con > .elementor-widget{max-width:100%}` (0,4,0) — same trap the
     heading max-width hit. Scope to (0,4,0) so it wins on source order. */
  /* WRAP IS WIDTH-DRIVEN. Every hard <br> was stripped from the §8 content (fix_overlay_br_all.php),
     so the mockup line breaks come from these caps alone. Width is min(cqw,px) MATCHING each cluster's
     font min() (list min(1.7cqw,21px) → width min(25.5cqw,315px); cap min(1.3cqw,15px) → min(9.7cqw,
     112px)) so the column scales WITH the font below the cap and holds at the cap above → the wrap
     stays LOCKED 1024→ultrawide. (em failed here — it resolved against the widget's inherited 16px,
     which neither scaled nor matched the 15/21px text.) Bullet lists carry the SAME copy every page →
     shared widths; captions differ per page → tuned per page (KE below; history sets its own).
     Measured vs figma/references/interior-pages/*Desktop.png. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__list--l { max-width: min(30cqw, calc(370 * var(--ags-px))); }   /* text area ~347px (370 − 23px bullet indent): fits "seeking authentic training" (332px) yet < "International groups looking" (360px) → ref-exact 2/2/2/3-line wrap */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__list--r { max-width: min(25.5cqw, calc(315 * var(--ags-px))); } /* right col a touch tighter, matching the ref top-right wrap */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__cap--l  { max-width: min(9.7cqw, calc(112 * var(--ags-px))); }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__cap--r  { max-width: min(9.7cqw, calc(112 * var(--ags-px))); }
  /* KE cap--r ("For individuals or small groups") a touch wider so "individuals or" holds on one line
     → "For / individuals or / small groups" (3 lines like the mockup, not 4). */
  body.ags-page-karate-experiences [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__cap--r { max-width: min(10.4cqw, calc(122 * var(--ags-px))); }

  /* §9 ACCORDION — big red headings + divider lines; EXCLUSIVE expand via a CSS grid height
     reveal (frame-accurate, no JS height measuring). Row: the accordion list (flexible) beside a
     small beach photo (fixed ~34%). */
  .ags-c-accordion__inner { display: flex; align-items: flex-start; }
  .ags-c-accordion__list { flex: 1 1 0; min-width: 0; }
  .ags-c-accordion__aside { flex: 0 0 38%; }
  /* GT §9 layout (reference #16): accordion list pinned TOP-LEFT, photo pinned BOTTOM-RIGHT and
     DECOUPLED from the list so opening an item can't move it. The section is a fixed 100svh box
     (below), so we take the aside OUT OF FLOW (absolute, bottom-right of the section) — the list's
     growing/shrinking height no longer shifts the photo. `--ags-acc-photo-w` scales the photo up a
     touch on ultrawide. Portrait 400:437 (the source SVG frame), cover-crop, rounded. */
  /* Photo width scales up on ultrawide but its HEIGHT is capped to a share of the viewport so it
     always fits (never overflows the fold). Portrait 400:437 → height ≈ width×1.09. */
  /* Width scales with viewport up to a cap matching the ~1800 content-well (so the photo scales up
     more on ultrawide, matching the bigger heading — not shrunken). Floor 360, cap 600. HEIGHT
     guard (78svh/1.0925) only limits genuinely short viewports. */
  .ags-c-accordion__inner { position: static; align-items: flex-start; }
  .ags-c-accordion__list { flex: 1 1 0; }
  /* Pinned to the SECTION (fixed-height box), not the inner (which grows when a panel opens) → the
     photo can't move on open. right = edge margin + the SAME content-well the section uses, so the
     photo sits at the right edge of the centred column (absolute `right` resolves from the section
     border edge, so the section's own padding doesn't shift it — mirror the well here). */
  .ags-c-accordion__aside {
    position: absolute;
    right: calc(max(12px, min(1.2vw, 28px)) + var(--ags-acc-well));
    right: calc(clamp(12px, 1.2vw, 28px) + var(--ags-acc-well));
    /* Mika's pad, not its own clamp: this photo is absolutely pinned, so IT — not the section's
       padding-block — sets the accordion's visible bottom gap (measured ink 29 against a 82 target). */
    bottom: var(--ags-sec-pad);
    /* Elementor's stray 10px again — and THIS is why the accordion's asymmetry grew with the
       viewport. The aside is pinned at the pad correctly, but its own FLAT (unscaled) 10px padding
       pushed the photo's painted edge to pad+10 at every size, while both extra terms on the top
       edge scale. Clear it and the bottom edge simply IS the pad.
       BOTTOM ONLY: the aside is border-box and positioned by `right`, so clearing all four sides
       also widened the photo 20px and slid it 10px toward the section edge — a horizontal change
       nobody asked for. Only the axis that was actually wrong. */
    padding-bottom: 0;
    flex: 0 0 auto; width: var(--ags-acc-photo-w); margin: 0;
  }
  .ags-c-accordion__photo { width: 100%; }
  /* Portrait 400:437 photo box (GT reference), cover-cropped + rounded. VIP/History override only
     object-position (their beach / belt photos frame differently). */
  .ags-c-accordion__aside .ags-c-accordion__photo img {
    width: 100%; aspect-ratio: 400 / 437; height: auto; -o-object-fit: cover; object-fit: cover; display: block; border-radius: 12px;
  }
  /* Divider lines: reference #16 puts ONE line BELOW each heading block, all the SAME fixed length
     (≈ the widest heading "Sample experience"), and NO line above the first heading. So: drop the
     per-item borders (which drew a full-width line above every item incl. the first) and instead
     draw a fixed-width bottom rule on each item via ::after. */
  .ags-c-accordion__item { border: 0; }
  /* THICKER divider rule, with EVEN space above and below it (margin-block) so each heading has
     matching breathing room to its line and the next heading. */
  .ags-c-accordion__item { position: relative; }
  .ags-c-accordion__item::after {
    content: ""; display: block; width: var(--ags-acc-rule-w, 9em); max-width: 100%;
    height: 3px; background: rgba(142,30,0,.6); margin-top: calc(18.4 * var(--ags-px)); margin-bottom: calc(18.4 * var(--ags-px));
  }
  /* Header = the <h3> heading (stays a heading for the a11y outline). JS wraps its text in a real
     <button> that carries the toggle. Big red heading; globally-typed (gstyle=secondary →
     per-element global 0,4,0) so scope to the rendered element + page type to win (see
     region-header convention). NOT body.ags-redesign. */
  .ags-c-accordion__header { padding: var(--ags-acc-hdr-pad) 0; }
  /* Reference #16: big headings that wrap to TWO lines ("Sample / experience", "Optional / add-ons";
     "Etiquette" stays one). Larger size + max-width (em, tracks size) forces the wrap. Weight 400
     (the reference weight — NOT bolder). */
  /* Size keys off BOTH width AND height: min(6vw, 9.5svh) so on SHORT viewports the svh term wins
     and shrinks the headings enough that all three 2-line blocks + dividers fit one screen (was
     width-only → "Etiquette" clipped at short heights like 1280×640 / 1600×720). Floor 34px. */
  /* min(6vw, 9.5svh) so short viewports shrink to fit; cap 118px ≈ the size at the ~2040 content-well
     width, so on ultrawide the heading scales up to match the wider column. Floor 34. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-accordion__header .elementor-heading-title {
    font-size: var(--ags-acc-h-fs); font-weight: 400; line-height: 1.0; max-width: 7em;
  }
  /* Divider rule length (--ags-acc-rule-w, set on .ags-c-accordion above) = the widest heading
     block ("Sample experience"); tracks the same width/height curve as the heading. */
  /* Button reset — the toggle spans the full header width (kills the padding dead-zone) and looks
     identical to the bare heading (no default button chrome). font:inherit keeps the scoped
     heading-title font-size above; color:inherit keeps the RED global. */
  .ags-c-accordion__btn {
    display: block; width: 100%; text-align: left;
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; color: inherit; cursor: pointer;
    /* `font:inherit` does NOT carry letter-spacing/line-height — inherit them explicitly so the
       button text matches the heading exactly (the negative tracking keeps "Sample experience" on
       one line; without it, it wraps). */
    letter-spacing: inherit; line-height: inherit;
  }
  /* Panel: grid 0fr→1fr height reveal. Inner clips so the collapsed row is truly 0 height. */
  .ags-c-accordion__panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .35s cubic-bezier(.22,1,.36,1); }
  .ags-c-accordion__panel > * { overflow: hidden; min-height: 0; }
  .ags-c-accordion__item.is-open .ags-c-accordion__panel { grid-template-rows: 1fr; padding-bottom: calc(16 * var(--ags-px)); }
  /* Panel body copy: Bounded (was Red Hat Display) + scales with vw so it stays readable on ultrawide.
     SHARED across all interior accordions. */
  .ags-c-accordion__panel .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400; font-size: calc(16 * var(--ags-px)); line-height: 1.4; max-width: 34em;
  }

  /* §10 CTA BAND — centred heading + pill. ENLARGED site-wide (2026-08-16): taller band, bigger
     heading, bigger pill + more generous padding — consistent on EVERY redesign page incl. GT. */
  /* Band geometry = the homepage's, via the shared tokens (was a scaled 158.4 with no min-height,
     giving a 450px band against the homepage's 368). justify-content is stated because the homepage
     gets its vertical centring from the .ags-redesign section-rhythm rule, which interior pages
     never match. */
  .ags-c-cta {
    padding-top: var(--ags-cta-band-pad);
    padding-bottom: var(--ags-cta-band-pad);
    min-height: var(--ags-cta-band-minh);
    justify-content: center;
  }
  .ags-c-cta__inner { align-items: center; text-align: center; row-gap: var(--ags-cta-igap); }   /* same heading→pill distance as the homepage */
  /* Heading: THE SAME as the homepage's (Simon 2026-09-09), via the shared tokens. Was
     calc(51.8 * --ags-px)/1.12 — 53% larger than the homepage's 33.75px above an identical pill. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-cta__h .elementor-heading-title {
    font-size: var(--ags-cta-h-fs); line-height: var(--ags-cta-h-lh);
  }
  /* …and the same heading→pill gap. The interior had no rule here at all, so the pill sat 22px under
     the heading against the homepage's 46. The homepage hangs this off .ags-btn--invert; the interior
     CTA button carries no variant class, so target its widget wrapper. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-cta .elementor-widget-button {
    margin-top: var(--ags-cta-gap);
  }
  /* Pill: THE SAME BUTTON AS THE HOMEPAGE (Simon 2026-09-09) — same tokens, so the two are
     identical by construction rather than by two people typing the same numbers. Still scoped (0,4,0)
     to beat the button's `accent` Red Rose Kit global, but the size it now asserts IS that global's
     0.8125rem; the old `calc(15.1 * --ags-px)` + `1.05em 3.8em` is what made the interior pill
     shorter and fatter (233x53 vs 300x32). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-cta .elementor-button {
    font-size: var(--ags-cta-fs);
    padding: var(--ags-cta-pad);
    min-width: var(--ags-cta-minw);
  }

  /* §11 EXPERIENCE LIST — animated vertical carousel, shares ageshio-exp.js + the .ags-exp* CSS
     with homepage §7. The carousel MECHANICS (mask window --slot/--slots, track, item colour+scale,
     edge-photo row, cross-fade, item font-size) all come from the UNSCOPED .ags-exp* rules above,
     which already apply on interior pages — so nothing about the rotation is duplicated here. Only
     the interior COMPOSITION is added: the centred eyebrow, the left-aligned heading, spacing, and
     pinning the lede to the right (mirrors homepage §7's bottom band). */
  .ags-c-explist__eyebrow,
  .ags-c-explist__eyebrow > .e-con-inner { align-items: center; text-align: center; }
  .ags-c-explist__h { margin-top: 8px; }
  /* Heading is a globally-typed <h2> (gstyle=secondary → per-element global 0,4,0); scope to the
     rendered element + page type to win (see region-header convention).
     Size parity (2026-09-22, Task 5): homepage §7's H2 carries NO font-size override of its own —
     its components.css rule (line-height:0.85) loses the specificity fight against Elementor's own
     per-widget CSS, so what actually renders is the UNTOUCHED Kit "secondary" global: 3.75rem /
     line-height 1 (create-redesign-kit.php). Matching that exactly (rem, not a --ags-hpx calc) tracks
     the SAME undamped html-root scale the homepage's real render uses, so this stays byte-identical
     to homepage 1024→ultrawide instead of drifting the way the old calc(63.4 * --ags-hpx) did. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist__h .elementor-heading-title {
    font-size: 3.75rem; line-height: 1; margin: 0;
  }
  /* Lede sits below the carousel row, right-aligned (mirrors homepage §7's bottom band). */
  .ags-c-explist__row { width: 100%; }
  .ags-c-explist .ags-exp__lede { align-items: flex-end; margin-left: auto; }

  /* §CONTACT HERO — maroon band; left-aligned eyebrow · H1 · intro (see .ags-c-contact below
     for the H1 size, which is width-agnostic). Intro caps its measure so it reads as a lede. */
  .ags-c-contact__herocol { align-items: flex-start; }
  .ags-c-contact__intro { max-width: 34em; }
}

/* §9 ACCORDION reduced-motion guard — panels open/close INSTANTLY (no height animation).
   OUTSIDE the min-width gate so it applies at every width. */
@media (prefers-reduced-motion: reduce) {
  .ags-c-accordion__panel { transition: none; }
}

/* §2 MARQUEE — continuous horizontal loop. OUTSIDE the min-width gate: it loops at all widths.
   The track = the unit repeated 3× (see c_marquee repeats=3); translateX(calc(-100%/3)) moves it
   by EXACTLY one copy, so it wraps seamlessly (no gap/jump). The repeat period is SELF-CONTAINED:
   the track has gap:0 + padding:0 (so Elementor's default gap/pad can't offset it, and zeroing the
   track pad in the editor can't break the loop), and inter-unit spacing lives on the unit's
   padding-inline-end. 3 copies (not 2) so no empty sliver appears on ultrawide (~3440px) before
   wrap. Speed = the single CSS var --marquee-dur. NOTE: keyframe end MUST match the copy count. */
.ags-c-marquee { --marquee-dur: 22s; overflow: hidden; }
.ags-c-marquee__track {
  display: flex; flex-wrap: nowrap; width: -webkit-max-content; width: -moz-max-content; width: max-content; gap: 0; padding: 0;
  animation: ags-c-marquee-scroll var(--marquee-dur) linear infinite;
}
.ags-c-marquee__unit { display: flex; align-items: center; flex: 0 0 auto; padding-right: 40px; }
/* Reference band proportion: crest is PROMINENT (nearly the band height) and sits close to the
   text; the phrase reads smaller (more of "…an Group Tours [crest] Okina…" fits across 1280).
   The builder set the unit's flex_gap to 40px inline → override on the plain class (marquee is a
   shared component with ONE intended size, consistent with the carousel !important usage). */
.ags-c-marquee__unit { gap: max(16px, min(2vw, 28px)) !important; gap: clamp(16px, 2vw, 28px) !important; }
.ags-c-marquee__text { white-space: nowrap; margin: 0; }
/* The text is a globally-typed heading (gstyle=primary) → its size renders on
   .elementor-heading-title and would LOSE to Elementor's per-element global (0,4,0). Scope to
   the rendered element + page type to win (see region-header convention). NOT body.ags-redesign. */
.elementor-page [data-elementor-type="wp-page"] .ags-c-marquee__text .elementor-heading-title {
  font-size: max(46px, min(7.7vw, 116px));
  font-size: clamp(46px, 7.7vw, 116px); line-height: 1; white-space: nowrap; margin: 0;
}
/* Marquee text + unit gap, reference-frame scaled. This must live in its OWN desktop gate placed
   AFTER the base rules above: those are base-scoped because the MOBILE layout shares them, and a
   media query adds no specificity — the interior desktop block sits earlier in the file, so a copy
   there would lose on source order. 110.9 / 28 are the base clamps' values at the 1440 reference. */
@media (min-width: 1024px) {
  .elementor-page [data-elementor-type="wp-page"] .ags-c-marquee__text .elementor-heading-title {
    font-size: calc(110.9 * var(--ags-hpx));
  }
  .ags-c-marquee__unit { gap: calc(28 * var(--ags-px)) !important; }
}

/* SHARED marquee treatment — LITERALLY IDENTICAL on every interior page; only the marquee TEXT
   (the page name) differs. All four pages already use the same emblem, crest-marquee.webp (the
   medallion cropped to its tight bbox; wired by scripts/wire_marquee_crest.php), so ONE set of
   rules fits all: crest ≈1.35× the text cap-height, bottom-aligned with the words, even horizontal
   spacing, ~25px band-bottom before the §3 photo. (Was per-page — different crest sizes/padding
   made the red section vary ~10px between pages.) */
.ags-c-marquee__crest img { height: max(54px, min(8vw, 128px)); height: clamp(54px, 8vw, 128px); width: auto; display: block; }
/* Match the crest→next-copy end padding to the text↔crest gap (builder set 40px inline) so the
   crest sits evenly between the two words. */
.ags-c-marquee__unit { padding-right: max(16px, min(2vw, 28px)); padding-right: max(16px, min(2vw, 28px)); padding-right: clamp(16px, 2vw, 28px); }
/* BOTTOM-ALIGN the unit (crest + words share the band-bottom baseline) so the crest reads large
   while the words sit low near the red's bottom edge. The UNIT is a static flex child (not the
   animated .ags-c-marquee__track), so this cross-axis change doesn't affect the translateX scroll. */
.ags-c-marquee__unit { align-items: flex-end; }
/* Band: no top pad, ~25px beneath before the §3 feature photo. `.elementor-page` scope (0,2,0)
   beats the builder band()'s default 10px padding on every interior page (a bare class ties it). */
.elementor-page .ags-c-marquee { padding-top: 0; padding-bottom: 25px; box-sizing: border-box; }
@keyframes ags-c-marquee-scroll { from { transform: translateX(0); } to { transform: translateX(calc(-100% / 3)); } }
@media (prefers-reduced-motion: reduce) {
  .ags-c-marquee__track { animation: none; }
}

/* =============================================================================
   §CONTACT — styled /contact page hosting the "Ageshio Enquiry" CF7 form.
   Width-agnostic (OUTSIDE the min/max gates): the form is a single centred column that
   reads the same at every width; only a couple of hero sizes + small mobile paddings live
   in the gated blocks below. The form markup is CF7's own (.wpcf7-form / .wpcf7-submit /
   .wpcf7-* messages) wrapped in our .ags-c-form column; the field <p> rows carry the
   .ags-c-form__row class set in the CF7 template (build_contact_form.php).
   ============================================================================= */
/* Hero H1 — globally-typed <h2>-styled heading (gstyle=secondary → per-element global 0,4,0);
   scope to the rendered element + page type to win (see region-header convention). Width-agnostic
   size clamp so it reads as a page title at every width. */
.elementor-page [data-elementor-type="wp-page"] .ags-c-contact__h .elementor-heading-title {
  font-size: max(34px, min(5vw, 68px));
  font-size: clamp(34px, 5vw, 68px); line-height: 1.02; margin: 0;
}

/* The form column: a comfortable centred measure so the fields don't sprawl on wide screens. */
.ags-c-form { max-width: 720px; margin-left: auto; margin-right: auto; width: 100%; }

/* CF7 renders a per-field <p class="ags-c-form__row"> with a <label> wrapping the field. Stack
   the label above its control and give the row breathing room. */
.ags-c-form .ags-c-form__row { margin: 0 0 16px; }
.ags-c-form .ags-c-form__row > label { display: block; }
.ags-c-form label {
  font-family: var(--e-global-typography-text-font-family, inherit);
  font-size: 15px; line-height: 1.4; color: var(--e-global-color-text, var(--ags-black));
}
/* The field label text sits on its own line (the CF7 template puts a <br> after it); add a small
   gap between that label text and the control below. */
.ags-c-form label br { content: ""; display: block; margin-bottom: 6px; }

/* Inputs / select / textarea — full-width, readable padding, subtle border, the site's body type,
   on a clean white field over the cream band. */
.ags-c-form input[type="text"],
.ags-c-form input[type="email"],
.ags-c-form input[type="number"],
.ags-c-form input[type="tel"],
.ags-c-form input[type="date"],
.ags-c-form select,
.ags-c-form textarea {
  display: block; width: 100%; box-sizing: border-box;
  margin-top: 6px; padding: 12px 14px;
  font-family: var(--e-global-typography-text-font-family, inherit);
  font-size: 16px; line-height: 1.4; color: var(--e-global-color-text, var(--ags-black));
  background: #ffffff;
  border: 1px solid rgba(17,16,16,.22); border-radius: 6px;
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  transition: border-color .18s ease, box-shadow .18s ease;
}
/* Message box: CF7's [textarea] defaults to rows=10 (~240px) which blew the form past one viewport.
   Drive the height from CSS instead — viewport-relative so the whole form fits one screen, still
   user-resizable. (Simon 2026-09-23.) */
.ags-c-form textarea { min-height: 0; height: max(90px, min(13vh, 150px)); height: clamp(90px, 13vh, 150px); resize: vertical; }
/* Native select needs its dropdown caret back (appearance:none stripped it) + room for it. */
.ags-c-form select {
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%238E1E00' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center;
}
.ags-c-form input:focus,
.ags-c-form select:focus,
.ags-c-form textarea:focus {
  outline: none;
  border-color: var(--e-global-color-primary, var(--ags-red));
  box-shadow: 0 0 0 3px rgba(142,30,0,.15);
}
.ags-c-form ::-moz-placeholder { color: rgba(17,16,16,.45); }
.ags-c-form ::placeholder { color: rgba(17,16,16,.45); }

/* Submit — CF7 outputs an <input type="submit" class="wpcf7-form-control wpcf7-submit">. Style it
   as the site's red pill (mirrors .ags-btn--solid: red bg, cream text, pill radius, accent type). */
.ags-c-form .wpcf7-submit,
.ags-c-form input[type="submit"] {
  display: inline-block; width: auto; cursor: pointer;
  margin-top: 4px; padding: 14px 40px;
  font-family: var(--e-global-typography-accent-font-family, inherit);
  font-size: 15px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--e-global-color-accent, var(--ags-cream));
  background: var(--e-global-color-primary, var(--ags-red));
  border: 0; border-radius: 999px;
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  transition: opacity .18s ease, transform .18s ease;
}
.ags-c-form .wpcf7-submit:hover,
.ags-c-form input[type="submit"]:hover { opacity: .88; }
.ags-c-form .wpcf7-submit:active,
.ags-c-form input[type="submit"]:active { transform: translateY(1px); }
/* CF7's AJAX spinner sits right after the submit button. */
/* CF7's AJAX spinner renders inline right AFTER the submit input (24px + margin), so text-align:center
   was centring the button+spinner as a pair — the button read left-of-centre. Hide it (cosmetic only;
   the AJAX submit works without it) so the button alone is centred. */
.ags-c-form .wpcf7-spinner { display: none; }

/* CF7 response + validation messages — legible on the cream band. */
.ags-c-form .wpcf7-response-output {
  margin: 18px 0 0; padding: 12px 16px; border-radius: 6px;
  font-family: var(--e-global-typography-text-font-family, inherit);
  font-size: 15px; line-height: 1.4; border: 1px solid transparent;
}
.ags-c-form .wpcf7-not-valid-tip {
  display: block; margin-top: 6px;
  color: var(--e-global-color-primary, var(--ags-red)); font-size: 13px;
}
.ags-c-form .wpcf7-form.invalid .wpcf7-response-output,
.ags-c-form .wpcf7-form.unaccepted .wpcf7-response-output,
.ags-c-form .wpcf7-form.payment-required .wpcf7-response-output {
  color: var(--e-global-color-primary, var(--ags-red));
  border-color: rgba(142,30,0,.4); background: rgba(142,30,0,.06);
}
.ags-c-form .wpcf7-form.sent .wpcf7-response-output {
  color: #1c5b2e; border-color: rgba(28,91,46,.4); background: rgba(28,91,46,.08);
}
.ags-c-form input[aria-invalid="true"],
.ags-c-form select[aria-invalid="true"],
.ags-c-form textarea[aria-invalid="true"] { border-color: var(--e-global-color-primary, var(--ags-red)); }

/* --- Sectioned layout (build_contact_form.php emits eyebrows + 2-col grids + a radio row) --------- */
/* Section eyebrow — small red caps with a hairline rule above, grouping the form into
   "Your enquiry" / "Trip details" / "Karate background" / "Your message". */
.ags-c-form__eyebrow {
  margin: 24px 0 12px; padding-top: 14px;
  border-top: 1px solid rgba(17,16,16,.12);
  font-family: var(--e-global-typography-accent-font-family, inherit);
  font-size: 12px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--e-global-color-primary, var(--ags-red));
}
.ags-c-form__eyebrow:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
/* 2-col grid for paired short fields; auto-collapses to 1 col below ~500px (mobile) — self-responsive
   so it needs no media query (the form block is width-agnostic). Row bottom-margin drives the gap. */
.ags-c-form__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-column-gap: 24px; -moz-column-gap: 24px; column-gap: 24px; }
.ags-c-form__submit { margin-top: 18px; }
/* Red required marker (a <span class="ags-c-form__req">*</span> placed after required labels in the
   CF7 template). CF7 still does the actual required/email validation on submit. */
.ags-c-form__req { color: var(--e-global-color-primary, #8E1E00); font-weight: 700; margin-left: 3px; }

/* ============================================================================
   CONTACT — cream form card on the maroon brand canvas (Direction C, Simon 2026-08-18; re-laid-out
   2026-09-23). The whole page is maroon (hero §1 is already primary; the form §2 formband is flipped
   from cream→maroon here). BASE / MOBILE: the hero (kanji 接触 + "Contact" + intro) is CENTRED on the
   page's centre line with the form card stacked below. DESKTOP (≥1024): the hero and form sit SIDE BY
   SIDE in a two-column grid (see the "CONTACT — DESKTOP TWO-COLUMN" block below the map rules). The
   old "Plan your trip" H1 is dropped (hidden). Portable (slug-scoped).
   ============================================================================ */
/* The whole page canvas is maroon. Also set it on the BODY so that when the desktop grid constrains
   its content to --ags-frame + centres it, the ultrawide letterbox margins read as the same maroon
   (seamless full-bleed) rather than exposing a white gutter. */
body.ags-page-contact { background-color: var(--e-global-color-primary, #8E1E00); overflow-x: hidden; }
/* Form section: cream (accent global) → maroon, so the canvas is continuous from the hero. (0,2,0)
   beats Elementor's per-element global-bg rule.) */
body.ags-page-contact .ags-c-contact__formband {
  /* !important: Elementor's global-color (accent/cream) bg rule ties on specificity and loads later,
     so without it the section stays cream and no red wraps the card. */
  background-color: var(--e-global-color-primary, #8E1E00) !important;
  /* Small TOP padding so the card sits close under the hero; keep generous BOTTOM padding for the
     maroon beneath the card (Simon 2026-08-18: the intro→card gap was ~130px, too much). */
  padding-top: max(12px, min(1.6vw, 22px));
  padding-top: clamp(12px, 1.6vw, 22px);
  padding-bottom: max(48px, min(6vw, 96px));
  padding-bottom: clamp(48px, 6vw, 96px);
}
/* Trim the hero's bottom padding too (it added ~53px to the gap). */
body.ags-page-contact .ags-c-contact { padding-bottom: max(14px, min(1.6vw, 24px)); padding-bottom: clamp(14px, 1.6vw, 24px); }
/* Hero centred — kanji, the "Contact" label beneath it, the H1 and the intro all centre-aligned. */
body.ags-page-contact .ags-c-contact__herocol { align-items: center; text-align: center; }
/* !important beats Elementor's per-widget left-align on the eyebrow headings (お問い合わせ / Contact);
   the H1 has no explicit align so it already inherits centre. */
body.ags-page-contact .ags-c-contact__herocol .elementor-widget-container,
body.ags-page-contact .ags-c-contact__herocol .elementor-heading-title { text-align: center !important; }
/* The eyebrow group is a NESTED flex container (kanji お問い合わせ over "Contact"); center its items too,
   overriding Elementor's default flex-start via BOTH the --flex-* consumer and align-items. */
body.ags-page-contact .ags-c-contact__herocol .e-con { --flex-align-items: center; align-items: center; }
body.ags-page-contact .ags-c-contact__intro { max-width: 34em; margin-left: auto; margin-right: auto; }
/* Intro reads in the rounded ACCENT face (Red Rose), bold — matches the reference (Simon 2026-09-23);
   was the thin Red Hat body face. Target the widget + its <p>. Applies at every width. */
body.ags-page-contact .ags-c-contact__intro,
body.ags-page-contact .ags-c-contact__intro p {
  font-family: var(--e-global-typography-accent-font-family, "Red Rose"), sans-serif;
  font-weight: 700;
  line-height: 1.4;
}
/* The form is a centred cream card sitting on the maroon. Its fields keep their existing white-field
   styling (reads correctly on cream); section eyebrows stay red. */
body.ags-page-contact .ags-c-form {
  max-width: 760px; margin-left: auto; margin-right: auto;
  background: var(--e-global-color-accent, #F7F4F2);
  border-radius: 16px; padding: max(20px, min(2.4vw, 34px)); padding: clamp(20px, 2.4vw, 34px);
  box-shadow: 0 30px 70px rgba(0, 0, 0, .28);
}
/* Submit centred under the form (matches the centred composition). */
body.ags-page-contact .ags-c-form__submit { text-align: center; }
/* "Plan your trip" H1 dropped from the hero (Simon 2026-09-23 redirection — the reference hero is just
   the kanji + "Contact" + intro, no big heading). Hidden rather than deleted from the DB so it's a
   one-line revert if the client wants it back; hidden at every width. */
body.ags-page-contact .ags-c-contact__h { display: none; }

/* §3 MAP — free keyless Google embed (single pin at the office, framed at z=5 so the whole of
   Japan is visible — Simon 2026-09-22). Sits on a full-width GREY band below the hero/form (Simon
   2026-09-23 redirection — the reference puts the map on a neutral band, not the maroon). Keeps its
   760px card framing (rounded corners, drop-shadow, centred). No API key / billing: the iframe is
   Google's `output=embed` form. Grey band + card measure are width-agnostic (a fixed card, not
   frame-scaled, so it never balloons on ultrawide). --ags-sec-pad / --ags-edge per the composition
   doc. Portable (slug-scoped). */
/* Contact map — the map FILLS the whole band edge-to-edge, no padding (Simon 2026-09-23: less tall).
   No grey band, no card framing/rounding/shadow — just a full-width map at a modest fixed height. */
body.ags-page-contact .ags-c-contact__mapband {
  position: relative;
  background-color: transparent;
  padding: 0;
}
body.ags-page-contact .ags-c-contact__map { width: 100%; position: relative; z-index: 1; }
.ags-c-contact__mapframe {
  position: relative; width: 100%; max-width: none; margin: 0;
  height: var(--ags-cta-band-minh);   /* == the CTA band below it, so map + CTA read as two equal-height bands scrolling down */
  border-radius: 0; overflow: hidden;
}
.ags-c-contact__mapframe iframe {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; border: 0; display: block;
}

/* MOBILE (≤1023): clear the fixed top-left crest (height 6.25·--mnav-u = min(28.75vw, 112px)) + a ~28px
   gap so the centred hero (kanji 接触 · Contact · intro) isn't tucked under the crest / nav pills
   (Simon 2026-09-24). Desktop uses the two-column grid below with its own 72px nav clearance, so this is
   scoped to mobile only. */
@media (max-width: 1023px) {
  body.ags-page-contact .ags-c-contact.ags-c-hero { padding-top: calc(min(28.75vw, 112px) + 28px); }
}

/* ---------------------------------------------------------------------------
   CONTACT — DESKTOP TWO-COLUMN (Simon 2026-09-23 redirection). Reference: the hero (kanji 接触 +
   "Contact" + intro) sits in a LEFT column beside the cream form card on the RIGHT; the map spans
   full width below on a grey band. The three sections (§1 .ags-c-contact.ags-c-hero,
   §2 .ags-c-contact__formband, §3 .ags-c-contact__mapband) are DIRECT children of the page wrapper,
   so grid the wrapper and place each by its stable class — no DOM surgery (keeps the CF7 form + its
   order-sensitive preselect script untouched). SPACING follows docs/COMPOSITION-METHODOLOGY.md: the
   content is constrained to --ags-frame and centred (the body carries the maroon so ultrawide
   letterboxes seamlessly), left/right edges + the centre gutter are --ags-edge, and the top clears
   the fixed navbar (the form was tucking under it). Base/mobile keep the stacked centred layout
   above; this block just re-flows the same DOM. Portable (slug-scoped). */
@media (min-width: 1024px) {
  body.ags-page-contact .elementor[data-elementor-type="wp-page"] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    /* Row 1 (hero + form) fills exactly one viewport MINUS the navbar clearance, so padding-top +
       row1 = 100svh and the grey map band starts right at the fold. `minmax(…, auto)` lets row1 GROW
       if the form is ever taller than that space (short viewports) rather than clipping the submit.
       padding-top ≈ the floating nav-pill's height (NOT an over-clear): row1 then starts just below the
       pill and align-items:center puts EQUAL space above (pill→form) and below (form→fold). The form
       sits on the right, well clear of the pill, so this can hug the pill without collision. */
    grid-template-rows: minmax(calc(100vh - 72px), auto) auto;
    grid-template-rows: minmax(calc(100svh - 72px), auto) auto;
    grid-column-gap: var(--ags-edge);
    -moz-column-gap: var(--ags-edge);
         column-gap: var(--ags-edge);
    align-items: stretch;                /* hero + form FILL the band; each centres its own content */
    max-width: var(--ags-frame); margin-left: auto; margin-right: auto;
    /* FIXED nav clearance (NOT --ags-px). The navbar is a fixed-height overlay (its type is clamped to
       ~15px and the logo is a fixed em size), so it does NOT scale up on ultrawide — a scaled clearance
       overshot it by ~67px there and dumped that all onto the TOP gap. A fixed 72px matches the nav at
       every width, so the space above the form == the space below it. (== 72·--ags-px at the 1440 ref.) */
    padding-top: 72px;
    background-color: var(--e-global-color-primary, #8E1E00);
  }
  body.ags-page-contact .ags-c-contact.ags-c-hero {
    grid-column: 1; grid-row: 1;
    min-height: 0;                         /* drop the shared .ags-c-hero 40svh floor + 120·px top pad */
    padding: 0 0 0 var(--ags-edge);        /* left outer edge; right side = the column gutter */
  }
  body.ags-page-contact .ags-c-contact__formband {
    grid-column: 2; grid-row: 1;
    /* The padding here IS the gap around the card: top/bottom/right = --ags-px (scales), left = 0 so the
       card sits at the column gutter, toward the kanji (Simon 2026-09-23). The card fills this space and
       its rows distribute, so the gaps read equal near the reference aspect and the fields stay evenly
       spaced (not clustered) on taller viewports. */
    display: flex; flex-direction: column;
    padding: calc(40 * var(--ags-px)) calc(40 * var(--ags-px)) calc(40 * var(--ags-px)) 0;
  }
  /* Map spans the full grid width AND breaks out of the --ags-frame cap so it's truly full-bleed on
     ultrawide. The wrapper is `max-width:--ags-frame; margin-inline:auto` (centred), so without this the
     map inset ~150px each side at 2560+ (measured). The 100vw + symmetric negative margins pull it to the
     viewport edges; `html,body{overflow-x:clip}` (top of file) guards against a horizontal scrollbar. */
  body.ags-page-contact .ags-c-contact__mapband {
    grid-column: 1 / -1; grid-row: 2;
    width: 100vw; max-width: 100vw;   /* lift Elementor's .e-con max-width:100% (= grid area) so 100vw actually takes */
    margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw);
  }
  /* §4 CTA band sits directly below the map (row 3). It stays frame-capped, but the wrapper + body are
     maroon (the CTA's own colour), so the letterbox is invisible — the band reads edge-to-edge and its
     centred heading/pill stay centred on screen. Its min-height == the map's height (both --ags-cta-band-minh). */
  body.ags-page-contact .ags-c-cta { grid-column: 1 / -1; grid-row: 3; }
  /* Enlarge the hero kanji 接触 (per-widget is 40px — too small; Simon 2026-09-23). Heading → --ags-hpx.
     High-specificity selector beats Elementor's per-widget size. First heading in the eyebrow group = 接触. */
  body.ags-page-contact [data-elementor-type="wp-page"] .ags-c-contact__herocol .e-con .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: calc(80 * var(--ags-hpx)); line-height: 1;
  }
  /* Intro wraps to TWO lines ("…and the" / "Ageshio…"). Break window for the Red Rose 700 face is
     [16.5em, 20.7em); 18.5em sits mid-window (em-based, holds at any px size). Use explicit `width`
     (not just max-width): the widget is a shrink-to-fit flex item, and max-width alone doesn't force
     the wrap — same fix the About text-editor uses (see .ags-about__left …text-editor). `max-width:100%`
     guards a narrow column. Also open up the gap from the 接触/Contact block above; the block stays
     centred on the form (Simon 2026-09-23). */
  body.ags-page-contact .ags-c-contact__intro {
    width: 18.5em; max-width: 100%; margin-left: auto; margin-right: auto;
    margin-top: max(24px, min(3.6vh, 52px));
    margin-top: clamp(24px, 3.6vh, 52px);
  }

  /* FORM scales with the CONTENT scale (--ags-px) — composition doc: a frozen px literal becomes
     calc(N * var(--ags-px)), where N is its 1440-reference value. The earlier fixed-px/vw/vh clamps
     didn't track the frame, so the form stayed one size and overflowed the one-viewport maroon band at
     1440×766. --ags-scale is a contain-fit bounded by height, so this can't overflow a short viewport;
     at the 1440×766 reference these resolve to their N px and the whole form (~one card) sits inside the
     100svh band (row 1) with balanced padding. Desktop only — mobile keeps the base sizing above. */
  body.ags-page-contact .ags-c-form {
    padding: calc(30 * var(--ags-px));        /* uniform internal padding */
    margin: 0;                                 /* fills the column width (right edge → the 40·px gap) */
    /* SCALE the max measure with the frame (composition doc: frozen 760px → calc(760 * --ags-px)).
       The base rule's fixed 760px stayed put while the frame scaled up on ultrawide, so the card
       floated at the left of a ~1350px column → the "massive space on the right". Scaled, the card
       fills its column proportionally (col-width < the scaled cap), so there's no float, the content
       wraps like the reference, and it centres in the band vertically. */
    max-width: calc(760 * var(--ags-px));
    /* Card FILLS the band (Simon likes the filled look); the form's rows then DISTRIBUTE to fill it
       (see the CF7-chain rule below) so on a tall-but-narrow viewport (e.g. 1280×950) the fields space
       out evenly instead of clustering in the middle with empty cream. */
    width: 100%;
    flex: 1 1 auto; min-height: -webkit-min-content; min-height: -moz-min-content; min-height: min-content;
    display: flex; flex-direction: column;
  }
  /* Flex the CF7 chain so the <form> fills the card, then space its rows out to fill the height. Extra
     card height → even gaps between fields (airier on tall viewports), not empty cream. Hidden fields
     are display:none (not flex items); the trailing .wpcf7-response-output sits at the bottom edge. */
  body.ags-page-contact .ags-c-form > .elementor-widget-shortcode,
  body.ags-page-contact .ags-c-form .elementor-widget-shortcode > .elementor-widget-container,
  body.ags-page-contact .ags-c-form .elementor-shortcode,
  body.ags-page-contact .ags-c-form .wpcf7,
  body.ags-page-contact .ags-c-form .wpcf7-form {
    display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0;
  }
  /* Rows distribute to fill the card, but the form's growth is CAPPED (max-height) so the gaps can only
     open up so far — beyond that the capped form is centred in the card (.wpcf7 justify-content:center)
     and the remainder is moderate breathing, not ever-wider gaps. So a tall viewport (1280×950) reads
     evenly spaced, not over-spread; near the reference the cap is above the card so it fills normally.
     The 640 cap is the knob for "how much spread". */
  body.ags-page-contact .ags-c-form .wpcf7 { justify-content: center; }
  body.ags-page-contact .ags-c-form .wpcf7-form {
    justify-content: space-between;
    max-height: calc(620 * var(--ags-px));
  }
  body.ags-page-contact .ags-c-form .ags-c-form__row { margin-bottom: calc(12 * var(--ags-px)); }
  body.ags-page-contact .ags-c-form__eyebrow {
    margin: calc(16 * var(--ags-px)) 0 calc(8 * var(--ags-px)); padding-top: calc(12 * var(--ags-px));
    font-size: calc(11 * var(--ags-px));
  }
  body.ags-page-contact .ags-c-form__eyebrow:first-child { margin-top: 0; padding-top: 0; }
  body.ags-page-contact .ags-c-form label { font-size: calc(13 * var(--ags-px)); }
  body.ags-page-contact .ags-c-form label br { margin-bottom: calc(4 * var(--ags-px)); }
  body.ags-page-contact .ags-c-form input[type="text"],
  body.ags-page-contact .ags-c-form input[type="email"],
  body.ags-page-contact .ags-c-form input[type="number"],
  body.ags-page-contact .ags-c-form input[type="tel"],
  body.ags-page-contact .ags-c-form input[type="date"],
  body.ags-page-contact .ags-c-form select,
  body.ags-page-contact .ags-c-form textarea {
    padding: calc(9 * var(--ags-px)) calc(13 * var(--ags-px));
    font-size: calc(15 * var(--ags-px)); margin-top: calc(5 * var(--ags-px));
  }
  body.ags-page-contact .ags-c-form select { padding-right: calc(36 * var(--ags-px)); }
  body.ags-page-contact .ags-c-form textarea { height: calc(90 * var(--ags-px)); }
  body.ags-page-contact .ags-c-form__submit { margin-top: calc(16 * var(--ags-px)); }
  body.ags-page-contact .ags-c-form .wpcf7-submit,
  body.ags-page-contact .ags-c-form input[type="submit"] {
    padding: calc(12 * var(--ags-px)) calc(34 * var(--ags-px)); font-size: calc(14 * var(--ags-px));
  }
}

/* =============================================================================
   §§ INTERIOR COMPONENTS — MOBILE REFLOW (≤1023). Same DOM, single-column layout
   per Okinawan Group Tours Mobile.png. Three-region model (memory: mobile-build):
   the desktop rules above gate at ≥1024, this block owns ≤1023. Desktop UNCHANGED.

   SPECIFICITY: globally-typed headings render their SIZE on .elementor-heading-title
   via Elementor's per-element Kit global (0,4,0) — and the marquee text has an UNGATED
   scoped size just above. A plain wrapper class (.ags-c-…) loses to both, so every mobile
   heading size here is SCOPED to the rendered element + page type (0,4,0) and sits AFTER
   those rules → wins on source order. Text-editor globals sit on the widget → target
   .elementor-widget-container. (Same convention as the desktop region header. NOT
   body.ags-redesign — that class is FRONT-PAGE-only, absent on interior pages.)
   ============================================================================= */
@media (max-width: 1023px) {
  /* 0. Release the desktop min-width:1024 lock (body.elementor-page, ungated, far above).
     The homepage only releases it for body.ags-redesign (front-page-only), so on interior
     pages the body would stay 1024px wide → horizontal overflow at every phone width. Release
     it for every Elementor page at ≤1023 (that's the whole purpose of the mobile region);
     desktop keeps the lock (this is inside the max-width gate). VERIFIED: this is the primary
     overflow fix — without it scrollWidth pins to 1024 at 390/360/320. */
  body.elementor-page { min-width: 0; }

  /* §1 HERO → stack: eyebrow · photo · intro · button. flex-direction / align-items / gap are set
     per-element by Elementor's `.elementor-element:where(.e-con-full,…)` rule (0,2,0), which beats
     a plain .ags-c-… class (0,1,0); scope to page type (0,3,0) to win — same fix the homepage uses
     (see .ags-exp__edge above). */
  /* Mockup composition (identical on all 4 interior heroes): the kanji eyebrow (旅行/後退/経験) sits
     TOP-RIGHT, a SMALL SQUARE photo sits on the LEFT, then a LARGE intro paragraph + CTA are CENTRED
     below. align-items:flex-end pushes the shrink-wrapped eyebrow to the right; the photo re-claims
     the left edge via align-self:flex-start. (Was flex-start + full-width photo + a 15px intro cap.) */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__row {
    flex-direction: column; align-items: flex-end; gap: 22px;
  }
  /* Eyebrow hugs the right edge as a shrink-to-content block (the desktop max-content is gated ≥1024). */
  .ags-c-hero__eyebrow-col,
  .ags-c-hero__eyebrow-col .ags-c-eyebrow { width: -webkit-max-content; width: -moz-max-content; width: max-content; max-width: none; }
  .ags-c-hero__eyebrow-col { flex: 0 0 auto; align-self: flex-end; }
  /* LARGE kanji — mockup ≈ 46px; the bare Kit global renders 40px on mobile (the desktop bump is
     gated ≥1024). Depth-matched page-type scope beats the per-element global size. SHARED. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__eyebrow-col .ags-c-eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: max(48px, min(15vw, 62px));
    font-size: clamp(48px, 15vw, 62px); line-height: 1;   /* POLISH: mockup hero kanji glyph ≈54px @390 (was 46.8px→43px glyph, undersized ~20%); 15vw@390≈58.5px→~54px glyph. SHARED all 4 heroes. */
  }
  /* ALL KANJI EYEBROWS — centre the romaji word beneath its kanji (Simon 2026-08-17). Each ags-c-eyebrow
     is two headings: kanji (:first-child) + romaji label (:last-child). Centre both within the eyebrow
     box so the word sits directly under the kanji instead of ragged-left. Applies to EVERY interior
     eyebrow on mobile — hero (旅行/後退/経験), numgrid (§7), explist (§11), locations 場所, closing 約. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-eyebrow { text-align: center; }
  .ags-c-eyebrow .elementor-widget-heading { width: 100%; }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-eyebrow .elementor-heading-title,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-eyebrow .elementor-widget-container { text-align: center; }
  /* Tiny gap between each kanji and the romaji word beneath it (Simon 2026-08-17). em is relative to the
     word's own font-size → a small, proportional space on every eyebrow. */
  .ags-c-eyebrow .elementor-widget-heading:last-child { margin-top: 0.3em; }
  /* SMALL SQUARE photo on the LEFT (mockup ≈ 42% width; the reflow forced it full-width landscape). */
  .ags-c-hero__photo-col { flex: 0 0 auto; width: max(150px, min(42vw, 210px)); width: clamp(150px, 42vw, 210px); align-self: flex-start; }
  .ags-c-hero__photo { aspect-ratio: 1 / 1; border-radius: 8px; overflow: hidden; }
  .ags-c-hero__photo figure, .ags-c-hero__photo img { border-radius: 8px; }
  .ags-c-hero__photo img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; }
  /* SAME rounded corners on EVERY interior hero photo (Simon 2026-08-17): some page widgets lack the
     native image_border_radius, so force 8px here (+overflow:hidden clips the cover img). SHARED. */
  /* POLISH: the mockups right-align the hero intro (flush-right, ragged-left) + right-offset the CTA,
     mirroring the top-right kanji AND the desktop shared intro rule (L3905/L3923 = text-align:right).
     The earlier "center mirrors desktop" comment was wrong. SHARED — all 4 interior heroes. */
  .ags-c-hero__intro-col { width: 100%; align-items: flex-end; text-align: right; }
  .ags-c-hero__intro { max-width: none; }
  .ags-c-hero__cta { align-self: flex-end; }
  /* Intro — the mockup keeps it LARGE + CENTRED (mirrors the desktop shared intro treatment, which is
     gated ≥1024). Force the ags_h3 look (Bounded 400, −3% tracking) + a large mobile size on EVERY
     interior hero: the intros are text-editor widgets carrying either the ags_h3 or the lighter text
     global, so pin the type + size to make them match the reference. SHARED — this replaces the old
     okinawan-only 15px cap (which shrank the reference page's intro far below its own mockup). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__intro .elementor-heading-title,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__intro .elementor-widget-container,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-hero__intro p {
    font-family: 'Bounded', sans-serif; font-weight: 400; letter-spacing: -0.03em;
    /* POLISH: mockup intro ≈22px (line pitch 25px @390, ~6-line wrap); was 29.25px → 10-line wrap. 5.7vw@390≈22px. Right-aligned per mockup. SHARED. */
    font-size: max(19px, min(5.7vw, 25px));
    font-size: clamp(19px, 5.7vw, 25px); line-height: 1.15; text-align: right;
  }

  /* §2 MARQUEE — smaller but still loops (overflow:hidden keeps it off the x-axis). Scoped to
     beat the UNGATED desktop marquee size that sits on .elementor-heading-title. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-marquee__text .elementor-heading-title {
    font-size: max(46px, min(14vw, 64px));
    font-size: clamp(46px, 14vw, 64px);   /* mockup marquee word ≈ 57px @390 */
  }
  /* Crest ≈ the text height (matches desktop's ~1.04× ratio) — track the text clamp instead of the old
     fixed 34px, which left the crest ~0.6× the words on mobile (it looked good on desktop only). */
  .ags-c-marquee__crest img { height: max(48px, min(14.5vw, 66px)); height: clamp(48px, 14.5vw, 66px); }
  /* The desktop text↔crest gap tuning (base !important) would otherwise bleed here; mobile keeps
     the builder's original 40px unit gap so the mobile marquee is visually unchanged. */
  .ags-c-marquee__unit { gap: 40px !important; }

  /* §3 FEATURE PHOTO — full-bleed SQUARE band, identical on EVERY interior page (Simon 2026-08-17: all
     the same square ratio, cropped per the mobile references, attached to the red §2 above). The desktop
     base is gated ≥1024, so mobile falls back to the padded, rounded, natural-aspect default. Here:
     kill ALL section padding (incl. the 56px phone TOP pad → the photo attaches flush to the red first
     section, no white gap) + force a 1:1 cover-crop. Per-page object-position places each image below. */
  .ags-c-photo { padding: 0; }
  .ags-c-photo__img,
  .ags-c-photo__img figure { margin: 0; }
  .ags-c-photo__img img { width: 100%; aspect-ratio: 1 / 1; height: auto; -o-object-fit: cover; object-fit: cover; display: block; border-radius: 0; }

  /* §4 STATEMENT — the two eyebrow captions sit SIDE BY SIDE on one row (Simon 2026-09-24), left hugging
     left + right hugging right, like the desktop corner labels — NOT stacked. The labels container's
     flex-direction is set per-element by Elementor (0,2,0) → scope to page type to win. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-statement__labels {
    flex-direction: row; justify-content: space-between; align-items: flex-start; gap: 12px;
  }
  /* About's maroon statement mobile pass isn't done — keep its labels stacked as they were. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-statement--maroon .ags-c-statement__labels {
    flex-direction: column; gap: 4px;
  }
  .ags-c-statement__labels { text-align: center; }
  /* Eyebrow LABELS: BLACK Bounded like the reference (Simon 2026-08-17). The desktop styling
     (Bounded 400 #111010) is gated ≥1024, so mobile fell back to the light Red Hat text global.
     Per-element alignment keeps "From airport…" left / "every detail…" right (matches the mockup). */
  .ags-c-statement__labels .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400; color: #111010;
    font-size: max(14px, min(4vw, 17px));
    font-size: clamp(14px, 4vw, 17px); line-height: 1.25;
  }
  /* EYEBROWS — show BOTH on mobile for the standard statements (Simon 2026-09-24, restoring the left;
     the 2026-08-17 "omit the left caption" call is reversed). Each is a narrow Bounded caption mirroring
     the other: the left ("From airport…" / "Onko Chishin") hugs the LEFT (text-align:left,
     margin-right:auto), the right ("every detail…" / "Learning from the past…") hugs the RIGHT — same
     width/format, mirrored side. Labels stack (the container is flex-direction:column above): left on top,
     right below.
     ⚠ EXCEPTION: the About .ags-c-statement--maroon variant stays with its left hidden — its mobile pass
     isn't done yet, and its labels are structured as PAIRS ("The Ageshio"|"Mission"), not captions. */
  .ags-c-statement__label--l .elementor-widget-container { max-width: 7.5em; margin-right: auto; text-align: left; }
  .ags-c-statement__label--r .elementor-widget-container { max-width: 7.5em; margin-left: auto; text-align: right; }
  .ags-c-statement--maroon .ags-c-statement__label--l { display: none; }   /* About: mobile refinement pending */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-statement__headline .elementor-heading-title {
    /* Match the mockup EXACTLY: ~32px headline in an 80%-width CENTRED column → the mockup's 8-line wrap
       ("We handle / everything — so / your group can / focus entirely / on training, / learning, and /
       experiencing / Okinawa"). Measured from the ref: font ≈8.5vw, column ≈9.5em (≈314px @390 = 80vw).
       (Earlier 35.5px @ full 330px width matched the LINE COUNT but was ~9% oversized vs the ref.) */
    font-size: max(28px, min(8.5vw, 34px));
    font-size: clamp(28px, 8.5vw, 34px); line-height: 1.12;
    max-width: 9.5em; margin-left: auto; margin-right: auto;
  }
  /* SEE WHAT'S INCLUDED link: BLACK + UNDERLINED + small, like the reference (Simon 2026-08-17). The
     desktop rule is gated ≥1024, so mobile fell back to the red button global with no underline. */
  .ags-c-statement__link .elementor-button { color: #111010; -webkit-text-decoration: underline; text-decoration: underline; text-underline-offset: 4px; }
  .ags-c-statement__link .elementor-button:hover { color: #111010; }
  .ags-c-statement__link .elementor-button-text { font-size: 11px; }
  .ags-c-statement__link .elementor-button::after { display: none; }

  /* §5 PHOTO ROW → single-column stack (flex-direction per-element from Elementor → scope to win).
     This is the NO-JS / reduced-motion / desktop fallback: without .is-carousel the row stays a
     simple vertical stack. The .is-carousel rules below (added by ageshio-gallery.js) override it. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-photorow__row { flex-direction: column; }

  /* §5 PHOTO ROW → mobile carousel. Reference shows ONE photo box (not 3 stacked). Mirrors the
     homepage §3 gallery: the 3 photos share one box, cross-fade on .is-active; a segmented progress
     bar rides the top edge. ageshio-gallery.js adds .is-carousel + the .ags-c-photorow__bar chrome.
     WITHOUT JS (bail/reduced-motion): the row stays a simple stack (rules below are only for .is-carousel).
     Scoped to page type (0,3,0+) to beat Elementor's per-element flex-direction:row (0,2,0) on __row —
     same reason the stack rule above is scoped; a plain .is-carousel selector (0,2,0) would lose the row. */
  /* COHESIVE SECTION (Simon 2026-08-17): §4 statement + §5 photo carousel are ONE cream section, so the
     cream must run continuously behind the carousel with NO white gap. The photo row is cream; the earlier
     margin-block:32px was TRANSPARENT (showed the white page bg above/below → a break). Use margin:0 so the
     section abuts the cream statement above and the red numgrid below, and put the space BELOW the photo as
     PADDING (cream). Keep padding-TOP:0 — the progress bar is absolute:top:12px from the root, so a top pad
     would push the in-flow photo down while the bar stayed put (they'd detach); the space ABOVE the photo
     comes from the statement's own cream padding-bottom. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-photorow.is-carousel {
    padding: 0 0 max(36px, min(9vw, 48px));
    padding: 0 0 clamp(36px, 9vw, 48px); margin-top: 0; margin-bottom: 0;
  }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-photorow.is-carousel .ags-c-photorow__row {
    position: relative; display: block;
    aspect-ratio: 1 / 1;              /* mockup §5 shows ONE ~square photo box (not a 3/2 landscape) */
    max-width: 92vw; margin-left: auto; margin-right: auto;
  }
  .ags-c-photorow.is-carousel .ags-c-photorow__col {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; margin: 0;
    opacity: 0; visibility: hidden;
    transition: opacity .6s ease, visibility .6s ease;
  }
  .ags-c-photorow.is-carousel .ags-c-photorow__col.is-active { opacity: 1; visibility: visible; }
  /* FILMSTRIP slide for the interior Group-Tours PHOTO carousel — cols fill the box (inset:0) and move via
     transform:translateX(off·100%); every col at its shortest circular offset from active (see the gallery
     note — smooth both directions, far-wrapping col jumps off-screen via JS). AgsCarousel.positionSlides. */
  /* Static __row box = rounded viewport: its overflow:hidden + border-radius clips the sliding (composited)
     cols to rounded corners throughout (rounding the moving col itself doesn't survive — see masters note). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-photorow.is-carousel .ags-c-photorow__row { overflow: hidden; border-radius: 8px; touch-action: pan-y; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
  .ags-c-photorow.is-carousel .ags-c-photorow__col {
    opacity: 1 !important; visibility: visible !important;
    transform: translateX(calc(var(--ags-off, 2) * 100%));
    transition: transform .5s ease !important;
  }
  .ags-c-photorow.is-carousel .ags-c-photorow__col.is-active { z-index: 2; }
  .ags-c-photorow.is-carousel .ags-c-photorow__col img { -webkit-user-drag: none; }
  .ags-c-photorow.is-carousel .ags-c-photorow__img,
  .ags-c-photorow.is-carousel .ags-c-photorow__img figure,
  .ags-c-photorow.is-carousel .ags-c-photorow__img img {
    width: 100%; height: 100%; margin: 0; -o-object-fit: cover; object-fit: cover; border-radius: 8px;
  }
  /* progress bar (shared filling-line indicator, injected by JS) */
  .ags-c-photorow.is-carousel .ags-c-photorow__bar {
    position: absolute; top: 12px; left: 14px; right: 14px; z-index: 3;
  }

  /* §6 SECTION TITLE — big but fits the column (scoped to beat the Kit global). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-titleband__h .elementor-heading-title {
    font-size: max(34px, min(11vw, 56px));
    font-size: clamp(34px, 11vw, 56px);
  }
  /* EDGE PADDING (Simon 2026-08-17): at 768–1023 Elementor drops the section side-padding to 0 (the
     phone 20px only applies ≤767), so the "What's included" heading + the numgrid eyebrow/cards touch
     the page edge on wider phones. Give both sections explicit side padding at ALL mobile widths
     (≈20px @390, growing gently) so nothing sits flush. padding-inline (0,3,0) beats Elementor's
     per-element padding; only the left/right is set, so the vertical padding is untouched. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-titleband,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid { padding-left: max(20px, min(4vw, 44px)); padding-right: max(20px, min(4vw, 44px)); padding-left: max(20px, min(4vw, 44px)); padding-left: clamp(20px, 4vw, 44px); padding-right: max(20px, min(4vw, 44px)); padding-right: clamp(20px, 4vw, 44px); }

  /* §7 NUMBERED GRID → HORIZONTAL side-scroll (Simon 2026-08-17, ref Image #8): NOT a 2-up grid but a
     single swipeable row of number+label cards, ~1.5 cards visible so the next one peeks in at the right
     edge. The grid scrolls INTERNALLY (overflow-x on the grid, NOT the page) so document scrollWidth
     stays 390 — no page-level horizontal overflow. SHARED across the numgrid-bearing pages (GT + VIP). */
  .ags-c-numgrid__grid {
    display: flex; flex-wrap: nowrap;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    gap: max(18px, min(5vw, 28px));
    gap: clamp(18px, 5vw, 28px);
    padding-left: 0;
    padding-right: 24px;    /* trailing pad so the last card rests fully in view */
    scrollbar-width: none;     /* Firefox — hide the bar (the peeking next card is the affordance) */
  }
  .ags-c-numgrid__grid::-webkit-scrollbar { display: none; }   /* WebKit — hide the bar */
  /* HORIZONTAL-SCROLL AFFORDANCE (Simon, ref Image #26): a long arrow at the bottom-right of the
     numgrid section signalling the card row scrolls sideways — the peeking-next-card cue alone wasn't
     obvious enough, especially at VIP's 12 items. A sibling of the (overflow-x) grid, so it doesn't
     scroll away. margin-left:auto right-aligns it (works whether the section is block or flex-column);
     cream stroke reads on the maroon "What's included" band. SHARED across the numgrid pages (GT + VIP). */
  .ags-c-numgrid::after {
    content: ""; display: block; flex: 0 0 auto;
    width: max(48px, min(14vw, 64px));
    width: clamp(48px, 14vw, 64px); height: 12px;
    margin: max(16px, min(4.5vw, 24px)) 0 0 auto;
    margin: clamp(16px, 4.5vw, 24px) 0 0 auto;
    background: center / contain no-repeat
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='12' viewBox='0 0 64 12' fill='none' stroke='%23f7f4f2' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='1' y1='6' x2='57' y2='6'/%3E%3Cpolyline points='51,1.5 57.5,6 51,10.5'/%3E%3C/svg%3E");
  }
  /* The items are Elementor .e-con containers → their flex sizing is driven by Elementor's per-element
     --flex-* consumer (0,2,0), which beats a bare .ags-c-numgrid__item (0,1,0) and shrinks them to ~21px.
     Assert the CARD WIDTH at page-type scope (0,3,0) to win — same --flex-* trap as the hero photo-col. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__grid > .ags-c-numgrid__item {
    flex: 0 0 max(190px, min(56vw, 260px));
    flex: 0 0 clamp(190px, 56vw, 260px); width: max(190px, min(56vw, 260px)); width: clamp(190px, 56vw, 260px); max-width: none;
    scroll-snap-align: start;
  }
  .ags-c-numgrid__item { display: flex; flex-direction: column; gap: 6px; }
  /* Kanji eyebrow (旅行/後退 + label) is CENTRED above the grid in the mockup; the desktop left-gutter
     positioning is page-scoped + gated ≥1024, so mobile inherits Elementor's per-element flex-start.
     Page-type scope (0,4,0) beats it. SHARED across the numgrid-bearing pages (GT + VIP). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__eyebrow { align-items: center; text-align: center; }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__eyebrow .elementor-heading-title { text-align: center; }
  /* POLISH: size the §7 eyebrow kanji to match the mockup (glyph ≈54px @390, same as the §1 hero kanji).
     It otherwise falls back to the small Kit eyebrow global (live ≈36px). Mirrors the hero-kanji clamp
     above. SHARED across the numgrid pages (GT + VIP). :first-child = the kanji heading (not the label). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: max(48px, min(15vw, 62px));
    font-size: clamp(48px, 15vw, 62px); line-height: 1;
  }
  /* Item "1." / "2." numbers — mockup ≈ 24px; the reflow leaves them at the ~13px label global. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-numgrid__grid .elementor-widget-heading .elementor-heading-title {
    font-size: max(20px, min(6vw, 28px));
    font-size: clamp(20px, 6vw, 28px);
  }
  /* Item LABEL — restore the Bounded styling (Simon 2026-08-17: the card "lost styling"). The desktop
     label rule (Bounded 400 + <b> at 500) is gated ≥1024, so on mobile the label fell back to the Red
     Hat Display 300 text global. Mirror the desktop rule here so it matches the reference (Image #8:
     Bounded, with "pickup & drop-off" emphasised). */
  .ags-c-numgrid__label .elementor-widget-container { font-family: 'Bounded', sans-serif; font-weight: 400; font-size: max(14px, min(3.9vw, 16px)); font-size: clamp(14px, 3.9vw, 16px); line-height: 1.35; }
  .ags-c-numgrid__label b { font-weight: 500; }

  /* §8 OVERLAY SPLIT → the desktop cqw stage (absolute children, container-type, aspect-ratio,
     center connector line, cluster max-widths) is ALL gated ≥1024, so at ≤1023 the stage is
     already a normal flex COLUMN of STATIC children with no max-width clamp (verified live:
     list--l max-width = 100% at 390). Here we only tidy the stack: photo first, comfortable
     padding/gap, headings above their lists, everything left-aligned. */
  /* Mockup §8 overlays the Who/Why text OVER the dark B&W photo (like desktop) with the two headings
     staggered L / R and larger (~50px). Make the photo a full-bleed BACKGROUND of the stage; the text
     flows in normal order on top, over a dark scrim for legibility. (The old reflow un-stacked it:
     photo as a separate block, both headings left on a plain black bg.) SHARED — same component on
     all 4 pages. */
  /* BACKGROUND FILLS THE WHOLE SECTION (Simon 2026-08-17): the section had 56px phone padding, so the
     stage+photo left a black frame (top/bottom/sides) instead of filling. Kill it → the photo is full-
     bleed edge-to-edge; the stage's own padding (below) insets the TEXT only. Also widens the stage so
     the bullets fit on one line (they were 1px over at the narrower width). */
  .ags-c-overlay { padding: 0; }
  /* Section length: the vertical spacing (stage padding-block + row-gap + the divider margins below) is
     scaled up together so the §8 section reads LONGER (≈ one screen, matching the reference) while every
     element keeps its proportional placement (Simon 2026-08-17). */
  .ags-c-overlay__stage { position: relative; overflow: hidden; display: grid; grid-template-columns: 1.2fr 1fr; grid-column-gap: 12px; -moz-column-gap: 12px; column-gap: 12px; grid-row-gap: 48px; row-gap: 48px; align-items: start; padding: 88px 24px; width: 100%; box-sizing: border-box; }
  /* Photo → absolute background covering the whole stage (the text flow drives the stage height). The
     img is pinned to the photo widget directly (inset:0) so intermediate figure/wrapper sizing can't
     break a height:100% chain. */
  .ags-c-overlay__photo { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 0; margin: 0; order: 0; }
  .ags-c-overlay__photo img { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; margin: 0; -o-object-fit: cover; object-fit: cover; display: block; }
  .ags-c-overlay__photo::after { content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(17,16,16,.58); }
  /* All text sits above the photo + scrim. min-width:0 lets grid items wrap inside their column. */
  .ags-c-overlay__stage > :not(.ags-c-overlay__photo) { position: relative; z-index: 1; min-width: 0; }
  /* GRID placement (Simon 2026-08-17): the heading + its caption share a ROW so their TOPS ALIGN
     (align-items:start), like the reference — not a vertical stack. Cluster 1: "Who is this for"(col1,
     left) + "Our Group Tours…"(col2, right). Cluster 2 MIRRORS: "Instead of…"(col1, left) + "Why a
     group tour"(col2, right). The bullet lists span BOTH columns; the divider (list--l::after) then
     sits between the two clusters. 50/50 columns keep each heading on its own side. */
  .ags-c-overlay__h--tl   { grid-column: 1; grid-row: 1; }
  .ags-c-overlay__cap--l  { grid-column: 2; grid-row: 1; }
  .ags-c-overlay__list--l { grid-column: 1 / -1; grid-row: 2; }
  .ags-c-overlay__cap--r  { grid-column: 1; grid-row: 3; }
  .ags-c-overlay__h--br   { grid-column: 2; grid-row: 3; }
  .ags-c-overlay__list--r { grid-column: 1 / -1; grid-row: 4; }
  /* Big headings, Who left / Why right (page-type scope beats the per-element text-align). Sized so each
     wraps cleanly in its 50%-column (~171px): "Who is / this for" (2 lines), "Why a / group / tour" (3). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__h .elementor-heading-title {
    font-size: max(32px, min(10.8vw, 46px));
    font-size: clamp(32px, 10.8vw, 46px); line-height: 1.02;
  }
  /* KE §8 headings are longer than GT's ("Book as a Group Tour" / "Book as an individual"); at the shared
     size "individual" (one atomic word) is ~71px too wide for its 50% column and clips. Shrink KE's two
     headings so both fit their columns (Simon 2026-08-18). NB: the KE *mobile* mockup shows GT content
     ("Why a group tour" / "Ready to plan your group tour?"), so KE §8 has no faithful mobile reference —
     this just makes it read cleanly. */
  body.ags-page-karate-experiences [data-elementor-type="wp-page"] .ags-c-overlay__h .elementor-heading-title { font-size: max(21px, min(7vw, 30px)); font-size: clamp(21px, 7vw, 30px); }
  /* VIP: "retreat" is a hair wider than its column at the shared size → nudge down so it doesn't spill. */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-overlay__h .elementor-heading-title { font-size: max(28px, min(9.2vw, 40px)); font-size: clamp(28px, 9.2vw, 40px); }
  /* About: "Ageshio" is one atomic word wider than its 50% column at the shared size → "Why Ageshio Japan"
     spilled off the right edge. Shrink About's overlay headings so it fits and right-aligns to the margin
     (same fix as KE/VIP above). Simon 2026-09-24. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-overlay__h .elementor-heading-title { font-size: max(24px, min(8vw, 34px)); font-size: clamp(24px, 8vw, 34px); }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__h--tl .elementor-heading-title { text-align: left; }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__h--br .elementor-heading-title { text-align: right; }
  /* Cream, full-opacity caption + list text so it reads over the photo (desktop bakes .9 opacity). */
  .ags-c-overlay__cap--l .elementor-widget-container,
  .ags-c-overlay__cap--r .elementor-widget-container,
  .ags-c-overlay__list--l .elementor-widget-container,
  .ags-c-overlay__list--r .elementor-widget-container { color: var(--ags-cream, #F7F4F2); opacity: 1; }
  /* POLISH: mockup runs the Who/Why bullets ≈14px (most bullets on ONE line); live rendered 16px → each
     bullet wrapped to 2 lines. Promote to page-type scope (0,4,0) to beat the per-element 16px global. SHARED. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__cap--l .elementor-widget-container,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__cap--r .elementor-widget-container,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__list--l .elementor-widget-container,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__list--r .elementor-widget-container {
    /* BOUNDED font (Simon 2026-08-17: use the intended font, not the Red Hat Display fallback) — matches
       the desktop overlay rule + the mockup. Smaller so the short bullets sit on ONE line like the mockup
       (14.4px put every bullet on 2 lines; the longest still wraps to 2, matching the ref). */
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(11px, min(3.3vw, 13px));
    font-size: clamp(11px, 3.3vw, 13px); line-height: 1.4;
  }
  /* Mockup left-aligns the second cluster (cap--r "Instead of planning…" + list--r incl. the trailing
     "You simply arrive…"); its desktop editorial right-align bleeds through otherwise. cap--l keeps
     its own right-align (mockup: the top "…ideal for" caption sits top-right). */
  .ags-c-overlay__cap--r .elementor-widget-container,
  .ags-c-overlay__list--r .elementor-widget-container { text-align: left; }
  /* CAPTIONS wrap NARROW like the mobile mockup — cap--l "Our Group / Tours are / ideal for" (3 lines,
     top-right) and cap--r "Instead of / planning / everything / yourself, we / provide" (5 lines, left) —
     an editorial column, not 2-3 wide lines (Simon 2026-08-18). max-width on the CONTAINER (which carries
     the caption font, clamp(11-13px)) in em → the wrap locks with the font. cap--l right-aligns within its
     column via margin-left:auto (matches the top-right placement). SHARED across the 4 pages. */
  .ags-c-overlay__cap--l .elementor-widget-container { max-width: 7em; margin-left: auto; }   /* ~90px @390: "Our Group" holds (3 lines), not "Our" alone */
  .ags-c-overlay__cap--r .elementor-widget-container { max-width: 8em; }                       /* ~103px @390: "yourself, we" holds (5 lines like the mockup), not split */
  /* HORIZONTAL DIVIDER (Simon 2026-08-17): the mobile mockup splits the section with a full-width
     horizontal rule between the first cluster (Who is this for + bullets) and the second (Why a group
     tour). It's the mobile counterpart of the desktop's vertical centre line. Drawn as an ::after on
     the first bullet list (which sits right before "Why a group tour" in the stack). */
  .ags-c-overlay__list--l::after {
    content: ""; display: block; width: 100%; height: 1px;
    background: var(--ags-cream, #f7f4f2); opacity: .6;
    /* Generous space ABOVE (margin-top) and BELOW (margin-bottom, + the 16px grid row-gap) the rule so
       the divider is isolated and the section reads longer, per the reference (Simon 2026-08-17). list--l
       is a grid item = BFC, so this margin is contained (doesn't collapse through). */
    margin: 100px 0 64px;
  }
  .ags-c-overlay__list--l ul,
  .ags-c-overlay__list--r ul { list-style-position: outside; padding-left: 1.1em; }
  /* HEADING WRAP (Simon 2026-08-17): constrain each big heading so it breaks like the mockup —
     "Who is / this for" (left) and "Why a / group / tour" (right). max-width on the HEADING-TITLE
     (font 46.8px) so em resolves against the big size, NOT the 16px widget. Widgets stay full-width. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__h--tl,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__h--br { max-width: none; }
  /* Heading width now comes from the widget's flex-basis (55%); the heading-title fills it and wraps to
     the column ("Who is / this for", "Why a / group / tour"). A max-width here would over-widen the
     widget past its basis and break the side-by-side row. */
  .ags-c-overlay__h--tl .elementor-heading-title,
  .ags-c-overlay__h--br .elementor-heading-title { max-width: none; }
  /* Caps + lists span the column (full-width bullets, one line each). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__cap--l,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__cap--r,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__list--l,
  .elementor-page [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__list--r { max-width: none; }

  /* HISTORY TOUR §8 overlay — the two "Who is this for" texts are authored into the REVERSE slots of the
     shared GT layout: cap--r holds the long "For practitioners…" line, list--r holds the short "Experience
     cultural…" caption (as a 1-item bullet). Swap their grid cells (+ width/bullet) for THIS page so it
     matches the reference: "Experience cultural…" beside the heading (row 3 left, narrow, no bullet),
     "For practitioners…" full-width along the bottom (row 4). Simon 2026-09-24. */
  body.ags-page-karate-history-tour .ags-c-overlay__list--r { grid-column: 1; grid-row: 3; }
  body.ags-page-karate-history-tour .ags-c-overlay__cap--r  { grid-column: 1 / -1; grid-row: 4; }
  body.ags-page-karate-history-tour .ags-c-overlay__list--r ul { list-style: none; padding-left: 0; }
  body.ags-page-karate-history-tour .ags-c-overlay__cap--r .elementor-widget-container { max-width: none; }

  /* §9 ACCORDION → single column; beach photo below the list (flex-direction per-element from
     Elementor (0,2,0) → scope to page type to win). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-accordion__inner { flex-direction: column; }
  .ags-c-accordion__list { width: 100%; }
  .ags-c-accordion__aside { width: 100%; order: 2; }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-accordion__header .elementor-heading-title {
    font-size: max(34px, min(11.5vw, 50px));
    font-size: clamp(34px, 11.5vw, 50px);   /* mockup headers ≈ 45px maroon (was ~31px @390) */
  }
  /* DIVIDER LINES + SPACING (Simon 2026-08-17): the mobile mockup omits them, but keep the desktop
     look — a thin red rule under each heading (Sample experience / Optional add-ons / Etiquette) with
     breathing room so the three items are spaced out. The desktop ::after rule + header padding are
     gated ≥1024, so mobile had neither → re-declare here. Full-column-width rule (mobile headings fill
     the column); even margin-block above/below = the inter-item gaps. */
  .ags-c-accordion__item { position: relative; border: 0; }
  .ags-c-accordion__header { padding: 6px 0; }
  .ags-c-accordion__item::after {
    content: ""; display: block; width: 100%; height: 3px;
    background: rgba(142,30,0,.6); margin-top: max(18px, min(5vw, 30px)); margin-bottom: max(18px, min(5vw, 30px)); margin-top: max(18px, min(5vw, 30px)); margin-top: clamp(18px, 5vw, 30px); margin-bottom: max(18px, min(5vw, 30px)); margin-bottom: clamp(18px, 5vw, 30px);
  }
  /* ROOT-CAUSE FIX (top shared bug): ageshio-accordion.js moves each header's text into a native
     <button class="ags-c-accordion__btn"> inside the heading-title. The reset that strips the button
     chrome and makes it inherit the big maroon heading font lives in the ≥1024 block, so on mobile the
     three headers render as raw UA buttons (grey border, ~13px system font, light bg). Re-declare the
     reset here so the button inherits the scoped heading font-size + the maroon global colour. SHARED
     (identical JS + markup on all 4 interior pages). */
  .ags-c-accordion__btn {
    display: block; width: 100%; text-align: left;
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; color: inherit; cursor: pointer;
    letter-spacing: inherit; line-height: inherit;
  }
  /* Panel reveal — the desktop grid 0fr→1fr collapse is gated ≥1024, so re-declare here or
     panels render fully expanded and the toggle is inert at ≤1023 (the reduced-motion guard is
     already ungated so it still applies). Mobile ref shows the accordion COLLAPSED at rest. */
  .ags-c-accordion__panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .35s cubic-bezier(.22,1,.36,1); }
  .ags-c-accordion__panel > * { overflow: hidden; min-height: 0; }
  .ags-c-accordion__item.is-open .ags-c-accordion__panel { grid-template-rows: 1fr; padding-bottom: 16px; }
  /* BUG FIX (i18n mobile overflow, 2026-09-20), About only: same class of bug as the §5+§11
     statement headline above — German's "Beispielerlebnis" (accordion item 1's heading, one
     unbreakable compound word) doesn't fit the 290px column and doesn't wrap (no space to break
     at), so its scrollWidth (396px) bled into <body>'s scrollWidth exactly like the headline case.
     GT/VIP/History share this same accordion component with shorter EN-length headings that don't
     hit this — scope to About (the page with the long German heading) rather than editing the
     shared rule for every consumer. */
  body.ags-page-about .ags-c-accordion__header .elementor-heading-title,
  body.ags-page-about .ags-c-accordion__btn {
    word-wrap: break-word;
    overflow-wrap: break-word; word-break: break-word;
  }

  /* §10 CTA — centred heading + pill, sized for the column (its desktop centring rule is gated). */
  .ags-c-cta__inner { align-items: center; text-align: center; }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-cta__h .elementor-heading-title {
    font-size: max(26px, min(8vw, 34px));
    font-size: clamp(26px, 8vw, 34px);   /* mockup CTA heading ≈ 33px centred (was ~23px @390) */
  }

  /* §11 EXPERIENCE LIST → mobile. The carousel JS bails <1024 (ageshio-exp.js), so the track never
     slides; the desktop mask (.ags-exp__mid: fixed 9rem, overflow:hidden) would clip items 4–5 to a
     dead window. The homepage never hits this (mobile swaps §7 for the programs carousel + hides the
     mask), but an interior page has no such replacement — so present the 5 items as a STATIC, un-clipped,
     centred list. Stack the row vertically, drop the edge photos (they flank a carousel that isn't here),
     and shrink the heading so the unbreakable word "Experiences" (60px desktop → ~416px) fits the column. */
  /* Scope the row→column flip to page type (0,3,0) to beat Elementor's per-element flex-direction:row
     (0,2,0) — a plain .ags-c-explist__row (0,1,0) loses, same as the sibling reflows above. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist__row { flex-direction: column; gap: 20px; align-items: center; }
  .ags-c-explist .ags-exp__edge { display: none; }             /* photos flank the desktop carousel; drop on mobile */
  .ags-c-explist .ags-exp__mid.e-con { height: auto; width: 100%; overflow: visible; }  /* unclip: show all items */
  /* The carousel JS bails <1024, but on a desktop→mobile RESIZE init() has already run and its
     resize handler re-applies an inline transform even at mobile width — so !important is required
     to keep the static list from carrying a stale slide offset. */
  .ags-c-explist .ags-exp__track.e-con { transform: none !important; }
  .ags-c-explist .ags-exp__item.elementor-widget-heading { height: auto; }              /* let each line size itself */
  .ags-c-explist .ags-exp__item .elementor-heading-title { white-space: normal; }       /* allow wrap in the column */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist__h .elementor-heading-title {
    font-size: max(30px, min(9vw, 46px));
    font-size: clamp(30px, 9vw, 46px); line-height: 1.02; word-wrap: anywhere; overflow-wrap: anywhere;
  }
  .ags-c-explist .ags-exp__lede { align-items: center; margin: 0; }                     /* lede centred under the list */
  .ags-c-explist__eyebrow, .ags-c-explist__h { text-align: center; }
  /* VIP explist (Simon 2026-09-21): centre the HEADING + lede paragraph text on mobile. Desktop has
     them bottom-left / bottom-right in the editorial composition, and those alignments leak to mobile
     (the heading-title carries Elementor's per-element text-align:left at (0,4,0); the intro copy is
     right-aligned) — so the plain (0,1,0) centre above loses. (0,4,1) VIP-scoped wins. */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-explist__h .elementor-heading-title { text-align: center; }
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-explist .ags-exp__introtext .elementor-widget-container,
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-explist .ags-exp__introtext p { text-align: center; }
  /* Centre the per-experience CTA ("SHARE THE TABLE") too, so it lines up with the centred heading +
     intro (the button wrapper was left-aligned — same fix the KE page uses, Simon 2026-09-24). */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-explist .ags-exp__cta .elementor-button-wrapper {
    justify-content: center; text-align: center;
  }
  /* VIP accordion (Simon 2026-09-21): match the desktop's warmer greige on mobile too — it was
     falling back to the near-white accent (#f7f4f2) because the desktop greige rule is ≥1024-gated. */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-accordion.e-con { background-color: #eee6e0; }

  /* §CONTACT — mobile. The form column is already width-agnostic (max-width:720px caps well above
     any phone width, so it's effectively 100% here); just tighten the hero title + keep the field
     type comfortable. box-sizing:border-box (set above) keeps the full-width inputs off the x-axis. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-contact__h .elementor-heading-title {
    font-size: max(30px, min(9vw, 44px));
    font-size: clamp(30px, 9vw, 44px); line-height: 1.04;
  }
  .ags-c-form .wpcf7-submit,
  .ags-c-form input[type="submit"] { width: 100%; }

  /* §FOOTER — POLISH. The contact-row email (row 2) carried `overflow-wrap:anywhere` (set in the
     ≤700 block) which split it mid-word ("…ageshiojapan.co / m"). The mockup fits it on ONE line.
     Same selector + specificity as that rule, but LATER (both media queries match at ≤700), so this
     wins: shrink the email a touch + forbid the mid-word break. SHARED (interior footer). */
  .ags-c-footer > .e-con:nth-child(2) a[href^="mailto"] {
    word-wrap: normal;
    overflow-wrap: normal; word-break: keep-all; font-size: max(11px, min(3.2vw, 13px)); font-size: clamp(11px, 3.2vw, 13px);
  }

  /* ==========================================================================
     PER-PAGE MOBILE DELTAS (≤1023). Portable slug scope only. Each block carries
     ONLY page-specific deltas; everything shared across pages is above.
     ========================================================================== */

  /* ---- Okinawan Group Tours (reference) -------------------------------------
     §3 kumite square — the shared §3 base sets the 1:1 cover-crop; this only places
     the crop (grayscale is set ungated in the finalize). */
  body.ags-page-okinawan-group-tours .ags-c-photo__img img { -o-object-position: center 28%; object-position: center 28%; }

  /* ---- VIP Karate Retreat ---------------------------------------------------
     Both rules exist only because VIP's wired photos have DIFFERENT source
     aspects than the other pages'. Photos are already wired; we only crop. */
  /* §3 feature — VIP's source is PORTRAIT (2000×3000); the shared 1:1 cover-crop
     squares it; just place the crop to keep the subject. */
  body.ags-page-vip-karate-retreat .ags-c-photo__img img { -o-object-position: center 45%; object-position: center 45%; }
  /* §9 accordion aside photo — VIP's source is LANDSCAPE (1024×682); crop taller
     toward the near-square mockup proportion without over-cropping the group. */
  body.ags-page-vip-karate-retreat .ags-c-accordion__aside .ags-c-accordion__photo img {
    /* POLISH: mockup shows a near-square box; 5/4 read too landscape. VIP-only delta. */
    width: 100%; height: auto; aspect-ratio: 1 / 1; -o-object-fit: cover; object-fit: cover; -o-object-position: center 45%; object-position: center 45%;
  }

  /* ---- Karate History Tour --------------------------------------------------
     TWO page-unique sections with NO generic reflow (they exist on no other
     interior page): §6 .ags-c-locations (RED title band) and §7 .ags-masters
     (kanji photo filmstrip). Their desktop layout is gated ≥1024, so at ≤1023
     they fall to Elementor flex defaults and break (ballooned mismatched cards
     + horizontal overflow). Reflow both to clean vertical stacks. */
  /* §filmstrip → the Authentic-Lineage mobile CAROUSEL (Simon 2026-09-24). ageshio-masters-mobile.js
     runs SITE-WIDE and already turns .ags-masters__row into a photo carousel (adds .is-carousel + a
     progress bar + directional slide via --ags-off); the homepage's carousel CSS is body.ags-redesign-
     scoped, so mirror it here for the History page. ONE knob --m-card-w sizes card + bar. Each card shows
     a kanji (会館…) bottom-left + place NAME + LOCATION over the photo. (Was a plain vertical stack.) */
  body.ags-page-karate-history-tour .ags-masters {
    padding-top: 4px;
    padding-bottom: 40px;
    --m-card-w: min(83vw, calc(var(--vh, 1vh) * 60), 560px);
    --m-card-h: calc(var(--m-card-w) * 1.05);
  }
  /* History's .ags-masters__row is e-con-FULL (no .e-con-inner) unlike the homepage's boxed row, so the
     ROW ITSELF is the card frame here (the JS uses `.e-con-inner || row` → lands on the row too). Centre
     it at the card width; it becomes the fixed-height rounded viewport in carousel mode below. */
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-masters__row {
    display: block; width: var(--m-card-w); max-width: 100%; height: auto; margin-left: auto; margin-right: auto;
    padding-left: 0;
    padding-right: 0; position: relative; box-sizing: border-box;
  }
  /* Portrait fill + bottom scrim on EVERY card (any can be the visible one in carousel mode). */
  body.ags-page-karate-history-tour .ags-masters .ags-master { display: none; }
  body.ags-page-karate-history-tour .ags-masters .ags-master .ags-portrait,
  body.ags-page-karate-history-tour .ags-masters .ags-master .ags-portrait .elementor-widget-container,
  body.ags-page-karate-history-tour .ags-masters .ags-master .ags-portrait .elementor-image,
  body.ags-page-karate-history-tour .ags-masters .ags-master .ags-portrait figure,
  body.ags-page-karate-history-tour .ags-masters .ags-master .ags-portrait img {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; margin: 0; -o-object-fit: cover; object-fit: cover; aspect-ratio: auto;
  }
  body.ags-page-karate-history-tour .ags-masters .ags-master::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 45%;
    background: linear-gradient(to top, rgba(17,16,16,0.85), rgba(17,16,16,0)); z-index: 1; pointer-events: none;
  }
  /* Fallback (JS off / bailed): the single server-featured card fills the frame in flow. */
  body.ags-page-karate-history-tour .ags-masters .ags-master.is-active {
    display: block; position: relative; width: 100%; height: var(--m-card-h); overflow: hidden; border-radius: 10px;
  }
  /* Carousel mode (JS present): the ROW becomes the fixed-height rounded viewport; cards stack absolute + slide. */
  body.ags-page-karate-history-tour .ags-masters__row.is-carousel {
    height: var(--m-card-h); position: relative; overflow: hidden; border-radius: 10px;
    touch-action: pan-y; -webkit-user-select: none; -moz-user-select: none; user-select: none;
  }
  body.ags-page-karate-history-tour .ags-masters:has(.ags-masters__row.is-carousel),
  body.ags-page-karate-history-tour .ags-masters:has(.ags-masters__row.is-carousel) * { transition: none !important; }
  body.ags-page-karate-history-tour .ags-masters__row.is-carousel .ags-master {
    display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; overflow: hidden;
    border-radius: 10px; z-index: 1;
    transform: translateX(calc(var(--ags-off, 2) * 100%)) !important; transition: transform .5s ease !important;
  }
  body.ags-page-karate-history-tour .ags-masters__row.is-carousel .ags-master.is-active { z-index: 2; }
  body.ags-page-karate-history-tour .ags-masters__row.is-carousel .ags-master img { -webkit-user-drag: none; }
  /* Card overlay: kanji (会館) bottom-left; NAME + LOCATION row along the bottom (cream, over the scrim). */
  body.ags-page-karate-history-tour .ags-masters .ags-master__caption {
    position: absolute; left: 16px; right: 16px; bottom: 16px; width: auto; z-index: 2; padding: 0;
    display: flex; flex-direction: row; align-items: flex-end; justify-content: space-between; gap: 8px;
  }
  /* Name clearance = the kanji block width (N chars ≈ N em ≈ N·--m-rank) + a 14px gap, so the name never
     collides with the bottom-left kanji whatever its size. data-rank-len is set at runtime by
     ageshio-masters.js (site-wide, every width). */
  body.ags-page-karate-history-tour .ags-master[data-rank-len="1"] .ags-master__caption { padding-left: calc(var(--m-rank) + 14px); }
  body.ags-page-karate-history-tour .ags-master[data-rank-len="2"] .ags-master__caption { padding-left: calc(2 * var(--m-rank) + 14px); }
  body.ags-page-karate-history-tour .ags-masters .ags-master__rank {
    position: absolute; z-index: 2; left: 16px; right: auto; bottom: 16px; text-align: left; margin: 0; opacity: 1; pointer-events: none;
  }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-master__rank .elementor-heading-title {
    line-height: 1; color: var(--ags-cream); font-size: var(--m-rank);
  }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-master__name .elementor-heading-title {
    white-space: nowrap; line-height: 1; font-size: 15px; color: var(--ags-cream);
  }
  /* LOCATION (e.g. Tomigusuku) = small plain cream text bottom-right — NOT the red role-pill the homepage
     masters use. */
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-master__title .elementor-heading-title {
    font-size: 11px; white-space: nowrap; line-height: 1; margin: 0; color: var(--ags-cream);
  }
  /* Progress bar riding the card's top edge (shared .ags-ind track/fill styling is global). */
  body.ags-page-karate-history-tour .ags-masters__bar {
    position: absolute; z-index: 3; left: 50%; top: 14px; transform: translateX(-50%); width: calc(var(--m-card-w) - 28px);
  }
  /* §6 LOCATIONS → the Authentic-Lineage stack (Simon 2026-09-24). The TITLE + DESCRIPTION live together
     in the titleband section; the card carousel + 場所 kanji live in the SEPARATE .ags-masters section
     that follows. To get the reference order (title → card → 場所 → description) the card must slot
     BETWEEN the title and the description — so flatten the titleband + its inner wrappers with
     display:contents (promoting the title __h and description __caption to direct flex items of the page
     wrapper), make the wrapper a flex column, then flex-order the whole run. (Safe: the full-width
     sections have no vertical margins to collapse, so flex-column stacks them exactly like block flow.) */
  body.ags-page-karate-history-tour .elementor[data-elementor-type="wp-page"] { display: flex; flex-direction: column; }
  /* Cream base (accent global) so the FLATTENED title + description show the same colour as the section
     bands around them — flattening the titleband dropped its own cream band, which left them on the white
     body → the "different background" seam. Also hoist the card knob to the body so the title can align to
     the card. */
  body.ags-page-karate-history-tour {
    background-color: var(--e-global-color-accent, #F7F4F2);
    --m-card-w: min(83vw, calc(var(--vh, 1vh) * 60), 560px);
    --m-card-h: calc(var(--m-card-w) * 1.05);
    --m-rank: calc(0.1 * var(--m-card-w));   /* card kanji size (~32px @390) — the name clearance derives from it */
  }
  body.ags-page-karate-history-tour .ags-c-titleband.ags-c-locations-title,
  body.ags-page-karate-history-tour .ags-c-locations__row,
  body.ags-page-karate-history-tour .ags-c-locations__left,
  body.ags-page-karate-history-tour .ags-c-locations__right,
  body.ags-page-karate-history-tour .ags-c-locations__capbtn { display: contents; }
  /* Title — LEFT-aligned + spaced like the Authentic Lineage title (hugging the gutter, generous room
     above/below), NOT the giant centred block it fell to. Left edge lines up with the centred card. */
  body.ags-page-karate-history-tour .ags-c-locations__h {
    order: 1; width: 100%; box-sizing: border-box;
    /* Gap below the title uses PADDING-bottom, not margin: Elementor zeroes margin-block-end on these
       heading/text widgets (verified — margin-top applies, margin-bottom is forced to 0). */
    padding: 0 16px 40px calc((100vw - var(--m-card-w)) / 2);
    margin: calc(var(--vh, 1vh) * 6) 0 0;
  }
  body.ags-page-karate-history-tour .ags-masters              { order: 2; }   /* card carousel + 場所 kanji */
  body.ags-page-karate-history-tour .ags-c-locations__caption { order: 3; }
  body.ags-page-karate-history-tour .ags-c-overlay.ags-c-closing { order: 4; }
  /* Closing "continue your journey" overlay — its 旅行/後退 kanji are a custom __kanji block that the
     shared overlay grid auto-placed into the LEFT column (all its own styling is ≥1024-gated). Span it
     full-width + centre the kanji horizontally on mobile (Simon 2026-09-24). */
  body.ags-page-karate-history-tour .ags-c-closing__kanji {
    grid-column: 1 / -1; align-items: center; text-align: center; width: 100%;
  }
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow { align-items: center; }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-closing__kanji .ags-c-eyebrow .elementor-heading-title { text-align: center; }
  body.ags-page-karate-history-tour .ags-c-cta                { order: 5; }
  body.ags-page-karate-history-tour .ags-c-footer             { order: 6; }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-locations__h .elementor-heading-title {
    text-align: left; color: var(--ags-red);
  }
  /* Description: a centred readable column below the 場所 kanji, DARK text (mobile never set a colour, so
     it fell to the light Kit body global → looked washed-out grey). */
  body.ags-page-karate-history-tour .ags-c-locations__caption {
    /* bottom gap via PADDING (Elementor zeroes margin-block-end on this text widget). */
    width: 100%; max-width: 34em; margin: 16px auto 0; padding: 0 24px 56px; box-sizing: border-box;
  }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-locations__caption .elementor-widget-container,
  body.ags-page-karate-history-tour .ags-c-locations__caption p { text-align: center; color: #111010; }
  /* 場所 (Basho) marker sits BELOW the card inside .ags-masters — centre it + maroon (its desktop styling
     is ≥1024-gated, and it defaults to the flex-start eyebrow position otherwise). */
  body.ags-page-karate-history-tour .ags-c-locations__marker {
    align-items: center; text-align: center; margin-top: 22px; width: 100%;
  }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-locations__marker .elementor-heading-title {
    text-align: center; color: var(--ags-red);
  }

  /* ---- Karate Experiences ---------------------------------------------------
     §11 Experience List exists ONLY on this page. The shared reflow INTENDS the
     whole section centred, but three desktop per-element alignments win (eyebrow
     + H2 flush-left, lede + CTA flush-right). Re-centre each at page-type scope. */
  body.ags-page-karate-experiences [data-elementor-type="wp-page"] .ags-c-explist__eyebrow {
    align-items: center; text-align: center;
  }
  body.ags-page-karate-experiences [data-elementor-type="wp-page"] .ags-c-explist__eyebrow .elementor-heading-title,
  body.ags-page-karate-experiences [data-elementor-type="wp-page"] .ags-c-explist__h .elementor-heading-title {
    text-align: center;
  }
  body.ags-page-karate-experiences [data-elementor-type="wp-page"] .ags-exp__lede { align-items: center; }
  body.ags-page-karate-experiences [data-elementor-type="wp-page"] .ags-exp__introtext .elementor-widget-container {
    text-align: center;
  }
  body.ags-page-karate-experiences [data-elementor-type="wp-page"] .ags-exp__cta .elementor-button-wrapper {
    justify-content: center; text-align: center;
  }
  /* §8 overlay zen-garden (Simon's real photo, 2026-08-17): mobile already has the shared
     .ags-c-overlay__photo::after scrim rgba(17,16,16,.58); the new garden's bright gravel still needs
     a mild brightness pull so the white bullets/heading over the mid-field stay legible. Milder than
     the desktop 0.42 (no scrim there) since here it stacks with the scrim. */
  body.ags-page-karate-experiences .ags-c-overlay__photo img { filter: grayscale(1) brightness(0.78); }

  /* ---- About Ageshio ---------------------------------------------------------
     14 sections total (docs/mobile/about-blog-mobile-sections.md — 13 PATTERN, 1
     NOVEL). §2 marquee / §5+§11 statement / §10 overlay / §12 accordion are the
     SAME shared components the rules above already reflow unconditionally — zero
     new CSS for those (confirmed against the worklist). Everything below is
     About's own ags-c-* hooks: §1, §3, §4, §6 (NOVEL), §7, §8+§9, §13, §14. */

  /* §1 PHOTO HERO — full-bleed photo + centred kanji eyebrow. Same composition at
     every width (no stacking decision needed), but the desktop implementation
     (components.css ~7104) is entirely gated ≥1024, so it needs its own mobile
     twin. Elementor's own band() padding_mobile (56/20, stray on every section
     built with the homepage builder) would otherwise break the full-bleed photo. */
  body.ags-page-about .ags-c-photohero { padding: 0; }
  body.ags-page-about .ags-c-photohero__stage { position: relative; overflow: hidden; height: calc(var(--vh, 1vh) * 100); }
  body.ags-page-about .ags-c-photohero__photo,
  body.ags-page-about .ags-c-photohero__photo .elementor-widget-container,
  body.ags-page-about .ags-c-photohero__photo figure {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; margin: 0;
  }
  body.ags-page-about .ags-c-photohero__photo img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; }
  body.ags-page-about .ags-c-photohero__stage::after {
    content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; pointer-events: none;
    background: linear-gradient(rgba(17,16,16,.42), rgba(17,16,16,.42));
  }
  body.ags-page-about .ags-c-photohero__eyebrow {
    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    z-index: 2; width: -webkit-max-content; width: -moz-max-content; width: max-content; margin: 0; text-align: center;
  }
  /* Kanji/label sizing — same clamp convention as every other interior kanji eyebrow
     (~5827); the shared .ags-c-eyebrow centring rule (~5833) already applies since
     this element carries both classes. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-photohero__eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: max(48px, min(15vw, 62px));
    font-size: clamp(48px, 15vw, 62px); line-height: 1;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-photohero__eyebrow .elementor-widget-heading:last-child .elementor-heading-title {
    font-size: max(14px, min(4vw, 18px));
    font-size: clamp(14px, 4vw, 18px);
  }
  /* Flanking hero taglines are a DESKTOP composition (they flank the centred kanji on the edge
     guide); on the narrow mobile hero they'd crowd/overlap it, so hide them here. The centred
     eyebrow carries the hero on mobile. (Revisit if a mobile stack is wanted.) */
  body.ags-page-about .ags-c-photohero__tag { display: none; }

  /* §3 INTRO — two-column paragraph → single column stack (plain text, no
     hierarchy conflict). padding-inline: Elementor's band() padding only reaches
     the SIDES at <768 (padding_mobile); 768–1023 falls back to the desktop 0-left/
     right base — same gap the shared titleband/numgrid fix above (~5981) patches
     for those two sections; fix it here for About's own sections too. */
  body.ags-page-about .ags-c-intro { padding-left: max(20px, min(5vw, 32px)); padding-right: max(20px, min(5vw, 32px)); padding-left: max(20px, min(5vw, 32px)); padding-left: clamp(20px, 5vw, 32px); padding-right: max(20px, min(5vw, 32px)); padding-right: clamp(20px, 5vw, 32px); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-intro__row { flex-direction: column; gap: max(22px, min(6vw, 30px)); gap: clamp(22px, 6vw, 30px); }
  body.ags-page-about .ags-c-intro__col { width: 100%; max-width: 46em; flex-basis: auto; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-intro__para .elementor-widget-container {
    font-family: 'Red Hat Display', sans-serif; font-weight: 400;   /* match desktop — body sans 400 */
    font-size: max(15px, min(4.2vw, 17px));
    font-size: clamp(15px, 4.2vw, 17px); line-height: 1.55;
  }

  /* §4 APPROACH — right-aligned title (already right-aligned at the widget level,
     width-agnostic) stays above; the 4 photo/number/caption cards become a
     horizontal-scroll strip — same numgrid mechanics as §7/§13 below, re-authored
     here since these are ags-c-approach__* hooks, not ags-c-numgrid. Row is
     row-flex by default already (no direction fight needed), only needs the
     scroll/snap/sizing treatment. */
  body.ags-page-about .ags-c-approach { padding-left: max(20px, min(5vw, 32px)); padding-right: max(20px, min(5vw, 32px)); padding-left: max(20px, min(5vw, 32px)); padding-left: clamp(20px, 5vw, 32px); padding-right: max(20px, min(5vw, 32px)); padding-right: clamp(20px, 5vw, 32px); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__h .elementor-heading-title {
    font-size: max(30px, min(9vw, 40px));
    font-size: clamp(30px, 9vw, 40px); line-height: 1;
  }
  /* [data-elementor-type="wp-page"] bump: .ags-c-approach__grid is one of the "clear it at
     every level" reset targets (~7075, unscoped by width, padding:0) which sits LATER in the
     file than this block — same specificity would let that later, unscoped rule win the
     trailing scroll-pad back to 0. The extra attribute selector breaks the tie in our favour
     regardless of source order (same trick used throughout this file for exactly this trap). */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__grid {
    flex-wrap: nowrap; margin-top: max(20px, min(5vw, 28px)); margin-top: clamp(20px, 5vw, 28px);
    overflow-x: auto; -webkit-overflow-scrolling: touch; scroll-snap-type: x proximity;
    gap: max(16px, min(4.5vw, 24px));
    gap: clamp(16px, 4.5vw, 24px); padding-left: 0; padding-right: 24px; scrollbar-width: none;
  }
  body.ags-page-about .ags-c-approach__grid::-webkit-scrollbar { display: none; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__grid > .ags-c-approach__item {
    flex: 0 0 max(180px, min(55vw, 230px));
    flex: 0 0 clamp(180px, 55vw, 230px); width: max(180px, min(55vw, 230px)); width: clamp(180px, 55vw, 230px); max-width: none;
    scroll-snap-align: start;
  }
  body.ags-page-about .ags-c-approach__item { gap: 8px; }
  body.ags-page-about .ags-c-approach__img.ags-c-ph,
  body.ags-page-about .ags-c-approach__img:not(.ags-c-ph) { aspect-ratio: 1 / 0.78; width: 100%; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__num .elementor-heading-title {
    font-size: max(17px, min(4.6vw, 20px));
    font-size: clamp(17px, 4.6vw, 20px); font-weight: 600; line-height: 1;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__label .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(14px, min(3.8vw, 16px));
    font-size: clamp(14px, 3.8vw, 16px); line-height: 1.3; max-width: none;
  }

  /* §5+§11 STATEMENT (Mission + Our Focus) — otherwise the SHARED, unmodified .ags-c-statement
     mobile rules (per the file note above); ONE narrow exception. The shared headline's
     `max-width:9.5em` (~5907) was solved for the EN 8-line wrap and assumes every word fits that
     column. German ("Immersionsprogramme.") and Dutch ("Groepskaratereizen") each have a single
     unbreakable compound word wider than the column, so the headline's own scrollWidth exceeded its
     box and bled into <body>'s scrollWidth (measured: DE headline 461px content in a 315px column) —
     real page-width overflow, invisible only because of the site's blanket `overflow-x:clip` safety
     net (content was still off-screen, not actually contained). Scoped to About (the longest
     translated headline of any `.ags-c-statement` consumer); allow the one long word to break rather
     than widen the box, which would fight the EN 8-line reference this column was tuned to. */
  body.ags-page-about .ags-c-statement__headline .elementor-heading-title {
    word-wrap: break-word;
    overflow-wrap: break-word; word-break: break-word;
  }

  /* §6 WHAT WE DO (NOVEL — Simon's locked decision, 2026-09-20): TITLE-LED, items
     SWIPE. The full-bleed dark photo stays the background of a photo-hero-style
     band at the TOP: kanji eyebrow 旅行/About → the giant "What we do" title → the
     caption, all centred over a scrim. The 5 numbered items move OUT of the
     desktop-only absolute cqw stage into normal flow BELOW, as a numgrid-style
     horizontal-scroll strip — NOT buried at the bottom like the desktop's corner-
     anchored composition. DOM order is photo→items→eyebrow→caption→title; `order`
     reflows it to the locked reading order (eyebrow, title, caption, then items). */
  body.ags-page-about .ags-c-whatwedo { padding: 0; }
  /* CONTENT-HEIGHT, not a 100svh floor: eyebrow→title→caption→items is well short
     of a full viewport, and a forced floor would leave a dead stretch of empty
     photo below the items row (measured 380px+ at 390x900) before the next
     section — the same "floor manufactures a gap" trap the desktop rhythm pass
     (~7044) hit elsewhere on this page. The photo (position:absolute inset:0)
     still fills exactly whatever height the content drives, so it stays full-
     bleed either way — it just isn't force-stretched past its content. */
  body.ags-page-about .ags-c-whatwedo__stage {
    position: relative; overflow: hidden; min-height: 0; height: auto;
    display: flex; flex-direction: column; padding: 0; margin: 0;
  }
  body.ags-page-about .ags-c-whatwedo__photo,
  body.ags-page-about .ags-c-whatwedo__photo .elementor-widget-container,
  body.ags-page-about .ags-c-whatwedo__photo figure {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; margin: 0; z-index: 0; order: 0;
  }
  body.ags-page-about .ags-c-whatwedo__photo img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; }
  body.ags-page-about .ags-c-whatwedo__stage::after {
    content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; pointer-events: none;
    background: rgba(17,16,16,.55);
  }
  /* Everything else sits above the scrim, reordered to the locked reading order. */
  body.ags-page-about .ags-c-whatwedo__stage > :not(.ags-c-whatwedo__photo) { position: relative; z-index: 2; }
  body.ags-page-about .ags-c-whatwedo__eyebrow {
    order: 1; margin-top: max(56px, min(15vw, 84px)); margin-top: clamp(56px, 15vw, 84px); padding-left: max(20px, min(5vw, 28px)); padding-right: max(20px, min(5vw, 28px)); padding-left: max(20px, min(5vw, 28px)); padding-left: clamp(20px, 5vw, 28px); padding-right: max(20px, min(5vw, 28px)); padding-right: clamp(20px, 5vw, 28px);
  }
  body.ags-page-about .ags-c-whatwedo__title {
    order: 2; margin-top: max(10px, min(3vw, 16px)); margin-top: clamp(10px, 3vw, 16px); padding-left: max(20px, min(5vw, 28px)); padding-right: max(20px, min(5vw, 28px)); padding-left: max(20px, min(5vw, 28px)); padding-left: clamp(20px, 5vw, 28px); padding-right: max(20px, min(5vw, 28px)); padding-right: clamp(20px, 5vw, 28px);
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__title .elementor-heading-title {
    font-size: max(40px, min(13vw, 60px));
    font-size: clamp(40px, 13vw, 60px); line-height: 1; text-align: center; margin: 0;
  }
  body.ags-page-about .ags-c-whatwedo__caption {
    order: 3; margin-top: max(12px, min(4vw, 18px)); margin-top: clamp(12px, 4vw, 18px); padding-left: max(20px, min(5vw, 28px)); padding-right: max(20px, min(5vw, 28px)); padding-left: max(20px, min(5vw, 28px)); padding-left: clamp(20px, 5vw, 28px); padding-right: max(20px, min(5vw, 28px)); padding-right: clamp(20px, 5vw, 28px);
    max-width: 32em; margin-left: auto; margin-right: auto;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__caption .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400; text-align: center;
    font-size: max(14px, min(4vw, 16px));
    font-size: clamp(14px, 4vw, 16px); line-height: 1.4;
  }
  /* [data-elementor-type="wp-page"] bump: .ags-c-whatwedo__items is a "clear it at every
     level" reset target too (~7075, unscoped, padding:0, later in the file) — same trap
     and same fix as the approach grid above. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__items {
    order: 4; width: 100%; margin-top: max(28px, min(7vw, 36px)); margin-top: clamp(28px, 7vw, 36px); margin-bottom: max(40px, min(10vw, 56px)); margin-bottom: clamp(40px, 10vw, 56px);
    flex-wrap: nowrap; gap: max(18px, min(5vw, 26px)); gap: clamp(18px, 5vw, 26px);
    overflow-x: auto; -webkit-overflow-scrolling: touch; scroll-snap-type: x proximity;
    padding-left: max(20px, min(5vw, 28px));
    padding-left: max(20px, min(5vw, 28px));
    padding-left: clamp(20px, 5vw, 28px);
    padding-right: 28px; scrollbar-width: none;
  }
  body.ags-page-about .ags-c-whatwedo__items::-webkit-scrollbar { display: none; }
  body.ags-page-about .ags-c-whatwedo__item {
    flex: 0 0 max(148px, min(45vw, 186px));
    flex: 0 0 clamp(148px, 45vw, 186px); width: max(148px, min(45vw, 186px)); width: clamp(148px, 45vw, 186px); max-width: none;
    scroll-snap-align: start; gap: 6px;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__num .elementor-heading-title {
    font-size: max(18px, min(5vw, 22px));
    font-size: clamp(18px, 5vw, 22px); font-weight: 600; line-height: 1;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__label .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(13px, min(3.6vw, 15px));
    font-size: clamp(13px, 3.6vw, 15px); line-height: 1.3;
  }

  /* §7 BASED IN OKINAWA — two columns → stack, text first then the map (same
     "list-then-aside, order:2 on the graphic column" idiom as the accordion aside
     ~6148). Row is row-flex by default, so the direction flip needs the page-type
     scope bump (same trap as every other row→column flip on this page). */
  body.ags-page-about .ags-c-mapband { padding-left: max(20px, min(5vw, 32px)); padding-right: max(20px, min(5vw, 32px)); padding-left: max(20px, min(5vw, 32px)); padding-left: clamp(20px, 5vw, 32px); padding-right: max(20px, min(5vw, 32px)); padding-right: clamp(20px, 5vw, 32px); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-mapband__row { flex-direction: column; gap: max(28px, min(7vw, 36px)); gap: clamp(28px, 7vw, 36px); }
  body.ags-page-about .ags-c-mapband__left { width: 100%; flex-basis: auto; order: 1; gap: max(16px, min(4vw, 22px)); gap: clamp(16px, 4vw, 22px); }
  body.ags-page-about .ags-c-mapband__right { width: 100%; flex-basis: auto; order: 2; }
  body.ags-page-about .ags-c-mapband__map.ags-c-ph { width: 100%; aspect-ratio: 748 / 633; }
  body.ags-page-about .ags-c-mapband__map:not(.ags-c-ph) { width: 100%; }
  body.ags-page-about .ags-c-mapband__map:not(.ags-c-ph) img { width: 100%; height: auto; margin: 0; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-mapband__intro .elementor-widget-container,
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-mapband__list .elementor-widget-container,
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-mapband__tail .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(15px, min(4vw, 17px));
    font-size: clamp(15px, 4vw, 17px); line-height: 1.45;
  }
  body.ags-page-about .ags-c-mapband__intro,
  body.ags-page-about .ags-c-mapband__list,
  body.ags-page-about .ags-c-mapband__tail { max-width: 42em; }
  body.ags-page-about .ags-c-mapband__list ul { margin: 0.5em 0; padding-left: 1.1em; }
  body.ags-page-about .ags-c-mapband__title { margin-top: max(6px, min(2vw, 12px)); margin-top: clamp(6px, 2vw, 12px); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-mapband__title .elementor-heading-title {
    font-size: max(30px, min(9.5vw, 40px));
    font-size: clamp(30px, 9.5vw, 40px); line-height: 1.08; margin: 0;
  }

  /* §8 CONNECTION TO GRAND MASTERS — mirrors the homepage's own Authentic Lineage
     mobile arrangement (title / intro / kanji-legend), adapted to About's own
     ags-c-masters-intro__* hooks: left-aligned 2-line title above, centred intro +
     button below (homepage ~4480), the 師範/師 legend side by side underneath
     (already row-flex by default — untouched, matches homepage ~4643). */
  body.ags-page-about .ags-c-masters-intro { padding-left: max(20px, min(5vw, 32px)); padding-right: max(20px, min(5vw, 32px)); padding-left: max(20px, min(5vw, 32px)); padding-left: clamp(20px, 5vw, 32px); padding-right: max(20px, min(5vw, 32px)); padding-right: clamp(20px, 5vw, 32px); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__row { flex-direction: column; gap: max(20px, min(5vw, 28px)); gap: clamp(20px, 5vw, 28px); }
  body.ags-page-about .ags-c-masters-intro__h { width: 100%; flex-basis: auto; }
  /* Same "box-clamp in em" 2-line trick as the homepage title (~4477): "Connection
     to" / "Grand Masters" fits a 10em box at any font-size since em scales with it. */
  /* Font size measured against the actual column width (canvas text-metrics, 390-1023):
     "Grand Masters" must clear the box at every width or the 2-line wrap (matching the
     homepage's "Authentic Lineage") slips to 3 lines — verified 30-42px holds it. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__h .elementor-heading-title {
    font-size: max(30px, min(9.2vw, 42px));
    font-size: clamp(30px, 9.2vw, 42px); line-height: 1.05; max-width: 10em; margin: 0; text-align: left;
  }
  body.ags-page-about .ags-c-masters-intro__body { width: 100%; flex-basis: auto; align-items: center; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__text .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400; text-align: center;
    font-size: max(14px, min(4vw, 16px));
    font-size: clamp(14px, 4vw, 16px); line-height: 1.45; max-width: 36em;
  }
  body.ags-page-about .ags-c-masters-intro__btn { align-self: center; }
  body.ags-page-about .ags-c-masters-intro__eyebrows { justify-content: center; margin-top: max(16px, min(4vw, 22px)); margin-top: clamp(16px, 4vw, 22px); gap: max(24px, min(6vw, 36px)); gap: clamp(24px, 6vw, 36px); }
  body.ags-page-about .ags-c-masters-intro__eb { width: -webkit-max-content; width: -moz-max-content; width: max-content; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__eb .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: max(44px, min(13.5vw, 72px));
    font-size: clamp(44px, 13.5vw, 72px); line-height: 1;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__eb .elementor-widget-heading:last-child .elementor-heading-title {
    font-size: max(12px, min(3.4vw, 14px));
    font-size: clamp(12px, 3.4vw, 14px);
  }

  /* §9 MASTERS FILMSTRIP — static CSS-only port (no masters JS on interior pages),
     same treatment as Karate History Tour's own static override (~6257, ported
     verbatim to this page's scope): a uniform full-width portrait card stack,
     fixed height (not aspect-ratio) so the tall source photo can't balloon it. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-masters__row {
    flex-direction: column; flex-wrap: nowrap; align-items: stretch;
    gap: 14px; padding-left: max(20px, min(5vw, 32px)); padding-right: max(20px, min(5vw, 32px)); padding-left: max(20px, min(5vw, 32px)); padding-left: clamp(20px, 5vw, 32px); padding-right: max(20px, min(5vw, 32px)); padding-right: clamp(20px, 5vw, 32px); box-sizing: border-box;
    width: 100% !important; max-width: 100% !important; height: auto !important;
  }
  body.ags-page-about .ags-masters .ags-master.e-con {
    flex: 0 0 auto; width: 100%; height: max(300px, min(82vw, 380px)); height: clamp(300px, 82vw, 380px);
    aspect-ratio: auto; overflow: hidden;
  }
  body.ags-page-about .ags-masters .ags-master .ags-portrait,
  body.ags-page-about .ags-masters .ags-master .ags-portrait .elementor-widget-container,
  body.ags-page-about .ags-masters .ags-master .ags-portrait .elementor-image,
  body.ags-page-about .ags-masters .ags-master .ags-portrait figure,
  body.ags-page-about .ags-masters .ags-master .ags-portrait img {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; margin: 0; -o-object-fit: cover; object-fit: cover;
  }
  body.ags-page-about .ags-masters .ags-master.is-active { flex-grow: 0; }
  body.ags-page-about .ags-masters .ags-master__rank.elementor-widget-heading {
    position: absolute; left: 16px; right: auto; bottom: 14px; text-align: left; z-index: 2; margin: 0;
  }
  body.ags-page-about .ags-masters .ags-master__caption.e-con {
    position: absolute; left: 16px; right: 16px; bottom: 14px; width: auto; z-index: 2;
  }
  body.ags-page-about .ags-masters .ags-master.is-active .ags-master__rank.elementor-widget-heading { opacity: 0; }
  body.ags-page-about .ags-masters { padding-top: max(16px, min(4vw, 24px)); padding-top: max(16px, min(4vw, 24px)); padding-top: clamp(16px, 4vw, 24px); padding-bottom: 40px; }

  /* §12 ACCORDION (blue variant) — shared reflow already applies; only the About-
     specific divider-legibility tweak (desktop ~7832) needs its mobile twin so the
     divider doesn't read muddy against the blue band here too. */
  body.ags-page-about .ags-c-accordion--blue .ags-c-accordion__item::after { background: rgba(255, 255, 255, .4); }

  /* ============================================================================
     /about mobile refinements (Simon 2026-09-24). Marquee overlap, Approach + Masters
     swipeable carousels, mission-statement cream, What-we-do slide arrow, accordion photo. */

  /* 1 — MARQUEE overlays the hero photo bottom on mobile (Simon 2026-09-24 r2) — like the desktop /about.
     The earlier negative-margin GUESS left gaps because it didn't match the band's real height. FIX: give
     the band an EXPLICIT height and set margin-top = exactly -that height → it overlaps precisely its own
     height onto the photohero with NO gap, stable through the scrolling text. Transparent bg so the cream
     text + crest read over the photo; z-index:3 above the hero scrim. Crest keeps nav-icon-red.svg. */
  body.ags-page-about .ags-c-marquee {
    position: relative; z-index: 3; background: transparent;
    /* Overlay via margin-top ONLY (like the working desktop recipe) — do NOT touch the marquee's internal
       flex layout, or the crest/text alignment breaks and repeats can land at different vertical levels.
       margin-top = -(natural band height) = -(mobile crest clamp + 25px padding-bottom + ~20px stray e-con
       padding). Both the crest clamp and this formula track together across widths, so it overlaps exactly
       its own height onto the photohero with no gap, and every repeat (crest + text) stays on one level. */
    margin-top: calc(-1 * (max(48px, min(14.5vw, 66px)) + 45px));
    margin-top: calc(-1 * (clamp(48px, 14.5vw, 66px) + 45px));
  }

  /* 3 — MISSION STATEMENTS (maroon): cream eyebrows/headline/link (mobile shared rules force black +
     hide the left label — mirror the desktop maroon override ~9930 and un-hide both eyebrows). */
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__label--l { display: block; }
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__label .elementor-widget-container,
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__headline .elementor-heading-title,
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__link .elementor-button,
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__link .elementor-button:hover { color: var(--ags-cream, #F7F4F2); }

  /* 4 — WHAT WE DO: white swipe-cue chevron (Our Approach's chevron, but WHITE since this section sits on
     a dark photo). MUST be ::before, NOT ::after — ::after is the dark SCRIM (~7581); the fork put the
     chevron on ::after too, which collided (mispositioned the arrow + clobbered the scrim). As a flex
     ::before with order:5 it trails the items row; align-self:flex-end + right content-margin = bottom-right. */
  body.ags-page-about .ags-c-whatwedo__stage::before {
    content: ""; order: 5; flex: 0 0 auto; position: relative; z-index: 2; align-self: flex-end;
    width: max(48px, min(14vw, 64px));
    width: clamp(48px, 14vw, 64px); height: 12px;
    margin: max(8px, min(2.5vw, 12px)) max(20px, min(5vw, 28px)) max(40px, min(10vw, 56px)) 0;
    margin: max(8px, min(2.5vw, 12px)) max(20px, min(5vw, 28px)) clamp(40px, 10vw, 56px) 0;
    margin: max(8px, min(2.5vw, 12px)) max(40px, min(10vw, 56px)) max(40px, min(10vw, 56px)) 0;
    margin: max(8px, min(2.5vw, 12px)) max(40px, min(10vw, 56px)) clamp(40px, 10vw, 56px) 0;
    margin: max(8px, min(2.5vw, 12px)) clamp(20px, 5vw, 28px) clamp(40px, 10vw, 56px) 0;
    margin: max(20px, min(5vw, 28px)) max(20px, min(5vw, 28px)) max(40px, min(10vw, 56px)) 0;
    margin: max(20px, min(5vw, 28px)) max(20px, min(5vw, 28px)) clamp(40px, 10vw, 56px) 0;
    margin: max(20px, min(5vw, 28px)) max(40px, min(10vw, 56px)) max(40px, min(10vw, 56px)) 0;
    margin: max(20px, min(5vw, 28px)) max(40px, min(10vw, 56px)) clamp(40px, 10vw, 56px) 0;
    margin: max(20px, min(5vw, 28px)) clamp(20px, 5vw, 28px) clamp(40px, 10vw, 56px) 0;
    margin: max(40px, min(10vw, 56px)) max(20px, min(5vw, 28px)) max(40px, min(10vw, 56px)) 0;
    margin: max(40px, min(10vw, 56px)) max(20px, min(5vw, 28px)) clamp(40px, 10vw, 56px) 0;
    margin: max(40px, min(10vw, 56px)) max(40px, min(10vw, 56px)) max(40px, min(10vw, 56px)) 0;
    margin: max(40px, min(10vw, 56px)) max(40px, min(10vw, 56px)) clamp(40px, 10vw, 56px) 0;
    margin: max(40px, min(10vw, 56px)) clamp(20px, 5vw, 28px) clamp(40px, 10vw, 56px) 0;
    margin: clamp(8px, 2.5vw, 12px) clamp(20px, 5vw, 28px) clamp(40px, 10vw, 56px) 0;
    background: center / contain no-repeat
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='12' viewBox='0 0 64 12' fill='none' stroke='%23ffffff' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='1' y1='6' x2='57' y2='6'/%3E%3Cpolyline points='51,1.5 57.5,6 51,10.5'/%3E%3C/svg%3E");
  }

  /* 8 — ACCORDION photo: square crop below the list. CAP + CENTRE so it doesn't blow up to a full-width
     728×728 square on tablet (min(100%,…) leaves mobile full-width, caps + centres on tablet). Simon r4. */
  body.ags-page-about .ags-c-accordion__aside .ags-c-accordion__photo img {
    display: block; width: 100%; max-width: min(100%, 420px); margin-left: auto; margin-right: auto;
    height: auto; aspect-ratio: 1 / 1; -o-object-fit: cover; object-fit: cover; -o-object-position: center 45%; object-position: center 45%;
  }

  /* 2 — OUR APPROACH → numgrid-style horizontal SWIPE STRIP (Simon correction: NOT an absolute-stack
     carousel — the same pattern as What's-included / .ags-c-whatwedo__items). Title LEFT-aligned at top;
     ONE card at a time (~86vw + mandatory snap); a maroon chevron below the strip cues the swipe. The base
     strip (flex/overflow-x/gap) is set in the §4 block above (~7520); here we widen the card to one-per-
     view, make the snap mandatory, and add the arrow. */
  body.ags-page-about .ags-c-approach__h,
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__h .elementor-heading-title { text-align: left; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__grid { scroll-snap-type: x mandatory; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__grid > .ags-c-approach__item {
    flex: 0 0 86vw; width: 86vw; max-width: 420px; scroll-snap-align: start;
  }
  /* maroon chevron below the strip (approach is on the light band → dark stroke, not the numgrid's cream one) */
  body.ags-page-about .ags-c-approach::after {
    content: ""; display: block; width: max(48px, min(14vw, 64px)); width: clamp(48px, 14vw, 64px); height: 12px;
    margin: max(12px, min(3.5vw, 18px)) 0 0 auto;
    margin: clamp(12px, 3.5vw, 18px) 0 0 auto;
    background: center / contain no-repeat
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='12' viewBox='0 0 64 12' fill='none' stroke='%238E1E00' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='1' y1='6' x2='57' y2='6'/%3E%3Cpolyline points='51,1.5 57.5,6 51,10.5'/%3E%3C/svg%3E");
  }

  /* 5 — GRAND MASTERS → swipeable carousel (masters-mobile.js already runs site-wide; port the
     History-Tour .is-carousel treatment into About's scope; !important to beat the static-stack rules). */
  body.ags-page-about { --m-card-w: min(83vw, calc(var(--vh, 1vh) * 60), 520px); --m-card-h: calc(var(--m-card-w) * 1.05); --m-rank: calc(0.1 * var(--m-card-w)); }
  body.ags-page-about .ags-masters__row.is-carousel {
    height: var(--m-card-h) !important; position: relative; overflow: hidden; border-radius: 10px;
    flex-direction: row !important; touch-action: pan-y; -webkit-user-select: none; -moz-user-select: none; user-select: none;
    /* cap to the card width + CENTRE (was width:100% → the carousel stretched edge-to-edge on tablet). */
    width: var(--m-card-w) !important; max-width: 100% !important; margin-left: auto !important; margin-right: auto !important;
  }
  body.ags-page-about .ags-masters:has(.ags-masters__row.is-carousel),
  body.ags-page-about .ags-masters:has(.ags-masters__row.is-carousel) * { transition: none !important; }
  body.ags-page-about .ags-masters__row.is-carousel .ags-master.e-con {
    display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100% !important; overflow: hidden;
    border-radius: 10px; z-index: 1; flex: 0 0 auto;
    transform: translateX(calc(var(--ags-off, 2) * 100%)) !important; transition: transform .5s ease !important;
  }
  body.ags-page-about .ags-masters__row.is-carousel .ags-master.is-active { z-index: 2; }
  body.ags-page-about .ags-masters__row.is-carousel .ags-master img { -webkit-user-drag: none; }
  body.ags-page-about .ags-masters__bar { position: absolute; z-index: 3; left: 50%; top: 14px; transform: translateX(-50%); width: calc(var(--m-card-w) - 28px); }

  /* 3b — APPROACH strip: CLEAN STABLE SWIPE (Simon 2026-09-24 r2). Lock the strip to horizontal pan and
     kill native image-drag + text-selection on EVERYTHING inside, and make the images pointer-inert so a
     finger/mouse can't "pick up" a card/image and drag it around — only the strip scroll-snaps. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__grid {
    touch-action: pan-x; overscroll-behavior-x: contain;
  }
  body.ags-page-about .ags-c-approach__grid,
  body.ags-page-about .ags-c-approach__grid * { -webkit-user-drag: none; -moz-user-select: none; user-select: none; -webkit-user-select: none; }
  body.ags-page-about .ags-c-approach__grid img { pointer-events: none; }

  /* 2b — the two Approach intro paragraphs (moved under the title) sit CLOSER together (Simon r2). The
     row gap was already ~10px; the visible gap came from the columns' internal top/bottom padding, so
     zero that too — text-to-text gap now = just the row gap. */
  body.ags-page-about .ags-c-approach .ags-c-intro__row { gap: max(4px, min(1.4vw, 7px)) !important; gap: clamp(4px, 1.4vw, 7px) !important; }
  body.ags-page-about .ags-c-approach .ags-c-intro__col { margin-top: 0 !important; margin-bottom: 0 !important; padding-top: 0 !important; padding-bottom: 0 !important; }
  body.ags-page-about .ags-c-approach .ags-c-intro__col > * + * { margin-top: max(4px, min(1.4vw, 7px)); margin-top: clamp(4px, 1.4vw, 7px); }

  /* 4 — MISSION STATEMENT eyebrows ACROSS from each other (row, not the maroon column default ~6865). */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-statement--maroon .ags-c-statement__labels {
    flex-direction: row; justify-content: space-between; align-items: flex-start; gap: 12px; text-align: left;
  }

  /* 5 — item strips: locked mandatory swipe; item 1 starts at the CONTENT MARGIN (aligned with each
     section's title/caption). Approach sits inside its section's padding, so its grid just needs the
     margin/scroll reset. What-we-do is a FULL-BLEED section (padding:0), so ITS strip must carry the
     content margin itself (else item 1 jams to the screen edge) + top room so the numbers aren't clipped
     by the forced overflow-y; the ::before chevron owns the section-bottom space, so drop the bottom margin. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__grid {
    margin-left: 0; padding-left: 0; scroll-padding-left: 0; scroll-snap-type: x mandatory;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__items {
    margin-left: 0; scroll-snap-type: x mandatory;
    padding-left: max(20px, min(5vw, 28px));
    padding-left: clamp(20px, 5vw, 28px); scroll-padding-left: max(20px, min(5vw, 28px)); scroll-padding-left: clamp(20px, 5vw, 28px);
    padding-top: max(10px, min(3vw, 14px));
    padding-top: clamp(10px, 3vw, 14px); margin-bottom: 0;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__items > .ags-c-whatwedo__item:first-child,
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__grid > .ags-c-approach__item:first-child { margin-left: 0; scroll-snap-align: start; }

  /* 7 — MASTER cards: port the History-Tour caption formatting so the kanji (rank) sits bottom-left and
     the name/location clear it (no overlap), cream on the scrim. Also re-show the rank on the ACTIVE card
     (the static block hid it ~7710, but in carousel mode the active card IS the visible one). */
  body.ags-page-about .ags-masters .ags-master__caption {
    position: absolute; left: 16px; right: 16px; bottom: 16px; width: auto; z-index: 2; padding: 0;
    display: flex; flex-direction: row; align-items: flex-end; justify-content: space-between; gap: 8px;
  }
  body.ags-page-about .ags-master[data-rank-len="1"] .ags-master__caption { padding-left: calc(var(--m-rank) + 14px); }
  body.ags-page-about .ags-master[data-rank-len="2"] .ags-master__caption { padding-left: calc(2 * var(--m-rank) + 14px); }
  body.ags-page-about .ags-masters .ags-master__rank.elementor-widget-heading {
    position: absolute; z-index: 2; left: 16px; right: auto; bottom: 16px; text-align: left; margin: 0; opacity: 1; pointer-events: none;
  }
  body.ags-page-about .ags-masters__row.is-carousel .ags-master.is-active .ags-master__rank.elementor-widget-heading { opacity: 1; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-master__rank .elementor-heading-title { line-height: 1; color: var(--ags-cream); font-size: var(--m-rank); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-master__name .elementor-heading-title { white-space: nowrap; line-height: 1; font-size: 15px; color: var(--ags-cream); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-master__title .elementor-heading-title { font-size: 11px; white-space: nowrap; line-height: 1; margin: 0; color: var(--ags-cream); }

  /* 6 — GRAND MASTERS reorder: image carousel → kanji → button (Simon 2026-09-24). The kanji legend +
     button live in the PRECEDING .ags-c-masters-intro section (nth-child 8); the photos in .ags-masters
     (nth-child 9). Flex the page root + display:contents the masters-intro wrappers to promote its pieces,
     then give EVERY top-level section an explicit :nth-child order so only masters moves (the two maroon
     statements stay put). (Approach's intro→approach reorder is JS; its hidden intro is harmless here.) */
  body.ags-page-about .elementor[data-elementor-type="wp-page"] { display: flex; flex-direction: column; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(1) { order: 10; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(2) { order: 20; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(3) { order: 30; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(4) { order: 40; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(5) { order: 50; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(6) { order: 60; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(7) { order: 70; }
  /* nth-child(8) = masters-intro → display:contents (below); its pieces carry orders 80/90/110/120 */
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(9) { order: 100; }   /* .ags-masters photos */
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(10) { order: 130; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(11) { order: 140; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(12) { order: 150; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(13) { order: 160; }
  body.ags-page-about .elementor[data-elementor-type="wp-page"] > :nth-child(14) { order: 170; }
  body.ags-page-about .ags-c-masters-intro,
  body.ags-page-about .ags-c-masters-intro__row,
  body.ags-page-about .ags-c-masters-intro__body { display: contents; }
  body.ags-page-about .ags-c-masters-intro__h { order: 80; }
  body.ags-page-about .ags-c-masters-intro__text { order: 90; }
  body.ags-page-about .ags-masters { order: 100; }
  body.ags-page-about .ags-c-masters-intro__eyebrows { order: 110; }
  body.ags-page-about .ags-c-masters-intro__btn { order: 120; }
  /* display:contents dropped the section's padding-inline → re-apply to the promoted pieces */
  body.ags-page-about .ags-c-masters-intro__h,
  body.ags-page-about .ags-c-masters-intro__text,
  body.ags-page-about .ags-c-masters-intro__eyebrows,
  body.ags-page-about .ags-c-masters-intro__btn { padding-left: max(20px, min(5vw, 32px)); padding-right: max(20px, min(5vw, 32px)); padding-left: max(20px, min(5vw, 32px)); padding-left: clamp(20px, 5vw, 32px); padding-right: max(20px, min(5vw, 32px)); padding-right: clamp(20px, 5vw, 32px); box-sizing: border-box; }

  /* 6b — GRAND MASTERS: one consistent background + even spacing (Simon 2026-09-24 r3). The cream
     `.ags-masters` filmstrip band read as a SEPARATE sub-section around the carousel (cream band vs the
     white the title/kanji/button sit on) → make it transparent so the whole group is one bg. Then space
     the promoted pieces (page-root flex children, orders 80–120) evenly + give the group consistent
     top/bottom breathing room (title top / button bottom) since display:contents stripped its section pad. */
  body.ags-page-about .ags-masters {
    background: transparent !important; padding-top: 0 !important; padding-bottom: 0 !important;
    margin-top: max(22px, min(6vw, 30px)) !important;
    margin-top: clamp(22px, 6vw, 30px) !important; margin-bottom: max(22px, min(6vw, 30px)) !important; margin-bottom: clamp(22px, 6vw, 30px) !important;
  }
  body.ags-page-about .ags-c-masters-intro__h { margin-top: max(40px, min(10vw, 52px)) !important; margin-top: clamp(40px, 10vw, 52px) !important; }
  body.ags-page-about .ags-c-masters-intro__text { margin-top: max(10px, min(3vw, 14px)) !important; margin-top: clamp(10px, 3vw, 14px) !important; }
  body.ags-page-about .ags-c-masters-intro__eyebrows { margin-top: 0 !important; }   /* the carousel margin-bottom owns this gap */
  body.ags-page-about .ags-c-masters-intro__btn { margin-top: max(16px, min(4vw, 22px)) !important; margin-top: clamp(16px, 4vw, 22px) !important; margin-bottom: max(40px, min(10vw, 52px)) !important; margin-bottom: clamp(40px, 10vw, 52px) !important; }
  /* CENTRE every element in the Grand Masters section (Simon 2026-09-24 r3): title + intro text were
     left/right-aligned; the carousel/kanji/button already centre. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__text .elementor-widget-container { text-align: center; }
  /* CENTRE the title at EVERY width. The heading WIDGET is a shrink-to-content flex item inside __h, so
     it left-aligned when its content (420) was narrower than the box (768 tablet) — while at 390 the
     content ≈ full width so it looked centred. width:100% on the <h2> didn't help (the WIDGET wrapper is
     what shrinks). Fix at the __h container: flex-column + centre the widget itself. Simon 2026-09-24 r4. */
  body.ags-page-about .ags-c-masters-intro__h { display: flex; flex-direction: column; align-items: center; text-align: center; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__h .elementor-heading-title { text-align: center; }
  body.ags-page-about .ags-c-masters-intro__text { margin-left: auto; margin-right: auto; max-width: 34em; }

  /* §13 FIND YOUR JOURNEY — search-bar affordance (centred, sized like the H2 it
     replaces) + a numgrid-style horizontal-scroll row of postcards. The shared
     .ags-c-postcard base (~6829) is itself gated ≥1024 with no mobile fallback
     anywhere, and About retrofits it to the overlay/articlecard look at desktop
     only (~7717) — so this ports that same retrofit to mobile, About-scoped only
     (matches the desktop comment: not a change to the shared, page-agnostic base). */
  body.ags-page-about .ags-c-journey { padding-left: max(20px, min(5vw, 32px)); padding-right: max(20px, min(5vw, 32px)); padding-left: max(20px, min(5vw, 32px)); padding-left: clamp(20px, 5vw, 32px); padding-right: max(20px, min(5vw, 32px)); padding-right: clamp(20px, 5vw, 32px); }
  body.ags-page-about .ags-c-journey__searchbar {
    display: flex; align-items: center; justify-content: center; gap: max(10px, min(3vw, 14px)); gap: clamp(10px, 3vw, 14px);
    width: 100%;
  }
  body.ags-page-about .ags-c-journey__caret {
    display: block; flex: 0 0 auto;
    width: max(2.5px, min(0.8vw, 3.5px));
    width: clamp(2.5px, 0.8vw, 3.5px); height: max(24px, min(7vw, 32px)); height: clamp(24px, 7vw, 32px);
    background: var(--ags-red); border-radius: 1px;
  }
  /* Sized to its OWN placeholder ("Find your journey", canvas-measured 22–30px holds
     it inside 11.5em) — the desktop input uses the same "size to content" idiom
     (~7654) at its own scale; a bigger vw coefficient here clipped the text inside
     the input's own single-line box (input text doesn't wrap). */
  body.ags-page-about .ags-c-journey__input {
    flex: 0 0 auto; width: 11.5em; max-width: 92%; min-width: 0;
    background: transparent; border: none; outline: none;
    -webkit-appearance: none; -moz-appearance: none; appearance: none; box-shadow: none; padding: 0; margin: 0;
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(22px, min(6.8vw, 30px));
    font-size: clamp(22px, 6.8vw, 30px); line-height: 1;
    color: var(--ags-red); caret-color: var(--ags-red);
  }
  body.ags-page-about .ags-c-journey__input::-moz-placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }
  body.ags-page-about .ags-c-journey__input::placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }
  body.ags-page-about .ags-c-journey__input::-webkit-input-placeholder { color: rgba(142, 30, 0, .22); }
  body.ags-page-about .ags-c-journey__input::-moz-placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }
  body.ags-page-about .ags-c-journey__row {
    flex-wrap: nowrap; gap: max(14px, min(4vw, 20px)); gap: clamp(14px, 4vw, 20px); margin-top: max(24px, min(6vw, 32px)); margin-top: clamp(24px, 6vw, 32px);
    overflow-x: auto; -webkit-overflow-scrolling: touch; scroll-snap-type: x proximity;
    scrollbar-width: none;
  }
  body.ags-page-about .ags-c-journey__row::-webkit-scrollbar { display: none; }
  body.ags-page-about .ags-c-journey__card {
    flex: 0 0 max(220px, min(68vw, 280px));
    flex: 0 0 clamp(220px, 68vw, 280px); scroll-snap-align: start;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard {
    position: relative; overflow: hidden; padding: 0; display: flex; flex-direction: column;
    aspect-ratio: 405 / 442; justify-content: flex-end; border-radius: 10px;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 56%;
    background: linear-gradient(to top, rgba(17,16,16,.85), rgba(17,16,16,0));
    pointer-events: none; z-index: 0;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__thumb {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; height: 100%; z-index: 0; padding: 0;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__img {
    aspect-ratio: auto; width: 100%; height: 100%; border-radius: 0;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__img img {
    border-radius: 0; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__tags {
    position: absolute; top: 10px; right: 10px;
    display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px;
    max-width: 78%; z-index: 2; padding: 0;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__tag .elementor-heading-title {
    display: inline-block; border-radius: 999px; padding: 4px 10px;
    font-size: 11px; line-height: 1; white-space: nowrap; margin: 0;
    color: var(--ags-white); background: rgba(20,20,20,.30);
    font-family: 'Bounded', sans-serif; font-weight: 400;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__title { position: relative; z-index: 2; margin: 0 14px; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__title .elementor-heading-title {
    color: var(--ags-white); font-size: max(17px, min(5vw, 20px)); font-size: clamp(17px, 5vw, 20px); line-height: 1.2; font-weight: 600;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__more { position: relative; z-index: 2; margin: 0 14px 14px; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__more .elementor-button {
    width: 100%; display: flex; align-items: center; justify-content: space-between;
    font-size: max(11px, min(3.2vw, 13px));
    font-size: clamp(11px, 3.2vw, 13px); -webkit-text-decoration: none; text-decoration: none;
    background-color: transparent; border: 1px solid var(--ags-white); border-radius: 999px;
    color: var(--ags-white); padding: 9px 16px;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__more .elementor-button:hover {
    background-color: #8E1E00; border-color: #8E1E00; color: var(--ags-white);
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__more .elementor-button::after {
    content: "\203A"; position: static; top: auto; right: auto; bottom: auto; left: auto; width: auto; height: auto;
    background: none; transform: none; transition: none; margin-left: 8px;
  }

  /* §14 FINAL CTA (.ags-c-cta3) — the already-wrapping 3-pill row (desktop's own
     flex-wrap:wrap, ~7822, is width-agnostic and already active at mobile) goes
     full-width-stacked instead, same "full-width button" convention as
     .ags-c-form .wpcf7-submit (~6217) and the homepage CTA's link (~4670). */
  body.ags-page-about .ags-c-cta3 { padding-left: max(20px, min(5vw, 32px)); padding-right: max(20px, min(5vw, 32px)); padding-left: max(20px, min(5vw, 32px)); padding-left: clamp(20px, 5vw, 32px); padding-right: max(20px, min(5vw, 32px)); padding-right: clamp(20px, 5vw, 32px); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-cta3__h .elementor-heading-title {
    font-size: max(24px, min(7vw, 30px));
    font-size: clamp(24px, 7vw, 30px); line-height: 1.15;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-cta3__btns {
    flex-direction: column; align-items: stretch; gap: max(12px, min(3.5vw, 16px)); gap: clamp(12px, 3.5vw, 16px);
    margin-top: max(24px, min(6vw, 32px));
    margin-top: clamp(24px, 6vw, 32px);
  }
  body.ags-page-about .ags-c-cta3__btn { width: 100%; }
  body.ags-page-about .ags-c-cta3__btn .elementor-button { width: 100%; display: flex; justify-content: center; }

  /* =============================================================================
     ARTICLE CARD MOBILE (.ags-c-articlecard) — SHARED, NOT page-scoped (Task M2).
     -----------------------------------------------------------------------------
     Ports the desktop overlay-card ruleset (components.css ~7255, `@media (min-width:
     1024px)`) down to <=1023: identical architecture (absolute-fill photo, bottom
     scrim, top pills, bottom-anchored title/excerpt/READ-MORE stack) — only the sizes
     move from --ags-px to clamp/px (same mechanical conversion as every other rule in
     this shared block). The aspect-ratio (405/442) is scale-free already (mockup's own
     ratio, no --ags-px), so it's copied verbatim — holds the same card shape at any
     width. Reused by BOTH the /articles Loop Grid (body.ags-page-articles below) and
     the single-post template's "Keep exploring" related-posts grid (body.single-post,
     Task M3) — kept HERE, unscoped, so both pick it up; scoped to the loop-item's own
     wrapper attributes (never a page slug) for the same reason as the desktop block:
     `.ags-c-articlecard` only ever renders AS a loop-item. */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard {
    position: relative; overflow: hidden; display: block;
    aspect-ratio: 405 / 442; border-radius: 10px;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard::after {
    /* STRENGTHENED (review fix, 2026-09-20): busy "flyer" featured images (posters with their own
       baked-in text/graphics, e.g. the "2018.04 Shito-Ryu SHOBUKAN" card) could still collide with
       the title/excerpt/READ-MORE stack under the old .85→0 / 56%-tall scrim — that band faded to
       fully transparent well above where a poster's own text often sits. A 3-stop gradient (near-
       opaque at the very bottom, still substantially dark at mid-band, only clearing near the TOP
       of the band) plus a taller band gives the text a reliably dark floor on ANY photo, while the
       photo's own upper ~1/3 stays untouched (0% opacity at the band's top) so a plain, calm photo
       card doesn't read as over-darkened. */
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 64%;
    background: linear-gradient(to top, rgba(17,16,16,.94) 0%, rgba(17,16,16,.72) 40%, rgba(17,16,16,0) 100%);
    pointer-events: none; z-index: 1;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__img,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__img .elementor-widget-container,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__img figure {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: 0; height: 100%; width: 100%;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__img img {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__date,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__tags {
    position: absolute; top: 10px; z-index: 2; margin: 0; padding: 0;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__date { left: 10px; }
  /* Extra `.ags-c-articlecard` ancestor qualifier — same specificity-bump fix as the desktop
     block above (~7472), needed so `max-width` beats Elementor core's `.elementor.elementor
     .e-con > .elementor-widget { max-width: 100% }`. Concrete `width` (not just `max-width`)
     so flex-wrap's line breaks compute off the real clipped box — see desktop comment ~7488. */
  /* width 74%→56% so the right-anchored tag pills can't reach into the left-anchored date pill on
     the same top line — a long localized date ("25 de abril de 2023") was overlapping the first tag
     (review item 3). Extra tag rows just wrap downward (they're top-anchored). */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__tags {
    right: 10px; width: 56%; max-width: 56%;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__tags .elementor-widget-container {
    display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__tags .elementor-widget-container > p,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__tags .elementor-widget-container > .elementor-text-editor {
    display: contents;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__date .elementor-heading-title,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__tags span {
    display: inline-flex; align-items: center; white-space: nowrap;
    border: 1px solid rgba(255,255,255,.75); border-radius: 999px;
    background: rgba(20,20,20,.30);
    padding: 4px 10px;
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: 11px; line-height: 1; color: var(--ags-white); margin: 0;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__body {
    position: absolute; left: 14px; right: 14px;
    width: calc(100% - 28px); box-sizing: border-box;
    bottom: 16px; z-index: 2;
    /* Same fix as desktop (~7517): zero Elementor's own default container padding, re-add
       ONLY a bottom value matching the title→excerpt/excerpt→button gap (8px) so the space
       below the text stack reads the same as the space below the title. */
    padding: 0 0 8px 0;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__title .elementor-heading-title {
    font-size: max(18px, min(5vw, 21px));
    font-size: clamp(18px, 5vw, 21px); line-height: 1.2; margin: 0; font-weight: 600;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__excerpt .elementor-widget-container p {
    font-size: max(13px, min(3.6vw, 14px));
    font-size: clamp(13px, 3.6vw, 14px); line-height: 1.4; margin: 0;
    color: rgba(247,244,242,.85);
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  }
  /* No margin-top here — the colw(gap=8) flex gap between excerpt/more already gives the
     same spacing as title→excerpt (was double-counted: gap + this margin = 16px, mismatched
     with the 8px above it). Matches desktop's `.ags-c-articlecard__more` (~7537), which never
     had this margin. */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__more { width: 100%; }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__more .elementor-button {
    width: 100%; display: flex; align-items: center; justify-content: space-between;
    font-size: max(12px, min(3.4vw, 13px));
    font-size: clamp(12px, 3.4vw, 13px);
    background-color: transparent; border: 1px solid var(--ags-white); color: var(--ags-white);
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__more .elementor-button:hover {
    background-color: #8E1E00; border-color: #8E1E00; color: var(--ags-white);
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__more .elementor-button::after {
    content: "\203A"; margin-left: 8px;
  }

  /* =============================================================================
     ARTICLES LISTING PAGE MOBILE (body.ags-page-articles) — Task M2.
     -----------------------------------------------------------------------------
     Slug: articles. Desktop rules: components.css ~8324 `@media (min-width:1024px)`.
     Search bar mirrors the About "Find your journey" mobile search idiom verbatim
     (same recipe, different class prefix — see the desktop comment at ~8311 for why
     it isn't shared directly). Tag pills (Simon's locked decision, docs/mobile/
     about-blog-mobile-sections.md §1b) become a horizontal-scroll strip — same numgrid
     overflow-x/scroll-snap/hidden-scrollbar mechanics (~5993 above), sized to each
     pill's own content width rather than a fixed card width. The card grid drops to a
     single column; the card itself is the SHARED `.ags-c-articlecard` mobile ruleset
     above, not duplicated here. ---- */
  /* --ags-ac-cardw: the single card column's own rendered width (viewport minus the
     section's own padding-inline, same formula as that padding below) — reused by
     grid-auto-rows just below to sidestep a real Chromium layout bug (see that rule's
     comment). Keep this in sync with .ags-c-articles-header's padding-inline. */
  body.ags-page-articles { --ags-ac-cardw: calc(100vw - 2 * max(20px, min(5vw, 32px))); --ags-ac-cardw: calc(100vw - 2 * clamp(20px, 5vw, 32px)); }
  /* padding-top clears the fixed mobile top-nav (crest + floating pills) so the big "Search
     Articles" bar doesn't sit under the crest at scroll-top (review item 2). */
  /* Top clearance = the fixed top-left crest height (6.25·--mnav-u = min(28.75vw, 112px)) + a ~28px gap,
     so the header isn't tucked under the crest / nav pills (Simon 2026-09-24; was clamp(96,26vw,128) →
     ~101px @390, under the 112px crest). */
  body.ags-page-articles .ags-c-articles-header { padding-left: max(20px, min(5vw, 32px)); padding-right: max(20px, min(5vw, 32px)); padding-left: max(20px, min(5vw, 32px)); padding-left: clamp(20px, 5vw, 32px); padding-right: max(20px, min(5vw, 32px)); padding-right: clamp(20px, 5vw, 32px); padding-top: calc(min(28.75vw, 112px) + 28px); }
  body.ags-page-articles .ags-c-articles-header__h { width: 100%; }
  body.ags-page-articles .ags-c-articles-header__searchbar {
    display: flex; align-items: center; justify-content: center; gap: max(10px, min(3vw, 14px)); gap: clamp(10px, 3vw, 14px);
    width: 100%;
  }
  body.ags-page-articles .ags-c-articles-header__caret {
    display: block; flex: 0 0 auto;
    width: max(2.5px, min(0.8vw, 3.5px));
    width: clamp(2.5px, 0.8vw, 3.5px); height: max(24px, min(7vw, 32px)); height: clamp(24px, 7vw, 32px);
    background: var(--ags-red); border-radius: 1px;
  }
  /* Sized to its OWN placeholder ("Search Articles", shorter than About's "Find your
     journey" — same "size to content" idiom, em so width scales with the font). */
  /* CENTER FIX (2026-09-21): width was 10.5em — wider than "Search Articles" (~9.1em) — so the
     row centred an oversized box and the visible text read LEFT-of-centre (same bug the desktop
     block fixed at ~8710). Size the box to the placeholder text (+~0.4em) per language so the
     visible ink is what gets centred by the row's justify-content:center. */
  body.ags-page-articles .ags-c-articles-header__input {
    flex: 0 0 auto; width: 9.5em; max-width: 100%; min-width: 0;
    background: transparent; border: none; outline: none;
    -webkit-appearance: none; -moz-appearance: none; appearance: none; box-shadow: none; padding: 0; margin: 0;
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(22px, min(6.8vw, 30px));
    font-size: clamp(22px, 6.8vw, 30px); line-height: 1;
    color: var(--ags-red); caret-color: var(--ags-red);
  }
  body.ags-lang-es .ags-c-articles-header__input { width: 10.2em; }   /* "Buscar artículos" */
  body.ags-lang-de .ags-c-articles-header__input { width: 12.5em; }   /* "Artikel durchsuchen" */
  body.ags-lang-nl .ags-c-articles-header__input { width: 10.5em; }   /* "Artikelen zoeken" */
  body.ags-page-articles .ags-c-articles-header__input::-moz-placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }
  body.ags-page-articles .ags-c-articles-header__input::placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }
  body.ags-page-articles .ags-c-articles-header__input::-webkit-input-placeholder { color: rgba(142, 30, 0, .22); }
  body.ags-page-articles .ags-c-articles-header__input::-moz-placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }

  /* NAV READABILITY on this LIGHT page — the interior top-bar pills are cream fill + red content
     over a TRANSPARENT bar, tuned to sit over the site's DARK heroes; on the articles page's cream
     background the cream pills vanish and the elements blend in. Flip the CLOSED pills to a solid
     maroon fill + cream content so the nav reads clearly on the light bg (the OPEN cream panel keeps
     the shared red/cream flip — :not(.is-open) leaves it alone). Matches the maroon crest already
     on the left, so the whole bar reads as one cohesive maroon nav on cream. */
  body.ags-page-articles .ags-mnav:not(.is-open) .ags-mnav__actions .ags-mnav__eng,
  body.ags-page-articles .ags-mnav:not(.is-open) .ags-mnav__actions .ags-mnav__lang-btn,
  body.ags-page-articles .ags-mnav:not(.is-open) .ags-mnav__actions .ags-mnav__book,
  body.ags-page-articles .ags-mnav:not(.is-open) .ags-mnav__actions .ags-mnav__burger {
    background: var(--e-global-color-primary, var(--ags-red));
    color: var(--e-global-color-accent, var(--ags-cream));
  }

  /* TAG PILLS → horizontal-scroll strip (Simon's locked decision, NOT wrapped rows).
     The grid scrolls INTERNALLY (overflow-x on the row, not the page) so document
     scrollWidth never exceeds the viewport. Pills are plain <a> tags (raw HTML, not
     Elementor widgets), so no --flex-* specificity fight is needed here (unlike the
     numgrid's Elementor-container items). */
  body.ags-page-articles .ags-c-articles-header__pillsrow { width: 100%; }
  body.ags-page-articles .ags-c-articles-header__pills {
    display: flex; flex-wrap: nowrap; align-items: center;
    gap: max(10px, min(3vw, 14px));
    gap: clamp(10px, 3vw, 14px);
    margin-top: max(24px, min(6vw, 32px));
    margin-top: clamp(24px, 6vw, 32px);
    overflow-x: auto; -webkit-overflow-scrolling: touch; scroll-snap-type: x proximity;
    padding-left: 0;
    padding-right: 24px; padding-top: 2px; padding-bottom: 2px;   /* trailing pad + a hair of top/bottom room */
    scrollbar-width: none;
  }
  body.ags-page-articles .ags-c-articles-header__pills::-webkit-scrollbar { display: none; }
  body.ags-page-articles .ags-c-articles-header__pill {
    flex: 0 0 auto; scroll-snap-align: start;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--ags-red); border-radius: 999px;
    padding: max(8px, min(2.4vw, 10px)) max(16px, min(4.5vw, 20px));
    padding: max(8px, min(2.4vw, 10px)) clamp(16px, 4.5vw, 20px);
    padding: max(16px, min(4.5vw, 20px)) max(16px, min(4.5vw, 20px));
    padding: max(16px, min(4.5vw, 20px)) clamp(16px, 4.5vw, 20px);
    padding: clamp(8px, 2.4vw, 10px) clamp(16px, 4.5vw, 20px);
    font-family: 'Bounded', sans-serif; font-weight: 400; font-size: max(13px, min(3.6vw, 14px)); font-size: clamp(13px, 3.6vw, 14px);
    color: var(--ags-red); -webkit-text-decoration: none; text-decoration: none; white-space: nowrap; line-height: 1;
  }
  body.ags-page-articles .ags-c-articles-header__pill.is-active {
    background: var(--ags-red); color: var(--ags-cream);
  }

  /* GRID — single column (Simon's locked decision). Row-gap only; column-gap is moot
     at 1 column. grid-template-columns restated on .elementor-grid, same belt-and-
     braces convention as the desktop rule. */
  body.ags-page-articles .ags-c-articles-header__gridwrap { width: 100%; margin-top: max(28px, min(7vw, 40px)); margin-top: clamp(28px, 7vw, 40px); }
  body.ags-page-articles .ags-c-articles-grid .elementor-grid {
    grid-template-columns: 1fr;
    --grid-column-gap: 0px;
    --grid-row-gap: max(28px, min(7vw, 36px));
    --grid-row-gap: clamp(28px, 7vw, 36px);
    /* BUG WORKAROUND (verified live, Chromium): a single-column CSS Grid whose items get
       their height from `aspect-ratio` (the card has NO in-flow children — everything is
       position:absolute) reports the WRONG auto/intrinsic height to this grid's OWN
       ancestor chain — confirmed empirically: with N cards visible the ancestor's measured
       height grew by exactly one row-gap per card and NOTHING for the card's own height
       (i.e. the aspect-ratio contribution is dropped from that specific calculation), even
       though the grid still PAINTS every row at the correct height internally. Net effect:
       .ags-c-articles-header (and everything after it — the CTA band, the footer) collapsed
       to a fraction of its true height and rendered UNDER the still-correctly-painted, very
       tall overflowing card column. Desktop (3 columns, `@media min-width:1024px` above) does
       NOT exhibit this — untouched, not reproduced there. Giving the implicit row track an
       EXPLICIT (non-auto, non-%, non-aspect-ratio) length via grid-auto-rows sidesteps the
       buggy code path entirely; verified this exact fix resolves it at 390/414/768/1023 (the
       ancestor's measured height now matches the grid's own painted height exactly). Uses
       the SAME width formula as this section's own padding-inline (--ags-ac-cardw above) so
       it tracks the card's true rendered width at any viewport in range. */
    grid-auto-rows: calc(var(--ags-ac-cardw) * 442 / 405);
  }

  /* CLIENT-SIDE FILTER (ageshio-articles.js) — identical to the desktop `:has()` collapse
     rule (~8402), duplicated here since that one is gated `@media (min-width:1024px)` and
     desktop must stay byte-for-byte unchanged. Collapses a hidden card's own grid cell so
     no empty row is left in the single column. `.ags-c-articles__empty` mirrors the desktop
     "No articles found" styling at mobile scale. */
  body.ags-page-articles .ags-c-articles-grid [data-elementor-type="loop-item"]:has(.ags-c-articlecard.is-hidden) {
    display: none;
  }
  body.ags-page-articles .ags-c-articles__empty {
    width: 100%; text-align: center;
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(16px, min(4.4vw, 18px));
    font-size: clamp(16px, 4.4vw, 18px);
    color: var(--ags-red);
    padding-top: max(32px, min(8vw, 48px));
    padding-bottom: max(32px, min(8vw, 48px));
    padding-top: max(32px, min(8vw, 48px));
    padding-top: clamp(32px, 8vw, 48px);
    padding-bottom: max(32px, min(8vw, 48px));
    padding-bottom: clamp(32px, 8vw, 48px);
  }

  /* PAGER (ageshio-articles.js) — "Showing N of M" + LOAD MORE, JS-inserted only (no-JS
     visitors never see this, they get the full grid — see the script's own header comment).
     Same outline-pill idiom as the tag filter pills (~6818) rather than the shared full-width
     `.ags-btn--bar` card button — this is a page-chrome control, not a card CTA. */
  body.ags-page-articles .ags-c-articles__pager {
    width: 100%; display: flex; flex-direction: column; align-items: center;
    gap: max(12px, min(3vw, 16px));
    gap: clamp(12px, 3vw, 16px);
    margin-top: max(28px, min(7vw, 40px));
    margin-top: clamp(28px, 7vw, 40px);
  }
  body.ags-page-articles .ags-c-articles__pager-status {
    margin: 0; text-align: center;
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(12px, min(3.4vw, 13px));
    font-size: clamp(12px, 3.4vw, 13px);
    color: rgba(142, 30, 0, .55);
  }
  body.ags-page-articles .ags-c-articles__loadmore {
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--ags-red); border-radius: 999px; background: transparent;
    padding: max(11px, min(2.6vw, 12px)) max(28px, min(6vw, 34px));
    padding: max(11px, min(2.6vw, 12px)) clamp(28px, 6vw, 34px);
    padding: max(28px, min(6vw, 34px)) max(28px, min(6vw, 34px));
    padding: max(28px, min(6vw, 34px)) clamp(28px, 6vw, 34px);
    padding: clamp(11px, 2.6vw, 12px) clamp(28px, 6vw, 34px);
    font-family: 'Bounded', sans-serif; font-weight: 400; font-size: max(13px, min(3.6vw, 14px)); font-size: clamp(13px, 3.6vw, 14px);
    color: var(--ags-red); cursor: pointer; white-space: nowrap; line-height: 1;
    transition: background-color .2s, color .2s;
  }
  body.ags-page-articles .ags-c-articles__loadmore:hover,
  body.ags-page-articles .ags-c-articles__loadmore:focus-visible {
    background: var(--ags-red); color: var(--ags-cream);
  }
  /* `pager.hidden`/`loadMore.hidden` (ageshio-articles.js) toggle the boolean `hidden`
     attribute — restate `[hidden]{display:none}` here since it'd otherwise lose to the
     `display:flex`/`inline-flex` rules above at equal specificity by source order. */
  body.ags-page-articles .ags-c-articles__pager[hidden],
  body.ags-page-articles .ags-c-articles__loadmore[hidden] { display: none; }
}


/* ===== VIP Karate Retreat — finalize 2026-08-16 ===== */
/* Photos are wired by attachment ID into the existing ags-c-* Image widgets; this block only sets
   per-slot proportions/pacing, mirroring the Group-Tours finalize (components.css ~3718-4269),
   retargeted to body.ags-page-vip-karate-retreat. Portable slug scope ONLY — never page-id-N /
   .elementor-N, never body.ags-redesign. Appendable at end-of-file (self-contained, balanced).
   GREYSCALE: none needed — the VIP feature source is already B&W; the photo-row, overlay, accordion
   and hero photos are all COLOUR in the Figma. (The shared >=1024 base rule bakes grayscale onto the
   overlay photo for GT, so it is explicitly reset to `none` below.) Desktop-only (1024->ultrawide). */

@media (min-width: 1024px) {
  /* ---- per-page tuning knobs ---- */
  body.ags-page-vip-karate-retreat {
    --vip-inc-gutter: calc(43.2 * var(--ags-px));
    --vip-inc-inset: max(0px, calc((100vw - var(--ags-frame)) / 2));   /* the frame's letterbox, replacing a hand-rolled ultrawide pull-in */
    --vip-inc-kanji: calc(72 * var(--ags-px));
  }

  /* §1 HERO — now the SHARED .ags-c-hero treatment (identical to GT); nothing page-specific here
     (the geta photo is default-centred). See the shared hero block near the top of this file. */

  /* §2 MARQUEE — compact band directly under the hero; crest ≈ text height, ~25px below before §3.
     (Crest ASSET stays the widget's own nav-icon-red.svg — this only tunes its size.) */
  /* §2 MARQUEE — now the SHARED marquee treatment (identical to GT); no per-page rules. */

  /* §3 FEATURE PHOTO — cinematic wide band. Source is a B&W portrait → cover-crop to a band (don't
     render the full portrait). Height fills the rest of the first screen on tall viewports. */
  body.ags-page-vip-karate-retreat .ags-c-photo__img,
  body.ags-page-vip-karate-retreat .ags-c-photo__img figure { margin: 0; }
  body.ags-page-vip-karate-retreat .ags-c-photo__img img {
    width: 100%; height: max(calc(590.4 * var(--ags-px)), 56vh); height: max(calc(590.4 * var(--ags-px)), 56svh);   /* taller band = zoom OUT: reveals more of the doorway scene, subjects sit smaller in-frame */
    -o-object-fit: cover;
       object-fit: cover; -o-object-position: center 55%; object-position: center 55%; display: block; border-radius: 0;
  }

  /* §4 STATEMENT — generous fixed spacing (headline/labels copy identical to GT; base rules size
     them). Space opens above the headline and below the link before the photo row. */
  body.ags-page-vip-karate-retreat .ags-c-statement__headline { margin-top: calc(86.4 * var(--ags-px)); }
  /* statement padding-bottom (was clamp(90px, 11vw, 200px)) now comes from the shared --ags-sec-pad rhythm. */

  /* §5 PHOTO ROW — three EQUAL near-square COLOUR tiles (corridor / monk / noren), capped + centred
     (large on laptop, margins on ultrawide). No greyscale — all three are colour in the Figma. */
  body.ags-page-vip-karate-retreat .ags-c-photorow__row {
    max-width: calc(1440 * var(--ags-px)); margin-left: auto; margin-right: auto; gap: calc(13 * var(--ags-px));
    align-items: stretch; padding-left: 18px; padding-right: 18px; box-sizing: border-box;
  }
  body.ags-page-vip-karate-retreat .ags-c-photorow__col { aspect-ratio: 1 / 1; }
  body.ags-page-vip-karate-retreat .ags-c-photorow__img,
  body.ags-page-vip-karate-retreat .ags-c-photorow__img figure { height: 100%; }
  body.ags-page-vip-karate-retreat .ags-c-photorow__img img { height: 100%; -o-object-fit: cover; object-fit: cover; }

  /* §6+§7 "What's included" — titleband (top-left, ~40svh) + numbered grid (bottom-right, ~60svh).
     Title and the 後退 kanji share one left edge; the grid packs bottom-right (diagonal composition).
     The grid columns/eyebrow-gutter/num+label fonts come from the shared base rules already. */
  body.ags-page-vip-karate-retreat .ags-c-titleband,
  body.ags-page-vip-karate-retreat .ags-c-numgrid { padding-left: var(--vip-inc-inset); padding-right: var(--vip-inc-inset); box-sizing: border-box; }
  body.ags-page-vip-karate-retreat .ags-c-titleband { min-height: 40vh; min-height: 40svh; display: flex; flex-direction: column; justify-content: flex-start; }   /* padding-top → shared --ags-sec-pad */
  body.ags-page-vip-karate-retreat .ags-c-titleband__h { padding-left: var(--vip-inc-gutter); }
  body.ags-page-vip-karate-retreat .ags-c-titleband + .ags-c-numgrid { min-height: 60vh; min-height: 60svh; display: flex; flex-direction: column; justify-content: flex-end; }   /* padding-bottom → shared --ags-sec-pad */
  /* Kanji block (後退 + Retreat) shares the title's left edge; zero Elementor's stray inner pads so
     kanji-left == title-left, then lift the block so its bottom sits just above the grid's "1". */
  body.ags-page-vip-karate-retreat .ags-c-numgrid__eyebrow { padding-left: var(--vip-inc-gutter); margin-top: calc(var(--vip-inc-kanji) * -1.95); }   /* lifted higher (was -1.35) so 静/Retreat clears the hover photo below it */
  body.ags-page-vip-karate-retreat .ags-c-numgrid > .e-con { padding-left: 0; }
  body.ags-page-vip-karate-retreat .ags-c-numgrid__eyebrow > .elementor-element,
  body.ags-page-vip-karate-retreat .ags-c-numgrid__eyebrow .elementor-widget-container { padding-left: 0; margin-left: 0; }
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-numgrid__eyebrow .elementor-widget-heading:first-child .elementor-heading-title { font-size: var(--vip-inc-kanji); line-height: 1; }
  body.ags-page-vip-karate-retreat .ags-c-numgrid__eyebrow .elementor-widget-heading:last-child { width: calc(var(--vip-inc-kanji) * 1); }   /* 静 is 1 char now (was 後退, 2) → match its width so "Retreat" centres under it */
  body.ags-page-vip-karate-retreat .ags-c-numgrid__eyebrow .elementor-widget-heading:last-child .elementor-heading-title { text-align: center; }

  /* §8 OVERLAY SPLIT — full-bleed COLOUR photo (green hills), exactly one viewport tall. Reset the
     grayscale the shared >=1024 base rule bakes onto every overlay photo (a GT-only design choice). */
  body.ags-page-vip-karate-retreat .ags-c-overlay,
  body.ags-page-vip-karate-retreat .ags-c-overlay > .e-con-inner { min-height: 100vh; min-height: 100svh; }
  body.ags-page-vip-karate-retreat .ags-c-overlay .ags-c-overlay__stage { aspect-ratio: auto; height: 100vh; height: 100svh; min-height: 0; }
  body.ags-page-vip-karate-retreat .ags-c-overlay__photo img { filter: none; }
  /* (Reverted 2026-08-16: a page-scoped overlay-list shrink + max-width cap made the right-column
     "Instead of planning…" and "You simply arrive…" texts overlap. VIP overlay uses the shared
     component list sizing, which reads fine — leave it alone.) */

  /* §9 ACCORDION — now the SHARED .ags-c-accordion treatment (identical to GT: 100svh box, list
     top-left, portrait photo pinned bottom-right, maroon dividers). Only the photo CROP is
     page-specific — the beach scene frames a touch above centre. */
  body.ags-page-vip-karate-retreat .ags-c-accordion__aside .ags-c-accordion__photo img { -o-object-position: center 45%; object-position: center 45%; }
}

/* ===== Karate Experiences — finalize 2026-08-16 ===== */
/* Slug: karate-experiences (post 19556). Scope: body.ags-page-karate-experiences.
   Section stack matches the Figma: Hero(経験) → Marquee → Feature(dojo floor) →
   Experience List(animated ags-c-explist) → Overlay Split(Book as Group / Individual) → CTA.
   Only the components this page HAS are tuned here (hero photo, feature band, explist
   composition, overlay viewport-fill). Numgrid / photorow / accordion are OMITTED (not on
   this page). Greyscale: NONE baked here — the §Overlay photo is greyscaled by the existing
   UNSCOPED `.ags-c-overlay__photo img { filter: grayscale(1) }` component rule; the hero source
   (exp-1) is already B&W, and the feature + edge photos are COLOUR in the reference. The overlay
   editorial composition (corner headings, centre divider, captions, lists, cover photo) is also
   supplied by the UNSCOPED .ags-c-overlay* rules already in components.css — we only pin height. */

@media (min-width: 1024px) {

  /* §1 HERO — now the SHARED .ags-c-hero treatment (identical to GT). Only the photo CROP is
     page-specific: the tall B&W high-kick (exp-1) frames best pulled up. */
  body.ags-page-karate-experiences .ags-c-hero__photo img { -o-object-position: center 28%; object-position: center 28%; }

  /* §3 FEATURE PHOTO — cinematic full-bleed band of the dojo floor (reference ≈ 2.8:1, warm COLOUR,
     NOT greyscale). cover-crop trims the dark ceiling / floor reflection. Page-scoped (0,2,2) beats
     the base full-image rule `.ags-c-photo__img img` (0,1,1) so sibling interior pages are
     unaffected. */
  body.ags-page-karate-experiences .ags-c-photo__img,
  body.ags-page-karate-experiences .ags-c-photo__img figure { margin: 0; }
  body.ags-page-karate-experiences .ags-c-photo__img img {
    width: 100%; height: calc(619.2 * var(--ags-px));   /* taller band = zoom OUT: reveals more of the dojo (floor + ceiling), students sit smaller in-frame */
    -o-object-fit: cover;
       object-fit: cover; -o-object-position: center 48%; object-position: center 48%; display: block; border-radius: 0;
  }

  /* §Experience List — reference composition: eyebrow(経験) CENTERED at top, the animated carousel
     row (2 edge photos · centre track · 2 edge photos) in the MIDDLE, then a bottom band with the
     big red H2 "Karate Experiences" on the LEFT and the lede + CTA on the RIGHT.
     DOM order is eyebrow → H2 → row → lede, so re-lay the section as a GRID (the section is a
     content_width:full .e-con whose 4 children are DIRECT items — verified live, no .e-con-inner)
     to move the H2 below the row and beside the lede. display:grid at (0,3,0) beats Elementor's
     .e-con.e-flex display:flex (0,2,0). The carousel MECHANICS live inside the row (site-wide
     .ags-exp* rules + ageshio-exp.js) and are untouched. */
  /* SHARED explist section layout (promoted from body.ags-page-karate-experiences 2026-09-16 so the
     VIP Retreat explist gets the identical composition; the ONLY page-specific bit left below is the
     H2 font-size, which is tuned to each heading's wrap). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist {
    /* Show all 5 carousel items like the reference (the mask defaults to --slots:3 off-homepage;
       only body.ags-redesign sets 5). Same element carries .ags-exp, so this beats the base
       `.ags-exp { --slots: 3 }` (0,2,0 > 0,1,0). The active item still centres/reddens/enlarges. */
    --slots: 5;
    /* CENTRE the CONTENT to --exp-row (min(94vw,128rem)) via symmetric PADDING, but keep the SECTION
       full-width so its cream background is FULL-BLEED (Simon 2026-08-18: the capped max-width left
       white page-margins on ultrawide). Padding — not max-width — reaches the same centred band, so the
       carousel + title/lede stay symmetric exactly as before while the cream now spans edge-to-edge. */
    max-width: none; width: 100%; margin-left: 0; margin-right: 0; box-sizing: border-box;
    padding-left: max(24px, calc((100% - var(--exp-row)) / 2));
    padding-right: max(24px, calc((100% - var(--exp-row)) / 2));
    display: grid;
    /* lede column = the homepage §7 lede width (clamp 300–400, 32% of the capped row); title takes
       the rest. (Was 1fr 1fr → lede got half the width = cramped + pushed the layout off.) */
    grid-template-columns: 1fr max(300px, min(32%, 400px));
    grid-template-columns: 1fr clamp(300px, 32%, 400px);
    /* Three rows distributed like the homepage §7 — but CORRECT BY CONSTRUCTION, no magic numbers.
       The section is exactly ONE viewport (height:100svh, mirroring the homepage's 100dvh deck card),
       border-box, with EQUAL --ags-sec-pad top+bottom (line ~5090) → the top and bottom gaps are
       symmetric by definition. `grid-template-rows: auto 1fr auto` makes the MIDDLE (photos+carousel)
       row absorb ALL the slack, so the eyebrow sits flush against the top pad and the title+lede sit
       flush against the bottom pad at every viewport height — no space-between/align-content trick and
       no bottom "tail compensation" needed (those were the homepage stage's own workarounds). overflow
       is clipped like the deck card so a pathologically short window can never push the page. */
    grid-template-areas:
      "eyebrow eyebrow"
      "row row"
      "h2 lede";
    grid-template-rows: auto 1fr auto;
    align-items: start;
    grid-column-gap: calc(43.2 * var(--ags-px));
    -moz-column-gap: calc(43.2 * var(--ags-px));
         column-gap: calc(43.2 * var(--ags-px));
    height: 100vh;
    height: 100svh;
    overflow: hidden;
  }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist__eyebrow { grid-area: eyebrow; }
  /* Zero the eyebrow's default container top padding so 経験 sits flush against the section's top pad,
     exactly like the homepage (`.ags-exp__eyebrow { padding-top: 0 }`). Without this the top gap is
     --ags-sec-pad + 10px while the bottom is --ags-sec-pad → the top reads heavier than the bottom. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist__eyebrow.e-con { padding-top: 0; }
  /* Carousel row spread symmetrically (edge-left ↔ mask centred ↔ edge-right), matching .ags-exp__top.
     Zero the builder's default container gap (32px) + side padding (10px): with space-between they
     forced the edges+mask (1152px) past the capped row (1203px) → the right photo pair bled off, and
     the 10px pad offset the row 10px. gap:0 lets space-between distribute the slack symmetrically. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist__row     { grid-area: row; align-self: center; justify-content: space-between; align-items: center; gap: 0 !important; padding-left: 0 !important; padding-right: 0 !important; }
  /* H2 sits in col 1; its left edge = the capped row's left edge = under the left photo pair (like
     the homepage title) — no extra gutter needed now the section is capped/centred. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist__h        { grid-area: h2; }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist .ags-exp__lede { grid-area: lede; justify-self: end; width: 100%; }
  /* Big red 2-line H2 ("Karate / Experiences"): max-width forces the 2-line wrap (the TEXT is
     page-specific, so the wrap width stays per-page). Scope (0,4,1) beats the shared
     `.elementor-page [data-elementor-type="wp-page"] .ags-c-explist__h …` (0,4,0) rule.
     Size parity (2026-09-22, Task 5): font-size/line-height changed from calc(80.6 * --ags-hpx)/0.98
     (a guess that happened to land near homepage at 2560 but ran ~19% BIGGER than homepage at 1440)
     to 3.75rem/1 — the homepage's own actual rendered value (see the base .ags-c-explist__h rule
     above for why 3.75rem, not a --ags-hpx calc, is the faithful match). */
  body.ags-page-karate-experiences [data-elementor-type="wp-page"] .ags-c-explist__h .elementor-heading-title {
    font-size: 3.75rem; line-height: 1; max-width: 6.5em; margin: 0;
  }
  /* VIP explist H2 "VIP Retreat Experiences": same big red treatment as KE, max-width forces the
     Figma's 2-line wrap ("VIP Retreat / Experiences") instead of one full-width line.
     Size parity (2026-09-22, Task 5): see the KE rule above — same fix, same reason. */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-explist__h .elementor-heading-title {
    font-size: 3.75rem; line-height: 1; max-width: 8.5em; margin: 0;
  }
  /* VIP carousel item names run longer than KE's ("Experience Okinawa from Within" = 458px vs the
     24rem/432px mask), so at the base 1.375rem they overflowed + clipped on the sides — worse at the
     ×1.1 active scale. Shrink VIP's items so the longest fits the mask even when active. */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-explist .ags-exp__item .elementor-heading-title {
    font-size: 1.15rem;
  }
  /* VIP §9 accordion sits directly beneath the near-white (#f7f4f2) Experience List — unlike the
     other pages, where the accordion follows a different section. Per the Figma, give it the warmer
     greige so the two light sections read as distinct instead of merging. VIP-scoped. */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-accordion.e-con { background-color: #eee6e0; }
  /* VIP explist — the viewport fill (min-height:100svh + align-content:center) and the tightened
     row-gap (60) are now the SHARED explist defaults (see the .ags-c-explist grid rule above, so KE
     gets them too). VIP only keeps its own lede-column WIDTH here. */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-explist {
    /* Lede column sized to the CENTRE of the 4-line wrap window (measured 440–530px at 1440),
       in --ags-px so the width scales with the font and the 4-line wrap holds across viewports
       (matches the reference; the old 400px cap forced 5 lines). H2 (1fr) keeps the remainder. */
    grid-template-columns: 1fr calc(485 * var(--ags-px));
  }
  /* Clear Elementor's stray 10px on the explist lede .e-con — it inset the paragraph/CTA 10px inside
     the guide AND left 10px below the button (breaking the button↔title bottom alignment). Shared to
     KE + VIP so both align. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-explist .ags-exp__lede { padding: 0; }
  /* Match the explist's L/R margins to the page's editorial sections: statement/overlay sit on a
     43.2px gutter (calc(43.2*--ags-px)), NOT the 1.83% outer guide the explist inherited. Re-point
     --exp-row so the whole composition (photos, H2, lede) lands on that same gutter. VIP-scoped. */
  body.ags-page-vip-karate-retreat .ags-c-explist { --exp-row: calc(var(--ags-frame) - 86.4 * var(--ags-px)); }
  /* Lede paragraph weight: the reference is the regular weight; the global 300 read too light. */
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-explist .ags-exp__introtext .elementor-widget-container,
  body.ags-page-vip-karate-retreat [data-elementor-type="wp-page"] .ags-c-explist .ags-exp__introtext .elementor-widget-container p { font-weight: 400; }

  /* §Overlay Split — "Book as a Group Tour" (TL) / "Book as an individual" (BR). The editorial
     composition (corner headings, centre divider line, flanking captions, the two bullet lists, and
     the cover + grayscale(1) photo) all comes from the UNSCOPED .ags-c-overlay* rules already in
     components.css and needs no duplication. Only pin the section to ONE viewport: the unscoped
     stage carries aspect-ratio 1280/720, which grows TALLER than the viewport on wide screens and
     overflows — mirror the Group-Tours fix (height:100svh on the stage). */
  body.ags-page-karate-experiences .ags-c-overlay,
  body.ags-page-karate-experiences .ags-c-overlay > .e-con-inner { min-height: 100vh; min-height: 100svh; }
  body.ags-page-karate-experiences .ags-c-overlay .ags-c-overlay__stage {
    aspect-ratio: auto; height: 100vh; height: 100svh; min-height: 0;
  }
  /* Darken the zen-garden bg (Simon's real photo, 2026-08-17) so the white bullets/headings stay
     legible over its BRIGHT raked gravel — the desktop overlay has no scrim (unlike mobile), so the
     darkening lives entirely in this filter. Base rule bakes grayscale(1); brightness tuned down
     from 0.62 (old darker gap-fill source) to 0.42 for the brighter real garden. */
  body.ags-page-karate-experiences .ags-c-overlay__photo img { filter: grayscale(1) brightness(0.42); }
  /* Corner-heading size override REMOVED (2026-09-09): this page's 6.5cqw is now the SHARED value
     for all four pages (Mika asked for the smaller heading everywhere), so the override was an exact
     duplicate of the base rule. KE renders identically; GT/VIP/History came DOWN to meet it. */

  /* §10 CTA — now the SHARED enlarged .ags-c-cta treatment (see the shared CTA block above);
     nothing page-specific here. */
}

/* ===== Karate History Tour — finalize 2026-08-16 ===== */
/* Slug: karate-history-tour (post 19555). Scope: body.ags-page-karate-history-tour ONLY —
   never page-id-N / .elementor-N, never body.ags-redesign (portable-scope rule; linter
   scripts/qa/check_portable_scope.py). Photos are wired by attachment ID into the existing
   ags-c-* Image widgets (wire_history_tour_photos.php); this block only sets per-slot
   proportions / pacing, mirroring the VIP + Group-Tours finalize blocks above, retargeted here.

   Section stack (matches the Figma desktop mockup — the middle "What's included / Who is this
   for" is a SINGLE editorial overlay, no blue title-band / numbered grid):
     1 Hero(経験·Experience) · 2 Marquee · 3 Feature(B&W kata) · 4 Statement ·
     5 Overlay "What's included / Who is this for" (B&W monk + dune) ·
     6 Locations Title (RED band) · 7 Locations Photo Row (maroon 4-card filmstrip, 3rd WIDE) ·
     8 Accordion (pinned belt photo + maroon dividers) · 9 Closing Overlay (約 centred, colour) · 10 CTA.

   GREYSCALE: the §5 overlay source is a B&W dune/monk (leave the shared grayscale(1) baked; add a
   dark scrim so the cream text is legible over the pale sand). The §9 CLOSING photo is a
   teal/blue rain frame that MUST keep its colour → explicitly reset filter:none (the shared
   >=1024 base rule `.ags-c-overlay__photo img{filter:grayscale(1)}` would otherwise bake it grey).
   The §3 feature source is already B&W. Desktop-only (1024 -> ultrawide). */

@media (min-width: 1024px) {

  /* ---- per-page tuning knobs ---- */
  body.ags-page-karate-history-tour {
    /* Frame-aware side inset: the letterbox well + the 43.2px editorial gutter, so the Locations
       title/filmstrip/場所 marker land on the SAME guide as the rest of the page on ultrawide instead
       of flinging out toward the screen edge (well was missing → content sat ~81px from the SCREEN
       edge at 3440). well=0 at ≤1920, so the 1440 reference is unchanged. (Simon 2026-09-23) */
    --hist-loc-inset: calc(var(--ags-well) + 43.2 * var(--ags-px));
  }

  /* §1 HERO — now the SHARED .ags-c-hero treatment (identical to GT). Only the photo CROP is
     page-specific: the tall red-torii frames best pulled up. */
  body.ags-page-karate-history-tour .ags-c-hero__photo img { -o-object-position: center 30%; object-position: center 30%; }

  /* §2 MARQUEE — compact band under the hero; crest ≈ text height, ~25px pad below before §3. */
  /* §2 MARQUEE — now the SHARED marquee treatment (identical to GT); no per-page rules. */

  /* §3 FEATURE PHOTO — cinematic full-bleed B&W kata band (source is already greyscale).
     Cover-crop to a wide band and fill the rest of the first screen on tall viewports. */
  body.ags-page-karate-history-tour .ags-c-photo__img,
  body.ags-page-karate-history-tour .ags-c-photo__img figure { margin: 0; }
  body.ags-page-karate-history-tour .ags-c-photo__img img {
    width: 100%; height: max(calc(460.8 * var(--ags-px)), 44vh); height: max(calc(460.8 * var(--ags-px)), 44svh);
    -o-object-fit: cover;
       object-fit: cover; -o-object-position: center 42%; object-position: center 42%; display: block; border-radius: 0;
  }

  /* §4 STATEMENT — corner labels + big centred headline + small link (copy identical to GT; base
     rules size them). Generous fixed spacing above the headline and below the link. */
  body.ags-page-karate-history-tour .ags-c-statement__headline { margin-top: calc(79.2 * var(--ags-px)); }
  /* statement padding-bottom (was clamp(72px, 9vw, 170px)) now comes from the shared --ags-sec-pad rhythm. */

  /* §5 OVERLAY "What's included" (TL) / "Who is this for" (BR) — full-bleed greyscale monk + dune,
     exactly ONE viewport tall. The corner headings, centre divider line, captions and the two
     bullet lists all come from the UNSCOPED .ags-c-overlay* rules; here we (a) pin height to 100svh,
     (b) re-place the right caption/list to the mockup positions, (c) add a dark scrim + text-shadow
     so the cream text is legible over the pale sand (fixes the low-contrast bug). */
  body.ags-page-karate-history-tour .ags-c-overlay,
  body.ags-page-karate-history-tour .ags-c-overlay > .e-con-inner { min-height: 100vh; min-height: 100svh; }
  body.ags-page-karate-history-tour .ags-c-overlay .ags-c-overlay__stage {
    aspect-ratio: auto; height: 100vh; height: 100svh; min-height: 0;
  }
  /* Darken the pale B&W dune (keep greyscale — it's a B&W source) so cream text reads. */
  body.ags-page-karate-history-tour .ags-c-overlay__photo img { filter: grayscale(1) brightness(0.62) contrast(1.04); }
  /* Scrim gradient over the stage (z-index:1 → below the z-index:2 text, above the image). Darkens
     top-left ("What's included") and the bottom band (the 4 items + "Who is this for"). */
  body.ags-page-karate-history-tour .ags-c-overlay .ags-c-overlay__stage::after {
    content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; pointer-events: none;
    background:
      linear-gradient(135deg, rgba(0,0,0,.55), rgba(0,0,0,0) 42%),
      linear-gradient(to top, rgba(0,0,0,.62), rgba(0,0,0,0) 46%);
  }
  /* Text-shadow behind the headings/captions/lists REMOVED (Simon 2026-09-23: "a bit of shading/shadow
     behind the text" — the reference has none). Legibility is carried by the darkened photo
     (brightness 0.62) + the scrim gradient below; if a small caption ever reads low-contrast, prefer
     nudging those over reinstating a per-glyph halo. */
  /* Right caption "For practitioners who want deeper understanding of karate roots" → TOP-RIGHT.
     right is frame-aware (letterbox well + 3% of the FRAME, matching the base __h--br / __list--r) so it
     lands on the page's right guide on ultrawide — `right:3%` was 3% of the full-width stage, flinging it
     to ~103px from the screen edge at 3440 while the heading above it stayed on the frame guide. well=0 at
     ≤1920 so the 1440 reference (3% ≈ 43px) is unchanged. (Simon 2026-09-23) */
  body.ags-page-karate-history-tour .ags-c-overlay__cap--r { top: 6%; right: calc(max(0px, (100vw - var(--ags-frame)) / 2) + 0.03 * var(--ags-frame)); left: auto; }
  body.ags-page-karate-history-tour .ags-c-overlay__cap--r .elementor-widget-container { text-align: right; }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__cap--r { max-width: 16em; }
  /* Right list "Experience cultural and historical landmarks" → sits cleanly just RIGHT of the divider,
     LEFT-aligned so it flushes toward the line (mirrors the right-aligned cap--l on the left side).
     ⚠️ It is a `list` element, so it inherited the 21px LIST font — bigger than the 15px captions
     ("Fully integrated…" / "For practitioners…"). That is the oversize Simon flagged AND why, at
     `right:37%`+right-align, its block reached LEFT across the divider. Force it to the CAPTION size so
     it reads as a caption like its siblings, and anchor its LEFT edge just right of the centre line.
     WRAP: the reference breaks it to FOUR lines — "Experience" / "cultural and" / "historical" /
     "landmarks". That needs a column narrow enough to break "Experience cultural" but wide enough to
     keep "cultural and" together: the reference block measures ~103px at the 1440 frame, so cap the
     width ~120px (fits "cultural and", breaks "Experience cultural"). Use min(cqw,px) with a coefficient
     matched to the caption font (font 1.3cqw/15px → width 11cqw/120px, ratio 8) so the 4-line break
     holds 1024→ultrawide instead of drifting (a plain em is frozen against the inherited 16px). */
  body.ags-page-karate-history-tour .ags-c-overlay__list--r { top: 36%; left: 54%; right: auto; bottom: auto; }
  body.ags-page-karate-history-tour .ags-c-overlay__list--r .elementor-widget-container { text-align: left; }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__list--r .elementor-widget-container { font-size: min(1.3cqw, calc(15 * var(--ags-px))); }
  body.ags-page-karate-history-tour .ags-c-overlay__list--r ul { list-style: none; padding-left: 0; margin: 0; }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-overlay__stage .ags-c-overlay__list--r { max-width: min(11cqw, calc(120 * var(--ags-px))); }
  /* Left caption "Fully integrated into your itinerary" → just LEFT of the divider (right edge ~46%,
     the shared cap--l gap), right-aligned so it flushes toward the line — was right:50%, which parked
     its right edge ON the divider with no gap. */
  body.ags-page-karate-history-tour .ags-c-overlay__cap--l { top: 52%; right: 54%; }

  /* §6 LOCATIONS TITLE — RED band: giant 2-line H2 on the LEFT; caption + PLAN YOUR TRIP button
     pinned TOP-RIGHT; the 場所 (Basho) marker pinned BOTTOM-RIGHT. The row is a flex row; the right
     column justifies space-between so its two clusters split top/bottom. (H2 size is inherited from
     the UNSCOPED .ags-c-titleband__h rule — clamp 52-108px.) */
  body.ags-page-karate-history-tour .ags-c-locations-title { padding-left: var(--hist-loc-inset); padding-right: var(--hist-loc-inset); box-sizing: border-box; }
  body.ags-page-karate-history-tour .ags-c-locations__row { width: 100%; align-items: stretch; position: relative; }
  /* Land the title (left) + caption/READ MORE (right) on the SAME 43px guide as the statement/
     filmstrip. --hist-loc-inset already pads the section to 43; these clear Elementor's stray
     per-level padding inside the row that was pushing the ink to 63/73 (asymmetric). */
  body.ags-page-karate-history-tour .ags-c-locations__row,
  body.ags-page-karate-history-tour .ags-c-locations__row .e-con,
  body.ags-page-karate-history-tour .ags-c-locations__row .e-con-inner,
  body.ags-page-karate-history-tour .ags-c-locations__left,
  body.ags-page-karate-history-tour .ags-c-locations__right { padding-left: 0; padding-right: 0; }
  body.ags-page-karate-history-tour .ags-c-locations__left { flex: 1 1 auto; justify-content: center; }   /* title column fills the space beside the fixed-width paragraph column (mirrors the explist's 1fr title next to its --ags-px lede) */
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-locations__h .elementor-heading-title { max-width: 8em; }
  body.ags-page-karate-history-tour .ags-c-locations__right {
    flex: 0 0 auto; align-items: flex-end; justify-content: flex-start; text-align: right;
    gap: calc(18 * var(--ags-px));   /* caption → READ MORE, tight (was space-between, which padded
                                        the column to the row height and inflated the title block) */
  }
  body.ags-page-karate-history-tour .ags-c-locations__capbtn { align-items: flex-end; }
  body.ags-page-karate-history-tour .ags-c-locations__right,
  body.ags-page-karate-history-tour .ags-c-locations__capbtn { padding-top: 0; padding-bottom: 0; }   /* clear Elementor stray 10px (40px total) that inflated the title block */
  body.ags-page-karate-history-tour .ags-c-locations__caption .elementor-widget-container,
  body.ags-page-karate-history-tour .ags-c-locations__caption p { text-align: right; }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-locations__caption .elementor-widget-container {
    font-size: calc(15.8 * var(--ags-px)); line-height: 1.3;
  }
  body.ags-page-karate-history-tour .ags-c-locations__btn { align-self: flex-end; }
  /* 場所 marker pinned bottom-right, ABSOLUTE (decoupled from flow so it never collides with the
     tall 3-line left heading — the earlier in-flow placement collapsed it onto "Locations").
     Positioned against .ags-c-locations__row (set position:relative above). */
  /* 場所 marker moved into the filmstrip (design places it at the filmstrip's right, vertically
     centred — not in the title where it collided with READ MORE). */
  /* HISTORY LOCATIONS fills EXACTLY ONE viewport as a §3b title-band + filmstrip PAIR (Simon 2026-09-23):
     the masters-carousel refactor had left the filmstrip's card ROW on the shared 46dvh-capped
     --masters-row-h, so the cards sat SMALL with empty space above them inside the 60svh band. Give the
     title band the pair's top half (36svh → 36+64 = one viewport; the title is only 2 lines so 36svh is
     enough and the extra goes to TALLER cards) and make the card ROW FILL the filmstrip's content area
     via a definite height (--hist-film-h, defined on .ags-masters below = 64svh − its 30/86.4 padding).
     It MUST be a definite calc, not flex-grow/auto — the master images use a height:100% chain that
     collapses to 0 in an auto-height row (the homepage §9 lesson). All svh so it FILLS at every viewport
     height, not only where the content happened to add up at 1440. */
  body.ags-page-karate-history-tour .ags-c-locations-title {
    min-height: 36vh;
    min-height: 36svh; display: flex; flex-direction: column; justify-content: flex-start; padding-bottom: 0;
  }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-masters__row.e-con {
    height: var(--hist-film-h);   /* fill the filmstrip's content area (see --hist-film-h on .ags-masters) */
  }
  /* 場所 marker: pinned to the RIGHT guide of the filmstrip, vertically centred, RED, label centred
     under the kanji. NB the element carries BOTH ags-c-eyebrow + ags-c-locations__marker, so target
     its headings DIRECTLY (the old `.ags-c-locations__marker .ags-c-eyebrow …` never matched → the
     marker fell back to the masters' white + left align). */
  body.ags-page-karate-history-tour .ags-c-locations__marker {
    position: absolute; right: var(--hist-loc-inset); bottom: calc(86.4 * var(--ags-px)); top: auto; left: auto;
    width: -webkit-max-content;
    width: -moz-max-content;
    width: max-content; z-index: 2; margin: 0; text-align: center;   /* marker BOTTOM sits at the filmstrip's bottom padding (86.4) = the card ROW's bottom edge, so 場所 aligns with the bottom of the carousel (Simon 2026-09-23) */
    display: flex; flex-direction: column; align-items: center;   /* centre the "Basho" label under 場所 (was left-anchored) */
  }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-locations__marker .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: calc(54.7 * var(--ags-hpx)); line-height: 1;
  }
  /* KANJI WEIGHT = 600, the site canonical AND the only Shippori Mincho face bundled. The fresh
     session set History's kanji to 400 (a deleted set_history_kanji_weight.php); since Shippori
     Mincho 400 is NOT loaded, every History kanji fell back to a serif system font (why 場所
     didn't match the other sections). Force 600 on the eyebrow kanji (first heading only — the
     romaji label is a different face), the closing 4-kanji nav, the 場所 marker (also .ags-c-eyebrow)
     and the filmstrip card ranks. Renders true Shippori Mincho like every other page. */
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-eyebrow .elementor-widget-heading:first-child .elementor-heading-title,
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-master__rank .elementor-heading-title {
    font-weight: 600;
  }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-locations__marker .elementor-heading-title,
  body.ags-page-karate-history-tour .ags-c-locations__marker .elementor-widget-container { color: var(--ags-red); text-align: center; }

  /* §6 Locations title RESTYLE to the 2026-09-16 Figma: CREAM band (was a red band), maroon heading
     + paragraph, READ MORE = maroon pill, short caption → PARAGRAPH constrained to the right column.
     The CREAM band itself is owned in the DB (section bg Global Color primary→accent), not here. */
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-locations__h .elementor-heading-title { color: var(--ags-red); font-size: calc(76 * var(--ags-hpx)); }   /* was ~101 (inherited titleband clamp); brought down toward the statement headline (70) so the title block is shorter and the location cards get more height */
  /* Paragraph column = an EXPLICIT reference-frame width, exactly like the explist lede
     (`.ags-exp__lede { flex: 0 0 auto; width: calc(N * --ags-px) }`, line ~2737). The caption FILLS this
     width and wraps to the reference's 4 lines (~720px at 1440 breaks line 1 at "…Okinawa Karate Kaikan");
     the title column (flex:1 1 auto) takes the rest. This is the pattern that works elsewhere — an
     auto/percent basis instead let the column either shrink (6 narrow lines) or size to the title's
     max-content and OVERFLOW (which pushed the paragraph + READ MORE button off-screen). Width in
     --ags-px scales with the frame, and both columns always fit. */
  /* 635px is MEASURED (Red Hat Display 400 @17.5px) as the width whose greedy wrap matches the
     reference: L1 ends "Okinawa Karate Kaikan", L2 ends "cultural heritage.", L3 STARTS at
     "Disseminating" (the sentence start Simon flagged), L4 "internationally...karate." (matching range
     620-650px → 635 has margin). Was 720 = too wide (L1 ran into "preserves,"). */
  body.ags-page-karate-history-tour .ags-c-locations__right { flex: 0 0 auto; width: calc(635 * var(--ags-px)); }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-locations__caption .elementor-widget-container,
  body.ags-page-karate-history-tour .ags-c-locations__caption p { color: var(--ags-red); font-size: calc(17.5 * var(--ags-px)); font-weight: 400; }   /* was the light 300 global — regular reads better */
  body.ags-page-karate-history-tour .ags-c-locations__btn .elementor-button { background-color: var(--ags-red); color: var(--ags-cream); }

  /* §7 LOCATIONS FILMSTRIP — reuses the homepage MASTERS card component (classes ags-masters /
     __row / ags-master / ags-portrait / ags-master__rank / __caption / __name / __title). The
     UNSCOPED .ags-masters* rules (components.css ~§9) already do the heavy lifting: cover-filled
     portraits, the bottom scrim, the collapsed-sliver kanji ↔ active-card caption crossfade, and
     the flex-grow accordion — STATIC here (the server .is-active card stays wide via CSS alone; NO
     masters JS, which is front-page-only + single-root). This block only overrides what the
     homepage COMPOSITION bakes in: (a) beat width:60%!important with a page-scoped 100%, (b) paint
     the maroon band (homepage masters is CREAM), (c) size/pad the row + widen the active card to the
     mockup filmstrip. Portable: body.ags-page-<slug> only. */
  /* Band colour (CREAM) is owned in the DB: section bg Global Color = accent (was primary/maroon). */
  /* Section padding: gap below the §6 title band, breathing room above §8 (beats the band's inline
     0/0 — page scope out-specifies Elementor's per-element rule). */
  body.ags-page-karate-history-tour .ags-masters {
    position: relative;   /* anchors the absolutely-placed 場所 marker */
    padding-top: calc(30 * var(--ags-px));      /* internal gap below the §6 title band */
    padding-bottom: calc(86.4 * var(--ags-px)); /* OUTSIDE bottom pad of the pair (breathing room above §8) */
    box-sizing: border-box;
    /* Bottom half of the Locations pair: 64svh (title 36svh + filmstrip 64svh = one full viewport at
       every size — the 2-line title needs less, so the filmstrip gets more → TALLER cards). --hist-film-h
       = the band's CONTENT area (64svh minus the 30 + 86.4 padding); the card row fills it exactly and the
       場所 marker + active card derive from it, so they all track together. */
    min-height: 64vh;
    min-height: 64svh; display: flex; flex-direction: column; justify-content: flex-end;
    --hist-film-h: calc(calc(var(--vh, 1vh) * 64) - 30 * var(--ags-px) - 86.4 * var(--ags-px));
  }
  /* Row: beat the homepage width:60%!important (composition-only) → full, capped + centred, inset
     to align the filmstrip with the §6 heading. Fixed row height (flex-grow animates WIDTH only —
     no aspect-ratio height, per the masters card rule). */
  body.ags-page-karate-history-tour .ags-masters__row {
    width: 100% !important;
    max-width: calc(1180 * var(--ags-px)) !important;   /* was capped-but-beaten (computed 100%) → cards filled full width + collided with 場所 */
    margin-left: 0 !important;
    margin-right: auto !important;                    /* LEFT-align at the guide (not centred) so the right gutter holds 場所 */
    gap: calc(13 * var(--ags-px)) !important;
    justify-content: flex-start;
    align-items: stretch;
    height: calc(330 * var(--ags-px));                   /* TALLER than the first pass (290; orig 430); smaller title + cleared stray padding free the room. The Locations PAIR
                                                            (title band + this card row) fits ONE
                                                            viewport: 82 top pad + ~260 title + 43 gap
                                                            + 290 row + 86 bottom pad ≈ 761 ≤ 766.
                                                            In --ags-px so it scales with the frame. */
    padding-left: var(--hist-loc-inset);
    padding-right: 0;                                    /* cards fill to the row's right edge (~82%); 場所 sits in the gutter beyond */
    box-sizing: border-box;
  }
  /* Active (会館 Kaikan) card wider vs the slivers than the homepage default (flex-grow 3.4) to
     match the mockup's ~1:4.7 proportion. */
  body.ags-page-karate-history-tour .ags-masters .ags-master.is-active { flex-grow: 4.7; max-width: calc(1.4 * var(--hist-film-h)); }
  /* Active-card max-width = 1.4 × --hist-film-h (the svh-based row-fill height), so the 会館 card holds
     the reference's ~1.4 landscape aspect at EVERY viewport — and ≤ the photo's 1.67, so object-fit:cover
     stays HEIGHT-driven (constant scale, no zoom as the card expands; the homepage masters fix). Tracking
     the row height (not the old flat 505*--ags-px) is what keeps it landscape after the fill change — a
     fixed px cap went portrait as the row grew taller on tall viewports. */
  /* Caption left-pad keyed to the kanji LENGTH via data-rank-len (set by ageshio-masters.js, which now
     runs site-wide), so the name sits a CONSISTENT gap from the kanji on every card — mirrors the
     homepage/About per-length pattern (42/70, 44/72). Was a single fixed 90 sized for the 2-char 会館,
     which left the 1-char 城/寺/園 names too far from their kanji (Simon 2026-09-23). 会館 keeps ~90;
     the 1-char cards get ~55 (90 scaled by the ~44/72 ratio). */
  body.ags-page-karate-history-tour .ags-master.is-active[data-rank-len="2"] .ags-master__caption { padding-left: calc(90 * var(--ags-px)); }
  body.ags-page-karate-history-tour .ags-master.is-active[data-rank-len="1"] .ags-master__caption { padding-left: calc(55 * var(--ags-px)); }
  /* Kanji rank alignment (Simon 2026-09-23): CENTRED on the CLOSED (sliver) cards; LEFT-aligned only on
     the EXPANDED (active) card, where it sits bottom-left beside the name. */
  body.ags-page-karate-history-tour .ags-master:not(.is-active) .ags-master__rank.elementor-widget-heading {
    left: 0; right: 0; text-align: center;
  }
  body.ags-page-karate-history-tour .ags-master.is-active .ags-master__rank.elementor-widget-heading {
    left: calc(16 * var(--ags-px)); right: auto; text-align: left;
  }
  /* Active-card caption sizes (name + title) tuned for the filmstrip. */
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-master__name .elementor-heading-title {
    font-size: calc(21.6 * var(--ags-px));
  }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-master__title .elementor-heading-title {
    font-size: calc(14.4 * var(--ags-px));
  }

  /* §8 ACCORDION — big red headings (Sample experience / Optional add-ons / Etiquette) + maroon
     divider rules under each, list on the LEFT; the B&W folded-belt photo pinned on the RIGHT and
     DECOUPLED from the list (opening an item never nudges it). Header size + panel size come from
     the shared base rules; here we lay it out + add the per-item divider (page-scoped, like VIP). */
  /* §8 ACCORDION — now the SHARED .ags-c-accordion treatment (identical to GT: 100svh box, list
     top-left, portrait photo pinned bottom-right, maroon dividers). Only the photo CROP is
     page-specific — the folded-belt closeup frames a touch above centre. */
  body.ags-page-karate-history-tour .ags-c-accordion__aside .ags-c-accordion__photo img { -o-object-position: center 45%; object-position: center 45%; }

  /* §9 CLOSING OVERLAY — full-bleed teal/blue rain photo, ONE viewport tall, with the large 約
     kanji + "(About)" label CENTRED (mockup centres it; the builder emits it as an overlay marker).
     Reuses the .ags-c-overlay__stage / __photo hooks: reset the shared grayscale to KEEP colour,
     hide the shared centre-divider line, and centre the kanji marker. */
  body.ags-page-karate-history-tour .ags-c-closing,
  body.ags-page-karate-history-tour .ags-c-closing > .e-con-inner { min-height: 100vh; min-height: 100svh; }
  body.ags-page-karate-history-tour .ags-c-closing .ags-c-closing__stage {
    aspect-ratio: auto; height: 100vh; height: 100svh; min-height: 0;
  }
  body.ags-page-karate-history-tour .ags-c-closing__photo img { filter: none; }
  /* no centre divider on the closing stage (the shared ::before is a light vertical rule). */
  body.ags-page-karate-history-tour .ags-c-closing__stage::before { display: none; }
  /* 4-kanji cross-nav row (旅行 Tour · 後退 Retreat · 経験 Experience · 約 About) centred over the
     blue photo — replaces the single 約 marker. Each .ags-c-eyebrow is a linked <a>. */
  body.ags-page-karate-history-tour .ags-c-closing__kanji {
    top: 50%; left: 50%; right: auto; bottom: auto; transform: translate(-50%, -50%);
    width: -webkit-max-content;
    width: -moz-max-content;
    width: max-content; max-width: min(94vw, calc(var(--ags-frame) - 2 * var(--ags-edge)));
    z-index: 2; display: flex; flex-direction: row; justify-content: center; align-items: flex-start;
    gap: calc(88 * var(--ags-px));
  }
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow {
    width: -webkit-max-content;
    width: -moz-max-content;
    width: max-content; margin: 0; text-align: center; -webkit-text-decoration: none; text-decoration: none; color: inherit;
    display: flex; flex-direction: column; align-items: center;   /* centre each label UNDER its kanji (was left-anchored) */
  }
  /* Kanji weight (thin mincho, not the default 600) is owned in the DB per-widget typography
     (typography_font_weight:400 on the Shippori Mincho headings) — no CSS override needed. */
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-closing__kanji .ags-c-eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: calc(64 * var(--ags-hpx)); line-height: 1; text-align: center;
    text-shadow: 0 1px 14px rgba(0,0,0,.5);
  }
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow .elementor-widget-heading:last-child .elementor-heading-title,
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow .elementor-widget-heading:last-child .elementor-widget-container {
    text-align: center; text-shadow: 0 1px 10px rgba(0,0,0,.5);
  }
  /* HOVER AFFORDANCE — these 4 kanji are cross-page links (旅行→tours, 後退→retreat, 経験→experiences,
     約→about) but gave no interactive cue, so users didn't know they lead anywhere (Simon 2026-09-23,
     chose scale+glow). On hover the kanji scales up ~8% and gains a soft white glow, and the English
     label brightens — so the whole group reads as a tappable button. `:hover`/`:focus-within` fire from
     the group container even though only the glyphs are the actual <a>; `cursor:pointer` extends the
     link cursor across the whole column (incl. the kanji→label gap). Desktop-only (this block is inside
     @media min-width:1024) — hover is meaningless on touch. */
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow { cursor: pointer; }
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow .elementor-heading-title a { -webkit-text-decoration: none; text-decoration: none; color: inherit; }
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
    transition: transform .3s cubic-bezier(.25,1,.5,1), text-shadow .3s ease;
    transform-origin: center;
  }
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow .elementor-widget-heading:last-child .elementor-heading-title {
    transition: text-shadow .3s ease;
  }
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-closing__kanji .ags-c-eyebrow:hover .elementor-widget-heading:first-child .elementor-heading-title,
  body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-closing__kanji .ags-c-eyebrow:focus-within .elementor-widget-heading:first-child .elementor-heading-title {
    transform: scale(1.08);
    text-shadow: 0 1px 14px rgba(0,0,0,.5), 0 0 24px rgba(255,255,255,.5);
  }
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow:hover .elementor-widget-heading:last-child .elementor-heading-title,
  body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow:focus-within .elementor-widget-heading:last-child .elementor-heading-title {
    text-shadow: 0 1px 10px rgba(0,0,0,.5), 0 0 16px rgba(255,255,255,.5);
  }
  @media (prefers-reduced-motion: reduce) {
    body.ags-page-karate-history-tour .ags-c-closing__kanji .ags-c-eyebrow .elementor-widget-heading:first-child .elementor-heading-title { transition: none; }
    body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-closing__kanji .ags-c-eyebrow:hover .elementor-widget-heading:first-child .elementor-heading-title,
    body.ags-page-karate-history-tour [data-elementor-type="wp-page"] .ags-c-closing__kanji .ags-c-eyebrow:focus-within .elementor-widget-heading:first-child .elementor-heading-title { transform: none; }
  }
}

/* =============================================================================
   POST CARD (.ags-c-postcard) — SHARED, reusable across pages, NOT page-scoped.
   Born on About's "Find your journey" carousel but built to be reused by a future
   blog listing grid, blog-post cards, and a WP posts loop, so its INTRINSIC styling
   (photo/tags/title/more) lives here rather than under body.ags-page-about. Scoped
   `.elementor-page [data-elementor-type="wp-page"] .ags-c-postcard*` — same
   page-type-scope convention as the interior ags-c-* components above (beats Kit
   globals on headings/text; see the CONVENTION note ~4787). Any page-specific
   CONTEXT the card sits in (carousel width/snap, grid columns, etc.) is that page's
   own concern — e.g. body.ags-page-about .ags-c-journey__card below, which carries
   both classes on the same element (.ags-c-postcard for the intrinsic half,
   .ags-c-journey__card for the carousel-item half).
   Markup: .ags-c-postcard > .ags-c-postcard__thumb (position context) >
   .ags-c-postcard__img (square photo) + .ags-c-postcard__tags > .ags-c-postcard__tag
   ×N (overlaid pills), then .ags-c-postcard__title (heading) + .ags-c-postcard__more
   (link).
   ============================================================================= */
@media (min-width: 1024px) {
  /* Card = column stack (thumb → title → more) + inner gap. WIDTH is the
     consuming context's job (carousel flex-basis, grid column, etc — not here). */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-postcard {
    display: flex; flex-direction: column; gap: calc(14 * var(--ags-px));
  }
  /* Thumb is a row-direction flex container with a single child (the tags are
     pulled out of flow, absolute) — position:relative gives the tags their
     context. Also clears Elementor's stray per-level 10px padding (the "clear it
     at every level" rule) since this wrapper is reused wherever the card is. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-postcard__thumb {
    position: relative; padding: 0;
  }
  /* Square photo (matches the reference's near-1:1 cards — Simon: "more square,
     not tall rectangles"). Squareness here is a scale-free ratio; the consuming
     page controls absolute size via the card's width. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-postcard__img {
    aspect-ratio: 1 / 1; width: 100%; border-radius: 10px; overflow: hidden;
  }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-postcard__img img {
    border-radius: 10px; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover;
  }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-postcard__tags {
    position: absolute; top: calc(12 * var(--ags-px)); right: calc(12 * var(--ags-px));
    display: flex; flex-wrap: wrap; justify-content: flex-end; gap: calc(6 * var(--ags-px));
    max-width: 78%; z-index: 2; padding: 0;
  }
  /* Outlined pill — border/colour tied to currentColor (the heading widget's own
     DB-set text colour), so this doesn't hardcode a brand colour, only the shape. */
  .elementor-page [data-elementor-type="wp-page"] .ags-c-postcard__tag .elementor-heading-title {
    display: inline-block; border: 1px solid currentColor; border-radius: 999px;
    padding: calc(4 * var(--ags-px)) calc(12 * var(--ags-px));
    font-size: calc(12 * var(--ags-px)); line-height: 1; white-space: nowrap; margin: 0;
  }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-postcard__title .elementor-heading-title {
    font-size: calc(23 * var(--ags-px)); line-height: 1.2; margin: 0;
  }
  .elementor-page [data-elementor-type="wp-page"] .ags-c-postcard__more .elementor-button-text {
    font-size: calc(11 * var(--ags-px));
  }
}

/* =============================================================================
   ARTICLE CARD (.ags-c-articlecard) — SHARED Loop Item, NOT page-scoped.
   -----------------------------------------------------------------------------
   "C · Article Card" (elementor_library post, _elementor_template_type=loop-item) — the
   overlay card from Desktop (3): a photo fills the card; tag pills (post_tag terms, dynamic,
   unlinked → one bare <span> per term) sit top-right; a date pill (dynamic Post Date) sits
   top-left; a bottom scrim carries a title/excerpt/READ-MORE stack anchored to the card's
   BOTTOM edge (so a longer/shorter excerpt never repositions the title or button). Built by
   scripts/build_articlecard.py, applied by scripts/apply_articlecard.php (Task 3, sub-project
   #2 "Blog page build" — supersedes Task 2's photo+title-only stub). Reused by the /articles
   Loop Grid (body.ags-page-articles, doc type wp-page), the About "Find your journey" grid
   (wp-page), and the single-post template's "Keep exploring" grid (doc type single-post, Task
   8) — three DIFFERENT enclosing page types, so scoping to the PAGE's own `[data-elementor-
   type]` value (as this block did originally, `.elementor-page [data-elementor-type="wp-page"]`)
   breaks on any page type that isn't "wp-page" (confirmed: the single-post template's `<body>`
   never even gets a bare `.elementor-page` class). Scoped instead to the LOOP-ITEM's OWN wrapper
   attributes — `[data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"]`
   — which are present unconditionally, on EVERY page this card is ever rendered on, because
   `.ags-c-articlecard` only ever renders AS a loop-item (never as a direct page widget). Never a
   page slug. `data-type`/`data-tags` on the card ROOT come from a PHP
   render filter (functions.php ags_articlecard_data_attrs(), keyed to the root's literal id
   "ac-root"), not from any rule here — Elementor's own dynamic tags can't emit taxonomy SLUGS
   (only display names), see that function's comment.
   Markup: .ags-c-articlecard (root — position context, clip + round, the bottom-scrim ::after)
   > .ags-c-articlecard__img (fills, absolute) + __tags (pills, abs top-right) + __date (pill,
   abs top-left) + __body (abs bottom-anchored: __title + __excerpt + __more).
   Aspect ratio 405/442 is the mockup's own card measured at its 1280 export — a scale-free
   ratio (like .ags-c-postcard__img's 1/1 above), so it holds at any grid column width without
   needing --ags-px. Everything ELSE here (pill/text sizes, insets, gaps) IS in --ags-px so it
   enlarges with the rest of the reference-frame composition on ultrawide.
   ============================================================================= */
@media (min-width: 1024px) {
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard {
    position: relative; overflow: hidden; display: block;
    aspect-ratio: 405 / 442;
    border-radius: calc(10 * var(--ags-px));
  }
  /* Bottom scrim — same recipe as .ags-master::after (Masters filmstrip, ~2813 above) for
     legibility over any photo. Height covers the title/excerpt/READ-MORE stack with slack. */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard::after {
    /* STRENGTHENED (review fix, 2026-09-20): busy "flyer" featured images (posters with their own
       baked-in text/graphics, e.g. the "2018.04 Shito-Ryu SHOBUKAN" card) could still collide with
       the title/excerpt/READ-MORE stack under the old .85→0 / 56%-tall scrim — that band faded to
       fully transparent well above where a poster's own text often sits. A 3-stop gradient (near-
       opaque at the very bottom, still substantially dark at mid-band, only clearing near the TOP
       of the band) plus a taller band gives the text a reliably dark floor on ANY photo, while the
       photo's own upper ~1/3 stays untouched (0% opacity at the band's top) so a plain, calm photo
       card doesn't read as over-darkened. */
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 64%;
    background: linear-gradient(to top, rgba(17,16,16,.94) 0%, rgba(17,16,16,.72) 40%, rgba(17,16,16,0) 100%);
    pointer-events: none; z-index: 1;
  }
  /* Photo fills the card — the root clips + rounds it, so no per-widget radius fighting this
     CSS (CSS-ownership rule: one source of truth per property). */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__img,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__img .elementor-widget-container,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__img figure {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: 0; height: 100%; width: 100%;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__img img {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block;
  }
  /* Pills (date top-left, tags top-right) — shared translucent-glass look. */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__date,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__tags {
    position: absolute; top: calc(14 * var(--ags-px)); z-index: 2; margin: 0; padding: 0;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__date { left: calc(14 * var(--ags-px)); }
  /* Extra `.ags-c-articlecard` ancestor qualifier (bumps 3→4 class-level selectors) so
     `max-width` beats Elementor core's own `.elementor.elementor .e-con > .elementor-widget
     { max-width: 100% }` (frontend.min.css) at tied specificity — components.css loads AFTER
     that stylesheet so the tie goes to source order, but ONLY once we're not simply losing on
     specificity first. Without this, a post with 3+ tags renders the pills row at ~90-100%
     width instead of the intended 74%, overlapping the date pill on the left (found while
     verifying "Keep exploring" — same bug on the /articles grid for any multi-tag post).
     `width` (not just `max-width`) on top: a shrink-to-fit auto-width abs box computes its
     flex-wrap line breaks off its OWN preferred (unclipped) content width before `max-width`
     clips the box, so with 3+ tags the pills still overlapped each other within a wrapped row
     even once the box itself was correctly clipped to 74%. A concrete `width` forces the wrap
     calculation to run against the real 74% box from the start. */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__tags {
    right: calc(14 * var(--ags-px)); width: 74%; max-width: 74%;
  }
  /* The Text Editor widget's own wrapper chain varies (a plain <p>, or a nested
     .elementor-text-editor div, depending on the DOM-optimization experiment) — `display:
     contents` on whichever wrapper is present unwraps it so its <span> children (one per
     post_tag term, from the unlinked Post Terms dynamic tag) become direct flex items of
     .elementor-widget-container, which is always present. Robust to either DOM shape. */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__tags .elementor-widget-container {
    display: flex; flex-wrap: wrap; justify-content: flex-end; gap: calc(8 * var(--ags-px));
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__tags .elementor-widget-container > p,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__tags .elementor-widget-container > .elementor-text-editor {
    display: contents;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__date .elementor-heading-title,
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__tags span {
    display: inline-flex; align-items: center; white-space: nowrap;
    /* 1px white outline + a slight grey/dark translucent tint (NOT solid) so white text stays
       legible over any photo — the reference's pill treatment (Simon feedback 2026-09-19:
       white, not cream). */
    border: 1px solid rgba(255,255,255,.75); border-radius: 999px;
    background: rgba(20,20,20,.30);
    padding: calc(6 * var(--ags-px)) calc(14 * var(--ags-px));
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(12 * var(--ags-px)); line-height: 1; color: var(--ags-white); margin: 0;
  }
  /* Bottom content stack — anchored to the card's own bottom edge, not to the excerpt's
     length (the "no hardcoded line count" rule — docs/COMPOSITION-METHODOLOGY.md §4). */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__body {
    position: absolute; left: calc(16 * var(--ags-px)); right: calc(16 * var(--ags-px));
    /* honor the right inset: Elementor's container width:100% otherwise wins over `right`,
       making the body full-width from left:16 → the title/excerpt/button ran ~16px off the
       card's right edge. Pin the width to the inset box. */
    width: calc(100% - 32 * var(--ags-px)); box-sizing: border-box;
    bottom: calc(18 * var(--ags-px)); z-index: 2;
    /* Zero Elementor's own container default padding (~10px all sides — this container never
       had one set via the builder) then re-add ONLY a bottom value, sized to the SAME unit as
       the title→excerpt / excerpt→button flex gap (colw(gap=8) in build_articlecard.py) — so
       the gap under READ MORE reads as the same rhythm as the gaps above it, instead of an
       accidental, undocumented 10px (Simon feedback 2026-09-19: card text needs padding below
       it to match the space below the title). The card's own bottom-edge margin is the
       SEPARATE `bottom` offset above (unrelated, keeps its existing value). */
    padding: 0 0 calc(8 * var(--ags-px)) 0;
  }
  /* NOTE the extra `.ags-c-articlecard` ancestor qualifier on title/date/tags/more below (vs
     just `[data-elementor-type=loop-item][data-elementor-post-type=elementor_library] .ags-c-
     articlecard__X` elsewhere in this block): a LOOP ITEM's own Kit-typography CSS (post-
     <template-id>.css) is enqueued AFTER components.css — unlike a normal single page, where
     the page's own post-CSS loads BEFORE it — so on a tied specificity these rules would lose
     to the Kit global by source order. Confirmed by inspecting the generated `.elementor-
     <loop-id> .elementor-element.elementor-element-<widget-id> .elementor-heading-title{font-
     size:var(--e-global-typography-secondary-font-size)}` rule: 4 class-level selectors — our
     2-attribute prefix + 2 card classes also totals 4 without this extra `.ags-c-articlecard`
     bump, which is what made the Kit global win by source order until this was added. One
     extra class (5 vs 4) wins regardless of load order — same invariant, now carried by the
     loop-item-attribute prefix instead of the old page-type prefix. */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__title .elementor-heading-title {
    font-size: calc(21 * var(--ags-px)); line-height: 1.2; margin: 0; font-weight: 600;
  }
  /* 2-line clamp — deliberately UNIFORM (not matching the mockup's own inconsistent per-card
     excerpt lengths) so every card in the 87-post grid holds the same shape regardless of how
     long a given post's trimmed excerpt runs. */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__excerpt .elementor-widget-container p {
    font-size: calc(13 * var(--ags-px)); line-height: 1.4; margin: 0;
    color: rgba(247,244,242,.85);
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__more { width: 100%; }
  /* Default = TRANSPARENT fill + 1px white outline + white text (red is ONLY the hover
     state — Simon feedback 2026-09-19: this is a `.ags-btn--bar` instance, whose base rule
     (~line 298) is a solid-red pill shared by other sections' bar CTAs, so it's overridden
     here at higher selector specificity rather than touched globally. #8E1E00 hover matches
     the reference's maroon (a touch darker than --ags-red, per spec). */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__more .elementor-button {
    width: 100%; display: flex; align-items: center; justify-content: space-between;
    font-size: calc(13 * var(--ags-px));
    background-color: transparent; border: 1px solid var(--ags-white); color: var(--ags-white);
  }
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard .ags-c-articlecard__more .elementor-button:hover {
    background-color: #8E1E00; border-color: #8E1E00; color: var(--ags-white);
  }
  /* Chevron — plain CSS, not an Elementor icon control (no icon-font dependency for one glyph;
     "component classes own the look", matching btn()'s own no-inline-style convention).
     Inherits `color` from .elementor-button above (currentColor via inheritance), so it's
     white in both the default and hover states without a separate rule. */
  [data-elementor-type="loop-item"][data-elementor-post-type="elementor_library"] .ags-c-articlecard__more .elementor-button::after {
    content: "\203A"; margin-left: calc(8 * var(--ags-px));
  }
}

/* `.is-hidden` state, JS-toggled (ageshio-articles.js, /articles Task 4 filter) — unscoped
   by page/breakpoint like the rest of .ags-c-articlecard (SHARED, not page-scoped): a plain
   visibility switch, not a layout rule, so it doesn't need --ags-px or the ≥1024 guard above. */
.ags-c-articlecard.is-hidden { display: none; }

/* =============================================================================
   ===== ABOUT PAGE (body.ags-page-about) =====
   -----------------------------------------------------------------------------
   Slug: about (post 19830). Scope: body.ags-page-about ONLY — portable slug hook,
   never page-id-N / .elementor-N (linter: scripts/qa/check_portable_scope.py).
   New/collapsed sections only — the REUSED components (marquee, both maroon
   .ags-c-statement bands, the §8 .ags-c-overlay "Who we serve / Why Ageshio",
   the blue .ags-c-accordion, footer) already render via the shared rules above;
   this region does not touch them except the one Task-4 divider tweak at the end.

   Section stack (DOM order, confirmed live): 1 Photo hero (+ eyebrow) · 2 Marquee
   (shared) · 3 Intro (2-col para) · 4 Approach (4 numbered cards) · 5 Mission
   statement (shared, maroon) · 6 What we do (dark cqw stage) · 7 Based in Okinawa
   (blue map band) · 8 Connection to Grand Masters intro · 9 Masters filmstrip
   (shared .ags-masters, static-ported like History) · 10 Overlay (shared) ·
   11 Statement #2 (shared, maroon) · 12 Accordion (shared, blue variant) ·
   13 Find your journey (search bar + 3 cards) · 14 Final CTA (.ags-c-cta3, NEW —
   maroon band, centred heading + 3-pill row) · 15 Footer (shared).

   Placeholders: every unsourced photo is a real Image widget pointing at
   Elementor's placeholder.png, painted neutral by the shared `.ags-c-ph` rule
   (components.css ~4784) — no image sourcing happens here. Colour is DB-owned
   (Global Colors on each widget/section) — this file is layout/position/size only;
   the one exception is the Task-4 accordion divider (visibility, not brand colour).

   RHYTHM PASS (2026-09-17, design-feedback round 3 — SUPERSEDES the two notes below): the
   round-2 "every section is a 100svh floor" treatment floated content mid-section and left an
   empty cream tail on sections whose content was shorter than one viewport (Approach read as
   "two sections" because of it; the mission statements' eyebrow→headline gap also collapsed
   under vertical centring). Simon: match the OTHER interior pages' rhythm instead — content-
   height sections, `--ags-sec-pad` top+bottom, content top-anchored, NOT forced full-viewport.
   `.ags-c-intro`, `.ags-c-approach`, `.ags-c-statement--maroon`, `.ags-c-mapband`,
   `.ags-c-masters-intro` (+ its paired `.ags-masters` filmstrip below) and `.ags-c-journey`
   are now `min-height:0` (content-height) with `justify-content:normal`, matching GT's
   `.ags-c-statement` model. `.ags-c-photohero`, `.ags-c-whatwedo`, `.ags-c-overlay` (full-bleed
   100svh stages), `.ags-c-accordion--blue` (100svh, matches GT's accordion) and `.ags-c-cta3`
   (compact band) are UNCHANGED — those correctly fill/are full-bleed. The card/photo/filmstrip
   SIZE reductions from the round-2 "VIEWPORT FIT PASS" (below each section) are kept as-is —
   only the section-level height/justify/pad wrapper changed, not the content sizing.

   VIEWPORT FIT PASS (2026-09-17, round 2 — sizing kept, framing above superseded): the four
   flagged sections (approach / mapband / journey / the masters-intro+filmstrip pair) ran
   taller than one viewport at the 1440x766 reference because their CONTENT (photo/card/
   filmstrip heights, or a title font size) was bigger than the reference band, not because of
   the pads/edges — those are untouched throughout. Each is now reduced (see the inline note at
   each section) so content+pads fit ≤766 at 1440, expressed in --ags-px so the fit holds at
   every size. Those content-size reductions remain valid under round 3's content-height model. */

/* ---- Clear Elementor's stray per-level 10px padding on every WRAPPER container
   introduced by this page (the "clear it at every level" rule — see the pre-flight
   checklist in docs/COMPOSITION-METHODOLOGY.md §4). Width-agnostic so it also holds
   under 1024 if these wrappers are ever reused there. ---- */
body.ags-page-about .ags-c-photohero__stage,
body.ags-page-about .ags-c-photohero__eyebrow,
body.ags-page-about .ags-c-intro__row,
body.ags-page-about .ags-c-intro__col,
body.ags-page-about .ags-c-approach__grid,
body.ags-page-about .ags-c-approach__item,
body.ags-page-about .ags-c-whatwedo__stage,
body.ags-page-about .ags-c-whatwedo__items,
body.ags-page-about .ags-c-whatwedo__item,
body.ags-page-about .ags-c-mapband__row,
body.ags-page-about .ags-c-mapband__left,
body.ags-page-about .ags-c-mapband__right,
body.ags-page-about .ags-c-masters-intro__row,
body.ags-page-about .ags-c-masters-intro__body,
body.ags-page-about .ags-c-masters-intro__eyebrows,
body.ags-page-about .ags-c-journey__row,
body.ags-page-about .ags-c-journey__card {
  padding: 0;
}

@media (min-width: 1024px) {

  /* ---- §1 PHOTO HERO — full-bleed photo, centred kanji eyebrow (band_00). The
     photo widget here already carries a real asset (about.webp, not a .ags-c-ph
     placeholder), so only the stage/positioning needs building. Stage height is
     "viewport-ish" (per the brief) rather than a hard 100svh — the mockup's photo
     band measures ~0.97 of a 1440x766 reference viewport (742/766). ---- */
  /* §3b FILL, not fit: the eyebrow is absolutely centred and the photo is inset:0, so
     nothing here actually needs the old max(690px, 82svh) content-height floor — that
     formula also OVERFLOWED wide-short viewports (880-style bug, same as whatwedo below:
     a px floor scaled by the height-bound --ags-scale always exceeds 100svh whenever the
     floor's reference value > 766). Plain 100svh fills every viewport exactly, matching
     the GT/VIP/History hero convention ("height:100svh, NOT aspect-ratio"). */
  body.ags-page-about .ags-c-photohero__stage {
    position: relative; overflow: hidden;
    height: 100vh;
    height: 100svh;
  }
  body.ags-page-about .ags-c-photohero__photo,
  body.ags-page-about .ags-c-photohero__photo .elementor-widget-container,
  body.ags-page-about .ags-c-photohero__photo figure {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; margin: 0;
  }
  body.ags-page-about .ags-c-photohero__photo img {
    width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block;
  }
  /* SCRIM — the hero photo is now the real B&W ageshio-way-bg.webp (not a CSS
     background-image like .ags-values), so the legibility gradient can't be baked into
     background-image here; an ::after covering the photo (below the kanji/marquee in
     z-order, above the <img>) does the same job. Same rgba as .ags-values' own treatment
     of this exact photo (components.css ~2047), kept consistent site-wide. */
  body.ags-page-about .ags-c-photohero__stage::after {
    content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; pointer-events: none;
    background: linear-gradient(rgba(17,16,16,.42), rgba(17,16,16,.42));
  }
  body.ags-page-about .ags-c-photohero__eyebrow {
    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    z-index: 2; width: -webkit-max-content; width: -moz-max-content; width: max-content; margin: 0;
    text-align: center;
  }
  /* CENTRE the romaji label under the kanji (site hard-rule, Simon 2026-08-17 — see the
     shared mobile-only .ags-c-eyebrow rule ~line 5818). The desktop override above sizes
     the eyebrow to max-content, but each heading widget still carries its own DB `align:
     left`, which the shared mobile rule doesn't reach past 1024px — reassert centring here. */
  body.ags-page-about .ags-c-photohero__eyebrow .elementor-widget-heading { width: 100%; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-photohero__eyebrow .elementor-heading-title,
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-photohero__eyebrow .elementor-widget-container {
    text-align: center;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-photohero__eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: calc(68 * var(--ags-hpx)); line-height: 1;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-photohero__eyebrow .elementor-widget-heading:last-child .elementor-heading-title {
    font-size: calc(20 * var(--ags-px));
  }
  /* FLANKING TAGLINES (Simon ref Desktop (27).png): "Authentic Okinawan Karate Experiences" (left)
     + "Guided by Local Lineage" (right) sit at mid-height flanking the centred eyebrow, on the edge
     guide, cream over the B&W photo (added as heading widgets by scripts/add_about_hero_taglines.php). */
  /* CENTRED + BALANCED (Simon 2026-09-21: "center align the text left and right of the centre
     kanji ... the right space between the elements so it looks balanced horizontally"). A FIXED
     equal width (not max-content) at SYMMETRIC insets makes the two taglines mirror-symmetric
     about the centred kanji, so the left→kanji and kanji→right gaps read even regardless of the
     two strings' different lengths (was: left-aligned 250-box vs right-aligned 200-box → the
     right gap read ~1.5x the left). */
  body.ags-page-about .ags-c-photohero__tag {
    position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; margin: 0;
    width: calc(250 * var(--ags-px)); text-align: center;
  }
  body.ags-page-about .ags-c-photohero__tag .elementor-widget-heading { width: 100%; }
  /* INSET from each edge (ref Desktop (27) — NOT flush to the edge guide); SAME inset both sides
     → symmetric. Scaled px so the inset tracks the frame. */
  body.ags-page-about .ags-c-photohero__tagl { left: calc(var(--ags-well) + 150 * var(--ags-px)); }   /* round 11: + well so the taglines flank the centred kanji within the frame on ultrawide */
  body.ags-page-about .ags-c-photohero__tagr { right: calc(var(--ags-well) + 150 * var(--ags-px)); }
  /* SAME FONT as the centred "About" eyebrow label = Bounded (Simon). Centred within the fixed box;
     each 2-word pair is bound by a non-breaking space in the copy (see the tagline script), so the
     ONLY break is between the pairs → 2 words per line, both taglines centred. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-photohero__tag .elementor-heading-title {
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(22 * var(--ags-px)); line-height: 1.32; color: var(--ags-cream);
    max-width: 100%; text-align: center; margin: 0;
  }

  /* ---- §2 MARQUEE — integrate into ONE viewport with §1 (Simon: "marquee without the red
     background so it sits on the first section full viewport, it's not separated"). The
     marquee's maroon bg was already cleared in the DB (transparent); pull the whole band UP
     by its own height so it visually overlaps the bottom of the 100svh photo hero instead of
     sitting as its own strip below it. Pin the crest + bottom pad to --ags-px (the shared
     component sizes them off raw vw) so the band's total height is a deterministic function
     of --ags-px, letting the negative margin below cancel it exactly — About-page ONLY, the
     shared vw-based marquee on every other interior page is untouched. */
  body.ags-page-about .ags-c-marquee__crest img { height: calc(110.9 * var(--ags-px)); }
  body.ags-page-about .ags-c-marquee { padding-bottom: calc(25 * var(--ags-px)); box-sizing: border-box; }
  /* -20px constant: .ags-c-marquee__unit is an Elementor e-con carrying its own stray
     unscaled 10px-top/10px-bottom default padding (measured, not part of the shared
     component's own rules) — added to the --ags-px-scaled crest+pad total below so the
     margin cancels the marquee's REAL rendered height exactly at every width (verified
     1280/1440/1920/3440: 0px residual gap in all four). */
  body.ags-page-about .ags-c-marquee {
    position: relative; z-index: 3;
    margin-top: calc((-135.9 * var(--ags-px)) - 20px);
  }

  /* ---- §3 INTRO — two-column paragraph block, anchored TOP-LEFT of the section on the
     shared edge guide (Simon 2026-09-17: "next to each other, top left", confirming band_00 —
     do not span the full row width). RHYTHM PASS (round 3): content-height, NOT a 100svh
     floor — the old floor left an empty cream tail below the pair on any viewport taller than
     the content. --ags-sec-pad top+bottom, content top-anchored (matches GT's statement). ---- */
  /* ONE-VIEWPORT PASS (2026-09-21, round 4 — SUPERSEDES round 3's content-height decision for
     the Intro+Approach PAIR only, per Simon's reference Desktop (25).png + "fix it so the section
     is in the one viewport at different screen sizes"). §2b Intro + §3 Approach are now the §3b
     min-height PAIR (docs/COMPOSITION-METHODOLOGY.md §3b): they SUM to 100svh, the internal
     boundary is COLLAPSED (intro bottom-pad:0, approach top-pad:0 → one continuous cream area, not
     two pads stacking into a gulf), pad on the OUTSIDE only. The empty-cream-tail that made round 3
     drop 100svh is avoided because the Approach card images FLEX-FILL the leftover space (below) —
     the composition fills the viewport instead of top-anchoring short content. Intro paras sit
     top-left; the lead gap below them is the pair's flexible space (reads as the reference's
     airy top), then the big right headline + the 4 filling cards occupy the lower band. */
  /* ROUND 5 (2026-09-21, ref Desktop (27) + Simon notes): reverted the round-4 fixed-height
     one-viewport PAIR — a fixed height clipped the intro on shorter viewports, and the flex-fill
     forced the cards LANDSCAPE. Back to CONTENT-HEIGHT (no clip) with reference-accurate PORTRAIT
     cards; the section is now sized by its content (a scrolling composition like the reference),
     not forced into one viewport. */
  /* ROUND 6 (2026-09-21, ref Desktop (28)): ONE VIEWPORT with SQUARE cards + WIDER top-left intro.
     Intro + Approach are a pair summing to 100svh; the intro sits top-left, "Our Approach" + the
     square cards are pinned to the bottom of their band (justify-content:flex-end), so the SLACK
     between them is the big flexible gap the reference shows (grows on taller screens, no clip). */
  body.ags-page-about .ags-c-intro {
    padding-left: var(--ags-edge-guide);
    padding-right: var(--ags-edge-guide); padding-top: calc(52 * var(--ags-px)); padding-bottom: 0; box-sizing: border-box;   /* round 11: cap to frame guide on ultrawide */
    height: 24vh;
    height: 24svh; display: flex; flex-direction: column; justify-content: normal;
  }
  /* Row is now INTRINSICALLY sized (not stretched to the full frame width) so the pair reads
     as a compact block hugging the left edge guide, not spanning edge-to-edge — was
     max-width:frame-2edge + margin-inline:auto (full-span, centred), which is the opposite of
     "top-left, not spanning the left/right". flex-start is the default main-axis alignment,
     stated explicitly for clarity. */
  body.ags-page-about .ags-c-intro__row {
    display: flex; gap: var(--ags-edge); justify-content: flex-start;   /* round 8: match the card grid's gap so paras align with the cards below */
  }
  /* Each column is now a FIXED (shrinkable) width rather than flex:1 1 0 — the old equal-stretch
     rule is what forced the row to span the whole guide-to-guide width regardless of content;
     capping each column keeps the pair a tight top-left block (measured against band_00's own
     column proportion, ~300px @1440-equivalent). */
  body.ags-page-about .ags-c-intro__col {
    /* round 8: each column = ONE card's width (same formula + cap + gap as the .ags-c-approach__grid
       below), so para 1 sits over card 1 and para 2 over card 2 — the paragraphs align to the outer
       edges of the photo boxes. */
    flex: 0 0 auto; min-width: 0;
    width: calc((100% - 3 * var(--ags-edge)) / 4); max-width: calc(336 * var(--ags-px));
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-intro__para .elementor-widget-container {
    font-family: 'Red Hat Display', sans-serif; font-weight: 400;   /* round 8: 300→400 — slightly darker/heavier, matches the reference */
    font-size: calc(15 * var(--ags-px)); line-height: 1.55; text-align: left;
  }

  /* ---- §4 APPROACH — right-aligned red title over a 4-equal-column card row,
     each card = photo (near-square) / number / red caption, left-aligned (band_01
     top). Flex row on the shared edge guide, matching the interior card-row idiom. ---- */
  /* RHYTHM PASS (round 3): content-height, NOT a 100svh floor — the old floor left an empty
     cream tail below the card row on any viewport taller than the content, which is exactly
     what made Approach read as "two sections" (title+cards, then a blank band). Removing the
     floor removes the tail; the section is now title+grid+--ags-sec-pad top/bottom, same as
     GT's statement. justify-content:normal (top-anchored) keeps the title pinned to the top
     pad, matching every other content section. VIEWPORT FIT PASS (2026-09-17, round 2, content
     sizing kept): at the 1440x766 reference this section's content (title + gap + 4 cards)
     measured ~857px tall — over by ~91px. band_01's photos are near-square (measured H:W ≈
     1.09, not far off), but the card WIDTH is already frame-scaled to the reference guide
     (336px @1440 — matches band_01's own 4-card row measured against its edge guide, do not
     touch), so the only lever that shrinks height without pulling the row off the guide is the
     aspect-ratio itself: pushed short-landscape (below) to close the gap. Pads/edges untouched. */
  /* round-4 (see the Intro note above): lower half of the one-viewport pair. top-pad:0 collapses
     the boundary with §2b; bottom-pad only; min-height 66svh so Intro(34)+Approach(66)=100svh. The
     grid below is flex:1 so the card images fill the leftover band height (no empty tail). */
  body.ags-page-about .ags-c-approach {
    padding-left: var(--ags-edge-guide);
    padding-right: var(--ags-edge-guide); padding-top: 0; padding-bottom: calc(52 * var(--ags-px)); box-sizing: border-box;   /* round 11: cap to frame guide on ultrawide */
    height: 76vh;
    height: 76svh; display: flex; flex-direction: column; justify-content: flex-end;   /* round 6: one-viewport pair (24+76); content pinned bottom, slack above = the reference's gap */
  }
  body.ags-page-about .ags-c-approach__h { text-align: right; }
  /* ONE-VIEWPORT PASS (2026-09-17, round 3): section still measured 760-789px at the 1440
     reference (over/knife-edge against 766) even after the round-2 photo-aspect cut below —
     shaving the title (88→80) and the title→grid gap (38→28) buys the rest of the margin
     without touching the card WIDTH or the guide it sits on. */
  /* round-4: restored toward the reference's large right-aligned headline (was shaved to 80 in
     round 3 to fit the content-height section; the cards now flex-fill, so a bigger headline just
     takes a little card height, it doesn't overflow). */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__h .elementor-heading-title {
    font-size: calc(74 * var(--ags-hpx)); line-height: 1;   /* round 8: 88→74 — the title read too big on most screens */
  }
  /* justify-content:space-between so once items hit their max-width (below) the leftover
     ultrawide space grows the GAPS rather than the cards — keeps both edges flush on the
     shared guide (matching the title above) instead of the row bunching left. */
  body.ags-page-about .ags-c-approach__grid {
    display: flex; gap: var(--ags-edge); margin-top: calc(28 * var(--ags-px));
    justify-content: space-between; align-items: flex-start;
    /* round 7 (ref Desktop (29) via the composition doc): the card gap is FRAME-PROPORTIONAL
       (--ags-edge = 1.83% of frame, ~26px @1440), matching the design's airy card spacing and
       scaling consistently at every width — was a too-tight fixed 14px that read cramped on the
       wide reference frame. The cards flex-shrink slightly to make room, keeping the row on the guide. */
  }
  /* max-width caps each card to its reference (1440) size — otherwise flex:1 1 0 with no cap
     tracks the raw viewport (2.4x at 3440 vs the ideal 1.88x) and the placeholder photos
     balloon far past the caption/number type sitting under them. */
  body.ags-page-about .ags-c-approach__item {
    flex: 1 1 0; min-width: 0; max-width: calc(336 * var(--ags-px));
    display: flex; flex-direction: column; align-items: stretch;
    gap: calc(12 * var(--ags-px)); min-height: 0;   /* round-4: allow the image child to flex-fill */
  }
  /* Elementor's own per-element default here is --align-items:flex-start (shrink-to-fit),
     which collapsed the placeholder box to its native tiny size — align-items:stretch above
     fixes the cross axis, but pin width explicitly too so it holds regardless. */
  /* Short-landscape (was 1/1.05, near-square) — the only lever available to cut the ~91px
     the section ran over at 1440x766 without shrinking the card WIDTH off the reference
     guide (see the section-level note above). ONE-VIEWPORT PASS (round 3): pushed a little
     further, 0.78→0.70, alongside the title/gap trims above — together they take the section
     from knife-edge-at-766 to a comfortable margin under one viewport. */
  /* round-4: images FLEX-FILL the card's leftover height (was a fixed aspect-ratio tuned to squeeze
     the content-height section under 766). The num+label zone below has a reserved min-height so all
     four cards devote the SAME height to their caption → the images end up EQUAL height with aligned
     bottoms (the reference), regardless of a caption running 3 vs 4 lines. The image then grows/shrinks
     with the viewport so the whole pair fills exactly one screen at any height. */
  /* round 6: SQUARE cards (ref Desktop (28) + Simon). */
  body.ags-page-about .ags-c-approach__img { width: 100%; aspect-ratio: 1; }
  body.ags-page-about .ags-c-approach__img .elementor-widget-container,
  body.ags-page-about .ags-c-approach__img figure { height: 100%; width: 100%; margin: 0; }
  body.ags-page-about .ags-c-approach__img img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; }
  /* card 4 = the reference's dark, dramatic solo closer. The real photo (Simon, Rectangle 62) is
     ALREADY a spotlit karateka in a near-black dojo hall — the exact moody tone the reference wanted
     — so it needs no CSS darkening (the old `filter: brightness(0.5)…` was a hack to dim a bright
     library placeholder and would crush this already-dark shot). The figure is horizontally centred,
     so the square cover-crop needs no object-position nudge either. */
  body.ags-page-about .ags-c-approach__num { margin-top: calc(2 * var(--ags-px)); flex: 0 0 auto; }
  /* Reserve the TALLEST caption's height (the longest EN caption wraps to 5 lines in the ~190px
     column) so every card devotes equal space to num+caption → with align-items:stretch the images
     come out EQUAL height, bottoms aligned (the reference); shorter captions just leave whitespace
     below, exactly as the reference does. Sized to cover the longest caption ACROSS languages (DE's
     card-4 is the tallest); the widened column (below) brings de/nl down so 140px reserves all four
     → images stay equal in every language (EN's shorter captions just leave whitespace below). */
  /* round 5: no caption-height reserve needed — images are equal via the FIXED aspect + flex-start,
     so captions just hang below at their natural length (the reference: card 3's shorter caption
     leaves whitespace below, images stay aligned). */
  body.ags-page-about .ags-c-approach__label { flex: 0 0 auto; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__num .elementor-heading-title {
    font-size: calc(18 * var(--ags-px)); font-weight: 600; line-height: 1;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-approach__label .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(15 * var(--ags-px)); line-height: 1.3;   /* round-4: 17→15, matches intro paras + the reference; fewer/cleaner caption lines */
  }
  /* Caption colour is already the brand red from the DB (verified: computed #8E1E00 on the
     title/numbers/captions) — only the WRAP needed fixing. band_01's captions wrap in a
     column roughly half the card width, not the full card width the caption defaulted to
     (measured off the mockup: ~160px @1440-equivalent, wide enough for "cultural exchange",
     the widest single line across all 4 captions). Elementor's own
     `.elementor .e-con > .elementor-widget { max-width:100% }` (frontend.min.css, 0,4,0) wins
     over a plain page-scoped max-width, hence !important. */
  body.ags-page-about .ags-c-approach__label { max-width: calc(230 * var(--ags-px)) !important; }  /* round-4: 190→230 so longer es/de/nl captions wrap to fewer lines (still narrower than the 336 card, matching the reference) */

  /* ---- §6 WHAT WE DO — dark full-bleed cqw editorial stage (SAME architecture as
     .ags-c-overlay__stage, band_02): 5 numbered items pinned near the top, kanji
     eyebrow left-mid, caption right-mid, giant title bottom-right. The dashed
     decorative swoosh from the mockup sits centred over the photo (Simon
     2026-09-21, ref Image #31) — see .ags-c-whatwedo__stage::before below;
     asset uploads/redesign/whatwedo-dash.webp (888x471, white on transparent). ---- */
  /* §3b FILL: max(calc(880*px), 100svh) LOOKED like the right "floor, not cap" shape but
     880 > the 766 reference frame height, so whenever --ags-scale is height-bound (any
     wide-short viewport, e.g. 1920x900) `880 * scale` works out to ~1.15x the viewport and
     the 880 branch wins over 100svh — overflow, not fill (measured: 1034px stage in a 900px
     viewport). Every child here is absolutely positioned off top/bottom/%, not flow height,
     so nothing actually needs 880 — plain 100svh fills exactly, matching photohero above. */
  body.ags-page-about .ags-c-whatwedo__stage {
    position: relative; overflow: hidden; container-type: inline-size;
    height: 100vh;
    height: 100svh;
  }
  body.ags-page-about .ags-c-whatwedo__photo { position: absolute; top: 0; right: 0; bottom: 0; left: 0; }
  body.ags-page-about .ags-c-whatwedo__photo.ags-c-ph { aspect-ratio: auto; width: 100%; height: 100%; }
  body.ags-page-about .ags-c-whatwedo__photo .elementor-widget-container,
  body.ags-page-about .ags-c-whatwedo__photo figure { height: 100%; width: 100%; margin: 0; }
  body.ags-page-about .ags-c-whatwedo__photo img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; }
  body.ags-page-about .ags-c-whatwedo__stage > :not(.ags-c-whatwedo__photo) { position: absolute; z-index: 2; }
  /* DASHED SWOOSH (Simon ref Image #31) — decorative dashed line centred over the photo,
     BELOW the text: z:1 sits above the z:auto photo and under the z:2 content. Box matches the
     swoosh's bbox in the reference; background-size:contain keeps the asset's 1.885:1 aspect.
     Desktop-only (this whole block is min-width:1024; mobile handled separately). */
  body.ags-page-about .ags-c-whatwedo__stage::before {
    content: ""; position: absolute; z-index: 1; pointer-events: none;
    left: 21%; top: 25%; width: 63%; height: 56%;   /* round 9: nudged down + right (Simon) */
    background: url("/wp-content/uploads/redesign/whatwedo-dash.jpg") no-repeat center / contain;
    background: url("/wp-content/uploads/redesign/whatwedo-dash.webp") no-repeat center / contain;
    opacity: 0.6;
  }
  /* §4 + §7 share the bright karateka photo — darken it (the reference scrims it) so the white gi
     doesn't blow out and the cream text stays legible. Same per-page brightness idiom as History/KE. */
  body.ags-page-about .ags-c-whatwedo__photo img,
  body.ags-page-about .ags-c-overlay__photo img { filter: grayscale(1) brightness(0.5); }

  /* Elementor forces width:100% on every .e-con container by default (its own
     --container-widget-width consumer) — for an absolutely-positioned container that
     beats left+right auto-sizing and the row ran edge-to-edge, clipping item 5 past the
     viewport. Explicit width (0,2,1) beats it regardless of source order. */
  body.ags-page-about .ags-c-whatwedo__items {
    top: var(--ags-sec-pad); left: var(--ags-edge-guide); right: var(--ags-edge-guide);   /* round 11: cap to frame guide on ultrawide */
    width: calc(100% - 2 * var(--ags-edge-guide));
    display: flex; justify-content: space-between; gap: calc(16 * var(--ags-px));
  }
  body.ags-page-about .ags-c-whatwedo__item { flex: 0 1 auto; max-width: calc(200 * var(--ags-px)); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__num .elementor-heading-title {
    font-size: min(1.4cqw, calc(20 * var(--ags-px))); font-weight: 600; line-height: 1;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__label .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: min(1.05cqw, calc(15 * var(--ags-px))); line-height: 1.3;
  }

  /* Kanji eyebrow, left-mid: the BLOCK is left-POSITIONED (left:edge, width:max-content = as wide
     as 旅行), but the TEXT is CENTRED so the "About" romaji label centres under the kanji — Simon's
     site hard-rule (2026-08-17, reaffirmed 2026-09-21). 旅行 is the widest line so centring doesn't
     move it; only the narrower "About" shifts to centre. */
  body.ags-page-about .ags-c-whatwedo__eyebrow.ags-c-eyebrow {
    left: var(--ags-edge-guide); right: auto; top: 56%; bottom: auto; transform: translateY(-50%);   /* round 9: 44%->56%, kanji sits lower per ref Image #31; round 11: cap to frame guide */
    text-align: center; width: -webkit-max-content; width: -moz-max-content; width: max-content;
  }
  /* Each heading widget must FILL the max-content container (= 旅行's width) so the narrower
     "About" has room to centre within it — without this the widget is shrink-to-fit and
     text-align:center is a no-op (mirrors the photohero eyebrow fix ~line 7863). */
  body.ags-page-about .ags-c-whatwedo__eyebrow.ags-c-eyebrow .elementor-widget-heading { width: 100%; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__eyebrow.ags-c-eyebrow .elementor-heading-title,
  body.ags-page-about .ags-c-whatwedo__eyebrow.ags-c-eyebrow .elementor-widget-container { text-align: center; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__eyebrow .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: min(6.5cqw, calc(64 * var(--ags-hpx))); line-height: 1;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__eyebrow .elementor-widget-heading:last-child .elementor-heading-title {
    font-size: min(1.6cqw, calc(18 * var(--ags-px)));
  }

  body.ags-page-about .ags-c-whatwedo__caption {
    right: var(--ags-edge-guide); top: 66%; bottom: auto; transform: translateY(-50%);   /* round 9: 42%->66%, paragraph lower per ref #31; round 11: cap to frame guide */
    /* !important: beat Elementor's `.e-con > .elementor-widget{max-width:100%}` which
       otherwise expands this caption to full width → one unwrapped line (matches the
       .ags-c-approach__label fix). */
    /* round 9 (Simon 2026-09-21): 280->300 so the paragraph wraps EXACTLY like ref Image #31 —
       "Everything is designed to" / "give you a complete" / "Okinawan karate experience" /
       "— not just a visit." The window is tight (measured EN widths): "Okinawan karate experience"
       =292px must fit and "Everything is designed to give"=308px must NOT → W in [292,308); 300 is
       centred in it. cqw cap kept proportional (22->23.5). */
    max-width: min(23.5cqw, calc(300 * var(--ags-px))) !important;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__caption .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400; text-align: right;
    font-size: min(1.5cqw, calc(17 * var(--ags-px))); line-height: 1.4;
  }

  body.ags-page-about .ags-c-whatwedo__title { right: var(--ags-edge-guide); bottom: var(--ags-sec-pad); left: auto; top: auto; }   /* round 11: cap to frame guide */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-whatwedo__title .elementor-heading-title {
    /* round 9 (Simon 2026-09-21, ref Image #31): 148->86 — the title read too big (spanned ~72%
       of the frame; the reference is ~44%). cqw cap kept proportional (10.5->6). */
    font-size: min(6cqw, calc(86 * var(--ags-hpx))); line-height: 1; text-align: right; margin: 0;
  }

  /* ---- §7 BASED IN OKINAWA — blue map band, two flex columns (band_03 top):
     left = intro/list/tail stacked with the big title bottom-left; right = the
     Japan-map placeholder filling the column. ---- */
  /* RHYTHM PASS (round 3): content-height, NOT a 100svh floor+centre — centring on a floor
     left an inconsistent top/bottom pad depending on viewport height (and, on any viewport
     taller than the content, an empty cream tail). justify-content:normal (top-anchored)
     matches every other content section; --ags-sec-pad top+bottom is unchanged. VIEWPORT FIT
     PASS (2026-09-17, round 2, content sizing kept): the row was 836px at 1440x766 (over by
     70) — the LEFT column (intro+list+tail+title, stacked with justify-content:space-between)
     is the tallest column and drives the row's height via align-items:stretch, so the map on
     the right simply follows it down once the left column shrinks (see __title below).
     Pads/edges untouched. */
  /* FILL PASS (2026-09-17): Simon asked this band to FILL one viewport (min-height:100svh) with
     the two-column content vertically centred/balanced in the space between the pads — matching
     the mission statements' treatment, not the content-height model the round-3 rhythm pass used
     everywhere else on this page. padding-block stays the shared --ags-sec-pad top+bottom
     (unchanged, still equal); justify-content:center distributes the leftover height above/below
     the row itself (the row's OWN internal composition — the left column's space-between stack —
     is untouched, since the row keeps its intrinsic content height and only the band's cross
     centring changes). */
  body.ags-page-about .ags-c-mapband {
    padding-left: var(--ags-edge);
    padding-right: var(--ags-edge); padding-top: var(--ags-sec-pad); padding-bottom: var(--ags-sec-pad); box-sizing: border-box;
    min-height: 100vh;
    min-height: 100svh; display: flex; flex-direction: column; justify-content: center;
  }
  /* Cap+centre: the left text column is already frame-scaled (calc(500*px)), but the map
     column (flex:1 1 0) absorbs 100% − left − gap of a viewport-tracking row, so it grew to
     2.76x reference at 3440 (faster even than the raw 2.39x vw-scale). Capping the ROW to the
     frame's usable width keeps the map:text proportion the same as the 1440 reference. */
  body.ags-page-about .ags-c-mapband__row {
    width: 100%; max-width: calc(var(--ags-frame) - 2 * var(--ags-edge)); margin-left: auto; margin-right: auto;
    display: flex; align-items: stretch; gap: calc(56 * var(--ags-px));
  }
  body.ags-page-about .ags-c-mapband__left {
    flex: 0 1 calc(500 * var(--ags-px)); min-width: 0;
    display: flex; flex-direction: column; justify-content: flex-start; align-items: stretch;
    /* round 9 (Simon 2026-09-21, ref Image #33): BUNCH intro/list/tail with a tight consistent gap
       at the TOP instead of space-between spreading them across the full (map-driven) column height;
       the big title is pushed to the bottom by margin-top:auto (below).
       round 10: 18->11 — tighter inter-paragraph spacing (Simon "reduce the spacing between the
       different paragraphs"), which also recovers the height for the slightly larger body font. */
    gap: calc(11 * var(--ags-px));
  }
  body.ags-page-about .ags-c-mapband__right { flex: 1 1 0; min-width: 0; display: flex; }
  body.ags-page-about .ags-c-mapband__map.ags-c-ph { width: 100%; height: 100%; aspect-ratio: auto; }
  /* REAL map (SVG): the fill rule above was scoped to .ags-c-ph only, so once the real
     about-japan-map.svg was wired it lost its sizing and froze at its intrinsic 748px —
     dropping from 52% of the band at 1440 to 22% at 3440 (a classic ultrawide freeze).
     Fill the column width and let the vector scale cleanly with the frame-capped row, so the
     map:text proportion holds 1440→3440 (matches band_03: map ~right-half, large). */
     Derive WIDTH from the available band HEIGHT (100svh − 2·sec-pad) so the vector grows with
     the frame but the map never overruns its one-viewport share (§3b: min-height is a floor;
     a width:100% map went 703→1322 tall and pushed the band to 1631 at 3440). Cap at the
     column width too. Aspect 748:633 = 1.1817. Right-aligned per band_03. */
  body.ags-page-about .ags-c-mapband__right { justify-content: flex-end; }
  body.ags-page-about .ags-c-mapband__map:not(.ags-c-ph) { width: auto; }
  body.ags-page-about .ags-c-mapband__map:not(.ags-c-ph) img {
    width: min(100%, calc((100vh - 2 * var(--ags-sec-pad)) * 748 / 633));
    width: min(100%, calc((100svh - 2 * var(--ags-sec-pad)) * 748 / 633));
    height: auto; display: block; margin-left: auto;
  }

  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-mapband__intro .elementor-widget-container,
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-mapband__list .elementor-widget-container,
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-mapband__tail .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400;
    /* round 10 (Simon 2026-09-21, ref Image #33): 15->16 — bump the body slightly back up; the
       vertical budget for the big 2-line title is recovered by TIGHTENING the inter-paragraph gaps
       (column gap + list margins below), not by shrinking type. (round 9 had dropped it to 15.) */
    font-size: calc(16 * var(--ags-px)); line-height: 1.4;
  }
  /* WRAP AUDIT: these three widgets defaulted to the full 500px left column (align-items:
     stretch), so lines ran much longer than band_03 ("Okinawa is the birthplace of karate
     and home to" on one line vs the mockup's "Okinawa is the birthplace of / karate and
     home to the original"). Narrow to the measured mockup column (~320px @1440-equivalent)
     so the same words break in the same places — driven by width, not a hardcoded line
     count. !important: same Elementor `.e-con > .elementor-widget` max-width:100% fight as
     the approach caption above. */
  body.ags-page-about .ags-c-mapband__intro { max-width: calc(350 * var(--ags-px)) !important; }
  body.ags-page-about .ags-c-mapband__list { max-width: calc(335 * var(--ags-px)) !important; }
  body.ags-page-about .ags-c-mapband__tail { max-width: calc(280 * var(--ags-px)) !important; }
  body.ags-page-about .ags-c-mapband__list ul { margin: 0.3em 0; padding-left: 1.1em; }   /* round 10: 0.6em->0.3em, tighter "Our location" → bullets gap (Simon) */
  body.ags-page-about .ags-c-mapband__title { margin-top: auto; }   /* round 9: push the big title to the bottom of the column (ref Image #33) */
  /* round 9 (Simon 2026-09-21, ref Image #33): 44->92 — a much bigger 2-line "Based in / Okinawa"
     title, bottom-left. It wraps to 2 lines because at 92px "Based in Okinawa" exceeds the 500px
     left-column width while "Based in" fits within it. (Earlier this was shaved to 44 to squeeze
     the column under one viewport with space-between; bunching the text at the top now frees the
     height for a large title.) */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-mapband__title .elementor-heading-title {
    font-size: calc(86 * var(--ags-hpx)); line-height: 1.05; margin: 0;
  }

  /* ---- §8 CONNECTION TO GRAND MASTERS — cream intro band (band_03 bottom /
     band_04 top): big red title left, intro + MEET THE MASTERS pill right
     (right-aligned), the 師師/師 eyebrow pair below, left-aligned under the title.

     PAIR with §9 below (same "History Tour Locations" idiom: titleband + card row = ONE
     cohesive block, pad on the OUTSIDE of the pair only). The internal boundary is
     collapsed (no padding-bottom here, only a small 24px top-pad on .ags-masters below).
     RHYTHM PASS (round 3): dropped the 40svh/40svh viewport-floor split from the pair —
     on any viewport taller than ~950px it opened an empty gap between the intro block and
     the filmstrip row below (the same "two sections" symptom flagged elsewhere on this
     page), re-splitting a pair that's supposed to read as one block. Both halves are now
     content-height (min-height:0); the pad-outside-only structure and the internal-
     boundary collapse are unchanged. VIEWPORT FIT PASS (2026-09-17, round 2, content
     sizing kept): the pair's own content summed to ~970px at 1440x766 (title ~485 +
     filmstrip ~486) against a 766-tall reference — over by ~205. The filmstrip's
     fixed-height row is most of that (380px alone), so it takes most of the cut (below,
     §9); the intro title/eyebrows/body sizes are trimmed a smaller amount each so the pair
     (intro band + filmstrip band, pad on the outside only) fits one viewport. Same
     edge/pad tokens throughout — nothing here is a pad or margin.

     MIRROR-HOMEPAGE PASS (2026-09-17, round 4, SUPERSEDES the round-2 shrink-to-fit budget
     above): Simon flagged the §8/§9 pair should look/behave like the homepage's own Authentic
     Lineage masters section — a 2-line title (not 3), a big red kanji legend (~72-84px, was
     30px), and TALL portrait card boxes (was a short ~260px strip). The homepage's OWN masters
     section is explicitly the one section on that page allowed to run taller than the rest
     (min-height:95svh vs every other section's Figma ratio) — it's a special feature block, not
     a content-height content section. Same license applies here: the round-2 "fit one viewport"
     budget for THIS pair is retired; title/kanji/row are sized to match the homepage's own
     proportions (measured off the live homepage at the 1440 reference) rather than a shrink
     target. The rest of the page (approach/mapband/journey) keeps its one-viewport budget —
     this is scoped to the masters pair only. */
  /* ONE-VIEWPORT PASS (2026-09-17, round 5): the intro+filmstrip PAIR measured 950px tall at
     1440x766 — 184px over one viewport — even after the round-4 mirror-homepage sizing. Simon's
     named lever (reduce the title→kanji and kanji→cards gaps) plus mirroring the HOMEPAGE's own
     reduced pad for this exact feature block (60px top / 20px bottom, not the standard
     --ags-sec-pad — the homepage masters section already gets this same license, see components.css
     ~2677) frees enough budget to keep the card row genuinely tall (not shrunk to a strip).
     padding-bottom:0 clears a stray Elementor default (24px) on this container so the pair's
     internal boundary is controlled ONLY by the filmstrip's own padding-top below, matching the
     established "pad on the outside only, boundary collapsed" pattern already used for this pair.
     gap:0 kills Elementor's default 20px flex-gap between __row and __eyebrows so the ONLY lever
     for that gap is __eyebrows' own margin-top (below), not two stacked, undocumented sources. */
  /* FILL PASS (2026-09-17, round 6 — the pair's HEIGHT budget, supersedes round 5's "shrink
     content to fit under one viewport" goal below): Simon asked this pair to FILL exactly one
     viewport — intro at its own content height (unchanged, still content-height/top-anchored,
     min-height:0), filmstrip cards GROWING to take up whatever's left, rather than everything
     shrinking to squeeze under 100svh. --ags-mi-pair-h names the intro's own rendered height
     (measured live at the 1440 reference, scale=1: 370.4375px) as a reusable token — declared on
     the page body (the pair's nearest common ancestor; a custom property on .ags-c-masters-intro
     itself wouldn't reach its FILMSTRIP SIBLING below, since these are two separate top-level
     Elementor sections, not nested) so .ags-masters__row's height formula below can subtract it
     from 100svh. Scales with --ags-px exactly like every other frozen-px constant in this file, so
     it holds at every viewport width, not just 1440. */
  body.ags-page-about {
    --ags-mi-pair-h: calc(370.4375 * var(--ags-px));
  }
  body.ags-page-about .ags-c-masters-intro {
    padding-left: var(--ags-edge-guide);
    padding-right: var(--ags-edge-guide); padding-top: calc(60 * var(--ags-px)); padding-bottom: 0;   /* round 11: cap to frame guide on ultrawide */
    box-sizing: border-box; gap: 0;
    min-height: 0; display: flex; flex-direction: column; justify-content: normal;
  }
  body.ags-page-about .ags-c-masters-intro__row {
    display: flex; align-items: flex-start; justify-content: space-between; gap: calc(40 * var(--ags-px));
  }
  body.ags-page-about .ags-c-masters-intro__h { flex: 1 1 0; min-width: 0; }
  /* MIRROR-HOMEPAGE (round 4): the 8em cap actually rendered 3 lines ("Connection" / "to Grand"
     / "Masters") — measured via canvas text-metrics at 58px Bounded: "Connection"=402px,
     "Connection to"=493px, "Connection to Grand"=720px, "Grand Masters"=520px against the old
     464px (8em) box, so the wrap broke after word 1, not word 2. Matching the homepage's
     "Authentic Lineage" 2-line treatment (font bumped 58→66, closer to the homepage's own
     measured 67.5px) needs a box that fits "Connection to" (≈561px @66px) and "Grand Masters"
     (≈592px) but NOT "Connection to Grand" (≈819px) — 10em (660px @66px) sits in that window
     with margin either side, and holds at any size since em scales with the font together. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__h .elementor-heading-title {
    font-size: calc(66 * var(--ags-hpx)); line-height: 1.05; max-width: 10em; margin: 0;
  }
  /* WRAP AUDIT: 430px wrapped the paragraph a line tighter than band_03/04 ("Train within a
     living karate tradition guided" fit on one line in the mockup; at 430 it broke after
     "guided" one word earlier). Widened to the measured mockup column (~540 @1440-equiv) so
     the same words break in the same places. */
  body.ags-page-about .ags-c-masters-intro__body {
    flex: 0 1 calc(540 * var(--ags-px)); min-width: 0;
    display: flex; flex-direction: column; align-items: flex-end;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__text .elementor-widget-container {
    font-family: 'Bounded', sans-serif; font-weight: 400; text-align: right;
    font-size: calc(14 * var(--ags-px)); line-height: 1.4;
  }
  body.ags-page-about .ags-c-masters-intro__btn { margin-top: calc(14 * var(--ags-px)); }

  /* MIRROR-HOMEPAGE (round 4): was margin-top 26 / kanji 30px / label 12px — the round-2 cut
     shrank this legend well below the homepage's own .ags-masters__kanji treatment (measured
     live: 72px kanji, ~77px gap from the title above). Restored to the homepage's scale (kanji
     in the brief's 72-84px range) and a bigger title→legend gap so it reads as the same "big
     red rank legend under the title" feature, not a small caption row. */
  /* ONE-VIEWPORT PASS (round 5): margin-top trimmed 48→20 (this is now the ONLY source of the
     title→kanji gap — the container's own default flex-gap is killed above) — the main lever that
     brings the intro+filmstrip pair back under one viewport without touching the kanji font-size
     or the card row's height. */
  body.ags-page-about .ags-c-masters-intro__eyebrows {
    /* margin-top 20→70 (Simon 2026-09-22 r2): drops the 師範/師 legend down (matching homepage §9) — and
       because ageshio-masters.js measures this intro to size the filmstrip (--ags-mi-pair-h), the taller
       intro shrinks the cards ~10% while the pair still fills one viewport. Legend stays ~16px above the cards. */
    display: flex; gap: calc(40 * var(--ags-px)); margin-top: calc(70 * var(--ags-px));
  }
  body.ags-page-about .ags-c-masters-intro__eb { width: -webkit-max-content; width: -moz-max-content; width: max-content; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__eb .elementor-widget-heading:first-child .elementor-heading-title {
    font-size: calc(74 * var(--ags-hpx)); line-height: 1;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-masters-intro__eb .elementor-widget-heading:last-child .elementor-heading-title {
    font-size: calc(14 * var(--ags-px));
  }

  /* ---- §9 MASTERS FILMSTRIP — reuses the homepage MASTERS accordion component
     (.ags-masters/.ags-master/.ags-portrait/__rank/__caption/__name/__title), which
     is collapsed here because there is no masters JS on interior pages. Ported from
     the History Tour static override (components.css, search
     body.ags-page-karate-history-tour + ags-master, ~line 6698): the ONE
     .ags-master.is-active card (Kenjiro Ueda) stays WIDE via CSS alone; the rest are
     narrow slivers. Every selector below is body.ags-page-about-scoped, which
     out-specifies the unscoped homepage rules regardless of source order (matches
     the History pattern) — this is NOT a homepage change. */
  /* ONE-VIEWPORT PASS (round 5): padding-bottom mirrors the homepage's own reduced bottom pad for
     this feature block (20px, not the standard --ags-sec-pad — see the intro note above); padding-
     top (the kanji→cards gap, Simon's other named lever) trimmed 24→16. */
  body.ags-page-about .ags-masters {
    padding-top: calc(16 * var(--ags-px));
    padding-bottom: calc(20 * var(--ags-px));
    box-sizing: border-box;
    /* Second half of the masters-intro+filmstrip pair (see the note above the intro block).
       RHYTHM PASS (round 3): dropped the min-height floor (was 40svh, and 60svh before that)
       — any floor bigger than the row's own content re-opens a gap ABOVE the row, INSIDE the
       pair, on tall viewports (the same "floor manufactures a gap" failure the 60svh value hit
       before it — see git history) — content-height (min-height:0) means the row's own
       calc(380*px) height (round 4, was 260 — see the note on .ags-masters__row below) is
       always what's rendered, never inflated by a floor. */
    min-height: 0; display: flex; flex-direction: column; justify-content: normal;
  }
  /* FILL PASS (2026-09-17, round 6 — SUPERSEDES this whole round-5 note: the goal is no longer to
     shrink the row so the pair fits UNDER one viewport, it's to GROW the row so the pair FILLS one
     viewport exactly): the row (and, via `.ags-master.e-con{height:100%}`, every card in it — the
     cards derive their height from the row, not the other way round, so growing the row alone
     grows the cards) now takes whatever's left of 100svh after the filmstrip's own top/bottom pad
     (16 + 20, above) and the intro pair-mate's own rendered height (--ags-mi-pair-h, declared on
     the page body above). At the 1440x766 reference this still lands the cards TALL portrait
     (≈360px, taller than the old fixed 340) — a fixed-px fallback via max() guards against the
     calc ever going negative/tiny on some pathological narrow-short combination outside the
     verified 766–1440 range. */
  body.ags-page-about .ags-masters__row {
    width: 100% !important;
    max-width: calc(var(--ags-frame) - 2 * var(--ags-edge)) !important;
    margin-left: auto !important;
    margin-right: auto !important;
    gap: calc(10 * var(--ags-px)) !important;
    justify-content: flex-start; align-items: stretch;
    height: max(calc(240 * var(--ags-px)), calc(100vh - var(--ags-mi-pair-h) - (16 * var(--ags-px)) - (20 * var(--ags-px))));
    height: max(calc(240 * var(--ags-px)), calc(100svh - var(--ags-mi-pair-h) - (16 * var(--ags-px)) - (20 * var(--ags-px))));
    height: max(calc(240 * var(--ags-px)), calc(100vh - var(--ags-mi-pair-h) - calc(16 * var(--ags-px)) - calc(20 * var(--ags-px))));
    height: max(calc(240 * var(--ags-px)), calc(100svh - var(--ags-mi-pair-h) - calc(16 * var(--ags-px)) - calc(20 * var(--ags-px))));
    padding-left: var(--ags-edge);
    padding-right: var(--ags-edge);
    box-sizing: border-box;
  }
  /* Row's actual flex parent is the .e-con-inner (same trap the homepage rule already
     documents) — match it so the cards fill edge-to-edge instead of a boxed 1120px. */
  body.ags-page-about .ags-masters__row.e-con > .e-con-inner {
    width: 100% !important; max-width: 100% !important;
  }
  /* Mockup proportion (narrow:wide ≈ 157:743 ≈ 1:4.7) — identical ratio to History's
     tuned value, reused rather than re-derived. */
  body.ags-page-about .ags-masters .ags-master.is-active { flex-grow: 4.7; }
  /* Active card's rank is 師 (1 kanji char) — there is no masters JS to set
     data-rank-len, so size the caption's kanji-clearing pad directly (same value the
     homepage's own [data-rank-len="1"] rule would have applied). */
  body.ags-page-about .ags-master.is-active .ags-master__caption { padding-left: calc(42 * var(--ags-px)); }
  body.ags-page-about .ags-master__rank.elementor-widget-heading { left: calc(16 * var(--ags-px)); right: auto; text-align: left; }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-master__name .elementor-heading-title { font-size: calc(21 * var(--ags-px)); }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-master__title .elementor-heading-title { font-size: calc(14 * var(--ags-px)); }

  /* ---- §10 OVERLAY ("Who we serve / Why Ageshio Japan") — FILL only; the composition
     itself (cqw editorial stage, corner-anchored headings/lists/captions) is entirely the
     shared UNSCOPED .ags-c-overlay* rules already in components.css (same component GT/VIP/
     Karate Experiences use) — About just needed the same per-page min-height hook those
     pages already carry (it had none until now, so the stage sat at its aspect-ratio default
     instead of filling the viewport). Identical pattern to
     body.ags-page-okinawan-group-tours above (~line 5289). */
  body.ags-page-about .ags-c-overlay,
  body.ags-page-about .ags-c-overlay > .e-con-inner { min-height: 100vh; min-height: 100svh; }
  body.ags-page-about .ags-c-overlay .ags-c-overlay__stage {
    aspect-ratio: auto; height: 100vh; height: 100svh; min-height: 0;
  }

  /* ---- §13 FIND YOUR JOURNEY — left title + a row of experience cards (band_07);
     only 3 real cards exist in the DOM (Group Tours / VIP Retreat / History Tour).
     SLIDING CAROUSEL PASS (2026-09-17, Simon): these will become blog-post links in a
     later phase; for now the row is a CSS scroll-snap carousel (no JS) — see the __row/
     __card rules below for the sizing math (card basis is a % of the ROW, not a capped
     --ags-px size, specifically so the peek survives ultrawide).
     RHYTHM PASS (round 3): content-height, NOT a 100svh floor+centre — matches every other
     content section (--ags-sec-pad top+bottom, justify-content:normal, top-anchored).
     VIEWPORT FIT PASS (2026-09-17, round 2, content sizing kept): content (search-bar
     heading + card row) ran ~92px over one viewport at 1440x766. The 400:437 aspect is
     band_07's OWN measured card ratio (not a guess) so it stays; the fix is the card's
     absolute SIZE — max-width scaled down below, height following via the unchanged
     aspect-ratio ("Keep the cards' near-square look but smaller", Simon). Pads untouched. ---- */
  body.ags-page-about .ags-c-journey {
    padding-left: var(--ags-edge-guide);
    padding-right: var(--ags-edge-guide); padding-top: var(--ags-sec-pad); padding-bottom: var(--ags-sec-pad); box-sizing: border-box;   /* round 11: cap to frame guide on ultrawide */
    min-height: 0; display: flex; flex-direction: column; justify-content: normal;
  }
  /* §11 SEARCH BAR — the old "Find your journey" H2 is now a live-look search input
     (Simon 2026-09-17): a red vertical bar (the type indicator) + a borderless input,
     sized like the H2 it replaced (50px @1440-equiv — this section's own established
     heading size, kept rather than jumping to another section's H2 scale) and spanning the
     full content guide (matches band_07: caret hugs the left edge, input fills the rest). */
  body.ags-page-about .ags-c-journey__h { width: 100%; }
  /* CENTER PASS (2026-09-17): was width:100% with the input flex:1 (fills all remaining row
     width), which reads as left-aligned — the caret+placeholder hug the left edge and the
     "centred" look only happens by accident if the typed text happens to be section-width. Simon
     wants the caret+"Find your journey" unit itself centred horizontally in the section (the
     cards below are untouched). justify-content:center on the row + the input sized to its OWN
     content (below) — not flex:1 — is what lets the pair centre as a compact unit. */
  body.ags-page-about .ags-c-journey__searchbar {
    display: flex; align-items: center; justify-content: center; gap: calc(16 * var(--ags-px));
    width: 100%;
  }
  /* Type-indicator caret: ~3.5px wide, sized to roughly the input's cap-height (not the full
     line-box) so it reads as a text-cursor sitting just before the glyphs, not a full-height
     rule. Static (no blink) — it is a permanent affordance here, not an actual OS caret. */
  body.ags-page-about .ags-c-journey__caret {
    display: block; flex: 0 0 auto;
    width: calc(3.5 * var(--ags-px)); height: calc(36 * var(--ags-px));
    background: var(--ags-red); border-radius: 1px;
  }
  /* Borderless, transparent, full-width — Simon: "the red bar is the type indicator; 'Find
     your journey' is placeholder until the user clicks in; when they type it's the same font
     but red text". font/size matches the old H2 exactly (calc(50*px)) so this reads as the
     section's heading, not a form control. appearance/outline reset for cross-browser parity
     (Safari/Firefox both paint native chrome on a bare <input> otherwise). */
  /* Sized to its OWN placeholder content (measured via canvas text-metrics, "Find your journey"
     @ 400 50px Bounded = 516px = 10.32em), not flex:1 (which fills the row and defeats the
     row's justify-content:center above) — `em` so the width scales with the font together. A
     little headroom over the exact measure for typing comfort. */
  body.ags-page-about .ags-c-journey__input {
    flex: 0 0 auto; width: 11em; max-width: 100%; min-width: 0;
    background: transparent; border: none; outline: none;
    -webkit-appearance: none; -moz-appearance: none; appearance: none; box-shadow: none;
    padding: 0; margin: 0;
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(50 * var(--ags-hpx)); line-height: 1;
    color: var(--ags-red); caret-color: var(--ags-red);
  }
  /* CENTER FIX (round 9, Simon 2026-09-21): the caret+placeholder read ~17px LEFT of centre because
     the fixed 11em input is wider than the placeholder text — its trailing dead space offset the ink
     from the row's justify-content:center. field-sizing:content auto-fits the input to its placeholder
     (empty state), so the caret+text unit centres EXACTLY and stays centred in every language (es/de/nl
     placeholders differ in width — one fixed em can't centre them all). The 11em above remains the
     fallback for browsers without field-sizing (Firefox/Safari): functional, not perfectly centred. */
  @supports (field-sizing: content) {
    body.ags-page-about .ags-c-journey__input { field-sizing: content; width: auto; }
  }
  /* Faded placeholder (band_07: a pale, low-contrast tint of the same red, not grey) —
     cross-browser needs both prefixed selectors; Firefox also needs opacity:1 to stop it
     double-fading its own default placeholder dimming. */
  body.ags-page-about .ags-c-journey__input::-moz-placeholder {
    color: rgba(142, 30, 0, .22); opacity: 1;
  }
  body.ags-page-about .ags-c-journey__input::placeholder {
    color: rgba(142, 30, 0, .22); opacity: 1;
  }
  body.ags-page-about .ags-c-journey__input::-webkit-input-placeholder { color: rgba(142, 30, 0, .22); }
  body.ags-page-about .ags-c-journey__input::-moz-placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }
  /* SLIDING CAROUSEL (2026-09-17): flex-wrap:nowrap + scroll-snap-type turns the row into a
     horizontal, swipe/scroll/trackpad-draggable strip — pure CSS, no JS. Scrollbar hidden
     (both engines) since the peeking next card is itself the "there's more" affordance. Inset
     on the page's content guide comes from the PARENT .ags-c-journey's padding-inline:
     var(--ags-edge) above — the row itself spans that padded box edge-to-edge. */
  body.ags-page-about .ags-c-journey__row {
    display: flex; flex-wrap: nowrap; gap: calc(24 * var(--ags-px)); margin-top: calc(42 * var(--ags-px));
    overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  body.ags-page-about .ags-c-journey__row::-webkit-scrollbar { display: none; }
  /* Card width is a FRACTION OF THE ROW (%), not a --ags-px-capped size, and on purpose: 7
     cards now exist (was 3 — Group Tours / VIP Retreat / History Tour), and --ags-px is
     HEIGHT-capped past ~1920 (the reference-frame's content scale plateaus at 1.305 while the
     row's own box keeps growing with the viewport — measured 1850px @1920 vs 3370px @3440,
     same --ags-px both). A px-capped card (the old max-width:356px) tops out ~465px regardless
     of viewport, so cards stop reaching the row's edge past ~1920 and the "slide" disappears
     exactly where ultrawide needs it most. A %-of-row basis instead scales WITH the row at
     every width, so the same peek holds from 1280 to 3440 — and with 7 cards at 3.5-up the row
     now overflows unconditionally (slides) at every width, laptop through ultrawide. scroll-
     snap-align:start is what makes the drag/scroll/swipe land cleanly on a card edge every time.
     NOTE: this rule owns the carousel-item half only (width + snap) — the card's INTRINSIC
     styling (column layout, photo, tags, title, more) is the shared .ags-c-postcard* component
     above (~6812); this element carries BOTH classes. */
  body.ags-page-about .ags-c-journey__card {
    /* ~3.5 cards visible (3 full + a peek, matching band_07) so the SQUARE image below stays
       small enough that search bar + card row + --ags-sec-pad top/bottom fits one reference
       viewport (≤766 at 1440). Narrower cards = shorter square images. */
    flex: 0 0 calc((100% - 48 * var(--ags-px)) / 3.5);
    scroll-snap-align: start;
  }

  /* JOURNEY CARD → ARTICLE-CARD RETROFIT (2026-09-19): visually align the "Find your journey"
     .ags-c-postcard with the canonical .ags-c-articlecard overlay treatment (blog /articles,
     Task 3) for site-wide card consistency. CSS-ONLY — no HTML/JSON change: the widget tree
     (__thumb>__img+__tags, __title, __more) is unchanged, only repainted from a stacked
     (photo-above, text-below) layout into an overlay (photo-fills-card, text/scrim on top).
     Scoped to body.ags-page-about ONLY, so the shared, page-agnostic .ags-c-postcard* base
     (~6812, still used as documented/reusable) and the /articles .ags-c-articlecard (~6871)
     are both untouched. Repeats the shared component's own `[data-elementor-type="wp-page"]`
     prefix (CSS-ownership convention, see body.ags-page-okinawan-group-tours etc. above) so
     this override's specificity (0,3,1)/(0,4,1) legitimately beats the shared rules' (0,3,0)/
     (0,4,0) via the trailing `body` type-selector tiebreak — NOT by source order alone, since
     several of these properties (position, display/gap) are also set by the shared block.
     Card footprint (flex-basis, 3.5-up carousel) above is UNTOUCHED — only the aspect-ratio
     changes (400:437 → articlecard's 405/442, nearly identical, no viewport-fit regression). */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard {
    position: relative; overflow: hidden; padding: 0;
    aspect-ratio: 405 / 442; justify-content: flex-end;
    border-radius: calc(10 * var(--ags-px));
  }
  /* Bottom scrim, same recipe as .ags-c-articlecard::after — legibility for the overlaid
     title/button over any photo. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 56%;
    background: linear-gradient(to top, rgba(17,16,16,.85), rgba(17,16,16,0));
    pointer-events: none; z-index: 0;
  }
  /* Thumb goes from "top portion of a stacked column" to "the whole card's background" —
     absolute + inset:0 fills the now-relative/padding:0 root exactly. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__thumb {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; height: 100%; z-index: 0;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__img {
    aspect-ratio: auto; width: 100%; height: 100%; border-radius: 0;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__img img { border-radius: 0; }
  /* Tag pills: same position (already overlaid top-right by the shared rule) — just the
     glass-pill treatment (white outline + translucent-dark fill) instead of an outlined-
     currentColor pill, matching .ags-c-articlecard__tags span. `color` (currentColor feeds the
     shared rule's `border:1px solid currentColor`) is the only overlapping property touched. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__tag .elementor-heading-title {
    color: var(--ags-white); background: rgba(20,20,20,.30);
    font-family: 'Bounded', sans-serif; font-weight: 400;
  }
  /* Title + "more" link were flow siblings below the thumb; now the thumb is out of flow
     (absolute), so they're .ags-c-postcard's only flex children — justify-content:flex-end
     above pins them to the card's bottom edge, overlay-style. Explicit position+z-index lifts
     them above the absolutely-positioned thumb/scrim (stacking-order rule: positioned
     descendants paint after static in-flow ones, so without this the photo would cover the
     text). Left/right/bottom insets are margin (not root padding, which stays 0 so the photo
     itself bleeds to the card's true edge). */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__title {
    position: relative; z-index: 2; margin: 0 calc(16 * var(--ags-px));
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__title .elementor-heading-title {
    color: var(--ags-white); font-size: calc(21 * var(--ags-px)); line-height: 1.2; font-weight: 600;
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__more {
    position: relative; z-index: 2; margin: 0 calc(16 * var(--ags-px)) calc(18 * var(--ags-px));
  }
  /* Default = transparent fill + white outline + white text, red fill on hover — same
     `.ags-btn--bar`-flavoured recipe as .ags-c-articlecard__more (~6997), applied here to the
     .ags-textlink button instead (About's widget carries the borderless-link variant, not the
     bar variant — restyled at the CSS level rather than reauthoring the page's JSON). */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__more .elementor-button {
    width: 100%; display: flex; align-items: center; justify-content: space-between;
    font-size: calc(13 * var(--ags-px)); -webkit-text-decoration: none; text-decoration: none;
    background-color: transparent; border: 1px solid var(--ags-white); border-radius: 999px;
    color: var(--ags-white); padding: calc(10 * var(--ags-px)) calc(18 * var(--ags-px));
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__more .elementor-button:hover {
    background-color: #8E1E00; border-color: #8E1E00; color: var(--ags-white);
  }
  /* Replace the shared .ags-textlink underline-sweep ::after (absolute, full-width, 1px strip)
     with a plain inline chevron glyph — every box/position property the sweep sets must be
     reset here since an `::after` override only wins per-DECLARED-property, not wholesale. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__more .elementor-button::after {
    content: "\203A"; position: static; top: auto; right: auto; bottom: auto; left: auto; width: auto; height: auto;
    background: none; transform: none; transition: none;
    margin-left: calc(8 * var(--ags-px));
  }
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-postcard__more .elementor-button:hover::after {
    transform: none;
  }

  /* ---- §14 FINAL CTA (.ags-c-cta3, NEW) — maroon band, centred white heading + a
     centred row of 3 cream pills (Image #17: "Experience Okinawan karate in its most
     authentic form" / BOOK A GROUP TOUR · EXPLORE EXPERIENCES · CONTACT US). The pills
     already render correctly from the shared .ags-btn--invert rule (cream bg, red text) —
     this block only owns the band's fill/centring, the heading size/colour, and the
     button row's gap/wrap.
     NOT a full-viewport section (Simon, 2026-09-17): unlike every other section on this
     page, the CTA is a normal COMPACT band, same idiom as the shared .ags-c-cta used on
     every other interior page (components.css ~5493) — reuse its own band tokens
     (--ags-cta-band-pad / --ags-cta-band-minh) rather than the page's 100svh/--ags-sec-pad
     pair, and exclude it from the fill-viewport measurement (it is deliberately short). */
  body.ags-page-about .ags-c-cta3 {
    padding-left: var(--ags-edge-guide);
    padding-right: var(--ags-edge-guide); padding-top: var(--ags-cta-band-pad); padding-bottom: var(--ags-cta-band-pad); box-sizing: border-box;   /* round 11: cap to frame guide on ultrawide */
    min-height: var(--ags-cta-band-minh); display: flex; flex-direction: column; justify-content: center;
    text-align: center;
  }
  /* Clear Elementor's stray per-level 10px padding on the two wrapper containers (same
     "clear it at every level" rule as the region-header list above — this block is new
     enough that its wrappers weren't in that list yet). */
  body.ags-page-about .ags-c-cta3__inner,
  body.ags-page-about .ags-c-cta3__btns { padding: 0; }
  /* Heading: sized to FIT ONE LINE at the 1440 reference (measured via canvas text-metrics
     against the live copy, "Experience Okinawan karate in its most authentic form" — the
     mission-statement's own 70px scale is too large and wraps to 2 lines at this section's
     full guide-to-guide width; 40px is the largest size that still clears one line with
     margin at 1440, ~1306px of the ~1387px available). Colour is already cream from the DB;
     line-height matches the other statement headlines' 1.12. */
  body.ags-page-about [data-elementor-type="wp-page"] .ags-c-cta3__h .elementor-heading-title {
    font-size: calc(40 * var(--ags-hpx)); line-height: 1.12; margin: 0;
  }
  /* Button row: even gaps, wraps gracefully if a translation ever makes 3 pills too wide
     for one line at a narrow desktop width (1024). Row is already justify-content:center
     from Elementor's own container setting (kept, not overridden) — only the gap (was an
     unscaled ~16px default) and the heading→row breathing room are this block's job. */
  body.ags-page-about .ags-c-cta3__btns {
    display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
    gap: calc(20 * var(--ags-px)); margin-top: calc(40 * var(--ags-px));
  }
  body.ags-page-about .ags-c-cta3__btn { flex: 0 0 auto; }

  /* ---- TASK 4 — blue accordion divider legibility. The shared
     .ags-c-accordion__item::after divider is a maroon rgba(142,30,0,.6), tuned for
     the cream accordion bands; on the blue "Sample experience / Optional add-ons /
     Etiquette" variant it reads muddy against the blue. Swap to a translucent cream
     line (matches the cream heading/panel text already set in the DB). */
  body.ags-page-about .ags-c-accordion--blue .ags-c-accordion__item::after {
    background: rgba(255, 255, 255, .4);
  }

  /* ---- PINNED PHOTO EDGE — REVISED 2026-09-22 (Simon): the accordion photo must MATCH the other
     accordions (GT/VIP/History), not About's page-wide --ags-edge guide. The earlier 2026-09-17 pin
     to --ags-edge sat the photo ~9px too far left of the shared component (26px vs the shared 17px at
     1440, and further on ultrawide since the edge guide grows faster than the well clamp). Use the
     shared right offset (clamp + --ags-acc-well) so the photo lines up with every other accordion;
     keep About's own bottom. */
  body.ags-page-about .ags-c-accordion__aside {
    right: calc(max(12px, min(1.2vw, 28px)) + var(--ags-acc-well));
    right: calc(clamp(12px, 1.2vw, 28px) + var(--ags-acc-well));
    bottom: var(--ags-edge);
  }

  /* ---- MAROON STATEMENTS (Mission + Our Focus, shared .ags-c-statement--maroon, used
     twice on this page) — the corner LABELS ("The Ageshio"/"Mission", "Our Focus"/
     "& Priority") and the "SEE EXPERIENCES" link render dark (the shared component
     hardcodes #111010 for the cream-band variant, components.css ~5160/5183) — low
     contrast on maroon. The big headline is already cream from its own rule; force the
     labels + link cream too. Page-type scope (body.ags-page-about .ags-c-statement--maroon
     …, 0,4,1) already out-specifies the shared rules (0,2,0) without !important.

     FILL PASS (2026-09-17, SUPERSEDES the round-3 note below): Simon asked these two bands to
     FILL one viewport (min-height:100svh) — the corner labels pinned at the TOP (right at the top
     pad) and the body (headline + link) vertically CENTRED in the space left below, so the
     eyebrow→headline gap is generous and grows/shrinks with the available space rather than being
     a fixed margin, while top pad still equals bottom pad (the shared `padding-block:
     var(--ags-sec-pad)`, components.css ~4891, is untouched). `__labels` is the first flex child
     (flex:0 0 auto — sized to its own content, stays pinned under the top pad); `__body` (the
     second flex child, itself already a flex column holding the headline+link) is flex:1 1 auto
     with its own justify-content so it positions AS A BLOCK in the remaining height. The old
     fixed margin-top on the headline (86.4px, borrowed from GT's content-height model) is removed
     — GT's statement is content-height and needs a fixed gap; this band is fill-height and gets
     its gap from the flex distribution instead, so a leftover fixed margin would only throw that
     off by its own value.

     BOTTOM-BALANCE PASS (2026-09-17): centring `__body` (justify-content:center) split the leftover
     space in HALF above and below its content — but only the half BELOW counts towards the
     section's bottom pad (measured link→section-bottom = internal-bottom-gap + --ags-sec-pad),
     while the section's top pad is measured straight to the eyebrows, before any of that leftover
     space. So centring reads bottom-heavy: measured 96 top / 245 bottom at 1440×900 (labels pinned
     right at the 96px top pad, but the link sits with a further ~149px of centred slack + the 96px
     bottom pad below it). Simon: "bring the bottom links down a notch along with the statement so
     the padding on the bottom looks visually the same as the top." Swapping to justify-content:
     flex-end pins the link flush to `__body`'s own bottom edge (zeroing that internal gap), so
     link→section-bottom = --ags-sec-pad exactly = the top pad — the two are equal BY CONSTRUCTION,
     at any viewport height, since the fix is a flex keyword, not a measured px offset. All of the
     leftover slack now sits above the headline (the eyebrow→headline gap) instead of splitting
     around the body — still clearly non-zero, and it is the "anchor the link nearer the bottom pad
     with the headline above it" shape the brief asked for.

     [RETIRED] RHYTHM PASS (round 3): had dropped the 100svh floor + vertical centring in favour of
     a content-height band with a fixed eyebrow→headline margin, matching GT's own statement model
     — reinstated the floor+centring above per this later instruction. */
  /* DISTRIBUTE PASS (2026-09-21, Simon): eyebrows pinned at the top pad, the "SEE EXPERIENCES" link
     pinned at the BOTTOM pad, headline centred between — with the SAME --ags-sec-pad top+bottom as
     every other section (consistent page rhythm). space-between over three flow items (labels,
     headline, link) does this: __body is display:contents so its headline + link become direct
     flex items of the section, giving [labels @ top-pad][headline @ centre][link @ bottom-pad]. */
  body.ags-page-about .ags-c-statement--maroon {
    min-height: 100vh;
    min-height: 100svh; display: flex; flex-direction: column; justify-content: space-between;
  }
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__labels { position: static; flex: 0 0 auto; }
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__body { display: contents; }
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__label .elementor-widget-container,
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__headline .elementor-heading-title,
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__link .elementor-button,
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__link .elementor-button:hover {
    color: var(--ags-cream);
  }

  /* ---- EDGE-GUIDE PASS (2026-09-17) — the maroon statements' corner labels used the shared
     .ags-c-statement__labels editorial gutter (padding-inline: 43.2px, ~3% of 1440 — components.css
     ~5167), which on this page sits noticeably further in than every OTHER section's --ags-edge
     guide (measured 43 vs 26 at 1440x900). That shared 43.2px gutter is intentional on GT/VIP/etc
     (their own statement isn't lined up against a page-wide edge guide the way About's is), so this
     is an About-only override, not a change to the shared component. About's overlay + accordion
     get the identical treatment below, for the same reason (this page's whole vertical stack reads
     off ONE edge guide; those two components ship their own, different insets). */
  body.ags-page-about .ags-c-statement--maroon .ags-c-statement__labels { padding-left: var(--ags-edge-guide); padding-right: var(--ags-edge-guide); }   /* round 11: cap to frame guide on ultrawide */

  /* ---- EDGE-GUIDE PASS (2026-09-17) — same fix as the statement above: the overlay's corner
     headings/lists ship a shared 3% horizontal inset (components.css ~5345/5346/5368/5369, ≈43px at
     1440 — deliberately kept at 3% on GT/VIP/KE/History, per the comment there: "Simon confirmed the
     side margins read correctly" on THOSE pages). About lines its whole section stack up on the
     page's own --ags-edge guide (~26px at 1440), so only here the corner clusters are re-pinned to
     match. The captions (__cap--l/--r) are near-centre, not corner, so they're untouched. */
  body.ags-page-about .ags-c-overlay .ags-c-overlay__h--tl,
  body.ags-page-about .ags-c-overlay .ags-c-overlay__list--l { left: var(--ags-edge-guide); }   /* round 11: cap to frame guide */
  body.ags-page-about .ags-c-overlay .ags-c-overlay__h--br,
  body.ags-page-about .ags-c-overlay .ags-c-overlay__list--r { right: var(--ags-edge-guide); }   /* round 11: cap to frame guide */

  /* ---- EDGE-GUIDE PASS (2026-09-17) — the blue accordion's list column inherits the shared
     .ags-c-accordion container's own inset (clamp(24px,3vw,40px) + the ultrawide well —
     components.css ~4876), tuned for GT/VIP/History's own centred content-well, not About's
     page-wide edge guide. Measured 70px left (40px container pad + ~30px of Elementor's stray
     unscaled per-level .e-con padding the shared "clear it at every level" pass never reached
     horizontally) vs the guide's 26px. Re-pin the whole container to --ags-edge — the aside/photo
     is unaffected (it's absolutely positioned off the SECTION edge via its own `right` rule, already
     pinned to --ags-edge in the PINNED PHOTO EDGE block above) and the headings' own max-width:7em
     (the reference's 2-line wrap) is untouched, so this only moves the column's LEFT start, not its
     text measure. */
  body.ags-page-about .ags-c-accordion--blue { padding-left: var(--ags-edge-guide); padding-right: var(--ags-edge-guide); }   /* round 11: cap to frame guide on ultrawide */
  /* The shared "clear it at every level" pass (components.css ~6864) never reached the accordion's
     own wrapper chain (__inner/__list/__item), and the shared vertical-only clear (~4926) leaves
     their horizontal 10px Elementor default untouched — three nested levels stack to +30px past the
     container's own edge pad above. About-scoped only: GT/VIP/History rely on this same stray pad as
     part of their own (different) content-well centring, so clearing it there is out of scope. */
  body.ags-page-about .ags-c-accordion__inner,
  body.ags-page-about .ags-c-accordion__list,
  body.ags-page-about .ags-c-accordion__item { padding-left: 0; padding-right: 0; }
}

/* =============================================================================
   ===== ARTICLES LISTING PAGE (body.ags-page-articles) =====
   -----------------------------------------------------------------------------
   Slug: articles. /articles — "Search Articles" listing (sub-project #2 "Blog page
   build", Task 2). Scope: body.ags-page-articles ONLY — portable slug hook, never
   page-id-N / .elementor-N (linter: scripts/qa/check_portable_scope.py).

   Section stack: 1 Search + tag pills + Loop Grid (ONE section, ags-c-articles-header —
   content-height rhythm, --ags-sec-pad top/bottom, matches every other interior page's
   model, NOT a 100svh floor) · 2 CTA (shared .ags-c-cta, UNSCOPED — needs no rules here,
   see components.css ~5498) · 3 Footer (theme-builder condition, auto-renders sitewide —
   no rules here either).

   The search bar re-declares About's "Find your journey" search idiom (caret bar + faded-
   placeholder input, components.css ~7473 body.ags-page-about .ags-c-journey__*) under a
   NEW class prefix (ags-c-articles-header__*) rather than reusing ags-c-journey__* directly
   — that rule is scoped body.ags-page-about only, so copying the ~8 declarations keeps each
   page's CSS independently portable (no cross-page class coupling).

   The Loop Grid's card is `.ags-c-articlecard` — the SHARED overlay card (Task 3), rules
   above under "ARTICLE CARD (.ags-c-articlecard) — SHARED Loop Item" (~6871), not here (it's
   reused by other pages too, so it isn't page-scoped). ---- */
body.ags-page-articles .ags-c-articles-header__gridwrap {
  padding: 0;
}

@media (min-width: 1024px) {

  /* ---- §1 SEARCH + PILLS + GRID — content-height section (not full-viewport: with 87
     cards this section is far taller than one screen either way). ---- */
  body.ags-page-articles .ags-c-articles-header {
    padding-left: var(--ags-edge);
    padding-right: var(--ags-edge); padding-top: var(--ags-sec-pad); padding-bottom: var(--ags-sec-pad); box-sizing: border-box;
    min-height: 0; display: flex; flex-direction: column; justify-content: normal;
  }

  /* SEARCH BAR — same recipe as ags-c-journey__searchbar (About): red vertical caret +
     borderless input sized to its own placeholder text, faded low-contrast red placeholder,
     centred as a compact unit via the row's justify-content:center (not input:flex:1, which
     would hug the left edge — see the About comment this is copied from for the full story). */
  body.ags-page-articles .ags-c-articles-header__h { width: 100%; }
  body.ags-page-articles .ags-c-articles-header__searchbar {
    display: flex; align-items: center; justify-content: center; gap: calc(16 * var(--ags-px));
    width: 100%;
  }
  body.ags-page-articles .ags-c-articles-header__caret {
    display: block; flex: 0 0 auto;
    width: calc(3.5 * var(--ags-px)); height: calc(36 * var(--ags-px));
    background: var(--ags-red); border-radius: 1px;
  }
  /* CENTER FIX (review fix, 2026-09-20): width was a flat 11em guess, well wider than the actual
     placeholder text ("Search Articles" canvas-measures to 9.1em @ this font). The row centres the
     input's whole BOX (justify-content:center, same recipe as ags-c-journey__searchbar above), but
     the input's own text is left-aligned inside that box — so the extra ~1.9em of empty box sat to
     the RIGHT of the visible text, making the caret+ink read as left-of-centre even though the
     invisible box was centred correctly. Canvas-measured per language (same idiom as
     ags-c-journey__input's "sized to its OWN placeholder" comment above) + ~0.4em headroom so the
     visible text — not an oversized invisible box — is what gets centred. German's "Artikel
     durchsuchen" is the long one (12.0em) — needs a wider box than EN or it would've clipped. */
  body.ags-page-articles .ags-c-articles-header__input {
    flex: 0 0 auto; width: 9.5em; max-width: 100%; min-width: 0;
    background: transparent; border: none; outline: none;
    -webkit-appearance: none; -moz-appearance: none; appearance: none; box-shadow: none;
    padding: 0; margin: 0;
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(50 * var(--ags-hpx)); line-height: 1;
    color: var(--ags-red); caret-color: var(--ags-red);
  }
  body.ags-lang-es .ags-c-articles-header__input { width: 10.2em; }   /* "Buscar artículos" 9.76em measured */
  body.ags-lang-de .ags-c-articles-header__input { width: 12.5em; }   /* "Artikel durchsuchen" 12.02em measured */
  body.ags-lang-nl .ags-c-articles-header__input { width: 10.5em; }   /* "Artikelen zoeken" 9.88em measured */
  /* CENTER FIX (round 9, Simon 2026-09-21): same as the About journey search bar — field-sizing:content
     auto-fits the input to its placeholder so the caret+text unit centres EXACTLY in every language,
     superseding the per-language fixed widths above (which remain the fallback for Firefox/Safari). */
  @supports (field-sizing: content) {
    body.ags-page-articles .ags-c-articles-header__input,
    body.ags-lang-es .ags-c-articles-header__input,
    body.ags-lang-de .ags-c-articles-header__input,
    body.ags-lang-nl .ags-c-articles-header__input { field-sizing: content; width: auto; }
  }
  body.ags-page-articles .ags-c-articles-header__input::-moz-placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }
  body.ags-page-articles .ags-c-articles-header__input::placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }
  body.ags-page-articles .ags-c-articles-header__input::-webkit-input-placeholder { color: rgba(142, 30, 0, .22); }
  body.ags-page-articles .ags-c-articles-header__input::-moz-placeholder { color: rgba(142, 30, 0, .22); opacity: 1; }

  /* TAG PILLS ROW — the 8 topic tags as outline pills (red border/text on cream), centred,
     wraps if the row is too narrow for all 8 (never happens ≥1024 at this pill size, but
     wrap is a free safety net). `.is-active` is a hook for the future click-filter (Task 4);
     inert here (plain hrefs, no JS yet — matches this task's "render, don't wire" scope). */
  body.ags-page-articles .ags-c-articles-header__pillsrow { width: 100%; }
  body.ags-page-articles .ags-c-articles-header__pills {
    display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
    gap: calc(14 * var(--ags-px)); margin-top: calc(40 * var(--ags-px));
  }
  body.ags-page-articles .ags-c-articles-header__pill {
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--ags-red); border-radius: 999px;
    padding: calc(10 * var(--ags-px)) calc(22 * var(--ags-px));
    font-family: 'Bounded', sans-serif; font-weight: 400; font-size: calc(14 * var(--ags-px));
    color: var(--ags-red); -webkit-text-decoration: none; text-decoration: none; white-space: nowrap; line-height: 1;
    transition: background-color .2s, color .2s;
  }
  body.ags-page-articles .ags-c-articles-header__pill:hover,
  body.ags-page-articles .ags-c-articles-header__pill.is-active {
    background: var(--ags-red); color: var(--ags-cream);
  }

  /* GRID — 3 columns; gap measured off Desktop (3) (~16px column-gap / ~40px row-gap at the
     mockup's 1280 export → 18/45 at the 1440 reference basis, rounded). grid-template-columns
     is restated explicitly on .elementor-grid (Elementor's own inner grid wrapper) rather than
     relying solely on the widget's `columns` setting/responsive prefix class — belt-and-braces
     so the composition holds even if that setting is ever changed in the editor. */
  body.ags-page-articles .ags-c-articles-header__gridwrap { width: 100%; margin-top: calc(48 * var(--ags-px)); }
  body.ags-page-articles .ags-c-articles-grid .elementor-grid {
    /* round 12 (Simon 2026-09-21): cards ballooned at ultrawide (3 cols × full width = 1089px cards
       with 1.88x content). Cap the card SIZE via auto-fill — more columns appear past ~1600 instead
       of the cards growing — and DAMPEN the card CONTENT by pointing --ags-px at --ags-hpx here (it
       cascades to every card). Below ~1440 this is unchanged (--ags-hpx == --ags-px, still 3 cols). */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, calc(380 * var(--ags-hpx))), 1fr));
    --grid-column-gap: calc(20 * var(--ags-px));
    --grid-row-gap: calc(44 * var(--ags-px));
    --ags-px: var(--ags-hpx);
  }

  /* CLIENT-SIDE FILTER (ageshio-articles.js, Task 4) — `.is-active` on the pill is already
     covered by the hover rule above (~7926, maroon fill / cream text). The `:has()` rule
     collapses a hidden card's OWN grid cell — the Loop Grid wraps every card in an
     `[data-elementor-type="loop-item"]` box that participates in the CSS grid; hiding just
     the inner `.ags-c-articlecard` would leave that cell reserved (an empty gap among the
     remaining cards) — so on the actual grid item, not the card itself. `.ags-c-articles__empty`
     ("No articles found") is JS-inserted after the grid — no-JS visitors never get it (they
     always see the full, unfiltered grid), so it's styled here but never server-rendered. */
  body.ags-page-articles .ags-c-articles-grid [data-elementor-type="loop-item"]:has(.ags-c-articlecard.is-hidden) {
    display: none;
  }
  body.ags-page-articles .ags-c-articles__empty {
    width: 100%; text-align: center;
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(18 * var(--ags-px));
    color: var(--ags-red);
    padding-top: calc(48 * var(--ags-px));
    padding-bottom: calc(48 * var(--ags-px));
  }

  /* PAGER (ageshio-articles.js) — "Showing N of M" + LOAD MORE, JS-inserted only (mirrors the
     mobile block ~6892). Same outline-pill idiom as the tag filter pills (~8594) rather than
     the shared full-width `.ags-btn--bar` card button — this is page chrome, not a card CTA. */
  body.ags-page-articles .ags-c-articles__pager {
    width: 100%; display: flex; flex-direction: column; align-items: center;
    gap: calc(16 * var(--ags-px));
    margin-top: calc(40 * var(--ags-px));
  }
  body.ags-page-articles .ags-c-articles__pager-status {
    margin: 0; text-align: center;
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(13 * var(--ags-px));
    color: rgba(142, 30, 0, .55);
  }
  body.ags-page-articles .ags-c-articles__loadmore {
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--ags-red); border-radius: 999px; background: transparent;
    padding: calc(12 * var(--ags-px)) calc(34 * var(--ags-px));
    font-family: 'Bounded', sans-serif; font-weight: 400; font-size: calc(14 * var(--ags-px));
    color: var(--ags-red); cursor: pointer; white-space: nowrap; line-height: 1;
    transition: background-color .2s, color .2s;
  }
  body.ags-page-articles .ags-c-articles__loadmore:hover,
  body.ags-page-articles .ags-c-articles__loadmore:focus-visible {
    background: var(--ags-red); color: var(--ags-cream);
  }
  /* `pager.hidden`/`loadMore.hidden` (ageshio-articles.js) toggle the boolean `hidden`
     attribute — restate `[hidden]{display:none}` here since it'd otherwise lose to the
     `display:flex`/`inline-flex` rules above at equal specificity by source order (mirrors
     the mobile block ~6913). */
  body.ags-page-articles .ags-c-articles__pager[hidden],
  body.ags-page-articles .ags-c-articles__loadmore[hidden] { display: none; }

}

/* =============================================================================
   ===== SINGLE POST TEMPLATE (body.single-post) =====
   -----------------------------------------------------------------------------
   Sub-project #2 "Blog page build", Task 8 — Theme Builder "Single Post" template
   (scripts/build_single_post.py / apply_single_post.php), built to `Desktop (4).png`.
   Scope: `body.single-post` — WordPress's OWN core body class for any singular post
   of post_type `post` (always present, portable across installs; NOT tied to a
   page slug the way interior wp-pages are, because this ONE template renders every
   post at its own slug — there is no single fixed slug to hook — and NOT tied to
   `[data-elementor-type="wp-page"]`/`.elementor-page` the way other interior pages
   are, because THIS template's own rendered wrapper is `data-elementor-type=
   "single-post"` and its `<body>` never gets a bare `.elementor-page` class at all
   (confirmed by inspecting a live render) — core's `single-post` class is the one
   reliable, portable hook here. Linter: scripts/qa/check_portable_scope.py.

   Stack: §1 Post Hero (split maroon/photo) · §2 Post Body (cream: tag pills +
   `the_content`) · §3 Keep Exploring (grey band, related-posts Loop Grid reusing
   the SHARED `.ags-c-articlecard` — rules above under "ARTICLE CARD", not here) ·
   §4 CTA (shared `.ags-c-cta`, unscoped — no rules needed here) · Header/Footer
   (Theme Builder locations, render automatically — no rules needed here either). */
@media (min-width: 1024px) {

  /* ---- §1 POST HERO — split maroon (left) / photo (right), EXACTLY 50/50 (Simon
     review #1 — was ~53/47: `.ags-c-posthero__left`/`__photocol` both carry the
     template's OWN Elementor `width:50%` (scripts/build_single_post.py `basis=50` on
     both columns) — this CSS no longer sets its own conflicting `width` on either
     column (a stray `width:100%` on BOTH used to fight that native 50%: with
     flex-basis:auto + flex-shrink:1, "100% each, please shrink to fit" resolves the
     shrink asymmetrically once `__left`'s TEXT content hits a min-content floor wider
     than its fair share, stealing width from `__photocol` — verified live, this was
     the actual ~53/47-reading bug, not a builder mis-set). Leaving Elementor's own
     `.e-con{width:var(--width)}` (its `--width` custom prop is inline, out-CSS-args
     anything here) alone gives a clean 50/50 every time. ---- */
  body.single-post .ags-c-posthero__row { width: 100%; }
  body.single-post .ags-c-posthero__left {
    padding: calc(90 * var(--ags-px)) calc(70 * var(--ags-px));
    box-sizing: border-box;
  }
  /* Kanji hero title when `_ags_kanji_title` is blank: Elementor's OWN heading
     widget already omits the entire element (wrapper included, not just an empty
     title) when its resolved title is the exact empty string — Widget_Base::
     render_content() bails via `if (empty($widget_content)) return;` before ever
     printing the wrapper div (verified: zero occurrences of `ags-c-posthero__kanji`
     in the raw HTML for a post with no kanji meta) — so the flex column already has
     one fewer child, no phantom gap, and the title reads as the lead element with
     NO css needed. This `:has()` rule is a defensive backstop only, for the edge
     case of the meta being set to non-empty whitespace (Elementor would then render
     a widget whose title is a single space — not caught by `''===` — but `:empty`
     below only matches truly childless nodes, so this still doesn't fire on real
     whitespace-padded kanji text, only on a literal empty result). */
  body.single-post .ags-c-posthero__kanji:has(.elementor-heading-title:empty) {
    display: none;
  }
  body.single-post .ags-c-posthero__kanji .elementor-heading-title {
    line-height: 1; margin: 0;
  }
  /* Simon review #2 — the real post title was rendering at the Kit's global
     "secondary" preset size (60px, the KANJI slot's own weight — a real post has no
     kanji, so this h1 is the ONLY big hero element and needs to read as a headline,
     not a marketing kanji mark). The `[data-elementor-type="single-post"]` qualifier
     is the SAME specificity boost the mobile rules below already use (~8886) and for
     the identical reason: Elementor emits its OWN per-widget override — `.elementor-
     20663 .elementor-element.elementor-element-<id> .elementor-heading-title{font-
     size:var(--e-global-typography-secondary-font-size)}` — 4 classes, 0 elements;
     our plain `body.single-post .ags-c-posthero__title .elementor-heading-title` was
     only 3 classes (+1 element), and class-count is compared before element-count, so
     it lost outright regardless of source order. Adding this attribute selector
     brings us to 4 classes + the `body` element, which wins the tie. Verified live:
     without it, the title rendered at 60px (wrapping 3-4 lines) instead of this rule's
     size at all. */
  body.single-post [data-elementor-type="single-post"] .ags-c-posthero__title .elementor-heading-title {
    font-size: calc(30 * var(--ags-px)); line-height: 1.25; margin: 0;
  }
  body.single-post .ags-c-posthero__excerpt {
    margin-top: calc(36 * var(--ags-px)); max-width: calc(400 * var(--ags-px));
  }
  body.single-post .ags-c-posthero__excerpt .elementor-widget-container p {
    text-align: center; font-size: calc(15 * var(--ags-px)); line-height: 1.6; margin: 0;
  }
  /* `__photocol` no longer sets its own `height`/`width` (see the §1 note above —
     same conflicting-`width` bug applies to `height:100%` too: it resolves against
     the ROW's own auto/content height, which is NOT a definite size for percentage
     purposes, so it fell back to whatever height the `__photo` widget itself computed
     — its native aspect ratio scaled to the column's width, i.e. Simon's review #1
     "short landscape strip that doesn't fill full height"). Leaving `__photocol`
     un-sized lets the row's own `flex_align_items:stretch` (Elementor setting) give
     it a genuinely DEFINITE stretched height — a flex item sized via stretch alignment
     counts as definite for its OWN children's percentage resolution, which is what
     `flex:1 1 auto` on `__photo` (below) and the `height:100%` chain under it need.
     `__photocol` DOES still need its OWN padding zeroed, though — Elementor's `.e-con`
     ships a default 10px on all 4 sides (`--container-default-padding-*`, the
     documented "stray .e-con 10px padding" gotcha) which every OTHER container here
     overrides explicitly (`__left`'s own 90/70 padding above, the row's content_width:
     full) but `__photocol` never did. That left a 10px inset on every edge — including
     top+bottom — so even with the correct stretched height, the photo rendered ~20px
     short of the column's full height with empty maroon visible above/below (caught
     on a follow-up review pass: the fix above alone wasn't sufficient). */
  body.single-post .ags-c-posthero__photocol { padding: 0; }
  body.single-post .ags-c-posthero__photo {
    flex: 1 1 auto; width: 100%;
  }
  body.single-post .ags-c-posthero__photo .elementor-widget-container {
    height: 100%; width: 100%;
  }
  body.single-post .ags-c-posthero__photo img {
    height: 100%; width: 100%; -o-object-fit: cover; object-fit: cover; display: block; margin: 0;
  }

  /* ---- §2 POST BODY — centred tag pills (dynamic post_tag, unlinked — same "Post
     Terms" dynamic-tag recipe as the article card) sit ABOVE `the_content` (see the
     builder's docblock for why: the mockup's big centred heading is `the_content`'s
     OWN first heading, not a widget we control, so the pills can't be interleaved
     between it and the rest of the article — a deliberate, flagged layout call). ---- */
  body.single-post .ags-c-postbody__inner { width: 100%; }
  body.single-post .ags-c-postbody__tags .elementor-widget-container {
    display: flex; flex-wrap: wrap; justify-content: center; gap: calc(10 * var(--ags-px));
  }
  body.single-post .ags-c-postbody__tags .elementor-widget-container > p,
  body.single-post .ags-c-postbody__tags .elementor-widget-container > .elementor-text-editor {
    display: contents;
  }
  body.single-post .ags-c-postbody__tags span {
    display: inline-flex; align-items: center;
    border: 1px solid var(--ags-red); border-radius: 999px;
    padding: calc(8 * var(--ags-px)) calc(18 * var(--ags-px));
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(13 * var(--ags-px)); line-height: 1; color: var(--ags-red);
  }
  /* Date (Simon review #4) — same red-outline pill language as the tag above, just
     stacked directly below it in their own tight-gap column (`.ags-c-postbody__
     tagdate`, gap set in the builder) rather than side-by-side like the article
     card's photo-overlay pills (no photo to overlay here, and "directly below the
     tag" is the explicit brief). Needs the SAME `[data-elementor-type="single-post"]`
     specificity boost as the hero title above for its font-size ONLY — `head()`
     also assigns it the "nav_link" GLOBAL typography preset, so it's subject to the
     identical 4-class Kit-generated override (border/padding/color below are safe;
     those aren't properties Kit's own rule sets). */
  body.single-post .ags-c-postbody__tagdate { width: 100%; }
  body.single-post .ags-c-postbody__date .elementor-heading-title {
    display: inline-flex; align-items: center;
    border: 1px solid var(--ags-red); border-radius: 999px;
    padding: calc(6 * var(--ags-px)) calc(16 * var(--ags-px));
    font-family: 'Bounded', sans-serif; font-weight: 400;
    line-height: 1; color: var(--ags-red); margin: 0;
  }
  body.single-post [data-elementor-type="single-post"] .ags-c-postbody__date .elementor-heading-title {
    font-size: calc(12 * var(--ags-px));
  }
  body.single-post .ags-c-postbody__content { width: 100%; }

  /* `the_content` generic typography — h2/h3/p/ul/blockquote/figure, matching
     Desktop (4)'s all-maroon body-copy treatment for HEADINGS (verified by sampling
     the mockup's own pixels: the big lead heading renders #8E1E00, not black). Plain
     running copy (`p`/`li`/`figcaption`, inheriting this container's base `color`)
     is darkened to the Kit's own main dark-text global (`--ags-black`, #111010 —
     Simon review #5: at this widget's light 300-weight/small size, #8E1E00 never hits
     full ink saturation at any one pixel, reading as faint; #111010 reads dark at any
     weight). Headings/links/blockquotes keep the branded red via their OWN explicit
     `color` below (all more specific than this base rule, so they're unaffected by
     this change) — only plain body copy moves to the dark-text token. */
  body.single-post .ags-c-postbody__content .elementor-widget-container {
    max-width: calc(760 * var(--ags-px)); margin: 0 auto;
    color: var(--ags-black);
    font-family: 'Red Hat Display', sans-serif; font-weight: 300;
    font-size: calc(16 * var(--ags-px)); line-height: 1.6;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container > h2:first-of-type {
    text-align: center; font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(40 * var(--ags-px)); line-height: 1.15; color: var(--ags-red);
    margin: 0 0 calc(28 * var(--ags-px));
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container h2:not(:first-of-type),
  body.single-post .ags-c-postbody__content .elementor-widget-container h3,
  body.single-post .ags-c-postbody__content .elementor-widget-container h4 {
    text-align: left; font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(22 * var(--ags-px)); line-height: 1.3; color: var(--ags-red);
    margin: calc(32 * var(--ags-px)) 0 calc(14 * var(--ags-px));
  }
  /* h5/h6 — a smaller sub-heading tier below h3/h4, same family/colour. Migrated
     content uses these for shorter labels (a masters-bio "Master info" sub-label
     ahead of its own table; a "Shuri-Te" eyebrow; assorted section sub-heads) —
     84 posts carry at least one <h6>. Previously fell through to plain unstyled
     body-copy sizing (no h5/h6 rule existed at all), reading as a stray small line
     with no visual role. */
  body.single-post .ags-c-postbody__content .elementor-widget-container h5,
  body.single-post .ags-c-postbody__content .elementor-widget-container h6 {
    text-align: left; font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: calc(17 * var(--ags-px)); line-height: 1.3; color: var(--ags-red);
    margin: calc(28 * var(--ags-px)) 0 calc(8 * var(--ags-px));
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container p {
    margin: 0 0 calc(16 * var(--ags-px)); font-size: calc(15 * var(--ags-px)); line-height: 1.6;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container b,
  body.single-post .ags-c-postbody__content .elementor-widget-container strong {
    font-weight: 600;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container a {
    color: var(--ags-red) !important; -webkit-text-decoration: underline !important; text-decoration: underline !important;
    font-family: 'Red Hat Display', sans-serif !important; font-size: inherit !important;
    background-color: transparent !important;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container ul,
  body.single-post .ags-c-postbody__content .elementor-widget-container ol {
    margin: 0 0 calc(16 * var(--ags-px)); padding-left: calc(22 * var(--ags-px));
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container li {
    margin-bottom: calc(6 * var(--ags-px));
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container blockquote {
    margin: calc(24 * var(--ags-px)) 0;
    padding: calc(4 * var(--ags-px)) 0 calc(4 * var(--ags-px)) calc(20 * var(--ags-px));
    border-left: 3px solid var(--ags-red); font-style: italic; color: var(--ags-red);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container figure {
    margin: calc(24 * var(--ags-px)) 0;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container figcaption {
    text-align: center; font-size: calc(13 * var(--ags-px)); margin-top: calc(8 * var(--ags-px));
  }
  /* Generic safety cap for EVERY content image (rows AND standalone) — a migrated
     post's raw <img width height> attributes (e.g. a legacy 1200x600 full-width
     banner) would otherwise overflow the 760px column. A standalone image (one
     ageshio-content-photorows.js left alone — a lone photo, not part of a >=2 run)
     is also centred, matching how the mockup treats single full-width photos. */
  body.single-post .ags-c-postbody__content .elementor-widget-container img {
    max-width: 100%; height: auto;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container > p > img:only-child {
    display: block; margin: calc(24 * var(--ags-px)) auto; border-radius: calc(4 * var(--ags-px));
  }
  /* A standalone <figure><img></figure> (a `gallery-item` or icon figure left alone
     because it wasn't part of a >=2 run — see ageshio-content-photorows.js) never
     had its OWN image centred: the `:only-child` rule above only matches a `<p>`-
     wrapped image, not a `<figure>`-wrapped one, so it sat flush against the
     figure's own left edge instead of matching every other standalone photo. */
  body.single-post .ags-c-postbody__content .elementor-widget-container figure img {
    display: block; margin: 0 auto;
  }

  /* Mini "info" tables — masters-bio "Dojo info"/"Master info"/"Lesson info" (all 19
     posts, byte-identical structure: real <table><th scope=row><td>, verified via a
     site-wide sweep) and the same th/td shape reused for a workshop's "Date/Venue/
     Fee"/"Cancel Policy" blocks. The migrated 30/70 column split lives in each cell's
     OWN `style="width:30%"` / `width:70%` — left alone, it's already consistent.
     Only the table's CHROME is unified: the legacy per-cell `bgcolor` attributes
     (table itself dark red, showing through as pseudo-borders between a pink header
     cell and a white body cell) are HTML presentational hints, the lowest possible
     CSS priority — a plain type-selector rule already wins over them with no
     `!important` needed. Swapped for one real border + a soft red-tinted header,
     matching the rest of the page's palette instead of the old site's. */
  body.single-post .ags-c-postbody__content .elementor-widget-container table {
    width: 100%; margin: calc(28 * var(--ags-px)) 0;
    border-collapse: separate; border-spacing: 0;
    border: 1px solid var(--ags-red); border-radius: calc(8 * var(--ags-px));
    overflow: hidden; background: var(--ags-cream);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container th,
  body.single-post .ags-c-postbody__content .elementor-widget-container td {
    padding: calc(12 * var(--ags-px)) calc(18 * var(--ags-px));
    font-size: calc(15 * var(--ags-px)); line-height: 1.5; vertical-align: top;
    border-bottom: 1px solid rgba(142, 30, 0, 0.14); background: var(--ags-cream);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container th {
    background: rgba(142, 30, 0, 0.08);
    font-family: 'Bounded', sans-serif; font-weight: 400; text-align: left;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container tr:last-child th,
  body.single-post .ags-c-postbody__content .elementor-widget-container tr:last-child td {
    border-bottom: none;
  }

  /* Embeds — a bare YouTube URL on its own line in the migrated source is expanded
     by WP core's OWN autoembed handler into a real `<iframe width height src=...>`
     (verified server-side via curl on 20409's rendered HTML: 28 posts carry one or
     more). It ships at the SOURCE video's fixed pixel size (1200x900, 1200x675, …) —
     this theme has no `add_theme_support('responsive-embeds')` wrapper, so
     unconstrained it would overflow the 760px column. `height:auto` does NOT derive
     an iframe's height from its own width/height ATTRIBUTES the way it does for
     <img> (verified live: an un-ratio'd `height:auto` iframe collapsed to Chromium's
     150px UA default, squashing every video) — `ageshio-content-photorows.js` reads
     those same attributes and sets a real per-embed `aspect-ratio` inline; the flat
     16/9 here is only the NO-JS fallback (16/9 is the majority ratio among these
     posts' embeds; the odd 4:3 source pillarboxes gracefully in a 16/9 box, same as
     it would on any site that standardises embed players to one ratio). */
  body.single-post .ags-c-postbody__content .elementor-widget-container iframe {
    display: block; width: 100%; height: auto; max-width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: calc(4 * var(--ags-px));
  }

  /* Migration-artifact suppression (belt-and-suspenders alongside the JS cleanup in
     ageshio-content-photorows.js — this half covers no-JS visitors): old-site nav-
     button graphics ("-btn-" filenames) + the one recurring decorative spacer icon
     present in ~50 of the 87 migrated posts (2020-06-05_18h25_36.png). Matched by
     filename substring — stable across installs (unlike a post-id/attachment-id
     selector), and specific enough not to catch a post's genuine content photos. */
  body.single-post .ags-c-postbody__content .elementor-widget-container img[src*="-btn-"],
  body.single-post .ags-c-postbody__content .elementor-widget-container img[src*="2020-06-05_18h25_36"] {
    display: none;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container a:has(> img[src*="-btn-"]) {
    display: none;
  }

  /* Image rows — `.ags-c-contentrow` / `__item` / `__cap` are inserted by
     ageshio-content-photorows.js around runs of >=2 real content <img>s (each with
     its own following-sibling <p> caption, if present); 3-per-row matches the
     dominant convention elsewhere on this site (c_photorow is always 3-across). */
  body.single-post .ags-c-contentrow {
    display: flex; flex-wrap: wrap; gap: calc(20 * var(--ags-px));
    margin: calc(24 * var(--ags-px)) 0;
  }
  body.single-post .ags-c-contentrow__item {
    /* flex-grow:0 — a row whose image count isn't a multiple of 3 (e.g. Desktop (4)'s
       own first row has 4) leaves a "leftover" item alone on its own line; grow:1
       would stretch that lone item to the FULL row width, reading as one big photo
       oddly bigger than its row-mates above. Keeping grow:0 sizes every item the
       SAME ~33% width regardless of how many end up sharing (or not sharing) a line. */
    flex: 0 1 calc(33.333% - 20 * var(--ags-px)); min-width: calc(160 * var(--ags-px));
    margin: 0; display: flex; flex-direction: column; gap: calc(10 * var(--ags-px));
  }
  body.single-post .ags-c-contentrow__item img {
    width: 100%; height: auto; display: block; margin: 0;
    border-radius: calc(4 * var(--ags-px));
  }
  body.single-post .ags-c-contentrow__cap {
    margin: 0; text-align: center; font-weight: 600; font-size: calc(14 * var(--ags-px));
  }
  body.single-post .ags-c-contentrow__cap a { color: inherit; }

  /* ---- §3 KEEP EXPLORING — centred heading on a neutral grey band (measured off
     Desktop (4), #E1E1E1 — a one-off tint not in the Kit's Global Colors, set as a
     literal background on the section in the JSON) + the related-posts grid. The
     card itself (`.ags-c-articlecard`) is styled by the SHARED rules above (~6896) —
     the whole point of the loop-item-attribute scope widen is that no rules are
     needed here for the card to render correctly. ---- */
  body.single-post .ags-c-keepexploring__h .elementor-heading-title {
    font-size: calc(48 * var(--ags-px)); line-height: 1.15;
  }
  body.single-post .ags-c-keepexploring__gridwrap { width: 100%; margin-top: calc(48 * var(--ags-px)); }
  body.single-post .ags-c-keepexploring__grid .elementor-grid {
    grid-template-columns: repeat(3, 1fr);
    --grid-column-gap: calc(24 * var(--ags-px));
    --grid-row-gap: calc(24 * var(--ags-px));
  }
}

/* =============================================================================
   SINGLE POST — MIGRATED INLINE-STYLE OVERRIDE (all widths, body.single-post)
   -----------------------------------------------------------------------------
   Deliberately OUTSIDE both breakpoint media queries — every property below is a
   colour/font-family/background SWAP, not a scaled length, so it doesn't need the
   desktop `--ags-px` reference-frame model or the mobile `clamp()` idiom used
   everywhere else in this section, and applies identically at every width.
   The 87 migrated posts carry stray inline `style=""` from the old site's editor —
   a site-wide sweep found `color:#0000ff` links (16 posts), Elementor `--e-global-
   color-text`/`--e-global-typography-text-*` var() references now meaningless in
   THIS theme (4 posts), and near-black greys (#000000/#2b2b2b, hundreds of spans/
   strongs/links, mostly from one workshop post's pasted-in "Venue" block) — plus one
   stray white inline `background-color` on a link. An inline `style=""` attribute on
   the element itself always outranks an external stylesheet rule no matter the
   selector's specificity, so `!important` is the only way to win this, not a
   selector-specificity fix. Scoped to the actual text tags that carried the inline
   styles (NOT `a` here — `a` gets its own rule, in both media blocks above/below,
   since it also needs `text-decoration`); genuinely-meaningful formatting (the
   bold/italic/underline TAGS themselves, list bullets) is untouched — only inline
   colour/font/background properties are reset back to the system's own values. */
body.single-post .ags-c-postbody__content .elementor-widget-container p,
body.single-post .ags-c-postbody__content .elementor-widget-container li,
body.single-post .ags-c-postbody__content .elementor-widget-container span,
body.single-post .ags-c-postbody__content .elementor-widget-container strong,
body.single-post .ags-c-postbody__content .elementor-widget-container b,
body.single-post .ags-c-postbody__content .elementor-widget-container em,
body.single-post .ags-c-postbody__content .elementor-widget-container u {
  color: var(--ags-black) !important;
  font-family: 'Red Hat Display', sans-serif !important;
  background-color: transparent !important;
}
/* font-size is reset separately, NARROWER than the list above — `p` sets its OWN
   deliberate `calc(15 * var(--ags-px))` / `clamp(...)` size in each media block, so
   resetting IT to `inherit` here would fight that (both would target the same `p`
   element's `font-size`; `!important` wins over a later non-important rule
   regardless of source order, which would have clobbered the intentional value).
   li/span/strong/b/em/u have no such rule of their own anywhere in this file, so
   `inherit` is safe for them — it simply pulls the *correct* size from whichever
   parent (p/li/the container) this override block already fixed above. */
body.single-post .ags-c-postbody__content .elementor-widget-container li,
body.single-post .ags-c-postbody__content .elementor-widget-container span,
body.single-post .ags-c-postbody__content .elementor-widget-container strong,
body.single-post .ags-c-postbody__content .elementor-widget-container b,
body.single-post .ags-c-postbody__content .elementor-widget-container em,
body.single-post .ags-c-postbody__content .elementor-widget-container u {
  font-size: inherit !important;
}
body.single-post .ags-c-postbody__content .elementor-widget-container b,
body.single-post .ags-c-postbody__content .elementor-widget-container strong {
  font-weight: 600 !important;
}

/* =============================================================================
   SINGLE POST TEMPLATE MOBILE (body.single-post) — Task M3.
   -----------------------------------------------------------------------------
   Ports the desktop `@media (min-width:1024px)` single-post rules above down to
   <=1023, additive-only (that block is untouched — desktop stays byte-for-byte
   identical). Decisions locked by Simon, docs/mobile/about-blog-mobile-sections.md
   "DECISIONS" block (2026-09-20):
     §1 Post Hero  → TEXT FIRST, PHOTO BELOW (not side-by-side): the maroon text
       block (kanji title + post title + excerpt) stacks on top, a full-width photo
       band (dynamic featured image, falling back to the SAME branded maroon-crest
       placeholder the article card uses) sits beneath it — preserves reading order.
       Centred (not the desktop's left-aligned kanji/title) — same "force centred
       regardless of the underlying widget's own alignment" idiom already used for
       every kanji eyebrow in this shared mobile block (~5829-5836): a narrow phone
       column reads better with the whole cluster centred than desktop's left-ragged
       CJK+Latin wrap (verified live: at 1280 the real kanji title, e.g. post
       `learning-okinawan-karate`, already wraps line-broken and left-aligned —
       mobile needs an even smaller, deliberately-centred size, not a smaller copy
       of that ragged wrap).
     §3 Content Image Rows → SINGLE COLUMN, one photo + its own caption per line
       (Simon's locked default) — `ageshio-content-photorows.js` is untouched; the
       JS still groups runs of >=2 content images into `.ags-c-contentrow` wrappers,
       this just makes that wrapper `flex-direction:column` instead of wrapping 3-up.
     "Keep exploring" grid → REUSES the shared `.ags-c-articlecard` mobile rule
       (Task M2, ~6680 above, already written anticipating this exact reuse — see
       its own docblock) — only the grid's OWN column-count collapses to 1 here;
       no card geometry is duplicated.
   Design truth: `Desktop (4).png` (1280x4904). Scope: `body.single-post` — see the
   desktop block's docblock (~8608) for why this is the correct portable hook (core's
   own body class, not a page-id/slug — this ONE template renders every post).
   Linter: scripts/qa/check_portable_scope.py. ============================================================================= */
@media (max-width: 1023px) {

  /* ---- §1 POST HERO — stack: text block (maroon) → full-width photo band. The
     section itself releases its Elementor-native mobile inline padding (padding-
     inline:0) so the maroon fill still bleeds edge-to-edge; the inset lives on
     `__left` instead (same "full-bleed colour, inset text" idiom as About's
     photohero eyebrow). `__row` drops from a side-by-side flex row to a column. ---- */
  body.single-post .ags-c-posthero { padding-left: 0; padding-right: 0; }
  body.single-post .ags-c-posthero__row { flex-direction: column; width: 100%; }
  body.single-post .ags-c-posthero__left {
    width: 100%; box-sizing: border-box;
    padding: max(48px, min(13vw, 64px)) max(24px, min(6vw, 32px)) max(40px, min(11vw, 56px));
    padding: max(48px, min(13vw, 64px)) max(24px, min(6vw, 32px)) clamp(40px, 11vw, 56px);
    padding: max(48px, min(13vw, 64px)) max(40px, min(11vw, 56px)) max(40px, min(11vw, 56px));
    padding: max(48px, min(13vw, 64px)) max(40px, min(11vw, 56px)) clamp(40px, 11vw, 56px);
    padding: max(48px, min(13vw, 64px)) clamp(24px, 6vw, 32px) clamp(40px, 11vw, 56px);
    padding: max(24px, min(6vw, 32px)) max(24px, min(6vw, 32px)) max(40px, min(11vw, 56px));
    padding: max(24px, min(6vw, 32px)) max(24px, min(6vw, 32px)) clamp(40px, 11vw, 56px);
    padding: max(24px, min(6vw, 32px)) max(40px, min(11vw, 56px)) max(40px, min(11vw, 56px));
    padding: max(24px, min(6vw, 32px)) max(40px, min(11vw, 56px)) clamp(40px, 11vw, 56px);
    padding: max(24px, min(6vw, 32px)) clamp(24px, 6vw, 32px) clamp(40px, 11vw, 56px);
    padding: max(40px, min(11vw, 56px)) max(24px, min(6vw, 32px)) max(40px, min(11vw, 56px));
    padding: max(40px, min(11vw, 56px)) max(24px, min(6vw, 32px)) clamp(40px, 11vw, 56px);
    padding: max(40px, min(11vw, 56px)) max(40px, min(11vw, 56px)) max(40px, min(11vw, 56px));
    padding: max(40px, min(11vw, 56px)) max(40px, min(11vw, 56px)) clamp(40px, 11vw, 56px);
    padding: max(40px, min(11vw, 56px)) clamp(24px, 6vw, 32px) clamp(40px, 11vw, 56px);
    padding: clamp(48px, 13vw, 64px) clamp(24px, 6vw, 32px) clamp(40px, 11vw, 56px);
    align-items: center; text-align: center;
  }
  /* Kanji hidden-when-blank backstop — same rule as desktop (~8651), restated here
     since that one is gated `@media (min-width:1024px)` and won't fire at mobile. */
  body.single-post .ags-c-posthero__kanji:has(.elementor-heading-title:empty) {
    display: none;
  }
  body.single-post .ags-c-posthero__kanji { width: 100%; text-align: center; }
  /* NOTE the `[data-elementor-type="single-post"]` ancestor qualifier on this + the
     title rule below: Elementor emits a PER-WIDGET generated rule at this exact
     breakpoint — `.elementor-<postID> .elementor-element.elementor-element-<id>
     .elementor-heading-title { font-size: var(--e-global-typography-secondary-
     font-size) }` inside its OWN `@media (max-width:767px)` block — FOUR classes
     (0,4,0), which beats a plain `body.single-post .ags-c-posthero__kanji
     .elementor-heading-title` (only 3 classes, 0,3,1) despite the extra `body`
     element, since class-count is compared before element-count. Verified live:
     without this, the kanji/title rendered at the Kit's mobile "secondary" global
     size (60px) instead of this clamp, overflowing the column (a real, visible bug
     — NOT the harmless off-canvas `.ags-mnav__panel--sub` artifact that also widens
     a full-page screenshot of this template). Adding the attribute selector (itself
     portable — the template's own `data-elementor-type`, same idiom as `[data-
     elementor-type="wp-page"]` elsewhere in this file) brings this to 4 classes +
     the `body` element, so it wins at equal class-count. */
  body.single-post [data-elementor-type="single-post"] .ags-c-posthero__kanji .elementor-heading-title {
    line-height: 1.15; margin: 0; text-align: center; word-wrap: break-word; overflow-wrap: break-word;
    font-size: max(32px, min(10vw, 42px));
    font-size: clamp(32px, 10vw, 42px);   /* smaller + centred than desktop's left-ragged wrap */
  }
  body.single-post .ags-c-posthero__title { width: 100%; text-align: center; }
  body.single-post [data-elementor-type="single-post"] .ags-c-posthero__title .elementor-heading-title {
    font-size: max(22px, min(6.5vw, 27px));
    font-size: clamp(22px, 6.5vw, 27px); line-height: 1.28; margin: 0; text-align: center;
    word-wrap: break-word;
    overflow-wrap: break-word;
  }
  body.single-post .ags-c-posthero__excerpt {
    margin-top: max(18px, min(5vw, 24px));
    margin-top: clamp(18px, 5vw, 24px); max-width: 32em; width: 100%;
  }
  body.single-post .ags-c-posthero__excerpt .elementor-widget-container p {
    text-align: center; font-size: max(14px, min(4vw, 15px)); font-size: clamp(14px, 4vw, 15px); line-height: 1.6; margin: 0;
  }
  /* Photo band — full-width, fixed height (not the desktop row-stretch trick, which
     has no meaning once the row is a column); same object-fit:cover treatment
     handles BOTH a real featured photo and the shared maroon-crest placeholder
     fallback (verified live: image-less posts render `article-placeholder-maroon.
     webp` through this exact hook — no separate placeholder CSS needed). */
  body.single-post .ags-c-posthero__photocol,
  body.single-post .ags-c-posthero__photo,
  body.single-post .ags-c-posthero__photo .elementor-widget-container {
    width: 100%; height: max(260px, min(82vw, 380px)); height: clamp(260px, 82vw, 380px);
  }
  body.single-post .ags-c-posthero__photo img {
    width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; margin: 0;
  }

  /* ---- §2 POST BODY — tag pills (centred, same "Post Terms" pill recipe as the
     article card's own mobile pills) + `the_content` generic typography, ported
     1:1 from the desktop ruleset (~8695-8774) at mobile scale + narrower column
     (max-width:100%, not 760px). Padding lives on the OUTER band (`.ags-c-postbody`
     itself), same convention as the Articles-listing header band (~6778). ---- */
  body.single-post .ags-c-postbody {
    padding-left: max(20px, min(5vw, 32px));
    padding-right: max(20px, min(5vw, 32px));
    padding-left: max(20px, min(5vw, 32px));
    padding-left: clamp(20px, 5vw, 32px);
    padding-right: max(20px, min(5vw, 32px));
    padding-right: clamp(20px, 5vw, 32px); box-sizing: border-box;
  }
  body.single-post .ags-c-postbody__inner { width: 100%; }
  body.single-post .ags-c-postbody__tags .elementor-widget-container {
    display: flex; flex-wrap: wrap; justify-content: center; gap: max(8px, min(2.4vw, 10px)); gap: clamp(8px, 2.4vw, 10px);
  }
  body.single-post .ags-c-postbody__tags .elementor-widget-container > p,
  body.single-post .ags-c-postbody__tags .elementor-widget-container > .elementor-text-editor {
    display: contents;
  }
  body.single-post .ags-c-postbody__tags span {
    display: inline-flex; align-items: center;
    border: 1px solid var(--ags-red); border-radius: 999px;
    padding: max(6px, min(2vw, 8px)) max(14px, min(4vw, 18px));
    padding: max(6px, min(2vw, 8px)) clamp(14px, 4vw, 18px);
    padding: max(14px, min(4vw, 18px)) max(14px, min(4vw, 18px));
    padding: max(14px, min(4vw, 18px)) clamp(14px, 4vw, 18px);
    padding: clamp(6px, 2vw, 8px) clamp(14px, 4vw, 18px);
    font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(12px, min(3.4vw, 13px));
    font-size: clamp(12px, 3.4vw, 13px); line-height: 1; color: var(--ags-red);
  }
  /* Date (Simon review #4, added post-M3 — NOT part of the original mobile reflow,
     purely additive so nothing already-locked-in above is touched) — same red-outline
     pill as the tag above it, mirroring the desktop treatment (~8730). Same
     `[data-elementor-type="single-post"]` font-size boost as the mobile keepexploring
     heading below (~9099) for the identical reason (`head()` assigns the "nav_link"
     GLOBAL typography preset, which gets its own 4-class Kit override at this
     breakpoint too). */
  body.single-post .ags-c-postbody__tagdate { width: 100%; }
  body.single-post .ags-c-postbody__date .elementor-heading-title {
    display: inline-flex; align-items: center;
    border: 1px solid var(--ags-red); border-radius: 999px;
    padding: max(6px, min(2vw, 8px)) max(14px, min(4vw, 18px));
    padding: max(6px, min(2vw, 8px)) clamp(14px, 4vw, 18px);
    padding: max(14px, min(4vw, 18px)) max(14px, min(4vw, 18px));
    padding: max(14px, min(4vw, 18px)) clamp(14px, 4vw, 18px);
    padding: clamp(6px, 2vw, 8px) clamp(14px, 4vw, 18px);
    font-family: 'Bounded', sans-serif; font-weight: 400;
    line-height: 1; color: var(--ags-red); margin: 0;
  }
  body.single-post [data-elementor-type="single-post"] .ags-c-postbody__date .elementor-heading-title {
    font-size: max(12px, min(3.4vw, 13px));
    font-size: clamp(12px, 3.4vw, 13px);
  }
  body.single-post .ags-c-postbody__content { width: 100%; }
  body.single-post .ags-c-postbody__content .elementor-widget-container {
    max-width: 100%; margin: 0 auto;
    /* Simon review #5 (desktop, ~8862) darkened plain running copy to the dark-text
       token because the light-weight/small red never hits full ink saturation —
       this mobile restatement had drifted to `--ags-red` instead (a copy-paste slip
       when the block was ported down to <=1023, never caught since nothing else
       here calls it out as wrong) and rendered EVERY mobile post's body copy in
       red. Bringing it in line with desktop's own, correct decision. */
    color: var(--ags-black);
    font-family: 'Red Hat Display', sans-serif; font-weight: 300;
    font-size: max(15px, min(4vw, 16px));
    font-size: clamp(15px, 4vw, 16px); line-height: 1.6;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container > h2:first-of-type {
    text-align: center; font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(26px, min(7.5vw, 32px));
    font-size: clamp(26px, 7.5vw, 32px); line-height: 1.2; color: var(--ags-red);
    margin: 0 0 max(20px, min(5.5vw, 26px));
    margin: 0 0 clamp(20px, 5.5vw, 26px);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container h2:not(:first-of-type),
  body.single-post .ags-c-postbody__content .elementor-widget-container h3,
  body.single-post .ags-c-postbody__content .elementor-widget-container h4 {
    text-align: left; font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(19px, min(5.5vw, 21px));
    font-size: clamp(19px, 5.5vw, 21px); line-height: 1.3; color: var(--ags-red);
    margin: max(24px, min(6.5vw, 30px)) 0 max(10px, min(3vw, 13px));
    margin: max(24px, min(6.5vw, 30px)) 0 clamp(10px, 3vw, 13px);
    margin: max(10px, min(3vw, 13px)) 0 max(10px, min(3vw, 13px));
    margin: max(10px, min(3vw, 13px)) 0 clamp(10px, 3vw, 13px);
    margin: clamp(24px, 6.5vw, 30px) 0 clamp(10px, 3vw, 13px);
  }
  /* h5/h6 — restated mobile-scaled version of the desktop rule (~8877); see that
     rule's docblock for why it exists at all. */
  body.single-post .ags-c-postbody__content .elementor-widget-container h5,
  body.single-post .ags-c-postbody__content .elementor-widget-container h6 {
    text-align: left; font-family: 'Bounded', sans-serif; font-weight: 400;
    font-size: max(16px, min(4.6vw, 18px));
    font-size: clamp(16px, 4.6vw, 18px); line-height: 1.3; color: var(--ags-red);
    margin: max(20px, min(5.5vw, 26px)) 0 max(7px, min(2vw, 9px));
    margin: max(20px, min(5.5vw, 26px)) 0 clamp(7px, 2vw, 9px);
    margin: max(7px, min(2vw, 9px)) 0 max(7px, min(2vw, 9px));
    margin: max(7px, min(2vw, 9px)) 0 clamp(7px, 2vw, 9px);
    margin: clamp(20px, 5.5vw, 26px) 0 clamp(7px, 2vw, 9px);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container p {
    margin: 0 0 max(14px, min(4vw, 16px));
    margin: 0 0 clamp(14px, 4vw, 16px); font-size: max(15px, min(4vw, 15px)); font-size: clamp(15px, 4vw, 15px); line-height: 1.6;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container b,
  body.single-post .ags-c-postbody__content .elementor-widget-container strong {
    font-weight: 600;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container a {
    color: var(--ags-red) !important; -webkit-text-decoration: underline !important; text-decoration: underline !important;
    font-family: 'Red Hat Display', sans-serif !important; font-size: inherit !important;
    background-color: transparent !important;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container ul,
  body.single-post .ags-c-postbody__content .elementor-widget-container ol {
    margin: 0 0 max(14px, min(4vw, 16px));
    margin: 0 0 clamp(14px, 4vw, 16px); padding-left: max(18px, min(5vw, 22px)); padding-left: clamp(18px, 5vw, 22px);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container li {
    margin-bottom: max(5px, min(1.5vw, 6px));
    margin-bottom: clamp(5px, 1.5vw, 6px);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container blockquote {
    margin: max(20px, min(5.5vw, 24px)) 0;
    margin: clamp(20px, 5.5vw, 24px) 0;
    padding: max(3px, min(1vw, 4px)) 0 max(3px, min(1vw, 4px)) max(16px, min(4.5vw, 20px));
    padding: max(3px, min(1vw, 4px)) 0 max(3px, min(1vw, 4px)) clamp(16px, 4.5vw, 20px);
    padding: max(3px, min(1vw, 4px)) 0 max(16px, min(4.5vw, 20px)) max(16px, min(4.5vw, 20px));
    padding: max(3px, min(1vw, 4px)) 0 max(16px, min(4.5vw, 20px)) clamp(16px, 4.5vw, 20px);
    padding: max(3px, min(1vw, 4px)) 0 clamp(3px, 1vw, 4px) clamp(16px, 4.5vw, 20px);
    padding: max(3px, min(1vw, 4px)) 0 max(3px, min(1vw, 4px)) max(16px, min(4.5vw, 20px));
    padding: max(3px, min(1vw, 4px)) 0 max(3px, min(1vw, 4px)) clamp(16px, 4.5vw, 20px);
    padding: max(3px, min(1vw, 4px)) 0 max(16px, min(4.5vw, 20px)) max(16px, min(4.5vw, 20px));
    padding: max(3px, min(1vw, 4px)) 0 max(16px, min(4.5vw, 20px)) clamp(16px, 4.5vw, 20px);
    padding: max(3px, min(1vw, 4px)) 0 clamp(3px, 1vw, 4px) clamp(16px, 4.5vw, 20px);
    padding: max(16px, min(4.5vw, 20px)) 0 max(3px, min(1vw, 4px)) max(16px, min(4.5vw, 20px));
    padding: max(16px, min(4.5vw, 20px)) 0 max(3px, min(1vw, 4px)) clamp(16px, 4.5vw, 20px);
    padding: max(16px, min(4.5vw, 20px)) 0 max(16px, min(4.5vw, 20px)) max(16px, min(4.5vw, 20px));
    padding: max(16px, min(4.5vw, 20px)) 0 max(16px, min(4.5vw, 20px)) clamp(16px, 4.5vw, 20px);
    padding: max(16px, min(4.5vw, 20px)) 0 clamp(3px, 1vw, 4px) clamp(16px, 4.5vw, 20px);
    padding: clamp(3px, 1vw, 4px) 0 clamp(3px, 1vw, 4px) clamp(16px, 4.5vw, 20px);
    border-left: 3px solid var(--ags-red); font-style: italic; color: var(--ags-red);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container figure {
    margin: max(20px, min(5.5vw, 24px)) 0;
    margin: clamp(20px, 5.5vw, 24px) 0;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container figcaption {
    text-align: center; font-size: max(12px, min(3.4vw, 13px)); font-size: clamp(12px, 3.4vw, 13px); margin-top: max(6px, min(2vw, 8px)); margin-top: clamp(6px, 2vw, 8px);
  }
  /* Generic safety cap for EVERY content image (rows AND standalone) — restated here
     since the desktop version (~8755) is gated `@media (min-width:1024px)`; without
     it a migrated post's raw <img width height> attributes (legacy full-width
     banners up to ~1200px) would overflow a 390px viewport. */
  body.single-post .ags-c-postbody__content .elementor-widget-container img {
    max-width: 100%; height: auto;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container > p > img:only-child {
    display: block; margin: max(20px, min(5.5vw, 24px)) auto; margin: clamp(20px, 5.5vw, 24px) auto; border-radius: max(3px, min(1vw, 4px)); border-radius: clamp(3px, 1vw, 4px);
  }
  /* Standalone figure image centring — restated mobile version of the desktop rule
     (~8926); see that rule's docblock. */
  body.single-post .ags-c-postbody__content .elementor-widget-container figure img {
    display: block; margin: 0 auto;
  }

  /* Mini "info" tables — mobile-scaled version of the desktop rule (~8956); see that
     rule's docblock for the structural + `bgcolor`-presentational-hint background. */
  body.single-post .ags-c-postbody__content .elementor-widget-container table {
    width: 100%; margin: max(20px, min(5.5vw, 24px)) 0; margin: clamp(20px, 5.5vw, 24px) 0;
    border-collapse: separate; border-spacing: 0;
    border: 1px solid var(--ags-red); border-radius: max(6px, min(1.6vw, 8px)); border-radius: clamp(6px, 1.6vw, 8px);
    overflow: hidden; background: var(--ags-cream);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container th,
  body.single-post .ags-c-postbody__content .elementor-widget-container td {
    padding: max(8px, min(2.4vw, 10px)) max(12px, min(3.4vw, 14px));
    padding: max(8px, min(2.4vw, 10px)) clamp(12px, 3.4vw, 14px);
    padding: max(12px, min(3.4vw, 14px)) max(12px, min(3.4vw, 14px));
    padding: max(12px, min(3.4vw, 14px)) clamp(12px, 3.4vw, 14px);
    padding: clamp(8px, 2.4vw, 10px) clamp(12px, 3.4vw, 14px);
    font-size: max(14px, min(3.8vw, 15px));
    font-size: clamp(14px, 3.8vw, 15px); line-height: 1.5; vertical-align: top;
    border-bottom: 1px solid rgba(142, 30, 0, 0.14); background: var(--ags-cream);
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container th {
    background: rgba(142, 30, 0, 0.08);
    font-family: 'Bounded', sans-serif; font-weight: 400; text-align: left;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container tr:last-child th,
  body.single-post .ags-c-postbody__content .elementor-widget-container tr:last-child td {
    border-bottom: none;
  }

  /* Embeds — mobile-scaled version of the desktop rule (~8987); see that rule's
     docblock — `ageshio-content-photorows.js` sets each embed's real aspect-ratio,
     this 16/9 is only the no-JS fallback. */
  body.single-post .ags-c-postbody__content .elementor-widget-container iframe {
    display: block; width: 100%; height: auto; max-width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: max(3px, min(1vw, 4px));
    border-radius: clamp(3px, 1vw, 4px);
  }
  /* Migration-artifact suppression — same filename-substring match as desktop
     (~8768), restated for the same reason (belt-and-suspenders alongside the JS
     cleanup, covers no-JS visitors at any width). */
  body.single-post .ags-c-postbody__content .elementor-widget-container img[src*="-btn-"],
  body.single-post .ags-c-postbody__content .elementor-widget-container img[src*="2020-06-05_18h25_36"] {
    display: none;
  }
  body.single-post .ags-c-postbody__content .elementor-widget-container a:has(> img[src*="-btn-"]) {
    display: none;
  }

  /* ---- §3 CONTENT IMAGE ROWS — SINGLE COLUMN (Simon's locked decision): the row
     wrapper goes flex-column instead of the desktop's wrap-3-up, each `.ags-c-
     contentrow__item` figure becomes one full-width photo + its own caption
     directly below it. `ageshio-content-photorows.js` itself is untouched — it
     still groups runs of >=2 content images into this same wrapper/item markup;
     this is a pure CSS re-flow of that existing structure. ---- */
  body.single-post .ags-c-contentrow {
    display: flex; flex-direction: column; flex-wrap: nowrap;
    gap: max(20px, min(5.5vw, 24px));
    gap: clamp(20px, 5.5vw, 24px); margin: max(20px, min(5.5vw, 24px)) 0; margin: clamp(20px, 5.5vw, 24px) 0;
  }
  body.single-post .ags-c-contentrow__item {
    flex: 1 1 100%; width: 100%; min-width: 0;
    margin: 0; display: flex; flex-direction: column; gap: max(8px, min(2.2vw, 10px)); gap: clamp(8px, 2.2vw, 10px);
  }
  body.single-post .ags-c-contentrow__item img {
    width: 100%; height: auto; display: block; margin: 0;
    border-radius: max(3px, min(1vw, 4px));
    border-radius: clamp(3px, 1vw, 4px);
  }
  body.single-post .ags-c-contentrow__cap {
    margin: 0; text-align: center; font-weight: 600; font-size: max(13px, min(3.6vw, 14px)); font-size: clamp(13px, 3.6vw, 14px);
  }
  body.single-post .ags-c-contentrow__cap a { color: inherit; }

  /* ---- §4 KEEP EXPLORING — heading + single-column related-posts grid, reusing
     the SHARED `.ags-c-articlecard` mobile ruleset (Task M2, ~6680 above) verbatim
     — no card geometry duplicated here, only the grid's own column count + the
     same grid-auto-rows Chromium workaround the Articles-listing grid needed
     (~6838-6854: a single-column grid whose items get their height purely from
     `aspect-ratio`, with no in-flow children, mis-reports its intrinsic height to
     its ancestor chain). `--ags-ac-cardw` mirrors the Articles-listing formula,
     using THIS section's own padding-inline. ---- */
  body.single-post { --ags-ac-cardw: calc(100vw - 2 * max(20px, min(5vw, 32px))); --ags-ac-cardw: calc(100vw - 2 * clamp(20px, 5vw, 32px)); }
  body.single-post .ags-c-keepexploring {
    padding-left: max(20px, min(5vw, 32px));
    padding-right: max(20px, min(5vw, 32px));
    padding-left: max(20px, min(5vw, 32px));
    padding-left: clamp(20px, 5vw, 32px);
    padding-right: max(20px, min(5vw, 32px));
    padding-right: clamp(20px, 5vw, 32px); box-sizing: border-box;
  }
  /* Same `[data-elementor-type="single-post"]` specificity boost as the hero
     kanji/title above (~8871) — this is ALSO an Elementor heading widget, subject
     to the identical 4-class per-widget mobile override. */
  body.single-post [data-elementor-type="single-post"] .ags-c-keepexploring__h .elementor-heading-title {
    font-size: max(26px, min(7.5vw, 32px));
    font-size: clamp(26px, 7.5vw, 32px); line-height: 1.2;
  }
  body.single-post .ags-c-keepexploring__gridwrap { width: 100%; margin-top: max(28px, min(7vw, 36px)); margin-top: clamp(28px, 7vw, 36px); }
  body.single-post .ags-c-keepexploring__grid .elementor-grid {
    grid-template-columns: 1fr;
    --grid-column-gap: 0px;
    --grid-row-gap: max(28px, min(7vw, 36px));
    --grid-row-gap: clamp(28px, 7vw, 36px);
    grid-auto-rows: calc(var(--ags-ac-cardw) * 442 / 405);
  }
}

/* =============================================================================
   TABLET (641–1023px) — review pass 2026-09-21.
   -----------------------------------------------------------------------------
   The mobile reflow (@media max-width:1023px) is tuned at ~390px; at tablet widths
   the text-heavy pages ran full-width (single-post prose hit ~110 chars/line at 1023)
   and the article cards went one-per-row (oversized, lots of scrolling). This block
   overrides the mobile rules for 641–1023 ONLY (it comes after the mobile block, so it
   wins on source order at equal specificity): cap the prose measure + centre it, and
   drop the two card grids to 2-up. Full-bleed elements (hero/body photos, band
   backgrounds) are deliberately NOT capped — only the reading columns. Below 641px the
   phone layout is unchanged. ============================================================ */
@media (min-width: 641px) and (max-width: 1023px) {
  /* single-post: comfortable reading measure, centred (was full-width) */
  body.single-post .ags-c-postbody__content .elementor-widget-container { max-width: 680px; margin-left: auto; margin-right: auto; }
  body.single-post .ags-c-posthero__excerpt .elementor-widget-container p { max-width: 640px; margin-left: auto; margin-right: auto; }
  /* 2-up card grids — recompute the shared card-width var for two columns so the
     grid-auto-rows height (keyed off --ags-ac-cardw) follows automatically. */
  body.ags-page-articles, body.single-post {
    --ags-ac-cardw: calc((100vw - 2 * max(20px, min(5vw, 32px)) - max(16px, min(3vw, 24px))) / 2);
    --ags-ac-cardw: calc((100vw - 2 * max(20px, min(5vw, 32px)) - clamp(16px, 3vw, 24px)) / 2);
    --ags-ac-cardw: calc((100vw - 2 * max(16px, min(3vw, 24px)) - max(16px, min(3vw, 24px))) / 2);
    --ags-ac-cardw: calc((100vw - 2 * max(16px, min(3vw, 24px)) - clamp(16px, 3vw, 24px)) / 2);
    --ags-ac-cardw: calc((100vw - 2 * clamp(20px, 5vw, 32px) - clamp(16px, 3vw, 24px)) / 2);
  }
  body.ags-page-articles .ags-c-articles-grid .elementor-grid,
  body.single-post .ags-c-keepexploring__grid .elementor-grid {
    grid-template-columns: 1fr 1fr;
    --grid-column-gap: max(16px, min(3vw, 24px));
    --grid-column-gap: clamp(16px, 3vw, 24px);
    -moz-column-gap: max(16px, min(3vw, 24px));
         column-gap: max(16px, min(3vw, 24px));
    -moz-column-gap: clamp(16px, 3vw, 24px);
         column-gap: clamp(16px, 3vw, 24px);
  }
}

/* =============================================================================
   INTERIOR EXPERIENCE-LIST — MOBILE immersive card (Simon 2026-09-21).
   -----------------------------------------------------------------------------
   Built by js/ageshio-explist-mobile.js on ≤1023 only (the element + the
   `.ags-explist--hasmcard` hook exist only when that JS has run on mobile, and are
   torn down ≥1024), so these rules are self-gating and need no media query. When the
   card is present, hide the desktop title list + the flanking edge photos; the card
   is a full-bleed photo that cross-fades through those same edge photos with the
   active experience title overlaid + a segmented progress bar. ============================ */
.ags-explist--hasmcard .ags-exp__track,
.ags-explist--hasmcard .ags-c-explist__row > .ags-exp__edge { display: none !important; }
.ags-explist--hasmcard .ags-c-explist__row { display: block; }

.ags-explist-mcard {
  position: relative; width: 100%;
  aspect-ratio: 4 / 5; max-height: min(560px, 68vh); max-height: min(560px, 68svh);
  margin-top: max(18px, min(5vw, 26px));
  margin-top: clamp(18px, 5vw, 26px);
  border-radius: 12px; overflow: hidden; cursor: pointer;
  background: #141010;
}
.ags-explist-mcard__photos, .ags-explist-mcard__photos img,
.ags-explist-mcard__scrim, .ags-explist-mcard__titles { position: absolute; top: 0; right: 0; bottom: 0; left: 0; }
.ags-explist-mcard__photos img {
  width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block;
  opacity: 0; transition: opacity .6s ease;
}
.ags-explist-mcard__photos img.is-active { opacity: 1; }
.ags-explist-mcard__scrim {
  background: linear-gradient(rgba(17,16,16,.22), rgba(17,16,16,.58));
  pointer-events: none;
}
.ags-explist-mcard__titles { display: grid; align-items: center; justify-items: center; place-items: center; padding: 0 9%; }
.ags-explist-mcard__titles span {
  grid-area: 1 / 1; text-align: center;
  opacity: 0; transition: opacity .5s ease;
  font-family: 'Bounded', sans-serif; font-weight: 400;
  color: var(--ags-cream, #f7f4f2);
  font-size: max(26px, min(7.6vw, 38px));
  font-size: clamp(26px, 7.6vw, 38px); line-height: 1.15;
  text-shadow: 0 2px 16px rgba(0,0,0,.45);
}
.ags-explist-mcard__titles span.is-active { opacity: 1; }
.ags-explist-mcard__bar {
  position: absolute; left: 6%; right: 6%; bottom: max(12px, min(3.5vw, 16px)); bottom: clamp(12px, 3.5vw, 16px);
  display: flex; gap: 6px; z-index: 2;
}
.ags-explist-mcard__bar span {
  flex: 1 1 0; height: 3px; border-radius: 2px;
  background: rgba(247,244,242,.35); transition: background .4s ease;
}
.ags-explist-mcard__bar span.is-active { background: var(--ags-cream, #f7f4f2); }
@media (prefers-reduced-motion: reduce) {
  .ags-explist-mcard__photos img, .ags-explist-mcard__titles span { transition: none; }
}

