/*
 * background.css — GENERATED FILE, DO NOT EDIT.
 * 
 * Compiled from core/admin/skin/ by control-plane/scripts/generate-skin.js
 * so the operator console runs the SAME skin engine as every tenant admin
 * rather than a second copy of it. Edit core, then regenerate:
 * 
 *   node control-plane/scripts/generate-skin.js
 * 
 * Drift guard: control-plane/functions/test/skin-parity.test.js
 */
/* =============================================================
   WarHorse admin — the background layer
   -------------------------------------------------------------
   The frame, two morphing brand-coloured blobs, a grain sheet and a veil.
   Everything the glass panels are glass *over*.

   REQUIRES tokens.css. Link it first.

   ------------------------------------------------------------- MARKUP
   The stack is four sibling divs inside one container. Put the container
   inside whatever element is your app frame:

     <div data-frame>                        <!-- or data-frame="inset" -->
       <div data-bg aria-hidden="true">
         <div data-blob="a"></div>
         <div data-blob="b"></div>
         <div data-grain></div>
         <div data-veil></div>
       </div>
       …your rail and your main column, as normal in-flow children…
     </div>

   You do NOT need a wrapper around your content and you do NOT need to give
   it a z-index. [data-frame] isolates, [data-bg] sits at z-index:-1 inside
   that isolation, and normal in-flow content paints over it untouched. The
   blobs are still part of the backdrop, so backdrop-filter on .gl-* panels
   samples them — that is the whole effect.

   [data-frame]          full-bleed. The production default: the admin fills
                         the window and a rounded corner there just clips
                         content against the browser chrome.
   [data-frame="inset"]  the reference presentation — 18px of breathing room,
                         a 22px radius and a hairline border, the way
                         admin-reference.html shows it.

   ------------------------------------------------------------- SAFETY
   Every rule below is nested under [data-skin='glass'] with ONE exception:
   [data-bg] is display:none unconditionally. That is deliberate. It means a
   page can add the background markup permanently — no build flag, no
   conditional rendering — and a classic render is completely unaffected,
   because the whole stack is display:none until the skin turns on. Adding
   markup to a shared file is a one-way door; making it inert by default is
   what lets four people do it in parallel.

   ------------------------------------------------------------- MOTION
   Two 74px-blurred layers under a backdrop-filter is continuous GPU work in
   an app that sits open all day. It stops in three cases:

     prefers-reduced-motion: reduce   — the accessibility contract.
     <html data-anim="paused">        — set by skin/theme.js on
                                        visibilitychange. Attribute, not an
                                        inline style, so CSS keeps owning
                                        what "paused" means.
     ≤720px                           — blob B is dropped entirely and A
                                        drops to blur(50px). Not worth the
                                        battery on a phone.
   ============================================================= */

/* ------------------------------------------------------------------ keyframes
   Copied VERBATIM from the <style> block of
   docs/design/admin-glass/admin-reference.html. Do not "tidy" these.

   The reason they are long: border-radius morphs on the same timeline as the
   transform. A blob that only translates and scales reads as a circle being
   slid around behind frosted glass. A blob whose corner radii are also
   travelling reads as a liquid. That is the entire trick, and it is why the
   radius pairs are all different from each other at every step. */
@keyframes liquidA {
  0%   { transform: translate3d(0,0,0) rotate(0deg) scale(1);    border-radius: 58% 42% 47% 53% / 52% 46% 54% 48%; }
  25%  { transform: translate3d(230px,110px,0) rotate(50deg) scale(1.22); border-radius: 44% 56% 62% 38% / 41% 58% 42% 59%; }
  50%  { transform: translate3d(310px,250px,0) rotate(104deg) scale(1.02); border-radius: 63% 37% 38% 62% / 57% 43% 57% 43%; }
  75%  { transform: translate3d(120px,190px,0) rotate(160deg) scale(1.18); border-radius: 52% 48% 40% 60% / 46% 55% 45% 54%; }
  100% { transform: translate3d(0,0,0) rotate(220deg) scale(1);  border-radius: 58% 42% 47% 53% / 52% 46% 54% 48%; }
}
@keyframes liquidB {
  0%   { transform: translate3d(0,0,0) rotate(0deg) scale(1.06); border-radius: 46% 54% 55% 45% / 48% 52% 48% 52%; }
  25%  { transform: translate3d(-215px,-125px,0) rotate(-46deg) scale(.9); border-radius: 61% 39% 42% 58% / 56% 44% 56% 44%; }
  50%  { transform: translate3d(-300px,-255px,0) rotate(-96deg) scale(1.2); border-radius: 39% 61% 58% 42% / 44% 57% 43% 56%; }
  75%  { transform: translate3d(-130px,-180px,0) rotate(-152deg) scale(.96); border-radius: 55% 45% 47% 53% / 51% 49% 52% 48%; }
  100% { transform: translate3d(0,0,0) rotate(-208deg) scale(1.06); border-radius: 46% 54% 55% 45% / 48% 52% 48% 52%; }
}

/* ------------------------------------------------------------------ inert by default */
[data-bg] { display: none; }

/* ------------------------------------------------------------------ frame */
[data-skin='glass'] [data-frame] {
  position: relative;
  /* isolation is what lets [data-bg] use z-index:-1 without sinking behind
     the frame's own background colour. Without it the blobs vanish. */
  isolation: isolate;
  background: var(--frame-bg);
  /* NO overflow:hidden here. The frame is the ancestor of every panel, and
     clipping at the frame slices each panel's 46px shadow flat against the
     frame padding — the hard-edged shadows the owner spotted between the
     rail and the content. The blobs don't need the frame to clip them:
     [data-bg] below is their own clipping wrapper. The inset variant keeps
     hidden because its rounded corners genuinely clip. */
  color: var(--ink);
  font-family: var(--font-chrome);
}

[data-skin='glass'] [data-frame='inset'] {
  margin: var(--pad-frame);
  border: 1px solid var(--frame-border);
  border-radius: var(--r-frame);
  overflow: hidden;
}

/* ------------------------------------------------------------------ stack */
[data-skin='glass'] [data-bg] {
  display: block;
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  /* The frame's radius must clip the blobs, or they bleed past the corners. */
  border-radius: inherit;
}

[data-skin='glass'] [data-blob] {
  position: absolute;
  filter: blur(var(--blob-blur));
  /* Promote once. These are the two most expensive layers in the admin and
     re-rasterising a 74px blur every frame is what makes a fan spin up. */
  will-change: transform, border-radius;
}

[data-skin='glass'] [data-blob='a'] {
  /* The handoff's 760px was measured in a 1180x740 reference frame, where it
     covers most of the upper left. Pinned at 760 on a real 1600px desktop the
     same blob is a smudge in the corner and the middle of the screen is bare
     --frame-bg. It has to scale with the window it is actually in, with the
     reference value as the floor so a small window still reads. */
  width: max(760px, 78vmax);
  height: max(760px, 78vmax);
  left: min(-300px, -21vmax);
  top: min(-320px, -23vmax);
  background: radial-gradient(circle at 46% 46%, var(--blob-a), transparent 66%);
  border-radius: 58% 42% 47% 53% / 52% 46% 54% 48%;
  opacity: var(--blob-a-opacity);
  animation: liquidA 19s ease-in-out infinite;
}

[data-skin='glass'] [data-blob='b'] {
  width: max(700px, 72vmax);
  height: max(700px, 72vmax);
  right: min(-260px, -18vmax);
  bottom: min(-300px, -21vmax);
  background: radial-gradient(circle at 52% 52%, var(--blob-b), transparent 66%);
  border-radius: 46% 54% 55% 45% / 48% 52% 48% 52%;
  opacity: var(--blob-b-opacity);
  animation: liquidB 16s ease-in-out infinite;
}

/* Grain. Generated inline — no image file ships, and no CDN is involved.
   img-src in the tenant CSPs already allows data:, which is what a CSS
   background-image is checked against. */
[data-skin='glass'] [data-grain] {
  position: absolute;
  inset: 0;
  opacity: var(--grain-opacity);
  /* multiply in light, overlay in dark: multiply over near-black is invisible. */
  mix-blend-mode: var(--grain-blend);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='180' height='180' filter='url(%23n)' opacity='.5'/></svg>");
}

/* The veil is the contrast safety net. Every legibility number in this design
   assumes it is there — text on .gl-work is measured against blob + veil, not
   blob alone. Do not remove it to "let the colour through". */
[data-skin='glass'] [data-veil] {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

/* ------------------------------------------------------------------ motion off */

/* Tab hidden. theme.js flips the attribute; CSS decides what it means. Paused
   rather than removed, so returning to the tab resumes mid-morph instead of
   snapping the blobs back to 0%. */
[data-anim='paused'] [data-blob] { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  [data-skin='glass'] [data-blob] {
    animation: none;
    will-change: auto;
  }
}

/* ------------------------------------------------------------------ small screens */
@media (max-width: 720px) {
  [data-skin='glass'] [data-blob='b'] { display: none; }
  [data-skin='glass'] [data-blob='a'] { filter: blur(50px); }
  [data-skin='glass'] [data-frame='inset'] {
    margin: 0;
    border: 0;
    border-radius: 0;
  }
}
