
:root {
  --brand: #194f90;
  --brand-deep: color-mix(in oklch, var(--brand) 100%, black 30%);
  --brand-deeper: color-mix(in oklch, var(--brand) 100%, black 50%);
  --brand-tint: color-mix(in oklch, var(--brand) 7%, white 93%);
  --gold: #ffb606;
  --green: #009d4f;
  --green-deep: color-mix(in oklch, var(--green) 100%, black 14%);
  --wa-green: #25d366; /* WhatsApp's own brand green -- deliberately NOT var(--green) (SESC's
                           brand green), used only on the WhatsApp glyph itself so it reads as
                           the real WhatsApp mark rather than an SESC-tinted icon. */

  --bg: oklch(0.995 0.001 258);
  --surface: oklch(0.965 0.005 258);
  --ink: oklch(0.24 0.028 258);
  --ink-soft: oklch(0.44 0.02 258);
  --ink-faint: oklch(0.56 0.016 258);
  --line: oklch(0.9 0.008 258);

  /* "Noto Sans Arabic" is declared at the FOOT of this file with a `unicode-range` that
     covers only the Arabic blocks, so it claims Arabic characters and nothing else: Latin
     letters and Western digits still fall through to Segoe UI, and an English-only page
     never downloads it. See the ARABIC TYPEFACE AND RTL section for the full reasoning. */
  --font: "Noto Sans Arabic", "Segoe UI", -apple-system, Roboto, "Helvetica Neue", Arial, sans-serif;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  --z-sticky: 20;
  --z-mega: 30;
  --z-float: 35;
  --z-drawer: 40;
  --shell: 1280px;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--font); line-height: 1.6; -webkit-font-smoothing: antialiased; overflow-x: hidden; }
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
ul { margin: 0; padding: 0; list-style: none; }
h1, h2, h3 { margin: 0; line-height: 1.15; letter-spacing: -0.015em; text-wrap: balance; }
p { margin: 0; }
button { font: inherit; }

:focus-visible { outline: 2.5px solid var(--gold); outline-offset: 2px; border-radius: 3px; }

.shell { max-width: var(--shell); margin-inline: auto; padding-inline: clamp(18px, 4vw, 44px); }

/* ---------- Top utility strip ---------- */
.utility { background: var(--brand); color: color-mix(in oklch, white 82%, transparent); font-size: 0.78rem; }
.utility .shell { display: flex; justify-content: space-between; align-items: center; height: 34px; gap: 16px; }
.utility a:hover { color: #fff; }
.utility-contact { display: flex; gap: 18px; align-items: center; }

/* Phone + Kuwait flag, centered, above the header on every page (client
   request, 2026-08-19) -- resources/views/partials/topbar.blade.php,
   included by both site.layout and storefront.layout. Sits on top of
   .utility's own brand-navy bar rather than a new background/color pair,
   so it reads as one small design system, not two. */
.topbar { display: flex; justify-content: center; padding: 7px 16px; }
.topbar-phone { display: flex; align-items: center; gap: 8px; color: #fff; font-weight: 700; letter-spacing: 0.01em; transition: opacity 160ms var(--ease); }
.topbar-phone:hover, .topbar-phone:focus-visible { opacity: 0.8; }
.topbar-flag { flex: none; border-radius: 2px; overflow: hidden; box-shadow: 0 0 0 1px rgba(255,255,255,0.3); }
.topbar-phone-icon { flex: none; }

/* ---------- Header ---------- */
.site-header { position: sticky; top: 0; z-index: var(--z-sticky); background: rgba(255,255,255,0.92); backdrop-filter: blur(10px) saturate(1.3); border-bottom: 3px solid var(--green); }
.header-row { display: flex; align-items: center; justify-content: space-between; height: 74px; gap: 20px; }

.logo { display: flex; align-items: center; gap: 11px; flex: none; }
.logo img { height: 46px; width: auto; }
/* 2026-09-01: bumped to match sesc.net's own WordPress footer, where the logo is the
   dominant element of its column (measured live: ~150px tall against a ~348px-tall footer
   row) rather than a small mark beside the tagline. clamp() keeps that same proportion down
   to mobile instead of a fixed px overflowing a narrower .fcol. */
.logo-foot img { height: clamp(92px, 11vw, 140px); width: auto; }
@media (max-width: 430px) { .logo img { height: 38px; } }
.logo-mark { width: 40px; height: 40px; border-radius: 11px; background: var(--brand); display: grid; place-items: center; color: #fff; }
.logo-text { font-weight: 800; font-size: 1.12rem; color: var(--brand); letter-spacing: 0.06em; line-height: 1; }
.logo-sub { display: block; font-size: 0.58rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint); margin-top: 3px; letter-spacing: 0.05em; }

.nav { display: flex; align-items: center; gap: 2px; }
.nav > li { position: relative; }
/* The mega panel is wider than its trigger, so it must center on the header, not on the <li>.
   Making this one item `static` promotes .site-header to its containing block. */
.nav > li.has-mega { position: static; }
.nav-link { display: inline-flex; align-items: center; gap: 5px; padding: 26px 13px; font-size: 0.83rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-soft); border: 0; background: none; font-family: inherit; cursor: pointer; transition: color 160ms ease; }
.nav-link:hover, .nav > li:hover .nav-link, .nav > li:focus-within .nav-link { color: var(--brand); }
.nav-link[aria-current], .nav-link.is-active { color: var(--green); }
.nav-link .chev { width: 9px; height: 9px; transition: transform 200ms var(--ease); }
.nav > li:hover .chev, .nav > li:focus-within .chev { transform: rotate(180deg); }

.header-tools { display: flex; align-items: center; gap: 8px; flex: none; }
.tool { width: 40px; height: 40px; display: grid; place-items: center; border: 1.5px solid var(--brand); border-radius: 8px; color: var(--brand); background: #fff; position: relative; transition: background 160ms ease, color 160ms ease; }
.tool:hover { background: var(--brand); color: #fff; }
.account { display: flex; align-items: center; gap: 8px; padding: 0 12px 0 6px; width: auto; border-radius: 999px; font-size: 0.76rem; font-weight: 700; }
.account .avatar { width: 26px; height: 26px; border-radius: 999px; background: var(--brand); color: #fff; display: grid; place-items: center; flex: none; }
.account:hover .avatar { background: #fff; color: var(--brand); }

.burger { display: none; }

/* ---------- Dropdowns ---------- */
.drop, .mega { opacity: 0; visibility: hidden; transform: translateY(-6px); transition: opacity 180ms var(--ease), transform 220ms var(--ease), visibility 180ms; }
li:hover > .drop, li:focus-within > .drop, li:hover > .mega, li:focus-within > .mega { opacity: 1; visibility: visible; transform: none; }

.drop { position: absolute; top: 100%; left: 6px; min-width: 190px; background: #fff; border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 18px 40px -24px rgba(25,79,144,0.45); padding: 8px; z-index: var(--z-mega); }
.drop a { display: block; padding: 10px 12px; border-radius: 8px; font-size: 0.88rem; font-weight: 600; color: var(--ink-soft); }
.drop a:hover { background: var(--brand-tint); color: var(--brand); }
.drop a[aria-current] { color: var(--green); font-weight: 700; }

.mega { position: absolute; top: 100%; left: 50%; translate: -50% 0; width: min(1180px, calc(100vw - 32px)); max-height: calc(100vh - 150px); overflow-y: auto; background: var(--surface); border: 1px solid var(--line); border-radius: 16px; box-shadow: 0 30px 60px -30px rgba(25,79,144,0.5); padding: 28px 26px; z-index: var(--z-mega); }
/* Columns hold wildly different item counts (Recovery ~8, Nutrition ~28) --
   without align-items:start, CSS Grid stretch makes every column match
   the tallest, leaving a dead gap under short columns while the tall one
   still needs to scroll. Each column own <ul> gets its own capped,
   independently scrolling area instead of one shared scrollbar for the
   whole 5-column panel, so a long list scrolls in place without carrying
   every other column header out of view with it. */
.mega-grid { align-items: start; }
.mega-grid ul { max-height: 336px; overflow-y: auto; scrollbar-width: thin; padding-right: 4px; margin-right: -4px; }
.mega-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 26px; }
.mega h3 { font-size: 0.74rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--brand); margin-bottom: 12px; padding-bottom: 9px; border-bottom: 2px solid color-mix(in oklch, var(--brand) 18%, transparent); }
.mega h3 a:hover { text-decoration: underline; }
.mega li + li { margin-top: 1px; }
.mega a { display: block; font-size: 0.79rem; color: var(--ink-soft); padding: 4px 6px; border-radius: 5px; transition: color 140ms ease, background 140ms ease; }
.mega a:hover { color: var(--brand); background: #fff; }
.mega a[aria-current] { color: var(--green); font-weight: 700; }

/* 2026-07-23 -- "Education" nav: two lanes under one dropdown ("University" =
   the 3 department/category pages; "Courses" = those same departments drilled
   one level further into individual course pages). Reuses the existing
   .has-mega/.mega hover mechanism and .mega h3/.mega a leaf styling as-is --
   only the lane layout below is new. */
.mega-edu { display: flex; gap: 34px; align-items: flex-start; }
.mega-edu-lane { flex: 0 0 190px; }
.mega-edu-lane--courses { flex: 1 1 auto; min-width: 0; }
.edu-course-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; }
.edu-course-grid h3 { font-size: 0.68rem; }

/* ---------- Title band ---------- */
.titleband { background: var(--brand); border-block: 3px solid var(--green); position: relative; }
.titleband::after { content: ""; position: absolute; inset: 0; background-image: radial-gradient(rgba(255,255,255,0.1) 1px, transparent 1px); background-size: 20px 20px; -webkit-mask-image: linear-gradient(90deg, transparent 45%, black); mask-image: linear-gradient(90deg, transparent 45%, black); pointer-events: none; }
.titleband .shell { position: relative; display: flex; align-items: center; gap: clamp(16px, 3vw, 30px); min-height: 108px; padding-block: 14px; z-index: 1; }
.badge-ring { position: relative; width: clamp(74px, 9vw, 92px); height: clamp(74px, 9vw, 92px); border-radius: 999px; background: #fff; border: 4px solid var(--green); flex: none; display: grid; place-items: center; overflow: hidden; box-shadow: 0 10px 24px -12px rgba(0,0,0,0.5); }
/* 2026-09-14: client report against /team/{slug}'s own breadcrumb avatar ("picture of team
   member get zoomed") -- present in both languages equally, not an RTL-only bug, but first
   actually seen and reported against the Arabic page. Root cause: `place-items: center` above
   sets align-items to `center`, not the grid default's effective `stretch` -- so the single
   implicit row this img sits in sizes to CONTENT, not to .badge-ring's own height. A
   non-replaced element resolving `height: 100%` against that content-sized (indeterminate)
   track falls back to `auto` per spec, and for a real photograph that means the image's OWN
   width/height ratio: width resolved fine (84px, against the equally content-sized but
   incidentally-matching column), so height rendered at 84 * (1001/768) =~ 109px -- ~25px
   TALLER than the 84px-tall circle, invisibly overflowing until .badge-ring's own
   `overflow: hidden` hard-clipped it. That clip lands wherever the extra height happens to
   sit, not object-fit's own centred math, which is what actually read as "zoomed": the photo
   was being rendered ~30% larger than the badge and then guillotined, not smoothly
   cover-cropped. Anchoring the img with inset:0 on the now-`position: relative` .badge-ring
   sizes it against a real, definite box (the padding edge) instead of an indeterminate grid
   track, sidestepping the auto-fallback entirely. width/height:100% kept alongside inset:0 as
   a harmless belt-and-braces value, not the thing doing the work any more. */
.badge-ring img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.titleband-text h1 { color: #fff; font-size: clamp(1.5rem, 3.1vw, 2.3rem); font-weight: 700; }
.crumb { display: flex; gap: 7px; align-items: center; flex-wrap: wrap; color: color-mix(in oklch, white 68%, transparent); font-size: 0.76rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; margin-bottom: 6px; }
.crumb a:hover { color: #fff; }
.crumb span.crumb-static { color: color-mix(in oklch, white 55%, transparent); }

/* ---------- Monogram fallback (no badge / no icon -- never a broken <img>) ---------- */
.monogram { width: 100%; height: 100%; display: grid; place-items: center; background: var(--brand); color: #fff; font-weight: 800; letter-spacing: 0.02em; }
.badge-ring .monogram { font-size: clamp(1.1rem, 2.2vw, 1.5rem); }
.svc-card-badge .monogram, .icon-tile .monogram { font-size: 0.95rem; border-radius: 999px; }

/* ---------- Section shell ---------- */
section { padding-block: clamp(48px, 6.5vw, 82px); }
.tone { background: var(--surface); }
.tone-navy { background: linear-gradient(155deg, var(--brand) 0%, var(--brand-deep) 60%, var(--brand-deeper) 100%); color: #fff; }

.split { display: grid; gap: clamp(28px, 5vw, 62px); align-items: center; }
@media (min-width: 900px) { .split { grid-template-columns: 1fr 1fr; } .split.flip .media { order: 2; } }

h2 { font-size: clamp(1.3rem, 2.3vw, 1.85rem); color: var(--brand); font-weight: 700; margin-bottom: 16px; }
.tone-navy h2 { color: #fff; }
.prose { color: var(--ink-soft); max-width: 62ch; font-size: 1rem; }
.prose p + p { margin-top: 0.85em; }

.media img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; border-radius: 16px; box-shadow: 0 24px 48px -30px rgba(25,79,144,0.6); }

/* ---------- Check list ---------- */
.checks { display: grid; gap: 3px; }
.check { display: flex; gap: 11px; align-items: flex-start; padding: 8px 4px; border-bottom: 1px solid var(--line); }
.check:last-child { border-bottom: 0; }
.tone-navy .check { border-color: rgba(255,255,255,0.14); }
.tick { width: 21px; height: 21px; border-radius: 999px; flex: none; display: grid; place-items: center; background: color-mix(in oklch, var(--green) 14%, white 86%); color: var(--green); margin-top: 2px; }
.tone-navy .tick { background: rgba(255,255,255,0.14); color: #fff; }
.check span { font-size: 0.93rem; font-weight: 500; color: var(--ink); }
.tone-navy .check span { color: color-mix(in oklch, white 92%, transparent); }

/* ---------- Full-width banner section (zigzag breaker) ---------- */
.banner { position: relative; border-radius: 20px; overflow: hidden; margin-bottom: 40px; }
.banner img { width: 100%; aspect-ratio: 24 / 7; object-fit: cover; }
.banner-veil { position: absolute; inset: 0; background: linear-gradient(90deg, color-mix(in oklch, var(--brand-deeper) 90%, transparent) 12%, transparent 78%); display: flex; align-items: center; padding: clamp(20px, 4vw, 52px); }
.banner-veil h2 { color: #fff; max-width: 16ch; margin: 0; }
@media (max-width: 720px) { .banner img { aspect-ratio: 16/9; } .banner-veil { background: color-mix(in oklch, var(--brand-deeper) 72%, transparent); } }

.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 0 44px; }

/* Single-column fallback for .split when a block has no photo -- without this, the fixed
   two-column .split grid leaves an empty, blank second column rather than letting the text take
   the full width. More specific than the bare `.split` rule, so it wins with no !important. */
.split.single { grid-template-columns: 1fr; max-width: 74ch; margin-inline: auto; }

/* ---------- Long list ---------- */
.longlist-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 20px; flex-wrap: wrap; margin-bottom: 26px; }
.count { font-size: 0.8rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.longlist { display: grid; grid-template-columns: repeat(auto-fit, minmax(255px, 1fr)); gap: 2px 34px; }
.plan { display: flex; gap: 10px; align-items: center; padding: 9px 6px; border-bottom: 1px solid var(--line); font-size: 0.9rem; }
.plan::before { content: ""; width: 5px; height: 5px; border-radius: 999px; background: var(--gold); flex: none; }

/* ---------- Icon grid (block type "icon-grid") ----------
   Each cell is the client's real per-item artwork PNG, which already has its CAPTION BAKED INTO
   the image -- so there is NO visible HTML caption (it would duplicate the baked text). The image
   IS the chip. Explicit column counts with real breakpoints (NOT auto-fit/auto-fill): fixed 1fr
   cells never stretch a lone item full-width (bug class 4); a partial last row just left-aligns.
   Icons render ~150-180 CSS px on desktop so the baked caption is legible, and scale down cleanly
   on narrow viewports. White tile backing so the transparent navy line-art always reads. */
.icongrid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; }
@media (max-width: 980px) { .icongrid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 720px) { .icongrid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 460px) { .icongrid { grid-template-columns: repeat(2, 1fr); } }
.icongrid-item { display: block; background: #fff; border: 1px solid var(--line); border-radius: 16px; padding: 12px; box-shadow: 0 8px 22px -18px rgba(25,79,144,0.4); }
.icongrid-item img { width: 100%; max-width: 190px; height: auto; display: block; margin-inline: auto; }
a.icongrid-item { transition: transform 160ms var(--ease), box-shadow 200ms var(--ease), border-color 160ms ease; }
a.icongrid-item:hover, a.icongrid-item:focus-visible { transform: translateY(-4px); border-color: var(--brand); box-shadow: 0 18px 34px -18px rgba(25,79,144,0.5); }
/* Monogram fallback -- ONLY when an item has no real icon (no baked caption), so it needs a
   visible label. Never triggers when every item matched 1:1. */
.icongrid-item--fallback { display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; padding: 20px 14px; }
.icongrid-mono { width: 64px; height: 64px; border-radius: 16px; background: var(--brand-tint); display: grid; place-items: center; font-weight: 800; font-size: 1.5rem; color: var(--brand); }
.icongrid-mono img { width: 38px; height: 38px; object-fit: contain; }
.icongrid-fallback-label { font-size: 0.84rem; font-weight: 700; color: var(--ink-soft); line-height: 1.25; }

/* ---------- Landing-page intro copy ---------- */
.page-intro { max-width: 68ch; color: var(--ink-soft); font-size: 1.02rem; margin-bottom: clamp(30px, 4vw, 46px); }

/* ---------- Service cards (category index -> its services, "icon + title") ---------- */
.cards-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 900px) { .cards-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .cards-grid { grid-template-columns: 1fr; } }
/* Client request (2026-08-19): a clean list, not a box/card per service --
   .svc-card is a plain row now (no border/background/shadow/radius), just a
   hairline under each one, the same divider treatment as .checks elsewhere
   on this stylesheet. Hover moves the accent to the text/arrow instead of
   lifting a card that no longer exists. */
.svc-card { display: flex; align-items: center; gap: 14px; padding: 14px 4px; background: none; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; transition: background 160ms var(--ease); }
.svc-card:last-child { border-bottom: 0; }
.svc-card:hover, .svc-card:focus-visible { background: var(--brand-tint); }
.svc-card:hover .svc-card-title, .svc-card:focus-visible .svc-card-title { color: var(--brand); }
.svc-card:hover .svc-card-arrow, .svc-card:focus-visible .svc-card-arrow { transform: rotate(-90deg) translateX(3px); }
.svc-card-badge { width: 52px; height: 52px; border-radius: 999px; flex: none; overflow: hidden; border: 2px solid var(--green); background: var(--brand-tint); }
.svc-card-badge img { width: 100%; height: 100%; object-fit: cover; }
.svc-card-title { flex: 1 1 auto; min-width: 0; font-weight: 700; font-size: 0.92rem; color: var(--ink); }
.svc-card-arrow { flex: none; color: var(--brand); transform: rotate(-90deg); transition: transform 160ms var(--ease); }

/* ---------- Category cards (services index -> its 5 categories) ---------- */
/* Overrides the generic `section { padding-block: clamp(48px, 6.5vw, 82px); }` rule (this is
   the only <section> on the page with no class of its own before this) with roughly half that
   value. That generic rule doesn't collapse between adjacent sections -- About's own bottom
   padding and this section's top padding both apply and simply add together -- so the gap
   above/below the category cards read as an oversized empty band. Scoped to .services-cats
   only: About/Team/Reviews still want the full generic spacing, this is the one section the
   client actually flagged. */
.services-cats { padding-block: clamp(24px, 3.5vw, 44px); }
/* Matches .partners-head/.reviews-head's own plain centered-heading treatment -- see
   home.blade.php's own comment on this block for why it was missing entirely until
   2026-09-01 despite HomeController already computing the values. */
.cats-head { text-align: center; margin-bottom: 30px; }
.cats-head h2 { font-size: clamp(1.6rem, 2.6vw, 2.2rem); }
.cats-head p { margin-top: 10px; color: var(--ink-faint); }
.cat-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; }
@media (max-width: 1080px) { .cat-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 720px) { .cat-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .cat-grid { grid-template-columns: 1fr; } }
.cat-card { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; padding: 28px 26px; background: #fff; border: 1px solid var(--line); border-radius: 18px; box-shadow: 0 14px 30px -22px rgba(25,79,144,0.45); transition: transform 170ms var(--ease), box-shadow 200ms var(--ease), border-color 160ms ease; }
.cat-card:hover, .cat-card:focus-visible { transform: translateY(-4px); border-color: var(--brand); box-shadow: 0 22px 40px -18px rgba(25,79,144,0.55); }
/* Icon replaces the old "N SERVICES" badge as the card's dominant visual --
   same round-badge language as .svc-card-badge elsewhere on the site (brand-tint
   fill, green ring), just holding a line icon instead of a photo. */
.cat-icon { width: 52px; height: 52px; border-radius: 999px; flex: none; display: grid; place-items: center;
  background: var(--brand-tint); border: 2px solid var(--green); color: var(--brand); }
.cat-icon svg { width: 26px; height: 26px; }
/* Four of the five categories now use the client's own real icon artwork
   (PNG, no matching Recovery icon exists yet -- that one keeps the sprite
   above) instead of a generic hand-drawn glyph. The source files are
   1250x1250 squares with a fair amount of transparent margin baked in
   around the drawing itself, so they need a noticeably larger box than the
   26px SVGs to read at the same visual weight -- sized here to fill the
   same 52px badge as the SVG icons do (object-fit: contain keeps their own
   aspect ratio, and they're already flat brand-green line art, so they sit
   naturally inside the badge's own green ring without any extra color
   treatment needed). */
.cat-icon img { width: 40px; height: 40px; object-fit: contain; }
.cat-card h3 { font-size: 1.1rem; color: var(--brand); font-weight: 800; margin: 0; }
.cat-card p { font-size: 0.86rem; color: var(--ink-soft); margin: 0; }
.cat-card-foot { display: flex; align-items: center; justify-content: flex-end; width: 100%; margin-top: auto; padding-top: 4px; }
/* Count kept, but demoted to a quiet secondary detail next to the arrow -- the
   loud uppercase green badge is gone, the icon above is the dominant mark now. */
.cat-count { font-size: 0.78rem; font-weight: 600; color: var(--ink-faint); }
.cat-card .cat-card-arrow { color: var(--brand); transform: rotate(-90deg); }

/* ---------- CTA / WhatsApp pill ----------
   The client's own button graphic (assets/brand/whatsapp-cta.png), used as-is rather than
   redrawn in CSS -- it's their exact button, not our reconstruction of it. Source PNG had its
   transparency flattened onto a checkerboard (no real alpha channel); tools/build_home_assets
   or an equivalent one-off pass restores it before it lands in assets/brand/. */
.cta { padding-block: clamp(40px, 5vw, 60px); padding-inline: clamp(16px, 4vw, 44px); display: grid; place-items: center; }
.wa-cta { display: inline-block; max-width: 100%; transition: transform 170ms var(--ease), filter 200ms var(--ease); filter: drop-shadow(0 14px 26px rgba(0,0,0,0.35)); }
.wa-cta:hover, .wa-cta:focus-visible { transform: translateY(-2px); filter: drop-shadow(0 18px 32px rgba(0,0,0,0.42)); }
.wa-cta:active { transform: scale(0.985); }
.wa-cta img { display: block; width: min(90vw, 420px); height: auto; }
/* Same graphic, sized for the 300px team-profile aside (store.css's .team-detail-aside)
   instead of the full-page banner width above. */
.tm-wa-cta { width: 100%; }
.tm-wa-cta img { width: 100%; }

/* Persistent floating WhatsApp bubble, bottom-right, every page (client request,
   2026-08-19) -- resources/views/partials/wa-float.blade.php, included by both
   site.layout and storefront.layout. --z-float sits below --z-drawer on purpose:
   the open mobile drawer's overlay should cover the bubble, not float above it.
   env(safe-area-inset-*) keeps it clear of a phone's home-indicator gesture bar. */
.wa-float {
  position: fixed;
  right: max(20px, env(safe-area-inset-right));
  bottom: max(20px, env(safe-area-inset-bottom));
  z-index: var(--z-float);
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 999px;
  background: var(--wa-green);
  color: #fff;
  box-shadow: 0 14px 28px -10px rgba(9, 20, 36, 0.45);
  transition: transform 180ms var(--ease), box-shadow 200ms var(--ease);
}
.wa-float:hover, .wa-float:focus-visible { transform: translateY(-3px); box-shadow: 0 18px 34px -10px rgba(9, 20, 36, 0.52); }
.wa-float:active { transform: scale(0.94); }
@media (max-width: 640px) {
  .wa-float { width: 50px; height: 50px; }
  .wa-float svg { width: 24px; height: 24px; }
}

/* ---------- Footer ---------- */
footer { background: var(--brand-deeper); color: color-mix(in oklch, white 66%, transparent); }
/* padding-block and .fcol ul's gap both bumped 2026-09-01 alongside the logo/link sizing
   below, to the same "sized and designed like sesc.net's own footer" client request --
   their footer's content row measured ~348px tall live vs. ours at the old sizing, mostly a
   product of a much smaller logo and 14px links rather than extra whitespace, so this column
   only needed to grow, not add empty padding on top of it. */
.foot-top { padding-block: 64px; display: grid; grid-template-columns: 1.5fr 1fr 1fr 1.4fr; gap: 36px; }
@media (max-width: 900px) { .foot-top { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .foot-top { grid-template-columns: 1fr; } }
footer h4 { font-size: 0.72rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: color-mix(in oklch, white 52%, transparent); margin: 0 0 14px; }
footer .fcol ul { display: grid; gap: 13px; }
/* Size/weight/color measured directly off the live sesc.net footer's own link rows
   (19px, weight 700, #009d4f -- exactly var(--green), so only size/weight needed to move). */
footer a { font-size: 1.02rem; font-weight: 700; color: var(--green); transition: color 150ms ease; }
footer a:hover { color: var(--gold); }
.map { position: relative; border-radius: 12px; aspect-ratio: 16/10; background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.14); overflow: hidden; }
.map iframe { width: 100%; height: 100%; border: 0; display: block; filter: invert(92%) hue-rotate(180deg) brightness(0.95) contrast(0.9); }
/* The business-profile card sesc.net overlays top-left of its own footer map (name,
   address, star rating, review count) -- reused vocabulary from .review-stars' green rating
   colour, sitting on a solid card so it stays legible over the map underneath regardless of
   what's rendered there. */
.map-card { position: absolute; top: 10px; left: 10px; z-index: 1; display: grid; gap: 3px;
  background: #fff; color: var(--ink); border-radius: 8px; padding: 9px 12px;
  box-shadow: 0 8px 18px -8px rgba(0,0,0,0.45); max-width: calc(100% - 20px);
  text-decoration: none; transition: transform 160ms var(--ease); }
.map-card:hover { transform: translateY(-1px); }
.map-card strong { font-size: 0.78rem; font-weight: 800; color: var(--brand); line-height: 1.3; }
.map-card span { font-size: 0.72rem; color: var(--ink-soft); }
.map-card-rating { display: flex; align-items: center; gap: 4px; font-weight: 700; color: var(--ink); }
.map-card-rating svg { width: 12px; height: 12px; color: var(--gold); flex: none; }
.map-card-count { font-weight: 500; color: var(--ink-faint); }
.map-link { display: inline-flex; align-items: center; gap: 6px; margin-top: 10px; color: var(--green) !important; font-weight: 700; }
.map-link svg { flex: none; }
.foot-bottom { border-top: 1px solid rgba(255,255,255,0.13); padding-block: 18px; display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap; font-size: 0.78rem; color: color-mix(in oklch, white 52%, transparent); }
.foot-bottom a { color: color-mix(in oklch, white 62%, transparent); }
.foot-bottom a:hover { color: #fff; }

/* ---------- Scroll reveal ----------
   Visible by default; enhanced only where the native scroll timeline exists. A JS/class-gated
   opacity:0 default renders blank in print, no-JS, and headless capture, so it is not used. */
@keyframes rise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@supports (animation-timeline: view()) {
  [data-reveal] { animation: rise linear both; animation-timeline: view(); animation-range: entry 0% cover 34%; }
}
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { animation: none !important; opacity: 1 !important; transform: none !important; }
  * { transition-duration: 0.01ms !important; }
}

@media (max-width: 1080px) {
  .nav, .account { display: none; }
  .burger { display: grid; }
}
/* Small phones: the header row is logo + 2 tool buttons. Below ~430px those two blocks together
   exceed the viewport, so shed the non-essential text rather than let it clip. */
@media (max-width: 560px) {
  .utility .shell > span:first-child { display: none; }
  .utility .shell { justify-content: center; }
}
@media (max-width: 430px) {
  .logo-sub { display: none; }
  .header-tools { gap: 6px; }
  .tool { width: 38px; height: 38px; }
}

/* ============================================================================================
   Mobile drawer -- real off-canvas accordion menu (<1080px), data-driven from the same
   category/page data the desktop mega-menu reads, so the two can never disagree.
   ============================================================================================ */
.drawer-overlay { position: fixed; inset: 0; background: rgba(9, 20, 36, 0.55); z-index: var(--z-drawer); opacity: 0; transition: opacity 220ms var(--ease); }
.drawer-overlay[hidden] { display: none; }
.drawer-overlay.is-open { opacity: 1; }

.drawer { position: fixed; top: 0; right: 0; height: 100dvh; width: min(388px, 88vw); max-width: 100vw; background: #fff; z-index: calc(var(--z-drawer) + 1); display: flex; flex-direction: column; transform: translateX(100%); transition: transform 260ms var(--ease); box-shadow: -24px 0 60px -24px rgba(0,0,0,0.45); }
.drawer[hidden] { display: none; }
.drawer.is-open { transform: translateX(0); }

.drawer-head { flex: none; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--line); }
.drawer-close { width: 38px; height: 38px; border-radius: 8px; border: 1.5px solid var(--brand); color: var(--brand); background: #fff; display: grid; place-items: center; flex: none; }
.drawer-close:hover { background: var(--brand); color: #fff; }

.drawer-body { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 6px 8px 28px; }
.drawer-list > li { border-bottom: 1px solid var(--line); }

.acc-trigger { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 15px 10px; background: none; border: 0; font-weight: 700; font-size: 0.92rem; color: var(--ink); text-align: left; cursor: pointer; }
.acc-trigger .chev { width: 11px; height: 11px; color: var(--ink-faint); flex: none; transition: transform 200ms var(--ease); }
.acc-trigger[aria-expanded="true"] .chev { transform: rotate(180deg); }
.acc-trigger.is-active { color: var(--green); }

.acc-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 240ms var(--ease); }
.acc-panel.is-open { grid-template-rows: 1fr; }
.acc-inner { overflow: hidden; min-height: 0; }

.acc-inner > ul { padding: 0 6px 10px 14px; display: grid; gap: 1px; }
.acc-inner a { display: block; padding: 9px 8px; font-size: 0.86rem; font-weight: 600; color: var(--ink-soft); border-radius: 7px; }
.acc-inner a:hover, .acc-inner a:focus-visible { background: var(--brand-tint); color: var(--brand); }
.acc-inner a[aria-current] { color: var(--green); font-weight: 700; }

/* nested category rows inside "Our Services" */
.acc-cat-row { display: flex; align-items: stretch; border-top: 1px solid var(--line); }
.acc-cat-row:first-child { border-top: 0; }
.acc-cat-link { flex: 1 1 auto; min-width: 0; padding: 13px 6px 13px 6px; font-size: 0.88rem; font-weight: 700; color: var(--ink); }
.acc-cat-link:hover, .acc-cat-link:focus-visible { color: var(--brand); }
.acc-cat-link[aria-current] { color: var(--green); }
.acc-chev-btn { width: 44px; flex: none; display: grid; place-items: center; background: none; border: 0; color: var(--ink-faint); cursor: pointer; }
.acc-chev-btn .chev { width: 10px; height: 10px; transition: transform 200ms var(--ease); }
.acc-chev-btn[aria-expanded="true"] .chev { transform: rotate(180deg); }
.acc-sub-panel .acc-inner > ul { padding-left: 28px; }

/* 2026-07-23 -- "Education" > University/Courses nesting: same generic
   .acc-trigger/.acc-panel pair as every other drawer item, one level deeper.
   site.js's querySelectorAll(".acc-trigger"/".acc-chev-btn") already picks
   these up with zero JS changes -- only the indentation/sizing below is new. */
.acc-inner > ul.drawer-list--nested > li.acc { border-top: 1px solid var(--line); }
.acc-inner > ul.drawer-list--nested > li.acc:first-child { border-top: 0; }
.acc-trigger--sub { font-size: 0.86rem; padding: 12px 8px; }

.drawer-link { display: block; padding: 15px 10px; font-weight: 700; font-size: 0.92rem; color: var(--ink); }
.drawer-link:hover, .drawer-link:focus-visible { color: var(--brand); }
.drawer-link[aria-current] { color: var(--green); }

/* Drawer utility row -- Search + My Account, the two header controls that are icon-only or hidden
   at mobile. Two explicit equal columns (not auto-fit), so no orphaned cell. */
.drawer-utility { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 16px 10px 4px; }
.drawer-util-btn { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 10px; border: 1.5px solid var(--brand); border-radius: 12px; background: #fff; color: var(--brand); font-weight: 700; font-size: 0.84rem; cursor: pointer; transition: background 150ms ease, color 150ms ease; }
.drawer-util-btn:hover, .drawer-util-btn:focus-visible { background: var(--brand); color: #fff; }
.drawer-util-btn svg { width: 17px; height: 17px; flex: none; }

/* Full oversized-bubble .pill is sized in vw units against the *viewport*, wider than the
   drawer's own (max 388px) box -- reusing it here would overflow the drawer's internal scroll
   container. A compact, single-line, content-sized button is the correct component here. */
.drawer-wa { display: flex; align-items: center; justify-content: center; gap: 10px; margin: 18px 10px 6px; padding: 13px 16px; background: var(--green); color: #fff; font-weight: 800; font-size: 0.86rem; border-radius: 999px; text-align: center; box-shadow: 0 12px 26px -14px rgba(0,157,79,0.7); }
.drawer-wa:hover { background: var(--green-deep); }
.drawer-wa svg { width: 18px; height: 18px; color: #fff; flex: none; }

body.drawer-locked { overflow: hidden; }

/* ============================================================================================
   Home hero
   ============================================================================================ */
.hero { position: relative; background: linear-gradient(155deg, var(--brand) 0%, var(--brand-deep) 55%, var(--brand-deeper) 100%); color: #fff; overflow: hidden; border-bottom: 3px solid var(--green); }
.hero::after { content: ""; position: absolute; inset: 0; background-image: radial-gradient(rgba(255,255,255,0.12) 1px, transparent 1px); background-size: 22px 22px; -webkit-mask-image: linear-gradient(180deg, black 0%, transparent 85%); mask-image: linear-gradient(180deg, black 0%, transparent 85%); pointer-events: none; }
.hero .shell { position: relative; z-index: 1; padding-block: clamp(56px, 9vw, 108px); display: grid; gap: 20px; max-width: 46ch; }
.hero-eyebrow { display: inline-flex; align-items: center; gap: 8px; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--gold); }
.hero h1 { font-size: clamp(2rem, 5vw, 3.1rem); font-weight: 800; color: #fff; }
.hero p { font-size: 1.05rem; color: color-mix(in oklch, white 88%, transparent); max-width: 52ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; }
.btn { display: inline-flex; align-items: center; gap: 8px; padding: 13px 24px; border-radius: 999px; font-weight: 800; font-size: 0.88rem; border: 2px solid transparent; transition: transform 160ms var(--ease), background 160ms ease, color 160ms ease, border-color 160ms ease; }
.btn:hover { transform: translateY(-2px); }
.btn-primary { background: var(--gold); color: var(--brand-deeper); }
.btn-primary:hover { background: #fff; }
.btn-outline { border-color: rgba(255,255,255,0.55); color: #fff; }
.btn-outline:hover { border-color: #fff; background: rgba(255,255,255,0.1); }
.hero-stats { display: grid; grid-template-columns: repeat(3, auto); gap: clamp(20px, 4vw, 44px); margin-top: 10px; }
.hero-stat b { display: block; font-size: clamp(1.4rem, 3vw, 1.9rem); color: var(--gold); font-weight: 800; }
.hero-stat span { font-size: 0.76rem; color: color-mix(in oklch, white 78%, transparent); text-transform: uppercase; letter-spacing: 0.05em; }

/* ============================================================================================
   Team grid (team page)
   ============================================================================================ */
.team-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
@media (max-width: 900px) { .team-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .team-grid { grid-template-columns: 1fr; } }
.team-card { background: #fff; border: 1px solid var(--line); border-radius: 18px; padding: 26px 22px; box-shadow: 0 14px 30px -24px rgba(25,79,144,0.4); display: grid; gap: 4px; }
.team-avatar { width: 62px; height: 62px; border-radius: 999px; background: var(--brand); color: #fff; display: grid; place-items: center; font-weight: 800; font-size: 1.15rem; margin-bottom: 10px; }
.team-card h3 { font-size: 1.02rem; color: var(--brand); font-weight: 800; }
.team-role { font-size: 0.82rem; font-weight: 700; color: var(--green); text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 6px; }
.team-cred { font-size: 0.85rem; color: var(--ink-soft); }

/* ============================================================================================
   Store / product grid
   ============================================================================================ */
.store-note { display: flex; gap: 12px; align-items: flex-start; background: var(--brand-tint); border: 1px solid color-mix(in oklch, var(--brand) 20%, transparent); border-radius: 14px; padding: 16px 18px; margin-bottom: clamp(30px, 4vw, 46px); font-size: 0.9rem; color: var(--ink-soft); max-width: 74ch; }
.store-note svg { flex: none; color: var(--brand); margin-top: 2px; }
.prod-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
@media (max-width: 1080px) { .prod-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 720px) { .prod-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 440px) { .prod-grid { grid-template-columns: 1fr; } }
.prod-card { display: flex; flex-direction: column; background: #fff; border: 1px solid var(--line); border-radius: 16px; overflow: hidden; box-shadow: 0 12px 26px -22px rgba(25,79,144,0.4); transition: transform 160ms var(--ease), box-shadow 200ms var(--ease), border-color 160ms ease; }
.prod-card:hover, .prod-card:focus-visible { transform: translateY(-3px); border-color: var(--brand); box-shadow: 0 20px 36px -18px rgba(25,79,144,0.5); }
.prod-thumb { aspect-ratio: 1 / 1; width: 100%; background: linear-gradient(155deg, var(--brand) 0%, var(--brand-deep) 100%); color: #fff; display: grid; place-items: center; text-align: center; padding: 14px; }
.prod-thumb span { font-weight: 800; font-size: 0.92rem; letter-spacing: 0.01em; }
.prod-body { padding: 14px 16px 18px; display: grid; gap: 6px; }
.prod-cat { font-size: 0.7rem; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; color: var(--green); }
.prod-name { font-size: 0.95rem; font-weight: 700; color: var(--ink); }
.prod-price { font-size: 0.98rem; font-weight: 800; color: var(--brand); font-variant-numeric: tabular-nums; }

/* Product detail page */
.prod-detail { display: grid; gap: clamp(28px, 5vw, 62px); align-items: start; }
@media (min-width: 860px) { .prod-detail { grid-template-columns: 1fr 1fr; } }
.prod-detail .prod-thumb { aspect-ratio: 1 / 1; border-radius: 18px; box-shadow: 0 24px 48px -30px rgba(25,79,144,0.6); padding: 32px; }
.prod-detail .prod-thumb span { font-size: 1.15rem; }
.prod-detail-price { font-size: 1.6rem; font-weight: 800; color: var(--brand); font-variant-numeric: tabular-nums; margin: 6px 0 4px; }
.prod-desc-empty { font-style: italic; color: var(--ink-faint); font-size: 0.92rem; padding: 10px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); margin: 14px 0; }
.variant-head { font-size: 0.78rem; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint); margin: 18px 0 10px; }
.variant-list { display: flex; flex-wrap: wrap; gap: 10px; }
.variant-chip { display: flex; align-items: baseline; gap: 6px; padding: 9px 14px; border: 1.5px solid var(--line); border-radius: 999px; font-size: 0.85rem; font-weight: 700; color: var(--ink); background: #fff; }
.variant-chip .stock { font-weight: 500; color: var(--ink-faint); font-size: 0.78rem; }
.variant-chip.low .stock { color: var(--gold); }
.sku-line { font-size: 0.78rem; color: var(--ink-faint); margin-top: 4px; }

/* ============================================================================================
   Store / product page -- Brand-Forward Editorial. Merged from the client-approved "Concept 2"
   store redesign; this is now the sitewide canonical /store/ and /product/*/ treatment, not a
   body-class-scoped variant. Reuses every token/class above (--brand/--gold/--green/--ink*/
   --line/--ease/--shell, .titleband/.badge-ring/.prod-detail-price/.variant-*/.store-note/
   .wa-cta/.longlist-head/.count, and the .prod-card/.prod-grid/.prod-thumb/.prod-detail rules
   just above) -- only the layout below is new. Confirmed via
   `grep -rl 'prod-card\|prod-grid\|prod-thumb' public/demo/ --include=*.html` that .prod-* is
   used nowhere outside /store/ and /product/*/, so extending it here is safe.
   ============================================================================================ */
.c1-attr, .c2-chip {
  display: inline-block;
  font-size: 0.76rem;
  font-weight: 700;
  color: var(--brand);
  background: var(--brand-tint);
  border-radius: 999px;
  padding: 5px 12px;
}
.c1-attrs, .c2-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 4px; }
.c1-desc { color: var(--ink-soft); font-size: 0.96rem; margin: 10px 0 0; max-width: 52ch; }

.c1-buy-btn { display: inline-flex; align-items: center; gap: 9px; background: var(--wa-green); color: #fff; font-weight: 800; font-size: 0.96rem; padding: 13px 22px; border-radius: 12px; margin-top: 20px; box-shadow: 0 12px 24px -12px rgba(37,211,102,0.55); transition: transform 150ms var(--ease), box-shadow 180ms var(--ease); }
.c1-buy-btn:hover { transform: translateY(-2px); box-shadow: 0 16px 30px -12px rgba(37,211,102,0.6); }
.c1-buy-btn:active { transform: translateY(0) scale(0.98); }

.c2-hero { background: linear-gradient(180deg, var(--brand-tint), transparent); padding-block: clamp(30px, 5vw, 54px) 10px; }
.c2-hero-grid { display: grid; gap: clamp(24px, 5vw, 56px); align-items: center; }
@media (min-width: 860px) { .c2-hero-grid { grid-template-columns: 1fr 1.1fr; } }
.c2-eyebrow { display: block; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--green); margin-bottom: 10px; }
.c2-hero-text h1 { font-size: clamp(1.8rem, 4vw, 2.7rem); color: var(--brand); font-weight: 800; margin-bottom: 12px; }
.c2-hero-text p { color: var(--ink-soft); font-size: 1.02rem; max-width: 46ch; margin-bottom: 22px; }
.c2-hero-photo { position: relative; display: block; border-radius: 22px; overflow: hidden; aspect-ratio: 6 / 5; box-shadow: 0 30px 60px -30px rgba(25,79,144,0.55); }
.c2-hero-photo img { width: 100%; height: 100%; object-fit: cover; transition: transform 300ms var(--ease); }
.c2-hero-photo:hover img { transform: scale(1.04); }
.c2-hero-tag { position: absolute; left: 16px; bottom: 16px; background: rgba(10,20,36,0.72); backdrop-filter: blur(6px); color: #fff; font-size: 0.82rem; font-weight: 700; padding: 8px 14px; border-radius: 999px; }

.c2-note { margin-top: 26px; }

.c2-band { padding-block: clamp(30px, 5vw, 50px); }
.c2-band--tint { background: var(--brand-tint); }

.c2-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }
@media (max-width: 1080px) { .c2-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 720px) { .c2-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 440px) { .c2-grid { grid-template-columns: 1fr; } }

.c2-card { display: block; }
.c2-thumb { aspect-ratio: 1 / 1; border-radius: 999px; overflow: hidden; box-shadow: 0 16px 30px -18px rgba(25,79,144,0.45); border: 4px solid #fff; transition: transform 200ms var(--ease), box-shadow 220ms var(--ease); }
.c2-thumb img { width: 100%; height: 100%; object-fit: cover; }
.c2-card:hover .c2-thumb, .c2-card:focus-visible .c2-thumb { transform: translateY(-5px); box-shadow: 0 22px 38px -16px rgba(25,79,144,0.55); }
.c2-card-body { text-align: center; padding-top: 12px; display: grid; gap: 3px; }
.c2-card-body .prod-name { font-size: 0.92rem; font-weight: 700; color: var(--ink); }
.c2-card-body .prod-price { font-size: 0.92rem; font-weight: 800; color: var(--brand); }

.c2-detail { display: grid; gap: clamp(28px, 5vw, 62px); align-items: start; }
@media (min-width: 860px) { .c2-detail { grid-template-columns: 1fr 1fr; } }
.c2-detail-photo { border-radius: 22px; overflow: hidden; aspect-ratio: 4 / 5; box-shadow: 0 30px 60px -30px rgba(25,79,144,0.55); }
.c2-detail-photo img { width: 100%; height: 100%; object-fit: cover; }

@media (prefers-reduced-motion: reduce) {
  .c2-hero-photo img, .c2-thumb { transition: none !important; }
}

/* ============================================================================================
   HOME: hero slider
   ============================================================================================ */
.vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
      clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* The base stylesheet styles the OLD static hero and its rules out-specify the slider's, so
   neutralise them explicitly rather than trusting source order. Three real collisions:
     1. a generic section rule gives `.hero` 82px of block padding;
     2. `.hero .shell` (0,2,0) beats `.hero-ui` (0,1,0) and drags the controls bar back into
        flow as a padded grid, adding 287px of navy band above and below the photo;
     3. `.shell`'s margin-inline:auto centred the text column into the part of the scrim that is
        nearly transparent, destroying contrast. The column must sit hard left inside the shell. */
.hero { position: relative; isolation: isolate; background: var(--brand-deeper); overflow: hidden;
  padding: 0; }
.hero::after { content: none; }

.hero .shell.hero-content { display: flex; flex-direction: column; justify-content: center;
  align-items: flex-start; gap: 0; padding-block: 72px 104px;
  max-width: var(--shell); margin-inline: auto; }
.hero .hero-content > * { max-width: min(52ch, 100%); }
.hero .hero-sub { font-size: clamp(1rem, 1.35vw, 1.16rem); max-width: 52ch; }

/* The controls used to float on top of the photo (bottom:26px, absolute), which depended on
   the scrim or navy letterbox behind them for contrast. Neither exists any more once the hero
   is sized to the banner's own ratio and the scrim only renders for slides with real overlay
   text (see hero.blade.php) -- a bright slide (the body-composition chart is near-white) left
   gold-on-white controls and a white-ish progress track essentially invisible. Moving the bar
   into normal flow below `.hero-viewport`, on its own light surface, fixes that for any photo
   regardless of how light or dark it is, rather than re-tuning contrast per slide. */
.hero .hero-ui { position: relative; z-index: 2; max-width: none; margin-inline: 0;
  width: 100%; background: #fff; border-top: 1px solid var(--line);
  padding-inline: clamp(18px, 4vw, 44px); padding-block: 12px;
  display: flex; align-items: center; gap: 16px; }

/* The client's actual banners are wide graphic strips (~1656x448, close to a 3.7:1 letterbox
   ratio), not the tall 1920x1080 photo the old clamp(460px, 74svh, 720px) height assumed.
   Forcing a wide, short image into a much taller box under object-fit:contain left the extra
   height as bare navy bars above/below -- the "blue box" the client saw is `.hero`'s own
   background showing through, not the picture. Sizing the viewport to the banner's own ratio
   removes that gap outright: the box is only ever as tall as the picture actually is, so
   `contain` never needs to letterbox at all. min()/max-height keep it sane at extreme
   viewport widths without reintroducing bars at any normal one. */
.hero-viewport { position: relative; aspect-ratio: 1656 / 448; max-height: 74svh; }
/* At 1656:448 a real phone width turns the ratio-locked height into a sliver (~105px at a
   390px-wide screen) -- too short to read the baked-in headline at all. Floor it to something
   still short-and-wide (the client's own banner format, not the 460px+ a photographic hero
   would want) but tall enough to work as a photo band on mobile, and switch to `cover` only
   at this floor so the banner fills that taller box instead of letterboxing again. */
@media (max-width: 640px) {
  .hero-viewport { aspect-ratio: auto; height: 240px; max-height: none; }
  .hero-media img { object-fit: cover; }
}

.hero-slide { position: absolute; inset: 0; opacity: 0; visibility: hidden;
  transition: opacity 900ms var(--ease), visibility 0s linear 900ms; }
.hero-slide.is-active { opacity: 1; visibility: visible; transition-delay: 0s, 0s; z-index: 1; }

.hero-media { position: absolute; inset: 0; overflow: hidden; }
/* object-fit: contain, not cover -- these are the client's own full graphic banners (baked-in
   text and CTA), not candid photography meant to be cropped into a tall band. Now that
   .hero-viewport is sized to the banner's own aspect ratio (above), contain and cover render
   identically -- contain is kept as the safe choice in case a future upload's ratio drifts
   slightly from the standard, so it still shows the whole graphic rather than cropping into
   baked-in text. */
.hero-media img { width: 100%; height: 100%; object-fit: contain; transform: scale(1.02); }
/* Ken Burns pushes only on the slide that is actually showing. */
.hero-slide.is-active .hero-media img { animation: heroKB 9500ms var(--ease) forwards; }
@keyframes heroKB { from { transform: scale(1.09); } to { transform: scale(1.005); } }

/* Scrim: heavy under the text column, clearing toward the right so the photo stays visible.
   Two layers -- a horizontal wash for text contrast, a bottom vignette to seat the controls.

   Strength is PER SLIDE, not a constant: --scrim is set inline from the measured mean luma of
   each photo's left 55% (see home-manifest.json). Slide 2 is a body-composition chart on a
   near-white background (luma 247) and needs a far heavier wash than the dark massage room on
   slide 4 (luma 50). A single opacity that works for one blows out the other.

   --scrim-dir and --scrim-mix exist so hero.css can reuse this exact formula for the other
   five overlay/position combinations (mirror it for a right-anchored slide, halve it for
   scrim-soft) instead of hand-duplicating five near-identical gradient strings. Defaults
   below (90deg / var(--scrim)) reproduce the original single-look design exactly -- this rule
   IS "today's look", just with its two knobs named instead of inlined. */
.hero-scrim { position: absolute; inset: 0; pointer-events: none;
  --scrim: 0.88;
  --scrim-dir: 90deg;
  --scrim-mix: var(--scrim);
  /* The dark plateau must extend past the RIGHT EDGE of the text column, not stop at 38%.
     Measured: on the brightest slide (body-composition chart, photo mean RGB 245,241,237) a
     headline ending at 56% of the viewport was sitting on ~0.50 scrim alpha, i.e. a 2.6:1
     contrast. Holding near-full alpha to 50% and fading out by 90% puts the same pixel at
     ~0.73 alpha, ~5.8:1. Keep the plateau wider than `.hero-content > *`'s max-width. */
  background:
    linear-gradient(var(--scrim-dir), color-mix(in oklch, var(--brand-deeper) calc(var(--scrim-mix) * 100%), transparent) 0%,
                           color-mix(in oklch, var(--brand-deeper) calc(var(--scrim-mix) * 92%), transparent) 50%,
                           color-mix(in oklch, var(--brand-deeper) calc(var(--scrim-mix) * 45%), transparent) 70%,
                           color-mix(in oklch, var(--brand-deeper) calc(var(--scrim-mix) * 6%), transparent) 90%,
                           transparent 100%),
    linear-gradient(0deg, color-mix(in oklch, var(--brand-deeper) 62%, transparent) 0%, transparent 42%);
}

.hero-content { position: relative; z-index: 2; min-height: clamp(460px, 74svh, 720px);
  color: #fff; }
/* No align-self here (there used to be one, hard-pinning the eyebrow to the left forever) --
   it must follow whatever data-pos sets on .hero-content's align-items like every other
   child, or centred/right slides get every line in place except a stray left-anchored
   eyebrow. */
.hero-eyebrow { display: inline-flex; align-items: center; gap: 9px;
  font-size: 0.74rem; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--gold); margin-bottom: 18px; }
.hero-eyebrow::before { content: ""; width: 26px; height: 2px; background: var(--gold); }
/* The client's banners are bilingual: Arabic sits above the English, larger and lighter.
   Arabic needs its own stack and a looser line-height; Segoe UI covers it on Windows but not
   on macOS/Linux, so name real Arabic faces before falling back. */
.hero .hero-h-ar { margin: 0 0 10px; font-family: "Segoe UI", "Noto Sans Arabic", "Geeza Pro", Tahoma, sans-serif;
  font-size: clamp(1.6rem, 3.4vw, 2.7rem); font-weight: 700; line-height: 1.5;
  color: color-mix(in oklch, white 92%, transparent); }
.hero .hero-h { margin: 0; font-size: clamp(1.75rem, 3.9vw, 3.3rem); font-weight: 800; line-height: 1.08;
  letter-spacing: -0.025em; text-wrap: balance; color: #fff; }
.hero .hero-sub { margin-top: 20px; font-size: clamp(1rem, 1.35vw, 1.16rem); line-height: 1.55;
  color: color-mix(in oklch, white 88%, transparent); max-width: 52ch; }
.hero .hero-sub-ar { margin-top: 18px; font-family: "Segoe UI", "Noto Sans Arabic", "Geeza Pro", Tahoma, sans-serif;
  font-size: 1.05rem; color: var(--gold); font-weight: 600; }
.hero .hero-phone { display: inline-flex; align-items: center; gap: 9px; margin-top: 14px;
  font-size: 1.15rem; font-weight: 800; color: #fff; letter-spacing: 0.02em; }
.hero-phone svg { width: 20px; height: 20px; color: var(--wa-green); }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 32px; }

/* Slide contents stagger in behind the crossfade. Rewritten against the real element set --
   the old list staggered .hero-sub/.hero-sub-ar/.hero-phone, none of which the new partial
   emits, and never staggered .hero-eyebrow at all (an old oversight, not a new-contract gap).
   .hero-proof is new (the DOM contract's trust line under the actions row). */
.hero-slide.is-active .hero-eyebrow,
.hero-slide.is-active .hero-h-ar,
.hero-slide.is-active .hero-h,
.hero-slide.is-active .hero-actions,
.hero-slide.is-active .hero-proof { animation: heroRise 760ms var(--ease) backwards; }
.hero-slide.is-active .hero-h-ar { animation-delay: 60ms; }
.hero-slide.is-active .hero-h { animation-delay: 120ms; }
.hero-slide.is-active .hero-actions { animation-delay: 220ms; }
.hero-slide.is-active .hero-proof { animation-delay: 280ms; }
@keyframes heroRise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }

.btn-ghost { background: transparent; color: #fff; border: 1.6px solid color-mix(in oklch, white 55%, transparent); }
.btn-ghost:hover { background: color-mix(in oklch, white 12%, transparent); border-color: #fff; }

/* Controls. `.hero-ui`'s own box (position/inset/shell-measure) is set above, next to the
   comment explaining why it can't depend on a `.shell` class; this second rule only adds the
   children's flex behaviour so both stay easy to find from their own concerns. */
.hero-dots { display: flex; gap: 10px; }
.hero-dot { width: 34px; height: 5px; border-radius: 99px; border: 0; padding: 0; cursor: pointer;
  background: var(--line); transition: background 220ms var(--ease); }
/* DOM contract state is class `.is-on` + aria-current, not aria-selected -- the old rule keyed
   on aria-selected, which this markup never sets, so no dot would ever have shown as current. */
.hero-dot.is-on, .hero-dot[aria-current="true"] { background: var(--gold); }
.hero-dot:hover { background: color-mix(in oklch, var(--brand) 45%, var(--line)); }
/* No `.hero-arrows` wrapper exists in the DOM contract -- prev/next are direct children of
   `.hero-ui` -- so there is nothing left here to group; `.hero-ui`'s own `gap` (set above)
   already spaces every direct child, arrows included. */
/* Standardized to the exact same size/fill/hover language every other prev/next control on
   the page uses (.t3d-nav on Team, .partners-nav on Partners/Our Clients, .rev-arrow on
   Reviews) -- 52px, outline circle on --bg, brand icon, solid brand fill on hover. This bar
   was the one place still running its own smaller 40px/tinted-fill variant. */
.hero-arrow { width: 52px; height: 52px; border-radius: 50%; display: grid; place-items: center;
  cursor: pointer; color: var(--brand); background: var(--bg);
  border: 1.4px solid var(--line); flex: none;
  transition: transform 200ms var(--ease), background 200ms var(--ease), color 200ms var(--ease); }
.hero-arrow:hover { background: var(--brand); color: #fff; transform: translateY(-2px) scale(1.06); }
.hero-arrow:active { transform: translateY(0) scale(0.95); }
.hero-arrow svg { width: 20px; height: 20px; }
/* NOTHING TO ROTATE HERE, and this is not an oversight. Every other arrow in this stylesheet
   is the sprite's #i-arrow, which points right, so its "previous" twin is that glyph turned
   around -- see .carousel-arrow.prev below. The hero's two arrows are NOT that glyph: they are
   inline paths drawn per-direction in site/partials/hero.blade.php, already pointing the way
   each one means. This rule used to rotate `prev` as though it were a sprite arrow, which left
   both hero arrows pointing right; found while mirroring the carousels for Arabic. The Arabic
   mirror lives in the RTL section at the foot of this file and turns BOTH of them, together. */

/* .hero-playpause's own box (size/color/icon-toggle) is styled in hero.css, not here -- it
   already owned this button (loaded after this file, so a second definition here would just be
   overridden dead weight) and has been updated there for the same light-bar surface as the
   arrows above. */

/* The load bar itself: a full-bleed line on the bottom edge of the control strip, i.e.
   directly under the picture rather than drawn over it. var(--line) track / var(--gold) fill
   is legible on the bar's white surface regardless of what the current slide's photo looks
   like -- the whole point of moving it off the image in the first place. */
.hero-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; z-index: 1;
  background: var(--line); }
.hero-progress span { display: block; height: 100%; width: 0; background: var(--gold); }
.hero[data-playing="true"] .hero-progress span { animation: heroBar var(--hero-interval, 6500ms) linear forwards; }
.hero[data-paused="true"] .hero-progress span { animation-play-state: paused; }
@keyframes heroBar { from { width: 0; } to { width: 100%; } }

@media (max-width: 860px) {
  .hero .shell.hero-content { padding-block: 60px 96px; }
  .hero .hero-content > * { max-width: 100%; }
  .hero-scrim { background:
    linear-gradient(180deg, color-mix(in oklch, var(--brand-deeper) 55%, transparent) 0%,
                            color-mix(in oklch, var(--brand-deeper) 86%, transparent) 62%,
                            color-mix(in oklch, var(--brand-deeper) 94%, transparent) 100%); }
  .hero-arrow { display: none; }
  /* Centring on justify-content used to matter here because the old rule set
     justify-content:space-between on `.hero-ui`; the replacement layout in hero.css groups the
     controls with a single margin-inline-end:auto on the play/pause button instead, and that
     grouping keeps working with the arrows gone, so there's nothing to override at this
     breakpoint any more. */
}

/* ============================================================================================
   HOME: shared prev/next arrow (Team / Our Clients / Partners / Reviews)
   ------------------------------------------------------------------------------------------
   One canonical definition for every carousel arrow on the homepage. Each of the four
   sections used to carry its own copy of this same button (52px outline circle, brand icon,
   solid-brand fill on hover) and they had quietly drifted apart -- Team's copy got a
   store.css-only fix for a double-transform "shake" (see below) that Partners/Our Clients and
   Reviews never received, and Reviews' pair lived in the section heading rather than flanking
   its own content at all. .t3d-nav / .partners-nav / .rev-arrow keep their own class names
   (team.js / partners.js query them by name; Reviews has no JS controller of its own) purely
   as behavioural hooks -- every visual rule now lives here once, so "pixel-identical across
   all four" is structural, not something that has to be kept in sync by hand.

   translate:0 -50% does the vertical centering on its own; hover/active only ever change
   `transform` (scale). Mixing translateY into that same `transform` on hover/active (the old
   per-section rules did) stacks on top of the standalone `translate`, doubling the vertical
   shift for one frame -- that was "the arrow shake" store.css patched for Team only.
   Doing it right here removes the need for that patch anywhere. */
.carousel-arrow { position: absolute; top: 50%; translate: 0 -50%; z-index: 5;
  width: 52px; height: 52px; border-radius: 50%; display: grid; place-items: center; cursor: pointer;
  color: var(--brand); background: var(--bg); border: 1.4px solid var(--line);
  transition: transform 200ms var(--ease), background 200ms var(--ease), color 200ms var(--ease); }
.carousel-arrow:hover { background: var(--brand); color: #fff; transform: scale(1.06); }
.carousel-arrow:active { transform: scale(0.95); }
.carousel-arrow:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.carousel-arrow svg { width: 20px; height: 20px; }
.carousel-arrow.prev svg { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .carousel-arrow:hover, .carousel-arrow:focus-visible, .carousel-arrow:active { transform: none; }
}
@media (max-width: 640px) {
  .carousel-arrow { width: 40px; height: 40px; }
  .carousel-arrow svg { width: 16px; height: 16px; }
}

/* ============================================================================================
   HOME: team carousel
   ------------------------------------------------------------------------------------------
   Redesigned (homepage redesign pass) to match the client's reference: a flatter, simpler
   read than the previous 3D coverflow -- a navy heading band, then a light-gray stage holding
   exactly three circular portraits at once (prev / active / next), each with its own name and
   role directly beneath it, a thin green ring marking the active one, and plain round
   prev/next arrows at the stage's outer edges. Reuses the .t3d-* structure and team.js's
   carousel logic (active state, roving tabindex, keyboard, swipe, click-through to the
   person's page) -- only the visual treatment and the SIDE_VISIBLE/transform math in team.js
   changed (flat translateX, no rotateY/translateZ, exactly one card either side of active).
   ============================================================================================ */
.team3d { position: relative; background: var(--bg); overflow: hidden;
  padding-top: clamp(52px, 6vw, 78px); }

/* 2026-09-01, reversed a THIRD time -- the two-tone navy/grey pass right above this comment
   was marked "final, not asking again" the same day, off an explicit client line ("the
   background has two colours gray and blue... match the box as well"). The client has since
   directly asked, in the same conversation, to follow the PDF instead: heading+subtitle in
   plain text above the box, and "box with one colour" -- i.e. drop the navy band entirely and
   go back to the single flat grey .t3d-stage the PDF (Web 23-8-2026/LANDING QUESTION.pdf) has
   always shown. Matches .reviews-head/.partners-head's own plain centered-heading treatment
   now, so Team reads consistently with the other three homepage carousels' headers. h2/p no
   longer need a white/near-white override -- the global h2 rule already paints navy on white,
   which is correct here now that there's no navy band to go invisible against. */
.team3d-head { text-align: center; padding-block: 0 clamp(20px, 2.6vw, 30px); }
.team3d-head h2 { font-size: clamp(1.6rem, 2.6vw, 2.2rem); }
.team3d-head p { margin-top: 10px; color: var(--ink-soft); }

/* CORRECTED 2026-08-26: an earlier pass this session made this box "contained" (max-width +
   rounded corners) off a small, ambiguous CROPPED reference screenshot -- the client has since
   supplied the actual full-page PDF (Web 23-8-2026/LANDING QUESTION.pdf, a literal 1440px-wide
   capture) and a direct pixel measurement of it (PyMuPDF, probing the rendered band's color
   transitions and the photo/ring edges at 1:1 px==pt) shows the grey band genuinely runs FULL
   BLEED edge-to-edge with square corners -- there is no rounded contained box in the real
   design. .t3d-stage itself was never wrapped in `.shell` (it's a direct child of the
   full-width <section class="team3d">), so simply dropping the max-width/margin-auto/
   border-radius here is enough to make the BACKGROUND full-bleed again. The card row and
   arrows still need to line up with the page's normal 1280px content measure though, so their
   own centering/gutter (the padding-inline this rule used to carry) moved to the new
   `.t3d-inner` wrapper below -- same clamp() values as before, just on a narrower box nested
   inside the now-full-width stage, so arrow position is pixel-identical to before this fix. */
/* Background sampled directly from the client's PDF reference (Web 23-8-2026/LANDING
   QUESTION.pdf, the Team section's flat background fill, via PyMuPDF + PIL) -- #c7c8c6, not
   var(--surface) (oklch(0.965 0.005 258), a near-white token meant for subtle section tinting
   elsewhere on the page). 2026-09-01: back to this exact PDF value a second time (see
   .team3d-head's comment for the two-tone detour in between) -- single flat colour, no navy
   band adjacent any more, so there's no "flush seam" to manage here either. */
.t3d-stage { position: relative; background: #c7c8c6;
  margin-top: 0; padding-block: clamp(6px, 0.8vw, 11px); touch-action: pan-y; }
/* Card row + arrows: kept to the page's normal 1280px shell measure so they read as part of
   the same content column as every other section, even though the grey background behind them
   (.t3d-stage, above) now runs full-bleed. padding-inline is the exact clamp() .t3d-stage used
   to carry pre-fix -- moving it here (a narrower box centered inside the full-width stage)
   keeps .t3d-nav's left/right position pixel-identical to before, since .t3d-nav positions
   against this element's padding box the same way it positioned against .t3d-stage's before. */
.t3d-inner { position: relative; max-width: var(--shell); margin-inline: auto;
  padding-inline: clamp(64px, 9vw, 110px); }
/* Band height + photo size: previous pass sized these off the same small cropped reference
   (372px stage / 160px photo at ~1440px viewport) -- direct pixel measurement of the real PDF
   (probing the rendered band's top/bottom color-transition rows and the photo/ring circle's
   own edges) puts the actual band at ~199px tall with an ~105px photo at 1440px viewport, both
   scaled down from the old values by the same clamp() shape so they still shrink proportionally
   at narrower widths.

   Max clamp value bumped from a strict 155px to 185px versus that raw PDF-derived number: the
   PDF's own example roster happens to use only short one-line job titles, but the live roster
   has 14 people and one of them ("Senior Physical Therapy Specialist") wraps its role to 2
   lines at this card width -- at a strict 155px that 2nd line renders ~9px below the grey
   background (.t3d-ring/.t3d-stage are both overflow:visible, so it wasn't literally clipped,
   just visibly poking onto the white page background past the band's own bottom edge). 185px
   is the smallest value that fully contains a 2-line role for the new, much smaller photo/name/
   role stack, confirmed live via getBoundingClientRect() against every roster member, not just
   the 3 currently visible -- still a fraction of the pre-fix 260px max. */
/* 2026-09-01: the negative margin-top this rule used to carry pulled the ring up to straddle
   the navy/grey divide line -- a two-tone-specific effect (see .team3d-head's history) that
   no longer applies now that .t3d-stage is a single flat colour with no divide to straddle.
   Removed; the ring now just sits inside .t3d-stage's own padding-block like any normal
   centered row. */
/* 2026-09-01: card count reverted from 4 back to 3 (see team.js's own docblock) specifically
   so each one could be BIGGER, not smaller -- client's own words, checking this against the
   PDF a second time: "make it 3 and make them bigger rather then 4 and small". Sized up from
   the 4-card pass's clamp(150px,15.6vw,185px), but NOT by the full jump the bigger .t3d-btn
   below would suggest -- the client also asked for the box itself to get smaller in the same
   message, which pulls the other way. 205px max is the smallest that still clears the bigger
   135px photo + a 2-line role (the exact clipping bug .t3d-ring's max-155px-to-185px history
   above already documents once) with a couple px to spare; .t3d-stage's own padding-block was
   also trimmed to its own practical floor in the same pass, so the box is as compact as it
   can be without that clipping returning. */
.t3d-ring { position: relative; display: flex; justify-content: center;
  height: clamp(170px, 17.5vw, 205px); }
/* Was 420ms, matching .partners-track.is-manual's manual-step speed, then 680ms per client
   feedback that the auto-advance read as too fast/abrupt. UPDATED 2026-09-01 again: the
   client pasted the real Elementor/Swiper config off sesc.net itself, which sets
   `"speed":1000` for the slide transition (a separate value from `"autoplay_speed":2500`, the
   interval -- already correct on dev from the previous pass). 1000ms matches that literal
   reference and reads even more deliberate than the 680ms guess did. Team still diverges from
   Partners/Our Clients on purpose: those still step instantly on a manual arrow click, where a
   snappy 420ms suits a visitor-initiated action, but Team's own change is unprompted and
   continuous, so a slower glide reads as considered rather than jumpy. var(--ease) unchanged
   -- it's already a smooth expo-out curve, this was always a duration problem, not an easing
   one. */
/* top:50% + team.js's own translateY(-50%) centers this card vertically on .t3d-ring
   regardless of its actual content height (1- vs 2-line role) -- see team.js's own comment
   on that translateY for why plain top:0 read as "pushed to the top of the box". */
.t3d-card { position: absolute; top: 50%; left: 50%; width: clamp(170px, 15vw, 220px);
  margin-left: calc(clamp(170px, 15vw, 220px) / -2); text-align: center;
  will-change: transform, opacity;
  transition: transform 1000ms var(--ease), opacity 1000ms var(--ease); }
.t3d-card[data-hidden="true"] { opacity: 0; pointer-events: none; }

/* Ring is decorative brand styling on EVERY visible photo -- not an active/selected indicator,
   so it lives unconditionally on the base .t3d-btn instead of on an .is-active override.

   UPDATED 2026-09-01 (third pass): the pixel-width math (ring ~1.2%/gap ~4% of photo
   diameter, twice independently measured off the real ali_aldraie.png) was correct, but
   rounding 1.3px UP to 2px for visibility was the wrong call -- the client still read it as
   thick after that shipped. Rounding down to 1px instead (closer to the true 1.3px anyway).
   Separately, and probably the bigger factor: this app's brand --green (#009d4f, a fully
   saturated pure green) is measurably darker/more saturated than the reference ring's actual
   color, sampled directly from the same ali_aldraie.png alpha scan at RGB(29,178,99) /
   ~#1db263 -- a lighter, slightly warmer green. A dark, fully-saturated stroke reads as
   bolder than a light one even at an identical pixel width, which is likely why 2px of
   --green still looked "thick" against sesc.net's softer reference ring. Using the
   color-mix()'d lighter tone here ONLY (not touching the global --green token used
   everywhere else on brand) to match what was actually measured, not a guess. */
/* 2026-09-02 -- ACTUAL ROOT CAUSE of every "ring too thick / no gap" report this section has
   ever gotten (going back through this whole comment history above): every previous version of
   this box-shadow used `transparent` for the inner (smaller-spread) layer, on the assumption
   that would leave a see-through gap showing the grey box behind it. It does not. CSS stacks
   multiple box-shadows with the FIRST listed painted ON TOP; `transparent` paint contributes
   nothing and does not occlude what's beneath it, so the green second layer (spread 0 to
   whatever its own spread was) showed through the entire "gap" region uninterrupted -- the
   ring was never a thin stroke with a gap, it was always one solid green disc from the photo's
   edge out to the ring layer's own spread. Every earlier "make the gap bigger" pass just made
   that solid green disc bigger, which is exactly why it kept reading as "thicker" no matter how
   the numbers were tuned.

   Fixed by making the inner layer OPAQUE and the same colour as .t3d-stage's own background
   (#c7c8c6, hardcoded here on purpose -- this ring sits on that exact colour, not a token
   that could legitimately differ) instead of transparent: an opaque grey layer on top DOES
   occlude the green layer beneath it within its own spread, leaving only the sliver between
   the two spreads (6px to 7px) visibly green -- a real 1px ring with a real 6px gap, this
   time actually rendering as one. */
.t3d-btn { display: block; width: clamp(100px, 9vw, 135px); height: clamp(100px, 9vw, 135px);
  margin-inline: auto; padding: 0; border: 0; border-radius: 50%; overflow: hidden;
  cursor: pointer; background: #fff; position: relative;
  box-shadow: 0 0 0 6px #c7c8c6, 0 0 0 7px color-mix(in oklch, var(--green) 65%, white), 0 20px 36px -22px rgba(25,79,144,0.4);
  transition: box-shadow 280ms var(--ease), transform 280ms var(--ease); }
/* Team cards are click-to-navigate, not draggable content -- without this, a visitor
   trying to swipe with a mouse (click-drag instead of touch) triggers the browser's
   native "drag this image" ghost preview instead of moving the carousel, which reads
   as the card glitching/detaching from its position. Same fix on .partner img below,
   the other carousel where the same click-drag habit is likely. */
.t3d-btn img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 20%;
  -webkit-user-drag: none; user-drag: none; -webkit-touch-callout: none; user-select: none; }
.t3d-card:hover .t3d-btn, .t3d-card:focus-within .t3d-btn { transform: translateY(-3px); }
/* Fallback for a specialist published before their photo is uploaded. */
.t3d-initials { display: grid; place-items: center; width: 100%; height: 100%;
  background: var(--brand-tint); color: var(--brand); font-weight: 800; font-size: 1.4rem; letter-spacing: 0.02em; }

.t3d-card-name { display: block; margin-top: 14px; font-weight: 800; font-size: 0.98rem;
  color: var(--brand); letter-spacing: -0.01em; }
.t3d-card-role { display: block; margin-top: 3px; font-size: 0.8rem; font-weight: 600;
  color: var(--ink-soft); }
.t3d-card:not(.is-active) .t3d-card-name,
.t3d-card:not(.is-active) .t3d-card-role { color: color-mix(in oklch, var(--brand) 55%, var(--ink-faint)); }

/* Position only -- visual treatment (size, fill, border, hover/active, focus ring) comes from
   the shared .carousel-arrow rule above, which every homepage carousel arrow now uses. z-index
   raised well past any card's own stacking: team.js sets each .t3d-card's z-index to
   `cards.length - distance` (up to 14 today, grows with the roster), and at narrow viewports
   the side card's peek reaches all the way to the stage's own edge -- the same spot the arrow
   sits in the padding-inline gutter. With the arrow at .carousel-arrow's base z-index:5 a
   peeking card (any distance under ~9) rendered on top of it, hiding the button entirely and
   making it unclickable on a 390px screen. 40 is comfortably above any realistic roster size.

   left/right FIXED 2026-08-27: this used to be `max(6px, 1.4vw)`, measured against .t3d-inner's
   own padding-box outer edge -- same "wrong edge" containing-block gotcha .partners-nav had
   (see its own rule further down for the full explanation), just never caught here because
   .t3d-inner's edge lines up with the page shell rather than the viewport, so the bug read as
   "arrows sit close to the section edge" rather than "arrows glued to the viewport edge". Since
   .t3d-inner uses the exact same max-width:var(--shell); margin-inline:auto recipe as
   .partners-marquee, its outer edge lands at the identical on-screen x-position at any
   viewport width -- so using .partners-nav's own inset expression here, verbatim, puts Team's
   arrows at the same distance from the page edge as Our Clients/Partners', instead of just
   coincidentally close. */
.t3d-nav { z-index: 40; }
.t3d-nav.prev { left: clamp(18px, 4vw, 44px); }
.t3d-nav.next { right: clamp(18px, 4vw, 44px); }

/* 2026-09-01 judgment call: the live sesc.net reference's Team arrows are bare chevrons with
   no circular button behind them (Elementor's default swiper-button, checked via computed
   style: transparent background, 0 border, no border-radius). Team here deliberately keeps
   the shared 52px .carousel-arrow circle instead of matching that literally. Reasoning: the
   circle is the thing an earlier session unified across Team/Partners/Our Clients/Reviews
   specifically because the client complained Team's own controls read as "a different, older
   design language" than its neighbors (see .team3d-head's earlier 2026-08-26 comment) --
   reverting Team's arrows to a bare chevron would reopen exactly that complaint, just for
   arrows instead of the band, on a page where the other three carousels sit right below it in
   the same white-circle style. The reference site's chevron-only look is very likely an
   incidental default of its old Elementor/Bookly stack, not a considered choice worth
   reproducing. The 52px circle is also a materially larger, easier touch target than a bare
   20px glyph. Box color and autoplay timing/direction were matched exactly per the client's
   instruction; the arrow chrome was judged not to be part of what "match the box styling and
   movement" was asking for. (Team's box itself was later reverted to plain white per a
   follow-up client checklist -- see .team3d-head/.t3d-stage -- which doesn't change this
   reasoning; the white .carousel-arrow circle already read cleanly there before this whole
   pass started.) NOTE: the same follow-up checklist also asks for every carousel arrow
   sitewide (Partners/Our Clients/Reviews) to eventually match whatever Team's arrows end up
   being -- tracked as separate follow-up work, not done in this pass. Since Team already used
   the shared .carousel-arrow class before this pass and still does now, no cross-carousel
   change is actually needed for that ask: Team's final arrow style IS .carousel-arrow,
   unchanged -- 52px circle, 1.4px var(--line) border, var(--bg) (white) fill, var(--brand)
   chevron icon, solid var(--brand) fill + white icon on hover, 40px/16px icon at <=640px. */

.t3d-dots { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px;
  margin-top: clamp(22px, 3vw, 30px); }
.t3d-dot { width: 9px; height: 9px; padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  background: color-mix(in oklch, var(--brand) 22%, white); transition: all 240ms var(--ease); }
.t3d-dot[aria-selected="true"] { background: var(--brand); width: 26px; border-radius: 99px; }

/* Reduced motion: no sliding transform, just an instant swap. The stage stays a fixed-height
   window (unlike the old coverflow's scroll-rail fallback) since there is no perspective left
   to remove and every visible card is already flat. */
@media (prefers-reduced-motion: reduce) {
  .t3d-card { transition: none; }
  .hero-slide { transition: none; }
  .hero-slide.is-active .hero-media img,
  .hero-slide.is-active .hero-eyebrow, .hero-slide.is-active .hero-h,
  .hero-slide.is-active .hero-h-ar, .hero-slide.is-active .hero-sub,
  .hero-slide.is-active .hero-sub-ar, .hero-slide.is-active .hero-phone,
  .hero-slide.is-active .hero-actions { animation: none; }
  .hero-progress { display: none; }
}

/* .t3d-nav's own width/height/icon-size at this width now come from .carousel-arrow's own
   640px breakpoint (see above) -- this one only still needs to widen .t3d-inner's own
   padding-inline (moved off .t3d-stage when that became full-bleed, see above) so the (now
   smaller) arrow still has clear room in the gutter. */
@media (max-width: 560px) {
  .t3d-inner { padding-inline: clamp(48px, 14vw, 70px); }

  /* 2026-09-14: below this width .t3d-card's width clamp is pinned at a fixed 170px (the
     15vw preferred value only overtakes that floor above ~1133px viewport), so the
     neighbor cards team.js fans out either side no longer fit their name/role text in the
     leftover gutter -- it was rendering cut off mid-word right at the screen edge, which
     read as broken rather than a carousel peek. team.js's own compact-step fallback (see
     its render()) now sits the neighbor photo snugly beside the active one instead of
     overshooting the viewport, but the text is hidden here regardless of how much of the
     card is visible: a fragment of a name is never useful, only the active card's full
     name/role should ever be legible. */
  .t3d-card:not(.is-active) .t3d-card-name,
  .t3d-card:not(.is-active) .t3d-card-role { display: none; }
}

/* ============================================================================================
   ABOUT (home) -- the client's own "About us" prose, set against a real SESC photograph
   rather than the stock paper-planes image their current site uses.
   ============================================================================================ */
.about-home { padding-block: clamp(56px, 7vw, 92px); }
.about-home .shell { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(30px, 5vw, 72px);
  align-items: center; }
.about-home h2 { font-size: clamp(1.8rem, 3vw, 2.6rem); }
.about-home p { margin-top: 20px; color: var(--ink-soft); line-height: 1.8; max-width: 62ch; }
/* Client's reference has a "Read More" pill under the About copy -- this build
   had the copy but no link through to the real About page. .btn-outline-brand
   (navy border/text on a light ground) already exists for exactly this --
   .btn-outline itself assumes the dark hero it was built for. */
.about-more { margin-top: 26px; }
.about-more svg { width: 15px; height: 15px; }
.about-figure { position: relative; border-radius: 20px; overflow: hidden; aspect-ratio: 4 / 3;
  box-shadow: 0 40px 70px -50px rgba(25,79,144,0.75); }
.about-figure img { width: 100%; height: 100%; object-fit: cover; }
.about-figure::after { content: ""; position: absolute; inset: 0;
  box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--brand) 18%, transparent); border-radius: 20px; }
@media (max-width: 860px) { .about-home .shell { grid-template-columns: 1fr; } }

/* Second "about" block ("How We Deliver Results") -- see home.blade.php's own comment.
   Cancels this section's own top padding so it sits close under the first about block
   instead of two independent `section`s' padding-block adding together into a big gap
   between what's meant to read as one two-part pair (the same doubled-padding effect
   .services-cats above works around, just solved with a modifier here instead of a new
   class, since this section otherwise wants the normal .about-home bottom spacing). */
.about-home--second { padding-top: 0; }
/* No client photo supplied yet for this block -- see HomeController's $about2 -- so it
   renders text-only until one is uploaded via Site Settings. Without this the text column
   would sit alone in the grid's left 1.05fr track, alone against a large empty right column;
   full-width and centered reads as an intentional text block instead. */
.about-home--no-figure .shell { grid-template-columns: 1fr; max-width: 68ch; margin-inline: auto; }
.about-home--no-figure p { max-width: none; }
/* 2026-09-01: the "How We Deliver Results" block's own photo, added per the client's PDF
   (see LandingGapsSeeder), sits photo-left/text-right there -- the opposite of every other
   .about-home block's default DOM order. `order` works on grid items the same way it does on
   flex items for auto-placed grid content (no named areas here), so this flips the figure to
   the first visual column without touching markup order or the text/figure column ratio. */
.about-home--flip .shell > figure { order: -1; }

/* ============================================================================================
   TEAM PAGE grid -- same data, same modal as the home coverflow. One row in the admin feeds
   both surfaces; they must never disagree.
   ============================================================================================ */
/* UPDATED 2026-09-01: was a fixed 4-column grid (repeat(4, 1fr), stepping to 3 then 2 by
   media query), which had two problems the client caught comparing against the real
   reference at sesc.net/team/: a department with 5 members (Physical Therapy) got its 5th
   card forced onto a lonely new row even though the viewport had plenty of spare width,
   AND a grid's 1fr columns stretch to fill the row regardless of how many cards are in it --
   a 2-member department's cards rendered visibly bigger than a 4-member department's, not
   "all the same size" like the reference. flex-wrap with a fixed (not stretchy) card width
   fixes both: cards never grow past their own basis no matter how few share a row, and a
   row holds as many as actually fit the available width -- 5, 4, 3 or 2, matching the
   reference's own department-sized rows instead of an artificial fixed column count. */
.team-people { display: flex; flex-wrap: wrap; gap: 22px; }
/* 220px, measured not guessed: --shell is 1280px but .shell's own padding-inline leaves
   .team-people only 1192px to actually work with (confirmed via getBoundingClientRect()
   live, not assumed from the --shell token alone) -- (1192 - 4 gaps at 22px) / 5 = 220.8px,
   the width that lets Physical Therapy's 5 members fit one row, matching sesc.net/team/'s
   reference. Two earlier values here (300px, then 238px) both still overflowed because they
   were computed against --shell's raw 1280px rather than the container's real measured
   width. */
.person { flex: 0 0 clamp(150px, 16.5vw, 220px); padding: 0; border: 1px solid var(--line); background: #0b1c33; border-radius: 18px; overflow: hidden;
  cursor: pointer; text-align: left; position: relative; display: block;
  transition: transform 260ms var(--ease), box-shadow 260ms var(--ease); }
.person:hover { transform: translateY(-5px); box-shadow: 0 30px 50px -28px rgba(11,28,51,0.7); }
/* height:auto is load-bearing. The <img> carries width/height attributes, which map to
   presentational hints; width:100% beats the width hint but the height hint survives and
   defeats aspect-ratio, rendering the card 747px tall instead of 389px. */
/* border-radius here too, matching .person's own clip -- without it, the parent's rounded
   overflow:hidden and the img's hard rectangular edge anti-alias independently, leaking a thin
   sliver of .person's navy background through at the top two corners specifically (most visible
   there since the image's own corner pixels are near-white, same as the page behind it). */
/* 768 / 1001, not 3/4 (0.75) -- this is the client's own WordPress-generated grid photo size
   (Rafat-Amayrah-768x1001.jpg and the same file family for every person, confirmed by inspecting
   several downloaded originals), a real 0.767 aspect ratio. 3/4 was 2% off, close enough that the
   card still looked plausible but cover was cropping a visible sliver off every photo that didn't
   need to be cropped at all -- these photos are already the right shape, matching this ratio
   exactly lets cover do effectively nothing rather than fight the source crop. */
.person img { width: 100%; height: auto; aspect-ratio: 768 / 1001; object-fit: cover;
  object-position: 50% 12%; border-radius: 18px; display: block; background: #fff; }
/* These are <span>s inside a <button>, so they default to inline and the name/role run together
   and spill outside the card. They must be blocks. */
.person-body { display: block; padding: 16px 18px 20px; background: var(--brand); color: #fff; }
.person-name { display: block; font-weight: 800; letter-spacing: -0.01em; }
.person-role { display: block; margin-top: 3px; font-size: 0.82rem; font-weight: 600;
  color: color-mix(in oklch, white 76%, transparent); }
.person::after { content: ""; position: absolute; inset: 0; border-radius: 18px;
  box-shadow: inset 0 0 0 2px transparent; transition: box-shadow 240ms var(--ease); }
.person:hover::after, .person:focus-visible::after { box-shadow: inset 0 0 0 2px var(--green); }
.team-dept-head { margin: clamp(36px, 4vw, 56px) 0 20px; display: flex; align-items: center; gap: 16px; }
.team-dept-head h2 { font-size: clamp(1.2rem, 2vw, 1.6rem); white-space: nowrap; }
.team-dept-head::after { content: ""; flex: 1; height: 1px; background: var(--line); }

/* ============================================================================================
   HOME: profile modal (native <dialog>)
   ============================================================================================ */
.tm { width: min(980px, 94vw); max-height: 90dvh; padding: 0; border: 0; border-radius: 22px;
  background: var(--bg); color: var(--ink); overflow: hidden; }
.tm::backdrop { background: color-mix(in oklch, var(--brand-deeper) 72%, transparent);
  backdrop-filter: blur(5px); }
.tm[open] { animation: tmIn 320ms var(--ease); }
.tm[open]::backdrop { animation: tmFade 320ms var(--ease); }
@keyframes tmIn { from { opacity: 0; transform: translateY(14px) scale(0.97); } to { opacity: 1; transform: none; } }
@keyframes tmFade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .tm[open], .tm[open]::backdrop { animation: none; } }

/* The portrait must keep its native 3:4. Letting the column stretch to the modal's full height
   made object-fit:cover scale a 560x747 photo up 1.08x and crop ~307px off the sides, beheading
   the shoulders. Anchor it to the top at its own aspect ratio and paint the column behind it. */
.tm-grid { display: grid; grid-template-columns: 300px 1fr; max-height: 90dvh;
  background: linear-gradient(to right, #0b1c33 0 300px, transparent 300px); }
.tm-portrait { position: relative; align-self: start; background: #0b1c33; }
/* 768 / 1001 -- same real-photo ratio as .person img above (see that rule's own comment).
   Applies here to both the home-carousel modal (photo_path) and the /team/{slug} page
   (profile_photo_path when set, same ~0.767 ratio family either way) since both reuse this
   class. display:block clears the few px of inline-image baseline gap that let #0b1c33 show
   as a sliver under the photo -- the actual cause of the "black around it" report, not a
   crop/ratio mismatch (cover never leaves a gap on its own). */
.tm-portrait img { width: 100%; height: auto; aspect-ratio: 768 / 1001; object-fit: cover;
  object-position: 50% 8%; display: block; background: #fff; }
.tm-portrait .tm-dept { position: absolute; left: 16px; bottom: 16px; font-size: 0.68rem;
  font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: #fff;
  background: color-mix(in oklch, var(--brand) 82%, transparent); padding: 7px 11px; border-radius: 99px; }

.tm-body { padding: clamp(24px, 3vw, 40px); overflow-y: auto; }
.tm-close { position: absolute; top: 14px; right: 14px; z-index: 3; width: 40px; height: 40px;
  border-radius: 50%; display: grid; place-items: center; cursor: pointer; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line); transition: transform 180ms var(--ease); }
.tm-close:hover { transform: rotate(90deg); }
.tm-close svg { width: 18px; height: 18px; }
.tm-name { font-size: clamp(1.5rem, 2.4vw, 2rem); font-weight: 800; letter-spacing: -0.02em; }
.tm-role { margin-top: 5px; color: var(--brand); font-weight: 700; }
.tm-bio { margin-top: 18px; color: var(--ink-soft); line-height: 1.72; max-width: 68ch; }
.tm-h { margin-top: 26px; margin-bottom: 12px; font-size: 0.74rem; font-weight: 800;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-faint); }
.tm-list { display: grid; gap: 9px; }
.tm-list li { display: flex; gap: 10px; color: var(--ink-soft); line-height: 1.55; }
.tm-list svg { width: 15px; height: 15px; margin-top: 5px; flex: 0 0 auto; color: var(--green); }
.tm-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.tm-chip { font-size: 0.82rem; font-weight: 600; padding: 7px 13px; border-radius: 99px;
  background: var(--brand-tint); color: var(--brand); }
.tm-cta { margin-top: 30px; display: flex; gap: 12px; flex-wrap: wrap; }
.tm-cta .btn svg { width: 16px; height: 16px; flex: none; color: var(--wa-green); }
/* The shell's .btn-outline is tuned for the dark hero (white border on navy). The modal sits on
   a light surface and needs a brand-coloured outline instead. */
.btn-outline-brand { background: transparent; color: var(--brand); border: 1.6px solid var(--brand); }
.btn-outline-brand:hover { background: var(--brand); color: #fff; }

@media (max-width: 760px) {
  .tm-grid { grid-template-columns: 1fr; background: none; }
  .tm-portrait { align-self: stretch; height: 260px; }
  .tm-portrait img { height: 100%; aspect-ratio: auto; object-position: 50% 18%; }
}

/* ---- Brief profile (home). Mirrors the client's own popup: circular portrait ringed in green,
   name, role, then labelled Job Title / Education fields and a short brief. The full history
   lives on the team page; this is the teaser, exactly as their site does it. ---- */
.tm[data-mode="brief"] { width: min(760px, 94vw); }
.tm-brief { padding: clamp(28px, 3.4vw, 44px); text-align: center; position: relative; }
.tm-brief .tm-close { top: 16px; right: 16px; }
.tm-avatar { width: 168px; height: 168px; margin: 4px auto 20px; border-radius: 50%;
  overflow: hidden; background: #0b1c33; box-shadow: 0 0 0 3px var(--green), 0 20px 40px -26px rgba(11,28,51,0.8); }
.tm-avatar img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 16%; }
.tm-brief .tm-name { font-size: clamp(1.4rem, 2.2vw, 1.85rem); }
.tm-brief .tm-role { margin-top: 4px; }
.tm-fields { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px 28px;
  margin: 28px 0 8px; text-align: left; }
.tm-field { background: var(--brand-tint); border-radius: 14px; padding: 16px 18px; }
.tm-field dt { font-size: 0.7rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-faint); }
.tm-field dd { margin: 6px 0 0; color: var(--ink); font-weight: 600; line-height: 1.5; }
.tm-briefhead { margin-top: 26px; text-align: left; font-size: 0.7rem; font-weight: 800;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-faint); }
.tm-brieftext { margin-top: 10px; text-align: left; color: var(--ink-soft); line-height: 1.75; }
.tm-brief .tm-cta { justify-content: flex-start; }
@media (max-width: 620px) {
  .tm-avatar { width: 132px; height: 132px; }
  .tm-fields { grid-template-columns: 1fr; }
  .tm-brief .tm-cta { flex-direction: column; align-items: stretch; }
  .tm-brief .tm-cta .btn { justify-content: center; }
}

/* ============================================================================================
   HOME: partners
   ============================================================================================ */
.partners { padding-block: clamp(52px, 6vw, 78px); overflow: hidden; }
.partners-head { text-align: center; margin-bottom: 40px; }
.partners-head h2 { font-size: clamp(1.5rem, 2.4vw, 2rem); }
.partners-head p { margin-top: 10px; color: var(--ink-faint); }
/* Contained, not full-bleed (client feedback: the marquee ran edge-to-edge on
   the viewport, same complaint as the Team stage, pinning .partners-nav out
   at the browser's outer edges instead of close to the logo row). Same
   max-width/centering as .t3d-stage and .shell elsewhere on the page, so the
   whole module reads as one contained card.

   .partners-marquee is now a plain positioning frame (no background/border of
   its own -- there is no visible "box" here, the client only ever asked for a
   consistent OUTSIDE-the-content arrow position, not a new panel around the
   logos) holding the arrows and .partners-row side by side. .partners-row is
   the part that actually clips/fades the scrolling track, and it gets LESS
   than the frame's full 1280px -- margin-inline reserves real room on both
   sides for the arrows, so they sit outside the row's own bounding box
   instead of overlapping its edge logos (the previous bug: arrows were inset
   a few px INSIDE the same box the track scrolled in, so a client screenshot
   caught them overlapping a visible logo no matter how wide the soft-edge
   mask was pushed). The horizontal-scroll-as-motion read is unaffected --
   .partners-track is still animated well past this row's own width and
   clipped by `overflow: hidden` here.

   FIXED 2026-08-26: this rule was missing the padding-inline .shell carries -- max-width +
   margin-inline:auto only centers a box THAT IS ACTUALLY NARROWER than its parent. .partners
   (the <section>) has no side padding of its own, so below ~1280px viewport width (the
   default screen scenario, not the wide-screen exception) this frame simply fills the section
   at 100% width with a zero auto-margin, and .partners-nav.prev/.next (left:0/right:0 against
   it) landed flush on the true section/viewport edge -- exactly the "glued to the edge" bug a
   client screenshot and a live getBoundingClientRect check both caught (prev.left was
   literally 0). The centering-margin math only ever worked ABOVE the 1280px breakpoint, which
   is why an earlier pass's own check (taken at a viewport that happened to sit just over/near
   that line) didn't catch it. padding-inline here is IDENTICAL to .shell's own
   (clamp(18px, 4vw, 44px)) so this frame guarantees the same minimum outer gutter every other
   contained section on the page has, at any width -- not just when 1280px happens to bind. */
.partners-marquee { position: relative; max-width: var(--shell); margin-inline: auto;
  padding-inline: clamp(18px, 4vw, 44px); }
.partners-row { overflow: hidden; margin-inline: clamp(60px, 7vw, 80px);
  mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent); }
.partners-track { display: flex; align-items: center; width: max-content;
  gap: clamp(28px, 3.5vw, 56px); animation: partnersScroll 34s linear infinite; }
.partners-marquee:hover .partners-track { animation-play-state: paused; }
/* Once an arrow is clicked, public/js/partners.js takes over positioning via inline
   `transform` and adds this class to kill the keyframe for good -- otherwise the two
   would fight every frame and the manual step would be invisible. */
.partners-track.is-manual { animation: none; transition: transform 420ms var(--ease); }
.partner { display: grid; place-items: center; flex: none; padding: 10px 6px; }
/* 62px -> 92px: client feedback the logos read too small/timid for a strip meant to carry
   real brand recognition. .partners-row's own margin-inline above (reserving arrow room out
   of the frame's 1280px, rather than the old edge-to-edge width) is sized with this bigger
   card in mind -- five logos of a typical badge/wordmark mix comfortably fill the row without
   crowding or clipping a sixth. Still `width: auto`, so every logo keeps its own real aspect
   ratio (see the width/height getimagesize() computes server-side in logo-marquee.blade.php)
   -- only the height changed.

   max-width: 150px added 2026-08-26. Every real client logo and 9 of 15 partner logos are a
   consistent 500x338 (ratio 1.48, ~136px wide at 92px tall) -- Our Clients hits exactly 5
   full cards in its 1032px row on that ratio alone. But 6 partner logos (site/partners/1.png,
   2.png, 3.png etc. -- confirmed via getimagesize() against the live DB, not assumed) are old
   260x110 placeholder-era uploads, ratio 2.36, which at the same 92px height render ~217px
   wide -- when several land consecutively in the loop (they do; low ids, so they sort first)
   five of them alone need over 1000px before any gap, visibly starving the row down to 3-4
   full cards, the exact live-count regression a getBoundingClientRect check caught. Rather
   than shrinking every logo on the page to defend against 6 outlier files, 150px caps ONLY
   the ones wide enough to hit it (2.36-ratio logos become ~64px tall instead of 92 -- still
   bigger than the original 62px baseline) while the other 23 real logos (136px wide, well
   under the cap) are completely unaffected and keep their full 92px prominence.

   The 150px figure accounts for .partner's own 6px horizontal padding on each side (a live
   getBoundingClientRect check on the FIRST attempt at this cap -- 160px -- showed rendered
   card width of 172px, not 160, because that 12px of card padding wasn't in the sum; 5 cards
   at 172px plus 4 gaps at 50.4px overshot the 1032px row by ~30px and only 4 fit. Solved for
   the real per-card footprint (image + 12px padding) against the row's actual measured width
   and this track's actual measured gap, not estimated ones. */
/* 92px -> 128px / 150px -> 208px, same 1.63 ratio between the two caps preserved so the
   6.2.36-ratio outlier logos documented above stay proportionally capped rather than
   suddenly dominating the row -- client feedback (2026-08-31) that the strip still read
   small/cramped next to Team's ~105px ring photos even after the earlier 62px->92px pass.
   Still `width: auto` / no explicit height, so every logo keeps its real aspect ratio --
   bigger, not stretched.

   128px -> 160px / 208px -> 260px (2026-09-01): client says still not big enough even after
   the pass above. Same 1.625 ratio kept between the two caps for the same reason (the 6
   2.36-ratio outlier files stay proportionally capped, not suddenly dominant). .partners-row
   is a continuously scrolling marquee (width:max-content + keyframe animation), not a
   fixed grid that has to fit an exact card count per row, so there's no fit-N-cards ceiling
   here the way Our Clients' old static layout had -- free to size for visual weight against
   Team's ~105px ring photos rather than a row-width budget. Checked live afterward against
   both the ring photos and the surrounding section padding before settling here. */
.partner img { max-height: 160px; max-width: 260px; width: auto; transition: transform 220ms var(--ease), filter 220ms var(--ease);
  -webkit-user-drag: none; user-drag: none; -webkit-touch-callout: none; user-select: none; }
.partner:hover img, .partner:focus-within img { transform: translateY(-5px);
  filter: drop-shadow(0 14px 18px color-mix(in oklch, var(--brand) 35%, transparent)); }
@keyframes partnersScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .partners-track { animation: none; } }

/* Position only -- visual treatment comes from the shared .carousel-arrow rule.

   FIXED 2026-08-26: `left: 0`/`right: 0` here was measured against the WRONG edge. Per the
   CSS spec, an absolutely positioned element's containing block is its relative ancestor's
   PADDING BOX -- which starts at the OUTER edge of that padding (i.e. `.partners-marquee`'s
   own border edge), not the inner edge where the padding ends and real content starts. Adding
   padding-inline to `.partners-marquee` (see that rule above) correctly pushed `.partners-row`
   inward, because a normal-flow child DOES respect its parent's padding -- but it did nothing
   for these arrows, since `left: 0` still meant "the padding box's own edge", i.e. still flush
   with the marquee's true outer edge, padding or no padding. A live getBoundingClientRect
   check after the first attempted fix caught this directly: .partners-row moved in, the arrow
   did not. Matching the padding value here explicitly (left/right: clamp(18px, 4vw, 44px), the
   exact same expression `.partners-marquee`'s own padding-inline uses) puts the arrow at the
   padding box's INNER edge instead -- i.e. exactly where a normal-flow child would land -- so
   it now sits at the same outer gutter every other contained section on the page keeps,
   regardless of viewport width relative to the 1280px shell breakpoint. */
.partners-nav.prev { left: clamp(18px, 4vw, 44px); }
.partners-nav.next { right: clamp(18px, 4vw, 44px); }
/* Same mobile convention as Reviews' .rev-row below: shrink the reserved gutter (not just the
   arrow, which .carousel-arrow's own breakpoint already handles) so a 40px arrow still sits
   fully outside the row instead of the row keeping its desktop-width reservation on a 390px
   screen. */
@media (max-width: 640px) { .partners-row { margin-inline: clamp(32px, 11vw, 52px); } }

/* ============================================================================================
   HOME: reviews -- rebuilt to match the client's original speech-bubble card (grey bubble with a
   tail, navy avatar disc ringed in green, name, green stars). Theirs ships as five flat PNGs;
   this is the same design as real, selectable, translatable text. Sampled from their artwork:
   bubble #e7e8e9, navy #1f4285, stars #1db263 (brighter than the brand green, so keep it).
   ============================================================================================ */
.reviews { background: var(--surface); padding-block: clamp(56px, 7vw, 88px); overflow: hidden; }
/* Centered, matching .team3d-head/.partners-head -- the arrows used to live in here
   (justify-content: space-between, heading left / arrows right), the one homepage carousel
   whose prev/next controls didn't flank its own content at all. They now sit outside .rev-rail
   itself (see .rev-frame below), so this goes back to being a plain centered heading like the
   other three sections'. */
.reviews-head { text-align: center; margin-bottom: 34px; }
.reviews-head h2 { font-size: clamp(1.6rem, 2.6vw, 2.2rem); }
.reviews-head p { margin-top: 10px; color: var(--ink-faint); }

/* Positioning context only (no visible box of its own) so .rev-arrow can sit OUTSIDE
   .rev-rail's bounding box the same way .t3d-nav/.partners-nav now flank Team/Our
   Clients/Partners -- visual treatment comes from the shared .carousel-arrow rule. */
.rev-frame { position: relative; }
.rev-arrow.prev { left: 0; }
.rev-arrow.next { right: 0; }

/* A scroll-snap rail, not a transform track: native touch, native keyboard, no index math to
   desync. public/js/reviews.js drives the arrows with plain rail.scrollBy() -- see its own
   docblock. margin-inline reserves room for .rev-arrow outside the rail, same convention as
   Our Clients/Partners' .partners-row (same clamp values, so the reserved gutter and the
   arrow's own outside-the-content position read identically across every homepage carousel,
   mobile included). */
.rev-rail { display: grid; grid-auto-flow: column; grid-auto-columns: calc((100% - 44px) / 3);
  gap: 22px; overflow-x: auto; scroll-snap-type: x mandatory; padding: 6px 2px 34px;
  margin-inline: clamp(60px, 7vw, 80px);
  scrollbar-width: none; overflow-anchor: none; }
.rev-rail::-webkit-scrollbar { display: none; }
@media (max-width: 1000px) { .rev-rail { grid-auto-columns: calc((100% - 22px) / 2); } }
@media (max-width: 640px) { .rev-rail { grid-auto-columns: 86%; margin-inline: clamp(32px, 11vw, 52px); } }

/* FIXED 2026-08-26: .review is a <figure> element, and the browser's own UA stylesheet gives
   every <figure> a default `margin: 1em 40px` (Chrome/Firefox/Safari all ship this). Only
   margin-bottom was ever set below, so margin-left/right silently stayed at that UA default
   40px each -- 80px total eaten from every card's width on top of its grid track. At desktop
   that's a few dozen px lost out of a ~380-420px column, easy to miss by eye; at mobile's much
   narrower ~215px column (grid-auto-columns: 86% of a small rail) it consumed more than a
   third of the card, leaving a genuinely cramped ~134px-wide box that then had to wrap its
   review text into many extra lines -- a live check found the resulting card rendering over
   600px tall on a 390px screen, the "giant broken block" a client screenshot flagged. margin
   is reset to 0 on every side except the intentional 22px bottom gap between stacked cards
   (same fix .review-body already applies to <blockquote>'s own UA margin, two rules below --
   this is the same class of bug on the sibling element that inherited it). */
.review { scroll-snap-align: start; position: relative; background: #e7e8e9; border-radius: 14px;
  padding: 26px 26px 30px; display: flex; flex-direction: column; gap: 16px; margin: 0 0 22px;
  transition: transform 260ms var(--ease); }
.review:hover { transform: translateY(-3px); }
/* The speech tail, bottom-left, exactly as in their artwork. */
.review::after { content: ""; position: absolute; left: 34px; bottom: -17px; width: 44px; height: 18px;
  background: #e7e8e9; clip-path: polygon(0 0, 100% 0, 22% 100%); }

.review-head { display: flex; align-items: center; gap: 16px; }
.review-avatar { flex: 0 0 auto; width: 62px; height: 62px; border-radius: 50%; display: grid;
  place-items: center; background: #1f4285; color: #fff; box-shadow: 0 0 0 2px #1db263; }
.review-avatar svg { width: 34px; height: 34px; }
.review-meta { min-width: 0; }
.review-name { font-weight: 800; font-size: 1.06rem; color: #1f4285; letter-spacing: -0.01em; }
.review-stars { display: flex; gap: 3px; margin-top: 5px; color: #1db263; }
.review-stars svg { width: 19px; height: 19px; }

/* <blockquote> carries a UA default of `margin: 1em 40px`, which strangles the quote to about
   half the card width. Reset it. */
.review-body { display: grid; gap: 8px; margin: 0; }
.review-ar { font-size: 1.04rem; line-height: 1.9; color: #1f4285; font-weight: 600; }
.review-en { color: #1f4285; line-height: 1.62; }
.review-en.is-translation { font-size: 0.92rem; color: color-mix(in oklch, #1f4285 74%, white); }

.rev-dots { display: flex; justify-content: center; gap: 8px; margin-top: 6px; }
.rev-dot { width: 9px; height: 9px; padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  background: color-mix(in oklch, var(--brand) 22%, white); transition: all 240ms var(--ease); }
.rev-dot[aria-selected="true"] { background: var(--brand); width: 26px; border-radius: 99px; }

@media (prefers-reduced-motion: reduce) {
  .review:hover { transform: none; }
}

/* ============================================================================================
   SERVICE PAGE: click-to-play video ("watch" module)
   ============================================================================================ */
.watch-head { text-align: center; margin-bottom: clamp(20px, 3vw, 32px); }
.watch-head h2 { margin: 0; }
.watch-head p { margin-top: 10px; color: var(--ink-soft); font-size: 0.95rem; }

.watch-card { max-width: 860px; margin-inline: auto; }
.watch-media { position: relative; aspect-ratio: 16 / 9; border-radius: 18px; overflow: hidden;
  background: #0b1c33; box-shadow: 0 30px 60px -34px rgba(25,79,144,0.55); isolation: isolate; }

/* Scoped to .watch-media rather than bare .watch-video/.watch-poster: on the
   centre pages the wrapper carries BOTH .media (so `.split.flip .media { order: 2 }`
   can push it to the right column, same as a plain photo) and .watch-media (for this
   16:9 box). `.media img { aspect-ratio: 16/10; height: auto }` further down this file
   is one class + one element (specificity 0,1,1) and was beating the old bare
   `.watch-poster` (0,1,0) -- every video on the /center pages was rendering in a 16:10
   box, cropping ~34px off the bottom of a 558px-wide player, never noticed because
   object-fit:cover crops cleanly with no visible distortion. Qualifying with
   .watch-media (0,2,0) wins outright without an !important and without touching the
   .media img rule, which still needs to do its job for a plain photo. */
.watch-media .watch-video, .watch-media .watch-poster { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; }
.watch-video { background: #0b1c33; }
.watch-poster { z-index: 1; transition: opacity 260ms var(--ease), visibility 0s linear 260ms; }

/* Always-on vignette behind the button, so the affordance reads on bright posters too, not just
   on hover -- this IS the primary call to action on the card, not a hover-only reveal. */
.watch-media::before { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(46% 46% at 50% 50%, rgba(9,20,36,0.34), transparent 72%); }

.watch-play { position: absolute; z-index: 2; inset: 0; margin: auto;
  width: clamp(72px, 10vw, 96px); height: clamp(72px, 10vw, 96px); border-radius: 50%;
  display: grid; place-items: center; cursor: pointer; color: #fff;
  background: color-mix(in oklch, var(--brand-deeper) 82%, transparent);
  border: 3px solid #fff;
  box-shadow: 0 20px 44px -16px rgba(0,0,0,0.6), 0 0 0 8px color-mix(in oklch, white 14%, transparent);
  transition: transform 220ms var(--ease), background 220ms var(--ease), box-shadow 220ms var(--ease); }
.watch-play svg { width: 34%; height: 34%; margin-left: 6%; }
.watch-media:hover .watch-play, .watch-play:hover, .watch-play:focus-visible {
  transform: scale(1.08); background: var(--green);
  box-shadow: 0 26px 54px -16px rgba(0,0,0,0.64), 0 0 0 10px color-mix(in oklch, var(--green) 26%, transparent); }
.watch-play:active { transform: scale(0.96); }

/* Playing state: poster + vignette + button all fade out together, revealing the <video> (which
   now carries native `controls`, added by JS) underneath. */
.watch-media.is-playing .watch-poster,
.watch-media.is-playing::before,
.watch-media.is-playing .watch-play { opacity: 0; visibility: hidden; pointer-events: none;
  transition-delay: 0s, 0s; }

@media (prefers-reduced-motion: reduce) {
  .watch-media:hover .watch-play, .watch-play:hover, .watch-play:focus-visible { transform: none; }
}

/* 2026-07-23 -- "Education" stays the single top-level nav item (as
   originally built), with University/Courses nested under it as plain,
   non-interactive "Coming Soon" labels -- no links out to the real
   store (the whole point of this demo is to never lead a client
   there). Reuses the existing .has-mega/.mega hover mechanism and
   .acc-trigger/.acc-panel toggle mechanism as-is for the "Education"
   parent itself; only the two coming-soon items' own styling is new. */
.mega-edu-simple { width: max-content; min-width: 220px; }
.edu-coming-soon-list { display: flex; flex-direction: column; gap: 2px; }
.nav-coming-soon-item {
  display: block;
  font-size: 0.79rem;
  color: var(--ink-faint);
  padding: 4px 6px;
  cursor: default;
}
.drawer-coming-soon-item {
  display: block;
  padding: 9px 8px;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--ink-faint);
  cursor: default;
}

/* ============================================================================================
   PHASE 19 -- CENTER SECTION (/center and the nine /center/{slug} pages).
   Appended block. Nothing above this line is modified.

   The centre pages are built almost entirely from the vocabulary already defined above
   (.titleband/.badge-ring, .split/.media/.prose, .checks, .watch-*, .longlist-head), so a
   centre page and a service page are the same site. Only what is below is new -- plus two
   additive fixes to shared rules, both found by rendering a real centre page in a browser
   and both explained where they sit.
   ============================================================================================ */

/* ---------- Fix: prose on a navy band ----------
   .tone-navy already re-colours h2, .check and .tick for the dark background, but never .prose:
   until now no navy section on this site carried a paragraph, only bullets. The centre pages'
   "Spirit" block is prose on navy, and it inherited --ink-soft (a dark grey) straight onto the
   navy gradient -- about 1.4:1, effectively invisible. 88% white matches what .tone-navy .check
   span already uses and clears 6.7:1 against the lightest end of the gradient.
   Additive: a new rule, nothing above is edited. */
.tone-navy .prose { color: color-mix(in oklch, white 88%, transparent); }

/* ---------- Fix: .media photos with width/height attributes ----------
   .media img sets width:100% and aspect-ratio:16/10 but never height, so an <img> carrying real
   width/height attributes gets height:900px from the UA's presentational hint and renders as a
   900px-tall crop instead of a 16:10 one. The service-page templates happen to omit those
   attributes; the centre templates set them so the browser can reserve the right box before the
   CSS lands. Declaring height:auto here makes the stylesheet authoritative either way, and
   changes nothing for any existing page (their height is already auto).
   Additive: a new rule, and the last .media img declaration in the file, so it wins. */
.media img { height: auto; }

/* ---------- Title band lede ----------
   One line under the H1, inside the navy band. Sized and coloured against the band, not the
   page: white at 78% gives 5.7:1 on --brand, where the .crumb's 68% would not clear 4.5:1 at
   body size. Capped at 52ch so it stays one or two lines beside the badge, and dropped on small
   phones where the band is already tight. */
.titleband-lede {
  margin-top: 8px;
  max-width: 52ch;
  font-size: 0.95rem;
  line-height: 1.55;
  color: color-mix(in oklch, white 78%, transparent);
}
@media (max-width: 560px) { .titleband-lede { display: none; } }

/* ---------- Check list spacing inside a centre block ----------
   The centre pages put a bullet list under prose in the same column, which the service pages
   never do (theirs are either prose OR bullets). Without this the ticks sit hard against the
   last paragraph. */
.prose + .ctr-checks { margin-top: 20px; }

/* ---------- Centre cards ----------
   Same box as .svc-card -- same border, radius, shadow and hover lift -- with the round badge
   promoted to 56px and a summary line added. Explicit column counts rather than auto-fit so a
   partial last row left-aligns instead of one card stretching full width. */
.ctr-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 900px) { .ctr-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .ctr-grid { grid-template-columns: 1fr; } }

.ctr-card {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 14px;
  padding: 16px 18px; background: #fff; border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 10px 24px -20px rgba(25,79,144,0.4);
  transition: transform 160ms var(--ease), box-shadow 200ms var(--ease), border-color 160ms ease;
}
.ctr-card:hover, .ctr-card:focus-visible {
  transform: translateY(-3px); border-color: var(--brand);
  box-shadow: 0 18px 34px -18px rgba(25,79,144,0.5);
}
.ctr-card:active { transform: translateY(-1px); }
@media (prefers-reduced-motion: reduce) { .ctr-card:hover, .ctr-card:focus-visible, .ctr-card:active { transform: none; } }

.ctr-card-badge {
  width: 56px; height: 56px; border-radius: 999px; flex: none; overflow: hidden;
  border: 2px solid var(--green); background: var(--brand-tint); display: grid; place-items: center;
}
.ctr-card-badge img { width: 100%; height: 100%; object-fit: cover; }
.ctr-card-badge .monogram { font-size: 0.95rem; border-radius: 999px; }

.ctr-card-text { min-width: 0; display: grid; gap: 3px; }
.ctr-card-title { font-weight: 700; font-size: 0.95rem; color: var(--ink); line-height: 1.3; }
/* --ink-soft on white is 7.0:1, so the summary stays readable rather than decorative.
   Clamped to two lines: nine cards in a grid must not have wildly different heights. */
.ctr-card-sub {
  font-size: 0.82rem; line-height: 1.45; color: var(--ink-soft);
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ctr-card-arrow { flex: none; color: var(--brand); transform: rotate(-90deg); }

/* ---------- A centre with no copy written yet ----------
   Not an error state and not styled like one: a plain lede paragraph, so a page that goes live
   before its content pass still reads as a real page with a CTA under it. */
.ctr-empty { max-width: 60ch; color: var(--ink-soft); font-size: 1.02rem; }

/* ---------- Nav: the one dropdown with nine children ----------
   "Center" carries all nine centres, which is more than twice what any other dropdown holds and
   would run ~420px down the viewport in the single 290px column .drop-rich defines in store.css.
   Selected by child count rather than a class because partials/site-nav.blade.php is shared
   markup owned by the nav unification pass, not by this section -- there is no class to hang it
   on and adding one would mean editing that file. Nothing else on the site has a dropdown that
   deep: the only other data-driven one is Store, with two shop categories.

   Specificity is deliberate: store.css loads AFTER site.css, so `.drop-rich` alone (0,1,0) would
   lose to it. `.site-header .drop.drop-rich` is (0,3,0) and wins regardless of order.
   Browsers without :has() simply keep today's single column, which is not broken, only tall. */
.site-header .drop.drop-rich:has(> :nth-child(7)) {
  width: min(560px, calc(100vw - 32px));
  display: grid; grid-template-columns: 1fr 1fr; gap: 0 4px;
}
@media (max-width: 1180px) {
  .site-header .drop.drop-rich:has(> :nth-child(7)) { width: min(460px, calc(100vw - 32px)); }
}

/* ============================================================================
   SERVICE PAGE SECTIONS -- labelled checklists, tall splits, modality cards

   Three related fixes to the shared section template, all driven by the same
   underlying problem: the imported department copy carries far more structure
   than the flat checklist markup was expressing.
   ========================================================================= */

/* --- Labelled checklist bullets -------------------------------------------
   A bullet written as "label + explaining sentence" gets two levels of weight
   instead of one uniform run of bold text. Nine or ten of these in a column
   were previously indistinguishable from each other at a glance. Bullets with
   no label keep the original single-span rendering untouched, which is all but
   53 of the 4,204 checklist bullets on the site.

   The .check-* selectors each carry two classes so they outrank the older
   `.check span` and `.tone-navy .check span` colour rules further up. */
.check-body { display: grid; gap: 2px; }

.check .check-title {
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.35;
  color: var(--brand);
}

.check .check-text {
  font-size: 0.88rem;
  font-weight: 400;
  line-height: 1.55;
  color: var(--ink-soft);
}

.tone-navy .check .check-title { color: #fff; }

/* white at 74 percent over the navy gradient measures about 5.1:1, so the
   explaining sentence still clears the 4.5:1 body-text floor at its lightest
   point on the gradient. */
.tone-navy .check .check-text { color: color-mix(in oklch, white 74%, transparent); }

/* Labelled rows are two lines tall, so they need real separation rather than
   the 3px gap that suits one-line bullets. */
.checks:has(.check--titled) { gap: 0; }
.check--titled { padding: 13px 4px; align-items: flex-start; }

/* --- Tall splits ----------------------------------------------------------
   When the copy column holds a labelled checklist it runs much taller than the
   photo beside it, which left most of a screen of empty background under the
   image. Top-align the pair and let the photo follow the list down instead.
   The offset clears the sticky site header. */
.split--tall { align-items: start; }

@media (min-width: 900px) {
  .split--tall .media {
    position: sticky;
    top: 96px;
    align-self: start;
  }
}

/* --- Modality cards -------------------------------------------------------
   Replaces a repeating four-section block per treatment modality. Each card is
   self-contained, so the three read as parallel choices rather than as one
   forced linear scroll through all of them. */
.modality-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
  margin-bottom: clamp(22px, 3vw, 32px);
}

.modality-jump a {
  display: inline-flex;
  align-items: center;
  padding: 8px 15px;
  border: 1.5px solid color-mix(in oklch, var(--brand) 24%, transparent);
  border-radius: 999px;
  background: #fff;
  color: var(--brand);
  font-size: 0.86rem;
  font-weight: 700;
  transition: background 160ms var(--ease), color 160ms var(--ease), border-color 160ms var(--ease);
}

.modality-jump a:hover,
.modality-jump a:focus-visible {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

.modality-list { display: grid; gap: clamp(20px, 3vw, 30px); }

.modality {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: clamp(18px, 2.6vw, 30px);
  box-shadow: 0 22px 44px -34px rgba(25, 79, 144, 0.55);
  /* the jump links land here, so keep the card clear of the sticky header */
  scroll-margin-top: 96px;
}

.modality-head {
  display: grid;
  grid-template-columns: minmax(0, 0.68fr) minmax(0, 1fr);
  gap: clamp(18px, 3vw, 34px);
  align-items: center;
}

.modality-media img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 14px;
  display: block;
}

.modality-intro h3 {
  font-size: clamp(1.15rem, 1.9vw, 1.45rem);
  font-weight: 700;
  color: var(--brand);
  margin-bottom: 10px;
}

.modality-intro p {
  color: var(--ink-soft);
  font-size: 0.95rem;
  line-height: 1.6;
  max-width: 52ch;
  margin-bottom: 16px;
}

.modality-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--brand);
  font-weight: 700;
  font-size: 0.88rem;
}

.modality-link svg { transform: rotate(-90deg); transition: transform 160ms var(--ease); }
.modality-link:hover svg, .modality-link:focus-visible svg { transform: rotate(-90deg) translateY(-3px); }

.modality-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: clamp(16px, 2.4vw, 30px);
  margin-top: clamp(20px, 2.6vw, 28px);
  padding-top: clamp(18px, 2.4vw, 26px);
  border-top: 1px solid var(--line);
}

.modality-group h4 {
  font-size: 0.83rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--green-deep);
  margin-bottom: 11px;
}

.modality-group ul { display: grid; gap: 7px; }

.modality-group li {
  position: relative;
  padding-left: 16px;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--ink);
}

.modality-group li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: var(--green);
}

@media (max-width: 760px) {
  .modality-head { grid-template-columns: 1fr; }
  .modality-media img { aspect-ratio: 16 / 9; }
}

/* ============================================================================================
   LEGAL PAGES -- Privacy Policy / Terms & Conditions (site.legal.privacy / site.legal.terms).

   A sticky left rail beside the prose, not a summary box sitting above it -- the standard shape
   for a long numbered legal document (13-15 clauses) because it keeps a reader oriented while
   scrolling instead of asking them to scroll back up to the top every time they want to jump
   sections. Reuses .titleband/.badge-ring/.page-intro/.empty-state as-is and the existing
   --brand/--brand-tint/--gold/--line tokens throughout -- no new palette, no new type family.
   ============================================================================================ */
.legal-section-wrap { padding-block: clamp(40px, 6vw, 64px) clamp(64px, 9vw, 108px); }
.legal-page {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: clamp(32px, 5vw, 80px);
  align-items: start;
}

/* ---------- Rail ---------- */
.legal-rail { position: sticky; top: 96px; display: grid; gap: 22px; }
.legal-rail-label { display: block; font-size: 0.72rem; font-weight: 800; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-faint); }
.legal-rail ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px;
  border-left: 1px solid var(--line); }
.legal-rail a {
  display: block; padding: 8px 0 8px 16px; margin-left: -1px; border-left: 1px solid transparent;
  font-size: 0.88rem; line-height: 1.4; color: var(--ink-soft);
  transition: color 180ms var(--ease), border-color 180ms var(--ease);
}
.legal-rail a:hover, .legal-rail a:focus-visible { color: var(--brand); border-left-color: var(--brand); }
/* Set by public/js/legal-toc.js as the reader scrolls past each clause. */
.legal-rail a.is-active { color: var(--brand); border-left-color: var(--brand); font-weight: 700; }
.legal-rail-cta {
  display: inline-flex; align-items: center; gap: 8px; font-size: 0.86rem; font-weight: 700;
  color: var(--green); padding-top: 4px;
}
.legal-rail-cta svg { flex: none; color: var(--wa-green); }
.legal-rail-cta:hover { text-decoration: underline; }
@media (max-width: 980px) {
  /* A sticky rail beside a single narrowing column starts to crowd the text well before the
     header's own 860px breakpoint -- collapse to a plain top block first. */
  .legal-page { grid-template-columns: 1fr; }
  .legal-rail { position: static; border: 1px solid var(--line); border-radius: 14px; padding: 20px 22px; }
  .legal-rail ol { columns: 2; column-gap: 24px; border-left: 0; }
  .legal-rail li { break-inside: avoid; }
  .legal-rail a { padding-left: 0; border-left: 0; }
  .legal-rail a:hover, .legal-rail a:focus-visible { border-left: 0; text-decoration: underline; }
}
@media (max-width: 560px) { .legal-rail ol { columns: 1; } }

/* ---------- Prose column ---------- */
.legal-body { max-width: 68ch; }
/* No sections yet (content not written) -- the rail never renders in that case (see the
   blade's @if(empty($sections))), so .legal-body is the grid's only child; without this it
   would just sit in the first 232px track instead of taking the full row. */
.legal-page > .legal-body:only-child { grid-column: 1 / -1; margin-inline: auto; }
.legal-body .page-intro { font-size: 1.05rem; line-height: 1.7; color: var(--ink); margin-bottom: 8px; }

.legal-clause { margin-top: 52px; padding-top: 40px; border-top: 1px solid var(--line);
  scroll-margin-top: 96px; }
.legal-clause:first-of-type { margin-top: 44px; padding-top: 0; border-top: 0; }
.legal-clause h2 { display: flex; align-items: baseline; gap: 14px; }
/* Same rounded-square/tint/bold-brand-text language .icongrid-mono already uses elsewhere on
   the site for a small accent badge -- reused here instead of inventing a second one. */
.legal-clause-num {
  flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px;
  background: var(--brand-tint); color: var(--brand); font-size: 0.95rem; font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.legal-clause p { color: var(--ink-soft); line-height: 1.8; margin-top: 14px; font-size: 1rem; }
.legal-clause p:first-of-type { margin-top: 16px; }

.legal-list { margin-top: 14px; display: grid; gap: 10px; padding-left: 0; list-style: none; }
.legal-list li { position: relative; padding-left: 24px; color: var(--ink-soft); line-height: 1.7; }
.legal-list li::before { content: ""; position: absolute; left: 3px; top: 0.66em; width: 7px; height: 7px;
  border-radius: 50%; background: var(--gold); }

.legal-contact {
  margin-top: 56px; padding: 28px 30px; background: var(--brand-tint); border-radius: 16px;
}
.legal-contact h2 { font-size: 1.15rem; }
.legal-contact p { color: var(--ink-soft); line-height: 1.7; margin-top: 10px; }
.legal-contact a { color: var(--brand); font-weight: 700; }
.legal-contact a:hover { text-decoration: underline; }

/* ============================================================================================
   SITEMAP (/sitemap, site.sitemap). Same "heading + link list" column the footer's own
   EXPLORE/OUR SERVICES/FIND US groups already use, re-themed for a light page body instead of
   the footer's navy -- one structural pattern, two surfaces, not two designs.
   ============================================================================================ */
.sitemap-wrap { padding-block: clamp(40px, 6vw, 64px) clamp(64px, 9vw, 108px); }
.sitemap-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: clamp(28px, 4vw, 44px) clamp(32px, 5vw, 56px);
}
.sitemap-group h2 {
  font-size: 0.76rem; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: 16px; padding-bottom: 14px; border-bottom: 1px solid var(--line);
}
.sitemap-group ul { display: grid; gap: 11px; }
.sitemap-group a {
  font-size: 0.95rem; color: var(--ink-soft); transition: color 160ms var(--ease), padding-left 160ms var(--ease);
}
.sitemap-group a:hover, .sitemap-group a:focus-visible { color: var(--brand); padding-left: 4px; }


/* ================================================================================================
   SERVICES SURFACES — 2026-09-08 redesign pass
   Scope: /services (the index), /services/{category} (the landing pages), the shared service-page
   section vocabulary, the "Our Services" mega menu and its mobile-drawer twin.

   Written against the home page, which is the reference for this site's own style: navy headings
   at 700 to 800 weight, a green hairline or ring as the accent, white cards on a 1px --line with
   a navy-tinted shadow, circular badges holding the client's own icon artwork, photos at a 16px
   radius, and alternating --surface tone bands for rhythm. No new colour, font or radius scale is
   introduced — every value below resolves from the tokens already in this file's :root.

   Loads last, so nothing here needs !important.

   What was wrong, in the order it is fixed below:
     1. the services index read as a sitemap: 105 hairline rows, no category identity, and the
        client's own category artwork used on the home page nowhere in sight
     2. a lone intro paragraph sitting in an 82px-padded band, so the first 300px of the page was
        empty
     3. the mega menu clipped a link mid-word at the bottom of every one of its five columns
     4. mega rows were 24px tall with 4px of separation — a phone book, not a menu
     5. the drawer dumped 28 links inline with no way back out and 41px touch targets
   ================================================================================================ */

/* ------------------------------------------------------------------------------------------------
   M1. SERVICES INDEX — category blocks.

   Each of the five specialities now opens with the same mark the home page gives it (the client's
   own icon artwork inside a green-ringed badge), its name, its blurb and its count, so a visitor
   scrolling the index sees five recognisable departments rather than five identical link tables.
   The block header reuses .cat-icon verbatim; only the row layout around it is new.
   ------------------------------------------------------------------------------------------------ */
.svc-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
  margin-top: 22px;
}
.svc-jump a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 8px 16px;
  border: 1.5px solid color-mix(in oklch, var(--brand) 24%, transparent);
  border-radius: 999px;
  background: #fff;
  color: var(--brand);
  font-size: 0.86rem;
  font-weight: 700;
  transition: background 160ms var(--ease), color 160ms var(--ease), border-color 160ms var(--ease), transform 200ms var(--ease);
}
.svc-jump a:hover,
.svc-jump a:focus-visible {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  transform: translateY(-2px);
}
.svc-jump .n {
  font-size: 0.74rem;
  font-weight: 800;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.svc-jump a:hover .n,
.svc-jump a:focus-visible .n { color: color-mix(in oklch, white 78%, transparent); }

.svc-block { scroll-margin-top: 96px; }

/* The title band's badge is a white disc on a navy band, so a glyph sitting DIRECTLY inside it
   inherited whatever colour happened to cascade down. Stated once, in brand navy.
   Direct child only, deliberately: the store home page puts its cart glyph inside a .monogram,
   which is a navy fill with white ink, and a descendant selector would repaint that glyph navy
   on navy and erase it. */
.badge-ring > svg { color: var(--brand); }

.svc-block-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(14px, 2vw, 22px);
  padding-bottom: 18px;
  margin-bottom: clamp(18px, 2.4vw, 26px);
  border-bottom: 2px solid color-mix(in oklch, var(--brand) 16%, transparent);
}
.svc-block-head .cat-icon { width: 58px; height: 58px; }
.svc-block-head .cat-icon img { width: 44px; height: 44px; }
.svc-block-head h2 { margin: 0; font-size: clamp(1.35rem, 2.4vw, 1.9rem); font-weight: 800; }
.svc-block-head h2 a { color: inherit; }
.svc-block-head h2 a:hover { color: var(--brand-deep); }
.svc-block-desc {
  margin-top: 5px;
  max-width: 62ch;
  font-size: 0.92rem;
  color: var(--ink-soft);
}
.svc-block-count {
  flex: none;
  align-self: center;
  padding: 7px 14px;
  border-radius: 999px;
  background: var(--brand-tint);
  color: var(--brand);
  font-size: 0.76rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
@media (max-width: 700px) {
  .svc-block-head { grid-template-columns: auto minmax(0, 1fr); }
  .svc-block-count { grid-column: 2; justify-self: start; margin-top: 4px; }
}

/* The row list itself. The client asked in 2026-08-19 for a clean list rather than a box per
   service, and that decision stands: these are still rows with a hairline, not cards. What
   changes is that a row is now a comfortable 54px target with a navy title, a green rule that
   grows in from the left on hover, and an arrow that actually travels. */
.cards-grid { gap: 4px clamp(20px, 3vw, 44px); }
.svc-card {
  position: relative;
  min-height: 54px;
  padding: 13px 12px 13px 14px;
  border-radius: 10px;
  border-bottom-color: color-mix(in oklch, var(--line) 88%, transparent);
  transition: background 180ms var(--ease), transform 200ms var(--ease);
}
.svc-card::before {
  content: "";
  position: absolute;
  inset-block: 10px;
  inset-inline-start: 0;
  width: 3px;
  border-radius: 999px;
  background: var(--green);
  transform: scaleY(0);
  transform-origin: center;
  transition: transform 220ms var(--ease);
}
.svc-card:hover::before,
.svc-card:focus-visible::before { transform: scaleY(1); }
.svc-card:hover,
.svc-card:focus-visible { transform: translateX(2px); }
.svc-card-title { font-size: 0.95rem; }
.svc-card-arrow { opacity: 0.55; transition: transform 180ms var(--ease), opacity 180ms ease; }
.svc-card:hover .svc-card-arrow,
.svc-card:focus-visible .svc-card-arrow { opacity: 1; }

/* ------------------------------------------------------------------------------------------------
   M2. PAGE LEDE — the opening band on /services and every category landing page.

   A .page-intro on its own inside a bare section produced a full 82px of padding above and below
   one short paragraph. The lede band pulls that first paragraph up against the title band it
   belongs to, sets it at a readable size, and separates it from the content with a hairline
   rather than 160px of nothing.
   ------------------------------------------------------------------------------------------------ */
section.lede { padding-block: clamp(26px, 3.4vw, 40px); }
.lede-inner {
  display: flex;
  align-items: flex-start;
  gap: clamp(16px, 2.4vw, 26px);
}

/* The department's artwork inside the title band's own disc. .badge-ring already draws the white
   fill and the green ring, so this only has to place and size the drawing inside it. The bare
   `.badge-ring img` rule above stretches a photo edge to edge with object-fit:cover, which is
   right for a service page's photographic badge and wrong for a line drawing. */
.badge-ring .badge-art { display: grid; place-items: center; width: 100%; height: 100%; }
/* position:static overrides the plain `.badge-ring img` rule's new position:absolute/inset:0
   (added 2026-09-14 to fix a photo-zoom bug, see that rule's own comment) -- this drawing was
   never that bug (contain never overflows the way a photo's cover crop did), and it still
   needs to be a normal, place-items-centered grid item, not pinned edge-to-edge by inset:0. */
.badge-ring .badge-art img { position: static; width: 62%; height: 62%; object-fit: contain; }
.badge-ring .badge-art svg { width: 40%; height: 40%; color: var(--brand); }
.lede .page-intro {
  margin-bottom: 0;
  font-size: clamp(1rem, 1.35vw, 1.12rem);
  line-height: 1.65;
  color: var(--ink-soft);
}
.lede-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin-top: 14px;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--ink-faint);
}
.lede-meta span { display: inline-flex; align-items: center; gap: 7px; }
.lede-meta span::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--green);
}
@media (max-width: 560px) {
  .lede-inner { gap: 14px; }
}

/* ------------------------------------------------------------------------------------------------
   M3. SERVICE PAGE SECTIONS — rhythm, not restructuring.

   The imported content and its seven block layouts are unchanged. These are proportion fixes:
   the banner heading was set at body-copy scale over a full-bleed photograph, checklist rows sat
   3px apart so ten of them read as one grey mass, and the technique tiles were a 64px pale square
   in the middle of a wide white box.
   ------------------------------------------------------------------------------------------------ */
.banner-veil h2 { font-size: clamp(1.5rem, 3.2vw, 2.5rem); font-weight: 800; letter-spacing: -0.02em; }
.banner { box-shadow: 0 26px 52px -34px rgba(25, 79, 144, 0.6); }

.checks { gap: 4px; }
.check { padding: 11px 4px; }
.check span { line-height: 1.55; }
.tick { width: 22px; height: 22px; }

/* Technique tiles: a real tile with the artwork centred at a size that reads, a caption with
   room to breathe, and the same lift the rest of the site's cards use. */
.icongrid { gap: clamp(12px, 1.6vw, 18px); }
.icongrid-item { border-radius: 14px; }
.icongrid-item--fallback { gap: 12px; padding: 22px 14px 18px; }
.icongrid-mono { width: 74px; height: 74px; border-radius: 18px; }
.icongrid-mono img { width: 46px; height: 46px; }
.icongrid-fallback-label { font-size: 0.86rem; color: var(--ink); }
a.icongrid-item:hover .icongrid-mono,
a.icongrid-item:focus-visible .icongrid-mono { background: color-mix(in oklch, var(--brand) 13%, white 87%); }

/* The "conditions we treat" chip lists sat at 0.9rem on a 9px row, so a column of twenty read as
   grey noise beside the photograph next to it. Same list, given a line height a person can scan. */
.longlist { gap: 2px clamp(22px, 3vw, 40px); }
.plan { padding: 11px 6px; font-size: 0.92rem; line-height: 1.45; align-items: flex-start; }
.plan::before { width: 6px; height: 6px; margin-top: 0.5em; }

/* The "N items" caption beside a section heading was 0.8rem grey text stranded at the far right
   of a 1280px row. Same treatment as the index's own count chip. */
.longlist-head .count {
  padding: 6px 13px;
  border-radius: 999px;
  background: var(--brand-tint);
  color: var(--brand);
  font-size: 0.75rem;
  font-weight: 800;
}
a.count:hover { background: var(--brand); color: #fff; }

/* ------------------------------------------------------------------------------------------------
   M4. MEGA MENU — 105 links that a person can actually use.

   The panel capped each column at 336px of scroll, which cut the last visible link through the
   middle of its own letters on all five columns at once. Nothing looks less finished than text
   sliced in half. Each column now ends in a fade and a real "View all" link, the rows are 34px
   with proper separation, and the panel closes with one full-width link to the index.
   ------------------------------------------------------------------------------------------------ */
.mega { padding: 26px 24px 18px; background: #fff; border-radius: 18px; }
/* Columns stretch to the tallest so every "View all" lands on one line across the panel. The
   older align-items:start existed to stop short columns leaving a dead gap under a scrolling
   neighbour; the capped scroll area below does that job now, and a ragged row of five footer
   links looked more accidental than the gap ever did. */
.mega-grid { gap: 8px clamp(14px, 1.6vw, 26px); align-items: stretch; }
.mega-col { display: flex; flex-direction: column; min-width: 0; }
.mega h3 {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 0.73rem;
  margin-bottom: 10px;
  padding-bottom: 10px;
}
.mega h3 .n {
  margin-inline-start: auto;
  font-size: 0.68rem;
  font-weight: 800;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
/* A short column is never capped, so its last item is never faded for no reason. A long one is
   capped and fades out: the fade is the honest edge, saying "there is more", where the previous
   hard cut through the middle of a word said "this is broken". The is-capped class is decided
   server-side by item count, because CSS cannot ask whether a box actually overflows. */
.mega-scroll {
  position: relative;
  min-height: 0;
  padding-inline-end: 6px;
  margin-inline-end: -6px;
}
.mega-scroll.is-capped {
  max-height: 322px;
  overflow-y: auto;
  scrollbar-width: thin;
  overscroll-behavior: contain;
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 26px), transparent 100%);
  mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 26px), transparent 100%);
}
.mega-grid .mega-scroll ul { max-height: none; overflow: visible; padding: 0; margin: 0; }
.mega li + li { margin-top: 0; }
.mega a {
  padding: 7px 9px;
  border-radius: 7px;
  font-size: 0.81rem;
  line-height: 1.35;
  transition: color 150ms ease, background 150ms ease, transform 200ms var(--ease);
}
.mega a:hover,
.mega a:focus-visible { background: var(--brand-tint); transform: translateX(2px); }
/* Two classes, because the panel's own `.mega a { display: block }` would otherwise beat a
   single-class rule and drop the chevron onto its own line under the label. */
.mega .mega-more {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  padding: 10px 9px 2px;
  border-top: 1px solid var(--line);
  border-radius: 0;
  font-size: 0.76rem;
  font-weight: 800;
  line-height: 1.3;
  color: var(--brand);
}
.mega .mega-more svg { width: 12px; height: 12px; flex: none; transform: rotate(-90deg); transition: transform 180ms var(--ease); }
.mega .mega-more:hover,
.mega .mega-more:focus-visible { background: none; text-decoration: underline; }
.mega .mega-more:hover svg { transform: rotate(-90deg) translateY(-3px); }
.mega-foot {
  display: flex;
  justify-content: center;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.mega-foot a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 20px;
  border-radius: 999px;
  background: var(--brand-tint);
  color: var(--brand);
  font-size: 0.82rem;
  font-weight: 800;
}
.mega-foot a:hover { background: var(--brand); color: #fff; transform: none; }
.mega-foot svg { width: 13px; height: 13px; transform: rotate(-90deg); }

/* The panel arrives with a short rise rather than appearing at full size on the pointer. */
li:hover > .mega,
li:focus-within > .mega { transition-duration: 200ms, 240ms, 200ms; }

/* Below 1200 the five columns become three rows-of-three, which makes the panel itself tall
   enough to scroll, so each column's own cap comes down to keep the whole thing reachable.
   Two classes, to beat the .is-capped rule above rather than lose to it on specificity. */
@media (max-width: 1200px) {
  .mega-grid { grid-template-columns: repeat(3, 1fr); }
  .mega-scroll.is-capped { max-height: 250px; }
}

/* ------------------------------------------------------------------------------------------------
   M5. MOBILE DRAWER — thumb-sized, and never a one-way trip.

   Opening a category poured all 28 of its services straight into the drawer, so reaching the
   next category meant scrolling past every service in this one, and each row was a 41px target.
   Rows are 46px now, a category's own panel is capped and scrolls in place, and every category
   panel ends in a link to that category's landing page.
   ------------------------------------------------------------------------------------------------ */
.drawer-cats .acc-sub-panel .acc-inner > ul.is-capped {
  max-height: 46vh;
  overflow-y: auto;
  scrollbar-width: thin;
  overscroll-behavior: contain;
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 22px), transparent 100%);
  mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 22px), transparent 100%);
}
.drawer-cats .acc-sub-panel a { min-height: 46px; display: flex; align-items: center; }
.acc-cat-row { min-height: 48px; }
.acc-chev-btn { min-width: 48px; min-height: 48px; }
.acc-sub-panel .acc-inner > .drawer-cat-all {
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 46px;
  margin: 6px 6px 10px 28px;
  padding: 10px 8px 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  font-size: 0.83rem;
  font-weight: 800;
  color: var(--brand);
}
.acc-sub-panel .acc-inner > .drawer-cat-all:hover { background: none; text-decoration: underline; }
.drawer-cat-all svg { width: 13px; height: 13px; transform: rotate(-90deg); }

/* ------------------------------------------------------------------------------------------------
   M6. MOTION — purposeful only. Entrance is already handled site-wide by the scroll-driven
   [data-reveal] rule further up this file; everything added above is hover and focus feedback.
   All of it stands down under prefers-reduced-motion.
   ------------------------------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .svc-jump a,
  .svc-card,
  .svc-card::before,
  .svc-card-arrow,
  .mega a,
  .mega-more svg { transition: none; }
  .svc-jump a:hover,
  .svc-jump a:focus-visible,
  .svc-card:hover,
  .svc-card:focus-visible,
  .mega a:hover,
  .mega a:focus-visible { transform: none; }
  .svc-card::before { transform: scaleY(1); opacity: 0; }
  .svc-card:hover::before,
  .svc-card:focus-visible::before { opacity: 1; }
}

/* ================================================================================================
   ARABIC TYPEFACE AND RTL
   Added by the EN/AR pass. Everything above this line is the English design, untouched.

   Read this before changing anything below it.

   1. THE ARABIC FACE IS SCOPED BY CODEPOINT, NOT BY LOCALE.
      Noto Sans Arabic is declared with a `unicode-range` covering only the Arabic blocks, and is
      then put FIRST in `--font` for every page in both languages. A browser picks, per character,
      the first family in the stack that actually covers it, so Latin letters and Western digits
      still render in the existing Segoe UI stack while Arabic letters render in Noto -- which is
      exactly what a line like "SESC مركز الطب الرياضي" or "15.000 KWD" needs. It also means the
      font file is never downloaded on an English-only page: no codepoint on it is in range.
      Do NOT "simplify" this by swapping the whole family under a dir or lang selector. That would
      put every English brand name, product code and price inside Arabic copy into an Arabic face
      built for a different x-height, and would re-download a 162 KB file on English pages.

   2. SELF-HOSTED, WITH `font-display: swap`.
      public/fonts/noto-sans-arabic-var.woff2 is the variable face, weights 400-800, which covers
      every weight this design uses (400 body, 600/700 headings and labels, 800 chips and prices).
      One file, one request, no third-party font host in the critical path.

   3. RTL IS AN OVERRIDE LAYER, NOT A REWRITE.
      Most of this stylesheet already uses logical properties (margin-inline, padding-inline,
      inset-inline) and flexbox, all of which mirror themselves the moment `dir="rtl"` is on the
      html element. Only the handful of genuinely physical rules left in the file need answering,
      and each one is answered here, next to a note on why it is physical in the first place.
      When you add new CSS, use logical properties and you will not need to come back here.
   ================================================================================================ */

@font-face {
  font-family: "Noto Sans Arabic";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/fonts/noto-sans-arabic-var.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF,
    U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC;
}

/* ---------- Arabic typographic tuning ----------
   Two things that are right for Latin and wrong for Arabic:
   - the negative letter-spacing on headings pulls joined Arabic letterforms into each other,
     so it is zeroed rather than reduced;
   - Arabic ascenders and descenders are taller than Latin ones at the same size, so the body
     line-height is opened up a little to stop consecutive lines colliding. */
[dir="rtl"] body { line-height: 1.8; }
[dir="rtl"] h1,
[dir="rtl"] h2,
[dir="rtl"] h3,
[dir="rtl"] h4 { letter-spacing: 0; line-height: 1.35; }
/* Uppercase eyebrow labels: Arabic has no letter case, so `text-transform: uppercase` does
   nothing, but the wide tracking that goes with it separates letters that must stay joined. */
[dir="rtl"] .eyebrow,
[dir="rtl"] .legal-rail-label,
[dir="rtl"] .sec-eyebrow,
[dir="rtl"] .kicker { letter-spacing: 0; }

/* ---------- Numbers, prices and phone numbers ----------
   A price is "15.000 KWD" and a phone number is "(+965) 2228 0055" in both languages; a Kuwaiti
   reader dials the same digits in the same order and compares the same price against the same
   competitor. Neither may be reordered by the bidi algorithm, and the two need different
   treatment to survive it. */
/* A phone number, and anything else carrying "+", brackets or a slash, genuinely needs its
   own LTR paragraph: those characters are bidi-neutral, so left to the surrounding Arabic they
   drift to the wrong end and "(+965) 2228 0055" comes out as something nobody can dial. */
[dir="rtl"] .topbar-phone,
[dir="rtl"] .map-card-rating,
[dir="rtl"] a[href^="tel:"] {
  direction: ltr;
  unicode-bidi: isolate;
}
/* A price is a plain run of digits and Latin letters, which the bidi algorithm already keeps in
   order. It needs ISOLATING from its neighbours (so an adjacent Arabic word cannot pull the
   currency across the number) but NOT reversing -- forcing direction:ltr here would also drag
   every price cell's alignment back to the left edge of an otherwise right-aligned column. */
[dir="rtl"] .price,
[dir="rtl"] .pcard-price,
[dir="rtl"] .prod-price,
[dir="rtl"] .money,
[dir="rtl"] .svc-table-num,
[dir="rtl"] time { unicode-bidi: isolate; }
/* The topbar is a centered row, so isolating it must not also left-align it. */
[dir="rtl"] .topbar-phone { justify-content: center; }

/* ---------- Header nav and dropdowns ---------- */
/* .drop is absolutely positioned against its own <li> with a physical left offset. */
[dir="rtl"] .drop { left: auto; right: 6px; }
/* The mega panel's scrollable columns reserve gutter for their own scrollbar. */
[dir="rtl"] .mega-grid ul { padding-right: 0; margin-right: 0; padding-left: 4px; margin-left: -4px; }

/* ---------- Directional glyphs ----------
   #i-chev points DOWN in the sprite, so every "forward" arrow in this stylesheet is a chevron
   rotated -90deg to point right. In Arabic, forward is left. */
[dir="rtl"] .svc-card-arrow,
[dir="rtl"] .cat-card .cat-card-arrow,
[dir="rtl"] .mega-more svg,
[dir="rtl"] .drawer-cat-all svg,
[dir="rtl"] .mega-foot a svg { transform: rotate(90deg); }
[dir="rtl"] .svc-card:hover .svc-card-arrow,
[dir="rtl"] .svc-card:focus-visible .svc-card-arrow { transform: rotate(90deg) translateX(-3px); }

/* ---------- Mobile drawer ----------
   The panel is parked one screen-width off the closing edge and slid back to zero by .is-open.
   Both the edge it is pinned to and the direction it is parked in have to mirror, or the drawer
   slides in from the side the burger is NOT on -- and its drop shadow falls on the wrong side. */
[dir="rtl"] .drawer {
  right: auto;
  left: 0;
  transform: translateX(-100%);
  box-shadow: 24px 0 60px -24px rgba(0, 0, 0, 0.45);
}
[dir="rtl"] .drawer.is-open { transform: translateX(0); }
[dir="rtl"] .acc-trigger { text-align: right; }
[dir="rtl"] .acc-sub-panel .acc-inner > ul { padding-left: 0; padding-right: 28px; }

/* ---------- Floating WhatsApp bubble ----------
   Bottom-trailing in both languages: it follows the thumb, and in Arabic the thumb's resting
   corner is the left one. */
[dir="rtl"] .wa-float {
  right: auto;
  left: max(20px, env(safe-area-inset-left));
}

/* ---------- Footer map card ---------- */
[dir="rtl"] .map-card { left: auto; right: 10px; }

/* ---------- Carousels ----------
   The prev/next buttons are pinned to physical edges and the "prev" glyph is the forward arrow
   turned around. Mirror both: the button that means "back" belongs on the side the reader came
   from, which in Arabic is the right. public/js/reviews.js, team.js, partners.js and
   university-trainers.js each carry the matching direction flip for what the button DOES -- see
   their own notes on why scrollLeft cannot simply be reused in RTL. */
[dir="rtl"] .rev-arrow.prev { left: auto; right: 0; }
[dir="rtl"] .rev-arrow.next { right: auto; left: 0; }
[dir="rtl"] .t3d-nav.prev { left: auto; right: clamp(18px, 4vw, 44px); }
[dir="rtl"] .t3d-nav.next { right: auto; left: clamp(18px, 4vw, 44px); }
[dir="rtl"] .partners-nav.prev { left: auto; right: clamp(18px, 4vw, 44px); }
[dir="rtl"] .partners-nav.next { right: auto; left: clamp(18px, 4vw, 44px); }
/* Two different arrow families, so two different mirrors.
   .carousel-arrow (Reviews, Team, Partners) is the sprite's right-pointing #i-arrow, with
   `prev` turned around; mirrored, it is `next` that needs turning and `prev` that does not.
   .hero-arrow is a pair of inline paths already drawn pointing opposite ways, so mirroring it
   means turning BOTH -- see the note on .hero-arrow above. */
[dir="rtl"] .hero-arrow svg { transform: rotate(180deg); }
[dir="rtl"] .carousel-arrow.prev svg { transform: none; }
[dir="rtl"] .carousel-arrow.next svg { transform: rotate(180deg); }

/* The partners marquee is a doubled track translated by -50% of its own width. Mirrored, the
   duplicate half sits on the other side, so the travel is +50%. */
@keyframes partnersScrollRtl { from { transform: translateX(0); } to { transform: translateX(50%); } }
[dir="rtl"] .partners-track:not(.is-manual) { animation-name: partnersScrollRtl; }

/* ---------- Team member cards ---------- */
[dir="rtl"] .tm-portrait .tm-dept { left: auto; right: 16px; }
[dir="rtl"] .tm-close { right: auto; left: 14px; }
[dir="rtl"] .tm-brief .tm-close { right: auto; left: 16px; }
[dir="rtl"] .tm-brief .tm-cta,
[dir="rtl"] .tm-briefhead,
[dir="rtl"] .tm-brieftext { text-align: right; }
[dir="rtl"] .tm-card { text-align: right; }

/* ---------- Bulleted lists drawn with a positioned pseudo-element ---------- */
[dir="rtl"] .modality-group li { padding-left: 0; padding-right: 16px; }
[dir="rtl"] .modality-group li::before { left: auto; right: 0; }

/* ---------- Legal pages: the sticky clause rail ----------
   The rail's hairline is the spine the active-clause marker thickens against, so it has to sit
   on the side the prose starts from. */
[dir="rtl"] .legal-rail ol { border-left: 0; border-right: 1px solid var(--line); }
[dir="rtl"] .legal-rail a {
  padding: 8px 16px 8px 0;
  margin-left: 0;
  margin-right: -1px;
  border-left: 0;
  border-right: 1px solid transparent;
}
[dir="rtl"] .legal-rail a:hover,
[dir="rtl"] .legal-rail a:focus-visible { border-left-color: transparent; border-right-color: var(--brand); }
[dir="rtl"] .legal-rail a.is-active { border-left-color: transparent; border-right-color: var(--brand); }

/* ---------- Hero slide anchoring ----------
   data-pos is an EDITORIAL position picked against the photograph behind it, not a reading
   position: "mid-right" means "this photo's subject is on the left, so put the card on the
   right". The overlay wash is derived from the same attribute. Left to flexbox, RTL would swing
   the card to the opposite edge while the wash stayed put, so the anchoring is re-pinned here to
   the physical side it was authored on. Only the TEXT inside the card flips. */
[dir="rtl"] .hero-slide:not([data-pos$="-center"]) .hero-content { align-items: flex-end; text-align: right; }
[dir="rtl"] .hero-slide[data-pos$="-right"] .hero-content { align-items: flex-start; text-align: right; }
[dir="rtl"] .hero-slide[data-pos$="-center"] .hero-content { text-align: center; }
@media (max-width: 860px) {
  [dir="rtl"] .hero-slide[data-pos$="-right"] .hero-content { align-items: flex-end; text-align: right; }
}

/* ---------- Language switcher ----------
   Lives in the header tools row and, on small screens, at the top of the drawer. Deliberately a
   two-item segmented control rather than a dropdown: there are exactly two languages, so the
   destination should be one tap away and visible, and each is written in its own script rather
   than behind a flag. A flag is a country. This clinic serves Kuwaitis, Gulf residents and a
   large expat population who read these two languages under a dozen different flags. */
.lang-switch {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  line-height: 1;
}
.lang-switch a,
.lang-switch span {
  display: block;
  padding: 6px 11px;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 700;
  color: var(--ink-soft);
  white-space: nowrap;
  transition: background-color 160ms var(--ease), color 160ms var(--ease);
}
.lang-switch a:hover,
.lang-switch a:focus-visible { color: var(--brand); background: var(--brand-tint); }
.lang-switch [aria-current="true"] { background: var(--brand); color: #fff; }
/* Each label is pinned to its own script's direction so "العربية" and "English" both read
   correctly no matter which way round the page is. */
.lang-switch [lang="ar"] { direction: rtl; unicode-bidi: isolate; font-size: 0.82rem; }
.lang-switch [lang="en"] { direction: ltr; unicode-bidi: isolate; }

/* The drawer copy is full width and sits above the nav list. */
.drawer-lang { margin: 4px 10px 10px; }
.drawer-lang .lang-switch { display: flex; width: 100%; }
.drawer-lang .lang-switch a,
.drawer-lang .lang-switch span { flex: 1; text-align: center; padding-block: 10px; font-size: 0.85rem; }

@media (max-width: 1040px) {
  /* The header row is already tight enough to drop the search and account labels at this width;
     the switcher goes with them and the drawer's copy takes over. */
  .header-tools .lang-switch { display: none; }
}

/* ---------- LTR islands: the codebase's existing convention ----------
   resources/views/invoices/pdf.blade.php has been rendering a fully Arabic document with
   correct KWD figures since long before this pass, and does it by wrapping every number,
   invoice reference, date, phone number and email in `class="ltr"`. That is the convention this
   site adopts too, rather than a second, parallel one: the same class name means the same thing
   in an invoice and on a page. `unicode-bidi: isolate` is used here instead of the PDF's
   `embed` only because mPDF predates isolate; in a browser, isolate is the stronger form of the
   same idea -- it also stops the island from re-ordering the text AROUND it. */
.ltr { direction: ltr; unicode-bidi: isolate; }
.rtl { direction: rtl; unicode-bidi: isolate; }
/* Inside a right-aligned Arabic column an LTR island should still hug the column's own edge,
   not jump to the opposite one, so alignment is inherited rather than reset by `direction`. */
[dir="rtl"] .ltr { text-align: inherit; }

/* ---------- Remaining physical rules, mirrored ---------- */
[dir="rtl"] .c2-hero-tag { left: auto; right: 16px; }
/* The speech-bubble tail on a review card points down from the quote's opening edge. */
[dir="rtl"] .review::after { left: auto; right: 34px; transform: scaleX(-1); }
[dir="rtl"] .person { text-align: right; }
[dir="rtl"] .legal-list { padding-right: 0; }
[dir="rtl"] .legal-list li { padding-left: 0; padding-right: 24px; }
[dir="rtl"] .legal-list li::before { left: auto; right: 3px; }
[dir="rtl"] .sitemap-group a:hover,
[dir="rtl"] .sitemap-group a:focus-visible { padding-left: 0; padding-right: 4px; }
@media (max-width: 980px) {
  /* The rail loses its spine entirely at this width, in both directions. */
  [dir="rtl"] .legal-rail ol { border-right: 0; }
  [dir="rtl"] .legal-rail a { padding-right: 0; border-right: 0; }
  [dir="rtl"] .legal-rail a:hover,
  [dir="rtl"] .legal-rail a:focus-visible { border-right: 0; }
}

/* ---------- Untranslated English inside the Arabic shell ----------
   THIS IS THE FALLBACK'S TYPOGRAPHY, AND IT IS NOT COSMETIC. While the client's
   translations are being loaded, an Arabic page legitimately renders English body copy inside
   Arabic chrome. Dropped into a right-to-left paragraph, the bidi algorithm treats the full
   stop, comma, colon, brackets and quotes as neutral characters belonging to the surrounding
   Arabic, and moves them to the far end of the line: "Clinic-grade equipment and apparel." is
   rendered ".Clinic-grade equipment and apparel", which reads as a broken page rather than an
   untranslated one.

   `unicode-bidi: plaintext` is the CSS form of HTML's `dir="auto"`: each paragraph takes its
   direction from its own first strong character, so an English paragraph lays out
   left-to-right with its punctuation where the writer put it, and an Arabic paragraph is
   untouched and still right-to-left. Applied per text element rather than to a wrapper,
   because the two languages sit side by side on the SAME page, sometimes in adjacent
   paragraphs, and the decision has to be made per paragraph.

   `text-align` is deliberately NOT set here. Left alone it stays at its initial `start`, which
   resolves against each paragraph's own resolved direction, so this alignment follows for
   free -- while any deliberately centred or otherwise aligned block in the design keeps the
   alignment it was given. Setting it here would flatten all of those.

   This rule has no effect once a page is fully translated, and is correct to leave in place. */
[dir="rtl"] p,
[dir="rtl"] h1,
[dir="rtl"] h2,
[dir="rtl"] h3,
[dir="rtl"] h4,
[dir="rtl"] h5,
[dir="rtl"] h6,
[dir="rtl"] li,
[dir="rtl"] dt,
[dir="rtl"] dd,
[dir="rtl"] td,
[dir="rtl"] th,
[dir="rtl"] figcaption,
[dir="rtl"] blockquote,
[dir="rtl"] summary,
[dir="rtl"] label { unicode-bidi: plaintext; }
/* The same problem one level down. A short English run inside an otherwise Arabic line -- a
   chip, a caption, a "Questions?" in a reassurance strip, a button label -- is not a paragraph
   of its own, so the block rule above cannot reach it. On an INLINE element `plaintext` behaves
   as an isolate that picks its own direction, which is exactly what each of these needs: the
   run keeps its own punctuation and cannot disturb the text either side of it. Deliberately
   inline elements only; block containers are already covered above, and applying this to every
   div would set a direction on wrappers that legitimately hold both languages at once. */
[dir="rtl"] span,
[dir="rtl"] a,
[dir="rtl"] button,
[dir="rtl"] strong,
[dir="rtl"] b,
[dir="rtl"] em,
[dir="rtl"] i,
[dir="rtl"] small,
[dir="rtl"] cite,
[dir="rtl"] abbr { unicode-bidi: plaintext; }


/* ============================================================================
   LANGUAGE SWITCH -- READING CONTINUITY
   ----------------------------------------------------------------------------
   Pairs with public/js/lang-switch.js, which carries the reader's position
   across an English/Arabic switch by content anchors and decides which
   navigations actually transition (only a language change; everything else is
   skipped there, so the rest of the site navigates exactly as before).

   One job: keep the reader's place while the words change language. So it is
   short (250ms), eases out without overshoot, and moves nothing sideways --
   the page flips LTR/RTL, and any horizontal travel would read as content
   sliding the wrong way. The header holds still (its own group) and the line
   being read dissolves into its translation in place (a slight blur on the
   way through, so the change of script registers as a change rather than a
   flicker). No opt-in support: an ordinary navigation, position still kept.
   ============================================================================ */
@view-transition { navigation: auto; }

:root { --lang-swap-ease: cubic-bezier(0.22, 1, 0.36, 1); }

::view-transition-group(root),
::view-transition-group(sesc-header),
::view-transition-group(sesc-reading) {
  animation-duration: 250ms;
  animation-timing-function: var(--lang-swap-ease);
}

/* Old copy leaves a little faster than the new one arrives: the overlap stays
   short, so the two scripts are never legible on top of each other for long. */
::view-transition-old(root),
::view-transition-old(sesc-header) {
  animation: 180ms cubic-bezier(0.4, 0, 1, 1) both sesc-lang-out;
}
::view-transition-new(root),
::view-transition-new(sesc-header) {
  animation: 250ms var(--lang-swap-ease) both sesc-lang-in;
}
::view-transition-old(sesc-reading) {
  animation: 180ms cubic-bezier(0.4, 0, 1, 1) both sesc-lang-read-out;
}
::view-transition-new(sesc-reading) {
  animation: 250ms var(--lang-swap-ease) both sesc-lang-read-in;
}

@keyframes sesc-lang-out { to { opacity: 0; } }
@keyframes sesc-lang-in { from { opacity: 0; } }
@keyframes sesc-lang-read-out { to { opacity: 0; filter: blur(2px); } }
@keyframes sesc-lang-read-in { from { opacity: 0; filter: blur(2px); } }

/* Reduced motion: no transition at all -- an instant swap. The script still
   restores the position, and also skips any transition it is handed. */
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* Browsers without `pagereveal` cannot hold the first frame until the page is
   scrolled into place, so the arriving page stays unpainted for that instant
   instead of flashing its top and jumping. lang-switch.js removes the class
   as soon as the position is applied, and after 1.5s regardless. */
html.sesc-lang-arriving body { visibility: hidden; }
