How I Built a Cinematic AI Landing Page with GSAP, Canvas API and Next.js A developer built Digital Rain, a cinematic AI landing page template featuring falling mathematical symbols, liquid glass UI, and GSAP scroll animations using Next.js 15 and Tailwind CSS. The template includes a custom rain animation engine with mouse interaction and premium glassmorphism effects. A deep dive into building Digital Rain — a cinematic AI landing page template with falling math symbols, liquid glass UI, and GSAP scroll animations using Next.js 15 and Tailwind CSS. Most AI landing pages look the same. Hero section. Features grid. Pricing table. Footer. I wanted to build something different — something that makes visitors stop scrolling and say "wow, what is this?" The result is Digital Rain — a cinematic AI landing page with falling mathematical symbols, liquid glass UI, and GSAP-powered scroll animations. Here's exactly how I built it. The concept came from the Matrix — that iconic falling green code effect. But instead of random characters, I wanted mathematical symbols falling and reacting to a simulated liquid surface. The goal was simple: make your AI product feel like a funded startup from day one. Live demo: https://og-ai-next.vercel.app/ https://og-ai-next.vercel.app/ The heart of the template is the custom rain animation engine. Here's how it works: js const canvas = document.createElement 'canvas' ; const ctx = canvas.getContext '2d' ; canvas.width = window.innerWidth; canvas.height = window.innerHeight; Instead of random characters, I used mathematical symbols for a more premium feel: js const symbols = '∑', 'π', '∆', '∫', '√', 'Ω', 'λ', 'φ', '∞', 'θ', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ; js const fontSize = 14; const columns = Math.floor canvas.width / fontSize ; const drops: number = new Array columns .fill 1 ; function drawRain { // Fade effect — creates trail ctx.fillStyle = 'rgba 0, 0, 0, 0.05 '; ctx.fillRect 0, 0, canvas.width, canvas.height ; // Draw symbols ctx.fillStyle = ' 14b575'; // brand green ctx.font = ${fontSize}px monospace ; drops.forEach drop, i = { const symbol = symbols Math.floor Math.random symbols.length ; const x = i fontSize; const y = drop fontSize; ctx.fillStyle = rgba 20, 181, 117, ${Math.random 0.8 + 0.2} ; ctx.fillText symbol, x, y ; // Reset drop randomly if y canvas.height && Math.random 0.975 { drops i = 0; } drops i ++; } ; } // Animate at 30fps setInterval drawRain, 33 ; This is the part that makes it feel interactive. When the mouse moves, nearby symbols react: js let mouseX = 0; let mouseY = 0; window.addEventListener 'mousemove', e = { mouseX = e.clientX; mouseY = e.clientY; } ; // In drawRain function — add proximity effect drops.forEach drop, i = { const x = i fontSize; const y = drop fontSize; const distance = Math.sqrt Math.pow x - mouseX, 2 + Math.pow y - mouseY, 2 ; // Symbols near cursor glow brighter const opacity = distance < 100 ? 1.0 : Math.random 0.8 + 0.2; ctx.fillStyle = rgba 20, 181, 117, ${opacity} ; ctx.fillText symbol, x, y ; } ; Glassmorphism is everywhere but most implementations look cheap. Here's how to make it look premium: .glass-card { background: rgba 255, 255, 255, 0.05 ; backdrop-filter: blur 20px ; -webkit-backdrop-filter: blur 20px ; border: 1px solid rgba 255, 255, 255, 0.1 ; box-shadow: 0 8px 32px rgba 0, 0, 0, 0.3 , inset 0 1px 0 rgba 255, 255, 255, 0.1 ; } The key differences from cheap glassmorphism: GSAP makes scroll animations smooth and performant. Here's the pattern I used for every section: js import { gsap } from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; gsap.registerPlugin ScrollTrigger ; // Fade up animation on scroll gsap.fromTo '.feature-card', { opacity: 0, y: 60 }, { opacity: 1, y: 0, duration: 0.8, stagger: 0.15, ease: 'power3.out', scrollTrigger: { trigger: '.features-section', start: 'top 80%', end: 'bottom 20%', } } ; The stagger: 0.15 is what makes multiple cards animate in sequence rather than all at once. This small detail makes the whole thing feel much more polished. export function Hero { return