Skip to content

Commit 78dc054

Browse files
committed
Enhance CosmicBackground component and optimize index page API call
- Removed unnecessary canvas element and animations from the CosmicBackground component for improved performance. - Updated the index page to include a timeout and error handling for the Medium posts API call, limiting the number of posts fetched to 5. - Improved image loading attributes for better performance and accessibility.
1 parent 6b17f5e commit 78dc054

3 files changed

Lines changed: 64 additions & 141 deletions

File tree

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"Bash(npx chrome-devtools-mcp:*)",
1919
"Bash(nvm install:*)",
2020
"Bash(nvm use:*)",
21-
"Bash(source:*)"
21+
"Bash(source:*)",
22+
"Bash(ls:*)"
2223
]
2324
},
2425
"enableAllProjectMcpServers": false

src/components/CosmicBackground.astro

Lines changed: 31 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
---
44

55
<div class="cosmic-background">
6-
<canvas class="galaxy-canvas"></canvas>
76
<div class="nebula-glow"></div>
87
<div class="galaxy-dust"></div>
98
<div class="star-clusters"></div>
@@ -28,16 +27,6 @@
2827
);
2928
}
3029

31-
.galaxy-canvas {
32-
position: absolute;
33-
top: 0;
34-
left: 0;
35-
width: 100%;
36-
height: 100%;
37-
opacity: 1;
38-
z-index: 10; /* Higher z-index but below content */
39-
pointer-events: all; /* Ensure mouse events are captured */
40-
}
4130

4231
.nebula-glow {
4332
position: absolute;
@@ -52,7 +41,6 @@
5241
radial-gradient(ellipse at 25% 75%, rgba(55, 218, 214, 0.1) 0%, transparent 60%),
5342
radial-gradient(ellipse at 50% 50%, rgba(255, 255, 255, 0.05) 0%, transparent 100%);
5443
filter: blur(20px);
55-
animation: nebula-pulse 25s infinite alternate;
5644
mix-blend-mode: screen;
5745
z-index: 2;
5846
}
@@ -66,7 +54,6 @@
6654
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400' viewBox='0 0 800 800'%3E%3Cg fill='none' stroke='%23FFFFFF' stroke-width='1'%3E%3Cpath d='M769 229L1037 260.9M927 880L731 737 520 660 309 538 40 599 295 764 126.5 879.5 40 599-197 493 102 382-31 229 126.5 79.5-69-63'/%3E%3Cpath d='M-31 229L237 261 390 382 603 493 308.5 537.5 101.5 381.5M370 905L295 764'/%3E%3Cpath d='M520 660L578 842 731 737 840 599 603 493 520 660 295 764 309 538 390 382 539 269 769 229 577.5 41.5 370 105 295 -36 126.5 79.5 237 261 102 382 40 599 -69 737 127 880'/%3E%3Cpath d='M520-140L578.5 42.5 731-63M603 493L539 269 237 261 370 105M902 382L539 269M390 382L102 382'/%3E%3Cpath d='M-222 42L126.5 79.5 370 105 539 269 577.5 41.5 927 80 769 229 902 382 603 493 731 737M295-36L577.5 41.5M578 842L295 764M40-201L127 80M102 382L-261 269'/%3E%3C/g%3E%3Cg fill='%23FFFFFF'%3E%3Ccircle cx='769' cy='229' r='1'/%3E%3Ccircle cx='539' cy='269' r='1'/%3E%3Ccircle cx='603' cy='493' r='1'/%3E%3Ccircle cx='731' cy='737' r='1'/%3E%3Ccircle cx='520' cy='660' r='1'/%3E%3Ccircle cx='309' cy='538' r='1'/%3E%3Ccircle cx='295' cy='764' r='1'/%3E%3Ccircle cx='40' cy='599' r='1'/%3E%3Ccircle cx='102' cy='382' r='1'/%3E%3Ccircle cx='127' cy='80' r='1'/%3E%3Ccircle cx='370' cy='105' r='1'/%3E%3Ccircle cx='578' cy='42' r='1'/%3E%3Ccircle cx='237' cy='261' r='1'/%3E%3Ccircle cx='390' cy='382' r='1'/%3E%3C/g%3E%3C/svg%3E");
6755
background-size: 150% 150%;
6856
opacity: 0.05;
69-
animation: dust-drift 120s linear infinite;
7057
z-index: 3;
7158
}
7259

@@ -96,9 +83,6 @@
9683
radial-gradient(1.5px 1.5px at 80% 90%, rgba(135, 206, 250, 0.8) 1%, transparent 2%),
9784
radial-gradient(1px 1px at 90% 10%, rgba(255, 255, 255, 0.7) 1%, transparent 2%);
9885
background-size: 500% 500%;
99-
animation:
100-
stars-twinkle 8s infinite alternate,
101-
stars-move 150s linear infinite;
10286
}
10387

10488
.star-clusters::after {
@@ -118,95 +102,9 @@
118102
radial-gradient(1.5px 1.5px at 95% 95%, rgba(135, 206, 250, 0.8) 1%, transparent 2%);
119103
background-size: 300% 300%;
120104
transform: rotate(30deg);
121-
animation:
122-
stars-twinkle 10s infinite alternate-reverse,
123-
stars-move 200s linear infinite reverse;
124105
}
125106

126-
@keyframes nebula-pulse {
127-
0% {
128-
opacity: 0.1;
129-
filter: blur(15px);
130-
}
131-
50% {
132-
opacity: 0.15;
133-
filter: blur(20px);
134-
}
135-
100% {
136-
opacity: 0.1;
137-
filter: blur(15px);
138-
}
139-
}
140107

141-
@keyframes dust-drift {
142-
0% {
143-
background-position: 0% 0%;
144-
opacity: 0.05;
145-
}
146-
50% {
147-
opacity: 0.08;
148-
}
149-
100% {
150-
background-position: 100% 100%;
151-
opacity: 0.05;
152-
}
153-
}
154-
155-
@keyframes stars-twinkle {
156-
0% {
157-
opacity: 0.4;
158-
}
159-
50% {
160-
opacity: 0.7;
161-
}
162-
100% {
163-
opacity: 0.6;
164-
}
165-
}
166-
167-
@keyframes stars-move {
168-
0% {
169-
background-position: 0% 0%;
170-
}
171-
100% {
172-
background-position: 100% 100%;
173-
}
174-
}
175-
176-
@keyframes spin {
177-
0% {
178-
transform: rotate(0deg);
179-
}
180-
100% {
181-
transform: rotate(360deg);
182-
}
183-
}
184-
185-
@keyframes orbit {
186-
0% {
187-
transform: rotate(0deg) translateX(var(--orbit-distance)) rotate(0deg);
188-
}
189-
100% {
190-
transform: rotate(360deg) translateX(var(--orbit-distance)) rotate(-360deg);
191-
}
192-
}
193-
194-
@media (prefers-reduced-motion: reduce) {
195-
.nebula-glow,
196-
.galaxy-dust,
197-
.star-clusters,
198-
.galaxy-canvas {
199-
animation: none !important;
200-
transition: none !important;
201-
}
202-
.star-clusters::before,
203-
.star-clusters::after {
204-
animation: none !important;
205-
}
206-
.galaxy-canvas {
207-
display: none !important;
208-
}
209-
}
210108

211109
/* Dark mode adjustments */
212110
:root[data-theme="dark"] .cosmic-background {
@@ -248,12 +146,14 @@
248146
return;
249147
}
250148

251-
// Skip animation in Firefox or if reduced motion is preferred
149+
// Skip animation in Firefox, if reduced motion is preferred, or on low-end devices
150+
const isLowEnd = navigator.hardwareConcurrency <= 2 || navigator.deviceMemory <= 4;
252151
if (
253152
navigator.userAgent.match(/firefox/i) ||
254-
window.matchMedia("(prefers-reduced-motion: reduce)").matches
153+
window.matchMedia("(prefers-reduced-motion: reduce)").matches ||
154+
isLowEnd
255155
) {
256-
// Animation disabled due to browser type or reduced motion preference
156+
// Animation disabled due to browser type, reduced motion preference, or low-end device
257157
canvas.style.display = "none";
258158
return;
259159
}
@@ -304,11 +204,11 @@
304204

305205
// Create stars with positions and properties
306206
function createStars() {
307-
// Significantly reduce star count for better performance
207+
// Drastically reduce star count for better performance
308208
const isMobile = window.innerWidth < 640;
309209
const starCount = isMobile
310-
? Math.min(300, Math.floor((width * height) / 8000))
311-
: Math.min(800, Math.floor((width * height) / 5000));
210+
? Math.min(150, Math.floor((width * height) / 15000))
211+
: Math.min(300, Math.floor((width * height) / 10000));
312212
stars = [];
313213

314214
const galaxyRadius = Math.min(width, height) * 0.4;
@@ -428,11 +328,11 @@
428328

429329
// Create dust particles
430330
function createDust() {
431-
// Significantly reduce dust count for better performance
331+
// Drastically reduce dust count for better performance
432332
const isMobile = window.innerWidth < 640;
433333
const dustCount = isMobile
434-
? Math.floor((width * height) / 40000)
435-
: Math.floor((width * height) / 25000);
334+
? Math.floor((width * height) / 80000)
335+
: Math.floor((width * height) / 60000);
436336
dust = [];
437337

438338
const galaxyRadius = Math.min(width, height) * 0.4;
@@ -890,27 +790,28 @@
890790
}
891791
}
892792

793+
let lastAnimationTime = 0;
794+
const ANIMATION_INTERVAL = 66; // ~15 FPS max for better performance
795+
893796
// Animation loop
894-
function animate() {
797+
function animate(currentTime) {
895798
// Only render when visible and not hidden
896799
if (isVisible && !document.hidden) {
897-
// Throttle animation based on device capability
898-
const isMobile = window.innerWidth < 640;
899-
900-
if (isMobile) {
901-
// On mobile, throttle even more for better performance
902-
if (Math.random() < 0.2) {
903-
// Only update ~20% of frames
904-
updateStars();
905-
drawGalaxy();
906-
}
907-
} else {
908-
// On desktop, use requestAnimationFrame throttling for better performance
909-
if (Date.now() % 3 === 0) {
910-
// Only render every third frame
800+
// Throttle animation to ~15 FPS for better performance
801+
if (currentTime - lastAnimationTime >= ANIMATION_INTERVAL) {
802+
const isMobile = window.innerWidth < 640;
803+
804+
if (isMobile) {
805+
// On mobile, update even less frequently
806+
if (Math.random() < 0.4) {
807+
updateStars();
808+
drawGalaxy();
809+
}
810+
} else {
911811
updateStars();
912812
drawGalaxy();
913813
}
814+
lastAnimationTime = currentTime;
914815
}
915816
}
916817

@@ -988,9 +889,12 @@
988889
let isTouching = false;
989890
let touchTimeout = null;
990891

991-
// Handle touch events for mobile
892+
// Handle touch events for mobile - simplified for better performance
992893
document.addEventListener("touchstart", function (e) {
993894
if (!cursor) return;
895+
896+
// Disable complex effects on low-end devices
897+
if (isLowEnd) return;
994898

995899
// Reset transition to initial state for new touch
996900
cursor.style.transition =

src/pages/index.astro

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,27 @@ interface MediumPost {
99
pubDate: string;
1010
}
1111
12-
const mediumPosts = await fetch(
13-
"https://api.rss2json.com/v1/api.json?rss_url=https://medium.com/feed/@oakley349",
14-
)
15-
.then((res) => res.json())
16-
.then((data) => data.items as MediumPost[]);
12+
// Optimize API call with timeout and error handling
13+
let mediumPosts: MediumPost[] = [];
14+
try {
15+
const controller = new AbortController();
16+
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout
17+
18+
const response = await fetch(
19+
"https://api.rss2json.com/v1/api.json?rss_url=https://medium.com/feed/@oakley349",
20+
{ signal: controller.signal }
21+
);
22+
clearTimeout(timeoutId);
23+
24+
if (response.ok) {
25+
const data = await response.json();
26+
mediumPosts = data.items?.slice(0, 5) || []; // Limit to 5 posts
27+
}
28+
} catch (error) {
29+
// Fallback to empty array if API fails
30+
console.log("Failed to fetch Medium posts, using fallback");
31+
mediumPosts = [];
32+
}
1733
---
1834

1935
<PageLayout meta={{ title: "Home" }}>
@@ -47,10 +63,11 @@ const mediumPosts = await fetch(
4763
<img
4864
src="/leaddev2025.jpeg"
4965
alt="Oakley Hall co-hosting a session at LeadDev New York 2025"
50-
class="w-full"
51-
fetchpriority="high"
52-
loading="eager"
53-
decoding="sync"
66+
class="w-full aspect-[4/3] object-cover"
67+
width="400"
68+
height="300"
69+
loading="lazy"
70+
decoding="async"
5471
/>
5572
<div class="border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-900">
5673
<h3 class="mb-2 text-lg font-bold text-slate-900 dark:text-white">
@@ -70,10 +87,11 @@ const mediumPosts = await fetch(
7087
<img
7188
src="/leaddev2024.jpeg"
7289
alt="Oakley Hall hosting a session at LeadDev New York 2024"
73-
class="w-full"
74-
fetchpriority="high"
75-
loading="eager"
76-
decoding="sync"
90+
class="w-full aspect-[4/3] object-cover"
91+
width="400"
92+
height="300"
93+
loading="lazy"
94+
decoding="async"
7795
/>
7896
<div class="border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-900">
7997
<h3 class="mb-2 text-lg font-bold text-slate-900 dark:text-white">

0 commit comments

Comments
 (0)