Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3556cbe29a | |||
| a1af3da06d | |||
| 69e0eda1e5 | |||
| afd79e7f80 |
@@ -11,6 +11,7 @@ import SocialProofOne from '@/components/sections/socialProof/SocialProofOne';
|
|||||||
import TestimonialCardFifteen from '@/components/sections/testimonial/TestimonialCardFifteen';
|
import TestimonialCardFifteen from '@/components/sections/testimonial/TestimonialCardFifteen';
|
||||||
import ContactSplit from '@/components/sections/contact/ContactSplit';
|
import ContactSplit from '@/components/sections/contact/ContactSplit';
|
||||||
import FooterLogoReveal from '@/components/sections/footer/FooterLogoReveal';
|
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';
|
import { Award, Badge, BookOpen, Brain, DollarSign, Eye, Heart, Layers, Lock, Mail, MessageSquare, Sparkles, Target, TrendingUp, Zap, BarChart3 } from 'lucide-react';
|
||||||
|
|
||||||
export default function LandingPage() {
|
export default function LandingPage() {
|
||||||
@@ -67,6 +68,10 @@ export default function LandingPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="canvas-scroll" data-section="canvas-scroll">
|
||||||
|
<CanvasScrollVideo />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="tutoring" data-section="tutoring">
|
<div id="tutoring" data-section="tutoring">
|
||||||
<SplitAbout
|
<SplitAbout
|
||||||
title="Transparent Tutoring Built on Trust"
|
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