Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1af3da06d | |||
| 69e0eda1e5 | |||
| afd79e7f80 |
@@ -11,6 +11,7 @@ import SocialProofOne from '@/components/sections/socialProof/SocialProofOne';
|
||||
import TestimonialCardFifteen from '@/components/sections/testimonial/TestimonialCardFifteen';
|
||||
import ContactSplit from '@/components/sections/contact/ContactSplit';
|
||||
import FooterLogoReveal from '@/components/sections/footer/FooterLogoReveal';
|
||||
import CanvasScrollVideo from '@/components/sections/canvas/CanvasScrollVideo';
|
||||
import { Award, Badge, BookOpen, Brain, DollarSign, Eye, Heart, Layers, Lock, Mail, MessageSquare, Sparkles, Target, TrendingUp, Zap, BarChart3 } from 'lucide-react';
|
||||
|
||||
export default function LandingPage() {
|
||||
@@ -67,6 +68,10 @@ export default function LandingPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="canvas-scroll" data-section="canvas-scroll">
|
||||
<CanvasScrollVideo />
|
||||
</div>
|
||||
|
||||
<div id="tutoring" data-section="tutoring">
|
||||
<SplitAbout
|
||||
title="Transparent Tutoring Built on Trust"
|
||||
|
||||
285
src/components/sections/canvas/CanvasScrollVideo.tsx
Normal file
285
src/components/sections/canvas/CanvasScrollVideo.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import gsap from "gsap";
|
||||
import ScrollTrigger from "gsap/ScrollTrigger";
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
interface Particle {
|
||||
x: number;
|
||||
y: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
size: number;
|
||||
opacity: number;
|
||||
hue: number;
|
||||
}
|
||||
|
||||
const CanvasScrollVideo: React.FC = () => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const particlesRef = useRef<Particle[]>([]);
|
||||
const scrollProgressRef = useRef(0);
|
||||
const cameraZRef = useRef(8);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
// Set canvas size
|
||||
const updateCanvasSize = () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
};
|
||||
updateCanvasSize();
|
||||
|
||||
// Initialize particles (levitating books with neon glow)
|
||||
const initializeParticles = () => {
|
||||
const particles: Particle[] = [];
|
||||
for (let i = 0; i < 40; i++) {
|
||||
particles.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height,
|
||||
vx: (Math.random() - 0.5) * 2,
|
||||
vy: (Math.random() - 0.5) * 2,
|
||||
size: Math.random() * 3 + 1,
|
||||
opacity: Math.random() * 0.7 + 0.3,
|
||||
hue: Math.random() * 60 + 240 // Purple to violet range
|
||||
});
|
||||
}
|
||||
particlesRef.current = particles;
|
||||
};
|
||||
initializeParticles();
|
||||
|
||||
// Draw apartment window frame
|
||||
const drawApartmentWindow = (progress: number) => {
|
||||
// Warm sunset-gold background
|
||||
const goldGrad = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
|
||||
goldGrad.addColorStop(0, "#1a1a2e"); // Deep purple
|
||||
goldGrad.addColorStop(0.5, "#d4a574"); // Warm sunset gold
|
||||
goldGrad.addColorStop(1, "#2d1b4e"); // Deep purple
|
||||
ctx.fillStyle = goldGrad;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Window frame (apartment building window)
|
||||
const frameWidth = canvas.width * 0.6;
|
||||
const frameHeight = canvas.height * 0.7;
|
||||
const frameX = (canvas.width - frameWidth) / 2;
|
||||
const frameY = (canvas.height - frameHeight) / 2;
|
||||
|
||||
// Outer frame shadow
|
||||
ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
|
||||
ctx.shadowBlur = 20;
|
||||
ctx.strokeStyle = "#5a3d3d";
|
||||
ctx.lineWidth = 8;
|
||||
ctx.strokeRect(frameX, frameY, frameWidth, frameHeight);
|
||||
ctx.shadowColor = "transparent";
|
||||
|
||||
// Window panes
|
||||
ctx.strokeStyle = "#8b7355";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(frameX + frameWidth / 2, frameY);
|
||||
ctx.lineTo(frameX + frameWidth / 2, frameY + frameHeight);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(frameX, frameY + frameHeight / 2);
|
||||
ctx.lineTo(frameX + frameWidth, frameY + frameHeight / 2);
|
||||
ctx.stroke();
|
||||
|
||||
// Inner glass reflection (gradient)
|
||||
const glassGrad = ctx.createLinearGradient(frameX, frameY, frameX + frameWidth, frameY + frameHeight);
|
||||
glassGrad.addColorStop(0, "rgba(255, 255, 255, 0.05)");
|
||||
glassGrad.addColorStop(0.5, "rgba(255, 255, 255, 0)");
|
||||
glassGrad.addColorStop(1, "rgba(212, 165, 116, 0.1)");
|
||||
ctx.fillStyle = glassGrad;
|
||||
ctx.fillRect(frameX, frameY, frameWidth, frameHeight);
|
||||
};
|
||||
|
||||
// Draw study room elements
|
||||
const drawStudyRoom = (progress: number) => {
|
||||
const roomWidth = canvas.width * 0.5;
|
||||
const roomHeight = canvas.height * 0.5;
|
||||
const roomX = (canvas.width - roomWidth) / 2;
|
||||
const roomY = (canvas.height - roomHeight) / 2;
|
||||
|
||||
// Room background with warm tones
|
||||
const roomGrad = ctx.createLinearGradient(roomX, roomY, roomX + roomWidth, roomY + roomHeight);
|
||||
roomGrad.addColorStop(0, "#3d2817");
|
||||
roomGrad.addColorStop(0.5, "#6b4226");
|
||||
roomGrad.addColorStop(1, "#2a1810");
|
||||
ctx.fillStyle = roomGrad;
|
||||
ctx.fillRect(roomX, roomY, roomWidth, roomHeight);
|
||||
|
||||
// Desk
|
||||
ctx.fillStyle = "#8b6f47";
|
||||
ctx.fillRect(roomX + roomWidth * 0.1, roomY + roomHeight * 0.6, roomWidth * 0.8, roomHeight * 0.3);
|
||||
ctx.fillStyle = "#5a4a2a";
|
||||
ctx.fillRect(roomX + roomWidth * 0.1, roomY + roomHeight * 0.5, roomWidth * 0.8, roomHeight * 0.1);
|
||||
|
||||
// Teenager at desk (simplified silhouette)
|
||||
ctx.fillStyle = "#1a0a0a";
|
||||
// Head
|
||||
ctx.beginPath();
|
||||
ctx.arc(roomX + roomWidth * 0.3, roomY + roomHeight * 0.35, roomHeight * 0.08, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
// Torso
|
||||
ctx.fillRect(roomX + roomWidth * 0.25, roomY + roomHeight * 0.42, roomWidth * 0.1, roomHeight * 0.15);
|
||||
// Arms
|
||||
ctx.fillRect(roomX + roomWidth * 0.15, roomY + roomHeight * 0.48, roomWidth * 0.15, roomHeight * 0.04);
|
||||
};
|
||||
|
||||
// Draw levitating books with neon glow
|
||||
const drawLevitatingBooks = (progress: number) => {
|
||||
const bookPositions = [
|
||||
{ x: 0.2, y: 0.25, rotation: Math.sin(progress * Math.PI * 2) * 0.3 },
|
||||
{ x: 0.5, y: 0.15, rotation: Math.cos(progress * Math.PI * 2) * 0.3 },
|
||||
{ x: 0.75, y: 0.3, rotation: Math.sin(progress * Math.PI * 2 + Math.PI / 3) * 0.3 },
|
||||
{ x: 0.35, y: 0.05, rotation: Math.cos(progress * Math.PI * 2 + Math.PI / 2) * 0.3 }
|
||||
];
|
||||
|
||||
bookPositions.forEach((pos, idx) => {
|
||||
const bookX = canvas.width * pos.x;
|
||||
const bookY = canvas.height * pos.y + Math.sin(progress * Math.PI * 2 + idx) * 15;
|
||||
|
||||
// Book neon glow
|
||||
ctx.shadowColor = `hsl(${240 + idx * 30}, 100%, 50%)`;
|
||||
ctx.shadowBlur = 25;
|
||||
ctx.shadowOffsetX = 0;
|
||||
ctx.shadowOffsetY = 0;
|
||||
|
||||
// Book shape
|
||||
ctx.save();
|
||||
ctx.translate(bookX, bookY);
|
||||
ctx.rotate(pos.rotation);
|
||||
ctx.fillStyle = `hsl(${240 + idx * 30}, 100%, 35%)`;
|
||||
ctx.fillRect(-20, -30, 40, 60);
|
||||
ctx.strokeStyle = `hsl(${240 + idx * 30}, 100%, 60%)`;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(-20, -30, 40, 60);
|
||||
ctx.restore();
|
||||
});
|
||||
|
||||
ctx.shadowColor = "transparent";
|
||||
};
|
||||
|
||||
// Draw glowing math symbols
|
||||
const drawMathSymbols = (progress: number) => {
|
||||
const symbols = ["∫", "∑", "∞", "π", "√", "≈", "±"];
|
||||
symbols.forEach((symbol, idx) => {
|
||||
const x = canvas.width * (0.15 + (idx * 0.12));
|
||||
const y = canvas.height * (0.45 + Math.sin(progress * Math.PI * 2 + idx) * 0.1);
|
||||
const hue = 270 + idx * 15;
|
||||
|
||||
ctx.save();
|
||||
ctx.shadowColor = `hsl(${hue}, 100%, 50%)`;
|
||||
ctx.shadowBlur = 15;
|
||||
ctx.font = "italic 28px serif";
|
||||
ctx.fillStyle = `hsl(${hue}, 100%, 60%)`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.globalAlpha = 0.6 + Math.sin(progress * Math.PI * 2 + idx) * 0.3;
|
||||
ctx.fillText(symbol, x, y);
|
||||
ctx.restore();
|
||||
});
|
||||
};
|
||||
|
||||
// Draw particles (levitating magical elements)
|
||||
const drawParticles = () => {
|
||||
particlesRef.current.forEach((particle) => {
|
||||
particle.x += particle.vx;
|
||||
particle.y += particle.vy;
|
||||
|
||||
// Bounce off edges
|
||||
if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1;
|
||||
if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1;
|
||||
|
||||
// Neon glow effect
|
||||
ctx.shadowColor = `hsl(${particle.hue}, 100%, 50%)`;
|
||||
ctx.shadowBlur = 10;
|
||||
ctx.fillStyle = `hsla(${particle.hue}, 100%, 50%, ${particle.opacity})`;
|
||||
ctx.beginPath();
|
||||
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
};
|
||||
|
||||
// Animation loop
|
||||
const animate = () => {
|
||||
const progress = scrollProgressRef.current;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Draw background and apartment window
|
||||
drawApartmentWindow(progress);
|
||||
|
||||
// Camera push-in effect (zoom in as progress increases)
|
||||
ctx.save();
|
||||
const zoomLevel = 1 + progress * 0.5;
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.scale(zoomLevel, zoomLevel);
|
||||
ctx.translate(-canvas.width / 2, -canvas.height / 2);
|
||||
|
||||
// Draw study room elements
|
||||
drawStudyRoom(progress);
|
||||
drawLevitatingBooks(progress);
|
||||
drawMathSymbols(progress);
|
||||
drawParticles();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
// Setup scroll trigger
|
||||
if (containerRef.current) {
|
||||
ScrollTrigger.create({
|
||||
trigger: containerRef.current,
|
||||
onUpdate: (self) => {
|
||||
scrollProgressRef.current = self.progress;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle window resize
|
||||
const handleResize = () => {
|
||||
updateCanvasSize();
|
||||
};
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative w-full h-screen overflow-hidden bg-gradient-to-b from-purple-900 via-amber-800 to-purple-900"
|
||||
style={{
|
||||
background: "linear-gradient(180deg, #1a1a2e 0%, #d4a574 50%, #2d1b4e 100%)"
|
||||
}}
|
||||
>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="w-full h-full"
|
||||
style={{
|
||||
display: "block", background: "transparent"
|
||||
}}
|
||||
/>
|
||||
{/* Optional: Add decorative elements around the canvas */}
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CanvasScrollVideo;
|
||||
Reference in New Issue
Block a user