/* ==========================================================================
   OACSYS - Crystal liquid background
   The animated background layer only (liquid blobs, particle canvas, grain).
   Split out of crystal-dark.css so pages outside the main layout (login, etc)
   can include it on its own. Markup: resources/views/layouts/crystal-bg.blade.php
   Behaviour (particles, parallax): dist/js/crystal-motion.js
   ========================================================================== */

/* ---------- animated background: liquid blobs + particle canvas + grain ----------
   Fixed behind the page at z-index -1 rather than lifting .hk-wrapper above it:
   giving .hk-wrapper a z-index would make it a stacking context and trap every
   Bootstrap .modal (rendered inside the wrapper) beneath the .modal-backdrop
   that Bootstrap appends to <body>. */

.crystal-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

/* The liquid layer is rendered at HALF size and scaled x2. It's heavily
   blurred, so the upscale is invisible - but blur cost scales with area, so
   this makes the continuously-animating filter ~4x cheaper. The blur +
   contrast pair is what makes separate blobs melt into each other with
   liquid "necks" instead of just overlapping: blur softens their alpha, then
   contrast pushes the soft edges back toward solid. On this near-black canvas
   the dark halos that trick normally produces are invisible.
   crystal-motion.js writes translate3d() into this element's transform for
   parallax - it must always keep the trailing scale(2). */
.crystal-liquid {
  position: absolute;
  top: 0;
  left: 0;
  width: 50%;
  height: 50%;
  transform: scale(2);
  transform-origin: 0 0;
  filter: blur(30px) contrast(1.7) saturate(1.15);
  will-change: transform;
}

/* Solid fills (not gradients) so the animated border-radius actually shapes
   the blob - a radial falloff would look round no matter the radius. Sizes
   are in the half-size container, so they appear doubled on screen. */
/* border-radius is a PAINT property: animating it forced this whole layer to
   re-rasterize every frame, and the parent's blur+contrast then had to re-run
   on the fresh paint, which in turn invalidated the backdrop-filter on every
   glass panel above it. That chain is what made the page flicker. The radii
   below are static and the morph comes from non-uniform scale + rotation
   instead - transform only, so the layer is painted once and merely
   re-composited. */
.crystal-blob {
  position: absolute;
  mix-blend-mode: screen;
  border-radius: 58% 42% 33% 67% / 62% 34% 66% 38%;
  will-change: transform;
}
.crystal-blob-2 { border-radius: 39% 61% 66% 34% / 71% 29% 71% 29%; }
.crystal-blob-3 { border-radius: 52% 48% 37% 63% / 59% 43% 57% 41%; }
.crystal-blob-4 { border-radius: 67% 33% 55% 45% / 36% 64% 36% 64%; }
.crystal-blob-5 { border-radius: 44% 56% 29% 71% / 65% 37% 63% 35%; }
.crystal-blob-6 { border-radius: 61% 39% 70% 30% / 41% 68% 32% 59%; }
.crystal-blob-7 { border-radius: 35% 65% 48% 52% / 55% 46% 54% 45%; }

/* Seven blobs: five around the edges plus two parked mid-viewport (6 and 7),
   because content pages stack their panels in the middle and the corner-only
   layout left that area dark.
   Opacities are deliberately moderate: the blobs are screen-blended and then
   pushed through contrast(), so where several overlap they add up fast - at
   0.5-0.9 each the centre blew out to near-white and drifted off-palette into
   cyan. These keep the overlaps sapphire and the glass panels legible. */
.crystal-blob-1 {
  width: 36vmax; height: 36vmax;
  top: -10vmax; left: -8vmax;
  background: rgba(61, 90, 224, 0.55);
  animation: crystal-morph-a 22s ease-in-out infinite alternate;
}
.crystal-blob-2 {
  width: 30vmax; height: 30vmax;
  top: -6vmax; right: -10vmax;
  background: rgba(125, 196, 255, 0.3);
  animation: crystal-morph-b 26s ease-in-out infinite alternate;
  animation-delay: -9s;
}
.crystal-blob-3 {
  width: 40vmax; height: 40vmax;
  bottom: -16vmax; left: 12vmax;
  background: rgba(32, 47, 122, 0.8);
  animation: crystal-morph-c 30s ease-in-out infinite alternate;
  animation-delay: -17s;
}
.crystal-blob-4 {
  width: 26vmax; height: 26vmax;
  top: 18vmax; left: 30vmax;
  background: rgba(62, 207, 142, 0.24);
  animation: crystal-morph-b 21s ease-in-out infinite alternate-reverse;
  animation-delay: -5s;
}
.crystal-blob-5 {
  width: 22vmax; height: 22vmax;
  bottom: 2vmax; right: 4vmax;
  background: rgba(240, 184, 88, 0.12);
  animation: crystal-morph-a 28s ease-in-out infinite alternate-reverse;
  animation-delay: -21s;
}
.crystal-blob-6 {
  width: 28vmax; height: 28vmax;
  top: 26vmax; left: 2vmax;
  background: rgba(125, 196, 255, 0.26);
  animation: crystal-morph-c 24s ease-in-out infinite alternate-reverse;
  animation-delay: -12s;
}
.crystal-blob-7 {
  width: 24vmax; height: 24vmax;
  top: 12vmax; right: 22vmax;
  background: rgba(61, 90, 224, 0.45);
  animation: crystal-morph-a 19s ease-in-out infinite alternate;
  animation-delay: -3s;
}

/* Non-uniform scale (the two scale values differ) plus rotation is what makes
   a blurred blob stretch and squash as it travels - the same liquid read as
   animating border-radius, but entirely on the compositor. */
@keyframes crystal-morph-a {
  0%   { transform: translate(0, 0) rotate(0deg) scale(1, 1); }
  33%  { transform: translate(15vw, 10vh) rotate(60deg) scale(1.42, 1.06); }
  66%  { transform: translate(-9vw, 21vh) rotate(140deg) scale(0.84, 1.24); }
  100% { transform: translate(5vw, 3vh) rotate(220deg) scale(1.18, 0.94); }
}
@keyframes crystal-morph-b {
  0%   { transform: translate(0, 0) rotate(0deg) scale(1, 1); }
  33%  { transform: translate(-18vw, 12vh) rotate(-75deg) scale(1.05, 1.38); }
  66%  { transform: translate(-6vw, 27vh) rotate(-160deg) scale(1.4, 1.1); }
  100% { transform: translate(10vw, 6vh) rotate(-250deg) scale(0.92, 1.16); }
}
@keyframes crystal-morph-c {
  0%   { transform: translate(0, 0) rotate(0deg) scale(1, 1); }
  33%  { transform: translate(19vw, -14vh) rotate(85deg) scale(1.3, 0.96); }
  66%  { transform: translate(-13vw, -6vh) rotate(180deg) scale(0.95, 1.28); }
  100% { transform: translate(6vw, -19vh) rotate(275deg) scale(1.14, 0.88); }
}

/* While a modal is open the user is reading a form, and every glass panel
   behind it would otherwise keep re-sampling a moving backdrop. Freeze the
   background for the duration - it also removes the worst of the flicker. */
body.modal-open .crystal-blob,
body.modal-open .crystal-liquid {
  animation-play-state: paused;
}
body.modal-open #crystalParticles { display: none; }

/* Particle constellation sits above the liquid. Slightly over-scaled so the
   parallax shift never exposes a bare edge. */
#crystalParticles {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.55;
  transform: scale(1.06);
  will-change: transform;
}

/* Static film grain on top: breaks up the banding smooth gradients get on
   dark screens and adds a little texture. A tiled SVG noise, so it costs
   nothing per frame. */
.crystal-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 220px 220px;
  opacity: 0.06;
  mix-blend-mode: overlay;
}

/* ---------- reduced motion: freeze the layer ---------- */

@media (prefers-reduced-motion: reduce) {
  .crystal-blob { animation: none !important; }
  .crystal-liquid { transform: scale(2) !important; }
  #crystalParticles { display: none; }
}
