Refine global card system

This commit is contained in:
MOH
2026-03-10 06:09:03 +01:00
parent f2b5a15191
commit 6f20f28a96
7 changed files with 526 additions and 101 deletions
+289 -59
View File
@@ -1,14 +1,29 @@
import type { Metadata } from "next";
import { Compass, Layers3, Users } from "lucide-react";
import {
ArrowRight,
Blocks,
BriefcaseBusiness,
Compass,
Layers3,
MessageSquareMore,
Sparkles,
Workflow,
} from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { Container } from "@/components/layout/container";
import { PageHero } from "@/components/layout/page-hero";
import { SiteLogo } from "@/components/layout/site-logo";
import { MotionFade } from "@/components/motion-fade";
import { buildLocalizedMetadata } from "@/lib/metadata";
import { resolveLocale } from "@/lib/locale";
import { AppCard } from "@/components/ui/app-card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { CardContent } from "@/components/ui/card";
import { getSiteSettings, getSiteSettingsMediaBindings } from "@/lib/app-config";
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
import { buildLocalizedMetadata } from "@/lib/metadata";
type AboutPageProps = {
params: {
@@ -16,6 +31,8 @@ type AboutPageProps = {
};
};
export const dynamic = "force-dynamic";
export async function generateMetadata({
params: { locale },
}: AboutPageProps): Promise<Metadata> {
@@ -32,7 +49,68 @@ export async function generateMetadata({
export default async function AboutPage({ params: { locale } }: AboutPageProps) {
const localeKey = resolveLocale(locale);
const t = await getTranslations({ locale: localeKey, namespace: "aboutPage" });
const [t, siteSettings, mediaBindings] = await Promise.all([
getTranslations({ locale: localeKey, namespace: "aboutPage" }),
getSiteSettings(),
getSiteSettingsMediaBindings(),
]);
const siteName = siteSettings.locales[localeKey].siteName;
const valueItems = [
{
icon: Compass,
title: t("valueA"),
text: t("valueAText"),
},
{
icon: Layers3,
title: t("valueB"),
text: t("valueBText"),
},
{
icon: MessageSquareMore,
title: t("valueC"),
text: t("valueCText"),
},
];
const processItems = [
{
icon: Sparkles,
title: t("processOne"),
text: t("processOneText"),
},
{
icon: Blocks,
title: t("processTwo"),
text: t("processTwoText"),
},
{
icon: Workflow,
title: t("processThree"),
text: t("processThreeText"),
},
];
const statItems = [
{
value: t("statOneValue"),
label: t("statOneLabel"),
text: t("statOneText"),
},
{
value: t("statTwoValue"),
label: t("statTwoLabel"),
text: t("statTwoText"),
},
{
value: t("statThreeValue"),
label: t("statThreeLabel"),
text: t("statThreeText"),
},
];
const highlightItems = [t("highlightOne"), t("highlightTwo"), t("highlightThree")];
return (
<>
@@ -43,70 +121,222 @@ export default async function AboutPage({ params: { locale } }: AboutPageProps)
description={t("intro")}
/>
<Container className="flex flex-col gap-section pb-12 lg:pb-16">
<Container className="relative z-10 flex flex-col gap-6 pb-12 lg:pb-16">
<MotionFade delay={0.05}>
<AppCard>
<CardContent className="p-6 lg:p-8">
<h2 className="text-2xl font-semibold text-foreground">
{t("valuesTitle")}
</h2>
<div className="mt-6 grid gap-4 md:grid-cols-3">
{[
{
icon: Compass,
title: t("valueA"),
text: t("valueAText"),
},
{
icon: Layers3,
title: t("valueB"),
text: t("valueBText"),
},
{
icon: Users,
title: t("valueC"),
text: t("valueCText"),
},
].map((item) => {
<section className="grid gap-6 lg:grid-cols-12">
<AppCard className="overflow-hidden lg:col-span-7">
<CardContent className="p-6 lg:p-8">
<Badge
variant="outline"
className="border-border/70 bg-background/80 px-3 py-1.5 text-[0.72rem] font-semibold tracking-[0.18em] text-foreground/60"
>
{t("storyEyebrow")}
</Badge>
<h2 className="mt-5 max-w-[11ch] text-balance text-4xl font-semibold leading-[0.92] tracking-[-0.06em] text-foreground sm:text-5xl lg:text-6xl">
{t("storyTitle")}
</h2>
<div className="mt-6 grid gap-5 lg:grid-cols-[minmax(0,1fr)_minmax(12rem,15rem)] lg:items-start">
<div className="space-y-4">
<p className="text-sm leading-7 text-muted-foreground sm:text-base">
{t("storyTextOne")}
</p>
<p className="text-sm leading-7 text-muted-foreground sm:text-base">
{t("storyTextTwo")}
</p>
</div>
<div className="grid gap-3">
{highlightItems.map((item) => (
<AppCard key={item} padding="sm" className="text-sm font-medium leading-6 text-foreground/84">
{item}
</AppCard>
))}
</div>
</div>
<div className="mt-8 flex flex-wrap gap-3">
<Button asChild size="lg" className="min-w-[11rem]">
<Link href={getLocalizedPath(localeKey, "/contact")}>
{t("primaryCta")}
<ArrowRight className="h-4 w-4" />
</Link>
</Button>
<Button
asChild
size="lg"
variant="outline"
className="min-w-[11rem] border-border/80 bg-background/70 hover:bg-background"
>
<Link href={getLocalizedPath(localeKey, "/portfolio")}>
{t("secondaryCta")}
</Link>
</Button>
</div>
</CardContent>
</AppCard>
<AppCard className="overflow-hidden lg:col-span-5">
<CardContent className="p-5">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<AppCard className="sm:col-span-2 lg:col-span-4">
<CardContent className="p-4">
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-xs font-medium uppercase tracking-[0.18em] text-foreground/48">
{t("logoEyebrow")}
</p>
<p className="mt-2 max-w-[16rem] text-sm leading-6 text-muted-foreground">
{t("logoText")}
</p>
</div>
<SiteLogo
alt={siteName}
priority
lightLogoUrl={mediaBindings.siteLogoLight?.url}
darkLogoUrl={mediaBindings.siteLogoDark?.url}
className="h-10 sm:h-11"
/>
</div>
</CardContent>
</AppCard>
<AppCard padding="none" className="overflow-hidden sm:col-span-1 lg:col-span-2">
<div className="relative min-h-[9rem] overflow-hidden rounded-nested">
<Image
src="/uploads/portfolio/demo-cover.svg"
alt={t("galleryAltOne")}
fill
sizes="(min-width: 1024px) 16rem, (min-width: 640px) 50vw, 100vw"
className="object-cover"
/>
</div>
</AppCard>
<AppCard className="min-h-[9rem] sm:col-span-1 lg:col-span-2">
<CardContent className="flex h-full flex-col justify-between p-4">
<div className="flex items-center gap-3 text-foreground/86">
<BriefcaseBusiness className="h-5 w-5 text-brand-primary" />
<span className="text-sm font-medium">{t("availabilityLabel")}</span>
</div>
<p className="text-sm leading-6 text-muted-foreground">
{t("availabilityText")}
</p>
</CardContent>
</AppCard>
<AppCard padding="none" className="overflow-hidden sm:col-span-2 lg:col-span-4">
<div className="relative min-h-[13rem] overflow-hidden rounded-nested">
<Image
src="/uploads/portfolio/demo-cover.svg"
alt={t("galleryAltTwo")}
fill
sizes="(min-width: 1024px) 28rem, (min-width: 640px) 100vw, 100vw"
className="object-cover"
/>
</div>
</AppCard>
</div>
</CardContent>
</AppCard>
</section>
</MotionFade>
<MotionFade delay={0.1}>
<section className="grid gap-6 lg:grid-cols-12">
<AppCard className="lg:col-span-3">
<CardContent className="flex h-full flex-col p-6">
<Badge
variant="outline"
className="w-fit border-border/70 bg-background/80 px-3 py-1.5 text-[0.72rem] font-semibold tracking-[0.18em] text-foreground/60"
>
{t("valuesEyebrow")}
</Badge>
<h2 className="mt-4 text-3xl font-semibold tracking-[-0.04em] text-foreground sm:text-4xl">
{t("valuesTitle")}
</h2>
<p className="mt-4 max-w-[28rem] text-sm leading-7 text-muted-foreground">
{t("valuesIntro")}
</p>
</CardContent>
</AppCard>
{valueItems.map((item) => {
const Icon = item.icon;
return (
<AppCard key={item.title} className="lg:col-span-3">
<CardContent className="p-5">
<div className="flex h-11 w-11 items-center justify-center rounded-full bg-[linear-gradient(135deg,hsl(var(--hero-left)/0.42),hsl(var(--hero-right)/0.26))]">
<Icon className="h-5 w-5 text-foreground/80" />
</div>
<h3 className="mt-4 text-lg font-semibold text-foreground">{item.title}</h3>
<p className="mt-3 text-sm leading-6 text-muted-foreground">{item.text}</p>
</CardContent>
</AppCard>
);
})}
</section>
</MotionFade>
<MotionFade delay={0.15}>
<section className="grid gap-6 lg:grid-cols-12">
<AppCard className="lg:col-span-4">
<CardContent className="flex h-full flex-col p-6 lg:p-7">
<Badge
variant="outline"
className="w-fit border-border/70 bg-background/80 px-3 py-1.5 text-[0.72rem] font-semibold tracking-[0.18em] text-foreground/60"
>
{t("processEyebrow")}
</Badge>
<h2 className="mt-4 text-3xl font-semibold tracking-[-0.04em] text-foreground sm:text-4xl">
{t("processTitle")}
</h2>
<p className="mt-4 max-w-[28rem] text-sm leading-7 text-muted-foreground">
{t("processIntro")}
</p>
<div className="mt-6 grid gap-3 sm:grid-cols-3 lg:grid-cols-1">
{statItems.map((item) => (
<AppCard key={item.label} layer="single" padding="sm" className="shadow-none">
<p className="text-3xl font-semibold tracking-[-0.05em] text-foreground">
{item.value}
</p>
<h3 className="mt-2 text-sm font-semibold uppercase tracking-[0.14em] text-foreground/58">
{item.label}
</h3>
<p className="mt-2 text-sm leading-6 text-muted-foreground">{item.text}</p>
</AppCard>
))}
</div>
</CardContent>
</AppCard>
<div className="grid gap-6 lg:col-span-8 lg:grid-cols-2">
{processItems.map((item, index) => {
const Icon = item.icon;
const isWide = index === 0;
return (
<AppCard key={item.title} level={2}>
<CardContent className="p-5">
<Icon className="h-5 w-5 text-brand-primary" />
<h3 className="mt-3 text-lg font-semibold text-foreground">
{item.title}
</h3>
<p className="mt-2 text-sm text-muted-foreground">{item.text}</p>
<AppCard key={item.title} className={isWide ? "lg:col-span-2" : ""}>
<CardContent className="p-5 lg:p-6">
<div className="flex items-center justify-between gap-4">
<span className="inline-flex h-8 min-w-[2.5rem] items-center justify-center rounded-pill border border-border/70 px-3 text-xs font-semibold tracking-[0.18em] text-foreground/60">
0{index + 1}
</span>
<Icon className="h-5 w-5 text-brand-primary" />
</div>
<h3 className="mt-5 text-xl font-semibold text-foreground">{item.title}</h3>
<p className="mt-3 max-w-[34rem] text-sm leading-6 text-muted-foreground">
{item.text}
</p>
</CardContent>
</AppCard>
);
})}
</div>
</CardContent>
</AppCard>
</MotionFade>
<MotionFade delay={0.1}>
<AppCard>
<CardContent className="p-6 lg:p-8">
<h2 className="text-2xl font-semibold text-foreground">
{t("processTitle")}
</h2>
<ol className="mt-5 grid gap-3 sm:grid-cols-3">
{[t("processOne"), t("processTwo"), t("processThree")].map((step, index) => (
<AppCard key={step} level={2}>
<CardContent className="p-4">
<span className="mb-2 inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-xs font-semibold text-foreground/80">
{index + 1}
</span>
<p className="text-sm text-foreground/80">{step}</p>
</CardContent>
</AppCard>
))}
</ol>
</CardContent>
</AppCard>
</section>
</MotionFade>
</Container>
</>
+5 -3
View File
@@ -133,9 +133,11 @@ export default async function HomePage({ params: { locale } }: HomePageProps) {
const Icon = serviceIcons[index];
return (
<div
<AppCard
key={item}
className="rounded-nested border border-border/70 bg-background/80 p-4 shadow-xs transition-colors hover:bg-accent/40"
layer="single"
interactive
className="h-full"
>
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-[linear-gradient(135deg,hsl(var(--hero-left)/0.42),hsl(var(--hero-right)/0.26))]">
<Icon className="h-5 w-5 text-foreground/78" />
@@ -143,7 +145,7 @@ export default async function HomePage({ params: { locale } }: HomePageProps) {
<p className="mt-4 text-sm font-medium leading-6 text-foreground/88 sm:text-base">
{item}
</p>
</div>
</AppCard>
);
})}
</div>
+107 -23
View File
@@ -5,29 +5,78 @@ import { Card } from "@/components/ui/card";
import { cn } from "@/lib/utils";
const appCardVariants = cva(
"rounded-surface transition-colors",
"rounded-surface border p-2.5 transition-colors",
{
variants: {
level: {
1: "border-border/80 bg-surface-2 text-card-foreground shadow-card",
2: "border-border/80 bg-surface-2 text-foreground shadow-xs",
3: "border-border/80 bg-surface-2 text-foreground shadow-panel",
inverse: "border-transparent bg-foreground/92 text-background shadow-card",
},
interactive: {
true: "hover:bg-accent/30 hover:text-accent-foreground",
false: "",
},
},
defaultVariants: {
level: 1,
interactive: false,
},
});
const appCardInnerVariants = cva(
"h-full rounded-nested border p-4 transition-colors",
{
variants: {
level: {
1: "border-border/70 bg-background dark:bg-surface-1",
2: "border-border/70 bg-background dark:bg-surface-1",
3: "border-border/70 bg-background dark:bg-surface-1",
inverse: "border border-background/10 bg-background/10 text-inherit",
},
padding: {
none: "p-0",
sm: "p-4",
md: "p-6",
lg: "p-8",
},
interactive: {
true: "hover:bg-accent/40 hover:text-accent-foreground",
false: "",
},
},
defaultVariants: {
level: 1,
padding: "sm",
interactive: false,
},
});
const appCardSingleLayerVariants = cva(
"rounded-nested border transition-colors",
{
variants: {
level: {
1: "border border-border/80 bg-card text-card-foreground shadow-card",
2: "border border-input bg-background text-foreground shadow-xs",
3: "border border-border/80 bg-surface-2 text-foreground shadow-panel",
inverse: "border-transparent bg-foreground text-background shadow-card",
1: "border-border/70 bg-background dark:bg-surface-1",
2: "border-border/70 bg-background dark:bg-surface-1",
3: "border-border/70 bg-background dark:bg-surface-1",
inverse: "border border-background/10 bg-background/10 text-inherit",
},
padding: {
none: "",
none: "p-0",
sm: "p-4",
md: "p-6",
lg: "p-8",
},
interactive: {
true: "hover:bg-accent/30 hover:text-accent-foreground",
true: "hover:bg-accent/40 hover:text-accent-foreground",
false: "",
},
},
defaultVariants: {
level: 1,
padding: "none",
padding: "sm",
interactive: false,
},
},
@@ -35,23 +84,58 @@ const appCardVariants = cva(
export interface AppCardProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof appCardVariants> {}
VariantProps<typeof appCardVariants>,
VariantProps<typeof appCardInnerVariants> {
layer?: "double" | "single";
}
const AppCard = React.forwardRef<HTMLDivElement, AppCardProps>(
({ className, level, padding, interactive, ...props }, ref) => (
<Card
ref={ref}
className={cn(
appCardVariants({
level,
padding,
interactive,
}),
className,
)}
{...props}
/>
),
({ className, level, padding, interactive, layer = "double", children, ...props }, ref) => {
if (layer === "single") {
return (
<Card
ref={ref}
className={cn(
appCardSingleLayerVariants({
level,
padding,
interactive,
}),
className,
)}
{...props}
>
{children}
</Card>
);
}
return (
<Card
ref={ref}
className={cn(
appCardVariants({
level,
interactive,
}),
className,
)}
{...props}
>
<div
className={cn(
appCardInnerVariants({
level,
padding,
interactive,
}),
)}
>
{children}
</div>
</Card>
);
},
);
AppCard.displayName = "AppCard";
+17 -1
View File
@@ -78,6 +78,8 @@ export function UiKitShowcase({ localeKey }: UiKitShowcaseProps) {
nestedContent: "Verschachtelte Inhalte nutzen den zweiten Radius und eine weichere Oberflaeche.",
nestedSurface: "Verschachtelte Ebene",
nestedSurfaceText: "Mehr Tiefe ohne einen neuen Component Stil zu erfinden.",
singleLayerCard: "Single Layer Card",
singleLayerCardText: "Fuer kompakte Kennzahlen und kleine Infobloecke ohne zweite innere Schale.",
interactiveOuterCard: "Interaktive aeussere Card",
hoverText: "Das Hover Verhalten bleibt an dieselbe Oberflaechenebene gebunden.",
inverseOuterCard: "Inverse aeussere Card",
@@ -175,6 +177,8 @@ export function UiKitShowcase({ localeKey }: UiKitShowcaseProps) {
nestedContent: "Nested content uses the secondary radius and a softer surface.",
nestedSurface: "Nested surface",
nestedSurfaceText: "Deeper emphasis without inventing a new component style.",
singleLayerCard: "Single layer card",
singleLayerCardText: "For compact metrics and small info blocks without a second inner shell.",
interactiveOuterCard: "Interactive outer card",
hoverText: "Hover behavior stays attached to the same surface level.",
inverseOuterCard: "Inverse outer card",
@@ -298,7 +302,19 @@ export function UiKitShowcase({ localeKey }: UiKitShowcaseProps) {
</CardContent>
</AppCard>
<div className="grid gap-4">
<div className="grid gap-4 lg:grid-cols-2">
<AppCard level={1} layer="single">
<CardContent className="space-y-3 p-5">
<p className="text-sm font-medium text-muted-foreground">{copy.singleLayerCard}</p>
<p className="text-3xl font-semibold tracking-[-0.05em] text-foreground">06+</p>
<p className="text-sm font-semibold uppercase tracking-[0.14em] text-foreground/58">
{copy.surfaceLevel}
</p>
<p className="text-sm text-muted-foreground">
{copy.singleLayerCardText}
</p>
</CardContent>
</AppCard>
<AppCard level={1} interactive>
<CardContent className="p-5">
<p className="text-sm font-medium text-muted-foreground">{copy.interactiveOuterCard}</p>
+36 -5
View File
@@ -66,18 +66,49 @@
"title": "من أنا",
"heroBadge": "خلّيني عرّفك عليّي",
"heroTitle": "معلومات عني",
"intro": "أبني تجارب رقمية واضحة للعلامات والمنتجات والفرق.",
"intro": "أبني تجارب رقمية واضحة للعلامات والمنتجات والفرق، مع تركيز على الهوية والواجهة والبنية القابلة للتوسع.",
"storyEyebrow": "نبذة سريعة",
"storyTitle": "أحوّل الأفكار المبعثرة إلى حضور رقمي مرتب وواضح.",
"storyTextOne": "هذا النص مؤقت حالياً. الفكرة هنا إن الصفحة تعطي انطباع جاهز: مين أنا، كيف أشتغل، وكيف أقدر أبني تجربة متماسكة من أول الفكرة لآخر الإطلاق.",
"storyTextTwo": "حالياً وضعت محتوى وهمي قريب من النبرة النهائية، حتى يصير أسهل استبداله لاحقاً بمحتوى فعلي بدون الرجوع لتعديل التصميم من جديد.",
"highlightOne": "هوية أوضح ومسار استخدام أنظف.",
"highlightTwo": "واجهة منظمة ومحتوى أسهل بالتبديل.",
"highlightThree": "بنية مناسبة للتطوير المستقبلي.",
"primaryCta": "تواصل معي",
"secondaryCta": "شوف معرض الأعمال",
"logoEyebrow": "الهوية الحالية",
"logoText": "الشعار مأخوذ من إعدادات الموقع حتى تبقى الصفحة متطابقة مع الهوية الحالية.",
"galleryAltOne": "صورة وهمية رئيسية لقسم معلومات عني",
"galleryAltTwo": "صورة وهمية ثانوية لقسم معلومات عني",
"availabilityLabel": "الوضع الحالي",
"availabilityText": "متاح حالياً لاستقبال مشاريع جديدة، وهذه الجملة أيضاً placeholder قابل للتعديل.",
"statOneValue": "06+",
"statOneLabel": "سنوات تقريبية",
"statOneText": "رقم تجريبي ليوضح مكان الخبرة أو عدد السنوات بعد تعبئة المحتوى الحقيقي.",
"statTwoValue": "24",
"statTwoLabel": "مشاريع مختارة",
"statTwoText": "مكان مخصص لعدد المشاريع أو العملاء أو الإطلاقات حسب الصياغة النهائية.",
"statThreeValue": "03",
"statThreeLabel": "مسارات خدمة",
"statThreeText": "يمكن تحويل هذا الرقم لاحقاً إلى تخصصات أو منتجات أو مجالات عمل رئيسية.",
"valuesEyebrow": "طريقة الشغل",
"valuesTitle": "كيف أعمل",
"valuesIntro": "هذا القسم صار جاهز لعرض المبادئ الأساسية بشكل سريع وواضح بدل فقرة نصية طويلة.",
"valueA": "الاستراتيجية أولاً",
"valueAText": "كل مشروع يبدأ بهدف واضح ونطاق وأولويات محددة.",
"valueAText": "أبدأ بتحديد الهدف، الجمهور، وحدود النطاق حتى ما يتحول التنفيذ إلى تخمين.",
"valueB": "أنظمة نظيفة",
"valueBText": عتمد على مكونات قابلة للصيانة وبنية واضحة.",
"valueBText": بني مكونات قابلة للصيانة والتوسعة حتى يظل المشروع مرتب بعد الإطلاق.",
"valueC": "تعاون قريب",
"valueCText": "دورات قصيرة مع ملاحظات مباشرة طوال التنفيذ.",
"valueCText": "أفضل دورات قصيرة وملاحظات مباشرة حتى يظل القرار واضحاً والنتيجة أدق.",
"processEyebrow": "من البداية للنهاية",
"processTitle": "آلية العمل",
"processIntro": "قسم مرتب يشرح الرحلة بثلاث خطوات سريعة، وبإمكانك لاحقاً تعديله حسب طريقتك الفعلية.",
"processOne": "اكتشاف وتحديد الهدف",
"processOneText": "نجمع الصورة العامة، نحدد الأولويات، ونرتب المطلوب قبل أي قرار بصري أو تقني.",
"processTwo": "تصميم ونمذجة أولية",
"processThree": "بناء واختبار وإطلاق"
"processTwoText": "أصيغ الاتجاه العام، أبني تصور واجهات، وأجهز أساس بصري يسهّل بقية التنفيذ.",
"processThree": "بناء واختبار وإطلاق",
"processThreeText": "بعد تثبيت الاتجاه، يتحول العمل إلى تنفيذ منظم، مراجعة سريعة، ثم إطلاق جاهز."
},
"contactPage": {
"title": "تواصل",
+36 -5
View File
@@ -66,18 +66,49 @@
"title": "Über mich",
"heroBadge": "Mehr über mich",
"heroTitle": "Über mich",
"intro": "Ich baue klare digitale Erlebnisse für Marken, Produkte und Teams.",
"intro": "Ich baue klare digitale Erlebnisse für Marken, Produkte und Teams, mit Fokus auf Identität, Interface Qualität und skalierbare Struktur.",
"storyEyebrow": "Kurzprofil",
"storyTitle": "Ich verwandle verstreute Ideen in einen klaren und strukturierten digitalen Auftritt.",
"storyTextOne": "Das ist vorerst Platzhaltertext. Ziel ist eine Seite, die fast fertig wirkt: wer ich bin, wie ich arbeite und wie aus einer Idee ein stimmiger Launch wird.",
"storyTextTwo": "Der aktuelle Inhalt ist bewusst nah an einer finalen Tonalität, damit echter Text später ohne neues visuelles Rework eingesetzt werden kann.",
"highlightOne": "Klarere Identität und sauberer Nutzerfluss.",
"highlightTwo": "Geordnete Oberfläche und leicht austauschbarer Content.",
"highlightThree": "Struktur vorbereitet für spätere Weiterentwicklung.",
"primaryCta": "Kontakt aufnehmen",
"secondaryCta": "Portfolio ansehen",
"logoEyebrow": "Aktuelle Identität",
"logoText": "Das Logo wird aus den Site Settings geladen, damit die Seite mit der aktuellen Markenbasis übereinstimmt.",
"galleryAltOne": "Primäres Platzhalterbild für die Über mich Seite",
"galleryAltTwo": "Sekundäres Platzhalterbild für die Über mich Seite",
"availabilityLabel": "Aktueller Status",
"availabilityText": "Aktuell offen für neue Projekte, und auch diese Zeile ist als Platzhalter für später gedacht.",
"statOneValue": "06+",
"statOneLabel": "Jahre grob",
"statOneText": "Ein Platzhalter für Erfahrung oder Jahre in der Praxis, sobald die echten Inhalte feststehen.",
"statTwoValue": "24",
"statTwoLabel": "Ausgewählte Projekte",
"statTwoText": "Ein flexibler Platz für Projekte, Kunden, Launches oder einen später stärkeren Nachweis.",
"statThreeValue": "03",
"statThreeLabel": "Leistungsfelder",
"statThreeText": "Später kann das in Spezialisierungen, Produktlinien oder Kernbereiche umgewandelt werden.",
"valuesEyebrow": "Arbeitsweise",
"valuesTitle": "Wie ich arbeite",
"valuesIntro": "Dieser Bereich ist jetzt so aufgebaut, dass die Grundprinzipien schnell erfassbar sind statt in einem langen Absatz zu verschwinden.",
"valueA": "Strategie zuerst",
"valueAText": "Jedes Projekt startet mit Zielbild, Scope und Prioritäten.",
"valueAText": "Ich beginne mit Ziel, Zielgruppe und Scope, damit die Umsetzung nicht in Vermutungen abgleitet.",
"valueB": "Saubere Systeme",
"valueBText": "Ich setze auf wartbare Komponenten und klare Strukturen.",
"valueBText": "Ich baue wartbare Komponenten und ein klares System, das nach dem Launch weiter wachsen kann.",
"valueC": "Enge Zusammenarbeit",
"valueCText": "Kurze Schleifen mit direktem Feedback im gesamten Ablauf.",
"valueCText": "Ich arbeite lieber in kurzen Schleifen mit direktem Feedback, damit Entscheidungen klar bleiben.",
"processEyebrow": "Von Start bis Launch",
"processTitle": "Mein Ablauf",
"processIntro": "Ein kompakter Drei Schritte Ablauf, der sich später mit deinem echten Prozess ersetzen lässt ohne den Bereich neu zu gestalten.",
"processOne": "Discovery und Zieldefinition",
"processOneText": "Wir ordnen das Gesamtbild, priorisieren die Anforderungen und klären das Ziel vor der visuellen oder technischen Arbeit.",
"processTwo": "Design und Prototyping",
"processThree": "Build, Test und Launch"
"processTwoText": "Ich forme die Richtung, entwickle erste Interface Ideen und setze ein visuelles System für die Umsetzung auf.",
"processThree": "Build, Test und Launch",
"processThreeText": "Sobald die Richtung steht, geht die Arbeit in strukturierte Umsetzung, Review und Launch Vorbereitung über."
},
"contactPage": {
"title": "Kontakt",
+36 -5
View File
@@ -66,18 +66,49 @@
"title": "About",
"heroBadge": "A bit more about me",
"heroTitle": "About me",
"intro": "I build clear digital experiences for brands, products, and teams.",
"intro": "I build clear digital experiences for brands, products, and teams, with focus on identity, interface quality, and scalable structure.",
"storyEyebrow": "Quick overview",
"storyTitle": "I turn scattered ideas into a clear and structured digital presence.",
"storyTextOne": "This is placeholder copy for now. The goal is to make the page feel nearly production-ready: who I am, how I work, and how I shape a cohesive experience from concept to launch.",
"storyTextTwo": "The current content is intentionally close to the final tone, so replacing it later with real copy should not require another visual rewrite.",
"highlightOne": "Sharper identity and cleaner user flow.",
"highlightTwo": "Organized interface and easy-to-update content.",
"highlightThree": "Structure prepared for future iteration.",
"primaryCta": "Contact me",
"secondaryCta": "View portfolio",
"logoEyebrow": "Current identity",
"logoText": "The logo is pulled from site settings so the page stays aligned with the current brand setup.",
"galleryAltOne": "Primary placeholder visual for the about page",
"galleryAltTwo": "Secondary placeholder visual for the about page",
"availabilityLabel": "Current status",
"availabilityText": "Available for new projects at the moment, and this line is also a placeholder ready for replacement.",
"statOneValue": "06+",
"statOneLabel": "Approx. years",
"statOneText": "A placeholder metric for experience or years in practice once the real content is ready.",
"statTwoValue": "24",
"statTwoLabel": "Selected projects",
"statTwoText": "A flexible slot for projects, clients, launches, or any stronger proof point later on.",
"statThreeValue": "03",
"statThreeLabel": "Service tracks",
"statThreeText": "This can later become specialties, product lines, or core work streams.",
"valuesEyebrow": "Work style",
"valuesTitle": "How I work",
"valuesIntro": "This section is now structured to present core principles quickly instead of relying on one long paragraph.",
"valueA": "Strategy first",
"valueAText": "Each project starts with goals, scope, and priorities.",
"valueAText": "I start by clarifying goals, audience, and scope so execution does not drift into guesswork.",
"valueB": "Clean systems",
"valueBText": "I rely on maintainable components and clear structure.",
"valueBText": "I build maintainable components and a clear system that can evolve after launch.",
"valueC": "Close collaboration",
"valueCText": "Short loops with direct feedback across the full process.",
"valueCText": "I prefer short loops and direct feedback so decisions stay sharp and momentum stays high.",
"processEyebrow": "From start to launch",
"processTitle": "My process",
"processIntro": "A compact three-step flow that can later be replaced with your exact process without redesigning the section.",
"processOne": "Discovery and goal definition",
"processOneText": "We frame the full picture, align priorities, and define the target before visual or technical work starts.",
"processTwo": "Design and prototyping",
"processThree": "Build, test, and launch"
"processTwoText": "I shape the direction, build interface concepts, and establish a visual system that supports implementation.",
"processThree": "Build, test, and launch",
"processThreeText": "Once the direction is locked, the work moves into structured implementation, review, and launch readiness."
},
"contactPage": {
"title": "Contact",