/* ----------------------------------------------------
   ROOT THEMES
---------------------------------------------------- */
:root {
    --primary: #CC3399;
    --primary-rgb: 204, 51, 153; /* Added this */
    /* Retargets Bootstrap 5.3's OWN "primary" variables to this site's actual brand pink, since
       bootstrap.min.css is used prebuilt (not recompiled from Sass with a custom $primary) - found
       while fixing the testimonial card's border-primary accent, which was rendering Bootstrap's
       stock blue (#0d6efd) instead of pink. Bootstrap's utilities (.text-primary, .border-primary,
       .bg-primary) and .btn-outline-primary all reference --bs-primary/--bs-primary-rgb via var(),
       so this one change corrects every bare use of them site-wide, not just this one card. Doesn't
       touch .btn-primary or the .bg-primary-subtle custom class below - those were already
       separately, correctly overridden to this same pink beforehand. (".text-primary-dark", used
       throughout this codebase, looks like a sibling custom class but isn't actually one - no such
       rule exists; every "pink dark heading" it appears to produce actually comes from the plain
       `h1, h2, h3, h4 { color: var(--primary-dark); }` element rule further down matching the
       heading tag itself. That's harmless on a heading, but it doesn't help a plain <a> - found
       when a new Services.razor card-title link rendered in Bootstrap's default link blue despite
       carrying "text-primary-dark", which lower down fixed by retargeting --bs-link-color(-rgb)/
       --bs-link-hover-color(-rgb) the same way.) */
    --bs-primary: #CC3399;
    --bs-primary-rgb: 204, 51, 153;
    /* a's own color comes from --bs-link-color-rgb (not -color) since Bootstrap 5.3 composes it
       with an opacity variable; :hover reads the -hover- pair. See the long comment above. */
    --bs-link-color: #A52A7A;
    --bs-link-color-rgb: 165, 42, 122;
    --bs-link-hover-color: #CC3399;
    --bs-link-hover-color-rgb: 204, 51, 153;
    --primary-light: #c7b7ff;
    /* WhatsApp's own green (#25D366) is 1.98:1 against white - the button's "Chat with me" and the icon circle were unreadable.
       These two are the accessible greens used instead (6.4:1 and 7.7:1 with white text). */
    --whatsapp-bg: #0b6b5e;
    --whatsapp-bg-hover: #075e54;
    --primary-dark: #A52A7A;
    --bg: #f9f7ff;
    --text: #333;
    --card-bg: #ffffff;
    --glow: 0 0 20px rgba(var(--primary-rgb), 0.6);
    --radius: 12px;
    --gradient: linear-gradient(135deg, #ffdd99, #cc3399);
    --color-light: #fefefe;
    --color-dark: #f0f4f8;
    --gradient-energy: linear-gradient(135deg, #f3f8ff 0%, #e0f7ff 100%);
}

/* Dark Theme */
[data-theme="dark"] {
    --bg: #1c1529;
    --text: #f0eaff;
    --card-bg: #291e3a;
    --glow: 0 0 20px rgba(200, 168, 255, 0.7);
    --gradient: linear-gradient(135deg, #3d2a63, #5f3ea8, #8e54c9);
}

/* ----------------------------------------------------
   FONTS
---------------------------------------------------- */
/* The @font-face rules for Nunito (body) and Quicksand (headings) live in Components/App.razor, not here: they need the fingerprinted
   URL (lib/fonts/nunito-latin.<hash>.woff2) so the font files are cached for a year ("immutable") instead of being re-checked after an
   hour - a CSS file cannot ask the static-asset system for a hashed URL, a Razor <style> can. The same file re-declares the Bootstrap Icons
   font the same way. Why the fonts are self-hosted at all, and why CJK falls back to the system font: see the comment there. */

/* ----------------------------------------------------
   GLOBAL
---------------------------------------------------- */
html {
    scroll-behavior: smooth;
/*    scroll-padding-top: 100px;*/
}

body {
    background: var(--bg);
    color: var(--text);
    font-family: 'Nunito', sans-serif;
    transition: background .4s, color .4s;
}

h1, h2, h3, h4 {
    font-family: 'Quicksand', sans-serif;
}

/*body[data-theme="light"] {
        --bg: var(--color-light);
        --gradient: var(--gradient-energy);
        color: #333;
    }

    body[data-theme="dark"] {
        --bg: #111;
        --gradient: linear-gradient(135deg, #222 0%, #333 100%);
        color: #eee;
    }*/

h1, h2, h3, h4 {
    font-weight: 600;
    color: var(--primary-dark);
}

.btn-primary {
    /* Bootstrap's generic .btn:hover/:active rules (background-color/border-color: var(--bs-btn-
       hover-bg) etc.) have higher specificity than this single .btn-primary class - two
       selectors (.btn + :hover) beat one - so they win over the plain `background: var(--primary)`
       below on hover/active regardless of source order, unless these per-state custom properties
       (which .btn:hover/:active actually read from) are set here too. Without this, Bootstrap's
       own hardcoded --bs-btn-hover-bg/--bs-btn-active-bg (#0b5ed7/#0a58ca, its default blue) shows
       through on hover/active even though the resting background above is correctly pink. */
    --bs-btn-hover-bg: var(--primary-dark);
    --bs-btn-hover-border-color: var(--primary-dark);
    --bs-btn-active-bg: var(--primary-dark);
    --bs-btn-active-border-color: var(--primary-dark);
    --bs-btn-disabled-bg: var(--primary);
    --bs-btn-disabled-border-color: var(--primary);
    background: var(--primary);
    border: none;
    border-radius: var(--radius);
    padding: 12px 24px;
    box-shadow: var(--glow);
    transition: transform .2s, box-shadow .3s;
}

    .btn-primary:hover {
        transform: translateY(-3px);
        /* Was rgba(142, 142, 246, 0.8) - a blue-purple glow left over from some earlier color
           scheme, clashing with this site's pink theme. --primary-rgb matches --glow's own resting
           box-shadow above, just brighter/wider for the hover state. */
        box-shadow: 0 0 25px rgba(var(--primary-rgb), 0.8);
    }

/* Bootstrap's compiled CSS bakes its button colors into hardcoded hex values on each
   .btn-outline-primary's own --bs-btn-* custom properties at build time (unlike .text-primary/
   .border-primary/.bg-primary, which reference the root --bs-primary variable directly) - so
   retargeting --bs-primary above doesn't reach outline buttons on its own; they need the same
   explicit override .btn-primary already gets below. */
.btn-outline-primary {
    padding-bottom: 10px;
    padding-top: 10px;
    --bs-btn-color: var(--primary-dark);
    --bs-btn-border-color: var(--primary);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--primary);
    --bs-btn-hover-border-color: var(--primary);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--primary);
    --bs-btn-active-border-color: var(--primary);
    --bs-btn-disabled-color: var(--primary-dark);
    --bs-btn-disabled-border-color: var(--primary);
}

/* Recommendation.razor gives the first (suggested) answer of every quiz question the class btn-primary-light, which had no
   rule, so that answer rendered as bare text with no button look at all, next to a solid pink button. A soft pink tint of the
   primary button: same family, quieter. The text is the darker pink (about 5.5:1 on the tint). */
/* The Privacy page's reset button: Bootstrap's secondary gray (#6c757d) is 4.45:1 on that page's light-gray panel; Bootstrap's own hover gray is 5.8:1. */
.btn-outline-secondary {
    --bs-btn-color: #5c636a;
    --bs-btn-disabled-color: #5c636a;
}

.btn-primary-light {
    --bs-btn-color: var(--primary-dark);
    --bs-btn-bg: rgba(var(--primary-rgb), 0.12);
    --bs-btn-border-color: rgba(var(--primary-rgb), 0.35);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--primary-dark);
    --bs-btn-hover-border-color: var(--primary-dark);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--primary-dark);
    --bs-btn-active-border-color: var(--primary-dark);
    --bs-btn-disabled-color: var(--primary-dark);
    --bs-btn-disabled-bg: rgba(var(--primary-rgb), 0.12);
    --bs-btn-disabled-border-color: rgba(var(--primary-rgb), 0.35);
}

/* Same class of bug as .btn-outline-primary above: Bootstrap's compiled CSS bakes its form
   control focus/checked colors into hardcoded hex (#86b7fe/#0d6efd, its default blue) rather than
   deriving them from --bs-primary/--bs-primary-rgb, so every InputText/InputSelect/InputDate/
   checkbox site-wide (Booking.razor's form is the highest-traffic example) shows a blue focus
   ring and blue checkboxes despite the pink theme everywhere else. */
.form-control:focus,
.form-select:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 0.25rem rgba(var(--primary-rgb), 0.25);
}

/* NOT reachable, tested directly: an open native <select>'s own popup list (the browser's
   dropdown showing all options) highlights the currently-selected row using the OS's system
   highlight color - confirmed empirically that neither `option:checked { background-color }` nor
   any other CSS reaches it here (this is the same in effectively every browser engine; it's
   drawn as OS chrome, outside the page's own rendering). Same story for <input type="date">'s
   actively-edited segment (e.g. the "09" in "09/28/2026" while typing) and its native calendar
   popup - `accent-color` and `::-webkit-datetime-edit-fields-wrapper` overrides were both tried
   and neither changed anything. The only way to fully theme those specific interactions is to
   replace the native <select>/<input type="date"> with a custom-built JS component - a real
   redesign, not a CSS fix, and one that trades away the native mobile date-wheel/OS picker UX. */

.form-check-input:checked {
    background-color: var(--primary);
    border-color: var(--primary);
}

.form-check-input:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 0.25rem rgba(var(--primary-rgb), 0.25);
}

.section {
    padding: 80px 0;
    transition: all 1s ease;
}

/* 80px was tuned for desktop; stacked with a preceding element's own bottom margin (e.g. the
   Home hero's mb-5), it reads as a disproportionately large empty band on a narrow phone screen
   where vertical space is scarce. */
@media (max-width: 768px) {
    .section {
        padding: 40px 0;
    }
}

.section-light {
    background: var(--bg);
}

/* ----------------------------------------------------
   BACKGROUNDS
---------------------------------------------------- */
.energy-mesh-bg {
    background-color: #ffffff;
    /* A soft blend of rose, lavender, and pale blue */
    background-image: radial-gradient(at 80% 0%, hsla(289, 100%, 96%, 1) 0px, transparent 50%), radial-gradient(at 0% 20%, hsla(255, 100%, 94%, 1) 0px, transparent 50%), radial-gradient(at 80% 50%, hsla(340, 100%, 96%, 1) 0px, transparent 50%), radial-gradient(at 0% 100%, hsla(22, 100%, 96%, 1) 0px, transparent 50%);
    background-attachment: fixed; /* Keeps the background still while you scroll */
    background-size: cover;
}

/* Ensure text remains readable on this new background */
.section-transparent {
    background: rgba(255, 255, 255, 0.6);
    backdrop-filter: blur(10px); /* Frosted glass effect */
    border-radius: 20px;
    border: 1px solid rgba(255, 255, 255, 0.5);
}

/* Custom Brand Backgrounds */
.bg-primary-light {
    background-color: var(--primary-light) !important;
}

/* The initials on Home's lilac testimonial avatar: the dark pink is only 3.6:1 on the lilac, this plum is 5.4:1 (ColourContrastTests). */
.avatar-circle-sm.bg-primary-light {
    color: #7a1b5c;
}

/* Subtle Backgrounds for Avatars & Badges */
.bg-primary-subtle {
    background-color: rgba(var(--primary-rgb), 0.15) !important;
}

.bg-success-subtle {
    background-color: #e6fffa !important;
}

/* Ensure text inside subtle backgrounds remains readable */
.text-success {
    color: #059669 !important;
}

.text-warning {
    color: #d97706 !important;
}

/* Pink as TEXT. The brand pink (#CC3399) is 4.67:1 on white but only 3.6-4.4:1 on the tinted badges, the pale page background and the
   pink-tinted gradient areas - under the 4.5:1 minimum (WCAG 1.4.3) - and .text-primary is used 236 times. Text gets the darker pink
   (--primary-dark, 5.0-6.5:1 on every one of those backgrounds); buttons, borders, bars and icons keep the brighter brand pink.
   Not on icons (.bi: a graphic only needs 3:1, and the vivid pink is the brand) and not on accordion buttons (they have their own
   colour rules, including white when open). Bootstrap's .text-primary is !important, so this must be too. */
.text-primary:not(.bi):not(.accordion-button) {
    color: var(--primary-dark) !important;
}

/* The online/offline line on the Contact page sits on a pink tint, where .text-success's green is only ~3:1. (The footer's status is on
   the dark footer, where the lighter green is the right one, so .text-success itself is unchanged.) */
.text-success-dark {
    color: #046c4e !important;
}

/* ----------------------------------------------------
   HOVER EFFECTS
---------------------------------------------------- */
/* Ensures smooth transition for the hover effect */
.group-hover-effect {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
    cursor: pointer; /* Visual cue that the whole box is clickable */
}

    /* On hover, lift the card and increase the glow */
    .group-hover-effect:hover {
        transform: translateY(-5px);
        box-shadow: 0 10px 25px rgba(204, 51, 153, 0.4); /* Stronger magenta glow */
        border-color: var(--primary);
    }

        /* Optional: Make the button look active when hovering the card */
        .group-hover-effect:hover .btn-outline-primary {
            background-color: var(--primary);
            color: white;
            box-shadow: 0 0 10px var(--primary);
        }

/* ----------------------------------------------------
   KEYBOARD FOCUS RING
---------------------------------------------------- */
/* One focus indicator for the whole site (WCAG 2.4.7, and 1.4.11: at least 3:1 against what is next to it). Two bands, so it shows on
   any background: white next to the element (visible on the dark footer and the pink bars) and dark pink outside it (6.5:1 on white).
   Before this, buttons and nav links showed Bootstrap's stock blue at 25-50% opacity - about 1.4:1 on the page background and not the
   brand colour - and an open accordion button showed nothing. :focus-visible means a mouse click does not draw it. */
:focus-visible {
    outline: 2px solid var(--primary-dark);
    outline-offset: 2px;
    box-shadow: 0 0 0 2px #fff;
}

/* Bootstrap's own focus rules for these (class + :focus-visible) have the same specificity and would draw their blue ring instead;
   this comes later in the cascade, so it wins. */
:is(.btn, .btn-close, .nav-link, .navbar-toggler, .page-link):focus-visible {
    outline: 2px solid var(--primary-dark);
    outline-offset: 2px;
    box-shadow: 0 0 0 2px #fff;
}

/* Bootstrap draws the menu button's ring on :focus, i.e. after a mouse click too. */
.navbar-toggler:focus:not(:focus-visible) {
    box-shadow: none;
}

/* Menu items fill the whole row, so the ring sits inside it. */
.dropdown-item:focus-visible {
    outline: 2px solid var(--primary-dark);
    outline-offset: -3px;
    box-shadow: none;
}

/* Headings and sections that script focuses (Blazor's FocusOnNavigate puts focus on the page's h1 after a page change; Booking.razor
   focuses its thank-you heading) are not controls: they need to receive focus, not to look focused. */
[tabindex="-1"]:focus-visible {
    outline: none;
    box-shadow: none;
}

/* ----------------------------------------------------
   ANIMATIONS
---------------------------------------------------- */
/* Fade/slide in on paint. A previous attempt made this genuinely scroll-gated (opacity:0 until
   scroll-effects.js added .visible), which fixed the "outrun the animation, see a blank gap"
   race below - but on an @rendermode InteractiveServer page (Booking and the recommendation quiz used to be two; both are server-rendered now),
   the static-prerendered DOM gets replaced once the SignalR circuit connects, and that swap can
   happen in a way that leaves the element stuck at its pre-.visible style permanently (verified:
   .visible was present in classList, yet opacity/transform stayed at their pre-reveal values
   indefinitely - not merely a timing race, content genuinely never recovered). Given that failure
   mode - permanently invisible content on the site's own booking form - is worse than the mobile
   gap it was meant to fix, and the gap itself turned out to be caused by .section's fixed 80px
   padding (see the @media rule on that class below), not by this animation, this stays back on
   the original unconditional animation: on-paint and independent of any JS/circuit timing. */
.fade-in {
    opacity: 0;
    transform: translateY(30px);
    animation: fadeInUp 1s forwards;
}

    .fade-in.visible {
        opacity: 1;
        transform: translateY(0);
    }

@keyframes fadeInUp {
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes fadeIn {
    to {
        opacity: 1;
    }
}

.slide-up {
    opacity: 0;
    transform: translateY(40px);
    animation: slideUp 1s forwards;
}

@keyframes slideUp {
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Same leftover blue-purple glow bug as .btn-primary:hover above, and more widespread - used
   alongside .btn-primary on ~24 buttons site-wide (Home, Services, Products, Booking, Contact,
   the recommendation quiz). Also matters for .btn-primary itself: this rule is defined LATER in
   this file than .btn-primary:hover's own box-shadow fix, so on any element carrying both classes
   (the common case - e.g. "btn btn-primary glow-hover"), this one wins the box-shadow property at
   equal specificity, silently undoing that fix wherever the two are combined. */
.glow-hover:hover {
    box-shadow: 0 0 35px rgba(var(--primary-rgb), 0.8);
}

/* ----------------------------------------------------
   CARDS
---------------------------------------------------- */
.card-energy {
    background: var(--card-bg);
    border-radius: var(--radius);
    padding: 20px;
    box-shadow: var(--glow);
    transition: transform .2s;
}

    .card-energy:hover {
        transform: translateY(-6px);
    }

    .card-energy img {
        border-radius: var(--radius);
        box-shadow: var(--glow);
    }

/* ----------------------------------------------------
   TESTIMONIALS (For Pages.zip/Testimonials.razor)
---------------------------------------------------- */
.card-testimonial {
    background: var(--card-bg);
    border-radius: var(--radius);
    padding: 30px;
    box-shadow: var(--glow);
    position: relative;
    border-left: 5px solid var(--primary);
}

    .card-testimonial .quote-text {
        font-style: italic;
        font-size: 1.05rem;
        margin-bottom: 15px;
    }

    .card-testimonial .client-info strong {
        color: var(--primary-dark);
        display: block;
        margin-top: 5px;
    }

/* FOOTER */
.footer {
    padding: 25px;
    text-align: center;
    background: #1e1e2f;
    color: #f0f0f0;
}

    .footer a {
        color: #ffdd99; /* Soft accent for links */
        text-decoration: none;
    }

        .footer a:hover {
            text-decoration: underline;
        }

/* a.footer-link (element + class), not just .footer-link - .footer a above is ALSO one class
   plus the element selector (same specificity), and it was defined first: without matching that
   specificity here, .footer a's text-decoration: none silently won regardless of source order,
   and this whole rule's underline never actually rendered. */
a.footer-link {
    color: rgba(255,255,255,0.7);
    /* Was text-decoration: none, underlined only on :hover - indistinguishable at rest from the
       plain (non-link) text around it in this column (headings, copyright, address), and hover
       doesn't exist on a touchscreen at all. A permanent, subtler underline (dotted, low-opacity)
       keeps the resting state calm while still reading as clickable; :hover below still switches
       to a solid one for the usual stronger feedback. */
    text-decoration: underline;
    text-decoration-style: dotted;
    text-decoration-color: rgba(255,255,255,0.35);
    font-size: 0.9rem;
    transition: color 0.3s ease;
    margin-bottom: 8px;
    display: inline-block;
}

    a.footer-link:hover {
        color: var(--primary);
        text-decoration: underline;
        text-decoration-style: solid;
        text-decoration-color: currentColor;
    }

footer {
    padding: 40px 0;
}

    footer .app-footer-dark {
        background: linear-gradient(135deg, #1e1e2f, #2a2a3f);
        color: #f0f0f0; /* Light gray text */
    }

    /* The footer's section headings are <h2 class="h5"> (see Footer.razor): a real h2 for the page outline, styled as the h5 they
       used to be. The h1-h4 rule near the top of this file gives every h2 the display font (Quicksand), but the old h5 had no such
       rule and used the body font (Nunito) - so keep the body font here, or the footer headings would change typeface. */
    footer .app-footer-dark h2.h5 {
        font-family: inherit;
    }

.disclaimer-box {
    border-top: 1px solid #333;
    padding-top: 0.5rem;
}

/* Live Status Pulse Dot */
.status-pulse {
    width: 8px;
    height: 8px;
    background-color: #22c55e;
    border-radius: 50%;
    display: inline-block;
    box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);
    animation: status-ring 1.5s infinite;
}

@keyframes status-ring {
    0% {
        transform: scale(0.95);
        box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);
    }

    70% {
        transform: scale(1);
        box-shadow: 0 0 0 6px rgba(34, 197, 94, 0);
    }

    100% {
        transform: scale(0.95);
        box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
    }
}

/* Contact Icon Buttons */
.contact-icon-btn {
    width: 45px;
    height: 45px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-size: 1.25rem;
    transition: all 0.3s ease;
    text-decoration: none;
}

    .contact-icon-btn:hover {
        transform: translateY(-3px);
        box-shadow: 0 5px 15px rgba(0,0,0,0.1);
        color: white;
        filter: brightness(1.1);
    }

.email {
    background-color: var(--primary);
}

.facebook {
    background-color: #1877F2;
}

.instagram {
    background: linear-gradient( 45deg, #feda75, /* yellow */
    #fa7e1e, /* orange */
    #d62976, /* pink */
    #962fbf, /* purple */
    #4f5bd5 /* blue */
    );
}

.telegram {
    background-color: #0088cc;
}

.whatsapp {
    background-color: var(--whatsapp-bg);
}

/* Brand-accurate icon GLYPH colors (as opposed to the filled circle badges above) - for contexts
   like Contact.razor's icon list, which previously used generic Bootstrap semantic classes
   (.text-primary/.text-danger/.text-info) as a rough color approximation. That was already
   inexact (Bootstrap's info cyan isn't quite Telegram's blue), and became flatly wrong for
   Facebook once --bs-primary was retargeted to this site's own pink brand color elsewhere in
   this file - the Facebook icon started rendering pink instead of Facebook's actual blue. These
   reuse the exact same hex values as the filled badges above, so both representations of each
   brand stay in sync. */
.text-facebook {
    color: #1877F2 !important;
}

.text-telegram {
    color: #0088cc !important;
}

.text-whatsapp {
    color: #25D366 !important;
}

/* Instagram has no single brand color (its real mark is the gradient above) - clipping that same
   gradient to the icon glyph's text shape reproduces it accurately, rather than picking one flat
   color that's only ever an approximation. */
.text-instagram {
    background: linear-gradient(45deg, #feda75, #fa7e1e, #d62976, #962fbf, #4f5bd5);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

/* Contact.razor's social buttons (.btn.btn-outline-primary + this class) carry real, externally-
   branded icon colors (Facebook blue, Instagram's gradient, Telegram blue) - .btn-outline-primary's
   normal hover state is a solid, saturated pink fill, which makes some of those hard to see
   against it (Instagram's own pink gradient segment in particular nearly disappears into an
   identically-hued background). A light pink tint keeps the hover feedback clearly on-brand while
   keeping every icon's real color legible against it. Overrides via the same --bs-btn-hover-*
   custom properties .btn-outline-primary itself sets (see that rule's own comment) rather than
   raw properties + !important - same specificity, later in this file, so it wins on source order
   alone, no !important tug-of-war needed. Scoped to this one class rather than changed on
   .btn-outline-primary itself, since every other outline-primary button on the site doesn't have
   this problem and its current solid-pink hover is fine for them. */
.btn-outline-primary.social-link-btn {
    --bs-btn-hover-bg: rgba(var(--primary-rgb), 0.12);
    --bs-btn-hover-color: var(--primary-dark);
    --bs-btn-hover-border-color: var(--primary);
}

/* ----------------------------------------------------
   GRID
---------------------------------------------------- */
.grid-3 {
    display: grid;
    /* min(320px, 100%), not a bare 320px: a column can never be wider than the grid itself, so on
       a phone narrower than ~370px (320px minimum column + page padding) the cards no longer
       overflow their container - off-centre at 360px, scrolling sideways at 320px. Identical to
       the old rule whenever the grid is at least 320px wide. */
    grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
    gap: 2rem;
}

/* ----------------------------------------------------
   Testimonial
---------------------------------------------------- */
.card-testimonial {
    background: #ffffff;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
    /* No border here (previously a `border: ... !important` shorthand) - every caller already
       carries Bootstrap's own border-0 + border-top border-4 border-primary utility classes to
       get a 4px primary-colored top accent, but this rule's !important shorthand set all four
       sides and silently beat those utilities, leaving every testimonial card with a flat, plain
       1px grey border instead of the intended accent. */
}

    .card-testimonial:hover {
        transform: translateY(-5px);
        box-shadow: 0 1rem 3rem rgba(0,0,0,0.1) !important;
    }

.italic {
    font-style: italic;
}

/* Used on small uppercase labels (footer location line, the homepage trust row, the 404 page) - had no rule, so it did nothing. */
.tracking-wider {
    letter-spacing: 0.05em;
}

/* Avatar Base Style */
.avatar-circle, .avatar-circle-sm {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    border-radius: 50%;
}

/* GDPR Compliance */
.cookie-banner {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(255, 255, 255, 0.98);
    backdrop-filter: blur(8px);
    border-top: 1px solid var(--primary-light);
    z-index: 10000; /* Higher than footer and nav */
    padding: 8px 0;
    box-shadow: 0 -5px 20px rgba(0,0,0,0.05);
}

/* --cookie-banner-height is set by App.razor's updateCookieBannerHeightVar() from the banner's
   actual offsetHeight the moment it's shown - it wraps to a much taller stacked layout below
   Bootstrap's md breakpoint, and text length differs per language, so a single guessed constant
   here couldn't track either. Falls back to 60px (this banner's typical single-line desktop
   height) for the brief window before JS runs. This is the page-level fallback for pages with no
   sticky booking bar (/booking, the only one where MainLayout doesn't render it) - everywhere
   else, body.has-cookie-banner .has-sticky-booking-bar below (Footer's own dark section) already
   reserves enough space to clear the combined cookie-banner+sticky-bar stack on its own, so this
   rule backs off entirely there instead of adding a second, redundant reservation on top of it -
   the two stacking together (one on <body>, one inside it) is what made the gap at the bottom of
   the page far bigger than intended. */
body.has-cookie-banner {
    padding-bottom: calc(var(--cookie-banner-height, 60px) + 10px);
}

body.has-cookie-banner:has(.has-sticky-booking-bar) {
    padding-bottom: 0;
}

.cookie-banner.d-none {
    display: none !important;
}

/* On a phone the banner is ONE row - the notice on the left, Accept and Close on the right - instead of the notice over two stacked buttons.
   Same words, smaller type: about 85 px tall (about 70 in Chinese) where the stack was 149 (128), so a first-time visitor on a 375 x 812
   screen loses 17% of it to the banner and the booking bar instead of 25%. From the md breakpoint up it is the one-line banner it always was. */
.cookie-banner-text {
    font-size: 0.75rem;
    line-height: 1.25;
}

@media (max-width: 767.98px) {
    .cookie-banner {
        padding: 2px 0;
    }
}

@media (min-width: 768px) {
    .cookie-banner-text {
        font-size: 0.875em;
        line-height: inherit;
    }
}

/* ----------------------------------------------------
   FEATURED PRODUCT STYLES
---------------------------------------------------- */
.card-featured {
    border: 2px solid var(--primary-light) !important;
    box-shadow: var(--glow) !important;
    transform: scale(1.02);
}

/* White text on a solid dark pink (6.5:1; ColourContrastTests). It was a yellow-to-pink gradient, which is only 1.3:1 at its left end. */
.featured-badge {
    position: absolute;
    top: 15px;
    right: 15px;
    background: var(--primary-dark);
    color: white;
    padding: 5px 15px;
    border-radius: 50px;
    font-size: 0.8rem;
    font-weight: bold;
    z-index: 5;
    box-shadow: 0 4px 10px rgba(204, 51, 153, 0.3);
}

.featured-reason {
    background-color: var(--bg);
    border-left: 4px solid var(--primary);
    padding: 10px 15px;
    border-radius: 0 8px 8px 0;
    margin: 1rem 0;
    font-size: 0.9rem;
}

.category-block {
    scroll-margin-top: 100px; /* Prevents the header from covering the title when jumping to a category */
}

.product-feature-list {
    list-style: none;
    padding-left: 0;
}

    .product-feature-list li {
        position: relative;
        padding-left: 1.5rem; /* Space for the icon */
        margin-bottom: 0.5rem;
    }

        .product-feature-list li::before {
            content: "";
            position: absolute;
            left: 0;
            top: 3px; /* Centers with first line of text */
            width: 16px;
            height: 16px;
            background-color: var(--primary);
            /* Using a Bootstrap Check Circle as a mask */
            -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-check-circle-fill' viewBox='0 0 16 16'%3E%3Cpath d='M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-check-circle-fill' viewBox='0 0 16 16'%3E%3Cpath d='M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z'/%3E%3C/svg%3E");
            -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
            -webkit-mask-size: contain;
            mask-size: contain;
        }

/* ----------------------------------------------------
   Products Overview
---------------------------------------------------- */

/* Healy Comparison Table Styles */
/* Ensure the table scrolls smoothly on mobile */
.table-responsive {
    scrollbar-width: thin;
    scrollbar-color: var(--primary-light) transparent;
}

/* Same class of bug as .btn-outline-primary/.form-select's chevron elsewhere in this file:
   Bootstrap's .table-hover row highlight reads --bs-table-hover-bg, which derives from
   --bs-emphasis-color-rgb (a neutral grey/black), not --bs-primary-rgb - so every .table-hover
   sitewide (HealyComparisonTable.razor, Services.razor, ProductRecommendation.razor) highlighted
   rows in grey regardless of the pink theme everywhere else. */
.table-hover > tbody > tr:hover > * {
    --bs-table-hover-bg: rgba(var(--primary-rgb), 0.12);
}

/* Custom shadow for the PDF section */
.btn-primary.glow-hover:hover {
    box-shadow: var(--glow);
    transform: translateY(-2px);
    transition: all 0.3s ease;
}

/* Make Obsidian Edition stand out slightly */
.product-col:last-child {
    background-color: rgba(0, 0, 0, 0.03) !important;
    border-left: 1px solid rgba(0, 0, 0, 0.05);
}

.healy-comparison-table .table {
    border-collapse: separate;
    border-spacing: 0;
}

/* Feature column stays left-aligned */
.feature-col {
    text-align: left;
    background-color: #f8f9fa;
    width: 30%;
}

/* Vertical alignment for all cells */
.healy-comparison-table td,
.healy-comparison-table th {
    vertical-align: middle;
}

/* Add a slight hover effect to rows */
.healy-comparison-table tbody tr:hover {
    background-color: rgba(var(--primary-rgb), 0.02);
}

/* Sticky header fix for better UX */
.healy-comparison-table thead {
    position: sticky;
    top: 0;
    z-index: 10;
}

.accordion-button {
    background-color: white !important;
    border: 1px solid rgba(var(--primary-rgb), 0.1) !important;
    color: var(--primary) !important;
}

    .accordion-button:not(.collapsed) {
        box-shadow: none;
        background-color: var(--primary) !important;
        color: white !important;
    }

/* The rule above removes the box-shadow of an OPEN accordion button, and Bootstrap draws its focus ring with a box-shadow - so an open
   button showed no focus indicator at all, and a closed one only Bootstrap's 25%-opacity blue. Both now get the site's focus ring in
   its inset form (the accordion items clip with overflow-hidden, so an outer ring would be cut off): dark pink outside, white inside,
   which is visible on the white closed button AND on the pink open one. A mouse click does not show it (:focus-visible only). */
.accordion-button:focus-visible {
    outline: 3px solid var(--primary-dark);
    outline-offset: -3px;
    box-shadow: inset 0 0 0 5px #fff;
}

.accordion-button:focus:not(:focus-visible) {
    box-shadow: none;
}

        .accordion-button:not(.collapsed)::after {
            filter: brightness(0) invert(1); /* Turns the arrow white when expanded */
        }

.accordion-item {
    transition: all 0.3s ease;
}

    /* Give the expanded accordion a slight "lift" effect */
    .accordion-item:has(.show) {
        box-shadow: 0 1rem 3rem rgba(0,0,0,0.1) !important;
    }

/* Ensure the table inside the accordion handles overflow correctly */
.accordion-body {
    overflow-x: auto;
}

    .accordion-body.bg-light {
        background-color: #f8f9fa !important; /* Light grey backdrop for the table */
    }

    /* Ensure table looks sharp inside the light background */
    .accordion-body .healy-comparison-table .table {
        border: 1px solid rgba(0,0,0,0.05);
    }

.rounded-5 {
    border-radius: 2.5rem !important;
}

.btn-lg {
    font-size: 1.1rem;
    letter-spacing: 0.5px;
    transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

    .btn-lg:hover {
        transform: scale(1.05);
    }

/* ----------------------------------------------------
   Products Details
---------------------------------------------------- */

/* Custom styling for the program accordion in details */
.custom-program-accordion .accordion-item {
    border: 1px solid rgba(0,0,0,0.08) !important;
}

.custom-program-accordion .accordion-button::after {
    margin-left: initial;
}

.custom-program-accordion .accordion-button:not(.collapsed) {
    background-color: var(--primary);
    color: white;
}

    .custom-program-accordion .accordion-button:not(.collapsed) small {
        color: rgba(255,255,255,0.8) !important;
    }

    .custom-program-accordion .accordion-button:not(.collapsed) .badge {
        background-color: var(--primary-light);
    }

.custom-program-accordion .accordion-button.collapsed .badge {
    background-color: rgba(var(--primary-rgb), 0.15);
}

/* The small brand-coloured dot before each program group's name in ProductDetail.razor's accordion (the markup always had the
   element, with a comment saying "add a CSS dot", but no rule gave it a size, so it was an empty box). */
.program-dot {
    flex-shrink: 0;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--primary);
}

/* Breadcrumb styling */
.breadcrumb-item + .breadcrumb-item::before {
    /* Was a corrupted replacement-character glyph (U+FFFD) - likely an encoding mismatch from a
       past save - rendering as a visible tofu box in every breadcrumb sitewide. */
    content: "\203A";
    font-size: 1.2rem;
    vertical-align: middle;
    line-height: 1;
}

/* Was var(--text-muted), a variable nothing defines - so the colour was silently dropped and the links just inherited the body
   colour. Bootstrap's own muted colour is what was meant. The hover colour is the darker pink: the brand pink on the pale page
   background is only ~4.4:1. */
.breadcrumb a {
    text-decoration: none;
    color: var(--bs-secondary-color);
    transition: color 0.2s;
}

    .breadcrumb a:hover {
        color: var(--primary-dark);
    }

.line-clamp-2 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.product-card-hover {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

    .product-card-hover:hover {
        transform: translateY(-5px);
        box-shadow: 0 10px 20px rgba(0,0,0,0.1) !important;
    }

/* ----------------------------------------------------
   Anchor-target highlight (Services.razor's #healing/#aura/#coaching cards)
---------------------------------------------------- */
/* Now that the testimonials/Home "path" cards link straight to each service's own dedicated
   page, nothing on the site currently lands here via the #anchor - but Services.razor's three
   cards render side by side on wider screens, all at the same vertical position, so arriving via
   any future #healing/#aura/#coaching link would otherwise give no visual sign of which one you
   were sent to. This is left in defensively rather than relied on. */
#healing:target, #aura:target, #coaching:target {
    box-shadow: 0 0 0 4px rgba(var(--primary-rgb), 0.6) !important;
    transition: box-shadow 0.6s ease-out;
}

/* ----------------------------------------------------
   Product Recommendation
---------------------------------------------------- */

/* --- Floating Animation for Product Images --- */
.floating-anim {
    animation: floating 3s ease-in-out infinite;
}

@keyframes floating {
    0% {
        transform: translateY(0px);
    }

    50% {
        transform: translateY(-15px);
    }

    100% {
        transform: translateY(0px);
    }
}

/* --- Progress Bar Polish ---
   The quiz's "Question 2 of 4" bar is a native <progress> (Recommendation.razor): the browser and screen readers know its value, and
   the length comes from the value/max attributes - no inline width. Each engine styles the track and the filled part through its own
   pseudo-element, so the three are set together. */
.quiz-progress {
    display: block;
    width: 100%;
    height: 6px;
    margin-bottom: 0.5rem;
    border: 0;
    border-radius: 10px;
    -webkit-appearance: none;
    appearance: none;
    background-color: #e9ecef;
    color: var(--primary);                      /* Firefox draws its bar in the text colour of the element */
}

.quiz-progress::-webkit-progress-bar {
    background-color: #e9ecef;
    border-radius: 10px;
}

.quiz-progress::-webkit-progress-value {
    background-color: var(--primary);
    border-radius: 10px;
    box-shadow: 0 0 10px var(--primary);
    transition: width 0.5s ease;
}

.quiz-progress::-moz-progress-bar {
    background-color: var(--primary);
    border-radius: 10px;
}

/* "Why this fits you:" on the result card is a real heading (h2) so the page's outline has no gap, but looks like the small bold line it
   always was: the site's heading rules would otherwise give an h2 the display font and the dark pink. */
.recap-title {
    font-family: inherit;
    color: inherit;
}

@media print {
    /* Hide everything except the result card. .whatsapp-float and #cookieBanner both live in
       MainLayout.razor as siblings of <main>, not inside it - so neither this list nor the
       "main:has(.product-recommendation) > *:not(.product-recommendation)" rule further down
       (which only reaches <main>'s own children) ever caught them; both rendered right on top of
       a printed report until added here explicitly. #backToTop is already handled via its own
       no-print class instead of being listed here. */
    nav, footer, .btn, .btn-link, .quiz-progress, .breadcrumb, .no-print,
    .whatsapp-float, #cookieBanner, #pageTransitionBar {
        display: none !important;
    }

    /* Reset layout for paper */
    body {
        background: white !important;
        font-size: 12pt;
    }

    /* The layout wrapper (MainLayout.razor) paints the site's soft gradient, which the white body above does not cover: with "Background graphics"
       ticked in the print dialog it printed as a large pink block under the report and over the top of the page. */
    .energy-mesh-bg {
        background: none !important;
    }

    .container {
        width: 100% !important;
        max-width: none !important;
        margin: 0 !important;
        padding: 0 !important;
    }

    /* Professional Card Styling */
    .card {
        border: 1px solid #eee !important;
        box-shadow: none !important;
    }

    .card-energy {
        box-shadow: none !important;
        border: 1px solid #eee !important;
    }

    .bg-primary-subtle {
        background-color: #f0f7ff !important;
        -webkit-print-color-adjust: exact; /* Forces background color in Chrome/Safari */
    }

    /* The result card's right-hand column keeps its on-screen p-4 p-md-5 padding (measured: ~120px
       of it goes completely unused here) since the buttons that padding used to give breathing
       room around (.btn, .btn-link - Shop/Save PDF/Discovery Chat/Back/Start New) are all hidden
       above. Left as-is, that reads as a large unexplained blank gap before "Next Steps" starts -
       tightened for print specifically, not changed on-screen where the buttons still need it. */
    .product-recommendation .col-lg-7 {
        padding: 0.5rem 0.5rem 0 !important;
    }

    .product-recommendation .d-print-block {
        margin-top: 0.5rem !important;
        padding-top: 0.5rem !important;
    }

    /* Sized for screen (a big, breathing-room product photo) - measured at ~276px tall on a
       report where every other section is print-tuned, this alone was the single largest
       contributor to spilling onto a second page. The on-screen size/padding are untouched.
       The padding is 0.75rem above and below, not less: the picture has to clear the card's top edge (the card is overflow-hidden and
       rounded) with room to spare, because the transparent margin inside the PNGs is only 3-14px and differs per product. */
    .product-recommendation .col-lg-5.bg-light {
        padding: 0.75rem 0.5rem !important;
    }

    /* A printed page is a still. On screen the picture floats (translateY 0 to -15px, a 3 s loop), and a printout caught it at whatever moment
       the dialog was opened: lifted, the picture rose 10px above the card's top edge and the card cut the top of the device off (Healy
       Discover, Evolve and Pro lost 6.5px of picture); at rest it sat 5px under the border. Held still it is the same in every printout. */
    .product-recommendation .floating-anim {
        animation: none !important;
        transform: none !important;
    }

    /* 140px: the report fills only about 60% of an A4 page, so the picture can be larger than the 90px it was squeezed to (it was 276px on
       screen, the reason the report once ran onto a second page); the longest language on the shortest paper (Letter) still fits on one. */
    .product-recommendation img {
        max-height: 140px !important;
    }

    .product-recommendation h1.display-5 {
        font-size: 16pt !important;
        margin-bottom: 0.4rem !important;
    }

    .product-recommendation .bg-primary-subtle.mb-4 {
        margin-bottom: 0.4rem !important;
        padding: 0.4rem 0.6rem !important;
    }

    .product-recommendation h2.h1 {
        font-size: 14pt !important;
        margin-bottom: 0.25rem !important;
    }

    .product-recommendation .col-lg-7 p.text-muted {
        margin-bottom: 0 !important;
    }

    /* Ensure table is legible */
    .table {
        border: 1px solid #dee2e6 !important;
    }

    th {
        background-color: #f8f9fa !important;
        -webkit-print-color-adjust: exact;
    }

    main:has(.product-recommendation) > *:not(.product-recommendation) {
        display: none;
    }

    .product-recommendation {
        margin-bottom: 0 !important;
        margin-top: 0 !important;
        padding-bottom: 0 !important;
        padding-top: 0 !important;
        background: none !important; /* .section-light's pale lavender: with "Background graphics" on it printed as a tinted rectangle that stops at the paper margins */
    }

        /* Report header - was a CSS ::before pseudo-element with the text hardcoded directly
           into content: (and, having no connection to IStringLocalizer, a fixed English string
           regardless of the active site language - it also said "Frequency & Wellness", not
           this site's actual name). Replaced by a real, localized element in the markup itself
           (Recommendation.razor's own d-print-block header) - this just styles it. */
        .print-report-header {
            display: block;
            text-align: center;
            font-weight: bold;
            font-size: 14pt;
            margin-bottom: 8px;
            color: #1a3a5f;
            border-bottom: 2px solid #1a3a5f;
            padding-bottom: 4px;
        }

        .print-report-header small {
            display: block;
            font-weight: normal;
            font-size: 10pt;
            color: #666;
            margin-top: 4px;
        }
}

/* ----------------------------------------------------
   Page transition indicator
---------------------------------------------------- */

/* See MainLayout.razor's remarks on #pageTransitionBar for why this exists. Starts collapsed and
   invisible; scroll-effects.js's 'enhancednavigationstart'/'enhancednavigationend' listeners toggle
   .is-loading/.is-done to sweep it across and fade it out. */
#pageTransitionBar {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    height: 4px;
    background: var(--primary);
    box-shadow: 0 0 8px var(--primary); /* same glow treatment as .quiz-progress elsewhere */
    z-index: 2000;
    opacity: 0;
    pointer-events: none;
}

#pageTransitionBar.is-loading {
    width: 75%;
    opacity: 1;
    transition: width 0.6s ease-out, opacity 0.15s ease-in;
}

#pageTransitionBar.is-done {
    width: 100%;
    opacity: 1;
    transition: width 0.2s ease-out;
}

/* ----------------------------------------------------
   WhatsApp floating button
---------------------------------------------------- */

/* Floating WhatsApp Button. Default bottom offset assumes no other bottom-anchored bar is
   present. .raise-for-sticky-bar (added in MainLayout.razor only when ShowStickyBookingBar is
   true - it's hidden on /booking itself) raises it clear of .sticky-booking-bar instead. */
.whatsapp-float {
    position: fixed;
    bottom: 24px;
    right: 30px;
    background-color: var(--whatsapp-bg);
    color: #FFF;
    border-radius: 50px;
    text-align: center;
    font-size: 24px; /* Icon size */
    box-shadow: 0px 4px 15px rgba(0,0,0,0.2);
    z-index: 1000;
    display: flex;
    align-items: center;
    padding: 12px 20px;
    text-decoration: none;
    transition: all 0.3s ease;
}

.whatsapp-float.raise-for-sticky-bar {
    bottom: 72px; /* clears .sticky-booking-bar (~55px tall) - see that rule's own comment */
}

/* Hide text on mobile, show only icon */
.whatsapp-text {
    font-size: 16px;
    font-weight: 600;
    margin-left: 10px;
    font-family: sans-serif;
}

.whatsapp-float:hover {
    background-color: var(--whatsapp-bg-hover);
    color: #FFF;
    transform: translateY(-5px);
}

/* #backToTop mirrors .whatsapp-float's default/raised split above and for the same reason
   (/booking has no sticky booking bar to clear). Its bottom offset used to live in an inline
   style in MainLayout.razor; moved into these classes so it can vary with ShowStickyBookingBar
   the same way .whatsapp-float does. */
#backToTop {
    bottom: 24px;
}

#backToTop.raise-for-sticky-bar {
    bottom: 90px;
}

/* Both the WhatsApp float and #backToTop need to clear whichever bottom-anchored bar is
   currently tallest. When the sticky booking bar is present (.raise-for-sticky-bar), showing the
   cookie banner stacks the sticky booking bar on top of itself (see body.has-cookie-banner
   .sticky-booking-bar below) rather than replacing it, so this needs to clear that combined
   height - hence var(--cookie-banner-height) (see App.razor's updateCookieBannerHeightVar) plus
   the sticky bar's own ~55px and a small margin. When the sticky bar is absent (e.g. /booking),
   there's nothing to stack on top of - just clear the cookie banner itself, matching the same
   "+10px" buffer body.has-cookie-banner already uses for its own reserved space (see below). */
body.has-cookie-banner .whatsapp-float.raise-for-sticky-bar,
body.has-cookie-banner #backToTop.raise-for-sticky-bar {
    bottom: calc(var(--cookie-banner-height, 60px) + 70px) !important;
}

body.has-cookie-banner .whatsapp-float:not(.raise-for-sticky-bar),
body.has-cookie-banner #backToTop:not(.raise-for-sticky-bar) {
    bottom: calc(var(--cookie-banner-height, 60px) + 10px) !important;
}

/* Pulse Animation */
.whatsapp-float {
    animation: whatsapp-pulse 2s infinite;
}

@keyframes whatsapp-pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(11, 107, 94, 0.7);
    }

    70% {
        box-shadow: 0 0 0 15px rgba(11, 107, 94, 0);
    }

    100% {
        box-shadow: 0 0 0 0 rgba(11, 107, 94, 0);
    }
}

@media screen and (max-width: 768px) {
    .whatsapp-float {
        bottom: 20px;
        right: 20px;
        /* Force a perfect circle */
        padding: 0;
        width: 60px;
        height: 60px;
        border-radius: 50%;
        /* Center the icon perfectly */
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .whatsapp-float.raise-for-sticky-bar {
        bottom: 68px; /* raised to clear .sticky-booking-bar */
    }

    .whatsapp-text {
        display: none; /* Icon only on mobile to save space */
    }

    .whatsapp-float i {
        margin: 0;
        font-size: 30px; /* Slightly larger icon for thumb-tapping */
    }

    #backToTop {
        bottom: 20px;
    }

    #backToTop.raise-for-sticky-bar {
        bottom: 68px; /* raised to clear .sticky-booking-bar, matching .whatsapp-float */
    }
}

/* ----------------------------------------------------
   REDUCED MOTION
---------------------------------------------------- */
/* For visitors whose system says "reduce motion" (vestibular disorders, migraines, or simply a preference). Every animation and
   transition still runs to its END state - so the fade-in/slide-up content that starts at opacity 0 is simply shown, not left
   invisible - but instantly, once, with no delay. That stops the endless ones (the WhatsApp button's pulse, the "online" dot, the
   floating product image, the frequency ripples, the 404 icon) and the smooth scrolling. scroll-effects.js and the inline handlers
   check the same media query for their scripted scrolling. Home.razor's ripple SVG sets a still picture of its own. */
@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        animation-delay: 0s !important;
        transition-duration: 0.01ms !important;
        transition-delay: 0s !important;
        scroll-behavior: auto !important;
    }

    /* A spinner IS the information ("busy"); Bootstrap already slows it down under this setting, so keep it turning. */
    .spinner-border,
    .spinner-grow {
        animation-duration: 1.5s !important;
        animation-iteration-count: infinite !important;
    }
}

/* ----------------------------------------------------
   Sticky booking bar
---------------------------------------------------- */
/* A persistent, low-friction "Book Now" affordance - this is a lead-gen site whose one real
   conversion goal is a booked session, but previously the only persistent CTA was the WhatsApp
   button (a chat channel, not a direct path to booking). Kept slim and bottom-anchored rather
   than another floating circular button so it doesn't compete for the same corner as WhatsApp/
   back-to-top - those two just get their bottom offset raised instead (see above) so nothing
   overlaps. .has-sticky-booking-bar (applied to Footer's own dark disclaimer section, not the
   page's outer wrapper - see Footer.razor) reserves matching space at the end of the page so this
   fixed bar never covers the footer's own last line once a visitor scrolls all the way down -
   same problem, same fix, as body.has-cookie-banner already solves for the cookie banner below. */
.sticky-booking-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 98;
    background: var(--primary);
    padding: 12px 16px;
    box-shadow: 0 -4px 15px rgba(0, 0, 0, 0.15);
}

/* 64px covers .sticky-booking-bar's own ~55px at its default bottom:0 position, plus a small
   buffer. When the cookie banner is also shown, the bar moves much higher (see below) - this
   static value doesn't follow it, so the footer's reserved space is no longer big enough and its
   last line can scroll in behind the (now higher) bar. body.has-cookie-banner overrides it below
   with the same math used for the bar's own position, kept in sync deliberately rather than
   computed from one shared value, since the two properties (a fixed element's `bottom` vs. a
   flow element's `padding-bottom`) aren't expressible as the same calc() safely. !important is
   load-bearing here, not defensive: this element also carries Bootstrap's .py-4 utility class,
   which sets its own padding-bottom marked !important by Bootstrap itself (utilities are meant to
   always win regardless of source order) - without matching it, this rule silently loses to
   .py-4's 24px and the reserved space never actually applies at all. Verified this the hard way:
   it "looked" fine at 24px only by luck (a paragraph's own line-height leaves a little natural
   buffer past its last character), until the much larger cookie-banner-shown gap below exposed a
   real, severe text/bar overlap that pointed straight back to this override never having taken
   effect in the first place. */
.has-sticky-booking-bar {
    padding-bottom: 64px !important;
}

/* The cookie banner (z-index 10000) sits well above this bar's own z-index:98 and spans the same
   full-width bottom:0 strip, so left alone it would simply cover the booking bar completely
   instead of the two coexisting. Stacking this bar flush against the cookie banner's own real
   height (var(--cookie-banner-height), set by App.razor's updateCookieBannerHeightVar - a fixed
   guess here previously undershot the banner's actual height once it wrapped to its taller
   stacked-on-narrow-widths layout, which is what let the two visually overlap instead of
   stacking) keeps both visible at once with no gap between them; it collapses back to bottom:0
   automatically once the banner is dismissed, since that just removes the body class this
   selector depends on. */
body.has-cookie-banner .sticky-booking-bar {
    bottom: var(--cookie-banner-height, 60px);
}

/* Matches the rule above: the bar's height (~55px) plus this same buffer, on top of wherever the
   bar now sits once the cookie banner pushed it up. !important for the same reason as the base
   rule above - this still needs to beat Bootstrap's .py-4. */
body.has-cookie-banner .has-sticky-booking-bar {
    padding-bottom: calc(var(--cookie-banner-height, 60px) + 64px) !important;
}

/* ----------------------------------------------------
   SIZING AND LAYOUT HELPERS
---------------------------------------------------- */
/* These replaced the inline style="" attributes that used to be scattered through the markup (116 of them: the same width/height pair
   14 times, the same max-width 10 times, ...). One place to change a size, nothing for a future Content-Security-Policy to forbid in the
   markup, and a source rule (SourceRulesTests) keeps it that way. They are named by what they set: size-N is an N-px square, h-/mh-/
   min-h- are heights, mw-N is a max-width of N px, fs-NNN is a font-size in hundredths of a rem (fs-075 = 0.75rem), object-* is
   object-fit. They are last in the file on purpose: an inline style used to beat every class, so these must come after Bootstrap's
   (and the rules above) to keep winning the same way. */
.size-35 { width: 35px; height: 35px; }
.size-50 { width: 50px; height: 50px; }
.size-70 { width: 70px; height: 70px; }

.h-150 { height: 150px; }
.h-200 { height: 200px; }
.h-220 { height: 220px; }
.h-250 { height: 250px; }
.mh-160 { max-height: 160px; }
.mh-320 { max-height: 320px; }
.mh-500 { max-height: 500px; }
.min-h-120 { min-height: 120px; }
.min-vh-70 { min-height: 70vh; }

.mw-380 { max-width: 380px; }
.mw-500 { max-width: 500px; }
.mw-600 { max-width: 600px; }
.mw-650 { max-width: 650px; }
.mw-700 { max-width: 700px; }
.mw-800 { max-width: 800px; }
.mw-900 { max-width: 900px; }
.mw-1000 { max-width: 1000px; }

.object-cover { object-fit: cover; }
.object-contain { object-fit: contain; }
.aspect-531-534 { aspect-ratio: 531 / 534; }
.aspect-1920-1000 { aspect-ratio: 1920 / 1000; }

.fs-050 { font-size: 0.5rem; }
.fs-065 { font-size: 0.65rem; }
.fs-070 { font-size: 0.7rem; }
.fs-075 { font-size: 0.75rem; }
.fs-080 { font-size: 0.8rem; }
.fs-085 { font-size: 0.85rem; }
.fs-400 { font-size: 4rem; }
.fs-800 { font-size: 8rem; }
.lh-16 { line-height: 1.6; }
.opacity-30 { opacity: 0.3; }
.text-site { color: var(--text); }
.bg-card-light { background-color: var(--color-light); }
.drop-glow { filter: drop-shadow(var(--glow)); }

/* The short pink rule under a page's title. */
.accent-bar { height: 4px; background: var(--primary); border-radius: 2px; }
.accent-bar-60 { width: 60px; }
.accent-bar-80 { width: 80px; }

/* ProductDetail's sticky image column: Bootstrap's .sticky-top is top:0, z-index:1020. */
.sticky-top.sticky-top-100 { top: 100px; z-index: 1; }

/* One-off looks that were inline styles on a single element. */
.disclaimer-box .disclaimer-text { line-height: 1.6; font-size: 0.75rem; }   /* the footer's small print; .x-small (Footer's <style>) would otherwise win */
.photo-quote-card { max-width: 250px; border-left: 4px solid var(--primary); background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(5px); }
.affiliate-lead { font-size: 1.1rem; color: #444; }
.hero-cta { bottom: 10%; width: 100%; }
.practitioner-photo { width: 250px; height: 250px; object-fit: cover; border: 5px solid var(--primary-light); }
.badge-seal { width: 60px; height: 60px; border: 4px solid white; }
.disclaimer-line { font-size: 0.8rem; letter-spacing: 0.5px; opacity: 0.8; }
.disclaimer-callout { background-color: #fff9db; border: 1px solid #ffec99; }

/* The back-to-top button: hidden until scrolled (scroll script sets display inline), pinned bottom-left. */
#backToTop { position: fixed; left: 30px; width: 50px; height: 50px; z-index: 99; display: none; transition: 0.3s; }

/* The homepage tagline as real text, shown under the banner on phones (the banner image has the same words painted into it, too small to read there). */
.hero-tagline { font-family: 'Quicksand', sans-serif; font-weight: 600; font-size: 1.4rem; line-height: 1.3; color: var(--primary-dark); text-align: center; margin-top: 0; }

/* A row of stars (StarRating.razor) never breaks in the middle. */
.star-rating { white-space: nowrap; }

/* ---- Navigation ----------------------------------------------------------------------------------------------------------------------- */
/* The current page in the menu: the dark brand pink plus an underline, so it does not rest on colour alone (WCAG 1.4.1). Bootstrap's own
   active colour is black, only a shade darker than the other links. The weight is left alone, so the links keep their width (and do not
   shift) when the current page changes. */
.navbar { --bs-navbar-active-color: var(--primary-dark); }
.navbar-nav .nav-link.active { text-decoration: underline; text-decoration-color: var(--primary); text-decoration-thickness: 2px; text-underline-offset: 0.45em; }

/* The current item inside a dropdown (a product line, a service, the language): Bootstrap's blue is not a colour of this site. */
.dropdown-menu { --bs-dropdown-link-active-bg: var(--primary-dark); --bs-dropdown-link-active-color: #fff; }

/* "Skip to main content": invisible until a keyboard user reaches it (Bootstrap's visually-hidden-focusable hides it), then a clear button in
   the corner. */
.skip-link {
    position: fixed;
    top: 0.5rem;
    left: 0.5rem;
    z-index: 2000;
    padding: 0.6rem 1.1rem;
    border: 2px solid var(--primary-dark);
    border-radius: 0.5rem;
    background: #fff;
    color: var(--primary-dark);
    font-weight: 700;
    text-decoration: none;
}

/* Between 1200 and 1399 px the container is 1140 px wide, and the Malay menu (the longest: "Laman Utama", "Soalan Lazim", "Semua Perkhidmatan")
   is 8 px too wide with its caret and its button, so labels wrapped onto two lines. A little less space between the links gives it about
   30 px to spare; from 1400 px (a 1320 px container) the roomy default is back. */
@media (min-width: 1200px) and (max-width: 1399.98px) {
    .navbar { --bs-navbar-nav-link-padding-x: 0.375rem; }
}

/* The booking panel is where the service pages' "Book" buttons scroll to (#booking-form-panel). Its first element is the "You're booking: ..."
   banner: a little room above it keeps the banner clear of the very edge of the screen (on a phone it would sit flush with it). */
#booking-form-panel { scroll-margin-top: 1rem; }
