/* ==========================================================================
   Rivexo Athletics - motion and interaction
   Loaded after main.css, so same-specificity rules here win on purpose.

   Two rules govern everything below:
   1. Only transform and opacity are animated, so nothing triggers layout.
   2. Every entrance effect is gated behind .js, so if JavaScript fails the
      page renders fully visible rather than blank.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Scroll reveal
   The observer adds .is-in. --reveal-delay is set per item by the script so
   a row of cards arrives as a run rather than all at once.
   -------------------------------------------------------------------------- */

.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 24px, 0);
  transition:
    opacity var(--dur-slow) var(--ease-out) var(--reveal-delay, 0ms),
    transform var(--dur-slow) var(--ease-out) var(--reveal-delay, 0ms);
  will-change: transform, opacity;
}
.js [data-reveal].is-in { opacity: 1; transform: none; will-change: auto; }

/* Headings arrive from slightly further, so the eye lands on them first. */
.js [data-reveal='lead'] { transform: translate3d(0, 34px, 0); }

/* A rule that draws itself across, used under section headings. */
.js [data-reveal='rule'] { opacity: 1; transform: scaleX(0); transform-origin: left; }
.js [data-reveal='rule'].is-in { transform: scaleX(1); }

/* --------------------------------------------------------------------------
   2. Banner: one orchestrated entrance, then a slow parallax
   -------------------------------------------------------------------------- */

@keyframes rivexo-rise {
  from { opacity: 0; transform: translate3d(0, 30px, 0); }
  to   { opacity: 1; transform: none; }
}

@keyframes rivexo-wipe {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

.js .banner-inner > * { animation: rivexo-rise var(--dur-xl) var(--ease-out) backwards; }
.js .banner-inner > *:nth-child(1) { animation-delay: 140ms; }
.js .banner-inner > *:nth-child(2) { animation-delay: 260ms; }
.js .banner-inner > *:nth-child(3) { animation-delay: 380ms; }
.js .banner-inner > *:nth-child(4) { animation-delay: 500ms; }

.js .banner-eyebrow::before { transform-origin: left; animation: rivexo-wipe var(--dur-slow) var(--ease-out) 320ms backwards; }

/* The image is oversized so the parallax offset never exposes an edge. */
.banner-media {
  transform: translate3d(0, var(--parallax, 0px), 0) scale(1.12);
  will-change: transform;
}

/* --------------------------------------------------------------------------
   3. Buttons
   The fill wipes across from the side the pointer is travelling towards, the
   label stays put, and a press physically compresses the control.
   -------------------------------------------------------------------------- */

.btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition:
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease),
    transform var(--dur-fast) var(--ease-spring),
    box-shadow var(--dur) var(--ease-out);
}

.btn::before {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  transform: scaleX(0); transform-origin: right;
  transition: transform var(--dur) var(--ease-out);
}
.btn:hover::before, .btn:focus-visible::before { transform: scaleX(1); transform-origin: left; }

.btn:active { transform: scale(0.965); transition-duration: 90ms; }

/* Red wipes to black. Black wipes to red. The accent is never wasted. */
.btn-primary { background: var(--red); border-color: var(--red); color: var(--white); }
.btn-primary::before { background: var(--ink); }
.btn-primary:hover { background: var(--red); border-color: var(--ink); color: var(--white); box-shadow: 0 12px 24px -12px rgba(200, 16, 46, 0.55); }

.btn-dark { background: var(--ink); border-color: var(--ink); color: var(--white); }
.btn-dark::before { background: var(--red); }
.btn-dark:hover { background: var(--ink); border-color: var(--red); color: var(--white); }

.btn-ghost::before { background: var(--ink); }
.btn-ghost:hover { color: var(--white); border-color: var(--ink); }

.on-ink .btn-ghost::before { background: var(--white); }
.on-ink .btn-ghost:hover { color: var(--ink); border-color: var(--white); }

.btn-light { background: var(--white); border-color: var(--white); color: var(--ink); }
.btn-light::before { background: var(--red); }
.btn-light:hover { background: var(--white); border-color: var(--red); color: var(--white); }

/* Text links: the underline retracts and redraws from the other side. */
.link-more { position: relative; border-bottom: 0; padding-bottom: 4px; }
.link-more::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--red); transform-origin: left;
  transition: transform var(--dur) var(--ease-out);
}
.link-more:hover::after { animation: rivexo-relay var(--dur-slow) var(--ease-out); }
@keyframes rivexo-relay {
  0%   { transform: scaleX(1); transform-origin: right; }
  49%  { transform: scaleX(0); transform-origin: right; }
  50%  { transform: scaleX(0); transform-origin: left; }
  100% { transform: scaleX(1); transform-origin: left; }
}

/* --------------------------------------------------------------------------
   4. Cards
   -------------------------------------------------------------------------- */

.card, .method, .swatch {
  position: relative;
  transition:
    transform var(--dur-slow) var(--ease-soft),
    box-shadow var(--dur-slow) var(--ease-soft),
    border-color var(--dur-slow) var(--ease-soft);
  will-change: transform;
}

/* --- The wash -----------------------------------------------------------
   Shared by every card that opts in with data-wash. Only the transform is
   animated, and the disc is promoted to its own layer, so a full-bleed red
   flood across twelve cards costs nothing on the main thread.             */

.wash {
  transition: transform var(--dur-wash) var(--ease-soft);
  will-change: transform;
}
[data-wash]:hover .wash,
[data-wash]:focus-within .wash { transform: translate(-50%, -50%) scale(1); }

/* The rule across the top only makes sense where there is no wash. */
.method::before, .swatch::before {
  content: '';
  position: absolute; top: 0; left: 0; right: 0; height: 3px; z-index: 3;
  background: var(--red);
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur) var(--ease-out);
}
.method:hover::before, .swatch:hover::before { transform: scaleX(1); }

.card:hover, .card:focus-within,
.method:hover, .swatch:hover {
  transform: translateY(-8px);
  border-color: var(--line-strong);
  box-shadow: 0 26px 50px -22px rgba(0, 0, 0, 0.32);
}
.card:hover, .card:focus-within { border-color: var(--red); }

/* The image pushes past its frame as the card lifts. */
.card-media, .method > .ph, .method > .media { overflow: hidden; }
.card-media .ph, .card-media .media img,
.method > .ph, .method > .media img {
  transition: transform 1100ms var(--ease-soft);
}
.card:hover .card-media .ph, .card:hover .card-media .media img,
.method:hover > .ph, .method:hover > .media img { transform: scale(1.07); }

/*
 * The card body keeps its own colours.
 *
 * It used to flip to white, which was right while the wash covered the card
 * completely. At 10% the wash is a tint over a white card, so white text
 * would sit on near-white and disappear. The text stays dark; only the spec
 * rule picks up the accent.
 */
.card-spec { transition: border-color var(--dur) var(--ease-out); }
.card:hover .card-spec, .card:focus-within .card-spec { border-color: var(--red); }

/* Swatch chips bloom slightly, the way a folded swatch opens out. */
.swatch-chip { transition: transform 1100ms var(--ease-soft); transform-origin: center bottom; }
.swatch:hover .swatch-chip { transform: scale(1.05); }

/* --------------------------------------------------------------------------
   5. Sport tiles
   -------------------------------------------------------------------------- */

/*
 * One gesture, several parts moving together: the tile lifts, the picture
 * pushes in, the veil closes down over it, the name rises to make room, and
 * the kit chips deal out one after another.
 *
 * :focus-within is on every rule so a keyboard reaches all of it.
 */

.sport {
  transition:
    transform var(--dur-slow) var(--ease-soft),
    box-shadow var(--dur-slow) var(--ease-soft);
  will-change: transform;
}
.sport:hover, .sport:focus-within {
  transform: translateY(-6px);
  box-shadow: 0 30px 56px -24px rgba(0, 0, 0, 0.55);
}

/* The picture pushes past the frame, slowest of everything here so it keeps
   drifting after the rest has settled. */
.sport-media .ph, .sport-media img { transition: transform 1400ms var(--ease-soft); will-change: transform; }
.sport:hover .sport-media .ph, .sport:hover .sport-media img,
.sport:focus-within .sport-media .ph, .sport:focus-within .sport-media img { transform: scale(1.1); }

/* The veil deepens under the wash so the panel has something solid to sit on. */
.sport-veil { transition: background-color var(--dur-slow) var(--ease-soft); }
.sport:hover .sport-veil, .sport:focus-within .sport-veil { background-color: rgba(0, 0, 0, 0.3); }

/* The name lifts as the panel opens beneath it. */
.sport-name { transition: transform var(--dur-slow) var(--ease-soft); }
.sport:hover .sport-name, .sport:focus-within .sport-name { transform: translateY(-3px); }

/* The panel itself. Rows, not height, so it opens to its own measure. */
.sport-detail { transition: grid-template-rows var(--dur-slow) var(--ease-soft); }
.sport:hover .sport-detail, .sport:focus-within .sport-detail { grid-template-rows: 1fr; }

/* Chips deal out in sequence rather than appearing all at once. The delays
   are staged behind the wash so each chip lands on red, not on photograph. */
.sport-kit li {
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity var(--dur-slow) var(--ease-soft),
    transform var(--dur-slow) var(--ease-soft);
}
.sport:hover .sport-kit li, .sport:focus-within .sport-kit li { opacity: 1; transform: none; }
.sport:hover .sport-kit li:nth-child(1), .sport:focus-within .sport-kit li:nth-child(1) { transition-delay: 120ms; }
.sport:hover .sport-kit li:nth-child(2), .sport:focus-within .sport-kit li:nth-child(2) { transition-delay: 180ms; }
.sport:hover .sport-kit li:nth-child(3), .sport:focus-within .sport-kit li:nth-child(3) { transition-delay: 240ms; }
.sport:hover .sport-kit li:nth-child(4), .sport:focus-within .sport-kit li:nth-child(4) { transition-delay: 300ms; }

.sport-go {
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity var(--dur-slow) var(--ease-soft) 340ms,
    transform var(--dur-slow) var(--ease-soft) 340ms;
}
.sport:hover .sport-go, .sport:focus-within .sport-go { opacity: 1; transform: none; }

/* The black rule seals the bottom edge, once the red has arrived. */
.sport::after { transition: transform var(--dur-slow) var(--ease-soft) 140ms; }
.sport:hover::after, .sport:focus-within::after { transform: scaleX(1); }

/*
 * Touch has no hover, so the panel would never open. On those devices the
 * tile shows its name and the call to action permanently and leaves the
 * chips out, which keeps the square from overflowing.
 */
@media (hover: none) {
  .sport-detail { grid-template-rows: 1fr; }
  .sport-kit { display: none; }
  .sport-go { opacity: 1; transform: none; }
  .sport::after { transform: scaleX(1); }

  /* No pointer means no origin to grow from, and a red card that never
     reverts would bury the photograph. The wash stays out of the way. */
  .wash { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* The panel still opens and the chips still arrive, they just do not
     travel or stagger. Nothing is hidden from anyone. */
  .sport-kit li, .sport-go { transform: none; transition: opacity var(--dur-fast) linear; }
  .sport:hover .sport-kit li, .sport:focus-within .sport-kit li,
  .sport:hover .sport-go, .sport:focus-within .sport-go { transition-delay: 0ms; }
  .sport:hover, .sport:focus-within { transform: none; }
  .sport:hover .sport-name, .sport:focus-within .sport-name { transform: none; }

  /* The red still arrives, it just fades in place instead of sweeping
     across from the pointer. */
  .wash {
    transform: translate(-50%, -50%) scale(1);
    opacity: 0;
    transition: opacity var(--dur-fast) linear;
  }
  [data-wash]:hover .wash,
  [data-wash]:focus-within .wash { transform: translate(-50%, -50%) scale(1); opacity: 0.2; }
}

/* --------------------------------------------------------------------------
   6. Header and navigation
   On scroll the utility strip collapses to nothing and the brand row tightens,
   leaving the sports bar stuck to the top. The reader keeps the navigation
   without carrying the whole header down the page.
   -------------------------------------------------------------------------- */

.site-header { transition: box-shadow var(--dur) var(--ease-out); }
.site-header.is-scrolled { box-shadow: 0 10px 30px -22px rgba(0, 0, 0, 0.55); }

/*
 * Nothing here animates the header's size. The utility strip leaves the
 * screen because the header is stuck at a negative offset, not because its
 * height is being transitioned, so scrolling can never feed back into layout.
 * is-scrolled only paints a shadow.
 */

/* Utility links: quiet. A hairline underline, never the red bar, so the
   strip never competes with the navigation under it. */
.nav-utility a { transition: color var(--dur-fast) var(--ease); }
.nav-utility a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -3px; height: var(--hairline);
  background: currentColor; opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.nav-utility a:hover::after { opacity: 1; }
.utility-contact a { transition: color var(--dur-fast) var(--ease); }

/* Sports bar: loud. A red bar sweeps in and the label lifts a hair. */
.sports-list a { transition: color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease-out); }
.sports-list a::after {
  content: ''; position: absolute; left: 0.35rem; right: 0.35rem; bottom: 0; height: 3px;
  background: var(--red);
  transform: scaleX(0); transform-origin: right;
  transition: transform var(--dur-fast) var(--ease-out);
}
.sports-list a:hover::after,
.sports-list .current-menu-item > a::after { transform: scaleX(1); transform-origin: left; }
.sports-list a:hover { color: var(--red); }

@media (min-width: 62rem) {
  .sports-list a:hover { transform: translateY(-1px); }
}

/* The footer's plain lists keep the red bar. */
.footer-base .nav-list a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--red);
  transform: scaleX(0); transform-origin: right;
  transition: transform var(--dur-fast) var(--ease-out);
}
.footer-base .nav-list a:hover::after { transform: scaleX(1); transform-origin: left; }

.brand .brand-mark { transition: transform var(--dur) var(--ease-spring); transform-origin: bottom; }
.brand:hover .brand-mark { transform: scaleY(1.3); }

.nav-toggle { transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
.nav-toggle:hover { border-color: var(--ink); }
.nav-toggle[aria-expanded='true'] { background: var(--ink); color: var(--white); border-color: var(--ink); }

/* Nested submenu, if one is ever used. */
@media (min-width: 62rem) {
  .sports-list .sub-menu {
    opacity: 0; visibility: hidden;
    transform: translateY(8px);
    transition:
      opacity var(--dur-fast) var(--ease-out),
      transform var(--dur-fast) var(--ease-out),
      visibility 0s linear var(--dur-fast);
  }
  .sports-list .menu-item-has-children:hover > .sub-menu,
  .sports-list .menu-item-has-children:focus-within > .sub-menu {
    opacity: 1; visibility: visible; transform: translateY(0); transition-delay: 0s;
  }
  .sports-list .sub-menu a::after { display: none; }
}

/* The mobile panel deals its links in. */
@media (max-width: 61.999rem) {
  .js .header-nav[data-open='true'] .sports-list > li {
    animation: rivexo-rise var(--dur) var(--ease-out) backwards;
  }
  .js .header-nav[data-open='true'] .sports-list > li:nth-child(1) { animation-delay: 20ms; }
  .js .header-nav[data-open='true'] .sports-list > li:nth-child(2) { animation-delay: 40ms; }
  .js .header-nav[data-open='true'] .sports-list > li:nth-child(3) { animation-delay: 60ms; }
  .js .header-nav[data-open='true'] .sports-list > li:nth-child(4) { animation-delay: 80ms; }
  .js .header-nav[data-open='true'] .sports-list > li:nth-child(5) { animation-delay: 100ms; }
  .js .header-nav[data-open='true'] .sports-list > li:nth-child(6) { animation-delay: 120ms; }
  .js .header-nav[data-open='true'] .sports-list > li:nth-child(7) { animation-delay: 140ms; }
  .js .header-nav[data-open='true'] .sports-list > li:nth-child(8) { animation-delay: 160ms; }
  .js .header-nav[data-open='true'] .sports-list > li:nth-child(n+9) { animation-delay: 180ms; }
}

/* --------------------------------------------------------------------------
   7. FAQ accordion
   The grid-rows trick animates height without measuring anything in JS.
   -------------------------------------------------------------------------- */

.faq-item summary::after { content: '+'; transition: transform var(--dur) var(--ease-spring), color var(--dur-fast) var(--ease); }
.faq-item[open] summary::after { content: '+'; transform: rotate(135deg); }

.faq-item { transition: background-color var(--dur) var(--ease-out); }
.faq-item[open] { background: var(--white); }

.faq-answer {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  padding-bottom: 0;
  transition:
    grid-template-rows var(--dur) var(--ease-out),
    opacity var(--dur-fast) var(--ease);
}
.faq-item[open] .faq-answer { grid-template-rows: 1fr; opacity: 1; }

/* The script keeps `open` set for the length of the collapse and flags the
   item, so the panel is still rendered while its height animates to zero.
   Same specificity as the rule above, and later, so it wins. */
.faq-item[open].is-closing .faq-answer { grid-template-rows: 0fr; opacity: 0; }
.faq-answer-inner { overflow: hidden; min-height: 0; }
.faq-answer-inner > :last-child { padding-bottom: var(--space-m); }

/* --------------------------------------------------------------------------
   8. Capacity counters
   The script counts up; this only holds the layout still while it does, so
   the row does not reflow as digits are added.
   -------------------------------------------------------------------------- */

.figure-value { font-variant-numeric: tabular-nums; display: block; }
.figure-item { transition: border-color var(--dur) var(--ease-out); }

/* --------------------------------------------------------------------------
   8b. Trust
   The red index rule draws itself across as each claim arrives, then runs
   further out when the pointer is on it.
   -------------------------------------------------------------------------- */

.js .trust-item::before {
  transform: scaleX(0);
  transition: transform var(--dur-slow) var(--ease-out) var(--reveal-delay, 0ms);
}
.js .trust-item.is-in::before { transform: scaleX(1); }

.trust-item h3 { transition: color var(--dur-fast) var(--ease); }
.trust-item:hover h3 { color: var(--red); }

.js .trust-item:hover::before {
  transform: scaleX(2.1);
  transition-delay: 0ms;
  transition-duration: var(--dur-slow);
  transition-timing-function: var(--ease-soft);
}

@media (prefers-reduced-motion: reduce) {
  .js .trust-item::before,
  .js .trust-item.is-in::before,
  .js .trust-item:hover::before { transform: scaleX(1) !important; transition: none !important; }
}

/* --------------------------------------------------------------------------
   8c. How it works
   Each step's red mark draws along the timeline as its column arrives, in
   sequence, so the eye is walked through the process in order.
   -------------------------------------------------------------------------- */

.js .step-body::after {
  transform: scaleX(0);
  transition: transform var(--dur-slow) var(--ease-out) var(--reveal-delay, 0ms);
}
.js .step.is-in .step-body::after { transform: scaleX(1); }

.step h3 { transition: color var(--dur-fast) var(--ease); }
.step:hover h3 { color: var(--red); }

.js .step:hover .step-body::after {
  transform: scaleX(2.4);
  transition-delay: 0ms;
  transition-duration: var(--dur-slow);
  transition-timing-function: var(--ease-soft);
}

/* The photograph pushes in, and the stage number rises with it. */
.step-media .ph, .step-media img { transition: transform 1400ms var(--ease-soft); }
.step:hover .step-media .ph, .step:hover .step-media img { transform: scale(1.07); }

.step-num { transition: transform var(--dur-slow) var(--ease-soft), color var(--dur-slow) var(--ease-soft); }
.step:hover .step-num { transform: translateY(-4px); color: var(--red); }

@media (prefers-reduced-motion: reduce) {
  .js .step-body::after,
  .js .step.is-in .step-body::after,
  .js .step:hover .step-body::after { transform: scaleX(1) !important; transition: none !important; }
  .step:hover .step-media .ph, .step:hover .step-media img { transform: none; }
  .step:hover .step-num { transform: none; }
}

/* --------------------------------------------------------------------------
   9. Client logo marquee
   -------------------------------------------------------------------------- */

/* The spacing lives on .logo-strip now, so this only handles the running
   track and the fade at either end. */
.logo-marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}
.logo-track { display: flex; align-items: center; gap: var(--space-m); width: max-content; }
.js .logo-track { animation: rivexo-marquee 42s linear infinite; }
.logo-marquee:hover .logo-track,
.logo-marquee:focus-within .logo-track { animation-play-state: paused; }
@keyframes rivexo-marquee { to { transform: translateX(-50%); } }

.logo-track .logo-slot {
  width: 10.5rem; flex: none; height: 3.5rem;
  display: grid; place-items: center;
  font-size: var(--step--2); color: var(--ink-40);
  background: var(--white); border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  transition:
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease),
    background-color var(--dur-fast) var(--ease);
}
.logo-track .logo-slot:hover { color: var(--ink); border-color: var(--line-strong); }

/* On black an empty slot should read as a waiting frame, not a white card.
   The label is held back but still has to clear AA at this size: #7E7E7E is
   5.2:1 on black, where the #6E6E6E it started at was only 4.1:1. */
.on-ink .logo-track .logo-slot {
  background: transparent;
  border-color: var(--dark-line);
  color: #7E7E7E;
}
.on-ink .logo-track .logo-slot:hover {
  color: var(--on-dark-60);
  border-color: #3C3C3C;
  background: rgba(255, 255, 255, 0.03);
}

/* --------------------------------------------------------------------------
   10. Gallery
   -------------------------------------------------------------------------- */

/* The red multiply overlay that used to live here is gone: the wash does the
   same job now, from the pointer rather than from everywhere at once. */
.gallery-item { transition: transform var(--dur-slow) var(--ease-soft); }
.gallery-item .ph, .gallery-item .media img { transition: transform 1400ms var(--ease-soft); }
.gallery-item:hover, .gallery-item:focus-within { transform: translateY(-4px); }
.gallery-item:hover .ph, .gallery-item:hover .media img { transform: scale(1.08); }

/* --------------------------------------------------------------------------
   11. Form feel
   -------------------------------------------------------------------------- */

.field input, .field select, .field textarea {
  transition:
    border-color var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease),
    background-color var(--dur-fast) var(--ease);
}
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(200, 16, 46, 0.12);
}
.check { transition: color var(--dur-fast) var(--ease); }
.check:hover { color: var(--red); }

button[type='submit']:disabled { opacity: 0.6; cursor: progress; }

/* --------------------------------------------------------------------------
   12. Reduced motion
   One switch. Entrances resolve instantly and nothing travels.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal],
  .js [data-reveal='lead'],
  .js [data-reveal='rule'] { opacity: 1 !important; transform: none !important; transition: none !important; }

  .js .banner-inner > *,
  .js .banner-eyebrow::before,
  .js .header-nav[data-open='true'] .sports-list > li { animation: none !important; }

  .banner-media { transform: scale(1.02) !important; }
  .js .logo-track { animation: none !important; }

  .btn:active { transform: none; }
  .card:hover, .method:hover, .swatch:hover, .sport:hover, .gallery-item:hover { transform: none; }
  .card:hover .card-media .ph, .card:hover .card-media .media img,
  .sport:hover .ph, .sport:hover .media img,
  .gallery-item:hover .ph, .gallery-item:hover .media img { transform: none; }
}

/* --------------------------------------------------------------------------
   13. Key terms slab
   One specular sweep as the plate arrives, then the rules draw themselves in
   behind it. Everything after that answers the pointer.
   -------------------------------------------------------------------------- */

.facts-slab::before {
  content: '';
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    104deg,
    transparent 32%,
    rgba(255, 255, 255, 0.14) 46%,
    rgba(255, 255, 255, 0.03) 54%,
    transparent 68%
  );
  transform: translateX(-130%);
}
.js .facts-slab.is-in::before { animation: rivexo-sheen 1500ms var(--ease-out) 420ms 1; }

@keyframes rivexo-sheen {
  from { transform: translateX(-130%); }
  to   { transform: translateX(130%); }
}

/* The red rule draws out from the left, in step with its own cell. */
.js .fact-rule {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-slow) var(--ease-out) var(--reveal-delay, 0ms);
}
.js .fact.is-in .fact-rule { transform: scaleX(1); }

/*
 * The reveal rule `.js [data-reveal]` out-specifies a plain `.fact` selector
 * and would replace its transition list outright, so the hover fade is
 * declared here on the same element with enough specificity to win, and it
 * restates the entrance transitions rather than dropping them.
 */
.js .fact[data-reveal] {
  transition:
    opacity var(--dur-slow) var(--ease-out) var(--reveal-delay, 0ms),
    transform var(--dur-slow) var(--ease-out) var(--reveal-delay, 0ms),
    background-color var(--dur) var(--ease-out);
}
.fact { transition: background-color var(--dur) var(--ease-out); }
.fact-value { transition: transform var(--dur) var(--ease-out); }
.fact-label { transition: color var(--dur) var(--ease-out); }

/* Deepen rather than lighten. A white wash on red just desaturates it; going
   darker reads as the cell being pressed. */
.fact:hover { background-color: rgba(0, 0, 0, 0.13); }
.fact:hover .fact-value { transform: translateY(-2px); }
.fact:hover .fact-label { color: var(--white); }

/* On hover the rule runs out across the cell. The delay only belongs to the
   entrance, so it is cleared here or the hover would lag behind the pointer.
   The band is slim now and its cells are narrower, so the rule extends less
   far than it did on the old plate. */
.js .fact:hover .fact-rule {
  transform: scaleX(1.9);
  transition-delay: 0ms;
  transition-duration: var(--dur);
}

@media (prefers-reduced-motion: reduce) {
  .js .facts-slab.is-in::before { animation: none !important; }
  .js .fact-rule, .js .fact.is-in .fact-rule { transform: scaleX(1) !important; transition: none !important; }
  .js .fact:hover .fact-rule { transform: scaleX(1) !important; }
  .fact:hover .fact-value { transform: none; }
}
