Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 21 additions & 39 deletions Components/animations/CloudAnimation.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
"use client"
import React from "react";
import { motion, useScroll, useTransform, useSpring } from "framer-motion";
import { motion, useScroll, useTransform, useSpring, useMotionTemplate } from "framer-motion";
import styles from "@/styles/Home.module.css";

const CloudSVG = React.memo(function CloudSVG({ width = 150, height = 90 }) {
return (
<svg viewBox="0 0 100 60" width={width} height={height} fill="white">
<path d="M 10,35 Q 5,20 20,15 Q 25,5 35,8 Q 45,0 55,10 Q 70,8 75,20 Q 85,15 90,30 Q 92,45 80,52 Q 70,58 55,55 Q 40,60 25,55 Q 10,58 5,45 Q 0,40 10,35 Z" />
</svg>
);
});

export default function CloudAnimation() {
const { scrollYProgress } = useScroll();

Expand Down Expand Up @@ -40,55 +48,29 @@ export default function CloudAnimation() {
const rawY = useTransform(easedProgress, [0, 1], ["0vh", "20vh"]);
const y = useSpring(rawY, { stiffness: 120, damping: 20 });

// Horizontal movement (left to right, off-screen to off-screen)
const rawX = useTransform(scrollYProgress, [0, rotateInputEnd], ["-120vw", "120vw"]);
const x = useSpring(rawX, { stiffness: 120, damping: 20 });
// Horizontal movement in numeric vw units (avoids string parsing each frame)
const rawXVw = useTransform(scrollYProgress, [0, rotateInputEnd], [-120, 120]);
const xVw = useSpring(rawXVw, { stiffness: 120, damping: 20 });
const x = useMotionTemplate`${xVw}vw`;

// Offset horizontal movement for second cloud
const x2 = useTransform(x, (xVal) => {
if (typeof xVal === 'string') {
const num = parseFloat(xVal);
return `${num * 0.7}vw`;
}
return `${Number(xVal) * 0.7}vw`;
});
const x2Vw = useTransform(xVw, (xVal) => xVal * 0.7);
const x2 = useMotionTemplate`${x2Vw}vw`;

// Create offset transforms for each cloud
const x3 = useTransform(x, (xVal) => {
if (typeof xVal === 'string') {
const num = parseFloat(xVal);
return `${num * 0.5}vw`;
}
return `${Number(xVal) * 0.5}vw`;
});
const x3Vw = useTransform(xVw, (xVal) => xVal * 0.5);
const x3 = useMotionTemplate`${x3Vw}vw`;

const x4 = useTransform(x, (xVal) => {
if (typeof xVal === 'string') {
const num = parseFloat(xVal);
return `${num * 0.85}vw`;
}
return `${Number(xVal) * 0.85}vw`;
});
const x4Vw = useTransform(xVw, (xVal) => xVal * 0.85);
const x4 = useMotionTemplate`${x4Vw}vw`;

const x5 = useTransform(x, (xVal) => {
if (typeof xVal === 'string') {
const num = parseFloat(xVal);
return `${num * 0.6}vw`;
}
return `${Number(xVal) * 0.6}vw`;
});
const x5Vw = useTransform(xVw, (xVal) => xVal * 0.6);
const x5 = useMotionTemplate`${x5Vw}vw`;

const startOffset = 55;
const topPosition = `calc(50% - ${startOffset}px)`;


//Define cloud SVG (I totally did not do this LOL)
const CloudSVG = ({ width = 150, height = 90 }) => (
<svg viewBox="0 0 100 60" width={width} height={height} fill="white">
<path d="M 10,35 Q 5,20 20,15 Q 25,5 35,8 Q 45,0 55,10 Q 70,8 75,20 Q 85,15 90,30 Q 92,45 80,52 Q 70,58 55,55 Q 40,60 25,55 Q 10,58 5,45 Q 0,40 10,35 Z" />
</svg>
);

// Cloud configurations: { scale, topOffset, xMotion, opacity, width, height }
const cloudConfigs = [
{ scale: 1.2, topOffset: 0, xMotion: x, opacity: 0.85, width: 180, height: 108 },
Expand Down
58 changes: 31 additions & 27 deletions Components/animations/TurbineAnimation.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"use client"
import React from "react";
import { motion, useScroll, useTransform, useSpring } from "framer-motion";
import { motion, useScroll, useTransform, useSpring, useMotionValueEvent } from "framer-motion";
import TurbineBlades from "./TurbineBlades";
import CloudAnimation from "./CloudAnimation";
import styles from "@/styles/Home.module.css";

export default function TurbineScrollAnimation() {
const { scrollYProgress } = useScroll();
const mainAboutRef = React.useRef(null);
const aboutVisibleRef = React.useRef(false);

// progress value (0..1) at which the about section reaches the top of the
// viewport. We'll compute this on mount and on resize so we can map the
Expand Down Expand Up @@ -64,6 +66,7 @@ export default function TurbineScrollAnimation() {
const rotateInputEnd = triggerProgress > 0
? Math.max(0.01, triggerProgress * completionMultiplier)
: completionMultiplier;
const rotateInputEndRef = React.useRef(rotateInputEnd);

const localProgress = useTransform(scrollYProgress, [0, rotateInputEnd], [0, 1]);
const easedProgress = useTransform(localProgress, (v) => Math.pow(v, easePower));
Expand All @@ -78,33 +81,35 @@ export default function TurbineScrollAnimation() {
const rotate = useSpring(rawRotate, { stiffness: 120, damping: 20 });

React.useEffect(() => {
mainAboutRef.current = document.querySelector("." + styles.mainAbout);
}, []);

useMotionValueEvent(rotate, "change", (v) => {
const revealThreshold = 0.9 * 1080; // 90% of 1080deg
const unsubscribe = rotate.onChange((v) => {
const aboutEl = document.querySelector("." + styles.mainAbout);
if (!aboutEl) return;
if (v >= revealThreshold) {
if (!aboutEl.classList.contains(styles.mainAboutVisible)) {
aboutEl.classList.add(styles.mainAboutVisible);
}
} else {
if (aboutEl.classList.contains(styles.mainAboutVisible)) {
aboutEl.classList.remove(styles.mainAboutVisible);
}
}
});
return () => unsubscribe();
}, [rotate]);
const aboutEl = mainAboutRef.current || document.querySelector("." + styles.mainAbout);
if (!aboutEl) return;
mainAboutRef.current = aboutEl;
const shouldShow = v >= revealThreshold;
if (aboutVisibleRef.current !== shouldShow) {
aboutVisibleRef.current = shouldShow;
aboutEl.classList.toggle(styles.mainAboutVisible, shouldShow);
}
});

// Position turbine to align with the center of the logo in the hero section
// Hero section is 60vh, and logo is centered within it
// We want the turbine at roughly 30vh from top (center of 60vh hero section)
const startOffset = 0;
const topPosition = `40vh`;

// Track if animation is complete and the absolute position to stick to
const [isAnimationComplete, setIsAnimationComplete] = React.useState(false);
const isAnimationCompleteRef = React.useRef(false);
const [stickyTop, setStickyTop] = React.useState(0);

React.useEffect(() => {
rotateInputEndRef.current = rotateInputEnd;
}, [rotateInputEnd]);

React.useEffect(() => {
// Calculate the sticky position once on mount/resize, not based on scroll
function computeStickyPosition() {
Expand Down Expand Up @@ -136,20 +141,19 @@ export default function TurbineScrollAnimation() {
window.addEventListener("resize", computeStickyPosition);
const t = setTimeout(computeStickyPosition, 300);

const unsubscribe = scrollYProgress.onChange((v) => {
if (v >= rotateInputEnd && !isAnimationComplete) {
setIsAnimationComplete(true);
} else if (v < rotateInputEnd && isAnimationComplete) {
setIsAnimationComplete(false);
}
});

return () => {
unsubscribe();
window.removeEventListener("resize", computeStickyPosition);
clearTimeout(t);
};
}, [scrollYProgress, rotateInputEnd, isAnimationComplete, startOffset]);
}, []);

useMotionValueEvent(scrollYProgress, "change", (v) => {
const nextComplete = v >= rotateInputEndRef.current;
if (isAnimationCompleteRef.current !== nextComplete) {
isAnimationCompleteRef.current = nextComplete;
setIsAnimationComplete(nextComplete);
}
});

return (
<>
Expand Down