diff --git a/Components/animations/CloudAnimation.jsx b/Components/animations/CloudAnimation.jsx
index 4711562..a7b57ea 100644
--- a/Components/animations/CloudAnimation.jsx
+++ b/Components/animations/CloudAnimation.jsx
@@ -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 (
+
+ );
+});
+
export default function CloudAnimation() {
const { scrollYProgress } = useScroll();
@@ -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 }) => (
-
- );
-
// Cloud configurations: { scale, topOffset, xMotion, opacity, width, height }
const cloudConfigs = [
{ scale: 1.2, topOffset: 0, xMotion: x, opacity: 0.85, width: 180, height: 108 },
diff --git a/Components/animations/TurbineAnimation.jsx b/Components/animations/TurbineAnimation.jsx
index 6198bf5..d1b80c9 100644
--- a/Components/animations/TurbineAnimation.jsx
+++ b/Components/animations/TurbineAnimation.jsx
@@ -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
@@ -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));
@@ -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() {
@@ -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 (
<>