/* Animated backdrop.
 *
 * Four soft colour fields drifting slowly behind everything. Built from
 * radial-gradient elements moved with transform rather than blurred boxes:
 * filter: blur() on something this large repaints on every frame, while a
 * gradient is already soft and a transform is composited on the GPU, so this
 * costs effectively nothing even on a mid-range phone.
 *
 * The palette is taken from the ID artwork itself — the same rose, peach and
 * pale gold — so the page reads as an extension of the card it is showing.
 */

.sj-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background:
    linear-gradient(180deg, #fffdfe 0%, #fff6f9 42%, #ffeef5 100%);
}

.sj-bg-blob {
  position: absolute;
  border-radius: 50%;
  /* Promoted once, up front: animating a layer that is composited from the
     first frame avoids the flicker of it being promoted mid-animation. */
  will-change: transform;
  transform: translate3d(0, 0, 0);
}

.sj-bg-blob.b1 {
  width: 52vw;
  height: 52vw;
  top: -14vw;
  left: -10vw;
  background: radial-gradient(circle at 34% 34%, rgba(255, 122, 165, .50), rgba(255, 122, 165, 0) 68%);
  animation: sj-drift-a 32s ease-in-out infinite alternate;
}

.sj-bg-blob.b2 {
  width: 46vw;
  height: 46vw;
  top: -6vw;
  right: -12vw;
  background: radial-gradient(circle at 60% 40%, rgba(255, 205, 170, .48), rgba(255, 205, 170, 0) 70%);
  animation: sj-drift-b 38s ease-in-out infinite alternate;
}

.sj-bg-blob.b3 {
  width: 58vw;
  height: 58vw;
  bottom: -22vw;
  left: 6vw;
  background: radial-gradient(circle at 45% 55%, rgba(226, 196, 255, .38), rgba(226, 196, 255, 0) 68%);
  animation: sj-drift-c 44s ease-in-out infinite alternate;
}

.sj-bg-blob.b4 {
  width: 40vw;
  height: 40vw;
  bottom: -14vw;
  right: -8vw;
  background: radial-gradient(circle at 50% 50%, rgba(255, 232, 167, .42), rgba(255, 232, 167, 0) 70%);
  animation: sj-drift-d 36s ease-in-out infinite alternate;
}

/* Long, uneven durations and opposing directions so the four never fall into a
   visible rhythm — a loop you can count is what makes a background distracting. */
@keyframes sj-drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(6vw, 5vw, 0) scale(1.14); }
}

@keyframes sj-drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.08); }
  to   { transform: translate3d(-7vw, 7vw, 0) scale(1); }
}

@keyframes sj-drift-c {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(8vw, -6vw, 0) scale(1.12); }
}

@keyframes sj-drift-d {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-5vw, -7vw, 0) scale(1); }
}

/* A single wide highlight passing over the top of the page, very faint. Gives
   the surface a sense of light rather than of flat colour. */
.sj-bg::after {
  content: '';
  position: absolute;
  inset: -30% -10%;
  background: linear-gradient(100deg,
    rgba(255, 255, 255, 0) 38%,
    rgba(255, 255, 255, .55) 50%,
    rgba(255, 255, 255, 0) 62%);
  animation: sj-sheen 24s linear infinite;
}

@keyframes sj-sheen {
  from { transform: translateX(-38%); }
  to   { transform: translateX(38%); }
}

/* Motion is decoration here, so it is the first thing to go. The colour field
   stays — it is part of the design, not part of the animation. */
@media (prefers-reduced-motion: reduce) {
  .sj-bg-blob,
  .sj-bg::after {
    animation: none;
  }
  .sj-bg::after { opacity: .4; }
}

/* ---------------------------------------------------------------- products */

/*
 * Product shots drifting behind the page.
 *
 * Placement, size, drift distance and timing are randomised per request in
 * layout.php and arrive as custom properties, so no two visits get the same
 * arrangement — which is the point, and is also why none of it is expressible
 * as a fixed rule here.
 *
 * Kept faint and blurred on purpose. This sits behind glass cards carrying an
 * ID, a letter and a form; the moment a product photo competes with any of
 * that for attention it has stopped being a background.
 */
.sj-bg-item {
  position: absolute;
  /* Above the sheen. Both are positioned children of .sj-bg and ::after paints
     last, so without this the white highlight sweeps over the products and
     washes them out to nothing. */
  z-index: 1;
  top: var(--sj-top);
  left: var(--sj-left);
  width: var(--sj-size);
  height: var(--sj-size);
  /* background-image is set inline, not passed through a custom property: a
     url() inside a custom property resolves against the stylesheet that
     substitutes it, not the element that declared it, so a relative path would
     be looked for under /css/. Set on the element it resolves against the
     document, which is what anyone reading it would expect. */
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  opacity: var(--sj-opacity);
  /* Barely blurred. 2.5px was enough to turn the products into coloured
     smudges — the point of putting real bottles back there is that they are
     recognisably the products, so this softens the edges and no more. Opacity
     is what keeps them behind the content, not blur. */
  filter: blur(0.6px);
  will-change: transform;
  transform: translate3d(0, 0, 0) rotate(var(--sj-tilt));
  animation: sj-float var(--sj-duration) ease-in-out var(--sj-delay) infinite alternate;
  /* Only opacity transitions. The drift is an animation on transform, and
     transitioning that as well would fight it every time one is swapped. */
  transition: opacity .9s ease;
}

/* Held invisible while its image and position are changed underneath. */
.sj-bg-item.is-swapping { opacity: 0; }

@keyframes sj-float {
  from { transform: translate3d(0, 0, 0) rotate(var(--sj-tilt)) scale(1); }
  to   { transform: translate3d(var(--sj-dx), var(--sj-dy), 0) rotate(var(--sj-tilt-to)) scale(var(--sj-scale)); }
}

/* Small screens carry fewer and smaller: the text column takes most of the
   width there, so anything behind it is both less visible and more in the way.
   The later ones are dropped rather than shrunk — four faint things on a phone
   is clutter however small they are.

   Marked with a class rather than selected by position. :nth-of-type counts
   every sibling span, and the four colour blobs are spans too, so a positional
   rule here matched all of the products and hid the lot. */
@media (max-width: 720px) {
  .sj-bg-item { opacity: calc(var(--sj-opacity) * .7); }
  .sj-bg-item.is-extra { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .sj-bg-item { animation: none; }
}
