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 db7d7530f4 Add mountain climbing animation + fix typography scale
- MountainClimb: SVG stick figure climbs 11-step mountain, text card above head each step, flag + bounce at summit, IntersectionObserver auto-start, mobile numbered list fallback
- Replace static content grid with MountainClimb animation
- Add mc-fade-in + mc-bounce keyframes to globals.css
- Typography: IconCard title text-sm→text-base, IconCard sub text-xs→text-sm
- Bonus card title text-sm→text-base, desc text-xs→text-sm
- FAQ answers text-sm→text-base
- WeekTimeline card title text-base→text-lg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 23:32:40 +02:00

252 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";
import { ClipboardIcon, CheckIcon } from "./Icons";
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-lg leading-snug">{week.title}</h4>
{/* Body */}
<p className="text-slate-500 text-sm leading-relaxed">{week.body}</p>
{/* Vorlagen */}
<p className="text-xs text-slate-400 italic border-t border-slate-100 pt-3 flex items-start gap-1.5">
<ClipboardIcon className="w-3.5 h-3.5 flex-shrink-0 mt-0.5" />
{week.vorlagen}
</p>
{/* Result */}
<p className="text-sm font-semibold text-slate-700 flex items-start gap-1.5">
<CheckIcon className="w-4 h-4 flex-shrink-0 mt-0.5 text-indigo-500" />
{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>
);
}