/* ============================================================
   Slab art surfaces. Owns the sl- prefix and nothing else.

   Everything here animates transform, opacity, filter or
   background-position only. The tilt writes CSS custom properties
   from a pointermove handler; the element's box is never read or
   written during a move, so a wall of slabs stays on the compositor.

   The handler publishes six properties on .sl-tilt:
     --sl-rx / --sl-ry   rotation, in deg
     --sl-nx / --sl-ny   the same gesture, unitless, -1..1
     --sl-mx / --sl-my   pointer position over the card, 0..100%
   Anything that needs to multiply the tilt by a length uses the
   unitless pair, because calc() cannot reliably divide an angle back
   down to a number yet.
   ============================================================ */

/* ---------- the tilt container ---------- */
.sl-slab {
  --sl-nx: 0;
  --sl-ny: 0;
  --sl-delay: 0s;
  display: block;
  position: relative;
  width: 200px;
  max-width: 100%;
  perspective: 1000px;          /* perspective lives on the parent */
  perspective-origin: 50% 45%;
}

.sl-slab.sm { width: 132px; }
.sl-slab.md { width: 200px; }
.sl-slab.lg { width: 296px; }

.sl-tilt {
  position: relative;
  display: block;
  transform-style: preserve-3d;
  transform:
    rotateX(var(--sl-rx, 0deg))
    rotateY(var(--sl-ry, 0deg))
    translateZ(0);
  transition: transform .38s cubic-bezier(.2, .9, .3, 1);
  will-change: transform;
}

/* While the pointer is actually over the slab the transform must
   track it exactly, so the settle transition is switched off. */
.sl-slab.is-tilting .sl-tilt { transition: none; }

.sl-plate {
  position: relative;
  display: block;
  border-radius: 7%/5%;
  transition: filter .3s ease;
}

.sl-slab svg,
.sl-card svg {
  display: block;
  width: 100%;
  height: auto;
}

/* ---------- idle float ----------
   A grid of slabs that sits perfectly still reads as a screenshot.
   Two pixels and a third of a degree over nine seconds is enough to
   make it feel alive, and --sl-delay (seeded off the card id in
   slabNode) keeps the grid from breathing in unison. */
@keyframes sl-float {
  0%   { transform: translate3d(0, 0, 0) rotate(-0.3deg); }
  50%  { transform: translate3d(0, -3px, 0) rotate(0.34deg); }
  100% { transform: translate3d(0, 0, 0) rotate(-0.3deg); }
}

.sl-plate,
.sl-holo,
.sl-glass {
  animation: sl-float 9s ease-in-out infinite;
  animation-delay: var(--sl-delay, 0s);
}

/* Under the pointer the tilt owns the transform, so the drift holds
   wherever it was. Freezing beats snapping back to zero. */
.sl-slab.is-tilting .sl-plate,
.sl-slab.is-tilting .sl-holo,
.sl-slab.is-tilting .sl-glass,
.sl-slab.is-tilting .sl-rim { animation-play-state: paused; }

/* ---------- parallax depth bands ----------
   cardFaceSVG splits every motif into a far wash, a mid subject and
   a near foreground. They are plain <g> elements inside the card's
   own clip, so sliding them by different amounts opens real depth
   without a single extra draw call. */
.sl-d1, .sl-d2, .sl-d3 {
  transition: transform .42s cubic-bezier(.2, .9, .3, 1);
}
.sl-d1 { transform: translate(calc(var(--sl-nx, 0) *  3px), calc(var(--sl-ny, 0) *  3px)); }
.sl-d2 { transform: translate(calc(var(--sl-nx, 0) *  7px), calc(var(--sl-ny, 0) *  7px)); }
.sl-d3 { transform: translate(calc(var(--sl-nx, 0) * 12px), calc(var(--sl-ny, 0) * 12px)); }

.sl-slab.is-tilting .sl-d1,
.sl-slab.is-tilting .sl-d2,
.sl-slab.is-tilting .sl-d3 { transition: none; }

/* ---------- pointer specular ----------
   A hotspot that actually tracks the pointer, not a gradient that
   fades in. This is the single thing that sells the acrylic as
   glossy, so it is two stacked cones: a tight core and a broad wash. */
.sl-sheen {
  position: absolute;
  inset: 0;
  border-radius: 7%/5%;
  pointer-events: none;
  opacity: 0;
  transition: opacity .25s ease;
  background-image:
    radial-gradient(
      circle at var(--sl-mx, 50%) var(--sl-my, 50%),
      rgba(255, 255, 255, 0.62) 0%,
      rgba(255, 255, 255, 0.20) 9%,
      rgba(255, 255, 255, 0) 22%
    ),
    radial-gradient(
      circle at var(--sl-mx, 50%) var(--sl-my, 50%),
      rgba(255, 255, 255, 0.12) 0%,
      rgba(255, 255, 255, 0.045) 30%,
      rgba(255, 255, 255, 0) 62%
    );
  mix-blend-mode: screen;
}
.sl-slab.is-tilting .sl-sheen { opacity: 1; }

/* ---------- glass: the slow specular streak ----------
   The case has a highlight of its own that drifts whether or not
   anybody is pointing at it, which is what stops a still grid from
   looking like flat print. */
/* Moved by transform, not background-position: a background-position
   animation repaints the layer every frame, and there is one of these
   on every slab in the grid. */
@keyframes sl-streak {
  0%   { transform: translate3d(-40%, 0, 0); }
  50%  { transform: translate3d( 40%, 0, 0); }
  100% { transform: translate3d(-40%, 0, 0); }
}

.sl-glass {
  position: absolute;
  inset: 0;
  border-radius: 7%/5%;
  overflow: hidden;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: .55;
  transition: opacity .3s ease;
}
.sl-glass::before {
  content: '';
  position: absolute;
  top: -8%;
  bottom: -8%;
  left: -55%;
  right: -55%;
  background-image: linear-gradient(
    104deg,
    rgba(255, 255, 255, 0)    41%,
    rgba(255, 255, 255, .05)  46%,
    rgba(255, 255, 255, .2)   50%,
    rgba(255, 255, 255, .05)  54%,
    rgba(255, 255, 255, 0)    59%
  );
  transform: translate3d(-40%, 0, 0);
  animation: sl-streak 14s ease-in-out infinite;
  animation-delay: var(--sl-delay, 0s);
}
.sl-slab.is-tilting .sl-glass { opacity: .8; }

/* ---------- diffraction foil ----------
   Real foil is a grating, not a rainbow. Two repeating gratings at
   different angles and different spatial frequencies drift against
   each other, the broad spectrum sits under them on color-dodge, and
   the whole sheet is masked to the card window rather than the case.
   Tilt shifts the gratings and rotates the hue, so the colours travel
   as the card turns. */
@keyframes sl-diffract-a {
  from { transform: translate3d(-4%, -2%, 0); }
  to   { transform: translate3d( 4%,  2%, 0); }
}
@keyframes sl-diffract-b {
  from { transform: translate3d( 3.5%,  2%, 0); }
  to   { transform: translate3d(-3.5%, -2%, 0); }
}

.sl-holo {
  position: absolute;
  /* the card sits at x 36..336 of 372 and y 120..540 of 576 */
  top: 20.8%;
  right: 9.7%;
  bottom: 6.3%;
  left: 9.7%;
  border-radius: 6%/4.3%;
  overflow: hidden;
  pointer-events: none;
  opacity: .15;
  mix-blend-mode: screen;
  filter: hue-rotate(calc(var(--sl-nx, 0) * 42deg)) saturate(1.15);
  transition: opacity .3s ease;
}

.sl-holo::before,
.sl-holo::after {
  content: '';
  position: absolute;
  inset: -30%;
}

/* broad spectrum plus the fine grating, dodged together */
.sl-holo::before {
  background-image:
    repeating-linear-gradient(
      118deg,
      rgba(255, 255, 255, 0)   0px,
      rgba(255, 255, 255, .24) 3px,
      rgba(255, 255, 255, 0)   8px
    ),
    linear-gradient(
      114deg,
      rgba(255,  95, 138, 0)   6%,
      rgba(255, 201,  95, .62) 20%,
      rgba(141, 255, 159, .34) 34%,
      rgba( 95, 216, 255, .62) 48%,
      rgba(166, 123, 255, .34) 62%,
      rgba(255,  95, 138, .62) 76%,
      rgba(255,  95, 138, 0)   92%
    );
  background-size: 100% 100%, 260% 100%;
  background-position:
    calc(50% + var(--sl-nx, 0) * 26%) calc(50% + var(--sl-ny, 0) * 26%),
    calc(50% + var(--sl-nx, 0) * 30%) 50%;
  background-blend-mode: color-dodge, normal;
  animation: sl-diffract-a 8s ease-in-out infinite alternate;
  animation-delay: var(--sl-delay, 0s);
}

/* the coarse grating, counter drifting */
.sl-holo::after {
  background-image: repeating-linear-gradient(
    -52deg,
    rgba(190, 255, 245, 0)   0px,
    rgba(190, 255, 245, .22) 5px,
    rgba(255, 190, 240, .14) 9px,
    rgba(255, 255, 255, 0)   19px
  );
  background-position: calc(50% - var(--sl-nx, 0) * 22%) calc(50% - var(--sl-ny, 0) * 22%);
  mix-blend-mode: screen;
  animation: sl-diffract-b 12.5s ease-in-out infinite alternate;
  animation-delay: var(--sl-delay, 0s);
}

/* rare gets the sheet, held right back. common and uncommon get none. */
.sl-slab[data-rarity="rare"]      .sl-holo { opacity: .06; }
.sl-slab[data-rarity="mythic"]    .sl-holo { opacity: .15; }
.sl-slab[data-rarity="legendary"] .sl-holo { opacity: .18; }

.sl-slab.is-tilting[data-rarity="rare"]      .sl-holo { opacity: .12; }
.sl-slab.is-tilting[data-rarity="mythic"]    .sl-holo { opacity: .3; }
.sl-slab.is-tilting[data-rarity="legendary"] .sl-holo { opacity: .34; }

/* ---------- animated rim light (mythic + legendary) ----------
   A gradient ring painted with the border-mask trick, swept by
   background-position so a highlight travels the edge of the case. */
@keyframes sl-rim-sweep {
  0%   { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}

.sl-rim {
  position: absolute;
  inset: -1px;
  padding: 1.6px;
  border-radius: 7%/5%;
  pointer-events: none;
  opacity: .5;
  mix-blend-mode: screen;
  background-image: linear-gradient(
    118deg,
    transparent 12%,
    var(--sl-rim-ink, #ffffff) 38%,
    #ffffff 48%,
    var(--sl-rim-ink, #ffffff) 58%,
    transparent 86%
  );
  background-size: 240% 100%;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  animation: sl-float 9s ease-in-out infinite, sl-rim-sweep 6.5s linear infinite;
  animation-delay: var(--sl-delay, 0s), var(--sl-delay, 0s);
  transition: opacity .3s ease;
}
.sl-slab[data-rarity="mythic"]    .sl-rim { --sl-rim-ink: var(--rarity-mythic); }
.sl-slab[data-rarity="legendary"] .sl-rim { --sl-rim-ink: var(--rarity-legendary); }
.sl-slab.is-tilting .sl-rim { opacity: .85; }

/* ---------- rarity drop shadow ---------- */
.sl-glow .sl-plate {
  filter: drop-shadow(0 14px 26px oklch(0% 0 0 / .45));
}
.sl-glow[data-rarity="common"]    .sl-plate { filter: drop-shadow(0 12px 24px oklch(0% 0 0 / .42)); }
.sl-glow[data-rarity="uncommon"]  .sl-plate { filter: drop-shadow(0 12px 26px oklch(from var(--rarity-uncommon) l c h / .34)); }
.sl-glow[data-rarity="rare"]      .sl-plate {
  filter:
    drop-shadow(0 12px 28px oklch(from var(--rarity-rare) l c h / .4))
    drop-shadow(0 2px 4px oklch(0% 0 0 / .4));
}
.sl-glow[data-rarity="mythic"]    .sl-plate {
  filter:
    drop-shadow(0 4px 10px oklch(from var(--rarity-mythic) l c h / .5))
    drop-shadow(0 16px 36px oklch(from var(--rarity-mythic) l c h / .5));
}
.sl-glow[data-rarity="legendary"] .sl-plate {
  filter:
    drop-shadow(0 4px 12px oklch(from var(--rarity-legendary) l c h / .55))
    drop-shadow(0 18px 44px oklch(from var(--rarity-legendary) l c h / .55));
}

.sl-slab.is-tilting.sl-glow[data-rarity="rare"]      .sl-plate {
  filter:
    drop-shadow(0 14px 32px oklch(from var(--rarity-rare) l c h / .52))
    drop-shadow(0 2px 5px oklch(0% 0 0 / .42));
}
.sl-slab.is-tilting.sl-glow[data-rarity="mythic"]    .sl-plate {
  filter:
    drop-shadow(0 6px 16px oklch(from var(--rarity-mythic) l c h / .7))
    drop-shadow(0 22px 48px oklch(from var(--rarity-mythic) l c h / .68));
}
.sl-slab.is-tilting.sl-glow[data-rarity="legendary"] .sl-plate {
  filter:
    drop-shadow(0 6px 18px oklch(from var(--rarity-legendary) l c h / .78))
    drop-shadow(0 24px 56px oklch(from var(--rarity-legendary) l c h / .76));
}

/* ---------- bare card face / back (used by the rip flip) ---------- */
.sl-card {
  position: relative;
  display: block;
  width: 100%;
  backface-visibility: hidden;
  border-radius: 6%/4.3%;
}
.sl-card.sm { width: 128px; }
.sl-card.md { width: 196px; }
.sl-card.lg { width: 292px; }

/* ---------- static frame, no interaction ---------- */
.sl-static { perspective: none; }
.sl-static .sl-tilt { transform: none; transition: none; }
.sl-static .sl-plate,
.sl-static .sl-holo,
.sl-static .sl-glass,
.sl-static .sl-rim { animation: none; }

/* ---------- placeholder while a face is still being built ---------- */
.sl-skeleton {
  width: 100%;
  aspect-ratio: 372 / 576;
  border-radius: 14px;
  background: linear-gradient(90deg, var(--muted) 0%, var(--border) 40%, var(--muted) 80%);
  background-size: 800px 100%;
  animation: shimmer 1.4s linear infinite;
}

/* ---------- caption block, shared by every grid that shows slabs ---------- */
.sl-cap {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 10px;
  min-width: 0;
}
.sl-cap-name {
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sl-cap-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--muted-fg);
  font-family: var(--font-mono);
}

@media (max-width: 520px) {
  .sl-slab.lg { width: 240px; }
  .sl-slab.md { width: 168px; }
}

@media (prefers-reduced-motion: reduce) {
  .sl-tilt { transition: none; transform: none; }
  .sl-plate,
  .sl-holo,
  .sl-holo::before,
  .sl-holo::after,
  .sl-glass,
  .sl-glass::before,
  .sl-rim,
  .sl-skeleton { animation: none; }
  .sl-holo { filter: none; }
  /* the sweeps stop mid stroke rather than parked at one end */
  .sl-rim { background-position: 50% 50%; }
  .sl-glass::before { transform: translate3d(-6%, 0, 0); }
  .sl-d1, .sl-d2, .sl-d3 { transition: none; transform: none; }
  .sl-sheen { transition: none; }
}
