/* ===== Base ===== */
/* Motion language: "rack focus". Nothing slides and nothing plainly fades —
   content arrives the way a cinema lens finds its subject: a soft optical
   blur pulling into a sharp, settled frame. Images are unveiled by slanted
   razor wipes, grouped content unfolds with a whisper of 3D perspective, and
   pointer interactions are magnetic (buttons lean in, cards tilt). Strong
   custom curves give every move intent; one shared duration keeps the whole
   page breathing at the same rate. */
:root {
  --ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out-strong: cubic-bezier(0.77, 0, 0.175, 1);
  /* One entrance rhythm for the whole site — the quick, responsive rack-focus
     the About section established. Phones drop it further (see the reveal
     block's mobile query) so every section resolves near-instantly on touch. */
  --reveal-dur: 0.5s;
}

html {
  -webkit-text-size-adjust: 100%;
  /* Smooth anchor scrolling; scroll-padding clears the fixed header so
     targets like #location land with breathing room above the map. */
  scroll-behavior: smooth;
  scroll-padding-top: 6rem;
}

body {
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* Tighter display headings to match reference weight/rhythm */
.font-display {
  font-stretch: 100%;
}

/* Even line lengths on headings; fewer orphans in prose */
h1,
h2,
h3 {
  text-wrap: balance;
}
p {
  text-wrap: pretty;
}

/* ===== Accessible keyboard focus ===== */
:focus-visible {
  outline: 2px solid #e8b84b;
  outline-offset: 3px;
  border-radius: 6px;
}
/* Buttons/links that already carry a pill radius keep a matching ring */
a:focus-visible,
button:focus-visible {
  outline-offset: 3px;
}
/* Inputs show the ring on the field itself, not offset */
input:focus-visible {
  outline-offset: 0;
}
/* Pointer interactions shouldn't paint the keyboard ring */
:focus:not(:focus-visible) {
  outline: none;
}

/* Service radio options: visible keyboard focus (survives JS class rewrite) */
.svc-option:focus-within {
  outline: 2px solid #e8b84b;
  outline-offset: 2px;
}

/* Robust screen-reader-only helper + skip link reveal */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.focus\:not-sr-only:focus {
  position: fixed;
  width: auto;
  height: auto;
  margin: 0;
  overflow: visible;
  clip: auto;
  white-space: normal;
}

/* Primary buttons: flat, no outer glow. Hover/press use the independent
   `translate`/`scale` properties so they compose with the reveal system's
   `transform` instead of fighting it for specificity. */
.glow-gold {
  transition: background-color 0.3s ease,
    translate 0.28s var(--ease-out-strong), scale 0.16s var(--ease-out-strong);
}

/* Card motion: cards tilt in 3D toward the cursor (script.js) instead of
   lifting. transform runs on a short curve so the tilt tracks live; surface
   properties keep their slower cross-fades. Touch devices get press feedback. */
.card-lift {
  transition: transform 0.25s var(--ease-out-strong),
    border-color 0.4s ease, box-shadow 0.4s ease, background-color 0.4s ease,
    scale 0.16s var(--ease-out-strong);
}
.card-lift:active {
  scale: 0.98;
}

/* Depth: faint radial shading layered behind section content */
.depth-vignette {
  background-image: radial-gradient(
    115% 75% at 50% 0%,
    rgba(255, 255, 255, 0.03),
    transparent 55%
  );
}

/* ===== Language switcher =====
   A single 44px pill that cycles EN → NL → FR on click; there is no panel to
   anchor, so the control occupies exactly its own footprint and the nav never
   shifts. flex-shrink keeps it at full size when the nav tightens on narrow
   widths, where the centre links are what should give way instead. */
.lang-switch {
  flex-shrink: 0;
}

/* ===== Rating star meter =====
   Two identical star rows stacked: a dim "empty" row and a gold "filled" row
   overlaid on top. The filled row is revealed left-to-right by clipping its
   right edge to --rating-fill, so a 4.9 leaves the fifth star's right point
   unfilled instead of rounding up to five solid stars. The count-up in
   script.js writes --rating-fill from the same eased value that drives the
   number, so the stars fill in perfect lockstep with the counting digits.
   script.js also re-measures the exact percentage from the rendered star
   geometry (the glyph's ink, not its padded box) — the values below are the
   pre-computed no-JS equivalents. */
.rating-stars {
  position: relative;
  display: inline-flex;
  line-height: 0;
  --rating-star-size: 15px;
  --rating-star-gap: 3px;
  --rating-star-stroke: 0;
  /* Default = final rating (4.9/5). This is what no-JS and reduced-motion
     users see; the count-up overrides it via inline style while animating. */
  --rating-fill: 97.1%;
}
/* Larger, stroked variant used in the reviews block, matching the weight of
   the Lucide icons it sits beside. */
.rating-stars--lg {
  --rating-star-size: 18px;
  --rating-star-gap: 4px;
  --rating-star-stroke: 2;
  --rating-fill: 97.7%;
}
/* Anti-flash: only when JS is active AND motion is allowed (so the count-up
   will actually run), start the badge in its animated origin — stars empty and
   the number hidden — until script.js seeds "0.0" and reveals it. This prevents
   the final "4.7 / full stars" from painting for a frame before the script
   resets it (most visible on mobile, where the observer fires a beat later). */
@media (prefers-reduced-motion: no-preference) {
  html.js #hero-rating-stars {
    --rating-fill: 0%;
  }
  html.js .hero-badge [data-countup] {
    visibility: hidden;
  }
}
.rating-stars-row {
  display: inline-flex;
  gap: var(--rating-star-gap);
}
.rating-stars-row svg {
  width: var(--rating-star-size);
  height: var(--rating-star-size);
  display: block;
  stroke: currentColor;
  stroke-width: var(--rating-star-stroke);
  stroke-linejoin: round;
  stroke-linecap: round;
}
/* Empty state: a neutral dim so the meter's footprint reads at 0%. Kept off
   the gold hue on purpose — a faint gold behind the gold fill made the
   unfilled tenth of the 4.9 star read as just a slightly duller gold, so the
   row looked like five full stars. Grey makes the shortfall legible. */
.rating-stars-bg {
  color: rgba(255, 255, 255, 0.2);
}
/* Filled overlay, clipped from the right by the fill amount. */
.rating-stars-fill {
  position: absolute;
  inset: 0;
  color: #e8b84b;
  clip-path: inset(0 calc(100% - var(--rating-fill, 0%)) 0 0);
}

/* Hero badge on phones.
   The label is the most length-volatile string on the page: "GOOGLE RATING"
   becomes "GOOGLE BEOORDELING" in Dutch, 27% wider. At the desktop 13px/0.16em
   the pill outgrows a 375px viewport in *every* language — English included —
   and the label wraps, stretching the pill into a two-line block.

   So below sm the badge scales as one unit rather than the label alone: the
   star meter and the pill's own padding tighten alongside a fluid type size,
   which keeps the widest translation on a single line down to ~345px. Wrapping
   is deliberately still allowed as the escape hatch — on a 320px screen Dutch
   takes two lines, which is untidy but safe, whereas `nowrap` there would push
   the pill past the viewport and give the whole page a horizontal scroll. */
@media (max-width: 639px) {
  .hero-badge > div {
    gap: 0.5rem;
    padding-inline: 0.875rem;
  }
  .hero-badge .rating-stars {
    --rating-star-size: 12px;
    --rating-star-gap: 2px;
  }
  /* Scoped through .hero-badge purely for specificity: this has to outrank the
     utility classes on the same element (text-[13px], tracking-[0.16em]) on
     specificity rather than on styles.css happening to load after app.css. */
  .hero-badge .hero-badge-label {
    font-size: clamp(10px, 2.9vw, 13px);
    letter-spacing: 0.1em;
  }
}

/* ===== About image cards — tap/click to swap with a 3D flip =====
   Two equally-sized cards share a perspective stage. Each sits in a "front" or
   "back" slot defined purely by transform (translate + rotate), so the resting
   swap is a single GPU-composited property. On tap, JS adds a one-shot flip
   keyframe to each card: the incoming card lifts toward the viewer and rotates
   in Y while the outgoing one recedes and counter-rotates, so they cross like a
   dealt card before settling flat and legible. The scroll-parallax on these
   same elements writes the independent `translate` property, so it layers on
   top of the flip's `transform` without either fighting the other. */
.about-stack {
  perspective: 1400px;
}
.about-card {
  position: absolute;
  top: 0;
  left: 0;
  width: 74%;
  height: 84%;
  margin: 0;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 22px;
  overflow: hidden;
  background: #111;
  cursor: pointer;
  transform-origin: 50% 50%;
  will-change: transform;
  box-shadow: 0 28px 60px -18px rgba(0, 0, 0, 0.9);
  /* Resting slot-to-slot move; the flip embellishment comes from the one-shot
     keyframe classes below, which override this during the swap. */
  transition: transform 0.72s var(--ease-in-out-strong),
    box-shadow 0.72s ease;
  -webkit-tap-highlight-color: transparent;
  -webkit-appearance: none;
  appearance: none;
}
/* The photos are wrapped in <picture> for AVIF/WebP negotiation. Taking it out
   of the box tree keeps the <img> a direct layout child of the card, so its
   height: 100% still resolves against the card instead of an inline wrapper
   that has no height of its own. Source selection is unaffected. */
.about-card picture {
  display: contents;
}
.about-card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Both photos are full colour from first paint — no interaction required. */
  filter: none;
  transition: filter 0.4s ease;
}
/* Resting slots (also the end state of every flip, so no jump on commit) */
.about-card.is-front {
  transform: translate(9%, 16%) rotate(3deg);
  z-index: 2;
}
.about-card.is-back {
  transform: translate(37%, 0) rotate(-6deg);
  z-index: 1;
}
/* Back card sits a touch dimmer so the front reads as the hero; hovering it
   lifts the shadow and brightens the image to invite the tap. Purely a depth
   cue — both images stay in full colour throughout. */
.about-card.is-back img {
  filter: brightness(0.82);
}
@media (hover: hover) and (pointer: fine) {
  .about-card.is-back:hover {
    box-shadow: 0 46px 90px -18px rgba(0, 0, 0, 0.95);
  }
  .about-card.is-back:hover img {
    filter: brightness(1);
  }
}
/* One-shot flip: classes added by script.js for the duration of the swap.
   z-index here keeps the incoming card above the outgoing as they cross. */
.about-card.flip-to-front {
  z-index: 3;
  animation: card-to-front 0.72s var(--ease-in-out-strong) both;
}
.about-card.flip-to-back {
  z-index: 0;
  animation: card-to-back 0.72s var(--ease-in-out-strong) both;
}
@keyframes card-to-front {
  0% {
    transform: translate(37%, 0) rotate(-6deg) translateZ(0) rotateY(0deg);
  }
  45% {
    transform: translate(23%, 9%) rotate(-1deg) translateZ(90px) rotateY(26deg);
  }
  100% {
    transform: translate(9%, 16%) rotate(3deg) translateZ(0) rotateY(0deg);
  }
}
@keyframes card-to-back {
  0% {
    transform: translate(9%, 16%) rotate(3deg) translateZ(0) rotateY(0deg);
  }
  45% {
    transform: translate(23%, 9%) rotate(-1deg) translateZ(-90px) rotateY(-26deg);
  }
  100% {
    transform: translate(37%, 0) rotate(-6deg) translateZ(0) rotateY(0deg);
  }
}
/* Reduced motion: no flip, no slide — the swap is an instant, legible cut. */
@media (prefers-reduced-motion: reduce) {
  .about-card {
    transition: none;
  }
  .about-card.flip-to-front,
  .about-card.flip-to-back {
    animation: none;
  }
}

/* Hero copy stays legible over the lighter image overlay */
.hero-copy h1,
.hero-copy > p,
.hero-copy .trusted-label {
  text-shadow: 0 2px 22px rgba(0, 0, 0, 0.6), 0 1px 3px rgba(0, 0, 0, 0.45);
}

/* ===== Hero — cinematic grade, premium lighting & staged entrance ===== */
/* Warm key-light bloom behind the headline. `screen` lets it read as light
   falling on the footage rather than a flat coloured wash. */
.hero-keylight {
  background: radial-gradient(
    58% 46% at 50% 33%,
    rgba(232, 184, 75, 0.14),
    rgba(232, 184, 75, 0.04) 46%,
    transparent 72%
  );
  mix-blend-mode: screen;
}
/* Cinematic vignette: edges recede, the eye falls to the centre. */
.hero-vignette {
  background: radial-gradient(
    125% 108% at 50% 42%,
    transparent 50%,
    rgba(0, 0, 0, 0.22) 78%,
    rgba(0, 0, 0, 0.42) 100%
  );
}

/* Headline lines are always block-level (independent of any animation). */
.hero-line {
  display: block;
}

/* Staged entrance: a title-card focus pull. Each block — and each headline
   line — materialises from a deep optical blur and a breath of extra scale,
   with no vertical travel. The scale grows from each line's own centre, so
   centred, wrapped headings stay perfectly aligned the whole way in. (An
   earlier letter-spacing "lock-up" was dropped: animating tracking changes
   each line's width by a different amount, which drifted the centred lines
   out of alignment mid-animation — most visibly on mobile where the heading
   wraps.) Base state is fully visible, so reduced-motion and no-animation
   environments (headless, print) simply render the finished hero. */
@media (prefers-reduced-motion: no-preference) {
  .hero-item,
  .hero-line {
    animation: hero-rise 1.1s var(--ease-out-strong) backwards;
  }
  .hero-badge { animation-delay: 0.12s; }
  .hero-line:nth-child(1) { animation-delay: 0.28s; }
  .hero-line:nth-child(2) { animation-delay: 0.4s; }
  .hero-line:nth-child(3) { animation-delay: 0.52s; }
  /* The sub-headline that used to sit at 0.72s is gone; the CTA and trust row
     move up into its slot so the cascade keeps its even beat. */
  .hero-cta-wrap { animation-delay: 0.72s; }
  .hero-trust { animation-delay: 0.86s; }
  .hero-scroll {
    animation: hero-rise 1.1s var(--ease-out-strong) 1.25s backwards;
  }
}
@keyframes hero-rise {
  from {
    opacity: 0;
    transform: scale(1.055);
    filter: blur(14px);
  }
  to {
    opacity: 1;
    transform: none;
    filter: none;
  }
}

/* CTA — a warm halo that quietly breathes, like a lamp left on for you.
   No sweeps or gimmicks: the magnetic pull (script.js) does the interactive
   talking. Small element, so the shadow pulse stays paint-cheap. */
.hero-cta {
  position: relative;
  box-shadow:
    0 16px 44px -12px rgba(232, 184, 75, 0.55),
    0 6px 16px -8px rgba(0, 0, 0, 0.55);
}
@media (prefers-reduced-motion: no-preference) {
  .hero-cta {
    animation: cta-breathe 4s ease-in-out infinite;
  }
}
@keyframes cta-breathe {
  0%, 100% {
    box-shadow: 0 16px 44px -12px rgba(232, 184, 75, 0.5),
      0 6px 16px -8px rgba(0, 0, 0, 0.55);
  }
  50% {
    box-shadow: 0 18px 60px -10px rgba(232, 184, 75, 0.82),
      0 6px 16px -8px rgba(0, 0, 0, 0.55);
  }
}

/* Trust avatars: a subtle spread + lift on hover. */
@media (hover: hover) and (pointer: fine) {
  .hero-avatars:hover img {
    transition: transform 0.4s var(--ease-out-strong);
  }
  .hero-avatars:hover img:hover {
    transform: translateY(-3px) scale(1.08);
    z-index: 1;
  }
}

/* Scroll cue: hairline rail with a gold bead that drifts down. Shown only when
   the hero is genuinely tall enough that it can't collide with the centred copy
   (short laptops simply don't render it). */
.hero-scroll {
  /* Layout lives here rather than in utility classes: app.css is a purged
     Tailwind build, so a utility no other element uses (bottom-8) simply isn't
     in the bundle. Centred with auto margins, not a translate — the hero-rise
     entrance animates `transform` and would drop a -50% x-shift mid-flight. */
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2rem;
  z-index: 10;
  width: fit-content;
  margin-inline: auto;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  display: none;
}
@media (min-width: 640px) and (min-height: 820px) {
  .hero-scroll {
    display: flex;
  }
}
.hero-scroll-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
  transition: color 0.35s ease;
}
.hero-scroll:hover .hero-scroll-label {
  color: rgba(255, 255, 255, 0.9);
}
.hero-scroll-rail {
  position: relative;
  width: 1px;
  height: 46px;
  overflow: hidden;
  background: linear-gradient(
    to bottom, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.04)
  );
}
.hero-scroll-bead {
  position: absolute;
  left: 50%;
  top: 0;
  width: 3px;
  height: 11px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: #e8b84b;
  box-shadow: 0 0 10px rgba(232, 184, 75, 0.85);
}
@media (prefers-reduced-motion: no-preference) {
  .hero-scroll-bead {
    animation: hero-bead 2.2s var(--ease-in-out-strong) infinite;
  }
}
@keyframes hero-bead {
  0% { transform: translateY(-12px); opacity: 0; }
  22% { opacity: 1; }
  75% { opacity: 1; }
  100% { transform: translateY(46px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-scroll-bead {
    top: 8px;
    opacity: 0.9;
  }
}

/* ===== Brand logo (nav + footer) =====
   Height is set per-placement by Tailwind classes; these rules keep the mark
   safe at any size: never wider than its column on narrow phones, and a soft
   dark drop shadow so the artwork lifts off the frosted nav glass instead of
   sitting flat on it. Sizing by height + w-auto means no CLS even though the
   intrinsic dimensions aren't declared. */
.brand-logo {
  max-width: 100%;
  filter: drop-shadow(0 4px 14px rgba(0, 0, 0, 0.55));
}
/* The nav pill shrinks its padding on scroll; ease the logo down with it so the
   header compacts as one piece rather than the mark popping a step later. */
#nav-pill .brand-logo {
  transition: height 0.35s var(--ease-out-strong);
}
@media (prefers-reduced-motion: reduce) {
  #nav-pill .brand-logo {
    transition: none;
  }
}

/* ===== Sticky glass header + rating badge (frosted glassmorphism) =====
   Transparent, softly tinted surfaces that let the hero blur through. A thin
   semi-transparent border plus an inset top highlight give the "pane of glass"
   edge; the soft drop shadow lifts them off the page. Kept dark enough that
   white/gold text stays fully legible. Always visible, unchanged on scroll. */
#nav-pill,
.hero-badge > div {
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  backdrop-filter: blur(20px) saturate(150%);
}
#nav-pill {
  background-color: rgba(12, 12, 14, 0.32);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 16px 44px -18px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.07);
}
/* Rating badge — same glass, slightly tighter blur, keeping its gold hairline. */
.hero-badge > div {
  background-color: rgba(12, 12, 14, 0.3);
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

/* Fallback: browsers without backdrop-filter get an opaque-enough tint so the
   text never sits on a see-through, unreadable surface. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  #nav-pill {
    background-color: rgba(12, 12, 14, 0.92);
  }
  .hero-badge > div {
    background-color: rgba(12, 12, 14, 0.9);
  }
}

/* Mobile dropdown: animate open via grid-template-rows (no layout jump) */
#mobile-menu.is-open {
  grid-template-rows: 1fr;
}

/* ===== Dark-themed Google Map embed ===== */
.map-dark {
  filter: invert(0.92) hue-rotate(180deg) brightness(0.95) contrast(0.9)
    saturate(0.75);
  transition: filter 0.5s ease;
}
/* Reveal the true-colour map on hover/focus for a premium touch */
.group:hover .map-dark,
.map-dark:focus-within {
  filter: none;
}

@media (prefers-reduced-motion: reduce) {
  .map-dark {
    transition: none;
  }
}

/* ===== Services cards =====
   Deliberately share the site's card DNA so the section reads as one system:
   the surface, hairline border, top-inset highlight and gold hover come from
   .review-card; the lift comes from .card-lift; the icon disc reuses
   .review-avatar; type is Archivo (display) + Inter like everywhere else.
   No bespoke borders, serifs or glow — cohesion over novelty. Only the tiny
   service-marker pill needs its own rule. */
.svc-mark {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #e8b84b;
  white-space: nowrap;
  border: 1px solid rgba(232, 184, 75, 0.28);
  background: rgba(232, 184, 75, 0.08);
  border-radius: 9999px;
  padding: 4px 10px;
}

/* Services entrance: quicker and punchier than the site default. A shorter
   focus-pull paired with the tighter 40ms card cascade (data-stagger="40")
   makes the grid resolve fast and feel dynamic, while keeping the same
   blur-to-sharp language. Scoped by #id so only this section speeds up. */
#services .reveal {
  --reveal-dur: 0.38s;
}
@media (max-width: 639px) {
  #services .reveal {
    --reveal-dur: 0.26s;
  }
}


/* ===== Gallery lightbox ===== */
/* Gold focus ring on gallery tiles (buttons) */
.gallery-item:focus-visible {
  outline: 2px solid #e8b84b;
  outline-offset: 3px;
}

.lightbox {
  /* Override <dialog> UA defaults: fill the viewport, no chrome */
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: #fff;
  overflow: hidden;
}
.lightbox::backdrop {
  background: rgba(0, 0, 0, 0.93);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.lightbox[open] {
  animation: lightbox-in 0.35s var(--ease-out-strong);
}
.lightbox[open]::backdrop {
  animation: lightbox-fade 0.35s ease;
}
@keyframes lightbox-in {
  from { opacity: 0; transform: scale(0.965); }
  to   { opacity: 1; transform: none; }
}
@keyframes lightbox-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Smooth close: JS adds .is-closing, then closes the dialog on animationend.
   Placed after the [open] rules so it wins on source order. */
.lightbox.is-closing {
  animation: lightbox-out 0.28s cubic-bezier(0.4, 0, 1, 1) forwards;
}
.lightbox.is-closing::backdrop {
  animation: lightbox-fade-out 0.28s ease forwards;
}
@keyframes lightbox-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: scale(0.97); }
}
@keyframes lightbox-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Round glass control buttons, matching the header pills */
.lightbox-btn {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 9999px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.85);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  transition: border-color 0.3s ease, background-color 0.3s ease,
    color 0.3s ease, scale 160ms var(--ease-out-strong);
  z-index: 1;
}
.lightbox-btn:hover {
  border-color: rgba(232, 184, 75, 0.5);
  background: rgba(232, 184, 75, 0.12);
  color: #fff;
}
.lightbox-btn:focus-visible {
  outline: 2px solid #e8b84b;
  outline-offset: 2px;
}

/* Lock page scroll while the lightbox is open */
body:has(#lightbox[open]) {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .lightbox[open],
  .lightbox[open]::backdrop,
  .lightbox.is-closing,
  .lightbox.is-closing::backdrop {
    animation: none;
  }
}

/* ===== Google Reviews: clean ambient backdrop ===== */
/* One soft overhead cool-white wash centred on the header, plus a faint
   warm floor tint, over the section's #0a0a0a. No shapes, no texture noise —
   just light that fades to the same black as the neighbouring sections so
   the whole page reads as one continuous surface. */
.reviews-ambient {
  background:
    radial-gradient(
      70% 42% at 50% 12%,
      rgba(255, 255, 255, 0.045),
      transparent 68%
    ),
    radial-gradient(
      90% 60% at 50% 100%,
      rgba(232, 184, 75, 0.035),
      transparent 62%
    );
}

/* ===== Google Reviews carousel ===== */
/* Viewport: soft fade at both edges so cards dissolve in/out of frame */
.reviews-viewport {
  -webkit-mask-image: linear-gradient(
    to right, transparent, #000 8%, #000 92%, transparent
  );
  mask-image: linear-gradient(
    to right, transparent, #000 8%, #000 92%, transparent
  );
}

/* Track: hidden scrollbar, grab cursor for the drag interaction */
.reviews-track {
  scrollbar-width: none;
  cursor: grab;
  overscroll-behavior-x: contain;
}
.reviews-track::-webkit-scrollbar {
  display: none;
}
.reviews-track.is-dragging {
  cursor: grabbing;
  scroll-behavior: auto;
  user-select: none;
}
.reviews-track:focus-visible {
  outline: 2px solid #e8b84b;
  outline-offset: 4px;
  border-radius: 8px;
}

/* Glass review card: blur + hairline border + inner top highlight */
.review-card {
  background: linear-gradient(
    180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.02)
  );
  border: 1px solid rgba(255, 255, 255, 0.09);
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.06);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}
.review-card:hover {
  border-color: rgba(232, 184, 75, 0.35);
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.08),
    0 0 40px -18px rgba(232, 184, 75, 0.4);
}

/* Avatar: initials on a soft gold-tinted disc with a hairline gold ring */
.review-avatar {
  background: radial-gradient(
    120% 120% at 30% 20%, rgba(232, 184, 75, 0.28), rgba(232, 184, 75, 0.08)
  );
  box-shadow: inset 0 0 0 1px rgba(232, 184, 75, 0.35);
}

/* Entrance: once the wrapper reveals, cards unfold from a whisper of 3D
   perspective — hinged near the top — while racking into focus, cascading
   left to right. Base state stays visible, so content never depends on the
   animation firing. */
.reveal.is-visible .review-card {
  transform-origin: 50% 15%;
  animation: review-card-in var(--reveal-dur) var(--ease-out-strong) backwards;
}
.reveal.is-visible .review-card:nth-child(2) { animation-delay: 80ms; }
.reveal.is-visible .review-card:nth-child(3) { animation-delay: 160ms; }
.reveal.is-visible .review-card:nth-child(4) { animation-delay: 240ms; }
.reveal.is-visible .review-card:nth-child(5) { animation-delay: 320ms; }
.reveal.is-visible .review-card:nth-child(n + 6) { animation-delay: 400ms; }

@keyframes review-card-in {
  from {
    opacity: 0;
    filter: blur(9px);
    transform: perspective(900px) rotateX(8deg) scale(0.965);
  }
  to {
    opacity: 1;
    filter: none;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .reveal.is-visible .review-card {
    animation: none;
  }
}

/* ===== Scroll experience ===== */
/* Gallery tiles: razor-wipe reveal — a slanted edge sweeps each photo in
   from the left (a nod to the blade) while the tile pulls into focus.
   clip-path is GPU-composited and rides the existing IntersectionObserver
   reveal at zero JS cost; the wipe runs slightly longer than the focus pull
   so the image finishes unveiling just after the tile has settled. */
.gallery-item.reveal {
  clip-path: polygon(-35% 0%, -12% 0%, -35% 100%, -58% 100%);
  transition: opacity var(--reveal-dur) var(--ease-out-strong),
    transform var(--reveal-dur) var(--ease-out-strong),
    filter var(--reveal-dur) var(--ease-out-strong),
    clip-path calc(var(--reveal-dur) + 0.25s) var(--ease-in-out-strong);
}
.gallery-item.reveal.is-visible {
  clip-path: polygon(0% 0%, 135% 0%, 112% 100%, -23% 100%);
}

/* Press feedback: every pressable answers the click. Uses the `scale`
   property (not `transform`) so it never collides with reveal transforms. */
#lang-btn:active,
#menu-btn:active,
.lightbox-btn:active,
.svc-option:active,
#booking-form button[type='submit']:active {
  scale: 0.97;
}
#lang-btn,
#menu-btn {
  transition: background-color 0.3s ease,
    translate 160ms var(--ease-out-strong), scale 160ms var(--ease-out-strong);
}

/* Buttons are magnetic: they lean toward the cursor (script.js writes the
   `translate` property; these transitions make the pull elastic and the
   release soft). Press still dips via the independent `scale`. */
.hover-lift {
  transition: translate 0.3s var(--ease-out-strong),
    scale 0.16s var(--ease-out-strong), background-color 0.3s ease,
    border-color 0.3s ease, color 0.3s ease, box-shadow 0.3s ease;
}
.glow-gold:active,
.hover-lift:active {
  scale: 0.96;
}

/* Scroll progress: hairline gold bar driven by the CSS scroll timeline —
   runs off the main thread, no JS, no layout cost. Browsers without
   scroll-timeline support simply never see it (base state is scaleX(0)). */
#scroll-progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 2px;
  z-index: 60;
  pointer-events: none;
  transform: scaleX(0);
  transform-origin: left;
  background: linear-gradient(90deg, rgba(232, 184, 75, 0.4), #e8b84b);
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    #scroll-progress {
      animation: scroll-progress-grow linear both;
      animation-timeline: scroll(root);
    }
  }
}
@keyframes scroll-progress-grow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

/* ===== Reveal-on-scroll: rack-focus (site-wide motion language) ===== */
/* Nothing slides. Content arrives the way a lens finds its subject — a soft
   optical blur and a breath of extra scale pulling into a sharp, settled
   frame. The blur is a one-shot transition (never scroll-linked), and the
   IntersectionObserver reveals once and unobserves, so nothing replays. */
.reveal {
  opacity: 0;
  transform: scale(1.02);
  filter: blur(6px);
  transition: opacity var(--reveal-dur) var(--ease-out-strong),
    transform var(--reveal-dur) var(--ease-out-strong),
    filter var(--reveal-dur) var(--ease-out-strong);
  will-change: opacity, transform, filter;
}

.reveal.is-visible {
  opacity: 1;
  transform: none;
  filter: none;
}

/* Wide full-bleed wrappers (e.g. the reviews strip) skip the blur so no
   viewport-sized surface ever repaints — their children carry the motion. */
.reveal.reveal-flat {
  transform: none;
  filter: none;
}

/* Image / media reveal: a slightly deeper focus pull — a touch more scale,
   same blur — so photography and the map feel like a lens racking onto the
   subject. The container hosts the transform, so inner hover effects are
   undisturbed. */
.reveal.reveal-zoom:not(.is-visible) {
  transform: scale(1.03);
}

/* Once a reveal has settled, transform switches to a short curve so the
   live cursor tilt (script.js) tracks instantly instead of lagging on the
   entrance's long easing. Surface cross-fades (border/shadow/background used
   by card hovers) are kept in the list so they stay smooth. Placed after the
   razor-wipe rules so it wins. */
.reveal.motion-done {
  transition: transform 0.22s var(--ease-out-strong), opacity 0.4s ease,
    filter 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease,
    background-color 0.4s ease;
}

/* Mobile: the whole site's reveals go near-instant — the exact feel the About
   section had. A very short pull, a lighter blur and a barely-there scale mean
   content is essentially sharp the moment its section enters; sequenced text
   staggers are compressed so each block resolves almost together instead of
   cascading. Paired with the phone-only edge trigger in script.js. Still eased,
   so it reads smooth, not abrupt. */
@media (max-width: 639px) {
  .reveal {
    --reveal-dur: 0.3s;
  }
  /* Exclude flat wrappers so the full-bleed reviews strip still never blurs. */
  .reveal:not(.reveal-flat):not(.is-visible) {
    filter: blur(4px);
  }
  .reveal.reveal-zoom:not(.is-visible) {
    transform: scale(1.02);
  }
  .reveal-seq.is-visible > *:nth-child(1) { animation-delay: 0s; }
  .reveal-seq.is-visible > *:nth-child(2) { animation-delay: 0.04s; }
  .reveal-seq.is-visible > *:nth-child(3) { animation-delay: 0.08s; }
  .reveal-seq.is-visible > *:nth-child(4) { animation-delay: 0.12s; }
  .reveal-seq.is-visible > *:nth-child(n + 5) { animation-delay: 0.14s; }
}

/* Sequenced reveal: the container fades as a soft mask while its direct
   children unfold in order — eyebrow, heading, paragraph, then buttons —
   each hinging from its top edge with a whisper of 3D perspective as it
   racks into focus. The parent's opacity:0 hides the children until it
   reveals, so `backwards` fill means no pre-scroll flash. */
.reveal.reveal-seq {
  transform: none;
  filter: none;
}
@media (prefers-reduced-motion: no-preference) {
  .reveal-seq.is-visible > * {
    transform-origin: 50% 0%;
    animation: reveal-rise var(--reveal-dur) var(--ease-out-strong) backwards;
  }
  .reveal-seq.is-visible > *:nth-child(1) { animation-delay: 0.04s; }
  .reveal-seq.is-visible > *:nth-child(2) { animation-delay: 0.13s; }
  .reveal-seq.is-visible > *:nth-child(3) { animation-delay: 0.22s; }
  .reveal-seq.is-visible > *:nth-child(4) { animation-delay: 0.31s; }
  .reveal-seq.is-visible > *:nth-child(n + 5) { animation-delay: 0.4s; }
}
@keyframes reveal-rise {
  from {
    opacity: 0;
    filter: blur(9px);
    transform: perspective(900px) rotateX(5deg) scale(0.99);
  }
  to {
    opacity: 1;
    filter: none;
    transform: none;
  }
}

/* Reduced motion: keep the fade (aids comprehension), drop all movement. */
@media (prefers-reduced-motion: reduce) {
  .reveal,
  .gallery-item.reveal {
    transform: none;
    filter: none;
    clip-path: none;
    transition: opacity 0.4s ease;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  /* Content is shown instantly; motion-bearing transitions are removed,
     while non-vestibular colour/blur transitions are left intact. */
  .reveal {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
    transition-delay: 0ms !important;
  }
  /* Curtain reveal is movement — show gallery images whole instead. */
  .gallery-item.reveal {
    clip-path: none;
  }
}

/* ===== Premium scroll experience =====
   A layered system, cheap by construction:
   1. Scroll-DRIVEN depth (this block): hero dolly-out + multi-speed parallax
      via CSS scroll timelines — runs on the compositor, zero JS. Browsers
      without support get the same effects from a tiny rAF fallback in
      script.js (same class names, same `translate`/`scale` properties).
   2. Scroll-TRIGGERED reveals: masked word-by-word headlines and draw-in
      rules, riding the existing IntersectionObserver (.reveal engine).
   All motion only touches translate / scale / opacity — the independent
   properties — so it composes with every existing transform-based hover
   (rotate cards, zoom images) instead of overriding it. Everything is gated
   behind prefers-reduced-motion. */

/* -- 1a. Scroll-driven keyframes (shared by CSS timelines and JS fallback) -- */
/* Hero dolly-out: as the page leaves the hero, the copy lifts away and
   dissolves while the footage sinks and swells behind it — the two layers
   separating is what sells the depth. */
@keyframes hero-layer-depart {
  to {
    translate: 0 6vh;
    scale: 1.12;
  }
}
@keyframes hero-copy-depart {
  to {
    translate: 0 -13vh;
    opacity: 0;
  }
}
/* Foreground layers travel slightly faster than the page… */
@keyframes sd-near {
  from { translate: 0 34px; }
  to   { translate: 0 -34px; }
}
/* …background layers slightly slower. Opposing directions on neighbouring
   elements (About photo stack) create true parallax separation. */
@keyframes sd-far {
  from { translate: 0 -24px; }
  to   { translate: 0 24px; }
}
/* Images drift inside their overflow-hidden frames (pre-scaled for cover). */
@keyframes img-drift {
  from { translate: 0 -3.5%; }
  to   { translate: 0 3.5%; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero-layer {
      animation: hero-layer-depart linear both;
      animation-timeline: scroll(root);
      animation-range: 0 120vh;
    }
    .hero-exit {
      animation: hero-copy-depart linear both;
      animation-timeline: scroll(root);
      animation-range: 0 85vh;
    }
    .sd-near,
    .sd-far,
    .img-drift {
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    .sd-near { animation-name: sd-near; }
    .sd-far { animation-name: sd-far; }
    .img-drift {
      animation-name: img-drift;
      scale: 1.1;
    }
  }
}

/* -- 1b. Masked word reveals for section headings ([data-split]) --
   script.js wraps each word in .sw (the mask) > .sw-in (the word). Words
   rise out of their masks in sequence once the surrounding .reveal fires.
   Base state is fully visible: no JS / reduced motion simply shows text. */
[data-split] .sw {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* clip room for descenders without changing line rhythm */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}
[data-split] .sw-in {
  display: inline-block;
}
@media (prefers-reduced-motion: no-preference) {
  [data-split] .sw-in {
    translate: 0 118%;
    transition: translate 0.85s var(--ease-out-strong);
    transition-delay: calc(0.1s + var(--wi, 0) * 70ms);
  }
  .reveal.is-visible [data-split] .sw-in {
    translate: 0 0;
  }
  /* A split heading carries its own word choreography — skip the generic
     child rise so the two entrances never stack. */
  .reveal-seq.is-visible > [data-split] {
    animation: none;
  }
}

/* -- 1c. Gold rules draw themselves in under revealed headings -- */
@media (prefers-reduced-motion: no-preference) {
  .rule-draw {
    scale: 0 1;
    transition: scale 1s var(--ease-in-out-strong) 0.45s;
  }
  .rule-draw-left {
    transform-origin: left;
  }
  .reveal.is-visible .rule-draw {
    scale: 1 1;
  }
}

/* -- 1d. Nav pill condenses once the page starts moving (script.js) -- */
#nav-pill {
  transition: background-color 0.4s ease, padding 0.35s var(--ease-out-strong),
    box-shadow 0.4s ease;
}
#nav-pill.is-scrolled {
  background-color: rgba(10, 10, 12, 0.78);
  padding-top: 0.625rem;
  padding-bottom: 0.625rem;
  box-shadow: 0 20px 50px -20px rgba(0, 0, 0, 0.7),
    inset 0 1px 0 rgba(255, 255, 255, 0.07);
}

/* -- 1e. Cursor spotlight: a warm pool of light follows the pointer across
   service and review cards (script.js feeds --mx/--my). Hover-gated. -- */
.review-card {
  position: relative;
}
.review-card::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s ease;
  background: radial-gradient(
    240px circle at var(--mx, 50%) var(--my, 50%),
    rgba(232, 184, 75, 0.09),
    transparent 65%
  );
}
@media (hover: hover) and (pointer: fine) {
  .review-card:hover::after {
    opacity: 1;
  }
}

/* ===== Lucide icon sizing safety ===== */
[data-lucide] {
  stroke-width: 2;
}

/* ===== Custom barbershop service icons =====
   Hand-drawn shears, clipper, razor, brush etc. sit on the gold disc and
   come alive on card hover: a small lift in scale + tilt with a soft golden
   glow. Kept snappy (280ms) and gently overshooting for a crafted feel. */
.svc-icon {
  width: 28px;
  height: 28px;
  transform-origin: 50% 55%;
  transition: transform 280ms cubic-bezier(0.34, 1.35, 0.5, 1),
    filter 280ms ease;
  will-change: transform;
}
#services a:hover .svc-icon,
#services a:focus-visible .svc-icon {
  transform: scale(1.08) rotate(4deg);
  filter: drop-shadow(0 0 6px rgba(232, 184, 75, 0.55))
    drop-shadow(0 0 2px rgba(232, 184, 75, 0.5));
}

@media (prefers-reduced-motion: reduce) {
  .svc-icon {
    transition: filter 280ms ease;
  }
  #services a:hover .svc-icon,
  #services a:focus-visible .svc-icon {
    transform: none;
  }
}

/* Custom thin scrollbar */
::-webkit-scrollbar {
  width: 10px;
}
::-webkit-scrollbar-track {
  background: #0a0a0a;
}
::-webkit-scrollbar-thumb {
  background: #2a2a2a;
  border-radius: 9999px;
}
::-webkit-scrollbar-thumb:hover {
  background: #3a3a3a;
}

/* Ambient overhead pool — warm gold fading to the section black */
.cw-ambient {
  background:
    radial-gradient(58% 42% at 50% 30%, rgba(232, 184, 75, 0.10), transparent 68%),
    radial-gradient(90% 60% at 50% 120%, rgba(0, 0, 0, 0.6), transparent 70%);
}

/* =====================================================================
   Circular gallery (#gallery)
   A WebGL ring of the shop's work: planes bend along an arc around a focal
   centre card and wrap infinitely as they leave the viewport. JS owns all
   motion (assets/gallery.js); CSS owns the stage box, the arrows and the
   static fallback that ships visible and is only retired once the renderer
   reports success.
   ===================================================================== */
.gal {
  position: relative;
  margin-top: clamp(2rem, 4.5vw, 3.5rem);
  isolation: isolate;
}

/* The stage is the renderer's box — its height is the deck's height. */
.gal-stage {
  position: relative;
  width: 100%;
  height: clamp(400px, 46vw, 600px);
  cursor: grab;
  /* Let the page keep vertical scrolling; horizontal drags are the deck's. */
  touch-action: pan-y;
  -webkit-tap-highlight-color: transparent;
}
.gal-stage:active {
  cursor: grabbing;
}
.gal-stage canvas {
  display: block;
  width: 100%;
  height: 100%;
}
/* The canvas is aria-hidden, so the focus ring belongs to the stage. */
.gal-stage:focus-visible {
  outline: 2px solid #e8b84b;
  outline-offset: 6px;
  border-radius: 18px;
}

/* --- Arrows: same frosted disc as the rest of the site's controls ----- */
.gal-arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-top: -26px;
  border-radius: 9999px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background-color: rgba(12, 12, 14, 0.42);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  backdrop-filter: blur(20px) saturate(150%);
  color: rgba(255, 255, 255, 0.85);
  box-shadow: 0 16px 44px -18px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.07);
  cursor: pointer;
  transition: background-color 0.3s ease, border-color 0.3s ease,
    color 0.3s ease, translate 0.28s var(--ease-out-strong),
    scale 0.16s var(--ease-out-strong);
}
.gal-arrow svg {
  width: 20px;
  height: 20px;
}
.gal-arrow:hover {
  border-color: rgba(232, 184, 75, 0.45);
  color: #e8b84b;
  background-color: rgba(12, 12, 14, 0.6);
}
.gal-arrow:active {
  scale: 0.94;
}
.gal-arrow--prev {
  left: clamp(10px, 3vw, 40px);
}
.gal-arrow--next {
  right: clamp(10px, 3vw, 40px);
}
.gal-arrow--prev:hover {
  translate: -3px 0;
}
.gal-arrow--next:hover {
  translate: 3px 0;
}

/* --- Static fallback -------------------------------------------------
   Ships visible: this is what no-JS, no-WebGL and crawler traffic sees,
   and it is the markup the JS reads its items from. Once the renderer is
   live, .is-webgl retires it. */
.gal-fallback {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.25rem;
  max-width: 72rem;
  margin: 0 auto;
  padding: 0 1.5rem;
  list-style: none;
}
.gal-fallback .gallery-item {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 18px;
  overflow: hidden;
  background: #0f0f0f;
  cursor: pointer;
  transition: border-color 0.4s ease, transform 0.35s var(--ease-out-strong);
}
.gal-fallback .gallery-item:hover {
  border-color: rgba(232, 184, 75, 0.4);
  transform: translateY(-4px);
}
.gal-fallback img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 7 / 9;
  object-fit: cover;
}

/* Renderer is live: the deck replaces the static grid. */
.gal.is-webgl .gal-fallback {
  display: none;
}
/* ...and until it is, there is nothing for the arrows to drive. */
.gal:not(.is-webgl) .gal-stage,
.gal:not(.is-webgl) .gal-arrow {
  display: none;
}

@media (max-width: 640px) {
  .gal-arrow {
    width: 44px;
    height: 44px;
    margin-top: -22px;
  }
  .gal-arrow svg {
    width: 17px;
    height: 17px;
  }
  .gal-fallback {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.85rem;
  }
}

/* Footer legal line: tighter gap between the divider and the copyright text
   (replaces the old pt-5 / sm:pt-6 padding). Spacing above the divider is
   unchanged. */
.footer-legal {
  padding-top: 0.625rem; /* 10px — mobile */
}
@media (min-width: 640px) {
  .footer-legal {
    padding-top: 0.75rem; /* 12px — desktop */
  }
}

/* Language switch (#lang-btn): mirror the primary CTA's interactive feel.
   The shared "#lang-btn, #menu-btn" rules above (ID specificity) otherwise
   override .glow-gold's timing/press; this later, lang-only rule restores an
   exact match — same magnetic-lean transition (translate 0.28s) and press dip
   (0.96) as "Book an Appointment". #menu-btn is deliberately left untouched. */
#lang-btn {
  transition: background-color 0.3s ease,
    translate 0.28s var(--ease-out-strong), scale 0.16s var(--ease-out-strong);
}
#lang-btn:active {
  scale: 0.96;
}
