/**
 * Shared FX layer for all Tunai themes — loaded before each theme's own
 * stylesheet. Generic mechanics only; each theme supplies the colours via
 * custom properties on its root:
 *
 *   --fx-accent  scroll-progress bar colour
 *   --fx-spot    cursor-spotlight tint (a translucent rgba)
 *
 * The matching behaviour lives in ThemeFX (data-tilt / data-spot /
 * data-magnetic / data-reveal / data-countup / data-parallax).
 */

/* Cursor spotlight — a soft light that follows the pointer across a card. */
[data-spot] {
  position: relative;
}

[data-spot]::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s ease;
  background: radial-gradient(
    240px circle at var(--mx, 50%) var(--my, 50%),
    var(--fx-spot, rgba(255, 255, 255, 0.12)),
    transparent 65%
  );
}

[data-spot]:hover::after {
  opacity: 1;
}

/* 3D tilt — ThemeFX drives --rx/--ry from the pointer; only under .tfx and a
   fine pointer, so touch devices and no-JS never get a stuck perspective.
   Hover also pops the card a touch (--tz) so tilt reads as a lift. */
.tfx [data-tilt] {
  transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg))
    scale(var(--tz, 1));
  transform-style: preserve-3d;
  will-change: transform;
  transition: transform 0.16s ease;
}

.tfx [data-tilt]:hover {
  --tz: 1.02;
}

/* Magnetic CTAs — ThemeFX nudges `translate` toward the cursor; the spring
   back happens here. Composes with any theme `transform` (skews etc.).
   Hover adds an accent bloom so the pull reads as energy, not drift. */
[data-magnetic] {
  transition: translate 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), filter 0.3s ease;
  will-change: translate;
}

.tfx [data-magnetic]:hover {
  filter: drop-shadow(0 8px 22px color-mix(in srgb, var(--fx-accent, #888) 40%, transparent));
}

@media (prefers-reduced-motion: reduce) {
  .tfx [data-tilt] {
    transform: none;
    transition: none;
  }

  [data-spot]::after {
    display: none;
  }
}

/* ── FX v3 ──────────────────────────────────────────────────────────────── */

/* Headline sizing + wrap defence, for every split-text headline. Not gated on
   .tfx so it also holds pre-hydration / no-JS / reduced-motion. Theme hero
   rules consume --tx-scale inside their font-size calc(); --tx-auto is set per
   element by ThemeFX when the text is long, --tx-user comes from the studio's
   "Headline size" chips (emitted on the theme root by ThemeRender). */
[data-splittext] {
  text-wrap: balance;
  overflow-wrap: anywhere;
  --tx-scale: calc(var(--tx-auto, 1) * var(--tx-user, 1));
}

/* Split-text headline: ThemeFX wraps each character in .fx-w > .fx-c; chars
   rise out of their own clipping box with a slight settling rotation,
   staggered via --d.

   Themes often set display line-heights around ~1 (sometimes < 1). Glyph ink
   — ascenders, descenders, italic side bearings, serifs — paints outside that
   line box, so a bare overflow:hidden clip shaves the letters. Pad the clip
   past the overhang and cancel with negative margin so layout/spacing stay
   the same. */
.tfx [data-splittext] .fx-w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-block: 0.28em;
  padding-inline: 0.14em;
  margin-block: -0.28em;
  margin-inline: -0.14em;
}

.tfx [data-splittext] .fx-c {
  display: inline-block;
  translate: 0 115%;
  rotate: 9deg;
  opacity: 0;
  transition: translate 0.75s cubic-bezier(0.2, 0.8, 0.2, 1),
    rotate 0.75s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.45s ease;
  transition-delay: var(--d, 0s);
}

.tfx [data-splittext].fx-ready .fx-c {
  translate: 0 0;
  rotate: 0deg;
  opacity: 1;
}

/* Curtain image reveal: an accent panel wipes off the image when it enters
   view, while the photo settles from a slight over-zoom. Uses ::before so it
   composes with themes that already use ::after on the same figure. */
.tfx [data-curtain] {
  position: relative;
  overflow: hidden;
}

.tfx [data-curtain]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  background: var(--fx-accent, #000);
  transform: scaleX(1);
  transform-origin: right;
  transition: transform 0.8s cubic-bezier(0.7, 0, 0.2, 1) 0.1s;
  pointer-events: none;
}

.tfx [data-curtain].is-in::before {
  transform: scaleX(0);
}

.tfx [data-curtain] img {
  scale: 1.14;
  transition: scale 1.2s cubic-bezier(0.2, 0.8, 0.2, 1) 0.15s;
}

.tfx [data-curtain].is-in img {
  scale: 1;
}

/* Pin theme app bar to the viewport (sticky fails under overflow-x: hidden).
   ThemeFX also earns the attribute its name: scrolling down slides the bar
   away (fx-nav-hidden), scrolling up brings it back; once past the fold it
   gains a soft drop shadow (fx-nav-scrolled) so it separates from content. */
[data-autohide].fx-nav-pinned {
  position: fixed !important;
  z-index: 50 !important;
}

.tfx [data-autohide].fx-nav-pinned {
  transition: translate 0.38s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.38s ease;
}

.tfx [data-autohide].fx-nav-hidden {
  translate: 0 -130%;
}

.tfx [data-autohide].fx-nav-scrolled {
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.14);
}

/* Accent-tinted text selection, everywhere the FX layer runs. */
.tfx ::selection {
  background: color-mix(in srgb, var(--fx-accent, #888) 28%, transparent);
}

/* Scrollspy: the nav link whose section is on screen lights up in accent. */
.tfx [data-nav] a.fx-active {
  color: var(--fx-accent, currentColor) !important;
}

/* Long section lists (studio Settings). A template sizes its bar for the four
   or five links it wrote; a merchant with every section switched on gets up to
   nine, which would push the Book CTA off the end. `ThemeNavLinks` measures the
   row and steps the list down until it fits — the template's own gap and type
   scale stay in charge for as long as they do fit. */
[data-nav][data-nav-synced] > * {
  white-space: nowrap;
}

[data-nav][data-nav-dense] {
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}

[data-nav][data-nav-dense]::-webkit-scrollbar {
  display: none;
}

[data-nav][data-nav-dense] > * {
  flex: 0 0 auto;
  white-space: nowrap;
}

[data-nav][data-nav-dense="1"] {
  gap: clamp(0.65rem, 1.5vw, 1.35rem) !important;
  font-size: 0.94em;
}

[data-nav][data-nav-dense="2"] {
  gap: clamp(0.5rem, 1.1vw, 0.9rem) !important;
  font-size: 0.86em;
}

[data-nav][data-nav-dense="3"] {
  gap: 0.42rem !important;
  font-size: 0.78em;
  letter-spacing: 0.02em !important;
}

/* Still too long for the bar (a narrow pill nav): it scrolls, and the edge
   fades so there is something to say that. */
[data-nav][data-nav-scroll] {
  -webkit-mask-image: linear-gradient(90deg, #000 86%, transparent);
  mask-image: linear-gradient(90deg, #000 86%, transparent);
}

/* Click ripple on CTAs. */
[data-ripple] {
  position: relative;
  overflow: hidden;
}

.fx-ripple {
  position: absolute;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.3;
  scale: 0;
  animation: fx-ripple 0.55s ease-out forwards;
  pointer-events: none;
}

@keyframes fx-ripple {
  to {
    scale: 1;
    opacity: 0;
  }
}

/* Marquee utility: render the track twice inside .fx-marquee. Edges fade out
   so the ticker melts into the page instead of clipping hard. */
.fx-marquee {
  display: flex;
  overflow: hidden;
  -webkit-user-select: none;
  user-select: none;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}

/* Let readers stop the ticker to read it. */
.fx-marquee:hover > span {
  animation-play-state: paused;
}

.fx-marquee > span {
  display: inline-flex;
  flex: 0 0 auto;
  min-width: 100%;
  justify-content: space-around;
  animation: fx-marquee var(--fx-mq, 28s) linear infinite;
}

@keyframes fx-marquee {
  to {
    translate: -100% 0;
  }
}

/* ── Theme hero carousel (ThemeHero) — drop-in for static hero <img>s ── */
.theme-hero {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
  cursor: grab;
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
}

.theme-hero:active {
  cursor: grabbing;
}

/* Full-bleed absolute hero frames (forge / ember / velocity). */
.fg-hero-bg > .theme-hero,
.em-hero-bg > .theme-hero,
.vx-hero-bg > .theme-hero {
  position: absolute;
  inset: 0;
  width: 100%;
  height: auto;
}

/*
 * Full-bleed heroes stack the image under copy. Raise the bg above the
 * gradient, let swipes hit the carousel, and keep CTAs clickable.
 */
.fg-hero-bg,
.em-hero-bg,
.vx-hero-bg {
  z-index: 0 !important;
}

.fg-hero::after,
.em-hero::after,
.vx-hero::after {
  z-index: 1;
  pointer-events: none;
}

.fg-hero-inner,
.em-hero-inner,
.vx-hero-inner {
  position: relative;
  z-index: 2;
  pointer-events: none;
}

.fg-hero-inner a,
.fg-hero-inner button,
.em-hero-inner a,
.em-hero-inner button,
.vx-hero-inner a,
.vx-hero-inner button {
  pointer-events: auto;
}

.theme-hero__sizer {
  position: static !important;
  inset: auto !important;
  display: block !important;
  width: 100% !important;
  height: auto;
  visibility: hidden;
  pointer-events: none;
  max-width: none !important;
  filter: none !important;
  object-fit: cover;
  -webkit-user-drag: none;
}

.theme-hero__viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
  touch-action: pan-y;
}

.theme-hero__track {
  display: flex;
  height: 100%;
  transition: transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

.theme-hero__slide {
  position: relative;
  flex: 0 0 100%;
  height: 100%;
  overflow: hidden;
}

/* Beat theme rules that absolute-position bare hero imgs (forge/ember/velocity). */
.theme-hero__slide img {
  position: absolute !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  object-fit: cover !important;
  max-width: none !important;
  filter: none !important;
  display: block;
  pointer-events: none;
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
}

/* Ken Burns drift: the visible slide's photo slowly settles outward, so the
   hero never sits perfectly still. Restarts each time a slide takes over. */
.tfx .theme-hero__slide--active img {
  animation: fx-kenburns 9s ease-out forwards;
}

@keyframes fx-kenburns {
  from {
    scale: 1;
  }
  to {
    scale: 1.07;
  }
}

/* Desktop arrows — appear on hover for fine pointers; swipe/dots elsewhere. */
.theme-hero__arrow {
  position: absolute;
  z-index: 2;
  top: 50%;
  display: none;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.32);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transform: translateY(-50%);
  transition: opacity 0.25s ease, background 0.2s ease, scale 0.2s ease;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

@media (hover: hover) and (pointer: fine) {
  .theme-hero__arrow {
    display: grid;
  }
}

.theme-hero__arrow--prev {
  left: 12px;
}

.theme-hero__arrow--next {
  right: 12px;
}

.theme-hero:hover .theme-hero__arrow,
.theme-hero__arrow:focus-visible {
  opacity: 1;
}

.theme-hero__arrow:hover {
  background: rgba(0, 0, 0, 0.55);
  scale: 1.08;
}

.theme-hero__dots {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 12px;
  display: flex;
  gap: 6px;
  align-items: center;
  transform: translateX(-50%);
  padding: 6px 10px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: rgba(0, 0, 0, 0.32);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: auto;
}

.theme-hero__dot {
  width: 6px;
  height: 6px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.45);
  cursor: pointer;
  transition: width 0.25s ease, background 0.25s ease;
  pointer-events: auto;
}

.theme-hero__dot--active {
  width: 18px;
  background: #fff;
}

/* With FX running, the active dot becomes an autoplay timer: a white fill
   sweeps across it over the slide interval (--fx-dot-ms from ThemeHero).
   Hover pauses the fill together with autoplay. */
.tfx .theme-hero__dot--active {
  position: relative;
  overflow: hidden;
  width: 22px;
  background: rgba(255, 255, 255, 0.38);
}

.tfx .theme-hero__dot--active::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: #fff;
  transform: scaleX(0);
  transform-origin: left;
  animation: fx-dotfill var(--fx-dot-ms, 4500ms) linear forwards;
}

.tfx .theme-hero:hover .theme-hero__dot--active::after {
  animation-play-state: paused;
}

@keyframes fx-dotfill {
  to {
    transform: scaleX(1);
  }
}

/* ── Gallery tiles (ThemeGallery arms [data-gallery-index]) ──
   Tiles are tap-to-enlarge everywhere; give them hover feedback and a real
   keyboard focus ring. Filter + translate compose with any theme transform. */
.tfx [data-gallery-index] {
  transition: filter 0.35s ease, translate 0.35s ease, scale 0.35s ease;
}

.tfx [data-gallery-index]:hover {
  filter: brightness(1.06) saturate(1.05);
  translate: 0 -3px;
  scale: 1.02;
}

[data-gallery-index]:focus-visible {
  outline: 2px solid var(--fx-accent, currentColor);
  outline-offset: 3px;
}

/* ── Back to top (rendered by ThemeFX) ──
   position is !important: ThemeFX chrome mounts as a direct child of the
   theme root, and some themes blanket-relative their children
   (`.x-theme > * { position: relative }`) which would drop it into flow. */
.fx-top {
  position: fixed !important;
  z-index: 40;
  right: 18px;
  bottom: 18px;
  display: none;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.32);
  border-radius: 999px;
  background: rgba(20, 20, 24, 0.55);
  color: #fff;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  cursor: pointer;
  opacity: 0;
  translate: 0 8px;
  pointer-events: none;
  transition: opacity 0.3s ease, translate 0.3s ease, background 0.2s ease;
}

/* Mobile keeps its bottom book bar clear; momentum-scroll covers the need. */
@media (min-width: 768px) {
  .fx-top {
    display: grid;
  }
}

.fx-top.is-vis {
  opacity: 1;
  translate: 0 0;
  pointer-events: auto;
}

.fx-top:hover {
  background: var(--fx-accent, rgba(20, 20, 24, 0.8));
}

/* Reading-progress ring around the button (--fx-progress set by ThemeFX). */
.tfx .fx-top::before {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: inherit;
  background: conic-gradient(
    var(--fx-accent, #fff) calc(var(--fx-progress, 0) * 360deg),
    transparent 0
  );
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .tfx [data-splittext] .fx-c {
    translate: 0 0;
    rotate: 0deg;
    opacity: 1;
    transition: none;
  }

  .tfx .theme-hero__dot--active::after {
    display: none;
  }

  .tfx [data-autohide].fx-nav-hidden {
    translate: 0 0;
  }

  .tfx [data-curtain]::before {
    display: none;
  }

  .tfx [data-curtain] img {
    scale: 1;
    transition: none;
  }

  .fx-marquee > span {
    animation: none;
  }

  .fx-ripple {
    display: none;
  }

  .theme-hero__track {
    transition: none;
  }
}

/* ── Theme outlet picker (ThemeOutletPicker) ──
   Frosted floating control so it stays readable on any theme palette.
   Positioned below the theme app bar via --theme-outlet-nav-offset. */
.theme-outlet-pill {
  position: fixed;
  z-index: 45;
  top: calc(var(--theme-outlet-nav-offset, 72px) + 10px);
  right: 16px;
  display: none;
}

@media (min-width: 1024px) {
  .theme-outlet-pill {
    display: block;
  }
}

.theme-outlet-pill__btn {
  display: flex;
  align-items: center;
  gap: 12px;
  width: min(320px, calc(100vw - 32px));
  padding: 12px 14px;
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 999px;
  background: rgba(20, 20, 24, 0.55);
  color: #fff;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.22);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  text-align: left;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}

.theme-outlet-pill__btn:hover {
  background: rgba(20, 20, 24, 0.7);
}

/* any.tunai.io — pull colours from the active template tokens */
.theme-outlet-pill--skinned .theme-outlet-pill__btn {
  border-color: color-mix(in srgb, var(--hairline) 70%, transparent);
  background: color-mix(in srgb, var(--canvas) 88%, transparent);
  color: var(--ink);
  box-shadow: 0 10px 30px color-mix(in srgb, var(--ink) 18%, transparent);
  font-family: var(--font-sans, inherit);
}

.theme-outlet-pill--skinned .theme-outlet-pill__btn:hover {
  background: var(--canvas);
}

.theme-outlet-pill--skinned .theme-outlet-pill__icon,
.theme-outlet-pill--skinned .theme-outlet-pill__chevron {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}

.theme-outlet-pill--skinned .theme-outlet-pill__city {
  opacity: 1;
  color: var(--muted);
}

.theme-outlet-pill__btn--static {
  cursor: default;
}

.theme-outlet-pill__icon,
.theme-outlet-pill__chevron {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.14);
}

.theme-outlet-pill__text {
  min-width: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.theme-outlet-pill__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.2;
}

.theme-outlet-pill__city {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 11.5px;
  line-height: 1.3;
  opacity: 0.78;
}

.theme-outlet-fab {
  position: fixed;
  z-index: 45;
  top: calc(var(--theme-outlet-nav-offset, 72px) + 8px);
  right: 12px;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 999px;
  background: rgba(20, 20, 24, 0.55);
  color: #fff;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  cursor: pointer;
}

.theme-outlet-fab--skinned {
  border-color: color-mix(in srgb, var(--hairline) 70%, transparent);
  background: color-mix(in srgb, var(--canvas) 88%, transparent);
  color: var(--accent);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--ink) 18%, transparent);
}

@media (min-width: 1024px) {
  .theme-outlet-fab {
    display: none;
  }
}

/* Portaled outlet dialog — match template tokens (Design Studio host). */
.theme-outlet-modal-skin {
  font-family: var(--font-sans, inherit);
  color: var(--ink);
}

.theme-outlet-modal-skin .anim-modal-in {
  border-radius: var(--radius-lg, 20px);
  background: var(--canvas);
  box-shadow: 0 8px 40px color-mix(in srgb, var(--ink) 18%, transparent);
}

.theme-outlet-modal-skin .icon-circle-soft {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--ink);
}

.theme-outlet-modal-skin .icon-circle-accent {
  background: var(--accent);
  color: var(--on-accent, #fff);
}

/* ── Theme app-bar action cluster ──────────────────────────────────────────
   Tunai's own app-bar controls (store icon, language picker) plus the theme's
   Book CTA, kept as one flex item so `space-between` navs don't blow a gap
   between them. Order: store (1), language (2), Book (3).

   `data-compact` is set from JS while the theme's own bottom Book bar is on
   screen — each template reveals that bar at its own breakpoint (720…940px),
   so this mirrors the template instead of guessing a shared media query. It
   also has to do the hiding itself: wrapping Book in the cluster breaks the
   theme's own `nav > .xx-btn { display: none }` direct-child rule. */
.theme-appbar-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
}

.theme-appbar-slot {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}

.theme-appbar-actions > .theme-appbar-book {
  order: 3;
}

/* Mobile: Book lives in the stacked bottom bar, so the app bar keeps only the
   brand + store icon + language picker. */
.theme-appbar-actions[data-compact="1"] > .theme-appbar-book {
  display: none !important;
}

/* ── Brand logo in the theme app-bar lockup ────────────────────────────────
   Every template shows `shop.icon` beside the brand name (same circle as the
   stock landing). Decorative marks (emoji, ::before bars, theme glyphs) stay
   as the no-icon fallback and are hidden while the real logo is present. */
.theme-brand-logo {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
  display: block;
  outline: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  outline-offset: 1px;
}

[class*="-theme"] a[class*="-logo"]:has(> .theme-brand-logo) {
  display: flex;
  align-items: center;
  gap: 10px;
}

[class*="-theme"] a[class*="-logo"]:has(> .theme-brand-logo)::before,
[class*="-theme"] a[class*="-logo"]:has(> .theme-brand-logo)::after {
  content: none !important;
  display: none !important;
}

[class*="-theme"] a[class*="-logo"]:has(> .theme-brand-logo) > [aria-hidden="true"] {
  display: none !important;
}

/* ── Unified mobile Book bar ───────────────────────────────────────────────
   Every template ports its own bottom Book bar (`.xx-mobile-bar`), each with
   its own reveal breakpoint (720…940px) and its own padding/backdrop. That
   left phones between 720 and 900px with no pinned CTA at all, and the bar's
   look drifting per template.

   These rules take over the bar for all templates at one shared breakpoint:
   full-bleed, pinned to the viewport bottom, button stretched across it,
   safe-area padded, on a solid band. The band's colour comes from the
   `--fx-bar-*` tokens `ThemeRender` emits from `theme.brand`, so it matches
   the template it sits under on light and dark alike.

   fx.css loads BEFORE the template stylesheet, so the layout declarations
   need `!important` to outrank the template's own bar rule. The colour ones
   do not: this selector (two attribute matches) already outranks the
   template's single-class rule, so a template that wants its own backdrop can
   still take it back by raising specificity.

   `theme-appbar.ts` watches this bar's computed `display` and hides the
   in-nav Book while it is up, so templates that only drop their nav CTA at
   720px do not show two Book buttons in the 720–900 band. */
@media (max-width: 900px) {
  [class*="-theme"] [class*="-mobile-bar"] {
    display: flex !important;
    position: fixed !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    top: auto !important;
    z-index: 99 !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 10px !important;
    width: auto !important;
    margin: 0 !important;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom)) !important;
    background: var(--fx-bar-bg, #fff);
    border-top: 1px solid var(--fx-bar-line, rgba(0, 0, 0, 0.12));
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
  }

  /* The CTA fills the bar — one button stretches, two share the row. */
  [class*="-theme"] [class*="-mobile-bar"] > a,
  [class*="-theme"] [class*="-mobile-bar"] > button {
    flex: 1 1 auto !important;
    width: auto !important;
    min-width: 0 !important;
    text-align: center !important;
  }

  /* Nothing at the end of the page may hide under the bar. Matched on the
     `footer` ELEMENT, not on `[class*="-footer"]` alone — templates also name
     inner rows `xx-footer-row` / `xx-footer-social`, and padding each of those
     would stack up into a huge dead strip. */
  [class*="-theme"] footer[class*="-footer"] {
    padding-bottom: 120px !important;
  }
}

/* Above the shared breakpoint the bar is never on screen, whatever the
   template's own media query says. */
@media (min-width: 901px) {
  [class*="-theme"] [class*="-mobile-bar"] {
    display: none !important;
  }
}

/* ── Social row (ThemeSocialLinks) ─────────────────────────────────────────
   Templates wrote their own footer social links as text ("Facebook", "IG"),
   styled like the fine print around them — easy to miss and different on
   every template.

   These are solid filled circles carrying each platform's mark, under a
   "Follow us" label that rides in the same container so the group reads as a
   section rather than as three stray icons. The fill is the TEMPLATE's accent
   (`--fx-social-bg`, emitted by ThemeRender from the theme's brand accent, or
   the merchant's custom one), with `--fx-social-ink` the white-or-dark glyph
   that reads on it — so the row is loud without importing colours the
   template never chose. The marks themselves do the recognising.

   Two classes deep on purpose: the template's own `.xx-footer-social a` rule
   (one class + one element) would otherwise win, since fx.css loads first. */
/* Let the row break instead of overflowing. Templates lay this row out
   themselves and none of them set `flex-wrap`, so at 320px a container with a
   22px gap (ayu and ~20 others) pushes the last circle past the edge — and
   several templates clip it with `overflow-x: hidden` rather than scrolling.
   `:has()` reaches whatever element the template wrapped these in. */
[class*="-theme"] *:has(> .theme-social-btn) {
  flex-wrap: wrap;
}

[class*="-theme"] .theme-social-label {
  align-self: center;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  line-height: 1;
  text-transform: uppercase;
  opacity: 0.7;
  white-space: nowrap;
}

[class*="-theme"] .theme-social-btn {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  /* Literal fallbacks, not currentColor: `currentColor` here resolves against
     this rule's own `color` — a white circle behind a white glyph — in exactly
     the case it was meant to cover (a stale fx.css with no tokens emitted). */
  background: var(--fx-social-bg, #111111);
  color: var(--fx-social-ink, #ffffff);
  line-height: 1;
  letter-spacing: normal;
  text-decoration: none;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--fx-social-bg, #111111) 36%, transparent);
  /* The lift is written with `translate`/`scale` rather than `transform`
     because bubble animates its footer links that way (independent
     properties compose with `transform`, they do not replace it) — resetting
     all three is what keeps every template's hover identical. */
  transform: none;
  translate: 0 0;
  scale: 1;
  transition: translate 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}

[class*="-theme"] .theme-social-btn:hover,
[class*="-theme"] .theme-social-btn:focus-visible {
  /* Restated on purpose: a template's own `.xx-footer-social a:hover` rule
     (0,2,1) outranks the base rule above (0,2,0), and would repaint the circle
     and the glyph on hover — splash and doodle swap in their sun yellow, sprout
     a translucent white wash, all under ink chosen for the accent fill. This
     selector is (0,3,0) and wins both back. */
  background: var(--fx-social-bg, #111111);
  color: var(--fx-social-ink, #ffffff);
  transform: none;
  translate: 0 -3px;
  scale: 1;
  filter: brightness(1.08);
  box-shadow: 0 10px 24px color-mix(in srgb, var(--fx-social-bg, #111111) 46%, transparent);
}

/* Only where the row genuinely cannot fit on one line: give the label the
   whole line so the buttons sit under it as a block, instead of the ragged
   split flex-wrap would otherwise leave (label + two circles, then one). The
   label stays — it is what makes the row read as a section, and hiding it
   would take it off nearly every phone, which the stock landing never does.
   Above this the row fits inline: ~90px label + gaps + 3×46px ≈ 294px, inside
   the ~323px a 375px viewport leaves after a template's page padding. */
@media (max-width: 360px) {
  [class*="-theme"] .theme-social-label {
    flex-basis: 100%;
  }
}

/* ── Long unbroken names stay inside their card ─────────────────────────────
   Service and staff names are merchant free text: one 60-character run with
   no spaces is a name a template never anticipated, and CSS's default is to
   let that word paint straight out of its card (pearl, vine, knead, sprout,
   fin, orchid and plume all did, and the stock landing with them).

   Two strengths, on purpose:
     • the root gets `break-word` — inherited by every descendant, it wraps a
       word only when it would otherwise overflow the line, and leaves
       min-content sizing (so every existing layout) exactly as it was.
     • names and card headings get `anywhere`, which additionally shrinks the
       element's min-content width. That is what a flex menu row (name, dotted
       leader, price) or a content-sized grid track needs: without it the item
       refuses to shrink below the long word and the whole row blows out.
   Headings are scoped to the merchant-content sections so a template's hero
   and nav keep their intrinsic widths. A template that already sets its own
   `overflow-wrap` (about half of them do) keeps it — its stylesheet loads
   after this one. */
[class*="-theme"] {
  overflow-wrap: break-word;
}

[class*="-theme"] :is(#services, #team, #classes) :is(h2, h3, h4, h5, h6),
[class*="-theme"] [class*="-name"],
[class*="-theme"] [class*="-title"] {
  overflow-wrap: anywhere;
}
