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

128 lines
5.9 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 { motion, useScroll, useTransform } from "motion/react"
import { useRef } from "react"
import { cn } from "@/lib/utils"
import { useNow } from "../shared/hooks"
import { wordmarkSize } from "../shared/motion"
import type { FooterSchlichtData, FooterWortmarkeData } from "./definitions"
import type { BlockProps, BrandData } from "./types"
import { Action, Brand, container, telHref } from "./ui"
const label = "text-[11px] tracking-[0.22em] text-t-muted uppercase"
const clock = new Intl.DateTimeFormat("de-DE", { hour: "2-digit", minute: "2-digit", timeZone: "Europe/Berlin" })
function ContactList({ brand }: { brand: BrandData }) {
return (
<ul className="mt-4 grid gap-2 text-t-fg/80">
{brand.email.trim() && (
<li>
<a href={`mailto:${brand.email}`} className="transition-colors hover:text-t-accent">{brand.email}</a>
</li>
)}
{brand.phone.trim() && (
<li>
<a href={telHref(brand.phone)} className="transition-colors hover:text-t-accent">{brand.phone}</a>
</li>
)}
{brand.address.trim() && <li className="text-t-muted">{brand.address}</li>}
</ul>
)
}
function Legal({ brand, note, className }: { brand: BrandData; note?: string; className?: string }) {
return (
<div className={cn(container, "flex flex-col gap-3 border-t border-t-fg/10 py-6 text-xs text-t-muted md:flex-row md:items-center md:justify-between", className)}>
<span>
© {brand.name}
{note?.trim() && ` · ${note}`}
</span>
<div className="flex gap-6">
<a href="#" className="transition-colors hover:text-t-fg">Impressum</a>
<a href="#" className="transition-colors hover:text-t-fg">Datenschutz</a>
<a href="#top" className="transition-colors hover:text-t-fg">Nach oben ↑</a>
</div>
</div>
)
}
// Abschluss mit Navigation, Kontakt und dem Namen über die volle Breite, der beim Erreichen des Seitenendes aufsteigt
export function FooterWortmarke({ data, brand, links }: BlockProps<FooterWortmarkeData>) {
const ref = useRef<HTMLElement>(null)
const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end end"] })
const y = useTransform(scrollYProgress, [0, 1], ["55%", "0%"])
return (
<footer ref={ref} className="relative overflow-hidden border-t border-t-fg/10 pt-20 md:pt-28">
<div className={cn(container, "grid gap-12 md:grid-cols-12 md:gap-6")}>
<div className="md:col-span-5">
{data.text.trim() && <p className="font-t-display text-[clamp(1.75rem,3vw,2.75rem)] leading-[1.1] tracking-[-0.02em]">{data.text}</p>}
<Action href={data.href} className="mt-8">
{data.cta}
</Action>
</div>
{links.length > 0 && (
<nav className="md:col-span-3 md:col-start-7">
<p className={label}>Navigation</p>
<ul className="mt-4 grid gap-2">
{links.map((link) => (
<li key={link.href}>
<a href={link.href} className="transition-colors hover:text-t-accent">{link.label}</a>
</li>
))}
</ul>
</nav>
)}
<div className={cn("md:col-span-3", links.length === 0 && "md:col-start-7")}>
<p className={label}>Kontakt</p>
<ContactList brand={brand} />
</div>
</div>
<div aria-hidden className="mt-16 overflow-hidden md:mt-20">
<motion.p
style={{ y, fontSize: wordmarkSize(brand.name, 26) }}
className="text-center font-t-display leading-[0.82] tracking-[-0.05em] whitespace-nowrap select-none"
>
{brand.name}
</motion.p>
</div>
<Legal brand={brand} />
</footer>
)
}
export function FooterSchlicht({ data, brand, links }: BlockProps<FooterSchlichtData>) {
const now = useNow()
return (
<footer className="border-t border-t-fg/10">
<div className={cn(container, "grid gap-10 py-14 md:grid-cols-12 md:gap-6 md:py-20")}>
<div className="md:col-span-4">
<Brand brand={brand} className="text-2xl md:text-3xl" />
{brand.tagline.trim() && <p className="mt-3 max-w-xs text-sm text-t-muted">{brand.tagline}</p>}
</div>
{links.length > 0 && (
<nav className="md:col-span-2 md:col-start-6">
<p className={label}>Navigation</p>
<ul className="mt-4 grid gap-2 text-t-fg/80">
{links.map((link) => (
<li key={link.href}>
<a href={link.href} className="transition-colors hover:text-t-accent">{link.label}</a>
</li>
))}
</ul>
</nav>
)}
<div className={cn("md:col-span-3", links.length === 0 && "md:col-start-6")}>
<p className={label}>Kontakt</p>
<ContactList brand={brand} />
</div>
<div className="md:col-span-2 md:col-start-11 md:text-right">
<p className={label}>Ortszeit {data.city}</p>
<p className="mt-3 font-t-display text-3xl tabular-nums">{now === null ? "––:––" : clock.format(now)}</p>
</div>
</div>
<Legal brand={brand} note={data.note} />
</footer>
)
}