mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 19:51:17 +00:00
128 lines
5.9 KiB
TypeScript
128 lines
5.9 KiB
TypeScript
"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>
|
||
)
|
||
}
|