/* ============================================================================
   JOSEPH SERVICES - motion
   ----------------------------------------------------------------------------
   A DIFFERENT VOCABULARY, not the same moves retimed.

   The sister site reveals everything by fading up: opacity 0 plus
   translateY(18px), settling on an expo-out curve. It is the move every site
   makes, and using it here would undo the rest of the redesign no matter what
   the colours did.

   This site WIPES. Content is uncovered left to right behind a hard edge, like
   a trailer door rolling open, on a mechanical curve that starts fast and
   stops flat. Nothing floats and nothing fades in from nowhere.

   Every animation below can be justified in one sentence:
     .reveal      uncovers content in reading order as it enters the viewport
     .enter       the same wipe on load, staggered, so the hero resolves in
                  sequence rather than all at once
     .enter-media a photograph is uncovered top to bottom, matching the way a
                  load is looked at
     .lift        a crimson edge grows on hover to say "this is pressable"
     .press       1px of travel on :active, the only feedback a touch screen
                  can give
   Anything that could not be justified is not here. THERE IS NO MARQUEE: the
   sister site has one, and a second scrolling word strip on a sister company's
   site would read as the same template twice.

   WHAT IS NOT IN HERE: photograph framing and the lane-marking rules live in
   site.css beside the systems they belong to, each with its own reduced-motion
   block. Only some pages load this file. Anything that must work everywhere
   goes in site.css. That rule was learned the expensive way.
   ========================================================================= */

/* ---- the wipe ------------------------------------------------------------
   clip-path animates on the compositor, so this is as cheap as opacity and
   considerably more distinctive. The 2% inset on the start state stops a
   sub-pixel sliver of content showing before the wipe begins.
   ------------------------------------------------------------------------ */
@keyframes wipe-in {
  /* The inset is expanded 3rem on the three edges the wipe does not travel
     along, and that is not cosmetic.

     clip-path: inset() clips to the element's BORDER BOX. Several components
     put decoration deliberately OUTSIDE that box: the numbered steps on the
     home page sit their ::before counter at top:-.85rem so it straddles the
     rule above them. With a plain inset(0 0 0 0) end state - which is exactly
     the border box - the top half of "01", "02" and "03" was cut off
     permanently, not just while animating. The old fade-up used opacity and
     transform, which do not clip, so nothing revealed this until the wipe
     replaced it.

     Only the RIGHT edge travels. Everything else is held well outside the box
     so no component can lose its overhang. */
  from { clip-path: inset(-3rem 100% -3rem -3rem); }
  to   { clip-path: inset(-3rem -3rem -3rem -3rem); }
}
@keyframes wipe-down {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* ---- hero entrance -------------------------------------------------------
   Runs once on load in reading order. No scroll involvement, nothing to jank. */
.enter > * {
  animation: wipe-in var(--slow) var(--ease) both;
}
.enter > *:nth-child(1) { animation-delay: .04s; }
.enter > *:nth-child(2) { animation-delay: .13s; }
.enter > *:nth-child(3) { animation-delay: .22s; }
.enter > *:nth-child(4) { animation-delay: .31s; }

.enter-media {
  animation: wipe-down 760ms var(--ease) both;
  animation-delay: .1s;
  overflow: hidden;
}
.enter-late {
  animation: wipe-in var(--slow) var(--ease) both;
  animation-delay: .42s;
}

/* ---- hover ---------------------------------------------------------------
   The sister site lifts a card on a shadow. On a steel ground a soft shadow
   turns to mud, so depth here is a CRIMSON EDGE that grows out of the left
   side. It is mechanical, it reads instantly, and it costs one transform. */
.lift { position: relative; transition: background var(--med) var(--ease); }
.lift::before {
  content: "";
  position: absolute; inset: 0 auto 0 0;
  width: 3px; background: var(--accent);
  transform: scaleY(0); transform-origin: 50% 0;
  transition: transform var(--med) var(--ease);
}
@media (hover: hover) {
  .lift:hover::before { transform: scaleY(1); }
  .lift:hover { background: var(--panel); }
}
.lift:focus-within::before { transform: scaleY(1); }

/* ---- photograph ----------------------------------------------------------
   A slow push in, not a zoom out. Half the distance the sister site travels,
   because on a dark page a moving photograph is already loud. */
.zoom { overflow: hidden; }
.zoom img { transition: transform 700ms var(--ease); will-change: transform; }
@media (hover: hover) { .zoom:hover img { transform: scale(1.03); } }

/* ---- press ---------------------------------------------------------------
   FEEDBACK, and specifically feedback for the half of this audience with no
   cursor. Every hover state above is invisible on a phone, so a pressable
   surface takes the press: 1px, 90ms, gone. */
.press { transition: transform 90ms var(--ease-out); }
.press:active { transform: translateY(1px); }

/* ---- buttons -------------------------------------------------------------
   The sister site runs a sheen across its primary button. This one fills from
   the left on the same axis as every other motion on the page, so the whole
   site moves in one direction. */
.btn-primary { position: relative; overflow: hidden; isolation: isolate; }
.btn-primary::after {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: var(--accent-press);
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform var(--med) var(--ease);
}
@media (hover: hover) {
  .btn-primary:hover::after { transform: scaleX(1); }
}

/* ---- equipment strip -----------------------------------------------------
   Replaces the sister site's scrolling marquee. Deliberately STATIC: a hard
   grid of plates with crimson dividers, read at a glance rather than waited
   for. A second scrolling word strip on a sister company's site would read as
   the same template twice.
   ------------------------------------------------------------------------ */
.equip-strip {
  border-block: 1px solid var(--rule);
  background: var(--sunk);
}
.equip-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  max-width: var(--wrap-wide);
  margin-inline: auto;
}
.equip-item {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s5) var(--s4);
  font-family: var(--font-display);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--ink-soft);
  border-right: 1px solid var(--rule);
}
.equip-item:last-child { border-right: 0; }
.equip-item::before {
  content: ""; flex: none;
  width: 10px; height: 3px; background: var(--accent);
}

/* ---- reduced motion ------------------------------------------------------
   All of it collapses. A wipe is still motion, and someone who has asked for
   less of it should get the content, not a performance. This audience opens
   the site on mobile data in a cab.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .enter > *,
  .enter-media,
  .enter-late {
    animation: none !important;
    clip-path: none !important;
  }
  .lift::before,
  .btn-primary::after { transition: none !important; }
  .zoom img { transition: none !important; }
  .press { transition: none !important; }
}
