Build a single-page Next.js hero landing page for a luxury hypercar brand called Aspirox. All styles must be written as a self-contained <style> tag inside the component (no external CSS files). The page is a single "use client" React component with minimal interactivity.
Tech Stack
Next.js (App Router), TypeScript
All CSS self-contained in a <style> JSX block
No Tailwind, no external component libraries
Fonts (Google Fonts import at top of the <style> block)
@import url('
fonts.googleapis.com/css2?fa…');
Display / Headline: Big Shoulders Display, weight 900 → used for the giant brand title
Serif / Nav links: Cormorant Garamond, weight 300/400 → used for nav items
Mono / Body: Space Mono → everything else (contact info, ticker, badges)
CSS Design Tokens (:root variables)
css
--color-text:
#ffffff;
--color-bg-fallback:
#04121a;
--color-accent:
#bf1525;
--color-grid-line: rgba(255, 255, 255, 0.12);
--color-dot-glow: rgba(255, 255, 255, 0.8);
--color-social-bg: rgba(255, 255, 255, 0.08);
--color-social-bg-hover:
#ffffff;
--color-social-icon-hover:
#051a24;
--font-serif: 'Cormorant Garamond', serif;
--font-mono: 'Space Mono', monospace;
--font-display: 'Big Shoulders Display', sans-serif;
--ticker-height: 48px;
html, body: overflow: hidden, background --color-bg-fallback, font --font-mono.
Layout: 3 Layers (stacking order)
Layer 1 — Background (z-index 0–2)
A .bg-container (position: absolute, covers 100% w/h):
Video element .bg-video: position: absolute, object-fit: cover, opacity: 0.65, filter: contrast(1.1) brightness(0.9), z-index: 1.Source:
cdn.jiro.build/Nabil/Aspirox…
Poster fallback: /luxury_hypercar_bg.png
Attributes: autoPlay loop muted playsInline
Gradient overlay .bg-gradient-overlay: position: absolute, z-index: 2, pointer-events: none.Gradient: linear-gradient(180deg, rgba(14,74,96,0.8) 0%, rgba(8,33,46,0.6) 30%, rgba(92,9,17,0.7) 70%, rgba(191,21,37,0.9) 100%)
Layer 2 — Interactive Grid (position: fixed, z-index: 1)
.grid-overlay:
display: grid, grid-template-columns: repeat(4, 1fr), grid-template-rows: 35vh 30vh 1fr
Height: calc(100vh - var(--ticker-height))
pointer-events: none on the overlay itself, but each cell has mouse events
12 grid cells (3 rows × 4 cols):
Each .grid-cell has border-right and border-bottom set to var(--color-grid-line) (1px solid)
Last cell in each row (nth-child(4n)) has border-right: none
Cells where index % 4 !== 0 get an intersection dot .intersection-dot:5×5px white diamond (transform: rotate(45deg))
Positioned at left: -2px, bottom: -2.5px
On hover: scale 1.8×, color changes to --color-accent, red glow box-shadow
On hover, the cell gets class active-line-h which adds a pseudo-element: a glowing horizontal line at the bottom edge (white gradient, pulsing animation)
State tracked via useState<number | null>(null) for hoveredCell
Layer 3 — Hero Content (position: relative, z-index: 2)
.hero-content: full viewport height, display: flex, flex-direction: column, justify-content: space-between
Content Structure
Top Row .hero-top-row
display: grid, grid-template-columns: repeat(4, 1fr), height: 35vh, padding-top: 6vh
Column 1: Navigation .nav-menu (padding-left: 3.5vw) Nav items array: ["Main", "Heritage", "Speedster", "Gallery", "Team"]
Each item rendered as <a> with class .nav-link
Font: --font-serif, size 2.3rem, weight 300
Active item: full white. Inactive: rgba(255,255,255,0.8)
Hover: slide right translateX(8px), italic, text-shadow glow
State: useState<string>("Main") → activeLink, set onClick
Columns 2 & 3: Empty spacers (visual breathing room)
Column 4: Contacts Panel .contacts-panel (padding: 0 3vw, font-size 0.78rem, color rgba(255,255,255,0.85))
Badge: .contacts-badge → .CONTACTS — pill shape with border, border-radius: 20px, background: rgba(255,255,255,0.05), margin-bottom: 2.2rem
Three contact groups (.contacts-group):Address: L71-75 Shelton Street, Covent Garden, London, WC2H 9JQ, United Kingdom
Email links (.mono-link): enquiries@aspiroxmotors.com and press@aspiroxmotors.com — hover shows red underline that scales in from right-to-left
Company: Aspirox Motorcar Company Limited - Company number 15044627
Bottom Row .hero-bottom-row
display: grid, grid-template-columns: repeat(4, 1fr), height: 35vh, align-items: end, padding-bottom: 4vh
Columns 1–3 span: Brand Title .brand-title-container (grid-column: 1 / span 3, padding-left: 3vw)
<h1 className="brand-title">ASPIROX 1</h1>
Font: --font-display, size 15vw, weight 900, line-height: 0.76, letter-spacing: -0.06em
Transform: translateY(1.5vw) (overflows slightly below viewport for dramatic effect)
Hover: subtle text-shadow glow
Column 4: Social Links .socials-container (display: flex, gap: 0.8rem, padding-left: 3vw) Four .social-circle links (48×48px, border-radius: 50%, glass-morph background):
Instagram — box icon with circle
LinkedIn — path icon
X/Twitter — SVG fill icon (16×16)
YouTube — play button in rectangle
Hover on social circles: white background, dark icon, translateY(-6px), white box-shadow glow.
Footer: Ticker Marquee .ticker-footer
height: var(--ticker-height) = 48px, background-color:
#ffffff, white strip
.ticker-track: display: inline-flex, gap: 4.5rem, animated with
@keyframes scroll-tickerAnimation: translateX(0) → translateX(-50%), duration 28s linear infinite
Pauses on hover (.ticker-container:hover .ticker-track)
Items (rendered twice for seamless loop): "POWERED BY REVAMPLY", "CLIMATE STATEMENT", "TERMS & CONDITIONS", "PRIVACY POLICY", "MADE IN DIGITAL BUTLERS", "© COPYRIGHT 2026. POWERED BY REVAMPLY", …
Item style: font-family: --font-mono, font-size: 0.68rem, font-weight: 700, color: #000000, letter-spacing: 0.1em
Animations
css
@keyframes scroll-ticker {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
@keyframes pulse-line {
0% { opacity: 0.5; }
100% { opacity: 1; }
}
Responsive Breakpoints
≤1024px:
Nav links: 2rem
Brand title: 16vw
Social circles: 44×44px
≤768px:
Grid overlay switches to 2 columns (repeat(2, 1fr))
hero-top-row and hero-bottom-row switch to single column, stacked
Brand title: 19vw, no transform
Everything uses 6vw padding-left
overflow: hidden removed from body to allow scroll; hero-content uses min-height: 100vh
State
tsx
const [activeLink, setActiveLink] = useState<string>("Main");
const [hoveredCell, setHoveredCell] = useState<number | null>(null);
Key Visual Details to Not Miss
The grid dots are diamond-shaped (rotated square), not circles
The brand title slightly overflows the viewport bottom — this is intentional
The gradient overlay makes the bottom of the page red/crimson, creating a ground-level glow effect
The social icons are SVG inline — no icon library
The ticker is doubled (items rendered twice) for a seamless infinite loop
overflow: hidden on html, body is what makes the page feel like an app (no scroll on desktop)
The contact email links use a custom ::after underline that animates in via