/* ==========================================================================
   motion.css — "Editorial Restraint" motion layer for drbikramdentist.com
   Load order: css/index.css  →  css/site.css  →  css/motion.css   (in <head>)
   Paired with js/motion.js (deferred, after js/main.js).

   Contract
   --------
   * Every animating rule is prefixed html.m-motion. js/motion.js adds that
     class only when IntersectionObserver + requestAnimationFrame exist and
     prefers-reduced-motion is NOT set. With JS off, or motion reduced, this
     file contributes nothing but layout tokens and a focus ring.
   * EVERY rule that can hold content back — the [data-reveal] hold (§5), the
     cream veil over the patient photographs (§7), the image/video settle (§8)
     and the step numerals (§11) — also requires html.m-gated, which motion.js
     schedules for removal at 8s before doing anything else and strips in its
     catch block. Fail open, always. The rest (the hero lines, the gold
     strapline, the word stagger) is released by a class that motion.js adds
     on its own fallback timers instead.
   * Transform / opacity only. No width, height, top, left, margin, filter or
     clip-path animation anywhere — the FAQ panels fade rather than grow, and
     the step bars use scaleX rather than width.
   ========================================================================== */

/* --- 1. Tokens, header offset ------------------------------------------- */
:root {
  --m-ease: cubic-bezier(.16, 1, .3, 1);
  --m-micro: .4s;
  --m-reveal: .7s;
  --m-type: .9s;
  --m-image: 1.2s;
  --m-header-h: 96px; /* js/motion.js overwrites with the measured value */
}

/* Layout, not motion: hash + keyboard landings clear the fixed header.
   Applies under reduced motion and with JS disabled. */
html { scroll-padding-top: calc(var(--m-header-h) + 12px); }

/* --- 1d. Room for the sticky mobile bar ---------------------------------
   #mobile-cta is position:fixed (css/site.css) and sits in the DOM as a
   sibling of <footer>, so with nothing reserved it parks on top of the last
   ~64px of the footer — the GDC line and the copyright row. Nothing in
   index.css or site.css reserves that space, so it is reserved here.
   Layout, not motion: ungated, and applies under reduced motion and
   whenever js/motion.js is absent. main.js owns .has-mobile-cta, which only
   toggles near the top of the page, and padding-bottom appends space at the
   document end — it moves no visible content, so there is no CLS. */
@media (max-width: 767px) {
  body.has-mobile-cta { padding-bottom: calc(4.25rem + env(safe-area-inset-bottom)); }
}

/* --- 1b. Focus. Not motion, so deliberately outside the gate ------------ */
a:focus-visible,
button:focus-visible {
  outline: 2px solid #C9A861;
  outline-offset: 3px;
  box-shadow: none;
}

/* --- 1c. Retire the template loops -------------------------------------- */
/* The hero blobs carry INLINE animation-duration (8s/10s/12s), so !important
   is required here, not optional. */
html.m-motion .animate-light-sweep,
html.m-motion .animate-float-slow { animation: none !important; }
html.m-motion .animate-pulse { animation-duration: 14s !important; }
html.m-motion .light-glow { animation-duration: 7s; }

/* Redefined globally (this file loads after index.css) so the blobs breathe
   instead of throbbing. */
@keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: .78 } }

/* JS owns the easing for in-page jumps; scroll-padding-top still protects
   non-JS and keyboard landings. */
html.m-motion { scroll-behavior: auto; }


/* --- 2. Hero headline — line-mask rise ---------------------------------- */
html.m-motion .m-line {
  display: block;
  overflow: hidden;
  /* the padding / negative-margin pair stops leading-[0.85] and the italic
     descender clipping while keeping metrics — and therefore CLS — identical */
  padding: .06em .1em;
  margin: -.06em -.1em;
}
html.m-motion .m-line-in {
  display: block;
  transform: translateY(108%);
  transition: transform var(--m-type) var(--m-ease);
  transition-delay: calc(var(--m-i, 0) * 90ms);
}
/* Not tied to h2: the same recipe serves the treatment/location pages' h1. */
html.m-motion .m-in .m-line-in { transform: none; }

/* Gold strapline, sequenced after the slogan. */
html.m-motion [data-m-h1] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--m-reveal) var(--m-ease) .32s,
              transform var(--m-reveal) var(--m-ease) .32s;
}
html.m-motion [data-m-h1].m-in { opacity: 1; transform: none; }


/* --- 3. Anchor headings — word stagger (four, by design) ---------------- */
html.m-motion .m-w {
  display: inline-block;
  opacity: 0;
  transform: translateY(.35em);
  transition: opacity var(--m-reveal) var(--m-ease),
              transform var(--m-reveal) var(--m-ease);
  transition-delay: calc(var(--m-i, 0) * 40ms);
}
html.m-motion [data-m-type] > * {
  opacity: 0;
  transform: translateY(.35em);
  transition: opacity var(--m-reveal) var(--m-ease),
              transform var(--m-reveal) var(--m-ease);
  transition-delay: calc(var(--m-i, 0) * 40ms);
}
html.m-motion [data-m-type].m-in .m-w,
html.m-motion [data-m-type].m-in > * { opacity: 1; transform: none; }


/* --- 4. Eyebrow rule-draw ----------------------------------------------- */
html.m-motion [data-m-eyebrow] { position: relative; }
html.m-motion [data-m-eyebrow]::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -.45rem;
  width: 2rem;
  height: 1px;
  background: #C9A861;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .6s var(--m-ease) .15s;
  pointer-events: none;
}
html.m-motion .text-center > [data-m-eyebrow]::after {
  left: 50%;
  margin-left: -1rem;
  transform-origin: center;
}
html.m-motion [data-m-eyebrow].m-in::after { transform: scaleX(1); }


/* --- 5. Reveal cadence — re-timed, staggered, fail-open ----------------- */
html.m-motion [data-reveal] {
  transform: translateY(14px);
  transition-duration: var(--m-reveal);
}
/* The only rule in this file that can hold content back. It needs BOTH gate
   classes; motion.js removes .m-gated at 8s and in its catch block. */
html.m-motion.m-gated [data-reveal].is-visible:not([data-m-in]) {
  opacity: 0;
  transform: translateY(14px);
}
html.m-motion [data-reveal][data-m-in] { opacity: 1; transform: none; }

/* Nested reveals ride the parent's fade, except the two hero blocks that are
   genuinely sequenced (marked data-m-seq by motion.js). */
html.m-motion [data-reveal] [data-reveal]:not([data-m-seq]) {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}


/* --- 6. Hero video — dissolve, drift, pause off screen ------------------ */
html.m-motion #home > video {
  opacity: 0 !important;
  transition: opacity var(--m-image) var(--m-ease);
}
html.m-motion #home > video[data-m-playing] { opacity: .5 !important; }

@media (hover: hover) and (min-width: 768px) {
  html.m-motion #home > video {
    top: -8% !important;
    bottom: -8% !important;
    height: 116% !important;
    transform: translate3d(0, var(--m-py, 0px), 0);
  }
}


/* --- 7. Patient results — before-then-after reading order --------------- */
/* The case wrapper is div.relative.w-full.overflow-hidden[style*="aspect-ratio"];
   --reveal-delay is set inline by main.js on the grid child and inherits down. */
html.m-motion #results [style*="aspect-ratio"] {
  position: relative;
  overflow: hidden;
}
/* Cream veil: origin bottom, so its lower edge travels top → bottom and the
   BEFORE half (top of the composite) is uncovered first.
   The veil is opaque and covers a consented clinical photograph, so the
   covering declaration lives behind html.m-gated exactly like the one in
   §5: if a card never reaches the observer's threshold, the 8s fail-open
   timer uncovers it rather than leaving a cream rectangle for ever.
   Resting state is therefore scaleY(0) — uncovered. */
html.m-motion #results [style*="aspect-ratio"]::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit; /* the wrapper is rounded-sm; don't overhang it */
  background: #F5F0E8;
  transform: scaleY(0);
  transform-origin: bottom center;
  transition: transform .9s var(--m-ease);
  transition-delay: var(--reveal-delay, 0s);
  pointer-events: none;
  z-index: 1;
}
html.m-motion.m-gated #results [style*="aspect-ratio"]::after { transform: scaleY(1); }
html.m-motion #results [data-m-in] [style*="aspect-ratio"]::after { transform: scaleY(0); }

/* Gold seam on the midline, drawn as the veil crosses it. */
html.m-motion #results [style*="aspect-ratio"]::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: #C9A861;
  transform: scaleX(0);
  transform-origin: left center;
  opacity: .9;
  z-index: 2;
  pointer-events: none;
  transition: transform .38s var(--m-ease) calc(var(--reveal-delay, 0s) + .4s),
              opacity 1.2s linear calc(var(--reveal-delay, 0s) + .8s);
}
html.m-motion #results [data-m-in] [style*="aspect-ratio"]::before {
  transform: scaleX(1);
  opacity: .35;
}

html.m-motion #results [style*="aspect-ratio"] img {
  transition: transform var(--m-image) var(--m-ease);
}
html.m-motion.m-gated #results [style*="aspect-ratio"] img { transform: scale(1.04); }
html.m-motion #results [data-m-in] [style*="aspect-ratio"] img { transform: none; }

/* Beats Tailwind's hover:scale-105 duration-700 by specificity. */
html.m-motion #results [style*="aspect-ratio"]:hover img {
  transform: scale(1.03);
  transition-duration: var(--m-image);
}


/* --- 8. The other large imagery — one settle ----------------------------
   The release has to be ID-qualified. A plain
   `[data-reveal][data-m-in] video` (0,3,2) loses to `#about img` /
   `#inside video` (1,1,2), so the earlier version of this block set
   scale(1.04) and then never took it off: the three consented videos and
   the portrait sat permanently 4% over-cropped and the settle never played.
   The covering scale also lives behind html.m-gated, so the 8s fail-open
   timer returns everything to its true crop.                              */
html.m-motion #inside video,
html.m-motion #mirror video,
html.m-motion #testimonials video {
  transition: transform var(--m-image) var(--m-ease);
}
html.m-motion.m-gated #inside video,
html.m-motion.m-gated #mirror video,
html.m-motion.m-gated #testimonials video { transform: scale(1.04); }

/* The portrait keeps its authored grayscale->colour hover, so filter stays in
   the transition list alongside transform. */
html.m-motion #about img {
  transition: transform var(--m-image) var(--m-ease),
              filter .5s cubic-bezier(.4, 0, .2, 1);
}
html.m-motion.m-gated #about img { transform: scale(1.04); }

html.m-motion #inside [data-reveal][data-m-in] video,
html.m-motion #mirror [data-reveal][data-m-in] video,
html.m-motion #testimonials [data-reveal][data-m-in] video,
html.m-motion #about [data-reveal][data-m-in] img { transform: none; }


/* --- 9. Hover and focus — one vocabulary -------------------------------- */
html.m-motion a[href="#contact"],
html.m-motion a[href^="tel:"] {
  transition: background-color var(--m-micro) var(--m-ease),
              color var(--m-micro) var(--m-ease),
              border-color var(--m-micro) var(--m-ease);
}
/* Scoped to the links that actually carry the authored lift, i.e. the ones
   whose class list holds hover:-translate-y-*. Matching every a[href^="tel:"]
   was too wide: the floating call button is a tel: link whose shadow-lg is
   structural, and box-shadow:none !important made it blink out on hover.
   Anchors only — the process cards use hover:-translate-y-2 and keep it. */
html.m-motion a[class*="hover:-translate-y"]:hover {
  transform: none !important;
  box-shadow: none !important;
}

/* Only arrow glyphs travel. `#services a svg` matched all twelve icons in
   that section — eleven are lucide category marks sitting inside the card
   link, on a wrapper that already does group-hover:scale-110, so they slid
   sideways and scaled at the same time. */
html.m-motion a [class*="lucide-arrow-right"],
html.m-motion a [class*="lucide-arrow-up-right"],
html.m-motion a [class*="lucide-chevron-right"] {
  transition: transform var(--m-micro) var(--m-ease);
}
html.m-motion a:hover [class*="lucide-arrow-right"],
html.m-motion a:hover [class*="lucide-arrow-up-right"],
html.m-motion a:hover [class*="lucide-chevron-right"] { transform: translateX(3px); }

/* Text links: a 1px underline drawn from the left instead of a colour swap. */
html.m-motion a[href="#contact"].border-b,
html.m-motion #locations a:not([class*="bg-"]),
html.m-motion footer a:not([class*="border"]):not([class*="bg-"]) {
  border-bottom-color: transparent !important;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size var(--m-micro) var(--m-ease);
}
html.m-motion a[href="#contact"].border-b:hover,
html.m-motion #locations a:not([class*="bg-"]):hover,
html.m-motion footer a:not([class*="border"]):not([class*="bg-"]):hover { background-size: 100% 1px; }

html.m-motion #mobile-cta a:active {
  transform: scale(.98);
  transition-duration: 120ms;
}


/* --- 11. Process steps, step bars, FAQ ---------------------------------- */
/* The 01-04 step numerals. opacity:0 here is content being held back, and its
   only release is [data-m-in] — so, like §5, the holding declaration needs
   html.m-gated and is lifted by the 8s fail-open timer. */
html.m-motion .animate-float-slow {
  transition: transform var(--m-reveal) var(--m-ease),
              opacity var(--m-reveal) var(--m-ease);
  transition-delay: var(--reveal-delay, 0s);
}
html.m-motion.m-gated .animate-float-slow {
  transform: translateY(.35em);
  opacity: 0;
}
html.m-motion [data-reveal][data-m-in] .animate-float-slow {
  transform: none;
  opacity: 1;
}

/* scaleX, not width: animating width would relayout and repaint inside a
   backdrop-blur card every frame for .7s, four times over. */
html.m-motion [data-m-bar] {
  width: 100% !important;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .7s var(--m-ease);
  transition-delay: calc(var(--reveal-delay, 0s) + .25s);
}
html.m-motion [data-reveal][data-m-in] [data-m-bar],
html.m-motion .group:hover [data-m-bar] { transform: scaleX(1); }

/* FAQ panels fade rather than animate their height.
   Two reasons. First, @keyframes accordion-down in css/index.css ends at
   height:var(--radix-accordion-content-height), but js/main.js sets
   --radix-collapsible-content-height — the variable never resolves, the `to`
   keyframe falls back to height:auto and the growth was never interpolating
   anyway. Second, height is a layout property: ten FAQ rows relaying out per
   frame is exactly what this layer is meant to avoid. Opacity and transform
   only; the panel's height snaps, which is what the broken version already
   did visually. main.js waits for animationend (or 260ms) before it sets
   [hidden], so the closing animation below is what keeps that path honest.

   No fill-mode on the opening animation, deliberately. A hidden document
   freezes the animation timeline outright — open the page in a background
   tab and document.timeline never advances — so an entrance animation with
   `both` would hold its FROM keyframe and pin the answer at opacity:0 with
   nothing to release it. Without a fill, the resting state is the element's
   own style: visible. The closing animation keeps `forwards` because its end
   state is the one main.js is about to make [hidden] anyway, and a frozen
   clock there simply leaves the panel opaque until the 260ms fallback. */
html.m-motion [data-faq] [role="region"][data-state="open"],
html.m-motion [data-orientation="vertical"] [role="region"][data-state="open"] {
  animation: m-faq-in .3s var(--m-ease);
}
html.m-motion [data-faq] [role="region"][data-state="closed"],
html.m-motion [data-orientation="vertical"] [role="region"][data-state="closed"] {
  animation: m-faq-out .2s var(--m-ease) forwards;
}
@keyframes m-faq-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes m-faq-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}
html.m-motion button[id^="faq-trigger-"] svg {
  transition: transform var(--m-micro) var(--m-ease);
}


/* --- 12. Mobile bar, menu and FAB panels -------------------------------- */
html.m-motion #mobile-cta { transition: transform .45s var(--m-ease); }
html.m-motion #mobile-cta::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, #C9A861 30%, #C9A861 70%, transparent);
  opacity: 0;
  transition: opacity .6s var(--m-ease) .2s;
}
html.m-motion #mobile-cta.is-visible::before { opacity: 1; }

/* The bar yields to the booking form. The padding reservation in §1d stays
   put while it slides, so the GHL iframe never reflows. js/motion.js decides
   when, from the form's own position rather than a share of the section's
   height — see contactAwareBar(). */
html.m-motion body.m-contact-open #mobile-cta { transform: translateY(100%); }

/* No fill-mode here either, for the reason given in §11: a frozen animation
   timeline (hidden document) plus `both` would hold the FROM keyframe and
   leave the quiz, the assistant panel or the mobile menu at opacity:0. The
   resting style of all three is already the visible one. */
html.m-motion #fab .animate-in,
html.m-motion #mobile-menu {
  animation: m-panel-in .4s var(--m-ease);
}
@keyframes m-panel-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}


/* ==========================================================================
   Reduced motion. The only block in this file that overrides outside
   html.m-motion — because css/index.css sets a global scroll-behavior:smooth
   and css/site.css covers only [data-reveal], .site-header, #mobile-menu and
   .light-glow. js/motion.js never adds .m-motion here, so items 2-12 are
   inert; scroll-padding-top still applies, being layout rather than motion.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto !important; }

  .animate-pulse,
  .animate-light-sweep,
  .animate-float-slow,
  .animate-in,
  .light-glow,
  .chat-typing span,
  [data-faq] [role="region"],
  [data-orientation="vertical"] [role="region"] { animation: none !important; }

  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}
