/* =========================================================================
   Wisdom Healing Center — Ghost theme
   v2.0.0 — repainted to the meditation.wisdomhealingcenter.com design system.

   WHAT CHANGED AND WHY
   The site previously reproduced the Salvation (MyThemeShop) WordPress palette:
   coral #f18469 / rose #ea6170 / cream #fffbf6 / warm brown #70534c. That palette
   is retired here. This theme now carries the palette of the sister site
   meditation.wisdomhealingcenter.com — indigo night, violet, sunset magenta, gold
   and a warm sand ground, taken originally from the "THM - Zoom Poster" artwork.

   EVERY PAIRING BELOW WAS MEASURED, NOT CHOSEN. The ratios beside each token are
   from a WCAG contrast script run over the real grounds, so a later edit cannot
   quietly break one.

   THE BAND TRAP, WHICH BIT AGAIN
   An ink has to clear AA against the DARKEST ground it actually sits on, not
   against the page. This theme has three light grounds — page sand #FDF7EE, band
   #F7EDDF and widget #F2E6D5 — and the widget panel is the darkest. Taking the
   meditation site's --gold-ink #8A6410 verbatim measured 5.04:1 on sand but only
   4.36:1 inside a sidebar widget, i.e. a fail. It was re-derived against #F2E6D5
   and is #83611A here. That is why it differs from the sister site: same system,
   one extra ground to satisfy.

   DECORATIVE COLOURS CARRY NO TEXT
   --rose #E0247B measures 4.19:1 on sand and 4.46:1 behind white, so it is used
   only for gradients, hairlines, borders and icon fills. Anything carrying text
   uses --rose-ink #B81361 (6.36:1 behind white) instead. Same rule for --gold and
   --blue, both far below AA as text.
   ========================================================================= */

/* Self-hosted display serif, copied verbatim from the meditation theme where the
   roman/italic file mapping was already proven against the fonts' own metadata.
   One variable file covers 600 and 700 per style. Do NOT re-pair these by
   guessing from the Google CSS: the two latin files there list font-weight and
   src but not font-style, and pairing them by eye renders every upright heading
   in italic. default.hbs preloads both, so font-display:swap has nothing to swap. */
@font-face{
    font-family:"Cormorant Garamond";
    font-style:normal;
    font-weight:600 700;
    font-display:swap;
    src:url("../fonts/cormorant-roman.woff2") format("woff2");
    unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
    font-family:"Cormorant Garamond";
    font-style:italic;
    font-weight:600 700;
    font-display:swap;
    src:url("../fonts/cormorant-italic.woff2") format("woff2");
    unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

:root {
    /* ---- night / ink ---------------------------------------------------- */
    --night:       #131046;   /* dark band ground.   white on it = 17.64:1     */
    --night-deep:  #0C0A31;   /* copyright bar.      white on it = 18.94:1     */
    --ink:         #1E1A4D;   /* headings.  sand 15.05 / band 13.84 / widget 13.02 */
    --ink-soft:    #4A466E;   /* body.      sand  8.24 / band  7.58 / widget  7.13 */
    --on-dark:     #DED9F5;   /* body on night      = 12.90:1                  */
    --on-dark-2:   #C4BDEA;   /* muted on night     =  9.93:1                  */

    /* ---- accents -------------------------------------------------------- */
    --violet:      #6B2FA0;   /* links.     sand  7.76 / band 7.14 / widget 6.72 */
    --violet-dp:   #4A1D73;   /* white on it = 12.25:1                         */
    --rose:        #E0247B;   /* DECORATIVE ONLY — 4.19:1 on sand              */
    --rose-ink:    #B81361;   /* white on it = 6.36:1.  sand 5.97 / widget 5.16 */
    --rose-deep:   #9C0E52;   /* white on it =  8.08:1  — button gradient stop */
    --plum:        #7E1E86;   /* white on it =  8.69:1  — button gradient stop */
    --gold:        #D9A22B;   /* DECORATIVE / on night = 7.69:1                */
    --gold-ink:    #83611A;   /* eyebrows.  widget 4.62 / band 4.91 / sand 5.34 */
    --red-ink:     #C62828;   /* tagline.   band 4.85 / sand 5.28              */
    --blue:        #2E7DD1;   /* DECORATIVE ONLY — 3.97:1 on sand              */
    --green-dp:    #2E7D52;

    /* Light counterparts of --violet and --red-ink, for the SAME lockup when it
       sits on the dark footer. Both of those are dark inks — --violet measures
       2.30:1 on the footer ground — so the footer brand and tagline must use
       these instead. Ratios are against --night-deep #0C0A31. */
    --violet-lt:   #D3C4F0;   /* footer brand name    = 11.71:1                */
    --red-lt:      #FF9E95;   /* footer tagline       =  9.59:1                */

    /* ---- grounds -------------------------------------------------------- */
    --sand:        #FDF7EE;
    --sand-2:      #F7EDDF;
    --widget-bg:   #F2E6D5;   /* DARKEST light ground — inks are derived here  */
    --white:       #ffffff;
    --line:        #EDE3D6;
    --line-dark:   rgba(222, 217, 245, .20);

    /* ---- compatibility aliases -------------------------------------------
       The v1.x rules below (and any future edit that reaches for the old
       vocabulary) keep working because the Salvation token names still resolve —
       they simply point into the new system now. New rules should prefer the
       names above. */
    --bg:          var(--sand);
    --band:        var(--sand-2);
    --widget:      var(--widget-bg);
    --coral:       var(--rose);       /* decorative accent: rules, borders     */
    --coral-ink:   var(--violet);     /* links + small text                    */
    --coral-btn:   var(--rose-ink);   /* button ground, white text             */
    --footer-dark: var(--night-deep);

    --wrap:        1080px;
    --gap:         40px;
    --r:           18px;      /* cards, matching the meditation card radius    */
    --r-sm:        10px;      /* inputs, code blocks, small chrome             */
    --radius:      var(--r-sm);

    --shadow-sm:   0 2px 10px rgba(30, 26, 77, .06);
    --shadow-md:   0 14px 40px rgba(30, 26, 77, .10);
    --shadow-lg:   0 30px 80px rgba(19, 16, 70, .28);

    --serif: "Cormorant Garamond", Georgia, "Times New Roman", serif;
    --font: 'Lato', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

    /* One easing for every motion in the theme, so nothing feels out of step. */
    --ease: cubic-bezier(.22, .61, .36, 1);
}

/* ---- reset / base ------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
    /* `clip`, NOT `hidden`. overflow-x:hidden on html/body silently makes every
       position:sticky descendant stop sticking — it cost this fleet a broken
       header on twelve sites. `clip` prevents the sideways scroll without
       creating a scroll container. */
    overflow-x: clip;
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
}

body {
    margin: 0;
    overflow-x: clip;
    background: var(--bg);
    color: var(--ink-soft);
    font: 400 16px/1.75 var(--font);
}

img, svg, video, iframe { max-width: 100%; }
img, video { height: auto; }

a { color: var(--coral-ink); text-decoration: none; }
a:hover, a:focus-visible { color: var(--rose-ink); text-decoration: underline; }

/* Headings take the display serif, exactly as the meditation site sets them —
   serif display over a sans body is the one typographic move that makes the two
   sites read as the same brand. */
h1, h2, h3, h4, h5, h6 {
    margin: 0 0 12px;
    color: var(--ink);
    font-family: var(--serif);
    font-weight: 700;
    line-height: 1.28;
    letter-spacing: .005em;
}
/* Sizes are the WordPress customiser's, converted to a fluid ramp so nothing
   has to be re-tuned per breakpoint, then nudged up: Cormorant runs optically
   smaller than Lato at the same px, so matching the old numbers exactly would
   have shrunk every heading on the site. */
h1 { font-size: clamp(25px, 1.3rem + 1.3vw, 33px); }
h2 { font-size: clamp(23px, 1.2rem + 1.0vw, 29px); }
h3 { font-size: clamp(20px, 1.1rem + 0.6vw, 25px); }
h4 { font-size: 22px; }
h5 { font-size: 20px; }
h6 { font-size: 17px; }

/* Any element that toggles `hidden` must lose to nothing. A plain
   [hidden]{display:none} is out-specified by a component's own display rule,
   which is how a "hidden" video poster stayed on screen over a playing video. */
[hidden] { display: none !important; }

.skip-link {
    position: absolute; left: -9999px; top: 0; z-index: 200;
    background: var(--white); color: var(--violet);
    padding: 10px 16px; border: 2px solid var(--violet);
    border-radius: 0 0 var(--r-sm) 0;
}
.skip-link:focus { left: 8px; top: 8px; }

:focus-visible { outline: 3px solid var(--rose-ink); outline-offset: 2px; border-radius: 4px; }

.wrap {
    width: min(100% - 2rem, var(--wrap));
    margin-inline: auto;
}

.ico { width: 1em; height: 1em; fill: currentColor; vertical-align: -0.12em; margin-right: .4em; flex: none; }

/* =========================================================================
   MOTION
   One reveal primitive, one ripple, one breathe. Everything that animates in
   this theme uses these rather than inventing its own timing.
   ========================================================================= */

/* Scroll reveal. main.js adds .is-in via IntersectionObserver; if JS never runs
   the `no-js` guard below means content is simply visible, never stuck at
   opacity 0 — a reveal that depends on JS must fail open. */
.reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity .65s var(--ease), transform .65s var(--ease);
    transition-delay: var(--reveal-delay, 0ms);
    /* Deliberately NO will-change here. main.js tags several hundred elements on
       a long Elementor page, and a promoted layer for each one costs far more
       memory than it saves; opacity and transform are compositor-friendly
       without the hint. */
}
.reveal.is-in { opacity: 1; transform: none; }
/* Until main.js has run, nothing is hidden. The class is added to <html> by the
   inline snippet in default.hbs and removed as soon as the observer is wired. */
html.no-reveal .reveal { opacity: 1; transform: none; }

/* Held for one synchronous block while main.js tags elements that have already
   painted. Without it, applying .reveal to visible content transitions it from
   opacity 1 down to 0 — a fade-OUT on load. See the comment in main.js. */
html.reveal-arming .reveal { transition: none !important; }

@keyframes whc-ripple {
    0%   { transform: scale(.94); opacity: .85; }
    70%  { transform: scale(1.3); opacity: 0; }
    100% { transform: scale(1.3); opacity: 0; }
}
@keyframes whc-breathe {
    0%, 100% { transform: scale(.9);  opacity: .75; }
    50%      { transform: scale(1.06); opacity: 1; }
}
@keyframes whc-float {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-7px); }
}

/* THE LOGO HALO IS THE BUTTON'S HALO — the same rule, not an imitation.
   A first attempt used an animated box-shadow, which needs no extra element but
   renders as a soft spreading glow. The button's ring is a 2px BORDER that
   scales out and fades, which is a visibly different thing. Matching it exactly
   means a real pseudo-element, and an <img> cannot have one — hence the wrapper
   <span> in default.hbs and site-footer.hbs.

   Every value below is copied from .btn::before: inset -6px, 2px solid,
   rgba(184,19,97,.34), whc-ripple 3.2s ease-out infinite. The only difference is
   border-radius: the button inherits a 999px pill, these inherit a circle. */
.site-brand__halo,
.footer-logo-halo {
    position: relative;
    display: inline-flex;
    flex: none;
    border-radius: 50%;
}
.site-brand__halo::before,
.footer-logo-halo::before {
    content: "";
    position: absolute;
    inset: -6px;
    border-radius: inherit;
    border: 2px solid rgba(184, 19, 97, .34);
    animation: whc-ripple 3.2s ease-out infinite;
    pointer-events: none;
}
/* The footer ring goes gold. Everything else about it stays identical to the
   button's — same 2px border, same inset, same whc-ripple at 3.2s — only the
   hue differs, so the two logos still pulse in step.
   Alpha is .55 rather than the header's .34: the magenta sits on sand where a
   third of an opacity reads clearly, while this ring sits on the indigo footer
   and needs more of itself to be seen at all. */
.footer-logo-halo::before { border-color: rgba(252, 182, 13, .55); }

/* ---- header ------------------------------------------------------------- */

/* Sticky + translucent, matching the meditation header. `clip` on html above is
   what allows this to actually stick.

   The ground is --sand at 88%, NOT --sand-2. v2.0–2.1 used the deeper sand-2 and
   the header read visibly tanner than the sister site's; this is that site's
   exact value. Nav ink stays --ink and the hover pill stays --sand-2, which is
   what gives the hover something to show against on this lighter ground. */
.site-header {
    background: rgba(253, 247, 238, .88);
    -webkit-backdrop-filter: saturate(1.4) blur(12px);
    backdrop-filter: saturate(1.4) blur(12px);
    border-bottom: 1px solid var(--line);
    position: sticky;
    top: 0;
    z-index: 50;
}
/* Four children now — brand, menu, CTA, hamburger — so the meditation header's
   layout rule applies: everything packs from the left and the MENU takes the auto
   margin, which pins the menu, CTA and hamburger as one group on the right.
   `space-between` would have spread all four apart instead. */
.site-header__inner {
    display: flex;
    align-items: center;
    gap: 14px;
    min-height: 88px;
    padding-block: 10px;
    flex-wrap: wrap;
}
.site-brand__link { display: flex; align-items: center; gap: 12px; color: inherit; }
.site-brand__link:hover { text-decoration: none; }
.site-brand__mark {
    width: 62px; height: 62px; object-fit: contain; flex: none;
    border-radius: 50%;
    transition: transform .45s var(--ease);
}
/* The hover lift stays and simply rides on top — box-shadow and transform are
   independent properties, so the halo keeps pulsing through it. */
.site-brand__link:hover .site-brand__mark,
.site-brand__link:focus-visible .site-brand__mark { transform: scale(1.07) rotate(-4deg); }
.site-brand__text { display: flex; flex-direction: column; }
/* Serif + violet, the meditation lockup. */
.site-brand__name {
    font-family: var(--serif);
    font-size: clamp(18px, 1rem + 0.6vw, 24px);
    font-weight: 700; color: var(--violet); line-height: 1.16;
}
/* Secondary BY CONSTRUCTION, not by eye: every term of the tagline's clamp is
   below the corresponding term of the name's, so it cannot outgrow the name at
   any viewport width. */
/* Floor is 12px, not 11.5px: below 12 the italic serif at 600 stops being
   comfortably legible on a phone, and the audit flags it. The maximum stays
   under the name's so the ordering still holds by construction. */
.site-brand__tagline {
    font-family: var(--serif);
    font-size: clamp(12px, 0.7rem + 0.3vw, 14px);
    font-style: italic; font-weight: 600;
    color: var(--red-ink); line-height: 1.3;
}

/* ---- navigation --------------------------------------------------------- */

/* The menu takes the auto margin, so it and everything after it sit right. */
.site-nav { display: flex; align-items: center; margin-left: auto; }

.site-nav__list { list-style: none; margin: 0; padding: 0; }
.site-nav__list { display: flex; align-items: center; gap: 2px; }

.site-nav__item { position: relative; }

/* Sentence case at 15.5px in a 10x13 pill, the sister site's menu metrics. The
   uppercase treatment came from the old Salvation header and is gone with it. */
.site-nav__item > a {
    display: inline-flex;
    align-items: center;
    position: relative;
    padding: 10px 13px;
    font-size: 15.5px;
    color: var(--ink);
    letter-spacing: .01em;
    white-space: nowrap;
    border-radius: 8px;
    transition: color .25s var(--ease), background-color .25s var(--ease);
}
/* Underline that grows from the centre on hover. Drawn on a pseudo-element so
   it cannot affect layout or wrap the menu onto a second line. */
.site-nav__item > a::after {
    content: "";
    position: absolute; left: 11px; right: 11px; bottom: 6px;
    height: 2px; border-radius: 2px;
    background: linear-gradient(90deg, var(--violet), var(--rose));
    transform: scaleX(0);
    transition: transform .3s var(--ease);
}
.site-nav__item > a:hover { color: var(--violet); background: var(--sand-2); text-decoration: none; }
.site-nav__item > a:hover::after,
.site-nav__item > a:focus-visible::after { transform: scaleX(1); }

@keyframes whc-drop {
    from { opacity: 0; transform: translateY(-8px); }
    to   { opacity: 1; transform: none; }
}

/* ---- header CTA ---------------------------------------------------------
   The sister site's compact join pill, same shape and same destination. It keeps
   its own solid ground rather than the .btn gradient: sitting on a translucent
   header it has to read as a single confident block at 15px, and it must not
   carry the .btn halo, which would pulse behind the menu on every page. */
.head-join {
    display: inline-flex; align-items: center; gap: 8px;
    flex: none;
    padding: 11px 20px;
    border-radius: 999px;
    background: var(--rose-ink);
    color: var(--white);
    font-size: 15px; font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
    box-shadow: 0 6px 18px rgba(184, 19, 97, .28);
    transition: background-color .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease);
}
.head-join:hover, .head-join:focus-visible {
    background: var(--rose-deep);
    color: var(--white); text-decoration: none;
    transform: translateY(-2px);
    box-shadow: 0 14px 30px rgba(184, 19, 97, .4);
}
.head-join svg { width: 18px; height: 18px; fill: currentColor; flex: none; }

.nav-toggle {
    display: none;
    width: 44px; height: 44px;              /* WCAG 2.5.8 target size */
    background: none; border: 1px solid var(--line); border-radius: var(--r-sm);
    cursor: pointer; padding: 10px;
    flex-direction: column; justify-content: space-between;
    transition: border-color .25s var(--ease), background-color .25s var(--ease);
}
.nav-toggle:hover { border-color: var(--violet); background: var(--sand-2); }
.nav-toggle__bar {
    display: block; height: 2px; background: var(--ink); border-radius: 2px;
    transition: transform .3s var(--ease), opacity .2s var(--ease);
    transform-origin: center;
}
/* Bars fold into an X while the drawer is open. */
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) { transform: translateY(10px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) { transform: translateY(-10px) rotate(-45deg); }

/* ---- layout ------------------------------------------------------------- */

.site-main { display: block; }

.layout { padding-block: 28px 44px; }
.layout--sidebar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: var(--gap);
    align-items: start;
}
.content-col { min-width: 0; }   /* without this a wide table blows the grid out */

.page-title { margin-bottom: 20px; }
.archive-desc { color: var(--ink-soft); margin-top: -8px; }

/* Full-width pages (the ported Elementor layouts). The Elementor container caps
   itself at 1140px, so this wrapper only needs to stop constraining it — and it
   must NOT add horizontal padding, or the full-bleed background sections inside
   would be inset from the page edges. */
.wrap-full { padding-block: 20px 0; }

/* THE TOP GAP ON EVERY ELEMENTOR PAGE, measured not guessed: the first
   .elementor-top-section carries margin-top:120px and the .elementor wrapper adds
   another 15.75px, so ~136px of empty band sat between the header and the first
   ink on all 27 pages. Both are zeroed here.
   !important is load-bearing: those margins come from the page's OWN <style>
   block, which is stored in the database and sits after this stylesheet, so an
   equal-specificity rule loses. Scoped to :first-child so spacing BETWEEN
   sections is untouched. */
.post-content--full > .elementor { margin-top: 0 !important; }
.post-content--full > .elementor > .elementor-section:first-child { margin-top: 0 !important; }
.wrap-full > .single--full > .breadcrumb { margin-bottom: 10px; }
.post-content--full { font-size: 16px; }
.post-content--full > * { margin-top: 0; }
/* The ported markup is a chain of <section>s that carry their own spacing. */
.post-content--full .elementor-section { max-width: 100%; }
/* Elementor lays out with floats/flex of its own; the theme's generic content
   rules must not fight it. Scope them off. */
.post-content--full .elementor img { margin-inline: 0; }
.post-content--full .elementor p { margin: 0; }
.post-content--full .elementor a { text-decoration: none; }

/* ---- the 27 ported Elementor pages -------------------------------------
   Their colours, heading font and button radius are rewritten AT SOURCE by
   whc-repaint-pages.mjs, because each page carries its own <style> block in the
   database and that block sits after this stylesheet in the document — on equal
   specificity it wins. What is left here is only what those blocks do NOT set,
   so these rules never have to fight and never need !important.

   Specificity is not decoration: the page blocks reach
   `.elementor-widget-heading .elementor-heading-title` at (0,2,0), so the
   `.post-content--full` prefix below is what makes these land. */

.post-content--full .elementor-widget-heading .elementor-heading-title {
    font-family: var(--serif);
    letter-spacing: .005em;
}

/* 🚨 THE DIVIDER WAS NOT CENTRED ON THE ELEMENTOR PAGES, AND justify-content WAS
   NEVER THE PROBLEM. Elementor lays its sections out with flex, so the injected
   .lotus became a FLEX ITEM: it shrink-wrapped to the ~180px width of its own SVG
   and sat at the start of the row. Its internal `justify-content: center` was
   working perfectly — on a box only as wide as the thing it was centring.
   Measured 470px left of the heading it belongs to.
   Forcing it to take the whole line is what centres it. */
.post-content--full .lotus {
    width: 100%;
    flex: 0 0 100%;
    justify-content: center;
    margin-bottom: 0;
}
/* On /workshop/ the heading under the divider carried a 60px margin-top of its
   own, so the measured gap there was 75px against 21px on the home bands. The
   page's rule is (0,4,0) and stored in the database, so !important is the only
   way a theme rule reaches it. Scoped to a heading that DIRECTLY FOLLOWS a
   divider, so headings elsewhere on the page keep their spacing. */
.post-content--full .lotus + .elementor-element .elementor-heading-title {
    margin-top: 0 !important;
}

/* Elementor page buttons take the site's primary button colour. Their own rules
   reach `.elementor-NNNN .elementor-element.elementor-element-x .elementor-button`
   — (0,4,0) — and set the navy `--e-global-color-primary` as the ground, so a
   plain theme rule cannot reach them. This is one of the few places !important is
   the honest tool: the alternative is rewriting a background declaration inside
   27 database-stored style blocks to change a colour the theme already owns. */
.post-content--full .elementor-button,
.post-content--full .elementor-button-link {
    background-color: var(--rose-ink) !important;
    background-image: linear-gradient(100deg, var(--rose-ink) 0%, var(--rose-deep) 58%, var(--plum) 100%) !important;
    border-color: transparent !important;
    color: var(--white) !important;
    fill: var(--white) !important;
    font-weight: 700;
}
.post-content--full .elementor-button:hover,
.post-content--full .elementor-button-link:hover {
    background-image: linear-gradient(100deg, var(--rose-deep) 0%, var(--plum) 100%) !important;
    color: var(--white) !important;
}

/* The page blocks set background, border, font and padding on buttons, but never
   transition, box-shadow or transform — so the meditation hover lands cleanly at
   this specificity. */
.post-content--full .elementor-button,
.post-content--full .elementor-button-link {
    transition: transform .22s var(--ease), box-shadow .22s var(--ease), filter .22s var(--ease);
    box-shadow: 0 10px 24px rgba(184, 19, 97, .24);
}
.post-content--full .elementor-button:hover,
.post-content--full .elementor-button-link:hover,
.post-content--full .elementor-button:focus-visible {
    transform: translateY(-2px);
    filter: saturate(1.08);
    box-shadow: 0 18px 38px rgba(184, 19, 97, .38);
}

/* Image widgets get the card treatment — rounded, and lifting on hover — so the
   service pages read like the rest of the site. */
.post-content--full .elementor-widget-image img {
    border-radius: 14px;
    transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}
.post-content--full .elementor-widget-image:hover img {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
}

/* 🚨 REVERTED — DO NOT REINSTATE WITHOUT CHECKING THE CARD-GRID PAGES.
   v3.3.0 added `height: 100%` to image widgets in any column that had a sibling,
   to close a 12px shelf under the photos in the two-column "postcard" rows on
   /workshop/. `:not(:only-child)` also matches the THREE-UP CARD GRIDS on
   /energy-healing/ and /home-and-business/, where every cell is a column with
   siblings — so the image stretched to fill each cell, pushed the card titles out
   from under their pictures and collapsed the sections, cutting the page off
   mid-row. It looked fine on the page it was written for and broke two others.

   The distortion fix stands on its own and is what actually mattered: the images
   were `object-fit: fill`, i.e. squashed to the box. `cover` corrects that
   WITHOUT forcing any height, so nothing reflows.

   The 12px shelf on /workshop/ is left in place. It is cosmetic, and closing it
   is not worth a rule that reaches every multi-column section on 27 pages. */
.post-content--full .elementor-widget-image img { object-fit: cover; }

/* Rounded corners on the Elementor "cards" — /energy-healing/,
   /home-and-business/ and /workshop/. The panel is painted by
   .elementor-widget-wrap (measured: #F7EDDF, radius 0, six per page), not by the
   column, which is transparent.

   Applying this to EVERY wrap is deliberate and safe: a radius on a box with no
   background, no border and no overflow clipping is invisible, so the wraps that
   are transparent are unaffected. 14px matches the radius already on the images
   inside, so the top corners agree.

   NOTE: no `overflow: hidden`. That would clip the image corners square against
   the wrap and, more importantly, is the kind of broad Elementor rule that
   collapsed the card grids two versions ago — verified on all three named pages
   plus /our-products/ and /services/ before shipping. */
.post-content--full .elementor-widget-wrap { border-radius: 14px; }

/* Tighten the picture-to-title gap inside the stacked cards. Measured at 53px:
   20px of margin-bottom on the image widget plus a 33px margin-top on the
   heading itself. Cutting the heading side brings it to ~26px.

   The ADJACENT-SIBLING selector is the scope. It only matches a heading that
   directly follows an image in the same box — i.e. the stacked cards on
   /energy-healing/ and /home-and-business/. On /workshop/ the image sits in a
   separate column beside the text, so those headings are not adjacent to an
   image and keep their spacing, which is right: there is no vertical gap to
   close there. Nothing else on the 27 pages has this shape.
   !important because the page's own rule is (0,4,0) and lives in the database. */
.post-content--full .elementor-widget-image + .elementor-widget-heading .elementor-heading-title {
    margin-top: 6px !important;
}

/* ---- Font Awesome stand-ins ---------------------------------------------
   THE PORTED PAGES ASK FOR ICONS THIS THEME NEVER LOADED. Four of the 27
   Elementor pages carry 33 <i class="fas fa-…"> elements, and Font Awesome is not
   loaded anywhere — so every one rendered as an empty tofu box. That is what the
   squares before each product name and before the phone, email and opening hours
   on /contact-us/ were.

   Supplying the whole icon font for six glyphs would cost ~75 KB and undo the
   theme's no-icon-font decision. These are masks instead: the shape comes from an
   inline data URI and the COLOUR from background-color: currentColor, so each icon
   inherits whatever the surrounding text is set to.

   ⚠ fa-react is deliberately NOT the React logo. It is used 22 times as the bullet
   before product names on /our-products/ — the original page picked it as a
   decorative dingbat, and a JavaScript framework's logo beside "Bead Bracelets" is
   plainly not what was meant. It maps to a lotus petal here. */
.post-content--full .elementor-icon-list-icon i[class*="fa-"] {
    display: inline-block;
    width: 1em; height: 1em;
    vertical-align: -0.12em;
    background-color: currentColor;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center;  mask-position: center;
    -webkit-mask-size: contain;     mask-size: contain;
}
.post-content--full .elementor-icon-list-icon i.fa-check { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z%22/%3E%3C/svg%3E"); }
.post-content--full .elementor-icon-list-icon i.fa-phone-alt { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M6.6 10.8a15.1 15.1 0 0 0 6.6 6.6l2.2-2.2a1 1 0 0 1 1-.2 11.4 11.4 0 0 0 3.6.6 1 1 0 0 1 1 1V20a1 1 0 0 1-1 1A17 17 0 0 1 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1 11.4 11.4 0 0 0 .6 3.6 1 1 0 0 1-.2 1z%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M6.6 10.8a15.1 15.1 0 0 0 6.6 6.6l2.2-2.2a1 1 0 0 1 1-.2 11.4 11.4 0 0 0 3.6.6 1 1 0 0 1 1 1V20a1 1 0 0 1-1 1A17 17 0 0 1 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1 11.4 11.4 0 0 0 .6 3.6 1 1 0 0 1-.2 1z%22/%3E%3C/svg%3E"); }
.post-content--full .elementor-icon-list-icon i.fa-envelope { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M3 5h18v2l-9 6-9-6zm0 4.2V19h18V9.2l-9 6z%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M3 5h18v2l-9 6-9-6zm0 4.2V19h18V9.2l-9 6z%22/%3E%3C/svg%3E"); }
.post-content--full .elementor-icon-list-icon i.fa-shield-alt { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 2 4 5v6c0 5 3.4 9.4 8 11 4.6-1.6 8-6 8-11V5z%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 2 4 5v6c0 5 3.4 9.4 8 11 4.6-1.6 8-6 8-11V5z%22/%3E%3C/svg%3E"); }
.post-content--full .elementor-icon-list-icon i.fa-gem { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M7 3h10l4 6-9 12L3 9z%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M7 3h10l4 6-9 12L3 9z%22/%3E%3C/svg%3E"); }
.post-content--full .elementor-icon-list-icon i.fa-react { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 4c2.9 3.1 2.9 8.4 0 11.5C9.1 12.4 9.1 7.1 12 4z%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 4c2.9 3.1 2.9 8.4 0 11.5C9.1 12.4 9.1 7.1 12 4z%22/%3E%3C/svg%3E"); }

/* =========================================================================
   OUR PRODUCTS — a real card grid
   This page is no longer a ported Elementor layout. Its body is plain markup
   using these classes, so it repaints with the rest of the theme instead of
   carrying its own <style> block in the database.
   ========================================================================= */

/* .wrap-full deliberately adds no horizontal padding, because the Elementor
   pages inside it are full-bleed and cap themselves at 1140px. This page is not
   one of those, so it has to bring its own bounds or the cards run to the edge of
   a 1440px viewport. --wrap is what the rest of the site uses. */
.prodpage {
    width: min(100% - 2rem, var(--wrap));
    margin-inline: auto;
    padding-block: 4px 8px;
}

/* 🚨 THE GENERIC CONTENT RULES REACH INTO THIS PAGE, AND ONE OF THEM MOVED
   EVERYTHING. `.post-content ul { padding-left: 1.4em }` is (0,1,1) and beats
   `.prodgrid { padding: 0 }` at (0,1,0), so every product grid carried a 21px
   left inset. That is what put each category's cards ~11px right of its centred
   heading — half of 21, since centring happens inside the padded box — and it is
   also why the paired columns wrapped: 2x150 + 22 gap needs 322px and the inset
   left only 312. Second time today the content rules have out-specified this
   page's own; the button colour was the first.
   `.prodpage ul` was the first attempt and it did NOT work: it is also (0,1,1),
   an exact tie, and `.post-content ul` sits at line ~803 while this block is at
   ~531 — on a tie the later rule wins, so the content rule kept its 21px. Class +
   class is (0,2,0) and beats it outright regardless of order, which is what this
   needs to be robust against anyone moving either block.
   Scoped to .prodpage so nothing else changes. */
.prodpage .prodgrid,
.prodpage ul,
.prodpage ol { padding-left: 0; margin: 0; }
.prodpage__title {
    margin: 0 0 14px;
    text-align: center;
    font-size: clamp(28px, 1.4rem + 1.7vw, 42px);
    color: var(--ink);
}
.prodpage__lead {
    max-width: 62ch; margin: 0 auto 8px;
    text-align: center; font-size: 16px; color: var(--ink-soft); text-wrap: pretty;
}
/* The divider inside a page BODY is centred, like the home-page bands — the
   left-aligned variant is for the inner-page templates where it sits above a
   left-aligned <h1>. .wrap-full would otherwise pull it left. */
.prodpage .lotus { justify-content: center; }
.prodpage .lotus__svg { width: clamp(150px, 24vw, 230px); }

/* Two short categories side by side. align-items:start so the shorter of the two
   does not stretch its cards to match the taller one — the whole point of the
   fixed 150px track is that a card is the same size everywhere. */
/* Flex, not equal columns. As a 2x1fr grid each category owned half the 1080px
   wrap, so two 150px cards floated in the middle of a 520px cell and the space
   between Feng Shui Products and Crystal Combos read as a large gap that was
   really just empty column. Flex sizes each category to its own cards and
   centres the pair, so the only space between them is the gap itself. */
.prodrow {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: clamp(18px, 3vw, 40px);
    align-items: start;
}
.prodrow > .prodcat { flex: 0 1 auto; }
.prodrow > .prodcat { margin-top: clamp(30px, 4vw, 48px); }

.prodcat { margin-top: clamp(30px, 4vw, 48px); }
.prodcat__title {
    margin: 0 0 18px;
    font-size: clamp(22px, 1.1rem + 1.1vw, 30px);
    color: var(--ink);
    text-align: center;
    text-wrap: balance;
}

/* auto-FIT WITH A FIXED TRACK. All three parts matter and each was arrived at by
   fixing the previous one:

   - `minmax(170px, 1fr)` made cards different sizes per category — a 2-product
     category stretched its two cards into 420px slabs beside a 6-product one at
     170px.
   - `auto-fill, 150px` fixed the size but broke the ALIGNMENT: auto-fill keeps
     the empty tracks, so `justify-content:center` centred a six-track block and
     the two real cards sat left inside it, well off the centred heading above.
   - `auto-fit, 150px` collapses the empty tracks, so centring acts on the FILLED
     cards only. And because the track is a fixed 150px rather than 1fr, there is
     nothing for auto-fit to stretch — the cards stay identical everywhere.

   150 + 22 gap fits six across the 1080px wrap, which is what Crystal Healing
   needs to stay on one line.

   And then it is NOT a grid at all. auto-fit got the cards to the same size but
   left every category's card block sitting exactly 11px right of its centred
   heading — a constant across all seven, which is half the 22px gap: the gutter
   of a collapsed track that centring still counted. Since every card is the same
   fixed width, flex-wrap does the same job with no phantom tracks and centres
   the real cards exactly. Measured after: 0px offset on all seven. */
.prodgrid {
    list-style: none; margin: 0; padding: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: clamp(14px, 2vw, 22px);
}
.prodcard { flex: 0 0 150px; }

/* 🚨 A .btn INSIDE POST CONTENT LOSES ITS OWN COLOUR.
   `.post-content a { color: var(--violet) }` is (0,2,0) and `.btn` is (0,1,0),
   so the WhatsApp button on this page rendered violet text on its magenta
   gradient — measured rgb(107,47,160), effectively illegible. The generic
   content-link rule has to be undone for buttons explicitly; it is not enough
   that .btn sets a colour. Applies to any button placed in a post or page body,
   not just this one. */
.post-content .btn,
.post-content .btn span,
.post-content--full .btn,
.post-content--full .btn span {
    color: var(--white);
    text-decoration: none;
}
/* …and on hover too. `a:hover` in the base sheet adds an underline and swaps the
   colour, which on a button drew a line under the label and tinted the SVG —
   the icon is fill:currentColor, so it followed the link colour. Pinning both
   states leaves only the button's own lift. */
.post-content .btn:hover,
.post-content .btn:hover span,
.post-content--full .btn:hover,
.post-content--full .btn:hover span,
.post-content--full .btn:focus-visible {
    color: var(--white);
    text-decoration: none;
}
.post-content--full .btn svg,
.post-content--full .btn:hover svg { fill: currentColor; }

.prodcard {
    display: flex; flex-direction: column;
    background: var(--sand-2);
    border: 1px solid var(--line);
    border-radius: var(--r);
    overflow: hidden;
    transition: transform .3s var(--ease), box-shadow .3s var(--ease), border-color .3s var(--ease);
}
.prodcard:hover {
    transform: translateY(-5px);
    box-shadow: var(--shadow-md);
    border-color: rgba(107, 47, 160, .28);
}

/* The product cut-outs are transparent PNGs of wildly different proportions — a
   flat mat next to a tall spray bottle. `contain` on a square well shows each one
   whole at its own aspect rather than cropping a bottle in half, and the white
   ground is what those cut-outs were photographed on. */
.prodcard__media {
    display: grid; place-items: center;
    aspect-ratio: 1;
    background: var(--white);
    padding: 14px;
    border-bottom: 1px solid var(--line);
}
.prodcard__media img {
    display: block;
    width: 100%; height: 100%;
    object-fit: contain;
    transition: transform .4s var(--ease);
}
.prodcard:hover .prodcard__media img { transform: scale(1.06); }

/* The two products the source artwork never pictured. A lettered disc, so the
   card still reads as a product rather than as a broken image. */
.prodcard__media--none { background: var(--widget-bg); }
.prodcard__media--none span {
    display: grid; place-items: center;
    width: 64px; height: 64px;
    border-radius: 50%;
    background: linear-gradient(140deg, var(--violet), var(--violet-dp));
    color: var(--white);
    font-family: var(--serif); font-size: 30px; font-weight: 700;
}

/* EVERY CARD THE SAME HEIGHT, and it is the label that decides it. Names wrap to
   one, two or three lines depending on length — "Sandalwood Incense" takes three
   where "Mists" takes one — which gave four different card heights in the same
   row (measured 200/195/214/209) and made a row of three categories read as
   ragged rather than as one row.

   The label therefore reserves three lines whatever it holds, and centres its
   text in that box. Reserving space costs a little height on short names; the
   alternative, letting each card size to its own label, is the ragged edge this
   replaces. align-items:stretch on the grid then makes the media wells line up
   as well. */
.prodcard__name {
    padding: 12px 10px;
    min-height: calc(3 * 1.3em + 24px);
    display: flex; align-items: center; justify-content: center;
    text-align: center;
    font-size: 15px; font-weight: 700; line-height: 1.3;
    color: var(--ink);
}
.prodgrid { align-items: stretch; }

/* The bottom margin carries the whole gap down to the footer — .prodpage's own
   padding-block is only 8px, so without this the button sat almost against the
   dark band. */
.prodpage__cta { margin: clamp(34px, 5vw, 54px) 0 clamp(48px, 7vw, 90px); text-align: center; }

@media (max-width: 620px) {
    /* Two per row on a phone — a single column turns 22 products into a very
       long scroll of full-width squares. */
    /* Back to two flexible columns on a phone — fixed 160px tracks would leave a
       dead gutter at 320px. */
    /* Two per row on a phone. calc(50% - 6px) is the flex equivalent of two equal
       columns with the 12px gap between them. */
    .prodgrid { gap: 12px; }
    .prodcard { flex: 0 0 calc(50% - 6px); }
    /* Paired categories unstack on a phone — two half-columns of 2 cards each
       would be four very narrow cards across a 320px screen. */
    .prodrow { flex-direction: column; gap: 0; }
    .prodcard__name { font-size: 13.5px; padding: 10px 6px; }
    .prodcard__media { padding: 10px; }
}

/* Icon lists are the closest thing these pages have to a card body. */
.post-content--full .elementor-widget-icon-list .elementor-icon-list-item {
    transition: transform .25s var(--ease);
}
.post-content--full .elementor-widget-icon-list .elementor-icon-list-item:hover {
    transform: translateX(4px);
}

/* =========================================================================
   CARDS — the meditation card form
   Sand ground, hairline border, 18px radius, and a lift on hover. Applied to
   every card-like surface on the site so archives, services, reviews and the
   sidebar all read as one family.
   ========================================================================= */

.quote-card {
    display: grid;
    grid-template-columns: 90px minmax(0, 1fr);
    align-items: start;
    margin-bottom: 26px;
}
.quote-card__thumb {
    grid-column: 1 / 2; grid-row: 1;
    width: 120px; height: 120px;
    margin-top: 14px;
    z-index: 2;
    display: block;
    border-radius: 16px;
    box-shadow: 0 8px 22px rgba(30, 26, 77, .16);
    overflow: hidden;
    background: var(--widget-bg);
    transition: transform .35s var(--ease), box-shadow .35s var(--ease);
}
.quote-card__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.quote-card__thumb--none {
    display: block; width: 100%; height: 100%;
    background: var(--widget-bg);
    opacity: .5;
}
.quote-card__body {
    grid-column: 2 / 3; grid-row: 1;
    background: var(--sand-2);
    border: 1px solid var(--line);
    border-radius: var(--r);
    padding: 22px 26px 22px 50px;
    min-height: 148px;
    transition: transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s var(--ease);
}
.quote-card:hover .quote-card__body { transform: translateY(-5px); box-shadow: var(--shadow-md); border-color: rgba(107, 47, 160, .28); }
.quote-card:hover .quote-card__thumb { transform: translateY(-5px) scale(1.03); box-shadow: 0 14px 30px rgba(30, 26, 77, .24); }
.quote-card__title { font-size: clamp(20px, 1.1rem + 0.5vw, 25px); margin-bottom: 8px; }
.quote-card__title a { color: var(--ink); transition: color .25s var(--ease); }
.quote-card__title a:hover { color: var(--violet); text-decoration: none; }
.quote-card__excerpt { margin: 0 0 6px; font-size: 14px; color: var(--ink-soft); }
/* min-height clears the WCAG 2.5.8 24px target minimum; the v1 rule left this
   link 23px tall. */
.quote-card__more {
    font-size: 13px; color: var(--rose-ink); font-weight: 700; float: right;
    display: inline-flex; align-items: center; min-height: 24px;
    transition: transform .25s var(--ease), color .25s var(--ease);
}
.quote-card__more:hover { transform: translateX(4px); color: var(--violet); }

/* ---- single ------------------------------------------------------------- */

.breadcrumb { font-size: 13px; color: var(--ink-soft); margin-bottom: 14px; }
.breadcrumb a { color: var(--ink); }
.breadcrumb__sep { margin-inline: 6px; color: var(--rose); }

.single-title { font-size: clamp(25px, 1.35rem + 1.1vw, 34px); margin-bottom: 10px; }

.single-meta {
    display: flex; flex-wrap: wrap; gap: 6px 22px;
    font-size: 12px; color: var(--ink-soft);
    padding-bottom: 14px; border-bottom: 1px solid var(--line); margin-bottom: 18px;
}
.single-meta__item { display: inline-flex; align-items: center; }
.single-meta a { color: var(--violet); }

.single-feature { margin: 0 0 18px; }
.single-feature img { display: block; width: 100%; border-radius: var(--r); }
.single-feature figcaption { font-size: 13px; color: var(--ink-soft); padding-top: 6px; }

/* ---- post content ------------------------------------------------------- */

.post-content { font-size: 15px; }

/* Reset with `> *` ONLY. A blanket `.post-content p { margin: 0 }` out-specifies
   `> * + *` and silently deletes every paragraph gap. */
.post-content > * { margin-block: 0; }
.post-content > * + * { margin-top: 1.05em; }

.post-content a { color: var(--violet); text-decoration: underline; text-underline-offset: 2px; }
.post-content a:hover { color: var(--rose-ink); }

/* The migrated WordPress bodies wrap whole paragraphs in <strong> as a styling
   habit, not emphasis. Colouring it would turn entire posts a different colour. */
.post-content strong, .post-content b { color: inherit; font-weight: 700; }

.post-content img { display: block; height: auto; margin-inline: auto; border-radius: var(--r); }
.post-content figure { margin: 0; }
.post-content figcaption { font-size: 13px; color: var(--ink-soft); text-align: center; padding-top: 6px; }

.post-content ul, .post-content ol { padding-left: 1.4em; }
.post-content li + li { margin-top: .35em; }

.post-content blockquote {
    margin: 0; padding: 8px 0 8px 20px;
    border-left: 4px solid var(--violet);
    color: var(--ink); font-style: italic;
}

.post-content h2, .post-content h3, .post-content h4 { margin-top: 1.5em; }

/* Wide content must scroll inside its own box — never the page. */
.post-content table { width: 100%; border-collapse: collapse; font-size: 14px; }
.post-content table th, .post-content table td { border: 1px solid var(--line); padding: 8px 10px; text-align: left; }
.post-content table th { background: var(--sand-2); color: var(--ink); }
.post-content > table,
.post-content .table-scroll { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.post-content pre {
    overflow-x: auto; padding: 14px 16px; background: var(--widget-bg);
    border-radius: var(--r-sm); font-size: 13px;
}
.post-content iframe { display: block; width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0; border-radius: var(--r-sm); }

/* Ghost card widths — gscan errors if these are unstyled. */
.kg-width-wide  { width: min(100%, 900px); margin-inline: auto; }
.kg-width-full  { width: 100%; }
.kg-image       { display: block; margin-inline: auto; height: auto; }
.kg-card        { margin-block: 1.2em; }
.kg-embed-card  { display: flex; flex-direction: column; align-items: center; }
.kg-gallery-container { display: flex; flex-direction: column; }
.kg-gallery-row { display: flex; flex-direction: row; justify-content: center; gap: 6px; }
.kg-gallery-image img { display: block; width: 100%; height: auto; }
.kg-bookmark-card { border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; background: var(--sand-2); }
.kg-bookmark-container { display: flex; color: inherit; text-decoration: none; }
.kg-bookmark-content { padding: 16px; flex: 1 1 auto; min-width: 0; }
.kg-bookmark-thumbnail img { width: 100%; height: 100%; object-fit: cover; }

.tag-list { font-size: 13px; color: var(--ink-soft); margin-top: 26px; }
.tag-list a { color: var(--violet); }

/* =========================================================================
   BUTTONS — the meditation button form
   Pill, gradient, lift on hover, and a slow halo on the primary CTA. The halo is
   drawn on ::before OUTSIDE the button box so it can never alter layout.
   ========================================================================= */

.btn {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center; gap: 10px;
    min-height: 44px; padding: 13px 26px;
    background: linear-gradient(100deg, var(--rose-ink) 0%, var(--rose-deep) 58%, var(--plum) 100%);
    color: var(--white);
    border-radius: 999px; font-weight: 700; font-size: 15px; line-height: 1.2;
    text-decoration: none;
    box-shadow: 0 12px 28px rgba(184, 19, 97, .30);
    transition: transform .22s var(--ease), box-shadow .22s var(--ease), filter .22s var(--ease);
}
.btn::before {
    content: "";
    position: absolute; inset: -6px;
    border-radius: inherit;
    border: 2px solid rgba(184, 19, 97, .34);
    animation: whc-ripple 3.2s ease-out infinite;
    pointer-events: none;
}
.btn:hover, .btn:focus-visible {
    color: var(--white); text-decoration: none;
    transform: translateY(-2px);
    filter: saturate(1.08);
    box-shadow: 0 20px 42px rgba(184, 19, 97, .44);
}
.btn svg { width: 20px; height: 20px; fill: currentColor; flex: none; }

/* WhatsApp keeps its own green — it is a platform mark, not a brand colour, and
   white on it measures 5.02:1. It gets the form but not the palette. */
.btn--wa { background: linear-gradient(100deg, #1a7f4b 0%, #14653b 100%); box-shadow: 0 12px 28px rgba(26, 127, 75, .30); }
.btn--wa::before { border-color: rgba(26, 127, 75, .34); }
.btn--wa:hover { box-shadow: 0 20px 42px rgba(26, 127, 75, .44); }
/* Single line at the shared button size. This used to be a hard-wrapped
   "BOOK YOUR / SESSION" at 13px, which made the one CTA on the Twin Hearts band
   the smallest button on the page. */
.btn--wa span { text-align: center; font-size: 15px; white-space: nowrap; }

.booking-cta { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.booking-cta__note { font-size: 13px; color: var(--ink-soft); }

/* ---- related posts ------------------------------------------------------ */

.related { margin-top: 34px; }
.section-title { font-family: var(--serif); font-size: 25px; font-weight: 700; margin-bottom: 14px; color: var(--ink); }
.related__grid {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    background: var(--widget-bg); padding: 14px;
    border: 1px solid var(--line); border-radius: var(--r);
}
.related__item {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 10px; color: var(--ink); font-size: 14px;
    border-bottom: 1px solid var(--line);
    border-radius: 12px;
    transition: color .25s var(--ease), background-color .25s var(--ease), transform .25s var(--ease);
}
.related__item:hover { color: var(--violet); background: var(--white); transform: translateX(4px); text-decoration: none; }
.related__thumb { width: 46px; height: 46px; border-radius: 50%; object-fit: cover; flex: none; }
.related__thumb--none { background: var(--sand-2); display: block; }

/* ---- pagination --------------------------------------------------------- */

.pagination { margin-top: 30px; }
.pagination__list { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; margin: 0; padding: 0; }
.pg {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 36px; height: 36px; padding-inline: 8px;
    border: 1px solid var(--rose); border-radius: 50%;
    color: var(--violet); font-size: 14px; font-weight: 700; text-decoration: none;
    transition: background-color .25s var(--ease), color .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease);
}
.pg:hover, .pg:focus-visible {
    background: var(--rose-ink); color: var(--white); border-color: var(--rose-ink);
    text-decoration: none; transform: translateY(-2px);
    box-shadow: 0 8px 18px rgba(184, 19, 97, .32);
}
.pg--current {
    background: linear-gradient(140deg, var(--rose-ink), var(--plum));
    color: var(--white); border-color: transparent;
    box-shadow: 0 8px 18px rgba(184, 19, 97, .28);
}
.pg--gap { border-color: transparent; pointer-events: none; }
.pagination__count { font-size: 13px; color: var(--ink-soft); margin: 10px 0 0; }

/* ---- sidebar ------------------------------------------------------------ */

.sidebar { min-width: 0; }
.widget { margin-bottom: 28px; }
.widget-title {
    font-family: var(--serif); font-size: 25px; font-weight: 700;
    margin-bottom: 12px; color: var(--ink);
    display: flex; align-items: center; gap: 10px;
}
/* Hairline that fills the space beside the title, the meditation rule treatment. */
.widget-title::after {
    content: ""; flex: 1 1 auto; height: 1px;
    background: linear-gradient(90deg, rgba(224, 36, 123, .55), rgba(224, 36, 123, 0));
}

.widget--card {
    background: var(--sand-2); padding: 24px 20px; text-align: center;
    border: 1px solid var(--line); border-radius: var(--r);
    transition: transform .35s var(--ease), box-shadow .35s var(--ease);
}
.widget--card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.widget__logo {
    width: 90px; height: 90px; object-fit: contain; margin-inline: auto; display: block;
    border-radius: 50%;
    animation: whc-float 6s ease-in-out infinite;
}
.widget__brand { color: var(--violet); font-size: 23px; margin: 8px 0; }
.widget__pitch { font-size: 14px; font-style: italic; color: var(--ink); margin: 0 0 14px; }

.contact-row, .social-grid, .footer-social {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-wrap: wrap; gap: 10px; justify-content: center;
}
.social-grid { justify-content: flex-start; }

.cr, .sg, .fs {
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; border-radius: 50%;
    color: var(--white);
    transition: transform .28s var(--ease), box-shadow .28s var(--ease), filter .28s var(--ease);
}
/* No colour change on hover — each of these carries a platform's own brand colour
   and darkening it read as the icon dimming rather than responding. The lift and
   the shadow carry the whole affordance now.

   `color` is re-stated here and it is NOT redundant. The global `a:hover` sets
   `color: var(--rose-ink)`, and these glyphs are `fill: currentColor` — so
   dropping the old brightness filter was not enough on its own: the white icon
   still turned magenta on hover, because this rule never set `color` and the
   generic link rule supplied it. Pinning it to white is what actually removes
   the colour change. */
.cr:hover, .sg:hover, .fs:hover {
    color: var(--white);
    text-decoration: none;
    transform: translateY(-4px) scale(1.1);
    box-shadow: 0 10px 20px rgba(30, 26, 77, .28);
}
.cr svg, .sg svg, .fs svg { width: 20px; height: 20px; fill: currentColor; }

/* Platform marks stay their own colours — these identify third parties and are
   not ours to repaint. */
.cr--call, .fs--call { background: #e8452c; }
.cr--wa,   .fs--wa   { background: #17a24a; }
.cr--ig,   .sg--igg, .fs--ig { background: linear-gradient(45deg, #f09433, #dc2743 45%, #bc1888); }
.cr--msg             { background: #7646ff; }
.cr--tree, .fs--tree { background: #0f9d58; }
.sg--mail            { background: #e8762c; }
.sg--fb,   .fs--fb   { background: #1877f2; }
.sg--pin,  .fs--pin  { background: #d61b28; }
.sg--tw              { background: #111111; }
.fs--x               { background: #111111; }
.sg--yt              { background: #d92b23; }
.sg--tu              { background: #35465c; }
.sg--fl              { background: #ff0084; }

/* ---- Facebook page card ------------------------------------------------
   Geometry ported verbatim from wisdomquotesandstories.com, the fleet master.

   THE COLOURS ARE WQS'S LITERAL HEXES, NOT THIS SITE'S TOKENS, and that is
   deliberate — the same call the other eleven sites took when asked to match
   WQS. Facebook's own Follow button is blue; putting a magenta one beside a
   Facebook timeline would not look like the thing it is imitating. Everything
   else on this page keeps the brand.
       #022A5C  WQS navy-true  — the Page name
       #0072A1  WQS ocean-ink  — Follow fill, outline border, share icon
                                 (5.36:1 on white) */
.fb-card {
    border: 1px solid rgba(2, 42, 92, .14);
    border-radius: var(--r);
    background: var(--white);
    overflow: hidden;
}
/* 45% would cut through the face; the wordmark sits in the upper third of this
   particular banner, so the crop band is per-image and was chosen by rendering
   it at the real box, not copied from another site. */
.fb-card-cover {
    display: block; width: 100%; height: 100px;
    object-fit: cover; object-position: center 34%;
    background: var(--widget-bg);
}
.fb-card-head {
    display: flex; align-items: center; gap: .6rem;
    padding: .5rem .7rem;
    border-bottom: 1px solid rgba(2, 42, 92, .10);
}
.fb-card-avatar {
    flex: 0 0 auto; width: 42px; height: 42px;
    border-radius: 50%; background: var(--widget-bg);
}
.fb-card-text { min-width: 0; }
.fb-card-name {
    display: flex; align-items: center;
    min-height: 24px;                      /* WCAG 2.5.8 — measured 162x19 */
    font-weight: 700; font-size: .95rem; line-height: 1.25;
    color: #022A5C; text-decoration: none;
}
.fb-card-name:hover { color: #0072A1; text-decoration: underline; }
.fb-card-followers { margin: .15rem 0 0; font-size: .83rem; color: var(--ink-soft); }
.fb-card-actions {
    display: flex; gap: .5rem;
    padding: .45rem .7rem;
    background: var(--widget-bg);
    border-bottom: 1px solid rgba(2, 42, 92, .10);
}
.fb-card-btn {
    flex: 1 1 0;
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 34px; padding: 0 .8rem;
    border: 1px solid #0072A1; border-radius: 6px;
    background: var(--white); color: #0072A1;
    font-size: .85rem; font-weight: 600; text-decoration: none;
    transition: background-color .2s var(--ease), color .2s var(--ease);
}
.fb-card-btn:hover { background: #0072A1; color: var(--white); text-decoration: none; }
.fb-card-icon {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; min-height: 34px;
    border: 1px solid #0072A1; border-radius: 6px;
    background: var(--white); color: #0072A1; text-decoration: none;
    transition: background-color .2s var(--ease), color .2s var(--ease);
}
.fb-card-icon:hover { background: #0072A1; color: var(--white); }
.fb-card-icon svg { display: block; }
.fb-card-btn-primary { background: #0072A1; color: var(--white); }
.fb-card-btn-primary:hover { background: #022A5C; border-color: #022A5C; }

/* The clip is what makes the timeline usable. Facebook renders a 130px broken
   header above the feed; the frame is pulled up by exactly that so only the
   150px of real feed shows. requested 280 − visible 150 = 130. */
.fb-embed-clip { height: 150px; overflow: hidden; line-height: 0; background: var(--widget-bg); }
.fb-embed-frame {
    display: block; border: 0;
    width: 400px; height: 280px; max-width: 100%;
    margin-top: -130px;
}

.jump-select {
    width: 100%; padding: 11px 14px; font: inherit; font-size: 14px;
    color: var(--ink); background: var(--white);
    border: 1px solid var(--line); border-radius: var(--r-sm);
    min-height: 44px;
    transition: border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.jump-select:hover, .jump-select:focus {
    border-color: var(--violet);
    box-shadow: 0 0 0 3px rgba(107, 47, 160, .14);
}

/* ---- home bands --------------------------------------------------------- */

.band { padding-block: 56px; position: relative; }
.band--services { background: var(--bg); }

/* No band tint anywhere on the front page — every band sits on the plain page
   ground. Twin Hearts and Our clients went through three treatments before this
   one (night gradient in v2.0.x, then a sand-2 rose/blue wash) and both are
   removed by request; the page now reads as one continuous surface.

   Consequence worth knowing: --bg is the LIGHTEST ground, so every ink on these
   two bands gained contrast rather than losing it — nothing needed re-measuring
   in the loosening direction. */
.band--twin,
.band--clients {
    background: var(--bg);
    overflow: hidden;
}
.band--pills    { background: var(--bg); }
.band--reviews  { background: var(--bg); }

.band__title {
    text-align: center; font-size: clamp(27px, 1.2rem + 2vw, 42px);
    margin-bottom: 26px; text-wrap: balance;
}
.band__lead  { text-align: center; max-width: 780px; margin: -12px auto 24px; font-size: 15px; color: var(--ink-soft); text-wrap: pretty; }

/* ---- ornamental lotus divider ------------------------------------------- */

/* margin-bottom is only part of the gap: the SVG's viewBox leaves ~9px of empty
   canvas below the water line, so 12px of margin read as 21px on screen. 2px
   brings the measured ink-to-heading distance to ~11px. The dead space is not
   cropped out of the viewBox because eleven of these are already stored in the
   database with the old one, and they would then disagree with the template. */
.lotus { display: flex; justify-content: center; margin-bottom: 2px; }
.lotus__svg {
    display: block;
    width: clamp(150px, 24vw, 230px);
    height: auto;
    overflow: visible;
}
/* Colours live here, not in the markup, so a repaint of the tokens carries the
   divider with it. */
.lotus__line {
    fill: none;
    stroke: var(--rose);
    stroke-width: 1.4;
    stroke-linecap: round;
    opacity: .85;
}
.lotus__dot { fill: var(--rose); }
.lotus__dot--mid { opacity: .55; }
.lotus__dot--far { opacity: .3; }
.lotus__bloom { fill: var(--violet); }
.lotus__base {
    fill: none;
    stroke: var(--gold-ink);
    stroke-width: 1.6;
    stroke-linecap: round;
    opacity: .7;
}
/* The bloom breathes, very slowly, about its own base — the same gesture the
   sister site's mark uses. transform-box/origin are both required: without
   transform-box the percentage origin resolves against the viewport, not the
   element, and the flower drifts off across the page. */
.lotus__bloom {
    transform-box: fill-box;
    transform-origin: 50% 100%;
    animation: whc-breathe-soft 7s ease-in-out infinite;
}
@keyframes whc-breathe-soft {
    0%, 100% { transform: scale(.94); }
    50%      { transform: scale(1.03); }
}

/* CENTRED EVERYWHERE, by request — including the inner-page templates, where it
   sits above a left-aligned <h1>. It was previously pulled left there to line up
   with the title's left edge; centring is the explicit instruction, so the
   divider now reads as a mark over the section rather than as part of the
   heading's own block. Only the size still differs on inner pages. */
.layout .lotus,
.wrap-full .lotus { justify-content: center; margin-bottom: 8px; }
.layout .lotus__svg,
.wrap-full .lotus__svg { width: clamp(130px, 18vw, 180px); }

/* ---- service cards ---- */

/* Row gap carries the label's overhang. The pill straddles the bottom edge, so
   half its height (~25px) hangs below the card; at the old flat 24px gap the
   overhang from one row would have touched the card beneath it. */
.service-cards {
    list-style: none; margin: 0 0 34px; padding: 0;
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: 24px; row-gap: 62px;
}
/* The image COVERS the card, edge to edge. There used to be `padding-bottom:26px`
   here, inherited from the WordPress layout, which left a 27px strip of bare card
   ground under every photo with the label hanging half over it. The label is
   absolutely positioned, so it never needed that space reserved — dropping the
   padding makes the card exactly as tall as the picture and the label float on it. */
/* overflow is VISIBLE here — the label deliberately hangs outside the card, so
   the card cannot be the clipping box. The rounded mask moves to
   .service-card__media, which still clips the image and its hover zoom. */
.service-card a {
    display: block; position: relative;
    border-radius: var(--r); overflow: visible;
    background: var(--sand-2); border: 1px solid var(--line);
    line-height: 0;                 /* kills the inline-image descender gap */
    transition: transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s var(--ease);
}
.service-card a:hover {
    transform: translateY(-6px);
    box-shadow: var(--shadow-md);
    border-color: rgba(107, 47, 160, .3);
    text-decoration: none;
}
.service-card__media {
    display: block;
    border-radius: var(--r);
    overflow: hidden;
}
.service-card img {
    display: block; width: 100%; aspect-ratio: 1; object-fit: cover;
    transition: transform .5s var(--ease);
}
.service-card a:hover img { transform: scale(1.06); }
/* Straddles the card's bottom edge — half inside, half outside. `bottom: 0` puts
   the pill's lower edge on the card's, and translateY(50%) then pushes exactly
   half of its own height below, whatever that height turns out to be. Anchoring
   to a percentage of the element rather than a fixed px keeps it centred on the
   edge when the type wraps on a narrow card.
   Padding and type match .head-join so the two read as one button family. */
.service-card__label {
    position: absolute; left: 8%; right: 8%; bottom: 0;
    transform: translateY(50%);
    background: var(--white); color: var(--ink);
    text-align: center; padding: 13px 18px; font-size: 15px; font-weight: 700;
    line-height: 1.2;
    border-radius: 999px;
    box-shadow: 0 8px 22px rgba(30, 26, 77, .22);
    transition: color .25s var(--ease), background-color .25s var(--ease), box-shadow .25s var(--ease);
}
.service-card a:hover .service-card__label {
    color: var(--white);
    background: linear-gradient(100deg, var(--rose-ink), var(--plum));
    box-shadow: 0 10px 24px rgba(184, 19, 97, .34);
}

/* ---- twin hearts band (dark) ---- */

/* Art column trimmed 460 -> 390: with the circular mask gone the full artwork
   shows, and at 460 it sat visibly larger than the copy beside it. */
.twin { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 390px); gap: 40px; align-items: center; }
/* LEFT-ALIGNED on desktop, by request. It was briefly centred so the divider
   would sit over the kicker line; left is the wanted look, so the DIVIDER moves
   to the left edge with the text instead. That keeps the two connected — a
   centred ornament above left-aligned copy sits 146px away from the line it
   belongs to, which is what centring the block was solving.
   Below 900px the band stacks and the whole thing centres; that rule lives in
   the responsive block and still applies. */
.twin__copy { text-align: left; }
.twin__copy .twin__lead { margin-inline: 0; }
.twin__copy .twin__facts li { justify-content: flex-start; }
.twin__copy .btn { margin-inline: 0; }
.twin .lotus { justify-content: flex-start; }
.twin .lotus__svg { width: clamp(150px, 22vw, 210px); }
/* All four measured against --sand-2, the band's ground: gold-ink 4.91:1,
   ink 13.84:1, ink-soft 7.58:1, violet 7.14:1. --gold is NOT used here — it is
   2.15:1 on sand and is a dark-ground colour only. */
.twin__kicker { margin: 0; font-size: clamp(16px, 1rem + 0.3vw, 19px); font-weight: 700; color: var(--gold-ink); letter-spacing: .04em; text-transform: uppercase; }
.twin__title  { font-size: clamp(30px, 1.4rem + 2.4vw, 50px); margin: 2px 0 12px; color: var(--ink); }
.twin__lead   { margin: 0 0 16px; font-size: clamp(16px, 1rem + 0.3vw, 19px); max-width: 46ch; color: var(--ink-soft); }
.twin__facts  { list-style: none; margin: 0 0 20px; padding: 0; font-size: 15px; color: var(--ink); }
.twin__facts li { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.twin__facts .ico { width: 18px; height: 18px; color: var(--violet); }
/* NO border-radius. The artwork is already a circular composition, but it also
   carries brown blobs and two thin gold rings that run out to the edge of the
   canvas — a 50% radius clipped every one of them off at the corners, which is
   why the image looked cut about. It is a transparent cut-out, so it needs no
   mask of ours; drop-shadow follows the alpha and traces the real silhouette. */
.twin__art img {
    display: block; width: 100%; height: auto;
    animation: whc-float 8s ease-in-out infinite;
    filter: drop-shadow(0 18px 40px rgba(224, 36, 123, .24));
}

/* ---- pills ---- */

/* EVERY PILL IS THE SAME SIZE, and that takes three rules, not one.
   `justify-items: center` (used until v2.1.1) shrink-wraps each <li> to its own
   text, so `width:100%` on the pill inside resolved against a box that was
   already only as wide as the label — measured 137px for "Space Cleansing"
   against 174px for "Psychological Ailment". Stretching the item is what makes
   `max-width` the thing that decides the width, and therefore makes them equal.
     justify-items: stretch  -> the <li> fills its column
     grid-auto-rows: 1fr     -> every ROW is as tall as the tallest row, so a
                                label that wraps to two lines cannot make one
                                pill taller than the rest
     li { display: grid }    -> lets the <a> fill the cell it was given */
.pill-grid {
    list-style: none; margin: 0 0 34px; padding: 0;
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 16px 40px;
    justify-items: stretch;
    grid-auto-rows: 1fr;
}
.pill-grid > li { display: grid; }
.pill {
    display: flex; align-items: center; justify-content: center;
    width: 100%; max-width: 250px; margin-inline: auto;
    height: 100%; min-height: 48px; padding: 12px 18px;
    background: linear-gradient(100deg, var(--violet-dp) 0%, var(--violet) 100%);
    color: var(--white);
    border-radius: 999px;
    font-size: 14px; font-weight: 700; text-align: center; line-height: 1.25;
    box-shadow: 0 10px 24px rgba(74, 29, 115, .24);
    transition: transform .25s var(--ease), box-shadow .25s var(--ease), background .3s var(--ease);
}
.pill:hover, .pill:focus-visible {
    background: linear-gradient(100deg, var(--rose-ink) 0%, var(--plum) 100%);
    color: var(--white); text-decoration: none;
    transform: translateY(-4px);
    box-shadow: 0 18px 36px rgba(184, 19, 97, .36);
}

/* Breathing room between the two pill blocks inside the same band, so "Healing
   for Physical…" and "Home & Business" read as two sections rather than one long
   list. Scoped to a pill-grid FOLLOWED BY a lotus, which is exactly the seam
   between them — a plain bottom margin on .pill-grid would also push the band's
   own closing edge out. */
.band--pills .pill-grid + .lotus { margin-top: clamp(38px, 5.5vw, 70px); }

.worldmap img { display: block; width: 100%; height: auto; }

/* ---- reviews ---- */

/* ---- reviews are a slider at EVERY width -------------------------------
   It used to be a 3-up grid on desktop that only became a slider below 620px,
   with the extra reviews hidden by `nth-child(n+4){display:none}`. Now all six
   ride the same rail everywhere: three visible at a time on desktop, one on a
   phone, advancing on a timer.

   Still CSS-first — flex plus scroll-snap. With JavaScript off it is an ordinary
   horizontal scroller holding every review, not a dead carousel stuck on card
   one. main.js only adds the automatic advance. */
.reviews {
    display: flex;
    gap: 20px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    align-items: stretch;
    padding-bottom: 4px;
}
.reviews::-webkit-scrollbar { display: none; }

/* Arrow shell. main.js wraps the rail in this and appends the two buttons, so
   none of it exists without JavaScript — see the note there. */
.rev-shell { position: relative; }
.rev-nav {
    position: absolute; top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    display: grid; place-items: center;
    width: 44px; height: 44px;          /* WCAG 2.5.8 target size */
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 50%;
    background: var(--white);
    color: var(--violet);
    cursor: pointer;
    box-shadow: 0 6px 18px rgba(30, 26, 77, .16);
    transition: background-color .25s var(--ease), color .25s var(--ease),
                border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.rev-nav svg { width: 20px; height: 20px; }
.rev-nav:hover, .rev-nav:focus-visible {
    background: linear-gradient(140deg, var(--rose-ink), var(--plum));
    color: var(--white);
    border-color: transparent;
    box-shadow: 0 12px 26px rgba(184, 19, 97, .34);
}
/* The arrows get their OWN GUTTER rather than floating over the rail. At -22px
   they sat 22px on top of the first card and clipped its opening words — an
   overlay control that covers the content it navigates is worse than no control.
   Padding the shell insets the rail instead, so the buttons always land in empty
   space at either end. */
.rev-shell { padding-inline: 56px; }
.rev-nav--prev { left: 0; }
.rev-nav--next { right: 0; }
.reviews .review {
    flex: 0 0 calc((100% - 40px) / 3);   /* three across, minus the two gaps */
    scroll-snap-align: start;
}
.review {
    background: var(--sand-2); padding: 22px; font-size: 14px;
    border: 1px solid var(--line); border-radius: var(--r);
    transition: transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s var(--ease);
}
.review:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); border-color: rgba(107, 47, 160, .28); }
.review__head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.review__avatar {
    width: 38px; height: 38px; border-radius: 50%; flex: none;
    background: linear-gradient(140deg, var(--violet), var(--violet-dp));
    color: var(--white);
    display: inline-flex; align-items: center; justify-content: center;
    font-weight: 700; text-transform: uppercase;
}
/* The two reviewers who actually uploaded a picture. `object-fit: cover` matters:
   these come back square from Google but a future one may not, and a squashed
   face is worse than a crop. The gradient above still shows for the initial
   discs, which is the same treatment Google gives those reviewers. */
.review__avatar--img {
    object-fit: cover;
    background: var(--widget-bg);
    box-shadow: 0 0 0 1px rgba(107, 47, 160, .22);
}


.review__who { display: flex; flex-direction: column; line-height: 1.25; }
.review__who strong { color: var(--ink); }
.review__when { font-size: 12px; color: var(--ink-soft); }
.review__stars { margin: 0 0 6px; color: #b07d12; letter-spacing: 2px; }
.review__text { margin: 0; color: var(--ink); }

/* ---- footer ------------------------------------------------------------- */

/* Deep indigo, the meditation site's .site-foot ground. EVERY ink in here had to
   change with it: the light-ground colours this footer used before are unreadable
   on #0C0A31 — --violet measures 2.30:1 there, so the brand name and every link
   move to the light counterparts (--violet-lt 11.71:1, --on-dark 13.91:1,
   --red-lt 9.59:1, white 19.03:1). Hairlines move to --line-dark for the same
   reason. */
/* The sister site's footer is NOT a flat dark block — that is what v2.0–2.1 had
   here and why it read heavier than the original. It is the night GRADIENT with
   two soft radial glows over it (rose top-left, blue bottom-right), with only the
   thin copyright strip at the very bottom left flat. Reproduced exactly.

   The glows are drawn on ::before behind the content, so `isolation` and a
   negative z-index keep them off the links. Contrast was already measured against
   #1B1560 — the LIGHTEST stop of this gradient, i.e. the worst case — which is
   why --violet-lt and --red-lt were chosen against that value and not against the
   flat ground: 9.77:1 and 8.00:1 hold across the whole ramp. */
.site-footer {
    position: relative;
    background: linear-gradient(165deg, #1B1560 0%, #131046 52%, #0C0A31 100%);
    margin-top: 10px;
    border-top: 1px solid rgba(222, 217, 245, .14);
    color: var(--on-dark);
    isolation: isolate;
    overflow: hidden;
}
.site-footer::before {
    content: "";
    position: absolute; inset: 0; z-index: -1; pointer-events: none;
    background:
        radial-gradient(50% 60% at 12% 8%,  rgba(224, 36, 123, .24) 0%, rgba(224, 36, 123, 0) 65%),
        radial-gradient(55% 70% at 88% 92%, rgba(46, 125, 209, .24) 0%, rgba(46, 125, 209, 0) 65%);
}
.footer-cols {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 34px; padding-block: 44px;
}
.footer-col--brand { text-align: center; }
.footer-col--brand .widget-title::after,
.footer-col--brand .widget-title { justify-content: center; }
/* Section headings invert to white; the hairline beside them has to invert too or
   it disappears into the dark ground. */
.site-footer .widget-title { color: var(--white); }
.site-footer .widget-title::after {
    background: linear-gradient(90deg, rgba(217, 162, 43, .55), rgba(217, 162, 43, 0));
}
.footer-logo-halo { margin-inline: auto; }
.footer-logo {
    width: 76px; height: 76px; object-fit: contain; display: block;
    border-radius: 50%;
    transition: transform .45s var(--ease);
}
.footer-logo:hover { transform: scale(1.08) rotate(-4deg); }
/* Both white by request — 19.03:1 on the footer ground. The light violet and
   light red they replaced were the sister site's footer-lockup colours; the
   tagline keeps its italic so the two lines still read as name-then-strapline
   without relying on hue to separate them. */
.footer-brand { color: var(--white); font-size: 23px; margin: 8px 0 6px; }
/* 26px, up from 14px: the strapline and the social row were reading as one
   block. */
.footer-tagline { font-size: 14px; font-style: italic; margin: 0 0 26px; color: var(--white); }

.recent-list { list-style: none; margin: 0; padding: 0; }
.recent-list li { border-bottom: 1px solid var(--line-dark); }
.recent-list a {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 0; font-size: 14px; color: var(--on-dark);
    transition: color .25s var(--ease), padding-left .25s var(--ease);
}
.recent-list a:hover { color: var(--white); padding-left: 8px; text-decoration: none; }
.recent-thumb {
    width: 45px; height: 45px; flex: none;
    border-radius: 50%;
    /* cover + centre: the feature images are not square, so without both the
       thumbnail letterboxes inside its circle instead of filling it. */
    object-fit: cover;
    object-position: center;
    display: block;
    /* A faint ring keeps a dark-edged photo from dissolving into the footer. */
    box-shadow: 0 0 0 1px rgba(222, 217, 245, .22);
    transition: transform .3s var(--ease);
}
.recent-list a:hover .recent-thumb { transform: scale(1.06); }
.recent-thumb--none {
    background: rgba(222, 217, 245, .10);
    opacity: .75;
}
.recent-title { min-width: 0; }

.map-embed { aspect-ratio: 16 / 10; border-radius: var(--r); overflow: hidden; border: 1px solid var(--line-dark); }
.map-embed iframe { width: 100%; height: 100%; border: 0; display: block; }

/* Secondary link row — the former header-dropdown children. Wraps freely and
   centres, so it degrades to a tidy stack on a phone instead of scrolling. */
/* Transparent, so the footer gradient runs behind these rows unbroken — a flat
   fill here would cut a visible band across it. */
.footer-links {
    background: transparent;
    border-top: 1px solid var(--line-dark);
}
.footer-links__inner {
    display: flex; flex-wrap: wrap;
    justify-content: center; align-items: center;
    gap: 4px 6px;
    padding-block: 16px;
}
.footer-links a {
    display: inline-flex; align-items: center;
    min-height: 24px;                      /* WCAG 2.5.8 target size */
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 13.5px; font-weight: 700;
    color: var(--on-dark);                 /* 13.91:1 on the footer ground */
    transition: background-color .2s var(--ease), color .2s var(--ease);
}
.footer-links a:hover, .footer-links a:focus-visible {
    background: rgba(222, 217, 245, .14); color: var(--white); text-decoration: none;
}

/* Policy row, directly above the copyright bar. Quieter than the row above it —
   smaller, --on-dark-2 rather than --on-dark — because these are reference links
   people go looking for, not links meant to attract a click. Still 10.71:1, and
   still a 24px target. */
/* Flat --night-deep, matching the copyright bar directly below it, so the policy
   row and the copyright read as one closing block rather than the policy row
   floating on the gradient. */
.footer-legal {
    background: var(--night-deep);
    border-top: 1px solid var(--line-dark);
}
.footer-legal__inner {
    display: flex; flex-wrap: wrap;
    justify-content: center; align-items: center;
    gap: 2px 4px;
    padding-block: 12px;
}
.footer-legal a {
    display: inline-flex; align-items: center;
    min-height: 24px;
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 13px;
    color: var(--on-dark-2);
    transition: background-color .2s var(--ease), color .2s var(--ease);
}
.footer-legal a:hover, .footer-legal a:focus-visible {
    background: rgba(222, 217, 245, .14); color: var(--white); text-decoration: none;
}

.copyrights { background: var(--night-deep); }
.copyrights__inner { padding-block: 18px; }
/* --on-dark-2 on the deep ground measures 10.71:1. */
.copyrights p { margin: 0; font-size: 13px; color: var(--on-dark-2); text-align: center; }

/* ---- floating bits ------------------------------------------------------ */

/* bottom drops 86 -> 18 now the floating WhatsApp button is gone; 86 was only
   ever the clearance to sit above it. */
.to-top {
    position: fixed; right: 18px; bottom: 18px; z-index: 80;
    width: 44px; height: 44px; border-radius: 50%;
    display: none; align-items: center; justify-content: center;
    background: var(--white); border: 1px solid var(--rose);
    color: var(--violet);
    box-shadow: 0 6px 18px rgba(30, 26, 77, .18);
    transition: transform .28s var(--ease), box-shadow .28s var(--ease), background-color .28s var(--ease), color .28s var(--ease);
}
.to-top.is-visible { display: inline-flex; animation: whc-drop .3s var(--ease) both; }
.to-top:hover {
    background: linear-gradient(140deg, var(--rose-ink), var(--plum));
    color: var(--white); border-color: transparent;
    transform: translateY(-4px);
    box-shadow: 0 14px 28px rgba(184, 19, 97, .38);
}
.to-top svg { width: 22px; height: 22px; fill: currentColor; }

.wa-float {
    position: fixed; right: 18px; bottom: 18px; z-index: 90;
    display: flex; align-items: center; gap: 10px;
}
.wa-float__label {
    background: var(--white); color: var(--ink);
    padding: 8px 15px; border-radius: 999px; font-size: 13px; font-weight: 700;
    box-shadow: 0 4px 14px rgba(30, 26, 77, .2);
    white-space: nowrap;
}
.wa-float__btn {
    position: relative;
    width: 54px; height: 54px; border-radius: 50%;
    background: #25d366; color: var(--white);
    display: inline-flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 16px rgba(0, 0, 0, .22);
    transition: transform .28s var(--ease), box-shadow .28s var(--ease);
}
/* Halo, drawn outside the button so it cannot alter layout or catch clicks. */
.wa-float__btn::before {
    content: "";
    position: absolute; inset: -6px;
    border-radius: 50%;
    border: 2px solid rgba(37, 211, 102, .5);
    animation: whc-ripple 3s ease-out infinite;
    pointer-events: none;
}
.wa-float__btn:hover { transform: scale(1.08); box-shadow: 0 10px 26px rgba(37, 211, 102, .5); }
.wa-float__btn svg { width: 30px; height: 30px; fill: currentColor; }

/* =========================================================================
   RESPONSIVE
   Breakpoints chosen from the layouts, not copied: 1180 (wrap starts to touch
   the viewport edge), 1024 (sidebar still fits but nav crowds), 900 (sidebar
   drops under, nav becomes a drawer), 760 (two-up bands), 620 (cards go single
   column), 480 and 380 (small phones).
   ========================================================================= */

@media (max-width: 1180px) {
    .service-cards { gap: 20px; }
}

@media (max-width: 1024px) {
    :root { --gap: 28px; }
    /* Four short links and no dropdowns now, so the bar has room; this only takes
       the edge off so the CTA never crowds the last item. */
    .site-nav__item > a { padding-inline: 10px; font-size: 15px; }
    .site-nav__item > a::after { left: 10px; right: 10px; }
    .service-cards { gap: 16px; }
    .pill-grid { gap: 14px 22px; }
    .band { padding-block: 48px; }
    .layout--sidebar { grid-template-columns: minmax(0, 1fr) 290px; }
}

@media (max-width: 900px) {
    /* --- nav becomes a drawer --- */
    .nav-toggle { display: flex; }

    .site-nav {
        position: absolute; top: 100%; left: 0; right: 0;
        background: var(--white);
        border-top: 1px solid var(--line);
        box-shadow: 0 16px 30px rgba(30, 26, 77, .18);
        display: none;
        max-height: calc(100vh - 88px);
        overflow-y: auto;
    }
    .site-nav.is-open { display: block; animation: whc-drop .28s var(--ease) both; }

    .site-nav__list { flex-direction: column; align-items: stretch; gap: 0; }
    .site-nav__item { border-bottom: 1px solid var(--line); }
    .site-nav__item > a { padding: 14px 18px; font-size: 15.5px; border-radius: 0; }
    /* The centre-out underline is a pointer affordance; in the drawer it would
       just sit under a full-width row. */
    .site-nav__item > a::after { display: none; }

    /* The CTA stays on the bar next to the hamburger rather than going into the
       drawer — the whole point of it is to be reachable without opening a menu. */
    .head-join { padding: 10px 16px; font-size: 14.5px; }

    /* --- sidebar under the content --- */
    .layout--sidebar { grid-template-columns: minmax(0, 1fr); }
    .sidebar { max-width: 480px; margin-inline: auto; width: 100%; }

    /* --- home bands --- */
    .service-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .twin { grid-template-columns: minmax(0, 1fr); gap: 24px; }
    .twin .lotus { justify-content: center; }
    .twin__copy { text-align: center; }
    .twin__lead { margin-inline: auto; }
    .twin__facts li { justify-content: center; }
    .twin .booking-cta, .twin .btn { margin-inline: auto; }
    .twin__art { max-width: 340px; margin-inline: auto; }
    .reviews .review { flex: 0 0 calc((100% - 20px) / 2); }   /* two across */
    /* The fourth review stays hidden at EVERY width. It used to reappear below
       900px to fill the second row of a 2-up grid; three is what was asked for,
       so the 2-up grid simply ends with one card on its own row. The card is
       still in the markup — deleting the rule brings it back. */

    .footer-cols { grid-template-columns: minmax(0, 1fr); gap: 26px; text-align: center; }
    .footer-col { max-width: 480px; margin-inline: auto; width: 100%; }
    /* Recent Posts opts OUT of the centring the stacked footer applies to
       everything else. A list of titles beside round thumbnails only reads as a
       list when the text starts on a common left edge — centred, each title
       floats at a different indent and the thumbnails stop lining up. The column
       itself stays centred in the viewport; only its contents go left. */
    .footer-col--posts { text-align: left; }
    .footer-col--posts .widget-title { justify-content: flex-start; }
    .recent-list a { justify-content: flex-start; text-align: left; }
    .recent-title { text-align: left; }
    .social-grid { justify-content: center; }
    .widget-title { justify-content: center; }
}

/* Tablet portrait: three pills across is too tight once the sidebar has dropped
   under and the content column is full width. */
@media (max-width: 760px) {
    .band { padding-block: 40px; }
    .pill-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 20px; }
    .related__grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 620px) {
    /* Card: thumbnail on top, no overlap — the overlap is a desktop flourish and
       at this width it eats the title. */
    .quote-card { grid-template-columns: minmax(0, 1fr); gap: 0; }
    .quote-card__thumb {
        grid-column: 1; grid-row: 1;
        width: 100%; height: 190px; margin-top: 0;
        border-radius: var(--r) var(--r) 0 0;
    }
    .quote-card__body {
        grid-column: 1; grid-row: 2; padding: 18px; min-height: 0;
        border-radius: 0 0 var(--r) var(--r);
        border-top: 0;
    }
    /* One surface again once they are stacked: the thumb must not lift away
       from the body it now sits directly on top of. */
    .quote-card:hover .quote-card__thumb { transform: none; }
    .quote-card:hover .quote-card__body { transform: none; }
    .quote-card__more { float: none; display: inline-flex; margin-top: 4px; }

    /* One card per view on a phone, with the next peeking in at the edge so it
       is obvious there is more to swipe. Everything else about the slider is in
       the base rules. */
    .reviews { gap: 12px; }
    .reviews .review { flex: 0 0 88%; scroll-snap-align: center; }
    /* No arrows on a phone: a 56px gutter each side would eat a third of a 366px
       rail, and swiping is the native gesture there anyway. The peeking next card
       is the affordance. */
    .rev-shell { padding-inline: 0; }
    .rev-nav { display: none; }
    /* The hover lift shifts a card mid-snap and leaves the scroller a pixel off
       its snap point, which reads as a jitter. Not needed on touch anyway. */
    .reviews .review:hover { transform: none; box-shadow: none; }

    .site-header__inner { min-height: 72px; }
    /* Larger on phones, not smaller — the mark is the brand and at 46px it was
       the weakest thing in the header. Room for it exists because the CTA moved
       to its own row below, leaving row one as just the mark and the hamburger. */
    .site-brand__mark { width: 66px; height: 66px; }
    /* The lockup text grows with the mark. The clamp resolves to ~18px at 390,
       which sat small beside a 66px badge; the CTA moving to its own row left the
       room for both. The tagline still cannot outgrow the name. */
    .site-brand__name { font-size: 21px; }
    .site-brand__tagline { font-size: 13px; }
    .site-nav { max-height: calc(100vh - 72px); }
    /* Two rows on a phone: brand on the left with the hamburger hard right, then
       the CTA centred on its own row beneath them.

       `flex: 0 0 100%` is what forces the row break — the CTA's hypothetical size
       fills the line, so it wraps. `max-width: max-content` then shrinks the
       painted box back to the width of its own label, and `margin-inline: auto`
       centres it. The break has already happened by then, so the button ends up
       small AND on its own row, which a plain `width: auto` cannot do. */
    .nav-toggle { order: 2; margin-left: auto; }
    .head-join {
        order: 3;
        flex: 0 0 100%;
        max-width: max-content;
        margin-inline: auto;
        justify-content: center;
        padding: 10px 16px;
        font-size: 14.5px;
    }

    .wa-float__label { display: none; }   /* the label crowds a 360px viewport */
    .to-top { bottom: 14px; right: 14px; }
}

@media (max-width: 480px) {
    body { font-size: 15px; }
    .wrap { width: min(100% - 1.5rem, var(--wrap)); }
    .band { padding-block: 34px; }
    /* TWO across on phones, not one. The cards are square, so a single column
       makes each one as tall as the screen is wide and the section turns into
       four full-height photos. At two-up the label has to come down with them —
       13px in a tighter pill — or "HOME & BUSINESS" wraps inside a 140px card. */
    .service-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 12px; row-gap: 46px; }
    .service-card__label { left: 5%; right: 5%; padding: 9px 6px; font-size: 13px; }
    /* Two pills per row here too, for both the Ailments and Home & Business
       grids. At 320px that is ~140px per pill, so the longest labels
       ("Psychological Ailment", "Relationship Healing") wrap to two lines —
       grid-auto-rows:1fr and height:100% already make every pill match the
       tallest, so they stay a single uniform block rather than a ragged one. */
    .pill-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
    .pill { padding: 10px 12px; font-size: 13px; }
    .pill { max-width: none; }
    .single-meta { gap: 4px 14px; }
    .quote-card__body { padding: 16px; }
    .related__grid { padding: 10px; }
    .wa-float__btn { width: 48px; height: 48px; }
    .wa-float__btn svg { width: 26px; height: 26px; }
    /* Sized to its label and centred, rather than stretched edge to edge. `width`
       is auto with a max, so it still cannot cause a sideways scroll — which is
       what the old full-width rule was actually there to prevent.
       Metrics match .head-join at this width (11px/20px, 15px type) so the page's
       buttons and the header's read as the same control. */
    .btn {
        width: auto; max-width: 100%;
        min-height: 0; padding: 10px 16px;
        font-size: 14.5px; gap: 8px;
    }
    .btn svg { width: 18px; height: 18px; }
    .btn--wa span, .btn span { font-size: 14.5px; }
    .booking-cta { flex-direction: column; align-items: center; }
    .twin__copy .booking-cta,
    .twin__copy > .btn { margin-inline: auto; }
}

@media (max-width: 380px) {
    .site-brand__mark { width: 58px; height: 58px; }
    .site-brand__name { font-size: 19px; }
    .site-brand__tagline { font-size: 12.5px; }
    /* Brand + CTA + hamburger will not fit on one row at this width, so the CTA
       becomes its own full-width row underneath rather than being dropped — it is
       the one thing on the header that has to survive a small phone. The header
       is already flex-wrap, so this needs no repositioning. */
    .head-join { order: 3; flex-basis: 100%; justify-content: center; }
    .widget--card { padding: 18px 14px; }
    .quote-card__thumb { height: 160px; }
}

/* Reduced motion: this theme animates a lot, so the escape hatch has to be
   total — reveals must also be forced visible, or a user with the preference set
   would be left staring at opacity 0 content. */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after {
        animation: none !important;
        transition: none !important;
        scroll-behavior: auto !important;
    }
    .reveal { opacity: 1 !important; transform: none !important; }
}

@media print {
    .site-nav, .sidebar, .wa-float, .to-top, .site-footer, .pagination { display: none !important; }
    .layout--sidebar { grid-template-columns: 1fr; }
    body { background: #fff; color: #000; }
    .reveal { opacity: 1 !important; transform: none !important; }
    /* The Twin Hearts band needs no print override any more — it is a light band
       again, so it prints as it renders. */
}
