Skip to content

Commit fbf2f27

Browse files
hallelx2claude
andcommitted
Add animated brand shader to hero + text-roll CTA
Adapts the Axion hero treatment to Vectorless while keeping our brand: - components/HeroShader.tsx: WebGL background using @paper-design/shaders-react (v0.0.76). Swirl (soft blue-white light base) + GrainGradient (blue→pink color flow with built-in grain). Recolored to brand blue #1456f0 / pink #ea5ec1. Skips FlutedGlass (too "agency" for a dev tool, and absent in this version). The Axion spec's Swirl+ChromaFlow+FilmGrain maps to Swirl+GrainGradient here. - Hero.tsx: layers the shader behind existing content (desktop/lg+ only via `hidden lg:block`), keeps the CSS radial-gradient as the always-on fallback for mobile + prefers-reduced-motion. Content gets z-10 to sit above. Headline, document-card mockup, fonts, pink dot all unchanged. - Primary CTA converted to the Axion text-roll hover: two stacked text copies that slide up 50% on group-hover (duration-500, cubic-bezier(0.25,0.1,0.25,1)), arrow in a white circle rotating -45°. - HeroShader guards: prefers-reduced-motion disables it, mounts after hydration (no SSR/WebGL mismatch), readability mask fades the shader toward center. - next.config.ts: added shader packages to transpilePackages. Verified: tsc --noEmit clean; `next build` compiled successfully (the build's only failure was ENOSPC disk space during static export, unrelated to this change). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8aa93a7 commit fbf2f27

5 files changed

Lines changed: 129 additions & 6 deletions

File tree

apps/web/components/Hero.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
'use client';
22

33
import Link from 'next/link';
4+
import { ArrowRight } from 'lucide-react';
45
import InstallTabs from './InstallTabs';
6+
import HeroShader from './HeroShader';
57
import { useGsapEffect, useScopedRef, gsap } from '@/hooks/useGsap';
68

79
export default function Hero() {
@@ -47,10 +49,19 @@ export default function Hero() {
4749
ref={ref}
4850
className="relative min-h-[100vh] pt-32 pb-24 px-6 md:px-12 overflow-hidden flex items-center"
4951
>
50-
<div className="absolute inset-0 grid-paper [mask-image:radial-gradient(ellipse_at_center,black_30%,transparent_75%)] pointer-events-none" />
52+
{/* CSS gradient fallback — always present; the WebGL shader layers
53+
on top of it on desktop, and is the only background on mobile /
54+
reduced-motion. */}
5155
<div className="absolute -top-32 left-1/2 -translate-x-1/2 w-[1100px] h-[600px] bg-[radial-gradient(ellipse_at_center,rgba(20,86,240,0.10)_0%,rgba(234,94,193,0.05)_40%,transparent_70%)] blur-[40px] pointer-events-none" />
5256

53-
<div className="hero-canvas relative w-full max-w-[1240px] mx-auto grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
57+
{/* Animated brand shader — desktop only (lg+), recolored blue→pink. */}
58+
<div className="hidden lg:block">
59+
<HeroShader />
60+
</div>
61+
62+
<div className="absolute inset-0 grid-paper [mask-image:radial-gradient(ellipse_at_center,black_30%,transparent_75%)] pointer-events-none" />
63+
64+
<div className="hero-canvas relative z-10 w-full max-w-[1240px] mx-auto grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
5465
<div className="lg:col-span-7 text-left">
5566
<div className="hero-chip inline-flex items-center gap-2 px-3 py-1.5 rounded-full border border-border-light bg-white/70 backdrop-blur-sm mb-8 shadow-sm">
5667
<span className="relative flex h-2 w-2">
@@ -110,10 +121,19 @@ export default function Hero() {
110121
<div className="hero-cta flex flex-col sm:flex-row items-start sm:items-center gap-3 mb-3">
111122
<Link
112123
href="/register"
113-
className="group relative inline-flex items-center gap-2 bg-bg-dark text-white px-6 py-3.5 rounded-full text-[15px] font-medium hover:bg-black transition-colors"
124+
className="group relative inline-flex items-center gap-3 bg-bg-dark text-white pl-6 pr-2 py-2 rounded-full text-[15px] font-medium hover:bg-black transition-colors"
114125
>
115-
Start building free
116-
<span className="inline-block transition-transform group-hover:translate-x-1"></span>
126+
{/* Text-roll: two stacked copies, the column slides up 50%
127+
on hover so the second copy rolls into view. */}
128+
<span className="inline-flex overflow-hidden h-[24px]">
129+
<span className="flex flex-col transition-transform duration-500 ease-[cubic-bezier(0.25,0.1,0.25,1)] group-hover:-translate-y-1/2">
130+
<span className="flex h-[24px] items-center">Start building free</span>
131+
<span className="flex h-[24px] items-center">Start building free</span>
132+
</span>
133+
</span>
134+
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-white">
135+
<ArrowRight className="h-4 w-4 text-bg-dark transition-transform duration-500 ease-[cubic-bezier(0.25,0.1,0.25,1)] group-hover:-rotate-45" />
136+
</span>
117137
</Link>
118138
<Link
119139
href="https://github.com/hallelx2/vectorless"

apps/web/components/HeroShader.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
import { Swirl, GrainGradient } from '@paper-design/shaders-react';
5+
6+
/**
7+
* Animated WebGL hero background — adapted from the Axion shader idea
8+
* but recolored to Vectorless brand (blue #1456f0 → pink #ea5ec1) and
9+
* kept deliberately subtle so the headline and document card stay
10+
* readable.
11+
*
12+
* The Axion spec named Swirl + ChromaFlow + FilmGrain. In the installed
13+
* @paper-design/shaders-react (v0.0.76) the equivalent is:
14+
* - Swirl → soft light base (blue-white)
15+
* - GrainGradient → brand color flow WITH built-in grain
16+
* (covers both ChromaFlow's color momentum and
17+
* FilmGrain's texture in one shader)
18+
* We skip FlutedGlass entirely — its refraction reads too "agency"
19+
* for a dev tool (and it isn't in this version anyway).
20+
*
21+
* Guards:
22+
* - Disabled under prefers-reduced-motion (accessibility + battery).
23+
* - Rendered inside the parent's `hidden lg:block` so mobile keeps
24+
* only the lightweight CSS gradient fallback.
25+
* - Mounts after hydration to avoid SSR/WebGL mismatch.
26+
*/
27+
export default function HeroShader() {
28+
const [enabled, setEnabled] = useState(false);
29+
30+
useEffect(() => {
31+
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
32+
if (mq.matches) return;
33+
setEnabled(true);
34+
35+
const onChange = (e: MediaQueryListEvent) => setEnabled(!e.matches);
36+
mq.addEventListener('change', onChange);
37+
return () => mq.removeEventListener('change', onChange);
38+
}, []);
39+
40+
if (!enabled) return null;
41+
42+
const fill = { width: '100%', height: '100%' } as const;
43+
44+
return (
45+
<div className="absolute inset-0 overflow-hidden pointer-events-none">
46+
{/* Base flowing light — soft blue/pink-tinted white swirl */}
47+
<Swirl
48+
className="absolute inset-0 h-full w-full"
49+
colorBack="#ffffff"
50+
colors={['#ffffff', '#eaf0ff', '#f6ecfb']}
51+
bandCount={2}
52+
twist={0.3}
53+
center={0.2}
54+
proportion={0.5}
55+
softness={1}
56+
speed={0.15}
57+
style={fill}
58+
/>
59+
60+
{/* Brand color flow + grain — blue→pink wash on white */}
61+
<GrainGradient
62+
className="absolute inset-0 h-full w-full mix-blend-multiply opacity-70"
63+
colorBack="#ffffff"
64+
colors={['#1456f0', '#ea5ec1']}
65+
shape="wave"
66+
softness={1}
67+
intensity={0.4}
68+
noise={0.15}
69+
speed={0.35}
70+
style={fill}
71+
/>
72+
73+
{/* Readability mask: fade the shader toward center so the headline
74+
and CTAs sit on calmer ground. */}
75+
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,transparent_0%,rgba(255,255,255,0.4)_55%,rgba(255,255,255,0.72)_100%)]" />
76+
</div>
77+
);
78+
}

apps/web/next.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const nextConfig: NextConfig = {
2020
],
2121
},
2222
// output: 'standalone', // Disabled: requires symlink permissions on Windows
23-
transpilePackages: ['motion'],
23+
transpilePackages: ['motion', '@paper-design/shaders-react', '@paper-design/shaders'],
2424
webpack: (config, {dev}) => {
2525
// HMR is disabled in AI Studio via DISABLE_HMR env var.
2626
// Do not modify—file watching is disabled to prevent flickering during agent edits.

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@hookform/resolvers": "^5.2.1",
1616
"@modelcontextprotocol/sdk": "^1.29.0",
1717
"@neondatabase/serverless": "^0.10.0",
18+
"@paper-design/shaders-react": "^0.0.76",
1819
"@radix-ui/react-avatar": "^1.1.10",
1920
"@radix-ui/react-dialog": "^1.1.14",
2021
"@radix-ui/react-dropdown-menu": "^2.1.15",

pnpm-lock.yaml

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)