@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;1,300;1,400&family=Poppins:wght@300;400;500;600;700&family=Kanit:wght@300;400;500;600;700&display=swap');

/* ── Locked horizontal gutter ──────────────────────────────
   One value, identical on every device and every section.
   Change this single variable to re-space the whole site.   */
/* Fluid side gutter — grows with the viewport so wide screens get generous
   editorial margins (premium feel), while phones stay comfortably tight. */
:root { --gx: clamp(22px, 6.5vw, 150px); }
@media (max-width: 640px) { :root { --gx: 20px; } }

/* overflow-x: clip (not hidden) prevents horizontal scroll WITHOUT making
   html/body a scroll container — hidden would break ScrollTrigger pinning
   + position:sticky. */
html, body { margin: 0; padding: 0; background: #0C2E29; overflow-x: clip; }
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-family: 'Poppins', 'Kanit', sans-serif; }

/* Font system:
   - Thai copy            → Kanit (loopless)
   - Numbers/stats/prices → Poppins, via .font-num
   - Display headings     → Cormorant Garamond, unchanged
   - English body/UI text → Poppins (site default, .font-sans / root)
   Kanit is appended to every family so any Thai glyph inside an English-
   font block still renders loopless instead of falling back to a looped
   system font. */
.font-display { font-family: 'Cormorant Garamond', 'Kanit', serif; font-weight: 300; }
.font-sans  { font-family: 'Poppins', 'Kanit', sans-serif; }
.font-thai  { font-family: 'Kanit', sans-serif; font-weight: 300; }
.font-num   { font-family: 'Poppins', 'Kanit', sans-serif; }
.eyebrow    { font-family: 'Poppins', 'Kanit', sans-serif; font-weight: 500; letter-spacing: 0.26em; text-transform: uppercase; font-size: 11px; }

/* ═══════════ EDITORIAL TYPE SCALE ("The Precision Monograph") ═══════
   Fluid clamp sizes so display type is dramatic on desktop and graceful on
   mobile — the single biggest lever on how "designed" the page reads. Pair
   these size classes with .font-display / .font-thai for the family. Thai
   overrides keep tone marks from clipping (looser LH) and never track. */
.t-hero    { font-size: clamp(3.5rem, 8.5vw, 8rem);   line-height: 0.96; letter-spacing: -0.025em; font-weight: 300; }
.t-display { font-size: clamp(2.6rem, 5.2vw, 4.75rem); line-height: 1.02; letter-spacing: -0.018em; font-weight: 300; }
.t-h2      { font-size: clamp(2rem, 3.4vw, 3rem);      line-height: 1.1;  letter-spacing: -0.012em; font-weight: 400; }
.t-h3      { font-size: clamp(1.5rem, 2.2vw, 2rem);    line-height: 1.18; letter-spacing: -0.008em; font-weight: 500; }
.t-lead    { font-size: clamp(1.06rem, 1.2vw, 1.3rem); line-height: 1.72; letter-spacing: 0; }
/* Numerals stay SANS (Poppins) — no serif/"heads" on numbers per client (reads too AI). */
.t-numeral { font-family: 'Poppins', 'Kanit', sans-serif; font-weight: 600; font-size: clamp(4.5rem, 13vw, 11rem); line-height: 0.82; letter-spacing: -0.02em; }
:lang(th).t-hero, .t-hero:lang(th) { line-height: 1.14; letter-spacing: normal; }
:lang(th).t-display, .t-display:lang(th) { line-height: 1.2; letter-spacing: normal; }
:lang(th).t-h2, .t-h2:lang(th) { line-height: 1.3; letter-spacing: normal; }
/* Hair-thin luxury rule + generous editorial section rhythm helpers */
.rule-gold { height: 1px; background: linear-gradient(90deg, #C9A86A, rgba(201,168,106,0)); }
/* More breathing room between sections — editorial/magazine rhythm. */
.section-py { padding-top: clamp(6rem, 13vh, 12rem); padding-bottom: clamp(6rem, 13vh, 12rem); }

/* Apply to every section's outer wrapper for a perfectly aligned column.
   Banner / full-bleed media intentionally omits this class.            */
.gx { padding-left: var(--gx); padding-right: var(--gx); }

/* Continuous left-moving review marquee. The track renders the review
   list twice back-to-back; animating to -50% moves exactly one full copy
   off screen, so the loop point is seamless. Pause on hover so a visitor
   can actually read a card. */
@keyframes marqueeScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.marquee-track { animation: marqueeScroll 140s linear infinite; }
.marquee-track:hover { animation-play-state: paused; }

/* Hides the scrollbar on manually-draggable horizontal rows (Firefox is
   handled via the inline scrollbarWidth style; this covers WebKit/Chrome). */
.no-scrollbar::-webkit-scrollbar { display: none; }

/* ── Scroll-reveal entrance ──────────────────────────────────
   Add class="reveal" to any element; an IntersectionObserver in
   components.js (initRevealObserver) adds .is-visible the first time it
   scrolls into view. Use the --reveal-i custom property (set inline per
   item, e.g. style={{"--reveal-i": i}}) to stagger a grid — each index
   adds 70ms of delay, capped at 8 steps so long lists don't crawl in. */
.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.6s cubic-bezier(0,0,0.2,1), transform 0.6s cubic-bezier(0,0,0.2,1);
  transition-delay: calc(min(var(--reveal-i, 0), 8) * 70ms);
}
.reveal.is-visible { opacity: 1; transform: translateY(0); }

/* ── Card / interactive hover-lift ───────────────────────────
   One consistent depth signal sitewide instead of ad-hoc shadow strings
   per element — subtle rise + soft emerald-tinted shadow (not default
   Tailwind gray, which reads flat against the cream/emerald palette). */
.card-hover {
  transition: transform 0.3s cubic-bezier(0,0,0.2,1), box-shadow 0.3s cubic-bezier(0,0,0.2,1), border-color 0.3s;
}
.card-hover:hover {
  transform: translateY(-4px);
  box-shadow: 0 20px 40px rgba(12,46,41,0.14), 0 8px 16px rgba(12,46,41,0.08);
}

/* ── Lightbox popup (Before/After enlarge on reviews.html) ───── */
@keyframes lightboxFadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes lightboxPopIn { from { opacity: 0; transform: scale(0.94) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }
.lightbox-backdrop { animation: lightboxFadeIn 0.2s cubic-bezier(0,0,0.2,1); }
.lightbox-panel { animation: lightboxPopIn 0.32s cubic-bezier(0.34,1.56,0.64,1); }

/* ── Hero floating trust cards ───────────────────────────────
   Two nested layers so entrance and idle-bob animate the transform
   property independently without fighting: the OUTER .float-card-enter
   does the one-time fade+rise (fill: both so it holds its end state),
   the INNER .float-card-bob does the slow infinite drift. Stagger each
   card's entrance + bob via inline animation-delay so they don't move
   in lockstep. Desktop-only (positioned over the doctor cutout, which
   only has room beside the copy at lg+). */
@keyframes floatCardIn { from { opacity: 0; transform: translateY(14px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes floatBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
.float-card-enter { opacity: 0; animation: floatCardIn 0.7s cubic-bezier(0,0,0.2,1) both; }
.float-card-bob { animation: floatBob 4.5s ease-in-out infinite; }

/* ═══════════ PREMIUM MOTION + TEXTURE SYSTEM ═══════════════
   Shared "luxury polish" layer applied across the site: a subtle film
   grain + directional glow to give the flat emerald sections depth, a
   clip-mask heading reveal, and a slow specialty ticker. Kept restrained
   — one texture + one depth signal per surface, per the design system. */

/* Film grain — a single low-opacity SVG-noise tile blended over a surface.
   Sits on top at ~4% soft-light so it textures everything (incl. text) the
   way real grain does, needing no z-index juggling. Pointer-transparent. */
.grain { position: relative; }
.grain::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 4;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 140px 140px; opacity: 0.05; mix-blend-mode: soft-light;
}

/* Directional emerald glow — layered as a background-image over a section's
   own background-color for depth instead of a flat block. Two anchor points. */
.glow-tl { background-image: radial-gradient(115% 85% at 12% -10%, rgba(38,92,83,0.55) 0%, rgba(12,46,41,0) 55%); }
.glow-tr { background-image: radial-gradient(110% 80% at 90% 0%, rgba(38,92,83,0.5) 0%, rgba(12,46,41,0) 52%); }

/* Clip-mask heading reveal — the line wipes up from behind a mask + a small
   rise, far more "designed" than a plain fade. Driven by the reveal observer
   (same .is-visible toggle + 3.5s safety fallback, so an above-fold hero line
   is never left permanently clipped). Negative bottom inset so italic
   descenders never clip. Stagger multi-line headings via --reveal-i per line. */
.reveal-mask {
  clip-path: inset(0 0 108% 0);
  transform: translateY(0.18em);
  opacity: 0.001;
  filter: blur(7px);
  transition: clip-path 0.9s cubic-bezier(0.16,1,0.3,1), transform 0.9s cubic-bezier(0.16,1,0.3,1), opacity 0.6s ease-out, filter 0.9s cubic-bezier(0.16,1,0.3,1);
  transition-delay: calc(min(var(--reveal-i, 0), 8) * 90ms);
  will-change: clip-path, transform, filter;
}
.reveal-mask.is-visible { clip-path: inset(0 0 -8% 0); transform: translateY(0); opacity: 1; filter: blur(0); }

/* Pop-in — a soft scale-up "pops" the element in, staggered per --reveal-i so
   a group (e.g. the Our-Promise cards) appears one after another. Overshoot
   easing gives the gentle pop. Same .is-visible toggle + fallback as .reveal. */
.pop {
  opacity: 0; transform: scale(0.82) translateY(16px);
  transition: opacity 0.5s ease-out, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
  transition-delay: calc(min(var(--reveal-i, 0), 6) * 150ms);
  will-change: transform, opacity;
}
.pop.is-visible { opacity: 1; transform: none; }

/* Always-on gentle float for the Our-Promise cards — each card gets a negative
   animation-delay (inline) so they drift out of phase. Transform-only, cheap. */
@keyframes promiseFloat {
  0%   { transform: translateY(0) rotate(0deg); }
  30%  { transform: translateY(-15px) rotate(-1.4deg); }
  60%  { transform: translateY(-5px) rotate(0.9deg); }
  100% { transform: translateY(0) rotate(0deg); }
}
.promise-float { animation: promiseFloat 5s ease-in-out infinite; will-change: transform; }

/* Small gold hairline that draws in before an eyebrow label. */
.eyebrow-line { position: relative; padding-left: 34px; }
.eyebrow-line::before {
  content: ""; position: absolute; left: 0; top: 50%; height: 1px; width: 24px;
  background: #C9A86A; transform-origin: left center; transform: scaleX(0);
  transition: transform 0.7s cubic-bezier(0.16,1,0.3,1) 0.15s;
}
.reveal.is-visible .eyebrow-line::before, .is-visible.eyebrow-line::before { transform: scaleX(1); }
.eyebrow-line.always::before { transform: scaleX(1); }

/* Specialty ticker — a slow, continuous marquee band; content duplicated
   2× so -50% loops seamlessly (same technique as the review marquee). */
@keyframes tickerScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.ticker-track { animation: tickerScroll 38s linear infinite; }
.ticker-track:hover { animation-play-state: paused; }

/* Lux button sheen — a soft highlight sweeps across on hover. Element must
   be position:relative + overflow-hidden (utility classes on the element). */
.btn-lux { position: relative; overflow: hidden; }
.btn-lux::after {
  content: ""; position: absolute; top: 0; left: -60%; width: 45%; height: 100%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,0.35), transparent);
  transform: skewX(-18deg); transition: left 0.6s cubic-bezier(0.16,1,0.3,1); pointer-events: none;
}
.btn-lux:hover::after { left: 130%; }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .card-hover:hover { transform: none; }
  .lightbox-backdrop, .lightbox-panel { animation: none; }
  .float-card-enter { opacity: 1; animation: none; }
  .float-card-bob { animation: none; }
  .reveal-mask { clip-path: none; opacity: 1; transform: none; filter: none; transition: none; }
  .pop { opacity: 1; transform: none; transition: none; }
  .promise-float { animation: none; }
  .eyebrow-line::before { transition: none; transform: scaleX(1); }
  .ticker-track { animation: none; }
  .btn-lux::after { display: none; }
}

/* ═══════════ AWARD MOTION FOUNDATION ═══════════════════════
   "The Precision Monograph" — GSAP + Lenis layer. Every rule here is
   designed to DEGRADE SAFELY: if a CDN is blocked or JS never runs, the
   page stays fully readable with nothing hidden. */

/* Anti-stuck-hidden gate — GSAP-targeted nodes only hide once the JS has
   confirmed gsap loaded (adds .gsap-ready). CDN blocked → class never set →
   content stays visible. A 4s JS failsafe also clears these inline. GSAP
   only ever owns [data-gsap]/[data-split]; the CSS .reveal system is
   separate (one owner per node). */
html.gsap-ready [data-gsap] { opacity: 0; }
html.gsap-ready [data-split] { visibility: hidden; }

/* Lenis smooth-scroll required styles (only active once Lenis adds .lenis). */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-scrolling iframe { pointer-events: none; }

/* Branded preloader — a pure-CSS curtain that ALWAYS wipes itself up (never
   JS-dependent, so it can never trap the page). Content renders underneath
   from frame 1, so LCP is untouched. */
#preloader {
  position: fixed; inset: 0; z-index: 120; background: #0C2E29;
  display: flex; align-items: center; justify-content: center;
  animation: preloaderOut 1.25s cubic-bezier(0.76,0,0.24,1) 0.15s forwards;
}
#preloader img { height: 72px; width: auto; opacity: 0; animation: preloaderMark 1.0s ease-out 0.08s forwards; }
#preloader::after {
  content: ""; position: absolute; left: 50%; bottom: 30%; width: 0; height: 1px;
  background: linear-gradient(90deg, transparent, #C9A86A, transparent);
  transform: translateX(-50%); animation: preloaderLine 1.1s ease-out 0.15s forwards;
}
@keyframes preloaderOut { 0%, 46% { transform: translateY(0); } 100% { transform: translateY(-100%); } }
@keyframes preloaderMark { 0% { opacity: 0; transform: translateY(8px); } 45%, 60% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; } }
@keyframes preloaderLine { 0% { width: 0; } 55% { width: 120px; } 100% { width: 0; } }

/* Custom cursor — enhancement layer. Native cursor is hidden ONLY once JS
   confirms it can run (adds html.cursor-ready), so a JS failure leaves the
   normal cursor intact. Desktop fine-pointer only. */
#cursor-dot, #cursor-ring {
  position: fixed; top: 0; left: 0; border-radius: 50%; pointer-events: none;
  z-index: 115; transform: translate(-50%, -50%); opacity: 0;
  will-change: transform;
}
#cursor-dot { width: 7px; height: 7px; background: #C9A86A; }
#cursor-ring {
  width: 40px; height: 40px; border: 1px solid rgba(201,168,106,0.55);
  transition: width 0.3s cubic-bezier(0.16,1,0.3,1), height 0.3s cubic-bezier(0.16,1,0.3,1), background-color 0.3s, border-color 0.3s;
}
html.cursor-ready #cursor-dot, html.cursor-ready #cursor-ring { opacity: 1; }
html.cursor-ready.cursor-hover #cursor-ring { width: 58px; height: 58px; background: rgba(201,168,106,0.08); border-color: rgba(201,168,106,0.8); }
@media (hover: hover) and (pointer: fine) {
  html.cursor-ready, html.cursor-ready a, html.cursor-ready button, html.cursor-ready [data-cursor], html.cursor-ready .tilt, html.cursor-ready * { cursor: none; }
}

/* Coarse pointer / touch — never show the custom cursor or preloader curtain
   dependency; native scroll only. */
@media (hover: none), (pointer: coarse) {
  #cursor-dot, #cursor-ring { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  #preloader { display: none; }
  #cursor-dot, #cursor-ring { display: none; }
  html.gsap-ready [data-gsap] { opacity: 1; }
  html.gsap-ready [data-split] { visibility: visible; }
}

/* ── Hero mobile text legibility ─────────────────────────────
   The dark scrim gradient's fade-to-transparent point is the same
   PERCENTAGE of container width on every viewport, but hero body copy
   wraps across far more of a narrow phone's width than desktop's — so its
   later lines/wrapped text can sit over the gradient's fully-transparent
   tail, right where the source photo's bright doctor coat is. A text-
   shadow keeps it legible regardless of what's behind it, instead of
   re-tuning gradient stops per breakpoint (which would need constant
   rebalancing against the vertical scrim too). Mobile-only since desktop
   text stays within the gradient's still-opaque region. */
@media (max-width: 640px) {
  .hero-legible { text-shadow: 0 1px 8px rgba(12,46,41,.85), 0 1px 3px rgba(12,46,41,.9); }
}

/* ── Hero background — responsive focal point ───────────────
   The source photo has a bright/near-white ground on the left where the
   copy sits, so a dark gradient overlay is permanent (not mobile-only)
   to keep the light-colored heading and eyebrow text readable.          */
.hero {
  background-image: url('/hero.jpg');
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center right;           /* desktop / default */
}
/* Doctor cutout (transparent PNG) layered above the hero background as a
   background-image div, not an <img> — a plain <img> with object-fit here
   sized itself off its own intrinsic aspect ratio instead of stretching
   to the section's (content-driven, non-fixed) height. Using the same
   background-image + inset:0 mechanism as .hero::before sidesteps that
   entirely. Must track the exact same position as .hero's background at
   every breakpoint to stay pixel-aligned with the room behind it.        */
.hero-doctor-overlay { background-position: center right; }
/* Desktop hero runs off a fixed 1600×900 design canvas (see index.html):
   the stage supplies its own photo, fade and doctor layers, so the
   section-level background + gradient must step aside there. */
.hero.is-staged { background-image: none; }
.hero.is-staged::before { display: none; }
/* Explicit hero layer stack, back to front:
   1. .hero background-image (Madam-banner-Doctor1, set via CSS background)
   2. .hero::before   — green fade gradient          (z-index 1)
   3. .hero-doctor-overlay — Madam-Doctor.png cutout  (z-index 2, see above)
   4. hero body copy (left-side text)                 (z-index 10)
   5. NavBar                                           (z-index 20, own class)  */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(12,46,41,.35) 0%, rgba(12,46,41,.10) 38%, rgba(12,46,41,.45) 100%),
    linear-gradient(90deg, rgba(12,46,41,1) 0%, rgba(12,46,41,1) 32%, rgba(12,46,41,.92) 38%, rgba(12,46,41,.75) 44%, rgba(12,46,41,.55) 50%, rgba(12,46,41,.36) 56%, rgba(12,46,41,.2) 62%, rgba(12,46,41,.08) 68%, rgba(12,46,41,0) 74%, rgba(12,46,41,0) 100%);
}
@media (max-width: 640px) {
  .hero { background-position: 63% 22%; }       /* nudge the doctor a touch right on mobile */
  .hero-doctor-overlay { background-position: 63% 22%; }
  .hero::before {
    background:
      linear-gradient(180deg, rgba(12,46,41,.55) 0%, rgba(12,46,41,.20) 38%, rgba(12,46,41,.55) 100%),
      linear-gradient(90deg, rgba(12,46,41,1) 0%, rgba(12,46,41,1) 32%, rgba(12,46,41,.92) 38%, rgba(12,46,41,.75) 44%, rgba(12,46,41,.55) 50%, rgba(12,46,41,.36) 56%, rgba(12,46,41,.2) 62%, rgba(12,46,41,.08) 68%, rgba(12,46,41,0) 74%, rgba(12,46,41,0) 100%);
  }
}

/* ═══════════ MOTION — PREMIUM POLISH · WAVE 1 ═══════════════
   Cross-page "expensive feel" signals. All additive + reduced-motion safe:
   nothing here can leave content hidden (defaults are the final state). */

/* Scroll progress — a hairline gold bar tracking page scroll (every page).
   Width is driven by a tiny vanilla listener in components.js. */
#scroll-progress {
  position: fixed; top: 0; left: 0; height: 2px; width: 0;
  background: linear-gradient(90deg, #C9A86A, #E7D2A2);
  box-shadow: 0 0 10px rgba(201,168,106,0.55);
  z-index: 130; pointer-events: none;
  transition: width 0.12s linear;
}
@media (prefers-reduced-motion: reduce) { #scroll-progress { display: none; } }

/* Hero cinematic intro (home). The doctor cutout is a separate empty layer,
   so blurring/scaling it in never touches the copy above it. Mouse-parallax
   uses the independent `translate`/`rotate` properties (see JS) so it composes
   with this keyframe's `transform` instead of fighting it. */
.hero { overflow: hidden; }
.hero-doctor-overlay { transition: translate 0.7s cubic-bezier(0.16,1,0.3,1), rotate 0.7s cubic-bezier(0.16,1,0.3,1); }
@media (prefers-reduced-motion: no-preference) {
  /* one-shot light sweep across the hero */
  .hero::after {
    content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none;
    background: linear-gradient(105deg, transparent 34%, rgba(255,255,255,0.12) 48%, transparent 60%);
    transform: translateX(-115%); animation: heroSweep 2.6s cubic-bezier(0.16,1,0.3,1) 0.55s 1 both;
  }
  /* doctor rises + clears from a soft blur, after the copy has begun revealing */
  .hero-doctor-overlay { animation: heroDoctorIn 1.7s cubic-bezier(0.16,1,0.3,1) 0.3s both; }
}
@keyframes heroSweep { to { transform: translateX(115%); } }
@keyframes heroDoctorIn {
  from { opacity: 0; transform: translateY(24px) scale(1.05); filter: blur(16px); }
  to   { opacity: 1; transform: translateY(0)   scale(1);    filter: blur(0); }
}
