/* ==========================================================================
   Hope Harbour: motion
   Rules this file obeys, without exception:
     • transform + opacity + clip-path only. Never width/height/top/left.
     • Nothing animates for longer than 900ms.
     • Every animation is skipped under prefers-reduced-motion or the
       in-page "reduce motion" toggle (see tokens.css).
     • No animation blocks input, and none causes layout shift: every
       revealing element occupies its final box from first paint.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Scroll reveal: the workhorse. js/motion.js adds .is-in.
   The `.js` class is set by an inline head script, so if JS never runs the
   content is simply visible. No FOUC, no invisible page.
   -------------------------------------------------------------------------- */
.js .reveal {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition:
    opacity var(--dur-reveal) var(--ease-out),
    transform var(--dur-reveal) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
.js .reveal.is-in {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}
/* Drop the compositor hint once the animation has finished */
.js .reveal.is-done { will-change: auto; }

/* Directional variants */
.js .reveal--left  { transform: translate3d(-22px, 0, 0); }
.js .reveal--right { transform: translate3d(22px, 0, 0); }
.js .reveal--up    { transform: translate3d(0, 32px, 0); }
.js .reveal--scale { transform: translate3d(0, 14px, 0) scale(.975); }
.js .reveal--fade  { transform: none; }
.js .reveal--left.is-in,
.js .reveal--right.is-in,
.js .reveal--up.is-in,
.js .reveal--scale.is-in,
.js .reveal--fade.is-in { transform: translate3d(0, 0, 0) scale(1); }

/* Stagger: js/motion.js writes --reveal-delay on each child of [data-stagger] */
[data-stagger] > * { --reveal-delay: 0ms; }

/* --------------------------------------------------------------------------
   Hero headline: line-by-line rise from behind a mask
   Markup: <h1><span class="line"><span>text</span></span> …</h1>
   -------------------------------------------------------------------------- */
.line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.06em;   /* room for descenders inside the mask */
  margin-bottom: -0.06em;
}
/* Base state is VISIBLE: without JS the headline must simply read. */
.line > span { display: block; }

.js .hero-title .line > span {
  transform: translate3d(0, 105%, 0);
  transition: transform 880ms var(--ease-out);
  transition-delay: var(--line-delay, 0ms);
}
.js .hero-title.is-in .line > span { transform: translate3d(0, 0, 0); }

/* --------------------------------------------------------------------------
   Image reveal: a soft wipe plus a settling scale.
   The wrapper keeps its box, so nothing shifts.
   -------------------------------------------------------------------------- */
.js .img-reveal > img,
.js .img-reveal > picture > img {
  transform: scale(1.07);
  clip-path: inset(0 0 100% 0);
  transition:
    transform 1100ms var(--ease-out),
    clip-path 900ms var(--ease-out);
}
.js .img-reveal.is-in > img,
.js .img-reveal.is-in > picture > img {
  transform: scale(1);
  clip-path: inset(0 0 0 0);
}

/* --------------------------------------------------------------------------
   Hero ornament: a very slow, very small drift. Decorative but it earns
   its keep by making the cream ground feel alive rather than flat.
   -------------------------------------------------------------------------- */
@keyframes hh-drift {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
  50%      { transform: translate3d(0, -14px, 0) rotate(1.6deg); }
}
.hero__ornament--tr { animation: hh-drift 15s var(--ease-soft) infinite; }
.hero__ornament--bl { animation: hh-drift 18s var(--ease-soft) infinite reverse; }
.hero__ornament--bl { transform-origin: bottom left; }

/* --------------------------------------------------------------------------
   Counters
   -------------------------------------------------------------------------- */
.stat__num[data-count] { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   Service-area marquee: CSS only, duplicated track, pauses on hover/focus
   -------------------------------------------------------------------------- */
.marquee {
  --marquee-dur: 46s;
  position: relative;
  overflow: hidden;
  display: flex;
  user-select: none;
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--s-5);
  padding-right: var(--s-5);
  animation: hh-marquee var(--marquee-dur) linear infinite;
}
.marquee:hover .marquee__track,
.marquee:focus-within .marquee__track { animation-play-state: paused; }
@keyframes hh-marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-100%, 0, 0); }
}
.marquee__item {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  font-family: var(--hh-font-display);
  font-size: clamp(1.1rem, 0.95rem + 0.7vw, 1.5rem);
  font-weight: 500;
  color: var(--hh-navy);
  white-space: nowrap;
}
.marquee__item::after {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--hh-gold);
  flex: none;
}
.band-navy .marquee__item { color: var(--hh-on-navy); }

/* Under reduced motion the marquee becomes a static, wrapping list */
html[data-hh-motion="reduce"] .marquee,
html[data-hh-motion="reduce"] .marquee__track {
  overflow: visible;
  mask-image: none;
  -webkit-mask-image: none;
  animation: none;
  flex-wrap: wrap;
}
html[data-hh-motion="reduce"] .marquee__track[aria-hidden="true"] { display: none; }
html[data-hh-motion="reduce"] .hero__ornament { animation: none; }
@media (prefers-reduced-motion: reduce) {
  .marquee, .marquee__track {
    overflow: visible;
    mask-image: none;
    -webkit-mask-image: none;
    animation: none;
    flex-wrap: wrap;
  }
  .marquee__track[aria-hidden="true"] { display: none; }
  .hero__ornament, .cta-band__ornament { animation: none; }
}

/* --------------------------------------------------------------------------
   Page-load: header settles in. Runs once, 500ms, no scroll dependency.
   -------------------------------------------------------------------------- */
@keyframes hh-header-in {
  from { opacity: 0; transform: translate3d(0, -10px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}
.js .site-header { animation: hh-header-in 620ms var(--ease-out) both; }

/* --------------------------------------------------------------------------
   A note on cross-document view transitions
   @view-transition { navigation: auto } was tried here and removed. It looks
   good, but every skipped transition rejects an internal promise the page
   cannot catch, filling the console with "AbortError: Transition was skipped".
   The per-element reveals below already carry the page-to-page feel, so the
   clean console is worth more than the cross-fade.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Focus-visible ring animation: a quick, reassuring pulse
   -------------------------------------------------------------------------- */
@keyframes hh-focus-pop {
  from { outline-offset: 8px; opacity: .4; }
  to   { outline-offset: 3px; opacity: 1; }
}
@media (prefers-reduced-motion: no-preference) {
  a:focus-visible, button:focus-visible, [tabindex]:focus-visible {
    animation: hh-focus-pop 200ms var(--ease-out);
  }
}
