Compare commits
22 Commits
5663c3ae38
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d9e0845266 | |||
| ede34ab7ce | |||
| 3fac158185 | |||
| 001c8514b5 | |||
| 00a8e3248f | |||
| 9951a357ac | |||
| 3395e6ebf8 | |||
| 55c25ca203 | |||
| 53808c416a | |||
| 9dad18daa5 | |||
| d4b8c1ce4a | |||
| 04c3d73964 | |||
| 442983fe06 | |||
| cb5be5426d | |||
| 85a0e2e051 | |||
| f1d5d9e859 | |||
| 0fb5e0cb66 | |||
| d08eaa45fc | |||
| 3f50ab034e | |||
| 55e67e38e2 | |||
| c35d411874 | |||
| 07c8d7c668 |
@@ -158,22 +158,25 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "Disk space before nuclear prune:"
|
echo "Disk space before nuclear prune:"
|
||||||
df -h
|
df -h
|
||||||
|
|
||||||
# Massive cleanup of pre-installed runner software to free up ~5GB+
|
# Massive cleanup of pre-installed runner software to free up ~5GB+
|
||||||
# These are standard on VM-based runners and often unnecessary
|
# These are standard on VM-based runners and often unnecessary
|
||||||
sudo rm -rf /usr/share/dotnet || true
|
sudo rm -rf /usr/share/dotnet || true
|
||||||
sudo rm -rf /usr/local/lib/android || true
|
sudo rm -rf /usr/local/lib/android || true
|
||||||
sudo rm -rf /opt/ghc || true
|
sudo rm -rf /opt/ghc || true
|
||||||
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
|
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
|
||||||
|
|
||||||
# Comprehensive Docker purge
|
# Comprehensive Docker purge
|
||||||
|
docker buildx stop || true
|
||||||
|
docker buildx rm -a || true
|
||||||
|
docker rm -fv $(docker ps -aq --filter "ancestor=moby/buildkit") || true
|
||||||
docker system prune -af --volumes
|
docker system prune -af --volumes
|
||||||
docker builder prune -af
|
docker builder prune -af
|
||||||
docker buildx prune -af
|
docker buildx prune -af
|
||||||
|
|
||||||
# Clean temp build artifacts
|
# Clean temp build artifacts
|
||||||
rm -rf /tmp/docker-actions-toolkit-* || true
|
rm -rf /tmp/docker-actions-toolkit-* || true
|
||||||
|
|
||||||
echo "Disk space after nuclear prune:"
|
echo "Disk space after nuclear prune:"
|
||||||
df -h
|
df -h
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -241,8 +244,6 @@ jobs:
|
|||||||
DIRECTUS_URL: ${{ needs.prepare.outputs.directus_url }}
|
DIRECTUS_URL: ${{ needs.prepare.outputs.directus_url }}
|
||||||
DIRECTUS_HOST: cms.${{ needs.prepare.outputs.traefik_host }}
|
DIRECTUS_HOST: cms.${{ needs.prepare.outputs.traefik_host }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Mail
|
# Mail
|
||||||
MAIL_HOST: ${{ secrets.SMTP_HOST || vars.SMTP_HOST }}
|
MAIL_HOST: ${{ secrets.SMTP_HOST || vars.SMTP_HOST }}
|
||||||
MAIL_PORT: ${{ secrets.SMTP_PORT || vars.SMTP_PORT || '587' }}
|
MAIL_PORT: ${{ secrets.SMTP_PORT || vars.SMTP_PORT || '587' }}
|
||||||
@@ -427,7 +428,7 @@ jobs:
|
|||||||
|
|
||||||
echo "Purging S3 cache for ${{ env.S3_PREFIX }} ..."
|
echo "Purging S3 cache for ${{ env.S3_PREFIX }} ..."
|
||||||
rclone --config rclone.conf delete mintel-s3:${{ env.S3_BUCKET }}/${{ env.S3_PREFIX }}/cache/ --include "*" || true
|
rclone --config rclone.conf delete mintel-s3:${{ env.S3_BUCKET }}/${{ env.S3_PREFIX }}/cache/ --include "*" || true
|
||||||
|
|
||||||
rm rclone.conf rclone-current-linux-amd64.deb
|
rm rclone.conf rclone-current-linux-amd64.deb
|
||||||
|
|
||||||
- name: 🧹 Post-Deploy Cleanup (Runner)
|
- name: 🧹 Post-Deploy Cleanup (Runner)
|
||||||
@@ -453,7 +454,7 @@ jobs:
|
|||||||
COUNT=0
|
COUNT=0
|
||||||
MAX=24
|
MAX=24
|
||||||
URL="${{ needs.prepare.outputs.next_public_url }}"
|
URL="${{ needs.prepare.outputs.next_public_url }}"
|
||||||
|
|
||||||
echo "Verifying $URL is responsive..."
|
echo "Verifying $URL is responsive..."
|
||||||
while [ $COUNT -lt $MAX ]; do
|
while [ $COUNT -lt $MAX ]; do
|
||||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL/" || echo "000")
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL/" || echo "000")
|
||||||
@@ -465,7 +466,7 @@ jobs:
|
|||||||
sleep 5
|
sleep 5
|
||||||
COUNT=$((COUNT + 1))
|
COUNT=$((COUNT + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ "$STATUS" != "200" ]; then
|
if [ "$STATUS" != "200" ]; then
|
||||||
echo "❌ Site failed smoke test after 2 minutes! (Status: $STATUS)"
|
echo "❌ Site failed smoke test after 2 minutes! (Status: $STATUS)"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -475,7 +476,7 @@ jobs:
|
|||||||
shell: sh
|
shell: sh
|
||||||
run: |
|
run: |
|
||||||
BASE_URL="${{ needs.prepare.outputs.next_public_url }}"
|
BASE_URL="${{ needs.prepare.outputs.next_public_url }}"
|
||||||
|
|
||||||
echo "Verifying case study page..."
|
echo "Verifying case study page..."
|
||||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/case-studies/klz-cables")
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/case-studies/klz-cables")
|
||||||
if [ "$STATUS" != "200" ]; then
|
if [ "$STATUS" != "200" ]; then
|
||||||
@@ -483,7 +484,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "✅ Case study page UP"
|
echo "✅ Case study page UP"
|
||||||
|
|
||||||
echo "Verifying breeze CSS (root path)..."
|
echo "Verifying breeze CSS (root path)..."
|
||||||
CSS_PATH="/assets/klz-cables.com/wp-content/cache/breeze-minification/css/breeze_klz-cables-com-1-10895.css"
|
CSS_PATH="/assets/klz-cables.com/wp-content/cache/breeze-minification/css/breeze_klz-cables-com-1-10895.css"
|
||||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL$CSS_PATH")
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL$CSS_PATH")
|
||||||
@@ -492,7 +493,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "✅ Root asset path OK"
|
echo "✅ Root asset path OK"
|
||||||
|
|
||||||
echo "Verifying breeze CSS (relative path from case-study)..."
|
echo "Verifying breeze CSS (relative path from case-study)..."
|
||||||
REL_CSS_PATH="/case-studies/assets/klz-cables.com/wp-content/cache/breeze-minification/css/breeze_klz-cables-com-1-10895.css"
|
REL_CSS_PATH="/case-studies/assets/klz-cables.com/wp-content/cache/breeze-minification/css/breeze_klz-cables-com-1-10895.css"
|
||||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL$REL_CSS_PATH")
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL$REL_CSS_PATH")
|
||||||
|
|||||||
@@ -1,217 +1,150 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
|
||||||
import { Section } from "@/src/components/Section";
|
|
||||||
import { Reveal } from "@/src/components/Reveal";
|
import { Reveal } from "@/src/components/Reveal";
|
||||||
import {
|
import { Section } from "@/src/components/Section";
|
||||||
ExperienceIllustration,
|
|
||||||
ResponsibilityIllustration,
|
|
||||||
ResultIllustration,
|
|
||||||
ContactIllustration,
|
|
||||||
HeroLines,
|
|
||||||
ParticleNetwork,
|
|
||||||
GridLines,
|
|
||||||
} from "@/src/components/Landing";
|
|
||||||
import { Signature } from "@/src/components/Signature";
|
import { Signature } from "@/src/components/Signature";
|
||||||
import { Check } from "lucide-react";
|
|
||||||
import {
|
import {
|
||||||
H1,
|
H1,
|
||||||
H3,
|
H3,
|
||||||
H4,
|
|
||||||
LeadText,
|
LeadText,
|
||||||
BodyText,
|
BodyText,
|
||||||
Label,
|
Label,
|
||||||
MonoLabel,
|
MonoLabel,
|
||||||
} from "@/src/components/Typography";
|
} from "@/src/components/Typography";
|
||||||
import { Card, Container } from "@/src/components/Layout";
|
|
||||||
import { Button } from "@/src/components/Button";
|
import { Button } from "@/src/components/Button";
|
||||||
import { IconList, IconListItem } from "@/src/components/IconList";
|
import { AbstractCircuit, GradientMesh } from "@/src/components/Effects";
|
||||||
import {
|
import { GlitchText } from "@/src/components/GlitchText";
|
||||||
GradientMesh,
|
|
||||||
CodeSnippet,
|
|
||||||
AbstractCircuit,
|
|
||||||
} from "@/src/components/Effects";
|
|
||||||
|
|
||||||
import { Marker } from "@/src/components/Marker";
|
|
||||||
|
|
||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col bg-white overflow-hidden relative">
|
<div className="flex flex-col bg-white overflow-hidden relative">
|
||||||
{/* Background decoration removed per user request */}
|
<AbstractCircuit />
|
||||||
|
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="relative pt-12 md:pt-32 pb-8 md:pb-24 overflow-hidden border-b border-slate-50">
|
<section className="relative pt-24 md:pt-40 pb-12 md:pb-24 overflow-hidden border-b border-slate-50">
|
||||||
<Container variant="narrow" className="relative z-10">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full relative z-10">
|
||||||
<div className="flex flex-col items-center text-center space-y-6 md:space-y-12">
|
<div className="flex flex-col items-center text-center space-y-12">
|
||||||
<Reveal width="fit-content">
|
<Reveal width="fit-content">
|
||||||
<div className="relative">
|
<div className="w-40 h-40 md:w-56 md:h-56 rounded-full overflow-hidden grayscale hover:grayscale-0 transition-all duration-1000">
|
||||||
{/* Structural rings around avatar */}
|
<img
|
||||||
{/* Structural rings removed per user request */}
|
src="/marc-mintel.png"
|
||||||
|
alt="Marc Mintel"
|
||||||
<div className="relative w-32 h-32 md:w-40 md:h-40 rounded-full overflow-hidden border border-slate-200 shadow-xl bg-white p-1 group">
|
className="object-cover w-full h-full"
|
||||||
<div className="w-full h-full rounded-full overflow-hidden relative aspect-square">
|
/>
|
||||||
<img
|
|
||||||
src="/marc-mintel.png"
|
|
||||||
alt="Marc Mintel"
|
|
||||||
className="object-cover grayscale transition-all duration-1000 ease-in-out scale-110 group-hover:scale-100 group-hover:grayscale-0 w-full h-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
<div className="space-y-3 md:space-y-6 max-w-3xl">
|
<div className="space-y-6 max-w-4xl">
|
||||||
<Reveal delay={0.1}>
|
<Reveal delay={0.1}>
|
||||||
<div className="flex items-center justify-center gap-2 md:gap-4 mb-1 md:mb-4">
|
<MonoLabel className="text-slate-400 tracking-[0.2em] text-xs">
|
||||||
<div className="h-px w-6 md:w-8 bg-slate-900"></div>
|
WEBENTWICKLER
|
||||||
<MonoLabel className="text-slate-900 text-[10px] md:text-sm">
|
</MonoLabel>
|
||||||
Digital Architect
|
|
||||||
</MonoLabel>
|
|
||||||
<div className="h-px w-6 md:w-8 bg-slate-900"></div>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
</Reveal>
|
||||||
<Reveal delay={0.2}>
|
<Reveal delay={0.2}>
|
||||||
<H1 className="text-4xl md:text-8xl leading-none tracking-tighter">
|
<H1 className="text-6xl md:text-8xl lg:text-9xl leading-none tracking-tighter">
|
||||||
Über <span className="text-slate-400">mich.</span>
|
Über <span className="text-slate-400">mich.</span>
|
||||||
</H1>
|
</H1>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
<Reveal delay={0.3}>
|
<Reveal delay={0.3}>
|
||||||
<p className="text-slate-400 font-medium max-w-xl mx-auto text-sm md:text-xl">
|
<p className="text-slate-500 max-w-2xl mx-auto text-xl md:text-3xl leading-snug">
|
||||||
15 Jahre Erfahrung. Ein Ziel: Websites, die ihre Versprechen
|
15 Jahre Erfahrung. Ein Ziel: Ihnen den Frust mit Websites zu
|
||||||
halten.
|
ersparen.
|
||||||
</p>
|
</p>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</div>
|
||||||
|
|
||||||
{/* Connector to first section */}
|
|
||||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-px h-12 md:h-16 bg-gradient-to-b from-transparent to-slate-200" />
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Section 01: Story */}
|
{/* Section 01: Story */}
|
||||||
<Section
|
<Section number="01" title="Erfahrung" borderTop>
|
||||||
number="01"
|
<div className="space-y-16 md:space-y-32">
|
||||||
title="Erfahrung"
|
|
||||||
borderTop
|
|
||||||
illustration={<ExperienceIllustration className="w-24 h-24" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-8 md:space-y-12">
|
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<H3 className="text-2xl md:text-5xl leading-tight max-w-3xl">
|
<H3 className="text-4xl md:text-7xl lg:text-8xl leading-none tracking-tighter max-w-4xl">
|
||||||
Vom Designer <br />
|
Aus der Praxis. <br />
|
||||||
<span className="text-slate-400">zum Architekten.</span>
|
<span className="text-slate-400">Für die Praxis.</span>
|
||||||
</H3>
|
</H3>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-12">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24">
|
||||||
<Reveal delay={0.1}>
|
<Reveal delay={0.1}>
|
||||||
<div className="space-y-6 md:space-y-8">
|
<LeadText className="text-2xl md:text-4xl text-slate-900 leading-snug">
|
||||||
<LeadText className="text-xl md:text-2xl text-slate-400">
|
Ich habe in Agenturen, Konzernen und Startups gearbeitet. Dabei
|
||||||
Agenturen, Konzerne, Startups – ich habe die Branche von allen
|
habe ich gesehen, woran Projekte scheitern: zu viele Leute, zu
|
||||||
Seiten kennengelernt. Was hängen geblieben ist:{" "}
|
viel Blabla.
|
||||||
<span className="text-slate-900">
|
</LeadText>
|
||||||
<Marker delay={0.2}>Ergebnisse</Marker> zählen. Nicht der
|
|
||||||
Weg dorthin.
|
|
||||||
</span>
|
|
||||||
</LeadText>
|
|
||||||
<IconList className="space-y-4">
|
|
||||||
{[
|
|
||||||
"Frontend, Backend, Infrastruktur – Fullstack",
|
|
||||||
"Komplexe Systeme auf das Wesentliche reduziert",
|
|
||||||
"Performance-Probleme systematisch gelöst",
|
|
||||||
].map((item, i) => (
|
|
||||||
<IconListItem key={i} bullet>
|
|
||||||
<BodyText className="text-lg">{item}</BodyText>
|
|
||||||
</IconListItem>
|
|
||||||
))}
|
|
||||||
</IconList>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
</Reveal>
|
||||||
<Reveal delay={0.2}>
|
<Reveal delay={0.2}>
|
||||||
<Card
|
<div className="space-y-12">
|
||||||
variant="gray"
|
<BodyText className="text-slate-500 text-lg md:text-xl">
|
||||||
hover={false}
|
Das mache ich anders. Keine Hierarchien, kein Fachchinesisch,
|
||||||
padding="normal"
|
sondern klare Kommunikation. Fokus auf Ergebnisse statt auf
|
||||||
className="group"
|
endlose Meetings.
|
||||||
>
|
</BodyText>
|
||||||
<H4 className="text-xl mb-6">
|
<div className="space-y-6 border-l border-slate-100 pl-8">
|
||||||
Heute: Direkte Zusammenarbeit ohne Reibungsverluste.
|
<div>
|
||||||
</H4>
|
<Label className="text-slate-900 text-lg">Direkt</Label>
|
||||||
<div className="flex flex-wrap gap-3">
|
<BodyText className="text-slate-500">
|
||||||
{["Effizient", "Pragmatisch", "Verlässlich"].map((tag, i) => (
|
Sie sprechen immer mit mir.
|
||||||
<span
|
</BodyText>
|
||||||
key={i}
|
</div>
|
||||||
className="px-4 py-2 bg-white border border-slate-200 rounded-full shadow-sm"
|
<div>
|
||||||
>
|
<Label className="text-slate-900 text-lg">
|
||||||
<Label className="text-slate-900">{tag}</Label>
|
Unkompliziert
|
||||||
</span>
|
</Label>
|
||||||
))}
|
<BodyText className="text-slate-500">
|
||||||
|
Wir beschränken uns auf das Wesentliche.
|
||||||
|
</BodyText>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* Section 02: Arbeitsweise – HOW I work */}
|
{/* Section 02: Ablauf */}
|
||||||
<Section
|
<Section number="02" title="Ablauf" borderTop>
|
||||||
number="02"
|
<div className="space-y-16 md:space-y-32">
|
||||||
title="Arbeitsweise"
|
|
||||||
variant="gray"
|
|
||||||
borderTop
|
|
||||||
illustration={<ResponsibilityIllustration className="w-24 h-24" />}
|
|
||||||
effects={<GradientMesh variant="subtle" className="opacity-60" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-12">
|
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<H3 className="text-2xl md:text-5xl leading-tight max-w-3xl">
|
<H3 className="text-4xl md:text-7xl lg:text-8xl leading-none tracking-tighter max-w-4xl">
|
||||||
So läuft ein Projekt <br />
|
So einfach läuft <br />
|
||||||
<span className="text-slate-400">bei mir ab.</span>
|
<span className="text-slate-400">ein Projekt ab.</span>
|
||||||
</H3>
|
</H3>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
{/* Timeline Steps */}
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 md:gap-12">
|
||||||
<div className="space-y-1 relative">
|
|
||||||
{/* Connecting line */}
|
|
||||||
<div className="absolute left-[15px] top-8 bottom-8 w-px bg-slate-200 hidden md:block" />
|
|
||||||
|
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
step: "01",
|
step: "01",
|
||||||
title: "Briefing",
|
title: "Kurzes Gespräch",
|
||||||
desc: "Sie beschreiben Ihr Vorhaben. Ich höre zu und stelle die richtigen Fragen.",
|
desc: "Kein Workshop, nur die Fakten.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step: "02",
|
step: "02",
|
||||||
title: "Angebot",
|
title: "Klares Angebot",
|
||||||
desc: "Ein Fixpreis-Angebot mit klarem Leistungsumfang. Keine Überraschungen.",
|
desc: "Fixpreis ohne Sternchen.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step: "03",
|
step: "03",
|
||||||
title: "Umsetzung",
|
title: "Umsetzung",
|
||||||
desc: "Schnelle Iterationen. Sie sehen regelmäßig den Fortschritt und geben Feedback.",
|
desc: "Ich baue, Sie entspannen.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step: "04",
|
step: "04",
|
||||||
title: "Launch",
|
title: "Go-Live & Support",
|
||||||
desc: "Go-Live mit automatisiertem Deployment. Dokumentiert und übergabereif.",
|
desc: "Ich übernehme die Wartung.",
|
||||||
},
|
},
|
||||||
].map((item, i) => (
|
].map((item, i) => (
|
||||||
<Reveal key={i} delay={0.1 + i * 0.1}>
|
<Reveal key={i} delay={0.1 * i}>
|
||||||
<div className="flex gap-4 md:gap-6 py-2 md:py-6 group">
|
<div className="space-y-6 pt-6 border-t border-slate-900">
|
||||||
<div className="relative z-10 shrink-0">
|
<Label className="text-slate-400 font-mono text-sm">
|
||||||
<div className="w-8 h-8 rounded-full bg-white border border-slate-200 flex items-center justify-center group-hover:border-slate-400 group-hover:shadow-md transition-all duration-500">
|
{item.step}
|
||||||
<span className="text-[9px] font-mono font-bold text-slate-400 group-hover:text-slate-900 transition-colors">
|
</Label>
|
||||||
{item.step}
|
<div className="space-y-2">
|
||||||
</span>
|
<Label className="text-slate-900 text-xl">
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1 md:space-y-2 pt-1">
|
|
||||||
<H4 className="text-base md:text-xl font-bold">
|
|
||||||
{item.title}
|
{item.title}
|
||||||
</H4>
|
</Label>
|
||||||
<BodyText className="text-slate-500 text-sm md:text-base">
|
<BodyText className="text-slate-500 text-lg">
|
||||||
{item.desc}
|
{item.desc}
|
||||||
</BodyText>
|
</BodyText>
|
||||||
</div>
|
</div>
|
||||||
@@ -222,68 +155,36 @@ export default function AboutPage() {
|
|||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* Section 03: Garantie – The Pledge */}
|
{/* Section 03: Garantie */}
|
||||||
<Section number="03" title="Garantie" borderTop>
|
<Section number="03" title="Garantie" borderTop>
|
||||||
<div className="relative">
|
<div className="space-y-16 md:space-y-32">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="max-w-4xl text-left space-y-12 md:space-y-16 py-8 md:py-16">
|
<H3 className="text-4xl md:text-7xl lg:text-8xl leading-none tracking-tighter max-w-4xl">
|
||||||
<H3 className="text-3xl md:text-6xl leading-tight">
|
Ich stehe für <br />
|
||||||
Ich stehe für <br />
|
<span className="text-slate-400">meine Arbeit gerade.</span>
|
||||||
<span className="text-slate-400">meine Arbeit gerade.</span>
|
</H3>
|
||||||
</H3>
|
|
||||||
|
|
||||||
<div className="prose prose-lg md:prose-2xl text-slate-500 leading-relaxed">
|
|
||||||
<p>
|
|
||||||
Keine Hierarchien. Keine Ausreden. Wenn etwas nicht passt,
|
|
||||||
liegt die Verantwortung bei mir.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Ich liefere nicht nur Code, sondern{" "}
|
|
||||||
<span className="text-slate-900 font-medium relative inline-block">
|
|
||||||
Ergebnisse
|
|
||||||
<svg
|
|
||||||
className="absolute -bottom-2 left-0 w-full h-3 text-blue-500/30"
|
|
||||||
viewBox="0 0 100 10"
|
|
||||||
preserveAspectRatio="none"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M0 5 Q 50 10 100 5"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
, auf die Sie bauen können.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-2xl text-left">
|
|
||||||
<div className="p-6 bg-slate-50 rounded-2xl border border-slate-100">
|
|
||||||
<h4 className="font-bold text-slate-900 mb-2">
|
|
||||||
Fixpreis-Garantie
|
|
||||||
</h4>
|
|
||||||
<p className="text-slate-500 text-sm">
|
|
||||||
Keine versteckten Kosten. Der vereinbarte Preis ist final.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="p-6 bg-slate-50 rounded-2xl border border-slate-100">
|
|
||||||
<h4 className="font-bold text-slate-900 mb-2">
|
|
||||||
Satisfaction Guarantee
|
|
||||||
</h4>
|
|
||||||
<p className="text-slate-500 text-sm">
|
|
||||||
Wir gehen erst live, wenn Sie zu 100% zufrieden sind.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-8 md:pt-12 flex flex-col items-start">
|
|
||||||
<div className="w-64 md:w-80">
|
|
||||||
<Signature delay={0.5} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24 items-end">
|
||||||
|
<Reveal delay={0.1}>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<LeadText className="text-2xl md:text-4xl text-slate-900 leading-snug">
|
||||||
|
Wenn etwas nicht passt, liegt die Verantwortung allein bei
|
||||||
|
mir.
|
||||||
|
</LeadText>
|
||||||
|
<BodyText className="text-slate-500 text-lg md:text-xl">
|
||||||
|
Sie beauftragen mich, damit Sie Ruhe haben. Fixpreis bedeutet
|
||||||
|
Fixpreis. Wir gehen erst live, wenn Sie zu 100% zufrieden
|
||||||
|
sind.
|
||||||
|
</BodyText>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
<Reveal delay={0.2}>
|
||||||
|
<div className="pb-8">
|
||||||
|
<Signature />
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
@@ -291,41 +192,37 @@ export default function AboutPage() {
|
|||||||
<Section
|
<Section
|
||||||
number="04"
|
number="04"
|
||||||
title="Kontakt"
|
title="Kontakt"
|
||||||
variant="gray"
|
|
||||||
borderTop
|
borderTop
|
||||||
illustration={<ContactIllustration className="w-24 h-24" />}
|
|
||||||
effects={<GradientMesh variant="metallic" className="opacity-60" />}
|
effects={<GradientMesh variant="metallic" className="opacity-60" />}
|
||||||
>
|
>
|
||||||
<div className="space-y-10 md:space-y-12">
|
<div className="py-12 md:py-24 relative z-10">
|
||||||
<Reveal>
|
<div className="space-y-16 md:space-y-24">
|
||||||
<H3 className="text-2xl md:text-5xl leading-tight max-w-3xl">
|
<Reveal>
|
||||||
Bereit für eine <br />
|
<H3 className="text-5xl md:text-8xl lg:text-9xl leading-none tracking-tighter">
|
||||||
<span className="text-slate-400">Zusammenarbeit?</span>
|
Bereit, das Thema <br />
|
||||||
</H3>
|
<span className="text-slate-400">
|
||||||
</Reveal>
|
<GlitchText delay={0.5} duration={1}>
|
||||||
|
abzuhaken?
|
||||||
<Card
|
</GlitchText>
|
||||||
variant="glass"
|
|
||||||
hover={false}
|
|
||||||
padding="normal"
|
|
||||||
techBorder
|
|
||||||
className="rounded-3xl shadow-xl relative overflow-hidden group"
|
|
||||||
>
|
|
||||||
<div className="relative z-10 space-y-6 md:space-y-8">
|
|
||||||
<LeadText className="text-lg md:text-4xl leading-tight max-w-2xl text-slate-400">
|
|
||||||
Lassen Sie uns gemeinsam etwas bauen, das{" "}
|
|
||||||
<span className="text-slate-900">
|
|
||||||
wirklich <Marker delay={0.3}>funktioniert.</Marker>
|
|
||||||
</span>
|
</span>
|
||||||
</LeadText>
|
</H3>
|
||||||
|
</Reveal>
|
||||||
|
|
||||||
<div className="pt-2 md:pt-4">
|
<Reveal delay={0.2}>
|
||||||
<Button href="/contact" className="w-full md:w-auto">
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-12">
|
||||||
Projekt anfragen
|
<LeadText className="text-xl md:text-3xl text-slate-500 max-w-xl">
|
||||||
|
Schreiben Sie mir einfach – wir finden die beste Lösung für
|
||||||
|
Ihre Website.
|
||||||
|
</LeadText>
|
||||||
|
<Button
|
||||||
|
href="/contact"
|
||||||
|
className="w-full md:w-auto px-12 py-6 text-xl"
|
||||||
|
>
|
||||||
|
Sagen Sie Hallo
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Reveal>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,22 @@ export default function Error({
|
|||||||
// Log the error to Sentry/GlitchTip
|
// Log the error to Sentry/GlitchTip
|
||||||
Sentry.captureException(error);
|
Sentry.captureException(error);
|
||||||
console.error("Caught in error.tsx:", error);
|
console.error("Caught in error.tsx:", error);
|
||||||
|
|
||||||
|
// Auto-reload on ChunkLoadError or Server Action errors to fetch the new deployment assets
|
||||||
|
const isChunkLoadError =
|
||||||
|
error.message &&
|
||||||
|
(error.message.includes("ChunkLoadError") ||
|
||||||
|
error.message.includes("Loading chunk") ||
|
||||||
|
error.message.includes("Failed to find Server Action"));
|
||||||
|
|
||||||
|
if (isChunkLoadError) {
|
||||||
|
const lastReload = sessionStorage.getItem("last-chunk-reload");
|
||||||
|
const now = Date.now();
|
||||||
|
if (!lastReload || now - parseInt(lastReload) > 10000) {
|
||||||
|
sessionStorage.setItem("last-chunk-reload", now.toString());
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
import Error from "next/error";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Inter, Newsreader } from "next/font/google";
|
import { Inter, Newsreader } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
@@ -23,6 +22,22 @@ export default function GlobalError({
|
|||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Sentry.captureException(error);
|
Sentry.captureException(error);
|
||||||
|
|
||||||
|
// Auto-reload on ChunkLoadError or Server Action errors to fetch the new deployment assets
|
||||||
|
const isChunkLoadError =
|
||||||
|
error.message &&
|
||||||
|
(error.message.includes("ChunkLoadError") ||
|
||||||
|
error.message.includes("Loading chunk") ||
|
||||||
|
error.message.includes("Failed to find Server Action"));
|
||||||
|
|
||||||
|
if (isChunkLoadError) {
|
||||||
|
const lastReload = sessionStorage.getItem("last-chunk-reload");
|
||||||
|
const now = Date.now();
|
||||||
|
if (!lastReload || now - parseInt(lastReload) > 10000) {
|
||||||
|
sessionStorage.setItem("last-chunk-reload", now.toString());
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ const newsreader = Newsreader({
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
default: "Marc Mintel",
|
default: "Marc Mintel — Professionelle Websites für Unternehmen",
|
||||||
template: "%s | Marc Mintel",
|
template: "%s | Marc Mintel",
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
"Technical problem solver's blog - practical insights and learning notes",
|
"Professionelle Websites zum Fixpreis. Ein Entwickler, ein Ansprechpartner — von der Idee bis zum fertigen Ergebnis. Inklusive Sorglos-Paket: Änderungen, Wartung und Sicherheit komplett abgedeckt.",
|
||||||
metadataBase: new URL("https://mintel.me"),
|
metadataBase: new URL("https://mintel.me"),
|
||||||
icons: {
|
icons: {
|
||||||
icon: "/favicon.svg",
|
icon: "/favicon.svg",
|
||||||
@@ -33,7 +33,7 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={`${inter.variable} ${newsreader.variable}`}>
|
<html lang="de" className={`${inter.variable} ${newsreader.variable}`}>
|
||||||
<body className="min-h-screen bg-white">
|
<body className="min-h-screen bg-white">
|
||||||
<Header />
|
<Header />
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
|
|||||||
@@ -1,320 +1,303 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
|
||||||
ComparisonRow,
|
|
||||||
ConceptCode,
|
|
||||||
ConceptCommunication,
|
|
||||||
ConceptPrice,
|
|
||||||
ConceptPrototyping,
|
|
||||||
ConceptWebsite,
|
|
||||||
} from "@/src/components/Landing";
|
|
||||||
import { Reveal } from "@/src/components/Reveal";
|
import { Reveal } from "@/src/components/Reveal";
|
||||||
import { Section } from "@/src/components/Section";
|
import { Section } from "@/src/components/Section";
|
||||||
import {
|
import { H1, H3, LeadText, BodyText, Label } from "@/src/components/Typography";
|
||||||
H1,
|
|
||||||
H3,
|
|
||||||
LeadText,
|
|
||||||
BodyText,
|
|
||||||
MonoLabel,
|
|
||||||
Label,
|
|
||||||
} from "@/src/components/Typography";
|
|
||||||
import { Card, Container } from "@/src/components/Layout";
|
|
||||||
import { Button } from "@/src/components/Button";
|
import { Button } from "@/src/components/Button";
|
||||||
import { GradientMesh, CodeSnippet } from "@/src/components/Effects";
|
|
||||||
import { IconList, IconListItem } from "@/src/components/IconList";
|
|
||||||
import { HeroSection } from "@/src/components/HeroSection";
|
import { HeroSection } from "@/src/components/HeroSection";
|
||||||
import { GlitchText } from "@/src/components/GlitchText";
|
|
||||||
import { Marker } from "@/src/components/Marker";
|
|
||||||
import { PenCircle } from "@/src/components/PenCircle";
|
|
||||||
import { Availability } from "@/src/components/Availability";
|
import { Availability } from "@/src/components/Availability";
|
||||||
|
import { AbstractCircuit, GradientMesh } from "@/src/components/Effects";
|
||||||
|
import { Marker } from "@/src/components/Marker";
|
||||||
|
import { GlitchText } from "@/src/components/GlitchText";
|
||||||
|
|
||||||
export default function LandingPage() {
|
export default function LandingPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col bg-white overflow-hidden relative">
|
<div className="flex flex-col bg-white overflow-hidden relative">
|
||||||
{/* Dark Hero */}
|
<AbstractCircuit />
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
|
|
||||||
{/* Rest of page on white */}
|
{/* Section 02: Ihr Vorteil */}
|
||||||
|
<Section number="02" title="Ihr Vorteil" borderTop>
|
||||||
{/* Section 02: The Promise – Streamlined */}
|
<div className="space-y-16 md:space-y-32 relative z-10">
|
||||||
<Section
|
|
||||||
number="02"
|
|
||||||
title="Das Versprechen"
|
|
||||||
borderTop
|
|
||||||
effects={<GradientMesh variant="metallic" className="opacity-70" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-10 md:space-y-16 relative">
|
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<H3 className="max-w-3xl">
|
<H3 className="text-3xl md:text-6xl lg:text-8xl leading-none tracking-tighter">
|
||||||
Kein Agentur-Zirkus. <br />
|
Sie kümmern sich um Ihr Geschäft.{" "}
|
||||||
<Marker delay={0.3}>Ergebnisse.</Marker>
|
<span className="text-slate-400">
|
||||||
|
Ich mich um Ihre <Marker delay={0.3}>Website.</Marker>
|
||||||
|
</span>
|
||||||
</H3>
|
</H3>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 relative z-10">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24">
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
icon: <ConceptCommunication className="w-8 h-8" />,
|
title: "Ein Ansprechpartner",
|
||||||
title: "Direkte Kommunikation",
|
text: "Direkte Kommunikation. Keine Agentur, keine Projektmanager, kein Stille-Post-Effekt.",
|
||||||
text: "Sie sprechen mit dem Entwickler. Keine Stille Post, keine Umwege.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <ConceptPrototyping className="w-8 h-8" />,
|
title: "Fixpreis",
|
||||||
title: "Schnelle Umsetzung",
|
text: "Keine versteckten Kosten, keine Stundenzettel. Sie wissen exakt, was es kostet.",
|
||||||
text: "Sichtbare Fortschritte in Tagen. Prototypen statt Konzeptpapiere.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <ConceptCode className="w-8 h-8" />,
|
title: "Schnelle Ergebnisse",
|
||||||
title: "Sauberer Code",
|
text: "Kein monatelanges Warten. Erste Entwürfe innerhalb weniger Tage.",
|
||||||
text: "Maßgeschneiderte Architektur. Kein Baukasten, kein Plugin-Chaos.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <ConceptPrice className="w-8 h-8" />,
|
title: "Null Arbeit für Sie",
|
||||||
title: "Klare Fixpreise",
|
text: "Keine CMS-Einarbeitung. Änderungen oder Updates? Eine kurze E-Mail genügt.",
|
||||||
text: "Volle Budgetsicherheit. Keine versteckten Kosten.",
|
|
||||||
},
|
},
|
||||||
].map((item, i) => (
|
].map((item, i) => (
|
||||||
<Reveal key={i} delay={0.1 + i * 0.1}>
|
<Reveal key={i} delay={0.1 * i}>
|
||||||
<Card
|
<div className="space-y-4">
|
||||||
variant="glass"
|
<div className="h-px w-full bg-slate-100 mb-6" />
|
||||||
padding="normal"
|
<Label className="text-slate-900 text-lg md:text-xl font-medium tracking-tight">
|
||||||
techBorder
|
{item.title}
|
||||||
className="group"
|
</Label>
|
||||||
>
|
<BodyText className="text-slate-500 text-base md:text-lg">
|
||||||
<div className="space-y-4 relative z-10">
|
{item.text}
|
||||||
<div className="w-12 h-12 rounded-xl bg-slate-50 border border-slate-100 flex items-center justify-center group-hover:scale-110 transition-transform duration-500">
|
</BodyText>
|
||||||
{item.icon}
|
</div>
|
||||||
</div>
|
|
||||||
<Label className="text-slate-900">{item.title}</Label>
|
|
||||||
<BodyText className="text-slate-500">{item.text}</BodyText>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</Reveal>
|
</Reveal>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* Section 03: The Difference – Visual Comparison */}
|
{/* Section 03: Der Unterschied */}
|
||||||
<Section number="03" title="Der Unterschied" variant="white" borderTop>
|
<Section number="03" title="Der Unterschied" borderTop>
|
||||||
<div className="space-y-10 md:space-y-16 relative">
|
<div className="space-y-16 md:space-y-32">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<H3 className="max-w-3xl">
|
<H3 className="text-3xl md:text-6xl lg:text-8xl leading-none tracking-tighter">
|
||||||
Ich arbeite für das Ergebnis, <br />
|
Warum nicht einfach{" "}
|
||||||
nicht gegen die <Marker delay={0.4}>Uhr.</Marker>
|
<span className="text-slate-400">eine Agentur?</span>
|
||||||
</H3>
|
</H3>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-8 relative z-20">
|
<div className="space-y-12 md:space-y-0">
|
||||||
<ComparisonRow
|
{[
|
||||||
negativeLabel="Klassisch"
|
{
|
||||||
negativeText="Wochen in Planung, bevor eine einzige Zeile Code geschrieben wird."
|
label: "Agentur",
|
||||||
positiveLabel="Mein Weg"
|
text: "Projektmanager, Designer, Entwickler — Sie erklären alles drei Mal. Stundenabrechnungen, unklare Endkosten.",
|
||||||
positiveText={
|
},
|
||||||
<>
|
{
|
||||||
Schnelle Prototypen. Ergebnisse in{" "}
|
label: "Baukasten (Wix, etc.)",
|
||||||
<PenCircle delay={0.5}>Tagen</PenCircle>, nicht Monaten.
|
text: "Sie bauen selbst, kämpfen mit Templates und sind auf sich allein gestellt, wenn etwas nicht funktioniert.",
|
||||||
</>
|
},
|
||||||
}
|
{
|
||||||
delay={0.1}
|
label: "Bei mir",
|
||||||
/>
|
text: "Ein Ansprechpartner. Fixpreis. Ich baue, Sie lehnen sich zurück. Sie brauchen eine Änderung? Kurze Nachricht genügt.",
|
||||||
<ComparisonRow
|
highlight: true,
|
||||||
negativeLabel="Klassisch"
|
},
|
||||||
negativeText="Unvorhersehbare Kosten durch Stundenabrechnungen."
|
].map((item, i) => (
|
||||||
positiveLabel="Mein Weg"
|
<Reveal key={i} delay={0.1 * i}>
|
||||||
positiveText={
|
<div
|
||||||
<>
|
className={`grid grid-cols-1 md:grid-cols-4 gap-4 md:gap-8 py-8 md:py-16 border-t ${item.highlight ? "border-slate-900" : "border-slate-100"}`}
|
||||||
<PenCircle delay={0.5}>Fixpreise.</PenCircle> Sie wissen von
|
>
|
||||||
Anfang an, was es kostet.
|
<div className="md:col-span-1">
|
||||||
</>
|
<Label
|
||||||
}
|
className={`text-lg md:text-xl ${item.highlight ? "text-slate-900 font-bold" : "text-slate-400"}`}
|
||||||
reverse
|
>
|
||||||
delay={0.2}
|
{item.label}
|
||||||
/>
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-3">
|
||||||
|
<LeadText
|
||||||
|
className={`text-xl md:text-3xl leading-snug ${item.highlight ? "text-slate-900" : "text-slate-500"}`}
|
||||||
|
>
|
||||||
|
{item.text}
|
||||||
|
</LeadText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* Section 04: Target Group */}
|
{/* Section 04: Zielgruppe */}
|
||||||
<Section number="04" title="Für wen" borderTop>
|
<Section number="04" title="Für wen" borderTop>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6 lg:gap-10 relative z-10">
|
<div className="space-y-16 md:space-y-32">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<Card variant="glass" padding="normal" techBorder className="group">
|
<H3 className="text-3xl md:text-6xl lg:text-8xl leading-none tracking-tighter">
|
||||||
<div className="space-y-4 md:space-y-6 relative overflow-hidden">
|
Fokus auf das <span className="text-slate-400">Wesentliche.</span>
|
||||||
<div className="w-12 h-12 md:w-16 md:h-16 bg-slate-50 border border-slate-100 rounded-xl flex items-center justify-center">
|
</H3>
|
||||||
<ConceptPrice className="w-6 h-6 md:w-8 md:h-8" />
|
</Reveal>
|
||||||
</div>
|
|
||||||
<H3 className="text-xl md:text-3xl">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24">
|
||||||
Unternehmer & <br />
|
<Reveal>
|
||||||
Geschäftsführer
|
<div className="space-y-8">
|
||||||
|
<H3 className="text-2xl md:text-4xl">
|
||||||
|
Unternehmer & Geschäftsführer
|
||||||
</H3>
|
</H3>
|
||||||
<LeadText className="text-slate-400 text-base md:text-lg">
|
<LeadText className="text-slate-500">
|
||||||
Sie wollen eine Website, die funktioniert – ohne sich mit
|
Sie brauchen eine professionelle Website, die Ihr Unternehmen
|
||||||
Technik beschäftigen zu müssen.
|
repräsentiert — ohne sich selbst mit Technik beschäftigen zu
|
||||||
|
müssen. Ich übernehme alles.
|
||||||
</LeadText>
|
</LeadText>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-6 md:pt-8 border-t border-slate-50 mt-6 md:mt-8">
|
</Reveal>
|
||||||
<Label className="group-hover:text-slate-900 transition-colors">
|
<Reveal delay={0.2}>
|
||||||
Perfekt für Sie
|
<div className="space-y-8">
|
||||||
</Label>
|
<H3 className="text-2xl md:text-4xl">
|
||||||
</div>
|
Selbstständige & kleine Teams
|
||||||
</Card>
|
|
||||||
</Reveal>
|
|
||||||
<Reveal delay={0.2}>
|
|
||||||
<Card variant="glass" padding="normal" techBorder className="group">
|
|
||||||
<div className="space-y-6 relative overflow-hidden">
|
|
||||||
<div className="w-12 h-12 md:w-16 md:h-16 bg-slate-50 border border-slate-100 rounded-xl flex items-center justify-center">
|
|
||||||
{/* Icon placeholder or same as above if needed */}
|
|
||||||
<ConceptWebsite className="w-6 h-6 md:w-8 md:h-8" />
|
|
||||||
</div>
|
|
||||||
<H3 className="text-xl md:text-3xl">
|
|
||||||
Marketing & <br />
|
|
||||||
Vertrieb
|
|
||||||
</H3>
|
</H3>
|
||||||
<LeadText className="text-slate-400 text-base md:text-lg">
|
<LeadText className="text-slate-500">
|
||||||
Sie brauchen Landingpages und Tools, die Ergebnisse liefern.
|
Sie wollen online sichtbar sein, aber keine Zeit in eine
|
||||||
Schnell und zuverlässig.
|
Website stecken. Ich liefere eine fertige Lösung, damit Sie
|
||||||
|
sich auf Ihre Kunden konzentrieren können.
|
||||||
</LeadText>
|
</LeadText>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-6 md:pt-8 border-t border-slate-50 mt-6 md:mt-8">
|
</Reveal>
|
||||||
<Label className="group-hover:text-slate-900 transition-colors">
|
</div>
|
||||||
Perfekt für Sie
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</Reveal>
|
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* Section 05: Leistungen — Interactive Service Rows */}
|
{/* Section 05: Leistungen */}
|
||||||
<Section number="05" title="Leistungen" variant="gray" borderTop>
|
<Section number="05" title="Leistungen" borderTop>
|
||||||
<div className="space-y-0 relative z-20">
|
<div className="space-y-0">
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
num: "01",
|
|
||||||
binary: "00000001",
|
|
||||||
title: "Websites",
|
title: "Websites",
|
||||||
text: "High-Performance Websites mit maßgeschneiderter Architektur. Von der Konzeption bis zum Go-Live — individuell, schnell, messbar.",
|
text: "Professionelle, schnelle Websites — individuell für Ihr Unternehmen gestaltet. Ohne Overhead.",
|
||||||
tags: ["Next.js", "React", "TypeScript", "Performance"],
|
|
||||||
href: "/websites",
|
href: "/websites",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
num: "02",
|
title: "Sorglos-Paket",
|
||||||
binary: "00000010",
|
text: "Änderungen, Wartung, Sicherheit — alles inklusive. Sie sagen Bescheid, ich erledige das.",
|
||||||
title: "Systeme",
|
|
||||||
text: "Web-Applikationen und interne Tools, wenn Standard-Software nicht reicht. Dashboards, Portale, Automatisierungen.",
|
|
||||||
tags: ["Full-Stack", "APIs", "Datenbanken", "Auth"],
|
|
||||||
href: "/contact",
|
href: "/contact",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
num: "03",
|
title: "Web-Anwendungen",
|
||||||
binary: "00000011",
|
text: "Kundenportale, interne Tools oder individuelle Lösungen für Ihr Business.",
|
||||||
title: "Automatisierung",
|
href: "/showcase",
|
||||||
text: "Verbindung von Tools, automatische Prozesse, Daten-Synchronisation. Weniger manuelle Arbeit, mehr Effizienz.",
|
|
||||||
tags: ["CI/CD", "Workflows", "Integrationen", "Monitoring"],
|
|
||||||
href: "/contact",
|
|
||||||
},
|
},
|
||||||
].map((service, i) => (
|
].map((service, i) => (
|
||||||
<Reveal key={i} delay={0.1 + i * 0.15}>
|
<Reveal key={i} delay={0.1 * i}>
|
||||||
<div className="group py-8 md:py-12 lg:py-16 border-b border-slate-100 last:border-b-0 cursor-pointer transition-all duration-500">
|
<a href={service.href} className="group block">
|
||||||
<div className="flex flex-col md:flex-row md:items-start gap-6 md:gap-10 lg:gap-16">
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-6 py-12 md:py-20 border-b border-slate-100 transition-colors hover:border-slate-300">
|
||||||
{/* Number + Binary */}
|
<div className="space-y-4 max-w-2xl">
|
||||||
<div className="shrink-0 flex md:block items-baseline gap-4">
|
<H3 className="text-3xl md:text-5xl group-hover:translate-x-2 transition-transform duration-500">
|
||||||
<span className="text-4xl md:text-6xl font-black text-slate-100 group-hover:text-slate-200 transition-colors duration-500 tracking-tighter block leading-none">
|
{service.title}
|
||||||
{service.num}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="text-[8px] font-mono text-slate-200 tracking-[0.3em] mt-2 block select-none group-hover:text-blue-300 transition-colors duration-700 leading-none"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
{service.binary}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="flex-1 space-y-4 md:space-y-6">
|
|
||||||
<H3 className="text-xl md:text-4xl group-hover:translate-x-2 transition-transform duration-500">
|
|
||||||
<GlitchText
|
|
||||||
trigger="inView"
|
|
||||||
delay={0.2 + i * 0.15}
|
|
||||||
duration={0.6}
|
|
||||||
>
|
|
||||||
{service.title}
|
|
||||||
</GlitchText>
|
|
||||||
</H3>
|
</H3>
|
||||||
<BodyText className="text-slate-400 text-sm md:text-base max-w-xl group-hover:text-slate-500 transition-colors duration-500">
|
<LeadText className="text-slate-400 group-hover:text-slate-600 transition-colors">
|
||||||
{service.text}
|
{service.text}
|
||||||
</BodyText>
|
</LeadText>
|
||||||
{/* Tags */}
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{service.tags.map((tag, j) => (
|
|
||||||
<span
|
|
||||||
key={j}
|
|
||||||
className="px-3 py-1 text-[8px] md:text-[9px] font-mono uppercase tracking-widest text-slate-400 border border-slate-100 rounded-full bg-white/50 group-hover:border-slate-200 group-hover:text-slate-500 transition-all duration-500"
|
|
||||||
>
|
|
||||||
{tag}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-slate-300 group-hover:text-slate-900 transition-colors pb-1">
|
||||||
{/* Arrow */}
|
<svg
|
||||||
<div className="md:self-center shrink-0 pt-4 md:pt-0">
|
width="24"
|
||||||
<Button
|
height="24"
|
||||||
href={service.href}
|
viewBox="0 0 24 24"
|
||||||
variant="ghost"
|
fill="none"
|
||||||
size="normal"
|
stroke="currentColor"
|
||||||
showArrow
|
strokeWidth="2"
|
||||||
className="w-full md:w-auto"
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
>
|
>
|
||||||
Details
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||||
</Button>
|
<polyline points="12 5 19 12 12 19"></polyline>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* Section 06: Contact */}
|
{/* Section 06: Blog / Insights */}
|
||||||
<Section number="06" title="Kontakt" borderTop>
|
<Section number="06" title="Einblicke" borderTop>
|
||||||
<div className="relative py-4 md:py-12" id="contact">
|
<div className="space-y-16 md:space-y-32">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="space-y-8 md:space-y-12 lg:space-y-16">
|
<H3 className="text-3xl md:text-6xl lg:text-8xl leading-none tracking-tighter">
|
||||||
<H1 className="text-4xl md:text-6xl lg:text-8xl">
|
Wissen statt <span className="text-slate-400">Werbung.</span>
|
||||||
Lassen Sie uns <br />
|
</H3>
|
||||||
<span className="text-slate-400">starten.</span>
|
</Reveal>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24">
|
||||||
|
<Reveal>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<Label className="text-slate-400">Aktueller Artikel</Label>
|
||||||
|
<H3 className="text-2xl md:text-4xl leading-tight">
|
||||||
|
Warum Websites nach Updates oft brechen – und wie man das
|
||||||
|
verhindert.
|
||||||
|
</H3>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
href="/blog/why-websites-break-after-updates"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
Artikel lesen
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
<Reveal delay={0.2}>
|
||||||
|
<div className="space-y-8 border-l border-slate-100 pl-8 md:pl-12 flex flex-col justify-end">
|
||||||
|
<LeadText className="text-slate-500">
|
||||||
|
Ich teile meine Gedanken zu digitaler Architektur, Fixpreisen
|
||||||
|
und warum weniger oft mehr ist.
|
||||||
|
</LeadText>
|
||||||
|
<a
|
||||||
|
href="/blog"
|
||||||
|
className="text-slate-900 font-bold hover:underline underline-offset-4"
|
||||||
|
>
|
||||||
|
Alle Artikel ansehen →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Section 07: Contact */}
|
||||||
|
<Section
|
||||||
|
number="07"
|
||||||
|
title="Kontakt"
|
||||||
|
borderTop
|
||||||
|
effects={<GradientMesh variant="metallic" className="opacity-60" />}
|
||||||
|
>
|
||||||
|
<div className="relative py-12 md:py-32" id="contact">
|
||||||
|
<Reveal>
|
||||||
|
<div className="space-y-12 md:space-y-24 relative z-10">
|
||||||
|
<H1 className="text-5xl md:text-8xl lg:text-9xl leading-none tracking-tighter">
|
||||||
|
Bereit für den Start? <br />
|
||||||
|
<span className="text-slate-400">
|
||||||
|
<GlitchText delay={0.5} duration={1}>
|
||||||
|
Sagen Sie Hallo.
|
||||||
|
</GlitchText>
|
||||||
|
</span>
|
||||||
</H1>
|
</H1>
|
||||||
|
|
||||||
<div className="flex flex-col md:flex-row gap-6 md:gap-10 lg:gap-16 items-start relative z-10">
|
<div className="flex flex-col md:flex-row gap-12 md:gap-24 items-start">
|
||||||
<div className="space-y-4 md:space-y-8 flex-1">
|
<div className="space-y-8 flex-1">
|
||||||
<LeadText className="text-lg md:text-3xl text-slate-400">
|
<LeadText className="text-2xl md:text-4xl text-slate-500 leading-snug">
|
||||||
Beschreiben Sie kurz Ihr Vorhaben. Ich melde mich{" "}
|
Schreiben Sie mir kurz, was Sie vorhaben. Ich melde mich
|
||||||
<span className="text-slate-900 border-b-2 border-slate-900/10">
|
innerhalb von 24 Stunden bei Ihnen.
|
||||||
<Marker>zeitnah</Marker>
|
|
||||||
</span>{" "}
|
|
||||||
bei Ihnen.
|
|
||||||
</LeadText>
|
</LeadText>
|
||||||
<div className="pt-2 md:pt-4">
|
<div className="pt-4">
|
||||||
<Button
|
<Button
|
||||||
href="/contact"
|
href="/contact"
|
||||||
size="large"
|
size="large"
|
||||||
className="w-full md:w-auto"
|
className="text-lg px-8 py-4"
|
||||||
>
|
>
|
||||||
Projekt anfragen
|
Jetzt Nachricht schreiben
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full md:w-72 space-y-4 md:space-y-6 p-6 glass rounded-2xl border border-slate-100">
|
<div className="w-full md:w-80 space-y-4 pt-4 md:pt-0">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
<div className="w-2 h-2 bg-slate-900 rounded-full"></div>
|
||||||
<Label className="text-slate-900">Verfügbarkeit</Label>
|
<Label className="text-slate-900 text-sm">
|
||||||
|
Verfügbarkeit
|
||||||
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<BodyText className="text-sm md:text-base leading-snug">
|
<BodyText className="text-slate-500">
|
||||||
Aktuell nehme ich Projekte für{" "}
|
Aktuell nehme ich Projekte für{" "}
|
||||||
<span className="font-bold text-slate-900"><Availability /></span>{" "}
|
<span className="font-bold text-slate-900">
|
||||||
|
<Availability />
|
||||||
|
</span>{" "}
|
||||||
an.
|
an.
|
||||||
</BodyText>
|
</BodyText>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,371 +3,241 @@
|
|||||||
import { Reveal } from "@/src/components/Reveal";
|
import { Reveal } from "@/src/components/Reveal";
|
||||||
import { Section } from "@/src/components/Section";
|
import { Section } from "@/src/components/Section";
|
||||||
import {
|
import {
|
||||||
SpeedPerformance,
|
H1,
|
||||||
SolidFoundation,
|
|
||||||
LayerSeparation,
|
|
||||||
TaskDone,
|
|
||||||
} from "@/src/components/Landing";
|
|
||||||
import {
|
|
||||||
H3,
|
H3,
|
||||||
LeadText,
|
LeadText,
|
||||||
BodyText,
|
BodyText,
|
||||||
Label,
|
Label,
|
||||||
MonoLabel,
|
MonoLabel,
|
||||||
} from "@/src/components/Typography";
|
} from "@/src/components/Typography";
|
||||||
import { Card } from "@/src/components/Layout";
|
|
||||||
import { Button } from "@/src/components/Button";
|
import { Button } from "@/src/components/Button";
|
||||||
import { IconList, IconListItem } from "@/src/components/IconList";
|
|
||||||
import {
|
|
||||||
GradientMesh,
|
|
||||||
CodeSnippet,
|
|
||||||
AbstractCircuit,
|
|
||||||
CMSVisualizer,
|
|
||||||
ArchitectureVisualizer,
|
|
||||||
ResultVisualizer,
|
|
||||||
} from "@/src/components/Effects";
|
|
||||||
import { Marker } from "@/src/components/Marker";
|
import { Marker } from "@/src/components/Marker";
|
||||||
|
import { AbstractCircuit, GradientMesh } from "@/src/components/Effects";
|
||||||
|
import { GlitchText } from "@/src/components/GlitchText";
|
||||||
|
|
||||||
export default function WebsitesPage() {
|
export default function WebsitesPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col bg-white overflow-hidden relative">
|
<div className="flex flex-col bg-white overflow-hidden relative">
|
||||||
<AbstractCircuit />
|
<AbstractCircuit />
|
||||||
|
|
||||||
<Section className="pt-24 pb-12 md:pt-40 md:pb-24">
|
<Section className="pt-24 pb-12 md:pt-40 md:pb-24">
|
||||||
<div className="space-y-12 md:space-y-24">
|
<div className="space-y-16 md:space-y-32">
|
||||||
<div className="space-y-6 md:space-y-10 max-w-5xl">
|
<div className="space-y-10 max-w-4xl">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="space-y-4">
|
<div className="space-y-6">
|
||||||
<MonoLabel className="text-blue-500 tracking-[0.2em] text-[10px] md:text-xs">
|
<MonoLabel className="text-slate-400 tracking-[0.2em] text-xs">
|
||||||
SYSTEM ENGINEERING
|
PROFESSIONELLE WEBSITES
|
||||||
</MonoLabel>
|
</MonoLabel>
|
||||||
<H3 className="text-4xl md:text-8xl leading-[1.0] tracking-tighter">
|
<H1 className="text-5xl md:text-8xl lg:text-9xl leading-[0.9] tracking-tighter">
|
||||||
Websites, die einfach <br />
|
Ihre Website. <br />
|
||||||
<span className="text-slate-400">
|
<span className="text-slate-400">Komplett sorgenfrei.</span>
|
||||||
<Marker>funktionieren.</Marker>
|
</H1>
|
||||||
</span>
|
|
||||||
</H3>
|
|
||||||
</div>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
<Reveal delay={0.2}>
|
<Reveal delay={0.2}>
|
||||||
<LeadText className="text-lg md:text-2xl max-w-2xl text-slate-500 md:text-slate-400 leading-relaxed">
|
<LeadText className="text-xl md:text-3xl text-slate-500 leading-snug">
|
||||||
Kein Baukasten. Kein Plugin-Chaos. Maßgeschneiderte Architektur
|
Kein WordPress-Chaos. Kein Baukasten-Frust. Ich baue Ihnen eine
|
||||||
für{" "}
|
maßgeschneiderte Website, mit der Sie{" "}
|
||||||
<span className="text-slate-900 font-bold underline decoration-slate-200 underline-offset-8">
|
<span className="text-slate-900 font-bold underline decoration-slate-200 underline-offset-8">
|
||||||
maximale Performance
|
null Arbeit
|
||||||
</span>
|
</span>{" "}
|
||||||
.
|
haben.
|
||||||
</LeadText>
|
</LeadText>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-12">
|
<Reveal delay={0.3}>
|
||||||
<Reveal delay={0.3} direction="up">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 md:gap-12 py-12 md:py-20 border-y border-slate-100">
|
||||||
<ArchitectureVisualizer />
|
|
||||||
</Reveal>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
label: "Next.js",
|
label: "Kein CMS-Zwang",
|
||||||
sub: "Architecture",
|
desc: "Sie müssen kein System lernen. Ich übernehme alles.",
|
||||||
desc: "React-Framework für maximale SEO & Speed.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Docker",
|
label: "Fixpreis",
|
||||||
sub: "Infrastructure",
|
desc: "Klare Kosten von Anfang an. Keine versteckten Gebühren.",
|
||||||
desc: "Reproduzierbare Umgebungen überall.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Directus",
|
label: "Pfeilschnell",
|
||||||
sub: "Management",
|
desc: "Keine langen Ladezeiten. Kunden bleiben auf der Seite.",
|
||||||
desc: "Headless CMS für flexible Datenabfrage.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Gitea",
|
label: "Sorglos",
|
||||||
sub: "Pipeline",
|
desc: "Hosting, Sicherheit, Backups? Darum kümmere ich mich.",
|
||||||
desc: "Self-hosted Git & CI/CD Pipelines.",
|
|
||||||
},
|
},
|
||||||
].map((item, i) => (
|
].map((item, i) => (
|
||||||
<Reveal key={i} delay={0.4 + i * 0.1}>
|
<div key={i} className="space-y-4">
|
||||||
<div className="space-y-2 p-6 rounded-2xl border border-slate-50 bg-white shadow-sm hover:border-slate-200 transition-all group">
|
<Label className="text-slate-900 text-lg">{item.label}</Label>
|
||||||
<Label className="text-slate-900 group-hover:text-blue-600 transition-colors uppercase tracking-widest text-[10px]">
|
<BodyText className="text-slate-500">{item.desc}</BodyText>
|
||||||
{item.label}
|
</div>
|
||||||
</Label>
|
|
||||||
<BodyText className="text-xs text-slate-400">
|
|
||||||
{item.desc}
|
|
||||||
</BodyText>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Reveal>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* 02: Performance */}
|
{/* 02: Performance */}
|
||||||
<Section
|
<Section number="02" title="Schnelligkeit" borderTop>
|
||||||
number="02"
|
<div className="space-y-16 md:space-y-32">
|
||||||
title="Performance"
|
|
||||||
borderTop
|
|
||||||
variant="gray"
|
|
||||||
illustration={<SpeedPerformance className="w-24 h-24" />}
|
|
||||||
effects={<GradientMesh variant="metallic" className="opacity-60" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-8 md:space-y-12">
|
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<H3 className="text-2xl md:text-5xl leading-tight max-w-3xl">
|
<H3 className="text-4xl md:text-7xl lg:text-8xl leading-none tracking-tighter max-w-4xl">
|
||||||
Geschwindigkeit ist <br />
|
Niemand wartet gerne. <br />
|
||||||
<span className="text-slate-400">
|
<span className="text-slate-400">
|
||||||
kein Extra. Sie ist <Marker delay={0.3}>Standard.</Marker>
|
Ihre Kunden erst recht nicht.
|
||||||
</span>
|
</span>
|
||||||
</H3>
|
</H3>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-8 md:gap-12 items-center">
|
|
||||||
<div className="md:col-span-12 lg:col-span-7 space-y-6 md:space-y-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24">
|
||||||
<Reveal delay={0.2}>
|
<Reveal delay={0.2}>
|
||||||
<LeadText className="text-lg md:text-xl text-slate-500 md:text-slate-400">
|
<LeadText className="text-xl md:text-2xl text-slate-500">
|
||||||
Jede Seite wird vorab gerendert und über ein CDN ausgeliefert.
|
Langsame Websites kosten bares Geld. Wenn Ihre Seite nicht
|
||||||
Das Ergebnis: Ladezeiten unter einer Sekunde. Messbar.{" "}
|
sofort lädt, ist der Besucher schon bei der Konkurrenz. Meine
|
||||||
<span className="text-slate-900">Reproduzierbar.</span>
|
Websites sind auf maximale Geschwindigkeit optimiert.
|
||||||
</LeadText>
|
</LeadText>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
<Reveal delay={0.4}>
|
<Reveal delay={0.3}>
|
||||||
<IconList className="space-y-2 md:space-y-4">
|
<div className="space-y-8 border-l border-slate-100 pl-8 md:pl-12">
|
||||||
{[
|
{[
|
||||||
"Server-Side Rendering für sofortige Inhalte",
|
"Sofortiger Seitenaufbau ohne Verzögerung",
|
||||||
"Automatische Bild-Optimierung (WebP, AVIF)",
|
"Automatische Bild-Optimierung für Smartphones",
|
||||||
"Lighthouse-Score 90+ als Mindeststandard",
|
"Bessere Google-Platzierung (SEO) durch Top-Speed",
|
||||||
"Core Web Vitals im grünen Bereich",
|
].map((item, i) => (
|
||||||
].map((item, i) => (
|
<div key={i} className="space-y-2">
|
||||||
<IconListItem key={i} bullet>
|
<Label className="text-slate-900">0{i + 1}</Label>
|
||||||
<LeadText className="text-base md:text-xl">
|
<BodyText className="text-slate-500 text-lg">
|
||||||
{item}
|
{item}
|
||||||
</LeadText>
|
</BodyText>
|
||||||
</IconListItem>
|
|
||||||
))}
|
|
||||||
</IconList>
|
|
||||||
</Reveal>
|
|
||||||
</div>
|
|
||||||
<div className="md:col-span-5">
|
|
||||||
<Reveal delay={0.6}>
|
|
||||||
<Card
|
|
||||||
variant="glass"
|
|
||||||
padding="normal"
|
|
||||||
techBorder
|
|
||||||
className="text-center group py-10 md:py-12"
|
|
||||||
>
|
|
||||||
<div className="text-5xl md:text-8xl font-bold text-slate-900 tracking-tighter group-hover:scale-110 transition-transform duration-700">
|
|
||||||
90+
|
|
||||||
</div>
|
</div>
|
||||||
<Label className="mt-4">Lighthouse Score</Label>
|
))}
|
||||||
<span className="block text-[8px] md:text-[9px] font-mono text-slate-300 mt-2 tracking-wider">
|
</div>
|
||||||
PERFORMANCE · ACCESSIBILITY · SEO
|
</Reveal>
|
||||||
</span>
|
|
||||||
</Card>
|
|
||||||
</Reveal>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* 03: Code-Qualität */}
|
{/* 03: Code-Qualität */}
|
||||||
<Section
|
<Section number="03" title="Qualität" borderTop>
|
||||||
number="03"
|
<div className="space-y-16 md:space-y-32">
|
||||||
title="Code"
|
|
||||||
borderTop
|
|
||||||
illustration={<SolidFoundation className="w-24 h-24" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-8 md:space-y-12">
|
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<H3 className="text-2xl md:text-5xl leading-tight max-w-3xl">
|
<H3 className="text-4xl md:text-7xl lg:text-8xl leading-none tracking-tighter max-w-4xl">
|
||||||
Keine Plugins. <br />
|
Handarbeit statt <br />
|
||||||
<span className="text-slate-400">Keine Abhängigkeiten.</span>
|
<span className="text-slate-400">Fertigbausatz.</span>
|
||||||
</H3>
|
</H3>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
<Reveal delay={0.2}>
|
|
||||||
<LeadText className="text-xl md:text-2xl max-w-2xl text-slate-500 md:text-slate-400">
|
|
||||||
Ihre Website besteht aus{" "}
|
|
||||||
<span className="text-slate-900">Ihrem Code</span>. Kein
|
|
||||||
WordPress, kein Wix, keine Blackbox. Alles versioniert, alles
|
|
||||||
nachvollziehbar.
|
|
||||||
</LeadText>
|
|
||||||
</Reveal>
|
|
||||||
|
|
||||||
{/* Git Branch Visualization */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
||||||
<Reveal delay={0.4}>
|
|
||||||
<CodeSnippet variant="git" />
|
|
||||||
</Reveal>
|
|
||||||
<Reveal delay={0.5}>
|
|
||||||
<div className="space-y-4 md:space-y-6">
|
|
||||||
<Card variant="glass" padding="normal" className="group">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-slate-900">Versionskontrolle</Label>
|
|
||||||
<BodyText>
|
|
||||||
Jede Änderung ist dokumentiert. Rollbacks in Sekunden.
|
|
||||||
Kein „wer hat das kaputt gemacht?".
|
|
||||||
</BodyText>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
<Card variant="glass" padding="normal" className="group">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-slate-900">
|
|
||||||
Automatisches Deployment
|
|
||||||
</Label>
|
|
||||||
<BodyText>
|
|
||||||
Code wird geprüft, getestet und automatisch live
|
|
||||||
geschaltet. Ohne manuellen Eingriff.
|
|
||||||
</BodyText>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
{/* 04: Content-System */}
|
|
||||||
<Section
|
|
||||||
number="04"
|
|
||||||
title="Inhalte"
|
|
||||||
borderTop
|
|
||||||
variant="gray"
|
|
||||||
illustration={<LayerSeparation className="w-24 h-24" />}
|
|
||||||
effects={<GradientMesh variant="subtle" className="opacity-60" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-12 md:space-y-20">
|
|
||||||
<div className="space-y-6 md:space-y-10 max-w-5xl">
|
|
||||||
<Reveal>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<MonoLabel className="text-blue-500 tracking-[0.2em] text-[10px] md:text-xs">
|
|
||||||
ARCHITECTURAL SEPARATION
|
|
||||||
</MonoLabel>
|
|
||||||
<H3 className="text-4xl md:text-7xl leading-[1.1] tracking-tighter">
|
|
||||||
Inhalte pflegen <br />
|
|
||||||
<span className="text-slate-400 italic font-serif">
|
|
||||||
ohne Angst.
|
|
||||||
</span>
|
|
||||||
</H3>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24">
|
||||||
<Reveal delay={0.2}>
|
<Reveal delay={0.2}>
|
||||||
<div className="space-y-6">
|
<LeadText className="text-xl md:text-2xl text-slate-500">
|
||||||
<LeadText className="text-lg md:text-2xl text-slate-500 md:text-slate-400 leading-relaxed max-w-3xl">
|
Vergessen Sie Sicherheitslücken durch veraltete Plugins oder
|
||||||
Vergessen Sie zerschossene Layouts nach einem Textupdate.
|
Themes, die ständig aktualisiert werden müssen.
|
||||||
Meine Websites trennen{" "}
|
</LeadText>
|
||||||
<span className="text-slate-900 font-bold underline decoration-blue-500/30 underline-offset-8">
|
</Reveal>
|
||||||
Daten von Design
|
<Reveal delay={0.3}>
|
||||||
</span>
|
<div className="space-y-12">
|
||||||
.
|
<div>
|
||||||
</LeadText>
|
<Label className="text-slate-900 text-xl mb-4 block">
|
||||||
|
Saubere Programmierung
|
||||||
|
</Label>
|
||||||
|
<BodyText className="text-slate-500 text-lg">
|
||||||
|
Ihre Website wird von Grund auf solide entwickelt. Kein
|
||||||
|
unnötiger Ballast, der die Seite verlangsamt oder angreifbar
|
||||||
|
macht.
|
||||||
|
</BodyText>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-slate-900 text-xl mb-4 block">
|
||||||
|
Zukunftssicher
|
||||||
|
</Label>
|
||||||
|
<BodyText className="text-slate-500 text-lg">
|
||||||
|
Moderne Technologien garantieren, dass Ihre Website auch in
|
||||||
|
ein paar Jahren noch perfekt und fehlerfrei läuft.
|
||||||
|
</BodyText>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 items-start">
|
|
||||||
<div className="lg:col-span-8 relative">
|
|
||||||
<Reveal delay={0.4} direction="up">
|
|
||||||
<CMSVisualizer className="w-full mx-auto" />
|
|
||||||
</Reveal>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="lg:col-span-4 space-y-8">
|
|
||||||
<Reveal delay={0.5}>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<BodyText className="text-slate-500 leading-relaxed">
|
|
||||||
Durch eine krisenfeste Headless-Architektur (Directus)
|
|
||||||
bewegen Sie sich in einem geschützten Sandkasten – während
|
|
||||||
das Frontend-System die visuelle Integrität Ihrer Marke
|
|
||||||
garantiert.
|
|
||||||
</BodyText>
|
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-3">
|
|
||||||
{["Layout-Schutz", "Live-Vorschau", "Role-RBAC"].map(
|
|
||||||
(tag, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="px-3 py-1 bg-white border border-slate-100 rounded-full text-[10px] font-mono text-slate-400"
|
|
||||||
>
|
|
||||||
{tag}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
|
||||||
|
|
||||||
<Reveal delay={0.6}>
|
|
||||||
<div className="p-6 bg-slate-900 rounded-2xl shadow-xl text-[10px] font-mono text-white/50 space-y-3">
|
|
||||||
<div className="flex justify-between items-center text-white/90">
|
|
||||||
<span>PROTOCOL</span>
|
|
||||||
<span className="text-green-500 font-bold">ENFORCED</span>
|
|
||||||
</div>
|
|
||||||
<p className="leading-tight">
|
|
||||||
Website architecture validates all CMS payloads against the
|
|
||||||
design schema before rendering.
|
|
||||||
</p>
|
|
||||||
<div className="pt-2 border-t border-white/10 flex items-center justify-between">
|
|
||||||
<span>INTEGRITY</span>
|
|
||||||
<span className="text-white">100%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Reveal delay={0.7}>
|
|
||||||
<div className="p-px w-full bg-gradient-to-r from-transparent via-slate-100 to-transparent" />
|
|
||||||
</Reveal>
|
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* 05: Was Sie bekommen */}
|
{/* 04: Sorglos Paket */}
|
||||||
|
<Section number="04" title="Sorglos-Paket" borderTop>
|
||||||
|
<div className="space-y-16 md:space-y-32">
|
||||||
|
<Reveal>
|
||||||
|
<H3 className="text-4xl md:text-7xl lg:text-8xl leading-none tracking-tighter max-w-4xl">
|
||||||
|
Das Prinzip: <br />
|
||||||
|
<span className="text-slate-400">Null Aufwand.</span>
|
||||||
|
</H3>
|
||||||
|
</Reveal>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24 items-start">
|
||||||
|
<Reveal delay={0.2}>
|
||||||
|
<LeadText className="text-2xl md:text-4xl text-slate-900 leading-snug">
|
||||||
|
Sie haben Besseres zu tun, als Bilder zu tauschen oder Updates
|
||||||
|
zu installieren.
|
||||||
|
</LeadText>
|
||||||
|
</Reveal>
|
||||||
|
|
||||||
|
<Reveal delay={0.3}>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<BodyText className="text-slate-500 text-lg md:text-xl leading-relaxed">
|
||||||
|
Mein Sorglos-Paket deckt alles für ein ganzes Jahr ab. Wenn
|
||||||
|
Sie eine Änderung brauchen, schreiben Sie mir einfach eine
|
||||||
|
E-Mail. Kein kompliziertes System, keine Schulungen. Ich setze
|
||||||
|
es für Sie um.
|
||||||
|
</BodyText>
|
||||||
|
<div className="pt-8 border-t border-slate-100 flex gap-8">
|
||||||
|
<div>
|
||||||
|
<Label className="text-slate-900">Inklusive</Label>
|
||||||
|
<BodyText className="text-slate-500 mt-2">
|
||||||
|
Änderungen & Wartung
|
||||||
|
</BodyText>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-slate-900">Ihr Stresslevel</Label>
|
||||||
|
<BodyText className="text-slate-500 mt-2">0%</BodyText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* 05: CTA */}
|
||||||
<Section
|
<Section
|
||||||
number="05"
|
number="05"
|
||||||
title="Ergebnis"
|
title="Ergebnis"
|
||||||
borderTop
|
borderTop
|
||||||
illustration={<TaskDone className="w-24 h-24" />}
|
effects={<GradientMesh variant="metallic" className="opacity-60" />}
|
||||||
>
|
>
|
||||||
<div className="space-y-12 md:space-y-24">
|
<div className="py-12 md:py-24 relative z-10">
|
||||||
<div className="max-w-4xl space-y-6">
|
<div className="space-y-16 md:space-y-24">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<H3 className="text-4xl md:text-7xl leading-[1.1] tracking-tighter">
|
<H3 className="text-5xl md:text-8xl lg:text-9xl leading-none tracking-tighter">
|
||||||
Was Sie konkret <br />
|
Ihre fertige <br />
|
||||||
<span className="text-slate-400">bekommen.</span>
|
<span className="text-slate-400">
|
||||||
|
<GlitchText delay={0.5} duration={1}>
|
||||||
|
Website.
|
||||||
|
</GlitchText>
|
||||||
|
</span>
|
||||||
</H3>
|
</H3>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
<Reveal delay={0.2}>
|
<Reveal delay={0.2}>
|
||||||
<LeadText className="text-lg md:text-2xl text-slate-500 md:text-slate-400 max-w-2xl">
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-12">
|
||||||
Keine halben Sachen. Ich liefere Ihnen ein schlüsselfertiges
|
<LeadText className="text-xl md:text-3xl text-slate-500 max-w-xl">
|
||||||
System mit voller Kontrolle und Transparenz.
|
Kein Stress, kein Overhead. Schreiben Sie mir einfach eine
|
||||||
</LeadText>
|
Nachricht.
|
||||||
|
</LeadText>
|
||||||
|
<Button
|
||||||
|
href="/contact"
|
||||||
|
className="w-full md:w-auto px-12 py-6 text-xl"
|
||||||
|
>
|
||||||
|
Sagen Sie Hallo
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Reveal delay={0.3} direction="up">
|
|
||||||
<ResultVisualizer />
|
|
||||||
</Reveal>
|
|
||||||
|
|
||||||
<Reveal delay={0.5}>
|
|
||||||
<div className="pt-10 md:pt-16 border-t border-slate-100 flex flex-col md:flex-row justify-between items-start md:items-center gap-12">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<MonoLabel className="text-blue-500">
|
|
||||||
BEREIT FÜR DEN NÄCHSTEN SCHRITT?
|
|
||||||
</MonoLabel>
|
|
||||||
<div className="text-xl md:text-3xl font-bold tracking-tight text-slate-900">
|
|
||||||
Lassen Sie uns über Ihr Projekt sprechen.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
href="/contact"
|
|
||||||
className="w-full md:w-auto h-16 px-10 text-lg rounded-2xl shadow-2xl shadow-blue-500/10"
|
|
||||||
>
|
|
||||||
Projekt anfragen
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
25
apps/web/app/debug-mdx/route.ts
Normal file
25
apps/web/app/debug-mdx/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const searchParams = request.nextUrl.searchParams;
|
||||||
|
const pathParam = searchParams.get("path") || "/";
|
||||||
|
const typeParam =
|
||||||
|
(searchParams.get("type") as "page" | "layout") || "layout";
|
||||||
|
|
||||||
|
// Revalidate the requested path (defaults to entire app via layout)
|
||||||
|
revalidatePath(pathParam, typeParam);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
revalidatedPath: pathParam,
|
||||||
|
revalidatedType: typeParam,
|
||||||
|
message: `Cache revalidated for ${pathParam} (${typeParam})`,
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
return NextResponse.json({ success: false, error: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
110
apps/web/content/blog/website-as-a-service.mdx
Normal file
110
apps/web/content/blog/website-as-a-service.mdx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
title: "Website as a Service: Warum das Sorglos-Paket der einzig logische Weg für Unternehmer ist"
|
||||||
|
thumbnail: "/blog/website-as-a-service.png"
|
||||||
|
description: "Vergessen Sie Plugin-Updates, Sicherheitslücken und endlose Stundenabrechnungen. Ein Plädoyer für Websites ohne Overhead und warum Fixpreise den Markt revolutionieren."
|
||||||
|
date: "2026-05-06"
|
||||||
|
tags: ["business", "management", "strategy"]
|
||||||
|
---
|
||||||
|
|
||||||
|
<LeadParagraph>
|
||||||
|
Die meisten Unternehmer wollen keine Website besitzen – sie wollen die Ergebnisse, die eine professionelle Website liefert: Sichtbarkeit, Vertrauen, qualifizierte Anfragen.
|
||||||
|
</LeadParagraph>
|
||||||
|
<LeadParagraph>
|
||||||
|
Trotzdem verbringen unzählige Geschäftsführer und Marketing-Teams Stunden damit, Sicherheits-Updates einzuspielen, sich in Baukästen einzuarbeiten oder mit Support-Hotlines zu telefonieren, weil ein simples Plugin die Seite zerschossen hat. Das ist betriebswirtschaftlicher Wahnsinn.
|
||||||
|
</LeadParagraph>
|
||||||
|
<LeadParagraph>
|
||||||
|
In meiner Arbeit als Digital Architect habe ich das Modell "Website as a Service" (WaaS) perfektioniert. Es ist die radikalste und fairste Antwort auf die Dysfunktionen der klassischen Webentwicklung.
|
||||||
|
</LeadParagraph>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<H2>Der Mythos der "kostenlosen" Website</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Ein Baukasten-System kostet vielleicht nur 15 Euro im Monat. Ein Premium-WordPress-Theme gibt es für 50 Euro. Doch die wahren Kosten verstecken sich in der Zeit. Wenn ein Geschäftsführer oder Head of Marketing, dessen Zeit konservativ geschätzt 150 Euro pro Stunde wert ist, sich nur fünf Stunden im Monat mit technischen Problemen seiner Website beschäftigt, kostet diese Seite in Wahrheit 750 Euro im Monat.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Dazu kommt das <Marker>Opportunitätsrisiko</Marker>: Was hätte das Unternehmen in diesen fünf Stunden erreichen können? Ein entscheidendes Verkaufsgespräch mehr? Eine strategische Prozessoptimierung? Die Verwaltung von IT-Infrastruktur sollte niemals die Kernaufgabe eines Nicht-IT-Unternehmens sein.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<ArticleQuote quote="The most expensive software is the one that requires your constant attention to keep it running safely." author="Digital Economics" isCompany={true} source="SaaS Industry Report" translated={false} />
|
||||||
|
|
||||||
|
<H2>Website as a Service: Das Ende des Overheads</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Der Ansatz "Website as a Service" (WaaS) – bei mir das "Sorglos-Paket" genannt – dreht dieses Modell komplett um. Sie mieten keine Software, Sie buchen eine fortlaufende Dienstleistung mit absoluter Kostensicherheit.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<div className="my-12">
|
||||||
|
<Mermaid id="waas-logic" title="Klassisches vs. WaaS Modell" showShare={true}>
|
||||||
|
graph LR
|
||||||
|
A["Klassisch (WordPress/Baukasten)"] --> B["Versteckte Zeitkosten"]
|
||||||
|
B --> C["Sicherheitsrisiken"]
|
||||||
|
C --> D["Frust bei Änderungen"]
|
||||||
|
|
||||||
|
E["Sorglos-Paket (WaaS)"] --> F["Fixer Jahresbeitrag"]
|
||||||
|
F --> G["Zero Overhead"]
|
||||||
|
G --> H["Skalierbares Business"]
|
||||||
|
|
||||||
|
style A fill:#f87171,stroke:#333
|
||||||
|
style E fill:#4ade80,stroke:#333
|
||||||
|
</Mermaid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Bei meinem Sorglos-Paket ist die Prämisse kompromisslos einfach: <Marker>Zero Overhead für die Technik.</Marker> Sie kümmern sich um Ihr Geschäft und schreiben Ihre Fachartikel oder News ganz entspannt im Headless CMS. Alles andere – Server-Wartung, Security-Updates, Performance-Monitoring oder Layout-Anpassungen – läuft unsichtbar im Hintergrund ab. Keine zerschossenen Plugins, keine Angst vor fehlerhaften Updates.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<H2>Sicherheit durch Architektur (Security by Design)</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Warum sind klassische CMS wie WordPress so angreifbar? Weil sie dynamisch sind. Jeder Seitenaufruf triggert eine Datenbankabfrage, und Millionen von Websites nutzen dieselben veralteten Plugins, was sie zu einem perfekten Ziel für automatisierte Hacker-Skripte macht.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
In einem WaaS-Modell übernehme ich als Entwickler nicht nur die inhaltlichen Änderungen, sondern baue die Seite von Grund auf auf einer modernen, statischen Architektur (Jamstack / Next.js). Das bedeutet: Die Seite wird im Vorfeld generiert. Es gibt keine Datenbank, die live am Netz hängt und gehackt werden könnte.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<ComparisonRow description="Wie unterscheidet sich WaaS von klassischem Hosting?" negativeLabel="Klassisches Hosting & CMS" negativeText="Sie sind selbst für Updates, Backups und Plugin-Kompatibilität verantwortlich. Die Seite ist ein Sicherheitsrisiko und veraltet technologisch schnell." positiveLabel="Sorglos-Paket (WaaS)" positiveText="Proaktives Monitoring, unsichtbare Updates und sofortige strukturelle Anpassungen durch einen Experten. Kein Stress, absolute Sicherheit und volle redaktionelle Kontrolle." showShare={true} />
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Das Ergebnis: Die Seite kann faktisch nicht gehackt werden. [Updates und Wartung](/blog/why-websites-break-after-updates) laufen unsichtbar im Hintergrund ab, und die Ladezeiten sind extrem optimiert, da nur statische Dateien (HTML/CSS/JS) vom Server ausgeliefert werden.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<StatsGrid stats="0%|Einarbeitungszeit|für Sie oder Ihr Team~100%|Kostensicherheit|durch garantierte Fixpreise~99.9%|Uptime|durch statische Architektur" />
|
||||||
|
|
||||||
|
<H2>Finanzielle Vorhersehbarkeit statt Blackbox</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
"Wir haben 2 Stunden gebraucht, um das Kontaktformular wieder zum Laufen zu bringen." – Solche Rechnungen gehören mit einem Service-Paket der Vergangenheit an. Ein festes Jahresbudget erlaubt Ihnen eine exakte Finanzplanung.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Diese <Marker>Budgetsicherheit</Marker> ist für B2B-Unternehmen extrem wertvoll. Unvorhergesehene Serverausfälle oder nötige Sicherheits-Patches sind nicht mehr Ihr finanzielles Risiko, sondern mein technisches. Es liegt also in meinem absoluten Eigeninteresse, die Architektur von Tag eins an so robust und fehlerfrei wie möglich zu bauen. Das ist das Prinzip des [strategischen Festpreises](/blog/fixed-price-digital-projects).
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<LeadMagnet title="Bereit für Zero Overhead?" description="Lassen Sie uns Ihre aktuelle Website analysieren. Ich zeige Ihnen, wie wir den Aufwand für Sie auf null reduzieren können." buttonText="Sorglos-Paket anfragen" href="/contact" variant="standard" />
|
||||||
|
|
||||||
|
<H2>Fazit: Kaufen Sie Zeit, keine Software</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Eine Website sollte ein stiller Mitarbeiter sein, der 24/7 funktioniert, Leads generiert und Ihr Unternehmen professionell repräsentiert – kein IT-Projekt, das ständig Ihre Aufmerksamkeit fordert. Ein Sorglos-Paket ist nicht einfach nur ein Wartungsvertrag. Es ist die strategische Entscheidung, technischen Ballast abzuwerfen und sich wieder zu 100% auf das eigene Kerngeschäft zu konzentrieren.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<FAQSection>
|
||||||
|
<H3>Welche Änderungen sind im Sorglos-Paket enthalten?</H3>
|
||||||
|
<Paragraph>
|
||||||
|
Das Paket umfasst alle **nicht-redaktionellen** Anpassungen, Wartungsarbeiten und Security-Updates. Wenn sich Ihr Angebot ändert, ein neues technisches Feature integriert werden muss oder Layout-Anpassungen nötig sind, setze ich das zeitnah um. Reine redaktionelle Aufgaben (wie das wöchentliche Schreiben neuer Blog-Posts) sind nicht inkludiert, da Sie hierfür über das Headless CMS volle Autonomie besitzen.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<H3>Was passiert, wenn wir doch komplett neue Funktionen brauchen?</H3>
|
||||||
|
<Paragraph>
|
||||||
|
Große strukturelle Erweiterungen (z.B. der Bau eines komplett neuen Karriere-Portals oder die Integration eines komplexen CRM-Systems) werden als separates Festpreis-Projekt behandelt. Alles, was den bestehenden Rahmen nutzt, ist abgedeckt.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<H3>Verlieren wir nicht die Kontrolle über die Technik?</H3>
|
||||||
|
<Paragraph>
|
||||||
|
Im Gegenteil: Sie gewinnen Qualitätskontrolle und Sicherheit. Sie haben über das Headless CMS die volle redaktionelle Freiheit für Ihre Inhalte. Was ich Ihnen abnehme, ist die technische Last. Sie können das Layout nicht versehentlich zerschießen und müssen keine Angst vor Datenbank-Fehlern haben. Die Technik bleibt professionell betreut und zu 100% "on brand".
|
||||||
|
</Paragraph>
|
||||||
|
</FAQSection>
|
||||||
110
apps/web/content/blog/zero-overhead-agencies.mdx
Normal file
110
apps/web/content/blog/zero-overhead-agencies.mdx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
title: "Zero Overhead: Warum klassische Agenturen oft zu langsam (und zu teuer) sind"
|
||||||
|
thumbnail: "/blog/zero-overhead-agencies.png"
|
||||||
|
description: "Projektmanager, Account Manager, Junior Designer – bei klassischen Agenturen zahlen Sie oft den Wasserkopf mit. Wie ein Lean-Ansatz bessere und schnellere Ergebnisse liefert."
|
||||||
|
date: "2026-05-06"
|
||||||
|
tags: ["management", "strategy", "efficiency"]
|
||||||
|
---
|
||||||
|
|
||||||
|
<LeadParagraph>
|
||||||
|
Sie kennen den Ablauf: Ein Kick-off-Meeting mit fünf Personen. Ein Account Manager, ein Projektmanager, ein Designer, ein Entwickler und vielleicht noch ein Stratege.
|
||||||
|
</LeadParagraph>
|
||||||
|
<LeadParagraph>
|
||||||
|
Was sich auf dem Papier nach geballter Kompetenz anhört, ist in der Realität oft der Startschuss für den <Marker>Stille-Post-Effekt</Marker>. Ressourcen werden verschwendet, Deadlines reißen und Budgets explodieren.
|
||||||
|
</LeadParagraph>
|
||||||
|
<LeadParagraph>
|
||||||
|
Die Alternative ist "Lean Engineering". Ein Ansatz, der Hierarchien eliminiert, technische Exzellenz in den Vordergrund stellt und den administrativen Wasserkopf radikal abschneidet.
|
||||||
|
</LeadParagraph>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<H2>Der Preis des Wasserkopfs</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Klassische Agenturen haben einen hohen administrativen Overhead. Großraumbüros in Top-Lagen, repräsentative Empfangsbereiche und Gehälter für Mitarbeiter, die keinen einzigen direkten Beitrag zu Ihrem Produkt leisten (Projektmanagement, HR, Sales). All das muss über Ihren Stundensatz finanziert werden.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Das Problem dabei ist nicht nur der aufgeblähte Preis, sondern die **Geschwindigkeit**. Wenn Sie eine einfache inhaltliche oder funktionale Anpassung anfordern, geht die E-Mail an den Projektmanager. Dieser schreibt ein Ticket für den Designer. Der Designer macht einen Entwurf. Der Projektmanager schickt Ihnen den Entwurf. Sie geben Feedback. Das Ticket geht zurück zum Designer, dann zum Entwickler, dann in die QA, dann wieder zu Ihnen.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<ArticleQuote quote="Every layer of communication halves the precision and doubles the time required to execute a task. In software, middle management is often a bug, not a feature." author="Lean Engineering Principles" isCompany={false} source="The Minimalist Developer" translated={false} />
|
||||||
|
|
||||||
|
<H2>Der Lean-Ansatz: Ein Ansprechpartner, volle Verantwortung</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Der Gegensatz dazu ist der Lean-Ansatz. Als unabhängiger Digital Architect bin ich Ihr einziger Ansprechpartner. Ich analysiere Ihre Business-Ziele, entwerfe die Systemarchitektur, designe das Interface und schreibe den Code.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<div className="my-12">
|
||||||
|
<Mermaid id="agency-vs-lean" title="Kommunikationswege im Vergleich" showShare={true}>
|
||||||
|
graph TD
|
||||||
|
A[Kunde] --> B[Projektmanager]
|
||||||
|
B --> C[Designer]
|
||||||
|
B --> D[Entwickler]
|
||||||
|
B --> E[Copywriter]
|
||||||
|
|
||||||
|
F[Kunde] --> G[Marc Mintel (Architect)]
|
||||||
|
G --> H[Direktes Ergebnis (Code/Design)]
|
||||||
|
|
||||||
|
style A fill:#f87171,stroke:#333
|
||||||
|
style F fill:#4ade80,stroke:#333
|
||||||
|
</Mermaid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Dieser direkte Weg reduziert Missverständnisse auf null. Wenn wir in einem Call über eine neue Funktion sprechen, weiß ich sofort, wie diese technisch im Hintergrund umsetzbar ist und ob sie in den [strategischen Festpreis](/blog/fixed-price-digital-projects) passt. Es gibt keine unrealistischen Versprechungen im Vertrieb, die die Technik später mühsam ausbaden muss.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<H2>Weniger ist technologisch schneller</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Ein oft unterschätzter Faktor ist die technologische Reibung innerhalb großer Teams. In Agenturen müssen Technologien oft so gewählt werden, dass auch der Junior-Entwickler oder der Praktikant sie versteht. Es werden schwere, veraltete Frameworks oder Baukasten-CMS-Systeme (wie WordPress) eingesetzt, weil sie der kleinste gemeinsame Nenner sind.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Wenn man den Overhead eliminiert, kann man auf [hochperformante, moderne Stacks (Jamstack, Next.js)](/blog/digital-longevity-architecture) setzen. Diese wären für ein heterogenes Team oft zu komplex im Setup, können von einem Senior-Entwickler aber in einem Bruchteil der Zeit fehlerfrei und in industrieller Qualität gebaut werden.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<ComparisonRow description="Wofür bezahlen Sie wirklich?" negativeLabel="Klassische Agentur" negativeText="Sie finanzieren Meetings, interne Abstimmungen, Projektmanagement-Tools, Büromieten und Junior-Gehälter." positiveLabel="Lean Engineer (Zero Overhead)" positiveText="Sie investieren 100% Ihres Budgets in pure Code-Qualität, High-End Design und technologische Langlebigkeit." showShare={true} />
|
||||||
|
|
||||||
|
<StatsGrid stats="60%|Weniger Meetings|durch direkten Kontakt~3x|Schnellere Umsetzung|durch fehlende Feedback-Schleifen~100%|Verantwortung|bei einer einzigen Person" />
|
||||||
|
|
||||||
|
<H2>Agile vs. Waterfall: Warum Agenturen oft stecken bleiben</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Viele Agenturen behaupten, agil zu arbeiten, praktizieren aber eigentlich einen "Wasserfall-Prozess" in Sprints. Das Design wird komplett finalisiert (und abgerechnet), bevor die erste Zeile Code geschrieben wird. Wenn in der Entwicklung auffällt, dass eine Interaktion technisch unsauber ist, fängt der Kreislauf von vorne an.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Im Zero-Overhead-Modell verschmelzen Design und Entwicklung. Ich gestalte oft direkt im Code. Das bedeutet, wir können echte Klick-Prototypen im Browser testen, statt statische Bilder anzustarren. Das spart nicht nur Wochen an Projektzeit, sondern führt zu digitalen Produkten, die sich von Grund auf flüssiger anfühlen.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<H2>Wann eine Agentur Sinn macht</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Um fair zu bleiben: Es gibt Projekte, für die eine Agentur zwingend notwendig ist. Wenn Sie eine globale 360-Grad-Kampagne planen, physische Plakatwände buchen, TV-Spots produzieren und 50 verschiedene Landingpages gleichzeitig in 10 Sprachen ausrollen müssen, brauchen Sie schlichtweg Manpower.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Aber für 90% der B2B-Unternehmen, die eine hochprofessionelle, blitzschnelle und zukunftssichere Plattform brauchen, um digitale Dominanz auszustrahlen und Leads zu generieren? Dafür ist eine Agentur meistens Overkill – und ein massiver Rendite-Killer.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<LeadMagnet title="Machen wir es unkompliziert." description="Lassen Sie uns den administrativen Overhead streichen. In einem kurzen Call klären wir die Fakten, danach erhalten Sie ein glasklares Angebot." buttonText="Jetzt Blueprint-Call anfragen" href="/contact" variant="standard" />
|
||||||
|
|
||||||
|
<FAQSection>
|
||||||
|
<H3>Was passiert, wenn der alleinige Entwickler ausfällt?</H3>
|
||||||
|
<Paragraph>
|
||||||
|
Das ist eine berechtigte Frage (der sogenannte Bus-Faktor). Die Antwort liegt in der Code-Qualität. Durch strikte, industrieweite Standards, extrem saubere Dokumentation und den Verzicht auf obskure proprietäre Plugins kann sich jeder erfahrene React/Next.js-Entwickler innerhalb von Stunden in meine Architektur einarbeiten.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<H3>Fehlt ohne Agentur nicht die strategische Beratung?</H3>
|
||||||
|
<Paragraph>
|
||||||
|
Ganz im Gegenteil. Als Digital Architect übernehme ich genau diesen strategischen Part. Da ich nicht versuchen muss, meine 20-köpfige Design-Abteilung auszulasten, ist meine Beratung 100% objektiv und auf Ihren geschäftlichen Erfolg fokussiert, nicht auf meine interne Auslastung.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<H3>Können große Projekte überhaupt alleine gestemmt werden?</H3>
|
||||||
|
<Paragraph>
|
||||||
|
Ja, durch moderne Tooling-Ketten, KI-Unterstützung und hochgradig wiederverwendbare Architektur-Komponenten. Ein Senior Architect baut heute allein Systeme, für die man vor 5 Jahren noch ein Team aus 5 Leuten gebraucht hätte. Qualität schlägt in der Softwareentwicklung immer Quantität.
|
||||||
|
</Paragraph>
|
||||||
|
</FAQSection>
|
||||||
@@ -71,6 +71,10 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
// In Standalone mode, Next.js expects the tracing root to be the monorepo root
|
// In Standalone mode, Next.js expects the tracing root to be the monorepo root
|
||||||
outputFileTracingRoot: path.join(dirname, '../../'),
|
outputFileTracingRoot: path.join(dirname, '../../'),
|
||||||
|
outputFileTracingIncludes: {
|
||||||
|
'/(site)/blog/[slug]': ['./content/blog/**/*'],
|
||||||
|
'/blog/[slug]': ['./content/blog/**/*'],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const withMDX = createMDX({});
|
const withMDX = createMDX({});
|
||||||
|
|||||||
BIN
apps/web/public/blog/website-as-a-service.png
Normal file
BIN
apps/web/public/blog/website-as-a-service.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 456 KiB |
BIN
apps/web/public/blog/zero-overhead-agencies.png
Normal file
BIN
apps/web/public/blog/zero-overhead-agencies.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 528 KiB |
@@ -1,71 +1,91 @@
|
|||||||
import { ThumbnailGenerator } from '@mintel/thumbnail-generator';
|
import { ThumbnailGenerator } from "@mintel/thumbnail-generator";
|
||||||
import * as path from 'node:path';
|
import * as path from "node:path";
|
||||||
import * as fs from 'node:fs/promises';
|
import * as fs from "node:fs/promises";
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
const apiKey = process.env.REPLICATE_API_TOKEN || process.env.REPLICATE_API_KEY;
|
const apiKey =
|
||||||
if (!apiKey) {
|
process.env.REPLICATE_API_TOKEN || process.env.REPLICATE_API_KEY;
|
||||||
console.error("❌ Missing REPLICATE_API_TOKEN in environment.");
|
if (!apiKey) {
|
||||||
process.exit(1);
|
console.error("❌ Missing REPLICATE_API_TOKEN in environment.");
|
||||||
}
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
const targetFile = process.argv[2];
|
const targetFile = process.argv[2];
|
||||||
if (!targetFile) {
|
if (!targetFile) {
|
||||||
console.error("❌ Usage: npx tsx scripts/generate-thumbnail.ts <file-or-topic>");
|
console.error(
|
||||||
process.exit(1);
|
"❌ Usage: npx tsx scripts/generate-thumbnail.ts <file-or-topic>",
|
||||||
}
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
let topic = targetFile;
|
let topic = targetFile;
|
||||||
let filename = "thumbnail.png";
|
let filename = "thumbnail.png";
|
||||||
|
|
||||||
// Try to parse the topic from the MDX frontmatter if a file is provided
|
// Try to parse the topic from the MDX frontmatter if a file is provided
|
||||||
if (targetFile.endsWith('.mdx')) {
|
if (targetFile.endsWith(".mdx")) {
|
||||||
try {
|
|
||||||
const content = await fs.readFile(targetFile, 'utf8');
|
|
||||||
const titleMatch = content.match(/title:\s*"?([^"\n]+)"?/);
|
|
||||||
topic = titleMatch ? titleMatch[1] : path.basename(targetFile, '.mdx');
|
|
||||||
filename = `${path.basename(targetFile, '.mdx')}-thumb.png`;
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`⚠️ Could not read ${targetFile} as a file. Using literal argument as topic.`);
|
|
||||||
topic = targetFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Generating abstract thumbnail for topic: "${topic}"`);
|
|
||||||
|
|
||||||
const generator = new ThumbnailGenerator({ replicateApiKey: apiKey });
|
|
||||||
const isRoot = process.cwd().endsWith('mintel.me');
|
|
||||||
const baseDir = isRoot ? path.join(process.cwd(), 'apps', 'web') : process.cwd();
|
|
||||||
|
|
||||||
const outputPath = path.join(baseDir, 'public', 'blog', filename);
|
|
||||||
|
|
||||||
// Check if thumbnail already exists to avoid redundant generation
|
|
||||||
try {
|
try {
|
||||||
await fs.access(outputPath);
|
const content = await fs.readFile(targetFile, "utf8");
|
||||||
console.log(`⏭️ Thumbnail already exists, skipping: ${filename}`);
|
const titleMatch = content.match(/title:\s*"?([^"\n]+)"?/);
|
||||||
return;
|
topic = titleMatch ? titleMatch[1] : path.basename(targetFile, ".mdx");
|
||||||
} catch {
|
filename = `${path.basename(targetFile, ".mdx")}.png`;
|
||||||
// File does not exist, proceed with generation
|
} catch (e) {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ Could not read ${targetFile} as a file. Using literal argument as topic.`,
|
||||||
|
);
|
||||||
|
topic = targetFile;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const inspirationPath = path.join(baseDir, 'public', 'blog', 'inspiration.png');
|
console.log(`Generating abstract thumbnail for topic: "${topic}"`);
|
||||||
let hasInspiration = false;
|
|
||||||
try {
|
|
||||||
await fs.access(inspirationPath);
|
|
||||||
hasInspiration = true;
|
|
||||||
} catch {
|
|
||||||
hasInspiration = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const customPrompt = `Extremely clean, flat, abstract geometric illustration. Use the provided image prompt ONLY as a STRICT style, color, and texture reference. Do not copy the image content, just the aesthetic. Characteristics: Flat vector design, 2D only (no 3D), tech/startup/agency aesthetics, highly professional, abstract data representations, extensive use of whitespace. No text, no chaotic lines, no humans.`;
|
const generator = new ThumbnailGenerator({ replicateApiKey: apiKey });
|
||||||
|
const isRoot = process.cwd().endsWith("mintel.me");
|
||||||
|
const baseDir = isRoot
|
||||||
|
? path.join(process.cwd(), "apps", "web")
|
||||||
|
: process.cwd();
|
||||||
|
|
||||||
await generator.generateImage(topic, outputPath, {
|
const outputPath = path.join(baseDir, "public", "blog", filename);
|
||||||
systemPrompt: customPrompt,
|
|
||||||
imagePrompt: hasInspiration ? inspirationPath : undefined,
|
// Check if thumbnail already exists to avoid redundant generation
|
||||||
});
|
try {
|
||||||
|
await fs.access(outputPath);
|
||||||
|
console.log(`⏭️ Thumbnail already exists, skipping: ${filename}`);
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
// File does not exist, proceed with generation
|
||||||
|
}
|
||||||
|
|
||||||
|
const inspirationPath = path.join(
|
||||||
|
baseDir,
|
||||||
|
"public",
|
||||||
|
"blog",
|
||||||
|
"inspiration.png",
|
||||||
|
);
|
||||||
|
let hasInspiration = false;
|
||||||
|
try {
|
||||||
|
await fs.access(inspirationPath);
|
||||||
|
hasInspiration = true;
|
||||||
|
} catch {
|
||||||
|
hasInspiration = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let customPrompt = `Extremely clean, flat, abstract geometric illustration. Use the provided image prompt ONLY as a STRICT color and texture reference. You MUST generate completely new, distinct geometric shapes that directly represent the topic. Characteristics: Flat vector design, 2D only (no 3D), tech/startup/agency aesthetics, highly professional, extensive use of whitespace. No text, no chaotic lines, no humans.`;
|
||||||
|
|
||||||
|
if (topic.includes("Service") || topic.includes("Sorglos")) {
|
||||||
|
customPrompt +=
|
||||||
|
" Visually depict the concept of 'effortless automation'. Use a large, distinct, central endless loop or a floating interconnected minimal machine.";
|
||||||
|
} else if (topic.includes("Zero") || topic.includes("Overhead")) {
|
||||||
|
customPrompt +=
|
||||||
|
" Visually depict the concept of 'cutting out the middleman'. Use a massive, heavy block being sliced in half or a direct, unfiltered sharp lightning/beam connecting two points.";
|
||||||
|
}
|
||||||
|
|
||||||
|
await generator.generateImage(topic, outputPath, {
|
||||||
|
systemPrompt: customPrompt,
|
||||||
|
imagePrompt: hasInspiration ? inspirationPath : undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
run().catch((e) => {
|
run().catch((e) => {
|
||||||
console.error("❌ Thumbnail generation failed:", e);
|
console.error("❌ Thumbnail generation failed:", e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useState, useMemo, useEffect, useRef } from "react";
|
import { useState, useMemo, useEffect, useRef } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import * as confetti from "canvas-confetti";
|
import confetti from "canvas-confetti";
|
||||||
import {
|
import {
|
||||||
Layers,
|
Layers,
|
||||||
BrainCircuit,
|
BrainCircuit,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const HeroSection: React.FC = () => {
|
|||||||
<div className="w-1 h-1 rounded-full bg-blue-300 animate-pulse delay-100" />
|
<div className="w-1 h-1 rounded-full bg-blue-300 animate-pulse delay-100" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[9px] md:text-[10px] font-mono font-bold tracking-[0.3em] md:tracking-[0.4em] text-slate-500 uppercase">
|
<span className="text-[9px] md:text-[10px] font-mono font-bold tracking-[0.3em] md:tracking-[0.4em] text-slate-500 uppercase">
|
||||||
Digital_Architect // v.2026
|
Webentwicklung // Fixpreis
|
||||||
</span>
|
</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@@ -83,16 +83,16 @@ export const HeroSection: React.FC = () => {
|
|||||||
className="flex flex-col items-center gap-6 md:gap-8 lg:gap-12"
|
className="flex flex-col items-center gap-6 md:gap-8 lg:gap-12"
|
||||||
>
|
>
|
||||||
<p className="text-base md:text-xl lg:text-3xl text-slate-400 font-medium max-w-2xl leading-relaxed px-4">
|
<p className="text-base md:text-xl lg:text-3xl text-slate-400 font-medium max-w-2xl leading-relaxed px-4">
|
||||||
Ein Entwickler. Ein Ansprechpartner.{" "}
|
Ohne Agentur, ohne Baukasten, ohne Stress.{" "}
|
||||||
<br className="hidden md:block" />
|
<br className="hidden md:block" />
|
||||||
<span className="text-slate-900 font-bold tracking-tight">
|
<span className="text-slate-900 font-bold tracking-tight">
|
||||||
Systematische Architekturen für das Web.
|
Ein Entwickler. Fixpreis. Fertig.
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col md:flex-row items-center justify-center gap-4 md:gap-6 w-full px-5 md:px-0">
|
<div className="flex flex-col md:flex-row items-center justify-center gap-4 md:gap-6 w-full px-5 md:px-0">
|
||||||
<Button href="/contact" size="large" className="w-full md:w-auto">
|
<Button href="/contact" size="large" className="w-full md:w-auto">
|
||||||
Projekt anfragen
|
Kostenlos anfragen
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
href="/websites"
|
href="/websites"
|
||||||
@@ -100,7 +100,7 @@ export const HeroSection: React.FC = () => {
|
|||||||
size="large"
|
size="large"
|
||||||
className="w-full md:w-auto"
|
className="w-full md:w-auto"
|
||||||
>
|
>
|
||||||
Prozess ansehen
|
So funktioniert's
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -111,12 +111,12 @@ export const HeroSection: React.FC = () => {
|
|||||||
<div className="absolute inset-0 pointer-events-none border-[1px] border-slate-100 m-4 md:m-8 opacity-40 md:opacity-40" />
|
<div className="absolute inset-0 pointer-events-none border-[1px] border-slate-100 m-4 md:m-8 opacity-40 md:opacity-40" />
|
||||||
<div className="absolute top-8 left-8 p-4 hidden md:block opacity-20 transform -rotate-90 origin-top-left transition-opacity hover:opacity-100 group">
|
<div className="absolute top-8 left-8 p-4 hidden md:block opacity-20 transform -rotate-90 origin-top-left transition-opacity hover:opacity-100 group">
|
||||||
<span className="text-[10px] font-mono tracking-widest text-slate-400">
|
<span className="text-[10px] font-mono tracking-widest text-slate-400">
|
||||||
POS_TRANSMISSION_001
|
MARC_MINTEL_DEV
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-4 right-4 md:bottom-8 md:right-8 p-4 opacity-20 transition-opacity hover:opacity-100 scale-75 md:scale-100 origin-bottom-right">
|
<div className="absolute bottom-4 right-4 md:bottom-8 md:right-8 p-4 opacity-20 transition-opacity hover:opacity-100 scale-75 md:scale-100 origin-bottom-right">
|
||||||
<span className="text-[10px] font-mono tracking-widest text-slate-400">
|
<span className="text-[10px] font-mono tracking-widest text-slate-400">
|
||||||
EST_2026 // M-ARCH
|
FRANKFURT // 2026
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
7
test-middleware.js
Normal file
7
test-middleware.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const remainingPath =
|
||||||
|
"wp-content/cache/breeze-minification/css/breeze_klz-cables-com-1-10895.css";
|
||||||
|
const directory = "klz-cables.com";
|
||||||
|
const targetPath = remainingPath.startsWith(`assets/${directory}/`)
|
||||||
|
? `/${remainingPath}`
|
||||||
|
: `/assets/${directory}/${remainingPath}`;
|
||||||
|
console.log(`/showcase/${directory}${targetPath}`);
|
||||||
Reference in New Issue
Block a user