/* ==========================================================================
   Motion layer. Loaded last, and entirely removable: delete this file and
   js/motion.js and the site still works, just without the interaction.

   One idea, three surfaces:
   the cursor is a light. It trails, it blends with what is under it, and
   when it passes over the work the work brightens. That is the same thing
   Sho does in a dark gallery, moved onto a page.
   ========================================================================== */

/* --- The light ----------------------------------------------------------- */

/* Sized once at its largest, then scaled down. The element moves every
   frame, so only compositor properties (translate / scale / opacity) may
   animate; touching width or margin here would relayout on every move. */
.beam {
  position: fixed;
  top: 0; left: 0;
  width: 82px; height: 82px;
  margin: -41px 0 0 -41px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 400;
  opacity: 0;
  scale: 0.27;
  background: oklch(0.92 0.09 222.6);
  mix-blend-mode: difference;
  transition: scale 420ms var(--ease-out), opacity 300ms linear,
              background-color 300ms linear;
  will-change: transform, scale;
}
.beam.is-live { opacity: 1; }

/* Over anything clickable the light opens up. */
.beam.is-wide { scale: 1; background: oklch(0.72 0.14 222.6); }

/* The trail: a second, slower, softer blob. Together they read as one
   light with weight, not as two dots. */
.beam-trail {
  position: fixed;
  top: 0; left: 0;
  width: 130px; height: 130px;
  margin: -65px 0 0 -65px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 399;
  opacity: 0;
  background: radial-gradient(circle closest-side,
              oklch(0.62 0.13 222.6 / 0.30), oklch(0.62 0.13 222.6 / 0) 72%);
  transition: opacity 500ms linear;
  will-change: transform;
}
.beam-trail.is-live { opacity: 1; }

/* Touch, coarse pointers and reduced-motion users get none of it. */
@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce) {
  .beam, .beam-trail { display: none; }
}

/* --- Work grid: the light falls on the work ------------------------------ */

.piece__frame img {
  filter: saturate(0.86) brightness(0.95) contrast(1.02);
  transition: transform var(--dur-slow) var(--ease-out),
              filter var(--dur) var(--ease-out);
}
.piece:hover .piece__frame img,
.piece:focus-visible .piece__frame img { filter: none; }

/* The spotlight itself. --mx/--my are written by motion.js, only for the
   frame currently under the pointer, so this costs one element at a time. */
.piece__frame::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out);
  background: radial-gradient(
    circle 34vmin at var(--mx, 50%) var(--my, 50%),
    oklch(1 0 0 / 0.26), oklch(1 0 0 / 0.08) 42%, transparent 68%);
  mix-blend-mode: soft-light;
}
.piece:hover .piece__frame::after { opacity: 1; }

@media (hover: none), (prefers-reduced-motion: reduce) {
  .piece__frame img { filter: none; }
  .piece__frame::after { display: none; }
}

/* --- Kinetic type: lines rise out of a mask as they arrive ---------------- */

/* Readable text is the default in every state. The hidden-below-the-mask
   position is only applied once motion.js has confirmed the document is
   visible and can actually run the transition ("is-armed"). A hidden tab,
   a headless render, or a dead script therefore ships plain type rather
   than an empty page: transitions do not advance while a document is
   hidden, so anything that starts hidden would never come back. */
.line-mask {
  display: block;
  overflow: hidden;
  padding-bottom: 0.04em;   /* room for descenders inside the mask */
}
.line-mask > span { display: block; }

/* Arming is instant: no transition here, or the line would visibly drop
   into hiding before it rises. Only the reveal animates. */
.is-armed .line-mask > span {
  transform: translateY(105%);
  will-change: transform;
}
.is-armed.is-lit .line-mask > span {
  transform: none;
  transition: transform 1.05s var(--ease-out);
  transition-delay: var(--line-delay, 0ms);
}

/* Smaller copy rises faster: the same gesture, scaled down. */
.is-armed.is-lit.hero__lede .line-mask > span,
.is-armed.is-lit.chapter__body .line-mask > span { transition-duration: 0.8s; }

@media (prefers-reduced-motion: reduce) {
  .line-mask { overflow: visible; }
  .is-armed .line-mask > span { transform: none; transition: none; }
}

/* --- Scroll depth --------------------------------------------------------
   Images drift slightly slower than the page inside their own frames. The
   frame never moves and nothing is distorted, so this reads as depth rather
   than as motion. (An earlier version skewed the whole grid on scroll
   velocity: it warped every vertical edge and was genuinely nauseating.
   Never distort geometry in response to scrolling.)                        */

.piece__frame img,
.reel__frame img {
  /* Oversized so the drift never exposes an edge. */
  scale: 1.08;
  translate: 0 var(--drift, 0px);
}

@media (prefers-reduced-motion: reduce) {
  .piece__frame img, .reel__frame img { scale: 1; translate: none; }
}

/* --- More work: the typographic tail of the index ------------------------- */

.more__title {
  margin-top: clamp(3rem, 2rem + 4vw, 6rem);
  margin-bottom: 1.25rem;
  font-size: var(--step-2);
  font-weight: 700;
  letter-spacing: -0.036em;
}

.more { border-top: 1px solid var(--rule-strong); }

.more__row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 0.3rem 2rem;
  padding-block: clamp(0.9rem, 0.55rem + 1.1vw, 1.5rem);
  border-bottom: 1px solid var(--rule);
  position: relative;
  isolation: isolate;
}
.more__row::before {
  content: '';
  position: absolute;
  inset: 0 -1.1rem;
  background: var(--bg-sunk);
  opacity: 0;
  z-index: -1;
  transition: opacity var(--dur) var(--ease-out);
}
.more__row:hover::before, .more__row:focus-visible::before { opacity: 1; }

.more__name {
  font-size: clamp(1.35rem, 0.95rem + 1.5vw, 2.1rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.06;
  transition: transform var(--dur) var(--ease-out);
}
.more__row:hover .more__name { transform: translateX(0.5rem); }

.more__side {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.2rem 1rem;
  text-align: right;
  font-size: var(--step--1);
  color: var(--ink-faint);
}
@media (max-width: 42rem) {
  .more__row { grid-template-columns: 1fr; }
  .more__side { justify-content: flex-start; text-align: left; }
}

/* The floating preview for the More work list. */
.peek {
  position: fixed;
  top: 0; left: 0;
  width: clamp(16rem, 22vw, 24rem);
  aspect-ratio: 3 / 2;
  pointer-events: none;
  z-index: 380;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.94);
  transition: opacity 320ms var(--ease-out), transform 560ms var(--ease-out);
  overflow: hidden;
  background: var(--bg-sunk);
  box-shadow: 0 18px 60px oklch(0.18 0.01 255 / 0.24);
  will-change: transform;
}
.peek.is-on { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.peek img { width: 100%; height: 100%; object-fit: cover; }

@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce) {
  .peek { display: none; }
}
