This repository has been archived on 2026-06-19. You can view files and clone it, but cannot push or open issues or pull requests.
gso-landingpage/src/app/components/WeekTimeline.tsx
Athena a5b0e4c9a6 feat: scroll-animierte Zick-Zack Timeline für alle 8 Wochen
- WeekTimeline Client-Component mit IntersectionObserver
- Woche 1,3,5,7 erscheint von links — Woche 2,4,6,8 von rechts
- Vertikale Gradient-Linie in der Mitte (indigo → violet)
- Nummerierter Dot auf der Linie pro Woche
- Modul-Divider (1/2/3) als animierte Trennelemente
- CSS-Transitionen: opacity + translateX, 0.6s ease
- Expert-Badges auf den Karten (Can, Stefan & Philipp, Manuela)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 22:06:50 +02:00

247 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useRef } from "react";
interface Week {
week: string;
expert?: string;
title: string;
body: string;
vorlagen: string;
result: string;
}
interface Module {
label: string;
headline: string;
weeks: Week[];
}
const MODULES: Module[] = [
{
label: "Modul 1 — Wochen 1 & 2",
headline: "Wunschkunde, Angebot, Positionierung",
weeks: [
{
week: "Woche 1",
title: "Dein Wunschkunde + dein Markt",
body: "Du schaust konkret hin: Wer ist dein Wunschkunde wirklich? Welches Problem hat er — und was hat er schon alles versucht? Was kostet ihn dieses Problem jeden Monat? Du machst einen ehrlichen Marketing-Audit: Was läuft, was nicht — und warum.",
vorlagen: "Marketing-Audit-Vorlage · Wunschkunden-Persona-Vorlage",
result: "Klarheit über deinen Wunschkunden — konkret, nicht allgemein.",
},
{
week: "Woche 2",
title: "Dein unwiderstehliches Angebot + deine Positionierung",
body: "Du legst dein Angebot auf den Tisch: Was bietest du genau an? Welches Problem löst du? Dann entwickelst du deine Positionierungsformel — für wen, welches Problem, was macht dich anders. In einem Satz. Du übst ihn live in der Gruppe.",
vorlagen: "Positionierungsformel-Vorlage · Kurztext für Website, Instagram und LinkedIn",
result: "Dein Angebot klar auf den Punkt — erklärt in einem Satz.",
},
],
},
{
label: "Modul 2 — Wochen 3 & 4",
headline: "Dein System steht.",
weeks: [
{
week: "Woche 3",
title: "Deine Verkaufsseite",
body: "Du lernst wie eine Verkaufsseite aufgebaut sein muss — die 7 Pflicht-Elemente, ihre Reihenfolge, die Psychologie dahinter. Du bekommst die komplette Vorlage. Du setzt es direkt auf deiner Seite um — Katja gibt Feedback.",
vorlagen: "Verkaufsseiten-Vorlage + alle Textelemente · 7-Elemente-Checkliste",
result: "Vollständige Struktur für deine Verkaufsseite — fertig zum Umsetzen.",
},
{
week: "Woche 4",
title: "Deine Kanal-Strategie + 90-Tage-Marketingplan",
body: "Welche 2 Kanäle fokussierst du — auf Basis deiner Zielgruppe, nicht dem was gerade alle machen. Du baust deine Marketing-Systemkarte und startest deinen 90-Tage-Plan.",
vorlagen: "Kanal-Entscheidungsmatrix · Marketing-Systemkarte · 90-Tage-Marketingplan",
result: "Gewusst welche 2 Kanäle du fokussierst — und 90-Tage-Plan begonnen.",
},
],
},
{
label: "Modul 3 — Wochen 58",
headline: "Sichtbarkeit. Anfragen. Und du weißt wie es weitergeht.",
weeks: [
{
week: "Woche 5",
expert: "Can Turkdogan · Social Media",
title: "Dein Content-System",
body: "Deine 34 Content-Themen die zeigen wofür du stehst. Du erstellst live deinen 4-Wochen-Redaktionsplan, schreibst deinen ersten Hook und weißt ab dieser Woche genau: was du postest, wann, und warum.",
vorlagen: "Content-Säulen-Template · 4-Wochen-Redaktionsplan · 10 bewährte Hook-Formeln",
result: "Content-Plan für 4 Wochen — weißt genau was und wie du postest.",
},
{
week: "Woche 6",
expert: "Stefan & Philipp · Onlinewerbevideo",
title: "Social Media Verkauf + Video-Setup",
body: "Du lernst wie du dein Angebot aktiv über Social Media verkaufst — mit Strategie, nicht mit Druck. Die Video-Experten zeigen live: professionelle Videos mit dem Handy.",
vorlagen: "Launch-Checkliste · Equipment-Checkliste · Filming-Guide",
result: "Plan für Social Media Verkauf + professionell auf Video.",
},
{
week: "Woche 7",
expert: "Manuela Ludewig · Vertrieb",
title: "Verkauf + Direktansprache",
body: "Dein persönliches Verkaufsskript für die Direktansprache — angepasst auf deine Positionierung. Du übst es live in der Gruppe mit konkretem Plan für deine nächsten Kontakte.",
vorlagen: "Persönliches Verkaufsskript · Follow-up-Template · Event-Recherche-Guide",
result: "Dein Verkaufsskript für die Direktansprache — einmal geübt.",
},
{
week: "Woche 8",
expert: "Katja Pestereva · Market Compass",
title: "Sichtbarkeit auf Google + Abschluss",
body: "Du optimierst dein Google Business Profil Schritt für Schritt in der Gruppe. Du bringst deine Website auf die Grundlagen die Google belohnt. Abschlussrunde: Was hat sich in 8 Wochen verändert?",
vorlagen: "SEO-Checkliste · Keyword-Recherche-Vorlage · Website-Leitfaden",
result: "Google Business vollständig optimiert + Seite für dein Haupt-Keyword.",
},
],
},
];
/* Running week counter so odd/even alternates across all modules */
let globalWeekIndex = 0;
function WeekCard({ week, isLeft }: { week: Week; isLeft: boolean }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
el.classList.add("tl-visible");
obs.disconnect();
}
},
{ threshold: 0.15 }
);
obs.observe(el);
return () => obs.disconnect();
}, []);
return (
<div
ref={ref}
className={`tl-card ${isLeft ? "tl-from-left" : "tl-from-right"} w-full md:w-[46%] ${
isLeft ? "md:mr-auto md:pr-10" : "md:ml-auto md:pl-10"
}`}
>
<div className="mc-card p-6 space-y-3 hover:shadow-lg hover:shadow-indigo-100 transition-shadow">
{/* Header */}
<div className="flex flex-wrap items-center gap-2">
<span className="bg-gradient-to-r from-indigo-500 to-violet-500 text-white text-xs font-bold px-3 py-1 rounded-full uppercase tracking-wide">
{week.week}
</span>
{week.expert && (
<span className="bg-indigo-50 text-indigo-700 text-xs font-medium px-3 py-1 rounded-full border border-indigo-100">
{week.expert}
</span>
)}
</div>
{/* Title */}
<h4 className="font-extrabold text-slate-900 text-base leading-snug">{week.title}</h4>
{/* Body */}
<p className="text-slate-500 text-sm leading-relaxed">{week.body}</p>
{/* Vorlagen */}
<p className="text-xs text-indigo-600 italic border-t border-slate-100 pt-3">
📋 {week.vorlagen}
</p>
{/* Result */}
<p className="text-sm font-semibold text-emerald-600"> {week.result}</p>
</div>
</div>
);
}
export default function WeekTimeline() {
/* Reset global counter each render (SSR-safe: only matters client-side) */
globalWeekIndex = 0;
return (
<section className="py-20">
<div className="max-w-5xl mx-auto px-6">
{/* Section header */}
<div className="text-center mb-16">
<span className="inline-flex items-center gap-2 bg-indigo-50 text-indigo-700 text-xs font-bold uppercase tracking-widest rounded-full px-4 py-2 border border-indigo-100">
<span className="w-2 h-2 rounded-full bg-indigo-500 animate-pulse" />
8 Wochen · Schritt für Schritt
</span>
<h2 className="mt-4 text-3xl md:text-4xl font-extrabold text-slate-900">
Dein Programm Woche für Woche
</h2>
<p className="mt-3 text-slate-500 max-w-xl mx-auto">
Jede Woche baut auf der nächsten auf. Am Ende hast du kein Zertifikat sondern ein fertiges System.
</p>
</div>
{MODULES.map((mod) => (
<div key={mod.label} className="mb-6">
{/* Module divider */}
<ModuleDivider label={mod.label} headline={mod.headline} />
{/* Weeks */}
<div className="relative">
{/* Vertical timeline line */}
<div className="hidden md:block absolute left-1/2 top-0 bottom-0 w-0.5 bg-gradient-to-b from-indigo-300 via-violet-300 to-indigo-200 -translate-x-1/2" />
<div className="flex flex-col gap-10 py-6">
{mod.weeks.map((week) => {
const isLeft = globalWeekIndex % 2 === 0;
globalWeekIndex++;
return (
<div key={week.week} className="relative flex items-start">
{/* Timeline dot — centered */}
<div className="hidden md:flex absolute left-1/2 -translate-x-1/2 top-7 w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-violet-500 items-center justify-center shadow-lg shadow-indigo-200 z-10 flex-shrink-0">
<span className="text-white text-xs font-bold">
{week.week.replace("Woche ", "")}
</span>
</div>
<WeekCard week={week} isLeft={isLeft} />
</div>
);
})}
</div>
</div>
</div>
))}
</div>
</section>
);
}
function ModuleDivider({ label, headline }: { label: string; headline: string }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
el.classList.add("tl-visible");
obs.disconnect();
}
},
{ threshold: 0.2 }
);
obs.observe(el);
return () => obs.disconnect();
}, []);
return (
<div
ref={ref}
className="tl-card tl-from-bottom text-center py-8 mb-2"
>
<div className="inline-flex items-center gap-3">
<div className="h-px w-12 bg-gradient-to-r from-transparent to-indigo-300" />
<span className="bg-gradient-to-r from-indigo-600 to-violet-600 bg-clip-text text-transparent text-xs font-extrabold uppercase tracking-widest">
{label}
</span>
<div className="h-px w-12 bg-gradient-to-l from-transparent to-indigo-300" />
</div>
<h3 className="mt-2 text-xl md:text-2xl font-extrabold text-slate-900">{headline}</h3>
</div>
);
}