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
+52
View File
@@ -0,0 +1,52 @@
import NewsletterForm from "@/components/NewsletterForm"
export default function CtaSection() {
return (
<section className="cta-section v4">
<div className="w-layout-blockcontainer container-default w-container">
<div data-w-id="43e48b1f-85e7-b846-9296-3a85fcb30cd3" className="cta-v4-content-wrapper">
<div className="cta-v4-content-top">
<div className="subtitle dark-mode">Newsletter</div>
<div className="mg-top-4x-extra-small">
<h2 className="text-titles-dm">
Subscribe for cutting-edge AI updates </h2>
</div>
<div className="mg-top-4x-extra-small">
<p className="text-paragraph-dm">
ipsum dolor sit amet consectetur at amet felis nulla molestie non viverra diam sed augue gravida ante risus pulvinar diam turpis ut bibendum ut velit felis at nisl lectus. </p>
</div>
</div>
<div className="corner-gradient-container">
<div className="cta-v4-content-bottom">
<div className="cta-v4-form-wrapper">
<NewsletterForm variant="dark" />
<div className="corner-gradient-wrapper hidden-on-tablet">
<div className="corner-gradient-vertical small---dark-mode top-left">
</div>
</div>
</div>
<div className="check-item-wrapper">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 17 16" fill="none" className="squared-icon text-titles-dm">
<path d="M3.62891 8.00429L6.87307 11.2485L13.3711 4.75" stroke="currentColor" strokeWidth="1.5">
</path>
</svg>
<div className="text-color-neutral-500">
One email per month No spam! </div>
</div>
</div>
<div className="corner-gradient-wrapper hidden-on-tablet">
<div className="corner-gradient-horizontal small---dark-mode bottom-left">
</div>
<div className="corner-gradient-horizontal small---dark-mode bottom-right">
</div>
<div className="corner-gradient-horizontal small---dark-mode top-left">
</div>
<div className="corner-gradient-horizontal small---dark-mode top-right">
</div>
</div>
</div>
</div>
</div>
</section>
)
}
+40
View File
@@ -0,0 +1,40 @@
import { BLOG_POSTS } from "@/lib/blog-data"
import BlogCard from "@/components/BlogCard"
export default function HeroSection() {
const featured = BLOG_POSTS.slice(0, 2)
return (
<section className="section top overflow-hidden">
<div className="w-layout-blockcontainer container-default w-container">
<div data-w-id="1d4184ff-9d8d-d0a9-7964-1fe6bc1712ef" style={{ opacity: "0", filter: "blur(8px)" }} className="title-left-content-right">
<div className="inner-container _480px">
<div className="subtitle">Blog</div>
<div className="mg-top-4x-extra-small">
<h1>Latest news</h1>
</div>
<div className="mg-top-5x-extra-small">
<p>Lorem ipsum dolor sit amet consectetur nec quis suspendisse nulla.</p>
</div>
</div>
<form action="/search" className="form-block _365px position-relative---z-index-1 w-form">
<label htmlFor="search" className="hidden">Search</label>
<input className="input w-input" maxLength={256} name="query" placeholder="Search for articles…" type="search" id="search" required />
<div className="button-inside-input-wrapper left-mbp">
<input type="submit" className="form-button inside-input light-mode w-button" value="Search" />
</div>
</form>
</div>
<div className="mg-top-regular">
<div data-w-id="1d4184ff-9d8d-d0a9-7964-1fe6bc1712fd" style={{ opacity: "0", filter: "blur(8px)" }} className="w-dyn-list">
<div role="list" className="w-dyn-items">
{featured.map((post) => (
<BlogCard key={post.slug} post={post} variant="featured" />
))}
</div>
</div>
</div>
</div>
</section>
)
}
+46
View File
@@ -0,0 +1,46 @@
"use client"
import { useState } from "react"
import { BLOG_POSTS, BLOG_CATEGORIES } from "@/lib/blog-data"
import BlogCard from "@/components/BlogCard"
export default function PostsGridSection() {
const [activeCategory, setActiveCategory] = useState("All")
const filtered = activeCategory === "All"
? BLOG_POSTS
: BLOG_POSTS.filter((p) => p.category === activeCategory)
return (
<section className="section">
<div className="w-layout-blockcontainer container-default w-container">
<div data-w-id="41a231e8-d013-bd0a-0639-c179719a51f6" style={{ opacity: "0", filter: "blur(8px)" }} className="title-left-content-right align-center">
<h2>All articles</h2>
<div className="category-list-wrapper">
<div role="list" className="category-list">
{BLOG_CATEGORIES.map((cat) => (
<button
key={cat}
className={`category-link${activeCategory === cat ? " w--current" : ""}`}
onClick={() => setActiveCategory(cat)}
type="button"
>
{cat}
</button>
))}
</div>
</div>
</div>
<div className="mg-top-regular">
<div data-w-id="e99a5296-8389-ce52-d99c-66f9d94e1815" style={{ opacity: "0", filter: "blur(8px)" }} className="w-dyn-list">
<div role="list" className="blog-v1-grid w-dyn-items">
{filtered.map((post) => (
<BlogCard key={post.slug} post={post} variant="grid" />
))}
</div>
</div>
</div>
</div>
</section>
)
}
+3
View File
@@ -0,0 +1,3 @@
export { default as CtaSection } from "./CtaSection"
export { default as HeroSection } from "./HeroSection"
export { default as PostsGridSection } from "./PostsGridSection"