/*
Theme Name: VC Kadence
Theme URI: https://vistaclinic.ie
Description: Vista Clinic child theme for Kadence. Replaces medcity-child. Owns the
             `service` CPT registration (ported off elementor-theme-core1).
Author: Vista Clinic
Template: kadence
Version: 0.6.24
*/

/* ---------------------------------------------------------------------------
   MINIMAL BY POLICY. Colours, fonts, spacing, button styling and layout widths
   all live in theme.json, so core/Kadence blocks pick them up as native block
   attributes with no CSS. Before adding a rule here, check whether a block
   attribute or a theme.json token already does it.

   Replaced by native blocks (do not reintroduce as CSS):
     FAQ accordion   -> core/details
     pricing panel   -> core/group  backgroundColor="vc-bg-soft"
     treatments nav  -> core/list
     testimonial     -> core/quote  backgroundColor="vc-bg-soft"
     two-col layout  -> core/columns (or Kadence Row Layout)
     hero CTAs       -> core/buttons
     colours         -> theme.json palette (vc-teal / vc-ink / vc-body / ...)

   Version: above is the ONLY cache-busting mechanism. Bump with a
   version-agnostic regex, NEVER a hardcoded sed (it has silently no-opped
   on this project and three "fixes" were then measured against stale CSS):
     perl -pi -e 's/^(Version:\s*)(\d+)\.(\d+)\.(\d+)/"$1$2.$3.".($4+1)/e' style.css

   ⛔ Every selector is class-based. NEVER use a `>` child combinator to reach
   block children: WordPress inserts <div class="wp-block-group__inner-
   container"> between a group and its children, so `>` silently misses and the
   rule sits here doing nothing. Six prior bugs on this project.
   --------------------------------------------------------------------------- */

/* ===========================================================================
   AFACAD — self-hosted, and it must STAY self-hosted.
   fonts.googleapis.com was removed sitewide on 2026-07-26: it leaks visitor IPs
   to Google before consent, and the audience is in the EU. Do not reinstate a
   Google Fonts link, a plugin that adds one, or a Kadence Customizer font that
   resolves to one. The woff2 files live in this theme's own fonts/ directory
   (copied from medcity-child, which is now inactive and could be deleted).

   Without these declarations Kadence falls back to -apple-system, which is what
   made dev's header visibly different from production. This is a font the theme
   must provide; there is no block-level or theme.json route to an @font-face.
   =========================================================================== */
@font-face {
	font-family: 'Afacad';
	font-style: normal;
	font-weight: 400 700;
	font-display: swap;
	src: url('fonts/afacad-latin.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
		U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122,
		U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: 'Afacad';
	font-style: normal;
	font-weight: 400 700;
	font-display: swap;
	src: url('fonts/afacad-latin-ext.woff2') format('woff2');
	unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
		U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
	font-family: 'Cinzel';
	font-style: normal;
	font-weight: 400 700;
	font-display: swap;
	src: url('fonts/cinzel-latin.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
		U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122,
		U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Kadence sets its own font stack from the Customizer, so the family has to be
   asserted here to win. Matches production exactly: Afacad, Poppins, sans-serif. */
body,
button,
input,
select,
optgroup,
textarea,
.site-title,
h1, h2, h3, h4, h5, h6 {
	font-family: 'Afacad', 'Poppins', sans-serif;
}

/* ===========================================================================
   TYPOGRAPHY DEFERS TO KADENCE (2026-08-05 audit). Kadence's generated CSS
   already ships a consistent scale — h1 32 / h2 28 / h3 24 / h4 22, ALL at
   line-height 1.5, coloured var(--global-palette3) ("Heading Ink", measured
   off prod) — which is the calm, consistent hierarchy the client asked for.
   The old prod-measured fluid clamp() scale (h1 up to 50px, h2 up to 60px)
   was DELETED: it read overpowering over the new short 26vh hero, and its
   caps were the very thing the client flagged.

   2026-08-05 later the same day: the heading font-weight:400 override that
   used to live here is GONE. It was added on the claim "prod is 400
   everywhere" — remeasured today, prod CONTENT headings are 700 (contact h1
   46px/700, h2 28px/700; treatment h2 37px/700); only prod's photo-hero
   titles are 400–500. Kadence's stock heading weight is 700, so deleting the
   override both matches prod and removes a rule — the client's "prod font
   seems more elegant" was this weight difference, not the family (both hosts
   load the same self-hosted Afacad; verified via document.fonts).
   =========================================================================== */

/* Kadence outputs no body colour/line-height rule at all on this install
   (verified against its inline CSS) — without these, body text falls back to
   browser defaults. Values measured off prod 2026-08-05: 18px at 1.69 leading,
   muted grey. Prod's actual grey #848E9F is 3.31:1 on white — an AA failure —
   so this uses palette4's #677284 (same hue, 4.86:1), the same substitution
   the palette itself documents. */
body {
	color: #677284;
	font-size: 18px;
	line-height: 1.65;
}

/* Template hero (vc_kadence_page_hero). Core's cover CSS handles the layering; these
   rules are the ones a PHP-rendered cover does not get for free, because there is
   no block wrapper to carry them. */
.vc-tpl-hero .wp-block-cover__inner-container {
	max-width: 1200px;
	margin-inline: auto;
	/* Fluid, was a flat 3rem: the non-home hero is now ~26vh, and at 402px wide
	   a 48px gutter each side left too little room for title + subtitle + CTA
	   to fit inside it. 20px at phone width, the original 48px from ~1200px up. */
	padding-inline: clamp(1.25rem, 4vw, 3rem);
}

/* Size and line-height come from Kadence's global h1 rule (32px/1.5) so the
   hero matches every other template; the hero only needs what the global rule
   cannot know: white text over a photo, and prod's photo-hero weight (500 —
   lighter than the 700 content headings, measured off prod's own hero). */
.vc-tpl-hero h1 {
	font-weight: 500;
	color: #FFFFFF;
}

.vc-tpl-hero p {
	color: #FFFFFF;
	/* Tight lead-in under the title so subtitle + CTA fit a ~26vh hero at
	   402px wide; font-size is body default (16px), no override needed. */
	margin-top: 0.75rem;
	margin-bottom: 0;
}

/* Space between subtitle and CTA button; the PHP hero has no block gap CSS
   (no block wrapper), so the margin lives here. */
.vc-tpl-hero .wp-block-buttons {
	margin-top: 1rem;
}

/* ⛔ The sidebar is deliberately NOT sticky (client direction 2026-08-05:
   "main column and sidebar scroll together"). It was position:sticky once;
   on price-heavy pages the pinned aside was taller than the viewport and its
   bottom half (Similar Treatments) could never scroll into view. Do not
   reintroduce sticky without a measured plan for asides taller than 100vh. */

/* Breadcrumbs (core/breadcrumbs, first block on treatment pages). The ol keeps
   the browser's default padding-inline-start, pushing the crumb text 32px right
   of the main column's first h2 (measured: link x 155.5 vs h2 x 123.5 at 1440).
   Zeroing it aligns both to the same left edge. list-style:none because core
   ships no breadcrumb list reset on this install. */
.wp-block-breadcrumbs ol {
	list-style: none;
	padding-left: 0;
	margin: 0;
}

/* Thin rule under the breadcrumb row, spanning the breadcrumb's content width.
   Template-level by design: one border here beats injecting a core/separator
   into 19 pages' content (and every future page gets it for free). Same muted
   teal as the price-row rules so the chrome reads as one system. */
.wp-block-breadcrumbs {
	padding-bottom: 0.6rem;
	border-bottom: 1px solid rgba(74, 120, 116, 0.18);
}

/* One paragraph per price row: label text + <strong>€amount</strong> pushed
   right. Core has no "space between" for inline text within a paragraph. */
.vc-price__row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 1rem;
	/* border-TOP, not bottom: `:last-of-type` would also match the trailing
	   note paragraph, leaving a stray rule under the final row. */
	border-top: 1px solid rgba(74, 120, 116, 0.18);
	/* 0.35rem, down from 0.55rem (client: price panel too airy). Keeps the
	   >=0.3rem floor so rows don't touch. */
	padding: 0.35rem 0;
	margin: 0;
	/* Body leading (1.6) is prose leading; in a data table it reads airy and
	   doubles the cost of a wrapped label. 1.35 keeps wrapped lines together
	   without touching. Measured: a 2-line row went 62px -> 54px. */
	line-height: 1.35;
}

/* The `margin: 0` above NEVER WON: Kadence's entry-content paragraph spacing
   (higher specificity) kept a computed 32px margin-bottom on every row — the
   single biggest source of the "too airy" price panel (measured on the PRP
   page: 10 sidebar rows x 32px of dead gap). Same declaration, enough
   specificity to actually apply. */
.entry-content .vc-price__row,
.site-main .vc-price__row {
	margin: 0;
}

.vc-price__row:first-of-type {
	border-top: 0;
}

/* Closing rule under the FINAL price row. `:last-of-type` is unusable here (it
   would match the trailing note paragraph — see the border-top comment above),
   so match "price row with no later price-row sibling" via :has(). Immune to
   both the trailing note and h3 group labels between row runs. */
.vc-price__row:not(:has(~ .vc-price__row)) {
	border-bottom: 1px solid rgba(74, 120, 116, 0.18);
}

/* A price must never wrap mid-figure. Measured on /micro-needling-treatment-dublin/
   and /cheek-fillers-dublin/ at 1400px: "€275" broke to two lines (54.4px tall vs
   27.2px), rendering as "€27 / 5". One declaration, no native equivalent. */
.vc-price__row strong {
	white-space: nowrap;
}

/* …and the LABEL must be able to wrap while the price stays put. Grid, not flex,
   for one specific reason: the markup is `<p>Label: <strong>€200</strong></p>`,
   so the label is a bare TEXT NODE. Under flex it becomes an anonymous flex item,
   which no selector can reach — there is no way to give it `min-width: 0`, so it
   hits its min-content floor and either overflows or (with flex-wrap) shoves the
   price onto its own line, which is what broke the price column's alignment.

   Grid places the same anonymous text into column 1 without needing to select it,
   and `minmax(0, 1fr)` gives that column a zero minimum, so the label wraps freely
   while `auto` sizes the price to its content. Result: labels wrap to as many lines
   as they need, prices stay right-aligned on the first line and share one column
   edge down the whole panel.

   History: this replaces a flex + flex-wrap rule that fixed a real bug — on
   /skincare-treatments/lip-fillers-dublin/ at 820x1180 with 1.5x text the sidebar
   is ~150px and all 6 rows overflowed, tallest 784px of single vertical letters.
   Grid fixes that case too (the 0 minimum is what the flex version lacked) without
   the price ever leaving its column. */
.vc-price__row {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: baseline;
	column-gap: 1rem;
}

/* ⏳ TEMPORARY SHIM — delete once the six pre-existing Gutenberg pages (10, 36,
   8631, 13692, 18410, 18575, 18625) and blocks 18617/18624 are rebuilt on
   core/buttons. Their CTAs use .vc-btn, which medcity-child/vc-blocks.css styled;
   that sheet stopped loading the moment the theme switched to vc-kadence, so every
   one of those CTAs now renders as a plain inline text link. Verified that core's
   own .wp-element-button does NOT style a bare <a> (measured: only `display`
   changes), so there is no zero-CSS route. Colours come from the palette, so this
   shim cannot drift from the brand. Tracked in TODO.md. */
.vc-btn {
	display: inline-block;
	padding: 0.75rem 1.75rem;
	border-radius: 4px;
	background: var(--global-palette1);
	color: var(--global-palette9);
	text-decoration: none;
}

.vc-btn:hover,
.vc-btn:focus-visible {
	background: var(--global-palette2);
	color: var(--global-palette9);
}

.vc-btn--ghost {
	background: transparent;
	color: var(--global-palette1);
	box-shadow: inset 0 0 0 1px var(--global-palette1);
}

.vc-btn--ghost:hover,
.vc-btn--ghost:focus-visible {
	background: var(--global-palette1);
	color: var(--global-palette9);
}

/* Footer treatment index: an unstyled link list. Core has no list-style control,
   so this is the one thing the block editor genuinely cannot express. Underlines are
   removed natively via the block's link typography, not here. */
.vc-foot-list {
	list-style: none;
}

/* ===========================================================================
   Three-template system chrome (vc_kadence_treatment_grid, the taxonomy and
   single-service templates). PHP-generated markup, so no block attributes can
   carry the styling — this is the same justification as .vc-tpl-hero.
   =========================================================================== */
.vc-term-hero {
	background: var(--global-palette1);
	padding: clamp(3rem, 6vw, 5rem) 1.5rem;
}

/* Size/line-height from Kadence's global h1 rule, same as .vc-tpl-hero h1
   (and the same 500 hero weight, for one consistent hero voice). */
.vc-term-hero h1 {
	color: #FFFFFF;
	font-weight: 500;
	text-align: center;
	margin: 0;
}

.vc-term-body,
.vc-related__inner {
	max-width: 1200px;
	margin-inline: auto;
	padding: 3rem 1.5rem;
}

.vc-term-desc {
	max-width: 46rem;
	margin: 0 auto 2.5rem;
	text-align: center;
}

/* `minmax(260px, …)` is a FLOOR, not a hint: once the container is narrower
   than 260px the track keeps its 260px and the cards hang out of the grid.
   Measured on /treatments/dermal-fillers/ at 268x583 (a 402px phone at 150%
   browser zoom): container 220px, card 260px, 40px of every card clipped off
   the right edge — images and titles cut. `min(260px, 100%)` keeps the 260px
   floor everywhere it fits and collapses to the container below that. */
.vc-cat-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
	gap: 1.5rem;
}

.vc-card {
	display: block;
	text-decoration: none;
	color: var(--global-palette3);
	background: var(--global-palette7);
}

.vc-card__media img {
	display: block;
	width: 100%;
	aspect-ratio: 3 / 2;
	object-fit: cover;
}

.vc-card__title {
	display: block;
	padding: 1rem 1.25rem;
	font-size: 20px;
}

.vc-card:hover .vc-card__title,
.vc-card:focus-visible .vc-card__title {
	color: var(--global-palette1);
	text-decoration: underline;
}

.vc-related {
	background: var(--global-palette7);
	margin-top: 3rem;
}

/* Testimonial highlights (block 18617). Stars + cite have no block-attribute
   route: a paragraph can't colour part of itself and cite is inline HTML. Gold
   and metrics copied from production (medcity vc-home.css .vc-stars). */
.vc-stars {
	color: #d9a441;
	letter-spacing: 0.16em;
	font-size: 18px;
	margin-bottom: 0.4em;
}

.vc-quote-card cite {
	display: block;
	font-style: normal;
	font-weight: 600;
	margin-top: 0.8em;
}

/* Click-to-load map facade on /contact/ (vc-map.js). Ported from medcity's
   vc-blocks.css — the panel, and the iframe the JS injects on click. */
.vc-map {
	display: flex;
	flex-direction: column;
	justify-content: center;
	min-height: 360px;
	padding: clamp(20px, 3vw, 32px);
	border: 1px solid #d8dcd6;
	background: #EEF1EE;
}

.vc-map__frame {
	width: 100%;
	height: 360px;
	border: 0;
	display: block;
}

/* ===========================================================================
   Desktop dropdown (Treatments). Kadence's stock dropdown is an opaque ink
   panel (--global-palette3) whose hover shade produced the "grey 2-tone" the
   client flagged — and with 28 items the panel is taller than the viewport
   with NO scroll, so the bottom entries were simply unreachable (measured at
   1440×900). White panel, ink text, teal hover, scroll cap. Scoped to
   .header-navigation only: the mobile drawer (#mobile-drawer) keeps Kadence's
   drawer styling untouched.
   =========================================================================== */
.header-navigation .header-menu-container ul ul.sub-menu,
.header-navigation .header-menu-container ul ul.submenu {
	background: #FFFFFF;
	border-top: 2px solid var(--global-palette1);
	box-shadow: 0 6px 20px rgba(66, 81, 99, 0.16);
}

/* The scroll cap is SEPARATE from the panel styling above, and deliberately
   never applies to a panel that contains a flyout.

   History: the cap was added when Treatments was one flat 28-item list taller
   than the viewport with no way to reach the bottom. The menu has since been
   restructured to 4 category items, each with its own flyout — and a flyout is
   an absolutely-positioned child at left:100%, i.e. 200px OUTSIDE its parent's
   content box. Inside `overflow:auto` that is scrollable overflow, so the
   4-item panel reported scrollWidth 400 / clientWidth 200 and scrollHeight 180
   / clientHeight 178 (measured 1440x900, flyout open): a horizontal scrollbar,
   a 2px vertical one, and — far worse — the flyout itself CLIPPED to nothing.
   Confirmed by screenshot: hovering a category showed no flyout at all.

   :not(:has(ul)) keeps the cap where it is still safe (a leaf list, including
   each flyout) and drops it wherever clipping a child menu would be the cost.
   overflow-x:hidden so no leaf list can ever produce a horizontal bar either.
   min(70vh, …) is the sane ceiling the old calc() was reaching for. */
.header-navigation .header-menu-container ul ul.sub-menu:not(:has(ul)),
.header-navigation .header-menu-container ul ul.submenu:not(:has(ul)) {
	max-height: min(70vh, calc(100vh - 160px));
	overflow-y: auto;
	overflow-x: hidden;
}

/* li.menu-item > a mirrors Kadence's own inline rule — anything weaker loses
   the specificity contest and the links stay white-on-white (measured). */
.header-navigation .header-menu-container ul ul li.menu-item > a {
	color: var(--global-palette3);
	background: transparent;
}

.header-navigation .header-menu-container ul ul li.menu-item > a:hover,
.header-navigation .header-menu-container ul ul li.menu-item > a:focus-visible,
.header-navigation .header-menu-container ul ul li.menu-item.current-menu-item > a {
	color: var(--global-palette1);
	background: rgba(74, 120, 116, 0.08);
}

/* Contact page channel CTAs: prod renders WhatsApp/Call/Fresha as full-column
   teal bars; dev's are .vc-btn links (the legacy shim) sitting inside
   .vc-contact__cols. Width is presentation, not content, so it lives here
   rather than in post_content. Flat corners to match prod. */
.vc-contact__cols .vc-btn {
	display: block;
	width: 100%;
	text-align: center;
	border-radius: 0;
	padding-top: 0.9rem;
	padding-bottom: 0.9rem;
}

/* Header "BOOK AN APPOINTMENT": hover fills teal with WHITE text, instantly
   (client direction 2026-08-05: "onHover text turns white, no transition").
   The #main-header ID is REQUIRED, not decoration: Kadence's header.min.css
   ships `#main-header .header-button.button-style-outline { background:
   transparent !important }` — ID + !important — so a class-only rule loses on
   specificity even with its own !important (measured: hover text went white
   but the fill stayed transparent, leaving white-on-white). */
#main-header .button.header-button,
#main-header .button.header-button:hover {
	transition: none !important;
}

#main-header .button.header-button:hover,
#main-header .button.header-button:focus-visible {
	color: #FFFFFF !important;
	background: var(--global-palette1) !important;
	border-color: var(--global-palette1) !important;
}

/* Footer treatments band — PHP-generated (vc_kadence_footer_treatments), so no
   per-block elements.link CSS exists; white links live here instead. */
.vc-foot-band,
.vc-foot-band a {
	color: var(--global-palette8);
	text-decoration: none;
}

.vc-foot-band a:hover,
.vc-foot-band a:focus-visible {
	text-decoration: underline;
}

/* Sidebar headings: panel labels in a 25% column, one step below the global
   h2 (28px) so they don't compete with the page's section headings. */
.entry-content .vc-svc-aside h2,
.entry-content .vc-similar h2,
.site-main .vc-svc-aside h2 {
	font-size: 22px;
	/* 0.75rem top / 0.25rem bottom, down from 2rem / ~0.7em: the sidebar panels
	   read compact (client request), and the sticky aside now has a max-height
	   so every saved pixel is content that fits without scrolling. */
	margin-top: 0.75rem;
	margin-bottom: 0.25rem;
}

/* Sidebar h3 sub-headings (price-group labels like "COMBINATION THERAPIES")
   inherited the fluid prose h3 scale — measured 137px tall for ONE label in the
   223px-wide aside (30px type wrapped to 3 lines + 45px margins). In a sidebar
   they are eyebrow labels, not section headings. */
.entry-content .vc-svc-aside h3,
.site-main .vc-svc-aside h3 {
	font-size: 15px;
	letter-spacing: 0.05em;
	margin-top: 1rem;
	margin-bottom: 0.25rem;
}

/* Similar Treatments — injected into the sidebar by vc_kadence_aside_similar.
   PHP markup, no block wrapper to carry list styling. */
.vc-similar ul {
	list-style: none;
	padding-left: 0;
	margin: 0 0 1rem;
}

.vc-similar li {
	padding: 0.45rem 0;
	border-top: 1px solid rgba(74, 120, 116, 0.18);
}

.vc-similar li:first-child {
	border-top: 0;
}

/* Marks a slot where an image is needed but none exists yet. Deliberately
   ugly so it cannot ship to production unnoticed. */
.vc-img-placeholder {
	min-height: 220px;
	outline: 2px dashed rgba(132, 142, 159, 0.5);
	outline-offset: -2px;
}

/* Treatments hub (18625) + home (18575): row images are library photos with
   different intrinsic ratios; render them all 16:10 so the rows line up.
   Crop, don't letterbox — object-fit keeps the focal area. The home
   before/after figure is deliberately NOT .wp-block-image (see .vc-ba). */
.page-id-18625 .wp-block-columns .wp-block-image img,
.home .wp-block-columns .wp-block-image img {
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 10;
	object-fit: cover;
}

/* Home before/after crossfade (pattern 18624, Regenerative row) — ported from
   medcity vc-home.css, where prod ran it. Two stacked imgs, compositor-only
   8s opacity cycle; reduced motion gets both states side by side (showing only
   "after" would misrepresent a clinical result). Box is 332/380, NOT 16:10:
   every profhilo source is ~656x738 portrait, so a 16:10 crop loses the
   treated neck area or the Before/After labels. The img is 107% tall and
   top-anchored so the burnt-in credit band (bottom 6.5% of the source) falls
   outside the box; attribution is carried by the real-text .vc-credit line —
   do not re-crop the files (sips centre-crops and ate the "Before" label). */
.home .vc-ba {
	position: relative;
	display: grid;
	grid-template-columns: 1fr;
	margin: 0;
	aspect-ratio: 332 / 380;
	overflow: hidden;
}
.home .vc-ba img {
	grid-area: 1 / 1;
	align-self: start;
	width: 100%;
	height: 107%;
	object-fit: cover;
	object-position: left top;
}
@media (prefers-reduced-motion: no-preference) {
	@keyframes vc-ba-fade {
		0%, 44%   { opacity: 1; }
		50%, 94%  { opacity: 0; }
		100%      { opacity: 1; }
	}
	.home .vc-ba img:first-of-type {
		animation: vc-ba-fade 8s ease-in-out infinite;
		position: relative;
		z-index: 1;
	}
}
@media (prefers-reduced-motion: reduce) {
	.home .vc-ba { grid-template-columns: 1fr 1fr; gap: 6px; aspect-ratio: auto; }
	.home .vc-ba img { grid-area: auto; }
}
.home .vc-credit {
	margin: 0.5rem 0 0;
	font-size: 0.8rem;
	color: #848E9F;
}

/* Testimonial cards (pattern 18617): equal rectangles per row regardless of
   quote length — the column is already flex-stretched by core, the card just
   has to fill it. */
.vc-quote-band .wp-block-column {
	display: flex;
}
.vc-quote-band .wp-block-column > .vc-quote-card {
	width: 100%;
}

/* ⛔ NEVER add overflow:hidden to .vc-ba. It clipped the licensed credit
   caption off-screen while also cropping the burnt-in label band, leaving the
   third-party clinical images with no visible attribution at all — a breach of
   the image licence, not just a layout bug. Left as a named class purely so
   this warning has somewhere to live. */

/* FAQ question headings — vc_kadence_faq_heading() in functions.php wraps each
   core/details <summary> in an h3 (h4 where the questions sit under an h3
   sub-heading) so the questions exist in the document outline. This rule is
   what keeps that invisible: the heading renders exactly as the bare summary
   text did before. If a question ever starts rendering as a big bold heading,
   this rule is what stopped matching. */
.wp-block-details summary .vc-faq-q {
	display: inline;
	font: inherit;
	line-height: inherit;
	letter-spacing: inherit;
	text-transform: inherit;
	color: inherit;
	margin: 0;
	padding: 0;
}

/* ===========================================================================
   ENTRANCE MOTION — promo pages (19169 /special-offers/, 19170 /current-offers/)
   and the HOME page (`.home`, added 0.6.24 at the client's request).
   Scroll-driven entrance motion. Page-scoped by body class ON PURPOSE:
   .vc-tpl-hero is the sitewide hero template, so an unscoped rule would
   animate every page on the site.

   Home is scoped by `.home`, not `.page-id-18575`, and not by the `.vc-reveal`
   class already sitting in its markup:
     - `.vc-reveal` is vestigial markup carried over from the production theme
       (zero CSS, zero JS anywhere in this theme). It matches THREE elements on
       home — two h2s and one subhead — and none of the nine treatment /
       testimonial columns, so it cannot express the effect without editing
       18575's block attributes, which this change deliberately does not touch.
     - `.home` is a WordPress core body class for the front page and survives
       the front page being pointed at a different post; `.page-id-18575` does
       not. Verified in the live DOM: /blog/ carries `blog`, NOT `home`, so the
       posts index is not caught by these rules.
   Home has no `.wp-block-details` and no `.entry-content > .wp-block-image`, so
   those two promo selectors get no `.home` twin — a selector matching nothing is
   just a future reader's wild goose chase.

   ⛔ The home CONTACT BAND and the footer bands render through action hooks,
   OUTSIDE `.entry-content`, so scoping the body rules to `.entry-content` keeps
   every phone / WhatsApp / booking surface below the fold entirely un-animated.
   That is load-bearing, not incidental — do not lift these selectors out of
   `.entry-content`.

   Standard: modern-web-guidance/scroll-entry-exit-effects.
     - native CSS scroll-driven animations only, zero JS, no polyfill
     - the whole thing sits inside prefers-reduced-motion: no-preference
     - the scroll-driven half additionally sits inside
       @supports ((animation-timeline: view()) and (animation-range: entry))
       — the animation-range half is REQUIRED, it filters out partial support
     - no fallback: this is decorative, so Firefox gets the static page and
       that static page must stay correct. Every keyframe here therefore
       animates FROM a hidden state TO the page's natural state, never the
       other way round. Nothing is authored at opacity 0 in the base styles.
     - compositor properties only (opacity / translate / scale). Nothing here
       may change a box size or trigger reflow.
     - animation-timeline is declared AFTER the animation shorthand, or the
       shorthand resets it back to auto.

   ⛔ .vc-promo-sticky is deliberately absent from every selector below. It is
   the conversion path and must be present and clickable from the first paint.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {

	/* --- Hero: time-based, NOT scroll-driven -------------------------------
	   The hero is above the fold, so its view() entry range is already at
	   100% on load and a scroll-driven entry animation would never be seen.
	   A plain time-based animation is the only thing that can express "settles
	   in on load", and it needs no @supports guard — CSS animations are
	   universal, so Firefox gets this part too. Total 0.75s worst case; the
	   CTA is hit-testable throughout (opacity 0 still takes pointer events).

	   Home's hero is 615px against the promos' ~230px banner, and it still gets
	   the TIME-BASED treatment, for the reason above rather than by copying:
	   the trigger is being above the fold, not being short. Measured at
	   402x874 and 1440x900 the home hero's top is at scroll 0, so its view()
	   entry range is already at 100% before a visitor can scroll — a
	   scroll-driven entry animation there would render its final frame and
	   nothing else. Height changes nothing about that. */
	@keyframes vc-promo-rise {
		from { opacity: 0; translate: 0 14px; }
	}
	.page-id-19169 .vc-tpl-hero .wp-block-cover__inner-container > *,
	.page-id-19170 .vc-tpl-hero .wp-block-cover__inner-container > *,
	.home .vc-tpl-hero .wp-block-cover__inner-container > * {
		animation: vc-promo-rise 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
	}
	.page-id-19169 .vc-tpl-hero .wp-block-cover__inner-container > :nth-child(2),
	.page-id-19170 .vc-tpl-hero .wp-block-cover__inner-container > :nth-child(2),
	.home .vc-tpl-hero .wp-block-cover__inner-container > :nth-child(2) {
		animation-delay: 0.1s;
	}
	.page-id-19169 .vc-tpl-hero .wp-block-cover__inner-container > :nth-child(3),
	.page-id-19170 .vc-tpl-hero .wp-block-cover__inner-container > :nth-child(3),
	.home .vc-tpl-hero .wp-block-cover__inner-container > :nth-child(3) {
		animation-delay: 0.2s;
	}

	@supports ((animation-timeline: view()) and (animation-range: entry)) {

		/* ⛔ NO exit-range effect on the hero photo. A scale+fade on
		   .wp-block-cover__image-background was written, deployed and then
		   removed: .vc-tpl-hero on these two pages is a 234px banner strip
		   (1440x900) / 227px (402x874), not a full-bleed hero. At that height a
		   6% scale and a fade to 0.72 across the exit range produced crops that
		   were BYTE-IDENTICAL at two scroll offsets — measured, not assumed. It
		   was invisible decoration. If the banner ever becomes a tall hero,
		   that is the moment to reconsider, and re-measure before believing it.

		   --- Body: fade-and-rise as each block enters -------------------------
		   Named view-timeline declared on the element that is also the subject
		   (the form the guide shows for per-child staggering) so every card,
		   heading, FAQ row and figure runs on its own entry rather than the
		   whole section moving as one slab.
		   Range stops at entry 70% so a block is fully settled before it
		   reaches reading height, rather than still moving under the eye.
		   translate is vertical only — a horizontal one would extend the
		   scrollable width and trip the overflow gate. */
		@keyframes vc-promo-enter {
			from { opacity: 0; translate: 0 24px; }
		}
		.page-id-19169 .entry-content .wp-block-column,
		.page-id-19170 .entry-content .wp-block-column,
		.page-id-19169 .entry-content .wp-block-details,
		.page-id-19170 .entry-content .wp-block-details,
		.page-id-19169 .entry-content > .wp-block-image,
		.page-id-19170 .entry-content > .wp-block-image,
		.page-id-19169 .entry-content h2,
		.page-id-19170 .entry-content h2,
		.home .entry-content .wp-block-column,
		.home .entry-content h2 {
			view-timeline: --vc-promo-in;
			animation: vc-promo-enter auto linear backwards;
			animation-timeline: --vc-promo-in;
			animation-range: entry 10% entry 70%;
		}
	}
}

/* ===========================================================================
   MOBILE: utility bar + home hero (client direction 2026-08-05)
   Appended, not merged into the sections above, because another session was
   editing this file at the same time.
   =========================================================================== */

/* --- Teal utility bar (synced block 18958, kadence_before_header) ----------
   The block carries no class of its own, so it is selected by its literal
   teal. #4A7874 is a documented literal in that markup and it is the ONLY
   direct child of #wrapper carrying it (verified in the live DOM 2026-08-05:
   the other hook-rendered bands are .vc-foot-band #6C9F9A and a #000000 group).
   If the bar's colour ever changes these rules stop applying — visible, not
   silent.

   ROOT CAUSE of the "Google / Maps" break at phone widths: the inner icon row
   is a flex-wrap:nowrap flex container, so its four paragraphs are SHRUNK
   below their content width and each label wraps under its own icon. Fixing
   only the Maps link would leave Facebook/Instagram/WhatsApp doing the same
   thing one viewport narrower. So: let the row wrap BETWEEN links, and forbid
   a break INSIDE one. Applies at every width, not just mobile. */
#wrapper > .wp-block-group[style*="4A7874"] .wp-block-group-is-layout-flex {
	flex-wrap: wrap;
}
#wrapper > .wp-block-group[style*="4A7874"] p {
	white-space: nowrap;
}

/* Below 820px the four labels simply cannot share a row: measured intrinsic
   widths are Google Maps 97 + Facebook 76 + Instagram 80 + WhatsApp 80 = 333,
   plus 60px of gaps and the 168px phone line = 561px, against 354px of content
   box at 402px wide. Shrinking the type enough to fit would put 11px labels in
   front of an older-skewing audience. So the labels go and the icons stay, and
   the phone number is the only text left — with real 44x44 tap targets, which
   the 17px-tall text links never had.
   820 is excluded deliberately: at exactly 820 the labelled bar still fits on
   one row (content edge 796 of 796 available), so tablet is untouched. */
@media (max-width: 819px) {
	#wrapper > .wp-block-group[style*="4A7874"] {
		padding-left: 1rem;
		padding-right: 1rem;
	}
	/* font-size:0 collapses the label TEXT NODES only. The inline SVGs keep
	   their own width/height attributes, and the accessible name is untouched,
	   so a screen reader still announces "Google Maps". No markup change, no
	   href change. */
	#wrapper > .wp-block-group[style*="4A7874"] p {
		/* !important is load-bearing: the pattern sets font-size:13px/14px as an
		   INLINE style on every paragraph, and nothing but !important beats it. */
		font-size: 0 !important;
		letter-spacing: 0 !important;
	}
	/* ...except the phone number, which is the point of the bar. */
	#wrapper > .wp-block-group[style*="4A7874"] a[href^="tel:"] {
		display: inline-flex;
		align-items: center;
		min-height: 44px;
		font-size: 14px;
	}
	#wrapper > .wp-block-group[style*="4A7874"] a:not([href^="tel:"]) {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-width: 44px;
		min-height: 44px;
	}
	/* 14px glyphs are too small once they are the only affordance left. */
	#wrapper > .wp-block-group[style*="4A7874"] a:not([href^="tel:"]) svg {
		width: 20px;
		height: 20px;
		margin-right: 0;
		vertical-align: middle;
	}
	/* The 1.25rem inter-link gap now lives inside the 44px targets. */
	#wrapper > .wp-block-group[style*="4A7874"] .wp-block-group-is-layout-flex {
		gap: 0;
	}
}

/* --- Home hero, MOBILE ONLY ------------------------------------------------
   Client direction 2026-08-05: title centred in the view, CTA deliberately
   just off the centre axis. Scoped to body.home because every other hero on
   the site is left-aligned by design (see vc_kadence_page_hero), and gated at
   the same 819px breakpoint as the bar so tablet/desktop are byte-identical. */
@media (max-width: 819px) {
	body.home .vc-tpl-hero h1,
	body.home .vc-tpl-hero p {
		text-align: center;
	}
	body.home .vc-tpl-hero .wp-block-buttons {
		/* display:flex is load-bearing: the hero is PHP-rendered markup with no
		   block wrapper, so core never adds wp-block-buttons-is-layout-flex and
		   justify-content alone does nothing. */
		display: flex;
		justify-content: center;
		/* The asymmetry. The nudge is a third of the slack left over once the
		   button has taken its width, capped at 18px — so it is visible at
		   402px (14px) and collapses to 0 by 360px, where the button already
		   fills the content box and any offset would just look like a mistake.
		   ponytail: one clamp rather than a second breakpoint. */
		transform: translateX(calc(-1 * clamp(0px, (100vw - 360px) / 3, 18px)));
	}
}

/* ===========================================================================
   MOBILE POLISH PASS — 2026-08-05 (client phone screenshots + 2 mobile audits)

   Everything below 819px only. Desktop (>=820) is deliberately byte-identical:
   every item in this pass came from a phone, and the pass is gated so a
   desktop regression is structurally impossible rather than merely unlikely.
   The one exception is the dropdown scroll-cap fix, which is a desktop bug and
   lives up with the dropdown rules it corrects.

   Breakpoints in use, and why there are two:
     max-width: 819px                 phones + narrow tablets. Same boundary the
                                      utility-bar and home-hero rules already use.
     orientation: landscape           landscape PHONES only. max-height keeps
       and max-height: 600px          desktop (900/1080 tall) out of it — a
                                      width query cannot, since a landscape
                                      phone is 874px wide.
   =========================================================================== */

/* --- 1. Contact CTA band: 113.5vh -> target ~60vh at 402x874 ---------------
   Synced block 18409, rendered sitewide by vc_kadence_footer_bands(). Measured
   before this rule: 992px / 113.5vh at 402x874, 608px / 151.2vh at 874x402,
   1076px / 189.4vh at 320x568 — on all 23 pages. More than a full screen of
   scrolling for one band, on every page.

   ⚠️ !important is load-bearing here and NOT a specificity shortcut. Every
   number this rule changes is an INLINE style written by the block editor as a
   block attribute (padding on the cover, font-size + margin on each heading and
   paragraph, padding on each button). Inline styles cannot be beaten by any
   selector, only by !important. The alternative was editing the synced block
   itself, which would change the client-approved desktop design.

   Content is untouched: same copy, same three channels, same photo, same dim.
   This is spacing and type scale only. #wrapper > … because the band is the
   only .wp-block-cover.is-light on the site (verified live) and the child
   combinator keeps it that way if a page ever adds a light cover of its own. */
@media (max-width: 819px), (orientation: landscape) and (max-height: 600px) {
	#wrapper > .wp-block-cover.is-light {
		padding-top: 1.5rem !important;
		padding-bottom: 1.5rem !important;
	}
	/* Kadence's entry-content paragraph spacing puts a 15px margin-top on EVERY
	   paragraph in the band — 15px x 2 per channel column x 3 columns, plus the
	   tagline, is 105px of dead vertical gap and was the single largest remaining
	   contributor after the padding cut. The block gap already separates them. */
	#wrapper > .wp-block-cover.is-light p {
		margin-top: 0 !important;
	}
	/* h2 "Contact us": 46px is a desktop display size. */
	#wrapper > .wp-block-cover.is-light h2 {
		font-size: 26px !important;
		line-height: 1.25 !important;
		margin-bottom: 0.4rem !important;
	}
	/* Tagline: 21px wrapped to two lines at 402px; 16px fits on one. */
	#wrapper > .wp-block-cover.is-light > .wp-block-cover__inner-container > p:first-of-type {
		font-size: 16px !important;
		margin-bottom: 0.9rem !important;
	}
	/* The three stacked channel rows were 445px of the 992 — the single
	   biggest block. Tightened, not shortened. */
	#wrapper > .wp-block-cover.is-light .wp-block-columns {
		gap: 0.9rem !important;
		margin-bottom: 0 !important;
	}
	#wrapper > .wp-block-cover.is-light .wp-block-column p {
		font-size: 15px !important;
		line-height: 1.35 !important;
		margin-bottom: 0.3rem !important;
	}
	#wrapper > .wp-block-cover.is-light .wp-block-button__link {
		/* 0.72rem, not lower: measured, this lands the button at 44.4px and the
		   44px tap-target floor is the binding constraint on this band, not the
		   height budget. 0.62rem produced a 42.2px button. */
		padding-top: 0.72rem !important;
		padding-bottom: 0.72rem !important;
	}
	/* Address / hours / email: 2.5rem of air above them, three 15px lines. */
	#wrapper > .wp-block-cover.is-light .wp-block-columns ~ p {
		font-size: 14px !important;
		line-height: 1.45 !important;
		margin-top: 0 !important;
		margin-bottom: 0 !important;
	}
	#wrapper > .wp-block-cover.is-light .wp-block-columns + p {
		margin-top: 1.1rem !important;
	}
}

/* --- 2. Content images: cap against VIEWPORT HEIGHT, both orientations -----
   Client: "content images 16:10, capped at ~40% of viewport height"; audit 2
   measured body images at up to 89.7vh in landscape on 9 of 20 treatment pages.

   ⛔ 16/10 is NOT forced, deliberately. Measured every content image on the
   site first: they are all WIDER than 16:10 already (natural ratios 1.51 to
   2.77 — several are side-by-side before/after diptychs, e.g.
   Reemplazar-Lip-filler-before-and-after-dublin.jpg at 800x304 = 2.63).
   Forcing 16/10 on those crops 31-44% off the LEFT AND RIGHT, which cuts into
   both halves of the pair. The only content image on the site TALLER than 16:10
   is the home before/after crossfade, which is exempt for its own documented
   reasons (see .vc-ba below). So the aspect rule had no image it would have
   helped and several it would have damaged. What the client actually saw — a
   photo eating the screen — is entirely the height cap, which is here.

   object-position: 50% 0 (TOP), not the browser default 50% 50%: where a crop
   does happen it must take from the bottom. Several before/after photos carry
   "Before"/"After" burnt into the TOP-LEFT corner of the JPG; a centre crop
   decapitates the labels and the image stops meaning anything. */
@media (max-width: 819px) {
	.entry-content figure.wp-block-image img {
		width: 100%;
		height: auto;
		max-height: 40vh;
		object-fit: cover;
		object-position: 50% 0;
	}
}
@media (orientation: landscape) and (max-height: 600px) {
	.entry-content figure.wp-block-image img {
		width: 100%;
		height: auto;
		max-height: 60vh;
		object-fit: cover;
		object-position: 50% 0;
	}
}

/* --- 2b. Home before/after crossfade: 4:3 top crop on phones --------------
   The Regenerative row's before/after (pattern 18624, .vc-ba) is the one image
   the client called out twice: two ~656x738 PORTRAIT frames crossfading on an
   8s opacity cycle. Uncropped it rendered 338x414 = 47.4vh portrait and 77.4vh
   landscape — the largest thing on the phone screen, and the reason the row
   read as "square and enormous".

   4:3, NOT 16:10, and that is a measured judgement rather than a preference.
   The treatment is on the NECK, which sits low in these frames (roughly y 280
   to 660 of 738), while the "Before"/"After" label is burnt into the TOP-LEFT
   corner. A crop must therefore start at the top (or the label goes) and reach
   as far down as it can (or the result goes). Rendered and compared both:
     16:10 -> shows to source y~410: label kept, but the frame is mouth and jaw
              with barely any neck. The before/after stops demonstrating
              anything, which is worse than a tall image.
     4:3   -> shows to source y~492: label, jaw AND the crepe-lined neck. Both
              frames read as a genuine comparison. This is what shipped.
   At 402x874 that is 338x254 = 29vh; the 40vh cap is the belt-and-braces
   ceiling for shorter screens, not the binding constraint.

   height:100% replaces the 107% trick ON MOBILE ONLY. The 107% existed to push
   a burnt-in credit band off the BOTTOM of the box; a 4:3 top crop already
   discards the bottom third of the source, so the band is gone either way and
   107% would only eat live neck. The real-text credit (.vc-credit, "Courtesy of
   Dr Hema Sundaram & Dr Antonello Tateo") is a separate paragraph below the
   figure and is untouched — it is a licence condition, not decoration.

   Both frames get the identical rule, so the crossfade cannot shift between
   states (verified by capturing the cycle at two phases).

   ⛔ DESKTOP KEEPS THE FULL PORTRAIT FRAME. The earlier exemption reasoning
   still holds there: at 1440 the figure is 481px in a 33% column, it costs
   nothing, and the uncropped frame shows more of the treated area. The
   breakpoint is exactly the two queries below — 819px wide, or a landscape
   viewport under 600px tall. */
@media (max-width: 819px), (orientation: landscape) and (max-height: 600px) {
	.home .vc-ba {
		aspect-ratio: 4 / 3;
		max-height: 40vh;
		/* grid-template-rows is the load-bearing half. .vc-ba is a 1-column grid
		   with an IMPLICIT row, so the row track sizes to the image's natural
		   380px and the aspect-ratio box just clips it — the frame looks right but
		   every img measurement reports the uncropped 380px, and object-fit never
		   runs. A definite `1fr` row makes the track equal the 4:3 box, so the
		   crop is a real object-fit crop that measures honestly. */
		grid-template-rows: 1fr;
	}
	.home .vc-ba img {
		/* height:100% (of the 1fr row) replaces the base rule's 107%. The 107%
		   existed only to push a burnt-in credit band off the bottom of the frame;
		   a 4:3 top crop already discards the bottom third of the source, so the
		   band is gone either way and 107% would only eat live neck.
		   ⚠️ An explicit height is REQUIRED — `align-self: stretch` does nothing
		   here. Box Alignment makes stretch behave as `start` for an item with an
		   intrinsic aspect ratio, and an <img> always has one. Tried it; the frame
		   stayed 380px and was merely clipped by overflow. */
		height: 100%;
		object-position: left top;
	}
}

/* --- 3. Section separation ------------------------------------------------
   Client, twice: consecutive sections merge into one white column, and
   explicitly "not the heading needs a different background" — the SECTIONS need
   separating, by a rule or a background transition.

   Two page shapes, two answers, because the markup genuinely differs:

   (a) HOME has real groupable units. The "Our treatments" area is three
       repeating [image + h3 + copy + Includes links + CTA] blocks, each one a
       core/columns element inside .vc-wrap. Measured at 402x874 they abut with
       nothing between them: headings at y 1268 / 2004 / 2740, ~736px apart, no
       boundary. So each block becomes a full-bleed BAND and the tint alternates
       grey / white / grey. Four visible boundaries out of one rule.

   (b) TREATMENT AND BLOG PAGES have nothing to group. The main column is a FLAT
       run of h2 / p / figure / details siblings (verified on profhilo,
       cheek-fillers, lip-fillers, acne-treatment), the `service` CPT is
       template_lock:contentOnly so blocks cannot be regrouped in the editor,
       and CSS has no "select every sibling until the next h2". These therefore
       get the sanctioned fallback: a hairline rule above each section heading
       plus real space around it. NOT a tinted heading — that is the thing the
       client ruled out.

   #F6F6F6 is palette slot 7, already the price panel's and the testimonial
   band's background; the rule colour is the same muted teal as the breadcrumb
   and price rows. No new tokens. Body #677284 on #F6F6F6 is 4.50:1 (AA).

   ⚠️ Seam check: the band immediately below this area (.vc-section--alt, the
   about statement) is deliberately left WHITE. The testimonial band under it is
   already palette-7 grey, so tinting --alt too would run three greys together
   into one undifferentiated block — the exact problem, restated in grey.

   Landscape phones are included: the same device rotated should not lose the
   section rhythm it had a second ago. Desktop is excluded — it reads as a
   3-column row there, not a stack, and the client's report is a phone report. */
@media (max-width: 819px), (orientation: landscape) and (max-height: 600px) {
	/* :not(--alt) scopes this to the "Our treatments" section. The second
	   .vc-section (--alt) contains the testimonial band, whose own columns would
	   otherwise be dragged out to full bleed and re-tinted a grey they already
	   have. */
	.home .vc-section:not(.vc-section--alt) .vc-wrap .wp-block-columns {
		/* Edge-to-edge without knowing the gutter: -(50vw - 50%) of margin pulls
		   the box out to the viewport edges, the same value as padding puts the
		   content back where it was. Self-correcting at 320, 402 and 819. */
		margin-inline: calc(50% - 50vw);
		padding: 1.75rem calc(50vw - 50%);
		/* The 3rem inline margin-bottom would leave a white stripe between two
		   bands; bands should abut. Inline style, so !important. */
		margin-bottom: 0 !important;
	}
	.home .vc-section:not(.vc-section--alt) .vc-wrap .wp-block-columns:nth-of-type(odd) {
		background: #F6F6F6;
	}

	.entry-content h2 {
		border-top: 1px solid rgba(74, 120, 116, 0.18);
		padding-top: 1.4rem;
		margin-top: 2rem;
		margin-bottom: 0.9rem;
		/* -1rem against the content column's 32px gutter: the rule reads as
		   spanning the section rather than as an underline on the text. NOT a
		   viewport-width bleed — in landscape the treatment template is still a
		   two-column row, and a 100vw rule would run straight through the price
		   aside. */
		margin-inline: -1rem;
		padding-inline: 1rem;
	}
	/* No rule above the FIRST heading on a page: on treatment pages it would
	   double up with the breadcrumb's own bottom border, and on every other page
	   it would draw a line immediately under the hero. */
	.entry-content h2:first-of-type {
		border-top: 0;
		padding-top: 0;
	}
	/* …and never inside a panel that already has its own background (the price
	   aside, the testimonial band): a rule plus negative margins there is chrome
	   on top of chrome. */
	.entry-content .has-background h2 {
		border-top: 0;
		padding-top: 0;
		margin-inline: 0;
		padding-inline: 0;
	}
}

/* --- 3b. Consent banner must not make the page bottom unreachable ---------
   The vc-consent banner (#cc-main, CookieConsent v3) is a FIXED bottom box —
   386x301 at 402x874, covering the bottom ~310px of the viewport until the
   visitor chooses. Overlaying content while it is up is what a consent banner
   does and is not something to fight; what is worth fixing is that the last
   ~310px of the document is unreachable behind it.

   CookieConsent puts `show--consent` on <html> only while the banner is
   showing, so this padding appears with the banner and disappears the moment
   consent is given. ⛔ The mu-plugin itself is NOT touched — this is one rule
   keyed off a class it already sets. */
@media (max-width: 819px) {
	html.show--consent body {
		padding-bottom: 21rem;
	}
}

/* --- 4. Headings that fit the phone ---------------------------------------
   Client: "Regenerative Anti-Aging Treatments" breaks after "Anti-Aging" and
   orphans "Treatments". Audit 2: h1 wraps on 7 of 20 treatment pages, with a
   one-word orphan on marionette-lines-filler-dublin.

   Measured, not guessed (cloned node, white-space:nowrap, 402px):
   that h3 string renders 340px at 24px and 311px at 22px against a 338px
   column — so 22px is the first size that fits, and it is what is set here.
   Siblings "Hair Loss Treatments" / "Skin Treatments" are far shorter.

   h1 gets a clamp rather than a fixed step-down so 320px phones scale too.
   6.6vw resolves to 26.5px at 402, which fits "Marionette Lines Filler Dublin"
   (329px in a 362px hero container). Genuinely long titles — the home hero's
   47-character line — still wrap, and that is correct; text-wrap:balance is
   what stops them wrapping to an orphan. */
@media (max-width: 819px) {
	.vc-tpl-hero h1 {
		font-size: clamp(24px, 6.6vw, 32px);
		text-wrap: balance;
	}
	.entry-content h2,
	.entry-content h3 {
		text-wrap: balance;
	}
	.entry-content h3 {
		font-size: 22px;
	}
}

/* --- 5. Tap targets -------------------------------------------------------
   Both measured under the 44x44 minimum on every page, on an older-skewing
   clinic audience. FAQ summary rows were 29.7px across 122 accordions site-wide
   — root cause is that core's <summary> ships no padding at all and
   .vc-faq-q (the a11y heading wrapper) resets its own to 0. Padding on the
   summary itself adds the hit area back without touching that reset.
   Not gated to mobile: a 30px row is under-sized with a mouse too. */
.wp-block-details summary {
	padding: 0.5rem 0;
}

/* Hamburger measured 36.8x31.2. Kadence sizes it in em off a 14px font
   (.mobile-toggle-open-container .menu-toggle-open{padding:.4em .6em}), so a
   min-box + centring is more robust than out-guessing its padding maths.
   #mobile-toggle is the element's own id — used to clear Kadence's own
   two-class rule without !important. */
#mobile-toggle.menu-toggle-open {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
}

/* --- 6. Promo sticky bar vs the floating chat bubble ----------------------
   Measured overlap on all three promo URLs at 874x402, 820x1180, 1440x900 and
   1920x1080 (i.e. every viewport >= the chat widget's own 600px breakpoint):
   bubble box 68x68 at right:15 bottom:15, sticky bar full-width to the viewport
   edge, ~42px of the bubble sitting on the "Call the clinic" tap target.

   ⛔ The chat widget is NOT touched. It is the site's busiest contact surface
   and is plugin-owned; the sticky bar is ours, so the sticky bar moves. Ending
   the bar 88px short of the right edge clears the bubble's 83px footprint with
   5px to spare and costs the bar nothing — its two CTAs simply share slightly
   less width.

   `body` prefixes the selector on purpose: .vc-promo-sticky's own rules live in
   an inline <style> inside the page content, which renders AFTER this
   stylesheet, so an equal-specificity rule here would lose on source order.
   One extra element in the selector wins it without !important. */
@media (min-width: 600px) {
	body .vc-promo-sticky {
		right: 88px;
	}
}

/* --- 7. Blog index intro ---------------------------------------------------
   /blog/ renders the posts page's own content above ul#archive-container (see
   vc_kadence_blog_index_intro in functions.php). Without this the only gap
   before the first post card is the last paragraph's own margin — measured 28px
   at 1440 against 80px between the hero and the intro, which reads as if the
   copy were a caption on the first article. --global-lg-spacing (2.5em) brings
   the two gaps to roughly the same size.

   The selector is deliberately over-qualified. Kadence prints its content CSS
   with print_styles('kadence-content') from inside index.php, i.e. in the BODY
   and therefore AFTER this stylesheet, so anything at equal specificity loses on
   source order. The rule that has to be beaten is
   `.content-title-style-hide .entry-content { margin: 0 }` at (0,2,0) — the blog
   index carries that body class because the archive title is hidden. Three
   classes take it without !important. Verified over CDP getMatchedStylesForNode:
   a bare `.vc-blog-intro` and a `body .vc-blog-intro` both computed to 0px. */
.site-main .entry-content.vc-blog-intro {
	margin-bottom: var(--global-lg-spacing, 2.5em);
}

/* The half that moved below the grid (0.6.24) needs the gap on the other side,
   and beats the same `.content-title-style-hide .entry-content { margin: 0 }`
   for the same reason — three classes, no !important. margin-bottom is still
   inherited from the rule above; both elements carry .vc-blog-intro. */
.site-main .entry-content.vc-blog-outro {
	margin-top: var(--global-lg-spacing, 2.5em);
}
