From 8973123adfe84fdd57db1b9677fe538c57c31b07 Mon Sep 17 00:00:00 2001
From: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5
<8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Date: Wed, 8 Jul 2026 12:49:52 +0100
Subject: [PATCH 1/8] feat(desktop): animated bee on loading gates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Replace the static Buzz mark and spinner on the setup splash and
workspace-switch gate with the looping FuzzyLogo (fuzzy texture,
theme-adaptive tint). A static BuzzMark renders underneath so the box
paints instantly on hard reload instead of flashing blank.
Loops continuously via loopRestSeconds=0 — the prior rest window left the
mark visually static for ~63% of each cycle, which read as 'not animated'.
Signed-off-by: Fizz <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
---
desktop/src/app/App.tsx | 47 +++++++++++++++++++++++++++++++++++------
1 file changed, 40 insertions(+), 7 deletions(-)
diff --git a/desktop/src/app/App.tsx b/desktop/src/app/App.tsx
index c3624af24..4c11fe15d 100644
--- a/desktop/src/app/App.tsx
+++ b/desktop/src/app/App.tsx
@@ -25,27 +25,56 @@ import { createBuzzQueryClient } from "@/shared/api/queryClient";
import { isSharedIdentity as isSharedIdentityCmd } from "@/shared/api/tauri";
import { listenForDeepLinks } from "@/shared/deep-link";
import { useSystemColorScheme } from "@/shared/theme/useSystemColorScheme";
+import { cn } from "@/shared/lib/cn";
import { Button } from "@/shared/ui/button";
import { BuzzMark } from "@/shared/ui/buzz-logo/BuzzMark";
-import { Spinner } from "@/shared/ui/spinner";
+import { FuzzyLogo } from "@/shared/ui/buzz-logo/FuzzyLogo";
import { StartupWindowDragRegion } from "@/shared/ui/StartupWindowDragRegion";
import { StepProgress } from "@/shared/ui/step-progress";
const LOADING_TEXT = "Setting up your workspace...";
-// Cold boot gate: a plain static Buzz mark (#D7D72E) on solid black. No
-// animation machinery, no gradient — the mark must paint complete on the very
-// first frame, even in webviews that render before scripting or SMIL start.
+// Animated Buzz mark for the loading gates. The static BuzzMark renders in
+// normal flow and sizes the box — it's plain SVG (no JS/SMIL), so it paints on
+// the very first frame even before scripting starts, avoiding a blank flash on
+// hard reload. The animated FuzzyLogo is layered on top and takes over once it
+// begins playing.
+function BeeLoader({
+ ariaLabel,
+ className,
+ tintClassName = "text-foreground",
+}: {
+ ariaLabel: string;
+ className?: string;
+ tintClassName?: string;
+}) {
+ return (
+
+
+
+
+ );
+}
+
+// Cold boot gate: the animated Buzz mark (fuzzy texture, theme-adaptive tint)
+// on the app background, with a static mark underneath so it paints instantly
+// on reload rather than flashing blank while the animation boots.
function AppLoadingGate() {
return (
{LOADING_TEXT}
-
+
);
}
@@ -69,7 +98,11 @@ function WorkspaceSwitchGate() {
Switching workspace…
{showSpinner ? (
-
+
) : null}
);
From 5d9c10725100ef3bfaf9c074413753513ee7136b Mon Sep 17 00:00:00 2001
From: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5
<8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Date: Wed, 8 Jul 2026 13:17:14 +0100
Subject: [PATCH 2/8] feat(desktop): restore theme-adaptive grainient setup
screen with bee hero
Restore the pre-#1570 theme-adaptive grainient loading gate, with the
animated Buzz bee as the visual hero replacing the old "Setting up your
workspace" text (the caption stays as sr-only for screen readers).
- Restore ThemeGrainientBackground and the grainient/shell CSS (orbiting
radial grainient driven by --chart-* / --primary theme vars, plus the
dark-mode alpha overrides and reduced-motion fallback) that #1570 stripped.
- Rewire AppLoadingGate to the .buzz-setup-loading-shell + grainient +
BeeLoader (relative z-10) structure. Drop the text-only rules
(buzz-setup-loading-text, buzz-setup-character-rise) since the bee replaces
the text.
- index.html: paint the pre-React window in the cached theme --background
(buzz-theme-cache) instead of always black, killing the black flash on
light themes; falls back to black on the first-ever launch.
Scoped to the cold-boot AppLoadingGate only; the quiet WorkspaceSwitchGate
keeps the bee on a plain themed background.
Signed-off-by: Fizz <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
---
desktop/index.html | 29 ++-
desktop/src/app/App.tsx | 16 +-
desktop/src/app/ThemeGrainientBackground.tsx | 12 ++
.../src/shared/styles/globals/animations.css | 191 ++++++++++++++++++
4 files changed, 237 insertions(+), 11 deletions(-)
create mode 100644 desktop/src/app/ThemeGrainientBackground.tsx
diff --git a/desktop/index.html b/desktop/index.html
index 31d90dc4d..93320788e 100644
--- a/desktop/index.html
+++ b/desktop/index.html
@@ -6,18 +6,35 @@
+
diff --git a/desktop/src/app/App.tsx b/desktop/src/app/App.tsx
index 4c11fe15d..2bb795ec8 100644
--- a/desktop/src/app/App.tsx
+++ b/desktop/src/app/App.tsx
@@ -12,6 +12,7 @@ import {
} from "react";
import { router } from "@/app/router";
+import { ThemeGrainientBackground } from "@/app/ThemeGrainientBackground";
import { useReloadShortcut } from "@/app/useReloadShortcut";
import { useAppOnboardingState } from "@/features/onboarding/hooks";
import { OnboardingSlideTransition } from "@/features/onboarding/ui/OnboardingSlideTransition";
@@ -62,19 +63,24 @@ function BeeLoader({
);
}
-// Cold boot gate: the animated Buzz mark (fuzzy texture, theme-adaptive tint)
-// on the app background, with a static mark underneath so it paints instantly
-// on reload rather than flashing blank while the animation boots.
+// Cold boot gate: the theme-adaptive grainient background with the animated
+// Buzz mark as the hero (replacing the old "Setting up your workspace" text —
+// the caption stays as sr-only for screen readers). A static mark renders
+// underneath the animation so it paints instantly on hard reload.
function AppLoadingGate() {
return (
+ {LOADING_TEXT}
-
+
);
}
diff --git a/desktop/src/app/ThemeGrainientBackground.tsx b/desktop/src/app/ThemeGrainientBackground.tsx
new file mode 100644
index 000000000..f9a81ef4b
--- /dev/null
+++ b/desktop/src/app/ThemeGrainientBackground.tsx
@@ -0,0 +1,12 @@
+export function ThemeGrainientBackground() {
+ return (
+
+
+
+
+ );
+}
diff --git a/desktop/src/shared/styles/globals/animations.css b/desktop/src/shared/styles/globals/animations.css
index a057a55c5..3922f224b 100644
--- a/desktop/src/shared/styles/globals/animations.css
+++ b/desktop/src/shared/styles/globals/animations.css
@@ -567,3 +567,194 @@
}
/* ── Tiptap rich-text composer ──────────────────────────────────────── */
+
+/* ── Setup / cold-boot loading screen ───────────────────────────────────
+ Theme-adaptive grainient background for the "setting up your workspace"
+ gate. Restored from pre-#1570; the animated Buzz mark is the hero (see
+ AppLoadingGate in app/App.tsx). */
+
+@property --buzz-grainient-x-0 {
+ syntax: "";
+ inherits: false;
+ initial-value: 85%;
+}
+
+@property --buzz-grainient-y-0 {
+ syntax: "";
+ inherits: false;
+ initial-value: 80%;
+}
+
+@property --buzz-grainient-x-1 {
+ syntax: "";
+ inherits: false;
+ initial-value: 60%;
+}
+
+@property --buzz-grainient-y-1 {
+ syntax: "";
+ inherits: false;
+ initial-value: 24%;
+}
+
+@property --buzz-grainient-x-2 {
+ syntax: "";
+ inherits: false;
+ initial-value: 13%;
+}
+
+@property --buzz-grainient-y-2 {
+ syntax: "";
+ inherits: false;
+ initial-value: 82%;
+}
+
+@property --buzz-grainient-x-3 {
+ syntax: "";
+ inherits: false;
+ initial-value: 24%;
+}
+
+@property --buzz-grainient-y-3 {
+ syntax: "";
+ inherits: false;
+ initial-value: 7%;
+}
+
+.buzz-setup-loading-shell {
+ --buzz-grainient-alpha-strong: 0.46;
+ --buzz-grainient-alpha-medium: 0.34;
+ --buzz-grainient-alpha-soft: 0.24;
+ --buzz-grainient-color-0: hsl(
+ var(--chart-5) /
+ var(--buzz-grainient-alpha-strong)
+ );
+ --buzz-grainient-color-1: hsl(
+ var(--chart-3) /
+ var(--buzz-grainient-alpha-strong)
+ );
+ --buzz-grainient-color-2: hsl(
+ var(--primary) /
+ var(--buzz-grainient-alpha-medium)
+ );
+ --buzz-grainient-color-3: hsl(
+ var(--chart-2) /
+ var(--buzz-grainient-alpha-strong)
+ );
+ background-color: hsl(var(--background));
+ color: hsl(var(--foreground));
+ isolation: isolate;
+ position: relative;
+}
+
+.dark .buzz-setup-loading-shell {
+ --buzz-grainient-alpha-strong: 0.68;
+ --buzz-grainient-alpha-medium: 0.5;
+ --buzz-grainient-alpha-soft: 0.34;
+}
+
+.buzz-setup-grainient {
+ inset: 0;
+ overflow: hidden;
+ pointer-events: none;
+ position: absolute;
+ z-index: 0;
+}
+
+.buzz-setup-grainient__wash,
+.buzz-setup-grainient__veil {
+ inset: 0;
+ position: absolute;
+}
+
+.buzz-setup-grainient__wash {
+ --buzz-grainient-s-start-0: 9%;
+ --buzz-grainient-s-end-0: 55%;
+ --buzz-grainient-s-start-1: 5%;
+ --buzz-grainient-s-end-1: 72%;
+ --buzz-grainient-s-start-2: 5%;
+ --buzz-grainient-s-end-2: 52%;
+ --buzz-grainient-s-start-3: 13%;
+ --buzz-grainient-s-end-3: 68%;
+ --buzz-grainient-x-0: 85%;
+ --buzz-grainient-y-0: 80%;
+ --buzz-grainient-x-1: 60%;
+ --buzz-grainient-y-1: 24%;
+ --buzz-grainient-x-2: 13%;
+ --buzz-grainient-y-2: 82%;
+ --buzz-grainient-x-3: 24%;
+ --buzz-grainient-y-3: 7%;
+ animation: buzz-grainient-orbit 10s linear infinite alternate;
+ background-color: hsl(var(--background));
+ background-image:
+ radial-gradient(
+ circle at var(--buzz-grainient-x-0) var(--buzz-grainient-y-0),
+ var(--buzz-grainient-color-0) var(--buzz-grainient-s-start-0),
+ transparent var(--buzz-grainient-s-end-0)
+ ),
+ radial-gradient(
+ circle at var(--buzz-grainient-x-1) var(--buzz-grainient-y-1),
+ var(--buzz-grainient-color-1) var(--buzz-grainient-s-start-1),
+ transparent var(--buzz-grainient-s-end-1)
+ ),
+ radial-gradient(
+ circle at var(--buzz-grainient-x-2) var(--buzz-grainient-y-2),
+ var(--buzz-grainient-color-2) var(--buzz-grainient-s-start-2),
+ transparent var(--buzz-grainient-s-end-2)
+ ),
+ radial-gradient(
+ circle at var(--buzz-grainient-x-3) var(--buzz-grainient-y-3),
+ var(--buzz-grainient-color-3) var(--buzz-grainient-s-start-3),
+ transparent var(--buzz-grainient-s-end-3)
+ );
+ background-blend-mode: normal, normal, normal, normal;
+ contain: paint;
+ filter: saturate(1.08);
+ transform: translateZ(0);
+ will-change: transform, opacity;
+}
+
+.buzz-setup-grainient__veil {
+ background:
+ radial-gradient(
+ ellipse 74% 54% at 50% 52%,
+ transparent 0%,
+ hsl(var(--background) / 0.04) 72%,
+ hsl(var(--background) / 0.16) 100%
+ ),
+ linear-gradient(
+ 180deg,
+ hsl(var(--background) / 0.04),
+ hsl(var(--background) / 0.12)
+ );
+}
+
+@keyframes buzz-grainient-orbit {
+ 0% {
+ --buzz-grainient-x-0: 85%;
+ --buzz-grainient-y-0: 80%;
+ --buzz-grainient-x-1: 60%;
+ --buzz-grainient-y-1: 24%;
+ --buzz-grainient-x-2: 13%;
+ --buzz-grainient-y-2: 82%;
+ --buzz-grainient-x-3: 24%;
+ --buzz-grainient-y-3: 7%;
+ }
+
+ 100% {
+ --buzz-grainient-x-0: 31%;
+ --buzz-grainient-y-0: 94%;
+ --buzz-grainient-x-1: 2%;
+ --buzz-grainient-y-1: 25%;
+ --buzz-grainient-x-2: 98%;
+ --buzz-grainient-y-2: 20%;
+ --buzz-grainient-x-3: 95%;
+ --buzz-grainient-y-3: 92%;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .buzz-setup-grainient__wash {
+ animation: none;
+ }
+}
From f4f3e442f6afea6b66523e3c3b288ceac3fa9ad0 Mon Sep 17 00:00:00 2001
From: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5
<8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Date: Wed, 8 Jul 2026 13:44:55 +0100
Subject: [PATCH 3/8] feat(desktop): flap the setup-gate bee's wings
Keep the setup loading gate's bee exactly as before (identical BuzzMark
geometry) and just add motion: a new FlappingBee component tags the two
wing lobes with bee-wing classes so the ported wing-flap keyframes beat
them on a continuous infinite loop. Reduced-motion falls back to the
static silhouette. Also fixes a biome useTemplate lint in index.html's
cached-theme boot script.
Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
---
desktop/index.html | 2 +-
desktop/src/app/App.tsx | 14 ++--
.../src/shared/styles/globals/animations.css | 49 +++++++++++++
.../src/shared/ui/buzz-logo/FlappingBee.tsx | 72 +++++++++++++++++++
4 files changed, 128 insertions(+), 9 deletions(-)
create mode 100644 desktop/src/shared/ui/buzz-logo/FlappingBee.tsx
diff --git a/desktop/index.html b/desktop/index.html
index 93320788e..a697c2624 100644
--- a/desktop/index.html
+++ b/desktop/index.html
@@ -29,7 +29,7 @@
cached = window.localStorage.getItem("buzz-theme-cache");
if (!cached) return;
bg = JSON.parse(cached).vars["--background"];
- if (bg) document.documentElement.style.backgroundColor = "hsl(" + bg + ")";
+ if (bg) document.documentElement.style.backgroundColor = `hsl(${bg})`;
} catch {
/* fall back to the black default above */
}
diff --git a/desktop/src/app/App.tsx b/desktop/src/app/App.tsx
index 2bb795ec8..ac0c773a3 100644
--- a/desktop/src/app/App.tsx
+++ b/desktop/src/app/App.tsx
@@ -29,6 +29,7 @@ import { useSystemColorScheme } from "@/shared/theme/useSystemColorScheme";
import { cn } from "@/shared/lib/cn";
import { Button } from "@/shared/ui/button";
import { BuzzMark } from "@/shared/ui/buzz-logo/BuzzMark";
+import { FlappingBee } from "@/shared/ui/buzz-logo/FlappingBee";
import { FuzzyLogo } from "@/shared/ui/buzz-logo/FuzzyLogo";
import { StartupWindowDragRegion } from "@/shared/ui/StartupWindowDragRegion";
import { StepProgress } from "@/shared/ui/step-progress";
@@ -63,10 +64,10 @@ function BeeLoader({
);
}
-// Cold boot gate: the theme-adaptive grainient background with the animated
-// Buzz mark as the hero (replacing the old "Setting up your workspace" text —
-// the caption stays as sr-only for screen readers). A static mark renders
-// underneath the animation so it paints instantly on hard reload.
+// Cold boot gate: the theme-adaptive grainient background with a single
+// centered Buzz bee flying over it — the same static mark as before, now with
+// its wings flapping (ported from the Buzz website's wing-flap). Replaces the
+// old "Setting up your workspace" text, which stays as an sr-only caption.
function AppLoadingGate() {
return (
{LOADING_TEXT}
-
+
);
}
diff --git a/desktop/src/shared/styles/globals/animations.css b/desktop/src/shared/styles/globals/animations.css
index 3922f224b..06f4d2343 100644
--- a/desktop/src/shared/styles/globals/animations.css
+++ b/desktop/src/shared/styles/globals/animations.css
@@ -758,3 +758,52 @@
animation: none;
}
}
+
+/* Buzz bee wing-flap (ported from the Buzz website's BeeField, tuned for a
+ single hero bee). The two wing lobes beat continuously — a fast, small
+ in/out scale so the bee reads as actively flapping on the loading gate rather
+ than resting most of the cycle (the website staggers many bees, so it could
+ afford long rests; one bee cannot). The optional --flap-delay custom property
+ can still stagger multiple bees. */
+.bee-sprite .bee-wing {
+ animation-delay: var(--flap-delay, 0s);
+ animation-duration: 0.28s;
+ animation-iteration-count: infinite;
+ animation-timing-function: ease-in-out;
+ transform-box: fill-box;
+ transform-origin: center;
+}
+
+.bee-sprite .bee-wing-left {
+ animation-name: bee-wing-left-flap;
+}
+
+.bee-sprite .bee-wing-right {
+ animation-name: bee-wing-right-flap;
+}
+
+@keyframes bee-wing-left-flap {
+ 0%,
+ 100% {
+ transform: translateX(0) scaleX(1);
+ }
+ 50% {
+ transform: translateX(30px) scaleX(0.62);
+ }
+}
+
+@keyframes bee-wing-right-flap {
+ 0%,
+ 100% {
+ transform: translateX(0) scaleX(1);
+ }
+ 50% {
+ transform: translateX(-30px) scaleX(0.62);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .bee-sprite .bee-wing {
+ animation: none;
+ }
+}
diff --git a/desktop/src/shared/ui/buzz-logo/FlappingBee.tsx b/desktop/src/shared/ui/buzz-logo/FlappingBee.tsx
new file mode 100644
index 000000000..a7669864f
--- /dev/null
+++ b/desktop/src/shared/ui/buzz-logo/FlappingBee.tsx
@@ -0,0 +1,72 @@
+import { useId } from "react";
+
+/**
+ * The Buzz bee mark with flapping wings. Geometry is identical to the static
+ * {@link BuzzMark} (v8 final keyframe) — the same silhouette, rendered in
+ * `currentColor` so it tints per-theme — but the two wing lobes carry the
+ * `bee-wing` classes so the wing-flap keyframes (ported from the Buzz website)
+ * beat them on an infinite loop. It's plain SVG + CSS (no JS/SMIL), so it paints
+ * on the very first frame and the flap starts as soon as styles load. Reduced
+ * motion falls back to the static silhouette via the CSS media query.
+ */
+export function FlappingBee({ className }: { className?: string }) {
+ const maskId = `flapping-bee-cutouts-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
+
+ return (
+
+ );
+}
From fe55729871fca798c55492d6ff38108391bf6dc1 Mon Sep 17 00:00:00 2001
From: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5
<8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Date: Wed, 8 Jul 2026 14:04:49 +0100
Subject: [PATCH 4/8] fix(desktop): start the setup-gate bee mid-flap so a warm
refresh isn't caught at rest
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
On a warm Cmd+R refresh the cold-boot loading gate can be very short-lived,
so the wing-flap was often caught at its 0% rest pose (no visible motion).
Start the flap with a negative animation-delay (-0.14s) so the very first
painted frame is already mid-beat — the bee never appears frozen at rest.
Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
---
desktop/src/shared/styles/globals/animations.css | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/desktop/src/shared/styles/globals/animations.css b/desktop/src/shared/styles/globals/animations.css
index 06f4d2343..4a6c5d620 100644
--- a/desktop/src/shared/styles/globals/animations.css
+++ b/desktop/src/shared/styles/globals/animations.css
@@ -760,13 +760,15 @@
}
/* Buzz bee wing-flap (ported from the Buzz website's BeeField, tuned for a
- single hero bee). The two wing lobes beat continuously — a fast, small
- in/out scale so the bee reads as actively flapping on the loading gate rather
- than resting most of the cycle (the website staggers many bees, so it could
- afford long rests; one bee cannot). The optional --flap-delay custom property
- can still stagger multiple bees. */
+ single hero bee). The two wing lobes beat continuously — a fast in/out scale
+ so the bee reads as actively flapping on the loading gate rather than resting
+ most of the cycle (the website staggers many bees, so it could afford long
+ rests; one bee cannot). The loading gate can be short-lived on a warm
+ refresh, so the flap starts already mid-beat (negative animation-delay) — the
+ bee is never caught frozen at the rest pose on first paint. The optional
+ --flap-delay custom property can still stagger multiple bees. */
.bee-sprite .bee-wing {
- animation-delay: var(--flap-delay, 0s);
+ animation-delay: var(--flap-delay, -0.14s);
animation-duration: 0.28s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
From a806e48d609130643ff2065ab451082e5c0ee834 Mon Sep 17 00:00:00 2001
From: klopez4212
Date: Wed, 8 Jul 2026 15:16:24 +0100
Subject: [PATCH 5/8] fix(desktop): run the setup-gate wing flap on the
compositor so refresh doesn't freeze the bee
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
On a warm refresh the bee flashed frozen: WebKit paints SVG *children* on
the main thread, and boot work (bundle eval, first React render of the app
tree) hogs that thread for most of the window the loading gate is visible,
so a transform animation on the wing s never advances. Rebuild
FlappingBee as layered HTML-level