Skip to content

Commit 5f4a76e

Browse files
author
psriraj3
committed
Made the bubbles background a bit slow to avoid heating issue
1 parent ee671a2 commit 5f4a76e

7 files changed

Lines changed: 387 additions & 6 deletions

File tree

dist/assets/index-482G4h7R.js

Lines changed: 181 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<title>FUNton</title>
99
<meta name="description" content="FUNton — generate UK-style practice questions and printable worksheets for Years 1–6." />
1010
<meta name="robots" content="index,follow" />
11-
<script type="module" crossorigin src="/assets/index-_gpR58jE.js"></script>
12-
<link rel="stylesheet" crossorigin href="/assets/index-B4dlRwOR.css">
11+
<script type="module" crossorigin src="/assets/index-482G4h7R.js"></script>
12+
<link rel="stylesheet" crossorigin href="/assets/index-D529qSQv.css">
1313
</head>
1414
<body>
1515
<div id="root"></div>

node_modules/.tmp/tsconfig.app.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/App.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {MATHS_TOPICS, type MathsTopic} from "./core/maths/topics";
1111
import {FeedbackPage} from "./ui/FeedbackPage";
1212
import {SupportPage} from "./ui/SupportPage";
1313
import { YearBackdrop } from "./ui/YearBackdrop";
14-
import { BubblesBackground } from "./ui/BubblesBackground";
14+
// import { BubblesBackground } from "./ui/BubblesBackground";
15+
import { BubblyBackground } from "./ui/BubblyBackground";
1516

1617
import React from "react";
1718
import ReactDOM from "react-dom/client";
@@ -288,7 +289,8 @@ export default function App() {
288289
return (
289290
<>
290291
<YearBackdrop ukYear={ukYear} />
291-
<BubblesBackground year={ukYear} />
292+
{/*<BubblesBackground year={ukYear} />*/}
293+
<BubblyBackground disabled={mode === "focus"} />
292294
<div className="siteGrid">
293295
<aside className="adRail" aria-label="Left ads">
294296
<AdUnit client={ADS_CLIENT} slot={ADS_LEFT} className="adBox"/>

src/index.css

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,4 +728,74 @@ html, body {
728728
padding-left: var(--pad);
729729
padding-right: var(--pad);
730730
}
731+
}
732+
733+
/* ===== Low-CPU Bubbly Background (no canvas) ===== */
734+
735+
/* Keep the app above the bubbles */
736+
.container { position: relative; z-index: 1; }
737+
738+
/* Fixed layer behind everything */
739+
.bubblyBg{
740+
position: fixed;
741+
inset: 0;
742+
z-index: 0;
743+
pointer-events: none;
744+
overflow: hidden;
745+
}
746+
747+
/* Each bubble wrapper drifts horizontally (transform = compositor-friendly) */
748+
.bb{
749+
position: absolute;
750+
transform: translate3d(0,0,0);
751+
animation: bbDrift var(--durX) ease-in-out infinite;
752+
animation-delay: var(--delay);
753+
}
754+
755+
/* Inner rises vertically (separate transform so animations can stack) */
756+
.bbInner{
757+
display: block;
758+
width: 100%;
759+
height: 100%;
760+
border-radius: 999px;
761+
762+
/* “3D” bubble look without blur filters */
763+
background:
764+
radial-gradient(circle at 28% 24%, rgba(255,255,255,.95) 0%, rgba(255,255,255,.35) 30%, rgba(255,255,255,0) 62%),
765+
radial-gradient(circle at 60% 70%, hsla(var(--h), 95%, 78%, var(--a)) 0%, hsla(var(--h), 95%, 78%, 0) 70%),
766+
radial-gradient(circle at 48% 55%, rgba(255,255,255,.14) 0%, rgba(255,255,255,0) 60%);
767+
768+
opacity: 0.95;
769+
770+
transform: translate3d(0,0,0);
771+
animation: bbRise var(--durY) linear infinite;
772+
animation-delay: var(--delay);
773+
}
774+
775+
/* Animate only when allowed */
776+
.bubblyBg--static .bb,
777+
.bubblyBg--static .bbInner{
778+
animation: none !important;
779+
}
780+
781+
.bubblyBg--paused .bb,
782+
.bubblyBg--paused .bbInner{
783+
animation-play-state: paused !important;
784+
}
785+
786+
/* Gentle motion (avoid heavy background-position animation) */
787+
@keyframes bbDrift{
788+
0% { transform: translate3d(0px, 0px, 0); }
789+
50% { transform: translate3d(var(--dx), 0px, 0); }
790+
100% { transform: translate3d(0px, 0px, 0); }
791+
}
792+
@keyframes bbRise{
793+
0% { transform: translate3d(0px, 0px, 0) scale(1); }
794+
50% { transform: translate3d(0px, var(--dy), 0) scale(1.02); }
795+
100% { transform: translate3d(0px, 0px, 0) scale(1); }
796+
}
797+
798+
/* Respect OS setting */
799+
@media (prefers-reduced-motion: reduce){
800+
.bb, .bbInner { animation: none !important; }
731801
}

src/ui/BubblyBackground.tsx

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { useEffect, useMemo, useState } from "react";
2+
3+
type PerfMode = "off" | "static" | "animate";
4+
5+
function prefersReducedMotion(): boolean {
6+
if (typeof window === "undefined") return true;
7+
return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false;
8+
}
9+
10+
function detectPerfMode(): PerfMode {
11+
// If user prefers reduced motion, never animate.
12+
if (prefersReducedMotion()) return "static";
13+
14+
const nav: any = typeof navigator !== "undefined" ? navigator : {};
15+
const saveData = !!nav.connection?.saveData;
16+
const mem = typeof nav.deviceMemory === "number" ? nav.deviceMemory : 8; // GB (Chrome)
17+
const cores = typeof nav.hardwareConcurrency === "number" ? nav.hardwareConcurrency : 8;
18+
19+
// Very constrained devices: turn it off completely.
20+
if (saveData || mem <= 2 || cores <= 2) return "off";
21+
22+
// Medium devices: keep bubbles but static (no animation).
23+
if (mem <= 4 || cores <= 4) return "static";
24+
25+
return "animate";
26+
}
27+
28+
type Bubble = {
29+
x: string;
30+
y: string;
31+
size: number;
32+
hue: number;
33+
drift: number; // px
34+
rise: number; // px
35+
durX: number; // seconds
36+
durY: number; // seconds
37+
delay: number; // seconds
38+
alpha: number;
39+
};
40+
41+
export function BubblyBackground(props: { disabled?: boolean }) {
42+
const { disabled } = props;
43+
44+
const [perfMode, setPerfMode] = useState<PerfMode>(() => detectPerfMode());
45+
const [paused, setPaused] = useState(false);
46+
47+
useEffect(() => {
48+
const onVisibility = () => setPaused(document.visibilityState !== "visible");
49+
const onBlur = () => setPaused(true);
50+
const onFocus = () => setPaused(document.visibilityState !== "visible");
51+
52+
document.addEventListener("visibilitychange", onVisibility);
53+
window.addEventListener("blur", onBlur);
54+
window.addEventListener("focus", onFocus);
55+
56+
// Re-evaluate perf mode once after mount (in case of hydration / env differences)
57+
setPerfMode(detectPerfMode());
58+
59+
return () => {
60+
document.removeEventListener("visibilitychange", onVisibility);
61+
window.removeEventListener("blur", onBlur);
62+
window.removeEventListener("focus", onFocus);
63+
};
64+
}, []);
65+
66+
const bubbles = useMemo(() => {
67+
// Fewer bubbles = less work. (Still looks good.)
68+
const count = perfMode === "animate" ? 10 : perfMode === "static" ? 7 : 0;
69+
70+
const rand = (min: number, max: number) => Math.random() * (max - min) + min;
71+
72+
const out: Bubble[] = [];
73+
for (let i = 0; i < count; i++) {
74+
out.push({
75+
x: `${Math.round(rand(-10, 110))}%`,
76+
y: `${Math.round(rand(-10, 110))}%`,
77+
size: Math.round(rand(90, 220)),
78+
hue: Math.round(rand(0, 360)),
79+
drift: Math.round(rand(-90, 90)),
80+
rise: Math.round(rand(80, 220)),
81+
durX: Math.round(rand(28, 60)),
82+
durY: Math.round(rand(34, 72)),
83+
delay: Math.round(rand(-40, 0)),
84+
alpha: Number(rand(0.22, 0.45).toFixed(2)),
85+
});
86+
}
87+
return out;
88+
}, [perfMode]);
89+
90+
if (disabled || perfMode === "off") return null;
91+
92+
const animate = perfMode === "animate" && !paused;
93+
94+
return (
95+
<div
96+
className={[
97+
"bubblyBg",
98+
animate ? "bubblyBg--animate" : "bubblyBg--static",
99+
paused ? "bubblyBg--paused" : "",
100+
].join(" ")}
101+
aria-hidden="true"
102+
>
103+
{bubbles.map((b, idx) => (
104+
<span
105+
key={idx}
106+
className="bb"
107+
style={
108+
{
109+
left: b.x,
110+
top: b.y,
111+
width: `${b.size}px`,
112+
height: `${b.size}px`,
113+
["--h" as any]: b.hue,
114+
["--a" as any]: b.alpha,
115+
["--dx" as any]: `${b.drift}px`,
116+
["--dy" as any]: `${-b.rise}px`,
117+
["--durX" as any]: `${b.durX}s`,
118+
["--durY" as any]: `${b.durY}s`,
119+
["--delay" as any]: `${b.delay}s`,
120+
} as React.CSSProperties
121+
}
122+
>
123+
<i className="bbInner" />
124+
</span>
125+
))}
126+
</div>
127+
);
128+
}

0 commit comments

Comments
 (0)