mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 20:51:17 +00:00
126 lines
7.7 KiB
TypeScript
126 lines
7.7 KiB
TypeScript
"use client"
|
|
|
|
import { motion } from "motion/react"
|
|
import { ArrowUpRightIcon, BadgeCheckIcon, MailIcon } from "lucide-react"
|
|
import { cn } from "@/lib/utils"
|
|
import type { TemplateProps } from "../../lib/types"
|
|
import { DemoForm } from "../shared/demo-form"
|
|
import { TImage } from "../shared/media"
|
|
import { ease } from "../shared/motion"
|
|
import { TemplateShell } from "../shared/shell"
|
|
import { linkinbio, type LinkinbioData } from "./definition"
|
|
import { fonts } from "./fonts"
|
|
|
|
const column = "relative mx-auto w-full max-w-md px-5"
|
|
const heading = "font-t-display font-bold tracking-[-0.02em]"
|
|
|
|
const appear = (i: number) => ({
|
|
initial: { opacity: 0, y: 20 },
|
|
animate: { opacity: 1, y: 0 },
|
|
transition: { duration: 0.6, ease, delay: 0.15 + i * 0.07 },
|
|
})
|
|
|
|
export function LinkinbioTemplate({ content }: TemplateProps<LinkinbioData>) {
|
|
const { brand, socials, featured, links, gallery, newsletter } = content.data
|
|
const show = (key: string) => !content.hidden.includes(key)
|
|
|
|
return (
|
|
<TemplateShell def={linkinbio} content={content} fonts={fonts[content.fonts] ?? fonts.modern} className="pb-20">
|
|
<div aria-hidden className="pointer-events-none fixed inset-0 overflow-hidden">
|
|
<motion.div className="absolute -top-32 -left-32 size-96 rounded-full bg-t-accent/30 blur-3xl" animate={{ x: [0, 80, 0], y: [0, 60, 0] }} transition={{ duration: 16, repeat: Infinity, ease: "easeInOut" }} />
|
|
<motion.div className="absolute -right-32 bottom-0 size-[28rem] rounded-full bg-t-accent/20 blur-3xl" animate={{ x: [0, -60, 0], y: [0, -80, 0] }} transition={{ duration: 20, repeat: Infinity, ease: "easeInOut" }} />
|
|
</div>
|
|
|
|
<header className={cn(column, "pt-14 text-center")}>
|
|
<motion.div initial={{ scale: 0.6, opacity: 0 }} animate={{ scale: 1, opacity: 1 }} transition={{ type: "spring", bounce: 0.4 }} className="relative mx-auto size-28">
|
|
<div className="absolute -inset-1.5 rounded-full bg-[conic-gradient(from_0deg,var(--t-accent),transparent,var(--t-accent))] animate-[spin_6s_linear_infinite]" />
|
|
<div className="relative size-full overflow-hidden rounded-full border-4 border-t-bg">
|
|
<TImage src={brand.avatar} alt={brand.name} eager sizes="112px" />
|
|
</div>
|
|
</motion.div>
|
|
<motion.h1 {...appear(0)} className={cn(heading, "mt-5 flex items-center justify-center gap-1.5 text-3xl")}>
|
|
{brand.name} <BadgeCheckIcon className="size-6 text-t-accent" />
|
|
</motion.h1>
|
|
<motion.p {...appear(1)} className="mx-auto mt-2 max-w-xs text-t-muted">{brand.bio}</motion.p>
|
|
</header>
|
|
|
|
{show("socials") && (
|
|
<motion.section data-order="socials" {...appear(2)} className={cn(column, "mt-6 flex flex-wrap justify-center gap-2")}>
|
|
{socials.items.map((social) => (
|
|
<a key={social.name} href="#" className="rounded-full bg-t-surface px-4 py-2 text-sm font-semibold transition-transform hover:-translate-y-0.5">
|
|
{social.name} <span className="font-normal text-t-muted">· {social.count}</span>
|
|
</a>
|
|
))}
|
|
</motion.section>
|
|
)}
|
|
|
|
{show("featured") && (
|
|
<motion.section data-order="featured" {...appear(3)} className={cn(column, "mt-8")}>
|
|
<a href={featured.url || "#"} className="group block overflow-hidden rounded-3xl bg-t-surface shadow-lg ring-1 ring-t-fg/5">
|
|
<div className="relative aspect-[16/9] overflow-hidden">
|
|
<TImage src={featured.image} alt={featured.title} sizes="448px" className="transition-transform duration-700 group-hover:scale-105" />
|
|
<span className="absolute top-3 left-3 rounded-full bg-t-accent px-3 py-1 text-xs font-bold text-t-accent-fg">{featured.label}</span>
|
|
</div>
|
|
<div className="flex items-center gap-3 p-5">
|
|
<div className="flex-1">
|
|
<p className={cn(heading, "text-lg leading-snug")}>{featured.title}</p>
|
|
<p className="mt-1 text-sm text-t-muted">{featured.text}</p>
|
|
</div>
|
|
<ArrowUpRightIcon className="size-5 shrink-0 transition-transform group-hover:rotate-45" />
|
|
</div>
|
|
</a>
|
|
</motion.section>
|
|
)}
|
|
|
|
<section className={cn(column, "mt-4 grid gap-3")}>
|
|
{links.items.map((link, i) => (
|
|
<motion.a
|
|
key={i}
|
|
href={link.url || "#"}
|
|
{...appear(4 + i)}
|
|
whileHover={{ y: -3 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
className="flex items-center gap-4 rounded-2xl border-2 border-t-fg/10 bg-t-bg/80 px-5 py-4 backdrop-blur transition-colors hover:border-t-accent"
|
|
>
|
|
<span className="flex-1">
|
|
<span className="block font-semibold leading-snug">{link.title}</span>
|
|
<span className="text-xs font-semibold uppercase tracking-wider text-t-accent">{link.tag}</span>
|
|
</span>
|
|
<ArrowUpRightIcon className="size-5 shrink-0 text-t-muted" />
|
|
</motion.a>
|
|
))}
|
|
</section>
|
|
|
|
{show("gallery") && (
|
|
<section data-order="gallery" className={cn(column, "mt-10")}>
|
|
<p className={cn(heading, "mb-3 text-center")}>{gallery.title}</p>
|
|
<div className="grid grid-cols-3 gap-1.5 overflow-hidden rounded-2xl">
|
|
{gallery.images.map((image, i) => (
|
|
<motion.div key={i} {...appear(8 + i)} className="group relative aspect-square overflow-hidden">
|
|
<TImage src={image.src} sizes="150px" className="transition-transform duration-500 group-hover:scale-110" />
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{show("newsletter") && (
|
|
<section data-order="newsletter" className={cn(column, "mt-10")}>
|
|
<DemoForm className="rounded-3xl bg-t-fg p-6 text-t-bg" success={<p className={cn(heading, "py-4 text-center text-xl")}>Danke! Bis Sonntag in deinem Postfach.</p>}>
|
|
<p className={cn(heading, "flex items-center gap-2 text-xl")}><MailIcon className="size-5 text-t-accent" /> {newsletter.title}</p>
|
|
<p className="mt-1 text-sm opacity-70">{newsletter.text}</p>
|
|
<div className="mt-4 flex gap-2">
|
|
<input type="email" required placeholder="deine@mail.de" aria-label="E-Mail-Adresse" className="min-w-0 flex-1 rounded-full bg-t-bg/10 px-4 py-3 text-sm outline-none placeholder:text-t-bg/50 focus:ring-2 focus:ring-t-accent" />
|
|
<button type="submit" className="cursor-pointer rounded-full bg-t-accent px-5 text-sm font-bold text-t-accent-fg">{newsletter.cta}</button>
|
|
</div>
|
|
</DemoForm>
|
|
</section>
|
|
)}
|
|
|
|
<footer className={cn(column, "mt-10 flex justify-center gap-5 text-xs text-t-muted")}>
|
|
<a href="#" className="hover:text-t-fg">Impressum</a>
|
|
<a href="#" className="hover:text-t-fg">Datenschutz</a>
|
|
</footer>
|
|
</TemplateShell>
|
|
)
|
|
}
|