chore: capture live server state before migration (2026-06-10)
This commit is contained in:
@@ -6,7 +6,7 @@ Personal portfolio website showcasing my work as an **AI & Automation Specialist
|
|||||||
|
|
||||||
## Current Role
|
## 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
|
- Developing AI agents with n8n and Zapier
|
||||||
- Building web scraping solutions
|
- Building web scraping solutions
|
||||||
- Migration from Power Automate to n8n
|
- Migration from Power Automate to n8n
|
||||||
|
|||||||
@@ -65,5 +65,15 @@
|
|||||||
"tailwindcss": "^4.1.0",
|
"tailwindcss": "^4.1.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vitest": "^1.3.1"
|
"vitest": "^1.3.1"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"@parcel/watcher",
|
||||||
|
"@swc/core",
|
||||||
|
"esbuild",
|
||||||
|
"sharp",
|
||||||
|
"unrs-resolver"
|
||||||
|
],
|
||||||
|
"ignoredBuiltDependencies": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ export default defineConfig({
|
|||||||
|
|
||||||
// Shared settings for all projects
|
// Shared settings for all projects
|
||||||
use: {
|
use: {
|
||||||
// Base URL - uses dedicated test port to avoid conflicts with other dev servers
|
// Base URL - test against running Docker container
|
||||||
baseURL: 'http://localhost:3100',
|
baseURL: process.env.BASE_URL || 'http://localhost:3001',
|
||||||
|
|
||||||
// Collect trace when retrying the failed test
|
// Collect trace when retrying the failed test
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
@@ -71,13 +71,13 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
// Configure web server to start before tests on a dedicated port
|
// Use running Docker container - no webServer needed
|
||||||
webServer: {
|
// webServer: {
|
||||||
command: 'pnpm dev --port 3100',
|
// command: 'pnpm dev --port 3100',
|
||||||
url: 'http://localhost:3100',
|
// url: 'http://localhost:3100',
|
||||||
reuseExistingServer: false,
|
// reuseExistingServer: false,
|
||||||
timeout: 120000,
|
// timeout: 120000,
|
||||||
},
|
// },
|
||||||
|
|
||||||
// Global timeout for each test
|
// Global timeout for each test
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
|
|||||||
Generated
+6
-5
@@ -2105,6 +2105,7 @@ packages:
|
|||||||
basic-ftp@5.1.0:
|
basic-ftp@5.1.0:
|
||||||
resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==}
|
resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
deprecated: Security vulnerability fixed in 5.2.1, please upgrade
|
||||||
|
|
||||||
bidi-js@1.0.3:
|
bidi-js@1.0.3:
|
||||||
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
||||||
@@ -4366,8 +4367,8 @@ packages:
|
|||||||
third-party-web@0.27.0:
|
third-party-web@0.27.0:
|
||||||
resolution: {integrity: sha512-h0JYX+dO2Zr3abCQpS6/uFjujaOjA1DyDzGQ41+oFn9VW/ARiq9g5ln7qEP9+BTzDpOMyIfsfj4OvfgXAsMUSA==}
|
resolution: {integrity: sha512-h0JYX+dO2Zr3abCQpS6/uFjujaOjA1DyDzGQ41+oFn9VW/ARiq9g5ln7qEP9+BTzDpOMyIfsfj4OvfgXAsMUSA==}
|
||||||
|
|
||||||
third-party-web@0.29.0:
|
third-party-web@0.29.2:
|
||||||
resolution: {integrity: sha512-nBDSJw5B7Sl1YfsATG2XkW5qgUPODbJhXw++BKygi9w6O/NKS98/uY/nR/DxDq2axEjL6halHW1v+jhm/j1DBQ==}
|
resolution: {integrity: sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g==}
|
||||||
|
|
||||||
tinybench@2.9.0:
|
tinybench@2.9.0:
|
||||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
@@ -5798,7 +5799,7 @@ snapshots:
|
|||||||
'@paulirish/trace_engine@0.0.61':
|
'@paulirish/trace_engine@0.0.61':
|
||||||
dependencies:
|
dependencies:
|
||||||
legacy-javascript: 0.0.1
|
legacy-javascript: 0.0.1
|
||||||
third-party-web: 0.29.0
|
third-party-web: 0.29.2
|
||||||
|
|
||||||
'@playwright/test@1.58.0':
|
'@playwright/test@1.58.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6136,7 +6137,7 @@ snapshots:
|
|||||||
|
|
||||||
'@testing-library/dom@9.3.4':
|
'@testing-library/dom@9.3.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.27.1
|
'@babel/code-frame': 7.29.0
|
||||||
'@babel/runtime': 7.28.6
|
'@babel/runtime': 7.28.6
|
||||||
'@types/aria-query': 5.0.4
|
'@types/aria-query': 5.0.4
|
||||||
aria-query: 5.1.3
|
aria-query: 5.1.3
|
||||||
@@ -9606,7 +9607,7 @@ snapshots:
|
|||||||
|
|
||||||
third-party-web@0.27.0: {}
|
third-party-web@0.27.0: {}
|
||||||
|
|
||||||
third-party-web@0.29.0: {}
|
third-party-web@0.29.2: {}
|
||||||
|
|
||||||
tinybench@2.9.0: {}
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
ignoredBuiltDependencies:
|
||||||
|
- '@parcel/watcher'
|
||||||
|
- '@swc/core'
|
||||||
|
- esbuild
|
||||||
|
- sharp
|
||||||
|
- unrs-resolver
|
||||||
+6
-14
@@ -1,16 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1000 1000">
|
<rect width="32" height="32" rx="6" fill="#181C14"/>
|
||||||
<!-- Generator: Adobe Illustrator 29.7.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 8) -->
|
<g transform="translate(2.5, 4.5) scale(0.027)">
|
||||||
<defs>
|
<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"/>
|
||||||
<style>
|
<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"/>
|
||||||
.st0 {
|
<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"/>
|
||||||
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"/>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -121,10 +121,10 @@ export default async function AboutPage({ params }: Props) {
|
|||||||
<section id="skills" className="py-24 relative">
|
<section id="skills" className="py-24 relative">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="text-center mb-16">
|
<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')}
|
{t('skills.title')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 max-w-2xl mx-auto">
|
<p className="text-muted-foreground max-w-2xl mx-auto">
|
||||||
{t('skills.description')}
|
{t('skills.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,10 +136,10 @@ export default async function AboutPage({ params }: Props) {
|
|||||||
<section className="py-24 relative">
|
<section className="py-24 relative">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="text-center mb-16">
|
<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')}
|
{t('workflow.title')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 max-w-2xl mx-auto">
|
<p className="text-muted-foreground max-w-2xl mx-auto">
|
||||||
{t('workflow.description')}
|
{t('workflow.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1335,7 +1335,7 @@ export default async function BlogPostPage({ params }: Props) {
|
|||||||
{/* Back Link */}
|
{/* Back Link */}
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/blog`}
|
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">
|
<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" />
|
<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 */}
|
||||||
<header className="mb-12">
|
<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>
|
<time dateTime={post.date}>{getFormattedDate(post.date)}</time>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Clock className="h-4 w-4" />
|
<Clock className="h-4 w-4" />
|
||||||
<span>{readingTime} min read</span>
|
<span>{readingTime} min read</span>
|
||||||
</div>
|
</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}
|
{post.category}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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}
|
{post.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-xl text-zinc-400">
|
<p className="text-xl text-muted-foreground">
|
||||||
{post.excerpt}
|
{post.excerpt}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -1369,7 +1369,7 @@ export default async function BlogPostPage({ params }: Props) {
|
|||||||
{post.tags?.map((tag, index) => (
|
{post.tags?.map((tag, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
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}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
@@ -1378,7 +1378,7 @@ export default async function BlogPostPage({ params }: Props) {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Cover Image */}
|
{/* 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
|
<Image
|
||||||
src={post.coverImage}
|
src={post.coverImage}
|
||||||
alt={post.title}
|
alt={post.title}
|
||||||
|
|||||||
@@ -129,26 +129,26 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/blog/${post.slug}`}
|
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
|
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
|
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"
|
transform hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<div className="relative overflow-hidden rounded-xl">
|
<div className="relative overflow-hidden rounded-xl">
|
||||||
{/* Image Container */}
|
{/* 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
|
<BlogImage
|
||||||
src={getImagePath(post.coverImage)}
|
src={getImagePath(post.coverImage)}
|
||||||
alt={post.title}
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="relative -mt-2 p-6 bg-zinc-900 z-10">
|
<div className="relative -mt-2 p-6 bg-background z-10">
|
||||||
<div className="flex items-center gap-4 mb-4 text-zinc-500">
|
<div className="flex items-center gap-4 mb-4 text-muted-foreground">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
<span>{getFormattedDate(post.date, locale)}</span>
|
<span>{getFormattedDate(post.date, locale)}</span>
|
||||||
@@ -167,12 +167,12 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-xl font-semibold text-white mb-3
|
<h3 className="text-xl font-semibold text-foreground mb-3
|
||||||
group-hover:text-zinc-100 transition-colors duration-300">
|
group-hover:text-foreground transition-colors duration-300">
|
||||||
{post.title}
|
{post.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400 text-sm line-clamp-2 mb-4
|
<p className="text-muted-foreground text-sm line-clamp-2 mb-4
|
||||||
group-hover:text-zinc-300 transition-colors duration-300">
|
group-hover:text-foreground/80 transition-colors duration-300">
|
||||||
{post.excerpt}
|
{post.excerpt}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -181,17 +181,17 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
|
|||||||
{post.tags?.slice(0, 3).map((tag, index) => (
|
{post.tags?.slice(0, 3).map((tag, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="px-3 py-1 bg-zinc-800/50
|
className="px-3 py-1 bg-card/50
|
||||||
text-sm text-zinc-400 rounded-full
|
text-sm text-muted-foreground rounded-full
|
||||||
ring-1 ring-zinc-700/50 group-hover:ring-zinc-600/50
|
ring-1 ring-border/50 group-hover:ring-ring/50
|
||||||
group-hover:bg-zinc-800/70 group-hover:text-zinc-300
|
group-hover:bg-card/70 group-hover:text-foreground/80
|
||||||
transition-all duration-300"
|
transition-all duration-300"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{post.tags && post.tags.length > 3 && (
|
{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
|
+{post.tags.length - 3} more
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -199,11 +199,11 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
|
|||||||
|
|
||||||
{/* Link Icon */}
|
{/* Link Icon */}
|
||||||
<div className="absolute top-6 right-6 p-2.5 rounded-full
|
<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
|
opacity-0 group-hover:opacity-100
|
||||||
transform translate-y-2 group-hover:translate-y-0
|
transform translate-y-2 group-hover:translate-y-0
|
||||||
transition-all duration-300">
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -266,9 +266,9 @@ function Pagination({
|
|||||||
{currentPage > 1 ? (
|
{currentPage > 1 ? (
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/blog${currentPage > 2 ? `?page=${currentPage - 1}` : ''}`}
|
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
|
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:text-foreground
|
||||||
bg-zinc-800/50 hover:bg-zinc-800 rounded-lg ring-1 ring-zinc-700/50
|
bg-card/50 hover:bg-card rounded-lg ring-1 ring-border/50
|
||||||
hover:ring-zinc-600 transition-all duration-200"
|
hover:ring-accent transition-all duration-200"
|
||||||
aria-label={t('ui.pagination.previous')}
|
aria-label={t('ui.pagination.previous')}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
@@ -276,8 +276,8 @@ function Pagination({
|
|||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-600
|
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground/60
|
||||||
bg-zinc-800/30 rounded-lg ring-1 ring-zinc-800/50 cursor-not-allowed"
|
bg-card/30 rounded-lg ring-1 ring-border/50 cursor-not-allowed"
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
@@ -290,7 +290,7 @@ function Pagination({
|
|||||||
{getPageNumbers().map((page, index) => {
|
{getPageNumbers().map((page, index) => {
|
||||||
if (page === 'ellipsis') {
|
if (page === 'ellipsis') {
|
||||||
return (
|
return (
|
||||||
<span key={`ellipsis-${index}`} className="px-2 text-zinc-600">
|
<span key={`ellipsis-${index}`} className="px-2 text-muted-foreground/60">
|
||||||
...
|
...
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -306,7 +306,7 @@ function Pagination({
|
|||||||
ring-1 transition-all duration-200
|
ring-1 transition-all duration-200
|
||||||
${isCurrentPage
|
${isCurrentPage
|
||||||
? 'bg-[#697565] text-white ring-[#697565] font-medium'
|
? '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}
|
aria-current={isCurrentPage ? 'page' : undefined}
|
||||||
>
|
>
|
||||||
@@ -320,9 +320,9 @@ function Pagination({
|
|||||||
{currentPage < totalPages ? (
|
{currentPage < totalPages ? (
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/blog?page=${currentPage + 1}`}
|
href={`/${locale}/blog?page=${currentPage + 1}`}
|
||||||
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-400 hover:text-white
|
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:text-foreground
|
||||||
bg-zinc-800/50 hover:bg-zinc-800 rounded-lg ring-1 ring-zinc-700/50
|
bg-card/50 hover:bg-card rounded-lg ring-1 ring-border/50
|
||||||
hover:ring-zinc-600 transition-all duration-200"
|
hover:ring-accent transition-all duration-200"
|
||||||
aria-label={t('ui.pagination.next')}
|
aria-label={t('ui.pagination.next')}
|
||||||
>
|
>
|
||||||
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
|
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
|
||||||
@@ -330,8 +330,8 @@ function Pagination({
|
|||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-600
|
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground/60
|
||||||
bg-zinc-800/30 rounded-lg ring-1 ring-zinc-800/50 cursor-not-allowed"
|
bg-card/30 rounded-lg ring-1 ring-border/50 cursor-not-allowed"
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
>
|
>
|
||||||
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
|
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
|
||||||
@@ -387,17 +387,17 @@ export default async function BlogPage({ params, searchParams }: Props) {
|
|||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<div className="mb-12 sm:mb-16 lg:mb-20">
|
<div className="mb-12 sm:mb-16 lg:mb-20">
|
||||||
<div className="flex items-center gap-4 mb-8">
|
<div className="flex items-center gap-4 mb-8">
|
||||||
<ArrowRight className="h-5 w-5 text-zinc-500" />
|
<ArrowRight className="h-5 w-5 text-muted-foreground" />
|
||||||
<h2 className="text-white text-sm tracking-wider">BLOG</h2>
|
<h2 className="text-foreground text-sm tracking-wider">BLOG</h2>
|
||||||
</div>
|
</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')}
|
{t('meta.header.title')}
|
||||||
</h1>
|
</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')}
|
{t('meta.header.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
{/* Post count */}
|
{/* Post count */}
|
||||||
<p className="text-sm text-zinc-500 mt-4">
|
<p className="text-sm text-muted-foreground mt-4">
|
||||||
{t('ui.pagination.showing', {
|
{t('ui.pagination.showing', {
|
||||||
start: startIndex + 1,
|
start: startIndex + 1,
|
||||||
end: Math.min(endIndex, allPosts.length),
|
end: Math.min(endIndex, allPosts.length),
|
||||||
@@ -424,7 +424,7 @@ export default async function BlogPage({ params, searchParams }: Props) {
|
|||||||
{/* Empty State */}
|
{/* Empty State */}
|
||||||
{posts.length === 0 && (
|
{posts.length === 0 && (
|
||||||
<div className="text-center py-12">
|
<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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -221,21 +221,21 @@ export default async function ImprintPage({ params }: Props) {
|
|||||||
<nav className="mb-8">
|
<nav className="mb-8">
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}`}
|
href={`/${locale}`}
|
||||||
className="text-zinc-400 hover:text-white transition-colors"
|
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Home
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
<span className="mx-2 text-zinc-600">/</span>
|
<span className="mx-2 text-muted-foreground/60">/</span>
|
||||||
<span className="text-white">{c.title}</span>
|
<span className="text-foreground">{c.title}</span>
|
||||||
</nav>
|
</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">
|
<div className="prose prose-invert max-w-none space-y-8">
|
||||||
{/* Contact */}
|
{/* Contact */}
|
||||||
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
|
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.contact.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.contact.title}</h2>
|
||||||
<div className="text-zinc-300 space-y-1">
|
<div className="text-foreground/80 space-y-1">
|
||||||
{c.contact.content.map((line, index) => (
|
{c.contact.content.map((line, index) => (
|
||||||
<p key={index}>{line}</p>
|
<p key={index}>{line}</p>
|
||||||
))}
|
))}
|
||||||
@@ -244,25 +244,25 @@ export default async function ImprintPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Liability */}
|
{/* Liability */}
|
||||||
<section>
|
<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) => (
|
{c.liability.sections.map((section, index) => (
|
||||||
<div key={index} className="mb-4">
|
<div key={index} className="mb-4">
|
||||||
<h3 className="text-xl font-medium text-white mb-2">{section.title}</h3>
|
<h3 className="text-xl font-medium text-foreground mb-2">{section.title}</h3>
|
||||||
<p className="text-zinc-300">{section.content}</p>
|
<p className="text-foreground/80">{section.content}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Copyright */}
|
{/* Copyright */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.copyright.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.copyright.title}</h2>
|
||||||
<p className="text-zinc-300">{c.copyright.content}</p>
|
<p className="text-foreground/80">{c.copyright.content}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Images */}
|
{/* Images */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.images.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.images.title}</h2>
|
||||||
<ul className="list-disc list-inside text-zinc-300 space-y-1">
|
<ul className="list-disc list-inside text-foreground/80 space-y-1">
|
||||||
{c.images.content.map((item, index) => (
|
{c.images.content.map((item, index) => (
|
||||||
<li key={index}>{item}</li>
|
<li key={index}>{item}</li>
|
||||||
))}
|
))}
|
||||||
@@ -271,8 +271,8 @@ export default async function ImprintPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Privacy */}
|
{/* Privacy */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.privacy.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.privacy.title}</h2>
|
||||||
<p className="text-zinc-300">
|
<p className="text-foreground/80">
|
||||||
{c.privacy.content}{' '}
|
{c.privacy.content}{' '}
|
||||||
<Link href={`/${locale}/privacy`} className="text-orange-500 hover:text-orange-400 underline">
|
<Link href={`/${locale}/privacy`} className="text-orange-500 hover:text-orange-400 underline">
|
||||||
{c.privacy.link}
|
{c.privacy.link}
|
||||||
@@ -282,15 +282,15 @@ export default async function ImprintPage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Technical */}
|
{/* Technical */}
|
||||||
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
|
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.technical.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.technical.title}</h2>
|
||||||
<p className="text-zinc-300">{c.technical.design}</p>
|
<p className="text-foreground/80">{c.technical.design}</p>
|
||||||
<p className="text-zinc-300">{c.technical.tech}</p>
|
<p className="text-foreground/80">{c.technical.tech}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Copyright Notice */}
|
{/* Copyright Notice */}
|
||||||
<section>
|
<section>
|
||||||
<p className="text-zinc-400">
|
<p className="text-muted-foreground">
|
||||||
© {new Date().getFullYear()} Damjan Savić. {c.copyrightNotice}
|
© {new Date().getFullYear()} Damjan Savić. {c.copyrightNotice}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export default async function LocaleLayout({ children, params }: Props) {
|
|||||||
<OrganizationJsonLd locale={locale as Locale} />
|
<OrganizationJsonLd locale={locale as Locale} />
|
||||||
<WebSiteJsonLd locale={locale as Locale} />
|
<WebSiteJsonLd locale={locale as Locale} />
|
||||||
</head>
|
</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}>
|
<NextIntlClientProvider messages={messages}>
|
||||||
<div className="min-h-screen flex flex-col relative">
|
<div className="min-h-screen flex flex-col relative">
|
||||||
<GlobalBackground />
|
<GlobalBackground />
|
||||||
|
|||||||
@@ -170,23 +170,23 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
{/* Back Link */}
|
{/* Back Link */}
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/leistungen`}
|
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" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
{locale === 'de' ? 'Alle Leistungen' : locale === 'sr' ? 'Sve usluge' : 'All Services'}
|
{locale === 'de' ? 'Alle Leistungen' : locale === 'sr' ? 'Sve usluge' : 'All Services'}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="flex items-center gap-4 mb-6">
|
<div className="flex items-center gap-4 mb-6">
|
||||||
<div className="p-4 bg-zinc-800/50 rounded-xl">
|
<div className="p-4 bg-card/50 rounded-xl">
|
||||||
<IconComponent className="h-8 w-8 text-white" />
|
<IconComponent className="h-8 w-8 text-foreground" />
|
||||||
</div>
|
</div>
|
||||||
</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]}
|
{service.name[locale as Locale]}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-xl text-zinc-400 mb-8">
|
<p className="text-xl text-muted-foreground mb-8">
|
||||||
{service.description[locale as Locale]}
|
{service.description[locale as Locale]}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
{service.technologies.map((tech) => (
|
{service.technologies.map((tech) => (
|
||||||
<span
|
<span
|
||||||
key={tech}
|
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}
|
{tech}
|
||||||
</span>
|
</span>
|
||||||
@@ -205,19 +205,19 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Features 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">
|
<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'}
|
{locale === 'de' ? 'Was ich anbiete' : locale === 'sr' ? 'Šta nudim' : 'What I offer'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{service.features[locale as Locale].map((feature, index) => (
|
{service.features[locale as Locale].map((feature, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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" />
|
<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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -227,20 +227,20 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
{/* Use Cases Section */}
|
{/* Use Cases Section */}
|
||||||
<section className="py-16">
|
<section className="py-16">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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'}
|
{locale === 'de' ? 'Anwendungsbeispiele' : locale === 'sr' ? 'Primeri primene' : 'Use Cases'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{service.useCases[locale as Locale].map((useCase, index) => (
|
{service.useCases[locale as Locale].map((useCase, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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="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}
|
{index + 1}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-zinc-200">{useCase}</span>
|
<span className="text-foreground/90">{useCase}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -249,9 +249,9 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Related Services */}
|
{/* 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">
|
<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'}
|
{locale === 'de' ? 'Weitere Leistungen' : locale === 'sr' ? 'Ostale usluge' : 'Other Services'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
@@ -261,15 +261,15 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={relatedService.slug}
|
key={relatedService.slug}
|
||||||
href={`/${locale}/leistungen/${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">
|
<div className="p-2 bg-card/50 rounded-lg w-fit mb-3">
|
||||||
<RelatedIcon className="h-5 w-5 text-zinc-400 group-hover:text-white transition-colors" />
|
<RelatedIcon 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-2">
|
<h3 className="text-lg font-semibold text-foreground mb-2">
|
||||||
{relatedService.name[locale as Locale]}
|
{relatedService.name[locale as Locale]}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-muted-foreground">
|
||||||
{relatedService.shortDescription[locale as Locale]}
|
{relatedService.shortDescription[locale as Locale]}
|
||||||
</p>
|
</p>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -282,7 +282,7 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
{/* FAQ Section */}
|
{/* FAQ Section */}
|
||||||
<section className="py-16">
|
<section className="py-16">
|
||||||
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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'
|
{locale === 'de'
|
||||||
? `Häufige Fragen zu ${service.name.de}`
|
? `Häufige Fragen zu ${service.name.de}`
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
@@ -293,12 +293,12 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
{serviceFaqItems.map((item, index) => (
|
{serviceFaqItems.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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}
|
{item.question}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400">{item.answer}</p>
|
<p className="text-muted-foreground">{item.answer}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -308,14 +308,14 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
{/* CTA Section */}
|
{/* CTA Section */}
|
||||||
<section className="py-24">
|
<section className="py-24">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
<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'
|
{locale === 'de'
|
||||||
? `${service.name.de} für Ihr Unternehmen?`
|
? `${service.name.de} für Ihr Unternehmen?`
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
? `${service.name.sr} za vašu kompaniju?`
|
? `${service.name.sr} za vašu kompaniju?`
|
||||||
: `${service.name.en} for your business?`}
|
: `${service.name.en} for your business?`}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 mb-8">
|
<p className="text-muted-foreground mb-8">
|
||||||
{locale === 'de'
|
{locale === 'de'
|
||||||
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
|
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
@@ -324,7 +324,7 @@ export default async function ServicePage({ params }: Props) {
|
|||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/contact`}
|
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'}
|
{locale === 'de' ? 'Projekt besprechen' : locale === 'sr' ? 'Razgovarajte o projektu' : 'Discuss project'}
|
||||||
<ArrowRight className="h-5 w-5 ml-2" />
|
<ArrowRight className="h-5 w-5 ml-2" />
|
||||||
|
|||||||
@@ -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="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="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||||
<div className="text-center lg:text-left">
|
<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}
|
{pageTitle}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-zinc-400 max-w-2xl">
|
<p className="text-xl text-muted-foreground max-w-2xl">
|
||||||
{pageSubtitle}
|
{pageSubtitle}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -262,7 +262,7 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
{/* Core Services Grid */}
|
{/* Core Services Grid */}
|
||||||
<section className="pb-16">
|
<section className="pb-16">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{services.map((service) => {
|
{services.map((service) => {
|
||||||
const IconComponent = iconMap[service.icon] || Brain;
|
const IconComponent = iconMap[service.icon] || Brain;
|
||||||
@@ -270,28 +270,28 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={service.slug}
|
key={service.slug}
|
||||||
href={`/${locale}/leistungen/${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">
|
<div className="p-3 bg-card/50 rounded-lg w-fit mb-4">
|
||||||
<IconComponent className="h-6 w-6 text-zinc-400 group-hover:text-white transition-colors" />
|
<IconComponent className="h-6 w-6 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
</div>
|
</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]}
|
{service.name[locale as Locale]}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400 mb-4">
|
<p className="text-muted-foreground mb-4">
|
||||||
{service.shortDescription[locale as Locale]}
|
{service.shortDescription[locale as Locale]}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2 mb-4">
|
<div className="flex flex-wrap gap-2 mb-4">
|
||||||
{service.technologies.slice(0, 4).map((tech) => (
|
{service.technologies.slice(0, 4).map((tech) => (
|
||||||
<span
|
<span
|
||||||
key={tech}
|
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}
|
{tech}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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">
|
<span className="text-sm">
|
||||||
{locale === 'de' ? 'Mehr erfahren' : locale === 'sr' ? 'Saznaj više' : 'Learn more'}
|
{locale === 'de' ? 'Mehr erfahren' : locale === 'sr' ? 'Saznaj više' : 'Learn more'}
|
||||||
</span>
|
</span>
|
||||||
@@ -307,7 +307,7 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
{/* Technology Services Grid */}
|
{/* Technology Services Grid */}
|
||||||
<section className="pb-16">
|
<section className="pb-16">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{techServices.map((service) => {
|
{techServices.map((service) => {
|
||||||
const IconComponent = iconMap[service.icon] || Brain;
|
const IconComponent = iconMap[service.icon] || Brain;
|
||||||
@@ -315,28 +315,28 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={service.slug}
|
key={service.slug}
|
||||||
href={`/${locale}/leistungen/${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">
|
<div className="p-3 bg-accent/50 rounded-lg w-fit mb-4">
|
||||||
<IconComponent className="h-6 w-6 text-zinc-300 group-hover:text-white transition-colors" />
|
<IconComponent className="h-6 w-6 text-foreground/80 group-hover:text-foreground transition-colors" />
|
||||||
</div>
|
</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]}
|
{service.name[locale as Locale]}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400 mb-4">
|
<p className="text-muted-foreground mb-4">
|
||||||
{service.shortDescription[locale as Locale]}
|
{service.shortDescription[locale as Locale]}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2 mb-4">
|
<div className="flex flex-wrap gap-2 mb-4">
|
||||||
{service.technologies.slice(0, 4).map((tech) => (
|
{service.technologies.slice(0, 4).map((tech) => (
|
||||||
<span
|
<span
|
||||||
key={tech}
|
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}
|
{tech}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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">
|
<span className="text-sm">
|
||||||
{locale === 'de' ? 'Mehr erfahren' : locale === 'sr' ? 'Saznaj više' : 'Learn more'}
|
{locale === 'de' ? 'Mehr erfahren' : locale === 'sr' ? 'Saznaj više' : 'Learn more'}
|
||||||
</span>
|
</span>
|
||||||
@@ -352,7 +352,7 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
{/* Industry Services Grid */}
|
{/* Industry Services Grid */}
|
||||||
<section className="pb-24">
|
<section className="pb-24">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{industryServices.map((service) => {
|
{industryServices.map((service) => {
|
||||||
const IconComponent = iconMap[service.icon] || Brain;
|
const IconComponent = iconMap[service.icon] || Brain;
|
||||||
@@ -360,28 +360,28 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={service.slug}
|
key={service.slug}
|
||||||
href={`/${locale}/leistungen/${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">
|
<div className="p-3 bg-card/70 rounded-lg w-fit mb-4">
|
||||||
<IconComponent className="h-6 w-6 text-zinc-400 group-hover:text-white transition-colors" />
|
<IconComponent className="h-6 w-6 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
</div>
|
</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]}
|
{service.name[locale as Locale]}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400 mb-4">
|
<p className="text-muted-foreground mb-4">
|
||||||
{service.shortDescription[locale as Locale]}
|
{service.shortDescription[locale as Locale]}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2 mb-4">
|
<div className="flex flex-wrap gap-2 mb-4">
|
||||||
{service.technologies.slice(0, 4).map((tech) => (
|
{service.technologies.slice(0, 4).map((tech) => (
|
||||||
<span
|
<span
|
||||||
key={tech}
|
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}
|
{tech}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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">
|
<span className="text-sm">
|
||||||
{locale === 'de' ? 'Mehr erfahren' : locale === 'sr' ? 'Saznaj više' : 'Learn more'}
|
{locale === 'de' ? 'Mehr erfahren' : locale === 'sr' ? 'Saznaj više' : 'Learn more'}
|
||||||
</span>
|
</span>
|
||||||
@@ -397,7 +397,7 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
{/* Technologies Section */}
|
{/* Technologies Section */}
|
||||||
<section className="pb-24">
|
<section className="pb-24">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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'}
|
{locale === 'de' ? 'Technologien' : locale === 'sr' ? 'Tehnologije' : 'Technologies'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
@@ -405,10 +405,10 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={tech.slug}
|
key={tech.slug}
|
||||||
href={`/${locale}/leistungen/technologie/${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" />
|
<Code className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
<span className="text-zinc-300 group-hover:text-white transition-colors font-medium">
|
<span className="text-foreground/80 group-hover:text-foreground transition-colors font-medium">
|
||||||
{tech.name}
|
{tech.name}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -418,21 +418,21 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Cities 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="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<h2 className="text-3xl font-bold text-white mb-4">{citiesTitle}</h2>
|
<h2 className="text-3xl font-bold text-foreground mb-4">{citiesTitle}</h2>
|
||||||
<p className="text-zinc-400">{citiesSubtitle}</p>
|
<p className="text-muted-foreground">{citiesSubtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||||
{cities.map((city) => (
|
{cities.map((city) => (
|
||||||
<Link
|
<Link
|
||||||
key={city.slug}
|
key={city.slug}
|
||||||
href={`/${locale}/leistungen/standort/${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" />
|
<MapPin className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
<span className="text-zinc-300 group-hover:text-white transition-colors">
|
<span className="text-foreground/80 group-hover:text-foreground transition-colors">
|
||||||
{city.name[locale as Locale]}
|
{city.name[locale as Locale]}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -451,12 +451,12 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
{faqItems.map((item, index) => (
|
{faqItems.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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}
|
{item.question}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400">{item.answer}</p>
|
<p className="text-muted-foreground">{item.answer}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -464,7 +464,7 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* CTA 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="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="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">
|
<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>
|
||||||
<div className="text-center lg:text-left order-1 lg:order-2">
|
<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?'}
|
{locale === 'de' ? 'Bereit für Ihr Projekt?' : locale === 'sr' ? 'Spremni za vaš projekat?' : 'Ready for your project?'}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 mb-8">
|
<p className="text-muted-foreground mb-8">
|
||||||
{locale === 'de'
|
{locale === 'de'
|
||||||
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
|
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
@@ -489,7 +489,7 @@ export default async function ServicesPage({ params }: Props) {
|
|||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/contact`}
|
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'}
|
{locale === 'de' ? 'Kontakt aufnehmen' : locale === 'sr' ? 'Kontaktirajte me' : 'Get in touch'}
|
||||||
<ArrowRight className="h-5 w-5 ml-2" />
|
<ArrowRight className="h-5 w-5 ml-2" />
|
||||||
|
|||||||
@@ -190,22 +190,22 @@ export default async function CityPage({ params }: Props) {
|
|||||||
{/* Back Link */}
|
{/* Back Link */}
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/leistungen`}
|
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" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
{locale === 'de' ? 'Alle Leistungen' : locale === 'sr' ? 'Sve usluge' : 'All Services'}
|
{locale === 'de' ? 'Alle Leistungen' : locale === 'sr' ? 'Sve usluge' : 'All Services'}
|
||||||
</Link>
|
</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" />
|
<MapPin className="h-5 w-5" />
|
||||||
<span>{city.region}</span>
|
<span>{city.region}</span>
|
||||||
</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">
|
||||||
{pageTitle}
|
{pageTitle}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-xl text-zinc-400 mb-8">
|
<p className="text-xl text-muted-foreground mb-8">
|
||||||
{city.description[locale as Locale]}
|
{city.description[locale as Locale]}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ export default async function CityPage({ params }: Props) {
|
|||||||
{city.keywords[locale as Locale]?.map((keyword) => (
|
{city.keywords[locale as Locale]?.map((keyword) => (
|
||||||
<span
|
<span
|
||||||
key={keyword}
|
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}
|
{keyword}
|
||||||
</span>
|
</span>
|
||||||
@@ -224,9 +224,9 @@ export default async function CityPage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Services 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">
|
<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'
|
{locale === 'de'
|
||||||
? `Meine Leistungen in ${city.name.de}`
|
? `Meine Leistungen in ${city.name.de}`
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
@@ -240,16 +240,16 @@ export default async function CityPage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={service.slug}
|
key={service.slug}
|
||||||
href={`/${locale}/leistungen/${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">
|
<div className="p-2 bg-card/50 rounded-lg">
|
||||||
<IconComponent className="h-5 w-5 text-zinc-400 group-hover:text-white transition-colors" />
|
<IconComponent className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
</div>
|
</div>
|
||||||
<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]}
|
{service.name[locale as Locale]}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-muted-foreground">
|
||||||
{service.shortDescription[locale as Locale]}
|
{service.shortDescription[locale as Locale]}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -263,7 +263,7 @@ export default async function CityPage({ params }: Props) {
|
|||||||
{/* Why Choose Me Section */}
|
{/* Why Choose Me Section */}
|
||||||
<section className="py-16">
|
<section className="py-16">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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'
|
{locale === 'de'
|
||||||
? `Warum mit mir arbeiten in ${city.name.de}?`
|
? `Warum mit mir arbeiten in ${city.name.de}?`
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
@@ -300,10 +300,10 @@ export default async function CityPage({ params }: Props) {
|
|||||||
).map((point, index) => (
|
).map((point, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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" />
|
<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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -311,16 +311,16 @@ export default async function CityPage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Nearby Areas */}
|
{/* 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">
|
<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'
|
{locale === 'de'
|
||||||
? 'Auch verfügbar in der Region'
|
? 'Auch verfügbar in der Region'
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
? 'Takođe dostupno u regionu'
|
? 'Takođe dostupno u regionu'
|
||||||
: 'Also available in the region'}
|
: 'Also available in the region'}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 mb-6">
|
<p className="text-muted-foreground mb-6">
|
||||||
{locale === 'de'
|
{locale === 'de'
|
||||||
? `Neben ${city.name.de} arbeite ich auch mit Unternehmen in:`
|
? `Neben ${city.name.de} arbeite ich auch mit Unternehmen in:`
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
@@ -331,7 +331,7 @@ export default async function CityPage({ params }: Props) {
|
|||||||
{city.nearbyAreas.map((area) => (
|
{city.nearbyAreas.map((area) => (
|
||||||
<span
|
<span
|
||||||
key={area}
|
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}
|
{area}
|
||||||
</span>
|
</span>
|
||||||
@@ -343,7 +343,7 @@ export default async function CityPage({ params }: Props) {
|
|||||||
{/* Other Cities */}
|
{/* Other Cities */}
|
||||||
<section className="py-16">
|
<section className="py-16">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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'}
|
{locale === 'de' ? 'Weitere Standorte' : locale === 'sr' ? 'Ostale lokacije' : 'Other Locations'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
@@ -351,10 +351,10 @@ export default async function CityPage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={otherCity.slug}
|
key={otherCity.slug}
|
||||||
href={`/${locale}/leistungen/standort/${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" />
|
<MapPin className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
<span className="text-zinc-300 group-hover:text-white transition-colors">
|
<span className="text-foreground/80 group-hover:text-foreground transition-colors">
|
||||||
{otherCity.name[locale as Locale]}
|
{otherCity.name[locale as Locale]}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -364,9 +364,9 @@ export default async function CityPage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ 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">
|
<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'
|
{locale === 'de'
|
||||||
? `Häufige Fragen für ${city.name.de}`
|
? `Häufige Fragen für ${city.name.de}`
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
@@ -377,12 +377,12 @@ export default async function CityPage({ params }: Props) {
|
|||||||
{faqItems.map((item, index) => (
|
{faqItems.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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}
|
{item.question}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400">{item.answer}</p>
|
<p className="text-muted-foreground">{item.answer}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -392,14 +392,14 @@ export default async function CityPage({ params }: Props) {
|
|||||||
{/* CTA Section */}
|
{/* CTA Section */}
|
||||||
<section className="py-24">
|
<section className="py-24">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
<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'
|
{locale === 'de'
|
||||||
? `Projekt in ${city.name.de} starten?`
|
? `Projekt in ${city.name.de} starten?`
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
? `Započnite projekat u ${city.name.sr}?`
|
? `Započnite projekat u ${city.name.sr}?`
|
||||||
: `Start a project in ${city.name.en}?`}
|
: `Start a project in ${city.name.en}?`}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 mb-8">
|
<p className="text-muted-foreground mb-8">
|
||||||
{locale === 'de'
|
{locale === 'de'
|
||||||
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
|
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
@@ -408,7 +408,7 @@ export default async function CityPage({ params }: Props) {
|
|||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/contact`}
|
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'}
|
{locale === 'de' ? 'Kontakt aufnehmen' : locale === 'sr' ? 'Kontaktirajte me' : 'Get in touch'}
|
||||||
<ArrowRight className="h-5 w-5 ml-2" />
|
<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">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/leistungen`}
|
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" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
{locale === 'de' ? 'Alle Leistungen' : locale === 'sr' ? 'Sve usluge' : 'All Services'}
|
{locale === 'de' ? 'Alle Leistungen' : locale === 'sr' ? 'Sve usluge' : 'All Services'}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="flex items-center gap-4 mb-6">
|
<div className="flex items-center gap-4 mb-6">
|
||||||
<div className="p-4 bg-zinc-800/50 rounded-xl">
|
<div className="p-4 bg-card/50 rounded-xl">
|
||||||
<Code className="h-8 w-8 text-white" />
|
<Code className="h-8 w-8 text-foreground" />
|
||||||
</div>
|
</div>
|
||||||
</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]}
|
{tech.title[locale as Locale]}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-xl text-zinc-400 mb-8">
|
<p className="text-xl text-muted-foreground mb-8">
|
||||||
{tech.description[locale as Locale]}
|
{tech.description[locale as Locale]}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
{tech.keywords[locale as Locale].map((keyword) => (
|
{tech.keywords[locale as Locale].map((keyword) => (
|
||||||
<span
|
<span
|
||||||
key={keyword}
|
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}
|
{keyword}
|
||||||
</span>
|
</span>
|
||||||
@@ -188,19 +188,19 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Features 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">
|
<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`}
|
{locale === 'de' ? `${tech.name} – Was ich anbiete` : locale === 'sr' ? `${tech.name} – Šta nudim` : `${tech.name} – What I offer`}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{tech.features[locale as Locale].map((feature, index) => (
|
{tech.features[locale as Locale].map((feature, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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" />
|
<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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -210,20 +210,20 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
{/* Use Cases Section */}
|
{/* Use Cases Section */}
|
||||||
<section className="py-16">
|
<section className="py-16">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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'}
|
{locale === 'de' ? 'Anwendungsbeispiele' : locale === 'sr' ? 'Primeri primene' : 'Use Cases'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{tech.useCases[locale as Locale].map((useCase, index) => (
|
{tech.useCases[locale as Locale].map((useCase, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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="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}
|
{index + 1}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-zinc-200">{useCase}</span>
|
<span className="text-foreground/90">{useCase}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -233,9 +233,9 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Related Services */}
|
{/* Related Services */}
|
||||||
{relatedServiceData.length > 0 && (
|
{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">
|
<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'}
|
{locale === 'de' ? 'Passende Leistungen' : locale === 'sr' ? 'Odgovarajuće usluge' : 'Related Services'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
@@ -243,16 +243,16 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={service.slug}
|
key={service.slug}
|
||||||
href={`/${locale}/leistungen/${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">
|
<div className="p-2 bg-card/50 rounded-lg">
|
||||||
<Zap className="h-5 w-5 text-zinc-400 group-hover:text-white transition-colors" />
|
<Zap className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
</div>
|
</div>
|
||||||
<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]}
|
{service.name[locale as Locale]}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-muted-foreground">
|
||||||
{service.shortDescription[locale as Locale]}
|
{service.shortDescription[locale as Locale]}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -266,7 +266,7 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
{/* Other Technologies */}
|
{/* Other Technologies */}
|
||||||
<section className="py-16">
|
<section className="py-16">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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'}
|
{locale === 'de' ? 'Weitere Technologien' : locale === 'sr' ? 'Ostale Tehnologije' : 'Other Technologies'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
@@ -274,10 +274,10 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={otherTech.slug}
|
key={otherTech.slug}
|
||||||
href={`/${locale}/leistungen/technologie/${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" />
|
<Code className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
<span className="text-zinc-300 group-hover:text-white transition-colors">
|
<span className="text-foreground/80 group-hover:text-foreground transition-colors">
|
||||||
{otherTech.name}
|
{otherTech.name}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -287,9 +287,9 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Cities Cross-Link */}
|
{/* 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">
|
<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`}
|
{locale === 'de' ? `${tech.name} Entwickler in Ihrer Stadt` : locale === 'sr' ? `${tech.name} Developer u vašem gradu` : `${tech.name} Developer in your city`}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
@@ -297,10 +297,10 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
key={city.slug}
|
key={city.slug}
|
||||||
href={`/${locale}/leistungen/standort/${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" />
|
<MapPin className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
<span className="text-zinc-300 group-hover:text-white transition-colors">
|
<span className="text-foreground/80 group-hover:text-foreground transition-colors">
|
||||||
{city.name[locale as Locale]}
|
{city.name[locale as Locale]}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -312,19 +312,19 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
{/* FAQ Section */}
|
{/* FAQ Section */}
|
||||||
<section className="py-16">
|
<section className="py-16">
|
||||||
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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}`}
|
{locale === 'de' ? `Häufige Fragen zu ${tech.name}` : locale === 'sr' ? `Česta pitanja o ${tech.name}` : `FAQ about ${tech.name}`}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{faqItems.map((item, index) => (
|
{faqItems.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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}
|
{item.question}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400">{item.answer}</p>
|
<p className="text-muted-foreground">{item.answer}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -334,14 +334,14 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
{/* CTA Section */}
|
{/* CTA Section */}
|
||||||
<section className="py-24">
|
<section className="py-24">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
<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'
|
{locale === 'de'
|
||||||
? `${tech.name}-Projekt starten?`
|
? `${tech.name}-Projekt starten?`
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
? `Započnite ${tech.name} projekat?`
|
? `Započnite ${tech.name} projekat?`
|
||||||
: `Start a ${tech.name} project?`}
|
: `Start a ${tech.name} project?`}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 mb-8">
|
<p className="text-muted-foreground mb-8">
|
||||||
{locale === 'de'
|
{locale === 'de'
|
||||||
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
|
? 'Lassen Sie uns besprechen, wie ich Ihnen helfen kann.'
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
@@ -350,7 +350,7 @@ export default async function TechnologyPage({ params }: Props) {
|
|||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/contact`}
|
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'}
|
{locale === 'de' ? 'Kontakt aufnehmen' : locale === 'sr' ? 'Kontaktirajte me' : 'Get in touch'}
|
||||||
<ArrowRight className="h-5 w-5 ml-2" />
|
<ArrowRight className="h-5 w-5 ml-2" />
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/portfolio`}
|
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" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
<span>{t('backToPortfolio')}</span>
|
<span>{t('backToPortfolio')}</span>
|
||||||
@@ -476,7 +476,7 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Project Header */}
|
{/* Project Header */}
|
||||||
<div className="mb-12">
|
<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 && (
|
{project.meta.date && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
@@ -497,18 +497,18 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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}
|
{project.meta.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-xl text-zinc-400">
|
<p className="text-xl text-muted-foreground">
|
||||||
{project.meta.description}
|
{project.meta.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Cover Image */}
|
{/* Cover Image */}
|
||||||
<div className="mb-12">
|
<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
|
<Image
|
||||||
src={`/images/projects/${slug}/cover.avif`}
|
src={`/images/projects/${slug}/cover.avif`}
|
||||||
alt={project.meta.title}
|
alt={project.meta.title}
|
||||||
@@ -517,28 +517,28 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
className="object-cover"
|
className="object-cover"
|
||||||
priority
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Project Content */}
|
{/* Project Content */}
|
||||||
<div className="prose prose-invert max-w-none">
|
<div className="prose prose-invert max-w-none">
|
||||||
{/* Intro */}
|
{/* Intro */}
|
||||||
<div className="mb-12 p-6 bg-zinc-800/30 rounded-lg border border-zinc-800">
|
<div className="mb-12 p-6 bg-card/30 rounded-lg border border-border">
|
||||||
<p className="text-lg text-zinc-300 m-0">
|
<p className="text-lg text-foreground/80 m-0">
|
||||||
{project.content.intro}
|
{project.content.intro}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Challenge Section */}
|
{/* Challenge Section */}
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.challenge.title}</h2>
|
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.challenge.title}</h2>
|
||||||
<p className="text-zinc-300 mb-4">{project.content.challenge.description}</p>
|
<p className="text-foreground/80 mb-4">{project.content.challenge.description}</p>
|
||||||
{project.content.challenge.points && (
|
{project.content.challenge.points && (
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{project.content.challenge.points.map((point, index) => (
|
{project.content.challenge.points.map((point, index) => (
|
||||||
<li key={index} className="text-zinc-300 flex items-start gap-2">
|
<li key={index} className="text-foreground/80 flex items-start gap-2">
|
||||||
<span className="text-zinc-500">•</span>
|
<span className="text-muted-foreground">•</span>
|
||||||
{point}
|
{point}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -548,13 +548,13 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Solution Section */}
|
{/* Solution Section */}
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.solution.title}</h2>
|
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.solution.title}</h2>
|
||||||
<p className="text-zinc-300 mb-4">{project.content.solution.description}</p>
|
<p className="text-foreground/80 mb-4">{project.content.solution.description}</p>
|
||||||
{project.content.solution.points && (
|
{project.content.solution.points && (
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{project.content.solution.points.map((point, index) => (
|
{project.content.solution.points.map((point, index) => (
|
||||||
<li key={index} className="text-zinc-300 flex items-start gap-2">
|
<li key={index} className="text-foreground/80 flex items-start gap-2">
|
||||||
<span className="text-zinc-500">•</span>
|
<span className="text-muted-foreground">•</span>
|
||||||
{point}
|
{point}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -565,20 +565,20 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
{/* Technical Section */}
|
{/* Technical Section */}
|
||||||
{project.content.technical && (
|
{project.content.technical && (
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.technical.title}</h2>
|
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.technical.title}</h2>
|
||||||
<p className="text-zinc-300 mb-4">{project.content.technical.description}</p>
|
<p className="text-foreground/80 mb-4">{project.content.technical.description}</p>
|
||||||
{project.content.technical.points && (
|
{project.content.technical.points && (
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{project.content.technical.points.map((point, index) => (
|
{project.content.technical.points.map((point, index) => (
|
||||||
<li key={index} className="text-zinc-300 flex items-start gap-2">
|
<li key={index} className="text-foreground/80 flex items-start gap-2">
|
||||||
<span className="text-zinc-500">•</span>
|
<span className="text-muted-foreground">•</span>
|
||||||
{point}
|
{point}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
{project.content.technical.code && (
|
{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>
|
<code className="text-sm">{project.content.technical.code}</code>
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
@@ -588,13 +588,13 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
{/* Implementation Section */}
|
{/* Implementation Section */}
|
||||||
{project.content.implementation && (
|
{project.content.implementation && (
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.implementation.title}</h2>
|
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.implementation.title}</h2>
|
||||||
<p className="text-zinc-300 mb-4">{project.content.implementation.description}</p>
|
<p className="text-foreground/80 mb-4">{project.content.implementation.description}</p>
|
||||||
{project.content.implementation.points && (
|
{project.content.implementation.points && (
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{project.content.implementation.points.map((point, index) => (
|
{project.content.implementation.points.map((point, index) => (
|
||||||
<li key={index} className="text-zinc-300 flex items-start gap-2">
|
<li key={index} className="text-foreground/80 flex items-start gap-2">
|
||||||
<span className="text-zinc-500">•</span>
|
<span className="text-muted-foreground">•</span>
|
||||||
{point}
|
{point}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -605,13 +605,13 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Results Section */}
|
{/* Results Section */}
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="text-2xl font-bold mb-4 text-white">{project.content.results.title}</h2>
|
<h2 className="text-2xl font-bold mb-4 text-foreground">{project.content.results.title}</h2>
|
||||||
<p className="text-zinc-300 mb-4">{project.content.results.description}</p>
|
<p className="text-foreground/80 mb-4">{project.content.results.description}</p>
|
||||||
{project.content.results.points && (
|
{project.content.results.points && (
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{project.content.results.points.map((point, index) => (
|
{project.content.results.points.map((point, index) => (
|
||||||
<li key={index} className="text-zinc-300 flex items-start gap-2">
|
<li key={index} className="text-foreground/80 flex items-start gap-2">
|
||||||
<span className="text-zinc-500">•</span>
|
<span className="text-muted-foreground">•</span>
|
||||||
{point}
|
{point}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -621,8 +621,8 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Conclusion */}
|
{/* Conclusion */}
|
||||||
{project.content.conclusion && (
|
{project.content.conclusion && (
|
||||||
<section className="mb-12 p-6 bg-zinc-800/30 rounded-lg border border-zinc-800">
|
<section className="mb-12 p-6 bg-card/30 rounded-lg border border-border">
|
||||||
<p className="text-lg text-zinc-300 m-0">
|
<p className="text-lg text-foreground/80 m-0">
|
||||||
{project.content.conclusion}
|
{project.content.conclusion}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
@@ -631,14 +631,14 @@ export default async function ProjectPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Technologies */}
|
{/* Technologies */}
|
||||||
{project.meta.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">
|
<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">
|
<div className="flex flex-wrap gap-2">
|
||||||
{project.meta.technologies.map((tech, index) => (
|
{project.meta.technologies.map((tech, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
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}
|
{tech}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ export default async function PortfolioPage({ params, searchParams }: Props) {
|
|||||||
<h1 className="text-3xl font-bold text-white mb-4">
|
<h1 className="text-3xl font-bold text-white mb-4">
|
||||||
{t('sections.title')}
|
{t('sections.title')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-zinc-400 max-w-2xl mx-auto">
|
<p className="text-muted-foreground max-w-2xl mx-auto">
|
||||||
{t('sections.subtitle')}
|
{t('sections.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -481,28 +481,28 @@ export default async function PrivacyPage({ params }: Props) {
|
|||||||
<nav className="mb-8">
|
<nav className="mb-8">
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}`}
|
href={`/${locale}`}
|
||||||
className="text-zinc-400 hover:text-white transition-colors"
|
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Home
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
<span className="mx-2 text-zinc-600">/</span>
|
<span className="mx-2 text-muted-foreground/60">/</span>
|
||||||
<span className="text-white">{c.title}</span>
|
<span className="text-foreground">{c.title}</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<h1 className="text-4xl font-bold text-white mb-2">{c.title}</h1>
|
<h1 className="text-4xl font-bold text-foreground mb-2">{c.title}</h1>
|
||||||
<p className="text-zinc-400 mb-8">{c.lastUpdated}</p>
|
<p className="text-muted-foreground mb-8">{c.lastUpdated}</p>
|
||||||
|
|
||||||
<div className="prose prose-invert max-w-none space-y-8">
|
<div className="prose prose-invert max-w-none space-y-8">
|
||||||
{/* Introduction */}
|
{/* Introduction */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.intro.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.intro.title}</h2>
|
||||||
<p className="text-zinc-300">{c.intro.content}</p>
|
<p className="text-foreground/80">{c.intro.content}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Data Controller */}
|
{/* Data Controller */}
|
||||||
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
|
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.controller.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.controller.title}</h2>
|
||||||
<div className="text-zinc-300 space-y-1">
|
<div className="text-foreground/80 space-y-1">
|
||||||
{c.controller.content.map((line, index) => (
|
{c.controller.content.map((line, index) => (
|
||||||
<p key={index}>{line}</p>
|
<p key={index}>{line}</p>
|
||||||
))}
|
))}
|
||||||
@@ -511,13 +511,13 @@ export default async function PrivacyPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Data Collection */}
|
{/* Data Collection */}
|
||||||
<section>
|
<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) => (
|
{c.dataCollection.sections.map((section, index) => (
|
||||||
<div key={index} className="mb-6">
|
<div key={index} className="mb-6">
|
||||||
<h3 className="text-xl font-medium text-white mb-2">{section.title}</h3>
|
<h3 className="text-xl font-medium text-foreground mb-2">{section.title}</h3>
|
||||||
<p className="text-zinc-300 mb-2">{section.content}</p>
|
<p className="text-foreground/80 mb-2">{section.content}</p>
|
||||||
{section.items && (
|
{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) => (
|
{section.items.map((item, itemIndex) => (
|
||||||
<li key={itemIndex}>{item}</li>
|
<li key={itemIndex}>{item}</li>
|
||||||
))}
|
))}
|
||||||
@@ -529,46 +529,46 @@ export default async function PrivacyPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Cookies */}
|
{/* Cookies */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.cookies.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.cookies.title}</h2>
|
||||||
<p className="text-zinc-300 mb-4">{c.cookies.intro}</p>
|
<p className="text-foreground/80 mb-4">{c.cookies.intro}</p>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full text-left border-collapse">
|
<table className="w-full text-left border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-zinc-700">
|
<tr className="border-b border-border">
|
||||||
<th className="py-2 pr-4 text-white font-semibold">Cookie</th>
|
<th className="py-2 pr-4 text-foreground 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 pr-4 text-foreground 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>
|
<th className="py-2 text-foreground font-semibold">{locale === 'de' ? 'Dauer' : locale === 'sr' ? 'Trajanje' : 'Duration'}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{c.cookies.types.map((cookie, index) => (
|
{c.cookies.types.map((cookie, index) => (
|
||||||
<tr key={index} className="border-b border-zinc-800">
|
<tr key={index} className="border-b border-border">
|
||||||
<td className="py-3 pr-4 text-zinc-300">{cookie.name}</td>
|
<td className="py-3 pr-4 text-foreground/80">{cookie.name}</td>
|
||||||
<td className="py-3 pr-4 text-zinc-400">{cookie.purpose}</td>
|
<td className="py-3 pr-4 text-muted-foreground">{cookie.purpose}</td>
|
||||||
<td className="py-3 text-zinc-400">{cookie.duration}</td>
|
<td className="py-3 text-muted-foreground">{cookie.duration}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
</section>
|
||||||
|
|
||||||
{/* Third-Party Services */}
|
{/* Third-Party Services */}
|
||||||
<section>
|
<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">
|
<div className="space-y-6">
|
||||||
{c.thirdParty.services.map((service, index) => (
|
{c.thirdParty.services.map((service, index) => (
|
||||||
<div key={index} className="bg-zinc-900/30 p-5 rounded-lg ring-1 ring-zinc-800">
|
<div key={index} className="bg-background/30 p-5 rounded-lg ring-1 ring-border">
|
||||||
<h3 className="text-xl font-medium text-white mb-2">{service.name}</h3>
|
<h3 className="text-xl font-medium text-foreground mb-2">{service.name}</h3>
|
||||||
<p className="text-zinc-300 mb-2">
|
<p className="text-foreground/80 mb-2">
|
||||||
<strong className="text-zinc-200">{locale === 'de' ? 'Zweck:' : locale === 'sr' ? 'Svrha:' : 'Purpose:'}</strong> {service.purpose}
|
<strong className="text-foreground/90">{locale === 'de' ? 'Zweck:' : locale === 'sr' ? 'Svrha:' : 'Purpose:'}</strong> {service.purpose}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-zinc-300 mb-2">
|
<p className="text-foreground/80 mb-2">
|
||||||
<strong className="text-zinc-200">{locale === 'de' ? 'Verarbeitete Daten:' : locale === 'sr' ? 'Obrađeni podaci:' : 'Data processed:'}</strong> {service.dataProcessed}
|
<strong className="text-foreground/90">{locale === 'de' ? 'Verarbeitete Daten:' : locale === 'sr' ? 'Obrađeni podaci:' : 'Data processed:'}</strong> {service.dataProcessed}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-zinc-400 text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
<strong className="text-zinc-300">{locale === 'de' ? 'Datenschutz:' : locale === 'sr' ? 'Privatnost:' : 'Privacy:'}</strong>{' '}
|
<strong className="text-foreground/80">{locale === 'de' ? 'Datenschutz:' : locale === 'sr' ? 'Privatnost:' : 'Privacy:'}</strong>{' '}
|
||||||
<a
|
<a
|
||||||
href={service.privacy}
|
href={service.privacy}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -585,27 +585,27 @@ export default async function PrivacyPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Legal Basis */}
|
{/* Legal Basis */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.legalBasis.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.legalBasis.title}</h2>
|
||||||
<p className="text-zinc-300 mb-4">{c.legalBasis.content}</p>
|
<p className="text-foreground/80 mb-4">{c.legalBasis.content}</p>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{c.legalBasis.bases.map((item, index) => (
|
{c.legalBasis.bases.map((item, index) => (
|
||||||
<li key={index} className="text-zinc-300">
|
<li key={index} className="text-foreground/80">
|
||||||
<strong className="text-white">{item.basis}:</strong> {item.description}
|
<strong className="text-foreground">{item.basis}:</strong> {item.description}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* User Rights */}
|
{/* User Rights */}
|
||||||
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
|
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.rights.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.rights.title}</h2>
|
||||||
<p className="text-zinc-300 mb-4">{c.rights.intro}</p>
|
<p className="text-foreground/80 mb-4">{c.rights.intro}</p>
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-3">
|
||||||
{c.rights.items.map((item, index) => (
|
{c.rights.items.map((item, index) => (
|
||||||
<li key={index} className="text-zinc-300">
|
<li key={index} className="text-foreground/80">
|
||||||
<strong className="text-white">{item.right}</strong>
|
<strong className="text-foreground">{item.right}</strong>
|
||||||
<br />
|
<br />
|
||||||
<span className="text-zinc-400">{item.description}</span>
|
<span className="text-muted-foreground">{item.description}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -614,26 +614,26 @@ export default async function PrivacyPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Data Retention */}
|
{/* Data Retention */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.dataRetention.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.dataRetention.title}</h2>
|
||||||
<p className="text-zinc-300">{c.dataRetention.content}</p>
|
<p className="text-foreground/80">{c.dataRetention.content}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Data Security */}
|
{/* Data Security */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.dataSecurity.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.dataSecurity.title}</h2>
|
||||||
<p className="text-zinc-300">{c.dataSecurity.content}</p>
|
<p className="text-foreground/80">{c.dataSecurity.content}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Changes */}
|
{/* Changes */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.changes.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.changes.title}</h2>
|
||||||
<p className="text-zinc-300">{c.changes.content}</p>
|
<p className="text-foreground/80">{c.changes.content}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Contact */}
|
{/* Contact */}
|
||||||
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
|
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.contact.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.contact.title}</h2>
|
||||||
<div className="text-zinc-300 space-y-1">
|
<div className="text-foreground/80 space-y-1">
|
||||||
{c.contact.content.map((line, index) => (
|
{c.contact.content.map((line, index) => (
|
||||||
<p key={index}>{line}</p>
|
<p key={index}>{line}</p>
|
||||||
))}
|
))}
|
||||||
@@ -642,7 +642,7 @@ export default async function PrivacyPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Link to Imprint */}
|
{/* Link to Imprint */}
|
||||||
<section>
|
<section>
|
||||||
<p className="text-zinc-400">
|
<p className="text-muted-foreground">
|
||||||
{locale === 'de'
|
{locale === 'de'
|
||||||
? 'Weitere rechtliche Informationen finden Sie im'
|
? 'Weitere rechtliche Informationen finden Sie im'
|
||||||
: locale === 'sr'
|
: locale === 'sr'
|
||||||
|
|||||||
@@ -271,24 +271,24 @@ export default async function TermsPage({ params }: Props) {
|
|||||||
<nav className="mb-8">
|
<nav className="mb-8">
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}`}
|
href={`/${locale}`}
|
||||||
className="text-zinc-400 hover:text-white transition-colors"
|
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Home
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
<span className="mx-2 text-zinc-600">/</span>
|
<span className="mx-2 text-muted-foreground/60">/</span>
|
||||||
<span className="text-white">{c.title}</span>
|
<span className="text-foreground">{c.title}</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<h1 className="text-4xl font-bold text-white mb-2">{c.title}</h1>
|
<h1 className="text-4xl font-bold text-foreground mb-2">{c.title}</h1>
|
||||||
<p className="text-zinc-400 mb-8">{c.lastUpdated}</p>
|
<p className="text-muted-foreground mb-8">{c.lastUpdated}</p>
|
||||||
|
|
||||||
<div className="prose prose-invert max-w-none space-y-8">
|
<div className="prose prose-invert max-w-none space-y-8">
|
||||||
{c.sections.map((section, index) => (
|
{c.sections.map((section, index) => (
|
||||||
<section key={index}>
|
<section key={index}>
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{section.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{section.title}</h2>
|
||||||
<p className="text-zinc-300">{section.content}</p>
|
<p className="text-foreground/80">{section.content}</p>
|
||||||
{section.items && (
|
{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) => (
|
{section.items.map((item, itemIndex) => (
|
||||||
<li key={itemIndex}>{item}</li>
|
<li key={itemIndex}>{item}</li>
|
||||||
))}
|
))}
|
||||||
@@ -298,9 +298,9 @@ export default async function TermsPage({ params }: Props) {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Contact */}
|
{/* Contact */}
|
||||||
<section className="bg-zinc-900/50 p-6 rounded-xl ring-1 ring-zinc-800">
|
<section className="bg-background/50 p-6 rounded-xl ring-1 ring-border">
|
||||||
<h2 className="text-2xl font-semibold text-white mb-4">{c.contact.title}</h2>
|
<h2 className="text-2xl font-semibold text-foreground mb-4">{c.contact.title}</h2>
|
||||||
<div className="text-zinc-300 space-y-1">
|
<div className="text-foreground/80 space-y-1">
|
||||||
{c.contact.content.map((line, index) => (
|
{c.contact.content.map((line, index) => (
|
||||||
<p key={index}>{line}</p>
|
<p key={index}>{line}</p>
|
||||||
))}
|
))}
|
||||||
@@ -309,7 +309,7 @@ export default async function TermsPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Privacy link */}
|
{/* Privacy link */}
|
||||||
<section>
|
<section>
|
||||||
<p className="text-zinc-400">
|
<p className="text-muted-foreground">
|
||||||
{c.privacyNote.text}{' '}
|
{c.privacyNote.text}{' '}
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/privacy`}
|
href={`/${locale}/privacy`}
|
||||||
@@ -323,7 +323,7 @@ export default async function TermsPage({ params }: Props) {
|
|||||||
|
|
||||||
{/* Copyright */}
|
{/* Copyright */}
|
||||||
<section>
|
<section>
|
||||||
<p className="text-zinc-400">
|
<p className="text-muted-foreground">
|
||||||
© {new Date().getFullYear()} Damjan Savić.{' '}
|
© {new Date().getFullYear()} Damjan Savić.{' '}
|
||||||
{locale === 'de' ? 'Alle Rechte vorbehalten.' : locale === 'sr' ? 'Sva prava zadržana.' : 'All rights reserved.'}
|
{locale === 'de' ? 'Alle Rechte vorbehalten.' : locale === 'sr' ? 'Sva prava zadržana.' : 'All rights reserved.'}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
+6
-2
@@ -1,4 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
<rect width="32" height="32" rx="6" fill="#0a0a0a"/>
|
<rect width="32" height="32" rx="6" fill="#181C14"/>
|
||||||
<text x="16" y="22" font-family="system-ui, sans-serif" font-size="14" font-weight="600" fill="#ffffff" text-anchor="middle">DS</text>
|
<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: 260 B After Width: | Height: | Size: 1.1 KiB |
@@ -6,16 +6,16 @@ export default async function NotFound() {
|
|||||||
const t = await getTranslations('pages.notfound');
|
const t = await getTranslations('pages.notfound');
|
||||||
|
|
||||||
return (
|
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">
|
<div className="text-center px-4">
|
||||||
<h1 className="text-6xl font-bold mb-4">404</h1>
|
<h1 className="text-6xl font-bold mb-4">404</h1>
|
||||||
<h2 className="text-2xl font-semibold mb-4">{t('title')}</h2>
|
<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')}
|
{t('description')}
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href={`/${defaultLocale}`}
|
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')}
|
{t('backHome')}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ const Hero = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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">
|
<div className="flex flex-col items-center justify-center min-h-screen px-4 relative z-10">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<motion.h1
|
<motion.h1
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
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')}
|
{t('title')}
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
@@ -43,7 +43,7 @@ const Hero = () => {
|
|||||||
transition={{ duration: 0.5, delay: 0.2 }}
|
transition={{ duration: 0.5, delay: 0.2 }}
|
||||||
className="max-w-3xl text-center mb-12"
|
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')}
|
{t('description')}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -55,30 +55,30 @@ const Hero = () => {
|
|||||||
transition={{ duration: 0.5, delay: 0.3 }}
|
transition={{ duration: 0.5, delay: 0.3 }}
|
||||||
className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12"
|
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">
|
<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-white" />
|
<Terminal className="h-8 w-8 mb-4 text-foreground" />
|
||||||
<h3 className="text-lg font-semibold mb-2">
|
<h3 className="text-lg font-semibold mb-2">
|
||||||
{t('expertise.processAutomation.title')}
|
{t('expertise.processAutomation.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400 text-center text-sm">
|
<p className="text-muted-foreground text-center text-sm">
|
||||||
{t('expertise.processAutomation.description')}
|
{t('expertise.processAutomation.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center p-6 border border-zinc-800 rounded-lg bg-zinc-900/50">
|
<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-white" />
|
<Globe2 className="h-8 w-8 mb-4 text-foreground" />
|
||||||
<h3 className="text-lg font-semibold mb-2">
|
<h3 className="text-lg font-semibold mb-2">
|
||||||
{t('expertise.systemIntegration.title')}
|
{t('expertise.systemIntegration.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400 text-center text-sm">
|
<p className="text-muted-foreground text-center text-sm">
|
||||||
{t('expertise.systemIntegration.description')}
|
{t('expertise.systemIntegration.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center p-6 border border-zinc-800 rounded-lg bg-zinc-900/50">
|
<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-white" />
|
<Code2 className="h-8 w-8 mb-4 text-foreground" />
|
||||||
<h3 className="text-lg font-semibold mb-2">
|
<h3 className="text-lg font-semibold mb-2">
|
||||||
{t('expertise.customDevelopment.title')}
|
{t('expertise.customDevelopment.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400 text-center text-sm">
|
<p className="text-muted-foreground text-center text-sm">
|
||||||
{t('expertise.customDevelopment.description')}
|
{t('expertise.customDevelopment.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,9 +94,9 @@ const Hero = () => {
|
|||||||
{getLanguages().map(([key, value]) => (
|
{getLanguages().map(([key, value]) => (
|
||||||
<div
|
<div
|
||||||
key={key}
|
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>
|
</div>
|
||||||
))}
|
))}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -107,7 +107,7 @@ const Hero = () => {
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5, delay: 0.5 }}
|
transition={{ duration: 0.5, delay: 0.5 }}
|
||||||
onClick={scrollToSkills}
|
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')}
|
{t('aboutMe')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ const Journey = async () => {
|
|||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
{/* Section Header */}
|
{/* Section Header */}
|
||||||
<div className="text-center mb-16">
|
<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')}
|
{t('title')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 max-w-2xl mx-auto">
|
<p className="text-muted-foreground max-w-2xl mx-auto">
|
||||||
{t('subtitle')}
|
{t('subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,33 +54,33 @@ const Journey = async () => {
|
|||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||||
{/* Education Column */}
|
{/* Education Column */}
|
||||||
<div className="space-y-6">
|
<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">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<GraduationCap className="h-5 w-5 text-zinc-400" />
|
<GraduationCap className="h-5 w-5 text-muted-foreground" />
|
||||||
<h3 className="text-lg font-medium text-white">
|
<h3 className="text-lg font-medium text-foreground">
|
||||||
{t('education.title')}
|
{t('education.title')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-white font-medium">
|
<h4 className="text-foreground font-medium">
|
||||||
{t('education.degrees.masters.degree')}
|
{t('education.degrees.masters.degree')}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-zinc-400 text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
{t('education.degrees.masters.university')}
|
{t('education.degrees.masters.university')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-zinc-500 text-sm">
|
<p className="text-muted-foreground/70 text-sm">
|
||||||
{t('education.degrees.masters.period')}
|
{t('education.degrees.masters.period')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-white font-medium">
|
<h4 className="text-foreground font-medium">
|
||||||
{t('education.degrees.bachelors.degree')}
|
{t('education.degrees.bachelors.degree')}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-zinc-400 text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
{t('education.degrees.bachelors.university')}
|
{t('education.degrees.bachelors.university')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-zinc-500 text-sm">
|
<p className="text-muted-foreground/70 text-sm">
|
||||||
{t('education.degrees.bachelors.period')}
|
{t('education.degrees.bachelors.period')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,18 +88,18 @@ const Journey = async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Languages */}
|
{/* 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">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<Globe className="h-5 w-5 text-zinc-400" />
|
<Globe className="h-5 w-5 text-muted-foreground" />
|
||||||
<h3 className="text-lg font-medium text-white">
|
<h3 className="text-lg font-medium text-foreground">
|
||||||
{t('languages.title')}
|
{t('languages.title')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
{languageItems.map(([key, item]) => (
|
{languageItems.map(([key, item]) => (
|
||||||
<div key={key}>
|
<div key={key}>
|
||||||
<div className="text-zinc-300">{item.name}</div>
|
<div className="text-foreground/80">{item.name}</div>
|
||||||
<div className="text-zinc-500 text-sm">{item.level}</div>
|
<div className="text-muted-foreground/70 text-sm">{item.level}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -111,21 +111,21 @@ const Journey = async () => {
|
|||||||
{workPositions.map((job, index) => (
|
{workPositions.map((job, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-medium text-white">{job.title}</h3>
|
<h3 className="text-lg font-medium text-foreground">{job.title}</h3>
|
||||||
<span className="text-zinc-500 text-sm">{job.period}</span>
|
<span className="text-muted-foreground/70 text-sm">{job.period}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<Briefcase className="h-4 w-4 text-zinc-400" />
|
<Briefcase className="h-4 w-4 text-muted-foreground" />
|
||||||
<p className="text-zinc-300">{job.company}</p>
|
<p className="text-foreground/80">{job.company}</p>
|
||||||
<span className="text-zinc-500">• {job.location}</span>
|
<span className="text-muted-foreground/70">• {job.location}</span>
|
||||||
</div>
|
</div>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{job.highlights.map((highlight, idx) => (
|
{job.highlights.map((highlight, idx) => (
|
||||||
<li key={idx} className="text-zinc-400 text-sm flex items-start gap-2">
|
<li key={idx} className="text-muted-foreground text-sm flex items-start gap-2">
|
||||||
<span className="text-zinc-500">•</span>
|
<span className="text-muted-foreground/70">•</span>
|
||||||
{highlight}
|
{highlight}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -77,21 +77,21 @@ const Skills = () => {
|
|||||||
>
|
>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-white text-sm font-medium">
|
<span className="text-foreground text-sm font-medium">
|
||||||
{skill.name}
|
{skill.name}
|
||||||
</span>
|
</span>
|
||||||
{selectedSkill === skill.name && (
|
{selectedSkill === skill.name && (
|
||||||
<span className="text-zinc-400 text-xs">
|
<span className="text-muted-foreground text-xs">
|
||||||
{skill.level}% Proficiency
|
{skill.level}% Proficiency
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-white text-sm">
|
<span className="text-foreground text-sm">
|
||||||
{skill.level}%
|
{skill.level}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="h-2 bg-zinc-800 rounded-full overflow-hidden"
|
className="h-2 bg-card rounded-full overflow-hidden"
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
aria-valuenow={skill.level}
|
aria-valuenow={skill.level}
|
||||||
aria-valuemin={0}
|
aria-valuemin={0}
|
||||||
@@ -99,7 +99,7 @@ const Skills = () => {
|
|||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
className={`h-full rounded-full ${
|
className={`h-full rounded-full ${
|
||||||
selectedSkill === skill.name ? 'bg-zinc-500' : 'bg-zinc-600'
|
selectedSkill === skill.name ? 'bg-accent' : 'bg-primary'
|
||||||
}`}
|
}`}
|
||||||
initial={{ width: 0 }}
|
initial={{ width: 0 }}
|
||||||
whileInView={{ width: `${skill.level}%` }}
|
whileInView={{ width: `${skill.level}%` }}
|
||||||
@@ -116,22 +116,22 @@ const Skills = () => {
|
|||||||
{allSkills.map((skill) => (
|
{allSkills.map((skill) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={skill.name}
|
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 ${
|
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-zinc-500' : ''
|
selectedSkill === skill.name ? 'ring-2 ring-ring' : ''
|
||||||
}`}
|
}`}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
onClick={() => handleClick(skill.name)}
|
onClick={() => handleClick(skill.name)}
|
||||||
role="button"
|
role="button"
|
||||||
aria-pressed={selectedSkill === skill.name}
|
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" />}
|
{iconMap[skill.name] || <Box className="w-8 h-8" />}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-white text-sm text-center">
|
<span className="text-foreground text-sm text-center">
|
||||||
{skill.name}
|
{skill.name}
|
||||||
</span>
|
</span>
|
||||||
{selectedSkill === skill.name && (
|
{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}%
|
{skill.level}%
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -33,18 +33,18 @@ const Workflow = async () => {
|
|||||||
role="listitem"
|
role="listitem"
|
||||||
aria-label={`Step ${step.number} of ${totalSteps}: ${step.title}`}
|
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="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-zinc-600 font-light">
|
<div className="w-12 sm:w-20 text-sm text-muted-foreground/60 font-light">
|
||||||
{step.number}
|
{step.number}
|
||||||
</div>
|
</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}
|
{step.title}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Line Indicator */}
|
{/* Line Indicator */}
|
||||||
<div
|
<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"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -73,14 +73,14 @@ export default function DashboardContent() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-12">
|
<div className="flex items-center justify-between mb-12">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-white">{t('header.title')}</h1>
|
<h1 className="text-3xl font-bold text-foreground">{t('header.title')}</h1>
|
||||||
<p className="text-zinc-400 mt-2">
|
<p className="text-muted-foreground mt-2">
|
||||||
127 {t('header.activeUsers')}
|
127 {t('header.activeUsers')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
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 className="h-4 w-4" />
|
||||||
Logout
|
Logout
|
||||||
@@ -92,10 +92,10 @@ export default function DashboardContent() {
|
|||||||
{metrics.map((metric, index) => (
|
{metrics.map((metric, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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="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}
|
{metric.icon}
|
||||||
</div>
|
</div>
|
||||||
{metric.trend && (
|
{metric.trend && (
|
||||||
@@ -111,22 +111,22 @@ export default function DashboardContent() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-sm text-zinc-400 mb-1">{metric.title}</h3>
|
<h3 className="text-sm text-muted-foreground mb-1">{metric.title}</h3>
|
||||||
<p className="text-2xl font-bold text-white">{metric.value}</p>
|
<p className="text-2xl font-bold text-foreground">{metric.value}</p>
|
||||||
<p className="text-sm text-zinc-500 mt-1">{metric.subtitle}</p>
|
<p className="text-sm text-muted-foreground mt-1">{metric.subtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top Pages */}
|
{/* Top Pages */}
|
||||||
<div className="bg-zinc-900/50 backdrop-blur-sm rounded-xl ring-1 ring-zinc-800">
|
<div className="bg-background/50 backdrop-blur-sm rounded-xl ring-1 ring-border">
|
||||||
<div className="p-6 border-b border-zinc-800">
|
<div className="p-6 border-b border-border">
|
||||||
<h2 className="text-xl font-semibold text-white">{t('topPages.title')}</h2>
|
<h2 className="text-xl font-semibold text-foreground">{t('topPages.title')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<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.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.views')}</th>
|
||||||
<th className="px-6 py-4 font-medium">{t('topPages.columns.change')}</th>
|
<th className="px-6 py-4 font-medium">{t('topPages.columns.change')}</th>
|
||||||
@@ -134,9 +134,9 @@ export default function DashboardContent() {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{topPages.map((page, index) => (
|
{topPages.map((page, index) => (
|
||||||
<tr key={index} className="border-b border-zinc-800/50 last:border-0">
|
<tr key={index} className="border-b border-border/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-foreground font-mono text-sm">{page.path}</td>
|
||||||
<td className="px-6 py-4 text-zinc-300">{page.views.toLocaleString()}</td>
|
<td className="px-6 py-4 text-foreground/80">{page.views.toLocaleString()}</td>
|
||||||
<td className={`px-6 py-4 ${
|
<td className={`px-6 py-4 ${
|
||||||
page.change.startsWith('+') ? 'text-green-500' : 'text-red-500'
|
page.change.startsWith('+') ? 'text-green-500' : 'text-red-500'
|
||||||
}`}>
|
}`}>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default function LoginForm() {
|
|||||||
<div className="max-w-md w-full space-y-8">
|
<div className="max-w-md w-full space-y-8">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Lock className="mx-auto h-12 w-12 text-orange-500" />
|
<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>
|
</div>
|
||||||
|
|
||||||
<form className="mt-8 space-y-6" onSubmit={handleLogin}>
|
<form className="mt-8 space-y-6" onSubmit={handleLogin}>
|
||||||
@@ -118,7 +118,7 @@ export default function LoginForm() {
|
|||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder={t('form.email.placeholder')}
|
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>
|
</div>
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ export default function LoginForm() {
|
|||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder={t('form.password.placeholder')}
|
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>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,7 +154,7 @@ export default function LoginForm() {
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</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">
|
<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">
|
<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" />
|
<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" />
|
||||||
|
|||||||
@@ -71,27 +71,27 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/blog/${post.slug}`}
|
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
|
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
|
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"
|
transform hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<div className="relative overflow-hidden rounded-xl">
|
<div className="relative overflow-hidden rounded-xl">
|
||||||
{/* Image Container */}
|
{/* 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
|
<BlogImage
|
||||||
src={getImagePath(post.coverImage)}
|
src={getImagePath(post.coverImage)}
|
||||||
alt={post.title}
|
alt={post.title}
|
||||||
fallback={PLACEHOLDER_IMAGE}
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="relative -mt-2 p-6 bg-zinc-900 z-10">
|
<div className="relative -mt-2 p-6 bg-background z-10">
|
||||||
<div className="flex items-center gap-4 mb-4 text-zinc-500">
|
<div className="flex items-center gap-4 mb-4 text-muted-foreground">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
<span>{getFormattedDate(post.date, locale)}</span>
|
<span>{getFormattedDate(post.date, locale)}</span>
|
||||||
@@ -104,12 +104,12 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-xl font-semibold text-white mb-3
|
<h3 className="text-xl font-semibold text-foreground mb-3
|
||||||
group-hover:text-zinc-100 transition-colors duration-300">
|
group-hover:text-foreground/90 transition-colors duration-300">
|
||||||
{post.title}
|
{post.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400 text-sm line-clamp-2 mb-4
|
<p className="text-muted-foreground text-sm line-clamp-2 mb-4
|
||||||
group-hover:text-zinc-300 transition-colors duration-300">
|
group-hover:text-foreground/80 transition-colors duration-300">
|
||||||
{post.excerpt}
|
{post.excerpt}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -118,17 +118,17 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
|
|||||||
{post.tags?.slice(0, 3).map((tag, index) => (
|
{post.tags?.slice(0, 3).map((tag, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="px-3 py-1 bg-zinc-800/50
|
className="px-3 py-1 bg-card/50
|
||||||
text-sm text-zinc-400 rounded-full
|
text-sm text-muted-foreground rounded-full
|
||||||
ring-1 ring-zinc-700/50 group-hover:ring-zinc-600/50
|
ring-1 ring-border/50 group-hover:ring-ring/50
|
||||||
group-hover:bg-zinc-800/70 group-hover:text-zinc-300
|
group-hover:bg-card/70 group-hover:text-foreground/80
|
||||||
transition-all duration-300"
|
transition-all duration-300"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{post.tags && post.tags.length > 3 && (
|
{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
|
+{post.tags.length - 3} more
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -136,11 +136,11 @@ function BlogPostCard({ post, locale }: { post: BlogPost; locale: string }) {
|
|||||||
|
|
||||||
{/* Link Icon */}
|
{/* Link Icon */}
|
||||||
<div className="absolute top-6 right-6 p-2.5 rounded-full
|
<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
|
opacity-0 group-hover:opacity-100
|
||||||
transform translate-y-2 group-hover:translate-y-0
|
transform translate-y-2 group-hover:translate-y-0
|
||||||
transition-all duration-300">
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,9 +203,9 @@ function Pagination({
|
|||||||
{currentPage > 1 ? (
|
{currentPage > 1 ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => onPageChange(currentPage - 1)}
|
onClick={() => onPageChange(currentPage - 1)}
|
||||||
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-400 hover:text-white
|
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:text-foreground
|
||||||
bg-zinc-800/50 hover:bg-zinc-800 rounded-lg ring-1 ring-zinc-700/50
|
bg-card/50 hover:bg-card rounded-lg ring-1 ring-border/50
|
||||||
hover:ring-zinc-600 transition-all duration-200"
|
hover:ring-ring transition-all duration-200"
|
||||||
aria-label={t('ui.pagination.previous')}
|
aria-label={t('ui.pagination.previous')}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
@@ -213,8 +213,8 @@ function Pagination({
|
|||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-600
|
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground/60
|
||||||
bg-zinc-800/30 rounded-lg ring-1 ring-zinc-800/50 cursor-not-allowed"
|
bg-card/30 rounded-lg ring-1 ring-border/50 cursor-not-allowed"
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
@@ -227,7 +227,7 @@ function Pagination({
|
|||||||
{getPageNumbers().map((page, index) => {
|
{getPageNumbers().map((page, index) => {
|
||||||
if (page === 'ellipsis') {
|
if (page === 'ellipsis') {
|
||||||
return (
|
return (
|
||||||
<span key={`ellipsis-${index}`} className="px-2 text-zinc-600">
|
<span key={`ellipsis-${index}`} className="px-2 text-muted-foreground/60">
|
||||||
...
|
...
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -242,8 +242,8 @@ function Pagination({
|
|||||||
className={`min-w-[44px] h-11 flex items-center justify-center text-sm rounded-lg
|
className={`min-w-[44px] h-11 flex items-center justify-center text-sm rounded-lg
|
||||||
ring-1 transition-all duration-200
|
ring-1 transition-all duration-200
|
||||||
${isCurrentPage
|
${isCurrentPage
|
||||||
? 'bg-[#697565] text-white ring-[#697565] font-medium'
|
? 'bg-[#697565] text-foreground 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-ring'
|
||||||
}`}
|
}`}
|
||||||
aria-current={isCurrentPage ? 'page' : undefined}
|
aria-current={isCurrentPage ? 'page' : undefined}
|
||||||
>
|
>
|
||||||
@@ -257,9 +257,9 @@ function Pagination({
|
|||||||
{currentPage < totalPages ? (
|
{currentPage < totalPages ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => onPageChange(currentPage + 1)}
|
onClick={() => onPageChange(currentPage + 1)}
|
||||||
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-400 hover:text-white
|
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:text-foreground
|
||||||
bg-zinc-800/50 hover:bg-zinc-800 rounded-lg ring-1 ring-zinc-700/50
|
bg-card/50 hover:bg-card rounded-lg ring-1 ring-border/50
|
||||||
hover:ring-zinc-600 transition-all duration-200"
|
hover:ring-ring transition-all duration-200"
|
||||||
aria-label={t('ui.pagination.next')}
|
aria-label={t('ui.pagination.next')}
|
||||||
>
|
>
|
||||||
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
|
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
|
||||||
@@ -267,8 +267,8 @@ function Pagination({
|
|||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className="flex items-center gap-1 px-3 py-2 text-sm text-zinc-600
|
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground/60
|
||||||
bg-zinc-800/30 rounded-lg ring-1 ring-zinc-800/50 cursor-not-allowed"
|
bg-card/30 rounded-lg ring-1 ring-border/50 cursor-not-allowed"
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
>
|
>
|
||||||
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
|
<span className="hidden sm:inline">{t('ui.pagination.next')}</span>
|
||||||
@@ -389,7 +389,7 @@ export function BlogList({ posts, locale, initialSearch = '', initialCategory =
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Results count */}
|
{/* Results count */}
|
||||||
<p className="text-sm text-zinc-500 mb-6">
|
<p className="text-sm text-muted-foreground mb-6">
|
||||||
{translations.showingText(
|
{translations.showingText(
|
||||||
startIndex + 1,
|
startIndex + 1,
|
||||||
Math.min(endIndex, filteredPosts.length),
|
Math.min(endIndex, filteredPosts.length),
|
||||||
@@ -421,7 +421,7 @@ export function BlogList({ posts, locale, initialSearch = '', initialCategory =
|
|||||||
) : (
|
) : (
|
||||||
/* Empty State */
|
/* Empty State */
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-zinc-400">{translations.noPostsText}</p>
|
<p className="text-muted-foreground">{translations.noPostsText}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export function CategoryFilter({
|
|||||||
className={`${className}`}
|
className={`${className}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<Tag className="h-5 w-5 text-zinc-400 flex-shrink-0" />
|
<Tag className="h-5 w-5 text-muted-foreground flex-shrink-0" />
|
||||||
<h3 className="text-sm font-medium text-zinc-300">Filter by Category</h3>
|
<h3 className="text-sm font-medium text-foreground/80">Filter by Category</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile: Horizontal scrollable */}
|
{/* Mobile: Horizontal scrollable */}
|
||||||
@@ -44,8 +44,8 @@ export function CategoryFilter({
|
|||||||
onClick={() => onCategoryChange(category === 'All' ? null : category)}
|
onClick={() => onCategoryChange(category === 'All' ? null : category)}
|
||||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all whitespace-nowrap ${
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all whitespace-nowrap ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-[#697565] text-white'
|
? 'bg-primary text-primary-foreground'
|
||||||
: 'bg-zinc-900/50 text-zinc-400 border border-zinc-800 hover:bg-zinc-800/50 hover:text-white'
|
: 'bg-background/50 text-muted-foreground border border-border hover:bg-card/50 hover:text-foreground'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{category}
|
{category}
|
||||||
@@ -68,8 +68,8 @@ export function CategoryFilter({
|
|||||||
onClick={() => onCategoryChange(category === 'All' ? null : category)}
|
onClick={() => onCategoryChange(category === 'All' ? null : category)}
|
||||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-[#697565] text-white'
|
? 'bg-primary text-primary-foreground'
|
||||||
: 'bg-zinc-900/50 text-zinc-400 border border-zinc-800 hover:bg-zinc-800/50 hover:text-white'
|
: 'bg-background/50 text-muted-foreground border border-border hover:bg-card/50 hover:text-foreground'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{category}
|
{category}
|
||||||
|
|||||||
@@ -27,18 +27,18 @@ export function SearchBar({
|
|||||||
className={`relative ${className}`}
|
className={`relative ${className}`}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
placeholder={placeholder}
|
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 && (
|
{value && (
|
||||||
<button
|
<button
|
||||||
onClick={handleClear}
|
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"
|
aria-label="Clear search"
|
||||||
>
|
>
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
|
|||||||
@@ -61,14 +61,14 @@ export function ChatMessage({ message, index = 0 }: ChatMessageProps) {
|
|||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
|
className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
|
||||||
isUser
|
isUser
|
||||||
? 'bg-zinc-600/50'
|
? 'bg-primary/50'
|
||||||
: 'bg-zinc-800/50 border border-zinc-700'
|
: 'bg-card/50 border border-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isUser ? (
|
{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>
|
</div>
|
||||||
|
|
||||||
@@ -81,21 +81,21 @@ export function ChatMessage({ message, index = 0 }: ChatMessageProps) {
|
|||||||
<div
|
<div
|
||||||
className={`px-4 py-3 rounded-2xl ${
|
className={`px-4 py-3 rounded-2xl ${
|
||||||
isUser
|
isUser
|
||||||
? 'bg-zinc-600/50 text-white rounded-tr-sm'
|
? 'bg-primary/50 text-foreground rounded-tr-sm'
|
||||||
: 'bg-zinc-800/50 border border-zinc-800 text-zinc-200 rounded-tl-sm'
|
: 'bg-card/50 border border-border text-foreground/90 rounded-tl-sm'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="text-sm whitespace-pre-wrap break-words leading-relaxed">
|
<p className="text-sm whitespace-pre-wrap break-words leading-relaxed">
|
||||||
{content}
|
{content}
|
||||||
{isStreaming && (
|
{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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Timestamp */}
|
{/* Timestamp */}
|
||||||
{formattedTime && !isStreaming && (
|
{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}
|
{formattedTime}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -116,17 +116,17 @@ export function TypingIndicator() {
|
|||||||
className="flex gap-3"
|
className="flex gap-3"
|
||||||
>
|
>
|
||||||
{/* Avatar */}
|
{/* 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">
|
<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-zinc-400" />
|
<Bot className="w-4 h-4 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Typing dots */}
|
{/* 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">
|
<div className="flex gap-1.5">
|
||||||
{[0, 1, 2].map((i) => (
|
{[0, 1, 2].map((i) => (
|
||||||
<motion.span
|
<motion.span
|
||||||
key={i}
|
key={i}
|
||||||
className="w-2 h-2 bg-zinc-500 rounded-full"
|
className="w-2 h-2 bg-muted-foreground rounded-full"
|
||||||
animate={{
|
animate={{
|
||||||
opacity: [0.4, 1, 0.4],
|
opacity: [0.4, 1, 0.4],
|
||||||
scale: [0.8, 1, 0.8],
|
scale: [0.8, 1, 0.8],
|
||||||
@@ -154,13 +154,13 @@ export function WelcomeMessage() {
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="flex flex-col items-center justify-center py-8 px-4 text-center"
|
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">
|
<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-zinc-400" />
|
<Bot className="w-6 h-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-white mb-2">
|
<h3 className="text-lg font-medium text-foreground mb-2">
|
||||||
Hi, I'm Damjan's AI Assistant
|
Hi, I'm Damjan's AI Assistant
|
||||||
</h3>
|
</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's skills, services, and experience.
|
I can help you learn about Damjan's skills, services, and experience.
|
||||||
Feel free to ask me anything!
|
Feel free to ask me anything!
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ export function Chatbot() {
|
|||||||
whileHover={{ scale: 1.1 }}
|
whileHover={{ scale: 1.1 }}
|
||||||
whileTap={{ scale: 0.9 }}
|
whileTap={{ scale: 0.9 }}
|
||||||
onClick={toggleChat}
|
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"
|
aria-label="Open chat"
|
||||||
>
|
>
|
||||||
<MessageCircle className="w-6 h-6" />
|
<MessageCircle className="w-6 h-6" />
|
||||||
@@ -275,7 +275,7 @@ export function Chatbot() {
|
|||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
exit={{ opacity: 0, y: 20, scale: 0.95 }}
|
exit={{ opacity: 0, y: 20, scale: 0.95 }}
|
||||||
transition={{ duration: 0.2 }}
|
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
|
isMinimized
|
||||||
? 'bottom-6 right-6 w-80 h-14 rounded-full'
|
? '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)]'
|
: '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 */}
|
{/* Header */}
|
||||||
<div
|
<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'
|
isMinimized ? 'h-full rounded-full' : 'h-14'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 h-8 rounded-full bg-zinc-700/50 flex items-center justify-center">
|
<div className="w-8 h-8 rounded-full bg-muted/50 flex items-center justify-center">
|
||||||
<MessageCircle className="w-4 h-4 text-zinc-300" />
|
<MessageCircle className="w-4 h-4 text-foreground/80" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<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 && (
|
{!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>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,7 +310,7 @@ export function Chatbot() {
|
|||||||
{!isMinimized && messages.length > 0 && (
|
{!isMinimized && messages.length > 0 && (
|
||||||
<button
|
<button
|
||||||
onClick={clearChat}
|
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"
|
aria-label="Clear chat"
|
||||||
title="Clear chat"
|
title="Clear chat"
|
||||||
>
|
>
|
||||||
@@ -321,7 +321,7 @@ export function Chatbot() {
|
|||||||
{/* Close Button */}
|
{/* Close Button */}
|
||||||
<button
|
<button
|
||||||
onClick={toggleChat}
|
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"
|
aria-label="Close chat"
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4" />
|
<X className="w-4 h-4" />
|
||||||
@@ -340,14 +340,14 @@ export function Chatbot() {
|
|||||||
|
|
||||||
{/* Suggested Prompts */}
|
{/* Suggested Prompts */}
|
||||||
<div className="flex flex-col gap-2">
|
<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:
|
Try asking:
|
||||||
</p>
|
</p>
|
||||||
{SUGGESTED_PROMPTS.map((prompt, index) => (
|
{SUGGESTED_PROMPTS.map((prompt, index) => (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => handleSuggestedPrompt(prompt)}
|
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}
|
{prompt}
|
||||||
</button>
|
</button>
|
||||||
@@ -397,7 +397,7 @@ export function Chatbot() {
|
|||||||
{/* Input Form */}
|
{/* Input Form */}
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
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">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
@@ -407,13 +407,13 @@ export function Chatbot() {
|
|||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
placeholder="Type a message..."
|
placeholder="Type a message..."
|
||||||
disabled={isLoading}
|
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"
|
aria-label="Chat message input"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading || !inputValue.trim()}
|
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"
|
aria-label="Send message"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -425,7 +425,7 @@ export function Chatbot() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Privacy note */}
|
{/* 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
|
Messages may be stored to improve service
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export function ContactForm() {
|
|||||||
<motion.h1
|
<motion.h1
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
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')}
|
{t('hero.title')}
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
@@ -137,7 +137,7 @@ export function ContactForm() {
|
|||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.1 }}
|
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')}
|
{t('hero.subtitle')}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
@@ -155,7 +155,7 @@ export function ContactForm() {
|
|||||||
>
|
>
|
||||||
{/* Headshot */}
|
{/* Headshot */}
|
||||||
<div className="flex items-center gap-6">
|
<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
|
<Image
|
||||||
src="/images/headshot.avif"
|
src="/images/headshot.avif"
|
||||||
alt="Damjan Savić - Full-Stack Web Developer"
|
alt="Damjan Savić - Full-Stack Web Developer"
|
||||||
@@ -165,10 +165,10 @@ export function ContactForm() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<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')}
|
{t('contactInfo.title')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400">
|
<p className="text-muted-foreground">
|
||||||
{t('contactInfo.description')}
|
{t('contactInfo.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -181,14 +181,14 @@ export function ContactForm() {
|
|||||||
transition={{ delay: 0.3 }}
|
transition={{ delay: 0.3 }}
|
||||||
className="flex items-start gap-4"
|
className="flex items-start gap-4"
|
||||||
>
|
>
|
||||||
<div className="p-3 bg-zinc-800/50 rounded-lg">
|
<div className="p-3 bg-card/50 rounded-lg">
|
||||||
<MapPin className="h-6 w-6 text-zinc-400" aria-hidden="true" />
|
<MapPin className="h-6 w-6 text-muted-foreground" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<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')}
|
{t('contactInfo.address.label')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400">
|
<p className="text-muted-foreground">
|
||||||
{t('contactInfo.address.value')}
|
{t('contactInfo.address.value')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -200,16 +200,16 @@ export function ContactForm() {
|
|||||||
transition={{ delay: 0.4 }}
|
transition={{ delay: 0.4 }}
|
||||||
className="flex items-start gap-4"
|
className="flex items-start gap-4"
|
||||||
>
|
>
|
||||||
<div className="p-3 bg-zinc-800/50 rounded-lg">
|
<div className="p-3 bg-card/50 rounded-lg">
|
||||||
<Phone className="h-6 w-6 text-zinc-400" aria-hidden="true" />
|
<Phone className="h-6 w-6 text-muted-foreground" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<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')}
|
{t('contactInfo.phone.label')}
|
||||||
</h3>
|
</h3>
|
||||||
<a
|
<a
|
||||||
href="tel:+491756950979"
|
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')}
|
{t('contactInfo.phone.value')}
|
||||||
</a>
|
</a>
|
||||||
@@ -222,16 +222,16 @@ export function ContactForm() {
|
|||||||
transition={{ delay: 0.5 }}
|
transition={{ delay: 0.5 }}
|
||||||
className="flex items-start gap-4"
|
className="flex items-start gap-4"
|
||||||
>
|
>
|
||||||
<div className="p-3 bg-zinc-800/50 rounded-lg">
|
<div className="p-3 bg-card/50 rounded-lg">
|
||||||
<Mail className="h-6 w-6 text-zinc-400" aria-hidden="true" />
|
<Mail className="h-6 w-6 text-muted-foreground" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<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')}
|
{t('contactInfo.email.label')}
|
||||||
</h3>
|
</h3>
|
||||||
<a
|
<a
|
||||||
href="mailto:info@damjan-savic.com"
|
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')}
|
{t('contactInfo.email.value')}
|
||||||
</a>
|
</a>
|
||||||
@@ -243,7 +243,7 @@ export function ContactForm() {
|
|||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
transition={{ delay: 0.6 }}
|
transition={{ delay: 0.6 }}
|
||||||
className="text-sm text-zinc-500"
|
className="text-sm text-muted-foreground"
|
||||||
>
|
>
|
||||||
{t('contactInfo.availabilityNote')}
|
{t('contactInfo.availabilityNote')}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
@@ -255,21 +255,21 @@ export function ContactForm() {
|
|||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
transition={{ delay: 0.3 }}
|
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">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Mail className="h-5 w-5 text-zinc-400" aria-hidden="true" />
|
<Mail className="h-5 w-5 text-muted-foreground" aria-hidden="true" />
|
||||||
<h2 className="text-xl font-semibold text-white">
|
<h2 className="text-xl font-semibold text-foreground">
|
||||||
{t('contactForm.title')}
|
{t('contactForm.title')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-zinc-400 mb-6">
|
<p className="text-muted-foreground mb-6">
|
||||||
{t('contactForm.description')}
|
{t('contactForm.description')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<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')}
|
{t('contactForm.name.label')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -283,7 +283,7 @@ export function ContactForm() {
|
|||||||
aria-required="true"
|
aria-required="true"
|
||||||
aria-invalid={!!errors.name}
|
aria-invalid={!!errors.name}
|
||||||
aria-describedby={errors.name ? 'name-error' : undefined}
|
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 && (
|
{errors.name && (
|
||||||
<p id="name-error" role="alert" className="text-sm text-red-400">{errors.name}</p>
|
<p id="name-error" role="alert" className="text-sm text-red-400">{errors.name}</p>
|
||||||
@@ -291,7 +291,7 @@ export function ContactForm() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<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')}
|
{t('contactForm.email.label')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -305,7 +305,7 @@ export function ContactForm() {
|
|||||||
aria-required="true"
|
aria-required="true"
|
||||||
aria-invalid={!!errors.email}
|
aria-invalid={!!errors.email}
|
||||||
aria-describedby={errors.email ? 'email-error' : undefined}
|
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 && (
|
{errors.email && (
|
||||||
<p id="email-error" role="alert" className="text-sm text-red-400">{errors.email}</p>
|
<p id="email-error" role="alert" className="text-sm text-red-400">{errors.email}</p>
|
||||||
@@ -313,7 +313,7 @@ export function ContactForm() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<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')}
|
{t('contactForm.message.label')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -328,7 +328,7 @@ export function ContactForm() {
|
|||||||
aria-required="true"
|
aria-required="true"
|
||||||
aria-invalid={!!errors.message}
|
aria-invalid={!!errors.message}
|
||||||
aria-describedby={errors.message ? 'message-error' : undefined}
|
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 && (
|
{errors.message && (
|
||||||
<p id="message-error" role="alert" className="text-sm text-red-400">{errors.message}</p>
|
<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'
|
? 'text-red-500'
|
||||||
: messageLength >= MAX_MESSAGE_LENGTH * 0.8
|
: messageLength >= MAX_MESSAGE_LENGTH * 0.8
|
||||||
? 'text-yellow-500'
|
? 'text-yellow-500'
|
||||||
: 'text-zinc-500'
|
: 'text-muted-foreground'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t('contactForm.characterCounter', { current: messageLength, max: MAX_MESSAGE_LENGTH })}
|
{t('contactForm.characterCounter', { current: messageLength, max: MAX_MESSAGE_LENGTH })}
|
||||||
@@ -397,7 +397,7 @@ export function ContactForm() {
|
|||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
aria-busy={isSubmitting}
|
aria-busy={isSubmitting}
|
||||||
aria-label={isSubmitting ? t('contactForm.submitting') : undefined}
|
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 ? (
|
{isSubmitting ? (
|
||||||
<Loader2 className="h-5 w-5 animate-spin" aria-hidden="true" />
|
<Loader2 className="h-5 w-5 animate-spin" aria-hidden="true" />
|
||||||
|
|||||||
@@ -9,31 +9,31 @@ export function ContactInfo() {
|
|||||||
const t = useTranslations('footer.sections.contact');
|
const t = useTranslations('footer.sections.contact');
|
||||||
|
|
||||||
return (
|
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">
|
<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')}
|
{t('title')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="tel:+491756950979"
|
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>
|
<span>+49 175 695 0979</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="mailto:info@damjan-savic.com"
|
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>
|
<span>{t('email')}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/contact`}
|
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>
|
<span className="tracking-wide">Kontakt aufnehmen</span>
|
||||||
<ArrowRight className="h-4 w-4 transform group-hover:translate-x-1 transition-transform duration-200" />
|
<ArrowRight className="h-4 w-4 transform group-hover:translate-x-1 transition-transform duration-200" />
|
||||||
|
|||||||
@@ -8,24 +8,24 @@ const Footer = async () => {
|
|||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
return (
|
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="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">
|
<div className="flex flex-col sm:grid sm:grid-cols-2 lg:grid-cols-4 gap-6 sm:gap-8">
|
||||||
{/* Contact Section */}
|
{/* Contact Section */}
|
||||||
<div>
|
<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')}
|
{t('sections.contact.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<a
|
<a
|
||||||
href={`mailto:${t('sections.contact.email')}`}
|
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>
|
<span>{t('sections.contact.email')}</span>
|
||||||
</a>
|
</a>
|
||||||
<div className="flex items-center gap-2 text-zinc-400 text-sm w-fit">
|
<div className="flex items-center gap-2 text-muted-foreground text-sm w-fit">
|
||||||
<MapPin className="h-4 w-4 text-zinc-500 flex-shrink-0" />
|
<MapPin className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||||
<span>{t('sections.contact.location')}</span>
|
<span>{t('sections.contact.location')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,31 +33,31 @@ const Footer = async () => {
|
|||||||
|
|
||||||
{/* Navigation Section */}
|
{/* Navigation Section */}
|
||||||
<div>
|
<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')}
|
{t('sections.navigation.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<nav className="flex flex-col space-y-2">
|
<nav className="flex flex-col space-y-2">
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/portfolio`}
|
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')}
|
{t('sections.navigation.links.portfolio')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/blog`}
|
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')}
|
{t('sections.navigation.links.blog')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/about`}
|
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')}
|
{t('sections.navigation.links.about')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/contact`}
|
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')}
|
{t('sections.navigation.links.contact')}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -66,7 +66,7 @@ const Footer = async () => {
|
|||||||
|
|
||||||
{/* Social Media Section */}
|
{/* Social Media Section */}
|
||||||
<div>
|
<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')}
|
{t('sections.social.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
@@ -74,7 +74,7 @@ const Footer = async () => {
|
|||||||
href="https://www.linkedin.com/in/damjan-savić-720288127/"
|
href="https://www.linkedin.com/in/damjan-savić-720288127/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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"
|
aria-label="LinkedIn"
|
||||||
>
|
>
|
||||||
<Linkedin className="transform transition-transform group-hover:scale-110" size={20} />
|
<Linkedin className="transform transition-transform group-hover:scale-110" size={20} />
|
||||||
@@ -83,7 +83,7 @@ const Footer = async () => {
|
|||||||
href="https://github.com/damjan1996"
|
href="https://github.com/damjan1996"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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"
|
aria-label="GitHub"
|
||||||
>
|
>
|
||||||
<Github className="transform transition-transform group-hover:scale-110" size={20} />
|
<Github className="transform transition-transform group-hover:scale-110" size={20} />
|
||||||
@@ -92,27 +92,27 @@ const Footer = async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Legal Section */}
|
{/* 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">
|
<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">
|
||||||
© {currentYear} Damjan Savić. {t('legal.copyright')}
|
© {currentYear} Damjan Savić. {t('legal.copyright')}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/privacy`}
|
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')}
|
{t('legal.links.privacy')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/imprint`}
|
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')}
|
{t('legal.links.imprint')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/terms`}
|
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')}
|
{t('legal.links.terms')}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const GlobalBackground = () => {
|
|||||||
{/* Background layer */}
|
{/* Background layer */}
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="fixed inset-0 bg-zinc-900"
|
className="fixed inset-0 bg-background"
|
||||||
style={{ zIndex: -2 }}
|
style={{ zIndex: -2 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ const GlobalBackground = () => {
|
|||||||
className="fixed inset-0"
|
className="fixed inset-0"
|
||||||
style={{
|
style={{
|
||||||
zIndex: -1,
|
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
|
opacity: 0.9
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -46,7 +46,7 @@ const GlobalBackground = () => {
|
|||||||
y1="20%"
|
y1="20%"
|
||||||
x2="100%"
|
x2="100%"
|
||||||
y2="20%"
|
y2="20%"
|
||||||
stroke="white"
|
stroke="#ECDFCC"
|
||||||
strokeWidth="1"
|
strokeWidth="1"
|
||||||
opacity="0.2"
|
opacity="0.2"
|
||||||
>
|
>
|
||||||
@@ -73,7 +73,7 @@ const GlobalBackground = () => {
|
|||||||
y1="50%"
|
y1="50%"
|
||||||
x2="100%"
|
x2="100%"
|
||||||
y2="50%"
|
y2="50%"
|
||||||
stroke="white"
|
stroke="#ECDFCC"
|
||||||
strokeWidth="1"
|
strokeWidth="1"
|
||||||
opacity="0.15"
|
opacity="0.15"
|
||||||
>
|
>
|
||||||
@@ -92,7 +92,7 @@ const GlobalBackground = () => {
|
|||||||
y1="0%"
|
y1="0%"
|
||||||
x2="20%"
|
x2="20%"
|
||||||
y2="100%"
|
y2="100%"
|
||||||
stroke="white"
|
stroke="#ECDFCC"
|
||||||
strokeWidth="1"
|
strokeWidth="1"
|
||||||
opacity="0.1"
|
opacity="0.1"
|
||||||
>
|
>
|
||||||
@@ -122,8 +122,8 @@ const GlobalBackground = () => {
|
|||||||
style={{
|
style={{
|
||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
backgroundImage: `
|
backgroundImage: `
|
||||||
linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
|
linear-gradient(rgba(236,223,204,.05) 1px, transparent 1px),
|
||||||
linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px)
|
linear-gradient(90deg, rgba(236,223,204,.05) 1px, transparent 1px)
|
||||||
`,
|
`,
|
||||||
backgroundSize: '50px 50px',
|
backgroundSize: '50px 50px',
|
||||||
opacity: 0.5
|
opacity: 0.5
|
||||||
|
|||||||
@@ -92,9 +92,9 @@ const Header = () => {
|
|||||||
<nav
|
<nav
|
||||||
className={`
|
className={`
|
||||||
fixed w-full z-40 transition-all duration-300
|
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
|
${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"
|
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 />
|
<LanguageSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Menu Button */}
|
{/* Mobile Menu Button */}
|
||||||
<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)}
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||||
aria-expanded={isMenuOpen}
|
aria-expanded={isMenuOpen}
|
||||||
aria-controls="mobile-menu"
|
aria-controls="mobile-menu"
|
||||||
@@ -156,17 +156,17 @@ const Header = () => {
|
|||||||
{/* Mobile Sidebar */}
|
{/* Mobile Sidebar */}
|
||||||
<div
|
<div
|
||||||
id="mobile-menu"
|
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'
|
isMenuOpen ? 'translate-x-0' : 'translate-x-full'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* Sidebar Header */}
|
{/* Sidebar Header */}
|
||||||
<div className="flex items-center justify-between px-4 h-14 sm:h-16 border-b border-zinc-800">
|
<div className="flex items-center justify-between px-4 h-14 sm:h-16 border-b border-border">
|
||||||
<span className="text-white font-semibold">Menu</span>
|
<span className="text-foreground font-semibold">Menu</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsMenuOpen(false)}
|
onClick={() => setIsMenuOpen(false)}
|
||||||
aria-label="Close sidebar"
|
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" />
|
<X className="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
@@ -193,9 +193,9 @@ const Header = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Language Switcher */}
|
{/* 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">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm text-zinc-400">
|
<span className="text-sm text-muted-foreground">
|
||||||
Sprache ändern
|
Sprache ändern
|
||||||
</span>
|
</span>
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|||||||
@@ -80,9 +80,9 @@ const LanguageSwitcher = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="relative inline-block" ref={dropdownRef}>
|
<div className="relative inline-block" ref={dropdownRef}>
|
||||||
<button
|
<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
|
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"
|
hover:scale-105 active:scale-95"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
@@ -98,7 +98,7 @@ const LanguageSwitcher = () => {
|
|||||||
|
|
||||||
<div
|
<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
|
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
|
transition-all duration-200 origin-top
|
||||||
${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95 pointer-events-none'}`}
|
${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95 pointer-events-none'}`}
|
||||||
role="listbox"
|
role="listbox"
|
||||||
@@ -112,12 +112,12 @@ const LanguageSwitcher = () => {
|
|||||||
key={lang.code}
|
key={lang.code}
|
||||||
id={`lang-option-${lang.code}`}
|
id={`lang-option-${lang.code}`}
|
||||||
className={`w-full text-left px-4 py-2 text-sm transition-colors duration-200
|
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
|
${locale === lang.code
|
||||||
? 'bg-zinc-800 text-white'
|
? 'bg-card text-foreground'
|
||||||
: 'text-zinc-400 hover:text-white'
|
: '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)}
|
onClick={() => handleLanguageChange(lang.code)}
|
||||||
role="option"
|
role="option"
|
||||||
aria-selected={locale === lang.code}
|
aria-selected={locale === lang.code}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export function NavLink({ href, icon, label, onClick, className }: NavLinkProps)
|
|||||||
className={`
|
className={`
|
||||||
relative flex items-center gap-2 rounded-full py-2.5 px-4 min-h-[44px]
|
relative flex items-center gap-2 rounded-full py-2.5 px-4 min-h-[44px]
|
||||||
transition-all duration-200 ease-in-out
|
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
|
focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background
|
||||||
${isActive ? 'text-white bg-zinc-700/60' : 'text-zinc-400 hover:text-white hover:bg-zinc-800/30'}
|
${isActive ? 'text-foreground bg-accent/60' : 'text-muted-foreground hover:text-foreground hover:bg-card/30'}
|
||||||
${className || ''}
|
${className || ''}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ export function CategoryFilter() {
|
|||||||
relative whitespace-nowrap rounded-full py-2 px-4
|
relative whitespace-nowrap rounded-full py-2 px-4
|
||||||
transition-all duration-200 ease-in-out text-sm tracking-wide
|
transition-all duration-200 ease-in-out text-sm tracking-wide
|
||||||
${!selectedCategory
|
${!selectedCategory
|
||||||
? 'text-white bg-zinc-700/60'
|
? 'text-foreground bg-accent/60'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-zinc-800/30'
|
: '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
|
relative whitespace-nowrap rounded-full py-2 px-4
|
||||||
transition-all duration-200 ease-in-out text-sm tracking-wide
|
transition-all duration-200 ease-in-out text-sm tracking-wide
|
||||||
${isActive
|
${isActive
|
||||||
? 'text-white bg-zinc-700/60'
|
? 'text-foreground bg-accent/60'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-zinc-800/30'
|
: 'text-muted-foreground hover:text-foreground hover:bg-card/30'
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/portfolio/${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
|
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
|
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"
|
transform hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -45,7 +45,7 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
|
|||||||
className="relative overflow-hidden rounded-xl"
|
className="relative overflow-hidden rounded-xl"
|
||||||
>
|
>
|
||||||
{/* Image Container */}
|
{/* 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
|
<Image
|
||||||
src={`/images/projects/${project.slug}/cover.avif`}
|
src={`/images/projects/${project.slug}/cover.avif`}
|
||||||
alt={project.title}
|
alt={project.title}
|
||||||
@@ -56,9 +56,9 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* 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 */}
|
{/* 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 && (
|
{project.date && (
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
@@ -74,12 +74,12 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Title & Description */}
|
{/* Title & Description */}
|
||||||
<h3 className="text-xl font-semibold text-white mb-3
|
<h3 className="text-xl font-semibold text-foreground mb-3
|
||||||
group-hover:text-zinc-100 transition-colors duration-300">
|
group-hover:text-foreground/90 transition-colors duration-300">
|
||||||
{project.title}
|
{project.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-400 text-sm line-clamp-2 mb-4
|
<p className="text-muted-foreground text-sm line-clamp-2 mb-4
|
||||||
group-hover:text-zinc-300 transition-colors duration-300">
|
group-hover:text-foreground/70 transition-colors duration-300">
|
||||||
{project.excerpt || project.description}
|
{project.excerpt || project.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -88,17 +88,17 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
|
|||||||
{displayTags.slice(0, 3).map((tag, index) => (
|
{displayTags.slice(0, 3).map((tag, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="px-3 py-1 bg-zinc-800/50
|
className="px-3 py-1 bg-card/50
|
||||||
text-sm text-zinc-400 rounded-full
|
text-sm text-muted-foreground rounded-full
|
||||||
ring-1 ring-zinc-700/50 group-hover:ring-zinc-600/50
|
ring-1 ring-border/50 group-hover:ring-accent/50
|
||||||
group-hover:bg-zinc-800/70 group-hover:text-zinc-300
|
group-hover:bg-card/70 group-hover:text-foreground/80
|
||||||
transition-all duration-300"
|
transition-all duration-300"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{displayTags.length > 3 && (
|
{displayTags.length > 3 && (
|
||||||
<span className="text-sm text-zinc-600">
|
<span className="text-sm text-muted-foreground/60">
|
||||||
+{displayTags.length - 3} more
|
+{displayTags.length - 3} more
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -106,17 +106,17 @@ const PortfolioCard = ({ project }: PortfolioCardProps) => {
|
|||||||
|
|
||||||
{/* Link Icon */}
|
{/* Link Icon */}
|
||||||
<div className="absolute top-6 right-6 p-2.5 rounded-full
|
<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
|
opacity-0 group-hover:opacity-100
|
||||||
transform translate-y-2 group-hover:translate-y-0
|
transform translate-y-2 group-hover:translate-y-0
|
||||||
transition-all duration-300">
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Subtle Hover Border */}
|
{/* Subtle Hover Border */}
|
||||||
<div className="absolute inset-0 rounded-xl pointer-events-none
|
<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" />
|
transition-all duration-500" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ const About = async () => {
|
|||||||
<div className="space-y-6 order-2 lg:order-1">
|
<div className="space-y-6 order-2 lg:order-1">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div>
|
<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')}
|
{t('title')}
|
||||||
</h2>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Highlight Pills */}
|
{/* Highlight Pills */}
|
||||||
@@ -38,17 +38,17 @@ const About = async () => {
|
|||||||
{highlights.map((item, index) => (
|
{highlights.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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-muted-foreground">{item.icon}</span>
|
||||||
<span className="text-zinc-300 text-sm">{item.label}</span>
|
<span className="text-foreground/80 text-sm">{item.label}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="space-y-4">
|
<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')}
|
{t('content')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,7 +57,7 @@ const About = async () => {
|
|||||||
<div className="flex flex-wrap gap-4 pt-4">
|
<div className="flex flex-wrap gap-4 pt-4">
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/about`}
|
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" />
|
<GraduationCap className="w-4 h-4" />
|
||||||
{t('buttons.learnMore')}
|
{t('buttons.learnMore')}
|
||||||
@@ -65,7 +65,7 @@ const About = async () => {
|
|||||||
<a
|
<a
|
||||||
href={getCvUrl()}
|
href={getCvUrl()}
|
||||||
download
|
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" />
|
<Download className="w-4 h-4" />
|
||||||
{t('buttons.downloadCV')}
|
{t('buttons.downloadCV')}
|
||||||
@@ -75,7 +75,7 @@ const About = async () => {
|
|||||||
|
|
||||||
{/* Image Section */}
|
{/* Image Section */}
|
||||||
<div className="relative order-1 lg:order-2">
|
<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
|
<Image
|
||||||
src="/images/gallery/frontal-smile.avif"
|
src="/images/gallery/frontal-smile.avif"
|
||||||
alt="Damjan Savić"
|
alt="Damjan Savić"
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ const Experience = () => {
|
|||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] }}
|
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')}
|
{t('title')}
|
||||||
</h2>
|
</h2>
|
||||||
<div
|
<div
|
||||||
@@ -99,15 +99,15 @@ const Experience = () => {
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.6, delay: index * 0.15, ease: [0.25, 0.46, 0.45, 0.94] }}
|
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 } }}
|
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}
|
{exp.role}
|
||||||
</h3>
|
</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}
|
{exp.company}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-zinc-400 mb-6">
|
<p className="text-sm text-muted-foreground mb-6">
|
||||||
{exp.period}
|
{exp.period}
|
||||||
</p>
|
</p>
|
||||||
<ul className="space-y-3 md:space-y-4">
|
<ul className="space-y-3 md:space-y-4">
|
||||||
@@ -117,9 +117,9 @@ const Experience = () => {
|
|||||||
initial={{ opacity: 0, x: -20 }}
|
initial={{ opacity: 0, x: -20 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
transition={{ duration: 0.4, delay: 0.3 + (hIndex * 0.1), ease: 'easeOut' }}
|
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>
|
<span>{highlight}</span>
|
||||||
</motion.li>
|
</motion.li>
|
||||||
))}
|
))}
|
||||||
@@ -135,7 +135,7 @@ const Experience = () => {
|
|||||||
<div className="flex justify-center items-center gap-4 mt-8">
|
<div className="flex justify-center items-center gap-4 mt-8">
|
||||||
<button
|
<button
|
||||||
onClick={() => currentPage > 0 && setCurrentPage(prev => prev - 1)}
|
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}
|
disabled={currentPage === 0}
|
||||||
aria-label="Previous page"
|
aria-label="Previous page"
|
||||||
>
|
>
|
||||||
@@ -146,13 +146,12 @@ const Experience = () => {
|
|||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => setCurrentPage(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"
|
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}`}
|
aria-label={`Page ${index + 1}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`rounded-full transition-all duration-300 ${
|
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>
|
</button>
|
||||||
@@ -160,7 +159,7 @@ const Experience = () => {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => currentPage < totalPages - 1 && setCurrentPage(prev => prev + 1)}
|
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}
|
disabled={currentPage === totalPages - 1}
|
||||||
aria-label="Next page"
|
aria-label="Next page"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ const Hero = async () => {
|
|||||||
const t = await getTranslations('pages.home.hero');
|
const t = await getTranslations('pages.home.hero');
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* Concentric Circles Background */}
|
||||||
<div className="absolute inset-0 pointer-events-none flex items-center justify-center" style={{ zIndex: 2 }}>
|
<div className="absolute inset-0 pointer-events-none flex items-center justify-center" style={{ zIndex: 2 }}>
|
||||||
{[1, 2, 3].map((index) => (
|
{[1, 2, 3].map((index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="absolute rounded-full border border-zinc-800/30"
|
className="absolute rounded-full border border-border/30"
|
||||||
style={{
|
style={{
|
||||||
width: `${index * 30}%`,
|
width: `${index * 30}%`,
|
||||||
height: `${index * 30}%`,
|
height: `${index * 30}%`,
|
||||||
@@ -31,7 +31,7 @@ const Hero = async () => {
|
|||||||
href="https://www.linkedin.com/in/damjan-savi%C4%87-720288127/"
|
href="https://www.linkedin.com/in/damjan-savi%C4%87-720288127/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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"
|
aria-label="LinkedIn"
|
||||||
>
|
>
|
||||||
<Linkedin className="transform transition-transform group-hover:scale-110" size={20} />
|
<Linkedin className="transform transition-transform group-hover:scale-110" size={20} />
|
||||||
@@ -40,7 +40,7 @@ const Hero = async () => {
|
|||||||
href="https://github.com/damjan1996"
|
href="https://github.com/damjan1996"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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"
|
aria-label="GitHub"
|
||||||
>
|
>
|
||||||
<Github className="transform transition-transform group-hover:scale-110" size={20} />
|
<Github className="transform transition-transform group-hover:scale-110" size={20} />
|
||||||
@@ -48,7 +48,7 @@ const Hero = async () => {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="mailto:info@damjan-savic.com"
|
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')}
|
{t('cta')}
|
||||||
</a>
|
</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 }}>
|
<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 */}
|
{/* Profile Image */}
|
||||||
<div
|
<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)' }}
|
style={{ transform: 'translateZ(0)' }}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@@ -74,48 +74,48 @@ const Hero = async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Title and Name */}
|
{/* 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')}
|
{t('title')}
|
||||||
</p>
|
</p>
|
||||||
<h1 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold mb-3 flex items-center">
|
<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>
|
{t('name')}<span className="animate-pulse ml-1">|</span>
|
||||||
</h1>
|
</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')}
|
{t('description')}
|
||||||
</p>
|
</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')}
|
{t('currentRole')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* 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
|
<a
|
||||||
href="#experience"
|
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"
|
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>
|
<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>
|
||||||
<a
|
<a
|
||||||
href="#skills"
|
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"
|
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>
|
<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>
|
||||||
<a
|
<a
|
||||||
href="#projects"
|
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"
|
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>
|
<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>
|
||||||
<a
|
<a
|
||||||
href="#about"
|
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"
|
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>
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</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 }}>
|
<div className="absolute bottom-6 sm:bottom-8 left-1/2 transform -translate-x-1/2" style={{ zIndex: 40 }}>
|
||||||
<a
|
<a
|
||||||
href="mailto:info@damjan-savic.com"
|
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')}
|
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"/>
|
<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>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ const Projects = async () => {
|
|||||||
<section id="projects" className="py-12 sm:py-16 md:py-24">
|
<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="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">
|
<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')}
|
{t('title')}
|
||||||
</h2>
|
</h2>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/portfolio`}
|
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')}
|
{t('viewAll')}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -55,16 +55,16 @@ const Projects = async () => {
|
|||||||
<Link
|
<Link
|
||||||
key={project.slug}
|
key={project.slug}
|
||||||
href={`/${locale}/portfolio/${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
|
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
|
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"
|
transform hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<div className="relative overflow-hidden rounded-xl">
|
<div className="relative overflow-hidden rounded-xl">
|
||||||
{/* Image Container */}
|
{/* 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
|
<Image
|
||||||
src={`/images/projects/${project.slug}/cover.avif`}
|
src={`/images/projects/${project.slug}/cover.avif`}
|
||||||
alt={project.title}
|
alt={project.title}
|
||||||
@@ -75,8 +75,8 @@ const Projects = async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="relative -mt-2 p-6 bg-zinc-900 z-10">
|
<div className="relative -mt-2 p-6 bg-background z-10">
|
||||||
<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 && (
|
{project.date && (
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
@@ -91,10 +91,10 @@ const Projects = async () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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}
|
{project.title}
|
||||||
</h3>
|
</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.excerpt}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -103,16 +103,16 @@ const Projects = async () => {
|
|||||||
{project.technologies.slice(0, 3).map((tag, tagIndex) => (
|
{project.technologies.slice(0, 3).map((tag, tagIndex) => (
|
||||||
<span
|
<span
|
||||||
key={tagIndex}
|
key={tagIndex}
|
||||||
className="px-3 py-1 bg-zinc-800/50 text-sm text-zinc-400 rounded-full
|
className="px-3 py-1 bg-card/50 text-sm text-muted-foreground rounded-full
|
||||||
ring-1 ring-zinc-700/50 group-hover:ring-zinc-600/50
|
ring-1 ring-border/50 group-hover:ring-accent/50
|
||||||
group-hover:bg-zinc-800/70 group-hover:text-zinc-300
|
group-hover:bg-card/70 group-hover:text-foreground/80
|
||||||
transition-all duration-300"
|
transition-all duration-300"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{project.technologies.length > 3 && (
|
{project.technologies.length > 3 && (
|
||||||
<span className="text-sm text-zinc-600">
|
<span className="text-sm text-muted-foreground/60">
|
||||||
+{project.technologies.length - 3} more
|
+{project.technologies.length - 3} more
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -120,11 +120,11 @@ const Projects = async () => {
|
|||||||
|
|
||||||
{/* Link Icon */}
|
{/* Link Icon */}
|
||||||
<div className="absolute top-6 right-6 p-2.5 rounded-full
|
<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
|
opacity-0 group-hover:opacity-100
|
||||||
transform translate-y-2 group-hover:translate-y-0
|
transform translate-y-2 group-hover:translate-y-0
|
||||||
transition-all duration-300">
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface SkeletonProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Skeleton: React.FC<SkeletonProps> = ({ className }) => (
|
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 = () => (
|
const SkillsSkeleton = () => (
|
||||||
@@ -40,7 +40,7 @@ const SkillsSkeleton = () => (
|
|||||||
{/* Skills Icons Grid Skeleton */}
|
{/* Skills Icons Grid Skeleton */}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
||||||
{[1, 2, 3, 4, 5, 6].map((i) => (
|
{[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="w-8 h-8 mx-auto mb-3" />
|
||||||
<Skeleton className="h-4 w-16 mx-auto" />
|
<Skeleton className="h-4 w-16 mx-auto" />
|
||||||
</div>
|
</div>
|
||||||
@@ -106,7 +106,7 @@ const Skills = () => {
|
|||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.6, ease: [0.25, 0.1, 0.25, 1] }}
|
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')}
|
{t('title')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8">
|
<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 }}
|
transition={{ duration: 0.5, delay: idx * 0.1 }}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-white text-sm font-medium">{skill.name}</span>
|
<span className="text-foreground text-sm font-medium">{skill.name}</span>
|
||||||
<span className="text-white text-sm">{skill.level}%</span>
|
<span className="text-foreground text-sm">{skill.level}%</span>
|
||||||
</div>
|
</div>
|
||||||
<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"
|
role="progressbar"
|
||||||
aria-valuenow={skill.level}
|
aria-valuenow={skill.level}
|
||||||
aria-valuemin={0}
|
aria-valuemin={0}
|
||||||
aria-valuemax={100}
|
aria-valuemax={100}
|
||||||
>
|
>
|
||||||
<motion.div
|
<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 }}
|
initial={{ width: 0 }}
|
||||||
whileInView={{ width: `${skill.level}%` }}
|
whileInView={{ width: `${skill.level}%` }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
@@ -153,8 +153,8 @@ const Skills = () => {
|
|||||||
whileInView={{ opacity: 1, scale: 1 }}
|
whileInView={{ opacity: 1, scale: 1 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.6, delay: idx * 0.1, ease: [0.25, 0.1, 0.25, 1] }}
|
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 ${
|
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-zinc-500 z-20' : ''
|
selectedSkill === skill.name ? 'ring-2 ring-ring z-20' : ''
|
||||||
}`}
|
}`}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
@@ -163,7 +163,7 @@ const Skills = () => {
|
|||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
className={selectedSkill === skill.name ? 'text-zinc-500' : 'text-white'}
|
className={selectedSkill === skill.name ? 'text-accent' : 'text-foreground'}
|
||||||
animate={{
|
animate={{
|
||||||
rotate: selectedSkill === skill.name ? [0, -10, 10, 0] : 0,
|
rotate: selectedSkill === skill.name ? [0, -10, 10, 0] : 0,
|
||||||
scale: selectedSkill === skill.name ? 1.1 : 1
|
scale: selectedSkill === skill.name ? 1.1 : 1
|
||||||
@@ -172,12 +172,12 @@ const Skills = () => {
|
|||||||
>
|
>
|
||||||
{iconMap[skill.name] || <Code2 className="w-8 h-8" />}
|
{iconMap[skill.name] || <Code2 className="w-8 h-8" />}
|
||||||
</motion.div>
|
</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>
|
<AnimatePresence>
|
||||||
{selectedSkill === skill.name && skill.description && (
|
{selectedSkill === skill.name && skill.description && (
|
||||||
<motion.div
|
<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 }}
|
initial={{ opacity: 0, y: 5 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: 5 }}
|
exit={{ opacity: 0, y: 5 }}
|
||||||
@@ -189,7 +189,7 @@ const Skills = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{skill.description}
|
{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>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
+19
-19
@@ -19,7 +19,7 @@ function parseInlineMarkdown(text: string): React.ReactNode {
|
|||||||
const codeMatch = remaining.match(/^`([^`]+)`/);
|
const codeMatch = remaining.match(/^`([^`]+)`/);
|
||||||
if (codeMatch) {
|
if (codeMatch) {
|
||||||
parts.push(
|
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]}
|
{codeMatch[1]}
|
||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
@@ -30,7 +30,7 @@ function parseInlineMarkdown(text: string): React.ReactNode {
|
|||||||
// Bold
|
// Bold
|
||||||
const boldMatch = remaining.match(/^\*\*([^*]+)\*\*/);
|
const boldMatch = remaining.match(/^\*\*([^*]+)\*\*/);
|
||||||
if (boldMatch) {
|
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);
|
remaining = remaining.slice(boldMatch[0].length);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -88,13 +88,13 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
if (listItems) {
|
if (listItems) {
|
||||||
if (listItems.type === 'ul') {
|
if (listItems.type === 'ul') {
|
||||||
elements.push(
|
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}
|
{listItems.items}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
elements.push(
|
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}
|
{listItems.items}
|
||||||
</ol>
|
</ol>
|
||||||
);
|
);
|
||||||
@@ -111,9 +111,9 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
<div key={`table-${elements.length}`} className="overflow-x-auto my-6">
|
<div key={`table-${elements.length}`} className="overflow-x-auto my-6">
|
||||||
<table className="min-w-full border-collapse">
|
<table className="min-w-full border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-zinc-700">
|
<tr className="border-b border-border">
|
||||||
{header.map((cell, idx) => (
|
{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()}
|
{cell.trim()}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
@@ -121,9 +121,9 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{body.map((row, rowIdx) => (
|
{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) => (
|
{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()}
|
{cell.trim()}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
@@ -145,8 +145,8 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
if (codeBlock) {
|
if (codeBlock) {
|
||||||
// End code block
|
// End code block
|
||||||
elements.push(
|
elements.push(
|
||||||
<pre key={`code-${elements.length}`} className="bg-zinc-900 border border-zinc-800 rounded-lg p-4 overflow-x-auto my-6">
|
<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-zinc-300">
|
<code className="text-sm font-mono text-foreground/80">
|
||||||
{codeBlock.lines.join('\n')}
|
{codeBlock.lines.join('\n')}
|
||||||
</code>
|
</code>
|
||||||
</pre>
|
</pre>
|
||||||
@@ -188,7 +188,7 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
if (line.startsWith('#### ')) {
|
if (line.startsWith('#### ')) {
|
||||||
flushList();
|
flushList();
|
||||||
elements.push(
|
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))}
|
{parseInlineMarkdown(line.slice(5))}
|
||||||
</h4>
|
</h4>
|
||||||
);
|
);
|
||||||
@@ -199,7 +199,7 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
if (line.startsWith('### ')) {
|
if (line.startsWith('### ')) {
|
||||||
flushList();
|
flushList();
|
||||||
elements.push(
|
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))}
|
{parseInlineMarkdown(line.slice(4))}
|
||||||
</h3>
|
</h3>
|
||||||
);
|
);
|
||||||
@@ -210,7 +210,7 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
if (line.startsWith('## ')) {
|
if (line.startsWith('## ')) {
|
||||||
flushList();
|
flushList();
|
||||||
elements.push(
|
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))}
|
{parseInlineMarkdown(line.slice(3))}
|
||||||
</h2>
|
</h2>
|
||||||
);
|
);
|
||||||
@@ -221,7 +221,7 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
if (line.startsWith('# ')) {
|
if (line.startsWith('# ')) {
|
||||||
flushList();
|
flushList();
|
||||||
elements.push(
|
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))}
|
{parseInlineMarkdown(line.slice(2))}
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
@@ -269,7 +269,7 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
const itemContent = line.replace(/^[-*]\s\[[ x]\]\s?/i, '');
|
const itemContent = line.replace(/^[-*]\s\[[ x]\]\s?/i, '');
|
||||||
listItems.items.push(
|
listItems.items.push(
|
||||||
<li key={`li-${listItems.items.length}`} className="flex items-center gap-2">
|
<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 ? '✓' : '○'}
|
{checked ? '✓' : '○'}
|
||||||
</span>
|
</span>
|
||||||
{parseInlineMarkdown(itemContent)}
|
{parseInlineMarkdown(itemContent)}
|
||||||
@@ -282,7 +282,7 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
// Horizontal rule
|
// Horizontal rule
|
||||||
if (line.match(/^[-*_]{3,}$/)) {
|
if (line.match(/^[-*_]{3,}$/)) {
|
||||||
flushList();
|
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++;
|
i++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -291,7 +291,7 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
if (line.startsWith('> ')) {
|
if (line.startsWith('> ')) {
|
||||||
flushList();
|
flushList();
|
||||||
elements.push(
|
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))}
|
{parseInlineMarkdown(line.slice(2))}
|
||||||
</blockquote>
|
</blockquote>
|
||||||
);
|
);
|
||||||
@@ -309,7 +309,7 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
// Regular paragraph
|
// Regular paragraph
|
||||||
flushList();
|
flushList();
|
||||||
elements.push(
|
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)}
|
{parseInlineMarkdown(line)}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
@@ -320,5 +320,5 @@ export function renderMarkdown(content: string): React.ReactNode {
|
|||||||
flushList();
|
flushList();
|
||||||
flushTable();
|
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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,7 +151,7 @@
|
|||||||
{
|
{
|
||||||
"role": "Process Automation Specialist",
|
"role": "Process Automation Specialist",
|
||||||
"company": "Everlast Consulting GmbH",
|
"company": "Everlast Consulting GmbH",
|
||||||
"period": "12/2024 - HEUTE",
|
"period": "12/2025 - HEUTE",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
"Entwicklung von KI-Agenten mit n8n und Zapier",
|
"Entwicklung von KI-Agenten mit n8n und Zapier",
|
||||||
"Aufbau von Web-Scraping-Lösungen",
|
"Aufbau von Web-Scraping-Lösungen",
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
{
|
{
|
||||||
"role": "Consultant Digital Solutions",
|
"role": "Consultant Digital Solutions",
|
||||||
"company": "Ritter Digital GmbH",
|
"company": "Ritter Digital GmbH",
|
||||||
"period": "08/2023 - 11/2024",
|
"period": "08/2023 - 11/2025",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
"Backend-Services auf dedizierten Servern",
|
"Backend-Services auf dedizierten Servern",
|
||||||
"KI-Integration via Power Automate",
|
"KI-Integration via Power Automate",
|
||||||
@@ -327,7 +327,7 @@
|
|||||||
"positions": [
|
"positions": [
|
||||||
{
|
{
|
||||||
"title": "Process Automation Specialist",
|
"title": "Process Automation Specialist",
|
||||||
"period": "12/2024 - Heute",
|
"period": "12/2025 - Heute",
|
||||||
"company": "Everlast Consulting GmbH",
|
"company": "Everlast Consulting GmbH",
|
||||||
"location": "Köln",
|
"location": "Köln",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
@@ -338,7 +338,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Consultant Digital Solutions",
|
"title": "Consultant Digital Solutions",
|
||||||
"period": "08/2023 - 11/2024",
|
"period": "08/2023 - 11/2025",
|
||||||
"company": "Ritter Digital GmbH",
|
"company": "Ritter Digital GmbH",
|
||||||
"location": "Köln",
|
"location": "Köln",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
|
|||||||
@@ -165,7 +165,7 @@
|
|||||||
{
|
{
|
||||||
"role": "Process Automation Specialist",
|
"role": "Process Automation Specialist",
|
||||||
"company": "Everlast Consulting GmbH",
|
"company": "Everlast Consulting GmbH",
|
||||||
"period": "12/2024 - PRESENT",
|
"period": "12/2025 - PRESENT",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
"Development of AI agents with n8n and Zapier",
|
"Development of AI agents with n8n and Zapier",
|
||||||
"Building web scraping solutions",
|
"Building web scraping solutions",
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
{
|
{
|
||||||
"role": "Consultant Digital Solutions",
|
"role": "Consultant Digital Solutions",
|
||||||
"company": "Ritter Digital GmbH",
|
"company": "Ritter Digital GmbH",
|
||||||
"period": "08/2023 - 11/2024",
|
"period": "08/2023 - 11/2025",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
"Backend services on dedicated servers",
|
"Backend services on dedicated servers",
|
||||||
"AI integration via Power Automate",
|
"AI integration via Power Automate",
|
||||||
@@ -342,7 +342,7 @@
|
|||||||
"positions": [
|
"positions": [
|
||||||
{
|
{
|
||||||
"title": "Process Automation Specialist",
|
"title": "Process Automation Specialist",
|
||||||
"period": "12/2024 - Present",
|
"period": "12/2025 - Present",
|
||||||
"company": "Everlast Consulting GmbH",
|
"company": "Everlast Consulting GmbH",
|
||||||
"location": "Cologne",
|
"location": "Cologne",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
@@ -353,7 +353,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Consultant Digital Solutions",
|
"title": "Consultant Digital Solutions",
|
||||||
"period": "08/2023 - 11/2024",
|
"period": "08/2023 - 11/2025",
|
||||||
"company": "Ritter Digital GmbH",
|
"company": "Ritter Digital GmbH",
|
||||||
"location": "Cologne",
|
"location": "Cologne",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
|
|||||||
@@ -172,7 +172,7 @@
|
|||||||
{
|
{
|
||||||
"role": "Process Automation Specialist",
|
"role": "Process Automation Specialist",
|
||||||
"company": "Everlast Consulting GmbH",
|
"company": "Everlast Consulting GmbH",
|
||||||
"period": "12/2024 - DANAS",
|
"period": "12/2025 - DANAS",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
"Razvoj AI agenata sa n8n i Zapier",
|
"Razvoj AI agenata sa n8n i Zapier",
|
||||||
"Izgradnja web scraping rešenja",
|
"Izgradnja web scraping rešenja",
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
{
|
{
|
||||||
"role": "Consultant Digital Solutions",
|
"role": "Consultant Digital Solutions",
|
||||||
"company": "Ritter Digital GmbH",
|
"company": "Ritter Digital GmbH",
|
||||||
"period": "08/2023 - 11/2024",
|
"period": "08/2023 - 11/2025",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
"Backend servisi na namenskim serverima",
|
"Backend servisi na namenskim serverima",
|
||||||
"AI integracija preko Power Automate",
|
"AI integracija preko Power Automate",
|
||||||
@@ -349,7 +349,7 @@
|
|||||||
"positions": [
|
"positions": [
|
||||||
{
|
{
|
||||||
"title": "Process Automation Specialist",
|
"title": "Process Automation Specialist",
|
||||||
"period": "12/2024 - Danas",
|
"period": "12/2025 - Danas",
|
||||||
"company": "Everlast Consulting GmbH",
|
"company": "Everlast Consulting GmbH",
|
||||||
"location": "Keln",
|
"location": "Keln",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Consultant Digital Solutions",
|
"title": "Consultant Digital Solutions",
|
||||||
"period": "08/2023 - 11/2024",
|
"period": "08/2023 - 11/2025",
|
||||||
"company": "Ritter Digital GmbH",
|
"company": "Ritter Digital GmbH",
|
||||||
"location": "Keln",
|
"location": "Keln",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const CONTACT = {
|
|||||||
export const CURRENT_ROLE = {
|
export const CURRENT_ROLE = {
|
||||||
company: "Everlast Consulting GmbH",
|
company: "Everlast Consulting GmbH",
|
||||||
position: "Process Automation Specialist",
|
position: "Process Automation Specialist",
|
||||||
since: "2024-12"
|
since: "2025-12"
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user