/* The H Clinic — CI theme (STAGE 1): rose-gold brand palette + Anuphan */
:root {
    --color-primary: #AB7E69;
    --color-primary-2: #B78E78;
    --color-beige: #E4C8B3;
    --color-beige-2: #E8CDBA;
    --color-cream: #F2EBE5;
    --color-ink: #3E2B23;
}

* { box-sizing: border-box; }
@media (prefers-reduced-motion: no-preference) {
    html { scroll-behavior: smooth; }
}
body { margin: 0; font-family: 'Anuphan', system-ui, -apple-system, "Segoe UI", sans-serif; color: var(--color-ink); background: var(--color-cream); line-height: 1.5; }
h1, h2, h3, h4, h5, h6 { font-family: 'Anuphan', system-ui, -apple-system, "Segoe UI", sans-serif; }
.container { max-width: 1100px; margin: 0 auto; padding: 0 16px; }
a { color: inherit; }

/* 11.9.2026 follow-up — soft satin-texture background (was flat white),
   cropped from the same fabric-fold photo used behind the old logo file
   (client wanted that texture reused, not the icon itself) — same visual
   language as the why-us cards / certificate image / footer already on
   this site. background-color is the fallback/edge-fill for viewports
   wider than the 1920px source. */
.site-header {
    border-bottom: 1px solid var(--color-beige); position: sticky; top: 0;
    background: var(--color-beige-2) url('../images/header-bg.jpg') center/cover no-repeat;
    z-index: 10; transition: box-shadow .25s ease;
}
.site-header.is-scrolled { box-shadow: 0 2px 12px rgba(62, 43, 35, 0.10); border-bottom-color: transparent; }
.header-inner { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; gap: 16px; transition: padding .25s ease; }
.site-header.is-scrolled .header-inner { padding: 8px 16px; }
.logo { font-weight: 700; text-decoration: none; display: flex; align-items: center; }
/* 11.9.2026 follow-up — reverted to the full mark+wordmark lockup (client
   wants the clinic name visible under the icon, not the icon alone), so
   this needs to be noticeably taller than a plain icon logo would — the
   lockup is a vertical stack (frame+H on top, "THE H CLINIC" text below),
   and the text row is a small fraction of the total height, so anything
   much shorter than this makes the wordmark unreadable. */
.logo img { height: 62px; width: auto; display: block; transition: height .25s ease; }
.site-header.is-scrolled .logo img { height: 48px; }
/* 26.8.2026 follow-up — center the nav in the header now that .header-cta
   (call/LINE) is gone; flex:1 + justify-content:center lets it center itself
   within the space left of the logo, rather than sitting wherever
   .header-inner's justify-content:space-between happens to push it. */
.main-nav { display: flex; flex: 1; justify-content: center; gap: 12px; flex-wrap: wrap; font-size: 14px; }
.main-nav a { text-decoration: none; position: relative; padding-bottom: 2px; }
/* STAGE 7 Group E — underline expands from the left on hover/focus. */
.main-nav a::after {
    content: '';
    position: absolute; left: 0; bottom: -2px;
    width: 0; height: 2px;
    background: var(--color-primary);
    transition: width .25s ease;
}
.main-nav a:hover::after, .main-nav a:focus-visible::after { width: 100%; }
.header-cta { display: flex; gap: 8px; }

/* P1-1 — mobile hamburger toggle + off-canvas drawer. .main-nav's inline
   link list hides below ~900px (matching the breakpoint the sticky-bar/
   floating-line swap already uses everywhere else in this file) and this
   button takes over; .header-cta stays visible at every width regardless. */
.nav-toggle {
    display: none;
    flex-direction: column; justify-content: center; align-items: center; gap: 5px;
    width: 36px; height: 36px; padding: 6px;
    border: 0; background: transparent; cursor: pointer;
}
.nav-toggle-bar { display: block; width: 100%; height: 2px; background: var(--color-ink); border-radius: 2px; }

.nav-drawer { display: none; position: fixed; inset: 0; z-index: 40; }
.nav-drawer.is-open { display: block; }
.nav-drawer-backdrop {
    position: absolute; inset: 0; background: rgba(62, 43, 35, .5);
    opacity: 0; transition: opacity .3s ease;
}
.nav-drawer.is-visible .nav-drawer-backdrop { opacity: 1; }
.nav-drawer-panel {
    position: absolute; top: 0; right: 0; bottom: 0; width: min(280px, 80vw);
    background: #fff; padding: 20px; display: flex; flex-direction: column; gap: 2px;
    transform: translateX(100%); transition: transform .3s ease;
    box-shadow: -8px 0 24px rgba(62, 43, 35, .18);
    overflow-y: auto;
}
.nav-drawer.is-visible .nav-drawer-panel { transform: translateX(0); }
.nav-drawer-panel a {
    padding: 12px 8px; text-decoration: none; color: var(--color-ink);
    border-bottom: 1px solid var(--color-beige); font-size: 15px;
}
.nav-drawer-close {
    align-self: flex-end; background: transparent; border: 0;
    font-size: 30px; line-height: 1; cursor: pointer; padding: 4px 8px; color: var(--color-ink);
}
.nav-drawer-cta { display: flex; gap: 8px; margin-top: 16px; }
.nav-drawer-cta .btn { flex: 1; text-align: center; }

/* Mobile-only flag language switch, next to the hamburger. Desktop keeps
   the existing text .lang-switch link inside .main-nav instead. Base rule
   (hidden) must come BEFORE the mobile media query override below, or the
   override loses the cascade at equal specificity — same gotcha this file
   has hit before with mobile-first overrides. */
.lang-flags { display: none; align-items: center; gap: 6px; margin-left: auto; }
.lang-flag { display: inline-flex; border-radius: 3px; overflow: hidden; opacity: .45; transition: opacity .15s ease; line-height: 0; }
.lang-flag.is-active { opacity: 1; box-shadow: 0 0 0 2px var(--color-primary); }
.lang-flag svg { width: 22px; height: 15px; display: block; }

/* 26.8.2026 follow-up — call/LINE buttons removed from the header on both
   mobile and desktop now (still reachable via the nav drawer, the mobile
   sticky-bar, and the floating chat launcher's popup). */
.header-cta { display: none; }

@media (max-width: 899px) {
    .main-nav { display: none; }
    .nav-toggle { display: inline-flex; }
    .lang-flags { display: flex; }
}
@media (min-width: 900px) {
    .nav-toggle, .nav-drawer { display: none !important; }
}

.btn { display: inline-block; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 14px; transition: transform .2s ease, box-shadow .2s ease, background-color .2s ease; }
.btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(62, 43, 35, 0.15); }
.btn-call, .btn-line { background: var(--color-beige-2); color: var(--color-ink); }
/* Touch-target fix: 8px vertical padding alone worked out to ~37px tall,
   short of the ~44px minimum recommended for a comfortable tap target.
   Scoped to mobile (not desktop, where a mouse pointer doesn't need it)
   via inline-flex centering so the label stays centered at the taller
   height rather than just top-aligned within extra padding. */
@media (max-width: 899px) {
    .btn { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
}
.btn-primary { background: var(--color-primary); color: #fff; }

main { min-height: 60vh; padding: 24px 16px; }
.hero { text-align: center; padding: 48px 16px; position: relative; overflow: hidden; }
.hero-image-only { padding: 0 16px; position: relative; overflow: hidden; }
.hero-banner-img { display: block; width: 100%; max-width: 1100px; height: auto; margin: 0 auto; border-radius: 12px; }

/* P1-1 — unified hero: media + a gradient-scrimmed text overlay sitting on
   top of it, on both desktop and mobile. Supersedes the STAGE 7 Group B
   .hero-video-bg scaffolding below as the swap-in mechanism for a future
   video — that block is now inert/superseded, left in place rather than
   ripped out mid-phase.
   .hero-media-wrap is the shared positioning context for the media +
   overlay (rather than positioning the overlay against the outer
   .hero-feature section) so the gradient always lines up with the actual
   image box, not the section's full-bleed padding area. */
.hero-feature { padding: 16px; }
.hero-media-wrap { position: relative; max-width: 1100px; margin: 0 auto; border-radius: 12px; overflow: hidden; }
.hero-media { display: block; width: 100%; object-fit: cover; }
.hero-overlay {
    position: absolute; inset: 0; display: flex; align-items: flex-end;
    background: linear-gradient(180deg, rgba(62, 43, 35, .05) 0%, rgba(62, 43, 35, .55) 55%, rgba(62, 43, 35, .85) 100%);
}
.hero-overlay-inner { width: 100%; padding: 24px 20px; text-align: center; color: #fff; box-sizing: border-box; }
.hero-headline { margin: 0 0 8px; font-size: 26px; line-height: 1.3; color: #fff; }
.hero-tagline { margin: 0 0 16px; font-size: 15px; color: #fff; opacity: .9; }
.hero-trust-points {
    list-style: none; display: flex; flex-wrap: wrap; justify-content: center; gap: 14px 20px;
    padding: 0; margin: 0 0 20px; font-size: 13px; font-weight: 600; color: #fff;
}
.hero-trust-points li { display: flex; align-items: center; gap: 6px; }
.hero-trust-points svg { width: 18px; height: 18px; flex-shrink: 0; color: #fff; }
.hero-overlay-inner .hero-cta-bar { max-width: none; margin: 0; padding: 0; }

/* Below ~900px the 16:9 lock (from x-placeholder's ratio prop) is too
   short to hold headline + tagline + trust points + 3 CTA buttons
   legibly, so this overrides it with a fixed height instead — still a
   static value known before the image loads, so no CLS. Two classes
   (.hero-feature .hero-media) rather than just `.hero-media` so this
   reliably beats the P1-0 `.placeholder-media { height: 100% }` rule
   regardless of source order — same specificity would otherwise let
   whichever rule comes later in the file win. */
@media (max-width: 899px) {
    .hero-feature .hero-media { height: 480px; }
    /* Client request 26.8.2026 (refined again) — split into two groups on
       mobile: headline alone pinned to the top; tagline+trust-points+CTA
       buttons pinned to the bottom together, both centered, with the photo
       visible in between. Text stays centered via .hero-overlay-inner's
       existing text-align: center. */
    .hero-overlay { align-items: stretch; }
    .hero-overlay-inner { display: flex; flex-direction: column; height: 100%; }
    .hero-overlay-inner .hero-tagline { margin-top: auto; }
}

@media (min-width: 900px) {
    .hero-overlay {
        align-items: center;
        background: linear-gradient(90deg, rgba(62, 43, 35, .65) 0%, rgba(62, 43, 35, .3) 55%, transparent 100%);
    }
    .hero-overlay-inner { max-width: 460px; padding: 0 48px; text-align: left; }
    .hero-overlay .hero-trust-points { justify-content: flex-start; }
    .hero-overlay-inner .hero-cta-bar { justify-content: flex-start; }
}

/* STAGE 7 Group B — hero entrance (fade + slide-up, staggered by JS via
   inline transition-delay). Load-triggered, not scroll-triggered, since
   the hero is always above the fold — see the reduced-motion kill-switch
   above for how this gets neutralized when the user asks for no motion. */
.hero-enter {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity .6s ease-out, transform .6s ease-out;
}
.hero-enter.hero-enter-visible { opacity: 1; transform: translateY(0); }

/* Hero video, prepared ahead of the clinic supplying real (text-free)
   footage — see STAGE7-design-polish-spec.md Group B. Inert until a
   `.hero-video-bg` container + <video> is actually added to the markup;
   the crossfade-from-poster and reduced-motion behavior are ready now so
   wiring in the real <video> later is a template-only change. */
.hero-video-bg { position: absolute; inset: 0; z-index: 0; }
.hero-video-bg video {
    width: 100%; height: 100%; object-fit: cover;
    opacity: 0; transition: opacity .8s ease;
}
.hero-video-bg video.is-ready { opacity: 1; }
.hero-image-only.has-video-bg .hero-banner-img { position: relative; z-index: 0; }
@media (prefers-reduced-motion: reduce) {
    /* A silently autoplaying background video is itself a motion
       preference — keep the static poster/banner image instead. */
    .hero-video-bg { display: none; }
}

/* Subtle drifting accent (sol-style) — a soft blurred brand-color blob,
   pure CSS (::before), no markup needed. Very low-contrast on purpose so
   it reads as ambient texture, not a distraction. */
.hero::before, .hero-image-only::before {
    content: '';
    position: absolute;
    top: -60px; right: -60px;
    width: 220px; height: 220px;
    border-radius: 50%;
    background: radial-gradient(circle, var(--color-beige-2) 0%, transparent 70%);
    opacity: .5;
    filter: blur(4px);
    z-index: -1;
    animation: hero-accent-float 10s ease-in-out infinite;
}
@keyframes hero-accent-float {
    0%, 100% { transform: translate(0, 0); }
    50% { transform: translate(-16px, 16px); }
}

/* 10.9.2026 client feedback — video background (was a plain color, then a
   still-photo slot that was never activated). Video sits absolutely
   positioned behind the content at z-index 0; .footer-inner/.footer-bottom
   get z-index 1 to stay above it. Dark overlay (::before) keeps the
   existing white/beige text legible over the moving footage.
   Follow-up feedback — overlay darkened .78 -> .88 (text was unreadable);
   the video is mobile-only now (see .site-footer-bg-img below), text-shadow
   added as a second safety net over whichever background is showing. */
.site-footer { position: relative; background-color: var(--color-ink); color: var(--color-beige-2); margin-top: 48px; overflow: hidden; }
.site-footer-bg-img, .site-footer-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
/* z-index:1 (not 0) — at equal z-index, paint order follows DOM order, and
   this pseudo-element is the first "child" of .site-footer, so at z-index:0
   it painted BEHIND the img/video instead of over them, leaving the video/
   image fully unfiltered (this was the actual cause of the illegible text
   feedback, not just a color/opacity issue). z-index:1 puts it above the
   background media and still below .footer-inner/.footer-bottom (same
   z-index, later in DOM order so on top). */
.site-footer::before { content: ''; position: absolute; inset: 0; background: rgba(62, 43, 35, .65); z-index: 1; }
.footer-inner, .footer-bottom { position: relative; z-index: 1; }
.footer-inner { display: flex; flex-wrap: wrap; gap: 24px; padding: 32px 16px; }
.footer-col { flex: 1 1 200px; }
.footer-col a { color: var(--color-beige-2); }
.footer-bottom { padding: 12px 16px; border-top: 1px solid rgba(255, 255, 255, 0.15); font-size: 13px; }
.footer-col, .footer-bottom p { text-shadow: 0 1px 3px rgba(0, 0, 0, .35); }
/* Video (a 540x960 portrait clip) only suits the narrower mobile-width
   footer bar — at desktop widths object-fit:cover forced a heavy zoom/crop
   on the wide, short bar, so swap to the still frame there instead. */
@media (min-width: 900px) {
    .site-footer-video { display: none; }
}
/* A silently autoplaying background video is itself a motion preference —
   hide it and rely on the still frame + overlay tint, same convention as
   the hero's .hero-video-bg kill-switch. */
@media (prefers-reduced-motion: reduce) {
    .site-footer-video { display: none; }
}

.sticky-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; background: #fff; border-top: 1px solid var(--color-beige); z-index: 20; }
.sticky-item { flex: 1; text-align: center; padding: 12px 0; text-decoration: none; font-size: 14px; color: var(--color-ink); }
.sticky-item-primary { background: var(--color-primary); color: #fff; }
@media (min-width: 900px) { .sticky-bar { display: none; } }

/* 26.8.2026 follow-up — now shown on every screen size (was desktop-only;
   the client noticed mobile had no equivalent floating contact button,
   since the header call/LINE buttons and the mobile sticky-bar don't cover
   the fuller FB/IG/TikTok/email popup). Sits above the mobile sticky-bar
   (~50px tall) with room to spare at bottom:80px, same offset as before. */
.floating-widget {
    position: fixed; right: 20px; bottom: 80px; z-index: 20;
    display: flex;
    transition: bottom .2s ease;
}
/* Follow-up fix — the cookie banner's "ยอมรับ" button sits at the same
   bottom-right corner (any screen size), close enough at bottom:80px that a
   real click/tap could land on the chat button instead. Pushed up only
   while the banner is actually visible (body.has-cookie-banner, toggled in
   main.js) — drops back to the normal spot once accepted, so this doesn't
   permanently waste the corner once the banner is gone. */
body.has-cookie-banner .floating-widget { bottom: 170px; }
button.btn { border: 0; font: inherit; cursor: pointer; } /* reset default <button> chrome, .btn is used on both <a> and <button> now */

.floating-chat-btn {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 56px; height: 56px; border-radius: 50%;
    background: var(--color-primary); color: #fff; border: 0; cursor: pointer;
    box-shadow: 0 4px 14px rgba(62, 43, 35, .28);
    transition: transform .2s ease, box-shadow .2s ease;
}
.floating-chat-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(62, 43, 35, .32); }
.floating-chat-btn svg { width: 26px; height: 26px; }
/* Small "unread message" notification dot, top-right of the icon. */
.floating-chat-badge {
    position: absolute; top: 2px; right: 2px;
    width: 12px; height: 12px; border-radius: 50%;
    background: #e5484d; border: 2px solid #fff;
    animation: floating-chat-badge-pulse 2.6s ease-out infinite;
}
@keyframes floating-chat-badge-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(229, 72, 77, .5); }
    50% { box-shadow: 0 0 0 6px rgba(229, 72, 77, 0); }
}

/* Bottom-anchored slide-up panel (not a centered modal — see .contact-popup
   for that variant, used by the pre-hero bar instead). Same hidden/.is-open/
   .is-visible pattern as everywhere else on this site, but no backdrop:
   closes on outside click instead (see main.js). */
.chat-launcher-panel {
    display: none;
    position: fixed; right: 20px; bottom: 148px; z-index: 21;
    width: min(300px, calc(100vw - 40px));
    background: #fff; border-radius: 12px;
    box-shadow: 0 10px 30px rgba(62, 43, 35, .25);
    opacity: 0; transform: translateY(16px);
    transition: opacity .25s ease, transform .25s ease, bottom .2s ease;
}
/* Follows the chat button's own cookie-banner offset above, so the panel
   still opens directly above the button regardless of which position it's
   currently in. */
body.has-cookie-banner .chat-launcher-panel { bottom: 238px; }
.chat-launcher-panel.is-open { display: block; }
.chat-launcher-panel.is-visible { opacity: 1; transform: translateY(0); }
.chat-launcher-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 14px 16px; border-bottom: 1px solid var(--color-beige);
}
.chat-launcher-header h4 { margin: 0; font-size: 15px; }
.chat-launcher-close { background: transparent; border: 0; color: var(--color-ink); font-size: 22px; line-height: 1; cursor: pointer; padding: 4px; }
.chat-launcher-panel .contact-popup-list { padding: 8px; }

.cookie-banner { position: fixed; bottom: 0; left: 0; right: 0; background: var(--color-ink); color: #fff; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; gap: 12px; z-index: 30; }
/* Real bug found 26.8.2026 follow-up — the `hidden` attribute (set on
   accept, main.js) was being silently overridden by the `display: flex`
   rule directly above (same specificity, author CSS always wins ties over
   the UA stylesheet's [hidden]{display:none}, regardless of source order)
   — same class of bug documented at length for .lightbox-overlay/.nav-drawer
   elsewhere in this file. The banner never actually disappeared even though
   `.hidden` read true. This selector is more specific, so it wins for real. */
.cookie-banner[hidden] { display: none; }
.cookie-banner button { background: var(--color-primary); color: #fff; border: none; padding: 8px 16px; border-radius: 6px; }

.error-404 { text-align: center; padding: 80px 16px; }

/* Home sections + shared frontend components (Phase 3) */
main section { max-width: 1100px; margin: 0 auto 56px; padding: 0 16px; }
.section-head { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin-bottom: 16px; }
.hero-cta { display: flex; gap: 12px; justify-content: center; margin-top: 16px; }
.hero-cta-bar { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; max-width: 1100px; margin: 20px auto 40px; padding: 0 16px; }

/* STAGE 7 Group C — section label pill + heading + accent underline.
   The underline's draw-in is pure CSS: it just watches for the ancestor
   <section>'s existing Group A .reveal.is-visible state, no new JS. */
.section-heading { margin-bottom: 4px; }
.section-heading h2 { margin: 0; }
.section-label {
    display: inline-block;
    font-size: 12px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
    color: var(--color-primary); background: var(--color-beige-2);
    padding: 4px 14px; border-radius: 999px; margin-bottom: 10px;
}
.section-accent {
    display: block;
    width: 44px; height: 3px; margin-top: 10px;
    background: var(--color-primary); border-radius: 2px;
    transform: scaleX(0); transform-origin: left;
    transition: transform .6s ease .15s;
}
section.reveal.is-visible .section-accent { transform: scaleX(1); }
.home-why, .home-faq { text-align: center; }
.home-why .section-accent, .home-faq .section-accent {
    margin-left: auto; margin-right: auto;
}

/* P1-2 — Why Us icon cards (was a plain 4-item flex list). 3 columns
   desktop, 2 tablet, 1 narrow mobile. */
.why-us-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 900px) { .why-us-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .why-us-grid { grid-template-columns: 1fr; } }
.why-us-card {
    aspect-ratio: 16 / 9; display: flex; flex-direction: column; justify-content: center; align-items: center;
    border: 1px solid var(--color-beige); border-radius: 12px;
    padding: 28px 20px; text-align: center; overflow: hidden;
    transition: transform .2s ease, box-shadow .2s ease;
    background-color: #fff;
    background-size: cover; background-position: center;
}
/* 10.9.2026 client feedback — real per-card background photos (client
   supplied one distinct image per card, not a shared one). White-tint
   overlay keeps the text legible over any photo. Lowered from .85 to .55
   (follow-up feedback: photos were nearly invisible at .85) — text-shadow
   below is the legibility safety net now that the photo shows through more. */
.why-us-card-doctor { background-image: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), url('../images/why-us/doctor.jpg'); }
.why-us-card-license { background-image: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), url('../images/why-us/license.jpg'); }
.why-us-card-medicine { background-image: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), url('../images/why-us/authentic-medicine.jpg'); }
.why-us-card-results { background-image: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), url('../images/why-us/natural-results.jpg'); }
.why-us-card-equipment { background-image: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), url('../images/why-us/standard-equipment.jpg'); }
.why-us-card-cases { background-image: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), url('../images/why-us/cases.jpg'); }
.why-us-card:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(62, 43, 35, 0.10); }
.why-us-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 56px; height: 56px; border-radius: 50%;
    background: var(--color-beige-2); color: var(--color-primary);
    margin-bottom: 14px;
}
.why-us-icon svg { width: 30px; height: 30px; }
.why-us-card h3 { margin: 0 0 8px; font-size: 16px; text-shadow: 0 1px 12px rgba(255,255,255,.9); }
.why-us-card p { margin: 0; font-size: 13px; color: var(--color-ink); opacity: .9; line-height: 1.6; text-shadow: 0 1px 12px rgba(255,255,255,.9); }

.card-grid, .ba-grid, .review-grid { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
/* 23.9.2026 — promotions grouped by category (Device/Botox/Filler/
   Biostimulator/Skin Booster), same underline heading style as
   .service-detail-section h2. */
.promo-category-section { margin-bottom: 40px; }
.promo-category-heading { font-size: 20px; border-bottom: 2px solid var(--color-primary); display: inline-block; padding-bottom: 4px; margin: 0; }
/* Follow-up — client said the poster images already have all the pricing/
   detail text baked in, so drop the card's own title/price text and show
   the full poster uncropped instead of the generic 160px cover-crop
   thumbnail (which was hiding most of the text). */
.promo-category-section .card img { height: auto; object-fit: contain; cursor: zoom-in; }
/* Follow-up — one booking button per whole category (next to its heading)
   instead of one under every single card; too many repeated buttons. */
.promo-category-heading-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
.promo-category-section .card { padding: 8px; }
@media (max-width: 480px) {
    .promo-category-heading-row { flex-direction: column; align-items: flex-start; }
}
.card, .review-card { background: #fff; border: 1px solid var(--color-beige); border-radius: 10px; padding: 16px; overflow: hidden; transition: transform .2s ease, box-shadow .2s ease; }
.card:hover, .review-card:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(62, 43, 35, 0.10); }
.card img { width: 100%; height: 160px; object-fit: cover; border-radius: 6px; margin-bottom: 8px; transition: transform .3s ease; }
.card:hover img { transform: scale(1.05); }
.card .price { color: var(--color-primary); font-weight: 600; }

/* P1-3 — 5 homepage/`/services` display-group cards. The icon badge
   overlaps the bottom edge of the placeholder/photo (negative margin) —
   stays clipped to the card's own rounded corners via the card's own
   overflow:hidden, doesn't bleed outside it.
   Flexbox (not grid) + justify-content:center + flex-grow:0 on purpose:
   with exactly 5 cards, a fixed column count leaves an odd one out on the
   last row (e.g. 4+1) sitting alone at the left edge — centering the
   whole row via flexbox fixes that at every breakpoint automatically,
   including the in-between widths a media-query-only fix would miss. */
.service-group-grid {
    list-style: none; padding: 0; margin: 0;
    display: flex; flex-wrap: wrap; justify-content: center; gap: 20px;
}
.service-group-card {
    flex: 0 1 100%;
    background: #fff; border: 1px solid var(--color-beige); border-radius: 12px;
    overflow: hidden; text-align: center;
    transition: transform .2s ease, box-shadow .2s ease;
}
/* Mobile-first ramp: 1 col -> 2 -> 3 (5 cards = balanced 3+2) -> 5 (single
   row, no leftover at all). Declared after the base rule above so each
   breakpoint's flex-basis actually overrides it, per the project's
   established mobile-first CSS convention. */
@media (min-width: 560px) {
    .service-group-card { flex-basis: calc(50% - 10px); }
}
@media (min-width: 900px) {
    .service-group-card { flex-basis: calc(33.333% - 13.333px); }
}
@media (min-width: 1300px) {
    .service-group-card { flex-basis: calc(20% - 16px); }
}
.service-group-card:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(62, 43, 35, 0.10); }
/* 10.9.2026 fix — the global .placeholder-media{height:100%} rule (P1-0)
   was fighting this card's auto-height flex layout: with no explicit
   height on .service-group-card, the flex "stretch" pass gave it a
   height based on the image's aspect-ratio alone, then height:100% on
   the <img> locked to THAT value on the resolved pass, leaving zero
   room for the icon/h3/p/a that follow — they rendered below the
   card's own overflow:hidden edge and were invisible. height:auto lets
   the inline aspect-ratio govern the image and the card grow to fit
   all of its content again, same override pattern as .doctor-slide. */
.service-group-card-media { display: block; }
/* Compound selector for specificity — the shared .placeholder-media{height:100%}
   rule (P1-0) is declared later in this file at equal specificity, so a bare
   .service-group-card-media override would lose on source order. Same fix
   pattern as .contact-info/.doctor-slide's placeholder-media overrides below. */
.service-group-card .service-group-card-media { height: auto; }
.service-group-card-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 48px; height: 48px; border-radius: 50%;
    background: #fff; color: var(--color-primary);
    margin-top: -24px; box-shadow: 0 2px 8px rgba(62, 43, 35, .15);
    position: relative; z-index: 1;
}
.service-group-card-icon svg { width: 26px; height: 26px; }
.service-group-card h3 { margin: 12px 16px 6px; font-size: 16px; }
.service-group-card p { margin: 0 16px 16px; font-size: 13px; color: var(--color-ink); opacity: .8; line-height: 1.6; }
.service-group-card > a { display: inline-block; margin: 0 0 20px; font-size: 14px; font-weight: 600; color: var(--color-primary); text-decoration: none; }
.service-group-card > a:hover { text-decoration: underline; }

/* STAGE 7 Group E — rounded-arch portrait frame for doctor photos, with a
   soft bottom gradient for a touch of depth. Wraps the <img> so the
   gradient (an ::after) has somewhere to sit — generated content doesn't
   render on replaced elements like <img> itself. */
.portrait-frame {
    position: relative;
    overflow: hidden;
    border-radius: 40px 40px 8px 8px;
    margin-bottom: 8px;
}
.portrait-frame img { display: block; margin-bottom: 0; border-radius: 0; }
.portrait-frame::after {
    content: '';
    position: absolute; left: 0; right: 0; bottom: 0;
    height: 45%;
    background: linear-gradient(to top, rgba(62, 43, 35, .28), transparent);
    pointer-events: none;
}

/* P1-5 — big-photo/bio "Team" layout (home condensed + /doctors full),
   40/60 desktop split, stacks under ~800px matching the existing
   .contact-grid breakpoint convention. .team-feature-reverse (used on
   even rows on /doctors when there's more than one doctor) flips which
   side the photo sits on via CSS order, no markup duplication needed. */
/* gap tightened (was 32px) and align-items:start (was center) so the text
   column begins level with the top of the photo instead of being
   vertically centered against it — with the photo now capped at 400px
   tall (see .team-feature-photo below) and a much shorter text block,
   center-alignment left visible empty space above/below the text, making
   the two columns read as disconnected rather than one balanced unit. */
.team-feature { display: grid; grid-template-columns: 2fr 3fr; gap: 20px; align-items: start; margin-bottom: 32px; }
/* Capped so the photo can't tower over a short text block (was stretching
   to the full 2fr column width, which at 4:5 made for a very tall crop on
   wide screens) — max-width/max-height are set to the same 4:5 ratio
   (320:400) so whichever one is the binding constraint gives the same
   result either way. justify-self:center keeps it centered within its
   grid track instead of stretching (the grid default). */
.team-feature-photo {
    margin-bottom: 0;
    width: 100%;
    max-width: 320px;
    max-height: 400px;
    justify-self: center;
}
.team-feature-photo img { display: block; width: 100%; height: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.team-feature-body h2, .team-feature-body h3 { margin: 0 0 8px; font-size: 22px; }
.team-feature-license { color: var(--color-primary); font-weight: 600; font-size: 14px; margin: 0 0 12px; }
.team-feature-body p { line-height: 1.8; margin: 0 0 16px; }
.team-feature-quote { font-style: italic; color: var(--color-primary); }

/* 26.8.2026 client feedback — real doctor credentials (_material/09) shown
   as styled blocks (icon + heading + bullet list) on /doctors + home.
   16.9.2026 follow-up — client asked for "heading stays left, details drop
   out to the right" instead of the old boxed/centered 2-3-column layout:
   each block is now its own full-width row (label column fixed-left, list
   flowing right of it), stacked, separated by a thin rule instead of a
   card background — reads as an editorial credentials list rather than a
   UI-kit card grid. */
.doctor-credentials { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
.doctor-credential-block {
    display: grid; grid-template-columns: 150px 1fr; gap: 16px; align-items: start;
    padding: 14px 0; border-bottom: 1px solid var(--color-beige);
}
.doctor-credential-block:last-child { border-bottom: none; }
.doctor-credential-block h4 {
    display: flex; align-items: center; gap: 8px;
    margin: 0; font-size: 13px; color: var(--color-primary);
    text-transform: uppercase; letter-spacing: .04em;
}
.doctor-credential-icon {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 26px; height: 26px; border-radius: 50%;
    background: var(--color-beige-2); color: var(--color-primary);
}
.doctor-credential-icon svg { width: 15px; height: 15px; }
.doctor-credential-block ul { margin: 0; padding-left: 18px; font-size: 13.5px; line-height: 1.65; color: var(--color-ink); }
.doctor-credential-block li { margin-bottom: 6px; }
.doctor-credential-block li:last-child { margin-bottom: 0; }
/* Label column too cramped to sit beside the list below ~600px — falls
   back to stacked (label above list), same breakpoint convention as
   .team-feature/.clinic-gallery elsewhere in this file. */
@media (max-width: 600px) {
    .doctor-credential-block { grid-template-columns: 1fr; gap: 6px; }
}

/* 26.8.2026 client feedback — clinic atmosphere gallery (/about), 5 real
   photos in a simple responsive grid, click through to the existing
   lightbox for full-size viewing. */
.clinic-gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin: 16px 0 32px; }
.clinic-gallery-img {
    width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 10px;
    cursor: zoom-in; transition: transform .2s ease, box-shadow .2s ease;
}
.clinic-gallery-img:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(62, 43, 35, .15); }
@media (max-width: 700px) { .clinic-gallery { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 800px) {
    .team-feature { grid-template-columns: 1fr; }
    .team-feature-reverse .team-feature-photo { order: 0; }
}
@media (min-width: 801px) {
    .team-feature-reverse { grid-template-columns: 3fr 2fr; }
    .team-feature-reverse .team-feature-photo { order: 2; }
    .team-feature-reverse .team-feature-body { order: 1; }
}

.trust-strip {
    display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 24px;
    padding: 20px; background: var(--color-beige-2); border-radius: 12px;
    margin-top: 12px; font-size: 13px; font-weight: 600; color: var(--color-ink);
}

/* 10.9.2026 client feedback — decorative trust image (was a 2-up gallery of
   the actual license document photos, right after the trust-strip on home
   + /doctors; the real license number is already text in the trust-strip).
   Follow-up feedback: dropped the heading (image speaks for itself) and
   sized up on desktop — was capped at the same 320px on every viewport. */
.certificate-gallery { margin-top: 20px; text-align: center; }
.certificate-trust-img {
    display: block; margin: 0 auto; max-width: 320px; width: 100%;
    aspect-ratio: 3 / 4; object-fit: cover; border-radius: 12px;
}
@media (min-width: 900px) {
    .certificate-trust-img { max-width: 460px; }
}

/* STAGE 7 Group C — hover lift to match .card, plus a subtle image zoom
   (clipped by this element's own overflow:hidden so it never bleeds past
   the rounded corners). */
.ba-pair { display: flex; flex-wrap: wrap; gap: 4px; background: #fff; border-radius: 10px; overflow: hidden; border: 1px solid var(--color-beige); transition: transform .2s ease, box-shadow .2s ease; }
.ba-pair:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(62, 43, 35, 0.10); }
.ba-pair img { width: 50%; height: 160px; object-fit: cover; transition: transform .3s ease; }
.ba-pair:hover img { transform: scale(1.04); }
.ba-pair p { width: 100%; padding: 8px; font-size: 13px; }
.ba-pair.ba-composite { display: block; }
.ba-pair.ba-composite img { display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover; cursor: zoom-in; }
.ba-pair img.js-lightbox { cursor: zoom-in; }

.ba-load-more-wrap { text-align: center; margin-top: 20px; }

/* P1-4 — "Our Results" portfolio grid (home + /reviews): explicit 4
   columns desktop / 2 mobile (not auto-fill like .ba-grid, per spec's
   precise 4x3), bigger tiles with a bottom-gradient procedure-name
   caption baked onto the photo itself. */
.results-grid { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
@media (max-width: 900px) { .results-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; } }
.result-tile { position: relative; border-radius: 10px; overflow: hidden; aspect-ratio: 4 / 5; cursor: zoom-in; }
.result-tile img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform .3s ease; }
.result-tile:hover img { transform: scale(1.05); }
.result-tile-caption {
    position: absolute; left: 0; right: 0; bottom: 0;
    padding: 20px 12px 10px;
    background: linear-gradient(to top, rgba(62, 43, 35, .8), transparent);
    color: #fff; font-size: 12px; font-weight: 600; line-height: 1.3;
    pointer-events: none;
}

/* P1-4 — closing CTA band before Contact ("Are you ready" style). */
.cta-band {
    background: var(--color-primary); color: #fff;
    border-radius: 16px; padding: 40px 24px; text-align: center;
}
.cta-band h2 { margin: 0 0 8px; color: #fff; }
.cta-band p { margin: 0 0 20px; opacity: .92; }
.cta-band .btn-primary { background: #fff; color: var(--color-primary); }

/*
 * Hidden by default. NOTE: the `hidden` HTML attribute alone is not
 * reliable here — the UA stylesheet's `[hidden] { display: none }` has the
 * same specificity as an author `.lightbox-overlay { display: ... }` rule,
 * and author CSS always wins that tie regardless of source order, so a
 * flex/block display declared directly on this class would silently
 * cancel `hidden`. Visibility is driven entirely by the `.is-open` class
 * (toggled in main.js), kept in sync with the `hidden` attribute for
 * accessibility (screen readers).
 */
.lightbox-overlay {
    display: none;
    position: fixed; inset: 0; background: rgba(0, 0, 0, 0.85);
    align-items: center; justify-content: center;
    padding: 24px; z-index: 50; cursor: zoom-out;
    pointer-events: none;
    opacity: 0;
    transition: opacity .25s ease;
}
.lightbox-overlay.is-open { display: flex; pointer-events: auto; }
/* .is-visible is added one frame after .is-open (see main.js) so the
   opacity/scale transition actually has a "from" state to animate out of,
   same double-rAF trick as the Group B hero entrance. */
.lightbox-overlay.is-visible { opacity: 1; }
.lightbox-overlay img {
    max-width: 100%; max-height: 100%; border-radius: 8px; cursor: default;
    opacity: 0; transform: scale(.94);
    transition: opacity .3s ease, transform .25s ease;
}
.lightbox-overlay.is-visible img.is-loaded { opacity: 1; transform: scale(1); }
.lightbox-close {
    position: absolute; top: 16px; right: 20px; background: transparent; border: 0;
    color: #fff; font-size: 32px; line-height: 1; cursor: pointer; padding: 8px;
}

/* Shown while the medium-size image is loading (its `src` was just set),
   hidden once the browser fires `load` and main.js adds `.is-loaded`. */
.lightbox-spinner {
    position: absolute;
    width: 40px; height: 40px;
    border: 3px solid rgba(255, 255, 255, .25);
    border-top-color: #fff;
    border-radius: 50%;
    opacity: 0;
    transition: opacity .2s ease;
    animation: lightbox-spin .8s linear infinite;
    pointer-events: none;
}
.lightbox-overlay.is-loading .lightbox-spinner { opacity: 1; }
@keyframes lightbox-spin {
    to { transform: rotate(360deg); }
}

.review-stars { color: var(--color-primary-2); }
.review-author { display: block; margin-top: 8px; font-size: 13px; color: #777; }

.faq-list { display: flex; flex-direction: column; gap: 8px; }
.faq-item { background: var(--color-beige-2); border-radius: 8px; padding: 12px 16px; }
.faq-item summary { cursor: pointer; font-weight: 600; }

.booking-form { max-width: 480px; margin: 0 auto; background: #fff; border: 1px solid var(--color-beige); border-radius: 10px; padding: 24px; }
.booking-form label { display: block; margin-bottom: 12px; font-size: 14px; }
.booking-form input, .booking-form select, .booking-form textarea {
    width: 100%; padding: 8px; margin-top: 4px; border: 1px solid #ccc; border-radius: 4px; font-family: inherit;
}
.booking-form-row { display: flex; gap: 12px; }
.booking-form-row label { flex: 1; }
.booking-form .checkbox-label { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; }
.booking-form .checkbox-label input { width: auto; margin-top: 3px; }
.booking-form button { width: 100%; margin-top: 8px; }
.form-flash { padding: 10px; border-radius: 6px; font-size: 14px; }
.form-flash-success { background: #dcfce7; color: #166534; }
.form-flash-error { background: #fee2e2; color: #991b1b; }

/* About page — 3 pillars */
.static-page { max-width: 1000px; margin: 0 auto; padding: 0 16px; }
.about-pillars { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 28px; margin-top: 40px; }
.pillar-card {
    background: #fff;
    border-top: 3px solid var(--color-primary);
    border-radius: 12px;
    padding: 32px 24px;
    text-align: center;
    box-shadow: 0 4px 16px rgba(62, 43, 35, 0.08);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.pillar-card:hover { transform: translateY(-4px); box-shadow: 0 8px 24px rgba(62, 43, 35, 0.14); }
.pillar-card h2 { font-size: 15px; letter-spacing: 0.06em; color: var(--color-primary); margin: 0 0 14px; text-transform: uppercase; font-weight: 700; }
.pillar-card p { font-size: 14px; line-height: 1.8; margin: 0; color: var(--color-ink); }

/* Service detail template (spec §5) */
.service-detail-section { margin-bottom: 32px; }
.service-detail-section h2 { font-size: 18px; border-bottom: 2px solid var(--color-primary); display: inline-block; padding-bottom: 4px; }
.service-price-box { background: var(--color-beige-2); border: 1px solid var(--color-beige); border-radius: 8px; padding: 16px; }
.breadcrumb { font-size: 13px; color: #777; margin-bottom: 16px; }
.breadcrumb a { text-decoration: none; }
/* .article-detail/.service-detail render as <article> directly inside
   <main>, so they miss the sitewide `main section`/.container 1100px cap —
   on a wide screen the cover image's object-fit:cover box stretched to
   the full viewport width, cropping it to an unusably extreme sliver. */
.breadcrumb, .article-detail, .service-detail { max-width: 1100px; margin-left: auto; margin-right: auto; }

/* Article detail */
.article-detail .article-meta { color: #777; font-size: 13px; margin-bottom: 16px; }
/* object-fit:contain (not cover) — client's real photos mix portrait
   (event/red-carpet shots) and landscape orientation; cover was cropping
   portrait photos into unusable slivers. contain shows the whole photo at
   any orientation, letterboxed on --color-cream so it blends into the page
   instead of showing a visible box. Also scoped to .service-detail since
   its cover image reuses the same .article-cover class/markup. */
.article-detail .article-cover, .service-detail .article-cover {
    display: block;
    width: 100%;
    max-height: 520px;
    object-fit: contain;
    background: var(--color-cream);
    border-radius: 8px;
    margin-bottom: 16px;
}
.article-detail .article-body { line-height: 1.8; }
.article-detail iframe { width: 100%; aspect-ratio: 16/9; border: 0; border-radius: 8px; margin: 16px 0; }

/* 16.9.2026 client feedback — article category pills (index page filter)
   and badge (card + detail page), for the new Product/News-Activities split. */
.article-category-pills { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; padding: 0; margin: 0 0 20px; }
.article-category-pill {
    display: inline-block; padding: 6px 16px; border-radius: 999px; font-size: 13px;
    text-decoration: none; color: var(--color-ink); background: #fff; border: 1px solid var(--color-beige);
}
.article-category-pill.is-active { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }
.article-category-badge {
    display: inline-block; font-size: 12px; font-weight: 600; color: var(--color-primary);
    background: var(--color-beige-2); padding: 4px 10px; border-radius: 999px; margin-bottom: 8px;
    text-decoration: none;
}
.article-detail .article-category-badge { margin-bottom: 12px; }

.contact-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
/* Follow-up feedback — the photo slider (below) was pushing this whole
   column, and the section with it, wider than its 1fr share and past the
   page's own max-width. Classic CSS Grid trap: a grid item's default
   min-width is auto, which sizes to its content's max intrinsic width —
   for an overflow-x:auto flex row that's the full un-scrolled width of all
   7 photos combined, not the visible scroll window. min-width:0 makes the
   column actually respect the 1fr track instead. */
.contact-info { min-width: 0; }
/* aspect-ratio instead of a fixed pixel height (was 260px regardless of
   width) so it scales proportionally at any column/viewport width — same
   technique already used for .article-detail iframe below. max-height is
   a belt-and-suspenders cap on top of that (the real fix for the
   distortion on wide screens was giving /contact's whole page a
   max-width container — see contact.blade.php — since the map's own
   column was never the actual problem, an unconstrained page was). */
.contact-info iframe { width: 100%; aspect-ratio: 4 / 3; height: auto; max-height: 320px; border: 0; border-radius: 8px; margin-top: 16px; }
@media (max-width: 800px) { .contact-grid { grid-template-columns: 1fr; } }

/* 26.8.2026 client feedback — real clinic photo box, sits above the address
   text as the first item in .contact-info. Same 4:3 ratio as the map iframe
   right below it for visual consistency in this column. */
/* height:auto overrides the global .placeholder-media{height:100%} rule
   (main.css ~line 843) — without it, .contact-info stretches to match its
   taller grid sibling (.contact-form) and the image fills that whole
   height instead of respecting its own aspect-ratio, badly distorting the
   photo and pushing the map/footer out of place below it. */
.contact-info .placeholder-box, .contact-info .placeholder-media { margin-bottom: 16px; border-radius: 8px; height: auto; }

/* Follow-up feedback — clinic photos in the contact section as a
   slide-able row instead of one static photo. Plain <img> tags (not
   .placeholder-media) so this never inherits the global height:100% rule
   that caused the earlier aspect-ratio bug above. */
.contact-photo-slider-wrap { position: relative; margin-bottom: 16px; }
.contact-photo-slider {
    display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory;
    padding-bottom: 8px;
    scrollbar-width: thin; scrollbar-color: var(--color-primary) var(--color-beige-2);
}
.contact-photo-slider::-webkit-scrollbar { height: 6px; }
.contact-photo-slider::-webkit-scrollbar-track { background: var(--color-beige-2); border-radius: 4px; }
.contact-photo-slider::-webkit-scrollbar-thumb { background: var(--color-primary); border-radius: 4px; }
.contact-photo-slide {
    /* Sized off this column's own width (not the viewport) now that
       .contact-info actually respects its grid track — a touch under the
       full column width so the next photo visibly peeks in as a "there's
       more" cue, same idea as the explicit arrow buttons below. */
    flex: 0 0 auto; width: 88%; max-width: 320px; aspect-ratio: 4 / 3; object-fit: cover;
    border-radius: 8px; scroll-snap-align: start; display: block;
}
/* Follow-up feedback — the scroll-drag row alone gave no visual hint it
   was navigable; explicit prev/next buttons make that obvious at a glance,
   same "click to advance" affordance as a normal carousel. */
.contact-photo-slider-nav {
    position: absolute; top: 45%; transform: translateY(-50%);
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border-radius: 50%; border: 0; cursor: pointer;
    background: rgba(255, 255, 255, .9); color: var(--color-ink);
    box-shadow: 0 2px 8px rgba(62, 43, 35, .2);
}
.contact-photo-slider-nav svg { width: 18px; height: 18px; }
.contact-photo-slider-nav.is-prev { left: 8px; }
.contact-photo-slider-nav.is-next { right: 8px; }
.contact-photo-slider-nav:hover { background: #fff; }

/* STAGE 7 Group A — scroll-reveal foundation.
   `.reveal` only ever touches opacity/transform (never box size), so it
   can never introduce layout shift regardless of when the observer fires.
   Elements are tagged + observed by main.js, not hand-added in Blade. */
.reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity .5s ease-out, transform .5s ease-out;
}
.reveal.is-visible { opacity: 1; transform: translateY(0); }

/* 26.8.2026 client feedback — one-line contact bar just before the hero.
   16.9.2026 — now sits directly above the "OUR TEAM" section-label pill
   (doctor section moved to lead the homepage) instead of the large Hero
   visual it used to sit above, so the two pill shapes were crowding.
   Follow-up — client preferred a cleaner look over the boxed background
   band tried first: no visible panel edge, background blends straight into
   the page, and the button itself carries a soft floating shadow instead
   (same shadow language as .card/.doctor-slide elsewhere on this site) for
   a bit of depth without a hard-edged box. */
.contact-bar { text-align: center; padding: 24px 16px; margin: 0 auto; }
.contact-bar .btn-primary { padding: 8px 24px; box-shadow: 0 6px 16px rgba(62, 43, 35, .16); }
.contact-bar .btn-primary:hover { box-shadow: 0 8px 22px rgba(62, 43, 35, .22); transform: translateY(-2px); }

/* Contact-channels popup — same hidden/.is-open/.is-visible pattern as
   .lightbox-overlay (main.css:474-483) and the same z-index-above-everything
   convention (header 10 < sticky-bar/floating-widget 20 < cookie-banner 30 <
   nav-drawer 40 < lightbox 50 < this popup 55). */
.contact-popup {
    display: none;
    position: fixed; inset: 0; z-index: 55;
    align-items: center; justify-content: center; padding: 24px;
}
.contact-popup.is-open { display: flex; }
.contact-popup-backdrop {
    position: absolute; inset: 0; background: rgba(62, 43, 35, .5);
    opacity: 0; transition: opacity .25s ease;
}
.contact-popup.is-visible .contact-popup-backdrop { opacity: 1; }
.contact-popup-panel {
    position: relative; background: #fff; border-radius: 12px;
    padding: 32px 28px; width: min(360px, 100%);
    opacity: 0; transform: scale(.94);
    transition: opacity .25s ease, transform .25s ease;
}
.contact-popup.is-visible .contact-popup-panel { opacity: 1; transform: scale(1); }
.contact-popup-close {
    position: absolute; top: 10px; right: 14px; background: transparent; border: 0;
    color: var(--color-ink); font-size: 26px; line-height: 1; cursor: pointer; padding: 8px;
}
.contact-popup-title { margin: 0 0 16px; text-align: center; }
.contact-popup-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 4px; }
.contact-popup-list a {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 8px; border-radius: 8px; text-decoration: none;
    color: var(--color-ink); font-size: 15px;
    transition: background-color .15s ease;
}
.contact-popup-list a:hover { background: var(--color-cream, #F7F1EC); }
.contact-popup-icon {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 36px; height: 36px; border-radius: 50%;
    background: var(--color-beige-2); color: var(--color-primary);
}
.contact-popup-icon svg { width: 20px; height: 20px; }
/* 26.8.2026 follow-up — real brand-colored badges instead of the generic
   beige circle, per the client's request to use each app's actual logo.
   Phone stays generic (no single "phone brand" to use). */
.contact-popup-icon.is-fb { background: #1877F2; }
.contact-popup-icon.is-ig { background: linear-gradient(45deg, #FEDA75, #FA7E1E, #D62976, #962FBF, #4F5BD5); }
.contact-popup-icon.is-line { background: #06C755; }
.contact-popup-icon.is-tiktok { background: #000; }
.contact-popup-icon.is-gmail { background: #fff; border: 1px solid var(--color-beige); }

/* 26.8.2026 client feedback — doctor photo slider, before "Our Team".
   Plain CSS scroll-snap, no JS needed; works naturally with touch swipe.
   Refined per follow-up feedback ("ดูดาดๆ ไปหน่อย"): reuses the site's
   signature .portrait-frame arch shape + gradient scrim (same treatment as
   the doctor portraits elsewhere) instead of plain rectangles, adds the
   same hover-lift as .card, a slim brand-colored scrollbar instead of the
   default gray one, and a right-edge fade hinting there's more to scroll. */
.home-doctor-spotlight { text-align: center; }
/* 16.9.2026 follow-up — client asked for the bio/credentials body to read
   left-aligned ("heading stays left, details drop right") instead of
   inheriting the section's centered-title convention; scoped to just this
   descendant so the section title (doctor name + "OUR TEAM" label) stays
   centered like every other homepage section title. */
.home-doctor-spotlight .team-feature-body { text-align: left; }
.doctor-slider-wrap { position: relative; text-align: left; margin-bottom: 24px; }
/* 16.9.2026 client feedback — recognition items (top 3 work-experience
   bullets), sits inside the credentials list (shares
   .doctor-credential-block's row/grid/divider styling, see home.blade.php).
   Follow-up — client asked for plain text, no pill/box around each item;
   now matches .doctor-credential-block ul/li exactly (same bullet-list
   look as Education/Expertise) instead of the earlier badge-chip style. */
.doctor-recognition-list { margin: 0; padding-left: 18px; font-size: 13.5px; line-height: 1.65; color: var(--color-ink); }
.doctor-recognition-badge { margin-bottom: 6px; }
.doctor-recognition-badge:last-child { margin-bottom: 0; }
.doctor-slider {
    display: flex; gap: 20px; overflow-x: auto; scroll-snap-type: x mandatory;
    padding: 4px 4px 16px;
    scrollbar-width: thin; scrollbar-color: var(--color-primary) var(--color-beige-2);
    -webkit-overflow-scrolling: touch;
}
.doctor-slider::-webkit-scrollbar { height: 6px; }
.doctor-slider::-webkit-scrollbar-track { background: var(--color-beige-2); border-radius: 4px; }
.doctor-slider::-webkit-scrollbar-thumb { background: var(--color-primary); border-radius: 4px; }
.doctor-slide {
    flex: 0 0 auto; width: min(260px, 72vw); scroll-snap-align: start;
    position: relative; overflow: hidden;
    border-radius: 40px 40px 8px 8px;
    box-shadow: 0 4px 14px rgba(62, 43, 35, .12);
    transition: transform .2s ease, box-shadow .2s ease;
}
.doctor-slide:hover { transform: translateY(-4px); box-shadow: 0 10px 24px rgba(62, 43, 35, .18); }
.doctor-slide::after {
    content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 45%;
    background: linear-gradient(to top, rgba(62, 43, 35, .28), transparent);
    pointer-events: none;
}
.doctor-slide .placeholder-box, .doctor-slide .placeholder-media { width: 100%; height: 100%; display: block; }
/* Fade hint on the right edge so the row reads as "scroll for more", not
   like it just stops abruptly — fades to the section's own background
   color so it's invisible once actually scrolled to the end. */
.doctor-slider-wrap::after {
    content: ''; position: absolute; top: 4px; right: 4px; bottom: 16px; width: 48px;
    background: linear-gradient(to right, transparent, var(--color-cream));
    pointer-events: none;
}

/* STAGE 7 Group E — fade-in once a lazy-loaded image's bytes actually
   arrive (`.img-loaded`, toggled in main.js off the `load` event / an
   already-`.complete` check for cached images). Scoped to
   `img[loading="lazy"]` only — the before/after gallery thumbnails, not a
   blanket rule over every image on the site (hero/logo/etc. render
   immediately, no fade needed, and starting them hidden would risk a
   flash-of-nothing if JS is slow to attach). Opacity-only, so it can't
   shift layout. */
img[loading="lazy"] { opacity: 0; transition: opacity .35s ease; }
img[loading="lazy"].img-loaded { opacity: 1; }

/* P1-0 — <x-placeholder> "materials needed" box. Meant to look intentional
   (client-meeting-ready), not like a broken image: dashed CI-rose-gold
   border, tinted cream fill, centered line-icon + label. aspect-ratio is
   set inline per-instance (see the component), so the box is always the
   exact real ratio the eventual asset will be, no CLS when it's swapped
   in later. */
.placeholder-box {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    border: 2px dashed var(--color-primary);
    border-radius: 12px;
    background: var(--color-beige-2);
    color: var(--color-primary);
    padding: 16px;
    text-align: center;
    box-sizing: border-box;
}
.placeholder-icon svg { width: 40px; height: 40px; color: var(--color-primary); opacity: .8; }
.placeholder-text { font-size: 13px; font-weight: 600; line-height: 1.5; max-width: 90%; }
.placeholder-media { display: block; width: 100%; height: 100%; object-fit: cover; }

/* Motion kill-switch: one place that turns everything off, rather than
   wrapping every rule above in its own reduced-motion guard. */
@media (prefers-reduced-motion: reduce) {
    /* Bare `*` does not match generated content — `::before`/`::after`
       need their own entry, otherwise pseudo-element animations (e.g. the
       hero accent float) keep running under reduced-motion. Confirmed via
       a real headless-Chrome check during STAGE 7 Group B. */
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
    .reveal { opacity: 1 !important; transform: none !important; }
}
