/* ============================================================
   SIPADAN.COM — UNIFIED RESPONSIVE STYLESHEET (mobile-first)
   Polish pass: Revision 1b folded into base (no trailing
   overrides), modern 2026 finish on the preserved skeleton.
   Requires: tokens.css loaded first.
   Desktop breakpoint: 1024px (tablets use the hamburger).
   ============================================================ */

/* ---------- 0. Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { transition: none !important; animation: none !important; }
}
body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  color: var(--c-ink);
  background: var(--c-white);   /* white base; sand appears as section bands */
}
img, svg, video { max-width: 100%; height: auto; }
a { color: var(--c-navy); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--c-teal-dark); }
:focus-visible { outline: 3px solid var(--c-teal); outline-offset: 2px; border-radius: var(--radius-sm); }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  color: var(--c-navy);
  line-height: var(--lh-tight);
  font-weight: 570;                 /* Fraunces variable axis: elegant, not heavy */
  font-variation-settings: "opsz" 40;
  letter-spacing: -0.01em;
  margin: var(--sp-6) 0 var(--sp-3);
  padding: 0;
}
h1 { font-size: var(--fs-2xl); margin-top: 0; letter-spacing: -0.015em; font-variation-settings: "opsz" 72; }
h2 { font-size: var(--fs-xl); }
h3 { font-size: var(--fs-lg); }
h2, h3 { scroll-margin-top: calc(var(--header-h) + var(--sp-5)); } /* sticky-header safe anchors */
h1, h2, h3, h4, h1 a, h2 a, h3 a, h4 a { text-decoration: none; }
h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover { text-decoration: none; color: var(--c-teal-dark); }
.content-card > h2 { margin-top: var(--sp-7); }
.content-card > h2::after {
  content: ""; display: block; width: 56px; height: 2px;
  background: var(--c-gold);
  margin-top: var(--sp-3); opacity: 0.8;
}
.content-card > h2.text-center::after,
.content-card > h1.text-center::after { margin-left: auto; margin-right: auto; }
p  { margin: 0 0 var(--sp-4); }
ul, ol { margin: 0 0 var(--sp-4); padding-left: 1.4em; }
li { margin-bottom: var(--sp-1); }
hr { border: 0; border-top: 1px solid var(--c-hairline-gold); margin: var(--sp-7) 0; }

.page-plain {
  /* info/article/form/thank-you/contact pages: cleaner modern headings.
     Plus Jakarta Sans is Latin-scoped; CN/RU glyphs fall back per-glyph
     to Source Sans Pro -> system, so non-Latin builds never break. */
  --font-display: 'Plus Jakarta Sans', 'Source Sans Pro', -apple-system, 'Segoe UI', Arial, sans-serif;
}
.page-plain h1, .page-plain h2, .page-plain h3, .page-plain h4 {
  font-weight: var(--fw-bold);
  font-variation-settings: normal;
  letter-spacing: -0.01em;
}

.skip-link {
  position: absolute; left: -999px; top: 0; z-index: 200;
  background: var(--c-navy); color: #fff; padding: var(--sp-2) var(--sp-4);
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus { left: 0; }

/* Inline SVG icon primitive (license-clean, multilingual-neutral) */
.icon {
  width: 1.25em; height: 1.25em;
  display: inline-block; vertical-align: -0.22em;
  fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
  flex: 0 0 auto;
}
.icon--fill { fill: currentColor; stroke: none; }

/* ---------- 1. Header (watery band + native transparent logo, NO glass) ---------- */
.site-header {
  background: var(--c-band-fall) url('/images/header/header-band.webp') center/cover no-repeat;
  border-bottom: 1px solid rgba(10,31,51,0.25);
  box-shadow: inset 0 -1px 0 rgba(255,255,255,0.25), var(--shadow-sm);
  position: sticky;            /* sticky on desktop AND mobile */
  top: 0;
  z-index: 80;                 /* above content + fly-outs; drawer (100) still covers it */
}
.site-header__inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 2px var(--gutter);         /* tight padding around the logo */
  min-height: var(--header-h);
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--sp-3);
}
.site-header__logo { display: inline-flex; align-items: center; flex: 0 0 auto; }
@media (min-width: 1024px) {
  .site-header__logo img { transition: filter var(--t-fast) var(--ease); }
  .site-header__logo:hover img { filter: brightness(1.05); }
}
.site-header__logo img { display: block; width: auto; height: 74px; }     /* mobile: bigger */
@media (min-width: 1024px) { .site-header__logo img { width: 250px; height: 129px; } } /* native, never resized */

/* right cluster: CTA (+ mobile mini flags + hamburger) */
.site-header__actions { margin-left: auto; display: flex; align-items: center; gap: var(--sp-3); }
/* header CTA inherits the enriched .btn--cta gradient/border/shadow */
@media (max-width: 1023px) {
  /* intentional exception to the 17px floor: compact single-line header CTA */
  .site-header .site-header__cta { display: none; }   /* removed on mobile (beats .btn) */
}

/* mobile-only EN+CN mini flags, side by side, centred with the menu icon */
.header-mini-flags { display: none; flex-direction: row; gap: 5px; align-items: center; align-self: center; }
@media (max-width: 1023px) { .header-mini-flags { display: flex; } }

/* ============ Build 14.4: mobile language MENU (all 6 languages, no flags) ============
   A frosted pill labeled "EN" opens a small language card. <details> = works
   without JS; site.js closes it on outside taps. Desktop keeps its flag rows. */
.lang-menu { position: relative; display: none; align-self: center; z-index: 95; }
.lang-menu[open]::before {           /* page dims 5% while the menu is open */
  content: ""; position: fixed; inset: 0; z-index: -1;
  background: rgba(0,0,0,.05); pointer-events: none;
}
@media (max-width: 1023px) { .lang-menu { display: block; } }
.lang-menu { margin-right: 10px; }   /* clear separation from the menu icon */
.lang-menu__btn {
  display: inline-flex; align-items: center; gap: 6px;
  list-style: none; cursor: pointer; user-select: none;
  font-size: 14px; font-weight: 500; letter-spacing: .06em; line-height: 1;
  color: var(--c-deep); opacity: .85;               /* secondary to the menu icon */
  padding: 6px 12px; border-radius: 999px;
  background: rgba(255,255,255,.13);                /* barely lighter than the sea */
  border: 1px solid rgba(255,255,255,.32);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
}
.lang-menu__btn::-webkit-details-marker { display: none; }
.lang-menu__chev { width: 11px; height: 8px; transition: transform var(--t-fast) var(--ease); }
.lang-menu[open] .lang-menu__chev { transform: rotate(180deg); }
.lang-menu__list {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 90;
  margin: 0; padding: var(--sp-2); list-style: none;
  min-width: 158px;
  background: var(--c-white);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius);
  box-shadow: 0 14px 38px rgba(6, 18, 30, .22);
}
.lang-menu__list a {
  display: block; padding: 9px 12px; border-radius: var(--radius-sm);
  font-size: var(--fs-base); text-decoration: none; color: var(--c-ink);
}
.lang-menu__list a:hover, .lang-menu__list a:focus-visible { background: var(--c-sand); color: var(--c-navy); }

/* form pages: the 6-flag row under the CTA is desktop-only (mobile uses the menu) */
@media (max-width: 1023px) { .header-flags { display: none; } }

/* Hamburger: frameless, dark ink on the light watery band */
.nav-toggle {
  display: inline-flex; align-items: center; justify-content: center; align-self: center;
  background: transparent;
  border: 0;                                   /* no frame (deliberate) */
  color: var(--c-deep);
  padding: 9px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.nav-toggle:hover { background: rgba(255,255,255,0.35); }
.nav-toggle__bars { display: inline-block; width: 22px; }
.nav-toggle__bars span {
  display: block; height: 2.5px; background: var(--c-deep); border-radius: 2px;
  margin: 4px 0; transition: transform var(--t-fast) var(--ease), opacity var(--t-fast);
}
[aria-expanded="true"] .nav-toggle__bars span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
[aria-expanded="true"] .nav-toggle__bars span:nth-child(2) { opacity: 0; }
[aria-expanded="true"] .nav-toggle__bars span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }
@media (min-width: 1024px) { .nav-toggle { display: none; } }

/* Language flags: rectangles; hover lightens only — never moves */
.lang-flag {
  width: 30px; height: 20px; display: inline-block; vertical-align: middle;
  border-radius: 3px; overflow: hidden; line-height: 0;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.08);          /* thin edge so light flags read on any bg */
  transition: filter var(--t-fast) var(--ease);
}
.lang-flag svg { display: block; width: 100%; height: 100%; }
.lang-flag:hover, .lang-flag:focus-visible { filter: brightness(1.08); transform: none; }

/* ---------- 2. Permit bar (254 pill + bold lead + flags right on desktop) ---------- */
.permit-bar {
  background: var(--c-white);
  border-bottom: 1px solid var(--c-hairline-gold);
}
.permit-bar__inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: var(--sp-3) var(--gutter);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.permit-bar__pill {
  flex: 0 0 auto;
  background: linear-gradient(180deg, #f7b165, var(--c-btn-book)); color: var(--c-deep);
  border: 1px solid var(--c-btn-border);
  font-weight: var(--fw-bold); font-size: var(--fs-sm); line-height: 1.7;
  border-radius: var(--radius-pill); padding: 2px 11px;
  box-shadow: 0 1px 3px rgba(207,127,40,0.3), inset 0 1px 0 rgba(255,255,255,0.42);
}
.permit-bar__hook { flex: 1 1 260px; margin: 0; font-size: var(--fs-sm); color: var(--c-ink); }
.permit-bar__hook strong { color: var(--c-navy); }
/* mobile: tighten spacing so the bold line fits on ONE line (same font size) */
@media (max-width: 559px) {
  .permit-bar__inner { padding: var(--sp-3) var(--sp-2); gap: 5px; }
  .permit-bar__pill { padding: 2px 8px; }
  .permit-bar__hook { flex: 1 1 auto; letter-spacing: -0.01em; }
}
.permit-bar__flags { flex: 0 0 auto; display: flex; gap: 6px; margin-left: auto; }
.permit-bar__flags .lang-flag { width: 30px; height: 20px; }   /* Build 14.5: +4px height (owner) */
.permit-bar__flags .lang-flag:hover, .permit-bar__flags .lang-flag:focus-visible { filter: brightness(1.25); }
@media (max-width: 1023px) { .permit-bar__flags { display: none; } }
@media (max-width: 767px)  { .permit-bar__more  { display: none; } } /* mobile: lead line only */

/* ---------- 3. Shell: one fluid container, two columns at >=1024px ---------- */
.shell {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: var(--sp-6) var(--gutter) var(--sp-8);
}
.shell--with-sidebar { display: block; }
.shell__main { min-width: 0; }
@media (min-width: 1024px) {
  .shell--with-sidebar {
    display: grid;
    grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
    gap: var(--sp-6);
    align-items: start;
  }
  /* sticky, no overflow clipping — the fly-out must stay in the foreground */
  .shell__sidebar { position: sticky; top: var(--sp-4); align-self: start; z-index: 85  /* WALK-R1 ②: was 20 — the sidebar is a stacking context, so the
      .site-nav__sub flyout (z:90 INSIDE it) was capped at 20 and clipped under the
      header banner (z:80). 85 floats the whole sidebar context above the banner,
      below the lang-menu (95) and the mobile drawer (100). */; }
}

/* ---------- 4. Navigation: one markup — drawer (<1024) / sidebar + fly-out (>=1024) ---------- */
.site-nav { font-size: 17px; }                      /* 17px floor applies to menus too */
.site-nav__list { list-style: none; margin: 0; padding: 0; }
.site-nav__item + .site-nav__item { border-top: 1px solid var(--c-hairline); }
.site-nav__row { display: flex; align-items: stretch; }
.site-nav__link {
  flex: 1 1 auto;
  display: block;
  padding: var(--sp-2) var(--sp-4);
  color: var(--c-navy);
  text-decoration: none;
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.045em;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.site-nav__link:hover { background: var(--c-sand); color: var(--c-navy); }
/* Two-line nav item: bold label + a muted, sentence-case tagline underneath, both in the
   same <a>. The tagline is visibly a subtitle (smaller, lighter, not uppercase) so it never
   reads as a second menu entry. */
.site-nav__label { display: block; }
.site-nav__tagline {
  display: block;
  margin-top: 2px;
  /* Sized to the legacy nav's sub-text proportion (12px sub / 16px label ≈ 0.78), scaled to
     the new nav — the old 0.68em read as unreadably small. Readable slate, not pale grey. */
  font-size: 0.78em;
  font-weight: var(--fw-reg);
  text-transform: none;
  letter-spacing: 0;
  color: #4a5b6d;
  line-height: 1.25;
}
.site-nav__link--cta .site-nav__tagline { color: inherit; opacity: 0.8; }
.site-nav__item.active > .site-nav__row .site-nav__link,
.site-nav__item.active-parent > .site-nav__row .site-nav__link {
  color: var(--c-navy);
  box-shadow: inset 2px 0 0 var(--c-gold);
  background: var(--c-surface-2);
}
.site-nav__link--cta { color: var(--c-gold-text); }
.site-nav__sub { font-size: 17px; }
.site-nav__sub li.active > a { color: var(--c-teal-dark); font-weight: var(--fw-semi); }

/* --- <1024px: off-canvas drawer with accordions --- */
@media (max-width: 1023px) {
  .site-nav {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(320px, 86vw);
    background: var(--c-white);
    box-shadow: var(--shadow-lg);
    transform: translateX(105%);
    transition: transform var(--t-med) var(--ease);
    z-index: 100;
    overflow-y: auto;
    padding-top: var(--sp-7);
  }
  .site-nav.active { transform: translateX(0); }
  .site-nav__close {
    position: absolute; top: var(--sp-3); right: var(--sp-3);
    background: transparent; border: 0; cursor: pointer;
    font-size: 1.6rem; line-height: 1; color: var(--c-navy);
    padding: var(--sp-2); border-radius: var(--radius-sm);
  }
  .site-nav__link { padding: var(--sp-3) var(--sp-4); } /* roomier tap targets in the drawer */
  .site-nav__disclosure {
    flex: 0 0 48px;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: 0;
    border-left: 1px solid var(--c-hairline);
    cursor: pointer; color: var(--c-navy);
  }
  .site-nav__disclosure::before {
    content: "";
    width: 8px; height: 8px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translateY(-2px);
    transition: transform var(--t-fast) var(--ease);
  }
  .site-nav__disclosure[aria-expanded="true"]::before { transform: rotate(-135deg) translateY(-2px); }
  .site-nav__sub {
    list-style: none; margin: 0;
    padding: var(--sp-1) 0 var(--sp-2);
    background: var(--c-surface-2);
    display: none;
  }
  .site-nav__item.open > .site-nav__sub { display: block; }
  .site-nav__sub a {
    display: block;
    padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-5);
    color: var(--c-ink);
    text-decoration: none;
    font-weight: var(--fw-reg);
    text-transform: none;
    letter-spacing: 0;
  }
  .site-nav__sub a:hover { color: var(--c-teal-dark); }

  .nav-overlay {
    position: fixed; inset: 0; z-index: 90;
    display: block;                       /* fix: overlay actually renders */
    background: rgba(2, 33, 63, 0.45);
    opacity: 0; pointer-events: none;
    transition: opacity var(--t-med) var(--ease);
  }
  .nav-overlay.active { opacity: 1; pointer-events: auto; }
}

/* --- >=1024px: persistent sidebar card; submenus FLY OUT right on hover, no arrows --- */
@media (min-width: 1024px) {
  .site-nav {
    background: var(--c-white);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    border: 1px solid var(--c-hairline);
    overflow: visible;                    /* let the fly-out escape the card */
  }
  .site-nav__list { border-radius: inherit; overflow: visible; }  /* NEVER hidden — it clips the fly-out */
  .site-nav__item { position: relative; }
  .site-nav__item:first-child .site-nav__link { border-radius: var(--radius) var(--radius) 0 0; }
  .site-nav__item:last-child  .site-nav__link { border-radius: 0 0 var(--radius) var(--radius); }
  /* Desktop first-level cells were too tall — halve the vertical padding (0.5rem→0.25rem) so
     the whole first-level block is ~15% shorter (~550px → ~470px). Roomy drawer taps (§ mobile)
     are unaffected; the fly-out panels keep their own padding. Verify no clip at 1024–1280px. */
  .site-nav__link { padding-top: var(--sp-1); padding-bottom: var(--sp-1); }
  .site-nav__close, .site-nav__disclosure, .nav-overlay { display: none; }

  .site-nav__sub {
    position: absolute; left: 100%; top: 0; min-width: 230px; margin: 0;
    list-style: none;
    background: var(--c-white);
    border: 1px solid var(--c-hairline);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lift);
    padding: var(--sp-2);
    z-index: 90;
    /* subtle fade/slide (killed globally by prefers-reduced-motion) */
    display: block;
    opacity: 0; visibility: hidden; pointer-events: none;
    transform: none;               /* fade only, no sideways slide */
    transition: opacity var(--t-fast) var(--ease),
                transform var(--t-fast) var(--ease),
                visibility 0s linear var(--t-fast);
  }
  /* Reveal on mouse HOVER, or on KEYBOARD focus only. Using :has(:focus-visible) instead of
     :focus-within is the fix for the two-panels-open bug: a right-click / mouse-click focuses
     the link (contextmenu path) but is NOT :focus-visible, so it no longer PINS the panel open
     while the mouse hovers another item. Keyboard Tab sets :focus-visible, so Tab still opens
     the panel. At most one panel is ever revealed. (JS in site.js blurs a mouse-focused child
     on mouseleave as a belt-and-braces fallback for browsers without :has()/:focus-visible.) */
  .site-nav__item:hover > .site-nav__sub,
  .site-nav__item:has(:focus-visible) > .site-nav__sub {
    opacity: 1; visibility: visible; pointer-events: auto;
    transition-delay: 0s;
  }
  .site-nav__item:hover > .site-nav__row .site-nav__link { background: var(--c-surface-2); color: var(--c-teal-dark); }
  .site-nav__sub li { margin: 0; }
  .site-nav__sub li + li { border-top: 1px solid var(--c-hairline); }   /* thin separators */
  .site-nav__sub a {
    display: block; padding: 7px 16px; line-height: 1.3;
    white-space: nowrap; text-decoration: none;
    color: var(--c-ink); font-weight: var(--fw-reg);
    text-transform: none; letter-spacing: 0;
    border-radius: 6px;
  }
  .site-nav__sub a:hover { text-decoration: none; color: var(--c-navy); background: var(--c-sand); }
  .site-nav__sub--dense a { padding-top: 5px; padding-bottom: 5px; }     /* long menus only */
}

/* Sidebar secondary module: Top 5 dive sites */
.top10-card {
  background: var(--c-white);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  margin-top: var(--sp-4);
  padding: var(--sp-4);
}
.top10-card h2 {
  /* TYPOGRAPHY unified with .content-card > h2 (A9): same display family + weight (inherited
     from the h1–h4 rule) and the same gold accent-bar underline, scaled to the sidebar card.
     The card width is unchanged — only the heading treatment is harmonised. */
  font-size: var(--fs-md);
  margin: 0 0 var(--sp-3);
}
.top10-card h2::after {
  content: ""; display: block; width: 40px; height: 2px;
  background: var(--c-gold);
  margin-top: var(--sp-2); opacity: 0.8;
}
.top10-card ul { list-style: none; margin: 0; padding: 0; font-size: var(--fs-sm); }
.top10-card li { padding: var(--sp-1) 0; border-bottom: 1px dashed var(--c-hairline); margin: 0; }
.top10-card li:last-child { border-bottom: 0; }
.top10-card a { text-decoration: none; transition: color var(--t-fast) var(--ease); }
.top10-card a:hover { text-decoration: none; color: #3f8fb8; }
@media (max-width: 1023px) { .shell__sidebar .top10-card { display: none; } }

/* ---------- 5. Content card (scuba depth) ---------- */
.content-card {
  position: relative;
  background: var(--c-white);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-6) var(--gutter) var(--sp-7);
}
@media (min-width: 1024px) { .content-card { padding: var(--sp-6) var(--sp-7) var(--sp-7); } }
.content-card > h1:first-child { margin-top: 0; }

/* Z3-3 (owner): on the inquiry forms the eyebrow carries the FUNCTION label ("Booking &
   enquiries") now that the h1 is the resort name alone, so it is stepped up one size to
   make the page's purpose read clearly. Scoped to the form hero — the site-wide eyebrow
   (course pages, resort pages) is unchanged. */
.eyebrow--form {
  font-size: var(--fs-sm);
  letter-spacing: 0.10em;
}

.eyebrow {
  display: block;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-gold-text);
  margin-bottom: var(--sp-3);
}
.wave-rule {
  border: 0; height: 14px; margin: var(--sp-6) 0;
  background: left/24px 14px repeat-x
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='14'%3E%3Cpath d='M0 7 q6 -7 12 0 t12 0' fill='none' stroke='%2312a3b8' stroke-width='1.6' opacity='0.4'/%3E%3C/svg%3E");
}
.section-tint {
  background: var(--c-sand);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-lg);
  padding: var(--sp-6) var(--gutter);
  margin: var(--sp-7) calc(-1 * var(--sp-2));
}
@media (min-width: 1024px) { .section-tint { padding: var(--sp-6); } }

/* ---------- 6. Buttons (semantic colours; hover = background only, text fixed) ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: var(--sp-2) var(--sp-5);
  border: 0;
  border-radius: var(--radius-btn);
  font: var(--fw-bold) var(--fs-base)/1.25 var(--font-sans);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background var(--t-fast) var(--ease),
              box-shadow var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
.btn:hover { box-shadow: var(--shadow); transform: translateY(-1px); }
.btn:active { transform: translateY(0); box-shadow: var(--shadow-sm); }
@media (max-width: 1023px) { .btn { font-size: var(--fs-btn-m); } }  /* mobile buttons 18px */

.btn .nw { white-space: nowrap; }                 /* controlled 2-line wrap */
.btn-sub { display: inline; }
@media (max-width: 767px) { .btn-sub { display: none; } }   /* hide "— online discount" suffix on mobile */
/* gold = book / enquiry */
.btn--cta {
  background: linear-gradient(180deg, #f7b165, var(--c-btn-book));
  color: var(--c-deep);
  border: 1px solid var(--c-btn-border);
  box-shadow: 0 2px 5px rgba(207,127,40,0.32), inset 0 1px 0 rgba(255,255,255,0.45);
}
.btn--cta:hover {
  background: linear-gradient(180deg, #f3a552, var(--c-btn-book-hover));
  color: var(--c-deep);
  box-shadow: 0 5px 12px rgba(207,127,40,0.42), inset 0 1px 0 rgba(255,255,255,0.45);
  transform: translateY(-1px);
}
/* blue = call */
.btn--tel { background: var(--c-btn-call); color: var(--c-deep); }
.btn--tel:hover { background: var(--c-btn-call-hover); color: var(--c-deep); }
/* green = WhatsApp */
.btn--whatsapp { background: var(--c-btn-wa); color: #fff; }
.btn--whatsapp:hover { background: var(--c-btn-wa-hover); color: #fff; }
/* utility variants */
.btn--primary { background: var(--c-teal); color: #fff; }
.btn--primary:hover { background: var(--c-teal-dark); color: #fff; }
.btn--ghost { background: transparent; color: var(--c-navy); border: 2px solid var(--c-navy); box-shadow: none; }
.btn--ghost:hover { background: var(--c-navy); color: #fff; }
.btn--block { display: flex; width: 100%; }

.contact-strip { display: grid; gap: var(--sp-3); margin: var(--sp-5) 0; }
@media (min-width: 600px) { .contact-strip { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); } }

/* ---------- 7. Hero (resort pages) ---------- */
.page-hero { margin-bottom: var(--sp-6); }
.page-hero h1 { margin-bottom: var(--sp-2); }
.page-hero__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-4);
}
.rating { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); color: var(--c-ink-soft); }
.rating__stars { color: var(--c-star); letter-spacing: 2.5px; font-size: 1.3rem; line-height: 1; }
.badge {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  background: var(--c-white); color: var(--c-navy);
  border: 1px solid var(--c-hairline-gold);
  border-radius: var(--radius-pill);
  padding: 3px 14px;
  font-size: var(--fs-sm); font-weight: var(--fw-semi);
}
.page-hero__media {
  /* HERO RULE v3 (owner): NEVER crop a hero. The image renders FULL (natural aspect) at
     the COLUMN width — upscaling to column width is ACCEPTED for heroes. No aspect box,
     no dark backing, no truncation. */
  position: relative;
  margin: 0 0 var(--sp-4);
  max-width: 100%;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow);
}
.page-hero__media img {
  width: 100%; height: auto; object-fit: unset; display: block;
  transition: transform 600ms var(--ease);
}
.page-hero__media:hover img { transform: scale(1.025); }
/* NATIVE hero mode (owner rule, site-wide): display at native size, NEVER upscale — fill the
   column only if the native width allows. Small sources render left-aligned at true pixel
   size with no aspect crop (cropping a small source caused the pixelation and the mobile
   dark-block: the aspect box's --c-deep backing showed beside the crop). */
.page-hero__media--native { /* obsolete under hero rule v3 */ }


.page-hero__media::after { display: none; }
.page-hero__lead { font-size: var(--fs-md); }
.page-hero__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-4); }
@media (max-width: 599px) { .page-hero__actions .btn { flex: 1 1 100%; } } /* Book btn may wrap to 2 lines */
@media (min-width: 600px) {
  .page-hero__actions { flex-wrap: nowrap; }
  .page-hero__actions .btn { flex: 1 1 auto; white-space: nowrap; }
}
/* the booking CTA is THE action on a resort page: prominent gold, soft glow */
.page-hero__actions .btn--cta { box-shadow: var(--glow-gold); }
.page-hero__actions .btn--cta:hover { box-shadow: var(--glow-gold); }
@media (min-width: 1024px) { .page-hero__actions .btn, .btn--lg { font-size: var(--fs-md); } }  /* Make-a-booking matches the hero Book button */

/* ---------- 8. Images & galleries (uniform tiles, gentle hover zoom, never upscale) ---------- */
.figure { margin: var(--sp-5) 0; }
/* Facing image pair (layout restoration): reproduces the original site's side-by-side
   left/right image placement (its <div class="imgvilla*"><div class="section" float> wrapper
   mechanism) with the new template's figure markup. Two figures per row, EQUAL HEIGHTS. */
.figure-pair { display: flex; flex-wrap: nowrap; gap: var(--sp-3); align-items: stretch; margin: var(--sp-5) 0; max-width: var(--pair-max, none); }
.figure-pair .figure { flex: var(--ar, 1) 1 0%; margin: 0; min-width: 0; }
.figure-pair .figure img { width: 100%; height: auto; display: block; }
@media (max-width: 767px) { .figure-pair { flex-direction: column; max-width: none; } }  /* W2C mobile release */
/* MOBILE IMAGE RULES (owner rulings, site-wide):
   1. ALL in-flow content images render CENTERED.
   2. EQUAL-SIZE for consecutive images (supersedes width:auto for pairs/runs): stacked
      figure-pair members and run members (figure--half) render at the SAME width — the
      COLUMN width — so a small-native member (e.g. the 279px rig shot) never renders
      smaller than the image above it. Lone in-prose singles keep natural centered size. */
@media (max-width: 1023px) {
  .content-card .figure--left, .content-card .figure--right, .content-card .figure--half {
    float: none; width: 100%; text-align: center;
  }
  .content-card .figure--left img, .content-card .figure--right img {
    width: 100%; height: auto; margin: 0 auto; display: block;   /* F2: column width, no exceptions */
  }
  .figure-pair .figure { text-align: center; }
  .figure-pair .figure img, .content-card .figure--half img {
    width: 100%; height: auto; margin: 0 auto; display: block;   /* column width, equal */
  }
}
.figure img {
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  display: block;
  margin: 0 auto;
  width: auto; max-width: 100%; height: auto;  /* HTML width attr = native cap; never upscale */
  transition: transform var(--t-med) var(--ease), box-shadow var(--t-med) var(--ease);
}
.figure:hover img { transform: scale(1.03); box-shadow: var(--shadow-lift); }  /* gentle zoom on all content images */
.figure figcaption { font-size: var(--fs-xs); color: var(--c-ink-soft); margin-top: var(--sp-2); text-align: center; }
/* pair of small legacy images shown at native size, side by side */
.figure-row {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-start;
  gap: var(--sp-4); margin: var(--sp-5) 0;
}
.figure-row img {
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
  display: block; width: auto; max-width: 100%; height: auto;
}

.gallery-grid { display: grid; gap: var(--sp-4); margin: var(--sp-4) 0 var(--sp-5); }
@media (min-width: 600px)  { .gallery-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1024px) { .gallery-grid--3 { grid-template-columns: 1fr 1fr 1fr; } }
.gallery-grid figure {
  margin: 0;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  aspect-ratio: 3 / 2;               /* uniform tiles: crop, never upscale */
  background: var(--c-tint-teal);
}
.gallery-grid img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  transition: transform 500ms var(--ease);
}
.gallery-grid figure:hover img { transform: scale(1.04); }

/* One slider sitewide: CSS scroll-snap (touch + mouse + keyboard) */
.snap-gallery {
  display: flex;
  gap: var(--sp-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  padding-bottom: var(--sp-2);
  margin: var(--sp-4) 0 var(--sp-5);
  scrollbar-color: var(--c-teal) transparent;
}
.snap-gallery > * {
  flex: 0 0 min(86%, 540px);
  scroll-snap-align: center;
  margin: 0;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.snap-gallery img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }

[data-full] { cursor: zoom-in; }     /* lightbox-ready hook (no JS yet) */

/* ---------- 9. Feature / spec / room cards ---------- */
.feature-grid {
  display: grid; gap: var(--sp-3);
  grid-template-columns: 1fr;                      /* mobile: 1 column */
  margin: var(--sp-4) 0 var(--sp-5);
  list-style: none; padding-left: 0;   /* usable as <ul> */
}
.feature-grid li { margin: 0; }
@media (min-width: 1024px) { .feature-grid { grid-template-columns: 1fr 1fr; } }  /* desktop: 2 */
.feature {
  display: flex; align-items: center; gap: var(--sp-3);
  background: var(--c-white);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius);
  padding: var(--sp-3);
  box-shadow: var(--shadow-sm);
  font-weight: var(--fw-semi); color: var(--c-navy); font-size: var(--fs-sm);
  transition: box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.feature:hover { box-shadow: var(--shadow); transform: translateY(-2px); }
.feature__icon {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(181,146,78,0.10); color: var(--c-gold-text);
}
.feature__icon .icon { width: 20px; height: 20px; vertical-align: 0; }

.spec-cards {
  display: grid; gap: var(--sp-3);
  grid-template-columns: 1fr;          /* single column, desktop AND mobile */
  margin: var(--sp-4) 0 var(--sp-5);
}
.spec {
  background: var(--c-surface-2);
  border: 1px solid var(--c-hairline);
  border-left: 2px solid var(--c-gold);
  border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4);
}
.spec__label {
  font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--c-ink-soft);
}
.spec__value {
  font-family: var(--font-display);
  font-size: var(--fs-md); font-weight: var(--fw-bold);
  color: var(--c-navy); line-height: 1.25;
}

.room-grid { display: grid; gap: var(--sp-4); grid-template-columns: 1fr; margin: var(--sp-4) 0 var(--sp-5); }
@media (min-width: 600px) { .room-grid { grid-template-columns: 1fr 1fr; } }
.room {
  background: var(--c-white);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column;
  transition: box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.room:hover { box-shadow: var(--shadow-lift); transform: translateY(-2px); }
.room__media { aspect-ratio: 4 / 3; background: var(--c-deep); overflow: hidden; }
.room__media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 500ms var(--ease); }
.room:hover .room__media img { transform: scale(1.04); }
.room__body { padding: var(--sp-3) var(--sp-4) var(--sp-4); }
.room__body h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-md); }
.room__body p { margin: 0; font-size: var(--fs-sm); color: var(--c-ink-soft); }

/* ---------- 10. Reviews & discounts ---------- */
.reviews { margin-top: var(--sp-7); }
.reviews__head {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2) var(--sp-4);
  border-top: 1px solid var(--c-hairline-gold);
  padding-top: var(--sp-7);
  margin-bottom: var(--sp-4);
}
.reviews__head h2 { margin: 0; }
.review {
  background: var(--c-surface-2);
  border: 1px solid var(--c-hairline);
  border-left: 2px solid var(--c-gold);
  border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-5);
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-base);           /* reviews readable >=17px */
}
.review p { margin: 0; }
.review cite { display: block; margin-top: var(--sp-2); font-style: normal; font-weight: var(--fw-semi); color: var(--c-navy); font-size: var(--fs-sm); }

.discount-box {
  background: linear-gradient(165deg, var(--c-navy), var(--c-deep));
  color: #fff;
  border: 1px solid var(--c-hairline-gold);
  border-radius: var(--radius-lg);
  padding: var(--sp-7) var(--sp-5);
  text-align: center;
  margin: var(--sp-8) 0 var(--sp-5);
  box-shadow: var(--shadow);
}
.discount-box h2::after {
  content: ""; display: block; width: 56px; height: 2px;
  background: var(--c-gold); margin: var(--sp-3) auto 0; opacity: 0.9;
}
.discount-box h2 { color: #fff; margin-top: 0; }
.discount-box p { color: rgba(255,255,255,0.85); max-width: 62ch; margin-left: auto; margin-right: auto; }  /* measure wide enough for the enquiry line in <=2 comfortable lines, no font tricks */
/* discount-box primary enquiry stays GOLD (calls blue, whatsapp green) */
/* discount-box CTA inherits the enriched .btn--cta gradient/border/shadow */

/* ---------- 11. Tables (keeps .tg + .spot_list compat) ---------- */
.table-scroll { overflow-x: auto; margin: var(--sp-4) 0; border-radius: var(--radius-sm); }
table, .tg { border-collapse: collapse; width: 100%; font-size: var(--fs-sm); }
th, td, .tg th, .tg td {
  border: 1px solid var(--c-hairline);
  padding: var(--sp-2) var(--sp-3);
  text-align: left;
  vertical-align: top;
  font-family: var(--font-sans);
}
th, .tg th { background: var(--c-sand); color: var(--c-navy); font-weight: var(--fw-bold); }
tr:nth-child(even) td { background: #fbf9f4; }
.spot_list, .spot_list_label, .spot_list-left, .spot_list_number { font-family: var(--font-sans); }
.spot_list_label { background: var(--c-sand); color: var(--c-navy); font-weight: var(--fw-bold); }
.spot_list-left strong { color: var(--c-navy); }
.spot_list_number { text-align: center; color: var(--c-ink-soft); }
.spot_list font { color: var(--c-ink-soft) !important; }

/* ---------- 12. Forms (Template C) ---------- */
.form-card { max-width: 560px; }
.form-field { margin-bottom: var(--sp-4); }
.form-field label {
  display: block;
  font-weight: var(--fw-semi);
  color: var(--c-navy);
  margin-bottom: var(--sp-1);
}
.form-field .hint { font-size: var(--fs-xs); color: var(--c-ink-soft); margin-top: var(--sp-1); }
.form-field input[type="text"],
.form-field input[type="email"],
.form-field input[type="tel"],
.form-field input[type="date"],
.form-field input[type="number"],
.form-field select,
.form-field textarea {
  width: 100%;
  font: var(--fw-reg) var(--fs-base)/1.4 var(--font-sans);
  color: var(--c-ink);
  background: var(--c-white);
  border: 1.5px solid var(--c-hairline);
  border-radius: var(--radius-sm);
  padding: var(--sp-3);
  min-height: 48px;
  appearance: none;
  -webkit-appearance: none;
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.form-field select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23003466' stroke-width='2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  padding-right: var(--sp-6);
}
.form-field textarea { min-height: 120px; resize: vertical; }
.form-field input:focus, .form-field select:focus, .form-field textarea:focus {
  border-color: var(--c-teal);
  box-shadow: 0 0 0 3px rgba(12, 143, 160, 0.16);
  outline: none;
}
.form-field input:user-invalid { border-color: var(--c-error); }
.form-row { display: grid; gap: var(--sp-4); }
@media (min-width: 600px) { .form-row { grid-template-columns: 1fr 1fr; } }
/* Build 14.4: long <option> text must never widen a grid column (page blowout
   on phones) — and every control, placeholder and option stays 17px. */
.form-row > .form-field, .form-field { min-width: 0; }
.form-field select { max-width: 100%; text-overflow: ellipsis; }
.form-field select option { font-size: 15px; }
@media (max-width: 1023px) {
  .form-field select { font-size: 16px; }          /* mobile: dropdown stays compact */
  .form-field select option { font-size: 15px; }
}
.form-field input::placeholder, .form-field textarea::placeholder { font-size: var(--fs-base); }

/* ---------- 13. Footer ---------- */
.site-footer {
  background: var(--c-deep);
  color: rgba(255,255,255,0.85);
  border-top: 1px solid var(--c-hairline-gold);
  margin-top: var(--sp-7);
}
.site-footer__inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: var(--sp-6) var(--gutter);
  text-align: center;
}
.site-footer__inner > p:first-child { font-size: 16px; }  /* ONLY the copyright line may be 16px */
.site-footer a { color: #fff; }
.site-footer__reg { font-size: var(--fs-xs); color: rgba(255,255,255,0.6); margin-top: var(--sp-2); }
.site-footer__social {
  display: flex; justify-content: center; gap: var(--sp-3);
  margin: var(--sp-4) 0 var(--sp-2);
}
.site-footer__social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px;
  border-radius: 50%;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fff;
  transition: background var(--t-fast) var(--ease);
}
.site-footer__social a:hover { background: var(--c-gold); }
.site-footer__social .icon { width: 20px; height: 20px; vertical-align: 0; }
.site-footer__lang { display: flex; justify-content: center; gap: var(--sp-2); margin-top: var(--sp-3); }
.footer-tawk-spacer { display: none; height: 88px; background: var(--c-sand); }
@media (max-width: 1023px) { .site-footer { margin-top: 0; } .footer-tawk-spacer { display: block; } }  /* beige clearance for the Tawk bubble; tighter top gap */

/* ---------- 14. Legacy compatibility layer ----------
   Existing page bodies use these classes; mapping here lets content
   pour into the new templates with minimal markup edits. */
.clr { clear: both; }
center { display: block; text-align: center; }
/* In-prose images get a larger BOTTOM margin (owner A7: too close to the following text) —
   consistent --sp-6 across every in-prose image wrapper, site-wide. */
.imgvilla, .imgvilla2, .imgvilla3 { float: none; margin: var(--sp-4) 0 var(--sp-6); }
.imgvilla img, .imgvilla2 img, .imgvilla3 img {
  width: 100%; height: auto; max-width: 540px;
  border: 0; border-radius: var(--radius); box-shadow: var(--shadow-sm);
  margin: 0;
}
.textandimg .text { padding: 0; color: inherit; font: inherit; line-height: inherit; }
.li-1-8 { line-height: 1.8; }
.sub_head h2 { margin-top: 0; }

.navigationWrapperResort { display: grid; gap: var(--sp-2); margin: var(--sp-3) 0 var(--sp-5); }
@media (min-width: 600px) { .navigationWrapperResort { grid-template-columns: 1fr 1fr; } }
.navigationWrapperResort a {
  display: flex; align-items: center; min-height: 48px;
  padding: var(--sp-2) var(--sp-4);
  background: var(--c-surface-2);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm);
  color: var(--c-navy);
  font-weight: var(--fw-semi);
  text-decoration: none;
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.navigationWrapperResort a:hover { border-color: var(--c-hairline-gold); background: var(--c-sand); box-shadow: var(--shadow-sm); }
.navigationWrapperResort a::after { content: "\2192"; margin-left: auto; color: var(--c-gold-text); }

.innerContactUsButton, a.makeBookingBtn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: var(--sp-2) var(--sp-5);
  background: var(--c-btn-book); color: var(--c-deep);
  border-radius: var(--radius-btn);
  font-weight: var(--fw-bold);
  text-decoration: none;
  box-shadow: var(--shadow-sm);
  margin: var(--sp-3) 0;
  border: 0; float: none;
  transition: background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.innerContactUsButton:hover, a.makeBookingBtn:hover { background: var(--c-btn-book-hover); color: var(--c-deep); box-shadow: var(--shadow); }
a.makeBookingBtn img { display: none; }   /* sprite image replaced by real text */

.socialButtonWrapper { display: grid; gap: var(--sp-3); margin: var(--sp-5) 0; }
@media (min-width: 600px) { .socialButtonWrapper { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); } }
.socialButtonWrapper a {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: 48px; padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius-btn);
  color: var(--c-deep); font-weight: var(--fw-bold); text-decoration: none;
  white-space: normal;
  box-shadow: var(--shadow-sm);
  transition: background var(--t-fast) var(--ease);
}
.socialButtonWrapper a.phoneIcon { background: var(--c-btn-call); }
.socialButtonWrapper a.phoneIcon:hover { background: var(--c-btn-call-hover); color: var(--c-deep); }
.socialButtonWrapper a.phoneIcon[href^="https://wa.me"] { background: var(--c-btn-wa); color: #fff; }
.socialButtonWrapper a.phoneIcon[href^="https://wa.me"]:hover { background: var(--c-btn-wa-hover); color: #fff; }
.socialButtonWrapper a.emailIcon { background: var(--c-teal); color: #fff; }
.socialButtonWrapper a.emailIcon:hover { background: var(--c-teal-dark); color: #fff; }
.socialButtonWrapper a::before { content: none; }   /* drop sprite icon */

.innerPagesWrapper { border: 0; background: transparent; box-shadow: none; padding: 0; margin: 0; overflow: visible; }
.singleBannerWrapper { display: block; margin: var(--sp-4) auto; border-radius: var(--radius); box-shadow: var(--shadow-sm); }

/* ---------- 15. Utilities ---------- */
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.text-center { text-align: center; }
.mt-0 { margin-top: 0; }
.back-to-top { display: block; text-align: right; font-size: var(--fs-sm); margin-top: var(--sp-5); }

/* ---------- Vertical rhythm (Part 1.6) ---------- */
.content-card > ul, .content-card > ol { margin-bottom: var(--sp-5); }
.content-card > .btn,
.content-card > .innerContactUsButton,
.content-card > a.makeBookingBtn { margin: var(--sp-2) 0 var(--sp-6); }
.content-card > h2 + .btn, .content-card > h3 + .btn { margin-top: var(--sp-3); }
.content-card > .snap-gallery, .content-card > .island-circles,
.content-card > .contact-card { margin-top: var(--sp-5); margin-bottom: var(--sp-6); }

/* ---------- Dive-stats: compact 2-card panel (count + conditions) ---------- */
.dive-stats {
  display: grid; grid-template-columns: 1fr; gap: var(--sp-4);
  margin: var(--sp-4) 0 var(--sp-5);
}
@media (min-width: 1024px) { .dive-stats { grid-template-columns: 1fr; } }  /* always ONE column, stacked */
.dive-stats__count {
  display: flex; flex-direction: row; align-items: center; justify-content: center; gap: var(--sp-3);
  background: #d8ecf3; color: var(--c-navy);  /* light blue */
  border-radius: var(--radius); padding: var(--sp-4) var(--sp-5); text-align: center;
}
.dive-stats__num {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(2.2rem, 1.8rem + 2vw, 2.9rem); line-height: 1; color: var(--c-navy);
}
.dive-stats__cap { font-size: var(--fs-base); letter-spacing: 0.04em; text-transform: uppercase; color: var(--c-navy); }
.dive-stats__list {
  margin: 0; background: var(--c-white); border: 1px solid var(--c-hairline);
  border-radius: var(--radius); padding: var(--sp-2) var(--sp-4);
}
.dive-stats__list > div {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-4);
  padding: var(--sp-3) 0; border-bottom: 1px solid var(--c-hairline);
}
.dive-stats__list > div:last-child { border-bottom: 0; }
.dive-stats__list dt { margin: 0; color: var(--c-ink-soft); font-size: var(--fs-base); }
.dive-stats__list dd { margin: 0; color: var(--c-navy); font-weight: var(--fw-semi); font-size: var(--fs-base); text-align: right; }

/* ---------- Mobile: permit bar moves inline, just above the Special Online Discounts card ---------- */
@media (max-width: 1023px) {
  .permit-bar:not(.permit-bar--inline) { display: none; }   /* hide the top permit bar on mobile */
}
.permit-bar--inline { display: none; border-bottom: 0; }    /* inline copy shows on mobile only */
@media (max-width: 1023px) {
  .permit-bar--inline { display: block; margin: calc(var(--sp-3) + 30px) 0 calc(var(--sp-4) - 30px); border-radius: var(--radius); border: 1px solid var(--c-hairline-gold); }
  .permit-bar--inline .permit-bar__inner { padding: var(--sp-3) var(--sp-4); }
}

/* ---------- Template C additions (Build 2) ---------- */
.inquiry-form { max-width: none; width: 100%; }   /* match the contact-card width (Build 13) */
.hp-field {                     /* honeypot: visually hidden, still in the DOM */
  position: absolute !important; left: -9999px !important;
  width: 1px; height: 1px; overflow: hidden; margin: 0;
}
.form-actions { margin-top: var(--sp-5); }
.form-actions .hint { text-align: center; margin-top: var(--sp-3); }
.form-field input[type="date"] { appearance: auto; -webkit-appearance: auto; } /* native picker UI */
.cf-turnstile { margin-bottom: var(--sp-4); }

/* ---------- Template D additions (Build 3) ---------- */
.confirm-card { text-align: initial; }
.confirm-mark {
  width: 72px; height: 72px; margin: var(--sp-2) auto var(--sp-4);
  border-radius: 50%;
  background: var(--c-tint-teal);
  border: 1px solid var(--c-hairline-gold);
  display: flex; align-items: center; justify-content: center;
  color: var(--c-teal-dark);
}
.confirm-mark .icon { width: 34px; height: 34px; stroke-width: 2.4; }
.confirm-emails {
  list-style: none; padding: 0; max-width: 420px;
  margin: var(--sp-4) auto var(--sp-5);
  text-align: center;
}
.confirm-emails li {
  margin: 0; padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--c-hairline);
  font-weight: var(--fw-semi);
}
.confirm-emails li:last-child { border-bottom: 0; }
.confirm-back { margin-top: var(--sp-7); }

/* ---------- Template A additions (homepage) ---------- */
/* A2 — intro line: centered kicker in the display font, gold middots + hairline */
.intro-line {
  text-align: center;
  font-family: 'Plus Jakarta Sans', var(--font-display), sans-serif;   /* Build 14.3: matches the resort-page H1 */
  font-size: var(--fs-md);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--c-navy);
  opacity: 0.92;
  margin: var(--sp-2) 0 var(--sp-5);
}
.intro-line__dot { color: var(--c-gold); margin: 0 0.45em; }
.intro-line__place { white-space: nowrap; }
.intro-line::after {
  content: ""; display: block;
  width: 72px; height: 1px;
  background: var(--c-hairline-gold);
  margin: var(--sp-3) auto 0;
}

/* A3 — hero FADE slider: cross-fade, dots only, zero scroll mechanics */
.hero-fade { max-width: 655px; margin: 0 auto var(--sp-5); }
.hero-fade__frame {
  position: relative;
  aspect-ratio: 655 / 333;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow);
  background: var(--c-deep);
}
.hero-fade__slide {
  position: absolute; inset: 0; margin: 0;
  opacity: 0;
  transition: opacity 700ms var(--ease);
}
.hero-fade__slide img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hero-fade__slide { pointer-events: none; }            /* stacked slides: only the...*/
.hero-fade__slide.is-active { opacity: 1; pointer-events: auto; }  /* ...visible one is clickable */
.hero-fade__slide.is-active img { cursor: zoom-in; }
.hero-fade__slide:first-child { opacity: 1; }             /* no-JS + reduced-motion baseline */
.hero-fade__slide.is-active ~ .hero-fade__slide:first-child { opacity: 0; }
.hero-fade__dots { display: flex; justify-content: center; gap: var(--sp-2); margin-top: var(--sp-3); }
.hero-fade__dot {
  width: 9px; height: 9px; border-radius: 50%;
  border: 0; padding: 0; cursor: pointer;
  background: var(--c-hairline);
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.hero-fade__dot[aria-current="true"] { background: var(--c-gold); transform: scale(1.25); }

/* A4 — Circular island PHOTO DISKS (brand signature).
   Assets: /images/circles/<island>-11.png — EXACTLY 180x360 = two stacked
   180x180 cells (top = resting disk, bottom = hover disk). The navy ring is
   part of the artwork; CSS adds the thin gold hairline, shadow and motion.
   Labels are live text below the disk — never baked into the image.
   (cycle_layer.png/webp sprite: retired — add to cleanup list.) */
.island-circles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 180px));
  gap: var(--sp-6) var(--sp-4);
  justify-content: center;
  margin: var(--sp-6) 0;
}
@media (min-width: 1024px) { .island-circles { grid-template-columns: repeat(4, 180px); } }
.island-circle { text-decoration: none; text-align: center; display: block;
  -webkit-tap-highlight-color: transparent; }   /* no blue tap rectangle on mobile */
.island-circle__disk {
  position: relative;
  display: block;
  width: 180px; max-width: 100%;
  aspect-ratio: 1;
  margin: 0 auto;
  border-radius: 50%;
  background-repeat: no-repeat;
  background-size: 100% 200%;
  background-position: top;                 /* cell 1: resting state */
  outline: 1px solid var(--c-hairline-gold);/* thin gold hairline around the navy ring */
  outline-offset: 2px;
  box-shadow: var(--shadow);
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.island-circle__disk::after {               /* cell 2: hover state, cross-fades in */
  content: ""; position: absolute; inset: 0;
  border-radius: 50%;
  background: inherit;
  background-position: bottom;
  opacity: 0;
  transition: opacity 350ms var(--ease);
}
/* image swap + lift on devices with a real pointer only — on touch the disk
   is a plain link (no swap before navigating) */
@media (hover: hover) {
  .island-circle:hover .island-circle__disk,
  .island-circle:focus-visible .island-circle__disk {
    transform: translateY(-4px) scale(1.02);
    box-shadow: var(--shadow-lift);
  }
  .island-circle:hover .island-circle__disk::after,
  .island-circle:focus-visible .island-circle__disk::after { opacity: 1; }
}
.island-circle--sipadan  .island-circle__disk { background-image: url('/images/circles/sipadan-11.png'); }
.island-circle--mabul    .island-circle__disk { background-image: url('/images/circles/mabul-11.png'); }
.island-circle--kapalai  .island-circle__disk { background-image: url('/images/circles/kapalai-11.png'); }
.island-circle--mataking .island-circle__disk { background-image: url('/images/circles/mataking-11.png'); }
.island-circle__label {
  display: block; margin-top: var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--fs-md); font-weight: 570; font-variation-settings: "opsz" 24;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--c-navy);
  line-height: 1.2;
}
.island-circle:hover .island-circle__label { color: var(--c-gold-text); }

/* ---------- Contact card (Parts 1.5, 1.7, 3) ---------- */
.contact-card {
  background: var(--c-surface-2);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  margin: var(--sp-6) 0;
}
.contact-card__title { margin: 0 0 var(--sp-4); text-align: center; }
.contact-card__grid {
  display: grid; gap: var(--sp-3);
  grid-template-columns: 1fr;
}
@media (min-width: 600px)  { .contact-card__grid { grid-template-columns: repeat(2, minmax(0,1fr)); } } /* two columns everywhere >=600 (owner request) */

/* icon button: ONE line always — icon carries the channel, label carries the number */
.cbtn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: 48px; padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius-btn);
  font: var(--fw-bold) var(--fs-base)/1.2 var(--font-sans);
  text-decoration: none;
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
  transition: background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.cbtn:hover { box-shadow: var(--shadow); }
.cbtn { min-width: 0; }
@media (max-width: 420px) { .cbtn { white-space: normal; } }  /* never clip numbers on tiny screens */
.cbtn .icon { width: 20px; height: 20px; flex: 0 0 auto; vertical-align: 0; }
@media (max-width: 1023px) { .cbtn { font-size: var(--fs-btn-m); } }
/* semantic colours — consistent on every row */
.cbtn--wa     { background: var(--c-btn-wa);   color: #fff; }
.cbtn--wa:hover     { background: var(--c-btn-wa-hover);   color: #fff; }
.cbtn--wechat { background: #07c160;           color: #fff; }
.cbtn--wechat:hover { background: #06a552;                 color: #fff; }
.cbtn--call, .cbtn--mobile { background: var(--c-btn-call); color: var(--c-deep); }
.cbtn--call:hover, .cbtn--mobile:hover { background: var(--c-btn-call-hover); color: var(--c-deep); }
.cbtn--email  { background: #275b86;  color: #fff; }   /* medium blue — lighter than navy, not pale */
.cbtn--email:hover  { background: #1f4a6e;  color: #fff; }


/* ---------- B1: responsive dive-site comparison table ----------
   Replaces the legacy fixed .spot_list layout (and the fixed-height
   .shortpage container): a real table >=1024px, stacked cards below.
   Cross-links inside cells are ordinary anchors (SEO preserved). */
.spot-table-wrap { margin: var(--sp-5) 0 var(--sp-6); }
.spot-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.spot-table caption {
  caption-side: top; text-align: left;
  font-weight: var(--fw-bold); color: var(--c-navy);
  padding-bottom: var(--sp-3);
}
.spot-table th, .spot-table td {
  border: 1px solid var(--c-hairline);
  padding: var(--sp-2) var(--sp-3);
  text-align: left; vertical-align: top;
}
.spot-table thead th { background: var(--c-sand); color: var(--c-navy); }
.spot-table tbody tr:nth-child(even) td { background: var(--c-surface-2); }
.spot-table tbody tr.is-current td { background: var(--c-tint-teal); }
.spot-table td[data-label="Dive site"] { font-weight: var(--fw-semi); }
@media (max-width: 1023px) {
  /* stacked cards: header row hidden, each cell labelled via data-label */
  .spot-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .spot-table, .spot-table tbody, .spot-table tr, .spot-table td { display: block; width: 100%; }
  .spot-table tr {
    border: 1px solid var(--c-hairline);
    border-radius: var(--radius);
    background: var(--c-white);
    box-shadow: var(--shadow-sm);
    margin-bottom: var(--sp-3);
    overflow: hidden;
  }
  .spot-table td { border: 0; border-bottom: 1px solid var(--c-hairline); display: flex; gap: var(--sp-3); }
  .spot-table td:last-child { border-bottom: 0; }
  .spot-table td::before {
    content: attr(data-label);
    flex: 0 0 42%;
    font-weight: var(--fw-semi);
    color: var(--c-ink-soft);
  }
  .spot-table tbody tr:nth-child(even) td { background: var(--c-white); }
}

/* ---------- B2: numbered course steps ---------- */
.steps { counter-reset: step; list-style: none; padding-left: 0; margin: var(--sp-4) 0 var(--sp-6); }
.steps > li {
  counter-increment: step;
  position: relative;
  padding: var(--sp-1) 0 var(--sp-4) 52px;
  margin: 0;
}
.steps > li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--c-tint-teal); color: var(--c-teal-dark);
  border: 1px solid var(--c-hairline);
  display: flex; align-items: center; justify-content: center;
  font-weight: var(--fw-bold);
}
.steps > li:not(:last-child)::after {
  content: ""; position: absolute; left: 17.5px; top: 40px; bottom: 0;
  width: 1px; background: var(--c-hairline);
}

/* ---------- B4: uniform photo grid (2 -> 3 -> 4 columns) ----------
   Fixed-aspect cover tiles so rows align; gentle hover zoom;
   [data-full] hook stays available for a future lightbox. */
.photo-grid {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(2, 1fr);
  margin: var(--sp-5) 0 var(--sp-6);
}
@media (min-width: 600px)  { .photo-grid { grid-template-columns: repeat(3, 1fr); gap: var(--sp-4); } }
@media (min-width: 1024px) { .photo-grid { grid-template-columns: repeat(4, 1fr); } }
.photo-grid figure {
  margin: 0;
  aspect-ratio: 1;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--c-tint-teal);
  box-shadow: var(--shadow-sm);
}
.photo-grid img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform 500ms var(--ease);
}
.photo-grid figure:hover img { transform: scale(1.05); }

/* ---------- B3 inclusions checklist + small utilities (Build 8) ---------- */
.checklist { list-style: none; padding-left: 0; margin: var(--sp-4) 0 var(--sp-6); display: grid; gap: var(--sp-2) var(--sp-5); }
@media (min-width: 600px) { .checklist { grid-template-columns: 1fr 1fr; } }
.checklist li { position: relative; padding-left: 30px; margin: 0; }
.checklist li::before {
  content: ""; position: absolute; left: 0; top: 0.35em;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--c-tint-teal);
  border: 1px solid var(--c-hairline);
}
.checklist li::after {
  content: ""; position: absolute; left: 4.5px; top: calc(0.35em + 3.5px);
  width: 7px; height: 4px;
  border-left: 2px solid var(--c-teal-dark);
  border-bottom: 2px solid var(--c-teal-dark);
  transform: rotate(-45deg);
}
.u-soft { color: var(--c-ink-soft); font-size: 0.9em; }
.room__body h2 { margin: 0 0 var(--sp-2); font-size: var(--fs-md); }  /* lodge cards keep verbatim heading levels */

/* ---------- Error pages 403/404 (Template A2) ---------- */
.error-hero { text-align: center; padding: var(--sp-5) 0 var(--sp-2); }
.error-hero__code {
  font-family: var(--font-display);
  font-size: clamp(4.5rem, 12vw, 7rem);
  font-weight: var(--fw-bold);
  line-height: 1;
  color: var(--c-navy);
  opacity: 0.14;
  letter-spacing: 0.02em;
}
.error-hero h1 { margin-top: calc(-0.6em); }
.error-hero .page-hero__lead { max-width: 52ch; margin-left: auto; margin-right: auto; }
.error-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-3); margin: var(--sp-5) 0 var(--sp-6); }
.error-links { text-align: center; font-size: var(--fs-sm); color: var(--c-ink-soft); }

/* ---------- Blog (Part B) ---------- */
.post-meta {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
  font-size: var(--fs-sm); color: var(--c-ink-soft);
  margin: calc(-1 * var(--sp-2)) 0 var(--sp-5);
}
.post-meta strong { color: var(--c-navy); font-weight: var(--fw-semi); }
.blog-card { text-decoration: none; }
.blog-card .room__body h2 { font-size: var(--fs-md); margin: 0 0 var(--sp-2); }
.blog-card .room__body p { color: var(--c-ink-soft); }
.blog-card .room__body .post-meta { margin: 0 0 var(--sp-2); font-size: var(--fs-xs); }
.blog-card .room__more { color: var(--c-gold-text); font-weight: var(--fw-semi); margin-top: var(--sp-3); display: inline-block; }

.post-toc { list-style: none; padding: var(--sp-3) var(--sp-4); margin: 0 0 var(--sp-5);
  background: var(--c-surface-2); border: 1px solid var(--c-hairline); border-radius: var(--radius); }
@media (min-width: 600px) { .post-toc { columns: 2; column-gap: var(--sp-6); } }
.post-toc li { margin: 0 0 var(--sp-1); break-inside: avoid; padding-left: 14px; position: relative; }
.post-toc li::before { content: ""; position: absolute; left: 0; top: 0.55em; width: 6px; height: 6px;
  border-radius: 50%; background: var(--c-gold); }
.post-toc a { text-decoration: none; }
.post-toc a:hover { color: var(--c-teal-dark); }

/* ---------- Gallery masonry (Build 11) ----------
   Column masonry per the reference look: equal-width columns, natural image
   heights, thin even gaps, CSS multi-column (no JS layout, no layout cost).
   Column counts map the directive to our sidebar shell (~830px main col):
   2 phone / 3 tablet / 5 desktop / 6 wide. */
.masonry { columns: 2; column-gap: 8px; margin: var(--sp-5) 0; column-fill: balance; }
@media (min-width: 600px)  { .masonry { columns: 2; } }
@media (min-width: 1024px) { .masonry { columns: 3; } }             /* ~265px columns; images fill for clean alignment */
/* desktop stays at 3 columns (owner request) */
.masonry figure {
  margin: 0 0 8px;
  break-inside: avoid;
  border-radius: var(--radius-sm, 6px);
  overflow: hidden;
  background: var(--c-tint-teal);
}
.masonry a.lb-item { display: block; outline-offset: 2px; }
.masonry img {
  width: 100%; height: auto; display: block;   /* fill column: equal widths, natural heights (reference look) */
  transition: transform 400ms var(--ease), filter 400ms var(--ease);
}
.masonry a.lb-item:hover img,
.masonry a.lb-item:focus-visible img { transform: scale(1.03); filter: brightness(1.06); }
@media (prefers-reduced-motion: reduce) {
  .masonry img { transition: none; }
  .masonry a.lb-item:hover img { transform: none; }
}
/* progressive reveal: JS adds is-collapsed + marks extras; no JS = all visible */
.masonry.is-collapsed .masonry--extra { display: none; }
.masonry-more {
  display: block; margin: var(--sp-2) auto var(--sp-6);
}

/* ---------- Lightbox (shared, vanilla) ---------- */
.lb {
  position: fixed; inset: 0; z-index: 200;
  /* transparent BLUR — the page stays visible behind, no blue wash */
  background: rgba(18, 22, 26, 0.28);
  -webkit-backdrop-filter: blur(14px) saturate(0.85);
  backdrop-filter: blur(14px) saturate(0.85);
  display: flex; align-items: center; justify-content: center;
  padding: 4vh 4vw;                     /* guaranteed click-outside space */
  opacity: 0; pointer-events: none;
  transition: opacity 200ms var(--ease);
}
.lb.is-open { opacity: 1; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .lb { transition: none; } }
/* 2026 popup: DARK-BLUE panel that shrink-wraps the image — the caption's
   left/right edges line up exactly with the image edges. Never full width. */
.lb__panel {
  position: relative;
  background: linear-gradient(170deg, #12314e, var(--c-deep));
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--radius);
  box-shadow: 0 30px 80px rgba(4,12,20,0.55);
  padding: var(--sp-3);
  width: fit-content;                  /* panel hugs the (native-capped) image */
  max-width: min(82vw, 1040px);
  max-height: 86vh;
  display: flex; flex-direction: column;
}
@media (max-width: 599px) { .lb__panel { max-width: 92vw; } }
.lb__img {
  /* never upscale in the popup: cap at the image's own pixel size, so small
     images sit at natural size with dark-blue padding around them (no blur/
     pixelation). Large images are bounded by the viewport instead. */
  max-width: min(100%, var(--lb-natw, 100%));
  max-height: calc(86vh - 110px);
  width: auto; height: auto;
  display: block; margin: 0 auto;        /* centered when panel is wider (small image) */
  border-radius: calc(var(--radius) - 6px);
}
/* give small images a comfortable minimum panel so the dark-blue frame reads
   as intentional padding, not a cramped border */
/* caption/panel width: hug the image, but never below a readable 400px floor
   (small images => caption wraps to several lines instead of a wide strip) */
.lb__panel { min-width: min(400px, 90vw); align-items: stretch; }
.lb__caption {
  padding: var(--sp-3) var(--sp-1) var(--sp-1);
  color: #dfe9f2;
  font-size: var(--fs-sm); line-height: 1.55;
  text-align: left;         /* flush with the image edges */
  /* KEY: takes the panel width (image-driven) WITHOUT widening it —
     without this, a long one-line caption stretches the whole panel. */
  width: 0;
  min-width: 100%;
}
/* content images anywhere open the same lightbox */
.content-card figure img { cursor: zoom-in; }
.lb__btn {
  position: absolute;
  width: 46px; height: 46px; border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.45);   /* thin outline only */
  background: transparent; color: #fff;        /* fully transparent disk */
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.lb__btn:hover, .lb__btn:focus-visible { background: transparent; border-color: rgba(255,255,255,0.9); }
.lb__btn svg { width: 20px; height: 20px; }
/* Build 14.6/14.7: buttons anchor to the PANEL (dark card), never the
   viewport. Desktop: the card gets a generous frame — a top band for the X
   and side gutters for the arrows — so every control rests comfortably on
   the dark blue, clear of both the photo and the blurred page behind.
   Mobile: tighter frame, controls at 50% size. */
.lb__btn { z-index: 2; }
.lb__close { top: 10px; right: 10px; }
.lb__prev { left: 10px; top: calc(50% - 20px); transform: translateY(-50%); }
.lb__next { right: 10px; top: calc(50% - 20px); transform: translateY(-50%); }
@media (min-width: 1024px) {
  .lb__panel { padding: 64px 78px 14px; }        /* room above for X, gutters for arrows */
  .lb__img { max-height: calc(86vh - 170px); }   /* keep the taller frame inside 86vh */
  .lb__close { top: 12px; right: 14px; width: 24px; height: 24px; }
  .lb__close svg { width: 12px; height: 12px; }
  .lb__prev { left: 16px; top: 50%; }
  .lb__next { right: 16px; top: 50%; }
  .lb__caption { padding-top: var(--sp-3); }
}
@media (max-width: 1023px) {
  .lb__btn { width: 24px; height: 24px; }
  .lb__btn svg { width: 12px; height: 12px; }
  .lb__close { top: 8px; right: 8px; }
  .lb__prev { left: 8px; }
  .lb__next { right: 8px; }
}

/* ---------- Video gallery: 16:9 matrix + YouTube facade ---------- */
.video-grid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(2, 1fr);
  margin: var(--sp-5) 0;
}
@media (min-width: 600px)  { .video-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .video-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1440px) { .video-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 479px)  { .video-grid { grid-template-columns: 1fr; } }
.video-cell {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm, 6px);
  overflow: hidden;
  background: var(--c-deep);
}
.video-cell iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.yt-facade {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border: 0; padding: 0; cursor: pointer; background: var(--c-deep);
}
.yt-facade img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 400ms var(--ease); }
.yt-facade:hover img, .yt-facade:focus-visible img { transform: scale(1.04); }
@media (prefers-reduced-motion: reduce) { .yt-facade img { transition: none; } .yt-facade:hover img { transform: none; } }
.yt-facade::after {  /* darken for play-button contrast */
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(circle at center, rgba(10,31,51,0.15), rgba(10,31,51,0.35));
}
.yt-facade__play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
  width: 58px; height: 42px; border-radius: 10px;
  background: rgba(14,42,68,0.85);
  display: flex; align-items: center; justify-content: center;
  z-index: 1;
  transition: background var(--t-fast) var(--ease);
}
.yt-facade:hover .yt-facade__play, .yt-facade:focus-visible .yt-facade__play { background: var(--c-cta); }
.yt-facade__play svg { width: 20px; height: 20px; fill: #fff; margin-left: 2px; }
.video-cell__label {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  padding: var(--sp-2) var(--sp-3);
  background: linear-gradient(transparent, rgba(10,31,51,0.8));
  color: #fff; font-size: var(--fs-sm); text-align: left;
  pointer-events: none;
}

/* ---------- Inquiry form v2 (Build 12) ---------- */
.form-row { display: grid; gap: var(--sp-3) var(--sp-4); grid-template-columns: 1fr; }
@media (min-width: 600px) { .form-row { grid-template-columns: 1fr 1fr; } }
.form-field .req { color: var(--c-error); }
.form-hint { font-size: var(--fs-sm); color: var(--c-ink-soft); margin: var(--sp-1) 0 0; }
.form-alert {
  background: #fbf3f1;
  border: 1px solid #e5b8b2;      /* softer than the full error red */
  border-radius: var(--radius-sm);
  color: #7c1d17;
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-5);
}
.form-alert__line1 { display: block; font-weight: var(--fw-semi); font-size: var(--fs-base); }  /* matches the field labels */
.form-alert__line2 { display: block; margin-top: var(--sp-1); font-size: var(--fs-base); }
.form-field.is-invalid label { color: var(--c-error); }
.form-field.is-invalid input, .form-field.is-invalid select, .form-field.is-invalid textarea {
  border-color: var(--c-error);
  box-shadow: 0 0 0 3px rgba(179,38,30,0.12);
}
.form-submit { width: 100%; margin-top: var(--sp-4); }
.form-page .contact-card { max-width: none; }   /* full main-column width on form pages */
/* form matches the contact card width: both span the full main column */
.form-page .inquiry-form,
.form-page .form-card { max-width: none; width: 100%; }
/* native selects: full width from the shared input rule; open direction is
   the browser's (downward whenever there's room below). */

/* ---------- Spec cards (Build 12): label on top, value below, left-aligned ---------- */
.spec-cards { display: grid; gap: var(--sp-3); grid-template-columns: 1fr; margin: var(--sp-4) 0 var(--sp-6); }
@media (min-width: 600px) { .spec-cards { grid-template-columns: 1fr 1fr; } }
.spec-card {
  background: var(--c-surface-2);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius);
  padding: var(--sp-4);
}
.spec-card dt {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  color: var(--c-navy);
  margin: 0 0 var(--sp-2);
}
.spec-card dd { margin: 0; text-align: left; color: var(--c-ink); }
/* B: bordered inclusions card */
.checklist--card {
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius);
  background: var(--c-surface-2);
  padding: var(--sp-4) var(--sp-5);
}

/* ---------- Rooms: carousel cards + details modal (Build 13) ---------- */
.rooms-grid { display: grid; gap: var(--sp-5); grid-template-columns: 1fr; margin: var(--sp-5) 0; align-items: stretch; }
@media (min-width: 700px) { .rooms-grid { grid-template-columns: repeat(var(--rooms-cols, 2), minmax(0,1fr)); } }
.room-card {
  background: var(--c-white);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column;
  height: 100%;
}
.room-card__body { padding: var(--sp-4) var(--sp-4) var(--sp-5); display: flex; flex-direction: column; flex: 1; }
.room-card__name { font-family: var(--font-display); font-size: var(--fs-lg); color: var(--c-navy); margin: 0 0 var(--sp-2); }
.room-card__meta { color: var(--c-ink-soft); font-size: var(--fs-sm); margin: 0 0 var(--sp-3); }

/* carousel: track of full-width slides, arrows + dots, swipe via JS */
.carousel { position: relative; overflow: hidden; background: var(--c-tint-teal); }
.carousel__track { display: flex; transition: transform 380ms var(--ease); }
@media (prefers-reduced-motion: reduce) { .carousel__track { transition: none; } }
.carousel__slide { flex: 0 0 100%; }
.carousel__slide img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; display: block; cursor: zoom-in; }
.carousel__btn {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 38px; height: 38px; border-radius: 50%;
  border: 0; cursor: pointer;
  background: rgba(14,42,68,0.55); color: #fff;
  display: flex; align-items: center; justify-content: center;
  backdrop-filter: blur(4px);
  transition: background var(--t-fast) var(--ease);
}
.carousel__btn:hover, .carousel__btn:focus-visible { background: var(--c-teal-dark); }
.carousel__btn svg { width: 17px; height: 17px; }
.carousel__btn--prev { left: 10px; }
.carousel__btn--next { right: 10px; }
.carousel__dots { position: absolute; left: 0; right: 0; bottom: 10px; display: flex; gap: 7px; justify-content: center; }
.carousel__dot { width: 8px; height: 8px; border-radius: 50%; border: 0; padding: 0; cursor: pointer; background: rgba(255,255,255,0.55); }
.carousel__dot[aria-current="true"] { background: #fff; width: 22px; border-radius: 99px; }
.carousel--single .carousel__btn, .carousel--single .carousel__dots { display: none; }

/* details modal: same transparent-blur backdrop language as the lightbox */
.modal {
  position: fixed; inset: 0; z-index: 210;
  background: rgba(18,22,26,0.28);
  -webkit-backdrop-filter: blur(14px) saturate(0.85);
  backdrop-filter: blur(14px) saturate(0.85);
  display: flex; align-items: center; justify-content: center;
  padding: 4vh 4vw;
  opacity: 0; pointer-events: none;
  transition: opacity 200ms var(--ease);
}
.modal.is-open { opacity: 1; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .modal { transition: none; } }
.modal__panel {
  background: var(--c-white);
  border-radius: var(--radius);
  box-shadow: 0 30px 80px rgba(4,12,20,0.45);
  max-width: min(92vw, 560px);
  max-height: 86vh; overflow-y: auto;
  padding: var(--sp-5) var(--sp-5) var(--sp-6);
  position: relative;
}
.modal__close {
  position: absolute; top: 12px; right: 12px;
  width: 38px; height: 38px; border-radius: 50%;
  border: 1px solid var(--c-hairline); background: var(--c-white);
  color: var(--c-navy); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.modal__close:hover { background: var(--c-sand); }
.modal__title { font-family: var(--font-display); color: var(--c-navy); margin: 0 var(--sp-6) var(--sp-3) 0; }

/* amenity / facility / stat icon lines */
.iconline { width: 21px; height: 21px; flex: 0 0 21px; color: var(--c-teal-dark); }
.amenities { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: grid; gap: var(--sp-2); }
@media (min-width: 480px) { .amenities--2col { grid-template-columns: 1fr 1fr; } }
.amenities li { display: flex; align-items: center; gap: var(--sp-2); }
/* Room-modal amenity list, ONE column with real bullets (owner feedback d — the icon-less
   2-column grid read as two empty columns). */
.amenities--bullets { display: block; list-style: disc; padding-left: 1.3em; }
.amenities--bullets li { display: list-item; margin: 0 0 var(--sp-1); }
/* Photo-less room types render as SMALL cards until their photos arrive (owner feedback c). */
.room-card--compact .room-card__body { padding: var(--sp-3) var(--sp-3) var(--sp-4); }
.room-card--compact .room-card__name { font-size: var(--fs-md); }
.rooms-grid--compact { gap: var(--sp-3); }
@media (max-width: 767px) { .rooms-grid, .rooms-grid--compact { grid-template-columns: 1fr !important; } }
/* Run-trailing single image sized to MATCH its pair row above (owner feedback b —
   seaventures-diving-sabah aligns at the pair members' width, not its small native size). */
.figure--half { width: calc(50% - var(--sp-3) / 2); }
.figure--half img { width: 100%; height: auto; }
@media (max-width: 1023px) { .figure--half { width: 100%; } }
.iconlist { list-style: none; padding: 0; }
.iconlist li { display: flex; align-items: center; gap: var(--sp-2); margin: 0 0 var(--sp-2); }
.dive-stats__list dt { display: flex; align-items: center; gap: var(--sp-2); }

/* fully-clickable cards (stretched link) — add .room--link to the card;
   the FIRST .stretch link inside covers the whole card. Cards without a
   link yet: add data-href later or just leave — nothing breaks. */
.room--link { position: relative; transition: box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); }
.room--link:hover { box-shadow: var(--shadow-lift); transform: translateY(-2px); }
.room--link { cursor: pointer; }
.room--link a.stretch::after { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: inherit; }
.room--link a.stretch { position: static; }  /* the ::after (not the inline box) is the hit area */

/* Build 14.3: link lists (Table of Contents, resort lists) read cleaner
   without underlines; underline returns on hover/focus. Paragraph links
   keep their underline. */
.content-card li a { text-decoration: none; }
.content-card li a:hover, .content-card li a:focus-visible { text-decoration: underline; }

/* Build 14.3: breathing room below content tables (e.g. the permit-stay table) */
.content-card table { margin-bottom: var(--sp-6); }

/* footer: reg line last + breathing room above the flags */
.site-footer__lang { margin-top: var(--sp-5); }
@media (max-width: 1023px) {
  .site-footer__reg .reg-sep { display: none; }
  .site-footer__reg .reg-lic { display: block; margin-top: 2px; }
}
.site-footer__reg { margin-top: var(--sp-4); font-size: 16px; opacity: 0.85; }
/* header flags: more separation from the CTA */
.header-flags { margin-top: var(--sp-3); }
/* junk-folder note under the send button (smaller by owner request) */
.form-hint--small { font-size: 16px; }


/* ============================================================
   Build 14 — animated homepage header "Reef Channel" (FR5).
   Pages opt in with  $HEADER_SCENE = true;  before header.php.
   Pure CSS/SVG, transform/opacity only, reduced-motion safe.
   ============================================================ */
.site-header--scene {
  position: relative;   /* overflow moved to .hscene so dropdowns can escape */
  /* daylight sea: light enough for the navy logo to pop, deep enough for the
     silhouettes to read (Build 14.2, owner request) */
  background: linear-gradient(180deg,#8ecbdd 0%,#3f9fb7 62%,#136f86 150%);
}
.site-header--scene .site-header__inner { position: relative; z-index: 6; }
.site-header--scene .hscene { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
/* CTA hover (scene pages): the button brightens slightly and casts a soft
   diffuse glow into the water around it — no sweep (Build 14.3). */
.site-header--scene .site-header__cta {
  transition: background var(--t-fast) var(--ease), box-shadow .4s var(--ease), filter .4s var(--ease);
}
.site-header--scene .site-header__cta:hover {
  filter: brightness(1.04);
  box-shadow: 0 0 18px 4px rgba(255,214,150,.28), 0 0 46px 18px rgba(240,160,74,.18);
}
/* sun rays */
.hscene .ray { position:absolute; top:-30%; width:130px; height:160%;
  background:linear-gradient(180deg,rgba(255,255,255,.5),rgba(255,255,255,0) 75%);
  transform:skewX(-18deg); filter:blur(6px); animation:hs-ray 16s ease-in-out infinite; z-index:1; }
.hscene .r1 { left:14%; }
.hscene .r2 { left:38%; width:90px;  animation-duration:19s; animation-delay:-6s; }
.hscene .r3 { left:63%; width:150px; animation-duration:23s; animation-delay:-11s; }
@keyframes hs-ray { 0%,100% { opacity:.25; transform:skewX(-18deg) translateX(0) scaleX(1); }
  50% { opacity:.6; transform:skewX(-15deg) translateX(26px) scaleX(1.18); } }
/* bubbles */
.hscene .bub { position:absolute; bottom:-12px; border-radius:50%;
  border:1px solid rgba(255,255,255,.85); background:rgba(255,255,255,.3);
  animation:hs-rise linear infinite; z-index:2; }
@keyframes hs-rise { 0% { transform:translateY(0); } 12% { opacity:.85; }
  100% { transform:translateY(-235px) translateX(14px); opacity:0; } }
/* reef band */
.hscene .reef { position:absolute; left:0; right:0; bottom:0; width:100%; height:84px; z-index:4;
  opacity:.55;   /* corals stay atmospheric — never compete with the CTA */ }
/* pages without the permit bar (forms etc): the flags sit under the CTA, so
   the corals drop to a whisper (owner: ~90% transparent) */
.site-header--no-permit .hscene .reef { opacity: .1; }
/* creatures */
.hscene .creature { position:absolute; z-index:3; opacity:0; }
.hscene .fl { transform-origin:60px 42px; }
.hscene .fA { animation:hs-sweep 3.1s ease-in-out infinite; }
.hscene .fB { animation:hs-sweep 3.1s ease-in-out infinite reverse; }
@keyframes hs-sweep { 0%,100% { transform:rotate(-9deg); } 50% { transform:rotate(11deg); } }
.hscene .tail { transform-origin:32px 52px; animation:hs-beat 2.3s ease-in-out infinite; }
.hscene .pec  { transform-origin:92px 56px; animation:hs-beat 2.3s ease-in-out infinite reverse; }
@keyframes hs-beat { 0%,100% { transform:rotate(-6deg); } 50% { transform:rotate(7deg); } }
@keyframes hs-swimR { 0% { transform:translateX(-120px) translateY(0); opacity:0; } 5% { opacity:1; }
  45% { transform:translateX(44vw) translateY(6px); } 82% { opacity:1; }
  94% { transform:translateX(104vw) translateY(-2px); opacity:0; } 100% { transform:translateX(104vw); opacity:0; } }
@keyframes hs-swimL { 0% { transform:translateX(104vw) scaleX(-1) translateY(0); opacity:0; } 5% { opacity:1; }
  50% { transform:translateX(46vw) scaleX(-1) translateY(-6px); } 86% { opacity:1; }
  96% { transform:translateX(-130px) scaleX(-1); opacity:0; } 100% { transform:translateX(-130px) scaleX(-1); opacity:0; } }
/* barracudas: half-transparent silhouettes cruising left -> right */
.hscene .barr-lane { position:absolute; inset:0; pointer-events:none; }
.hscene .barrs { position:absolute; width:220px; height:84px; bottom:84px; opacity:0;
  animation:hs-swimR 34s linear infinite; animation-delay:-6s; }
.hscene .barrs .sf { opacity:.5; animation-name:hs-wig; }
.hscene .fish { position:absolute; background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 34 10'%3E%3Cpath fill='%23081a2c' d='M32 5 L25 1 q-13 -2 -20 3 l-3 -3 v8 l3 -3 q7 5 20 3z'/%3E%3C/svg%3E") center/contain no-repeat;
  offset-rotate:auto; offset-distance:0%;
  animation-name:hs-orbit; animation-timing-function:linear; animation-iteration-count:infinite;
  opacity:.9; z-index:3; }
@keyframes hs-orbit { from { offset-distance:0%; } to { offset-distance:100%; } }
@supports not (offset-path:path('M0 0 h1')) { .hscene .fish { display:none; } }
.hscene .shoal { position:absolute; width:200px; height:66px; z-index:3; opacity:0; }
.hscene .sf { position:absolute; background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 34 10'%3E%3Cpath fill='%23081a2c' d='M32 5 L25 1 q-13 -2 -20 3 l-3 -3 v8 l3 -3 q7 5 20 3z'/%3E%3C/svg%3E") center/contain no-repeat;
  animation:hs-wig 2.6s ease-in-out infinite; }
@keyframes hs-wig { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-4px); } }
/* FR5 placement: traffic through the channel, tornado beyond the right bommie */
.hscene .shA { bottom:80px;  animation:hs-swimR 32s linear infinite; }
.hscene .shB { bottom:108px; animation:hs-swimR 40s linear infinite; animation-delay:-16s; filter:opacity(.65); }
.hscene .tA  { bottom:70px;  animation:hs-swimL 46s linear infinite; animation-delay:-12s; filter:opacity(.7); }
.hscene .bA  { bottom:100px; animation:hs-swimL 50s linear infinite; animation-delay:-26s; filter:opacity(.4); }
.hscene .bB  { bottom:116px; animation:hs-swimL 50s linear infinite; animation-delay:-29s; filter:opacity(.3); }
.hscene .bC  { bottom:84px;  animation:hs-swimL 50s linear infinite; animation-delay:-31.4s; filter:opacity(.25); }
/* MOBILE (<1024px, where the hamburger + mini flags live): the full scene is
   desktop-only. Phones get a bright DAYTIME sea — light enough that the navy
   menu icon and the language flags read clearly — with just the bubbles. */
@media (max-width: 1023px) {
  .site-header--scene {
    background: linear-gradient(180deg,#8fc9db 0%,#48a3ba 55%,#1c7f97 140%);
  }
  .hscene .reef,
  .hscene .shoal,
  .hscene .creature { display: none; }
  /* sun rays stay on mobile — thinner than desktop */
  .hscene .ray { width: 58px; }
  .hscene .r2 { width: 40px; }
  .hscene .r3 { width: 68px; }
  /* no bubbles near the menu icon (right side) */
  .hscene .bub:nth-of-type(5), .hscene .bub:nth-of-type(6) { display: none; }
  .hscene .bub {
    border-color: rgba(255,255,255,.85);
    background: rgba(255,255,255,.3);
  }
  /* a few barracudas cruising right -> left, heavily transparent; the static
     lane's mask fades them further (70-90%) under the logo and the menu icon */
  .hscene .barrs {
    display: block; height: 56px; bottom: auto; top: 30px; width: 170px;
    animation: hs-swimL-m 15s linear infinite;   /* ~12s crossing + ~3s pause */
  }
  .hscene .barrs .sf { opacity: .35; }
  /* fish sprite faces right; flip so heads lead the right->left cruise */
  .hscene .barrs .sf:nth-child(n+6) { display: none; }   /* just a few */
  .hscene .barr-lane {
    -webkit-mask-image: linear-gradient(90deg,
      rgba(0,0,0,.10) 0%, rgba(0,0,0,.10) 22%,
      rgba(0,0,0,.55) 42%, rgba(0,0,0,.55) 58%,
      rgba(0,0,0,.10) 80%, rgba(0,0,0,.10) 100%);
    mask-image: linear-gradient(90deg,
      rgba(0,0,0,.10) 0%, rgba(0,0,0,.10) 22%,
      rgba(0,0,0,.55) 42%, rgba(0,0,0,.55) 58%,
      rgba(0,0,0,.10) 80%, rgba(0,0,0,.10) 100%);
  }
}
@keyframes hs-swimL-m {
  0%   { transform: translateX(104vw) scaleX(-1); opacity: 0; }
  3%   { opacity: 1; }
  76%  { opacity: 1; }
  80%  { transform: translateX(-190px) scaleX(-1); opacity: 0; }
  100% { transform: translateX(-190px) scaleX(-1); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .hscene .ray, .hscene .bub, .hscene .fish, .hscene .sf,
  .hscene .creature, .hscene .fl, .hscene .fA, .hscene .fB,
  .hscene .tail, .hscene .pec { animation:none !important; }
  .hscene .creature, .hscene .shoal { opacity:.85; }
}

/* Build 14.5: the form submit uses the IDENTICAL font shorthand as .cbtn */
.inquiry-form .form-submit { font: var(--fw-bold) var(--fs-base)/1.2 var(--font-sans); }

/* Build 14.7: left-aligned figure variant (contact page QR code) */
.figure--left { text-align: left; }
.figure--left img { margin-left: 0; }

/* Build 14.8: free-standing contact button rows (contact-details page).
   Flex-wrap with gap on BOTH axes so buttons never touch, desktop or mobile. */
.contact-card__btns {
  display: flex; flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-3);
  margin: var(--sp-2) 0 var(--sp-4);
}

/* Build 14.8: license sub-sub-title under Our office (contact page) */
.office-license { font-size: var(--fs-md); margin-top: var(--sp-4); margin-bottom: var(--sp-1); }

/* 7a — CTA button row: CTAs sit on their OWN line below the sentence, never inline.
   WhatsApp button sits beside the first CTA (resort pages). Wrap/stack on mobile. */
.cta-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; margin: var(--sp-4) 0; }
@media (max-width: 559px) { .cta-row .btn { flex: 1 1 100%; } }

/* ---------- 7c: resort cards on packages / resort-list pages ----------
   A main image under each resort title, clickable through to the resort page.
   Card box is fixed so the cards align; object-fit does the fitting.          */
.resort-thumb {
  display: block;
  width: 300px; max-width: 100%;
  margin: var(--sp-2) 0 var(--sp-4);
  border-radius: var(--radius-sm, 6px);
  overflow: hidden;
  background: var(--c-tint-teal);
}
.resort-thumb img {
  width: 100%; height: 170px; display: block;
  object-fit: cover;                 /* consistent card size across every resort */
  transition: transform 400ms var(--ease), filter 400ms var(--ease);
}
/* Sources smaller than the 300x170 card: cover would ENLARGE them past their
   natural size. scale-down never upscales and keeps the card box identical.  */
.resort-thumb img.is-small { object-fit: scale-down; }
.resort-thumb:hover img,
.resort-thumb:focus-visible img { transform: scale(1.03); filter: brightness(1.06); }
@media (prefers-reduced-motion: reduce) {
  .resort-thumb img { transition: none; }
  .resort-thumb:hover img { transform: none; }
}
/* Link-run underline rule: a short run (1-3) reads as prose and is underlined;
   a long run (>=4) reads as a menu, where underlining every item is noise.    */
.link-run a { text-decoration: none; }
.link-run--u a:not(.resort-thumb) { text-decoration: underline; }
.link-run .resort-thumb { text-decoration: none; }
/* .navigationWrapper (some FR pages) had NO styling at all — bare inline anchors.
   Keep it in flow so each title stacks above its own thumb (a grid would split
   the title and its image into separate cells).                               */
.navigationWrapper a:not(.resort-thumb) {
  display: block;
  font-weight: var(--fw-semi);
  margin-top: var(--sp-3);
}


/* ================= ITEM 7d / 7e / 7g — legacy imgvilla* content images =================
   PAIR-DETECTION (owner's rule, encoded as CSS so no markup changes and no churn in the
   translated twins): a PAIR is an .imgvilla3 whose .section carries float:left immediately
   followed by an .imgvilla3 whose .section does NOT. .imgvilla wraps both sections in one
   div instead. An UNPAIRED imgvilla3 is a single image and is treated like imgvilla2.   */

/* ---- 7d: paired images render at EQUAL height on desktop (widths may differ) ----
   The old markup gave both images of a pair height="195" — that is the parity to restore.
   The generic rule below (width:100%; height:auto) is exactly what destroyed it: each
   image resolved to its own aspect-ratio height. Crop with object-fit instead of editing
   any file; anchor BOTTOM so the crop takes from the top (owner's call).             */
@media (min-width: 900px) {
  .imgvilla3:has(> .section[style*="float:left"]):has(+ .imgvilla3:not(:has(> .section[style*="float:left"]))) img,
  .imgvilla3:has(> .section[style*="float:left"]) + .imgvilla3:not(:has(> .section[style*="float:left"])) img,
  .imgvilla > .section[style*="float:left"] img,
  .imgvilla > .section[style*="float:left"] + .section img {
    height: 195px;
    width: auto;
    max-width: 100%;
    object-fit: cover;
    object-position: 50% 100%;   /* anchor bottom, crop off the top */
  }
}

/* ---- 7e: single (unpaired) images align LEFT in the text column, never centred ---- */
.imgvilla2,
.imgvilla3:not(:has(> .section[style*="float:left"])):not(:has(+ .imgvilla3)) {
  margin-left: 0;
  margin-right: auto;
}
.imgvilla2 > .section,
.imgvilla2 img { margin-left: 0; margin-right: auto; }
/* Preserved exception (owner): sipadan.php's img1 stays RIGHT-aligned. */
.content-card img[src$="sipadan-turtle.png"] {
  float: right;
  margin: 0 0 var(--sp-3) var(--sp-4);
}

/* ---- 7g: on mobile a content image fills the paragraph width ----
   ROOT CAUSE: .section is float:left, so it shrink-wraps to the image's NATIVE width and
   the img's width:100% then resolves to 100% OF NATIVE — a 300px image in a ~380px column
   renders at ~79%, which is the reported "~80% of text width". Unfloat the section on
   mobile so the image can fill; the per-image caps below stop that from upscaling any
   image more than 10% past its native width (owner's 5-10% ceiling).                 */
@media (max-width: 899px) {
  .imgvilla > .section,
  .imgvilla2 > .section,
  .imgvilla3 > .section {
    float: none !important;
    width: 100%;
  }
  .imgvilla img, .imgvilla2 img, .imgvilla3 img {
    width: 100%;
    height: auto;          /* undo the 7d desktop crop */
    object-fit: fill;
    max-width: 100%;
    margin-left: 0;        /* anything capped below stays LEFT-aligned, never centred */
    margin-right: auto;
  }
  .content-card img[src$="sipadan-turtle.png"] { float: none; margin: var(--sp-3) 0; }

  }

/* ---------- A (owner ruling): SINGLE content images render at NATURAL width ----------
   A single (non-paired) content image must show at its own natural width, limited only
   by the text column — never visibly shrunk when it fits. The blanket max-width:540px
   above was reducing 110 single images across 62 pages (Dive-site-maps' 898px maps ->
   540px, Semporna.webp, Tawau, ...). Pairs are EXCLUDED: they keep the 7d height parity.
   Desktop only — on mobile 7g's fill + per-image native caps already govern.          */
@media (min-width: 900px) {
  .imgvilla2 img,
  .imgvilla3:not(:has(> .section[style*="float:left"])):not(:has(+ .imgvilla3)) img {
    width: auto;
    max-width: 100%;      /* the text column is the only cap */
    height: auto;
  }
  /* Re-assert 7d AFTER the singles rule so a PAIR always wins: the second image of a
     pair is not followed by an .imgvilla3, so it would otherwise match "single" above. */
  .imgvilla3:has(> .section[style*="float:left"]):has(+ .imgvilla3:not(:has(> .section[style*="float:left"]))) img,
  .imgvilla3:has(> .section[style*="float:left"]) + .imgvilla3:not(:has(> .section[style*="float:left"])) img,
  .imgvilla > .section[style*="float:left"] img,
  .imgvilla > .section[style*="float:left"] + .section img {
    height: 195px;
    width: auto;
    max-width: 100%;
    object-fit: cover;
    object-position: 50% 100%;
  }
}

/* ---------- B (owner ruling): packages typography + table of contents ----------
   Tier = SUBTITLE (largest below the h1). Resort name = SUB-SUB-TITLE: bigger than
   body, smaller than the tier, and NOT a bullet — it is a heading now.            */
.pk-tier {
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2.05rem);
  line-height: 1.2;
  margin: var(--sp-6) 0 var(--sp-3);
  color: var(--c-navy);
}
.pk-resort {
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.35rem);   /* > body, < tier */
  line-height: 1.3;
  margin: var(--sp-4) 0 var(--sp-2);
  font-weight: var(--fw-semi);
}
.pk-resort a { text-decoration: none; }
.pk-resort a:hover, .pk-resort a:focus-visible { text-decoration: underline; }
/* the resort entries are headings now -> no bullet, no list indent */
.pk-list { list-style: none; margin: 0; padding: 0; }
.pk-list > li { list-style: none; margin: 0 0 var(--sp-5); }
.pk-list > li::marker { content: none; }

.pk-toc {
  margin: var(--sp-4) 0 var(--sp-6);
  padding: var(--sp-4) var(--sp-5);
  background: var(--c-surface-2);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm, 6px);
}
/* F7 (supervisor-consolidated): the ToC title genuinely matches .content-card > h2 —
   display family + Fraunces variation + --fs-xl + the gold accent underline. */
.pk-toc__title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: 570;
  font-variation-settings: "opsz" 40;
  letter-spacing: -0.01em;
  color: var(--c-navy);
  line-height: var(--lh-tight);
  margin: 0 0 var(--sp-3);
}
.pk-toc__title::after {
  content: ""; display: block; width: 56px; height: 2px;
  background: var(--c-gold); margin-top: var(--sp-3); opacity: 0.8;
}
.pk-toc ul { list-style: none; margin: 0; padding: 0; }
.pk-toc__tier > a { font-weight: var(--fw-semi); }
.pk-toc__tier { margin-top: var(--sp-2); }
.pk-toc__sub { margin: var(--sp-1) 0 0 var(--sp-4) !important; }
.pk-toc__resort a { font-size: 0.95rem; }
.pk-toc a { color: var(--c-navy); text-decoration: none; }
.pk-toc a:hover, .pk-toc a:focus-visible { text-decoration: underline; }
@media (min-width: 900px) {
  .pk-toc { columns: 2; column-gap: var(--sp-6); }
  .pk-toc__title { column-span: all; }
}
/* an anchored heading must not hide under any sticky chrome */
.pk-tier, .pk-resort { scroll-margin-top: 90px; }

/* ---------- F: Special-Offers hero image (owner ruling) ----------
   Desktop: the small vertical nudibranch sits RIGHT, the opening text runs down its
   left. .imgvilla2 is float:none site-wide (and 7e pins singles left), so this page
   needs an explicit hook — same pattern as the sipadan-turtle.png exception.
   Mobile: no float, so it lands directly under the H1 in DOM order, which is exactly
   what the original m/Special-Offers.php did.                                      */
@media (min-width: 900px) {
  .imgvilla2.so-hero {
    float: right;
    width: auto;
    max-width: 300px;
    margin: 0 0 var(--sp-4) var(--sp-5);
  }
  .imgvilla2.so-hero img { width: auto; max-width: 100%; height: auto; margin: 0; }
}
@media (max-width: 899px) {
  .imgvilla2.so-hero { float: none; margin: var(--sp-3) 0; }
}

/* F3: "View details" buttons pinned to the card bottom — level across each row even when a
   meta line wraps (applies to photo AND compact cards). */
.room-card__body .btn { margin-top: auto; align-self: flex-start; }
/* F12b: neutral info card (e.g. the Bohey-Dulang Boat Transfer Schedule) */
.info-card {
  background: var(--c-white);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-4) var(--sp-5);
  margin: var(--sp-5) 0;
}
.info-card h3 { margin-top: 0; }

/* F13: icon list (BD treatment) — one column, sip_icon glyphs as bullets, text verbatim */
.amenities--icons { display: block; list-style: none; padding-left: 0; }
.amenities--icons li { display: flex; align-items: center; gap: var(--sp-2); margin: 0 0 var(--sp-1); }

/* BLOG (BL2/BL4): single-column post ToC + breathing room under the post title before meta */
.post-toc { column-count: 1 !important; columns: 1 !important; }
h1 + .post-meta { margin-top: var(--sp-3); padding-top: var(--sp-1); }

/* ===== OWNER EYEBALL-2 UNIT 1 ===== */
/* R-E: mobile — EVERY in-flow content-card image renders full column width, regardless of
   wrapper class (plain .figure, bare <img>, etc.) — not just the float/pair modifiers. */
@media (max-width: 1023px) {
  .content-card .figure { float: none; width: 100%; text-align: center; }
  /* EVERY content-card <img> (bare or wrapped) fills the column on mobile — sip_icon renders
     <svg>, not <img>, so amenity/line icons are unaffected. Carousels keep their own object-fit. */
  /* V1-CORRECTION: mobile only — fill column, but cap consecutive-pair members at their
     shared --pair-w (emitted by the render layer). Desktop never reads --pair-w. */
  .content-card img { width: min(100%, var(--pair-w, 9999px)) !important; height: auto; margin: 0 auto; display: block; }
  .content-card .carousel__slide img { height: 100%; }
}
/* R-F: image-less (compact) room cards render at the SAME width as photo cards — the compact
   grid matches the photo grid's 2 columns on desktop (overrides the inline --rooms-cols:3). */
@media (min-width: 700px) {
  .rooms-grid--compact { grid-template-columns: repeat(2, minmax(0,1fr)) !important; }
}
/* F-CD: contact-card button rows render EQUAL/full width on mobile (WhatsApp / Mobile-Office /
   email series + Open-inquiry-form) — no single wider button. */
@media (max-width: 599px) {
  .contact-card .cbtn, .contact-card__grid .cbtn, .contact-card__btns .cbtn, .contact-card .btn, .contact-card__btns .btn { width: 100%; }
}

/* F-PV: Sipadan photo matrix + 2x3 YouTube-embed placeholder video grid */
.pv-photo-matrix{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:var(--sp-3);margin:var(--sp-4) 0;}
.pv-photo-matrix a{display:block;overflow:hidden;border-radius:var(--radius-md,8px);}
.pv-photo-matrix img{width:100%;height:100%;aspect-ratio:4/3;object-fit:cover;display:block;transition:transform .3s;}
.pv-photo-matrix a:hover img{transform:scale(1.05);}
.pv-video-grid{display:grid;grid-template-columns:1fr;gap:var(--sp-3);margin:var(--sp-4) 0;}
@media(min-width:640px){.pv-video-grid{grid-template-columns:repeat(2,1fr);}}
.pv-video-card{position:relative;display:block;overflow:hidden;border-radius:var(--radius-md,8px);background:#0b1f33;}
.pv-video-card img{width:100%;aspect-ratio:16/9;object-fit:cover;display:block;opacity:.82;}
.pv-video-card__play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;}
.pv-video-card__play svg{width:54px;height:54px;fill:#fff;opacity:.94;filter:drop-shadow(0 2px 6px rgba(0,0,0,.55));}
.pv-video-card__cap{position:absolute;left:0;right:0;bottom:0;padding:var(--sp-2) var(--sp-3);color:#fff;background:linear-gradient(transparent,rgba(0,0,0,.72));font-size:.85rem;}

/* F-DS: comparison caption renders as a real h2-style subtitle (full width above the table). */
.spot-table caption{font-size:var(--fs-h3,1.35rem);line-height:1.25;padding-bottom:var(--sp-3);}
/* F-DS: Depth/Visibility/Location stats as a CARD with the dive-map image on the RIGHT (desktop). */
.dive-stats-card{display:flex;flex-wrap:wrap;gap:var(--sp-4);align-items:flex-start;margin:var(--sp-4) 0;padding:var(--sp-4);background:var(--c-surface-2,#f4f7f9);border-radius:var(--radius-md,8px);}
.dive-stats-card .dive-stats{flex:1 1 260px;min-width:0;margin:0;}
.dive-stats-card .dive-stats-card__map{flex:0 0 auto;margin:0;}
.dive-stats-card .dive-stats-card__map img{display:block;border-radius:var(--radius-md,8px);max-width:100%;height:auto;}
@media(max-width:600px){.dive-stats-card{flex-direction:column;}.dive-stats-card .dive-stats-card__map{align-self:center;}}

/* ============================================================================
   CSS-R28b — THE DIVE-SITE STAT CARD IS CENTRED ON MOBILE, IN EVERY TREE
   ----------------------------------------------------------------------------
   Owner screenshot: `/Sipadan/West-Ridge.php` at ~442px shows the stat panel
   hugging the left while the fr twin looks centred. Measured, the cause is not a
   language rule and nothing is missing from the EN markup — both trees run the
   same CSS. At <=600px the card becomes `flex-direction: column` with the flex
   default `align-items: flex-start`, so each child is shrink-to-fit: the panel is
   only as wide as its own content. FR's French labels happen to be long enough to
   fill the card (337 of 337px) while EN's shorter labels stop at 283px, leaving
   71px of slack on the right — the same rule, two contents, two appearances.
   Stretching the children makes the panel span the card in both, so the slack is
   symmetric whatever the language happens to say. Desktop keeps its row layout.
   ============================================================================ */
@media (max-width: 600px) {
  .dive-stats-card { align-items: stretch; }
  .dive-stats-card > .dive-stats { width: 100%; }
}

/* M6/G7 dive-site contact card: on plain/dive-site pages the page-end contact card renders
   as a BOUNDED card aligned under the right-side text column (not full-bleed). */
.page-plain .content-card > .contact-card{max-width:640px;margin-left:auto;margin-right:auto;}
@media(max-width:700px){.page-plain .content-card > .contact-card{max-width:none;margin-left:0;}}

/* M11 course eyebrow: subtitle promoted to eyebrow above the h1 on course pages — breathing padding. */
.content-card > .eyebrow:first-child{display:block;margin-top:var(--sp-2);margin-bottom:var(--sp-2);}

/* M2-FINAL: Chinese header lines render as distinct stacked lines under the EN title/subtitle. */
.hdr-zh{font-weight:600;color:var(--c-navy,#0d2b44);font-size:var(--fs-lg,1.15rem);line-height:1.2;margin:calc(-1*var(--sp-2)) 0 var(--sp-3);letter-spacing:.02em;}

/* V2: listing per-resort image = natural-aspect BLOCK under the resort-name subtitle (no fixed crop, never floated). */
.resort-thumb{display:block;width:auto;max-width:min(100%,360px);height:auto;background:none;overflow:visible;margin:var(--sp-2) 0 var(--sp-4);}
.resort-thumb img{width:auto;max-width:100%;height:auto;object-fit:initial;aspect-ratio:auto;display:block;}
/* V4: listing resort-name lists carry NO bullet (icon removed from the pk-resort title entries in markup). */
.pk-list,.link-run{list-style:none;padding-left:0;}


/* X-RES: m/-only restorations render MOBILE-ONLY — the originals never showed them on
   desktop. Owner-explicit both-view placements (B2 Massage-Spa, F-SR Luxury hero) are
   exempt and never carry this class. */
.figure--m-only{display:none;}
@media(max-width:1023px){.figure--m-only{display:block;}}

/* X-ANE: structural two-column feature row — list LEFT, narrow figure RIGHT, top-aligned.
   Mobile stacks: image AFTER the list (DOM order), W1 sizing applies. */
.feat-row{display:flex;gap:var(--sp-5);align-items:flex-start;margin:var(--sp-4) 0;}
.feat-row>ul{flex:1 1 auto;min-width:0;margin:0;}
.feat-row>.feat-row__fig{flex:0 0 auto;margin:0;max-width:44%;}
.feat-row>.feat-row__fig img{display:block;max-width:100%;height:auto;}
@media(max-width:767px){.feat-row{flex-direction:column;}.feat-row>.feat-row__fig{max-width:100%;align-self:center;}}

/* X-CUR: listing per-resort images are LINKS to the resort page — pointer cursor, no
   zoom affordance. Lightbox zoom-in stays only on genuine gallery figures. */
.content-card figure.resort-thumb img{cursor:pointer;}

/* B1: tablet-band cascade fix (768-1023): while a figure-pair renders as a ROW, its
   member imgs must NOT be width-capped by the W1 --pair-w (flex sizes the figures;
   a cap would unbalance the equal heights). The cap RE-APPLIES at <=767 when pairs
   stack (each member then behaves as a run image). Choice (a): selector exclusion —
   the inline --pair-w stays on the tags but is inert while the row is active. */
@media (min-width: 768px) and (max-width: 1023px){
  .content-card .figure-pair .figure img{ width:100% !important; }
}

/* Y10: uniform resort entry card (reusable; currently applied on diving-holiday-packages
   only). Inside stays V2-STRUCTURAL h3.pk-resort -> figure.resort-thumb -> p. */
.pk-card{background:var(--c-surface-2);border:1px solid var(--c-hairline);border-radius:var(--radius-lg);padding:var(--sp-4) var(--sp-5);margin:var(--sp-4) 0;box-shadow:var(--shadow-sm);}
.pk-card>.pk-resort{margin-top:0;}
.pk-card>p:last-child{margin-bottom:0;}

/* Y12-3 (diving-holiday-packages only): ToC row - one-column ToC left, nudibranch right. */
.feat-row--toc>.pk-toc{flex:1 1 auto;min-width:0;margin:0;}
.feat-row--toc .pk-toc ul{columns:1 !important;column-count:1 !important;}

/* ===== WALK-ROUND-3 REPAIRS (owner 2026-07-23) — BASE SCOPE, appended last so the cascade
   is unambiguous. Root causes: the pass-1 centering rules and the X01 :has rules had been
   inserted inside mobile @media blocks, so DESKTOP never received them. ===== */
.cta-row--center { justify-content: center; }
.cta-row--center .btn { flex: 0 1 auto; min-width: 260px; }
/* dive-site feature image: RIGHT of text on desktop. The base .imgvilla2 img width rules
   (width:100% / natural-width singles) defeated the align attribute's float. Explicit now. */
.imgvilla2 > img[align="right"] { float: right; width: auto; max-width: min(50%, 540px);
  margin: 0 0 var(--sp-4) var(--sp-4); }
.imgvilla2 > .section:has(> img[align="right"]) { float: right; width: auto;
  margin-left: var(--sp-4); margin-right: 0; }
.imgvilla2 > .section:has(> img[align="right"]) > img { width: auto; max-width: 100%; margin: 0; }
/* hero-title divider (owner round-2 item 2): the Discover-Scuba 56px gold bar, opt-in per page */
/* DIVIDER-SITEWIDE (owner 2026-07-23): the short gold bar renders under EVERY hero h1,
   site-wide — ONE rule, generalizing the .content-card>h2::after style. Supersedes the
   per-page hero-rule opt-in (class + build_hero flag stay in markup, now inert: ::after is
   a single pseudo-element, so no double-render is possible). Centered h1s inherit the
   existing h1.text-center::after margin-auto rule. */
.content-card h1::after { content: ""; display: block; width: 56px; height: 2px;
  background: var(--c-gold); margin-top: var(--sp-3); opacity: 0.8; }
@media (max-width: 899px) { /* mobile: centered stack, floats off */
  .imgvilla2 > img[align="right"] { float: none; width: 100%; max-width: 100%; margin: 0 auto; }
  .imgvilla2 > .section:has(> img[align="right"]) { float: none !important; width: 100%; margin: 0; }
}

/* ===== WALK-R1 ③–⑦ (owner 2026-07-24) ===== */
/* ③ thank-you contact: four titled blocks, each on its own line */
.contact-card__group { margin-bottom: var(--sp-4); }
.contact-card__subtitle { margin: 0 0 var(--sp-2); font-size: 17px; color: var(--c-navy); }
/* ⑤ blog: ONE column with generous breathing space */
.room-grid:has(> .blog-card) { grid-template-columns: 1fr; gap: var(--sp-6); max-width: 760px; }
.blog-card .room__body { padding: var(--sp-5); }
/* ⑥ one-column ToC modifier (long link texts; set per page from data) */
.pk-toc--one, .pk-toc--one ul { columns: 1 !important; column-count: 1 !important; }
/* R-ROOT-14 catch: the 2-col layout lives on the NAV element (.pk-toc{columns:2}),
   not the ul — the modifier must hit both. */
/* ⑦(a) IMAGE LAW: every *-Dive-Site-Map.webp (the big area maps — case-exact, the small
   card maps are lowercase -dive-map) renders FULL WIDTH */
.content-card img[src$="-Dive-Site-Map.webp"] { width: 100% !important; max-width: 100% !important; height: auto !important; float: none !important; }
/* ⑦(b) IMAGE LAW: narrow/vertical (portrait) content images float RIGHT, text LEFT —
   the render stamps .img--portrait when measured height > width */
.content-card p img.img--portrait, .imgvilla2 > img.img--portrait { float: right; width: auto; max-width: min(42%, 420px); margin: 0 0 var(--sp-4) var(--sp-4); }
@media (max-width: 899px) { .content-card p img.img--portrait, .imgvilla2 > img.img--portrait { float: none; width: auto; max-width: 100%; margin: 0 auto var(--sp-4); display: block; } }

/* ===== WALK-R2 ⑨⑪ (owner 2026-07-24) ===== */
/* ⑨ DIVIDER POSITION LAW (resort pages): the gold bar renders BELOW the stars+pill meta
   row (below stars alone if no pill). When a hero meta row exists, the sitewide h1 bar is
   suppressed and the meta row carries it; heroes without a meta row keep the h1 bar. */
.content-card .page-hero:has(> .page-hero__meta) h1::after { content: none; }
.page-hero__meta { flex-wrap: wrap; }
.page-hero__meta::after { content: ""; flex: 0 0 100%; height: 2px;
  /* FR-WALK-R6 ⑦ root-cause (2026-07-24): flex line-breaking uses the hypothetical main
     size CLAMPED by max-width — 56px fit INLINE beside stars+pill at wide viewports, so
     the bar never wrapped. Basis stays 100% (unclamped -> always wraps to its own line);
     the gold is painted 56px wide via gradient. Rendered proofs on resort heroes. */
  background: linear-gradient(90deg, var(--c-gold) 56px, transparent 0);
  margin-top: var(--sp-3); opacity: 0.8; }
/* ⑪ POPUP STANDARD (Borneo-Divers Deluxe model): 2-col amenity icons on desktop,
   breathing space under the room title, text-only content. */
.modal__panel .modal__title { margin-bottom: var(--sp-4); }
@media (min-width: 900px) {
  .modal__panel .amenities { columns: 2; column-gap: var(--sp-5); }
  .modal__panel .amenities li { break-inside: avoid; }
}
.modal__panel .cta-row { margin-top: var(--sp-4); }

/* ===== FR-WALK-R3 ⑥ TOC-BULLETS LAW (owner 2026-07-24) — fr-scoped: pk-toc entries
   always carry a bullet marker. Scoped to html[lang="fr"] so EN/CN adopt via their
   own owners (cross-terminal announcement _NOTIFY_walk-r3-laws.md). */
html[lang="fr"] .pk-toc ul { list-style: disc; padding-left: 1.25em; }
html[lang="fr"] .pk-toc li { display: list-item; }

/* CORPUS-AUDIT L11, values aligned to the RATIFIED spec (REPORT-FR-017): Send button
   75% desktop / 100% mobile, centered. Supersedes the interim 50%. */
.inquiry-form button[type="submit"] { width: 75%; margin-left: auto; margin-right: auto; display: block; }
@media (max-width: 899px) { .inquiry-form button[type="submit"] { width: 100%; } }

/* CORPUS-AUDIT L8 (owner): narrow-LANDSCAPE content images (w<=360, w>=h — the
   barracuda-tornado / hammerhead class) float RIGHT with text LEFT on desktop. */
.content-card p img.img--narrow, .imgvilla2 > img.img--narrow { float: right; width: auto; max-width: min(40%, 360px); margin: 0 0 var(--sp-4) var(--sp-4); }
@media (max-width: 899px) { .content-card p img.img--narrow, .imgvilla2 > img.img--narrow { float: none; display: block; margin: 0 auto var(--sp-4); } }

/* EN-R3 ③: dive-resorts ToC model — bulleted, bold links */
.pk-toc--bullets ul { list-style: disc; padding-left: 1.2em; }
.pk-toc--bullets ul ul { list-style: circle; }
.pk-toc--bullets a { font-weight: var(--fw-semi); font-size: 1rem; }
/* EN-R3-TOUCHUP ②: every entry the same size as the tier entry (resort links were 0.95rem) */
.pk-toc--bullets .pk-toc__resort a { font-size: 1rem; }
.pk-toc--bullets ul ul { list-style: disc; }  /* ③ flat look even if a sub ever returns */
/* FR-DIVESITE-STRUCTURAL-PARITY (2026-07-24): FR spot-tables carry French data-labels
   (mobile stacked-card captions), so the EN-keyed semibold selector above misses them.
   fr-scoped like the TOC-BULLETS law — EN/CN cascade untouched. */
html[lang="fr"] .spot-table td[data-label="Site de plongée"] { font-weight: var(--fw-semi); }

/* ===== WALK-R4 (owner 2026-07-24) ===== */
/* ① TOC TYPOGRAPHY LAW (sitewide): every ToC entry at the dive-resorts reference size,
   semi-bold, bulleted. Kills the 0.95rem resort-entry downsizing everywhere. */
.pk-toc ul { list-style: disc; padding-left: 1.2em; }
.pk-toc ul ul { list-style: disc; }
.pk-toc a, .pk-toc__resort a { font-size: 1rem; font-weight: var(--fw-semi); }
/* ② TOC COLUMN LAW: 1-4 entries = ONE column; 5+ may keep two.
   FR-WALK-R7 ③ (owner 2026-07-25) — SCOPED AWAY FROM fr. `> li:nth-child(5)` counts only
   TOP-LEVEL <li>, so every tiered fr TOC (3-4 tier rows carrying 9-16 nested resort rows)
   read as "<=4 entries" and was forced to one column with !important — the single-column
   regression, on every tiered page at once. CSS cannot count descendants, so on fr the
   counting law is computed over TOTAL entries and stamped as .pk-toc--one at build time
   (scripts/toc_columns_fr.py). EN/CN cascade deliberately unchanged — announced, their call. */
html:not([lang="fr"]) .pk-toc:has(> ul:not(:has(> li:nth-child(5)))) { columns: 1 !important; }
/* ⑦ DIVE-SITE FEATURE IMAGE — the real fix. The 7d system computes the WRAPPER as
   display:inline-block at half width, so floating the IMG was invisible (it floated inside
   a left-positioned half-width box; probed computed style, REPORT-EN-009). Float the
   WRAPPER itself right when it holds a single align-right image. */
.content-card .imgvilla2:has(> img[align="right"]):not(:has(> .section)):not(.so-hero) {
  display: block; float: right; width: auto; max-width: min(50%, 540px);
  margin: 0 0 var(--sp-4) var(--sp-4); }
.content-card .imgvilla2:has(> img[align="right"]):not(:has(> .section)) > img {
  float: none; width: auto; max-width: 100%; margin: 0; }
@media (max-width: 899px) {
  .content-card .imgvilla2:has(> img[align="right"]):not(:has(> .section)) {
    float: none; width: 100%; max-width: 100%; margin: 0 0 var(--sp-4); }
  .content-card .imgvilla2:has(> img[align="right"]):not(:has(> .section)) > img {
    display: block; margin: 0 auto; }
}
/* WALK-R5 B① (2026-07-24): hero subtitle slot (opt-in via hero data 'sub'). */
.page-hero__sub { font-size: var(--fs-md); font-weight: var(--fw-semi); margin: 0 0 var(--sp-2); }
/* FR-WALK-R6 ① (2026-07-24): popup subtitle under the (single) modal title. */
.modal__subtitle { font-size: var(--fs-md); font-weight: var(--fw-bold); margin: 0 0 var(--sp-3); }

/* ===== FR-WALK-R7 (owner 2026-07-25) ===== */
/* ② TOC TYPOGRAPHY LAW — TWO LEVELS (supersedes the WALK-R4 ① uniform-size reading).
   FIRST-LEVEL entries (the tier labels: «Top luxe», «Gamme luxe», «Gamme intermédiaire»,
   «Croisière Sipadan», and their equivalents on every TOC page) render at the LARGER size;
   SECOND-LEVEL entries (resort names nested under a tier) keep the current 1rem.
   Expressed by LEVEL, not by class, so TOCs whose rows carry no pk-toc__tier/__resort class
   (Tawau, Venir-ici, sipadan, Hotels-a-Semporna) obey the same law. fr-scoped like the R3
   bullets law; announced to EN for their adoption. */
html[lang="fr"] .pk-toc > ul > li > a { font-size: var(--fs-md); }
html[lang="fr"] .pk-toc > ul > li > ul > li > a { font-size: 1rem; }

/* ===== FR-WALK-R8 (owner 2026-07-25) ===== */
/* L-CTA-LEN — a paragraph CTA button renders on ONE line on desktop. The label sweep caps every
   button at 10 words, so nowrap cannot overflow the content column; mobile keeps the same text and
   is free to wrap (the ≤559px rule already gives it the full row). */
@media (min-width: 900px) { .cta-row .btn { white-space: nowrap; } }
/* ⑪ a CTA row the owner wants flush left on desktop (opt-in modifier, never a global change). */
.cta-row--left { justify-content: flex-start; }
.cta-row--left .btn { flex: 0 1 auto; }
/* ⑫ one-column resort cards (the Semporna budget water resorts digest). */
.room-grid--one { grid-template-columns: 1fr; max-width: 760px; }
.room-grid--one .resort-thumb { margin: var(--sp-2) 0 0; }
.room-grid--one .resort-thumb img { width: 100%; height: auto; border-radius: var(--radius-sm, 6px); }
/* TOC SIZE HARMONIZATION — supersedes the FR-WALK-R7 ② depth-based rule, which sized by NESTING:
   a flat TOC (Borneo-Dive-Resorts, Hotels-de-luxe-a-Mabul, Tawau, Packages-Sejours, and the five
   loose resort rows on Hotels-de-plongee) is all top level, so every resort name there rendered at
   the tier size. Size now follows the ROLE: a row that HEADS a tier (it carries pk-toc__tier or
   contains a nested list) is large; every other row — every resort name, at any depth — is the
   level-2 size. */
html[lang="fr"] .pk-toc > ul > li > a { font-size: 1rem; }
html[lang="fr"] .pk-toc > ul > li > ul > li > a { font-size: 1rem; }
html[lang="fr"] .pk-toc > ul > li.pk-toc__tier > a,
html[lang="fr"] .pk-toc > ul > li:has(> ul) > a { font-size: var(--fs-md); }
/* EN-WALK-R8 ① — the same ROLE-based TOC sizing adopted for EN (owner order: the
   Sipadan-Resorts / Luxury-Diving-Packages / Luxury-Non-Diving-Packages tier rows —
   Top Luxury, Luxury Range, Medium Range, Sipadan Liveaboard — render at the BIG tier
   size; resort names at any depth stay level-2). Gated on html[lang="en"], so cn/es/de/
   ru/zh output is untouched and each terminal adopts on its own order. */
html[lang="en"] .pk-toc > ul > li > a { font-size: 1rem; }
html[lang="en"] .pk-toc > ul > li > ul > li > a { font-size: 1rem; }
html[lang="en"] .pk-toc > ul > li.pk-toc__tier > a,
html[lang="en"] .pk-toc > ul > li:has(> ul) > a { font-size: var(--fs-md); }
/* L-LONE-IMG — a page whose ONLY image is a small one puts it at the TOP of the article, right of
   the text on desktop, centred above the text on mobile. The "only image / small" test cannot be
   expressed in CSS, so scripts/lone_image_fr.py stamps .img--lone at build time. */
.content-card img.img--lone { float: right; width: auto; max-width: min(42%, 420px); height: auto;
  margin: 0 0 var(--sp-4) var(--sp-4); }
.content-card figure.img--lone { float: right; width: auto; max-width: min(42%, 420px);
  margin: 0 0 var(--sp-4) var(--sp-4); }
.content-card figure.img--lone img { width: 100%; height: auto; }
@media (max-width: 899px) {
  .content-card img.img--lone, .content-card figure.img--lone { float: none; display: block;
    margin: 0 auto var(--sp-4); max-width: 100%; }
}
/* ⑰ ROOT CAUSE of "barracuda-tornado is still not right-aligned": the CORPUS-AUDIT L8 rule and the
   WALK-R1 ⑦(b) portrait rule only match `p img` and `.imgvilla2 > img`. The class WAS stamped on
   the image (measured 300x200 -> .img--narrow), but the image lives inside <figure class="figure">,
   which neither selector reaches — so the cascade never floated it. The earlier pass checked the
   class, not the resolved cascade. Float the FIGURE when it wraps such an image (same technique the
   WALK-R4 ⑦ fix used for .imgvilla2). */
.content-card figure.figure:has(> img.img--narrow),
.content-card figure.figure:has(> img.img--portrait),
.content-card figure.figure:has(> a > img.img--narrow),
.content-card figure.figure:has(> a > img.img--portrait) {
  float: right; width: auto; max-width: min(42%, 420px); margin: 0 0 var(--sp-4) var(--sp-4); }
.content-card figure.figure:has(> img.img--narrow) img,
.content-card figure.figure:has(> img.img--portrait) img { width: 100%; height: auto; }
@media (max-width: 899px) {
  .content-card figure.figure:has(> img.img--narrow),
  .content-card figure.figure:has(> img.img--portrait),
  .content-card figure.figure:has(> a > img.img--narrow),
  .content-card figure.figure:has(> a > img.img--portrait) {
    float: none; max-width: 100%; margin: 0 auto var(--sp-4); }
}
/* ⑩ package blocks are BULLETED lists (the .amenities icon-list style suppresses markers, and
   these rows carry no icons, so they read as loose lines). */
.content-card ul.pk-bullets { list-style: disc; padding-left: 1.25em; margin: var(--sp-3) 0; }
.content-card ul.pk-bullets li { display: list-item; margin: 0 0 var(--sp-2); }

/* ============================================================================
   CSS-R1 — MOBILE STICKY TOP BAR  (<=1023px ONLY)
   ----------------------------------------------------------------------------
   MEASURED CAUSE, not a guess: .site-header already declares
   `position: sticky; top: 0` (§1, "sticky on desktop AND mobile"), but EVERY
   page in both trees renders `class="site-header site-header--scene"`, and
   `.site-header--scene { position: relative }` (§ Build 14 header scene) has
   equal specificity and comes later in the cascade — so the sticky declaration
   has been dead on every page, at every width. Probed on 8 pages x 2 viewports:
   header rectTop == -scrollY on all 16, i.e. it scrolled away everywhere.

   This block restores stickiness AT THE MOBILE BREAKPOINT ONLY. It is a
   `max-width: 1023px` query, so no declaration in it can reach >=1024px: the
   baseline desktop header is untouched, byte for byte.

   IT IS DELIBERATELY NOT THE INVERSE RULE. The reverted WALK MOBILE-R1 block
   did this with `@media (min-width:1024px){ .site-header{ position:static } }`,
   which de-stickied the DESKTOP header and displaced the animated hero. That
   shape is the named anti-pattern: never reintroduce a min-width rule that
   touches .site-header positioning.

   z-index (80) and the stacking context are inherited from §1 and unchanged;
   the mobile drawer (.site-nav, z-index 100) is a SIBLING of <header>, not a
   descendant, so it still covers the bar exactly as before.
   ============================================================================ */
@media (max-width: 1023px) {
  .site-header,
  .site-header--scene {
    position: sticky;
    top: 0;
  }
}

/* ============================================================================
   CSS-R3 — DATA TABLES BECOME CARDS ON MOBILE  (<=1023px ONLY)
   ----------------------------------------------------------------------------
   Owner ruling from live mobile use: a content data table renders at the mobile
   breakpoint as stacked cards — one card per body row, each cell a
   label/value line, header row hidden.

   This is the SAME card language the site already uses for .spot-table
   (§ "Dive-spot table", max-width:1023px): same hairline border, same radius,
   same surface, same shadow, same 42% label column, all from the tokens. The
   only difference is where the label comes from — .spot-table has data-label
   baked into its markup, everything else is stamped at load by the CSS-R3
   module in js/site.js from the table's own header row, so each language tree
   gets its own translated labels for free.

   .spot-table is NOT retargeted here: it already does this, correctly, on 161
   tables. Touching it would be risk without a gain.

   `.t-cards` is added by the JS only after it has found a usable header row and
   at least one body row, so a table this cannot label keeps its desktop layout
   at every width rather than becoming an unlabelled card stack.

   Everything below is inside `max-width: 1023px`: no declaration can reach
   >=1024px, so the desktop table rendering is untouched, byte for byte. AP-1
   (a min-width rule that reaches back into desktop) is not used.
   ============================================================================ */
@media (max-width: 1023px) {
  .t-cards { display: block; width: 100%; border: 0; }
  .t-cards thead,
  .t-cards .t-cards__head { display: none; }
  .t-cards tbody,
  .t-cards tr,
  .t-cards td { display: block; width: 100%; }

  .t-cards tr {
    border: 1px solid var(--c-hairline);
    border-radius: var(--radius);
    background: var(--c-white);
    box-shadow: var(--shadow-sm);
    margin: 0 0 var(--sp-3);
    overflow: hidden;
  }
  .t-cards tr:last-child { margin-bottom: 0; }

  .t-cards td {
    display: flex;
    gap: var(--sp-3);
    border: 0;
    border-bottom: 1px solid var(--c-hairline);
    padding: var(--sp-2) var(--sp-3);
    /* the pre-rebuild tables carry nowrap="nowrap" on their cells; left alone it
       re-creates the very horizontal overflow this round exists to remove */
    white-space: normal;
    background: transparent;
  }
  /* § Tables stripes with `tr:nth-child(even) td` — (0,1,2), MORE specific than
     `.t-cards td` (0,1,1) — so a plain override loses and every second card came
     out beige. Same selector shape .spot-table already uses, for the same reason. */
  .t-cards tr:nth-child(even) td,
  .t-cards tr:nth-child(odd) td { background: transparent; }
  .t-cards td:last-child { border-bottom: 0; }
  /* R20 ⑥ (owner): a card reads TITLE ON TOP, then its lines BELOW — never a
     label-left / value-right pair. The label sits ABOVE its value as a small caption,
     and the row's first cell is the card title, which needs no label at all.
     Exemplar: the Mataking snorkelling row on the frontpage table. */
  .t-cards td { display: block; }
  .t-cards td::before {
    content: attr(data-label);
    display: block;
    flex: none;
    margin-bottom: 2px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semi);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--c-ink-soft);
  }
  .t-cards tr > td:first-child {
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--c-navy);
    padding-top: var(--sp-3);
  }
  .t-cards tr > td:first-child::before { content: none; }

  /* a colspan cell spans columns, so no single header labels it: full-width line */
  .t-cards td.is-span { display: block; }
  .t-cards td.is-span::before { content: none; }

  /* an empty cell would print a label with nothing after it */
  .t-cards td.is-empty { display: none; }
}

/* ============================================================================
   CSS-R2 — ANCHOR LANDINGS CLEAR THE MOBILE STICKY BAR  (<=1023px ONLY)
   ----------------------------------------------------------------------------
   CSS-R1 pinned the header at the mobile breakpoint, so anything scrolled to by
   an in-page link now has to start below it. Measured before this block, at
   414px across 6 pages and 64 in-page anchors:

     h2/h3          landed at 88px  (§ line 42, calc(--header-h + --sp-5))
     .pk-tier/.pk-resort  landed at 90px  (§ line 2078, a literal)
     #main          landed at   0px  -> UNDER the 79px bar, on every page that
                    has the "Skip to content" link, which is all of them

   So the compensation existed but was partial: it covered headings only, and it
   was expressed as two different constants, neither equal to the bar's real
   79px height.

   ROBUST FORM, not a magic number: --sticky-h is the MEASURED header height,
   published by the CSS-R2 module in js/site.js and re-measured on resize, font
   load and header resize. The fallback is the existing --header-h token, so a
   no-JS render still clears the bar rather than falling back to zero.

   [id] is deliberate: "anchor targets site-wide" includes <main id="main">, the
   accessibility skip-link target, which no heading selector reaches. It is
   (0,1,0), so it also supersedes the .pk-* literal here — at mobile only; both
   earlier rules keep their exact desktop behaviour, untouched.

   scroll-margin-top paints nothing. The static gate must read 0.0000% on every
   row, desktop and mobile alike: this is scroll geometry, not pixels.
   ============================================================================ */
@media (max-width: 1023px) {
  [id] { scroll-margin-top: calc(var(--sticky-h, var(--header-h)) + var(--sp-4)); }
}

/* ============================================================================
   CSS-R4 — WECHAT QR SIZE CAP  (<=1023px ONLY)
   ----------------------------------------------------------------------------
   Owner ruling, re-confirmed on live mobile: the WeChat QR must not render
   near full width. Measured before this block, on all six contact pages that
   carry it (en, cn, de, es, ru, zh):

     natural   154 x 154
     mobile    346 x 346   = x2.25 upscale  -> the pixelation
     desktop   154 x 154   = x1.00          -> already sane, left as it is

   WHAT WAS INFLATING IT — and why this needs `!important`. It is not a stray
   rule: `§ OWNER EYEBALL-2 UNIT 1 / R-E` (line ~2130) is an owner ruling that
   deliberately forces EVERY content-card image to the full column on mobile:

       .content-card img { width: min(100%, var(--pair-w, 9999px)) !important; }

   So CSS-R4 is a NARROW, DELIBERATE EXCEPTION to a standing owner ruling, not a
   correction of a mistake. It is scoped to this one asset by src, it is the only
   way to beat an !important declaration, and R-E keeps governing every other
   image on the page. **Two owner rulings meet here — flagged in the report.**

   THE CAP ITSELF. `--qr-nat-w` is the image's measured intrinsic width, published
   by the CSS-R4 module in js/site.js (the same trick the lightbox uses for
   --lb-natw). The 154px fallback is the asset's width today and is only used in
   the moment before the lazy image decodes.

     width      fills the column but never past 150% of natural  -> the cap
     min-width  never below 100% of natural  -> stays crisp on a narrow phone
     margin     auto/auto -> centred, as ordered

   Desktop is untouched: this is a max-width:1023px block, and desktop already
   measured x1.00.
   ============================================================================ */
@media (max-width: 1023px) {
  .content-card img[src*="QR-code-ADH"] {
    width: min(100%, calc(var(--qr-nat-w, 154px) * 1.5)) !important;
    min-width: var(--qr-nat-w, 154px);
    max-width: calc(var(--qr-nat-w, 154px) * 1.5);
    margin-left: auto;
    margin-right: auto;
  }
}

/* ============================================================================
   CSS-R5 — IMAGE SEQUENCES RENDER AT ONE WIDTH  (>=1024px ONLY)
   ----------------------------------------------------------------------------
   Owner ruling, desktop only: a resort/listing image sequence renders at a
   uniform width equal to the SMALLEST natural width in that sequence, so the run
   reads as a column and no member is ever upscaled.

   Exemplar /dive-resorts.php, Mabul Water Bungalows -> Pom Pom, measured before:
     325, 260, 360, 360, 279, 325, 325, 360   (natural 325,260,640,640,279,325,325,530
     capped by `.resort-thumb{max-width:min(100%,360px)}`) -> ragged.
   Smallest natural in the sequence = 260, so the whole run renders 260 wide.

   --seq-w and the .seq-uniform class are written by the CSS-R5 module in
   js/site.js; the detection rule for "sequence" is stated in full there. CSS
   never guesses: if the module cannot resolve a sequence it emits nothing and
   the existing rules keep governing.

   Alignment is NOT set here — the members keep whatever the page already gives
   them (`.resort-thumb` is display:block with its own margins), per "centered
   per the page's existing alignment".

   A min-width query is correct for a desktop-only ruling and is NOT the AP-1
   anti-pattern: AP-1 is specifically a min-width rule that changes
   .site-header POSITIONING. This touches listing figures only, never the header.
   ============================================================================ */
@media (min-width: 1024px) {
  .content-card .seq-uniform {
    width: var(--seq-w);
    max-width: var(--seq-w);
  }
  .content-card .seq-uniform > img,
  .content-card .seq-uniform > a > img {
    width: 100%;
    height: auto;
  }
}

/* ============================================================================
   CSS-R7 — MOBILE BODY IMAGES ARE FULL BLOCKS, NEVER FLOATS  (<= 1023px)
   ----------------------------------------------------------------------------
   Owner ruling: at the mobile breakpoint no image floats inside a paragraph —
   the image is a full block before or after the paragraph, with a standard
   margin ABOVE and BELOW.

   ROOT CAUSE (exemplar /fr/Mabul-Inn.php, the «Le» orphan). Three faults stack:
     1. the corpus writes `align="right"` ON THE IMAGE. The existing mobile rules
        unfloat the FIGURE, never the image, so the UA presentational float
        survived and the figure collapsed to height 0 (measured: figH 0).
     2. every unfloat rule in this file was written at `max-width: 899px`, but the
        canon mobile breakpoint is 1023px — so 900–1023px kept the desktop float.
     3. the desktop float rules set `margin: 0 0 var(--sp-4) var(--sp-4)`, i.e.
        NO top margin, which the 899px rules never restored.
   Measured on the exemplar before this block, first text line beside the float:
     414px 336px wide (no orphan) · 480px **17px — the word «Le» alone** ·
     560px 98px · 640px 159px · 720px 182px · 800px 298px · 900–1023px 400–515px.
   So the reported orphan is real and the whole 480–1023 band flowed around the
   float.

   SCOPE — `> figure` / `> img` / `p img` is the discriminator between prose and
   grid. Measured ancestry: a prose image is `FIGURE|IMG < ARTICLE.content-card`
   (and the legacy corpus wrapper is `.imgvilla* < DIV.text`), while every grid
   image sits one level deeper — `FIGURE < DIV.masonry`, `FIGURE < DIV.figure-pair`.
   So pairs, masonry, carousels, listing thumbs and room cards are untouched here.

   `!important` on float, mobile-only: the desktop float rules are written with
   `:has()` (specificity 0,3,2) and outrank any plain breakpoint-scoped selector.
   Same reasoning as CSS-R4/AD-6 — a scoped exception, and the desktop cascade is
   not touched by a max-width block.

   Margins are set on the block that occupies the column, as longhands, so the
   `margin: 0 auto` centring from OWNER EYEBALL-2 R-E survives. `.imgvilla*` is
   deliberately NOT in the margin list: it already carries 16px/32px, both at or
   above standard, and a shorthand there would have shrunk its bottom margin.
   ============================================================================ */
@media (max-width: 1023px) {
  .content-card > figure,
  .content-card > figure img,
  .content-card > img,
  .content-card p figure,
  .content-card p img,
  .imgvilla, .imgvilla2, .imgvilla3,
  .imgvilla > .section, .imgvilla2 > .section, .imgvilla3 > .section,
  .imgvilla img, .imgvilla2 img, .imgvilla3 img {
    float: none !important;
  }
  .content-card > figure,
  .content-card > img,
  .content-card p figure,
  .content-card p img {
    margin-top: var(--sp-4) !important;      /* 1rem — standard, above AND below */
    margin-bottom: var(--sp-4) !important;
  }
}

/* ============================================================================
   CSS-R8 — MOBILE LIGHTBOX FIT: THE CONTROLS NEVER SIT ON THE PHOTO (<= 1023px)
   ----------------------------------------------------------------------------
   Owner ruling: on mobile the popup image renders slightly smaller with side
   padding, so the top-right X never overlaps it.

   Measured before, /Sipadan-Photo-Video.php at 414 (and 480):
     panel  [17, 267, 381, 361]   padding 12px
     image  [30, 280, 355, 214]   -> right edge at panel_right - 13
     close  [364, 276,  24,  24]  -> left edge at panel_right - 34
     X ∩ image = 400px²   (and prev ∩ image = next ∩ image = 480px²)

   The panel is `width: fit-content` with the image capped at its content box, so
   widening only the side padding shrinks the image and opens a gutter for the
   controls — no change to the panel box, the caption width or the height budget.
   Gutter = 10px control offset + 24px control + 6px clearance = 40px, which puts
   the image's right edge 6px clear of the X's left edge.

   Desktop keeps its own frame (`min-width: 1024px`: padding 64px 78px 14px,
   where the X already clears the photo by 40px) and is not touched here.
   The prev/next arrows, at the same 10px offset on the other axis, stop
   overlapping as a by-product of the same gutter.
   ============================================================================ */
@media (max-width: 1023px) {
  .lb__panel {
    padding-left: 40px;
    padding-right: 40px;
  }
}

/* ============================================================================
   CSS-R9 — TOC CARDS TAKE THE FULL AVAILABLE WIDTH ON MOBILE  (<= 1023px)
   ----------------------------------------------------------------------------
   Owner ruling: at the mobile breakpoint a ToC card fills the available width,
   inner text left-aligned. Desktop ToC untouched.

   ROOT CAUSE (exemplar /fr/Packages-Sejours-Plongees.php). `.feat-row` is
   `display:flex; align-items:flex-start`. At <=767px it already switches to
   `flex-direction:column`, but in column mode `align-items` governs the
   HORIZONTAL (cross) axis, so `flex-start` makes each child SHRINK-WRAP to its
   content width — and `flex:1 1 auto` cannot help, it only sizes the main axis.
   Measured before, .pk-toc outer width vs the width available in its parent:
     414px  255.6 of 345.8  -> 90.2px short
     480px  255.6 of 401.3  -> 145.7px short
     /diving-holiday-packages.php  316.1 of 345.8 -> 29.7px short
   Every other ToC in the tree already measured shortfall 0.

   Two changes, both scoped to the ToC feature row:
     1. cross-axis stretch, so the card fills the column;
     2. the column direction extended from 767px to the canon 1023px breakpoint,
        because the ruling is "at the mobile breakpoint" and 768–1023 was still a
        side-by-side row. The figure keeps the centred, capped treatment the
        <=767px rule already gave it, so the band matches what mobile already did.
   Desktop (>=1024px) never sees this block: the row, the two columns and the
   nudibranch keep their measured geometry.

   text-align is pinned left as the ruling states. It already computes to `start`
   on every ToC measured (card, title, li, a), so this is a no-op today; it exists
   so a later centring rule cannot silently reach ToC prose.
   ============================================================================ */
@media (max-width: 1023px) {
  .feat-row--toc { flex-direction: column; align-items: stretch; }
  .feat-row--toc > .pk-toc { width: 100%; margin: 0 0 var(--sp-4); }
  .feat-row--toc > .feat-row__fig { max-width: 100%; align-self: center; }
  .pk-toc, .pk-toc__title, .pk-toc li, .pk-toc a { text-align: left; }
}

/* ============================================================================
   CSS-R10 — CONSECUTIVE IMAGE PAIRS: ONE SIZE PER PAIR
   ----------------------------------------------------------------------------
   Owner rulings: a pair STACKED (mobile) renders both members at one width,
   the SMALLER member's width, centred; a pair SIDE-BY-SIDE renders both members
   at EQUAL HEIGHT. Expressed by direction, not by breakpoint, because the pair's
   own stacking boundary is `max-width: 767px` (`.figure-pair{flex-direction:
   column}`) — a row is a row whether it is at 900px or at 1920px.

   ROOT CAUSE of unequal heights in a row. `.figure-pair > .figure` matches the ⑰
   L8 float rule `.content-card figure.figure:has(> img.img--narrow)`, which sets
   `max-width: min(42%, 420px)`. In a pair the percentage resolves against the ROW,
   so BOTH members were clamped to 42% of it — measured 259.97px each on
   /fr/Sipadan-Mabul-Resort.php — which defeats the `flex: var(--ar) 1 0%`
   proportional sizing that was supposed to produce equal heights. Members then
   rendered 195 vs 172.4, 183.7 vs 213.5, 187.3 vs 208.6.

   Equal height is driven by HEIGHT (--pair-h from js/site.js), not by widths:
   h = min(smallest natural height, height that makes the row fit its column).
   The first term means equal height never upscales a member past its natural
   size (the desktop no-upscale law); the second keeps the row inside the column.
   Measured consequence on the exemplar pair: 260x195 + 294x195 — both at natural
   size, equal height, sum 566px inside a 619px row.

   Stacked members read --pair-eq (min natural width) instead. `!important` there
   is required to beat OWNER EYEBALL-2 R-E (`.content-card img{width:min(100%,
   var(--pair-w))!important}`), whose per-member --pair-w is what made stacked
   members unequal (260 vs 286, 325 vs 300); this selector is more specific, so
   the R-E law keeps governing every image that is NOT a pair member.
   ============================================================================ */
.figure-pair { justify-content: center; }

/* ---- pair as a ROW: equal height ---- */
@media (min-width: 768px) {
  .content-card .figure-pair > .figure {
    /* !important is a measured necessity: the L8 rule
       `.content-card figure.figure:has(> img.img--narrow)` sits at specificity (0,3,2)
       and outranks any plain pair selector. Without it the clamp survived and, with the
       height now fixed, squashed the members — measured 304.9x210 on a 325x210 image,
       aspect ratio 1.452 against a natural 1.5476. */
    max-width: none !important;       /* undo the 42%-of-row clamp from the L8 rule */
    float: none;
    flex: 0 0 auto;                   /* height drives the size now, not flex-grow */
  }
  .content-card .figure-pair.pair-eq > .figure > img {
    height: var(--pair-h);
    width: auto !important;           /* beats the B1 768–1023 `width:100%!important` */
    max-width: 100%;
  }
}

/* ---- pair STACKED: one width, the smaller member's, centred ---- */
@media (max-width: 767px) {
  .content-card .figure-pair.pair-eq > .figure > img {
    width: min(100%, var(--pair-eq)) !important;
    height: auto;
    margin-left: auto;
    margin-right: auto;
  }
}

/* ============================================================================
   CSS-R11 — SECONDARY MENU, MOBILE: ITEM PADDING -2px TOP AND -2px BOTTOM
   ----------------------------------------------------------------------------
   Owner ruling, exact. The secondary menu is the drawer's second level,
   `.site-nav__sub` — the primary level is `.site-nav__link`, the sidebar's
   secondary module (`.top10-card`) is `display:none` below 1024px, and the
   footer carries no menu. Measured before: `.site-nav__sub a` padding
   `var(--sp-2)` = 8px top and bottom at the mobile breakpoint.
   8 - 2 = 6px, written as an absolute so the token cannot drift it.
   The desktop copy of this rule (inside `min-width: 1024px`, with its own
   `--dense` 5px variant) is untouched.
   ============================================================================ */
@media (max-width: 1023px) {
  .site-nav__sub a {
    padding-top: 6px;
    padding-bottom: 6px;
  }
}

/* ============================================================================
   CSS-R12 — FORM CONTROLS ON MOBILE READ AT THE PLACEHOLDER'S DECLARED SIZE
   ----------------------------------------------------------------------------
   Owner ruling: on mobile, the select, the date input and the country list use
   the text-input PLACEHOLDER's declared size. Read from the served stylesheet,
   not guessed:
     .form-field input::placeholder, .form-field textarea::placeholder
       { font-size: var(--fs-base) }        css/main.css:878
     --fs-base: 1.0625rem   (= 17px)        css/tokens.css:59
   So the target is `var(--fs-base)` — quoted as the token, so it tracks the
   placeholder if the token ever moves.

   Measured before, at <= 1023px:
     .form-field select                16px   (`mobile: dropdown stays compact`)
     .form-field select option         15px   (this is the country list, #f-country,
                                               and the resort list, #f-resort)
     .form-field input[type=date]      17px   (already at the placeholder size,
                                               inherited from the shared `font:`)
   So this round moves the select from 16 to 17 and its options from 15 to 17;
   the date input is asserted at 17, not changed.

   The NATIVE PICKER is untouched: no `appearance`, no
   `::-webkit-calendar-picker-indicator`, no `::-webkit-datetime-edit` rules here.
   `input[type=date]{appearance:auto}` (main.css:1050) keeps the native UI.

   Applies to both form surfaces because it is class-level on `.form-field`: the
   static `inquiryform*.php` pages and the registry-routed slugs both render the
   same `includes/form.inquiry.php` fields.
   ============================================================================ */
@media (max-width: 1023px) {
  .form-field select,
  .form-field select option,
  .form-field input[type="date"] {
    font-size: var(--fs-base);
  }
}

/* ============================================================================
   CSS-R13 ① — MEDIA EMBEDS NEVER EXCEED THE CONTENT COLUMN (both viewports)
   ----------------------------------------------------------------------------
   Exemplar /Borneo-From-Below.php. Measured before: the EN page wraps each
   iframe in `.video-cell`, which already sizes it (346x195 mobile, 726x408
   desktop). The FR twin has NO wrapper — its iframes keep the markup's
   width="560" and render 214px PAST the 346px column at 414px, and 560px flat
   on desktop. So the fix cannot live on the wrapper class alone; it has to
   catch a bare iframe as well.
   The 16/9 ratio comes from the attributes (560x315) and matches what
   `.video-cell` produces, so the EN and FR twins now render identically.
   ============================================================================ */
.content-card .video-cell { max-width: 100%; }
.content-card > iframe,
.content-card > video,
.content-card :not(.video-cell) > iframe,
.content-card :not(.video-cell) > video {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  border: 0;
  margin-left: auto;
  margin-right: auto;
}

/* ============================================================================
   CSS-R13 ② — VIDEO FACADE COMPONENT
   ----------------------------------------------------------------------------
   Binds to the markup contract EN shipped in EN-038
   (_REQUEST-CSS-video-facade-component.md): section.video-facades >
   .video-facade-grid > a.video-facade > (img.video-facade__thumb,
   span.video-facade__play, span.video-facade__cap).

   Owner addendum: 2 columns x 2 rows on desktop, 1 column stacked on mobile,
   consistent card sizing. The unstyled link-list state is the defect this
   closes. Caption text gets a standard margin ABOVE and BELOW (exemplar the
   «Turtle Rodeo at Sipadan» line).

   Card sizing is consistent by construction: the grid gives every card the same
   column width and the thumb is a fixed 16/9 crop, so all cards in a row match
   whatever the thumbnail's own proportions are (the ytimg hqdefault frames are
   4:3, hence object-fit: cover).

   The click-to-load swap is in js/site.js; `.video-facade__embed` below is the
   box it inserts. `.pv-video-card*` (the retired static matrix, EN-038 §③) is
   left in place for now — retiring it is a separate, orderable cleanup.
   ============================================================================ */
.video-facades { margin: var(--sp-6) 0; }
.video-facade-grid {
  display: grid;
  grid-template-columns: 1fr;              /* mobile: one column, stacked */
  grid-auto-rows: 1fr;                     /* cards in one row share a height */
  gap: var(--sp-5);
  margin: var(--sp-4) 0;
}
@media (min-width: 1024px) {
  .video-facade-grid { grid-template-columns: 1fr 1fr; }   /* 2 x 2 for four videos */
}
.video-facade {
  display: flex;
  flex-direction: column;                  /* with grid-auto-rows:1fr = consistent card sizing */
  text-decoration: none;
  color: inherit;
  position: relative;
  cursor: pointer;
}
.video-facade__thumb {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius);
  background: #0b1f33;
  box-shadow: var(--shadow-sm);
}
/* play badge: a CSS-drawn triangle in a disc — no external asset, no icon font */
.video-facade__play {
  position: absolute;
  top: 0; left: 0; right: 0;
  aspect-ratio: 16 / 9;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.video-facade__play::before {
  content: "";
  width: 62px; height: 62px;
  border-radius: 50%;
  background: rgba(2, 33, 63, 0.62);
  border: 2px solid rgba(255, 255, 255, 0.9);
  transition: background var(--t-fast) var(--ease);
}
.video-facade__play::after {
  content: "";
  position: absolute;
  width: 0; height: 0;
  border-style: solid;
  border-width: 11px 0 11px 18px;
  border-color: transparent transparent transparent #fff;
  margin-left: 6px;                        /* optical centring of a triangle in a disc */
}
.video-facade:hover .video-facade__play::before,
.video-facade:focus-visible .video-facade__play::before { background: rgba(2, 33, 63, 0.82); }
.video-facade__cap {
  display: block;
  margin-top: var(--sp-4);                 /* standard breathing margin ABOVE ... */
  margin-bottom: var(--sp-4);              /* ... and BELOW (owner addendum) */
  font-size: var(--fs-sm);
  line-height: 1.45;
  color: var(--c-navy);
}
/* the box the click handler swaps in */
.video-facade__embed {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  overflow: hidden;
  background: #0b1f33;
}
.video-facade__embed iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0;
  aspect-ratio: auto;                      /* the wrapper owns the ratio */
}

/* ============================================================================
   CSS-R13b — NO-UPSCALE LAW (DESKTOP)
   ----------------------------------------------------------------------------
   Owner ruling: on desktop an image never renders wider than its natural width;
   cap at natural, centred per context. MOBILE IS EXEMPT (full-width allowed).
   `--nat-w` is stamped per image by js/site.js — CSS cannot read naturalWidth,
   and the width attribute is not always the natural size.
   Exemplar /Luxury-Non-Diving-Packages.php,
   images/small/mabul-sunset-watervillage.webp, blown up to visible pixelation.

   Deliberately NOT `!important`: the ⑦(a) full-width map law
   (`*-Dive-Site-Map.webp { width:100% !important }`) and any other `!important`
   width law keep winning, so this round cannot silently overturn an existing
   ruling. Those collisions are listed in the report.
   ============================================================================ */
@media (min-width: 1024px) {
  .content-card img { max-width: min(100%, var(--nat-w, 100%)); }
}

/* ============================================================================
   CSS-R14a — DESKTOP FLOAT LAW  (executes EN's _REQUEST-CSS-image-float-law.md)
   ----------------------------------------------------------------------------
   Standing owner law (EN-KAPALAI-IMAGES): a right float is allowed ONLY for a
   small non-map image that is the page's ONLY image, beside the FIRST paragraph,
   never beside a title. Everything else is a left-aligned block in flow.

   The three rule groups that break it do so BY CONSTRUCTION: they float on
   measured geometry (`img--narrow` = w<=360 and w>=h, `img--portrait` = h>w),
   and geometry cannot express "is this the page's only image". EN measured the
   damage after its markup pass: 123 images on 42 pages, triggers
   figure.figure 91 · .imgvilla2 27 · p img 4 · legacy align=right 1.

   `.img--lone` is exactly the law's permitted case — the render layer stamps it
   only when the page has ONE small image — so the float SURVIVES there and is
   retired everywhere else. Each selector below therefore carries
   `:not(.img--lone)`, which also lifts it above the original rule's specificity
   (the `:has()` rules sit at (0,3,2)).

   "Never beside a title" is expressed too: a lone image whose next sibling is a
   heading loses the float, so the one permitted float can only ever sit beside
   prose.

   Mobile is untouched by construction — CSS-R7/AD-9 already unfloats every
   in-prose image at <=1023px with `!important`, so this block can only matter at
   desktop. Proven by A/B, not asserted: see the report.
   ============================================================================ */
/* DESKTOP-SCOPED (found by the probe, fixed here): these declarations are not only
   floats — they also set margins, and unscoped they reached 414px and overrode the
   mobile centring from OWNER EYEBALL-2 R-E on 121 images across the probe set. The law
   is a DESKTOP law; mobile is already block-level via CSS-R7/AD-9. */
@media (min-width: 1024px) {
  .content-card p img.img--narrow:not(.img--lone),
  .content-card p img.img--portrait:not(.img--lone),
  .imgvilla2 > img.img--narrow:not(.img--lone),
  .imgvilla2 > img.img--portrait:not(.img--lone),
  .imgvilla2 > img[align="right"]:not(.img--lone),
  .content-card figure.figure:not(.img--lone):has(> img.img--narrow),
  .content-card figure.figure:not(.img--lone):has(> img.img--portrait),
  .content-card figure.figure:not(.img--lone):has(> a > img.img--narrow),
  .content-card figure.figure:not(.img--lone):has(> a > img.img--portrait),
  .imgvilla2 > .section:not(.img--lone):has(> img[align="right"]) {
    float: none;
    max-width: 100%;
    margin-left: 0;                    /* left-aligned block in flow */
    margin-right: auto;
    margin-top: var(--sp-4);           /* standard margins, as in the mobile law */
    margin-bottom: var(--sp-4);
  }
  /* Legacy inline float on a corpus wrapper (measured: /Mount-Kinabalu.php carries
     <div class="section" style="float:right;">). Only !important can reach an inline
     style. LEFT inline floats are deliberately untouched — they are the .imgvilla3 PAIR
     mechanism, governed by the pair law, not image placement. */
  .content-card .section[style*="float:right"],
  .content-card .section[style*="float: right"] {
    float: none !important;
    margin-left: 0;
    margin-right: auto;
  }
  /* Owner ruling F (Special-Offers hero sits RIGHT) survives exactly where the new law
     permits it — a page whose ONLY image is stamped .img--lone. Measured:
     /Special-Offers.php has 1 image, stamped `img--portrait img--lone`, so the float
     stands there; /fr/Sipadan.php reuses .so-hero with 2 images and no .img--lone, so
     under the law it becomes a block. */
  .imgvilla2.so-hero:not(:has(img.img--lone)) {
    float: none;
    margin-left: 0;
    margin-right: auto;
  }

  /* The legacy `align="right"` presentational hint, wherever it survives outside the
     permitted lone case — measured on /Bohey-Dulang-Resort.php, whose hero figure image
     carries it and floats with no author rule at all (UA stylesheet). */
  .content-card img[align="right"]:not(.img--lone) { float: none; }
  /* A listing-sequence member can never be "the page's only image", whatever the render
     layer stamped. Measured: /Borneo-Dive-Resorts.php carries a STALE .img--lone on
     sangalaki.webp inside figure.resort-thumb while the page holds 9 images. Reported to
     EN as a data issue; the style half refuses the float regardless. */
  .content-card .resort-thumb img.img--lone,
  .content-card figure.resort-thumb.img--lone,
  .content-card .seq-uniform img.img--lone { float: none; }

  /* the permitted float, but never beside a title */
  .content-card img.img--lone:has(+ :is(h1, h2, h3, h4)),
  .content-card figure.img--lone:has(+ :is(h1, h2, h3, h4)) {
    float: none;
    margin-left: 0;
    margin-right: auto;
  }
}

/* ============================================================================
   CSS-R14 — TOP-10 GROUP LABELS: BOLD, CARDED, NO BULLET MARKER (both viewports)
   ----------------------------------------------------------------------------
   EN's earlier style request (EN-034, re-queued in
   _REQUEST-CSS-video-facade-component.md): the markup is live on
   /Top-10-Dive-Sites.php — `div.ds-index-card > ul.ds-index >
   li.ds-index__group > strong + ul > li > a` — but the styling died in the
   main.css rollback, so the three group labels («Sipadan's / Mabul's /
   Kapalai's Top Dive Sites») render as plain list rows with markers.

   Card treatment matches the site's other cards (surface-2 + hairline +
   radius, the same recipe as .pk-card / .top10-card), and the markers are
   removed at both levels — the ruling says both viewports, so nothing here is
   media-scoped.
   ============================================================================ */
.ds-index-card {
  background: var(--c-surface-2);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-lg, 16px);
  padding: var(--sp-4) var(--sp-5);
  margin: var(--sp-5) 0;
  box-shadow: var(--shadow-sm);
}
.ds-index,
.ds-index ul {
  list-style: none;                    /* NO bullet marker, either level */
  margin: 0;
  padding: 0;
}
.ds-index li { list-style: none; margin: 0; }
.ds-index li::marker { content: none; }
.ds-index__group + .ds-index__group { margin-top: var(--sp-4); }
.ds-index__group > strong {
  display: block;
  font-weight: var(--fw-bold);
  color: var(--c-navy);
  font-size: var(--fs-md);
  margin-bottom: var(--sp-2);
}
.ds-index__group > ul > li {
  padding: var(--sp-1) 0;
  border-bottom: 1px dashed var(--c-hairline);
}
.ds-index__group > ul > li:last-child { border-bottom: 0; }
.ds-index__group a { text-decoration: none; }
.ds-index__group a:hover, .ds-index__group a:focus-visible {
  text-decoration: underline;
  color: var(--c-teal-dark);
}
@media (min-width: 900px) {
  .ds-index { columns: 3; column-gap: var(--sp-6); }
  .ds-index__group { break-inside: avoid; }
  .ds-index__group + .ds-index__group { margin-top: 0; }
}

/* ============================================================================
   CSS-R15 — PAGE-SPECIFIC UNIFORMITY (three owner-named pages)
   ----------------------------------------------------------------------------
   CSS has no URL selector, so each fix is scoped to the SHAPE that produces the
   defect. Where a shape turned out to be shared, that is stated in the report —
   the alternative (leaving 27 sibling pages inconsistent) is worse.

   ① /fr/Cours-de-plongee-PADI.php — «PADI Divemaster» / «MSDT» / «IDC».
      Measured cause: the first row is `<li><h2><a>PADI Divemaster</a></h2></li>`
      while the other two are `<li><a><strong>…</strong></a></li>`, so one row
      renders at h2 size and two at body size. `li > h2` occurs exactly ONCE in
      the whole corpus (verified), so this rule is page-specific in effect: an h2
      nested in a list item is a ROW LABEL, not a section heading.

   ② /fr/Hotels-de-plongee.php — «Mabul Water Bungalows» and «Seaventures Dive
      Rig». Measured: `li.pk-toc__tier` renders 19px beside 16px
      `li.pk-toc__resort` SIBLINGS, and the ToC mixes rows that sit inside a `ul`
      with rows that sit directly under the nav, giving two left edges 21px apart
      (1122 vs 1143 at 1920; 59 vs 80 at 414).
      A tier row with NO sub-list is not a heading — it is a row — so it reads at
      row size. Indents are equalised only for a ToC that actually has the orphan
      shape (`:has(> li)`), so hierarchical ToCs keep their indent.

   ③ /fr/Hotels-a-Semporna.php — «Homestays». Already uniform in the live tree:
      measured 16px on all 12 rows at both viewports (the frozen baseline had two
      sizes, 16px and 17px). No rule added; the numbers are in the report.
   ============================================================================ */
/* ① a list-item heading is a row label */
.content-card li > h2 {
  font-size: var(--fs-base);
  font-weight: var(--fw-semi);
  font-family: var(--font-sans);
  line-height: 1.4;
  margin: 0;
  letter-spacing: 0;
}
.content-card li > h2::after { content: none; }      /* no gold section divider on a row */

/* ② a tier row with no sub-list reads at row size, like its siblings */
/* Specificity, measured the hard way: the winning rule is the ROLE-based
   `html[lang="fr"] .pk-toc > ul > li.pk-toc__tier > a` at (0,3,4) — and its own comment
   says a tier row is large because it "HEADS a tier". Here it heads nothing: the page's
   ToC markup is malformed (a `</li>` without its opening `<ul>`), so the parser leaves
   the tier and the four resort rows as SIBLINGS and drops three more tiers outside the
   list entirely. `:not(:has(> ul))` states the intent and reaches (0,3,5), which is why
   it wins. Reported to FR/MAIN as a markup defect. Because the indent is set in `em`,
   normalising the size also collapses the two left edges into one. */
html[lang] .pk-toc > ul > li.pk-toc__tier:not(:has(> ul)) > a,
html[lang] .pk-toc > li.pk-toc__tier:not(:has(> ul)) > a,
html[lang] .pk-toc li.pk-toc__tier:not(:has(> ul)) { font-size: 1rem; }
/* ② one left edge for a ToC that mixes rows inside a list with orphan rows */
/* absolute px, not em: with `em` the orphan row and the in-list rows resolved to 20px
   and 19.2px and the edges stayed 1px apart. */
/* Both edges pinned to the SAME absolute value. Two reasons it has to be absolute and
   this specific: the FR rule `html[lang="fr"] .pk-toc ul {padding-left:1.25em}` sits at
   (0,3,2) and resolves to 21.25px at the list's 17px, while the orphan row inherits the
   16px I just set — so `em` on the two boxes can never agree. The `.pk-toc--bullets`
   variant reaches (0,3,3) and wins; measured result is one edge, 80px = 80px. */
.pk-toc:has(> li) > li,
html[lang] .pk-toc:has(> li) ul,
html[lang] .pk-toc.pk-toc--bullets:has(> li) ul { padding-left: 20px; }
.pk-toc:has(> li) > li, .pk-toc:has(> li) ul > li { margin-left: 0; }
.pk-toc:has(> li) > li { list-style: disc; }

/* ============================================================================
   CSS-R16 — CSS-D1 CLOSED: NO MOBILE HOMEPAGE OVERFLOW
   ----------------------------------------------------------------------------
   Measured cause, at last isolated to ONE element. At 390px the EN homepage's
   scrollWidth is 417 against a 390 viewport, and after excluding the off-canvas
   drawer (parked right of the viewport on purpose) exactly ONE element crosses
   the edge: `span.intro-line__place` holding «Kapalai», x=300 w=118, +27.3px.

   Why it cannot wrap today: the markup is
     <span class="intro-line__place">Sipadan</span><span class="intro-line__dot">·</span>…
   with NO whitespace between the spans, and each place is `white-space: nowrap`.
   A line with no break opportunity cannot wrap however narrow the column gets —
   so this was never a sizing problem, it was a missing break opportunity.

   The fix gives the line a break opportunity after each gold dot (a zero-width
   space, which paints nothing) and drops the trailing letter-space of the last
   place, which is the 3.4px that pushed «Kapalai» past the edge even at 414.
   Mobile-scoped, so the desktop hero line stays byte-identical.
   ============================================================================ */
@media (max-width: 1023px) {
  .intro-line__dot::after { content: "\200B"; }        /* zero-width space: a break point */
  .intro-line__place:last-of-type { margin-right: -0.18em; }   /* trailing letter-space */
  .intro-line { text-wrap: balance; }
}

/* ============================================================================
   CSS-R18 — M-HOME v2: SEVEN NAV BUTTONS + FLAG ROW (mobile homepages)
   ----------------------------------------------------------------------------
   Supersedes the R17 card block (owner design correction). The markup is emitted
   by includes/chrome/home.cards.php ONLY on a mobile user agent, so these rules
   have nothing to match on desktop and the desktop homepages are untouched.

   NO BACKGROUND IMAGE INSIDE A BUTTON — ever. Photos behind text are ruled out
   for legibility, so the surface is flat: a light-blue wash mixed from the theme's
   own airy-blue token `--c-btn-call: #8ec9df` at 22% over white (= #e3f1f7), with
   `#e9f4f9` as the literal fallback where `color-mix()` is unavailable. That is the
   exact token proposed; a filled surface is a one-line change to
   `background: var(--c-btn-call)` if you prefer it stronger.

   Contrast, measured against the theme values: label `--c-navy #0e2a44` on
   #e3f1f7 ≈ 13:1, far past AA. Rows are >=56px tall (min-height), full width, and
   the whole row is the tap target. The leading glyph mirrors the m-original's
   <i class="finger|dollar|hotel|…"> row icons.
   ============================================================================ */
.home-nav {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-2);
  margin: var(--sp-3) 0 var(--sp-4);
}
.home-nav__btn {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 56px;                       /* >= the 44px tap floor, with room */
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm, 8px);
  background: #e9f4f9;                                        /* fallback */
  background: color-mix(in srgb, var(--c-btn-call) 22%, #fff);
  background-image: none;                 /* the ruling, stated as a declaration */
  color: var(--c-navy);
  text-decoration: none;
}
.home-nav__btn:hover,
.home-nav__btn:focus-visible {
  background: #ddeef6;
  background: color-mix(in srgb, var(--c-btn-call) 34%, #fff);
  border-color: var(--c-teal);
}
.home-nav__btn:focus-visible { outline: 3px solid var(--c-gold); outline-offset: 2px; }
.home-nav__icon {
  flex: 0 0 24px;
  width: 24px; height: 24px;
  color: var(--c-teal-dark);
}
.home-nav__label {
  font-family: var(--font-sans);
  /* R20 ①: one step up the scale — var(--fs-base) 17px -> var(--fs-md) 19px,
     readable at arm's length */
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  line-height: 1.25;
  letter-spacing: 0.01em;
}

/* ② the six flags, one line, directly below the buttons */
.home-flags {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  align-items: center;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-5);
  padding: var(--sp-2) 0 var(--sp-3);
  border-bottom: 1px solid var(--c-hairline);
}
.home-flags__flag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px; min-height: 44px;      /* tap floor on a flag too */
  border-radius: var(--radius-sm, 8px);
  text-decoration: none;
}
.home-flags__flag svg { width: 30px; height: 20px; display: block; border-radius: 2px; }
/* R20 ③: the selected language is marked by a LIGHT-BLUE WAVY line under the flag.
   The gold curve is withdrawn. The wave is an inline SVG data-URI painted in the theme's
   airy-blue token `--c-btn-call: #8ec9df` (%238ec9df url-encoded), so it costs no request
   and no extra asset. */
.home-flags__flag { position: relative; }
.home-flags__flag.is-current { background: var(--c-surface-2); }
.home-flags__flag.is-current::after {
  content: "";
  position: absolute;
  left: 4px; right: 4px; bottom: 3px;
  height: 6px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='6' viewBox='0 0 24 6'%3E%3Cpath d='M0 4c2-2.6 4-2.6 6 0s4 2.6 6 0 4-2.6 6 0 4 2.6 6 0' fill='none' stroke='%238ec9df' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: repeat-x;
  background-size: 24px 6px;
}
@media (min-width: 1024px) {
  /* belt and braces: were a desktop UA ever served the block, it must not render */
  .home-nav, .home-flags { display: none; }
}

/* ============================================================================
   CSS-R19 — IMAGE NORMALIZATION: A CONTENT IMAGE IS A CONTENT IMAGE
   ----------------------------------------------------------------------------
   Owner regression find after R14a. Measured causes, all three of them:

   ① The placement wrapper is `figure.figure--left` — it does NOT carry the class
      `figure`, so `.figure img { border-radius; box-shadow }` and the block
      display never reached it. Measured on /Mataking-Resort.php desktop:
        MTK-ISLAND-KAYAK  in FIGURE.figure       -> 295px, radius 16px, shadow, block
        mataking-private-beach in FIGURE.figure--left -> 182px, radius 0, no shadow, INLINE
      Same page, same kind of photo — the only difference was the wrapper class.

   ② The CSS-R5 sequence module was grouping those `figure--left` figures (same
      parent + same className = a "sequence") and unifying them at the group's
      smallest natural: 182px on Mataking, 280px on Pom-Pom. That is what made
      them small. `figure--left` / `figure--right` are PLACEMENT classes, not run
      classes, so the module now skips them (js/site.js). `.figure--half` stays a
      run class and is untouched.

   ③ The thin dark border is the legacy `border="1"` ATTRIBUTE in the corpus
      (measured `1px solid rgb(33,49,63)` on Beach-Villa, Water-Villa,
      Garden-Villas, Restaurant, Seafood-Restaurant, Yin-Yang-Spa,
      turtle-hatchery, pom-pom-liveaboard). CSS overrides the attribute.

   THE RULE: being alone-on-the-left changes nothing visually. Two exceptions
   only — the permitted lone float (CSS-R14a, untouched) and DIVE MAPS below.
   ============================================================================ */
.content-card .figure--left,
.content-card .figure--right {
  display: block;
  margin: var(--sp-5) 0;
}
.content-card .figure--left > img,
.content-card .figure--right > img,
.content-card .figure--left > a > img,
.content-card .figure--right > a > img,
.content-card > img,
.content-card p > img {
  display: block;
  border: 0;                        /* kills the legacy border="1" attribute */
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  height: auto;
}
/* the same two properties for any content image that carries the legacy attribute,
   wherever it sits */
.content-card img[border] { border: 0; }

/* ---- exception ②: DIVE MAPS render at natural size, up to the container ----
   A map is read, not admired: it may never be downscaled by a sequence rule.
   Measured before: pom-pom-dive-map.webp, natural 500, rendered 280 (squashed by
   --seq-w). `--nat-w` is the natural width stamped by js/site.js.
   `!important` is required to beat `.seq-uniform { width: var(--seq-w) }`. */
.content-card img[src$="dive-map.webp"],
.content-card img[src$="dive-map.jpg"] {
  width: min(100%, var(--nat-w, 100%)) !important;
  max-width: 100% !important;
  height: auto;
}
.content-card .seq-uniform:has(> img[src$="dive-map.webp"]),
.content-card .seq-uniform:has(> a > img[src$="dive-map.webp"]) {
  width: auto !important;
  max-width: 100% !important;
}

/* ============================================================================
   CSS-R21a — FLEX-RESOLUTION LAW: A COMPONENT IMAGE IS SIZED BY THE LAYOUT
   ----------------------------------------------------------------------------
   Owner ruling: every facade in a grid renders at IDENTICAL dimensions; the box
   comes from the grid column and the picture is fitted into it — never sized by
   the file's natural resolution — and never exceeds its container.

   Measured cause on /Sipadan-Photo-Video.php at 1920 (cards all 351px wide):
     Sipadan-Island.webp     natural 995 -> thumb 351   (filled)
     baby-turtle.webp        natural 280 -> thumb 280   (short)
     barracuda-tornado.webp  natural 300 -> thumb 300   (short)
     diver-turtle.webp       natural 300 -> thumb 300   (short)
   i.e. CSS-R13b's desktop no-upscale cap `max-width: min(100%, var(--nat-w))`
   was quite correctly protecting CONTENT photographs — and quite wrongly sizing
   a COMPONENT part. The two laws are separated here:

     CONTENT image  — no-upscale stands (R13b/AD-16): a photograph in prose is
                      never blown past its own pixels.
     COMPONENT part — a thumbnail inside a card/grid whose box the layout owns:
                      it FILLS the box and is cropped to fit (object-fit: cover),
                      whatever the file measures. Swapping in a higher-resolution
                      file must never change a layout, and a 4K file must never
                      escape its frame.

   Component parts are named, not guessed: they are the BEM `__thumb` / `__media`
   image parts and the members of the grid components.
   ============================================================================ */
.video-facade__thumb,
.room__media img,
.gallery-grid figure img,
.snap-gallery img,
.carousel__slide img {
  width: 100% !important;          /* the layout decides */
  max-width: 100% !important;      /* ... and the frame is never exceeded */
  height: auto;
  object-fit: cover;
}
.video-facade__thumb { aspect-ratio: 16 / 9; }
/* the facade grid's rows are already equal (grid-auto-rows: 1fr, CSS-R18); with the
   thumb filling its box every card in a row is now identical in both dimensions. */

/* ============================================================================
   CSS-R21b — TOP FIGURE IS A PLAIN CENTRED FIGURE + ROUNDED CORNERS EVERYWHERE
   ----------------------------------------------------------------------------
   ① The top image on /dive-resorts.php and /Sipadan-Resorts.php.
      Measured at 1920: `mabul-sunset-watervillage.webp`, natural 300, rendered
      300 — inside a `.page-hero__media` wrapper **726px wide** carrying
      `border-radius: 20px`, `overflow: hidden` and a shadow. So the wrapper drew
      a card 726 wide around a 300px picture: the "dead right space", with the
      IMAGE itself square (radius 0, measured).
      Cause: HERO RULE v3 accepted upscaling a hero to the column, but CSS-R13b's
      no-upscale law now caps the image at its natural 300 — the wrapper kept the
      old full-column card.
      Fix: the wrapper shrinks to its picture (`width: fit-content`) and centres,
      drops the card (no shadow, no clip), and the IMAGE carries the four rounded
      corners. A full-width hero is unaffected: fit-content is then the column.

   ② Round-corner sweep. Measured square (radius 0) on
      /diving-holiday-packages.php and /Borneo-Dive-Resorts.php listing images
      while the same class rendered 8px on /dive-resorts.php. The standard
      treatment now applies to EVERY content image, whatever its wrapper class,
      on every page and language — stated once, here, instead of per class.
      Components that own a radius (the facade thumb) keep theirs.
   ============================================================================ */
.content-card .page-hero__media {
  width: fit-content;
  max-width: 100%;
  margin-left: auto;
  margin-right: auto;
  box-shadow: none;
  border-radius: 0;
  overflow: visible;
}
.content-card .page-hero__media img {
  border-radius: var(--radius);
  margin-left: auto;
  margin-right: auto;
}
/* ② one standard: every content image is rounded */
.content-card img {
  border-radius: var(--radius);
}
.content-card .island-circle__disk,
.content-card .video-facade__thumb {
  border-radius: var(--radius);      /* components keep the same value, stated */
}

/* ============================================================================
   CSS-R22a — THE LEAD IMAGE FLOATS (amendment to the placement law)
   ----------------------------------------------------------------------------
   Owner amendment: a page's LEAD image floats right of the FIRST PARAGRAPH even
   when other images exist below it — the REF-0 state of the dive-site family
   (/Sipadan/*, scuba-dive-*, Mataking-dive-site, Siamil) and of the small info
   pages. CSS-R14a's lone-image law is unchanged; this is a SECOND permitted
   float, and everything else stays a block in flow.

   Measured before, /Sipadan/Coral-gardens.php at 1920: the baseline (REF-0)
   floats `humpback.webp` right with margin-top 0; live had `float: none` after
   R14a. /scuba-dive-sipadan.php: baseline image top 475 = paragraph top 475;
   live 475 vs 809 — the un-floated block had pushed the prose down.

   `.is-lead` is stamped by js/site.js, which also writes the vertical offset:
   the module only stamps an image that has prose after it and NO heading
   immediately after it, so the HERO-TITLE rule stands — never beside the title.
   Desktop only; mobile keeps CSS-R7's block treatment.
   ============================================================================ */
@media (min-width: 1024px) {
  .content-card .is-lead {
    float: right !important;
    width: auto;
    max-width: min(42%, 420px);
    margin-right: 0 !important;
    margin-left: var(--sp-4) !important;
    margin-bottom: var(--sp-4) !important;
    /* margin-top is written by the module: the lead's top aligns with the top of
       the paragraph block that follows it (the ~14 "too high" pages) */
  }
  .content-card .is-lead > img,
  .content-card .is-lead > a > img { width: 100%; height: auto; }
  .content-card img.is-lead { width: auto; max-width: min(42%, 420px); }
}

/* ============================================================================
   CSS-R22b — DIVE-SITE MAP PAGES: LEFT-ALIGNED TEXT, MAPS AT FULL WIDTH
   ----------------------------------------------------------------------------
   Measured on /Dive-site-maps.php and /scuba-dive-kapalai.php at 1920:
     * paragraph text-align came out `center` / `-webkit-center` (the legacy
       corpus centres those blocks), against the site's left-aligned prose
     * `Sipadan-Dive-Site-Map.webp`, natural 898, rendered **347** — a map you
       cannot read, because its wrapper is 347px wide, not because of the image
   A map renders at the CONTAINER width, or its natural width if that is smaller;
   its wrapper is widened to the column so the ⑦(a) full-width map law can act.
   ============================================================================ */
.content-card:has(img[src*="dive-map"]) p,
.content-card:has(img[src*="Dive-Site-Map"]) p,
.content-card:has(img[src*="dive-map"]) .text,
.content-card:has(img[src*="Dive-Site-Map"]) .text {
  text-align: left;
}
.content-card :is(figure, .section, .imgvilla, .imgvilla2, .imgvilla3):has(> img[src*="Dive-Site-Map"]),
.content-card :is(figure, .section, .imgvilla, .imgvilla2, .imgvilla3):has(> a > img[src*="Dive-Site-Map"]),
.content-card :is(figure, .section, .imgvilla, .imgvilla2, .imgvilla3):has(> img[src*="dive-map"]) {
  width: 100% !important;
  max-width: 100% !important;
  float: none !important;
  margin-left: 0 !important;
  margin-right: 0 !important;
}
.content-card img[src*="Dive-Site-Map"] {
  width: min(100%, var(--nat-w, 100%)) !important;
  max-width: 100% !important;
  height: auto;
}

/* ============================================================================
   CSS-R22c — A NARROW CONTENT IMAGE IS CENTRED, NOT LEFT-HUGGING
   ----------------------------------------------------------------------------
   Owner list: Regatta-Lepa (out of frame right, desktop), royal-resort-semporna
   top image, sea-star-resort, Seaventures (stray LEFT padding on desktop),
   Underwater-Wedding (left padding desktop). Measured at 1920: those images
   render 277–530px inside a 726px column with `margin-left: 0` and the slack
   entirely on the right (e.g. Regatta-Lepa 530 wide, 196px of empty space to the
   right of it).
   A content image narrower than its column is centred — the same treatment
   mobile already gets from OWNER EYEBALL-2 R-E. Floated images are exempt (the
   lone-image law and the lead-image law place those deliberately).
   ============================================================================ */
@media (min-width: 1024px) {
  /* at ANY depth: the owner's pages nest the image inside DIV.text > DIV.imgvilla2,
     so a direct-child selector reached only some of them (measured: Regatta-Lepa
     centred, royal-resort and sea-star still hugging left at gaps 0/0). */
  .content-card img:not(.is-lead):not(.img--lone):not(.video-facade__thumb),
  .content-card :is(figure, .section, .imgvilla, .imgvilla2, .imgvilla3):not(.is-lead):not(.img--lone):not(.figure-pair):not(.resort-thumb):not(.page-hero__media) {
    margin-left: auto;
    margin-right: auto;
  }
  .content-card :is(figure, .section, .imgvilla, .imgvilla2, .imgvilla3):not(.is-lead):not(.img--lone) > img {
    margin-left: auto;
    margin-right: auto;
    display: block;
  }
  /* the stray left padding the owner called out */
  .content-card .imgvilla2 > .section { padding-left: 0; }
}

/* ============================================================================
   CSS-R22d — TOP-10: THE MANTIS SITS RIGHT OF THE TOC, TOPS ALIGNED
   ----------------------------------------------------------------------------
   Measured before at 1920: the ToC card spans the full 726px column (x 743–1469)
   and `mantis-with-eggs` floats right at x=1269, y=408 — it OVERLAPS the card
   (measured overlap: true, top delta −202px).
   A block only flows beside a float when it establishes a block formatting
   context, so the card gets one; it then shrinks to the space left of the float
   instead of running under it. The float's top is aligned to the card's top by
   the CSS-R22a module, whose reference now includes a card-like block.
   ============================================================================ */
@media (min-width: 1024px) {
  .content-card .ds-index-card { overflow: hidden; }
}

/* ============================================================================
   CSS-R23 — .feat-row MOBILE ORDER (executes EN's request, owner EN-EYEBALL-R1 ①)
   ----------------------------------------------------------------------------
   Owner ruling: on /Budget-Non-Diving-Packages.php the anemonefish must sit ABOVE
   the «Budget Prices» list on mobile. Desktop keeps list-left / image-right.

   EN is right that markup cannot do it: `.feat-row` is one flex container, so DOM
   order sets BOTH the desktop left/right order and the mobile stacking order.
   `order` is the one property that separates them — and it changes nothing on
   desktop, where this block does not apply.

   SCOPE — measured, so EN does not have to stamp anything. 14 pages carry
   `.feat-row` and they fall into exactly two families:
     * `class="feat-row"`            — the Budget-Non-Diving-Packages family (7 langs),
                                       the card the owner ruled on
     * `class="feat-row feat-row--toc"` — the ToC row (7 langs), whose figure CSS-R9
                                       deliberately places centred BELOW the ToC
   So `:not(.feat-row--toc)` hits exactly the ruled card in every language and
   leaves the ToC pages as R9 arranged them. `.feat-row--figfirst` is honoured too,
   for a future opt-in on a page that does carry `--toc`.
   ============================================================================ */
@media (max-width: 767px) {
  .feat-row:not(.feat-row--toc) > .feat-row__fig,
  .feat-row--figfirst > .feat-row__fig {
    order: -1;
  }
}

/* ============================================================================
   CSS-R23c-CN — CHINESE FLYOUT: ONE CONTIGUOUS HOVER REGION, NO CARET
   ----------------------------------------------------------------------------
   Owner live test: the card vanished as soon as the pointer left the flag, so
   nothing in it could ever be clicked. Two causes, both fixed here.

   1. A 6px DEAD GAP. The card was offset with `top: calc(100% + 6px)`, so the
      pointer crossed six pixels that belonged to neither the trigger nor the
      card. The offset is now transparent PADDING inside `.lang-cn__flyout`,
      which starts flush at `top: 100%`; the visible card is the inner
      `.lang-cn__card`. The hover region is therefore continuous from flag to
      options, and both live inside `.lang-cn`, one shared wrapper.
   2. NO TOLERANCE. Hover open/close is now driven by js/site.js with a 250ms
      grace period on leave, which CSS cannot express for a `display` switch.
      The `:focus-within` rule below stays as the no-JS keyboard path.

   R23c also REMOVES the caret from the flag strips (owner ruling: hover
   discovery is enough, the strip is pure flags). The trigger BUTTON survives —
   it is the keyboard and touch path and carries aria-expanded — but in the
   strips it is clipped to a focusable 1px. The mobile home line, where hover
   does not exist, keeps the visible caret it shipped with in R23b.
   ============================================================================ */
.lang-cn {
  position: relative;
  display: inline-flex;
  align-items: center;
}
/* strips: no caret, no gap in the flag rhythm — but still keyboard-reachable */
.lang-cn__toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  background: transparent;
  color: inherit;
  cursor: pointer;
}
.lang-cn__caret { display: none; }

.lang-cn__flyout {
  position: absolute;
  top: 100%;                 /* flush with the trigger: no dead pixels between */
  left: 0;
  z-index: 120;
  display: flex;
  padding-top: 8px;          /* the visual gap, transparent and hoverable */
  background: transparent;
  border: 0;
}
.lang-cn__flyout[hidden] { display: none; }
/* the no-JS keyboard path; hover is js/site.js, which adds the 250ms tolerance */
.lang-cn:focus-within:not(.is-dismissed) .lang-cn__flyout { display: flex; }
.lang-cn__flyout.is-above {
  top: auto;
  bottom: 100%;
  padding-top: 0;
  padding-bottom: 8px;
}
.lang-cn__flyout.is-right { left: auto; right: 0; }

/* the card itself */
.lang-cn__card {
  display: flex;
  flex-direction: column;
  min-width: 132px;
  padding: 4px;
  /* STATED, not inherited — the footer is a dark surface */
  background: var(--c-white);
  border: 1px solid var(--c-hairline);
  border-radius: var(--radius-sm, 8px);
  box-shadow: 0 12px 28px rgba(4, 12, 20, 0.28);
}
/* AP-3: `.site-footer a { color:#fff }` is (0,1,1) and beats a single-class rule,
   which is how the labels went white-on-white. Every colour rule for this card is
   written at (0,2,1) or better so it wins in the footer as well as the header. */
.lang-cn__card a.lang-cn__opt {
  position: relative;
  display: block;
  padding: 7px 10px 9px;
  border-radius: var(--radius-sm, 6px);
  color: var(--c-navy);
  background: transparent;
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-reg);
  line-height: 1.25;
  text-decoration: none;
  white-space: nowrap;
}
.lang-cn__card a.lang-cn__opt:hover,
.lang-cn__card a.lang-cn__opt:focus-visible {
  background: #e9f4f9;
  background: color-mix(in srgb, var(--c-btn-call) 20%, #fff);
  color: var(--c-navy);
}
.lang-cn__card a.lang-cn__opt.is-active { font-weight: var(--fw-semi); color: var(--c-navy); }
.lang-cn__card a.lang-cn__opt.is-active::after {
  content: "";
  position: absolute;
  left: 10px; right: 10px; bottom: 2px;
  height: 4px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='6' viewBox='0 0 24 6'%3E%3Cpath d='M0 4c2-2.6 4-2.6 6 0s4 2.6 6 0 4-2.6 6 0 4 2.6 6 0' fill='none' stroke='%238ec9df' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: repeat-x;
  background-size: 24px 6px;
}

/* the mobile homepage line keeps R23b's visible caret and tap target: there is no
   hover on a phone, so the affordance has to stay */
.home-flags .lang-cn { gap: 2px; }
.home-flags .lang-cn__toggle {
  position: static;
  width: 18px;
  height: 44px;
  overflow: visible;
  clip-path: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.home-flags .lang-cn__caret {
  display: block;
  width: 0; height: 0;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  border-top: 4px solid currentColor;
  opacity: 0.65;
}
.home-flags .lang-cn__card a.lang-cn__opt { padding: 11px 12px 13px; font-size: var(--fs-base); }

/* the mobile language menu keeps the two script rows from R23 */
.lang-menu__list a.is-current {
  position: relative;
  font-weight: var(--fw-semi);
}
.lang-menu__list a.is-current::after {
  content: "";
  position: absolute;
  left: 12px; right: 12px; bottom: 2px;
  height: 4px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='6' viewBox='0 0 24 6'%3E%3Cpath d='M0 4c2-2.6 4-2.6 6 0s4 2.6 6 0 4-2.6 6 0 4 2.6 6 0' fill='none' stroke='%238ec9df' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: repeat-x;
  background-size: 24px 6px;
}

/* ============================================================================
   CSS-R24 ① — ERROR PAGES: THE GOLDEN RULE FOLLOWS ITS TITLE
   ----------------------------------------------------------------------------
   `.content-card h1::after` (the site-wide 56px gold rule) has no side margins,
   so it sits at the left edge of the h1's box. That is right for the ranged-left
   titles it was written for, and wrong on 404/403, where `.error-hero` centres
   the whole block via `text-align: center` — a block-level pseudo-element does
   not follow text-align, so the title was centred and its rule was not. The
   existing `h1.text-center::after` rule cannot help: the error hero centres from
   the PARENT, and the h1 carries no class.
   ============================================================================ */
.error-hero h1::after { margin-left: auto; margin-right: auto; }

/* ============================================================================
   CSS-R25 — GALLERY SECTIONS STACK: PHOTOS, THEN VIDEO
   ----------------------------------------------------------------------------
   `/fr/Gallerie-de-Photo-Video.php` rendered the video gallery to the RIGHT of the
   photos at 1920. Measured cause: on that page alone the `<section class="video-facades">`
   sits INSIDE `<div class="masonry">` (every other gallery page has it as a sibling of
   the masonry). `.masonry` is a 3-column multicol container, so a block placed inside it
   is column content — it flowed into the next column instead of below, and inherited the
   column's width (237px at 1920, 169px at 414, against 726/346px elsewhere).

   `column-span: all` takes the section out of the column flow: the figures before it fill
   the columns, the section spans the full width, and anything after resumes below. The
   order photos-then-video is therefore honoured wherever the markup puts the section.
   ============================================================================ */
.masonry > .video-facades,
.masonry .video-facades {
  column-span: all;
  width: 100%;
  margin-top: var(--sp-5);
}

/* ============================================================================
   CSS-R25b ① — MABUL-BACKPACKERS: CONTENT IMAGES ALIGN LEFT (DESKTOP)
   ----------------------------------------------------------------------------
   Owner PAGE ruling for /Mabul-Backpackers.php and its fr twin: single images sit
   at the column's left edge and a left+right pair is anchored left as a pair. This
   is a deliberate, page-scoped exception to two site-wide laws that otherwise
   centre them — AD-33/CSS-R22c ("a content image narrower than its column is
   centred", specificity (0,4,1), which beats `.figure--left img`) and CSS-R21b
   ("being alone-on-the-left changes nothing visually"). Measured before at 1920:
   lone images at column offsets 189 / 186 / 264 with the slack split evenly.

   The page hook is the canonical link, so no page file is touched (page content is
   not this terminal's scope) and both twins are covered by one suffix match. The
   whole block is >= 1024px: mobile keeps the standard centred stack.

   SPECIFICITY, counted rather than assumed: R22c's figure selector carries SEVEN
   classes (`.content-card` + the `:is()` argument + five `:not()`s) at (0,7,0). The
   first draft here reached only (0,6,2) - `:has()` contributes its argument's
   (0,2,1) - so the images moved while the half-figures stayed centred at 184.5px.
   The four `:not()`s below bring it to (0,8,2), and a lead or lone image on this
   page still obeys its own law.
   ============================================================================ */
@media (min-width: 1024px) {
  html:has(link[rel="canonical"][href$="/Mabul-Backpackers.php"]) .content-card
    :is(figure, .imgvilla, .imgvilla2, .imgvilla3, .section):not(.page-hero__media):not(.resort-thumb):not(.is-lead):not(.img--lone) {
    margin-left: 0;
    margin-right: auto;
    text-align: left;
  }
  html:has(link[rel="canonical"][href$="/Mabul-Backpackers.php"]) .content-card
    :is(figure, .imgvilla, .imgvilla2, .imgvilla3, .section):not(.page-hero__media):not(.resort-thumb):not(.is-lead):not(.img--lone) > img,
  html:has(link[rel="canonical"][href$="/Mabul-Backpackers.php"]) .content-card
    :is(figure, .imgvilla, .imgvilla2, .imgvilla3, .section):not(.page-hero__media):not(.resort-thumb):not(.is-lead):not(.img--lone) > a > img {
    margin-left: 0;
    margin-right: auto;
  }
  /* the pair travels together: anchored left, its own spacing untouched */
  html:has(link[rel="canonical"][href$="/Mabul-Backpackers.php"]) .content-card .figure-pair {
    justify-content: flex-start;
    margin-left: 0;
    margin-right: auto;
  }
}

/* ============================================================================
   CSS-R25b ② — A TABLE CAPTION SPANS ITS TABLE, NOT ITS FIRST COLUMN
   ----------------------------------------------------------------------------
   Owner defect: the dive-site comparison heading rendered "compressed inside the
   first column". Measured at 414 on /Mabul/froggy-lair.php: caption 119px wide and
   147px tall (7 wrapped lines) against a 346px table — 174px tall on the fr twin,
   whose heading is longer. Desktop was already correct (726 of 726).

   Cause: the mobile card rules set `.spot-table { display: block }` (and CSS-R3
   does the same for `.t-cards`), which leaves the caption as the only
   `display: table-caption` box in the subtree. It then belongs to an anonymous
   table wrapper that shrink-to-fits — so the heading sized itself to the widest
   label instead of to the table. Making the caption a block ties it back to the
   full container width, above the cards.
   ============================================================================ */
@media (max-width: 1023px) {
  .spot-table caption,
  .t-cards caption,
  .content-card table caption {
    display: block;
    width: 100%;
    text-align: left;
  }
}

/* ============================================================================
   CSS-R26 — SOURCE SERIF 4, SELF-HOSTED, ON ONE SAMPLE PAGE
   ----------------------------------------------------------------------------
   Owner ruling: Source Serif 4 site-wide, STAGED. This round ships the hosting and
   one representative page only; the sweep fires on the owner's confirm.

   SAMPLE PAGE: /scuba-dive-sipadan.php (EN) — chosen because it is long-form content
   with h1, five h2s, 27 paragraphs, figures, a list and a data table, AND because it
   is already a standing gate page (`divesites` en), so the round-to-round isolation
   gives an exact before/after with no extra capture.

   SELF-HOSTED, two files, no third-party request at runtime:
     css/fonts/source-serif-4-latin.woff2       50,824 bytes
     css/fonts/source-serif-4-latin-ext.woff2   42,040 bytes
   Both are the VARIABLE upstream files (weight axis 200-900), so one file per subset
   covers 400/600/700 — the three weights this site uses — with no synthetic bolding.

   FALLBACK STACK: "Source Serif 4", Georgia, serif.
   Georgia is deliberate: it is a metric-near serif present on every Windows and macOS
   install, so the fallback frame is close in x-height and advance width and a swap
   would move little. `serif` is the last resort.

   FONT-DISPLAY: block, with the page-scoped `<link rel="preload">` in header.php.
   The reasoning is measured, not assumed. Three candidates:
     swap     — paints Georgia, then swaps. A flash of fallback BY DEFINITION.
     optional — 100ms window, then Georgia for the rest of the visit, never swapping.
                No flash, but MEASURED on this page it lost the race every time: the
                sample rendered at Georgia's 324.89px instead of the webfont's 300.28px.
                The preload cannot save it, because these pages emit <head> themselves
                and header.php — the only head-adjacent file this terminal owns — is
                included after <body>, so the hint arrives level with the CSS request,
                not ahead of it.
     block    — the first painted glyphs are already Source Serif 4: no fallback frame
                and no swap, at the cost of a brief invisible-text window (capped at 3s
                by the spec, and in practice one frame here — 50KB, same origin,
                preloaded).
   `block` is the only one of the three that satisfies "no flash of fallback" AND
   actually shows the owner the typeface. If the sweep later needs the invisible window
   gone, the fix is a `size-adjust`/`ascent-override` fallback face so Georgia and
   Source Serif 4 occupy identical space — that is a sweep-time decision, not a
   sample-time one.
   ============================================================================ */
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 200 900;                 /* variable axis: 400/600/700 all real */
  font-display: block;
  src: url('/css/fonts/source-serif-4-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 200 900;
  font-display: block;
  src: url('/css/fonts/source-serif-4-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* METRIC-MATCHED FALLBACK. `font-display: block` keeps fallback GLYPHS off the screen,
   but the browser still lays the invisible text out in the fallback's metrics, so when
   the webfont lands the page reflows — measured: 29 of 40 boxes moved on the sample
   against 0 on the fontless control. Georgia sets the same string 6.47% narrower than
   Source Serif 4 (324.89px vs 345.92px at 17px), so Georgia is wrapped in an override
   face scaled by exactly that ratio. The fallback FAMILY is unchanged — this is still
   Georgia, and `serif` still ends the stack — it simply now occupies the same space,
   which is what removes the shift. Overriding the vertical metrics is unnecessary here:
   every line-height in this site is set in CSS, so line boxes do not depend on the
   font's own ascent/descent. */
@font-face {
  font-family: 'SS4 Metric Fallback';
  src: local('Georgia'), local('Times New Roman'), local('Times');
  size-adjust: 106.47%;
  font-display: swap;
}

/* R26-SWEEP: the sample page's `:has()` hook is RETIRED — the owner approved the
   roll-out, so the rule is global now and the hook would only be dead weight. The
   @font-face declarations, the stack, `font-display: block` and the metric-matched
   fallback above are untouched: only the reach changed. */

/* ============================================================================
   CSS-R28 — THE LEGACY align="right" ATTRIBUTE, NORMALISED TO THE FLOAT LAW
   ----------------------------------------------------------------------------
   Found by the AUDIT-100-C rendered pass: `/fr/Mabul/house-reef.php` and friends
   still floated an image at 414, and floated a non-lead image at 1920. Neither
   came from a stylesheet — `align="right"` is a presentational HTML attribute
   and the BROWSER'S OWN stylesheet turns it into `float: right`. No author rule
   ever mentioned it, so R7 (nothing floats on mobile) and R14a (only `.is-lead`
   / `.img--lone` may float on desktop) never reached these images: the laws were
   written against classes, and this shape carries none.

   Population: **248 occurrences across 238 files**, every tree. The attribute is
   page markup and not mine to delete; author rules beat the UA stylesheet, so the
   normalisation is done here and the pages are left alone.

   The two permitted floats are excluded by name, so the lead-image amendment
   (CSS-R22a) and the lone-image law (CSS-R14a) keep their exceptions — an image
   that is legitimately a lead still floats, attribute or no attribute. Everything
   else falls back to the standard treatment, which on desktop means CSS-R22c
   centres it if it is narrower than the column.
   ============================================================================ */
@media (max-width: 1023px) {
  /* R7: nothing floats on a phone — including a 330px image in a 348px column,
     which read as a right-hugging block rather than a wrap and was the defect the
     owner's list called out on eel-garden and froggy-lair. */
  .content-card img[align="right"],
  .content-card img[align="left"] {
    float: none;
  }
  .content-card :is(figure, .section, .imgvilla, .imgvilla2, .imgvilla3):has(img[align="right"]),
  .content-card :is(figure, .section, .imgvilla, .imgvilla2, .imgvilla3):has(img[align="left"]) {
    float: none;
  }
}
@media (min-width: 1024px) {
  /* R14a: only a lead or a lone image floats. `align="right"` is not a licence. */
  .content-card img[align="right"]:not(.is-lead):not(.img--lone),
  .content-card img[align="left"]:not(.is-lead):not(.img--lone) {
    float: none;
    /* R13b's no-upscale law, held through the change: once the float goes, the
       image falls into the normal flow where a width:100% rule was waiting for it
       — measured, `scorpion-fish.webp` (natural 300) stretched to 726 in the
       column. `width: auto` gives it its own size back; `max-width: 100%` keeps
       the column as the ceiling. Mobile is untouched: filling the column at 414 is
       the owner's own rule (EYEBALL-2 R-E), not an upscale defect. */
    width: auto;
    max-width: 100%;
  }
  /* the imgvilla2 shape: the WRAPPER floats and the image inside it is not a lead,
     which is how a non-lead image kept a desktop float at 1920 */
  .content-card :is(figure, .section, .imgvilla, .imgvilla2, .imgvilla3):has(img[align="right"]):not(.is-lead):not(.img--lone):not(:has(.is-lead)):not(:has(.img--lone)),
  .content-card :is(figure, .section, .imgvilla, .imgvilla2, .imgvilla3):has(img[align="left"]):not(.is-lead):not(.img--lone):not(:has(.is-lead)):not(:has(.img--lone)) {
    float: none;
  }
}

/* ============================================================================
   CSS-R26-SWEEP — SOURCE SERIF 4 FOR CONTENT TYPOGRAPHY, SITE-WIDE
   ----------------------------------------------------------------------------
   Owner verdict after the sample: APPROVED with scope. The typeface takes the
   READING surfaces — headings h1-h6, paragraphs, list items, table text,
   captions, blockquotes — in every tree. The CHROME keeps today's face: the main
   navigation, the left menu, every nav strip and fly-out, the footer nav, button
   and CTA labels, and form controls. The owner was explicit, so the exclusions
   below are written as their own rules rather than left to inheritance, and the
   probe measures them instead of trusting them.

   The @font-face declarations, the fallback stack, `font-display: block` and the
   metric-matched Georgia all stay exactly as staged in R26 — nothing about the
   loading strategy changes, only its reach. The sample page's `:has()` hook is
   retired here: it would be dead weight now that the rule is global.

   SCOPE MECHANISM: everything is anchored on `.content-card`, the article
   wrapper. Chrome lives outside it (header, `#site-nav`, footer are siblings), so
   the scope is structural, not a list of exceptions to maintain. The exceptions
   that follow exist for the chrome that is *inside* an article: CTA rows, the
   inline contact buttons, and any form the page carries.
   ============================================================================ */
/* CSS-R26b (owner verdict after seeing the sweep live): the serif is for HEADINGS.
   Body prose goes back to the face the menus use — the owner read the live pages and
   decided the serif works as a display voice, not as a reading one. */
.content-card :is(h1, h2, h3, h4, h5, h6) {
  font-family: 'Source Serif 4', 'SS4 Metric Fallback', Georgia, serif;
}
/* paragraph-level text reverts, stated rather than left to inheritance so the intent
   survives the next person reading this file */
.content-card :is(p, li, dd, dt, td, th, caption, blockquote, figcaption, address, summary),
.content-card :is(.dive-stats__num, .dive-stats__cap, .dive-stats__list dt,
                  .dive-stats__list dd) {
  font-family: var(--font-sans);
}

/* the excluded surfaces, stated: chrome that lives inside the article keeps the
   sans it has today. These are written at a higher specificity than the rule
   above so an element that is both (a CTA label inside a paragraph, a select
   inside a table cell) resolves to the chrome face. */
.content-card :is(button, input, select, textarea, label, optgroup, option, legend),
.content-card :is(.btn, .btn *, .innerContactUsButton, .innerContactUsButton *),
.content-card :is(.cta-row a, .cta-row button, .contact-card a),
.content-card :is(.site-nav, .site-nav *, .lang-menu, .lang-menu *),
.content-card :is(.home-nav, .home-nav *, .home-flags, .home-flags *),
.content-card :is(.permit-bar, .permit-bar *, .breadcrumb, .breadcrumb *),
.content-card :is(.lang-cn, .lang-cn *),
/* owner ruling: the page ToC is NAVIGATION, not prose — it reads in the sans with the
   menus, including its own heading, which is why `.pk-toc__title` is named here and no
   longer in the serif rule above. */
.content-card :is(.pk-toc, .pk-toc *, .pk-toc__title, .ds-index-card, .ds-index-card *) {
  font-family: var(--font-sans);
}
/* headings inside those surfaces follow the surface, not the article */
.content-card :is(.btn, .innerContactUsButton, .cta-row, .contact-card, .site-nav,
                  .lang-menu, .home-nav, .permit-bar, .pk-toc, .ds-index-card)
       :is(h1, h2, h3, h4, h5, h6, p, li, span) {
  font-family: var(--font-sans);
}


/* ---------- CSS-R29 · «Reservations» leads the mobile menu, in bold ---------- */
/* Owner ruling R29. The pinned reservations item is ALREADY the first child of
   .site-nav__list in the one shared nav markup (includes/nav.unified.php renders it
   before the $NAV loop), so no re-order is written here — R29 adds weight only.
   Scoped to the drawer (<1024px): the desktop sidebar keeps the --fw-semi it has
   always had, so the desktop menu is untouched, as ordered.
   Set on the LABEL SPAN, not the <a>, so the homepage tagline underneath stays at
   --fw-reg and reads as a subtitle (the R18/R20 two-line nav item rule). */
@media (max-width: 1023px) {
  .site-nav__link--cta .site-nav__label { font-weight: var(--fw-bold); }
}


/* ---------- CSS-R30 · the footer call row ---------- */
/* Intake _REQUEST-CSS-footer-phone.md. The request read "no css/** change is implied" —
   it is, and AP-3 is exactly why: `.site-footer a { color:#fff }` is (0,1,1) and BEATS
   `.cbtn--mobile { color: var(--c-deep) }` at (0,1,0), so an unqualified call button
   renders #fff on #8ec9df = 1.9:1 in the footer while looking correct on a contact page.
   Every colour this row needs is therefore stated at (0,2,1) or better, and the probe
   measures WCAG contrast in the FOOTER context, not on the contact card. */
.site-footer__contact {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
.site-footer a.cbtn--mobile,
.site-footer a.cbtn--call { color: var(--c-deep); }
.site-footer a.cbtn--mobile:hover,
.site-footer a.cbtn--call:hover { color: var(--c-deep); }
