/* ==========================================================================
   Top Boys Barbershop — motion.css
   The movement layer. Loaded after styles.css.

   The idea: everything moves like something in a barbershop. Blades sweep,
   foil catches the light, steam drifts, the pole turns. Nothing bounces,
   nothing spins for the sake of spinning — every movement answers either
   the scroll position or the visitor's own hand.
   ========================================================================== */

/* ---------- Motion tokens ---------- */
:root {
  --dur-fast: 0.35s;
  --dur: 0.6s;
  --dur-slow: 1.1s;
  --dur-blade: 0.9s;

  /* The easing used for anything that feels mechanical but precise. */
  --ease-blade: cubic-bezier(0.65, 0, 0.35, 1);
  /* Softer, for text settling into place. */
  --ease-settle: cubic-bezier(0.22, 1, 0.36, 1);
  /* A little overshoot, for things that land with weight. */
  --ease-land: cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* ==========================================================================
   1. Missing keyframes
   styles.css animates `foil`, `spin` and `pole` but never defines them, so
   all three were doing nothing. They are defined here.
   ========================================================================== */

/* The gold in the display type catches the light and travels. */
@keyframes foil {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

/* The round badge with the shop address turning around it. */
@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* The barber's pole: the stripes run down it. */
@keyframes pole {
  from { background-position: 0 0; }
  to   { background-position: 56px 56px; }
}

/* ==========================================================================
   2. The blade wipe
   The signature move. A section reveals as if a straight razor swept
   across it: a hard diagonal edge travels through, and the content is
   revealed behind it.
   ========================================================================== */

.blade-wipe {
  position: relative;
}

/* The edge that sweeps across. */
.blade-wipe::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 130%;
  pointer-events: none;
  z-index: 2;
  background: linear-gradient(
    104deg,
    transparent 0%,
    transparent 42%,
    rgba(212, 175, 55, 0.1) 48%,
    rgba(233, 201, 106, 0.55) 50%,
    rgba(255, 255, 255, 0.85) 50.4%,
    rgba(212, 175, 55, 0.25) 51%,
    transparent 58%,
    transparent 100%
  );
  transform: translateX(-115%);
  opacity: 0;
}

/* The content itself sits under the edge and waits. */
.blade-wipe > * {
  position: relative;
  z-index: 1;
  clip-path: inset(0 100% 0 0);
  transition: clip-path var(--dur-blade) var(--ease-blade);
}

.blade-wipe.is-wiped::before {
  animation: bladeSweep 1s var(--ease-blade) forwards;
}

.blade-wipe.is-wiped > * {
  clip-path: inset(0 0 0 0);
}

@keyframes bladeSweep {
  0%   { transform: translateX(-115%); opacity: 0; }
  12%  { opacity: 1; }
  55%  { opacity: 1; }
  100% { transform: translateX(115%); opacity: 0; }
}

/* ==========================================================================
   3. Foil that catches the light
   The gold in the display type is driven from JavaScript: the highlight
   drifts slowly on its own and jumps with the scroll, so the type always
   looks freshly polished. The CSS `foil` animation is switched off for
   these, because an animation would win over the inline position.
   ========================================================================== */

.text-gold-foil.foil-scroll {
  animation: none;
  background-size: 220% 100%;
  background-position: 0% 50%;
  transition: background-position 0.4s linear;
}

/* ==========================================================================
   4. Hero — the title arrives letter by letter
   The outer span of each line is already a mask (overflow hidden), so the
   letters inside it slide up out of view and are revealed in turn.

   The gradient has to move down to the letters. The styled span paints
   its gradient with `background-clip: text`, and that clip only reaches
   the text of the element itself — the letters are child elements, and
   they also inherit the parent's `color: transparent`. So each letter
   carries its own copy of the gradient instead, sized and positioned by
   JavaScript so the sweep still runs unbroken across the whole word.
   ========================================================================== */

.hero__title .b {
  --foil-image: linear-gradient(180deg, #ffffff 0%, #c9ced6 55%, #8d939c 100%);
}
.hero__title .text-gold-foil {
  --foil-image: linear-gradient(100deg, #a8862a 0%, #e9c96a 30%, #fff1c1 45%, #d4af37 60%, #8f7020 100%);
}

/* The styled span keeps the size of the line but paints nothing itself. */
.hero__title .b,
.hero__title .text-gold-foil {
  display: inline-block;
  overflow: visible;
  padding-bottom: 0;
  background: none;
  animation: none;
}

.hero__title .char {
  display: inline-block;
  overflow: visible;
  padding-bottom: 0;
  background-image: var(--foil-image);
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  transform: translateY(110%);
}

.is-loaded .hero__title .char {
  animation: charUp 0.95s var(--ease-settle) forwards;
  animation-delay: var(--char-delay, 0s);
}

@keyframes charUp {
  from { transform: translateY(110%); }
  to   { transform: translateY(0); }
}

/* The letters of the first line come first, then the second. */
.hero__title > span:nth-child(1) .char { --char-delay: calc(0.16s + var(--i) * 0.055s); }
.hero__title > span:nth-child(2) .char { --char-delay: calc(0.42s + var(--i) * 0.045s); }

/* A glint that runs across the gold as the title lands. */
.hero__title .text-gold-foil {
  position: relative;
}

.hero__title .glint {
  position: absolute;
  top: -20%;
  bottom: -20%;
  width: 40%;
  left: -60%;
  pointer-events: none;
  background: linear-gradient(
    100deg,
    transparent 0%,
    rgba(255, 255, 255, 0.55) 45%,
    rgba(255, 255,255, 0.9) 50%,
    rgba(255, 255, 255, 0.55) 55%,
    transparent 100%
  );
  mix-blend-mode: overlay;
  transform: skewX(-18deg);
}

.is-loaded .hero__title .glint {
  animation: glintAcross 1.5s var(--ease-blade) 0.7s forwards;
}

@keyframes glintAcross {
  0%   { left: -60%; opacity: 0; }
  20%  { opacity: 0.9; }
  100% { left: 120%; opacity: 0; }
}

/* ==========================================================================
   5. Steam
   A slow drift above the barber's shoulders. Purely atmospheric.
   ========================================================================== */

.steam {
  position: absolute;
  left: 50%;
  bottom: 18%;
  width: 3px;
  height: 26%;
  pointer-events: none;
  z-index: 1;
  background: linear-gradient(to top, rgba(226, 232, 240, 0.16), transparent);
  border-radius: 50%;
  filter: blur(3px);
  opacity: 0;
  transform-origin: bottom center;
}

.is-loaded .steam {
  animation: steamRise 7s ease-in-out infinite;
  animation-delay: var(--steam-delay, 0s);
}

@keyframes steamRise {
  0%   { opacity: 0; transform: translateX(-50%) scaleY(0.5); }
  25%  { opacity: 0.8; }
  60%  { opacity: 0.5; }
  100% { opacity: 0; transform: translateX(calc(-50% + 18px)) scaleY(1.5); }
}

/* ==========================================================================
   6. Marquee — it speeds up with the scroll
   The strip is steady when you are still, and rushes when you scroll.
   ========================================================================== */

.marquee__track {
  will-change: transform;
}

/* ==========================================================================
   7. Magnetic buttons
   The gold buttons lean towards the cursor, the way a well-balanced lever
   does. Only for a real pointer — never on touch.
   ========================================================================== */

.btn-gold,
.btn-ghost {
  transition:
    transform 0.35s var(--ease-settle),
    background-color 0.35s ease,
    border-color 0.35s ease,
    color 0.35s ease,
    box-shadow 0.35s ease;
  will-change: transform;
}

.btn-gold.is-magnetic {
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
}

/* ==========================================================================
   8. Cursor glow
   A faint pool of light follows the pointer over the dark page.
   ========================================================================== */

.cursor-glow {
  position: fixed;
  top: 0;
  left: 0;
  width: 460px;
  height: 460px;
  margin: -230px 0 0 -230px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 1;
  opacity: 0;
  background: radial-gradient(
    circle,
    rgba(212, 175, 55, 0.075) 0%,
    rgba(212, 175, 55, 0.03) 40%,
    transparent 70%
  );
  transition: opacity 0.6s ease;
  will-change: transform;
}

.cursor-glow.is-on {
  opacity: 1;
}

/* ==========================================================================
   9. Service rows — the wash follows the pointer
   ========================================================================== */

.service__price {
  transition: transform 0.45s var(--ease-settle), color 0.35s ease;
}

.service:hover .service__price,
.service:focus-visible .service__price {
  transform: translateY(-3px);
  color: var(--gold-2);
}

/* ==========================================================================
   10. Gallery — the pictures sharpen as they arrive
   ========================================================================== */

.work {
  opacity: 0;
  transform: translateY(40px) scale(0.97);
  filter: blur(8px);
  transition:
    opacity 0.9s var(--ease-settle),
    transform 1s var(--ease-settle),
    filter 0.9s var(--ease-settle);
  transition-delay: var(--work-delay, 0s);
}

.work.is-shown {
  opacity: 1;
  transform: none;
  filter: none;
}

/* The ring that circles the photo tightens on hover. */
.work__ring {
  transition: box-shadow 0.6s ease, transform 0.7s var(--ease-settle);
}

.work:hover .work__ring {
  box-shadow: inset 0 0 0 1px rgba(212, 175, 55, 0.7);
  transform: scale(1.03);
}

/* ==========================================================================
   11. Booking — the panel knows which way you are going
   Moving forward slides in from the right, back slides in from the left.
   ========================================================================== */

.booking__step.is-active {
  animation: stepForward 0.55s var(--ease-settle);
}

.booking__step.is-active.is-back {
  animation: stepBack 0.55s var(--ease-settle);
}

@keyframes stepForward {
  from { opacity: 0; transform: translateX(34px); }
  to   { opacity: 1; transform: none; }
}

@keyframes stepBack {
  from { opacity: 0; transform: translateX(-34px); }
  to   { opacity: 1; transform: none; }
}

/* The chosen day and slot land with a little weight. */
.day.is-selected,
.slot.is-selected {
  animation: land 0.45s var(--ease-land);
}

@keyframes land {
  0%   { transform: scale(0.92); }
  60%  { transform: scale(1.04); }
  100% { transform: scale(1); }
}

/* A slot that just became free gets a brief shine. */
.slot.is-fresh {
  animation: slotShine 0.9s var(--ease-settle);
}

@keyframes slotShine {
  0%   { box-shadow: inset 0 0 0 0 rgba(233, 201, 106, 0.55); }
  100% { box-shadow: inset 0 0 0 18px rgba(233, 201, 106, 0); }
}

/* The step marker fills as the booking advances. */
.step__no {
  transition: background-color 0.4s ease, color 0.4s ease, border-color 0.4s ease;
}

.step.is-done .step__no {
  background: var(--gold);
  color: var(--ink);
  border-color: var(--gold);
}

/* ==========================================================================
   12. Reviews — the cards drift in at different depths
   ========================================================================== */

/*
 * The stagger delay belongs to the reveal only. It is written into the
 * two properties that reveal, not into `transition-delay` as a whole —
 * otherwise hovering a card that has already arrived would wait for the
 * delay before answering.
 */
/*
 * A review card no longer reveals itself.
 *
 * It used to fade up on its own as it came into view — but the cards are
 * now inside a rail that drifts continuously, so most of them are never
 * observed and would have stayed at opacity 0 forever. The reveal belongs
 * to the whole block (`.reviews__rails`), which fades in once; the cards
 * inside it are always fully visible.
 *
 * `translate` is still here and still separate from `transform`, because
 * the hover lift uses it and the drift animation owns `transform`.
 */
.review {
  transition:
    translate 0.45s var(--ease-settle),
    border-color 0.45s ease,
    box-shadow 0.45s ease;
}

/* ==========================================================================
   13. Section headings — the words arrive one after another
   ========================================================================== */

.h2 .word {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.6em) rotate(1.5deg);
  transition:
    opacity 0.7s var(--ease-settle),
    transform 0.9s var(--ease-settle);
  transition-delay: calc(var(--i, 0) * 0.07s);
}

/* The gap between two words is drawn at a fixed width. A plain space
   between two inline-block words reads as too wide. */
.h2 .word-space {
  display: inline-block;
  width: 0.28em;
  overflow: hidden;
}

.h2.is-shown .word {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
   14. Counters
   ========================================================================== */

[data-count] {
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   15. The marquee words
   Each word lifts clear of the strip and takes the gold as it passes the
   pointer, so the band reads as something you can touch rather than a
   printed ribbon.
   ========================================================================== */

.marquee__item {
  display: inline-block;
  transition:
    color 0.4s var(--ease-settle),
    transform 0.5s var(--ease-settle),
    letter-spacing 0.5s var(--ease-settle);
}

.marquee__item:hover {
  color: var(--gold-2) !important;
  transform: translateY(-6px);
  letter-spacing: 0.02em;
}

/* The separators turn while the words are still. */
.marquee__star {
  display: inline-block;
  animation: starTurn 9s linear infinite;
}

.marquee__star:nth-of-type(even) {
  animation-direction: reverse;
}

@keyframes starTurn {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ==========================================================================
   17. The stamp in the about section
   It already turns. This gives it weight: it settles towards you as it
   comes up the page, and the gold ring breathes.
   ========================================================================== */

/* On `translate` and `scale`, so the absolute position from styles.css
   is untouched and the stamp keeps sitting where it was placed. */
.about__badge {
  animation: badgeBreath 7s ease-in-out infinite;
}

@keyframes badgeBreath {
  0%, 100% { translate: 0 0; scale: 1; }
  50%      { translate: 0 -4px; scale: 1.02; }
}

/* ==========================================================================
   18. The barber portraits
   A slow drift inside the frame, the way a portrait photographer keeps
   adjusting the focus. It runs even while nothing else is happening, so
   the section never looks like a still image.
   ========================================================================== */

/*
 * The drift is written on the independent `scale` and `translate`
 * properties, not on `transform`. styles.css already uses transform for
 * the hover state, and a running animation on the same property would
 * win over that transition — the photo would stop answering the pointer.
 * Kept separate, the two compose.
 */
.barber__media img {
  animation: portraitDrift 26s ease-in-out infinite alternate;
}

.barber:nth-child(2) .barber__media img {
  animation-duration: 32s;
  animation-delay: -8s;
}

@keyframes portraitDrift {
  from { scale: 1; translate: 0 0; }
  to   { scale: 1.06; translate: -1.2% -1.2%; }
}

/* A blade of gold light crosses the portrait when the card is hovered. */
.barber__media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(
    108deg,
    transparent 38%,
    rgba(233, 201, 106, 0.16) 48%,
    rgba(255, 255, 255, 0.3) 50%,
    rgba(233, 201, 106, 0.16) 52%,
    transparent 62%
  );
  transform: translateX(-120%);
  opacity: 0;
  transition: opacity 0.2s ease;
}

.barber:hover .barber__media::after {
  opacity: 1;
  animation: portraitSheen 1.15s var(--ease-blade);
}

@keyframes portraitSheen {
  from { transform: translateX(-120%); }
  to   { transform: translateX(120%); }
}

/* ==========================================================================
   19. The quotes
   The quotation mark pulls back and the card lifts, so a review feels
   like it was handed to you.
   ========================================================================== */

/* --------------------------------------------------------------------------
   The two rails drift.

   Each rail holds its content twice, so translating by exactly -50% lands
   the second copy where the first one started — the loop has no seam. The
   reverse rail starts already shifted, so the two rows move against each
   other instead of in lockstep.

   The duration is not a round number on purpose: at 48s and 55s the two
   rows drift in and out of phase slowly enough that the pattern never
   looks like it is repeating.
   -------------------------------------------------------------------------- */

.reviews__rail--fwd {
  animation: railDrift 48s linear infinite;
}

.reviews__rail--rev {
  animation: railDrift 55s linear infinite reverse;
}

/*
 * On a phone the card is nearly the full width, so a drifting row means
 * the text you are reading slides away. The rows still move — that is the
 * point of the section — but slowly enough to read a card before it goes,
 * and a touch anywhere on the row stops it (see initReviewRails).
 */
@media (max-width: 767px) {
  .reviews__rail--fwd { animation-duration: 90s; }
  .reviews__rail--rev { animation-duration: 105s; }
}

@keyframes railDrift {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

.review:hover,
.review:focus-within {
  border-color: rgba(212, 175, 55, 0.45);
  box-shadow: 0 18px 40px -28px rgba(0, 0, 0, 0.9);
  /* The lift uses `translate`, not `transform`: transform belongs to the
     rail's drift, and overriding it here would fight the animation. */
  translate: 0 -6px;
}

.review__quote {
  transition: transform 0.5s var(--ease-settle), color 0.4s ease;
}

.review:hover .review__quote {
  transform: translateX(2px);
  color: var(--bone);
}

/* ==========================================================================
   20. The two phone numbers
   Both are links. They answer the pointer together, and the number
   slides out from under the name.
   ========================================================================== */

.contact__phone a {
  position: relative;
  display: inline-block;
}

.contact__phone a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.45s var(--ease-settle);
}

.contact__phone a:hover,
.contact__phone a:focus-visible {
  color: var(--gold-2);
}

.contact__phone a:hover::after,
.contact__phone a:focus-visible::after {
  transform: scaleX(1);
}

/* ==========================================================================
   21. The directions button
   A thin light travels along the border, always, slowly. It is the one
   thing on the page that says "this takes you somewhere".
   ========================================================================== */

/*
 * The sweep is a background gradient that slides across, not a pseudo
 * element. A pseudo element would have to sit either over the label
 * (obscuring it) or behind the element's own box (invisible) — the
 * background has neither problem.
 */
.directions a {
  background-image: linear-gradient(
    100deg,
    transparent 30%,
    rgba(212, 175, 55, 0.16) 45%,
    rgba(233, 201, 106, 0.3) 50%,
    rgba(212, 175, 55, 0.16) 55%,
    transparent 70%
  );
  background-size: 260% 100%;
  background-repeat: no-repeat;
  background-position: 160% 0;
  animation: directionsSweep 5.2s var(--ease-blade) infinite;
}

.directions a:hover {
  animation-duration: 1.8s;
}

@keyframes directionsSweep {
  0%   { background-position: 160% 0; }
  60%  { background-position: -60% 0; }
  100% { background-position: -60% 0; }
}

.directions svg {
  transition: transform 0.5s var(--ease-land);
}

.directions a:hover svg {
  transform: translate(2px, -2px) rotate(6deg);
}

/* ==========================================================================
   22. The map
   The box is never empty: the fallback panel holds it until the map has
   actually loaded, and the corner card slides in behind it.
   ========================================================================== */

.contact__map {
  transition:
    opacity 1s var(--ease-settle),
    transform 1s var(--ease-settle);
}

/*
 * The card only hides itself when JavaScript is there to bring it back.
 * Without scripting the map panel and the fallback are both visible, which
 * looks busy but is never blank.
 */
.js .contact__map-card {
  transform: translateY(12px);
  opacity: 0;
  transition:
    transform 0.7s var(--ease-settle) 0.25s,
    opacity 0.7s var(--ease-settle) 0.25s;
}

.js .contact__map.is-live .contact__map-card {
  transform: none;
  opacity: 1;
}

/* ==========================================================================
   23. Reduced motion

   This block used to be a blanket kill switch:

       *, *::before, *::after {
         animation-duration: 0.001ms !important;
         animation-iteration-count: 1 !important;
         transition-duration: 0.001ms !important;
       }

   That one rule was silently switching off the entire motion layer — the
   marquee, the turning badge, the drifting review rails, the hero letters —
   on any machine where Windows reports `prefers-reduced-motion: reduce`.
   Windows reports it whenever Settings → Accessibility → Visual effects →
   "Animation effects" is off, which is the default on a great many
   installs, so "the animations do not work" was really "the operating
   system answered a question nobody meant to ask".

   The reference site does not do this: it treats the preference as one
   thing — do not play the intro curtain — and leaves the rest alone.

   So the same line is drawn here. What is actually removed is the motion
   that causes trouble: things that travel most of the viewport, effects
   that chase the pointer, and anything that loops over the content you
   are trying to read. What is kept is the small, local, constant motion
   that gives the page its character — a strip of type sliding a few
   dozen pixels a second is not what makes anyone ill.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  /* Scroll-jacking and long smooth scrolls are the real offenders. */
  html { scroll-behavior: auto; }

  /* Pointer-chasing effects: no use to anyone avoiding motion. */
  .cursor-glow,
  .steam,
  .hero__title .glint {
    display: none !important;
  }

  .btn-gold.is-magnetic {
    transform: none !important;
  }

  /* The blade wipe sweeps a bright edge right across the section. The
     content still arrives — it just does not have a blade dragged over it. */
  .blade-wipe::before {
    display: none !important;
  }

  .blade-wipe > * {
    clip-path: none !important;
  }

  /* The portrait drift is a slow zoom on a large photograph, which is
     exactly the kind of movement the preference is about. The hover zoom
     in styles.css still answers the pointer. */
  .barber__media img {
    animation: none !important;
    scale: 1 !important;
    translate: none !important;
  }
}
