since3-portal/modules/templates/catalog/blocks/process.tsx
2026-10-11 18:22:32 +02:00

115 lines
5.8 KiB
TypeScript

"use client"
import { motion, useInView, useScroll, useSpring } from "motion/react"
import { useRef } from "react"
import { cn } from "@/lib/utils"
import { ease } from "../shared/motion"
import type { AblaufLinieData, AblaufSpaltenData } from "./definitions"
import type { BlockProps } from "./types"
import { container, easeInOut, Eyebrow, Heading } from "./ui"
const ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"]
type Step = AblaufLinieData["items"][number]
// Zeitstrahl: die Linie zeichnet sich mit dem Scrollen, Schritte leuchten auf, sobald die Linie sie erreicht
export function AblaufLinie({ data }: BlockProps<AblaufLinieData>) {
const track = useRef<HTMLDivElement>(null)
const { scrollYProgress } = useScroll({ target: track, offset: ["start 75%", "end 55%"] })
const progress = useSpring(scrollYProgress, { stiffness: 120, damping: 30 })
return (
<section className={cn(container, "py-24 md:py-36")}>
<div className="mx-auto max-w-3xl md:text-center">
<Eyebrow className="md:justify-center">{data.eyebrow}</Eyebrow>
<Heading text={data.title} className="mt-6" />
</div>
<div ref={track} className="relative mx-auto mt-16 max-w-5xl md:mt-24">
<div aria-hidden className="absolute top-0 bottom-0 left-[11px] w-px bg-t-fg/12 md:left-1/2" />
<motion.div
aria-hidden
style={{ scaleY: progress }}
className="absolute top-0 bottom-0 left-[11px] w-px origin-top bg-t-accent shadow-[0_0_14px_var(--t-accent)] md:left-1/2"
/>
<ol className="flex flex-col gap-14 md:gap-24">
{data.items.map((item, i) => (
<TimelineStep key={i} item={item} index={i} />
))}
</ol>
</div>
</section>
)
}
function TimelineStep({ item, index }: { item: Step; index: number }) {
const ref = useRef<HTMLLIElement>(null)
// Gleiche Höhe wie das Ende der Linie (offset "end 55%"), damit Punkt und Linie zusammen ankommen
const lit = useInView(ref, { margin: "0px 0px -45% 0px" })
const right = index % 2 === 1
return (
<li ref={ref} className="relative grid pl-12 md:grid-cols-2 md:pl-0">
<span
aria-hidden
className={cn(
"absolute top-1 left-0 grid size-[23px] place-items-center rounded-full border transition-[background-color,border-color,box-shadow] duration-500 md:left-1/2 md:-translate-x-1/2",
lit ? "border-t-accent bg-t-accent shadow-[0_0_24px_var(--t-accent)]" : "border-t-fg/20 bg-t-bg"
)}
>
<span className={cn("size-1.5 rounded-full transition-colors duration-500", lit ? "bg-t-accent-fg" : "bg-t-fg/30")} />
</span>
<motion.div
initial={{ opacity: 0, x: right ? 24 : -24 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "0px 0px -20% 0px" }}
transition={{ duration: 0.8, ease }}
className={cn(right ? "md:col-start-2 md:pl-16" : "md:pr-16 md:text-right")}
>
<p className={cn("flex flex-wrap items-center gap-3 text-xs font-medium tracking-[0.2em] text-t-muted uppercase", !right && "md:justify-end")}>
Schritt {String(index + 1).padStart(2, "0")}
{item.duration.trim() && <span className="rounded-full border border-t-fg/15 px-2.5 py-1 tracking-normal normal-case">{item.duration}</span>}
</p>
<h3 className="mt-4 font-t-display text-3xl tracking-[-0.02em] md:text-4xl">{item.title}</h3>
<p className="mt-3 leading-relaxed text-t-muted">{item.text}</p>
</motion.div>
</li>
)
}
// Schritte nebeneinander: Linien zeichnen sich nacheinander, darunter römische Ziffern
export function AblaufSpalten({ data }: BlockProps<AblaufSpaltenData>) {
return (
<section className={cn(container, "py-24 md:py-36")}>
<div className="grid gap-6 md:grid-cols-12">
<div className="md:col-span-3">
<Eyebrow>{data.eyebrow}</Eyebrow>
</div>
<Heading text={data.title} className="md:col-span-9" />
</div>
<ol className="mt-14 grid gap-12 sm:grid-cols-2 md:mt-20 lg:grid-cols-4 lg:gap-8">
{data.items.map((item, i) => (
<li key={i}>
<motion.span
aria-hidden
className="block h-px origin-left bg-t-fg/25"
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
viewport={{ once: true, margin: "0px 0px -10% 0px" }}
transition={{ duration: 1.2, ease: easeInOut, delay: i * 0.12 }}
/>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "0px 0px -10% 0px" }}
transition={{ duration: 0.9, ease, delay: 0.2 + i * 0.12 }}
>
<span className="mt-6 block font-t-display text-6xl leading-none text-t-accent italic">{ROMAN[i] ?? i + 1}</span>
<h3 className="mt-8 font-t-display text-2xl tracking-[-0.01em] md:text-3xl">{item.title}</h3>
<p className="mt-3 leading-relaxed text-t-muted">{item.text}</p>
</motion.div>
</li>
))}
</ol>
</section>
)
}