/* BioStudio AI — modern shell effects.
 *
 * Every rule here is gated by a class on <html> that js/platform/visual-effects.js
 * sets from Settings → Visual effects and the performance budget:
 *   fx-glass      translucent, blurred window title bars
 *   fx-wallpaper  slowly drifting accent glow behind the desktop
 *   fx-glow       accent glow on the focused window, soft dim on the others
 *   fx-motion     spring easing for window open / close / minimize / restore
 * With none of those classes the platform renders exactly as before. The mobile
 * shell is excluded throughout.
 */

:root {
  --fx-spring: cubic-bezier(0.22, 1.2, 0.36, 1);
  --fx-accent: var(--accent, #2f6bff);
}

@supports (animation-timing-function: linear(0, 1)) {
  :root {
    /* Lightly damped spring: ~4% overshoot, settles by 70%. */
    --fx-spring: linear(0, 0.06, 0.25 9%, 0.63 19%, 0.83 27%, 0.95 35%, 1.02 43%, 1.04 50%, 1.03 58%, 1.01 70%, 1);
  }
}

/* --- 1. Glass title bars -------------------------------------------------
   macOS, BioStudioAI 1, Air and the tone shells already blur the whole window,
   so they keep their own glass. The opaque shells (Windows, BioStudioAI 2,
   Mono, Carbon) get a translucent title bar: the window paints its background
   only below the title bar strip, and .window-body stays fully opaque, so app
   content never sits on the wallpaper. A dragged window drops back to the
   solid look so nothing re-blurs every frame.
   css/app-color-themes.css paints both backgrounds with !important on every
   palette window, so these need it too (they win on specificity). */
html.fx-glass #os:not(.shell-mobile):is(.theme-windows, .theme-bs2, .theme-bs3, .theme-bs4) .window:not(.maximized):not(.dragging) {
  background:
    linear-gradient(var(--win-bg), var(--win-bg)) no-repeat 0 var(--window-titlebar-h) / 100% 100% !important;
}

html.fx-glass #os:not(.shell-mobile):is(.theme-windows, .theme-bs2, .theme-bs3, .theme-bs4) .window:not(.maximized):not(.dragging) .window-titlebar {
  background: color-mix(in srgb, var(--titlebar-bg) 70%, transparent) !important;
  backdrop-filter: blur(min(22px, var(--bs-max-blur, 22px))) saturate(1.4);
  -webkit-backdrop-filter: blur(min(22px, var(--bs-max-blur, 22px))) saturate(1.4);
}

/* window.css fades an unfocused title bar to 72% — over glass that would show
   the wallpaper straight through, so fade the contents instead. */
html.fx-glass #os:not(.shell-mobile):is(.theme-windows, .theme-bs2, .theme-bs3, .theme-bs4) .window:not(.focused) .window-titlebar {
  opacity: 1;
}
html.fx-glass #os:not(.shell-mobile):is(.theme-windows, .theme-bs2, .theme-bs3, .theme-bs4) .window:not(.focused) :is(.window-title-text, .window-controls) {
  opacity: 0.68;
}

/* --- 2. Living wallpaper -------------------------------------------------
   Three soft radial glows drifting on compositor-only transforms. Shown only
   for the built-in wallpaper; paused while the tab is hidden or a maximized
   window covers the desktop.

   The drift is stepped at 10 updates a second (see "Ambient motion" below):
   each keyframe interval lasts a tenth of the run, and steps(N) gives it N
   steps where N is that interval in tenths of a second. The keyframes sample
   the old ease-in-out curve (cubic-bezier(.42,0,.58,1)) at every 10%, so the
   path stays within 1px of the smooth version. */
.fx-aurora { display: none; }

html.fx-wallpaper #os:not(.shell-mobile) > .fx-aurora {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.fx-aurora-blob {
  position: absolute;
  width: 62vmax;
  height: 62vmax;
  border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--fx-accent) 34%, transparent), transparent 72%);
  opacity: 0.6;
  will-change: transform;
  animation: fx-drift-a 42s steps(42) infinite alternate;
}
.fx-aurora-blob:nth-child(1) { left: -14vmax; top: -20vmax; }
.fx-aurora-blob:nth-child(2) {
  right: -18vmax;
  bottom: -24vmax;
  background: radial-gradient(closest-side, color-mix(in srgb, color-mix(in srgb, var(--fx-accent) 45%, #a855f7) 30%, transparent), transparent 72%);
  animation-name: fx-drift-b;
  animation-duration: 54s;
  animation-timing-function: steps(54);
}
.fx-aurora-blob:nth-child(3) {
  left: 34vw;
  top: 38vh;
  width: 42vmax;
  height: 42vmax;
  background: radial-gradient(closest-side, color-mix(in srgb, color-mix(in srgb, var(--fx-accent) 50%, #22d3ee) 24%, transparent), transparent 72%);
  animation-name: fx-drift-c;
  animation-duration: 36s;
  animation-timing-function: steps(36);
}
.fx-aurora.is-paused .fx-aurora-blob { animation-play-state: paused; }

@keyframes fx-drift-a {
  0% { transform: translate3d(0vmax, 0vmax, 0) scale(1); }
  10% { transform: translate3d(0.178vmax, 0.138vmax, 0) scale(1.002); }
  20% { transform: translate3d(0.735vmax, 0.572vmax, 0) scale(1.01); }
  30% { transform: translate3d(1.687vmax, 1.312vmax, 0) scale(1.022); }
  40% { transform: translate3d(2.987vmax, 2.323vmax, 0) scale(1.04); }
  50% { transform: translate3d(4.5vmax, 3.5vmax, 0) scale(1.06); }
  60% { transform: translate3d(6.013vmax, 4.677vmax, 0) scale(1.08); }
  70% { transform: translate3d(7.313vmax, 5.688vmax, 0) scale(1.098); }
  80% { transform: translate3d(8.265vmax, 6.428vmax, 0) scale(1.11); }
  90% { transform: translate3d(8.822vmax, 6.862vmax, 0) scale(1.118); }
  100% { transform: translate3d(9vmax, 7vmax, 0) scale(1.12); }
}
@keyframes fx-drift-b {
  0% { transform: translate3d(0vmax, 0vmax, 0) scale(1); }
  10% { transform: translate3d(-0.158vmax, -0.118vmax, 0) scale(1.002); }
  20% { transform: translate3d(-0.653vmax, -0.49vmax, 0) scale(1.007); }
  30% { transform: translate3d(-1.499vmax, -1.124vmax, 0) scale(1.015); }
  40% { transform: translate3d(-2.655vmax, -1.991vmax, 0) scale(1.027); }
  50% { transform: translate3d(-4vmax, -3vmax, 0) scale(1.04); }
  60% { transform: translate3d(-5.345vmax, -4.009vmax, 0) scale(1.053); }
  70% { transform: translate3d(-6.501vmax, -4.876vmax, 0) scale(1.065); }
  80% { transform: translate3d(-7.347vmax, -5.51vmax, 0) scale(1.073); }
  90% { transform: translate3d(-7.842vmax, -5.882vmax, 0) scale(1.078); }
  100% { transform: translate3d(-8vmax, -6vmax, 0) scale(1.08); }
}
@keyframes fx-drift-c {
  0% { transform: translate3d(0vmax, 0vmax, 0) scale(1); }
  10% { transform: translate3d(-0.118vmax, 0.158vmax, 0) scale(0.998); }
  20% { transform: translate3d(-0.49vmax, 0.653vmax, 0) scale(0.992); }
  30% { transform: translate3d(-1.124vmax, 1.499vmax, 0) scale(0.981); }
  40% { transform: translate3d(-1.991vmax, 2.655vmax, 0) scale(0.967); }
  50% { transform: translate3d(-3vmax, 4vmax, 0) scale(0.95); }
  60% { transform: translate3d(-4.009vmax, 5.345vmax, 0) scale(0.933); }
  70% { transform: translate3d(-4.876vmax, 6.501vmax, 0) scale(0.919); }
  80% { transform: translate3d(-5.51vmax, 7.347vmax, 0) scale(0.908); }
  90% { transform: translate3d(-5.882vmax, 7.842vmax, 0) scale(0.902); }
  100% { transform: translate3d(-6vmax, 8vmax, 0) scale(0.9); }
}

/* --- Ambient motion ------------------------------------------------------
   Endless decorative spins redraw the whole composited desktop on every
   display refresh (60-120 times a second), and every glass surface above them
   re-blurs each time. Measured on an Apple M5: the idle desktop cost 47% of a
   CPU core with them running and 2% with them still. They move so slowly that
   10 updates a second looks the same, and costs about a fifth.

   These only swap linear for steps() at 10 per second, on the same selectors
   (same specificity, later in the cascade) as the rules that start the spins.
   Durations, keyframes and play-state are untouched, so every existing pause
   (hidden tab, maximized window, reduced motion) still applies. */

/* base.css: 360 s per turn (BIG_IDLE_MS_PER_TURN) -> 3600 steps. */
.wallpaper-logo-big { animation-timing-function: steps(3600); }

/* brand.css: 16 s per turn -> 160 steps. */
.brand.spin .brand-mark,
.bs-logo.spin .bs-logo-mark,
.taskbar-start .icon svg,
.tb-icon-btn.taskbar-start .icon svg {
  animation-timing-function: steps(160);
}

/* login-os.css: 64 s per turn -> 640 steps. */
.login-corner-logo .brand-mark,
.login-corner-logo .brand.spin .brand-mark {
  animation-timing-function: steps(640);
}

/* --- 3. Focused window glow ---------------------------------------------
   The glow follows the window's own accent (per-window palettes set --accent).
   The dim is a pointer-events:none overlay — never `filter`, which would make
   the window a containing block and move position:fixed popups inside apps.
   ::after is taken by .drop-target, so the overlay uses ::before. */
html.fx-glow #os:not(.shell-mobile) .window.focused:not(.maximized):not(.dragging) {
  box-shadow:
    var(--win-shadow-focus, var(--win-shadow)),
    0 0 0 1px color-mix(in srgb, var(--fx-accent) 40%, transparent),
    0 0 36px -6px color-mix(in srgb, var(--fx-accent) 42%, transparent);
  transition: box-shadow 0.24s ease;
}

html.fx-glow #os:not(.shell-mobile) .window::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 7;
  border-radius: inherit;
  background: rgb(6 8 14 / 0.07);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
html.fx-glow #os:not(.shell-mobile) .window:not(.focused)::before { opacity: 1; }

/* --- 8. Spring window motion ---------------------------------------------
   Durations stay inside the window manager's timers: the open pop is retired
   by a 400ms fallback and close destroys the window at 120ms. .dragging and
   .closing are excluded so the drag-transform guarantees in window.css hold. */
html.fx-motion {
  --fx-minimize-ease: cubic-bezier(0.55, 0, 0.85, 0.35);
  --fx-restore-ease: var(--fx-spring);
}

html.fx-motion #os:not(.shell-mobile) .window:not(.pop-done):not(.dragging):not(.closing) {
  animation: fx-window-pop 0.32s var(--fx-spring) both;
}

html.fx-motion #os:not(.shell-mobile) .window.closing {
  animation: fx-window-close 0.12s cubic-bezier(0.4, 0, 1, 1) forwards;
}

@keyframes fx-window-pop {
  from { opacity: 0; transform: translateY(10px) scale(0.955); }
  to   { opacity: 1; transform: none; }
}

@keyframes fx-window-close {
  to { opacity: 0; transform: translateY(6px) scale(0.94); }
}
