chore: capture live server state before migration (2026-06-10)

This commit is contained in:
2026-06-10 23:56:36 +02:00
parent 97bf6da106
commit b6ad1e0b2f
52 changed files with 704 additions and 691 deletions
+1
View File
@@ -0,0 +1 @@
manage-package-manager-versions=false
+1 -1
View File
@@ -6,7 +6,7 @@ Personal portfolio website showcasing my work as an **AI & Automation Specialist
## Current Role
**Process Automation Specialist** @ Everlast Consulting GmbH (since Dec 2024)
**Process Automation Specialist** @ Everlast Consulting GmbH (since Dec 2025)
- Developing AI agents with n8n and Zapier
- Building web scraping solutions
- Migration from Power Automate to n8n
+10
View File
@@ -65,5 +65,15 @@
"tailwindcss": "^4.1.0",
"typescript": "5.9.3",
"vitest": "^1.3.1"
},
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"@swc/core",
"esbuild",
"sharp",
"unrs-resolver"
],
"ignoredBuiltDependencies": []
}
}
+9 -9
View File
@@ -35,8 +35,8 @@ export default defineConfig({
// Shared settings for all projects
use: {
// Base URL - uses dedicated test port to avoid conflicts with other dev servers
baseURL: 'http://localhost:3100',
// Base URL - test against running Docker container
baseURL: process.env.BASE_URL || 'http://localhost:3001',
// Collect trace when retrying the failed test
trace: 'on-first-retry',
@@ -71,13 +71,13 @@ export default defineConfig({
},
],
// Configure web server to start before tests on a dedicated port
webServer: {
command: 'pnpm dev --port 3100',
url: 'http://localhost:3100',
reuseExistingServer: false,
timeout: 120000,
},
// Use running Docker container - no webServer needed
// webServer: {
// command: 'pnpm dev --port 3100',
// url: 'http://localhost:3100',
// reuseExistingServer: false,
// timeout: 120000,
// },
// Global timeout for each test
timeout: 60000,
+6 -5
View File
@@ -2105,6 +2105,7 @@ packages:
basic-ftp@5.1.0:
resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==}
engines: {node: '>=10.0.0'}
deprecated: Security vulnerability fixed in 5.2.1, please upgrade
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
@@ -4366,8 +4367,8 @@ packages:
third-party-web@0.27.0:
resolution: {integrity: sha512-h0JYX+dO2Zr3abCQpS6/uFjujaOjA1DyDzGQ41+oFn9VW/ARiq9g5ln7qEP9+BTzDpOMyIfsfj4OvfgXAsMUSA==}
third-party-web@0.29.0:
resolution: {integrity: sha512-nBDSJw5B7Sl1YfsATG2XkW5qgUPODbJhXw++BKygi9w6O/NKS98/uY/nR/DxDq2axEjL6halHW1v+jhm/j1DBQ==}
third-party-web@0.29.2:
resolution: {integrity: sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -5798,7 +5799,7 @@ snapshots:
'@paulirish/trace_engine@0.0.61':
dependencies:
legacy-javascript: 0.0.1
third-party-web: 0.29.0
third-party-web: 0.29.2
'@playwright/test@1.58.0':
dependencies:
@@ -6136,7 +6137,7 @@ snapshots:
'@testing-library/dom@9.3.4':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/code-frame': 7.29.0
'@babel/runtime': 7.28.6
'@types/aria-query': 5.0.4
aria-query: 5.1.3
@@ -9606,7 +9607,7 @@ snapshots:
third-party-web@0.27.0: {}
third-party-web@0.29.0: {}
third-party-web@0.29.2: {}
tinybench@2.9.0: {}
+6
View File
@@ -0,0 +1,6 @@
ignoredBuiltDependencies:
- '@parcel/watcher'
- '@swc/core'
- esbuild
- sharp
- unrs-resolver
+7 -15
View File
@@ -1,16 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1000 1000">
<!-- Generator: Adobe Illustrator 29.7.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 8) -->
<defs>
<style>
.st0 {
fill: #d6d7d8;
}
</style>
</defs>
<path class="st0" d="M965.47,97.09l-461.75,786.4L34.81,97.09h930.65M1000,77.5H0l17.59,29.5,468.89,786.4,17.35,29.1,17.15-29.22L982.73,106.88l17.27-29.38h0Z"/>
<g>
<polygon class="st0" points="755.12 213.96 524.03 214.02 544.73 249.03 734.41 249 830.87 249 782.41 330.91 759.95 368.91 687.8 490.96 639.56 572.54 591.35 490.96 519.2 368.98 615.65 368.95 639.56 409.41 663.47 368.95 685.97 330.91 593.15 330.95 496.71 330.95 448.28 249.05 427.57 214.02 379.13 132.11 475.57 132.11 803.57 132.05 900.03 132.02 851.57 213.96 755.12 213.96"/>
<polygon class="st0" points="618.8 607.63 570.59 689.19 548.33 726.83 500.09 808.39 451.88 726.83 240.28 369.01 217.81 331 169.39 249.08 265.85 249.08 314.27 330.97 336.73 369.01 500.09 645.29 522.35 607.63 381.23 368.98 358.76 330.97 310.33 249.05 289.61 214.06 245.11 214.06 148.69 214.08 100.24 132.17 196.69 132.14 241.17 132.14 337.64 132.11 386.05 214.02 406.76 249.05 455.21 330.97 477.68 368.98 570.59 526.08 618.8 607.63"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="6" fill="#181C14"/>
<g transform="translate(2.5, 4.5) scale(0.027)">
<path fill="#ECDFCC" d="M965.47,97.09l-461.75,786.4L34.81,97.09h930.65M1000,77.5H0l17.59,29.5,468.89,786.4,17.35,29.1,17.15-29.22L982.73,106.88l17.27-29.38h0Z"/>
<polygon fill="#ECDFCC" points="755.12 213.96 524.03 214.02 544.73 249.03 734.41 249 830.87 249 782.41 330.91 759.95 368.91 687.8 490.96 639.56 572.54 591.35 490.96 519.2 368.98 615.65 368.95 639.56 409.41 663.47 368.95 685.97 330.91 593.15 330.95 496.71 330.95 448.28 249.05 427.57 214.02 379.13 132.11 475.57 132.11 803.57 132.05 900.03 132.02 851.57 213.96 755.12 213.96"/>
<polygon fill="#ECDFCC" points="618.8 607.63 570.59 689.19 548.33 726.83 500.09 808.39 451.88 726.83 240.28 369.01 217.81 331 169.39 249.08 265.85 249.08 314.27 330.97 336.73 369.01 500.09 645.29 522.35 607.63 381.23 368.98 358.76 330.97 310.33 249.05 289.61 214.06 245.11 214.06 148.69 214.08 100.24 132.17 196.69 132.14 241.17 132.14 337.64 132.11 386.05 214.02 406.76 249.05 455.21 330.97 477.68 368.98 570.59 526.08 618.8 607.63"/>
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+4 -4
View File
@@ -121,10 +121,10 @@ export default async function AboutPage({ params }: Props) {
<section id="skills" className="py-24 relative">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold text-white mb-4">
<h2 className="text-3xl font-bold text-foreground mb-4">
{t('skills.title')}
</h2>
<p className="text-zinc-400 max-w-2xl mx-auto">
<p className="text-muted-foreground max-w-2xl mx-auto">
{t('skills.description')}
</p>
</div>
@@ -136,10 +136,10 @@ export default async function AboutPage({ params }: Props) {
<section className="py-24 relative">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold text-white mb-4">
<h2 className="text-3xl font-bold text-foreground mb-4">
{t('workflow.title')}
</h2>
<p className="text-zinc-400 max-w-2xl mx-auto">
<p className="text-muted-foreground max-w-2xl mx-auto">
{t('workflow.description')}
</p>
</div>
+7 -7
View File
@@ -1335,7 +1335,7 @@ export default async function BlogPostPage({ params }: Props) {
{/* Back Link */}
<Link
href={`/${locale}/blog`}
className="inline-flex items-center gap-2 text-zinc-400 hover:text-white transition-colors mb-8"
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors mb-8"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
@@ -1345,22 +1345,22 @@ export default async function BlogPostPage({ params }: Props) {
{/* Header */}
<header className="mb-12">
<div className="flex items-center gap-4 mb-6 text-zinc-500">
<div className="flex items-center gap-4 mb-6 text-muted-foreground">
<time dateTime={post.date}>{getFormattedDate(post.date)}</time>
<div className="flex items-center gap-2">
<Clock className="h-4 w-4" />
<span>{readingTime} min read</span>
</div>
<span className="px-3 py-1 bg-zinc-800 rounded-full text-sm">
<span className="px-3 py-1 bg-card rounded-full text-sm">
{post.category}
</span>
</div>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-6">
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-6">
{post.title}
</h1>
<p className="text-xl text-zinc-400">
<p className="text-xl text-muted-foreground">
{post.excerpt}
</p>
@@ -1369,7 +1369,7 @@ export default async function BlogPostPage({ params }: Props) {
{post.tags?.map((tag, index) => (
<span
key={index}
className="px-3 py-1 bg-zinc-800/50 text-sm text-zinc-400 rounded-full ring-1 ring-zinc-700/50"
className="px-3 py-1 bg-card/50 text-sm text-muted-foreground rounded-full ring-1 ring-border/50"
>
{tag}
</span>
@@ -1378,7 +1378,7 @@ export default async function BlogPostPage({ params }: Props) {
</header>
{/* Cover Image */}
<div className="aspect-[16/9] w-full mb-12 rounded-xl overflow-hidden bg-zinc-800 relative">
<div className="aspect-[16/9] w-full mb-12 rounded-xl overflow-hidden bg-card relative">
<Image
src={post.coverImage}
alt={post.title}
+36 -36
View File
@@ -129,26 +129,26 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
return (
<Link
href={`/${locale}/blog/${post.slug}`}
className="group relative block bg-zinc-900/50 backdrop-blur-sm
className="group relative block bg-background/50 backdrop-blur-sm
rounded-xl shadow-lg hover:shadow-2xl
ring-1 ring-zinc-800 hover:ring-zinc-700
ring-1 ring-border hover:ring-accent
transition-all duration-500 focus:outline-none focus:ring-2
focus:ring-zinc-600 focus:ring-offset-2 focus:ring-offset-zinc-950
focus:ring-ring focus:ring-offset-2 focus:ring-offset-background
transform hover:-translate-y-1"
>
<div className="relative overflow-hidden rounded-xl">
{/* Image Container */}
<div className="aspect-[16/9] w-full relative overflow-hidden bg-zinc-900">
<div className="aspect-[16/9] w-full relative overflow-hidden bg-background">
<BlogImage
src={getImagePath(post.coverImage)}
alt={post.title}
/>
<div className="absolute inset-x-0 -bottom-20 top-0 bg-gradient-to-t from-zinc-900 via-zinc-900/70 to-transparent opacity-95" />
<div className="absolute inset-x-0 -bottom-20 top-0 bg-gradient-to-t from-background via-background/70 to-transparent opacity-95" />
</div>
{/* Content */}
<div className="relative -mt-2 p-6 bg-zinc-900 z-10">
<div className="flex items-center gap-4 mb-4 text-zinc-500">
<div className="relative -mt-2 p-6 bg-background z-10">
<div className="flex items-center gap-4 mb-4 text-muted-foreground">
<div className="flex items-center gap-2 text-sm">
<Calendar className="h-4 w-4" />
<span>{getFormattedDate(post.date, locale)}</span>
@@ -167,12 +167,12 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
)}
</div>
<h3 className="text-xl font-semibold text-white mb-3
group-hover:text-zinc-100 transition-colors duration-300">
<h3 className="text-xl font-semibold text-foreground mb-3
group-hover:text-foreground transition-colors duration-300">
{post.title}
</h3>
<p className="text-zinc-400 text-sm line-clamp-2 mb-4
group-hover:text-zinc-300 transition-colors duration-300">
<p className="text-muted-foreground text-sm line-clamp-2 mb-4
group-hover:text-foreground/80 transition-colors duration-300">
{post.excerpt}
</p>
@@ -181,17 +181,17 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
{post.tags?.slice(0, 3).map((tag, index) => (
<span
key={index}
className="px-3 py-1 bg-zinc-800/50
text-sm text-zinc-400 rounded-full
ring-1 ring-zinc-700/50 group-hover:ring-zinc-600/50
group-hover:bg-zinc-800/70 group-hover:text-zinc-300
className="px-3 py-1 bg-card/50
text-sm text-muted-foreground rounded-full
ring-1 ring-border/50 group-hover:ring-ring/50
group-hover:bg-card/70 group-hover:text-foreground/80
transition-all duration-300"
>
{tag}
</span>
))}
{post.tags && post.tags.length > 3 && (
<span className="text-sm text-zinc-600">
<span className="text-sm text-muted-foreground/60">
+{post.tags.length - 3} more
</span>
)}
@@ -199,11 +199,11 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
{/* Link Icon */}
<div className="absolute top-6 right-6 p-2.5 rounded-full
bg-zinc-800/60 backdrop-blur-sm ring-1 ring-zinc-700/50
bg-card/60 backdrop-blur-sm ring-1 ring-border/50
opacity-0 group-hover:opacity-100
transform translate-y-2 group-hover:translate-y-0
transition-all duration-300">
<ExternalLink className="h-4 w-4 text-zinc-400" />
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</div>
</div>
</div>
@@ -266,9 +266,9 @@ function Pagination({
{currentPage > 1 ? (
<Link
href={`/${locale}/blog${currentPage > 2 ? `?page=${currentPage - 1}` : ''}`}
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-400 hover:text-white
bg-zinc-800/50 hover:bg-zinc-800 rounded-lg ring-1 ring-zinc-700/50
hover:ring-zinc-600 transition-all duration-200"
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:text-foreground
bg-card/50 hover:bg-card rounded-lg ring-1 ring-border/50
hover:ring-accent transition-all duration-200"
aria-label={t('ui.pagination.previous')}
>
<ChevronLeft className="h-4 w-4" />
@@ -276,8 +276,8 @@ function Pagination({
</Link>
) : (
<span
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-600
bg-zinc-800/30 rounded-lg ring-1 ring-zinc-800/50 cursor-not-allowed"
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground/60
bg-card/30 rounded-lg ring-1 ring-border/50 cursor-not-allowed"
aria-disabled="true"
>
<ChevronLeft className="h-4 w-4" />
@@ -290,7 +290,7 @@ function Pagination({
{getPageNumbers().map((page, index) => {
if (page === 'ellipsis') {
return (
<span key={`ellipsis-${index}`} className="px-2 text-zinc-600">
<span key={`ellipsis-${index}`} className="px-2 text-muted-foreground/60">
...
</span>
);
@@ -306,7 +306,7 @@ function Pagination({
ring-1 transition-all duration-200
${isCurrentPage
? 'bg-[#697565] text-white ring-[#697565] font-medium'
: 'text-zinc-400 hover:text-white bg-zinc-800/50 hover:bg-zinc-800 ring-zinc-700/50 hover:ring-zinc-600'
: 'text-muted-foreground hover:text-foreground bg-card/50 hover:bg-card ring-border/50 hover:ring-accent'
}`}
aria-current={isCurrentPage ? 'page' : undefined}
>
@@ -320,9 +320,9 @@ function Pagination({
{currentPage < totalPages ? (
<Link
href={`/${locale}/blog?page=${currentPage + 1}`}
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-400 hover:text-white
bg-zinc-800/50 hover:bg-zinc-800 rounded-lg ring-1 ring-zinc-700/50
hover:ring-zinc-600 transition-all duration-200"
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:text-foreground
bg-card/50 hover:bg-card rounded-lg ring-1 ring-border/50
hover:ring-accent transition-all duration-200"
aria-label={t('ui.pagination.next')}
>
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
@@ -330,8 +330,8 @@ function Pagination({
</Link>
) : (
<span
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-600
bg-zinc-800/30 rounded-lg ring-1 ring-zinc-800/50 cursor-not-allowed"
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground/60
bg-card/30 rounded-lg ring-1 ring-border/50 cursor-not-allowed"
aria-disabled="true"
>
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
@@ -387,17 +387,17 @@ export default async function BlogPage({ params, searchParams }: Props) {
{/* Header Section */}
<div className="mb-12 sm:mb-16 lg:mb-20">
<div className="flex items-center gap-4 mb-8">
<ArrowRight className="h-5 w-5 text-zinc-500" />
<h2 className="text-white text-sm tracking-wider">BLOG</h2>
<ArrowRight className="h-5 w-5 text-muted-foreground" />
<h2 className="text-foreground text-sm tracking-wider">BLOG</h2>
</div>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-6">
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-6">
{t('meta.header.title')}
</h1>
<p className="text-lg sm:text-xl text-zinc-400 max-w-2xl">
<p className="text-lg sm:text-xl text-muted-foreground max-w-2xl">
{t('meta.header.subtitle')}
</p>
{/* Post count */}
<p className="text-sm text-zinc-500 mt-4">
<p className="text-sm text-muted-foreground mt-4">
{t('ui.pagination.showing', {
start: startIndex + 1,
end: Math.min(endIndex, allPosts.length),
@@ -424,7 +424,7 @@ export default async function BlogPage({ params, searchParams }: Props) {
{/* Empty State */}
{posts.length === 0 && (
<div className="text-center py-12">
<p className="text-zinc-400">{t('ui.errors.posts')}</p>
<p className="text-muted-foreground">{t('ui.errors.posts')}</p>
</div>
)}
</div>
+21 -21
View File
@@ -221,21 +221,21 @@ export default async function ImprintPage({ params }: Props) {
<nav className="mb-8">
<Link
href={`/${locale}`}
className="text-zinc-400 hover:text-white transition-colors"
className="text-muted-foreground hover:text-foreground transition-colors"
>
Home
</Link>
<span className="mx-2 text-zinc-600">/</span>
<span className="text-white">{c.title}</span>
<span className="mx-2 text-muted-foreground/60">/</span>
<span className="text-foreground">{c.title}</span>
</nav>
<h1 className="text-4xl font-bold text-white mb-8">{c.title}</h1>
<h1 className="text-4xl font-bold text-foreground mb-8">{c.title}</h1>
<div className="prose prose-invert max-w-none space-y-8">
{/* Contact */}
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
<h2 className="text-2xl font-semibold text-white mb-4">{c.contact.title}</h2>
<div className="text-zinc-300 space-y-1">
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.contact.title}</h2>
<div className="text-foreground/80 space-y-1">
{c.contact.content.map((line, index) => (
<p key={index}>{line}</p>
))}
@@ -244,25 +244,25 @@ export default async function ImprintPage({ params }: Props) {
{/* Liability */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.liability.title}</h2>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.liability.title}</h2>
{c.liability.sections.map((section, index) => (
<div key={index} className="mb-4">
<h3 className="text-xl font-medium text-white mb-2">{section.title}</h3>
<p className="text-zinc-300">{section.content}</p>
<h3 className="text-xl font-medium text-foreground mb-2">{section.title}</h3>
<p className="text-foreground/80">{section.content}</p>
</div>
))}
</section>
{/* Copyright */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.copyright.title}</h2>
<p className="text-zinc-300">{c.copyright.content}</p>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.copyright.title}</h2>
<p className="text-foreground/80">{c.copyright.content}</p>
</section>
{/* Images */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.images.title}</h2>
<ul className="list-disc list-inside text-zinc-300 space-y-1">
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.images.title}</h2>
<ul className="list-disc list-inside text-foreground/80 space-y-1">
{c.images.content.map((item, index) => (
<li key={index}>{item}</li>
))}
@@ -271,8 +271,8 @@ export default async function ImprintPage({ params }: Props) {
{/* Privacy */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.privacy.title}</h2>
<p className="text-zinc-300">
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.privacy.title}</h2>
<p className="text-foreground/80">
{c.privacy.content}{' '}
<Link href={`/${locale}/privacy`} className="text-orange-500 hover:text-orange-400 underline">
{c.privacy.link}
@@ -282,15 +282,15 @@ export default async function ImprintPage({ params }: Props) {
</section>
{/* Technical */}
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
<h2 className="text-2xl font-semibold text-white mb-4">{c.technical.title}</h2>
<p className="text-zinc-300">{c.technical.design}</p>
<p className="text-zinc-300">{c.technical.tech}</p>
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.technical.title}</h2>
<p className="text-foreground/80">{c.technical.design}</p>
<p className="text-foreground/80">{c.technical.tech}</p>
</section>
{/* Copyright Notice */}
<section>
<p className="text-zinc-400">
<p className="text-muted-foreground">
&copy; {new Date().getFullYear()} Damjan Savić. {c.copyrightNotice}
</p>
</section>
+1 -1
View File
@@ -122,7 +122,7 @@ export default async function LocaleLayout({ children, params }: Props) {
<OrganizationJsonLd locale={locale as Locale} />
<WebSiteJsonLd locale={locale as Locale} />
</head>
<body className="min-h-screen bg-zinc-900 text-zinc-50 antialiased">
<body className="min-h-screen bg-background text-foreground antialiased">
<NextIntlClientProvider messages={messages}>
<div className="min-h-screen flex flex-col relative">
<GlobalBackground />
+28 -28
View File
@@ -170,23 +170,23 @@ export default async function ServicePage({ params }: Props) {
{/* Back Link */}
<Link
href={`/${locale}/leistungen`}
className="inline-flex items-center gap-2 text-zinc-400 hover:text-white transition-colors mb-8"
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors mb-8"
>
<ArrowLeft className="h-4 w-4" />
{locale === 'de' ? 'Alle Leistungen' : locale === 'sr' ? 'Sve usluge' : 'All Services'}
</Link>
<div className="flex items-center gap-4 mb-6">
<div className="p-4 bg-zinc-800/50 rounded-xl">
<IconComponent className="h-8 w-8 text-white" />
<div className="p-4 bg-card/50 rounded-xl">
<IconComponent className="h-8 w-8 text-foreground" />
</div>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6">
<h1 className="text-4xl md:text-5xl font-bold text-foreground mb-6">
{service.name[locale as Locale]}
</h1>
<p className="text-xl text-zinc-400 mb-8">
<p className="text-xl text-muted-foreground mb-8">
{service.description[locale as Locale]}
</p>
@@ -195,7 +195,7 @@ export default async function ServicePage({ params }: Props) {
{service.technologies.map((tech) => (
<span
key={tech}
className="px-3 py-1 bg-zinc-800/50 border border-zinc-700 text-sm text-zinc-300 rounded-full"
className="px-3 py-1 bg-card/50 border border-border text-sm text-foreground/80 rounded-full"
>
{tech}
</span>
@@ -205,19 +205,19 @@ export default async function ServicePage({ params }: Props) {
</section>
{/* Features Section */}
<section className="py-16 bg-zinc-800/20">
<section className="py-16 bg-card/20">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? 'Was ich anbiete' : locale === 'sr' ? 'Šta nudim' : 'What I offer'}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{service.features[locale as Locale].map((feature, index) => (
<div
key={index}
className="flex items-start gap-3 p-4 bg-zinc-800/30 rounded-lg"
className="flex items-start gap-3 p-4 bg-card/30 rounded-lg"
>
<CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0 mt-0.5" />
<span className="text-zinc-300">{feature}</span>
<span className="text-foreground/80">{feature}</span>
</div>
))}
</div>
@@ -227,20 +227,20 @@ export default async function ServicePage({ params }: Props) {
{/* Use Cases Section */}
<section className="py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? 'Anwendungsbeispiele' : locale === 'sr' ? 'Primeri primene' : 'Use Cases'}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{service.useCases[locale as Locale].map((useCase, index) => (
<div
key={index}
className="p-6 bg-zinc-800/30 border border-zinc-800 rounded-xl"
className="p-6 bg-card/30 border border-border rounded-xl"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-zinc-700 rounded-full flex items-center justify-center text-sm font-semibold text-white">
<div className="w-8 h-8 bg-accent rounded-full flex items-center justify-center text-sm font-semibold text-foreground">
{index + 1}
</div>
<span className="text-zinc-200">{useCase}</span>
<span className="text-foreground/90">{useCase}</span>
</div>
</div>
))}
@@ -249,9 +249,9 @@ export default async function ServicePage({ params }: Props) {
</section>
{/* Related Services */}
<section className="py-16 bg-zinc-800/20">
<section className="py-16 bg-card/20">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? 'Weitere Leistungen' : locale === 'sr' ? 'Ostale usluge' : 'Other Services'}
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
@@ -261,15 +261,15 @@ export default async function ServicePage({ params }: Props) {
<Link
key={relatedService.slug}
href={`/${locale}/leistungen/${relatedService.slug}`}
className="group p-6 bg-zinc-800/30 border border-zinc-800 rounded-xl hover:border-zinc-700 transition-all duration-300"
className="group p-6 bg-card/30 border border-border rounded-xl hover:border-accent transition-all duration-300"
>
<div className="p-2 bg-zinc-800/50 rounded-lg w-fit mb-3">
<RelatedIcon className="h-5 w-5 text-zinc-400 group-hover:text-white transition-colors" />
<div className="p-2 bg-card/50 rounded-lg w-fit mb-3">
<RelatedIcon className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors" />
</div>
<h3 className="text-lg font-semibold text-white mb-2">
<h3 className="text-lg font-semibold text-foreground mb-2">
{relatedService.name[locale as Locale]}
</h3>
<p className="text-sm text-zinc-400">
<p className="text-sm text-muted-foreground">
{relatedService.shortDescription[locale as Locale]}
</p>
</Link>
@@ -282,7 +282,7 @@ export default async function ServicePage({ params }: Props) {
{/* FAQ Section */}
<section className="py-16">
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white text-center mb-8">
<h2 className="text-2xl font-bold text-foreground text-center mb-8">
{locale === 'de'
? `Häufige Fragen zu ${service.name.de}`
: locale === 'sr'
@@ -293,12 +293,12 @@ export default async function ServicePage({ params }: Props) {
{serviceFaqItems.map((item, index) => (
<div
key={index}
className="bg-zinc-800/30 border border-zinc-800 rounded-lg p-6"
className="bg-card/30 border border-border rounded-lg p-6"
>
<h3 className="text-lg font-semibold text-white mb-2">
<h3 className="text-lg font-semibold text-foreground mb-2">
{item.question}
</h3>
<p className="text-zinc-400">{item.answer}</p>
<p className="text-muted-foreground">{item.answer}</p>
</div>
))}
</div>
@@ -308,14 +308,14 @@ export default async function ServicePage({ params }: Props) {
{/* CTA Section */}
<section className="py-24">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl font-bold text-white mb-4">
<h2 className="text-3xl font-bold text-foreground mb-4">
{locale === 'de'
? `${service.name.de} für Ihr Unternehmen?`
: locale === 'sr'
? `${service.name.sr} za vašu kompaniju?`
: `${service.name.en} for your business?`}
</h2>
<p className="text-zinc-400 mb-8">
<p className="text-muted-foreground mb-8">
{locale === 'de'
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
: locale === 'sr'
@@ -324,7 +324,7 @@ export default async function ServicePage({ params }: Props) {
</p>
<Link
href={`/${locale}/contact`}
className="inline-flex items-center px-8 py-4 bg-white text-zinc-900 font-semibold rounded-lg hover:bg-zinc-100 transition-colors"
className="inline-flex items-center px-8 py-4 bg-primary text-background font-semibold rounded-lg hover:bg-accent transition-colors"
>
{locale === 'de' ? 'Projekt besprechen' : locale === 'sr' ? 'Razgovarajte o projektu' : 'Discuss project'}
<ArrowRight className="h-5 w-5 ml-2" />
+43 -43
View File
@@ -239,10 +239,10 @@ export default async function ServicesPage({ params }: Props) {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
<div className="text-center lg:text-left">
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6">
<h1 className="text-4xl md:text-5xl font-bold text-foreground mb-6">
{pageTitle}
</h1>
<p className="text-xl text-zinc-400 max-w-2xl">
<p className="text-xl text-muted-foreground max-w-2xl">
{pageSubtitle}
</p>
</div>
@@ -262,7 +262,7 @@ export default async function ServicesPage({ params }: Props) {
{/* Core Services Grid */}
<section className="pb-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">{coreServicesTitle}</h2>
<h2 className="text-2xl font-bold text-foreground mb-8">{coreServicesTitle}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{services.map((service) => {
const IconComponent = iconMap[service.icon] || Brain;
@@ -270,28 +270,28 @@ export default async function ServicesPage({ params }: Props) {
<Link
key={service.slug}
href={`/${locale}/leistungen/${service.slug}`}
className="group bg-zinc-800/30 border border-zinc-800 rounded-xl p-6 hover:border-zinc-700 transition-all duration-300"
className="group bg-card/30 border border-border rounded-xl p-6 hover:border-accent transition-all duration-300"
>
<div className="p-3 bg-zinc-800/50 rounded-lg w-fit mb-4">
<IconComponent className="h-6 w-6 text-zinc-400 group-hover:text-white transition-colors" />
<div className="p-3 bg-card/50 rounded-lg w-fit mb-4">
<IconComponent className="h-6 w-6 text-muted-foreground group-hover:text-foreground transition-colors" />
</div>
<h3 className="text-xl font-semibold text-white mb-2">
<h3 className="text-xl font-semibold text-foreground mb-2">
{service.name[locale as Locale]}
</h3>
<p className="text-zinc-400 mb-4">
<p className="text-muted-foreground mb-4">
{service.shortDescription[locale as Locale]}
</p>
<div className="flex flex-wrap gap-2 mb-4">
{service.technologies.slice(0, 4).map((tech) => (
<span
key={tech}
className="px-2 py-1 bg-zinc-800/50 text-xs text-zinc-400 rounded"
className="px-2 py-1 bg-card/50 text-xs text-muted-foreground rounded"
>
{tech}
</span>
))}
</div>
<div className="flex items-center text-zinc-400 group-hover:text-white transition-colors">
<div className="flex items-center text-muted-foreground group-hover:text-foreground transition-colors">
<span className="text-sm">
{locale === 'de' ? 'Mehr erfahren' : locale === 'sr' ? 'Saznaj više' : 'Learn more'}
</span>
@@ -307,7 +307,7 @@ export default async function ServicesPage({ params }: Props) {
{/* Technology Services Grid */}
<section className="pb-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">{techServicesTitle}</h2>
<h2 className="text-2xl font-bold text-foreground mb-8">{techServicesTitle}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{techServices.map((service) => {
const IconComponent = iconMap[service.icon] || Brain;
@@ -315,28 +315,28 @@ export default async function ServicesPage({ params }: Props) {
<Link
key={service.slug}
href={`/${locale}/leistungen/${service.slug}`}
className="group bg-gradient-to-br from-zinc-800/40 to-zinc-800/20 border border-zinc-700/50 rounded-xl p-6 hover:border-zinc-600 transition-all duration-300"
className="group bg-gradient-to-br from-card/40 to-card/20 border border-border/50 rounded-xl p-6 hover:border-accent transition-all duration-300"
>
<div className="p-3 bg-zinc-700/50 rounded-lg w-fit mb-4">
<IconComponent className="h-6 w-6 text-zinc-300 group-hover:text-white transition-colors" />
<div className="p-3 bg-accent/50 rounded-lg w-fit mb-4">
<IconComponent className="h-6 w-6 text-foreground/80 group-hover:text-foreground transition-colors" />
</div>
<h3 className="text-xl font-semibold text-white mb-2">
<h3 className="text-xl font-semibold text-foreground mb-2">
{service.name[locale as Locale]}
</h3>
<p className="text-zinc-400 mb-4">
<p className="text-muted-foreground mb-4">
{service.shortDescription[locale as Locale]}
</p>
<div className="flex flex-wrap gap-2 mb-4">
{service.technologies.slice(0, 4).map((tech) => (
<span
key={tech}
className="px-2 py-1 bg-zinc-700/50 text-xs text-zinc-300 rounded"
className="px-2 py-1 bg-accent/50 text-xs text-foreground/80 rounded"
>
{tech}
</span>
))}
</div>
<div className="flex items-center text-zinc-400 group-hover:text-white transition-colors">
<div className="flex items-center text-muted-foreground group-hover:text-foreground transition-colors">
<span className="text-sm">
{locale === 'de' ? 'Mehr erfahren' : locale === 'sr' ? 'Saznaj više' : 'Learn more'}
</span>
@@ -352,7 +352,7 @@ export default async function ServicesPage({ params }: Props) {
{/* Industry Services Grid */}
<section className="pb-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">{industryServicesTitle}</h2>
<h2 className="text-2xl font-bold text-foreground mb-8">{industryServicesTitle}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{industryServices.map((service) => {
const IconComponent = iconMap[service.icon] || Brain;
@@ -360,28 +360,28 @@ export default async function ServicesPage({ params }: Props) {
<Link
key={service.slug}
href={`/${locale}/leistungen/${service.slug}`}
className="group bg-gradient-to-br from-zinc-800/30 to-zinc-900/30 border border-zinc-800 rounded-xl p-6 hover:border-zinc-600 transition-all duration-300"
className="group bg-gradient-to-br from-card/30 to-background/30 border border-border rounded-xl p-6 hover:border-accent transition-all duration-300"
>
<div className="p-3 bg-zinc-800/70 rounded-lg w-fit mb-4">
<IconComponent className="h-6 w-6 text-zinc-400 group-hover:text-white transition-colors" />
<div className="p-3 bg-card/70 rounded-lg w-fit mb-4">
<IconComponent className="h-6 w-6 text-muted-foreground group-hover:text-foreground transition-colors" />
</div>
<h3 className="text-xl font-semibold text-white mb-2">
<h3 className="text-xl font-semibold text-foreground mb-2">
{service.name[locale as Locale]}
</h3>
<p className="text-zinc-400 mb-4">
<p className="text-muted-foreground mb-4">
{service.shortDescription[locale as Locale]}
</p>
<div className="flex flex-wrap gap-2 mb-4">
{service.technologies.slice(0, 4).map((tech) => (
<span
key={tech}
className="px-2 py-1 bg-zinc-800/50 text-xs text-zinc-400 rounded"
className="px-2 py-1 bg-card/50 text-xs text-muted-foreground rounded"
>
{tech}
</span>
))}
</div>
<div className="flex items-center text-zinc-400 group-hover:text-white transition-colors">
<div className="flex items-center text-muted-foreground group-hover:text-foreground transition-colors">
<span className="text-sm">
{locale === 'de' ? 'Mehr erfahren' : locale === 'sr' ? 'Saznaj više' : 'Learn more'}
</span>
@@ -397,7 +397,7 @@ export default async function ServicesPage({ params }: Props) {
{/* Technologies Section */}
<section className="pb-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? 'Technologien' : locale === 'sr' ? 'Tehnologije' : 'Technologies'}
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
@@ -405,10 +405,10 @@ export default async function ServicesPage({ params }: Props) {
<Link
key={tech.slug}
href={`/${locale}/leistungen/technologie/${tech.slug}`}
className="group flex items-center gap-3 p-4 bg-zinc-800/30 border border-zinc-800 rounded-lg hover:border-zinc-700 transition-all duration-300"
className="group flex items-center gap-3 p-4 bg-card/30 border border-border rounded-lg hover:border-accent transition-all duration-300"
>
<Code className="h-5 w-5 text-zinc-500 group-hover:text-white transition-colors" />
<span className="text-zinc-300 group-hover:text-white transition-colors font-medium">
<Code className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors" />
<span className="text-foreground/80 group-hover:text-foreground transition-colors font-medium">
{tech.name}
</span>
</Link>
@@ -418,21 +418,21 @@ export default async function ServicesPage({ params }: Props) {
</section>
{/* Cities Section */}
<section className="py-24 bg-zinc-800/20">
<section className="py-24 bg-card/20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-white mb-4">{citiesTitle}</h2>
<p className="text-zinc-400">{citiesSubtitle}</p>
<h2 className="text-3xl font-bold text-foreground mb-4">{citiesTitle}</h2>
<p className="text-muted-foreground">{citiesSubtitle}</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{cities.map((city) => (
<Link
key={city.slug}
href={`/${locale}/leistungen/standort/${city.slug}`}
className="group flex items-center gap-2 p-4 bg-zinc-800/30 border border-zinc-800 rounded-lg hover:border-zinc-700 transition-all duration-300"
className="group flex items-center gap-2 p-4 bg-card/30 border border-border rounded-lg hover:border-accent transition-all duration-300"
>
<MapPin className="h-4 w-4 text-zinc-500 group-hover:text-white transition-colors" />
<span className="text-zinc-300 group-hover:text-white transition-colors">
<MapPin className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<span className="text-foreground/80 group-hover:text-foreground transition-colors">
{city.name[locale as Locale]}
</span>
</Link>
@@ -451,12 +451,12 @@ export default async function ServicesPage({ params }: Props) {
{faqItems.map((item, index) => (
<div
key={index}
className="bg-zinc-800/30 border border-zinc-800 rounded-lg p-6"
className="bg-card/30 border border-border rounded-lg p-6"
>
<h3 className="text-lg font-semibold text-white mb-2">
<h3 className="text-lg font-semibold text-foreground mb-2">
{item.question}
</h3>
<p className="text-zinc-400">{item.answer}</p>
<p className="text-muted-foreground">{item.answer}</p>
</div>
))}
</div>
@@ -464,7 +464,7 @@ export default async function ServicesPage({ params }: Props) {
</section>
{/* CTA Section */}
<section className="py-24 bg-zinc-800/20">
<section className="py-24 bg-card/20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
<div className="relative aspect-video rounded-2xl overflow-hidden order-2 lg:order-1">
@@ -477,10 +477,10 @@ export default async function ServicesPage({ params }: Props) {
/>
</div>
<div className="text-center lg:text-left order-1 lg:order-2">
<h2 className="text-3xl font-bold text-white mb-4">
<h2 className="text-3xl font-bold text-foreground mb-4">
{locale === 'de' ? 'Bereit für Ihr Projekt?' : locale === 'sr' ? 'Spremni za vaš projekat?' : 'Ready for your project?'}
</h2>
<p className="text-zinc-400 mb-8">
<p className="text-muted-foreground mb-8">
{locale === 'de'
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
: locale === 'sr'
@@ -489,7 +489,7 @@ export default async function ServicesPage({ params }: Props) {
</p>
<Link
href={`/${locale}/contact`}
className="inline-flex items-center px-8 py-4 bg-white text-zinc-900 font-semibold rounded-lg hover:bg-zinc-100 transition-colors"
className="inline-flex items-center px-8 py-4 bg-primary text-background font-semibold rounded-lg hover:bg-accent transition-colors"
>
{locale === 'de' ? 'Kontakt aufnehmen' : locale === 'sr' ? 'Kontaktirajte me' : 'Get in touch'}
<ArrowRight className="h-5 w-5 ml-2" />
@@ -190,22 +190,22 @@ export default async function CityPage({ params }: Props) {
{/* Back Link */}
<Link
href={`/${locale}/leistungen`}
className="inline-flex items-center gap-2 text-zinc-400 hover:text-white transition-colors mb-8"
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors mb-8"
>
<ArrowLeft className="h-4 w-4" />
{locale === 'de' ? 'Alle Leistungen' : locale === 'sr' ? 'Sve usluge' : 'All Services'}
</Link>
<div className="flex items-center gap-2 text-zinc-400 mb-4">
<div className="flex items-center gap-2 text-muted-foreground mb-4">
<MapPin className="h-5 w-5" />
<span>{city.region}</span>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6">
<h1 className="text-4xl md:text-5xl font-bold text-foreground mb-6">
{pageTitle}
</h1>
<p className="text-xl text-zinc-400 mb-8">
<p className="text-xl text-muted-foreground mb-8">
{city.description[locale as Locale]}
</p>
@@ -214,7 +214,7 @@ export default async function CityPage({ params }: Props) {
{city.keywords[locale as Locale]?.map((keyword) => (
<span
key={keyword}
className="px-3 py-1 bg-zinc-800/50 border border-zinc-700 text-sm text-zinc-300 rounded-full"
className="px-3 py-1 bg-card/50 border border-border text-sm text-foreground/80 rounded-full"
>
{keyword}
</span>
@@ -224,9 +224,9 @@ export default async function CityPage({ params }: Props) {
</section>
{/* Services Section */}
<section className="py-16 bg-zinc-800/20">
<section className="py-16 bg-card/20">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de'
? `Meine Leistungen in ${city.name.de}`
: locale === 'sr'
@@ -240,16 +240,16 @@ export default async function CityPage({ params }: Props) {
<Link
key={service.slug}
href={`/${locale}/leistungen/${service.slug}`}
className="group flex items-start gap-4 p-6 bg-zinc-800/30 border border-zinc-800 rounded-xl hover:border-zinc-700 transition-all duration-300"
className="group flex items-start gap-4 p-6 bg-card/30 border border-border rounded-xl hover:border-accent transition-all duration-300"
>
<div className="p-2 bg-zinc-800/50 rounded-lg">
<IconComponent className="h-5 w-5 text-zinc-400 group-hover:text-white transition-colors" />
<div className="p-2 bg-card/50 rounded-lg">
<IconComponent className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors" />
</div>
<div>
<h3 className="text-lg font-semibold text-white mb-1">
<h3 className="text-lg font-semibold text-foreground mb-1">
{service.name[locale as Locale]}
</h3>
<p className="text-sm text-zinc-400">
<p className="text-sm text-muted-foreground">
{service.shortDescription[locale as Locale]}
</p>
</div>
@@ -263,7 +263,7 @@ export default async function CityPage({ params }: Props) {
{/* Why Choose Me Section */}
<section className="py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de'
? `Warum mit mir arbeiten in ${city.name.de}?`
: locale === 'sr'
@@ -300,10 +300,10 @@ export default async function CityPage({ params }: Props) {
).map((point, index) => (
<div
key={index}
className="flex items-start gap-3 p-4 bg-zinc-800/30 rounded-lg"
className="flex items-start gap-3 p-4 bg-card/30 rounded-lg"
>
<CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0 mt-0.5" />
<span className="text-zinc-300">{point}</span>
<span className="text-foreground/80">{point}</span>
</div>
))}
</div>
@@ -311,16 +311,16 @@ export default async function CityPage({ params }: Props) {
</section>
{/* Nearby Areas */}
<section className="py-16 bg-zinc-800/20">
<section className="py-16 bg-card/20">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-4">
<h2 className="text-2xl font-bold text-foreground mb-4">
{locale === 'de'
? 'Auch verfügbar in der Region'
: locale === 'sr'
? 'Takođe dostupno u regionu'
: 'Also available in the region'}
</h2>
<p className="text-zinc-400 mb-6">
<p className="text-muted-foreground mb-6">
{locale === 'de'
? `Neben ${city.name.de} arbeite ich auch mit Unternehmen in:`
: locale === 'sr'
@@ -331,7 +331,7 @@ export default async function CityPage({ params }: Props) {
{city.nearbyAreas.map((area) => (
<span
key={area}
className="px-4 py-2 bg-zinc-800/50 border border-zinc-700 text-zinc-300 rounded-lg"
className="px-4 py-2 bg-card/50 border border-border text-foreground/80 rounded-lg"
>
{area}
</span>
@@ -343,7 +343,7 @@ export default async function CityPage({ params }: Props) {
{/* Other Cities */}
<section className="py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? 'Weitere Standorte' : locale === 'sr' ? 'Ostale lokacije' : 'Other Locations'}
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
@@ -351,10 +351,10 @@ export default async function CityPage({ params }: Props) {
<Link
key={otherCity.slug}
href={`/${locale}/leistungen/standort/${otherCity.slug}`}
className="group flex items-center gap-2 p-4 bg-zinc-800/30 border border-zinc-800 rounded-lg hover:border-zinc-700 transition-all duration-300"
className="group flex items-center gap-2 p-4 bg-card/30 border border-border rounded-lg hover:border-accent transition-all duration-300"
>
<MapPin className="h-4 w-4 text-zinc-500 group-hover:text-white transition-colors" />
<span className="text-zinc-300 group-hover:text-white transition-colors">
<MapPin className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<span className="text-foreground/80 group-hover:text-foreground transition-colors">
{otherCity.name[locale as Locale]}
</span>
</Link>
@@ -364,9 +364,9 @@ export default async function CityPage({ params }: Props) {
</section>
{/* FAQ Section */}
<section className="py-16 bg-zinc-800/20">
<section className="py-16 bg-card/20">
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white text-center mb-8">
<h2 className="text-2xl font-bold text-foreground text-center mb-8">
{locale === 'de'
? `Häufige Fragen für ${city.name.de}`
: locale === 'sr'
@@ -377,12 +377,12 @@ export default async function CityPage({ params }: Props) {
{faqItems.map((item, index) => (
<div
key={index}
className="bg-zinc-800/30 border border-zinc-800 rounded-lg p-6"
className="bg-card/30 border border-border rounded-lg p-6"
>
<h3 className="text-lg font-semibold text-white mb-2">
<h3 className="text-lg font-semibold text-foreground mb-2">
{item.question}
</h3>
<p className="text-zinc-400">{item.answer}</p>
<p className="text-muted-foreground">{item.answer}</p>
</div>
))}
</div>
@@ -392,14 +392,14 @@ export default async function CityPage({ params }: Props) {
{/* CTA Section */}
<section className="py-24">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl font-bold text-white mb-4">
<h2 className="text-3xl font-bold text-foreground mb-4">
{locale === 'de'
? `Projekt in ${city.name.de} starten?`
: locale === 'sr'
? `Započnite projekat u ${city.name.sr}?`
: `Start a project in ${city.name.en}?`}
</h2>
<p className="text-zinc-400 mb-8">
<p className="text-muted-foreground mb-8">
{locale === 'de'
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
: locale === 'sr'
@@ -408,7 +408,7 @@ export default async function CityPage({ params }: Props) {
</p>
<Link
href={`/${locale}/contact`}
className="inline-flex items-center px-8 py-4 bg-white text-zinc-900 font-semibold rounded-lg hover:bg-zinc-100 transition-colors"
className="inline-flex items-center px-8 py-4 bg-primary text-background font-semibold rounded-lg hover:bg-accent transition-colors"
>
{locale === 'de' ? 'Kontakt aufnehmen' : locale === 'sr' ? 'Kontaktirajte me' : 'Get in touch'}
<ArrowRight className="h-5 w-5 ml-2" />
@@ -153,23 +153,23 @@ export default async function TechnologyPage({ params }: Props) {
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<Link
href={`/${locale}/leistungen`}
className="inline-flex items-center gap-2 text-zinc-400 hover:text-white transition-colors mb-8"
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors mb-8"
>
<ArrowLeft className="h-4 w-4" />
{locale === 'de' ? 'Alle Leistungen' : locale === 'sr' ? 'Sve usluge' : 'All Services'}
</Link>
<div className="flex items-center gap-4 mb-6">
<div className="p-4 bg-zinc-800/50 rounded-xl">
<Code className="h-8 w-8 text-white" />
<div className="p-4 bg-card/50 rounded-xl">
<Code className="h-8 w-8 text-foreground" />
</div>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6">
<h1 className="text-4xl md:text-5xl font-bold text-foreground mb-6">
{tech.title[locale as Locale]}
</h1>
<p className="text-xl text-zinc-400 mb-8">
<p className="text-xl text-muted-foreground mb-8">
{tech.description[locale as Locale]}
</p>
@@ -178,7 +178,7 @@ export default async function TechnologyPage({ params }: Props) {
{tech.keywords[locale as Locale].map((keyword) => (
<span
key={keyword}
className="px-3 py-1 bg-zinc-800/50 border border-zinc-700 text-sm text-zinc-300 rounded-full"
className="px-3 py-1 bg-card/50 border border-border text-sm text-foreground/80 rounded-full"
>
{keyword}
</span>
@@ -188,19 +188,19 @@ export default async function TechnologyPage({ params }: Props) {
</section>
{/* Features Section */}
<section className="py-16 bg-zinc-800/20">
<section className="py-16 bg-card/20">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? `${tech.name} Was ich anbiete` : locale === 'sr' ? `${tech.name} Šta nudim` : `${tech.name} What I offer`}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{tech.features[locale as Locale].map((feature, index) => (
<div
key={index}
className="flex items-start gap-3 p-4 bg-zinc-800/30 rounded-lg"
className="flex items-start gap-3 p-4 bg-card/30 rounded-lg"
>
<CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0 mt-0.5" />
<span className="text-zinc-300">{feature}</span>
<span className="text-foreground/80">{feature}</span>
</div>
))}
</div>
@@ -210,20 +210,20 @@ export default async function TechnologyPage({ params }: Props) {
{/* Use Cases Section */}
<section className="py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? 'Anwendungsbeispiele' : locale === 'sr' ? 'Primeri primene' : 'Use Cases'}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{tech.useCases[locale as Locale].map((useCase, index) => (
<div
key={index}
className="p-6 bg-zinc-800/30 border border-zinc-800 rounded-xl"
className="p-6 bg-card/30 border border-border rounded-xl"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-zinc-700 rounded-full flex items-center justify-center text-sm font-semibold text-white">
<div className="w-8 h-8 bg-accent rounded-full flex items-center justify-center text-sm font-semibold text-foreground">
{index + 1}
</div>
<span className="text-zinc-200">{useCase}</span>
<span className="text-foreground/90">{useCase}</span>
</div>
</div>
))}
@@ -233,9 +233,9 @@ export default async function TechnologyPage({ params }: Props) {
{/* Related Services */}
{relatedServiceData.length > 0 && (
<section className="py-16 bg-zinc-800/20">
<section className="py-16 bg-card/20">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? 'Passende Leistungen' : locale === 'sr' ? 'Odgovarajuće usluge' : 'Related Services'}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
@@ -243,16 +243,16 @@ export default async function TechnologyPage({ params }: Props) {
<Link
key={service.slug}
href={`/${locale}/leistungen/${service.slug}`}
className="group flex items-start gap-4 p-6 bg-zinc-800/30 border border-zinc-800 rounded-xl hover:border-zinc-700 transition-all duration-300"
className="group flex items-start gap-4 p-6 bg-card/30 border border-border rounded-xl hover:border-accent transition-all duration-300"
>
<div className="p-2 bg-zinc-800/50 rounded-lg">
<Zap className="h-5 w-5 text-zinc-400 group-hover:text-white transition-colors" />
<div className="p-2 bg-card/50 rounded-lg">
<Zap className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors" />
</div>
<div>
<h3 className="text-lg font-semibold text-white mb-1">
<h3 className="text-lg font-semibold text-foreground mb-1">
{service.name[locale as Locale]}
</h3>
<p className="text-sm text-zinc-400">
<p className="text-sm text-muted-foreground">
{service.shortDescription[locale as Locale]}
</p>
</div>
@@ -266,7 +266,7 @@ export default async function TechnologyPage({ params }: Props) {
{/* Other Technologies */}
<section className="py-16">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? 'Weitere Technologien' : locale === 'sr' ? 'Ostale Tehnologije' : 'Other Technologies'}
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
@@ -274,10 +274,10 @@ export default async function TechnologyPage({ params }: Props) {
<Link
key={otherTech.slug}
href={`/${locale}/leistungen/technologie/${otherTech.slug}`}
className="group flex items-center gap-2 p-4 bg-zinc-800/30 border border-zinc-800 rounded-lg hover:border-zinc-700 transition-all duration-300"
className="group flex items-center gap-2 p-4 bg-card/30 border border-border rounded-lg hover:border-accent transition-all duration-300"
>
<Code className="h-4 w-4 text-zinc-500 group-hover:text-white transition-colors" />
<span className="text-zinc-300 group-hover:text-white transition-colors">
<Code className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<span className="text-foreground/80 group-hover:text-foreground transition-colors">
{otherTech.name}
</span>
</Link>
@@ -287,9 +287,9 @@ export default async function TechnologyPage({ params }: Props) {
</section>
{/* Cities Cross-Link */}
<section className="py-16 bg-zinc-800/20">
<section className="py-16 bg-card/20">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white mb-8">
<h2 className="text-2xl font-bold text-foreground mb-8">
{locale === 'de' ? `${tech.name} Entwickler in Ihrer Stadt` : locale === 'sr' ? `${tech.name} Developer u vašem gradu` : `${tech.name} Developer in your city`}
</h2>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
@@ -297,10 +297,10 @@ export default async function TechnologyPage({ params }: Props) {
<Link
key={city.slug}
href={`/${locale}/leistungen/standort/${city.slug}`}
className="group flex items-center gap-2 p-4 bg-zinc-800/30 border border-zinc-800 rounded-lg hover:border-zinc-700 transition-all duration-300"
className="group flex items-center gap-2 p-4 bg-card/30 border border-border rounded-lg hover:border-accent transition-all duration-300"
>
<MapPin className="h-4 w-4 text-zinc-500 group-hover:text-white transition-colors" />
<span className="text-zinc-300 group-hover:text-white transition-colors">
<MapPin className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<span className="text-foreground/80 group-hover:text-foreground transition-colors">
{city.name[locale as Locale]}
</span>
</Link>
@@ -312,19 +312,19 @@ export default async function TechnologyPage({ params }: Props) {
{/* FAQ Section */}
<section className="py-16">
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-white text-center mb-8">
<h2 className="text-2xl font-bold text-foreground text-center mb-8">
{locale === 'de' ? `Häufige Fragen zu ${tech.name}` : locale === 'sr' ? `Česta pitanja o ${tech.name}` : `FAQ about ${tech.name}`}
</h2>
<div className="space-y-4">
{faqItems.map((item, index) => (
<div
key={index}
className="bg-zinc-800/30 border border-zinc-800 rounded-lg p-6"
className="bg-card/30 border border-border rounded-lg p-6"
>
<h3 className="text-lg font-semibold text-white mb-2">
<h3 className="text-lg font-semibold text-foreground mb-2">
{item.question}
</h3>
<p className="text-zinc-400">{item.answer}</p>
<p className="text-muted-foreground">{item.answer}</p>
</div>
))}
</div>
@@ -334,14 +334,14 @@ export default async function TechnologyPage({ params }: Props) {
{/* CTA Section */}
<section className="py-24">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl font-bold text-white mb-4">
<h2 className="text-3xl font-bold text-foreground mb-4">
{locale === 'de'
? `${tech.name}-Projekt starten?`
: locale === 'sr'
? `Započnite ${tech.name} projekat?`
: `Start a ${tech.name} project?`}
</h2>
<p className="text-zinc-400 mb-8">
<p className="text-muted-foreground mb-8">
{locale === 'de'
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
: locale === 'sr'
@@ -350,7 +350,7 @@ export default async function TechnologyPage({ params }: Props) {
</p>
<Link
href={`/${locale}/contact`}
className="inline-flex items-center px-8 py-4 bg-white text-zinc-900 font-semibold rounded-lg hover:bg-zinc-100 transition-colors"
className="inline-flex items-center px-8 py-4 bg-primary text-background font-semibold rounded-lg hover:bg-accent transition-colors"
>
{locale === 'de' ? 'Kontakt aufnehmen' : locale === 'sr' ? 'Kontaktirajte me' : 'Get in touch'}
<ArrowRight className="h-5 w-5 ml-2" />
+34 -34
View File
@@ -467,7 +467,7 @@ export default async function ProjectPage({ params }: Props) {
<div className="mb-8">
<Link
href={`/${locale}/portfolio`}
className="inline-flex items-center gap-2 text-zinc-400 hover:text-white transition-colors duration-300"
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors duration-300"
>
<ArrowLeft className="h-4 w-4" />
<span>{t('backToPortfolio')}</span>
@@ -476,7 +476,7 @@ export default async function ProjectPage({ params }: Props) {
{/* Project Header */}
<div className="mb-12">
<div className="flex flex-wrap items-center gap-4 mb-6 text-zinc-400">
<div className="flex flex-wrap items-center gap-4 mb-6 text-muted-foreground">
{project.meta.date && (
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4" />
@@ -497,18 +497,18 @@ export default async function ProjectPage({ params }: Props) {
)}
</div>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-8">
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-8">
{project.meta.title}
</h1>
<p className="text-xl text-zinc-400">
<p className="text-xl text-muted-foreground">
{project.meta.description}
</p>
</div>
{/* Cover Image */}
<div className="mb-12">
<div className="relative aspect-video rounded-lg overflow-hidden bg-zinc-800">
<div className="relative aspect-video rounded-lg overflow-hidden bg-card">
<Image
src={`/images/projects/${slug}/cover.avif`}
alt={project.meta.title}
@@ -517,28 +517,28 @@ export default async function ProjectPage({ params }: Props) {
className="object-cover"
priority
/>
<div className="absolute inset-0 bg-gradient-to-t from-zinc-900/50 to-transparent opacity-50" />
<div className="absolute inset-0 bg-gradient-to-t from-background/50 to-transparent opacity-50" />
</div>
</div>
{/* Project Content */}
<div className="prose prose-invert max-w-none">
{/* Intro */}
<div className="mb-12 p-6 bg-zinc-800/30 rounded-lg border border-zinc-800">
<p className="text-lg text-zinc-300 m-0">
<div className="mb-12 p-6 bg-card/30 rounded-lg border border-border">
<p className="text-lg text-foreground/80 m-0">
{project.content.intro}
</p>
</div>
{/* Challenge Section */}
<section className="mb-12">
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.challenge.title}</h2>
<p className="text-zinc-300 mb-4">{project.content.challenge.description}</p>
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.challenge.title}</h2>
<p className="text-foreground/80 mb-4">{project.content.challenge.description}</p>
{project.content.challenge.points && (
<ul className="space-y-2">
{project.content.challenge.points.map((point, index) => (
<li key={index} className="text-zinc-300 flex items-start gap-2">
<span className="text-zinc-500"></span>
<li key={index} className="text-foreground/80 flex items-start gap-2">
<span className="text-muted-foreground"></span>
{point}
</li>
))}
@@ -548,13 +548,13 @@ export default async function ProjectPage({ params }: Props) {
{/* Solution Section */}
<section className="mb-12">
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.solution.title}</h2>
<p className="text-zinc-300 mb-4">{project.content.solution.description}</p>
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.solution.title}</h2>
<p className="text-foreground/80 mb-4">{project.content.solution.description}</p>
{project.content.solution.points && (
<ul className="space-y-2">
{project.content.solution.points.map((point, index) => (
<li key={index} className="text-zinc-300 flex items-start gap-2">
<span className="text-zinc-500"></span>
<li key={index} className="text-foreground/80 flex items-start gap-2">
<span className="text-muted-foreground"></span>
{point}
</li>
))}
@@ -565,20 +565,20 @@ export default async function ProjectPage({ params }: Props) {
{/* Technical Section */}
{project.content.technical && (
<section className="mb-12">
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.technical.title}</h2>
<p className="text-zinc-300 mb-4">{project.content.technical.description}</p>
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.technical.title}</h2>
<p className="text-foreground/80 mb-4">{project.content.technical.description}</p>
{project.content.technical.points && (
<ul className="space-y-2">
{project.content.technical.points.map((point, index) => (
<li key={index} className="text-zinc-300 flex items-start gap-2">
<span className="text-zinc-500"></span>
<li key={index} className="text-foreground/80 flex items-start gap-2">
<span className="text-muted-foreground"></span>
{point}
</li>
))}
</ul>
)}
{project.content.technical.code && (
<pre className="bg-zinc-800/50 p-4 rounded-lg overflow-x-auto mt-4">
<pre className="bg-card/50 p-4 rounded-lg overflow-x-auto mt-4">
<code className="text-sm">{project.content.technical.code}</code>
</pre>
)}
@@ -588,13 +588,13 @@ export default async function ProjectPage({ params }: Props) {
{/* Implementation Section */}
{project.content.implementation && (
<section className="mb-12">
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.implementation.title}</h2>
<p className="text-zinc-300 mb-4">{project.content.implementation.description}</p>
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.implementation.title}</h2>
<p className="text-foreground/80 mb-4">{project.content.implementation.description}</p>
{project.content.implementation.points && (
<ul className="space-y-2">
{project.content.implementation.points.map((point, index) => (
<li key={index} className="text-zinc-300 flex items-start gap-2">
<span className="text-zinc-500"></span>
<li key={index} className="text-foreground/80 flex items-start gap-2">
<span className="text-muted-foreground"></span>
{point}
</li>
))}
@@ -605,13 +605,13 @@ export default async function ProjectPage({ params }: Props) {
{/* Results Section */}
<section className="mb-12">
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.results.title}</h2>
<p className="text-zinc-300 mb-4">{project.content.results.description}</p>
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.results.title}</h2>
<p className="text-foreground/80 mb-4">{project.content.results.description}</p>
{project.content.results.points && (
<ul className="space-y-2">
{project.content.results.points.map((point, index) => (
<li key={index} className="text-zinc-300 flex items-start gap-2">
<span className="text-zinc-500"></span>
<li key={index} className="text-foreground/80 flex items-start gap-2">
<span className="text-muted-foreground"></span>
{point}
</li>
))}
@@ -621,8 +621,8 @@ export default async function ProjectPage({ params }: Props) {
{/* Conclusion */}
{project.content.conclusion && (
<section className="mb-12 p-6 bg-zinc-800/30 rounded-lg border border-zinc-800">
<p className="text-lg text-zinc-300 m-0">
<section className="mb-12 p-6 bg-card/30 rounded-lg border border-border">
<p className="text-lg text-foreground/80 m-0">
{project.content.conclusion}
</p>
</section>
@@ -631,14 +631,14 @@ export default async function ProjectPage({ params }: Props) {
{/* Technologies */}
{project.meta.technologies && (
<div className="mt-12 pt-6 border-t border-zinc-800">
<div className="mt-12 pt-6 border-t border-border">
<div className="flex items-center gap-4">
<Tag className="h-4 w-4 text-zinc-400" />
<Tag className="h-4 w-4 text-muted-foreground" />
<div className="flex flex-wrap gap-2">
{project.meta.technologies.map((tech, index) => (
<span
key={index}
className="px-3 py-1 bg-zinc-800/50 border border-zinc-800 rounded-full text-sm text-zinc-300"
className="px-3 py-1 bg-card/50 border border-border rounded-full text-sm text-foreground/80"
>
{tech}
</span>
+1 -1
View File
@@ -216,7 +216,7 @@ export default async function PortfolioPage({ params, searchParams }: Props) {
<h1 className="text-3xl font-bold text-white mb-4">
{t('sections.title')}
</h1>
<p className="text-zinc-400 max-w-2xl mx-auto">
<p className="text-muted-foreground max-w-2xl mx-auto">
{t('sections.subtitle')}
</p>
</div>
+54 -54
View File
@@ -481,28 +481,28 @@ export default async function PrivacyPage({ params }: Props) {
<nav className="mb-8">
<Link
href={`/${locale}`}
className="text-zinc-400 hover:text-white transition-colors"
className="text-muted-foreground hover:text-foreground transition-colors"
>
Home
</Link>
<span className="mx-2 text-zinc-600">/</span>
<span className="text-white">{c.title}</span>
<span className="mx-2 text-muted-foreground/60">/</span>
<span className="text-foreground">{c.title}</span>
</nav>
<h1 className="text-4xl font-bold text-white mb-2">{c.title}</h1>
<p className="text-zinc-400 mb-8">{c.lastUpdated}</p>
<h1 className="text-4xl font-bold text-foreground mb-2">{c.title}</h1>
<p className="text-muted-foreground mb-8">{c.lastUpdated}</p>
<div className="prose prose-invert max-w-none space-y-8">
{/* Introduction */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.intro.title}</h2>
<p className="text-zinc-300">{c.intro.content}</p>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.intro.title}</h2>
<p className="text-foreground/80">{c.intro.content}</p>
</section>
{/* Data Controller */}
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
<h2 className="text-2xl font-semibold text-white mb-4">{c.controller.title}</h2>
<div className="text-zinc-300 space-y-1">
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.controller.title}</h2>
<div className="text-foreground/80 space-y-1">
{c.controller.content.map((line, index) => (
<p key={index}>{line}</p>
))}
@@ -511,13 +511,13 @@ export default async function PrivacyPage({ params }: Props) {
{/* Data Collection */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.dataCollection.title}</h2>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.dataCollection.title}</h2>
{c.dataCollection.sections.map((section, index) => (
<div key={index} className="mb-6">
<h3 className="text-xl font-medium text-white mb-2">{section.title}</h3>
<p className="text-zinc-300 mb-2">{section.content}</p>
<h3 className="text-xl font-medium text-foreground mb-2">{section.title}</h3>
<p className="text-foreground/80 mb-2">{section.content}</p>
{section.items && (
<ul className="list-disc list-inside text-zinc-300 space-y-1 ml-4">
<ul className="list-disc list-inside text-foreground/80 space-y-1 ml-4">
{section.items.map((item, itemIndex) => (
<li key={itemIndex}>{item}</li>
))}
@@ -529,46 +529,46 @@ export default async function PrivacyPage({ params }: Props) {
{/* Cookies */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.cookies.title}</h2>
<p className="text-zinc-300 mb-4">{c.cookies.intro}</p>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.cookies.title}</h2>
<p className="text-foreground/80 mb-4">{c.cookies.intro}</p>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="border-b border-zinc-700">
<th className="py-2 pr-4 text-white font-semibold">Cookie</th>
<th className="py-2 pr-4 text-white font-semibold">{locale === 'de' ? 'Zweck' : locale === 'sr' ? 'Svrha' : 'Purpose'}</th>
<th className="py-2 text-white font-semibold">{locale === 'de' ? 'Dauer' : locale === 'sr' ? 'Trajanje' : 'Duration'}</th>
<tr className="border-b border-border">
<th className="py-2 pr-4 text-foreground font-semibold">Cookie</th>
<th className="py-2 pr-4 text-foreground font-semibold">{locale === 'de' ? 'Zweck' : locale === 'sr' ? 'Svrha' : 'Purpose'}</th>
<th className="py-2 text-foreground font-semibold">{locale === 'de' ? 'Dauer' : locale === 'sr' ? 'Trajanje' : 'Duration'}</th>
</tr>
</thead>
<tbody>
{c.cookies.types.map((cookie, index) => (
<tr key={index} className="border-b border-zinc-800">
<td className="py-3 pr-4 text-zinc-300">{cookie.name}</td>
<td className="py-3 pr-4 text-zinc-400">{cookie.purpose}</td>
<td className="py-3 text-zinc-400">{cookie.duration}</td>
<tr key={index} className="border-b border-border">
<td className="py-3 pr-4 text-foreground/80">{cookie.name}</td>
<td className="py-3 pr-4 text-muted-foreground">{cookie.purpose}</td>
<td className="py-3 text-muted-foreground">{cookie.duration}</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="text-zinc-400 mt-4 text-sm">{c.cookies.management}</p>
<p className="text-muted-foreground mt-4 text-sm">{c.cookies.management}</p>
</section>
{/* Third-Party Services */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.thirdParty.title}</h2>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.thirdParty.title}</h2>
<div className="space-y-6">
{c.thirdParty.services.map((service, index) => (
<div key={index} className="bg-zinc-900/30 p-5 rounded-lg ring-1 ring-zinc-800">
<h3 className="text-xl font-medium text-white mb-2">{service.name}</h3>
<p className="text-zinc-300 mb-2">
<strong className="text-zinc-200">{locale === 'de' ? 'Zweck:' : locale === 'sr' ? 'Svrha:' : 'Purpose:'}</strong> {service.purpose}
<div key={index} className="bg-background/30 p-5 rounded-lg ring-1 ring-border">
<h3 className="text-xl font-medium text-foreground mb-2">{service.name}</h3>
<p className="text-foreground/80 mb-2">
<strong className="text-foreground/90">{locale === 'de' ? 'Zweck:' : locale === 'sr' ? 'Svrha:' : 'Purpose:'}</strong> {service.purpose}
</p>
<p className="text-zinc-300 mb-2">
<strong className="text-zinc-200">{locale === 'de' ? 'Verarbeitete Daten:' : locale === 'sr' ? 'Obrađeni podaci:' : 'Data processed:'}</strong> {service.dataProcessed}
<p className="text-foreground/80 mb-2">
<strong className="text-foreground/90">{locale === 'de' ? 'Verarbeitete Daten:' : locale === 'sr' ? 'Obrađeni podaci:' : 'Data processed:'}</strong> {service.dataProcessed}
</p>
<p className="text-zinc-400 text-sm">
<strong className="text-zinc-300">{locale === 'de' ? 'Datenschutz:' : locale === 'sr' ? 'Privatnost:' : 'Privacy:'}</strong>{' '}
<p className="text-muted-foreground text-sm">
<strong className="text-foreground/80">{locale === 'de' ? 'Datenschutz:' : locale === 'sr' ? 'Privatnost:' : 'Privacy:'}</strong>{' '}
<a
href={service.privacy}
target="_blank"
@@ -585,27 +585,27 @@ export default async function PrivacyPage({ params }: Props) {
{/* Legal Basis */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.legalBasis.title}</h2>
<p className="text-zinc-300 mb-4">{c.legalBasis.content}</p>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.legalBasis.title}</h2>
<p className="text-foreground/80 mb-4">{c.legalBasis.content}</p>
<ul className="space-y-2">
{c.legalBasis.bases.map((item, index) => (
<li key={index} className="text-zinc-300">
<strong className="text-white">{item.basis}:</strong> {item.description}
<li key={index} className="text-foreground/80">
<strong className="text-foreground">{item.basis}:</strong> {item.description}
</li>
))}
</ul>
</section>
{/* User Rights */}
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
<h2 className="text-2xl font-semibold text-white mb-4">{c.rights.title}</h2>
<p className="text-zinc-300 mb-4">{c.rights.intro}</p>
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.rights.title}</h2>
<p className="text-foreground/80 mb-4">{c.rights.intro}</p>
<ul className="space-y-3">
{c.rights.items.map((item, index) => (
<li key={index} className="text-zinc-300">
<strong className="text-white">{item.right}</strong>
<li key={index} className="text-foreground/80">
<strong className="text-foreground">{item.right}</strong>
<br />
<span className="text-zinc-400">{item.description}</span>
<span className="text-muted-foreground">{item.description}</span>
</li>
))}
</ul>
@@ -614,26 +614,26 @@ export default async function PrivacyPage({ params }: Props) {
{/* Data Retention */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.dataRetention.title}</h2>
<p className="text-zinc-300">{c.dataRetention.content}</p>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.dataRetention.title}</h2>
<p className="text-foreground/80">{c.dataRetention.content}</p>
</section>
{/* Data Security */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.dataSecurity.title}</h2>
<p className="text-zinc-300">{c.dataSecurity.content}</p>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.dataSecurity.title}</h2>
<p className="text-foreground/80">{c.dataSecurity.content}</p>
</section>
{/* Changes */}
<section>
<h2 className="text-2xl font-semibold text-white mb-4">{c.changes.title}</h2>
<p className="text-zinc-300">{c.changes.content}</p>
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.changes.title}</h2>
<p className="text-foreground/80">{c.changes.content}</p>
</section>
{/* Contact */}
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
<h2 className="text-2xl font-semibold text-white mb-4">{c.contact.title}</h2>
<div className="text-zinc-300 space-y-1">
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.contact.title}</h2>
<div className="text-foreground/80 space-y-1">
{c.contact.content.map((line, index) => (
<p key={index}>{line}</p>
))}
@@ -642,7 +642,7 @@ export default async function PrivacyPage({ params }: Props) {
{/* Link to Imprint */}
<section>
<p className="text-zinc-400">
<p className="text-muted-foreground">
{locale === 'de'
? 'Weitere rechtliche Informationen finden Sie im'
: locale === 'sr'
+13 -13
View File
@@ -271,24 +271,24 @@ export default async function TermsPage({ params }: Props) {
<nav className="mb-8">
<Link
href={`/${locale}`}
className="text-zinc-400 hover:text-white transition-colors"
className="text-muted-foreground hover:text-foreground transition-colors"
>
Home
</Link>
<span className="mx-2 text-zinc-600">/</span>
<span className="text-white">{c.title}</span>
<span className="mx-2 text-muted-foreground/60">/</span>
<span className="text-foreground">{c.title}</span>
</nav>
<h1 className="text-4xl font-bold text-white mb-2">{c.title}</h1>
<p className="text-zinc-400 mb-8">{c.lastUpdated}</p>
<h1 className="text-4xl font-bold text-foreground mb-2">{c.title}</h1>
<p className="text-muted-foreground mb-8">{c.lastUpdated}</p>
<div className="prose prose-invert max-w-none space-y-8">
{c.sections.map((section, index) => (
<section key={index}>
<h2 className="text-2xl font-semibold text-white mb-4">{section.title}</h2>
<p className="text-zinc-300">{section.content}</p>
<h2 className="text-2xl font-semibold text-foreground mb-4">{section.title}</h2>
<p className="text-foreground/80">{section.content}</p>
{section.items && (
<ul className="list-disc list-inside text-zinc-300 space-y-1 ml-4 mt-3">
<ul className="list-disc list-inside text-foreground/80 space-y-1 ml-4 mt-3">
{section.items.map((item, itemIndex) => (
<li key={itemIndex}>{item}</li>
))}
@@ -298,9 +298,9 @@ export default async function TermsPage({ params }: Props) {
))}
{/* Contact */}
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
<h2 className="text-2xl font-semibold text-white mb-4">{c.contact.title}</h2>
<div className="text-zinc-300 space-y-1">
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.contact.title}</h2>
<div className="text-foreground/80 space-y-1">
{c.contact.content.map((line, index) => (
<p key={index}>{line}</p>
))}
@@ -309,7 +309,7 @@ export default async function TermsPage({ params }: Props) {
{/* Privacy link */}
<section>
<p className="text-zinc-400">
<p className="text-muted-foreground">
{c.privacyNote.text}{' '}
<Link
href={`/${locale}/privacy`}
@@ -323,7 +323,7 @@ export default async function TermsPage({ params }: Props) {
{/* Copyright */}
<section>
<p className="text-zinc-400">
<p className="text-muted-foreground">
&copy; {new Date().getFullYear()} Damjan Savić.{' '}
{locale === 'de' ? 'Alle Rechte vorbehalten.' : locale === 'sr' ? 'Sva prava zadržana.' : 'All rights reserved.'}
</p>
+6 -2
View File
@@ -1,4 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="6" fill="#0a0a0a"/>
<text x="16" y="22" font-family="system-ui, sans-serif" font-size="14" font-weight="600" fill="#ffffff" text-anchor="middle">DS</text>
<rect width="32" height="32" rx="6" fill="#181C14"/>
<g transform="translate(2.5, 4.5) scale(0.027)">
<path fill="#ECDFCC" d="M965.47,97.09l-461.75,786.4L34.81,97.09h930.65M1000,77.5H0l17.59,29.5,468.89,786.4,17.35,29.1,17.15-29.22L982.73,106.88l17.27-29.38h0Z"/>
<polygon fill="#ECDFCC" points="755.12 213.96 524.03 214.02 544.73 249.03 734.41 249 830.87 249 782.41 330.91 759.95 368.91 687.8 490.96 639.56 572.54 591.35 490.96 519.2 368.98 615.65 368.95 639.56 409.41 663.47 368.95 685.97 330.91 593.15 330.95 496.71 330.95 448.28 249.05 427.57 214.02 379.13 132.11 475.57 132.11 803.57 132.05 900.03 132.02 851.57 213.96 755.12 213.96"/>
<polygon fill="#ECDFCC" points="618.8 607.63 570.59 689.19 548.33 726.83 500.09 808.39 451.88 726.83 240.28 369.01 217.81 331 169.39 249.08 265.85 249.08 314.27 330.97 336.73 369.01 500.09 645.29 522.35 607.63 381.23 368.98 358.76 330.97 310.33 249.05 289.61 214.06 245.11 214.06 148.69 214.08 100.24 132.17 196.69 132.14 241.17 132.14 337.64 132.11 386.05 214.02 406.76 249.05 455.21 330.97 477.68 368.98 570.59 526.08 618.8 607.63"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 1.1 KiB

+3 -3
View File
@@ -6,16 +6,16 @@ export default async function NotFound() {
const t = await getTranslations('pages.notfound');
return (
<div className="bg-zinc-900 text-zinc-50 min-h-screen flex items-center justify-center">
<div className="bg-background text-foreground min-h-screen flex items-center justify-center">
<div className="text-center px-4">
<h1 className="text-6xl font-bold mb-4">404</h1>
<h2 className="text-2xl font-semibold mb-4">{t('title')}</h2>
<p className="text-zinc-400 mb-8">
<p className="text-muted-foreground mb-8">
{t('description')}
</p>
<Link
href={`/${defaultLocale}`}
className="px-6 py-3 bg-white text-zinc-900 hover:bg-zinc-200 rounded-lg transition-colors inline-block font-medium"
className="px-6 py-3 bg-primary text-background hover:bg-accent rounded-lg transition-colors inline-block font-medium"
>
{t('backHome')}
</Link>
+15 -15
View File
@@ -24,14 +24,14 @@ const Hero = () => {
};
return (
<section className="relative min-h-screen text-white overflow-hidden">
<section className="relative min-h-screen text-foreground overflow-hidden">
<div className="flex flex-col items-center justify-center min-h-screen px-4 relative z-10">
{/* Title */}
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="text-white text-3xl font-bold tracking-wider mb-4"
className="text-foreground text-3xl font-bold tracking-wider mb-4"
>
{t('title')}
</motion.h1>
@@ -43,7 +43,7 @@ const Hero = () => {
transition={{ duration: 0.5, delay: 0.2 }}
className="max-w-3xl text-center mb-12"
>
<p className="text-zinc-400 text-lg mb-8">
<p className="text-muted-foreground text-lg mb-8">
{t('description')}
</p>
</motion.div>
@@ -55,30 +55,30 @@ const Hero = () => {
transition={{ duration: 0.5, delay: 0.3 }}
className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12"
>
<div className="flex flex-col items-center p-6 border border-zinc-800 rounded-lg bg-zinc-900/50">
<Terminal className="h-8 w-8 mb-4 text-white" />
<div className="flex flex-col items-center p-6 border border-border rounded-lg bg-background/50">
<Terminal className="h-8 w-8 mb-4 text-foreground" />
<h3 className="text-lg font-semibold mb-2">
{t('expertise.processAutomation.title')}
</h3>
<p className="text-zinc-400 text-center text-sm">
<p className="text-muted-foreground text-center text-sm">
{t('expertise.processAutomation.description')}
</p>
</div>
<div className="flex flex-col items-center p-6 border border-zinc-800 rounded-lg bg-zinc-900/50">
<Globe2 className="h-8 w-8 mb-4 text-white" />
<div className="flex flex-col items-center p-6 border border-border rounded-lg bg-background/50">
<Globe2 className="h-8 w-8 mb-4 text-foreground" />
<h3 className="text-lg font-semibold mb-2">
{t('expertise.systemIntegration.title')}
</h3>
<p className="text-zinc-400 text-center text-sm">
<p className="text-muted-foreground text-center text-sm">
{t('expertise.systemIntegration.description')}
</p>
</div>
<div className="flex flex-col items-center p-6 border border-zinc-800 rounded-lg bg-zinc-900/50">
<Code2 className="h-8 w-8 mb-4 text-white" />
<div className="flex flex-col items-center p-6 border border-border rounded-lg bg-background/50">
<Code2 className="h-8 w-8 mb-4 text-foreground" />
<h3 className="text-lg font-semibold mb-2">
{t('expertise.customDevelopment.title')}
</h3>
<p className="text-zinc-400 text-center text-sm">
<p className="text-muted-foreground text-center text-sm">
{t('expertise.customDevelopment.description')}
</p>
</div>
@@ -94,9 +94,9 @@ const Hero = () => {
{getLanguages().map(([key, value]) => (
<div
key={key}
className="flex flex-col items-center p-3 border border-zinc-800 rounded-lg bg-zinc-900/50 hover:bg-zinc-800/50 transition-colors duration-200"
className="flex flex-col items-center p-3 border border-border rounded-lg bg-background/50 hover:bg-card/50 transition-colors duration-200"
>
<span className="text-white font-medium">{value as string}</span>
<span className="text-foreground font-medium">{value as string}</span>
</div>
))}
</motion.div>
@@ -107,7 +107,7 @@ const Hero = () => {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.5 }}
onClick={scrollToSkills}
className="mt-8 px-6 py-3 bg-white text-zinc-900 rounded-lg font-medium hover:bg-zinc-100 transition-colors duration-200"
className="mt-8 px-6 py-3 bg-primary text-primary-foreground rounded-lg font-medium hover:bg-accent transition-colors duration-200"
>
{t('aboutMe')}
</motion.button>
+24 -24
View File
@@ -43,10 +43,10 @@ const Journey = async () => {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section Header */}
<div className="text-center mb-16">
<h2 className="text-3xl font-bold text-white mb-4">
<h2 className="text-3xl font-bold text-foreground mb-4">
{t('title')}
</h2>
<p className="text-zinc-400 max-w-2xl mx-auto">
<p className="text-muted-foreground max-w-2xl mx-auto">
{t('subtitle')}
</p>
</div>
@@ -54,33 +54,33 @@ const Journey = async () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
{/* Education Column */}
<div className="space-y-6">
<div className="p-6 bg-zinc-800/30 border border-zinc-800 rounded-lg">
<div className="p-6 bg-card/30 border border-border rounded-lg">
<div className="flex items-center gap-3 mb-6">
<GraduationCap className="h-5 w-5 text-zinc-400" />
<h3 className="text-lg font-medium text-white">
<GraduationCap className="h-5 w-5 text-muted-foreground" />
<h3 className="text-lg font-medium text-foreground">
{t('education.title')}
</h3>
</div>
<div className="space-y-4">
<div>
<h4 className="text-white font-medium">
<h4 className="text-foreground font-medium">
{t('education.degrees.masters.degree')}
</h4>
<p className="text-zinc-400 text-sm">
<p className="text-muted-foreground text-sm">
{t('education.degrees.masters.university')}
</p>
<p className="text-zinc-500 text-sm">
<p className="text-muted-foreground/70 text-sm">
{t('education.degrees.masters.period')}
</p>
</div>
<div>
<h4 className="text-white font-medium">
<h4 className="text-foreground font-medium">
{t('education.degrees.bachelors.degree')}
</h4>
<p className="text-zinc-400 text-sm">
<p className="text-muted-foreground text-sm">
{t('education.degrees.bachelors.university')}
</p>
<p className="text-zinc-500 text-sm">
<p className="text-muted-foreground/70 text-sm">
{t('education.degrees.bachelors.period')}
</p>
</div>
@@ -88,18 +88,18 @@ const Journey = async () => {
</div>
{/* Languages */}
<div className="p-6 bg-zinc-800/30 border border-zinc-800 rounded-lg">
<div className="p-6 bg-card/30 border border-border rounded-lg">
<div className="flex items-center gap-3 mb-6">
<Globe className="h-5 w-5 text-zinc-400" />
<h3 className="text-lg font-medium text-white">
<Globe className="h-5 w-5 text-muted-foreground" />
<h3 className="text-lg font-medium text-foreground">
{t('languages.title')}
</h3>
</div>
<div className="grid grid-cols-2 gap-4">
{languageItems.map(([key, item]) => (
<div key={key}>
<div className="text-zinc-300">{item.name}</div>
<div className="text-zinc-500 text-sm">{item.level}</div>
<div className="text-foreground/80">{item.name}</div>
<div className="text-muted-foreground/70 text-sm">{item.level}</div>
</div>
))}
</div>
@@ -111,21 +111,21 @@ const Journey = async () => {
{workPositions.map((job, index) => (
<div
key={index}
className="p-6 bg-zinc-800/30 border border-zinc-800 rounded-lg"
className="p-6 bg-card/30 border border-border rounded-lg"
>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-medium text-white">{job.title}</h3>
<span className="text-zinc-500 text-sm">{job.period}</span>
<h3 className="text-lg font-medium text-foreground">{job.title}</h3>
<span className="text-muted-foreground/70 text-sm">{job.period}</span>
</div>
<div className="flex items-center gap-2 mb-4">
<Briefcase className="h-4 w-4 text-zinc-400" />
<p className="text-zinc-300">{job.company}</p>
<span className="text-zinc-500"> {job.location}</span>
<Briefcase className="h-4 w-4 text-muted-foreground" />
<p className="text-foreground/80">{job.company}</p>
<span className="text-muted-foreground/70"> {job.location}</span>
</div>
<ul className="space-y-2">
{job.highlights.map((highlight, idx) => (
<li key={idx} className="text-zinc-400 text-sm flex items-start gap-2">
<span className="text-zinc-500"></span>
<li key={idx} className="text-muted-foreground text-sm flex items-start gap-2">
<span className="text-muted-foreground/70"></span>
{highlight}
</li>
))}
+10 -10
View File
@@ -77,21 +77,21 @@ const Skills = () => {
>
<div className="flex justify-between items-center">
<div className="flex flex-col">
<span className="text-white text-sm font-medium">
<span className="text-foreground text-sm font-medium">
{skill.name}
</span>
{selectedSkill === skill.name && (
<span className="text-zinc-400 text-xs">
<span className="text-muted-foreground text-xs">
{skill.level}% Proficiency
</span>
)}
</div>
<span className="text-white text-sm">
<span className="text-foreground text-sm">
{skill.level}%
</span>
</div>
<div
className="h-2 bg-zinc-800 rounded-full overflow-hidden"
className="h-2 bg-card rounded-full overflow-hidden"
role="progressbar"
aria-valuenow={skill.level}
aria-valuemin={0}
@@ -99,7 +99,7 @@ const Skills = () => {
>
<motion.div
className={`h-full rounded-full ${
selectedSkill === skill.name ? 'bg-zinc-500' : 'bg-zinc-600'
selectedSkill === skill.name ? 'bg-accent' : 'bg-primary'
}`}
initial={{ width: 0 }}
whileInView={{ width: `${skill.level}%` }}
@@ -116,22 +116,22 @@ const Skills = () => {
{allSkills.map((skill) => (
<motion.div
key={skill.name}
className={`p-6 rounded-xl bg-zinc-800/30 backdrop-blur-sm flex flex-col items-center justify-center gap-3 cursor-pointer transition-all duration-300 hover:bg-zinc-700/50 ${
selectedSkill === skill.name ? 'ring-2 ring-zinc-500' : ''
className={`p-6 rounded-xl bg-card/30 backdrop-blur-sm flex flex-col items-center justify-center gap-3 cursor-pointer transition-all duration-300 hover:bg-accent/50 ${
selectedSkill === skill.name ? 'ring-2 ring-ring' : ''
}`}
whileHover={{ scale: 1.05 }}
onClick={() => handleClick(skill.name)}
role="button"
aria-pressed={selectedSkill === skill.name}
>
<div className={selectedSkill === skill.name ? 'text-zinc-500' : 'text-white'}>
<div className={selectedSkill === skill.name ? 'text-accent' : 'text-foreground'}>
{iconMap[skill.name] || <Box className="w-8 h-8" />}
</div>
<span className="text-white text-sm text-center">
<span className="text-foreground text-sm text-center">
{skill.name}
</span>
{selectedSkill === skill.name && (
<span className="text-zinc-400 text-xs text-center mt-1">
<span className="text-muted-foreground text-xs text-center mt-1">
{skill.level}%
</span>
)}
+4 -4
View File
@@ -33,18 +33,18 @@ const Workflow = async () => {
role="listitem"
aria-label={`Step ${step.number} of ${totalSteps}: ${step.title}`}
>
<div className="flex items-center py-6 border-b border-zinc-800 group-hover:border-zinc-700 transition-colors duration-300">
<div className="w-12 sm:w-20 text-sm text-zinc-600 font-light">
<div className="flex items-center py-6 border-b border-border group-hover:border-accent transition-colors duration-300">
<div className="w-12 sm:w-20 text-sm text-muted-foreground/60 font-light">
{step.number}
</div>
<h3 className="text-sm sm:text-base text-zinc-400 group-hover:text-white transition-colors duration-300">
<h3 className="text-sm sm:text-base text-muted-foreground group-hover:text-foreground transition-colors duration-300">
{step.title}
</h3>
</div>
{/* Line Indicator */}
<div
className="absolute left-0 bottom-0 w-0 h-px bg-white group-hover:w-full transition-all duration-500 ease-out"
className="absolute left-0 bottom-0 w-0 h-px bg-foreground group-hover:w-full transition-all duration-500 ease-out"
aria-hidden="true"
/>
</div>
+15 -15
View File
@@ -73,14 +73,14 @@ export default function DashboardContent() {
{/* Header */}
<div className="flex items-center justify-between mb-12">
<div>
<h1 className="text-3xl font-bold text-white">{t('header.title')}</h1>
<p className="text-zinc-400 mt-2">
<h1 className="text-3xl font-bold text-foreground">{t('header.title')}</h1>
<p className="text-muted-foreground mt-2">
127 {t('header.activeUsers')}
</p>
</div>
<button
onClick={handleLogout}
className="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 rounded-lg text-zinc-300 transition-colors"
className="flex items-center gap-2 px-4 py-2 bg-card hover:bg-accent rounded-lg text-foreground/80 transition-colors"
>
<LogOut className="h-4 w-4" />
Logout
@@ -92,10 +92,10 @@ export default function DashboardContent() {
{metrics.map((metric, index) => (
<div
key={index}
className="bg-zinc-900/50 backdrop-blur-sm rounded-xl p-6 ring-1 ring-zinc-800"
className="bg-background/50 backdrop-blur-sm rounded-xl p-6 ring-1 ring-border"
>
<div className="flex items-center justify-between mb-4">
<div className="p-2 bg-zinc-800 rounded-lg text-zinc-400">
<div className="p-2 bg-card rounded-lg text-muted-foreground">
{metric.icon}
</div>
{metric.trend && (
@@ -111,22 +111,22 @@ export default function DashboardContent() {
</div>
)}
</div>
<h3 className="text-sm text-zinc-400 mb-1">{metric.title}</h3>
<p className="text-2xl font-bold text-white">{metric.value}</p>
<p className="text-sm text-zinc-500 mt-1">{metric.subtitle}</p>
<h3 className="text-sm text-muted-foreground mb-1">{metric.title}</h3>
<p className="text-2xl font-bold text-foreground">{metric.value}</p>
<p className="text-sm text-muted-foreground mt-1">{metric.subtitle}</p>
</div>
))}
</div>
{/* Top Pages */}
<div className="bg-zinc-900/50 backdrop-blur-sm rounded-xl ring-1 ring-zinc-800">
<div className="p-6 border-b border-zinc-800">
<h2 className="text-xl font-semibold text-white">{t('topPages.title')}</h2>
<div className="bg-background/50 backdrop-blur-sm rounded-xl ring-1 ring-border">
<div className="p-6 border-b border-border">
<h2 className="text-xl font-semibold text-foreground">{t('topPages.title')}</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="text-left text-sm text-zinc-500 border-b border-zinc-800">
<tr className="text-left text-sm text-muted-foreground border-b border-border">
<th className="px-6 py-4 font-medium">{t('topPages.columns.path')}</th>
<th className="px-6 py-4 font-medium">{t('topPages.columns.views')}</th>
<th className="px-6 py-4 font-medium">{t('topPages.columns.change')}</th>
@@ -134,9 +134,9 @@ export default function DashboardContent() {
</thead>
<tbody>
{topPages.map((page, index) => (
<tr key={index} className="border-b border-zinc-800/50 last:border-0">
<td className="px-6 py-4 text-white font-mono text-sm">{page.path}</td>
<td className="px-6 py-4 text-zinc-300">{page.views.toLocaleString()}</td>
<tr key={index} className="border-b border-border/50 last:border-0">
<td className="px-6 py-4 text-foreground font-mono text-sm">{page.path}</td>
<td className="px-6 py-4 text-foreground/80">{page.views.toLocaleString()}</td>
<td className={`px-6 py-4 ${
page.change.startsWith('+') ? 'text-green-500' : 'text-red-500'
}`}>
+4 -4
View File
@@ -70,7 +70,7 @@ export default function LoginForm() {
<div className="max-w-md w-full space-y-8">
<div className="text-center">
<Lock className="mx-auto h-12 w-12 text-orange-500" />
<h1 className="mt-6 text-3xl font-bold text-white">{t('header.title')}</h1>
<h1 className="mt-6 text-3xl font-bold text-foreground">{t('header.title')}</h1>
</div>
<form className="mt-8 space-y-6" onSubmit={handleLogin}>
@@ -118,7 +118,7 @@ export default function LoginForm() {
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder={t('form.email.placeholder')}
className="w-full bg-zinc-900/50 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-orange-500"
className="w-full bg-background/50 border border-white/10 rounded-lg px-4 py-3 text-foreground placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-orange-500"
/>
</div>
@@ -133,7 +133,7 @@ export default function LoginForm() {
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={t('form.password.placeholder')}
className="w-full bg-zinc-900/50 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-orange-500"
className="w-full bg-background/50 border border-white/10 rounded-lg px-4 py-3 text-foreground placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-orange-500"
/>
</div>
</div>
@@ -154,7 +154,7 @@ export default function LoginForm() {
</button>
</form>
<div className="text-center text-sm text-zinc-500">
<div className="text-center text-sm text-muted-foreground">
<p className="flex items-center justify-center gap-2">
<svg className="h-4 w-4 text-green-500" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" />
+33 -33
View File
@@ -71,27 +71,27 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
return (
<Link
href={`/${locale}/blog/${post.slug}`}
className="group relative block bg-zinc-900/50 backdrop-blur-sm
className="group relative block bg-background/50 backdrop-blur-sm
rounded-xl shadow-lg hover:shadow-2xl
ring-1 ring-zinc-800 hover:ring-zinc-700
ring-1 ring-border hover:ring-border
transition-all duration-500 focus:outline-none focus:ring-2
focus:ring-zinc-600 focus:ring-offset-2 focus:ring-offset-zinc-950
focus:ring-ring focus:ring-offset-2 focus:ring-offset-background
transform hover:-translate-y-1"
>
<div className="relative overflow-hidden rounded-xl">
{/* Image Container */}
<div className="aspect-[16/9] w-full relative overflow-hidden bg-zinc-900">
<div className="aspect-[16/9] w-full relative overflow-hidden bg-background">
<BlogImage
src={getImagePath(post.coverImage)}
alt={post.title}
fallback={PLACEHOLDER_IMAGE}
/>
<div className="absolute inset-x-0 -bottom-20 top-0 bg-gradient-to-t from-zinc-900 via-zinc-900/70 to-transparent opacity-95" />
<div className="absolute inset-x-0 -bottom-20 top-0 bg-gradient-to-t from-background via-background/70 to-transparent opacity-95" />
</div>
{/* Content */}
<div className="relative -mt-2 p-6 bg-zinc-900 z-10">
<div className="flex items-center gap-4 mb-4 text-zinc-500">
<div className="relative -mt-2 p-6 bg-background z-10">
<div className="flex items-center gap-4 mb-4 text-muted-foreground">
<div className="flex items-center gap-2 text-sm">
<Calendar className="h-4 w-4" />
<span>{getFormattedDate(post.date, locale)}</span>
@@ -104,12 +104,12 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
)}
</div>
<h3 className="text-xl font-semibold text-white mb-3
group-hover:text-zinc-100 transition-colors duration-300">
<h3 className="text-xl font-semibold text-foreground mb-3
group-hover:text-foreground/90 transition-colors duration-300">
{post.title}
</h3>
<p className="text-zinc-400 text-sm line-clamp-2 mb-4
group-hover:text-zinc-300 transition-colors duration-300">
<p className="text-muted-foreground text-sm line-clamp-2 mb-4
group-hover:text-foreground/80 transition-colors duration-300">
{post.excerpt}
</p>
@@ -118,17 +118,17 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
{post.tags?.slice(0, 3).map((tag, index) => (
<span
key={index}
className="px-3 py-1 bg-zinc-800/50
text-sm text-zinc-400 rounded-full
ring-1 ring-zinc-700/50 group-hover:ring-zinc-600/50
group-hover:bg-zinc-800/70 group-hover:text-zinc-300
className="px-3 py-1 bg-card/50
text-sm text-muted-foreground rounded-full
ring-1 ring-border/50 group-hover:ring-ring/50
group-hover:bg-card/70 group-hover:text-foreground/80
transition-all duration-300"
>
{tag}
</span>
))}
{post.tags && post.tags.length > 3 && (
<span className="text-sm text-zinc-600">
<span className="text-sm text-muted-foreground/60">
+{post.tags.length - 3} more
</span>
)}
@@ -136,11 +136,11 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
{/* Link Icon */}
<div className="absolute top-6 right-6 p-2.5 rounded-full
bg-zinc-800/60 backdrop-blur-sm ring-1 ring-zinc-700/50
bg-card/60 backdrop-blur-sm ring-1 ring-border/50
opacity-0 group-hover:opacity-100
transform translate-y-2 group-hover:translate-y-0
transition-all duration-300">
<ExternalLink className="h-4 w-4 text-zinc-400" />
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</div>
</div>
</div>
@@ -203,9 +203,9 @@ function Pagination({
{currentPage > 1 ? (
<button
onClick={() => onPageChange(currentPage - 1)}
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-400 hover:text-white
bg-zinc-800/50 hover:bg-zinc-800 rounded-lg ring-1 ring-zinc-700/50
hover:ring-zinc-600 transition-all duration-200"
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:text-foreground
bg-card/50 hover:bg-card rounded-lg ring-1 ring-border/50
hover:ring-ring transition-all duration-200"
aria-label={t('ui.pagination.previous')}
>
<ChevronLeft className="h-4 w-4" />
@@ -213,8 +213,8 @@ function Pagination({
</button>
) : (
<span
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-600
bg-zinc-800/30 rounded-lg ring-1 ring-zinc-800/50 cursor-not-allowed"
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground/60
bg-card/30 rounded-lg ring-1 ring-border/50 cursor-not-allowed"
aria-disabled="true"
>
<ChevronLeft className="h-4 w-4" />
@@ -227,7 +227,7 @@ function Pagination({
{getPageNumbers().map((page, index) => {
if (page === 'ellipsis') {
return (
<span key={`ellipsis-${index}`} className="px-2 text-zinc-600">
<span key={`ellipsis-${index}`} className="px-2 text-muted-foreground/60">
...
</span>
);
@@ -242,8 +242,8 @@ function Pagination({
className={`min-w-[44px] h-11 flex items-center justify-center text-sm rounded-lg
ring-1 transition-all duration-200
${isCurrentPage
? 'bg-[#697565] text-white ring-[#697565] font-medium'
: 'text-zinc-400 hover:text-white bg-zinc-800/50 hover:bg-zinc-800 ring-zinc-700/50 hover:ring-zinc-600'
? 'bg-[#697565] text-foreground ring-[#697565] font-medium'
: 'text-muted-foreground hover:text-foreground bg-card/50 hover:bg-card ring-border/50 hover:ring-ring'
}`}
aria-current={isCurrentPage ? 'page' : undefined}
>
@@ -257,9 +257,9 @@ function Pagination({
{currentPage < totalPages ? (
<button
onClick={() => onPageChange(currentPage + 1)}
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-400 hover:text-white
bg-zinc-800/50 hover:bg-zinc-800 rounded-lg ring-1 ring-zinc-700/50
hover:ring-zinc-600 transition-all duration-200"
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:text-foreground
bg-card/50 hover:bg-card rounded-lg ring-1 ring-border/50
hover:ring-ring transition-all duration-200"
aria-label={t('ui.pagination.next')}
>
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
@@ -267,8 +267,8 @@ function Pagination({
</button>
) : (
<span
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-600
bg-zinc-800/30 rounded-lg ring-1 ring-zinc-800/50 cursor-not-allowed"
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground/60
bg-card/30 rounded-lg ring-1 ring-border/50 cursor-not-allowed"
aria-disabled="true"
>
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
@@ -389,7 +389,7 @@ export function BlogList({ posts, locale, initialSearch = '', initialCategory =
</div>
{/* Results count */}
<p className="text-sm text-zinc-500 mb-6">
<p className="text-sm text-muted-foreground mb-6">
{translations.showingText(
startIndex + 1,
Math.min(endIndex, filteredPosts.length),
@@ -421,7 +421,7 @@ export function BlogList({ posts, locale, initialSearch = '', initialCategory =
) : (
/* Empty State */
<div className="text-center py-12">
<p className="text-zinc-400">{translations.noPostsText}</p>
<p className="text-muted-foreground">{translations.noPostsText}</p>
</div>
)}
</div>
+6 -6
View File
@@ -26,8 +26,8 @@ export function CategoryFilter({
className={`${className}`}
>
<div className="flex items-center gap-3 mb-4">
<Tag className="h-5 w-5 text-zinc-400 flex-shrink-0" />
<h3 className="text-sm font-medium text-zinc-300">Filter by Category</h3>
<Tag className="h-5 w-5 text-muted-foreground flex-shrink-0" />
<h3 className="text-sm font-medium text-foreground/80">Filter by Category</h3>
</div>
{/* Mobile: Horizontal scrollable */}
@@ -44,8 +44,8 @@ export function CategoryFilter({
onClick={() => onCategoryChange(category === 'All' ? null : category)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all whitespace-nowrap ${
isActive
? 'bg-[#697565] text-white'
: 'bg-zinc-900/50 text-zinc-400 border border-zinc-800 hover:bg-zinc-800/50 hover:text-white'
? 'bg-primary text-primary-foreground'
: 'bg-background/50 text-muted-foreground border border-border hover:bg-card/50 hover:text-foreground'
}`}
>
{category}
@@ -68,8 +68,8 @@ export function CategoryFilter({
onClick={() => onCategoryChange(category === 'All' ? null : category)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${
isActive
? 'bg-[#697565] text-white'
: 'bg-zinc-900/50 text-zinc-400 border border-zinc-800 hover:bg-zinc-800/50 hover:text-white'
? 'bg-primary text-primary-foreground'
: 'bg-background/50 text-muted-foreground border border-border hover:bg-card/50 hover:text-foreground'
}`}
>
{category}
+3 -3
View File
@@ -27,18 +27,18 @@ export function SearchBar({
className={`relative ${className}`}
>
<div className="relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-zinc-400" />
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="w-full pl-12 pr-12 py-3 bg-zinc-900/50 border border-zinc-800 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-[#697565] focus:border-transparent transition-all"
className="w-full pl-12 pr-12 py-3 bg-background/50 border border-border rounded-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-all"
/>
{value && (
<button
onClick={handleClear}
className="absolute right-4 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-white transition-colors"
className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
aria-label="Clear search"
>
<X className="h-5 w-5" />
+16 -16
View File
@@ -61,14 +61,14 @@ export function ChatMessage({ message, index = 0 }: ChatMessageProps) {
<div
className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
isUser
? 'bg-zinc-600/50'
: 'bg-zinc-800/50 border border-zinc-700'
? 'bg-primary/50'
: 'bg-card/50 border border-border'
}`}
>
{isUser ? (
<User className="w-4 h-4 text-zinc-300" />
<User className="w-4 h-4 text-foreground/80" />
) : (
<Bot className="w-4 h-4 text-zinc-400" />
<Bot className="w-4 h-4 text-muted-foreground" />
)}
</div>
@@ -81,21 +81,21 @@ export function ChatMessage({ message, index = 0 }: ChatMessageProps) {
<div
className={`px-4 py-3 rounded-2xl ${
isUser
? 'bg-zinc-600/50 text-white rounded-tr-sm'
: 'bg-zinc-800/50 border border-zinc-800 text-zinc-200 rounded-tl-sm'
? 'bg-primary/50 text-foreground rounded-tr-sm'
: 'bg-card/50 border border-border text-foreground/90 rounded-tl-sm'
}`}
>
<p className="text-sm whitespace-pre-wrap break-words leading-relaxed">
{content}
{isStreaming && (
<span className="inline-block w-1.5 h-4 ml-1 bg-zinc-400 animate-pulse rounded-sm" />
<span className="inline-block w-1.5 h-4 ml-1 bg-muted-foreground animate-pulse rounded-sm" />
)}
</p>
</div>
{/* Timestamp */}
{formattedTime && !isStreaming && (
<span className="text-xs text-zinc-500 mt-1 px-1">
<span className="text-xs text-muted-foreground mt-1 px-1">
{formattedTime}
</span>
)}
@@ -116,17 +116,17 @@ export function TypingIndicator() {
className="flex gap-3"
>
{/* Avatar */}
<div className="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center bg-zinc-800/50 border border-zinc-700">
<Bot className="w-4 h-4 text-zinc-400" />
<div className="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center bg-card/50 border border-border">
<Bot className="w-4 h-4 text-muted-foreground" />
</div>
{/* Typing dots */}
<div className="px-4 py-3 rounded-2xl rounded-tl-sm bg-zinc-800/50 border border-zinc-800">
<div className="px-4 py-3 rounded-2xl rounded-tl-sm bg-card/50 border border-border">
<div className="flex gap-1.5">
{[0, 1, 2].map((i) => (
<motion.span
key={i}
className="w-2 h-2 bg-zinc-500 rounded-full"
className="w-2 h-2 bg-muted-foreground rounded-full"
animate={{
opacity: [0.4, 1, 0.4],
scale: [0.8, 1, 0.8],
@@ -154,13 +154,13 @@ export function WelcomeMessage() {
animate={{ opacity: 1, y: 0 }}
className="flex flex-col items-center justify-center py-8 px-4 text-center"
>
<div className="w-12 h-12 rounded-full bg-zinc-800/50 border border-zinc-700 flex items-center justify-center mb-4">
<Bot className="w-6 h-6 text-zinc-400" />
<div className="w-12 h-12 rounded-full bg-card/50 border border-border flex items-center justify-center mb-4">
<Bot className="w-6 h-6 text-muted-foreground" />
</div>
<h3 className="text-lg font-medium text-white mb-2">
<h3 className="text-lg font-medium text-foreground mb-2">
Hi, I&apos;m Damjan&apos;s AI Assistant
</h3>
<p className="text-sm text-zinc-400 max-w-xs">
<p className="text-sm text-muted-foreground max-w-xs">
I can help you learn about Damjan&apos;s skills, services, and experience.
Feel free to ask me anything!
</p>
+15 -15
View File
@@ -259,7 +259,7 @@ export function Chatbot() {
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={toggleChat}
className="fixed bottom-6 right-6 z-50 w-14 h-14 rounded-full bg-zinc-100 hover:bg-white text-zinc-900 shadow-lg shadow-black/20 flex items-center justify-center transition-colors"
className="fixed bottom-6 right-6 z-50 w-14 h-14 rounded-full bg-primary text-primary-foreground shadow-lg shadow-black/20 flex items-center justify-center hover:bg-accent transition-colors"
aria-label="Open chat"
>
<MessageCircle className="w-6 h-6" />
@@ -275,7 +275,7 @@ export function Chatbot() {
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.95 }}
transition={{ duration: 0.2 }}
className={`fixed z-50 bg-zinc-900 border border-zinc-800 shadow-2xl shadow-black/40 flex flex-col overflow-hidden ${
className={`fixed z-50 bg-background border border-border shadow-2xl shadow-black/40 flex flex-col overflow-hidden ${
isMinimized
? 'bottom-6 right-6 w-80 h-14 rounded-full'
: 'bottom-6 right-6 w-[380px] h-[600px] max-h-[calc(100vh-3rem)] rounded-2xl sm:max-w-[calc(100vw-3rem)]'
@@ -289,18 +289,18 @@ export function Chatbot() {
>
{/* Header */}
<div
className={`flex items-center justify-between px-4 bg-zinc-800/50 border-b border-zinc-800 ${
className={`flex items-center justify-between px-4 bg-card/50 border-b border-border ${
isMinimized ? 'h-full rounded-full' : 'h-14'
}`}
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-zinc-700/50 flex items-center justify-center">
<MessageCircle className="w-4 h-4 text-zinc-300" />
<div className="w-8 h-8 rounded-full bg-muted/50 flex items-center justify-center">
<MessageCircle className="w-4 h-4 text-foreground/80" />
</div>
<div className="flex flex-col">
<span className="text-sm font-medium text-white">AI Assistant</span>
<span className="text-sm font-medium text-foreground">AI Assistant</span>
{!isMinimized && (
<span className="text-xs text-zinc-400">Ask me anything</span>
<span className="text-xs text-muted-foreground">Ask me anything</span>
)}
</div>
</div>
@@ -310,7 +310,7 @@ export function Chatbot() {
{!isMinimized && messages.length > 0 && (
<button
onClick={clearChat}
className="p-2 rounded-lg hover:bg-zinc-700/50 text-zinc-400 hover:text-zinc-200 transition-colors"
className="p-2 rounded-lg hover:bg-muted/50 text-muted-foreground hover:text-foreground/80 transition-colors"
aria-label="Clear chat"
title="Clear chat"
>
@@ -321,7 +321,7 @@ export function Chatbot() {
{/* Close Button */}
<button
onClick={toggleChat}
className="p-2 rounded-lg hover:bg-zinc-700/50 text-zinc-400 hover:text-zinc-200 transition-colors"
className="p-2 rounded-lg hover:bg-muted/50 text-muted-foreground hover:text-foreground/80 transition-colors"
aria-label="Close chat"
>
<X className="w-4 h-4" />
@@ -340,14 +340,14 @@ export function Chatbot() {
{/* Suggested Prompts */}
<div className="flex flex-col gap-2">
<p className="text-xs text-zinc-500 text-center mb-2">
<p className="text-xs text-muted-foreground text-center mb-2">
Try asking:
</p>
{SUGGESTED_PROMPTS.map((prompt, index) => (
<button
key={index}
onClick={() => handleSuggestedPrompt(prompt)}
className="w-full px-4 py-2.5 text-sm text-left bg-zinc-800/50 border border-zinc-800 rounded-xl text-zinc-300 hover:bg-zinc-700/50 hover:border-zinc-700 transition-colors"
className="w-full px-4 py-2.5 text-sm text-left bg-card/50 border border-border rounded-xl text-foreground/80 hover:bg-accent/50 hover:border-accent transition-colors"
>
{prompt}
</button>
@@ -397,7 +397,7 @@ export function Chatbot() {
{/* Input Form */}
<form
onSubmit={handleSubmit}
className="p-4 border-t border-zinc-800 bg-zinc-900"
className="p-4 border-t border-border bg-background"
>
<div className="flex gap-2">
<input
@@ -407,13 +407,13 @@ export function Chatbot() {
onChange={(e) => setInputValue(e.target.value)}
placeholder="Type a message..."
disabled={isLoading}
className="flex-1 h-11 px-4 bg-zinc-800/50 border border-zinc-800 rounded-xl text-white placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-600 disabled:opacity-50"
className="flex-1 h-11 px-4 bg-card/50 border border-border rounded-xl text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
aria-label="Chat message input"
/>
<button
type="submit"
disabled={isLoading || !inputValue.trim()}
className="h-11 w-11 bg-zinc-100 hover:bg-white text-zinc-900 rounded-xl flex items-center justify-center transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
className="h-11 w-11 bg-primary hover:bg-accent text-primary-foreground rounded-xl flex items-center justify-center transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
aria-label="Send message"
>
{isLoading ? (
@@ -425,7 +425,7 @@ export function Chatbot() {
</div>
{/* Privacy note */}
<p className="text-xs text-zinc-500 text-center mt-2">
<p className="text-xs text-muted-foreground text-center mt-2">
Messages may be stored to improve service
</p>
</form>
+30 -30
View File
@@ -129,7 +129,7 @@ export function ContactForm() {
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-4xl md:text-5xl font-bold text-white mb-4"
className="text-4xl md:text-5xl font-bold text-foreground mb-4"
>
{t('hero.title')}
</motion.h1>
@@ -137,7 +137,7 @@ export function ContactForm() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="text-lg text-zinc-400 max-w-2xl mx-auto px-4"
className="text-lg text-muted-foreground max-w-2xl mx-auto px-4"
>
{t('hero.subtitle')}
</motion.p>
@@ -155,7 +155,7 @@ export function ContactForm() {
>
{/* Headshot */}
<div className="flex items-center gap-6">
<div className="relative w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 rounded-full overflow-hidden border-2 border-zinc-700 flex-shrink-0">
<div className="relative w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 rounded-full overflow-hidden border-2 border-border flex-shrink-0">
<Image
src="/images/headshot.avif"
alt="Damjan Savić - Full-Stack Web Developer"
@@ -165,10 +165,10 @@ export function ContactForm() {
/>
</div>
<div className="min-w-0">
<h2 className="text-2xl font-bold text-white mb-1 break-words">
<h2 className="text-2xl font-bold text-foreground mb-1 break-words">
{t('contactInfo.title')}
</h2>
<p className="text-zinc-400">
<p className="text-muted-foreground">
{t('contactInfo.description')}
</p>
</div>
@@ -181,14 +181,14 @@ export function ContactForm() {
transition={{ delay: 0.3 }}
className="flex items-start gap-4"
>
<div className="p-3 bg-zinc-800/50 rounded-lg">
<MapPin className="h-6 w-6 text-zinc-400" aria-hidden="true" />
<div className="p-3 bg-card/50 rounded-lg">
<MapPin className="h-6 w-6 text-muted-foreground" aria-hidden="true" />
</div>
<div>
<h3 className="text-sm font-medium text-zinc-300 mb-1">
<h3 className="text-sm font-medium text-foreground/80 mb-1">
{t('contactInfo.address.label')}
</h3>
<p className="text-zinc-400">
<p className="text-muted-foreground">
{t('contactInfo.address.value')}
</p>
</div>
@@ -200,16 +200,16 @@ export function ContactForm() {
transition={{ delay: 0.4 }}
className="flex items-start gap-4"
>
<div className="p-3 bg-zinc-800/50 rounded-lg">
<Phone className="h-6 w-6 text-zinc-400" aria-hidden="true" />
<div className="p-3 bg-card/50 rounded-lg">
<Phone className="h-6 w-6 text-muted-foreground" aria-hidden="true" />
</div>
<div>
<h3 className="text-sm font-medium text-zinc-300 mb-1">
<h3 className="text-sm font-medium text-foreground/80 mb-1">
{t('contactInfo.phone.label')}
</h3>
<a
href="tel:+491756950979"
className="text-zinc-400 hover:text-white transition-colors duration-200"
className="text-muted-foreground hover:text-foreground transition-colors duration-200"
>
{t('contactInfo.phone.value')}
</a>
@@ -222,16 +222,16 @@ export function ContactForm() {
transition={{ delay: 0.5 }}
className="flex items-start gap-4"
>
<div className="p-3 bg-zinc-800/50 rounded-lg">
<Mail className="h-6 w-6 text-zinc-400" aria-hidden="true" />
<div className="p-3 bg-card/50 rounded-lg">
<Mail className="h-6 w-6 text-muted-foreground" aria-hidden="true" />
</div>
<div>
<h3 className="text-sm font-medium text-zinc-300 mb-1">
<h3 className="text-sm font-medium text-foreground/80 mb-1">
{t('contactInfo.email.label')}
</h3>
<a
href="mailto:info@damjan-savic.com"
className="text-zinc-400 hover:text-white transition-colors duration-200"
className="text-muted-foreground hover:text-foreground transition-colors duration-200"
>
{t('contactInfo.email.value')}
</a>
@@ -243,7 +243,7 @@ export function ContactForm() {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6 }}
className="text-sm text-zinc-500"
className="text-sm text-muted-foreground"
>
{t('contactInfo.availabilityNote')}
</motion.p>
@@ -255,21 +255,21 @@ export function ContactForm() {
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 }}
>
<div className="bg-zinc-800/30 border border-zinc-800 rounded-xl p-6 md:p-8">
<div className="bg-card/30 border border-border rounded-xl p-6 md:p-8">
<div className="flex items-center gap-2 mb-2">
<Mail className="h-5 w-5 text-zinc-400" aria-hidden="true" />
<h2 className="text-xl font-semibold text-white">
<Mail className="h-5 w-5 text-muted-foreground" aria-hidden="true" />
<h2 className="text-xl font-semibold text-foreground">
{t('contactForm.title')}
</h2>
</div>
<p className="text-zinc-400 mb-6">
<p className="text-muted-foreground mb-6">
{t('contactForm.description')}
</p>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-4">
<div className="space-y-2">
<label htmlFor="name" className="block text-sm text-zinc-300">
<label htmlFor="name" className="block text-sm text-foreground/80">
{t('contactForm.name.label')}
</label>
<input
@@ -283,7 +283,7 @@ export function ContactForm() {
aria-required="true"
aria-invalid={!!errors.name}
aria-describedby={errors.name ? 'name-error' : undefined}
className="w-full h-12 px-4 bg-zinc-900/50 border border-zinc-800 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-600 disabled:opacity-50"
className="w-full h-12 px-4 bg-background/50 border border-border rounded-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
/>
{errors.name && (
<p id="name-error" role="alert" className="text-sm text-red-400">{errors.name}</p>
@@ -291,7 +291,7 @@ export function ContactForm() {
</div>
<div className="space-y-2">
<label htmlFor="email" className="block text-sm text-zinc-300">
<label htmlFor="email" className="block text-sm text-foreground/80">
{t('contactForm.email.label')}
</label>
<input
@@ -305,7 +305,7 @@ export function ContactForm() {
aria-required="true"
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
className="w-full h-12 px-4 bg-zinc-900/50 border border-zinc-800 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-600 disabled:opacity-50"
className="w-full h-12 px-4 bg-background/50 border border-border rounded-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
/>
{errors.email && (
<p id="email-error" role="alert" className="text-sm text-red-400">{errors.email}</p>
@@ -313,7 +313,7 @@ export function ContactForm() {
</div>
<div className="space-y-2">
<label htmlFor="message" className="block text-sm text-zinc-300">
<label htmlFor="message" className="block text-sm text-foreground/80">
{t('contactForm.message.label')}
</label>
<textarea
@@ -328,7 +328,7 @@ export function ContactForm() {
aria-required="true"
aria-invalid={!!errors.message}
aria-describedby={errors.message ? 'message-error' : undefined}
className="w-full px-4 py-3 bg-zinc-900/50 border border-zinc-800 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-600 disabled:opacity-50 resize-none"
className="w-full px-4 py-3 bg-background/50 border border-border rounded-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50 resize-none"
/>
{errors.message && (
<p id="message-error" role="alert" className="text-sm text-red-400">{errors.message}</p>
@@ -340,7 +340,7 @@ export function ContactForm() {
? 'text-red-500'
: messageLength >= MAX_MESSAGE_LENGTH * 0.8
? 'text-yellow-500'
: 'text-zinc-500'
: 'text-muted-foreground'
}`}
>
{t('contactForm.characterCounter', { current: messageLength, max: MAX_MESSAGE_LENGTH })}
@@ -397,7 +397,7 @@ export function ContactForm() {
disabled={isSubmitting}
aria-busy={isSubmitting}
aria-label={isSubmitting ? t('contactForm.submitting') : undefined}
className="w-full h-12 bg-zinc-100 hover:bg-white text-zinc-900 font-medium rounded-lg transition-colors duration-200 duration-300 flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
className="w-full h-12 bg-primary text-primary-foreground hover:bg-accent font-medium rounded-lg transition-colors duration-300 flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? (
<Loader2 className="h-5 w-5 animate-spin" aria-hidden="true" />
+7 -7
View File
@@ -9,31 +9,31 @@ export function ContactInfo() {
const t = useTranslations('footer.sections.contact');
return (
<div className="pb-6 px-6 pt-4 bg-zinc-800/30 border border-zinc-800 rounded-lg mx-4 mt-2">
<div className="pb-6 px-6 pt-4 bg-card/30 border border-border rounded-lg mx-4 mt-2">
<div className="space-y-4">
<h3 className="text-sm font-medium text-white tracking-wider uppercase">
<h3 className="text-sm font-medium text-foreground tracking-wider uppercase">
{t('title')}
</h3>
<a
href="tel:+491756950979"
className="flex items-center space-x-3 text-sm text-zinc-400 hover:text-white transition-colors duration-200 group"
className="flex items-center space-x-3 text-sm text-muted-foreground hover:text-foreground transition-colors duration-200 group"
>
<Phone className="h-4 w-4 text-zinc-400 group-hover:text-white transition-colors duration-200" />
<Phone className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors duration-200" />
<span>+49 175 695 0979</span>
</a>
<a
href="mailto:info@damjan-savic.com"
className="flex items-center space-x-3 text-sm text-zinc-400 hover:text-white transition-colors duration-200 group"
className="flex items-center space-x-3 text-sm text-muted-foreground hover:text-foreground transition-colors duration-200 group"
>
<Mail className="h-4 w-4 text-zinc-400 group-hover:text-white transition-colors duration-200" />
<Mail className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors duration-200" />
<span>{t('email')}</span>
</a>
<Link
href={`/${locale}/contact`}
className="flex items-center justify-between mt-6 px-4 py-2 bg-zinc-800/50 hover:bg-zinc-800 rounded-full text-sm text-zinc-400 hover:text-white transition-all duration-200 group border border-zinc-800 hover:border-zinc-700"
className="flex items-center justify-between mt-6 px-4 py-2 bg-card/50 hover:bg-card rounded-full text-sm text-muted-foreground hover:text-foreground transition-all duration-200 group border border-border hover:border-accent"
>
<span className="tracking-wide">Kontakt aufnehmen</span>
<ArrowRight className="h-4 w-4 transform group-hover:translate-x-1 transition-transform duration-200" />
+19 -19
View File
@@ -8,24 +8,24 @@ const Footer = async () => {
const currentYear = new Date().getFullYear();
return (
<footer className="relative bg-zinc-800/50 backdrop-blur-sm border-t border-zinc-700/30 pt-6 sm:pt-8 pb-4 px-4 sm:px-6 lg:px-8">
<footer className="relative bg-card/50 backdrop-blur-sm border-t border-border/30 pt-6 sm:pt-8 pb-4 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="flex flex-col sm:grid sm:grid-cols-2 lg:grid-cols-4 gap-6 sm:gap-8">
{/* Contact Section */}
<div>
<h3 className="text-white text-base font-semibold mb-3">
<h3 className="text-foreground text-base font-semibold mb-3">
{t('sections.contact.title')}
</h3>
<div className="space-y-2">
<a
href={`mailto:${t('sections.contact.email')}`}
className="group flex items-center gap-2 text-zinc-400 hover:text-white transition-colors duration-200 text-sm w-fit"
className="group flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors duration-200 text-sm w-fit"
>
<Mail className="h-4 w-4 text-zinc-500 group-hover:text-white transition-colors duration-200 flex-shrink-0" />
<Mail className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors duration-200 flex-shrink-0" />
<span>{t('sections.contact.email')}</span>
</a>
<div className="flex items-center gap-2 text-zinc-400 text-sm w-fit">
<MapPin className="h-4 w-4 text-zinc-500 flex-shrink-0" />
<div className="flex items-center gap-2 text-muted-foreground text-sm w-fit">
<MapPin className="h-4 w-4 text-muted-foreground flex-shrink-0" />
<span>{t('sections.contact.location')}</span>
</div>
</div>
@@ -33,31 +33,31 @@ const Footer = async () => {
{/* Navigation Section */}
<div>
<h3 className="text-white text-base font-semibold mb-3 text-left">
<h3 className="text-foreground text-base font-semibold mb-3 text-left">
{t('sections.navigation.title')}
</h3>
<nav className="flex flex-col space-y-2">
<Link
href={`/${locale}/portfolio`}
className="text-zinc-400 hover:text-white transition-colors duration-200 text-sm w-fit"
className="text-muted-foreground hover:text-foreground transition-colors duration-200 text-sm w-fit"
>
{t('sections.navigation.links.portfolio')}
</Link>
<Link
href={`/${locale}/blog`}
className="text-zinc-400 hover:text-white transition-colors duration-200 text-sm w-fit"
className="text-muted-foreground hover:text-foreground transition-colors duration-200 text-sm w-fit"
>
{t('sections.navigation.links.blog')}
</Link>
<Link
href={`/${locale}/about`}
className="text-zinc-400 hover:text-white transition-colors duration-200 text-sm w-fit"
className="text-muted-foreground hover:text-foreground transition-colors duration-200 text-sm w-fit"
>
{t('sections.navigation.links.about')}
</Link>
<Link
href={`/${locale}/contact`}
className="text-zinc-400 hover:text-white transition-colors duration-200 text-sm w-fit"
className="text-muted-foreground hover:text-foreground transition-colors duration-200 text-sm w-fit"
>
{t('sections.navigation.links.contact')}
</Link>
@@ -66,7 +66,7 @@ const Footer = async () => {
{/* Social Media Section */}
<div>
<h3 className="text-white text-base font-semibold mb-3 text-left">
<h3 className="text-foreground text-base font-semibold mb-3 text-left">
{t('sections.social.title')}
</h3>
<div className="flex space-x-4">
@@ -74,7 +74,7 @@ const Footer = async () => {
href="https://www.linkedin.com/in/damjan-savić-720288127/"
target="_blank"
rel="noopener noreferrer"
className="group text-zinc-400 hover:text-white transition-colors duration-200"
className="group text-muted-foreground hover:text-foreground transition-colors duration-200"
aria-label="LinkedIn"
>
<Linkedin className="transform transition-transform group-hover:scale-110" size={20} />
@@ -83,7 +83,7 @@ const Footer = async () => {
href="https://github.com/damjan1996"
target="_blank"
rel="noopener noreferrer"
className="group text-zinc-400 hover:text-white transition-colors duration-200"
className="group text-muted-foreground hover:text-foreground transition-colors duration-200"
aria-label="GitHub"
>
<Github className="transform transition-transform group-hover:scale-110" size={20} />
@@ -92,27 +92,27 @@ const Footer = async () => {
</div>
{/* Legal Section */}
<div className="border-t sm:border-t-0 lg:border-t border-zinc-700/30 pt-4 sm:pt-0 lg:pt-4">
<div className="border-t sm:border-t-0 lg:border-t border-border/30 pt-4 sm:pt-0 lg:pt-4">
<div className="flex flex-col space-y-4">
<p className="text-zinc-400 text-xs text-left">
<p className="text-muted-foreground text-xs text-left">
&copy; {currentYear} Damjan Savić. {t('legal.copyright')}
</p>
<div className="flex space-x-4">
<Link
href={`/${locale}/privacy`}
className="text-zinc-400 hover:text-white text-xs transition-colors duration-200"
className="text-muted-foreground hover:text-foreground text-xs transition-colors duration-200"
>
{t('legal.links.privacy')}
</Link>
<Link
href={`/${locale}/imprint`}
className="text-zinc-400 hover:text-white text-xs transition-colors duration-200"
className="text-muted-foreground hover:text-foreground text-xs transition-colors duration-200"
>
{t('legal.links.imprint')}
</Link>
<Link
href={`/${locale}/terms`}
className="text-zinc-400 hover:text-white text-xs transition-colors duration-200"
className="text-muted-foreground hover:text-foreground text-xs transition-colors duration-200"
>
{t('legal.links.terms')}
</Link>
+7 -7
View File
@@ -17,7 +17,7 @@ const GlobalBackground = () => {
{/* Background layer */}
<div
ref={ref}
className="fixed inset-0 bg-zinc-900"
className="fixed inset-0 bg-background"
style={{ zIndex: -2 }}
/>
@@ -26,7 +26,7 @@ const GlobalBackground = () => {
className="fixed inset-0"
style={{
zIndex: -1,
background: 'linear-gradient(135deg, #1e1e1e 0%, #2a2a2a 50%, #1e1e1e 100%)',
background: 'linear-gradient(135deg, #181C14 0%, #1E221A 50%, #181C14 100%)',
opacity: 0.9
}}
/>
@@ -46,7 +46,7 @@ const GlobalBackground = () => {
y1="20%"
x2="100%"
y2="20%"
stroke="white"
stroke="#ECDFCC"
strokeWidth="1"
opacity="0.2"
>
@@ -73,7 +73,7 @@ const GlobalBackground = () => {
y1="50%"
x2="100%"
y2="50%"
stroke="white"
stroke="#ECDFCC"
strokeWidth="1"
opacity="0.15"
>
@@ -92,7 +92,7 @@ const GlobalBackground = () => {
y1="0%"
x2="20%"
y2="100%"
stroke="white"
stroke="#ECDFCC"
strokeWidth="1"
opacity="0.1"
>
@@ -122,8 +122,8 @@ const GlobalBackground = () => {
style={{
zIndex: -1,
backgroundImage: `
linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px)
linear-gradient(rgba(236,223,204,.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(236,223,204,.05) 1px, transparent 1px)
`,
backgroundSize: '50px 50px',
opacity: 0.5
+10 -10
View File
@@ -92,9 +92,9 @@ const Header = () => {
<nav
className={`
fixed w-full z-40 transition-all duration-300
bg-zinc-800/50 backdrop-blur-sm border-b border-zinc-700/30
bg-card/50 backdrop-blur-sm border-b border-border/30
${scrolled
? 'bg-zinc-800/70 backdrop-blur-md shadow-lg shadow-zinc-900/30'
? 'bg-card/70 backdrop-blur-md shadow-lg shadow-background/30'
: ''
}
`}
@@ -126,14 +126,14 @@ const Header = () => {
className="text-sm"
/>
))}
<div className="ml-4 text-zinc-400 pl-2 border-l border-zinc-700">
<div className="ml-4 text-muted-foreground pl-2 border-l border-border">
<LanguageSwitcher />
</div>
</div>
{/* Mobile Menu Button */}
<button
className="md:hidden relative z-50 p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors active:scale-95"
className="md:hidden relative z-50 p-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-card/50 transition-colors active:scale-95"
onClick={() => setIsMenuOpen(!isMenuOpen)}
aria-expanded={isMenuOpen}
aria-controls="mobile-menu"
@@ -156,17 +156,17 @@ const Header = () => {
{/* Mobile Sidebar */}
<div
id="mobile-menu"
className={`fixed right-0 top-0 h-full w-[min(20rem,90vw)] bg-zinc-900/95 backdrop-blur-md shadow-xl md:hidden z-50 border-l border-zinc-800 transition-transform duration-300 ease-out ${
className={`fixed right-0 top-0 h-full w-[min(20rem,90vw)] bg-background/95 backdrop-blur-md shadow-xl md:hidden z-50 border-l border-border transition-transform duration-300 ease-out ${
isMenuOpen ? 'translate-x-0' : 'translate-x-full'
}`}
>
{/* Sidebar Header */}
<div className="flex items-center justify-between px-4 h-14 sm:h-16 border-b border-zinc-800">
<span className="text-white font-semibold">Menu</span>
<div className="flex items-center justify-between px-4 h-14 sm:h-16 border-b border-border">
<span className="text-foreground font-semibold">Menu</span>
<button
onClick={() => setIsMenuOpen(false)}
aria-label="Close sidebar"
className="p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors active:scale-95"
className="p-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-card/50 transition-colors active:scale-95"
>
<X className="h-6 w-6" />
</button>
@@ -193,9 +193,9 @@ const Header = () => {
</div>
{/* Language Switcher */}
<div className="px-4 py-3 border-t border-zinc-800">
<div className="px-4 py-3 border-t border-border">
<div className="flex items-center justify-between">
<span className="text-sm text-zinc-400">
<span className="text-sm text-muted-foreground">
Sprache ändern
</span>
<LanguageSwitcher />
+7 -7
View File
@@ -80,9 +80,9 @@ const LanguageSwitcher = () => {
return (
<div className="relative inline-block" ref={dropdownRef}>
<button
className="flex items-center space-x-2 text-zinc-400 hover:text-white
className="flex items-center space-x-2 text-muted-foreground hover:text-foreground
px-4 py-2.5 min-h-[44px] rounded-full transition-all duration-200
hover:bg-zinc-800/50 border border-transparent hover:border-zinc-800
hover:bg-card/50 border border-transparent hover:border-border
hover:scale-105 active:scale-95"
onClick={() => {
setIsOpen(!isOpen);
@@ -98,7 +98,7 @@ const LanguageSwitcher = () => {
<div
className={`absolute ${isMobile ? 'bottom-full mb-2' : 'top-full mt-2'} right-0 w-44 sm:w-48 max-w-[calc(100vw-2rem)] rounded-lg overflow-hidden
border border-zinc-800 bg-zinc-900/95 backdrop-blur-sm shadow-lg
border border-border bg-background/95 backdrop-blur-sm shadow-lg
transition-all duration-200 origin-top
${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95 pointer-events-none'}`}
role="listbox"
@@ -112,12 +112,12 @@ const LanguageSwitcher = () => {
key={lang.code}
id={`lang-option-${lang.code}`}
className={`w-full text-left px-4 py-2 text-sm transition-colors duration-200
hover:bg-zinc-800/50
hover:bg-card/50
${locale === lang.code
? 'bg-zinc-800 text-white'
: 'text-zinc-400 hover:text-white'
? 'bg-card text-foreground'
: 'text-muted-foreground hover:text-foreground'
}
${focusedIndex === index ? 'ring-2 ring-orange-500 ring-inset' : ''}`}
${focusedIndex === index ? 'ring-2 ring-ring ring-inset' : ''}`}
onClick={() => handleLanguageChange(lang.code)}
role="option"
aria-selected={locale === lang.code}
+2 -2
View File
@@ -27,8 +27,8 @@ export function NavLink({ href, icon, label, onClick, className }: NavLinkProps)
className={`
relative flex items-center gap-2 rounded-full py-2.5 px-4 min-h-[44px]
transition-all duration-200 ease-in-out
focus:outline-none focus:ring-2 focus:ring-zinc-600 focus:ring-offset-2 focus:ring-offset-zinc-950
${isActive ? 'text-white bg-zinc-700/60' : 'text-zinc-400 hover:text-white hover:bg-zinc-800/30'}
focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background
${isActive ? 'text-foreground bg-accent/60' : 'text-muted-foreground hover:text-foreground hover:bg-card/30'}
${className || ''}
`}
>
+4 -4
View File
@@ -40,8 +40,8 @@ export function CategoryFilter() {
relative whitespace-nowrap rounded-full py-2 px-4
transition-all duration-200 ease-in-out text-sm tracking-wide
${!selectedCategory
? 'text-white bg-zinc-700/60'
: 'text-zinc-400 hover:text-white hover:bg-zinc-800/30'
? 'text-foreground bg-accent/60'
: 'text-muted-foreground hover:text-foreground hover:bg-card/30'
}
`}
>
@@ -59,8 +59,8 @@ export function CategoryFilter() {
relative whitespace-nowrap rounded-full py-2 px-4
transition-all duration-200 ease-in-out text-sm tracking-wide
${isActive
? 'text-white bg-zinc-700/60'
: 'text-zinc-400 hover:text-white hover:bg-zinc-800/30'
? 'text-foreground bg-accent/60'
: 'text-muted-foreground hover:text-foreground hover:bg-card/30'
}
`}
>
+18 -18
View File
@@ -30,11 +30,11 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
return (
<Link
href={`/${locale}/portfolio/${project.slug}`}
className="group relative block bg-zinc-900/50 backdrop-blur-sm
className="group relative block bg-background/50 backdrop-blur-sm
rounded-xl shadow-lg hover:shadow-2xl
ring-1 ring-zinc-800 hover:ring-zinc-700
ring-1 ring-border hover:ring-accent
transition-all duration-500 focus:outline-none focus:ring-2
focus:ring-zinc-600 focus:ring-offset-2 focus:ring-offset-zinc-950
focus:ring-ring focus:ring-offset-2 focus:ring-offset-background
transform hover:-translate-y-1"
>
<motion.div
@@ -45,7 +45,7 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
className="relative overflow-hidden rounded-xl"
>
{/* Image Container */}
<div className="aspect-[16/9] w-full relative overflow-hidden bg-zinc-900">
<div className="aspect-[16/9] w-full relative overflow-hidden bg-background">
<Image
src={`/images/projects/${project.slug}/cover.avif`}
alt={project.title}
@@ -56,9 +56,9 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
</div>
{/* Content */}
<div className="relative -mt-2 p-6 bg-zinc-900 z-10">
<div className="relative -mt-2 p-6 bg-background z-10">
{/* Project Info */}
<div className="flex items-center gap-4 mb-4 text-zinc-500">
<div className="flex items-center gap-4 mb-4 text-muted-foreground">
{project.date && (
<div className="flex items-center gap-2 text-sm">
<Calendar className="h-4 w-4" />
@@ -74,12 +74,12 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
</div>
{/* Title & Description */}
<h3 className="text-xl font-semibold text-white mb-3
group-hover:text-zinc-100 transition-colors duration-300">
<h3 className="text-xl font-semibold text-foreground mb-3
group-hover:text-foreground/90 transition-colors duration-300">
{project.title}
</h3>
<p className="text-zinc-400 text-sm line-clamp-2 mb-4
group-hover:text-zinc-300 transition-colors duration-300">
<p className="text-muted-foreground text-sm line-clamp-2 mb-4
group-hover:text-foreground/70 transition-colors duration-300">
{project.excerpt || project.description}
</p>
@@ -88,17 +88,17 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
{displayTags.slice(0, 3).map((tag, index) => (
<span
key={index}
className="px-3 py-1 bg-zinc-800/50
text-sm text-zinc-400 rounded-full
ring-1 ring-zinc-700/50 group-hover:ring-zinc-600/50
group-hover:bg-zinc-800/70 group-hover:text-zinc-300
className="px-3 py-1 bg-card/50
text-sm text-muted-foreground rounded-full
ring-1 ring-border/50 group-hover:ring-accent/50
group-hover:bg-card/70 group-hover:text-foreground/80
transition-all duration-300"
>
{tag}
</span>
))}
{displayTags.length > 3 && (
<span className="text-sm text-zinc-600">
<span className="text-sm text-muted-foreground/60">
+{displayTags.length - 3} more
</span>
)}
@@ -106,17 +106,17 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
{/* Link Icon */}
<div className="absolute top-6 right-6 p-2.5 rounded-full
bg-zinc-800/60 backdrop-blur-sm ring-1 ring-zinc-700/50
bg-card/60 backdrop-blur-sm ring-1 ring-border/50
opacity-0 group-hover:opacity-100
transform translate-y-2 group-hover:translate-y-0
transition-all duration-300">
<ExternalLink className="h-4 w-4 text-zinc-400" />
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</div>
</div>
{/* Subtle Hover Border */}
<div className="absolute inset-0 rounded-xl pointer-events-none
ring-1 ring-zinc-600/0 group-hover:ring-zinc-600/30
ring-1 ring-ring/0 group-hover:ring-ring/30
transition-all duration-500" />
</motion.div>
</Link>
+9 -9
View File
@@ -27,10 +27,10 @@ const About = async () => {
<div className="space-y-6 order-2 lg:order-1">
{/* Title */}
<div>
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-2">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-2">
{t('title')}
</h2>
<div className="h-1 w-20 bg-gradient-to-r from-zinc-600 to-zinc-800 rounded-full" />
<div className="h-1 w-20 bg-gradient-to-r from-primary to-muted rounded-full" />
</div>
{/* Highlight Pills */}
@@ -38,17 +38,17 @@ const About = async () => {
{highlights.map((item, index) => (
<div
key={index}
className="flex items-center gap-2 px-3 py-1.5 bg-zinc-800/30 backdrop-blur-sm rounded-full border border-zinc-700/50 hover:border-zinc-600 transition-colors"
className="flex items-center gap-2 px-3 py-1.5 bg-card/30 backdrop-blur-sm rounded-full border border-border/50 hover:border-accent transition-colors"
>
<span className="text-zinc-400">{item.icon}</span>
<span className="text-zinc-300 text-sm">{item.label}</span>
<span className="text-muted-foreground">{item.icon}</span>
<span className="text-foreground/80 text-sm">{item.label}</span>
</div>
))}
</div>
{/* Main Content */}
<div className="space-y-4">
<p className="text-zinc-300 leading-relaxed text-lg">
<p className="text-foreground/80 leading-relaxed text-lg">
{t('content')}
</p>
</div>
@@ -57,7 +57,7 @@ const About = async () => {
<div className="flex flex-wrap gap-4 pt-4">
<Link
href={`/${locale}/about`}
className="px-6 py-3 bg-zinc-800/50 backdrop-blur-sm hover:bg-zinc-700/50 text-white rounded-xl font-medium transition-colors duration-300 flex items-center gap-2"
className="px-6 py-3 bg-card/50 backdrop-blur-sm hover:bg-accent/50 text-foreground rounded-xl font-medium transition-colors duration-300 flex items-center gap-2"
>
<GraduationCap className="w-4 h-4" />
{t('buttons.learnMore')}
@@ -65,7 +65,7 @@ const About = async () => {
<a
href={getCvUrl()}
download
className="px-6 py-3 bg-transparent hover:bg-zinc-800/30 text-zinc-300 border border-zinc-700/50 rounded-xl font-medium transition-colors duration-300 flex items-center gap-2"
className="px-6 py-3 bg-transparent hover:bg-card/30 text-foreground/80 border border-border/50 rounded-xl font-medium transition-colors duration-300 flex items-center gap-2"
>
<Download className="w-4 h-4" />
{t('buttons.downloadCV')}
@@ -75,7 +75,7 @@ const About = async () => {
{/* Image Section */}
<div className="relative order-1 lg:order-2">
<div className="relative rounded-3xl overflow-hidden aspect-square bg-zinc-800/30 backdrop-blur-sm">
<div className="relative rounded-3xl overflow-hidden aspect-square bg-card/30 backdrop-blur-sm">
<Image
src="/images/gallery/frontal-smile.avif"
alt="Damjan Savić"
+10 -11
View File
@@ -73,7 +73,7 @@ const Experience = () => {
viewport={{ once: true }}
transition={{ duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] }}
>
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold text-white text-center mb-8 sm:mb-12">
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold text-foreground text-center mb-8 sm:mb-12">
{t('title')}
</h2>
<div
@@ -99,15 +99,15 @@ const Experience = () => {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.15, ease: [0.25, 0.46, 0.45, 0.94] }}
whileHover={{ scale: 1.02, y: -5, transition: { duration: 0.3 } }}
className="p-6 md:p-8 rounded-3xl bg-zinc-800/40 hover:bg-zinc-800/60 transition-all duration-300 hover:shadow-xl hover:shadow-zinc-900/50"
className="p-6 md:p-8 rounded-3xl bg-card/40 hover:bg-card/60 transition-all duration-300 hover:shadow-xl hover:shadow-background/50"
>
<h3 className="text-lg md:text-xl font-semibold text-white mb-1">
<h3 className="text-lg md:text-xl font-semibold text-foreground mb-1">
{exp.role}
</h3>
<h4 className="text-base md:text-lg text-zinc-300 mb-2">
<h4 className="text-base md:text-lg text-foreground/80 mb-2">
{exp.company}
</h4>
<p className="text-sm text-zinc-400 mb-6">
<p className="text-sm text-muted-foreground mb-6">
{exp.period}
</p>
<ul className="space-y-3 md:space-y-4">
@@ -117,9 +117,9 @@ const Experience = () => {
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4, delay: 0.3 + (hIndex * 0.1), ease: 'easeOut' }}
className="text-zinc-400 text-sm leading-relaxed flex items-start"
className="text-muted-foreground text-sm leading-relaxed flex items-start"
>
<span className="text-white/40 mr-2 mt-0.5"></span>
<span className="text-foreground/40 mr-2 mt-0.5"></span>
<span>{highlight}</span>
</motion.li>
))}
@@ -135,7 +135,7 @@ const Experience = () => {
<div className="flex justify-center items-center gap-4 mt-8">
<button
onClick={() => currentPage > 0 && setCurrentPage(prev => prev - 1)}
className="text-zinc-400 hover:text-white transition-all duration-300 p-3 sm:p-2 text-2xl font-light disabled:opacity-30"
className="text-muted-foreground hover:text-foreground transition-all duration-300 p-3 sm:p-2 text-2xl font-light disabled:opacity-30"
disabled={currentPage === 0}
aria-label="Previous page"
>
@@ -146,13 +146,12 @@ const Experience = () => {
<button
key={index}
onClick={() => setCurrentPage(index)}
className="relative flex items-center justify-center transition-all duration-300"
className="relative flex items-center justify-center w-10 h-10 sm:w-6 sm:h-6 transition-all duration-300"
aria-label={`Page ${index + 1}`}
>
<span
className={`rounded-full transition-all duration-300 ${
index === currentPage ? 'bg-white w-2.5 h-2.5' : 'bg-zinc-600 hover:bg-zinc-400 w-2 h-2'
index === currentPage ? 'bg-foreground w-2.5 h-2.5' : 'bg-muted hover:bg-muted-foreground w-2 h-2'
}`}
/>
</button>
@@ -160,7 +159,7 @@ const Experience = () => {
</div>
<button
onClick={() => currentPage < totalPages - 1 && setCurrentPage(prev => prev + 1)}
className="text-zinc-400 hover:text-white transition-all duration-300 p-3 sm:p-2 text-2xl font-light disabled:opacity-30"
className="text-muted-foreground hover:text-foreground transition-all duration-300 p-3 sm:p-2 text-2xl font-light disabled:opacity-30"
disabled={currentPage === totalPages - 1}
aria-label="Next page"
>
+16 -16
View File
@@ -6,13 +6,13 @@ const Hero = async () => {
const t = await getTranslations('pages.home.hero');
return (
<section id="home" className="relative min-h-screen text-white overflow-hidden">
<section id="home" className="relative min-h-screen text-foreground overflow-hidden">
{/* Concentric Circles Background */}
<div className="absolute inset-0 pointer-events-none flex items-center justify-center" style={{ zIndex: 2 }}>
{[1, 2, 3].map((index) => (
<div
key={index}
className="absolute rounded-full border border-zinc-800/30"
className="absolute rounded-full border border-border/30"
style={{
width: `${index * 30}%`,
height: `${index * 30}%`,
@@ -31,7 +31,7 @@ const Hero = async () => {
href="https://www.linkedin.com/in/damjan-savi%C4%87-720288127/"
target="_blank"
rel="noopener noreferrer"
className="group text-zinc-400 hover:text-white transition-colors"
className="group text-muted-foreground hover:text-foreground transition-colors"
aria-label="LinkedIn"
>
<Linkedin className="transform transition-transform group-hover:scale-110" size={20} />
@@ -40,7 +40,7 @@ const Hero = async () => {
href="https://github.com/damjan1996"
target="_blank"
rel="noopener noreferrer"
className="group text-zinc-400 hover:text-white transition-colors"
className="group text-muted-foreground hover:text-foreground transition-colors"
aria-label="GitHub"
>
<Github className="transform transition-transform group-hover:scale-110" size={20} />
@@ -48,7 +48,7 @@ const Hero = async () => {
</div>
<a
href="mailto:info@damjan-savic.com"
className="text-sm text-zinc-400 hover:text-white transition-colors uppercase tracking-wider"
className="text-sm text-muted-foreground hover:text-foreground transition-colors uppercase tracking-wider"
>
{t('cta')}
</a>
@@ -59,7 +59,7 @@ const Hero = async () => {
<div className="flex flex-col items-center justify-start min-h-screen px-4 sm:px-6 pt-24 sm:pt-32 relative" style={{ zIndex: 30 }}>
{/* Profile Image */}
<div
className="w-44 h-44 sm:w-56 sm:h-56 md:w-64 md:h-64 lg:w-72 lg:h-72 mb-6 sm:mb-8 rounded-full overflow-hidden border-2 border-zinc-800"
className="w-44 h-44 sm:w-56 sm:h-56 md:w-64 md:h-64 lg:w-72 lg:h-72 mb-6 sm:mb-8 rounded-full overflow-hidden border-2 border-border"
style={{ transform: 'translateZ(0)' }}
>
<Image
@@ -74,48 +74,48 @@ const Hero = async () => {
</div>
{/* Title and Name */}
<p className="text-zinc-400 text-xs sm:text-sm md:text-base tracking-wider mb-2">
<p className="text-muted-foreground text-xs sm:text-sm md:text-base tracking-wider mb-2">
{t('title')}
</p>
<h1 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold mb-3 flex items-center">
{t('name')}<span className="animate-pulse ml-1">|</span>
</h1>
<p className="text-zinc-300 text-sm sm:text-base max-w-xl text-center mb-2 px-2">
<p className="text-foreground/80 text-sm sm:text-base max-w-xl text-center mb-2 px-2">
{t('description')}
</p>
<p className="text-zinc-400 text-xs sm:text-sm mb-6 sm:mb-8">
<p className="text-muted-foreground text-xs sm:text-sm mb-6 sm:mb-8">
{t('currentRole')}
</p>
{/* Navigation */}
<div className="flex flex-wrap justify-center gap-3 sm:gap-4 md:gap-6 text-white text-sm sm:text-base md:text-lg z-20 w-full max-w-2xl">
<div className="flex flex-wrap justify-center gap-3 sm:gap-4 md:gap-6 text-foreground text-sm sm:text-base md:text-lg z-20 w-full max-w-2xl">
<a
href="#experience"
className="relative px-5 sm:px-8 py-2.5 sm:py-3 rounded-full uppercase cursor-pointer overflow-hidden group text-center"
>
<span className="relative z-10">{t('navigation.experience')}</span>
<div className="absolute inset-0 bg-zinc-700/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-full" />
<div className="absolute inset-0 bg-accent/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-full" />
</a>
<a
href="#skills"
className="relative px-5 sm:px-8 py-2.5 sm:py-3 rounded-full uppercase cursor-pointer overflow-hidden group text-center"
>
<span className="relative z-10">{t('navigation.skills')}</span>
<div className="absolute inset-0 bg-zinc-700/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-full" />
<div className="absolute inset-0 bg-accent/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-full" />
</a>
<a
href="#projects"
className="relative px-5 sm:px-8 py-2.5 sm:py-3 rounded-full uppercase cursor-pointer overflow-hidden group text-center"
>
<span className="relative z-10">{t('navigation.projects')}</span>
<div className="absolute inset-0 bg-zinc-700/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-full" />
<div className="absolute inset-0 bg-accent/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-full" />
</a>
<a
href="#about"
className="relative px-5 sm:px-8 py-2.5 sm:py-3 rounded-full uppercase cursor-pointer overflow-hidden group text-center"
>
<span className="relative z-10">{t('navigation.about')}</span>
<div className="absolute inset-0 bg-zinc-700/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-full" />
<div className="absolute inset-0 bg-accent/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-full" />
</a>
</div>
</div>
@@ -124,10 +124,10 @@ const Hero = async () => {
<div className="absolute bottom-6 sm:bottom-8 left-1/2 transform -translate-x-1/2" style={{ zIndex: 40 }}>
<a
href="mailto:info@damjan-savic.com"
className="w-12 h-12 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/20 transition-colors"
className="w-12 h-12 flex items-center justify-center rounded-full bg-foreground/10 hover:bg-foreground/20 transition-colors"
aria-label={t('social.getInTouch')}
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="white">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M0 3v18h24v-18h-24zm21.518 2l-9.518 7.713-9.518-7.713h19.036zm-19.518 14v-11.817l10 8.104 10-8.104v11.817h-20z"/>
</svg>
</a>
+16 -16
View File
@@ -39,12 +39,12 @@ const Projects = async () => {
<section id="projects" className="py-12 sm:py-16 md:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center mb-8 sm:mb-12">
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold text-zinc-100">
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold text-foreground">
{t('title')}
</h2>
<Link
href={`/${locale}/portfolio`}
className="text-zinc-400 hover:text-white transition-colors duration-300"
className="text-muted-foreground hover:text-foreground transition-colors duration-300"
>
{t('viewAll')}
</Link>
@@ -55,16 +55,16 @@ const Projects = async () => {
<Link
key={project.slug}
href={`/${locale}/portfolio/${project.slug}`}
className="group relative block bg-zinc-900/50 backdrop-blur-sm
className="group relative block bg-background/50 backdrop-blur-sm
rounded-xl shadow-lg hover:shadow-2xl
ring-1 ring-zinc-800 hover:ring-zinc-700
ring-1 ring-border hover:ring-accent
transition-all duration-500 focus:outline-none focus:ring-2
focus:ring-zinc-600 focus:ring-offset-2 focus:ring-offset-zinc-950
focus:ring-ring focus:ring-offset-2 focus:ring-offset-background
transform hover:-translate-y-1"
>
<div className="relative overflow-hidden rounded-xl">
{/* Image Container */}
<div className="aspect-[16/9] w-full relative overflow-hidden bg-zinc-900">
<div className="aspect-[16/9] w-full relative overflow-hidden bg-background">
<Image
src={`/images/projects/${project.slug}/cover.avif`}
alt={project.title}
@@ -75,8 +75,8 @@ const Projects = async () => {
</div>
{/* Content */}
<div className="relative -mt-2 p-6 bg-zinc-900 z-10">
<div className="flex items-center gap-4 mb-4 text-zinc-500">
<div className="relative -mt-2 p-6 bg-background z-10">
<div className="flex items-center gap-4 mb-4 text-muted-foreground">
{project.date && (
<div className="flex items-center gap-2 text-sm">
<Calendar className="h-4 w-4" />
@@ -91,10 +91,10 @@ const Projects = async () => {
)}
</div>
<h3 className="text-xl font-semibold text-white mb-3 group-hover:text-zinc-100 transition-colors duration-300">
<h3 className="text-xl font-semibold text-foreground mb-3 group-hover:text-foreground/90 transition-colors duration-300">
{project.title}
</h3>
<p className="text-zinc-400 text-sm line-clamp-2 mb-4 group-hover:text-zinc-300 transition-colors duration-300">
<p className="text-muted-foreground text-sm line-clamp-2 mb-4 group-hover:text-foreground/70 transition-colors duration-300">
{project.excerpt}
</p>
@@ -103,16 +103,16 @@ const Projects = async () => {
{project.technologies.slice(0, 3).map((tag, tagIndex) => (
<span
key={tagIndex}
className="px-3 py-1 bg-zinc-800/50 text-sm text-zinc-400 rounded-full
ring-1 ring-zinc-700/50 group-hover:ring-zinc-600/50
group-hover:bg-zinc-800/70 group-hover:text-zinc-300
className="px-3 py-1 bg-card/50 text-sm text-muted-foreground rounded-full
ring-1 ring-border/50 group-hover:ring-accent/50
group-hover:bg-card/70 group-hover:text-foreground/80
transition-all duration-300"
>
{tag}
</span>
))}
{project.technologies.length > 3 && (
<span className="text-sm text-zinc-600">
<span className="text-sm text-muted-foreground/60">
+{project.technologies.length - 3} more
</span>
)}
@@ -120,11 +120,11 @@ const Projects = async () => {
{/* Link Icon */}
<div className="absolute top-6 right-6 p-2.5 rounded-full
bg-zinc-800/60 backdrop-blur-sm ring-1 ring-zinc-700/50
bg-card/60 backdrop-blur-sm ring-1 ring-border/50
opacity-0 group-hover:opacity-100
transform translate-y-2 group-hover:translate-y-0
transition-all duration-300">
<ExternalLink className="h-4 w-4 text-zinc-400" />
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</div>
</div>
</div>
+13 -13
View File
@@ -16,7 +16,7 @@ interface SkeletonProps {
}
const Skeleton: React.FC<SkeletonProps> = ({ className }) => (
<div className={`animate-pulse bg-zinc-700/50 rounded ${className}`}></div>
<div className={`animate-pulse bg-muted/50 rounded ${className}`}></div>
);
const SkillsSkeleton = () => (
@@ -40,7 +40,7 @@ const SkillsSkeleton = () => (
{/* Skills Icons Grid Skeleton */}
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
{[1, 2, 3, 4, 5, 6].map((i) => (
<div key={i} className="p-6 rounded-xl bg-zinc-800/30 backdrop-blur-sm">
<div key={i} className="p-6 rounded-xl bg-card/30 backdrop-blur-sm">
<Skeleton className="w-8 h-8 mx-auto mb-3" />
<Skeleton className="h-4 w-16 mx-auto" />
</div>
@@ -106,7 +106,7 @@ const Skills = () => {
viewport={{ once: true }}
transition={{ duration: 0.6, ease: [0.25, 0.1, 0.25, 1] }}
>
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold text-white mb-8 sm:mb-12">
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold text-foreground mb-8 sm:mb-12">
{t('title')}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8">
@@ -122,18 +122,18 @@ const Skills = () => {
transition={{ duration: 0.5, delay: idx * 0.1 }}
>
<div className="flex justify-between items-center">
<span className="text-white text-sm font-medium">{skill.name}</span>
<span className="text-white text-sm">{skill.level}%</span>
<span className="text-foreground text-sm font-medium">{skill.name}</span>
<span className="text-foreground text-sm">{skill.level}%</span>
</div>
<div
className="h-2 bg-zinc-800/50 rounded-full overflow-hidden"
className="h-2 bg-card/50 rounded-full overflow-hidden"
role="progressbar"
aria-valuenow={skill.level}
aria-valuemin={0}
aria-valuemax={100}
>
<motion.div
className="h-full rounded-full bg-gradient-to-r from-zinc-600 to-zinc-500"
className="h-full rounded-full bg-gradient-to-r from-primary to-accent"
initial={{ width: 0 }}
whileInView={{ width: `${skill.level}%` }}
viewport={{ once: true }}
@@ -153,8 +153,8 @@ const Skills = () => {
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: idx * 0.1, ease: [0.25, 0.1, 0.25, 1] }}
className={`relative p-6 rounded-xl bg-zinc-800/30 backdrop-blur-sm flex flex-col items-center justify-center gap-3 cursor-pointer transition-colors duration-300 hover:bg-zinc-700/50 ${
selectedSkill === skill.name ? 'ring-2 ring-zinc-500 z-20' : ''
className={`relative p-6 rounded-xl bg-card/30 backdrop-blur-sm flex flex-col items-center justify-center gap-3 cursor-pointer transition-colors duration-300 hover:bg-accent/50 ${
selectedSkill === skill.name ? 'ring-2 ring-ring z-20' : ''
}`}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.98 }}
@@ -163,7 +163,7 @@ const Skills = () => {
role="button"
>
<motion.div
className={selectedSkill === skill.name ? 'text-zinc-500' : 'text-white'}
className={selectedSkill === skill.name ? 'text-accent' : 'text-foreground'}
animate={{
rotate: selectedSkill === skill.name ? [0, -10, 10, 0] : 0,
scale: selectedSkill === skill.name ? 1.1 : 1
@@ -172,12 +172,12 @@ const Skills = () => {
>
{iconMap[skill.name] || <Code2 className="w-8 h-8" />}
</motion.div>
<span className="text-white text-sm text-center">{skill.name}</span>
<span className="text-foreground text-sm text-center">{skill.name}</span>
<AnimatePresence>
{selectedSkill === skill.name && skill.description && (
<motion.div
className="absolute left-1/2 top-0 -translate-x-1/2 bg-zinc-800/80 backdrop-blur-sm rounded-lg px-4 py-2 text-zinc-300 text-xs text-center shadow-xl pointer-events-none"
className="absolute left-1/2 top-0 -translate-x-1/2 bg-card/80 backdrop-blur-sm rounded-lg px-4 py-2 text-foreground/80 text-xs text-center shadow-xl pointer-events-none"
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 5 }}
@@ -189,7 +189,7 @@ const Skills = () => {
}}
>
{skill.description}
<div className="absolute left-1/2 bottom-0 translate-y-1/2 -translate-x-1/2 w-0 h-0 border-l-[6px] border-l-transparent border-r-[6px] border-r-transparent border-t-[6px] border-t-zinc-800" />
<div className="absolute left-1/2 bottom-0 translate-y-1/2 -translate-x-1/2 w-0 h-0 border-l-[6px] border-l-transparent border-r-[6px] border-r-transparent border-t-[6px] border-t-card" />
</motion.div>
)}
</AnimatePresence>
+19 -19
View File
@@ -19,7 +19,7 @@ function parseInlineMarkdown(text: string): React.ReactNode {
const codeMatch = remaining.match(/^`([^`]+)`/);
if (codeMatch) {
parts.push(
<code key={keyIndex++} className="bg-zinc-800 px-1.5 py-0.5 rounded text-[#697565] text-sm font-mono">
<code key={keyIndex++} className="bg-card px-1.5 py-0.5 rounded text-[#697565] text-sm font-mono">
{codeMatch[1]}
</code>
);
@@ -30,7 +30,7 @@ function parseInlineMarkdown(text: string): React.ReactNode {
// Bold
const boldMatch = remaining.match(/^\*\*([^*]+)\*\*/);
if (boldMatch) {
parts.push(<strong key={keyIndex++} className="font-semibold text-white">{boldMatch[1]}</strong>);
parts.push(<strong key={keyIndex++} className="font-semibold text-foreground">{boldMatch[1]}</strong>);
remaining = remaining.slice(boldMatch[0].length);
continue;
}
@@ -88,13 +88,13 @@ export function renderMarkdown(content: string): React.ReactNode {
if (listItems) {
if (listItems.type === 'ul') {
elements.push(
<ul key={`ul-${elements.length}`} className="list-disc list-inside space-y-2 my-4 text-zinc-300">
<ul key={`ul-${elements.length}`} className="list-disc list-inside space-y-2 my-4 text-foreground/80">
{listItems.items}
</ul>
);
} else {
elements.push(
<ol key={`ol-${elements.length}`} className="list-decimal list-inside space-y-2 my-4 text-zinc-300">
<ol key={`ol-${elements.length}`} className="list-decimal list-inside space-y-2 my-4 text-foreground/80">
{listItems.items}
</ol>
);
@@ -111,9 +111,9 @@ export function renderMarkdown(content: string): React.ReactNode {
<div key={`table-${elements.length}`} className="overflow-x-auto my-6">
<table className="min-w-full border-collapse">
<thead>
<tr className="border-b border-zinc-700">
<tr className="border-b border-border">
{header.map((cell, idx) => (
<th key={idx} className="px-4 py-2 text-left text-zinc-200 font-semibold">
<th key={idx} className="px-4 py-2 text-left text-foreground/90 font-semibold">
{cell.trim()}
</th>
))}
@@ -121,9 +121,9 @@ export function renderMarkdown(content: string): React.ReactNode {
</thead>
<tbody>
{body.map((row, rowIdx) => (
<tr key={rowIdx} className="border-b border-zinc-800">
<tr key={rowIdx} className="border-b border-border">
{row.map((cell, cellIdx) => (
<td key={cellIdx} className="px-4 py-2 text-zinc-300">
<td key={cellIdx} className="px-4 py-2 text-foreground/80">
{cell.trim()}
</td>
))}
@@ -145,8 +145,8 @@ export function renderMarkdown(content: string): React.ReactNode {
if (codeBlock) {
// End code block
elements.push(
<pre key={`code-${elements.length}`} className="bg-zinc-900 border border-zinc-800 rounded-lg p-4 overflow-x-auto my-6">
<code className="text-sm font-mono text-zinc-300">
<pre key={`code-${elements.length}`} className="bg-background border border-border rounded-lg p-4 overflow-x-auto my-6">
<code className="text-sm font-mono text-foreground/80">
{codeBlock.lines.join('\n')}
</code>
</pre>
@@ -188,7 +188,7 @@ export function renderMarkdown(content: string): React.ReactNode {
if (line.startsWith('#### ')) {
flushList();
elements.push(
<h4 key={`h4-${elements.length}`} className="text-lg font-semibold text-white mt-5 mb-2">
<h4 key={`h4-${elements.length}`} className="text-lg font-semibold text-foreground mt-5 mb-2">
{parseInlineMarkdown(line.slice(5))}
</h4>
);
@@ -199,7 +199,7 @@ export function renderMarkdown(content: string): React.ReactNode {
if (line.startsWith('### ')) {
flushList();
elements.push(
<h3 key={`h3-${elements.length}`} className="text-xl font-semibold text-white mt-6 mb-3">
<h3 key={`h3-${elements.length}`} className="text-xl font-semibold text-foreground mt-6 mb-3">
{parseInlineMarkdown(line.slice(4))}
</h3>
);
@@ -210,7 +210,7 @@ export function renderMarkdown(content: string): React.ReactNode {
if (line.startsWith('## ')) {
flushList();
elements.push(
<h2 key={`h2-${elements.length}`} className="text-2xl font-bold text-white mt-8 mb-4">
<h2 key={`h2-${elements.length}`} className="text-2xl font-bold text-foreground mt-8 mb-4">
{parseInlineMarkdown(line.slice(3))}
</h2>
);
@@ -221,7 +221,7 @@ export function renderMarkdown(content: string): React.ReactNode {
if (line.startsWith('# ')) {
flushList();
elements.push(
<h1 key={`h1-${elements.length}`} className="text-3xl font-bold text-white mt-8 mb-4">
<h1 key={`h1-${elements.length}`} className="text-3xl font-bold text-foreground mt-8 mb-4">
{parseInlineMarkdown(line.slice(2))}
</h1>
);
@@ -269,7 +269,7 @@ export function renderMarkdown(content: string): React.ReactNode {
const itemContent = line.replace(/^[-*]\s\[[ x]\]\s?/i, '');
listItems.items.push(
<li key={`li-${listItems.items.length}`} className="flex items-center gap-2">
<span className={checked ? 'text-green-500' : 'text-zinc-500'}>
<span className={checked ? 'text-green-500' : 'text-muted-foreground'}>
{checked ? '✓' : '○'}
</span>
{parseInlineMarkdown(itemContent)}
@@ -282,7 +282,7 @@ export function renderMarkdown(content: string): React.ReactNode {
// Horizontal rule
if (line.match(/^[-*_]{3,}$/)) {
flushList();
elements.push(<hr key={`hr-${elements.length}`} className="border-zinc-700 my-8" />);
elements.push(<hr key={`hr-${elements.length}`} className="border-border my-8" />);
i++;
continue;
}
@@ -291,7 +291,7 @@ export function renderMarkdown(content: string): React.ReactNode {
if (line.startsWith('> ')) {
flushList();
elements.push(
<blockquote key={`quote-${elements.length}`} className="border-l-4 border-[#697565] pl-4 my-4 text-zinc-400 italic">
<blockquote key={`quote-${elements.length}`} className="border-l-4 border-[#697565] pl-4 my-4 text-muted-foreground italic">
{parseInlineMarkdown(line.slice(2))}
</blockquote>
);
@@ -309,7 +309,7 @@ export function renderMarkdown(content: string): React.ReactNode {
// Regular paragraph
flushList();
elements.push(
<p key={`p-${elements.length}`} className="text-zinc-300 mb-4 leading-relaxed">
<p key={`p-${elements.length}`} className="text-foreground/80 mb-4 leading-relaxed">
{parseInlineMarkdown(line)}
</p>
);
@@ -320,5 +320,5 @@ export function renderMarkdown(content: string): React.ReactNode {
flushList();
flushTable();
return <div className="prose prose-invert prose-zinc max-w-none">{elements}</div>;
return <div className="prose prose-invert max-w-none">{elements}</div>;
}
+4 -4
View File
@@ -151,7 +151,7 @@
{
"role": "Process Automation Specialist",
"company": "Everlast Consulting GmbH",
"period": "12/2024 - HEUTE",
"period": "12/2025 - HEUTE",
"highlights": [
"Entwicklung von KI-Agenten mit n8n und Zapier",
"Aufbau von Web-Scraping-Lösungen",
@@ -161,7 +161,7 @@
{
"role": "Consultant Digital Solutions",
"company": "Ritter Digital GmbH",
"period": "08/2023 - 11/2024",
"period": "08/2023 - 11/2025",
"highlights": [
"Backend-Services auf dedizierten Servern",
"KI-Integration via Power Automate",
@@ -327,7 +327,7 @@
"positions": [
{
"title": "Process Automation Specialist",
"period": "12/2024 - Heute",
"period": "12/2025 - Heute",
"company": "Everlast Consulting GmbH",
"location": "Köln",
"highlights": [
@@ -338,7 +338,7 @@
},
{
"title": "Consultant Digital Solutions",
"period": "08/2023 - 11/2024",
"period": "08/2023 - 11/2025",
"company": "Ritter Digital GmbH",
"location": "Köln",
"highlights": [
+4 -4
View File
@@ -165,7 +165,7 @@
{
"role": "Process Automation Specialist",
"company": "Everlast Consulting GmbH",
"period": "12/2024 - PRESENT",
"period": "12/2025 - PRESENT",
"highlights": [
"Development of AI agents with n8n and Zapier",
"Building web scraping solutions",
@@ -175,7 +175,7 @@
{
"role": "Consultant Digital Solutions",
"company": "Ritter Digital GmbH",
"period": "08/2023 - 11/2024",
"period": "08/2023 - 11/2025",
"highlights": [
"Backend services on dedicated servers",
"AI integration via Power Automate",
@@ -342,7 +342,7 @@
"positions": [
{
"title": "Process Automation Specialist",
"period": "12/2024 - Present",
"period": "12/2025 - Present",
"company": "Everlast Consulting GmbH",
"location": "Cologne",
"highlights": [
@@ -353,7 +353,7 @@
},
{
"title": "Consultant Digital Solutions",
"period": "08/2023 - 11/2024",
"period": "08/2023 - 11/2025",
"company": "Ritter Digital GmbH",
"location": "Cologne",
"highlights": [
+4 -4
View File
@@ -172,7 +172,7 @@
{
"role": "Process Automation Specialist",
"company": "Everlast Consulting GmbH",
"period": "12/2024 - DANAS",
"period": "12/2025 - DANAS",
"highlights": [
"Razvoj AI agenata sa n8n i Zapier",
"Izgradnja web scraping rešenja",
@@ -182,7 +182,7 @@
{
"role": "Consultant Digital Solutions",
"company": "Ritter Digital GmbH",
"period": "08/2023 - 11/2024",
"period": "08/2023 - 11/2025",
"highlights": [
"Backend servisi na namenskim serverima",
"AI integracija preko Power Automate",
@@ -349,7 +349,7 @@
"positions": [
{
"title": "Process Automation Specialist",
"period": "12/2024 - Danas",
"period": "12/2025 - Danas",
"company": "Everlast Consulting GmbH",
"location": "Keln",
"highlights": [
@@ -360,7 +360,7 @@
},
{
"title": "Consultant Digital Solutions",
"period": "08/2023 - 11/2024",
"period": "08/2023 - 11/2025",
"company": "Ritter Digital GmbH",
"location": "Keln",
"highlights": [
+1 -1
View File
@@ -27,7 +27,7 @@ export const CONTACT = {
export const CURRENT_ROLE = {
company: "Everlast Consulting GmbH",
position: "Process Automation Specialist",
since: "2024-12"
since: "2025-12"
} as const;
/**