portfolio tech

This commit is contained in:
2026-03-27 22:06:53 +01:00
parent 37fd64c986
commit 76b13bcd85
4 changed files with 415 additions and 95 deletions

View File

@@ -4,6 +4,7 @@ import { HeroSection } from './components/hero/HeroSection'
import { TechStackSection } from './components/tech/TechStackSection'
import { ArchitectureSection } from './components/architecture/ArchitectureSection'
import { ProjectsSection } from './components/projects/ProjectsSection'
import { DeliverSection } from './components/deliver/DeliverSection'
import { ChatWidget } from './components/chat/ChatWidget'
function App() {
@@ -15,6 +16,7 @@ function App() {
<TechStackSection />
<ArchitectureSection />
<ProjectsSection />
<DeliverSection />
</main>
<Footer />
<ChatWidget />

View File

@@ -9,12 +9,20 @@ type BoxProps = { x: number; y: number; w: number; h: number; label: string; sub
function ServiceBox({ x, y, w, h, label, sub }: BoxProps) {
return (
<g>
<rect x={x} y={y} width={w} height={h} rx={8} fill={BOX_BG} stroke={BOX_BORDER} strokeWidth={1.5} />
<text x={x + w / 2} y={sub ? y + h / 2 - 6 : y + h / 2 + 5} textAnchor="middle" fill={TEXT} fontSize={13} fontFamily="JetBrains Mono, monospace" fontWeight={600}>
<rect x={x} y={y} width={w} height={h} rx={7} fill={BOX_BG} stroke={BOX_BORDER} strokeWidth={1.5} />
<text
x={x + w / 2} y={sub ? y + h / 2 - 5 : y + h / 2 + 5}
textAnchor="middle" fill={TEXT} fontSize={12}
fontFamily="JetBrains Mono, monospace" fontWeight={600}
>
{label}
</text>
{sub && (
<text x={x + w / 2} y={y + h / 2 + 12} textAnchor="middle" fill={TEXT_MUTED} fontSize={10} fontFamily="JetBrains Mono, monospace">
<text
x={x + w / 2} y={y + h / 2 + 10}
textAnchor="middle" fill={TEXT_MUTED} fontSize={10}
fontFamily="JetBrains Mono, monospace"
>
{sub}
</text>
)}
@@ -25,12 +33,20 @@ function ServiceBox({ x, y, w, h, label, sub }: BoxProps) {
function InfraBox({ x, y, w, h, label, sub }: BoxProps) {
return (
<g>
<rect x={x} y={y} width={w} height={h} rx={8} fill={BOX_BG} stroke={ACCENT} strokeWidth={1} strokeDasharray="4 3" opacity={0.85} />
<text x={x + w / 2} y={sub ? y + h / 2 - 6 : y + h / 2 + 5} textAnchor="middle" fill={TEXT} fontSize={12} fontFamily="JetBrains Mono, monospace" fontWeight={600}>
<rect x={x} y={y} width={w} height={h} rx={7} fill={BOX_BG} stroke={ACCENT} strokeWidth={1} strokeDasharray="4 3" opacity={0.9} />
<text
x={x + w / 2} y={sub ? y + h / 2 - 5 : y + h / 2 + 5}
textAnchor="middle" fill={TEXT} fontSize={12}
fontFamily="JetBrains Mono, monospace" fontWeight={600}
>
{label}
</text>
{sub && (
<text x={x + w / 2} y={y + h / 2 + 12} textAnchor="middle" fill={TEXT_MUTED} fontSize={10} fontFamily="JetBrains Mono, monospace">
<text
x={x + w / 2} y={y + h / 2 + 10}
textAnchor="middle" fill={TEXT_MUTED} fontSize={10}
fontFamily="JetBrains Mono, monospace"
>
{sub}
</text>
)}
@@ -39,35 +55,75 @@ function InfraBox({ x, y, w, h, label, sub }: BoxProps) {
}
export function ArchitectureSection() {
// Layout constants
const W = 760
const H = 440
const W = 1000
const H = 530
// Gateway
const gw = { x: 280, y: 30, w: 200, h: 52 }
const gwCx = gw.x + gw.w / 2
const gwCy = gw.y + gw.h
// ── Row Y positions (top edge) ──────────────────────────────
const Y_BROWSER = 18
const Y_NGINX = 88
const Y_GATEWAY = 158
const Y_SVC = 252
const Y_INFRA = 372
// Services row — evenly spaced
const svcY = 145
const svcH = 52
const svcW = 140
const services = [
{ label: 'auth-service', sub: 'JWT / OAuth2', x: 20 },
{ label: 'post-service', sub: 'CRUD / feed', x: 185 },
{ label: 'rag-service', sub: 'AI / RAG', x: 350 },
{ label: 'analytics-service', sub: 'metrics', x: 515 },
const H_SM = 38 // Browser, nginx
const H_MD = 44 // Gateway, services, infra
const CX = 490 // horizontal centre of the canvas
// ── Derived bottom edges ────────────────────────────────────
const browserBot = Y_BROWSER + H_SM
const nginxBot = Y_NGINX + H_SM
const gwBot = Y_GATEWAY + H_MD
const svcBot = Y_SVC + H_MD
const infraMidY = svcBot + (Y_INFRA - svcBot) / 2 // ≈ 332
// ── nginx box ───────────────────────────────────────────────
const nginxW = 150
const nginxX = CX - nginxW / 2 // 415
const nginxMidY = Y_NGINX + H_SM / 2
// ── Gateway box ─────────────────────────────────────────────
const gwW = 225
const gwX = CX - gwW / 2 // 378
const gwRight = gwX + gwW // 603
// ── Frontend views (left cluster, row 4) ───────────────────
const FE_W = 115
const feViews = [
{ label: 'auth-view', sub: '/auth/', x: 15 },
{ label: 'rag-view', sub: '/ragview/', x: 140 },
{ label: 'analytics-view', sub: '/analytics/', x: 265 },
]
const feCx = (i: number) => feViews[i].x + FE_W / 2
// Infra row
const infraY = 310
const infraH = 52
const infraW = 160
// ── Backend services (right cluster, row 4) ─────────────────
const BE_W = 130
const beServices = [
{ label: 'auth-service', sub: 'JWT / OAuth2', x: 400 },
{ label: 'post-service', sub: 'CRUD / feed', x: 540 },
{ label: 'rag-service', sub: 'AI / RAG', x: 680 },
{ label: 'analytics-service', sub: 'metrics', x: 820 },
]
const beCx = (i: number) => beServices[i].x + BE_W / 2
// beCx: 465, 605, 745, 885
// ── Infra row (centred under backend cluster) ───────────────
// Backend spans x=400 → 950 (width=550)
// 3 × 148 + 2 × 14 = 472 → start = 400 + (550-472)/2 = 439
const INF_W = 148
const infra = [
{ label: 'PostgreSQL', sub: 'persistence', x: 60 },
{ label: 'Kafka', sub: 'event bus', x: 295 },
{ label: 'Consul', sub: 'service mesh',x: 530 },
{ label: 'PostgreSQL', sub: 'persistence', x: 439 }, // cx = 513
{ label: 'Kafka', sub: 'event bus', x: 601 }, // cx = 675
{ label: 'Consul', sub: 'service mesh', x: 763 }, // cx = 837
]
const infCx = (i: number) => infra[i].x + INF_W / 2
// infCx: 513, 675, 837
// ── Consul right edge (for the L-path endpoint) ─────────────
const consulRight = infra[2].x + INF_W // 763+148 = 911
// Routing rail on the far right (clear of all service boxes, rightmost=950)
const RAIL_X = 972
return (
<section id="architecture" className="py-24 px-6">
@@ -91,75 +147,151 @@ export function ArchitectureSection() {
aria-label="Microservices architecture diagram"
>
<defs>
<marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
<marker id="arr" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto">
<path d="M0,0 L0,6 L8,3 z" fill={ACCENT} />
</marker>
</defs>
{/* ── Gateway → Services connections ── */}
{services.map((s) => {
const sx = s.x + svcW / 2
const sy = svcY
return (
<line
key={s.label}
x1={gwCx} y1={gwCy}
x2={sx} y2={sy - 2}
stroke={ACCENT}
strokeWidth={1.5}
strokeOpacity={0.7}
markerEnd="url(#arrow)"
/>
)
})}
{/* ═══════════════════════════════════════════════════════
SOLID ROUTING ARROWS (request routing)
═══════════════════════════════════════════════════════ */}
{/* ── Services → Infra connections ── */}
{services.map((s) => {
const sx = s.x + svcW / 2
const sy = svcY + svcH
// Each service connects to the nearest infra node
const targets = infra.map((inf) => inf.x + infraW / 2)
const nearest = targets.reduce((prev, cur) =>
Math.abs(cur - sx) < Math.abs(prev - sx) ? cur : prev
)
return (
<line
key={s.label + '-infra'}
x1={sx} y1={sy}
x2={nearest} y2={infraY - 2}
stroke={ACCENT}
strokeWidth={1}
strokeOpacity={0.35}
strokeDasharray="5 4"
markerEnd="url(#arrow)"
/>
)
})}
{/* Browser → nginx */}
<line x1={CX} y1={browserBot} x2={CX} y2={Y_NGINX - 2}
stroke={ACCENT} strokeWidth={1.5} strokeOpacity={0.85} markerEnd="url(#arr)" />
{/* ── Gateway box ── */}
<ServiceBox {...gw} label="Spring Cloud Gateway" />
{/* nginx → Spring Cloud Gateway */}
<line x1={CX} y1={nginxBot} x2={CX} y2={Y_GATEWAY - 2}
stroke={ACCENT} strokeWidth={1.5} strokeOpacity={0.85} markerEnd="url(#arr)" />
{/* ── Service boxes ── */}
{services.map((s) => (
<ServiceBox key={s.label} x={s.x} y={svcY} w={svcW} h={svcH} label={s.label} sub={s.sub} />
{/* nginx → frontend views (from nginx LEFT side) */}
{feViews.map((fv, i) => (
<line key={fv.label + '-route'}
x1={nginxX} y1={nginxMidY}
x2={feCx(i)} y2={Y_SVC - 2}
stroke={ACCENT} strokeWidth={1.2} strokeOpacity={0.65} markerEnd="url(#arr)" />
))}
{/* ── Section label: Shared Infrastructure ── */}
<text x={W / 2} y={infraY - 18} textAnchor="middle" fill={TEXT_MUTED} fontSize={10} fontFamily="JetBrains Mono, monospace" letterSpacing={2}>
{/* Gateway → backend services */}
{beServices.map((bs, i) => (
<line key={bs.label + '-route'}
x1={CX} y1={gwBot}
x2={beCx(i)} y2={Y_SVC - 2}
stroke={ACCENT} strokeWidth={1.5} strokeOpacity={0.75} markerEnd="url(#arr)" />
))}
{/* ═══════════════════════════════════════════════════════
DASHED INFRA DEPENDENCY ARROWS
═══════════════════════════════════════════════════════ */}
{/* ── All 4 services → PostgreSQL ── */}
{[0, 1, 2, 3].map(i => (
<line key={`svc${i}-pg`}
x1={beCx(i)} y1={svcBot}
x2={infCx(0)} y2={Y_INFRA - 2}
stroke={ACCENT} strokeWidth={0.9} strokeOpacity={0.45} strokeDasharray="5 4" markerEnd="url(#arr)" />
))}
{/* ── post-service, rag-service, analytics-service → Kafka ── */}
{[1, 2, 3].map(i => (
<line key={`svc${i}-kafka`}
x1={beCx(i)} y1={svcBot}
x2={infCx(1)} y2={Y_INFRA - 2}
stroke={ACCENT} strokeWidth={0.9} strokeOpacity={0.45} strokeDasharray="5 4" markerEnd="url(#arr)" />
))}
{/* ── All 4 backend services → Consul (faint — many lines) ── */}
{[0, 1, 2, 3].map(i => (
<line key={`svc${i}-consul`}
x1={beCx(i)} y1={svcBot}
x2={infCx(2)} y2={Y_INFRA - 2}
stroke={ACCENT} strokeWidth={0.7} strokeOpacity={0.22} strokeDasharray="5 4" markerEnd="url(#arr)" />
))}
{/* ── Gateway → Consul (L-path along right rail, avoids all boxes) ──
M gwRight, gwMid → RAIL_X, gwMid → RAIL_X, consulMidY → consulRight, consulMidY */}
<path
d={`M ${gwRight} ${Y_GATEWAY + H_MD / 2}
L ${RAIL_X} ${Y_GATEWAY + H_MD / 2}
L ${RAIL_X} ${Y_INFRA + H_MD / 2}
L ${consulRight + 2} ${Y_INFRA + H_MD / 2}`}
stroke={ACCENT} strokeWidth={1} strokeOpacity={0.5} strokeDasharray="5 4" fill="none" markerEnd="url(#arr)" />
{/* ═══════════════════════════════════════════════════════
BOXES (rendered on top of arrows)
═══════════════════════════════════════════════════════ */}
{/* Browser */}
<ServiceBox
x={CX - 50} y={Y_BROWSER} w={100} h={H_SM}
label="Browser" />
{/* nginx */}
<ServiceBox
x={nginxX} y={Y_NGINX} w={nginxW} h={H_SM}
label="nginx" sub="reverse proxy" />
{/* Spring Cloud Gateway */}
<ServiceBox
x={gwX} y={Y_GATEWAY} w={gwW} h={H_MD}
label="Spring Cloud Gateway" sub="port 8080" />
{/* Cluster labels row 4 */}
<text
x={feViews[1].x + FE_W / 2} y={Y_SVC - 12}
textAnchor="middle" fill={TEXT_MUTED} fontSize={9}
fontFamily="JetBrains Mono, monospace" letterSpacing={1.5}
>
FRONTEND
</text>
<text
x={(beServices[0].x + beServices[3].x + BE_W) / 2} y={Y_SVC - 12}
textAnchor="middle" fill={TEXT_MUTED} fontSize={9}
fontFamily="JetBrains Mono, monospace" letterSpacing={1.5}
>
BACKEND SERVICES
</text>
{/* Subtle divider between clusters */}
<line x1={388} y1={Y_SVC - 18} x2={388} y2={Y_SVC + H_MD + 4}
stroke={BOX_BORDER} strokeWidth={1} strokeOpacity={0.5} strokeDasharray="3 4" />
{/* Frontend view boxes */}
{feViews.map(fv => (
<ServiceBox key={fv.label} x={fv.x} y={Y_SVC} w={FE_W} h={H_MD} label={fv.label} sub={fv.sub} />
))}
{/* Backend service boxes */}
{beServices.map(bs => (
<ServiceBox key={bs.label} x={bs.x} y={Y_SVC} w={BE_W} h={H_MD} label={bs.label} sub={bs.sub} />
))}
{/* "SHARED INFRASTRUCTURE" label */}
<text
x={W / 2} y={Y_INFRA - 11}
textAnchor="middle" fill={TEXT_MUTED} fontSize={9}
fontFamily="JetBrains Mono, monospace" letterSpacing={2}
>
SHARED INFRASTRUCTURE
</text>
{/* ── Infra boxes ── */}
{infra.map((inf) => (
<InfraBox key={inf.label} x={inf.x} y={infraY} w={infraW} h={infraH} label={inf.label} sub={inf.sub} />
{/* Infra boxes */}
{infra.map(inf => (
<InfraBox key={inf.label} x={inf.x} y={Y_INFRA} w={INF_W} h={H_MD} label={inf.label} sub={inf.sub} />
))}
{/* ── Legend ── */}
<g transform={`translate(${W - 175}, ${H - 52})`}>
<line x1={0} y1={8} x2={28} y2={8} stroke={ACCENT} strokeWidth={1.5} markerEnd="url(#arrow)" />
<text x={34} y={12} fill={TEXT_MUTED} fontSize={10} fontFamily="JetBrains Mono, monospace">request routing</text>
<line x1={0} y1={26} x2={28} y2={26} stroke={ACCENT} strokeWidth={1} strokeDasharray="5 4" markerEnd="url(#arrow)" />
<text x={34} y={30} fill={TEXT_MUTED} fontSize={10} fontFamily="JetBrains Mono, monospace">infra dependency</text>
{/* ═══════════════════════════════════════════════════════
LEGEND
═══════════════════════════════════════════════════════ */}
<g transform={`translate(14, ${H - 52})`}>
<line x1={0} y1={8} x2={28} y2={8} stroke={ACCENT} strokeWidth={1.5} markerEnd="url(#arr)" />
<text x={34} y={12} fill={TEXT_MUTED} fontSize={10} fontFamily="JetBrains Mono, monospace">
request routing
</text>
<line x1={0} y1={28} x2={28} y2={28} stroke={ACCENT} strokeWidth={1} strokeDasharray="5 4" markerEnd="url(#arr)" />
<text x={34} y={32} fill={TEXT_MUTED} fontSize={10} fontFamily="JetBrains Mono, monospace">
infra dependency
</text>
</g>
</svg>
</div>

View File

@@ -0,0 +1,94 @@
const CARDS = [
{
title: 'Clean REST APIs',
detail: 'Spring Boot, OpenAPI, tested endpoints',
icon: (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-6 h-6">
<path d="M8 9l3 3-3 3M13 15h3" stroke="#22d3ee" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<rect x="3" y="3" width="18" height="18" rx="3" stroke="#22d3ee" strokeWidth="2"/>
</svg>
),
},
{
title: 'Docker-Ready Deployments',
detail: 'Containerized services, Docker Compose',
icon: (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-6 h-6">
<rect x="3" y="9" width="3" height="2.5" rx=".5" fill="#22d3ee"/>
<rect x="6.5" y="9" width="3" height="2.5" rx=".5" fill="#22d3ee"/>
<rect x="10" y="9" width="3" height="2.5" rx=".5" fill="#22d3ee"/>
<rect x="10" y="6" width="3" height="2.5" rx=".5" fill="#22d3ee"/>
<rect x="13.5" y="9" width="3" height="2.5" rx=".5" fill="#22d3ee"/>
<rect x="6.5" y="6" width="3" height="2.5" rx=".5" fill="#22d3ee"/>
<path d="M21 11c-.5-1-2-1.25-2.75-1.25-.15-1.25-1-2.25-2-2.25h-.5c.25.5.25 1 .25 1.5H16V11h-3V9.5h-.5V8H9v1H8.5V7H6v2h-.5C4.5 9 4 9.5 4 10.5c0 2.5 1.75 4.5 4.25 4.5h9c2 0 3.75-1.25 4.25-3 .75 0 1.25 0 1.5-1z" fill="#22d3ee"/>
</svg>
),
},
{
title: 'CI/CD From Day One',
detail: 'GitLab pipelines, automated builds and deploys',
icon: (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-6 h-6">
<circle cx="12" cy="12" r="2" fill="#22d3ee"/>
<circle cx="5" cy="7" r="2" fill="#22d3ee"/>
<circle cx="19" cy="7" r="2" fill="#22d3ee"/>
<circle cx="5" cy="17" r="2" fill="#22d3ee"/>
<circle cx="19" cy="17" r="2" fill="#22d3ee"/>
<path d="M7 7h10M7 17h10M12 10v4" stroke="#22d3ee" strokeWidth="1.5" strokeLinecap="round"/>
<path d="M12 10L5 7M12 10L19 7M12 14L5 17M12 14L19 17" stroke="#22d3ee" strokeWidth="1.5" strokeLinecap="round"/>
</svg>
),
},
{
title: 'Production Infrastructure',
detail: 'VPS, Nginx, SSL, monitoring',
icon: (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-6 h-6">
<rect x="3" y="4" width="18" height="5" rx="1.5" stroke="#22d3ee" strokeWidth="2"/>
<rect x="3" y="11" width="18" height="5" rx="1.5" stroke="#22d3ee" strokeWidth="2"/>
<circle cx="7" cy="6.5" r="1" fill="#22d3ee"/>
<circle cx="7" cy="13.5" r="1" fill="#22d3ee"/>
<path d="M9 19c0-1.7 1.3-3 3-3s3 1.3 3 3" stroke="#22d3ee" strokeWidth="2" strokeLinecap="round"/>
<circle cx="12" cy="21" r="1" fill="#22d3ee"/>
</svg>
),
},
]
export function DeliverSection() {
return (
<section id="deliver" className="py-24 px-6">
<div className="max-w-6xl mx-auto">
<div className="mb-14">
<p className="font-mono text-xs text-accent tracking-widest mb-3 uppercase">
Deliverables
</p>
<h2 className="font-heading font-bold text-4xl md:text-5xl text-white tracking-tight">
What I Deliver
</h2>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{CARDS.map(({ title, detail, icon }) => (
<div
key={title}
className="flex flex-col gap-4 p-6 bg-zinc-900 border border-zinc-800 rounded-lg hover:border-zinc-600 hover:bg-zinc-800/70 transition-all duration-200 group"
>
<div className="w-10 h-10 flex items-center justify-center rounded-md bg-zinc-800 group-hover:bg-zinc-700 transition-colors duration-200">
{icon}
</div>
<div className="flex flex-col gap-1.5">
<span className="font-heading font-semibold text-white text-sm leading-snug">
{title}
</span>
<span className="font-mono text-xs text-zinc-500 leading-relaxed">
{detail}
</span>
</div>
</div>
))}
</div>
</div>
</section>
)
}

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
/**
* Cycle timeline (all times relative to stage-1 firing):
@@ -22,6 +22,7 @@ export function HeroSection() {
const [alexOn, setAlexOn] = useState(false)
const [shiftLeft, setShiftLeft] = useState(false)
const [contentVisible, setContentVisible] = useState(false)
const scrollIndicatorRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const timers: ReturnType<typeof setTimeout>[] = []
@@ -51,6 +52,22 @@ export function HeroSection() {
return () => { alive = false; timers.forEach(clearTimeout) }
}, [])
useEffect(() => {
const el = scrollIndicatorRef.current
if (!el) return
const onScroll = () => {
if (window.scrollY > 80) {
el.style.opacity = '0'
el.style.pointerEvents = 'none'
} else {
el.style.opacity = '1'
el.style.pointerEvents = 'auto'
}
}
window.addEventListener('scroll', onScroll, { passive: true })
return () => window.removeEventListener('scroll', onScroll)
}, [])
/* ── style helpers ── */
const noTrans = nameStage === 0
@@ -244,18 +261,93 @@ export function HeroSection() {
</div>
</div>
{/* Scroll hint */}
<div style={fadeUp(contentVisible, '0.9s')}>
<div className="mt-16 flex flex-col items-center gap-2 text-zinc-600 text-xs font-mono tracking-widest">
<span>SCROLL</span>
<svg width="14" height="20" viewBox="0 0 14 20" fill="none" className="animate-bounce">
<rect x="1" y="1" width="12" height="18" rx="6" stroke="currentColor" strokeWidth="1.5" />
<circle cx="7" cy="6" r="1.5" fill="currentColor" className="animate-pulse" />
</svg>
</div>
{/* Animated scroll indicator */}
<style>{`
@keyframes scrollDot {
0% { opacity: 1; transform: translateY(0); }
60% { opacity: 0; transform: translateY(12px); }
61% { opacity: 0; transform: translateY(0); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes arrowPulse {
0%, 100% { opacity: 0.2; }
50% { opacity: 1; }
}
`}</style>
<div
ref={scrollIndicatorRef}
style={{
position: 'absolute',
bottom: '40px',
left: '50%',
transform: 'translateX(-50%)',
zIndex: 2,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '8px',
opacity: contentVisible ? 1 : 0,
transition: 'opacity 0.3s',
}}
>
{/* Label */}
<span
style={{
fontFamily: 'monospace',
fontSize: '10px',
letterSpacing: '3px',
textTransform: 'uppercase',
color: 'rgba(255,255,255,0.4)',
}}
>
SCROLL
</span>
{/* Mouse body */}
<div
style={{
width: '28px',
height: '44px',
border: '2px solid rgba(255,255,255,0.5)',
borderRadius: '14px',
display: 'flex',
justifyContent: 'center',
paddingTop: '6px',
}}
>
<div
style={{
width: '4px',
height: '8px',
backgroundColor: '#00d4ff',
borderRadius: '2px',
animation: 'scrollDot 1.6s ease-in-out infinite',
}}
/>
</div>
{/* Chevron arrows */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '3px' }}>
{[0, 0.2, 0.4].map((delay, i) => (
<div
key={i}
style={{
width: '10px',
height: '10px',
borderRight: '2px solid #00d4ff',
borderBottom: '2px solid #00d4ff',
transform: 'rotate(45deg)',
animation: `arrowPulse 1.6s ease-in-out ${delay}s infinite`,
}}
/>
))}
</div>
</div>
</div>
</section>
)
}