/*
 * Motion system.
 *
 * Three rules make this feel native rather than "a WordPress site with fades":
 *
 * 1. Only `transform` and `opacity` are ever animated. Both are composited on
 *    the GPU, so nothing here can trigger layout or paint mid-animation — that
 *    is the entire difference between 60fps and jank.
 *
 * 2. Nothing is hidden by CSS alone. The hidden state is scoped to
 *    [data-rim-reveal="pending"], an attribute only JavaScript sets. If the
 *    script fails, is blocked, or never runs, every element renders normally
 *    instead of leaving a blank page.
 *
 * 3. Easing is expo-out, not ease-in-out. Fast departure and a long settle is
 *    what reads as "designed" rather than "animated".
 */

:root {
  --rim-reveal-duration: 900ms;
  --rim-reveal-stagger: 90ms;
  --rim-reveal-shift: 28px;
}

/* Hidden state — applied only once JS has claimed the element. The compositor
   hint waits until the element actually animates, so dozens of pending cards
   never hold GPU layers at once. */
[data-rim-reveal='pending'] {
  opacity: 0;
}

/*
 * Two-phase reveal: opacity is DONE at 55% while the transform settles through
 * 100%. Fading and moving on one curve reads translucent; finishing the fade
 * early reads solid and "expensive". Keyframes are the only way to express it.
 */
[data-rim-reveal='in'] {
  animation: rim-rise 1000ms cubic-bezier(0.215, 0.61, 0.355, 1) both;
  animation-delay: calc(var(--rim-i, 0) * var(--rim-reveal-stagger));
  will-change: opacity, transform;
}

@keyframes rim-rise {
  0% { opacity: 0; transform: translate3d(0, 40px, 0); }
  55% { opacity: 1; }
  to { opacity: 1; transform: translate3d(0, 0, 0); }
}

[data-rim-reveal-from='fade'][data-rim-reveal='in'] {
  animation-name: rim-fade;
  animation-duration: 800ms;
}

@keyframes rim-fade {
  0% { opacity: 0; }
  to { opacity: 1; }
}

/* Pace set per section in the editor. */
[data-rim-pace='calm'][data-rim-reveal='in'] {
  animation-duration: 1400ms;
}

[data-rim-pace='brisk'][data-rim-reveal='in'] {
  animation-duration: 650ms;
}

/* Cards travel further and settle from a slight compression — a different
   voice from headings, so the page doesn't repeat one movement everywhere. */
:is(.rim-services__grid, .rim-projects__grid, .rim-team__grid, .rim-certs__grid) > [data-rim-reveal='in'] {
  animation-name: rim-rise-card;
}

@keyframes rim-rise-card {
  0% { opacity: 0; transform: translate3d(0, 52px, 0) scale3d(0.975, 0.975, 1); }
  55% { opacity: 1; }
  to { opacity: 1; transform: translate3d(0, 0, 0) scale3d(1, 1, 1); }
}

/* Section heads travel less — they should feel settled, not launched. */
:is(.rim-services__head, .rim-projects__headtext, .rim-team__head, .rim-certs__head, .rim-stats__head, .rim-faq__head, .rim-richtext__head)[data-rim-reveal='in'] {
  animation-name: rim-rise-soft;
}

@keyframes rim-rise-soft {
  0% { opacity: 0; transform: translate3d(0, 26px, 0); }
  55% { opacity: 1; }
  to { opacity: 1; transform: translate3d(0, 0, 0); }
}

/* Once settled, stop promoting the layer so we don't hold GPU memory. */
[data-rim-reveal='done'] {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/*
 * Hero entrance: pure CSS from first paint, no script involvement. The
 * headline is the page's most important text, so it is never put in a
 * JS-gated hidden state — the animation starts the moment CSS applies and
 * a no-JS or slow-JS visit still sees everything.
 *
 * Each word clips itself while its inner span rises from below the mask.
 * The clip is permanent, so the word carries its own descender room as
 * padding folded away by a negative margin; the inner starts far enough
 * down to clear that taller box.
 */
@media (prefers-reduced-motion: no-preference) {
  .rim-hero__title .rim-hero__word {
    overflow: clip;
    padding-block-end: 0.16em;
    margin-block-end: -0.16em;
  }

  .rim-hero__title .rim-hero__wordinner {
    display: inline-block;
    animation: rim-word-rise 1100ms cubic-bezier(0.215, 0.61, 0.355, 1) both;
    animation-delay: calc(var(--rim-i, 0) * 110ms);
  }

  .rim-hero__eyebrow,
  .rim-hero__lede,
  .rim-hero__actions {
    animation: rim-rise 1000ms cubic-bezier(0.215, 0.61, 0.355, 1) both;
  }

  .rim-hero__lede { animation-delay: 180ms; }
  .rim-hero__actions { animation-delay: 320ms; }
}

@keyframes rim-word-rise {
  0% { opacity: 0; transform: translate3d(0, 135%, 0) rotate(0.5deg); }
  45% { opacity: 1; }
  to { opacity: 1; transform: translate3d(0, 0, 0) rotate(0); }
}

/*
 * Hero parallax, native scroll-driven where supported.
 *
 * animation-timeline runs off the main thread, so the image drifts at a steady
 * rate no matter what JavaScript is doing. Progressive enhancement: browsers
 * without it simply get a static image, no fallback script needed.
 */
@supports (animation-timeline: view()) {
  .rim-hero__img {
    animation: rim-parallax linear both;
    animation-timeline: view();
    animation-range: entry 0% exit 100%;
  }

  @keyframes rim-parallax {
    from { transform: translate3d(0, -4%, 0) scale(1.12); }
    to { transform: translate3d(0, 4%, 0) scale(1.12); }
  }
}

/*
 * Curtain image reveals (the "soil" treatment): media unmasks upward via
 * clip-path while the image inside settles from a slight zoom. Keyed off the
 * SAME reveal attributes the runtime already sets, so this costs no new JS.
 * clip-path on a simple inset and transform both stay off the layout path.
 */
/*
 * Masked media is never ALSO opacity-hidden — a curtain fading in shows only
 * the tail of its own motion and reads sluggish. The mask alone is the reveal.
 */
.rim-about__media[data-rim-reveal='pending']:where(:not([data-rim-reveal-from='fade'])),
.rim-about__media[data-rim-reveal='in']:where(:not([data-rim-reveal-from='fade'])) {
  opacity: 1;
  animation: none;
}

.rim-about__media[data-rim-reveal='pending']:where(:not([data-rim-reveal-from='fade'])) .rim-about__media-inner {
  clip-path: inset(100% 0 0 0);
}

.rim-about__media[data-rim-reveal='in']:where(:not([data-rim-reveal-from='fade'])) .rim-about__media-inner {
  clip-path: inset(0 0 0 0);
  transition: clip-path 950ms var(--rim-ease-out-expo);
}

.rim-about__media[data-rim-reveal='done'] .rim-about__media-inner {
  clip-path: none;
}

.rim-about__media[data-rim-reveal='pending']:where(:not([data-rim-reveal-from='fade'])) .rim-about__img {
  transform: scale3d(1.09, 1.09, 1);
}

.rim-about__media[data-rim-reveal='in']:where(:not([data-rim-reveal-from='fade'])) .rim-about__img {
  transform: scale3d(1, 1, 1);
  transition: transform 1300ms var(--rim-ease-out-expo);
}

/* Hero eyebrow rule draws itself after the text lands — CSS-only, like the
   rest of the hero entrance. */
@media (prefers-reduced-motion: no-preference) {
  .rim-hero__eyebrow::after,
  .rim-hero__eyebrow::before {
    transform-origin: left center;
    animation: rim-rule-draw 900ms var(--rim-ease-out-expo) 500ms both;
  }
}

@keyframes rim-rule-draw {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* ---------- interaction motion ---------- */

.rim-services__item {
  position: relative;
  isolation: isolate;
  transition: transform calc(var(--rim-duration) * 2.15) var(--rim-ease-out-expo);
}

.rim-services__item:hover {
  transform: translate3d(0, -6px, 0);
}

/* The hover shadow is a pre-rendered pseudo-element whose OPACITY crossfades —
   animating box-shadow itself repaints every frame. */
.rim-services__item::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: var(--rim-radius-lg);
  box-shadow: var(--rim-shadow-lg);
  opacity: 0;
  transition: opacity calc(var(--rim-duration) * 1.75) var(--rim-ease-out-expo);
}

.rim-services__item:hover::after {
  opacity: 1;
}

.rim-project__img,
.rim-service__img {
  transition: transform calc(var(--rim-duration) * 3.75) var(--rim-ease-out-expo);
}

.rim-project:hover .rim-project__img,
.rim-service:hover .rim-service__img {
  transform: scale3d(1.05, 1.05, 1);
}

/* Buttons: a quick press response reads as physical. */
.rim-btn {
  transition:
    background var(--rim-duration) var(--rim-ease-out-quint),
    color var(--rim-duration) var(--rim-ease-out-quint),
    border-color var(--rim-duration) var(--rim-ease-out-quint),
    transform var(--rim-duration) var(--rim-ease-spring);
}

.rim-btn:not(.rim-btn--primary):hover { transform: translate3d(0, -2px, 0); }
.rim-btn:not(.rim-btn--primary):active { transform: translate3d(0, 0, 0) scale3d(0.98, 0.98, 1); }

.rim-hero__cta { transition: color var(--rim-duration) var(--rim-ease-out-quint); }

.rim-hero__chev { transition: transform calc(var(--rim-duration) * 1.75) var(--rim-ease-spring); }
.rim-hero__cta:hover .rim-hero__chev { transform: translate3d(6px, 0, 0); }

.rim-faq__marker::before,
.rim-faq__marker::after {
  transition: transform calc(var(--rim-duration) * 1.75) var(--rim-ease-spring);
}

/* Icon badges settle with a spring on reveal. */
.rim-certs__item:where([data-rim-reveal='in']) .rim-certs__iconwrap,
.rim-about__highlight:where([data-rim-reveal='in']) .rim-about__iconwrap {
  animation: rim-pop calc(var(--rim-duration) * 2.9) var(--rim-ease-spring) both;
  animation-delay: calc(var(--rim-i, 0) * var(--rim-reveal-stagger) + var(--rim-duration) * 0.5);
}

@keyframes rim-pop {
  from { transform: scale3d(0.6, 0.6, 1); opacity: 0; }
  to { transform: scale3d(1, 1, 1); opacity: 1; }
}

/*
 * Reduced motion: reveal instantly and cancel every drift. Content is never
 * left hidden — the pending state is overridden to fully visible.
 */
@media print, (prefers-reduced-motion: reduce) {
  [data-rim-reveal='pending'],
  [data-rim-reveal='in'],
  [data-rim-reveal='done'],
  .rim-hero__wordinner,
  .rim-hero__eyebrow,
  .rim-hero__lede,
  .rim-hero__actions,
  .rim-hero__eyebrow::after,
  .rim-hero__eyebrow::before {
    opacity: 1;
    transform: none;
    transition: none;
    animation: none;
    will-change: auto;
  }

  .rim-about__media[data-rim-reveal] .rim-about__media-inner,
  .rim-about__media[data-rim-reveal] .rim-about__img,
  .rim-hero__eyebrow::after,
  .rim-hero__eyebrow::before {
    clip-path: none;
    transform: none;
    transition: none;
  }

  .rim-hero__img,
  .rim-hero__scroll {
    animation: none;
  }

  .rim-certs__item .rim-certs__iconwrap,
  .rim-about__highlight .rim-about__iconwrap {
    animation: none;
  }
}
