/* ==========================================================================
   Liquid Glass Design System — Background System
   --------------------------------------------------------------------------
   Glass has nothing to say without something behind it. The background is
   therefore a real subsystem, not a colour (spec §17-§20):

     Base        gradient mesh (warm, slowly drifting)
     Decoration  SVG ornament + film grain
     Light       radial light sources

   Home pages get the full-strength version; article pages get a lower
   frequency variant so long-form reading stays calm.

   This lives on <body>'s own pseudo-elements rather than on Butterfly's
   #web_bg. #web_bg is only emitted when the theme's `background:` option is
   set, so keying the whole background system off it meant that clearing that
   option deleted the background — and with it every refraction, since glass
   over a flat fill bends nothing you can see.
   ========================================================================== */

/* The base colour lives on <html> (the canvas) and <body> stays transparent,
   so body's negative-z pseudo-elements have something to sit on and nothing
   opaque buries them. */
html {
  background-color: var(--g-bg);
}

body {
  background-color: transparent !important;
  background-image: none !important;
  position: relative;   /* keeps the fixed pseudo-elements' z-index predictable */
}

/* If Butterfly's `background:` option is ever set again, #web_bg would paint
   its own layer straight over this one. Keep it out of the way. */
#web_bg {
  display: none !important;
}

/* ---- Base + light -------------------------------------------------------
   Five radial lights on a warm base, plus a vignette. Fixed to the viewport
   so every glass surface has the same optical input wherever it scrolls to. */

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  background-color: var(--g-bg);
  background-image:
    /* Vignette first so the lights sit on top of it. */
    radial-gradient(120% 100% at 50% 50%, transparent 44%, rgba(150,  86,  44, 0.22) 100%),
    radial-gradient(58% 46% at 10% 10%,  rgba(246, 190,  96, 0.86), transparent 62%),
    radial-gradient(52% 42% at 88% 16%,  rgba(236, 146,  70, 0.78), transparent 64%),
    radial-gradient(50% 46% at 78% 84%,  rgba(214,  98,  74, 0.66), transparent 64%),
    radial-gradient(46% 40% at 18% 86%,  rgba(228, 132, 118, 0.64), transparent 62%),
    radial-gradient(36% 32% at 50% 50%,  rgba(255, 236, 206, 0.70), transparent 66%);
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

/* ---- Decoration: SVG ornament -------------------------------------------
   A tiled line ornament. Low contrast and low frequency: enough structure
   for the displacement map to bend visibly, not enough to fight text.
   It scrolls slowly, the way the reference wallpaper does. */

body::after {
  content: "";
  position: fixed;
  /* Sized for the drift rather than for the viewport. The layer travels one
     tile left and two tiles up over a cycle, so it has to reach that far past
     the right and bottom edges or it uncovers them on the way across. */
  top: -10%;
  left: -10%;
  right: -400px;
  bottom: -800px;
  pointer-events: none;
  z-index: -1;
  opacity: 0.78;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400' viewBox='0 0 400 400'%3E%3Cdefs%3E%3Cg id='fl'%3E%3Ccircle cx='4.6' cy='0' r='3.4'/%3E%3Ccircle cx='1.42' cy='4.37' r='3.4'/%3E%3Ccircle cx='-3.72' cy='2.7' r='3.4'/%3E%3Ccircle cx='-3.72' cy='-2.7' r='3.4'/%3E%3Ccircle cx='1.42' cy='-4.37' r='3.4'/%3E%3Ccircle cx='0' cy='0' r='1.7'/%3E%3C/g%3E%3Cpath id='st' d='M0,-10 L2.25,-3.09 L9.51,-3.09 L3.63,1.18 L5.88,8.09 L0,3.82 L-5.88,8.09 L-3.63,1.18 L-9.51,-3.09 L-2.25,-3.09 Z'/%3E%3Cpath id='sp' d='M0,-9 Q1.2,-1.6 9,0 Q1.2,1.6 0,9 Q-1.2,1.6 -9,0 Q-1.2,-1.6 0,-9 Z'/%3E%3Cpath id='ht' d='M0,7 C-6,2.5 -8,-1 -8,-3.5 C-8,-6.5 -5.5,-8 -3.2,-8 C-1.4,-8 -0.4,-6.9 0,-6 C0.4,-6.9 1.4,-8 3.2,-8 C5.5,-8 8,-6.5 8,-3.5 C8,-1 6,2.5 0,7 Z'/%3E%3Cpath id='mn' d='M3,-8 A8,8 0 1 0 3,8 A6.4,6.4 0 1 1 3,-8 Z'/%3E%3Cg id='lf'%3E%3Cpath d='M0,-9 C5,-5 6,2 0,9 C-6,2 -5,-5 0,-9 Z'/%3E%3Cpath d='M0,-6 L0,7'/%3E%3C/g%3E%3Cpath id='bd' d='M-9,0 Q-4.5,-6 0,-1 Q4.5,-6 9,0'/%3E%3Cg id='bf'%3E%3Cpath d='M0,0 C-3,-7 -10,-8 -9.5,-3 C-9.2,0.3 -4,0.8 0,0 Z'/%3E%3Cpath d='M0,0 C-3,7 -9,7 -8.6,2.6 C-8.3,0.5 -4,-0.3 0,0 Z'/%3E%3Cpath d='M0,0 C3,-7 10,-8 9.5,-3 C9.2,0.3 4,0.8 0,0 Z'/%3E%3Cpath d='M0,0 C3,7 9,7 8.6,2.6 C8.3,0.5 4,-0.3 0,0 Z'/%3E%3Cpath d='M0,-3.5 L0,3.5'/%3E%3C/g%3E%3Cg id='rg'%3E%3Ccircle r='11'/%3E%3Ccircle r='7'/%3E%3Ccircle r='3'/%3E%3C/g%3E%3CradialGradient id='gl'%3E%3Cstop offset='0%25' stop-color='%23e8973c' stop-opacity='0.5'/%3E%3Cstop offset='100%25' stop-color='%23e8973c' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='g2'%3E%3Cstop offset='0%25' stop-color='%23f2c46b' stop-opacity='0.38'/%3E%3Cstop offset='100%25' stop-color='%23f2c46b' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='54' cy='46' r='52' fill='url(%23gl)' stroke='none'/%3E%3Ccircle cx='256' cy='58' r='58' fill='url(%23gl)' stroke='none'/%3E%3Ccircle cx='128' cy='300' r='54' fill='url(%23g2)' stroke='none'/%3E%3Ccircle cx='322' cy='200' r='46' fill='url(%23g2)' stroke='none'/%3E%3Ccircle cx='340' cy='356' r='44' fill='url(%23gl)' stroke='none'/%3E%3Cg fill='none' stroke='%23c9713f' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M0,172 Q25,150 50,172 T100,172 T150,172 T200,172 T250,172 T300,172 T350,172 T400,172' stroke-width='0.9'/%3E%3Cpath d='M0,188 Q25,166 50,188 T100,188 T150,188 T200,188 T250,188 T300,188 T350,188 T400,188' stroke-width='0.45'/%3E%3Cpath d='M0,24 Q25,2 50,24 T100,24 T150,24 T200,24 T250,24 T300,24 T350,24 T400,24' stroke-width='0.6'/%3E%3Cuse href='%23fl' transform='translate(54,46) rotate(12) scale(1.25)' stroke-width='1'/%3E%3Cuse href='%23st' transform='translate(150,34) rotate(-8)' stroke-width='1'/%3E%3Cuse href='%23bf' transform='translate(256,58) rotate(6) scale(1.15)' stroke-width='0.9'/%3E%3Cuse href='%23sp' transform='translate(344,40) scale(0.9)' stroke-width='1'/%3E%3Cuse href='%23rg' transform='translate(32,132)' stroke-width='0.8'/%3E%3Cuse href='%23lf' transform='translate(110,120) rotate(28) scale(1.1)' stroke-width='0.9'/%3E%3Cuse href='%23ht' transform='translate(200,128) rotate(-14) scale(0.95)' stroke-width='1'/%3E%3Cuse href='%23bd' transform='translate(300,118)' stroke-width='1.1'/%3E%3Cuse href='%23mn' transform='translate(370,130) rotate(18) scale(0.85)' stroke-width='1'/%3E%3Cuse href='%23st' transform='translate(60,210) rotate(24) scale(0.8)' stroke-width='0.9'/%3E%3Cuse href='%23fl' transform='translate(146,196) rotate(-20) scale(0.95)' stroke-width='1'/%3E%3Cuse href='%23sp' transform='translate(232,208) rotate(15) scale(1.2)' stroke-width='0.9'/%3E%3Cuse href='%23rg' transform='translate(322,200) scale(0.75)' stroke-width='0.8'/%3E%3Cuse href='%23bd' transform='translate(36,288) rotate(-10) scale(0.9)' stroke-width='1.1'/%3E%3Cuse href='%23bf' transform='translate(128,300) rotate(-12) scale(0.9)' stroke-width='0.9'/%3E%3Cuse href='%23mn' transform='translate(214,286) rotate(-25)' stroke-width='1'/%3E%3Cuse href='%23lf' transform='translate(296,292) rotate(48) scale(0.85)' stroke-width='0.9'/%3E%3Cuse href='%23st' transform='translate(368,300) rotate(8) scale(1.05)' stroke-width='1'/%3E%3Cuse href='%23ht' transform='translate(74,362) rotate(10) scale(0.8)' stroke-width='1'/%3E%3Cuse href='%23fl' transform='translate(170,358) rotate(32) scale(1.1)' stroke-width='1'/%3E%3Cuse href='%23sp' transform='translate(258,366) rotate(-10) scale(0.8)' stroke-width='1'/%3E%3Cuse href='%23bd' transform='translate(340,356) rotate(12) scale(0.95)' stroke-width='1.1'/%3E%3Cuse href='%23rg' transform='translate(196,62) scale(0.6)' stroke-width='0.7'/%3E%3Cuse href='%23lf' transform='translate(18,60) rotate(-35) scale(0.7)' stroke-width='0.9'/%3E%3Cuse href='%23st' transform='translate(0,168) rotate(20) scale(0.9)' stroke-width='1'/%3E%3Cuse href='%23st' transform='translate(400,168) rotate(20) scale(0.9)' stroke-width='1'/%3E%3Cuse href='%23fl' transform='translate(240,0) rotate(10) scale(0.85)' stroke-width='1'/%3E%3Cuse href='%23fl' transform='translate(240,400) rotate(10) scale(0.85)' stroke-width='1'/%3E%3Cuse href='%23bd' transform='translate(0,336) scale(0.85)' stroke-width='1.1'/%3E%3Cuse href='%23bd' transform='translate(400,336) scale(0.85)' stroke-width='1.1'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 400px 400px;
  animation: g-drift 90s linear infinite;
}

/* Transform, not background-position.
   -------------------------------------------------------------------------
   Animating background-position repaints this layer every frame, and it is a
   full-viewport fixed layer with every glass surface in the blog sitting on
   top of it -- so each of those repaints also forces ~30 backdrop-filters,
   SVG displacement filters included, to re-sample and re-run. It was the most
   expensive thing on the page by a wide margin, and it ran forever, on every
   page, whether or not anything else was happening.

   A translate3d of the same distance looks identical and is compositor-only:
   the tile grid is rasterised once and then moved. The element is not given
   will-change -- a running transform animation already earns its own layer,
   and pinning one permanently would keep the cost after
   [data-glass-bg="static"] switches the animation off at the minimal tier.

   The distance has to be a whole number of tiles or the loop visibly jumps
   when it restarts. 400/800 against a 400px tile is exactly 1 and 2. */
@keyframes g-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-400px, -800px, 0); }
}

/* ---- Decoration: lattice ------------------------------------------------
   High frequency, low contrast, and it exists for one reason: refraction is
   only visible when there is structure behind it to bend. The radial lights
   are almost flat at the scale of a bezel, and the ornament's rings are large
   and sparse -- a 100px band of glass can sit entirely inside one and show
   nothing at all. A fine lattice guarantees several features inside every
   bezel wherever a surface happens to land, which is what makes the edge read
   as glass rather than as a tinted border.

   Between the base light (-3) and the ornament (-1) so the ornament still
   reads on top of it, and calmed right down on article pages where it would
   otherwise sit under body text. */

html::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: -2;
  opacity: 0.30;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cg stroke='%23c9713f' fill='none'%3E%3Cpath d='M0 12H24M12 0V24' stroke-width='0.4' opacity='0.45'/%3E%3Cpath d='M0 0H24M0 0V24' stroke-width='0.7' opacity='0.28'/%3E%3C/g%3E%3Ccircle cx='12' cy='12' r='1.15' fill='%23b3562e' opacity='0.55'/%3E%3C/svg%3E");
  background-size: 24px 24px;
}

@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"])::before { opacity: 0.16; }
}

html[data-theme="dark"]::before { opacity: 0.16; }

html:has(#body-wrap.post)::before { opacity: 0.12; }

/* ---- Decoration: grain --------------------------------------------------
   Breaks up the gradient banding and gives the glass something fine-grained
   to refract at the edges. Static — animating grain is a battery fire. */

html::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  opacity: 0.16;
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* ---- Dark theme ---------------------------------------------------------
   Same construction, deeper and less saturated: warm embers on near-black
   rather than a lit room. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body::before {
    background-image:
      radial-gradient(60% 48% at  8%  8%, rgba(226, 126,  48, 0.62), transparent 62%),
      radial-gradient(54% 44% at 90% 14%, rgba(198,  84,  56, 0.58), transparent 64%),
      radial-gradient(52% 48% at 80% 88%, rgba(150,  70,  96, 0.48), transparent 64%),
      radial-gradient(48% 42% at 16% 88%, rgba(206, 112,  56, 0.50), transparent 62%),
      radial-gradient(38% 34% at 50% 48%, rgba(244, 186, 118, 0.34), transparent 66%);
  }
  :root:not([data-theme="light"]) body::after { opacity: 0.44; }
  html:not([data-theme="light"])::after  { opacity: 0.22; mix-blend-mode: overlay; }
}

[data-theme="dark"] body::before {
  background-image:
    radial-gradient(60% 48% at  8%  8%, rgba(226, 126,  48, 0.62), transparent 62%),
    radial-gradient(54% 44% at 90% 14%, rgba(198,  84,  56, 0.58), transparent 64%),
    radial-gradient(52% 48% at 80% 88%, rgba(150,  70,  96, 0.48), transparent 64%),
    radial-gradient(48% 42% at 16% 88%, rgba(206, 112,  56, 0.50), transparent 62%),
    radial-gradient(38% 34% at 50% 48%, rgba(244, 186, 118, 0.34), transparent 66%);
}
[data-theme="dark"] body::after { opacity: 0.44; }
html[data-theme="dark"]::after  { opacity: 0.22; mix-blend-mode: overlay; }

/* ---- Article pages: calm it down ----------------------------------------
   The ornament and grain layers belong to <body>/<html>, so an article page
   is reached with :has() on the wrapper Butterfly puts the layout in.
   Browsers without :has() simply keep the home-page background — a visual
   nicety to lose, not a break. */

body:has(#body-wrap.post)::after {
  opacity: 0.34;
  background-size: 600px 600px;   /* lower frequency near body text */
  /* The tile is larger here, so the drift needs its own keyframes: reusing
     g-drift would travel 400/800 against a 600px tile, which is not a whole
     number of tiles and lands the loop on a visible jump. (The old
     background-position version had exactly that bug -- one jump every 160s.) */
  right: -600px;
  bottom: -1200px;
  animation-name: g-drift-post;
  animation-duration: 160s;
}

@keyframes g-drift-post {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-600px, -1200px, 0); }
}

html:has(#body-wrap.post)::after {
  opacity: 0.10;
}
