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:
@@ -0,0 +1,210 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect, useCallback } from "react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import ThemeToggle from "@/components/ThemeToggle"
|
||||
|
||||
const NAV_LINKS = [
|
||||
{ href: "/", label: "Home" },
|
||||
{ href: "/about", label: "About" },
|
||||
{ href: "/blog", label: "Blog" },
|
||||
{ href: "/contact", label: "Contact" },
|
||||
]
|
||||
|
||||
function ChevronIcon() {
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M15.5 7.03847L10 12.9615L4.5 7.03847" stroke="currentColor" strokeWidth="1.5" strokeLinecap="square" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Header() {
|
||||
const [mobileOpen, setMobileOpen] = useState(false)
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
const pathname = usePathname()
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setScrolled(window.scrollY > 20)
|
||||
window.addEventListener("scroll", onScroll, { passive: true })
|
||||
return () => window.removeEventListener("scroll", onScroll)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setMobileOpen(false)
|
||||
setDropdownOpen(false)
|
||||
}, [pathname])
|
||||
|
||||
const toggleMobile = useCallback(() => setMobileOpen((v) => !v), [])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`header-wrapper w-nav ${scrolled ? "header-scrolled" : ""}`}
|
||||
data-animation="default"
|
||||
data-easing2="ease"
|
||||
data-easing="ease"
|
||||
data-collapse="medium"
|
||||
role="banner"
|
||||
data-duration="300"
|
||||
>
|
||||
<div className="header-content">
|
||||
<div className="header-logo-wrapper">
|
||||
<div className="show-light-mode">
|
||||
<Link href="/" className="logo-link w-inline-block">
|
||||
<Image src="/assets/wubflow-shield-nocodexport-dev/68a342b7066c56fa60eb3af1/68a43b922d3d1ca923f36385_logo-icon-quantum-webflow-template.svg" width={54} height={54} alt="Logo" className="logo-icon" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="show-dark-mode">
|
||||
<Link href="/" className="logo-link w-inline-block">
|
||||
<Image src="/assets/wubflow-shield-nocodexport-dev/68a342b7066c56fa60eb3af1/68a73cf7c58867b86184dc28_logo-icon-quantum-webflow-template.svg" width={54} height={54} alt="Logo" className="logo-icon" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="header-right-side">
|
||||
<nav
|
||||
role="navigation"
|
||||
className={`nav-menu w-nav-menu ${mobileOpen ? "w--open" : ""}`}
|
||||
>
|
||||
<ul role="list" className="list-nav-menu w-list-unstyled">
|
||||
{NAV_LINKS.map(({ href, label }) => (
|
||||
<li key={href} className="link-nav-item sibling-blur-item">
|
||||
<Link
|
||||
href={href}
|
||||
className={`link w-inline-block ${pathname === href ? "w--current" : ""}`}
|
||||
>
|
||||
<div className="link-icon-wrapper">
|
||||
<div className="link-icon">
|
||||
<ChevronIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div className="link-text">{label}</div>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
<li className="link-nav-item sibling-blur-item">
|
||||
<div
|
||||
className={`header-dropdown w-dropdown ${dropdownOpen ? "w--open" : ""}`}
|
||||
onMouseEnter={() => setDropdownOpen(true)}
|
||||
onMouseLeave={() => setDropdownOpen(false)}
|
||||
>
|
||||
<button
|
||||
className="dropdown-toggle w-dropdown-toggle"
|
||||
onClick={() => setDropdownOpen((v) => !v)}
|
||||
aria-expanded={dropdownOpen}
|
||||
type="button"
|
||||
>
|
||||
<div>Pages</div>
|
||||
<div className="dropdown-icon">
|
||||
<DropdownIcon />
|
||||
</div>
|
||||
</button>
|
||||
{dropdownOpen && (
|
||||
<nav className="dropdown-content-wrapper w-dropdown-list w--open">
|
||||
<div className="card header-dropdown-card">
|
||||
<div className="w-layout-grid header-dropdown-grid">
|
||||
<div>
|
||||
<div className="dropdown-title">Main pages</div>
|
||||
<div className="w-layout-grid main-pages-grid">
|
||||
<div className="w-layout-grid pages-column">
|
||||
{[
|
||||
{ href: "/", label: "Home" },
|
||||
{ href: "/about", label: "About" },
|
||||
{ href: "/contact", label: "Contact" },
|
||||
].map(({ href, label }) => (
|
||||
<Link key={href} href={href} className="link w-inline-block">
|
||||
<div className="link-icon-wrapper">
|
||||
<div className="link-icon"><ChevronIcon /></div>
|
||||
</div>
|
||||
<div className="link-text">{label}</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-layout-grid pages-column">
|
||||
{[
|
||||
{ href: "/blog", label: "Blog" },
|
||||
].map(({ href, label }) => (
|
||||
<Link key={href} href={href} className="link w-inline-block">
|
||||
<div className="link-icon-wrapper">
|
||||
<div className="link-icon"><ChevronIcon /></div>
|
||||
</div>
|
||||
<div className="link-text">{label}</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
<li className="link-nav-item mbl-button">
|
||||
<Link href="/contact" className="primary-button w-inline-block">
|
||||
<div className="button-content">
|
||||
<div>Join our team</div>
|
||||
<div className="button-icon-wrapper primary">
|
||||
<ChevronIcon />
|
||||
<div className="button-icon-bg" />
|
||||
<div className="button-icon-bg-inside" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="hidden-on-mobile-landscape">
|
||||
<div className="show-light-mode">
|
||||
<Link href="/contact" className="primary-button w-inline-block">
|
||||
<div className="button-content">
|
||||
<div>Join our team</div>
|
||||
<div className="button-icon-wrapper primary">
|
||||
<ChevronIcon />
|
||||
<div className="button-icon-bg" />
|
||||
<div className="button-icon-bg-inside" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="show-dark-mode">
|
||||
<Link href="/contact" className="secondary-button w-inline-block">
|
||||
<div className="button-content">
|
||||
<div>Join our team</div>
|
||||
<div className="button-icon-wrapper secondary">
|
||||
<ChevronIcon />
|
||||
<div className="button-icon-bg bg-neutral-800" />
|
||||
<div className="button-icon-bg-inside bg-neutral-600" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
<button
|
||||
className={`hamburger-menu w-nav-button ${mobileOpen ? "w--open" : ""}`}
|
||||
onClick={toggleMobile}
|
||||
aria-label="Toggle menu"
|
||||
type="button"
|
||||
>
|
||||
<div className="hamburger-menu-flex">
|
||||
<div className="hamburger-menu-line top" />
|
||||
<div className="hamburger-menu-line bottom" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{mobileOpen && <div className="w-nav-overlay w--open" onClick={() => setMobileOpen(false)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user