Skip to content

Commit 890b250

Browse files
committed
feat: Add fallback for 3D background and improve accessibility with focus indicators
1 parent 10ff2b3 commit 890b250

5 files changed

Lines changed: 197 additions & 14 deletions

File tree

src/app.css

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,4 +381,76 @@ html.dark .glass {
381381
.char {
382382
display: inline-block;
383383
transform-origin: center bottom;
384+
}
385+
386+
/* Respect user's motion preferences */
387+
@media (prefers-reduced-motion: reduce) {
388+
*,
389+
*::before,
390+
*::after {
391+
animation-duration: 0.01ms !important;
392+
animation-iteration-count: 1 !important;
393+
transition-duration: 0.01ms !important;
394+
scroll-behavior: auto !important;
395+
}
396+
397+
/* Disable specific animations that might cause motion sickness */
398+
.animate-blob,
399+
.float,
400+
.float-delay-1,
401+
.float-delay-2,
402+
.profile-image,
403+
.animate-bounce,
404+
.animate-pulse,
405+
.animate-spin {
406+
animation: none !important;
407+
}
408+
409+
/* Keep hover effects but make them instant */
410+
.card-hover:hover,
411+
.btn-primary:hover,
412+
.btn-secondary:hover {
413+
transition-duration: 0.01ms !important;
414+
}
415+
416+
/* Disable parallax scrolling */
417+
.hero-bg {
418+
transform: none !important;
419+
}
420+
}
421+
422+
/* Enhanced focus indicators for better accessibility */
423+
*:focus-visible {
424+
outline: 2px solid rgb(37, 99, 235);
425+
outline-offset: 2px;
426+
border-radius: 4px;
427+
}
428+
429+
/* Custom focus styles for interactive elements */
430+
.btn-primary:focus-visible,
431+
.btn-secondary:focus-visible {
432+
outline: 3px solid rgb(96, 165, 250);
433+
outline-offset: 2px;
434+
}
435+
436+
.card-hover:focus-visible {
437+
outline: 2px solid rgb(37, 99, 235);
438+
outline-offset: 4px;
439+
}
440+
441+
/* Skip to main content link for screen readers */
442+
.skip-link {
443+
position: absolute;
444+
top: -40px;
445+
left: 6px;
446+
background: rgb(37, 99, 235);
447+
color: white;
448+
padding: 8px;
449+
text-decoration: none;
450+
border-radius: 4px;
451+
z-index: 9999;
452+
}
453+
454+
.skip-link:focus {
455+
top: 6px;
384456
}

src/lib/components/ThreeBackground.svelte

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,49 @@
11
<script>
22
import { onMount, onDestroy } from 'svelte';
33
import { browser } from '$app/environment';
4+
import ThreeBackgroundFallback from './ThreeBackgroundFallback.svelte';
45
56
let canvas;
67
let scene, camera, renderer;
78
let geometry, material, mesh;
89
let particles, particleGeometry, particleMaterial;
910
let animationId;
1011
let mouseX = 0, mouseY = 0;
12+
let threeFailed = false;
13+
let showFallback = false;
1114
1215
onMount(async () => {
1316
if (!browser) return;
1417
15-
const THREE = await import('three');
16-
initThree(THREE);
17-
animate();
18-
window.addEventListener('resize', handleResize);
19-
window.addEventListener('mousemove', handleMouseMove);
18+
// Check if user prefers reduced motion
19+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
20+
if (prefersReducedMotion) {
21+
showFallback = true;
22+
return;
23+
}
24+
25+
try {
26+
// Check for WebGL support
27+
if (!window.WebGLRenderingContext) {
28+
throw new Error('WebGL not supported');
29+
}
30+
31+
const canvas = document.createElement('canvas');
32+
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
33+
if (!gl) {
34+
throw new Error('WebGL context not available');
35+
}
36+
37+
const THREE = await import('three');
38+
initThree(THREE);
39+
animate();
40+
window.addEventListener('resize', handleResize);
41+
window.addEventListener('mousemove', handleMouseMove);
42+
} catch (error) {
43+
console.warn('3D background failed to initialize, using fallback:', error);
44+
threeFailed = true;
45+
showFallback = true;
46+
}
2047
});
2148
2249
onDestroy(() => {
@@ -130,7 +157,11 @@
130157
}
131158
</script>
132159

133-
<canvas bind:this={canvas} class="three-canvas"></canvas>
160+
{#if showFallback}
161+
<ThreeBackgroundFallback />
162+
{:else}
163+
<canvas bind:this={canvas} class="three-canvas" aria-hidden="true"></canvas>
164+
{/if}
134165

135166
<style>
136167
.three-canvas {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<div class="three-fallback" aria-hidden="true">
2+
<!-- Static gradient background as fallback -->
3+
<div class="fallback-gradient"></div>
4+
<div class="fallback-shapes">
5+
<div class="shape shape-1"></div>
6+
<div class="shape shape-2"></div>
7+
<div class="shape shape-3"></div>
8+
</div>
9+
</div>
10+
11+
<style>
12+
.three-fallback {
13+
position: fixed;
14+
top: 0;
15+
left: 0;
16+
width: 100%;
17+
height: 100%;
18+
pointer-events: none;
19+
z-index: 0;
20+
overflow: hidden;
21+
}
22+
23+
.fallback-gradient {
24+
position: absolute;
25+
inset: 0;
26+
background: radial-gradient(circle at 25% 25%, rgba(37, 99, 235, 0.1) 0%, transparent 50%),
27+
radial-gradient(circle at 75% 75%, rgba(147, 51, 234, 0.1) 0%, transparent 50%);
28+
opacity: 0.6;
29+
}
30+
31+
.shape {
32+
position: absolute;
33+
border-radius: 50%;
34+
background: linear-gradient(135deg, rgba(37, 99, 235, 0.1), rgba(147, 51, 234, 0.1));
35+
filter: blur(1px);
36+
}
37+
38+
.shape-1 {
39+
width: 200px;
40+
height: 200px;
41+
top: 20%;
42+
left: 10%;
43+
animation: float-fallback 8s ease-in-out infinite;
44+
}
45+
46+
.shape-2 {
47+
width: 150px;
48+
height: 150px;
49+
top: 60%;
50+
right: 15%;
51+
animation: float-fallback 6s ease-in-out infinite reverse;
52+
}
53+
54+
.shape-3 {
55+
width: 100px;
56+
height: 100px;
57+
bottom: 20%;
58+
left: 60%;
59+
animation: float-fallback 10s ease-in-out infinite;
60+
}
61+
62+
@keyframes float-fallback {
63+
0%, 100% {
64+
transform: translateY(0px) scale(1);
65+
opacity: 0.3;
66+
}
67+
50% {
68+
transform: translateY(-20px) scale(1.05);
69+
opacity: 0.5;
70+
}
71+
}
72+
73+
/* Respect reduced motion */
74+
@media (prefers-reduced-motion: reduce) {
75+
.shape {
76+
animation: none !important;
77+
}
78+
}
79+
</style>

src/routes/+layout.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
</script>
2121

2222
<div class="min-h-screen flex flex-col">
23+
<a href="#main-content" class="skip-link">Skip to main content</a>
2324
<Header />
24-
<main class="flex-1">
25+
<main id="main-content" class="flex-1" role="main">
2526
<slot />
2627
</main>
2728
<Footer />

src/routes/+page.svelte

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<ScrollAnimations />
6565

6666
<!-- Hero Section -->
67-
<section class="hero-section relative min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 via-white to-blue-50 dark:from-gray-900 dark:via-gray-900 dark:to-blue-900 overflow-hidden">
67+
<section class="hero-section relative min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 via-white to-blue-50 dark:from-gray-900 dark:via-gray-900 dark:to-blue-900 overflow-hidden" role="banner" aria-label="Hero section with introduction">
6868
<!-- Background Elements -->
6969
<div class="absolute inset-0">
7070
<div class="absolute top-20 left-10 w-72 h-72 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-blob"></div>
@@ -104,13 +104,13 @@
104104
</p>
105105

106106
<div class="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start hero-buttons">
107-
<a href="/projects" class="btn-primary inline-flex items-center justify-center space-x-2 group">
107+
<a href="/projects" class="btn-primary inline-flex items-center justify-center space-x-2 group" aria-label="View my portfolio projects">
108108
<span>View My Work</span>
109-
<ArrowRight size={20} class="group-hover:translate-x-1 transition-transform" />
109+
<ArrowRight size={20} class="group-hover:translate-x-1 transition-transform" aria-hidden="true" />
110110
</a>
111-
<a href="/contact" class="btn-secondary inline-flex items-center justify-center space-x-2 group">
111+
<a href="/contact" class="btn-secondary inline-flex items-center justify-center space-x-2 group" aria-label="Contact me for opportunities">
112112
<span>Get In Touch</span>
113-
<ArrowRight size={20} class="group-hover:translate-x-1 transition-transform" />
113+
<ArrowRight size={20} class="group-hover:translate-x-1 transition-transform" aria-hidden="true" />
114114
</a>
115115
</div>
116116

@@ -159,11 +159,11 @@
159159
</section>
160160

161161
<!-- Skills Section -->
162-
<section class="py-20 bg-white dark:bg-gray-900">
162+
<section class="py-20 bg-white dark:bg-gray-900" role="region" aria-labelledby="skills-heading">
163163
<div class="container mx-auto px-4">
164164
<div class="max-w-6xl mx-auto">
165165
<div class="text-center mb-16">
166-
<h2 class="section-title text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
166+
<h2 id="skills-heading" class="section-title text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
167167
What I <span class="gradient-text">Specialize</span> In
168168
</h2>
169169
<p class="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto reveal-text">

0 commit comments

Comments
 (0)