portfolio tech

This commit is contained in:
2026-03-27 22:43:42 +01:00
parent f1c47a7f90
commit 559f3c61bc
4 changed files with 103 additions and 39 deletions

View File

@@ -1,3 +1,5 @@
import { useState } from 'react'
const ACCENT = '#22d3ee'
const BOX_BG = '#27272a' // zinc-800
const BOX_BORDER = '#3f3f46' // zinc-700
@@ -30,6 +32,48 @@ function ServiceBox({ x, y, w, h, label, sub }: BoxProps) {
)
}
function ClickableServiceBox({ x, y, w, h, label, sub, href }: BoxProps & { href: string }) {
const [hovered, setHovered] = useState(false)
return (
<a href={href} target="_blank" rel="noopener noreferrer">
<g
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
cursor: 'pointer',
filter: hovered ? `drop-shadow(0 0 7px ${ACCENT}90)` : 'none',
transition: 'filter 0.2s',
}}
>
<rect
x={x} y={y} width={w} height={h} rx={7}
fill={BOX_BG}
stroke={hovered ? ACCENT : BOX_BORDER}
strokeWidth={hovered ? 2 : 1.5}
style={{ transition: 'stroke 0.2s, stroke-width 0.2s' }}
/>
<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 + 10}
textAnchor="middle" fill={hovered ? ACCENT : TEXT_MUTED} fontSize={10}
fontFamily="JetBrains Mono, monospace"
style={{ transition: 'fill 0.2s' }}
>
{sub}
</text>
)}
</g>
</a>
)
}
function InfraBox({ x, y, w, h, label, sub }: BoxProps) {
return (
<g>
@@ -90,8 +134,8 @@ export function ArchitectureSection() {
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 },
{ label: 'rag-view', sub: '/ragview/', x: 140, href: 'https://balexvic.com/ragview/' },
{ label: 'analytics-view', sub: '/analytics/', x: 265, href: 'https://balexvic.com/analyticsview/' },
]
const feCx = (i: number) => feViews[i].x + FE_W / 2
@@ -256,9 +300,13 @@ export function ArchitectureSection() {
stroke={BOX_BORDER} strokeWidth={1} strokeOpacity={0.5} strokeDasharray="3 4" />
{/* Frontend view boxes */}
{feViews.map(fv => (
{feViews.map(fv =>
fv.href ? (
<ClickableServiceBox key={fv.label} x={fv.x} y={Y_SVC} w={FE_W} h={H_MD} label={fv.label} sub={fv.sub} href={fv.href} />
) : (
<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 => (

View File

@@ -51,9 +51,8 @@ export function ProjectCard({ project, index }: ProjectCardProps) {
ref={ref}
className={`reveal ${delayClass} group relative flex flex-col bg-zinc-900 border border-zinc-800 rounded-lg p-6 hover:border-zinc-700 hover:bg-zinc-900/80 transition-all duration-300`}
>
{/* Top: icon + title + demo link */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
{/* Top: icon + title */}
<div className="flex items-center gap-3 mb-4">
<div className="text-accent/70 group-hover:text-accent transition-colors">
{project.isInfrastructure ? INFRA_ICON : DEFAULT_ICON}
</div>
@@ -62,27 +61,6 @@ export function ProjectCard({ project, index }: ProjectCardProps) {
</h3>
</div>
{project.demoUrl && (
<a
href={project.demoUrl}
target="_blank"
rel="noopener noreferrer"
aria-label={`Open ${project.title} demo`}
className="ml-2 shrink-0 text-zinc-600 hover:text-accent transition-colors"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
d="M6 2H2v12h12v-4M9 2h5v5M14 2L7 9"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</a>
)}
</div>
{/* Description */}
<p className="text-zinc-400 text-sm leading-relaxed mb-5 flex-1 font-body">
{project.description}
@@ -100,8 +78,34 @@ export function ProjectCard({ project, index }: ProjectCardProps) {
))}
</div>
{/* Demo badge */}
{/* Action buttons */}
{(project.demoUrl || project.docsUrl) && (
<div className="flex flex-wrap gap-2 mt-4">
{project.demoUrl && (
<a
href={project.demoUrl}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-mono px-3 py-1.5 rounded border border-accent/50 text-accent bg-accent/10 hover:bg-accent/20 hover:border-accent transition-colors"
>
{project.demoLabel ?? 'Demo'}
</a>
)}
{project.docsUrl && (
<a
href={project.docsUrl}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-mono px-3 py-1.5 rounded border border-zinc-600 text-zinc-400 hover:border-zinc-500 hover:text-zinc-300 transition-colors"
>
API Docs
</a>
)}
</div>
)}
{/* Demo badge */}
{(project.isLive || project.demoUrl) && (
<div className="absolute top-4 right-4">
<span className="inline-flex items-center gap-1 text-[10px] font-mono px-1.5 py-0.5 rounded-sm bg-accent/10 text-accent border border-accent/20">
<span className="w-1 h-1 rounded-full bg-accent" />

View File

@@ -8,6 +8,9 @@ const PROJECTS: Project[] = [
description:
'API Gateway with JWT authentication, OAuth2 (Google, GitHub, Facebook), reactive routing, and per-user rate limiting built on Spring Cloud Gateway.',
stack: ['Java 25', 'Spring Boot 3.5', 'Spring Cloud Gateway', 'WebFlux', 'R2DBC', 'PostgreSQL', 'jjwt'],
isLive: true,
demoUrl: 'https://balexvic.com/ui/dc1/services',
demoLabel: 'Consul UI',
},
{
id: 'rag',
@@ -15,7 +18,10 @@ const PROJECTS: Project[] = [
description:
'AI-powered chat with document context. Upload PDF/DOCX, vector semantic search, BM25 reranking, and real-time SSE streaming responses.',
stack: ['Java 25', 'Spring Boot 3.5', 'Spring AI', 'pgvector', 'Kafka', 'TikaDocumentReader'],
demoUrl: '/ragview/',
isLive: true,
demoUrl: 'https://balexvic.com/ragview/',
docsUrl: 'https://balexvic.com/api/rag/swagger-ui/index.html',
demoLabel: 'Open App',
},
{
id: 'analytics',
@@ -23,15 +29,18 @@ const PROJECTS: Project[] = [
description:
'Kafka consumer that aggregates platform events into PostgreSQL with a dynamic REST API supporting complex filtering and pagination.',
stack: ['Java 25', 'Spring Boot 3.5', 'Kafka', 'JPA', 'Flyway', 'JpaSpecificationExecutor'],
demoUrl: '/analyticsview/',
isLive: true,
demoUrl: 'https://balexvic.com/analyticsview/',
demoLabel: 'Open App',
},
{
id: 'audio-foreign',
title: 'Audio Foreign',
title: 'Learn Deutsch',
description:
'Voice-based language learning app: speak — get instant AI feedback. Speech-to-text via Whisper, grammar correction via Claude, playback via ElevenLabs.',
stack: ['Node.js', 'Express', 'OpenAI Whisper', 'Anthropic Claude API', 'ElevenLabs TTS'],
demoUrl: '/deutsch/',
demoUrl: 'https://balexvic.com/deutsch/',
demoLabel: 'Open App',
},
{
id: 'infra',

View File

@@ -4,6 +4,9 @@ export interface Project {
description: string
stack: string[]
demoUrl?: string
docsUrl?: string
demoLabel?: string
isLive?: boolean
isInfrastructure?: boolean
}