From 559f3c61bcc11646d78f7fe3728ce7aac84cc0c1 Mon Sep 17 00:00:00 2001 From: balex Date: Fri, 27 Mar 2026 22:43:42 +0100 Subject: [PATCH] portfolio tech --- .../architecture/ArchitectureSection.tsx | 58 +++++++++++++++-- .../src/components/projects/ProjectCard.tsx | 64 ++++++++++--------- .../components/projects/ProjectsSection.tsx | 17 +++-- portfolio-view/src/types/index.ts | 3 + 4 files changed, 103 insertions(+), 39 deletions(-) diff --git a/portfolio-view/src/components/architecture/ArchitectureSection.tsx b/portfolio-view/src/components/architecture/ArchitectureSection.tsx index 3bab93b..d6ebd6c 100644 --- a/portfolio-view/src/components/architecture/ArchitectureSection.tsx +++ b/portfolio-view/src/components/architecture/ArchitectureSection.tsx @@ -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 ( + + setHovered(true)} + onMouseLeave={() => setHovered(false)} + style={{ + cursor: 'pointer', + filter: hovered ? `drop-shadow(0 0 7px ${ACCENT}90)` : 'none', + transition: 'filter 0.2s', + }} + > + + + {label} + + {sub && ( + + {sub} + + )} + + + ) +} + function InfraBox({ x, y, w, h, label, sub }: BoxProps) { return ( @@ -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 ? ( + + ) : ( + + ) + )} {/* Backend service boxes */} {beServices.map(bs => ( diff --git a/portfolio-view/src/components/projects/ProjectCard.tsx b/portfolio-view/src/components/projects/ProjectCard.tsx index 5eb19e5..17bd2b3 100644 --- a/portfolio-view/src/components/projects/ProjectCard.tsx +++ b/portfolio-view/src/components/projects/ProjectCard.tsx @@ -51,36 +51,14 @@ 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 */} -
-
-
- {project.isInfrastructure ? INFRA_ICON : DEFAULT_ICON} -
-

- {project.title} -

+ {/* Top: icon + title */} +
+
+ {project.isInfrastructure ? INFRA_ICON : DEFAULT_ICON}
- - {project.demoUrl && ( - - - - - - )} +

+ {project.title} +

{/* Description */} @@ -100,8 +78,34 @@ export function ProjectCard({ project, index }: ProjectCardProps) { ))}
+ {/* Action buttons */} + {(project.demoUrl || project.docsUrl) && ( +
+ {project.demoUrl && ( + + {project.demoLabel ?? 'Demo'} + + )} + {project.docsUrl && ( + + API Docs + + )} +
+ )} + {/* Demo badge */} - {project.demoUrl && ( + {(project.isLive || project.demoUrl) && (
diff --git a/portfolio-view/src/components/projects/ProjectsSection.tsx b/portfolio-view/src/components/projects/ProjectsSection.tsx index e7adc79..4672061 100644 --- a/portfolio-view/src/components/projects/ProjectsSection.tsx +++ b/portfolio-view/src/components/projects/ProjectsSection.tsx @@ -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', diff --git a/portfolio-view/src/types/index.ts b/portfolio-view/src/types/index.ts index 223e230..b820d69 100644 --- a/portfolio-view/src/types/index.ts +++ b/portfolio-view/src/types/index.ts @@ -4,6 +4,9 @@ export interface Project { description: string stack: string[] demoUrl?: string + docsUrl?: string + demoLabel?: string + isLive?: boolean isInfrastructure?: boolean }