Skip to content

Commit 783ec46

Browse files
committed
feat: enhance accessibility and performance by updating image components to use Next.js Image; add alt attributes for better screen reader support
1 parent c8b118b commit 783ec46

File tree

9 files changed

+84
-39
lines changed

9 files changed

+84
-39
lines changed

src/app/about/page.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const teamMembers = [
1919

2020
export default function AboutPage() {
2121
return (
22-
<div className={cn("flex flex-col md:flex-row bg-gray-100 dark:bg-neutral-800 w-full flex-1 mx-auto border border-neutral-200 dark:border-neutral-700 overflow-hidden h-screen")}>
22+
<div className={cn("flex flex-col md:flex-row bg-gray-100 dark:bg-neutral-800 w-full flex-1 mx-auto border border-neutral-200 dark:border-neutral-700 overflow-visible md:overflow-hidden min-h-screen md:h-screen")}>
2323
<AppSidebar />
2424
<div className="flex flex-1">
2525
<div className="p-2 md:p-10 rounded-tl-2xl border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-900 flex flex-col gap-2 flex-1 w-full h-full overflow-y-auto">
@@ -39,23 +39,22 @@ export default function AboutPage() {
3939
{/* Team Cards */}
4040
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 max-w-5xl mx-auto">
4141
{teamMembers.map((member, index) => (
42-
<div key={index}>
42+
<div key={index} className="flex flex-col">
4343
{/* Pixelated Canvas Avatar */}
44-
<div className="relative mb-6 mx-auto w-64 h-64 md:w-80 md:h-80">
45-
<div className="absolute inset-0 rounded-2xl" style={{ background: 'linear-gradient(to bottom right, var(--ruixen-primary)/20, var(--ruixen-secondary)/20)' }} />
44+
<div className="relative mx-auto w-64 h-64 md:w-80 md:h-80 mb-12 md:mb-8">
4645
<div className="relative rounded-2xl overflow-hidden border-2 border-neutral-200 dark:border-neutral-700">
4746
<PixelatedCanvas
4847
src={member.image}
4948
width={320}
5049
height={320}
5150
interactive={true}
52-
className="w-full h-full"
51+
className="w-full h-full block"
5352
/>
5453
</div>
5554
</div>
5655

5756
{/* Content */}
58-
<div className="text-center space-y-4">
57+
<div className="text-center space-y-4 mt-8 md:mt-0">
5958
<div>
6059
<h3 className="text-2xl font-bold text-neutral-900 dark:text-neutral-100 mb-2">
6160
{member.name}
@@ -78,4 +77,4 @@ export default function AboutPage() {
7877
</div>
7978
</div>
8079
)
81-
}
80+
}

src/app/agency/page.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client"
22

33
import { useEffect, useRef, useState } from 'react'
4+
import Image from 'next/image'
45
import { AppSidebar } from '@/components/app-sidebar'
56
import { cn } from '@/lib/utils'
67
import {
@@ -36,6 +37,15 @@ export default function AgencyPage() {
3637
<div
3738
key={persona.id}
3839
onClick={() => handlePersonaClick(persona)}
40+
role="button"
41+
aria-label={`Open ${persona.name}`}
42+
tabIndex={0}
43+
onKeyDown={(e) => {
44+
if (e.key === 'Enter' || e.key === ' ') {
45+
e.preventDefault()
46+
handlePersonaClick(persona)
47+
}
48+
}}
3949
className="group relative bg-gradient-to-br from-neutral-50 to-neutral-100 dark:from-neutral-800 dark:to-neutral-900 aspect-square rounded-xl hover:from-neutral-100 hover:to-neutral-200 dark:hover:from-neutral-700 dark:hover:to-neutral-800 transition-all duration-300 cursor-pointer border border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600 hover:shadow-lg overflow-hidden"
4050
>
4151
{/* Video Background (lazy) */}
@@ -267,12 +277,13 @@ function LazyAutoplayVideo({ src, poster, className }: { src: string; poster: st
267277
</video>
268278
) : (
269279
// Poster-only until in view (no network fetch for video)
270-
<img
280+
<Image
271281
src={poster}
272282
alt=""
273-
loading="lazy"
274-
decoding="async"
275-
className="w-full h-full object-cover"
283+
fill
284+
sizes="(min-width: 768px) 20vw, 50vw"
285+
priority={false}
286+
className="object-cover"
276287
/>
277288
)}
278289
</div>

src/app/atlas/page.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export default function AtlasPage() {
192192
<div className="max-w-3xl mx-auto space-y-6 pt-2">
193193
{/* Feed */}
194194
{loading ? (
195-
<div className="space-y-6">
195+
<div className="space-y-6" style={{ contentVisibility: 'auto' }}>
196196
{Array.from({ length: 4 }).map((_, index) => (
197197
<div
198198
key={index}
@@ -209,7 +209,7 @@ export default function AtlasPage() {
209209
))}
210210
</div>
211211
) : (
212-
<div className="space-y-6">
212+
<div className="space-y-6" style={{ contentVisibility: 'auto' }}>
213213
{filteredArticles.slice(0, displayCount).map((article, index) => {
214214
const readingTime = Math.ceil(article.content.replace(/<[^>]*>/g, '').split(' ').length / 200)
215215
const authorString = article.frontmatter.author || 'Unknown'
@@ -257,6 +257,7 @@ export default function AtlasPage() {
257257
{authorPersona ? (
258258
<PersonaBadge
259259
imageSrc={authorPersona.imageSrc}
260+
alt={author}
260261
size="md"
261262
/>
262263
) : (
@@ -270,6 +271,7 @@ export default function AtlasPage() {
270271
persona ? (
271272
<PersonaBadge
272273
imageSrc={persona.imageSrc}
274+
alt={mainAuthor}
273275
size="md"
274276
/>
275277
) : (
@@ -389,4 +391,4 @@ export default function AtlasPage() {
389391
</div>
390392
</div>
391393
)
392-
}
394+
}

src/app/explore/page.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from "@/components/ui/table"
1414
import { PersonaBadge } from '@/components/persona-badge'
1515
import { ChannelBadge } from '@/components/channel-badge'
16-
import { Search, Calendar, Clock, Tag } from 'lucide-react'
16+
import { Search, Calendar, Clock, Tag, X } from 'lucide-react'
1717
import { useRouter } from 'next/navigation'
1818
import { personaMap } from '@/data/personas'
1919

@@ -33,6 +33,7 @@ export default function ExplorePage() {
3333
}
3434
}>>([])
3535
const [loading, setLoading] = useState(true)
36+
const [rawQuery, setRawQuery] = useState('')
3637
const [searchQuery, setSearchQuery] = useState('')
3738
const router = useRouter()
3839

@@ -111,6 +112,12 @@ export default function ExplorePage() {
111112
fetchArticles()
112113
}, [])
113114

115+
// Debounce search input (200ms)
116+
useEffect(() => {
117+
const id = setTimeout(() => setSearchQuery(rawQuery), 200)
118+
return () => clearTimeout(id)
119+
}, [rawQuery])
120+
114121
// Filtered articles based on search + selected tags
115122
const filteredArticles = useMemo(() => {
116123
const q = searchQuery.trim().toLowerCase()
@@ -149,10 +156,20 @@ export default function ExplorePage() {
149156
<input
150157
type="text"
151158
placeholder="Search by title, author, or tags..."
152-
value={searchQuery}
153-
onChange={(e) => setSearchQuery(e.target.value)}
159+
value={rawQuery}
160+
onChange={(e) => setRawQuery(e.target.value)}
154161
className="w-full pl-10 pr-4 py-2 border border-neutral-200 dark:border-neutral-700 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 placeholder-neutral-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
155162
/>
163+
{rawQuery && (
164+
<button
165+
type="button"
166+
onClick={() => setRawQuery('')}
167+
aria-label="Clear search"
168+
className="absolute right-2 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-200 p-1 rounded"
169+
>
170+
<X className="h-4 w-4" />
171+
</button>
172+
)}
156173
</div>
157174
{/* Filters removed per request; tags are ever-expanding */}
158175
</div>
@@ -166,7 +183,10 @@ export default function ExplorePage() {
166183
</div>
167184

168185
{/* Table region fills viewport with inner scrolling */}
169-
<div className="flex-1 min-h-0 border border-neutral-200 dark:border-neutral-700 rounded-lg overflow-hidden bg-white dark:bg-neutral-800">
186+
<div
187+
className="flex-1 min-h-0 border border-neutral-200 dark:border-neutral-700 rounded-lg overflow-hidden bg-white dark:bg-neutral-800"
188+
style={{ contentVisibility: 'auto' }}
189+
>
170190
{loading ? (
171191
<div className="p-8 text-center">
172192
<div className="text-neutral-500">Loading articles...</div>
@@ -221,6 +241,7 @@ export default function ExplorePage() {
221241
{authorPersona ? (
222242
<PersonaBadge
223243
imageSrc={authorPersona.imageSrc}
244+
alt={author}
224245
size="sm"
225246
/>
226247
) : (
@@ -236,6 +257,7 @@ export default function ExplorePage() {
236257
{persona ? (
237258
<PersonaBadge
238259
imageSrc={persona.imageSrc}
260+
alt={mainAuthor}
239261
size="sm"
240262
/>
241263
) : (

src/app/page.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BackgroundBeams } from '@/components/ui/background-beams'
33
import { Button } from '@/components/ui/button'
44
import { cn } from '@/lib/utils'
55
import Link from 'next/link'
6+
import Image from 'next/image'
67

78
export default function Home() {
89
return (
@@ -23,10 +24,14 @@ export default function Home() {
2324

2425
{/* Logo */}
2526
<div className="mb-8 md:mb-10">
26-
<img
27+
<Image
2728
src="/logo.svg"
2829
alt="RUIXEN Logo"
30+
width={144}
31+
height={144}
32+
sizes="(min-width: 1024px) 9rem, (min-width: 768px) 8rem, 6rem"
2933
className="w-24 h-24 md:w-32 md:h-32 lg:w-36 lg:h-36 mx-auto drop-shadow-xl rounded-full"
34+
priority
3035
/>
3136
</div>
3237

@@ -55,4 +60,4 @@ export default function Home() {
5560
</div>
5661
</div>
5762
)
58-
}
63+
}

src/components/channel-badge.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
interface Channel {
1414
id: string;
1515
name: string;
16-
icon: React.ComponentType<{ className?: string }>;
16+
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
1717
color: string;
1818
}
1919

@@ -78,10 +78,7 @@ export function ChannelBadge({
7878
}}
7979
>
8080
{showIcon && (
81-
<channel.icon
82-
className={iconSizeClasses[size]}
83-
style={{ color: channel.color }}
84-
/>
81+
<channel.icon className={iconSizeClasses[size]} />
8582
)}
8683
{showName && (
8784
<span className="font-medium">
@@ -95,4 +92,4 @@ export function ChannelBadge({
9592
// Simple helper for single channel display
9693
export function getChannelInfo(channelId: string) {
9794
return CHANNELS[channelId] || null;
98-
}
95+
}

src/components/persona-badge.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import Image from 'next/image'
2+
13
interface PersonaBadgeProps {
24
imageSrc: string
35
size?: 'sm' | 'md' | 'lg' | 'xl'
46
className?: string
7+
alt?: string
58
}
69

710
const sizeClasses = {
@@ -14,16 +17,22 @@ const sizeClasses = {
1417
export function PersonaBadge({
1518
imageSrc,
1619
size = 'sm',
17-
className = ''
20+
className = '',
21+
alt = 'Persona avatar',
1822
}: PersonaBadgeProps) {
1923
return (
2024
<div className={`relative ${sizeClasses[size]} overflow-hidden border border-neutral-200 dark:border-neutral-700 flex-shrink-0 ${className}`}>
21-
<img
22-
className="w-full h-full object-cover"
25+
<Image
2326
src={imageSrc}
24-
alt="Persona avatar"
25-
loading="lazy"
26-
decoding="async"
27+
alt={alt}
28+
fill
29+
sizes={
30+
size === 'xl' ? '96px' :
31+
size === 'lg' ? '64px' :
32+
size === 'md' ? '48px' : '32px'
33+
}
34+
className="object-cover"
35+
priority={false}
2736
/>
2837
</div>
2938
)

src/components/ui/animated-testimonials.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { IconArrowLeft, IconArrowRight } from "@tabler/icons-react";
44
import { motion, AnimatePresence } from "motion/react";
5+
import Image from "next/image";
56

67
import { useEffect, useState, useCallback } from "react";
78

@@ -79,15 +80,13 @@ export const AnimatedTestimonials = ({
7980
}}
8081
className="absolute inset-0 origin-bottom"
8182
>
82-
<img
83+
<Image
8384
src={testimonial.src}
8485
alt={testimonial.name}
85-
width={500}
86-
height={500}
87-
draggable={false}
88-
loading="lazy"
89-
decoding="async"
90-
className="h-full w-full rounded-3xl object-cover object-center"
86+
fill
87+
priority={false}
88+
sizes="(min-width: 768px) 32rem, 100vw"
89+
className="rounded-3xl object-cover object-center"
9190
/>
9291
</motion.div>
9392
))}

src/components/ui/toc.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export function Toc({
125125
<div key={index} className="flex gap-3 items-center">
126126
<PersonaBadge
127127
imageSrc={persona.imageSrc}
128+
alt={persona.name}
128129
size="md"
129130
/>
130131
<div className="flex-1">

0 commit comments

Comments
 (0)