feat: initial commit — Webflow to Next.js conversion

QuantumLab template converted to Next.js 16 + React 19 + TypeScript:
- 8 page routes (home, about, blog, contact, careers, team-members, coming-soon, 404)
- Dynamic routes for blog posts, career positions, and team members
- GSAP animations (marquee, counters, button hovers)
- IntersectionObserver-based scroll reveal (blur-to-clear transitions)
- Dark mode with next-themes
- React Hook Form + Zod contact form
- Framer Motion page transitions
- Lottie animations via lottie-web

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Leon-in
2026-04-26 18:19:56 +08:00
commit 95eb362bfc
134 changed files with 25831 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
import type { Metadata } from "next"
import Image from "next/image"
import Link from "next/link"
import { notFound } from "next/navigation"
import { TEAM_MEMBERS } from "@/lib/team-data"
interface Props {
params: Promise<{ id: string }>
}
export async function generateStaticParams() {
return TEAM_MEMBERS.map((member) => ({ id: member.slug }))
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params
const member = TEAM_MEMBERS.find((m) => m.slug === id)
if (!member) return { title: "Team Member Not Found" }
return {
title: member.name,
description: `${member.name} - ${member.role} at DalCode`,
openGraph: {
title: `${member.name} - ${member.role}`,
description: member.bio,
images: [{ url: member.image }],
},
}
}
export default async function TeamMemberPage({ params }: Props) {
const { id } = await params
const member = TEAM_MEMBERS.find((m) => m.slug === id)
if (!member) notFound()
return (
<main>
<section className="section-small top overflow-hidden">
<div className="w-layout-blockcontainer container-default w-container">
<div className="inner-container _650px center">
<div className="text-center">
<div className="team-member-avatar-large" style={{ position: "relative", width: 200, height: 200, margin: "0 auto", borderRadius: "50%", overflow: "hidden" }}>
<Image
src={member.image}
alt={member.name}
fill
style={{ objectFit: "cover" }}
priority
/>
</div>
<div className="mg-top-regular">
<h1>{member.name}</h1>
</div>
<div className="mg-top-4x-extra-small">
<div className="subtitle">{member.role}</div>
</div>
<div className="mg-top-4x-extra-small">
<p>{member.bio}</p>
</div>
</div>
</div>
<div className="mg-top-regular">
<div className="inner-container _650px center">
<div className="blog-post-rich-text w-richtext">
<h2>About</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.</p>
<p>Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.</p>
</div>
</div>
</div>
<div className="mg-top-regular">
<div className="inner-container _650px center">
<div className="team-member-buttons-wrapper" style={{ justifyContent: "center" }}>
<div>
<a href="https://x.com" className="social-square-icon-link w-inline-block" target="_blank" rel="noopener noreferrer">
<div className="social-square-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 19 18" fill="none" className="social-media-icon">
<path fillRule="evenodd" clipRule="evenodd" d="M7.04543 9.42969L0.0390625 0.25H5.98663L10.1792 5.74312L14.8487 0.25H17.7333L11.5439 7.53115L18.9618 17.25H13.0143L8.41014 11.2177L3.28238 17.25H0.397729L7.04543 9.42969ZM13.8802 15.4998L3.57671 2.00024H5.12071L15.4242 15.4998H13.8802Z" fill="currentColor" />
</svg>
</div>
<div className="social-square-bg" />
</a>
</div>
<div>
<a href="https://linkedin.com" className="social-square-icon-link w-inline-block" target="_blank" rel="noopener noreferrer">
<div className="social-square-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 20 20" fill="none" className="social-media-icon">
<path d="M1 2.99134C1 2.41413 1.20271 1.93794 1.60811 1.56277C2.01351 1.18758 2.54055 1 3.18919 1C3.82626 1 4.34169 1.18469 4.73552 1.55411C5.14092 1.93506 5.34363 2.43145 5.34363 3.04329C5.34363 3.5974 5.14672 4.05915 4.7529 4.42857C4.3475 4.80952 3.81467 5 3.15444 5H3.13707C2.49999 5 1.98456 4.80952 1.59073 4.42857C1.19691 4.04762 1 3.56854 1 2.99134ZM1.22587 18.1429V6.57576H5.08301V18.1429H1.22587ZM7.22008 18.1429H11.0772V11.684C11.0772 11.2799 11.1236 10.9682 11.2162 10.7489C11.3784 10.3564 11.6245 10.0245 11.9546 9.75324C12.2847 9.48195 12.6988 9.34632 13.1969 9.34632C14.4942 9.34632 15.1429 10.2179 15.1429 11.961V18.1429H19V11.5108C19 9.8023 18.5946 8.50649 17.7838 7.62337C16.973 6.74026 15.9015 6.2987 14.5695 6.2987C13.0753 6.2987 11.9112 6.93939 11.0772 8.22078V8.25541H11.0598L11.0772 8.22078V6.57576H7.22008C7.24324 6.94516 7.25483 8.09378 7.25483 10.0216C7.25483 11.9495 7.24324 14.6565 7.22008 18.1429Z" fill="currentColor" />
</svg>
</div>
<div className="social-square-bg" />
</a>
</div>
</div>
</div>
</div>
<div className="mg-top-regular">
<div className="inner-container _650px center text-center">
<Link href="/about" className="primary-button w-inline-block">
<div className="button-content">
<div>Back to team</div>
<div className="button-icon-wrapper primary">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 17 17" fill="none" className="squared-icon">
<path d="M6.25391 3.45312L10.7458 8.01563L6.25391 12.5781" stroke="currentColor" strokeWidth="1.5" strokeLinecap="square" />
</svg>
<div className="button-icon-bg" />
<div className="button-icon-bg-inside" />
</div>
</div>
</Link>
</div>
</div>
</div>
</section>
</main>
)
}