/* YourZanzibar — "Ledger" v3.0 chrome + component styles.
   The editorial atelier: serif display (Newsreader) over warm Stone Town paper,
   documents centred with a ceremonial head, and the verification machinery set
   as the page's jewellery. Tokens still live in theme.json.
   ONE generation (replaces the v1.1/hardening/v1.2 layers). theme.json owns the
   tokens; every value here references one. The core rule of this file is the
   GROUND SYSTEM: each band declares its ground (white / mist / ink) and every
   control reads its colours from the ground's variables — so a button, link or
   seal dropped on a dark band can never render invisible. */

/* ============================================================
   1 · Ground system + motion tokens
   ============================================================ */
:root,
.yz-ground--white {
  --g-fg: var(--wp--preset--color--ink);
  --g-muted: var(--wp--preset--color--slate);
  --g-link: var(--wp--custom--color--link);
  --g-link-hover: var(--wp--custom--color--link-hover);
  --g-hairline: var(--wp--preset--color--line);
  --g-card-bg: var(--wp--preset--color--white);
  --g-card-line: var(--wp--preset--color--line);
  --g-seal-bg: rgba(10, 106, 99, 0.09);
  --g-seal-fg: var(--wp--custom--color--link);
  --g-state-bg: rgba(116, 87, 15, 0.12);
  --g-state-fg: var(--wp--custom--color--state);
  --g-btn-bg: var(--wp--custom--color--cta);
  --g-btn-bg-hover: var(--wp--custom--color--cta-hover);
  --g-btn-fg: var(--wp--preset--color--white);
  --g-focus: var(--wp--custom--color--link);
  --yz-ease: cubic-bezier(.2, 0, 0, 1);
  --yz-dur: 160ms;
  --yz-dur-m: 600ms;
}
.yz-band--mist {
  background-color: var(--wp--preset--color--shell);
  --g-card-bg: var(--wp--preset--color--white);
  --g-card-line: transparent;
}
.yz-band--ink {
  background-color: var(--wp--preset--color--ink);
  color: var(--wp--preset--color--white);
  --g-fg: var(--wp--preset--color--white);
  --g-muted: var(--wp--preset--color--ash);
  --g-link: var(--wp--custom--color--link-on-dark);
  --g-link-hover: var(--wp--preset--color--white);
  --g-hairline: var(--wp--custom--rule--hairline-invert);
  --g-card-bg: var(--wp--preset--color--ink-raise);
  --g-card-line: rgba(255, 255, 255, 0.08);
  --g-seal-bg: rgba(95, 216, 201, 0.12);
  --g-seal-fg: var(--wp--custom--color--link-on-dark);
  --g-state-bg: rgba(232, 195, 104, 0.14);
  --g-state-fg: var(--wp--custom--color--state-on-dark);
  --g-btn-bg: var(--wp--preset--color--white);
  --g-btn-bg-hover: var(--wp--preset--color--shell);
  --g-btn-fg: var(--wp--preset--color--ink);
  --g-focus: var(--wp--custom--color--link-on-dark);
}
/* Photo ground: a first-class ground for photographic bands (formalises what
   the hero improvised). Controls over photos read these vars.

   The two photographic COMPONENTS carry the ground themselves. They used to
   be left out, and the consequence was measurable: the credit link inside
   .yz-photo drew its focus ring from :root's --g-focus (#0A6A63 dark teal),
   composited on a 72% scrim over a photograph at roughly 2:1 — under the 3:1
   WCAG 2.2 non-text-contrast floor, and effectively invisible on a dark image.
   Declaring the ground on the component rather than waiting for a band class
   makes any control dropped over a photograph correct by construction, and it
   stops .yz-band--photo being CSS that nothing emits — which is exactly the
   condition that let the mistake sit there unnoticed. */
.yz-band--photo,
.yz-photo,
.yz-tile--photo {
  color: var(--wp--preset--color--white);
  --g-fg: var(--wp--preset--color--white);
  --g-muted: rgba(255, 253, 248, 0.85);
  --g-link: var(--wp--preset--color--white);
  --g-link-hover: var(--wp--preset--color--teal-light);
  --g-hairline: rgba(255, 253, 248, 0.28);
  --g-btn-bg: var(--wp--preset--color--white);
  --g-btn-bg-hover: var(--wp--preset--color--shell);
  --g-btn-fg: var(--wp--preset--color--ink);
  --g-focus: var(--wp--preset--color--white);
}
/* Baraza screen: carved-fretwork suggestion in pure CSS. Opt-in via
   has-screen on an ink band; 3% warm-white keeps text contrast. */
.yz-band--ink.has-screen {
  background-image:
    repeating-linear-gradient(45deg, rgba(255, 253, 248, 0.03) 0 1px, transparent 1px 28px),
    repeating-linear-gradient(-45deg, rgba(255, 253, 248, 0.03) 0 1px, transparent 1px 28px);
}

.yz-band--ink :is(h1, h2, h3, h4, p, li) { color: inherit; }
.yz-band--ink a:not(.wp-element-button) { color: var(--g-link); }
.yz-band--ink a:not(.wp-element-button):hover { color: var(--g-link-hover); }

/* Bands: full-bleed sections with one padding cadence. */
.yz-band { padding-block: var(--wp--custom--space--band); margin-block: 0; }
.yz-band--hero { padding-block: var(--wp--custom--space--band-lg) var(--wp--custom--space--band); }

/* ============================================================
   2 · Base: focus, motion, links, helpers
   ============================================================ */

/* ---- LAYOUT GRAMMAR ------------------------------------------------------
   Two widths, three contexts. This section is the layout system; nothing else
   in the theme may take a position on horizontal alignment.

   SPINE   — 72rem (wideSize). The page column, centred on the viewport.
             The header pill, band inners, tables, cards and query lists all
             size to it. Set in template markup via contentSize, not here.

   PROSE   — 44rem (contentSize) reading measure, left-aligned ON the spine.
             Applies only in DOCUMENT contexts: the flowing content the publish
             pipeline emits (.entry-content) and the title/stamp head above it
             (.yz-doc-head). Nowhere else, ever.

   DISPLAY — centred compositions: hero, statement band, stat band, interlude,
             the footer. Designed objects with their own internal alignment.
             The grammar's whole job is to LEAVE THEM ALONE.

   HISTORY, so it cannot be un-learned. Revision 1 force-left-aligned every
   constrained child globally — it pinned centred display type to band edges.
   Revision 2 kept prose flush-left on the spine — measured 2026-08-01 at
   2000px, the empty right half read as a docs site, not a publication.
   v3.0 centres the DOCUMENT (both margins auto: a symmetric reading column)
   and fills the margin with apparatus (the rail) instead of emptiness. The
   document/display distinction still stands: these selectors touch document
   contexts only. Do not restore flush-start; do not widen the selectors.

   Mechanics: document containers declare contentSize 72rem in their template
   markup, so full-width things in them (tables, figures, query grids) size to
   the spine; the prose selectors then apply the 44rem measure, flush-start.
   !important is required to beat core's `margin: auto !important` centring —
   scoped to the two document contexts only. Logical properties mirror the
   whole thing under RTL (the site ships ar + he). Under 72rem of viewport the
   max-widths never bind, so phones render exactly as they always did.       */
:root { --yz-spine: 72rem; --yz-prose: 44rem; --yz-breakout: 48rem; }
/* Two vertical metrics that more than one section has to agree on. They were
   hardcoded — 64px appeared in four places (the pill's min-height, the hero
   pull, the hero content padding, the scrolly pin) and the document gap in
   one, which is how the hero pull came to be sized against a collapse chain
   it did not know the whole of. Change the pill height here and everything
   that has to clear the pill follows. */
:root { --yz-header-h: 64px; --yz-doc-gap: clamp(2rem, 4vh, 3rem); }

/* Prose in document contexts: reading measure, flush on the spine.
   .has-text-align-center is excluded so an author who centres a paragraph on
   purpose keeps their centring. */
.entry-content > :is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, pre.wp-block-code):not(.has-text-align-center),
.yz-doc-head > :is(h1, h2, p, .wp-block-group:not(.is-style-verified-stamp)):not(.has-text-align-center) {
  max-width: min(var(--yz-prose), 100%);
  margin-inline-start: auto !important;
  margin-inline-end: auto !important;
}

/* The two trust devices are document furniture wherever they appear in the
   page body; they keep their own 62ch measure but sit on the spine. The
   footer's copies are excluded — the footer is a display composition.
   The colophon is excluded too: it carries .is-style-verified-stamp for its
   voice but it is a rule-flanked ROW, not a paragraph of trust prose, and the
   62ch cap starved its flanking hairlines of the 8rem they are allowed. See
   .yz-colophon below, where `max-width: none` had been sitting dead. */
main .is-style-verified-stamp:not(.yz-colophon),
main .is-style-disclosure {
  max-width: min(62ch, 100%);
  margin-inline-start: auto !important;
  margin-inline-end: auto !important;
}

/* Query lists (404, archive, hub) read as documents: titles and excerpts take
   the prose measure. In grid contexts the cell is narrower than the measure,
   so this never binds there. */
main .wp-block-query :is(.wp-block-post-title, .wp-block-post-excerpt) {
  max-width: min(var(--yz-prose), 100%);
  margin-inline-start: auto !important;
  margin-inline-end: auto !important;
}

/* TYPE DECISION (owner, 2026-08-02, resolving spec 07 §6 Q3): the DISPLAY
   voice is Plus Jakarta Sans at confident weights (800 H1 / 600 H2-H3) on the
   Elementor scale — the look approved at v2 and asked for again at v4. The
   serif (Newsreader) is the EDITORIAL ACCENT, kept exactly where it earns its
   place: standfirst, pullquotes, drop caps, the footer's italic disclosure.
   Do not put the serif back on headings; do not drop the serif accents. */

/* ---- §2b · Document ceremony (Ledger) ----------------------------------
   The article head is a quiet ritual: kicker, balanced serif title, serif
   standfirst, colophon between hairlines, pindo rule, then a drop cap. All
   of it fails closed — a missing excerpt or date removes its own line and
   the pindo still closes the head.

   DOCUMENT SCALE — three widths, measured after the v3.0 launch collision:
     prose    44rem  the reading measure
     breakout 56rem  tables, pullquotes, figures — wider than text, never
                     into the rail (the first build let tables span the full
                     72rem spine and a table border ran straight through the
                     rail's text — measured 176px of overlap at 2000px)
     spine    72rem  page chrome and bands only, never document content    */
.entry-content > :is(figure.wp-block-table, .wp-block-pullquote, figure.wp-block-image) {
  /* BREAKOUT — 48rem, not 56. At 56rem a table overhung the 44rem prose by 96px
     per side, which reads as a broken column rather than deliberate emphasis
     (owner-reported 2026-08-02). 48rem gives a 32px shoulder AND is the width
     that lets the rail sit beside it from 1366px up (see the rail block). */
  max-width: min(var(--yz-breakout), 100%);
  margin-inline: auto;
}
/* Rhythm: the first build left everything at the 1.5rem block gap and the
   page read cramped. Elementor-grade generosity: sections breathe. */
.yz-doc-head { text-align: center; padding-block-start: clamp(2.5rem, 6vh, 4.5rem); }
.entry-content { margin-block-start: var(--yz-doc-gap); }
.entry-content > h2 { margin-block-start: clamp(3rem, 2rem + 2.5vw, 4.5rem); }
.entry-content > h3 { margin-block-start: 2.5rem; }
.entry-content > :is(figure.wp-block-table, .wp-block-pullquote, figure.wp-block-image) { margin-block: 2.5rem; }
.yz-doc-head .wp-block-separator.is-style-pindo { margin-block: 1.75rem 0.5rem; }
.yz-doc-head .wp-block-post-title, .yz-doc-head h1 {
  max-inline-size: 22ch;
  margin-inline: auto !important;
  text-wrap: balance;
}
.yz-kicker a { color: inherit; text-decoration: none; }
.yz-standfirst {
  font-family: var(--wp--preset--font-family--serif-display);
  font-size: var(--wp--preset--font-size--standfirst);
  line-height: 1.45;
  color: var(--g-muted, var(--wp--preset--color--slate));
  max-inline-size: 34rem;
}
.yz-doc-head .yz-standfirst { margin-inline: auto !important; }
.yz-doc-figure img { border-radius: 0; }

/* The colophon: plaque, reading time, corrections link, between hairlines
   that stop short of the text. Same fail-closed contract as the stamp. */
.yz-colophon { display: flex; align-items: center; justify-content: center; gap: 1.25rem; }
/* The row runs free; only its hairlines are capped (8rem each, below).
   `max-width: none` sat on the rule above for three releases and never took
   effect: TWO rules outrank a bare .yz-colophon (0,1,0) —
     `main .is-style-verified-stamp` at §2         (0,1,1), and
     `.is-style-verified-stamp { max-width: 62ch }` at §6 (0,1,0, but LATER in
   the file, so it wins the tie on source order).
   The first is now excluded at source; this (0,1,1) selector beats the second
   on specificity. Do not demote it back to a bare class. */
main .yz-colophon { max-width: none; }
.yz-colophon::before, .yz-colophon::after {
  content: ""; block-size: 1px; flex: 1; max-inline-size: 8rem;
  background: var(--g-hairline, var(--wp--preset--color--line));
}
.yz-colophon__item {
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--g-muted, var(--wp--preset--color--slate));
  white-space: nowrap;
}
.yz-colophon__item a { color: inherit; }

/* Drop cap: teak serif initial. Latin scripts only — §15 turns it off for the
   scripts Newsreader cannot draw, and for the ones where a floated initial is
   not a convention. */
.entry-content .has-drop-cap:not(:focus)::first-letter {
  font-family: var(--wp--preset--font-family--serif-display);
  font-weight: 400;
  float: inline-start;
  font-size: 3.4em;
  line-height: 0.85;
  padding-inline-end: 0.12em;
  color: var(--wp--preset--color--mlango);
}

/* The margin apparatus: ONE aside, two presentations. In flow (default) a
   collapsed details block after the content; at >=90em an absolute column
   holds it sticky beside the prose. Zero JS. */
.yz-apparatus {
  font-size: var(--wp--preset--font-size--small);
  color: var(--g-muted, var(--wp--preset--color--slate));
  /* In-flow presentation (below 85em): a quiet index at the prose measure,
     placed at the TOP of the document by the templates, closed off with a
     hairline. The 85em block below lifts it into the sticky margin column. */
  max-width: min(var(--yz-prose), 100%);
  margin-inline: auto;
  border-block-end: 1px solid var(--g-hairline, var(--wp--preset--color--line));
  padding-block-end: var(--wp--preset--spacing--30);
  margin-block-end: var(--wp--preset--spacing--50);
}
@media (min-width: 85em) {
  main .yz-apparatus { border-block-end: 0; padding-block-end: 0; margin-block-end: 0; }
}
.yz-apparatus summary {
  cursor: pointer; font-weight: 600;
  font-size: var(--wp--preset--font-size--x-small);
  letter-spacing: 0.1em; text-transform: uppercase;
  min-height: 44px; display: flex; align-items: center;
}
.yz-apparatus ol { list-style: none; margin: 0; padding: 0; }
.yz-apparatus li { padding-block: 0.35rem; }
.yz-apparatus a { color: inherit; text-decoration: none; }
.yz-apparatus a:hover { color: var(--g-link, var(--wp--custom--color--link)); }
.yz-apparatus__card {
  background: var(--wp--preset--color--white);
  border: 1px solid var(--g-hairline, var(--wp--preset--color--line));
  border-radius: var(--wp--custom--radius--sm);
  padding: 0.9rem 1rem;
  margin-block: 1rem;
}
/* The rail, measured rather than guessed. Geometry per viewport (20px root
   padding each side, rail right edge = 50vw + breakout/2 + gap + rail):
     1366  48rem breakout + 14rem rail = 1331 <= 1346  fits
     1440  48rem breakout + 14rem rail = 1368 <= 1420  fits
     1512  56rem breakout + 15rem rail = 1492 <= 1492  fits
   A 96em-only sidebar (the previous rule) meant every 1440px laptop — the most
   common desktop size there is — dropped the rail into the flow. Two tiers now,
   and below the first the rail sits at the TOP of the document, never the
   bottom: a table of contents after the article is furniture nobody uses. */
@media (min-width: 85em) {
  main { position: relative; }
  main .yz-apparatus {
    position: absolute;
    inset-inline-start: calc(50% + 26rem);
    inset-block-start: 0; inset-block-end: 0;
    inline-size: 14rem;
  }
  main .yz-apparatus > .yz-apparatus__inner { position: sticky; top: 7rem; }
}
/* Above 96em only the RAIL grows. The breakout deliberately stays at 48rem at
   every width: restoring 56rem here brought the 96px overhang straight back on
   large monitors, which is the thing that was reported. One breakout width,
   everywhere. */
@media (min-width: 96em) {
  main .yz-apparatus { inset-inline-start: calc(50% + 28rem); inline-size: 15rem; }
}
/* The report-error line separates from whatever precedes it. */
.yz-apparatus .yz-apparatus__inner > p {
  margin-block-start: 1.25rem;
  padding-block-start: 1rem;
  border-block-start: 1px solid var(--g-hairline, var(--wp--preset--color--line));
}

a { text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
::selection { background-color: var(--wp--preset--color--shell); color: var(--wp--preset--color--ink); }

/* figure.wp-block-table is in the list because inc/table-a11y.php makes every
   table figure a real tab stop (tabindex="0", so a keyboard user can scroll
   it). Without it that stop showed the browser's default ring — the one
   focusable thing on the page that did not look like the theme. */
a:focus-visible,
button:focus-visible,
select:focus-visible,
summary:focus-visible,
input:focus-visible,
figure.wp-block-table:focus-visible,
.wp-element-button:focus-visible {
  outline: 2px solid var(--g-focus, var(--wp--custom--color--link));
  outline-offset: 3px;
  border-radius: var(--wp--custom--radius--sm);
}

@media (prefers-reduced-motion: no-preference) {
  a, select, summary, .wp-element-button {
    transition: color var(--yz-dur) var(--yz-ease),
                background-color var(--yz-dur) var(--yz-ease),
                border-color var(--yz-dur) var(--yz-ease),
                opacity var(--yz-dur) var(--yz-ease);
  }
}

/* Visually hidden, still announced. Used by the ticker and video pause buttons.
   Block themes do not get core's classic-theme stylesheet, so without this rule
   every .screen-reader-text label renders as visible text. */
.screen-reader-text {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.screen-reader-text:focus {
  position: static !important;
  width: auto; height: auto;
  margin: 0; overflow: visible;
  clip-path: none; white-space: normal;
}

.yz-eyebrow {
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--g-muted, var(--wp--preset--color--slate));
  margin-block-end: var(--wp--preset--spacing--30);
}
.yz-light { font-weight: 400; }
.yz-sub {
  color: var(--g-muted, var(--wp--preset--color--slate));
  max-width: 44ch;
}

.yz-arrow-link a, a.yz-arrow-link { color: var(--g-link, var(--wp--custom--color--link)); font-weight: 600; text-decoration: none; }
.yz-arrow-link a::after, a.yz-arrow-link::after { content: " \2192"; }
[dir="rtl"] .yz-arrow-link a::after, [dir="rtl"] a.yz-arrow-link::after { content: " \2190"; }
.yz-arrow-link a:hover, a.yz-arrow-link:hover { text-decoration: underline; }

/* ============================================================
   3 · Header: floating pill nav
   ============================================================ */
/* STICKY LIVES ON THE TEMPLATE-PART WRAPPER, not on .site-header.
   A sticky element can only travel inside its parent's box, and
   <header class="wp-block-template-part"> is exactly 64px tall — so sticky on
   .site-header had nowhere to go and the header has never actually stuck.
   (A second cause, `overflow-x: clip` on .wp-site-blocks, was removed in the
   same commit; BOTH had to go. Verified by scrolling 1500px and measuring.)
   Do not move position:sticky back down to .site-header. */
.wp-site-blocks > header.wp-block-template-part {
  position: sticky;
  top: 0.75rem;
  z-index: 30;
}
.site-header {
  background: transparent;
  padding-block: 0;
}
.site-header__inner {
  /* Floating, borderless (owner directive 2026-08-02): the pill carries the
     one sanctioned shadow instead of a hairline, on translucent warm paper
     with backdrop blur where supported. Solid fallback first, so browsers
     without backdrop-filter still get an opaque, legible bar. */
  background-color: var(--wp--preset--color--white);
  border: 0;
  border-radius: var(--wp--custom--radius--md);
  box-shadow: var(--wp--custom--shadow--crisp);
  /* The pill's height is a shared metric — the hero pulls up under it and the
     scrolly pin parks below it. Set it in §2, not here.
     box-sizing is spelled out because the whole chrome geometry (this file's
     comment at §3 says the template part is "exactly 64px tall", and the hero
     pull is sized off that) assumes min-height IS the height. Left to inherit,
     content-box adds the 0.375rem padding twice and the pill measures 76px —
     twelve pixels the hero pull does not know about. */
  box-sizing: border-box;
  min-height: var(--yz-header-h);
  padding-block: 0.375rem;
  padding-inline: 1.25rem;
  /* Pill background bleeds one pad beyond the spine so the logo's text edge
     lands exactly ON the spine (no border to count any more). */
  max-width: calc(var(--yz-spine) + 2.5rem);
}
@supports (backdrop-filter: blur(8px)) {
  .site-header__inner { background-color: rgba(255, 253, 248, 0.86); backdrop-filter: blur(8px); }
}
.site-header .wp-block-navigation a {
  font-size: var(--wp--preset--font-size--small);
  font-weight: 600;
  color: var(--wp--preset--color--ink);
  text-decoration: none;
  padding: 0.75rem 0.55rem;
}
.site-header .wp-block-navigation a:hover { opacity: 0.62; }
/* Header search: a quiet icon that expands on click (core's button-only
   behavior — no theme JS). The icon fits every viewport, which also gives
   mobile its search entry. min 44px target; the expanded field is bordered
   only while open. */
.yz-header-search { margin: 0; }
.yz-header-search button.wp-block-search__button {
  border: 0; background: transparent; color: var(--wp--preset--color--ink);
  min-width: 44px; min-height: 44px; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--wp--custom--radius--sm);
}
.yz-header-search button.wp-block-search__button:hover { opacity: 0.62; }
.yz-header-search button.wp-block-search__button svg { width: 20px; height: 20px; }
.yz-header-search input.wp-block-search__input {
  border: 1px solid var(--wp--preset--color--line);
  border-radius: var(--wp--custom--radius--sm);
  background: var(--wp--preset--color--white);
  font-size: var(--wp--preset--font-size--small);
  padding: 0.45rem 0.7rem;
  /* 32vw, not 40: at 390px the expanded field must coexist with the
     hamburger and the language control on one row (44+125+6+44+95+gaps<=350,
     measured live). */
  inline-size: min(11rem, 32vw);
}
.yz-header-search .wp-block-search__inside-wrapper { gap: 6px; }
.yz-result-count { font-family: var(--wp--preset--font-family--body); font-size: var(--wp--preset--font-size--medium); font-weight: 400; color: var(--wp--preset--color--slate); white-space: nowrap; }
.yz-search-restate { max-inline-size: 28rem; margin-inline: auto; }
html { scroll-padding-top: 6rem; }
/* The inline nav is kept collapsed to 930px. Core flips to the inline list at
   600px, but between 600 and ~930 the wordmark + 5 links + search + language
   cluster does not fit one row, so the floating pill wrapped to 126px across
   the entire tablet range (measured at 820px). Verified at 1440 that the
   toggle stays hidden and all five links stay inline — the phantom-hamburger
   failure mode, checked rather than assumed. */
@media (max-width: 58.125em) {
  .site-header .wp-block-navigation__responsive-container-open:not(.always-shown) { display: flex; }
  .site-header .wp-block-navigation__responsive-container:not(.is-menu-open):not(.hidden-by-default) { display: none; }
}

/* The mobile menu toggle ships from core at 24x24 — the single most important
   control on a phone, well under the 44px minimum the rest of the theme holds.
   MIN-SIZES ONLY. Do not set `display` here: core hides this button at desktop
   with `display: none` at equal specificity, and theme.css loads later — a
   display value in this rule un-hides a phantom hamburger on desktop and
   wraps the nav. (That shipped once. Once.) */
.site-header .wp-block-navigation__responsive-container-open,
.site-header .wp-block-navigation__responsive-container-close {
  min-width: 44px; min-height: 44px;
}

.yz-logo { display: inline-flex; align-items: center; min-height: 44px; }
.yz-logo__img { display: block; height: 34px; width: auto; }
@media (min-width: 782px) { .yz-logo__img { height: 40px; } }
.site-header__end { gap: var(--wp--preset--spacing--20); }

/* Language: a compact ghost control (owner directive 2026-08-02 — "not such
   a big dropdown"). The visible "Language" word goes screen-reader-only at
   every width; the toggle reads as quiet text with a caret, borderless like
   the rest of the floating pill, still a 44px target.

   A <details> of real links, NOT a <select>. Polylang's dropdown switcher
   navigates on the select's `change` event, and on Windows/Linux Chrome,
   Edge and Firefox a *closed* select fires `change` on every arrow keypress —
   so a keyboard user pressing Down was thrown into language #2 and could
   never walk to Kiswahili or the CJK/RTL entries (WCAG 3.2.2, On Input).
   Native <details> needs no JS to open, close or navigate; the theme's JS
   only adds Escape and click-outside. Style it so the disclosure never looks
   like a system widget: no triangle, our own caret, panel over the page. */
.yz-lang-switcher { display: inline-flex; align-items: center; min-width: 0; }
.yz-lang-switcher__label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
.yz-lang-switcher__details { position: relative; }
.yz-lang-switcher__toggle {
  display: inline-flex; align-items: center;
  font-family: var(--wp--preset--font-family--body);
  font-size: var(--wp--preset--font-size--small);
  font-weight: 600;
  color: var(--wp--preset--color--ink);
  background-color: transparent;
  border: 0;
  border-radius: var(--wp--custom--radius--sm);
  padding-block: var(--wp--preset--spacing--20);
  padding-inline: 0.25rem 1.1rem;
  /* BORDER-BOX, and not optional. The <select> this replaced got border-box
     free from the UA sheet, so min-height:44px WAS its height; a <summary> is
     content-box, and 44 + 2x0.5rem padding made the control 60px, which pushed
     the floating pill from 64px to 76px. Measured at 1440. */
  box-sizing: border-box;
  min-height: 44px;
  min-width: 0;
  max-width: 7.5rem;
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
  /* Suppress the OS disclosure triangle in both engines, then draw our own
     caret — the same 10x6 chevron the select carried. */
  list-style: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236C6659' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.15rem center;
}
.yz-lang-switcher__toggle::-webkit-details-marker { display: none; }
/* The ellipsis has to live on the text, not on the flex container — a flex
   item is what gets truncated, and "Português (Brasil)" is longer than 7.5rem. */
.yz-lang-switcher__current { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.yz-lang-switcher__toggle:hover { opacity: 0.62; }
/* The caret is a physical background-position keyword; browsers do not flip it,
   so RTL moves it by hand. The GUTTER must NOT be flipped with it: padding-inline
   on the base rule is already logical, so 0.25rem 1.1rem resolves to a 1.1rem
   gutter on the LEFT under RTL — exactly where the caret now is. Restating it
   physically here put the gutter back on the right and drew the chevron over the
   first glyph of العربية / עברית: the <select> this replaced was width-pinned by
   its widest option so the clash never showed, but a <summary> shrink-wraps. */
[dir="rtl"] .yz-lang-switcher__toggle { background-position: left 0.15rem center; }
/* The panel: absolute, so opening it can never re-flow the header row. It is
   hung off the trailing edge and capped in height — 14 languages at a 44px
   touch target is 616px, which would run off any phone. */
.yz-lang-switcher__list {
  position: absolute;
  inset-block-start: 100%;
  inset-inline-end: 0;
  z-index: 1;
  margin: 0;
  padding: var(--wp--preset--spacing--20);
  list-style: none;
  min-inline-size: 10rem;
  max-block-size: min(60vh, 22rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Surface AND foreground both come from the ground, never one of each: the
     links inside read --g-link, so a hardcoded white panel under a dark-ground
     header would be the exact asymmetry §1 exists to prevent. */
  background-color: var(--g-card-bg, var(--wp--preset--color--white));
  border: 1px solid var(--g-hairline, var(--wp--preset--color--line));
  border-radius: var(--wp--custom--radius--sm);
  box-shadow: var(--wp--custom--shadow--crisp);
}
.yz-lang-switcher__list .lang-item { margin: 0; }
/* Ground-aware, because this control lives inside the floating pill and the
   pill's ground is the only thing that knows what a link should look like. */
.yz-lang-switcher__list a {
  display: flex; align-items: center;
  min-height: 44px;
  padding-inline: var(--wp--preset--spacing--30);
  font-size: var(--wp--preset--font-size--small);
  font-weight: 600;
  color: var(--g-link, var(--wp--custom--color--link));
  text-decoration: none;
  border-radius: var(--wp--custom--radius--sm);
  white-space: nowrap;
}
.yz-lang-switcher__list a:hover { background-color: var(--wp--preset--color--shell); }
.yz-lang-switcher__list .current-lang > a { color: var(--g-fg, var(--wp--preset--color--ink)); }
/* Small screens: the pill's 24px flex gap made logo+cluster 314px against the
   310 available at 390vw — a 4px wrap, measured live. Tighter gap + a slightly
   slimmer language control keeps everything on one row down to 360px. */
@media (max-width: 30em) {
  .site-header__inner { gap: 10px; }
  /* The expanded search field must take space the row can give, or the cluster
     wraps the moment the icon is tapped. */
  .site-header__end { gap: 4px; flex: 1 1 auto; min-width: 0; }
  .yz-header-search .wp-block-search__inside-wrapper { min-width: 0; }
  .yz-header-search input.wp-block-search__input { inline-size: 100%; min-width: 0; }
  .yz-lang-switcher__toggle { max-width: 6.5rem; padding-inline: 0.2rem 1rem; }
}

/* ============================================================
   4 · Footer (ink ground)
   ============================================================ */
.site-footer {
  margin-top: var(--wp--custom--space--band);
  padding-block: var(--wp--preset--spacing--60) var(--wp--preset--spacing--50);
  background-color: var(--wp--preset--color--ink);
  color: var(--wp--preset--color--white);
  border-top: 0;
}
.site-footer :is(p, li, h2, h3) { color: inherit; }
.site-footer a { color: rgba(255, 255, 255, 0.86); }
.site-footer a:hover { color: var(--wp--preset--color--white); }
.footer-nav { gap: var(--wp--preset--spacing--30); }
/* 44px minimum: these were 36px tall. Padding rather than min-height so the
   links still sit on one line and wrap normally. */
.footer-nav a {
  font-weight: 600; font-size: var(--wp--preset--font-size--small);
  text-decoration: none; display: inline-flex; align-items: center;
  min-height: 44px; padding-block: 0.5rem;
}
.site-footer .is-style-disclosure { font-family: var(--wp--preset--font-family--serif-display); font-style: italic; font-size: 0.9375rem; }
.site-footer .is-style-disclosure,
.site-footer .is-style-verified-stamp { color: var(--wp--preset--color--ash); }

/* ============================================================
   5 · Buttons
   ============================================================ */
.wp-element-button, .wp-block-button__link {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 2.75rem;
  background-color: var(--g-btn-bg, var(--wp--custom--color--cta));
  color: var(--g-btn-fg, var(--wp--preset--color--white));
}
.wp-element-button:hover, .wp-block-button__link:hover {
  background-color: var(--g-btn-bg-hover, var(--wp--custom--color--cta-hover));
  color: var(--g-btn-fg, var(--wp--preset--color--white));
}
.wp-block-button.is-style-quiet .wp-element-button {
  background-color: transparent;
  color: var(--g-fg, var(--wp--preset--color--ink));
  border: 1px solid var(--g-muted, var(--wp--preset--color--slate));
}
.wp-block-button.is-style-quiet .wp-element-button:hover {
  background-color: transparent;
  border-color: var(--g-fg, var(--wp--preset--color--ink));
}
@media (max-width: 599px) {
  .yz-hero-x .wp-block-button, .yz-concierge .wp-block-button, .yz-pulse .wp-block-button, .yz-quote-request .wp-block-button,
  .yz-hero-x .wp-block-button__link, .yz-concierge .wp-block-button__link, .yz-pulse .wp-block-button__link, .yz-quote-request .wp-block-button__link { width: 100%; }
}

/* ============================================================
   6 · Trust devices — the jewellery
   ============================================================ */
.yz-verified {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px;
  border-radius: var(--wp--custom--radius--pill);
  /* Engraved plaque, not sticker: transparent fill, 1px seal-colour border.
     The --g-seal-* contract keeps it correct on every ground. */
  background-color: transparent;
  border: 1px solid var(--g-seal-fg, var(--wp--custom--color--link));
  color: var(--g-seal-fg, var(--wp--custom--color--link));
  font-family: var(--wp--preset--font-family--body);
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 600;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.yz-verified::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.yz-verified time { font-variant-numeric: tabular-nums; }
.yz-verified--due { background-color: transparent; border-color: var(--g-state-fg); color: var(--g-state-fg); }

.is-style-provenance {
  /* Scholarly footnote: a teal cap-rule marks the line as verification
     apparatus. Teal is legitimate here — provenance IS verification. */
  position: relative;
  padding-inline-start: 0.9rem;
  font-size: var(--wp--custom--provenance--font-size);
  line-height: 1.55;
  color: var(--g-muted, var(--wp--custom--provenance--color));
  max-width: 58ch;
  text-wrap: pretty;
}
.is-style-provenance::before {
  content: ""; position: absolute;
  inset-inline-start: 0; inset-block-start: 0.18em;
  inline-size: 2px; block-size: 1em;
  background: var(--g-seal-fg, var(--wp--custom--color--link));
}
.is-style-provenance strong { font-weight: 600; color: var(--g-fg, var(--wp--preset--color--ink)); }
.is-style-provenance time { font-variant-numeric: tabular-nums; white-space: nowrap; }
.is-style-disclosure, .is-style-verified-stamp {
  font-size: var(--wp--preset--font-size--small);
  line-height: 1.55;
  color: var(--g-muted, var(--wp--preset--color--slate));
  max-width: 62ch;
  text-wrap: pretty;
}
.is-style-verified-stamp time { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Inline code. theme.json styles the code BLOCK (styles.blocks.core/code); an
   inline <code> is not a block, so it can only be done here. It is not
   decoration: the provenance lines cite ledger row ids in <code>, and the
   publish pipeline turns `backticks` into it — unstyled, they fell back to the
   browser's monospace at 1em beside Plus Jakarta Sans and read as a rendering
   fault. Ground-aware, and deliberately not coloured: teal means verified and
   ochre means unverified, and a code span means neither. */
:not(pre) > code {
  font-size: 0.9em;
  background-color: var(--wp--preset--color--shell);
  border-radius: var(--wp--custom--radius--sm);
  padding: 0.1em 0.36em;
  overflow-wrap: break-word;
}
.yz-band--ink :not(pre) > code { background-color: var(--wp--preset--color--ink-raise); }

.yz-correction { border-inline-start: var(--wp--custom--rule--spine) solid var(--wp--custom--color--link); padding-inline-start: var(--wp--preset--spacing--30); }
.yz-correction__date { margin-bottom: var(--wp--preset--spacing--20); font-size: var(--wp--preset--font-size--small); font-weight: 600; color: var(--wp--preset--color--slate); }
.yz-sources { font-size: var(--wp--preset--font-size--small); }

/* ============================================================
   7 · Cards, media, credits
   ============================================================ */
.yz-card, .yz-fact-callout, .yz-itinerary-block, .yz-price-comparison-row,
.yz-price-index-table, .yz-stay-card, .yz-money-explainer, .yz-tile {
  background-color: var(--g-card-bg, var(--wp--preset--color--white));
  border: 1px solid var(--g-card-line, var(--wp--preset--color--line));
  border-radius: var(--wp--custom--radius--md);
}
.yz-related-guides, .yz-official-partner-badge {
  background-color: transparent;
  border: 1px solid var(--g-hairline, var(--wp--preset--color--line));
  border-radius: var(--wp--custom--radius--md);
}
.yz-concierge, .yz-pulse, .yz-quote-request {
  padding: var(--wp--preset--spacing--50);
  border-radius: var(--wp--custom--radius--md);
  background-color: var(--wp--preset--color--ink);
  color: var(--wp--preset--color--white);
  --g-fg: var(--wp--preset--color--white);
  --g-muted: var(--wp--preset--color--ash);
  --g-link: var(--wp--custom--color--link-on-dark);
  --g-link-hover: var(--wp--preset--color--white);
  --g-seal-bg: rgba(95, 216, 201, 0.12);
  --g-seal-fg: var(--wp--custom--color--link-on-dark);
  --g-btn-bg: var(--wp--preset--color--white);
  --g-btn-bg-hover: var(--wp--preset--color--shell);
  --g-btn-fg: var(--wp--preset--color--ink);
  --g-focus: var(--wp--custom--color--link-on-dark);
}
.yz-concierge :is(h2, h3, p, li), .yz-pulse :is(h2, h3, p, li), .yz-quote-request :is(h2, h3, p, li) { color: inherit; }
.yz-safety-callout {
  padding: var(--wp--preset--spacing--40);
  border-inline-start: var(--wp--custom--rule--spine) solid var(--wp--custom--color--link);
  border-radius: var(--wp--custom--radius--sm);
  background-color: var(--wp--preset--color--shell);
}
.yz-safety-callout__title { margin-top: 0; margin-bottom: var(--wp--preset--spacing--20); font-size: var(--wp--preset--font-size--large); }
.yz-safety-callout__note { margin-top: 0; margin-bottom: var(--wp--preset--spacing--30); }

.yz-figure__credit, .yz-photo__cap, figcaption.wp-element-caption {
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--g-muted, var(--wp--preset--color--slate));
}
.yz-figure__credit { margin-top: var(--wp--preset--spacing--20); }

.yz-photo {
  position: relative;
  aspect-ratio: var(--yz-photo-ratio, 3 / 2);
  /* CLIP, NOT HIDDEN. `overflow: hidden` makes this a scroll container, and a
     scroll container is what `animation-timeline: view()` resolves against —
     so the parallax interlude's drift measured itself against a box with no
     scroll range and sat frozen at scale 1.12, permanently over-cropped and
     never moving. `clip` clips identically (border-radius included) without
     creating a scrollport; the hero has relied on that since v3. */
  overflow: clip;
  isolation: isolate;
  border-radius: var(--wp--custom--radius--md);
  background-color: var(--wp--preset--color--ink);
}
.yz-photo--wide { --yz-photo-ratio: 16 / 9; }
.yz-photo--square { --yz-photo-ratio: 1 / 1; }
.yz-photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.yz-photo:has(img)::after {
  content: ""; position: absolute; inset-inline: 0; inset-block-end: 0; block-size: 42%;
  background-image: linear-gradient(to top, rgba(14, 17, 19, 0.72), transparent);
  pointer-events: none;
}
.yz-photo__cap {
  position: absolute; z-index: 1;
  inset-inline: var(--wp--preset--spacing--30);
  inset-block-end: var(--wp--preset--spacing--30);
  margin: 0;
  color: rgba(255, 255, 255, 0.88);
}
.yz-photo__cap a { color: rgba(255, 255, 255, 0.88); }

/* ============================================================
   8 · Existing components (v2 restyle)
   ============================================================ */
.yz-fact-callout { padding: var(--wp--preset--spacing--40); }
.yz-fact-callout__value, .yz-trust-strip__value, .yz-stay-card__price,
.yz-price-comparison-row__price, .yz-tile__num, .yz-itinerary-block__price-value,
.yz-price-index-table__table td:nth-child(3), .yz-stat__num {
  font-family: var(--wp--preset--font-family--display);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--g-fg, var(--wp--preset--color--ink));
  margin: 0;
  font-variant-numeric: tabular-nums lining-nums;
  overflow-wrap: normal;
  hyphens: none;
}
.yz-fact-callout__value { font-size: var(--wp--preset--font-size--x-large); }
.yz-fact-callout__label { margin: 0; font-weight: 600; }
:is(.yz-fact-callout__value, .yz-trust-strip__value, .yz-stay-card__price, .yz-price-comparison-row__price, .yz-tile__num) .yz-light {
  font-weight: 500; font-size: 0.5em; letter-spacing: 0; vertical-align: 0.34em; margin-inline-end: 0.05em;
}

.yz-trust-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 0; }
/* Dividers only where the component is genuinely multi-column. :first-child
   means "first in the grid", not "first in the row" — so on a wrapped phone
   layout items 2..n each grew a stray hairline and a ~28px indent, reading as
   a broken list. */
.yz-trust-strip__item { padding-inline: 0; min-width: 0; }
@media (min-width: 48em) {
  .yz-trust-strip__item { padding-inline: var(--wp--preset--spacing--50); border-inline-start: 1px solid var(--g-hairline, var(--wp--preset--color--line)); }
  .yz-trust-strip__item:first-child { padding-inline-start: 0; border-inline-start: 0; }
}
.yz-trust-strip__item > * { margin-block: 0 var(--wp--preset--spacing--20); }
.yz-trust-strip__value { font-size: var(--wp--preset--font-size--x-large); }
.yz-trust-strip__label { margin: 0; font-size: var(--wp--preset--font-size--small); color: var(--g-muted, var(--wp--preset--color--slate)); }

.yz-byline__text { margin: 0; font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--slate); }
.yz-breadcrumb { font-size: var(--wp--preset--font-size--small); }
.yz-breadcrumb p { margin: 0; }

.yz-area-guide-hero { margin-bottom: var(--wp--preset--spacing--50); }
.yz-area-guide-hero__image { display: block; width: 100%; margin-bottom: var(--wp--preset--spacing--40); aspect-ratio: 16 / 9; overflow: hidden; border-radius: var(--wp--custom--radius--md); background-color: var(--wp--preset--color--shell); }
.yz-area-guide-hero__image img { width: 100%; height: 100%; object-fit: cover; }
.yz-area-guide-hero__title { margin-bottom: var(--wp--preset--spacing--20); }
.yz-area-guide-hero__lede { margin-top: 0; font-size: var(--wp--preset--font-size--large); color: var(--wp--preset--color--slate); }
.yz-area-guide-hero__credit { margin-top: var(--wp--preset--spacing--30); font-size: var(--wp--preset--font-size--x-small); color: var(--wp--preset--color--slate); }

.yz-food-filter-badges { gap: var(--wp--preset--spacing--20); }
.yz-food-filter-badges__badge, .yz-stay-card__badge {
  margin: 0;
  padding-block: var(--wp--preset--spacing--20);
  padding-inline: var(--wp--preset--spacing--30);
  font-size: var(--wp--preset--font-size--small);
  font-weight: 600;
  line-height: 1;
  color: var(--wp--preset--color--ink);
  background-color: var(--wp--preset--color--shell);
  border: 0;
  border-radius: var(--wp--custom--radius--pill);
}

.yz-itinerary-block { padding: var(--wp--preset--spacing--40); }
.yz-itinerary-block__intro { color: var(--wp--preset--color--slate); margin-bottom: var(--wp--preset--spacing--40); }
.yz-itinerary-block__day { padding-inline-start: var(--wp--preset--spacing--30); border-inline-start: var(--wp--custom--rule--spine) solid var(--wp--custom--color--link); }
.yz-itinerary-block__day + .yz-itinerary-block__day { margin-top: var(--wp--preset--spacing--30); }
.yz-itinerary-block__day-label { font-family: var(--wp--preset--font-family--display); font-weight: 600; font-size: var(--wp--preset--font-size--large); color: var(--wp--preset--color--ink); margin-bottom: var(--wp--preset--spacing--20); }
.yz-itinerary-block__day-line { color: var(--wp--preset--color--slate); margin-top: 0; }
.yz-itinerary-block__price { margin-top: var(--wp--preset--spacing--40); padding-top: var(--wp--preset--spacing--30); border-top: 1px solid var(--wp--preset--color--line); }
.yz-itinerary-block__price-value { font-size: var(--wp--preset--font-size--large); }

.yz-money-explainer { padding: var(--wp--preset--spacing--50); border-inline-start: var(--wp--custom--rule--spine) solid var(--wp--custom--color--link); }
.yz-money-explainer__intro { margin-top: 0; color: var(--wp--preset--color--slate); }
.yz-money-explainer__list { margin-block: var(--wp--preset--spacing--40) 0; padding-inline-start: var(--wp--preset--spacing--40); }
.yz-money-explainer__list li { margin-block: var(--wp--preset--spacing--30); }
.yz-money-explainer__figures { margin-top: var(--wp--preset--spacing--30); }
.yz-money-explainer__figures p { margin-block: var(--wp--preset--spacing--20); }

.yz-official-partner-badge { padding: var(--wp--preset--spacing--40); }
.yz-official-partner-badge__badge {
  display: inline-flex; align-items: center; gap: 7px;
  margin: 0;
  padding: 6px 12px;
  border-radius: var(--wp--custom--radius--pill);
  background-color: transparent;
  border: 1px solid var(--g-seal-fg, var(--wp--custom--color--link));
  color: var(--g-seal-fg, var(--wp--custom--color--link));
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 600;
  letter-spacing: 0.03em;
}
.yz-official-partner-badge__note { margin-top: var(--wp--preset--spacing--30); margin-bottom: 0; font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--slate); }
.yz-official-partner-badge__link { margin-top: var(--wp--preset--spacing--20); margin-bottom: 0; font-size: var(--wp--preset--font-size--small); }

.yz-price-comparison-row { gap: var(--wp--preset--spacing--40); padding: var(--wp--preset--spacing--40); }
.yz-price-comparison-row > * { min-width: 0; }
.yz-price-comparison-row__info { flex: 1 1 16rem; }
.yz-price-comparison-row__operator { margin: 0; }
.yz-price-comparison-row__detail { margin: var(--wp--preset--spacing--20) 0 0; color: var(--wp--preset--color--slate); }
.yz-price-comparison-row__quote { flex: 0 1 auto; }
.yz-price-comparison-row__price { font-size: var(--wp--preset--font-size--x-large); }
.yz-price-comparison-row__unit, .yz-stay-card__unit {
  font-family: var(--wp--preset--font-family--body); font-size: var(--wp--preset--font-size--small);
  font-weight: 400; letter-spacing: normal; line-height: 1.5; color: var(--wp--preset--color--slate);
}
.yz-price-comparison-row__quote .is-style-provenance { margin-top: var(--wp--preset--spacing--20); }

.yz-price-index-table { padding: var(--wp--preset--spacing--40); }
.yz-price-index-table__table { margin: 0; overflow-x: auto; }
.yz-price-index-table__table table { width: 100%; border-collapse: collapse; }
.yz-price-index-table__table th, .yz-price-index-table__table td {
  padding: var(--wp--preset--spacing--30); text-align: start; vertical-align: top;
  border-block-end: 1px solid var(--wp--preset--color--line);
}
.yz-price-index-table__table thead th {
  font-size: var(--wp--preset--font-size--x-small); font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--wp--preset--color--slate); white-space: nowrap;
}
.yz-price-index-table__table tbody tr:last-child :is(th, td) { border-block-end: 0; }
.yz-price-index-table__table td:nth-child(3) { font-size: var(--wp--preset--font-size--large); white-space: nowrap; }
.yz-price-index-table__table td:nth-child(4) { font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--slate); white-space: nowrap; }
.yz-price-index-table__table td:nth-child(4) time { font-variant-numeric: tabular-nums; }
@media (max-width: 599px) {
  .yz-price-index-table__table th:first-child, .yz-price-index-table__table tbody th {
    position: sticky; inset-inline-start: 0; background-color: var(--g-card-bg, var(--wp--preset--color--white)); z-index: 1;
  }
}
.yz-price-index-table > .is-style-provenance { margin-top: var(--wp--preset--spacing--30); margin-bottom: 0; }

.yz-related-guides { padding: var(--wp--preset--spacing--40); }
.yz-related-guides__heading { margin-top: 0; margin-bottom: var(--wp--preset--spacing--30); font-size: var(--wp--preset--font-size--large); }
.yz-related-guides__list { margin: 0; padding-inline-start: var(--wp--preset--spacing--40); }
.yz-related-guides__list li { margin-bottom: var(--wp--preset--spacing--20); font-weight: 600; }

.yz-stay-card { padding: var(--wp--preset--spacing--40); }
.yz-stay-card__media { display: block; width: 100%; margin: 0 0 var(--wp--preset--spacing--40); aspect-ratio: 3 / 2; overflow: hidden; border-radius: var(--wp--custom--radius--sm); background-color: var(--wp--preset--color--shell); }
.yz-stay-card__media img { width: 100%; height: 100%; object-fit: cover; }
.yz-stay-card__name { margin: 0 0 var(--wp--preset--spacing--20); }
.yz-stay-card__desc { margin: 0 0 var(--wp--preset--spacing--30); color: var(--wp--preset--color--slate); }
.yz-stay-card__price { font-size: var(--wp--preset--font-size--x-large); }
.yz-stay-card__price + .is-style-provenance { margin-top: var(--wp--preset--spacing--20); }
.yz-stay-card__badges { gap: var(--wp--preset--spacing--20); margin-top: var(--wp--preset--spacing--40); }
.yz-stay-card__badges + .wp-block-buttons { margin-top: var(--wp--preset--spacing--40); }

.yz-demo-notice { padding: var(--wp--preset--spacing--40); border: 1px dashed var(--wp--custom--color--state); border-radius: var(--wp--custom--radius--sm); background-color: var(--wp--preset--color--white); }
.yz-demo-notice p { margin: 0; font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--ink); }

.yz-embed { border-radius: var(--wp--custom--radius--md); overflow: hidden; }

/* ============================================================
   9 · Layout bands (hero, statement, feature, bento)
   ============================================================ */
/* THE HERO PULL. Zero the chain, then pull by the header — do not try to size a
   negative margin to cancel a collapse.
   The bug this replaces was a −88px margin sized for exactly two terms: the
   64px header and a 1.5rem root block gap. A third term (.entry-content's own
   --yz-doc-gap) joined the collapse and 8–24px of Paper showed above the
   photograph, with the floating pill half on paper and half on the image.
   Sizing the pull to the chain only ever moves the problem: it is right until
   the chain changes, and one of its terms is not even ours — core emits the
   root block gap only when settings.spacing.blockGap is enabled, which this
   theme.json does not declare, so that 1.5rem may not exist at all. A rule
   whose correctness depends on a value we cannot see is a rule that will break
   again quietly.
   So: whatever margin would sit above a hero is zeroed at source, and the hero
   pulls up by --yz-header-h and nothing else. Correct whether or not the root
   gap is emitted, and it needs no browser to confirm. Do not re-hardcode the
   64px — it is --yz-header-h in §2. */
.yz-hero-x {
  position: relative;
  overflow: clip;
  margin-block-start: calc(-1 * var(--yz-header-h));
  min-block-size: min(86vh, 760px);
  min-block-size: min(86svh, 760px);
  display: grid;
  align-items: center;
}
/* The two shapes a hero can open a page in: straight under main (a template
   that opens with the band) and under the content wrapper (front-page.html and
   anything whose main opens with post content). Both are zeroed so the pull
   above resolves to exactly −(header) in either. The five document templates
   put .yz-doc-head and the rail above .entry-content, so their chain starts
   below the ceremonial head and neither selector matches — which is right,
   a hero mid-document must not climb into the header. */
.wp-site-blocks > main:has(> .yz-hero-x:first-child),
.wp-site-blocks > main:has(> .entry-content:first-child > .yz-hero-x:first-child),
main > .entry-content:first-child:has(> .yz-hero-x:first-child) {
  margin-block-start: 0;
}
.yz-hero-x__media { position: absolute; inset-inline: 0; inset-block-start: -18%; block-size: 118%; inline-size: 100%; margin: 0; }
.yz-hero-x__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.yz-hero-x::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  /* Warm scrim. Deliberately darker than the blueprint proposed: the v2.0
     accessibility audit strengthened this to keep white text AA over the
     bright pano, and serif 400 strokes are thinner, not thicker. */
  background: linear-gradient(180deg, rgba(24, 20, 16, 0.50), rgba(24, 20, 16, 0.55) 45%, rgba(24, 20, 16, 0.68));
}
.yz-hero-x__content { position: relative; z-index: 2; text-align: center; padding: calc(var(--yz-header-h) + 4rem) var(--wp--preset--spacing--40) var(--wp--preset--spacing--70); }
.yz-hero-x__content :is(h1, p) { color: var(--wp--preset--color--white); }
.yz-hero-x__title { font-size: clamp(3rem, 2.2rem + 3.6vw, 5.25rem); font-weight: 800; line-height: 1.05; letter-spacing: -0.02em; max-width: 18ch; margin-inline: auto; }
.yz-hero-x__sub { font-size: clamp(1.05rem, 1.7vw, 1.3rem); max-width: 40ch; margin: var(--wp--preset--spacing--40) auto var(--wp--preset--spacing--40); color: rgba(255, 255, 255, 0.9); }
.yz-hero-x__credit {
  position: absolute; z-index: 2; inset-inline-end: var(--wp--preset--spacing--30); inset-block-end: var(--wp--preset--spacing--20);
  margin: 0; font-size: var(--wp--preset--font-size--x-small); color: rgba(255, 255, 255, 0.85);
}
.yz-hero-x__credit a { color: rgba(255, 255, 255, 0.85); }
.yz-hero-x { --g-focus: var(--wp--preset--color--white); --g-btn-bg: var(--wp--preset--color--white); --g-btn-bg-hover: var(--wp--preset--color--shell); --g-btn-fg: var(--wp--preset--color--ink); }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .yz-hero-x__media { animation: yz-drift linear both; animation-timeline: view(); animation-range: exit 0% exit 100%; }
  }
}
@keyframes yz-drift { to { transform: translateY(18%); } }

.yz-statement { text-align: center; }
.yz-statement h2 { font-weight: 600; letter-spacing: -0.015em; line-height: 1.15; max-width: 26ch; margin-inline: auto; }
.yz-statement h2 strong { font-weight: 800; }
.yz-statement__kicker { color: var(--g-muted, var(--wp--preset--color--slate)); margin-top: var(--wp--preset--spacing--40); text-wrap: pretty; }
.yz-statement .yz-eyebrow { text-indent: 0.14em; }

.yz-feature { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); gap: clamp(2rem, 4.5vw, 4rem); align-items: center; }
.yz-feature > * { min-width: 0; }
.yz-feature > :last-child { max-width: 34rem; }
.yz-feature--flip > :first-child { order: 2; }
@media (max-width: 899px) {
  .yz-feature { grid-template-columns: minmax(0, 1fr); gap: var(--wp--preset--spacing--40); }
  .yz-feature--flip > :first-child { order: 0; }
}
.yz-feature__fact { font-size: var(--wp--preset--font-size--xx-large); }

.yz-bento { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: minmax(12rem, auto); gap: var(--wp--preset--spacing--40); }
.yz-tile { padding: var(--wp--preset--spacing--50); display: flex; flex-direction: column; justify-content: flex-end; min-width: 0; }
.yz-tile--photo { position: relative; overflow: hidden; isolation: isolate; background-color: var(--wp--preset--color--ink); border: 0; padding: var(--wp--preset--spacing--40); }
.yz-tile--photo > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.yz-tile--photo .yz-photo__cap { position: relative; inset: auto; }
.yz-tile--wide { grid-column: span 2; }
.yz-tile--tall { grid-row: span 2; }
.yz-tile--full { grid-column: 1 / -1; }
.yz-tile__num { font-size: var(--wp--preset--font-size--x-large); }
.yz-tile__label { font-size: var(--wp--preset--font-size--small); color: var(--g-muted, var(--wp--preset--color--slate)); margin: var(--wp--preset--spacing--20) 0 0; }
.yz-tile__badge { align-self: flex-start; margin-block: 0 auto; }
@media (max-width: 1023px) { .yz-bento { grid-template-columns: repeat(2, minmax(0, 1fr)); } .yz-tile--tall { grid-row: auto; } }
@media (max-width: 599px) {
  .yz-bento { grid-template-columns: minmax(0, 1fr); grid-auto-rows: auto; }
  .yz-tile--wide, .yz-tile--tall, .yz-tile--full { grid-column: auto; grid-row: auto; }
  .yz-tile { min-height: 9rem; }
}
.yz-pullquote { font-family: var(--wp--preset--font-family--display); font-weight: 400; letter-spacing: -0.012em; line-height: 1.3; text-wrap: balance; }
.yz-pullquote strong { font-weight: 600; }

/* ============================================================
   10 · New interaction patterns
   ============================================================ */
/* Reveal: base state ALWAYS visible; hidden states only under RM-no-preference. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .yz-reveal > * { animation: yz-rise linear both; animation-timeline: view(); animation-range: entry 5% entry 45%; }
  }
  @supports not (animation-timeline: view()) {
    .yz-js .yz-reveal:not(.is-inview) > * { opacity: 0; translate: 0 14px; }
    .yz-js .yz-reveal > * { transition: opacity var(--yz-dur-m) var(--yz-ease), translate var(--yz-dur-m) var(--yz-ease); }
    .yz-js .yz-reveal > :nth-child(2) { transition-delay: 70ms; }
    .yz-js .yz-reveal > :nth-child(3) { transition-delay: 140ms; }
    .yz-js .yz-reveal > :nth-child(4) { transition-delay: 210ms; }
    .yz-js .yz-reveal > :nth-child(n+5) { transition-delay: 280ms; }
    .yz-js .yz-reveal > :focus-within { opacity: 1; translate: none; transition: none; }
  }
}
@keyframes yz-rise { from { opacity: 0; translate: 0 14px; } }

/* Scrollytelling: fact pinned while receipts pass. */
.yz-scrolly { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: clamp(2rem, 4vw, 4rem); align-items: start; }
.yz-scrolly__pin { position: sticky; top: calc(var(--yz-header-h) + 2rem); }
.yz-scrolly__step { padding-block: var(--wp--preset--spacing--50); border-block-start: 1px solid var(--g-hairline, var(--wp--preset--color--line)); }
.yz-scrolly__step:first-child { border-block-start: 0; }
@media (max-width: 781px) { .yz-scrolly { grid-template-columns: minmax(0, 1fr); } .yz-scrolly__pin { position: static; } }

/* Stat band: server-rendered figures; JS counts up. */
.yz-stat-band { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 0; }
.yz-stat { padding-inline: 0; min-width: 0; }
@media (min-width: 48em) {
  .yz-stat { padding-inline: var(--wp--preset--spacing--50); border-inline-start: 1px solid var(--g-hairline); }
  .yz-stat:first-child { padding-inline-start: 0; border-inline-start: 0; }
}
.yz-stat__num { font-size: var(--wp--preset--font-size--xx-large); }
.yz-stat__label { font-size: var(--wp--preset--font-size--small); color: var(--g-muted); margin: var(--wp--preset--spacing--20) 0 0; }

/* Accordion: styled native details. */
.yz-acc { border-block-start: 1px solid var(--g-hairline, var(--wp--preset--color--line)); }
.yz-acc details { border-block-end: 1px solid var(--g-hairline, var(--wp--preset--color--line)); }
.yz-acc summary {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding-block: var(--wp--preset--spacing--40); cursor: pointer; list-style: none;
  font-weight: 600; font-size: var(--wp--preset--font-size--medium); min-height: 44px;
}
.yz-acc summary::-webkit-details-marker { display: none; }
.yz-acc summary::after { content: "+"; font-weight: 400; font-size: 1.4em; line-height: 1; color: var(--g-muted, var(--wp--preset--color--slate)); flex: none; }
.yz-acc details[open] summary::after { content: "\2212"; }
.yz-acc details > :not(summary) { margin-block: 0 var(--wp--preset--spacing--40); max-width: 62ch; }

/* Before/after compare (tide honesty). */
.yz-compare { position: relative; aspect-ratio: 16 / 10; overflow: hidden; border-radius: var(--wp--custom--radius--md); background: var(--wp--preset--color--shell); }
.yz-compare img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.yz-compare__top { clip-path: inset(0 calc(100% - var(--yz-pos, 50%)) 0 0); }
/* inset() is physical (top right bottom left) and never mirrors, but the
   divider (::after, inset-inline-start), the browser's own range mirroring and
   the tag row are all direction-aware — so under RTL the seam sat on one side
   and the line on the other, and the tags named the wrong photograph. Flip the
   inset by hand, the way .yz-meter__mark flips its translate. */
[dir="rtl"] .yz-compare__top { clip-path: inset(0 0 0 calc(100% - var(--yz-pos, 50%))); }
.yz-compare__input {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: ew-resize; z-index: 2;
}
.yz-compare::after {
  content: ""; position: absolute; inset-block: 0; inline-size: 2px; z-index: 1;
  inset-inline-start: var(--yz-pos, 50%); background: var(--wp--preset--color--white);
  box-shadow: 0 0 0 1px rgba(14, 17, 19, 0.25);
}
.yz-compare:has(.yz-compare__input:focus-visible)::after { box-shadow: 0 0 0 3px var(--wp--custom--color--link); }
.yz-compare__tags { position: absolute; z-index: 1; inset-inline: var(--wp--preset--spacing--30); inset-block-end: var(--wp--preset--spacing--30); display: flex; justify-content: space-between; pointer-events: none; }
.yz-compare__tags span { font-size: var(--wp--preset--font-size--x-small); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: #fff; background: rgba(14, 17, 19, 0.62); padding: 4px 10px; border-radius: var(--wp--custom--radius--pill); }

/* Filter chips: CSS-only via :has(). */
.yz-filter__chips { display: flex; flex-wrap: wrap; gap: var(--wp--preset--spacing--20); }
.yz-filter__chips label {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  padding: 0.4rem 1rem; border: 1px solid var(--g-muted, var(--wp--preset--color--slate));
  border-radius: var(--wp--custom--radius--pill); font-size: var(--wp--preset--font-size--small); font-weight: 600; cursor: pointer;
}
.yz-filter__chips input { accent-color: var(--wp--custom--color--link); }
.yz-filter__chips label:has(input:checked) { background: var(--wp--preset--color--ink); color: var(--wp--preset--color--white); border-color: var(--wp--preset--color--ink); }
/* Scoped to the nearest .yz-filter and matched on data-filter, not on ids, so a
   second copy of the pattern on one page filters its own grid instead of the
   first copy's - and emits no duplicate ids. */
.yz-filter__set { border: 0; margin: 0; padding: 0; min-inline-size: 0; }
.yz-filter__legend {
  padding: 0;
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--g-muted, var(--wp--preset--color--slate));
  margin-block-end: var(--wp--preset--spacing--20);
}
/* Empty until JS fills it, so it announces nothing rather than "Showing of". */
.yz-filter__count {
  margin-block: var(--wp--preset--spacing--20) 0;
  font-size: var(--wp--preset--font-size--small);
  color: var(--g-muted, var(--wp--preset--color--slate));
}
.yz-filter__count:empty { display: none; }
.yz-filter:has(input[data-filter="veg"]:checked) .yz-filter__grid > :not([data-veg]),
.yz-filter:has(input[data-filter="halal"]:checked) .yz-filter__grid > :not([data-halal]),
.yz-filter:has(input[data-filter="family"]:checked) .yz-filter__grid > :not([data-family]) { display: none; }

/* Card rail: scroll-snap with a peeking next card. */
.yz-rail { display: grid; grid-auto-flow: column; grid-auto-columns: min(78%, 21rem); gap: var(--wp--preset--spacing--40); overflow-x: auto; scroll-snap-type: x mandatory; padding-block-end: var(--wp--preset--spacing--30); scrollbar-width: thin; }
.yz-rail > * { scroll-snap-align: start; min-width: 0; }
.yz-rail:focus-visible { outline: 2px solid var(--g-focus, var(--wp--custom--color--link)); outline-offset: 3px; }

/* Verify stepper. */
.yz-stepper { list-style: none; margin: 0; padding: 0; counter-reset: yzstep; }
.yz-stepper li { counter-increment: yzstep; position: relative; padding-inline-start: 3.5rem; padding-block-end: var(--wp--preset--spacing--50); }
.yz-stepper li::before {
  content: counter(yzstep, decimal-leading-zero);
  position: absolute; inset-inline-start: 0; inset-block-start: 0;
  font-family: var(--wp--preset--font-family--display); font-weight: 800; font-size: var(--wp--preset--font-size--medium);
  color: var(--g-seal-fg, var(--wp--custom--color--link));
  font-variant-numeric: tabular-nums;
}
.yz-stepper li::after { content: ""; position: absolute; inset-inline-start: 0.8rem; inset-block-start: 2.2rem; inset-block-end: 0.4rem; inline-size: 1px; background: var(--g-hairline, var(--wp--preset--color--line)); }
.yz-stepper li:last-child::after { display: none; }
.yz-stepper h3 { margin: 0 0 var(--wp--preset--spacing--20); font-size: var(--wp--preset--font-size--large); }
.yz-stepper p { margin: 0; color: var(--g-muted, var(--wp--preset--color--slate)); max-width: 56ch; }

/* Corrections timeline. */
.yz-timeline { list-style: none; margin: 0; padding: 0; border-inline-start: 1px solid var(--g-hairline, var(--wp--preset--color--line)); }
.yz-timeline li { position: relative; padding-inline-start: var(--wp--preset--spacing--50); padding-block-end: var(--wp--preset--spacing--50); }
.yz-timeline li::before { content: ""; position: absolute; inset-inline-start: -5px; inset-block-start: 0.4rem; width: 9px; height: 9px; border-radius: 50%; background: var(--g-seal-fg, var(--wp--custom--color--link)); }
.yz-timeline time { display: block; font-size: var(--wp--preset--font-size--small); font-weight: 600; color: var(--g-muted, var(--wp--preset--color--slate)); font-variant-numeric: tabular-nums; margin-bottom: var(--wp--preset--spacing--20); }

/* Area map: photo + physical-coordinate hotspots (pins must not flip in RTL). */
.yz-map { position: relative; border-radius: var(--wp--custom--radius--md); overflow: hidden; }
.yz-map img { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.yz-map__pin {
  position: absolute; transform: translate(-50%, -50%); z-index: 1;
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--wp--preset--color--white); color: var(--wp--preset--color--ink);
  font-size: var(--wp--preset--font-size--x-small); font-weight: 600;
  padding: 6px 12px; border-radius: var(--wp--custom--radius--pill); text-decoration: none;
  box-shadow: 0 1px 4px rgba(14, 17, 19, 0.3);
}
.yz-map__pin::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--wp--custom--color--link); }

/* Parallax interlude figure. */
.yz-interlude { position: relative; overflow: clip; }
.yz-interlude .yz-photo { border-radius: 0; aspect-ratio: 21 / 9; }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .yz-interlude .yz-photo img { animation: yz-drift-sm linear both; animation-timeline: view(); scale: 1.12; }
  }
}
@keyframes yz-drift-sm { from { transform: translateY(-4%); } to { transform: translateY(4%); } }

/* Verification ticker (marquee). RM: static row, no clip. */
.yz-ticker { overflow: hidden; border-block: 1px solid var(--g-hairline, var(--wp--preset--color--line)); padding-block: var(--wp--preset--spacing--30); }
.yz-ticker__row { display: flex; gap: var(--wp--preset--spacing--60); flex-wrap: wrap; }
.yz-ticker__row > * { white-space: nowrap; font-size: var(--wp--preset--font-size--small); font-weight: 600; color: var(--g-muted, var(--wp--preset--color--slate)); }
.yz-ticker__row[aria-hidden="true"] { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .yz-js .yz-ticker__track { display: flex; gap: var(--wp--preset--spacing--60); width: max-content; animation: yz-marquee 36s linear infinite; }
  .yz-js .yz-ticker__track .yz-ticker__row { flex-wrap: nowrap; }
  .yz-js .yz-ticker__track .yz-ticker__row[aria-hidden="true"] { display: flex; }
  .yz-ticker:hover .yz-ticker__track, .yz-ticker:focus-within .yz-ticker__track { animation-play-state: paused; }
}
/* WCAG 2.2.2 — motion over five seconds needs a stop. :hover/:focus-within is
   not a mechanism: nothing in the strip is focusable, so a keyboard or touch
   user could never reach it. The button is revealed only when the marquee can
   actually run (JS present, motion allowed), so it is never a dead control. */
.yz-ticker { position: relative; }
.yz-ticker__pause { display: none; }
.yz-ticker.is-paused .yz-ticker__track { animation-play-state: paused; }
.yz-ticker__lbl-play { display: none; }
/* The label swap keys off .is-paused, the class the JS toggles — never off
   aria-pressed. Pressed-true used to coincide exactly with the visible name
   "Play", so a screen reader announced "Play … pressed" over a stopped
   ticker: name and state moving together and pointing opposite ways. The
   button now follows the media-control model — the accessible NAME carries
   the state and there is no ARIA state to contradict it. */
.yz-ticker.is-paused .yz-ticker__lbl-pause { display: none; }
.yz-ticker.is-paused .yz-ticker__lbl-play { display: inline; }
@media (prefers-reduced-motion: no-preference) {
  .yz-js .yz-ticker { padding-inline-end: 5.5rem; }
  .yz-js .yz-ticker__pause {
    display: inline-flex; align-items: center; justify-content: center;
    position: absolute; z-index: 1;
    inset-inline-end: var(--wp--preset--spacing--30);
    inset-block-start: 50%; transform: translateY(-50%);
    min-height: 44px; min-width: 44px; padding-inline: 0.75rem;
    font-size: var(--wp--preset--font-size--x-small); font-weight: 600;
    color: var(--g-fg, var(--wp--preset--color--ink));
    background: var(--g-card-bg, var(--wp--preset--color--white));
    border: 1px solid var(--g-hairline, var(--wp--preset--color--line));
    border-radius: var(--wp--custom--radius--pill);
    cursor: pointer;
  }
}
@keyframes yz-marquee { to { transform: translateX(-50%); } }
[dir="rtl"] .yz-js .yz-ticker__track { animation-name: yz-marquee-rtl; }
@keyframes yz-marquee-rtl { to { transform: translateX(50%); } }

/* WhatsApp dock. */
.yz-wa-dock {
  position: fixed; z-index: 40;
  inset-inline-end: max(1rem, env(safe-area-inset-right));
  inset-block-end: calc(1rem + env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 6px;
}
.yz-wa-dock__btn {
  display: inline-flex; align-items: center; gap: 9px;
  min-height: 52px; padding: 0 1.3rem;
  background-color: var(--wp--preset--color--ink); color: var(--wp--preset--color--white);
  border-radius: var(--wp--custom--radius--pill);
  font-size: var(--wp--preset--font-size--small); font-weight: 600; text-decoration: none;
  box-shadow: 0 2px 10px rgba(14, 17, 19, 0.25);
}
.yz-wa-dock__btn:hover { background-color: var(--wp--custom--color--cta-hover); color: var(--wp--preset--color--white); }
.yz-wa-dock__close {
  display: none;
  width: 44px; height: 44px; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; cursor: pointer;
  background: var(--wp--preset--color--white); color: var(--wp--preset--color--slate);
  border: 1px solid var(--wp--preset--color--line); font-size: 18px; line-height: 1;
}
.yz-js .yz-wa-dock__close { display: inline-flex; }
.yz-wa-dock[hidden] { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .yz-wa-dock { animation: yz-dock-in 0.4s var(--yz-ease) 1.2s both; }
}
@keyframes yz-dock-in { from { opacity: 0; transform: translateY(12px); } }
@media print { .yz-wa-dock { display: none; } }

/* Interaction block styles. */
@media (prefers-reduced-motion: no-preference) {
  .is-style-lift { transition: translate var(--yz-dur) var(--yz-ease), box-shadow var(--yz-dur) var(--yz-ease); }
  .is-style-lift:hover, .is-style-lift:focus-within { translate: 0 -4px; box-shadow: 0 10px 28px -12px rgba(14, 17, 19, 0.28); }
  .is-style-hover-zoom img { transition: scale var(--yz-dur-m) var(--yz-ease); }
  .is-style-hover-zoom:hover img, .is-style-hover-zoom:focus-within img { scale: 1.05; }
}
.is-style-hover-zoom { overflow: hidden; }

/* ============================================================
   11 · Hardening
   ============================================================ */
.wp-site-blocks :is(h1, h2, h3, h4, p, li) { overflow-wrap: break-word; }
.wp-site-blocks :is(h1, h2, h3) { text-wrap: balance; }
/* NO overflow/clip on this element, ever. `overflow-x: clip` here (shipped in
   v2.0) creates a clipping context that silently disables position:sticky in
   every descendant — the header pill has not actually stuck since v2.0.
   Horizontal overflow is prevented at source instead: min-width:0 on flex/grid
   children (below), overflow-x:auto on tables, and max-width:100% on media.
   If a wide element ever escapes, clip THAT element, not the page wrapper. */
.wp-site-blocks > * { min-width: 0; }
img, video, svg, iframe { max-width: 100%; }
.wp-block-table { overflow-x: auto; }
.yz-bento > *, .yz-feature > *, .yz-trust-strip > *, .yz-stat-band > * { min-width: 0; }
@media print { [class*="yz-"] { animation: none !important; transition: none !important; } }

/* ============================================================
   12 · Ledger devices
   Caps, by doctrine: ONE pindo per page, ONE has-screen band per page.
   ============================================================ */
/* Pindo rule: the kanga-border signature. Three warm threads. */
.wp-block-separator.is-style-pindo {
  height: 5px; border: 0;
  max-inline-size: min(62ch, 100%);
  margin-inline: auto;
  background: linear-gradient(to bottom,
    var(--wp--preset--color--mlango) 0 1px, transparent 1px 2px,
    var(--wp--preset--color--mlango) 2px 3px, transparent 3px 4px,
    var(--wp--preset--color--mlango) 4px 5px);
  opacity: 1;
}
.yz-band--ink .wp-block-separator.is-style-pindo {
  background: linear-gradient(to bottom,
    rgba(255, 253, 248, 0.55) 0 1px, transparent 1px 2px,
    rgba(255, 253, 248, 0.55) 2px 3px, transparent 3px 4px,
    rgba(255, 253, 248, 0.55) 4px 5px);
}
/* Short rule: the between-sections breath. */
.wp-block-separator.is-style-short {
  inline-size: 3.5rem; height: 3px; border: 0;
  margin-inline: auto;
  background: var(--wp--preset--color--mlango);
  opacity: 1;
}
/* Pullquote: serif italic between hairlines. */
.wp-block-pullquote {
  border-block: 1px solid var(--g-hairline, var(--wp--preset--color--line));
  padding-block: var(--wp--preset--spacing--50);
}
.wp-block-pullquote blockquote p {
  font-family: var(--wp--preset--font-family--serif-display);
  font-style: italic;
  font-size: var(--wp--preset--font-size--x-large);
  line-height: 1.3;
  text-wrap: balance;
}
/* Journal index rows (homepage): hairline list, tabular dates. */
.yz-journal-row { border-block-end: 1px solid var(--g-hairline, var(--wp--preset--color--line)); padding-block: var(--wp--preset--spacing--40); }
.yz-journal-row time { font-family: var(--wp--preset--font-family--display); font-variant-numeric: tabular-nums; font-size: var(--wp--preset--font-size--small); color: var(--g-muted); }
/* Merged dark close: a final espresso band flows into the footer.
   The `main > .yz-band--ink` form this used to carry matched on zero pages:
   an ink band is always emitted INSIDE post content, so it is a grandchild of
   main (main > .entry-content > .yz-band--ink). Descend through the content
   wrapper instead. .wp-block-post-content is named alongside .entry-content
   because the front page and any bare post-content render carry both classes
   and a future template may carry only the block one.

   STILL UNPROVEN ON A REAL PAGE. patterns/statement-band.php is the only
   emitter of .yz-band--ink and it is an editor-placed pattern; no page in
   content/ currently ends with one. The selector is now correct for the shape
   it describes, but nobody has seen it fire — do not read it as verified. */
.wp-site-blocks:has(main :is(.entry-content, .wp-block-post-content) > .yz-band--ink:last-child) .site-footer,
.wp-site-blocks:has(main > .yz-band--ink:last-child) .site-footer { margin-block-start: 0; }

/* Compat: the older centred hero (home-hero.php) still used on inner pages. */
.yz-hero { text-align: center; padding-block: var(--wp--custom--space--band); }
.yz-hero__sub { font-size: var(--wp--preset--font-size--large); font-weight: 400; color: var(--g-muted, var(--wp--preset--color--slate)); max-width: 44ch; margin-inline: auto; line-height: 1.5; }
.yz-hero__light { font-weight: 400; }

/* ============================================================
   14 · The instrument register (v4.0 "Instrument")
   Two registers of voice, one site: DOCUMENTS keep the Ledger
   ceremony and rhythm; INSTRUMENTS — price-index, the register,
   investor fact sheets, hub — compress. The skew is achieved
   through density, anatomy and annotation, never palette: zero
   new colours, and there is NO legal colour here for urgency.
   Teal stays verified-only; ochre stays unverified-only; a price
   classification is DATA and renders in data ink.
   Spec: specs/07_V4_PATH.md §3c. Register CSS from spec 06 lands
   here too when Phase 4.1 builds it (its "§12" predates the v3
   renumbering).
   ============================================================ */

/* The instrument band: paper ground, compressed rhythm. Documents never
   carry this class, so the two registers cannot bleed by construction. */
.yz-band--instrument {
  --yz-figure-ink: var(--g-fg, var(--wp--preset--color--ink));
  padding-block: var(--wp--preset--spacing--50);
}
.yz-band--instrument :is(h2, h3) { margin-block-start: 2rem; }
/* Investor data ink: bahari, and at a glance the reader knows which economy
   a number belongs to. bahari never styles a link (doctrine). */
.yz-band--instrument.is-inv,
.yz-card--instrument.is-inv { --yz-figure-ink: var(--wp--preset--color--bahari); }

/* The instrument card: white surface on paper, control radius (8px = tool
   voice; document cards keep 16px), hairline + the ONE sanctioned shadow. */
.yz-card--instrument {
  background: var(--wp--preset--color--white);
  border: 1px solid var(--g-hairline, var(--wp--preset--color--line));
  border-radius: var(--wp--custom--radius--sm);
  box-shadow: var(--wp--custom--shadow--crisp);
  padding: var(--wp--preset--spacing--40);
}
.yz-figure {
  font-family: var(--wp--preset--font-family--display);
  font-size: var(--wp--preset--font-size--figure);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--yz-figure-ink, var(--g-fg));
}

/* The annotation voice: the small dated line under a figure. Unscoped on
   purpose — patterns/money-bar.php emits it with no wrapper, and it was
   invisible for three releases while its only rule waited for a
   .yz-row--annotated ancestor that nothing has ever emitted.

   The annotated-row DEVICE itself (spec 07 §3c: .yz-row--annotated wrapper,
   .yz-row__figure numeral) was removed on 2026-08-02. It was ~15 lines of
   rules for markup no pattern, template, part, inc/ file or publish script
   produces. When the row is actually built, restore it from spec 07 §3c and
   from git history — CSS for a device that does not exist reads as a device
   that does, which is how .yz-row__meta came to be broken in the first place. */
.yz-row__meta { font-size: var(--wp--preset--font-size--x-small); color: var(--g-muted); }

/* Annotated chips: the Kayak per-option figure, honest. Selected = espresso
   fill (never teal — teal is meaning, not state). The per-option annotation
   numeral (.yz-chip__figure) was removed on 2026-08-02 for the same reason as
   the annotated row above: nothing emits it. Spec 07 §3c holds the design. */
.yz-filter__chips label { padding-block: var(--wp--preset--spacing--10); }
.yz-filter__chips label:has(input:checked) {
  background: var(--wp--preset--color--ink);
  color: var(--wp--preset--color--paper);
  border-color: var(--wp--preset--color--ink);
}

/* Instrument tables: sticky header reading its ground, hover wash, bare
   numerals (units live in headers). Builds on price-index-table idiom.

   THE SCROLLPORT IS THE FIGURE, AND IT HAS TO BE BOUNDED.
   §11 gives every .wp-block-table `overflow-x: auto`; per CSS Overflow 3 an
   auto on one axis computes the visible on the other axis to auto too, so the
   figure — not the viewport — is what a sticky <th> inside it resolves
   against. Its block size is the table's, so there was no vertical scroll
   range and the header could never leave its natural position: the device was
   documented in spec 07 §3c and had never once fired.
   Bounding the figure gives it that range, and it also makes the tabindex="0"
   that inc/table-a11y.php puts on the same element an honest promise.
   NOT ON PHONES. Measured against the live fact sheet on
   /can-foreigners-buy-property-zanzibar: seven rows of quoted statute run past
   1000px at 390px wide, so a 70vh cap there would bury most of a money page's
   evidence inside a nested scroller — the opposite of checkable. Below 48em
   the table simply runs its full height and the sticky rule is not asked for.
   If you ever remove the max-block-size, remove the sticky rule with it. */
@media (min-width: 48em) {
  /* The band variant is bounded too. Its table is also a figure.wp-block-table,
     so it is also a scrollport with no height limit — sticky there was just as
     dead as it was on the card, and naming only the card would have fixed half
     the rule while leaving the other half describing something it cannot do. */
  .yz-card--instrument.wp-block-table,
  .yz-band--instrument figure.wp-block-table {
    max-block-size: 70vh;
    overflow: auto;
  }
  /* SEPARATE borders, not collapsed. theme.json's core/table css sets
     border-collapse:collapse and hangs each cell's hairline on border-block-end;
     under collapse the border belongs to the TABLE, not the cell, so a sticky th
     leaves its own underline behind and scrolls naked over the rows. With
     separate + zero spacing the cell paints its own border and takes it along,
     and tbody looks identical: td carries border-block-end only, so adjacent
     rows cannot double up, and the last-row rule still clears it. */
  .yz-card--instrument.wp-block-table table,
  .yz-band--instrument figure.wp-block-table table {
    border-collapse: separate;
    border-spacing: 0;
  }
  .yz-band--instrument thead th,
  .yz-card--instrument thead th {
    position: sticky; inset-block-start: 0;
    background: var(--g-card-bg, var(--wp--preset--color--white));
  }
}
.yz-band--instrument tbody tr:hover,
.yz-card--instrument tbody tr:hover { background: var(--wp--preset--color--shell); }
.yz-band--instrument td:has(> .yz-figure) { text-align: end; }

/* The range meter — Google Flights' low/typical/high, MONOCHROME BY LAW:
   the verdict is a classification, not a verification. Endpoints and the
   current figure are real text; the track is decoration. */
.yz-meter { position: relative; block-size: 2px; background: var(--g-hairline, var(--wp--preset--color--line)); margin-block: var(--wp--preset--spacing--40) var(--wp--preset--spacing--30); }
.yz-meter__mark {
  position: absolute; inset-block-start: 50%;
  transform: translate(-50%, -50%);
  inline-size: 10px; block-size: 10px; border-radius: 50%;
  background: var(--yz-figure-ink, var(--wp--preset--color--ink));
}
[dir="rtl"] .yz-meter__mark { transform: translate(50%, -50%); }
.yz-meter__labels { display: flex; justify-content: space-between; font-size: var(--wp--preset--font-size--x-small); color: var(--g-muted); font-variant-numeric: tabular-nums; }

/* Observation history: publish-time inline SVG (zero runtime, zero JS,
   nothing to reduce for reduced-motion). */
.yz-history svg { max-width: 100%; height: auto; display: block; }
.yz-history { margin-block: var(--wp--preset--spacing--30); }

/* The provenance-anchored chip corner (spec 07 §3c —
   `.yz-verified--anchored { border-radius: 8px 8px 8px 2px }`) was removed on
   2026-08-02: no pattern, template, part, inc/ file or publish script has ever
   emitted the class. One line, restore it from spec 07 when a chip actually
   anchors a provenance line. */

/* Sticky money bar (mobile money pages): one ledger figure + the concierge.
   CSS only; ground-aware; scrolls away past the CTA band. */
.yz-money-bar {
  position: sticky; inset-block-end: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--wp--preset--spacing--30);
  padding: var(--wp--preset--spacing--15) var(--wp--preset--spacing--40);
  background: var(--wp--preset--color--white);
  border-block-start: 1px solid var(--g-hairline, var(--wp--preset--color--line));
  box-shadow: var(--wp--custom--shadow--crisp);
}
@media (min-width: 782px) { .yz-money-bar { display: none; } }

/* The method footnote: visible text at point of use, never a tooltip. */
.yz-method {
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--g-muted, var(--wp--preset--color--slate));
  border-block-start: 1px solid var(--g-hairline, var(--wp--preset--color--line));
  padding-block-start: var(--wp--preset--spacing--15);
  margin-block-start: var(--wp--preset--spacing--30);
}

/* ============================================================
   15 · Script policy — what the design does when the page is not Latin
   ============================================================
   The site ships fourteen languages — English plus Italian, German, French,
   Polish, Russian, Spanish, Kiswahili (for locals), Chinese, Portuguese,
   Dutch, Hindi, Arabic and Hebrew. Six scripts arrive with them: Latin
   (en/it/de/fr/pl/es/pt/nl/sw), Cyrillic (ru), Han (zh), Devanagari (hi),
   Arabic (ar) and Hebrew (he). The Dhow design was drawn for Latin, and three
   of its habits are wrong everywhere else:

     · NEGATIVE tracking on display type. Optical correction for large Latin
       counters. On Devanagari it opens gaps in the shirorekha — the headline
       bar readers use to find word boundaries. On Arabic it stretches the
       joins. On Han it means nothing: the glyphs are fixed-advance boxes.
     · POSITIVE tracking (0.1-0.14em) under text-transform:uppercase. The
       tracking exists only to make uppercase Latin legible. Devanagari,
       Arabic, Hebrew, Thai and Han have no letter case at all, so the
       transform is a no-op on the native glyphs and the tracking that was
       compensating for it is left behind as damage.
     · TIGHT leading (1.06-1.25 on headings). Latin headings can be set tight
       because ascender and descender are the whole story. Devanagari hangs
       matras above and below the bar, Arabic sits dots under the baseline and
       hamza over it, Thai stacks two marks high, and Han fills its em box
       edge to edge. At 1.06 those collide or clip.

   WHY THIS SECTION IS LAST IN THE FILE. Every rule below is written to beat
   its competitor on SPECIFICITY, not on position — the table further down
   proves each pair — because the previous arrangement lost a tie on source
   order and nobody noticed for two releases. Being last is the second belt:
   anything this section missed still resolves here rather than being silently
   outranked. inc/assets.php declares `global-styles` as a dependency of this
   stylesheet, so it also loads after everything theme.json emits. Do not move
   this section, and do not add a script rule anywhere else in the file — one
   place to look is the whole value of it.

   WHY :lang() AND NOT [lang|="xx"]. :lang() matches on the element's
   *computed* language, which is inherited; [lang|="hi"] matches only the one
   element that literally carries the attribute. WordPress puts the language
   on <html> exactly once (core's template-canvas.php calls
   language_attributes(), which prints lang="…" and adds dir="rtl" when
   is_rtl()), so a p deep in the document has no lang attribute of its own and
   an attribute selector would never reach it. :lang() also survives the case
   that matters later: an Arabic pull-quote placed inside an English page with
   lang="ar" on the block wrapper is styled correctly by :lang(ar), and is
   invisible to [lang|="ar"] written as a descendant selector.

   WHAT POLYLANG ACTUALLY SETS — confirmed before relying on it. Polylang
   switches the WordPress locale per language, and language_attributes() emits
   str_replace('_','-', get_locale()). So the attribute is a full locale, not
   a bare code: lang="hi-IN", "zh-CN", "he-IL", "ru-RU", "pt-BR", and bare
   lang="ar" or "sw" only where the locale itself has no region. That is fine:
   per Selectors L3, :lang(C) matches a language that equals C *or* begins
   with C followed by a hyphen, so :lang(hi) matches hi-IN. Never write
   :lang(hi-IN) here — it would stop matching the day the locale changes to
   hi_IN vs hi, and it would miss a second Hindi variant.
   With Polylang absent the html element carries the site locale, every
   :lang() below fails to match, and the theme renders exactly as it does now.

   THE TIE TABLE. Every rule below and the rule it has to beat:

     this section                                          spec    competitor                                        spec
     :is(:lang) :is(label list)                           (0,2,2)  .wp-block-table thead th (theme.json core/table)  (0,1,2)
                                                                   .yz-price-index-table__table thead th  §8         (0,1,2)
                                                                   .yz-apparatus summary  §2b                        (0,1,1)
                                                                   .yz-compare__tags span §10                        (0,1,1)
                                                                   .yz-eyebrow §2 / .yz-colophon__item §2b /
                                                                   .yz-filter__legend §10                            (0,1,0)
     :is(:lang) :is(display list)                         (0,2,1)  .yz-statement h2 §9                               (0,1,1)
                                                                   .yz-hero-x__title §9 / .yz-pullquote §9           (0,1,0)
                                                                   :root :where(h1) (theme.json elements.h1)         (0,1,0)
     :is(:lang) :is(serif list)                           (0,3,0)  .site-footer .is-style-disclosure §4              (0,2,0)
                                                                   .wp-block-pullquote blockquote p §12              (0,1,2)
                                                                   .yz-standfirst §2b                                (0,1,0)
     :is(:lang) .yz-statement .yz-eyebrow                 (0,3,0)  .yz-statement .yz-eyebrow §9                      (0,2,0)
     :is(:lang) :is(chrome list)                          (0,2,1)  .wp-block-image figcaption (theme.json core/image)(0,1,1)
                                                                   .yz-verified §6                                   (0,1,0)
                                                                   .yz-official-partner-badge__badge §8              (0,1,0)
                                                                   :root :where(.wp-element-button) (theme.json)     (0,1,0)
     :is(:lang) .entry-content .has-drop-cap ..           (0,4,1)  .entry-content .has-drop-cap .. §2b               (0,3,1)

   Every pair above was computed with a specificity calculator, not eyeballed.
   THE ONE THAT HAD ALREADY GONE WRONG: the display-tracking reset used to live
   in §2b as `:is(:lang(..)) :is(h1, h2, h3)` — (0,1,1), exactly equal to
   `.yz-statement h2` (0,1,1) six hundred lines further down and therefore
   beaten by it on source order. An Arabic statement band kept its -0.015em and
   nothing in the file said why. Naming `.yz-statement h2` inside the :is()
   list is what lifts this to (0,2,1) and settles it on specificity instead of
   on where the rule happens to sit.

   TWO SCRIPT GROUPS, because they want opposite things:
     hi, ar, he  — tracking OFF. Devanagari and Arabic are damaged by it and
                   Hebrew has no tracking convention to speak of, so any
                   inherited letter-spacing reads as broken word spacing.
     zh, th      — tracking KEPT, reduced to 0.04em. Small tracking on Han is
                   a real typographic device for labels, not a Latin habit.
   th is listed throughout even though Thai is not one of the fourteen: the
   rules cost nothing, and the day a Thai page exists it should not need this
   file re-opened. ru and sw are deliberately absent — both are cased scripts
   with Latin-like metrics, and everything the design does works on them.     */

/* --- 15a · Labels: uppercase and its compensating tracking come off --------
   These seven are EVERY text-transform:uppercase in the theme — six in this
   file (§2 eyebrow, §2b colophon item, §2b apparatus summary, §8 price-index
   thead, §10 compare tags, §10 filter legend) and one in theme.json
   (styles.blocks.core/table, `& thead th`). If you add an eighth anywhere,
   add it to both lists below in the same commit. */
:is(:lang(hi), :lang(ar), :lang(he), :lang(zh), :lang(th)) :is(
  .yz-eyebrow,
  .yz-colophon__item,
  .yz-apparatus summary,
  .yz-filter__legend,
  .yz-compare__tags span,
  .wp-block-table thead th,
  .yz-price-index-table__table thead th
) {
  text-transform: none;
  letter-spacing: normal;
}
/* Han and Thai keep a small tracking: on Han it is an intentional label
   device, and it is what these locales have shipped since v3. */
:is(:lang(zh), :lang(th)) :is(
  .yz-eyebrow,
  .yz-colophon__item,
  .yz-apparatus summary,
  .yz-filter__legend,
  .yz-compare__tags span,
  .wp-block-table thead th,
  .yz-price-index-table__table thead th
) {
  letter-spacing: 0.04em;
}
/* The eyebrow steps up one size, and only the eyebrow. It is the label a
   reader is meant to read rather than scan, and at x-small with the tracking
   gone the Devanagari matras and the Arabic dots sit under 12px. The other
   x-small labels — colophon item, filter legend, table headers — deliberately
   do NOT get this: enlarging a thead would break the price table's column
   rhythm, which is the one thing on a money page that has to stay scannable.
   This bump shipped in v3 for hi/ar/he/zh; th joins it here. */
:is(:lang(hi), :lang(ar), :lang(he), :lang(zh), :lang(th)) .yz-eyebrow {
  font-size: var(--wp--preset--font-size--small);
}
/* The statement band optically re-centres its eyebrow with a text-indent
   equal to the trailing letter-space. Now that the two groups track
   differently, the compensation has to follow each of them.
   NEITHER OF THESE FIRES TODAY, and neither does the §9 rule they correct:
   patterns/statement-band.php emits a heading and a kicker and no eyebrow, so
   `.yz-statement .yz-eyebrow` matches nothing repo-wide (checked against every
   pattern's markup, 0 matches). They are kept because a correction has to
   travel with the rule it corrects — an editor can drop an eyebrow into a
   statement band by hand, and if it lands in Arabic the 0.14em indent would
   be compensating for tracking that 15a has just removed. If §9's rule is
   ever garbage-collected as dead CSS, delete these two lines with it. */
:is(:lang(hi), :lang(ar), :lang(he)) .yz-statement .yz-eyebrow { text-indent: 0; }
:is(:lang(zh), :lang(th)) .yz-statement .yz-eyebrow { text-indent: 0.04em; }

/* --- 15b · Display type: no negative tracking, and a leading floor --------
   .yz-pullquote is in the tracking and leading lists but NOT in the weight
   rule below: it is a 400-weight paragraph by design, and the 600 belongs to
   headings. It IS in the leading list — it was left out at first, and a
   pullquote is x-large display type, the single worst place to leave Devanagari
   matras or stacked Thai marks under the floor.
   h4-h6 are listed although no pattern emits one today: scripts/publish.mjs
   maps markdown #### to wp:heading level 4-6, and theme.json already styles
   h4 (letterSpacing 0.01em, lineHeight 1.4), so the level is supported
   authoring, not a hypothetical.
   The leading floor is 1.35 for every script in the group. It is a FLOOR
   chosen to clear the tallest mark stack, not a per-script optimum, and it
   has not been eyeballed on a device carrying these fonts — tighten it per
   script once there is real translated copy to look at. */
:is(:lang(hi), :lang(ar), :lang(he), :lang(zh), :lang(th)) :is(
  h1, h2, h3, h4, h5, h6,
  .yz-hero-x__title,
  .yz-pullquote,
  .yz-statement h2
) {
  letter-spacing: normal;
}
:is(:lang(hi), :lang(ar), :lang(he), :lang(zh), :lang(th)) :is(
  h1, h2, h3, h4, h5, h6,
  .yz-hero-x__title,
  .yz-statement h2,
  .yz-pullquote,
  .wp-block-pullquote blockquote p
) {
  line-height: 1.35;
}
/* Display weight: the sans at 800 turns dense scripts into a solid block, so
   the heading register drops to 600 — the same value the Ledger uses for h2.
   This is the rule that used to sit in §2b under the heading "Non-Latin
   display policy"; it was moved here intact because at (0,1,1) it lost the
   tie to .yz-statement h2 (0,1,1, and later in the file), so an Arabic
   statement band kept its -0.015em.
   THE EMPHASISED CLAUSE IS NAMED EXPLICITLY. Moving the rule here was not
   enough: `.yz-statement h2 strong` is (0,1,2) and still outranked the bare
   heading list at (0,2,1)... on the element, but not on the strong INSIDE it,
   which kept 800. The statement band's whole design is two weights in one
   sentence, so half of it staying at 800 produced exactly the solid block this
   rule exists to prevent — the same near-miss, one level deeper. */
:is(:lang(hi), :lang(ar), :lang(he), :lang(zh), :lang(th)) :is(
  h1, h2, h3,
  .yz-statement h2 strong,
  :is(h1, h2, h3) strong
) {
  font-weight: 600;
}

/* --- 15c · The serif accent, where Newsreader cannot follow ---------------
   MEASURED, not assumed: newsreader-normal-400.woff2 carries 564 cmap
   entries — Latin, Latin-1, Latin Ext-A/B and the full Vietnamese range —
   and ZERO Devanagari, Arabic, Hebrew, Han or Cyrillic. So on hi/ar/he/zh a
   serif standfirst is not "Newsreader with a fallback": it is Newsreader for
   the Latin brand names and digits and something else entirely for the
   sentence, in one line. CLAUDE.md's rule ("hi/ar/he/zh fall back to the sans
   at 500") existed only as documentation until this release; this is it.
   Italic goes too — none of these scripts has an italic, so the browser
   synthesises one by shearing the glyphs, which on Arabic destroys the
   baseline. Kiswahili is Latin and keeps the full serif.
   RUSSIAN IS DELIBERATELY NOT HERE. Newsreader has no Cyrillic either, but
   its fallbacks Georgia and Times New Roman both do, so a Russian standfirst
   lands on a real serif and the page stays coherent. See
   assets/fonts/README.md — shipping Cyrillic in the brand faces is an open
   owner decision, not a defect this file can fix. */
:is(:lang(hi), :lang(ar), :lang(he), :lang(zh), :lang(th)) :is(
  .yz-standfirst,
  .wp-block-pullquote blockquote p,
  .site-footer .is-style-disclosure
) {
  font-family: var(--wp--preset--font-family--body);
  font-style: normal;
  font-weight: 500;
}
/* Drop cap: a floated three-line initial is a Latin convention. In Devanagari
   it severs the shirorekha from the rest of the word, in Arabic it isolates a
   letter from its join, and in Han it is simply a bigger character. Off. */
:is(:lang(hi), :lang(ar), :lang(he), :lang(zh), :lang(th)) .entry-content .has-drop-cap:not(:focus)::first-letter {
  float: none; font-size: inherit; font-family: inherit; color: inherit; padding: 0;
}

/* --- 15d · Chrome: the small tracked controls ------------------------------
   0.03-0.06em is small enough to look like a rounding error and large enough
   to break an Arabic join, because letter-spacing is applied AFTER shaping —
   the join is drawn and then pulled apart. Han and Thai are left alone here:
   these are short Latin-ish controls and the values are already tiny. */
:is(:lang(hi), :lang(ar), :lang(he)) :is(
  .yz-verified,
  .yz-official-partner-badge__badge,
  .wp-element-button,
  .wp-block-button__link,
  .wp-block-image figcaption
) {
  letter-spacing: normal;
}

/* --- 15e · Line breaking ---------------------------------------------------
   Han breaks between any two characters, so it needs no help finding a break
   point — what it needs is the prohibition set, which stops a line opening
   with a closing bracket or a small kana and stops it ending with an opener.
   `strict` is that set.
   THAI GETS NO BREAK RULE ON PURPOSE. Thai has no inter-word spaces and is
   broken by the engine's dictionary (ICU in Blink and WebKit, Gecko's own).
   Nothing in this theme sets word-break, so that dictionary is already in
   charge, and §11's `overflow-wrap: break-word` stays as the last resort that
   keeps an unbreakable run inside the 360px floor. Adding `word-break:normal`
   here would be a rule that restates the initial value — CSS for a device
   that does not exist. Thai's real need is leading, and 15b gives it.        */
:lang(zh) { line-break: strict; }

/* --- 15f · Script coverage: which font actually draws the glyph ------------
   The shipped brand faces are Latin only — plus-jakarta-sans-*.woff2 and
   newsreader-*.woff2 carry no Devanagari, Arabic, Hebrew or Han, and the file
   named for the job, noto-sans-normal-400.woff2, is a 13KB LATIN subset
   despite the family being called "multilingual". So on a Hindi, Arabic,
   Hebrew or Chinese page the sentence is drawn by whatever the device
   happens to pick, and two devices pick differently.

   WHY THIS IS HERE AND NOT IN theme.json. The obvious move is to append the
   script fonts to the global stacks. It was tried and reverted: a font list
   is walked PER CHARACTER, so families added for Devanagari also become
   candidates for any character the Latin faces lack — and one of those is
   U+2713, the verified tick, which is the site's signature glyph. Measured in
   Chrome, the appended tail captured it and changed how it draws on ENGLISH
   pages. A global stack cannot express "only for readers of this script";
   :lang() can, so this is one of the things theme.json genuinely cannot say.

   HOW IT WORKS. Redefining the preset custom property re-points every
   consumer at once — theme.json's own body rule, its heading rules, and every
   has-*-font-family class all read these variables — so no rule here has to
   out-specify anything. :root:lang(hi) is (0,2,0) against theme.json's :root
   (0,1,0); it is not a tie, so it does not depend on load order.

   THE BRAND FACE STAYS FIRST in every list. A Hindi page still carries Latin
   brand names, URLs and figures, and those must render in Plus Jakarta Sans,
   not in the Devanagari face. Per-character walking is what makes that work:
   Latin resolves at the head of the list, Devanagari falls through to the
   first family that has it.

   THE ORDER WITHIN EACH SCRIPT is Apple, then Windows, then Android/Linux —
   the platforms in the order this audience actually uses them. Every name is
   a font that ships with its OS; none is downloaded, and CLAUDE.md's no-CDN
   rule is untouched. Where none is present the generic still ends the list.

   RUSSIAN IS NOT HERE, deliberately: Plus Jakarta Sans has no Cyrillic, but
   -apple-system, Segoe UI and Roboto all do, so ru already lands on a real
   face at the head of the existing stack. Thai is included because §15a-e
   already handle it and the cost of the extra two lines is nothing; the site
   does not currently ship a Thai locale.

   Adding real self-hosted coverage instead of borrowing the OS font is an
   open owner decision about bytes on a page with a >=90 mobile budget — see
   assets/fonts/README.md, which costs it out.                              */
:root:lang(hi) {
  --wp--preset--font-family--body: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Kohinoor Devanagari', 'Nirmala UI', 'Noto Sans Devanagari', sans-serif;
  --wp--preset--font-family--display: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Kohinoor Devanagari', 'Nirmala UI', 'Noto Sans Devanagari', sans-serif;
}
:root:lang(ar) {
  --wp--preset--font-family--body: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Geeza Pro', 'Noto Sans Arabic', sans-serif;
  --wp--preset--font-family--display: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Geeza Pro', 'Noto Sans Arabic', sans-serif;
}
:root:lang(he) {
  --wp--preset--font-family--body: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Arial Hebrew', 'Noto Sans Hebrew', sans-serif;
  --wp--preset--font-family--display: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Arial Hebrew', 'Noto Sans Hebrew', sans-serif;
}
:root:lang(zh) {
  --wp--preset--font-family--body: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Noto Sans SC', sans-serif;
  --wp--preset--font-family--display: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Noto Sans SC', sans-serif;
}
:root:lang(th) {
  --wp--preset--font-family--body: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Thonburi, 'Leelawadee UI', 'Noto Sans Thai', sans-serif;
  --wp--preset--font-family--display: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Thonburi, 'Leelawadee UI', 'Noto Sans Thai', sans-serif;
}

/* ---------------------------------------------------------------------------
   EDITOR NOTES IN SCAFFOLDS
   The three page scaffolds (patterns/scaffold-*.php) carry instructions to the
   writer. An instruction that survives into a published page is worse than no
   scaffold at all, so it is styled to be impossible to miss rather than quietly
   readable - ochre, which on this site means "unverified / recheck" and nothing
   else. If you can see this box on the front end, the page is not finished.
   --------------------------------------------------------------------------- */
.yz-editor-note {
  border: 2px dashed var(--wp--preset--color--spice);
  background: var(--wp--preset--color--shell);
  color: var(--wp--preset--color--spice);
  padding: var(--wp--preset--spacing--30, 1rem);
  border-radius: 8px;
  font-size: var(--wp--preset--font-size--small, 0.875rem);
}
.yz-editor-note::before {
  content: "REMOVE BEFORE PUBLISHING — ";
  font-weight: 800;
  letter-spacing: 0.04em;
}
