Skip to content

Commit 635161c

Browse files
committed
fix(web): improve accessibility and landing page performance
Accessibility: - Add aria-label to all icon-only buttons (copy, mobile menu) - Add aria-expanded to mobile menu toggle - Force terminal to always use dark theme for WCAG AA contrast - Add i18n keys: common.menu, common.close Performance: - Throttle mousemove with rAF, cache getBoundingClientRect on enter - Replace 10 box-shadow keyframe animations with 2 transform-only - Animate border glow only on hover instead of continuously - Reduce hero gradient orbs blur from 3xl to 2xl - Add passive flag to scroll listener - Add will-change and contain hints for animated dots
1 parent c1009cc commit 635161c

9 files changed

Lines changed: 85 additions & 355 deletions

File tree

web/src/components/landing/DownloadSection.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ onMounted(async () => {
139139
@click="copyInstallCommand"
140140
class="flex-shrink-0 p-2 rounded-lg bg-surface/50 text-muted-foreground hover:text-foreground hover:bg-surface transition-all"
141141
:title="copied ? t('landing.download.copied') : t('common.copy')"
142+
:aria-label="copied ? t('landing.download.copied') : t('common.copy')"
142143
>
143144
<svg v-if="!copied" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
144145
<path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />

web/src/components/landing/HeroSection.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ onMounted(() => {
3333
<!-- Animated gradient orbs -->
3434
<div class="absolute inset-0 overflow-hidden pointer-events-none">
3535
<div
36-
class="absolute w-[600px] h-[600px] rounded-full opacity-30 blur-3xl animate-particle-float"
36+
class="absolute w-[600px] h-[600px] rounded-full opacity-30 blur-2xl animate-particle-float"
3737
style="background: radial-gradient(circle, hsl(var(--primary) / 0.4) 0%, transparent 70%); top: -20%; left: -10%;"
3838
/>
3939
<div
40-
class="absolute w-[400px] h-[400px] rounded-full opacity-20 blur-3xl animate-particle-float"
40+
class="absolute w-[400px] h-[400px] rounded-full opacity-20 blur-2xl animate-particle-float"
4141
style="background: radial-gradient(circle, hsl(var(--accent) / 0.4) 0%, transparent 70%); bottom: 10%; right: -5%; animation-delay: -4s;"
4242
/>
4343
</div>
@@ -97,7 +97,7 @@ onMounted(() => {
9797
>
9898
<span class="text-muted-foreground select-none">$</span>
9999
<span class="text-foreground/90">{{ quickCommand }}</span>
100-
<button class="ml-2 text-muted-foreground hover:text-primary transition-colors">
100+
<button class="ml-2 text-muted-foreground hover:text-primary transition-colors" :aria-label="t('common.copy')">
101101
<svg v-if="!copied" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
102102
<path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
103103
</svg>

web/src/components/landing/HowItWorksSection.vue

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,37 @@ const sectionRef = ref<HTMLElement | null>(null)
4141
const cardsContainerRef = ref<HTMLElement | null>(null)
4242
const isHovering = ref(false)
4343
44-
function handleMouseMove(e: MouseEvent) {
45-
if (!cardsContainerRef.value) return
44+
let rafId: number | null = null
45+
let cachedCards: { el: HTMLElement; rect: DOMRect }[] = []
4646
47+
function cacheCardRects() {
48+
if (!cardsContainerRef.value) return
4749
const cards = cardsContainerRef.value.querySelectorAll<HTMLElement>('[data-card]')
50+
cachedCards = Array.from(cards).map((el) => ({ el, rect: el.getBoundingClientRect() }))
51+
}
4852
49-
cards.forEach((card) => {
50-
const cardRect = card.getBoundingClientRect()
51-
const x = e.clientX - cardRect.left
52-
const y = e.clientY - cardRect.top
53-
card.style.setProperty('--mouse-x', `${x}px`)
54-
card.style.setProperty('--mouse-y', `${y}px`)
53+
function handleMouseMove(e: MouseEvent) {
54+
if (rafId) return
55+
rafId = requestAnimationFrame(() => {
56+
for (const { el, rect } of cachedCards) {
57+
el.style.setProperty('--mouse-x', `${e.clientX - rect.left}px`)
58+
el.style.setProperty('--mouse-y', `${e.clientY - rect.top}px`)
59+
}
60+
rafId = null
5561
})
5662
}
5763
5864
function handleMouseEnter() {
5965
isHovering.value = true
66+
cacheCardRects()
6067
}
6168
6269
function handleMouseLeave() {
6370
isHovering.value = false
71+
if (rafId) {
72+
cancelAnimationFrame(rafId)
73+
rafId = null
74+
}
6475
}
6576
6677
onMounted(() => {
@@ -88,6 +99,7 @@ onMounted(() => {
8899
})
89100
90101
onUnmounted(() => {
102+
if (rafId) cancelAnimationFrame(rafId)
91103
if (cardsContainerRef.value) {
92104
cardsContainerRef.value.removeEventListener('mousemove', handleMouseMove)
93105
cardsContainerRef.value.removeEventListener('mouseenter', handleMouseEnter)
@@ -189,6 +201,7 @@ onUnmounted(() => {
189201
<button
190202
class="p-1.5 rounded bg-surface/80 text-muted-foreground hover:text-foreground transition-colors"
191203
@click="copyCode(step.code, index)"
204+
:aria-label="t('common.copy')"
192205
>
193206
<svg v-if="copiedIndex !== index" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
194207
<path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />
@@ -280,12 +293,12 @@ onUnmounted(() => {
280293
mask-composite: exclude;
281294
pointer-events: none;
282295
opacity: 0;
283-
animation: border-spin 4s linear infinite;
284296
transition: opacity 0.5s;
285297
}
286298
287299
.spotlight-card:hover .card-border-glow {
288300
opacity: 1;
301+
animation: border-spin 4s linear infinite;
289302
}
290303
291304
@keyframes border-spin {

0 commit comments

Comments
 (0)