179 lines
7.0 KiB
JavaScript
179 lines
7.0 KiB
JavaScript
import { ZWA2RenderAnimation } from "./render-animations.js"; // Adjust path if needed
|
|
import { ZWA2Animations } from "animations";
|
|
import { ConnectHeader } from "header";
|
|
import { Simon } from "simon";
|
|
|
|
// A flag to ensure animations are only initialized once.
|
|
let animationsLoaded = false;
|
|
// let smoother = ScrollSmoother.create({
|
|
// smooth: 2,
|
|
// effects: true
|
|
// });
|
|
|
|
/**
|
|
* Checks the screen width and initializes the animations if the screen is
|
|
* large enough and they haven't been loaded yet.
|
|
*/
|
|
function maybeLoadAnimations() {
|
|
// Only run on larger screens to save resources on mobile.
|
|
if (window.innerWidth >= 1024 && !animationsLoaded) {
|
|
animationsLoaded = true;
|
|
// --- Scene 1 Configuration ---
|
|
const scene1Sections = [
|
|
// The hero section autoplays from frame 0 to 186, then the user
|
|
// can scroll-animate it from frame 186 to 246.
|
|
//{ selector: "#hero", start: 186, end: 246, autoplay: { start: 0, end: 186, duration: 1200 } },
|
|
{ selector: "#hero", start: 0, end: 246 },
|
|
{ selector: "#features", start: 246, end: 314 },
|
|
{ selector: "#chipset", start: 314, end: 386 },
|
|
// The last section just holds the final frame.
|
|
{ selector: "#long-range", start: 386, end: 386 }
|
|
];
|
|
const scene1TotalFrames = scene1Sections[scene1Sections.length - 1].end;
|
|
const scene1 = new ZWA2RenderAnimation("scene1", "canvas.render-scroller#scene-one", scene1Sections, scene1TotalFrames);
|
|
scene1.start();
|
|
|
|
// Deferred Scene 2 loading via IntersectionObserver
|
|
const scene2Sections = [
|
|
{ selector: "#built-for-home-assistant", start: 63, end: 135 },
|
|
{ selector: "#plug-and-play", start: 135, end: 201 },
|
|
{ selector: "#buy", start: 201, end: 201 }
|
|
];
|
|
const scene2TotalFrames = scene2Sections[scene2Sections.length - 1].end;
|
|
const scene2 = new ZWA2RenderAnimation("scene2", "canvas.render-scroller#scene-two", scene2Sections, scene2TotalFrames);
|
|
const initScene2 = () => {
|
|
if (initScene2._done) return; // idempotent
|
|
initScene2._done = true;
|
|
scene2.start();
|
|
};
|
|
|
|
const scene2Canvas = document.querySelector('#built-for-home-assistant');
|
|
if (scene2Canvas) {
|
|
// Trigger when the top of scene-two canvas reaches the bottom edge of the viewport
|
|
const observer = new IntersectionObserver((entries, obs) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
initScene2();
|
|
obs.disconnect();
|
|
}
|
|
});
|
|
}, {
|
|
root: null,
|
|
threshold: 0,
|
|
rootMargin: '0px 0px -100% 0px' // shrink root bottom by 100% viewport height
|
|
});
|
|
observer.observe(scene2Canvas);
|
|
} else {
|
|
// Fallback: if canvas not found yet, retry on next frame
|
|
requestAnimationFrame(() => {
|
|
const retryCanvas = document.querySelector('canvas.render-scroller#scene-two');
|
|
if (retryCanvas) {
|
|
const observer = new IntersectionObserver((entries, obs) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
initScene2();
|
|
obs.disconnect();
|
|
}
|
|
});
|
|
}, {
|
|
root: null,
|
|
threshold: 0,
|
|
rootMargin: '0px 0px -100% 0px'
|
|
});
|
|
observer.observe(retryCanvas);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Initial Setup ---
|
|
|
|
// Initialize other page components.
|
|
new ConnectHeader();
|
|
|
|
// A simple utility to hide the canvas for debugging purposes.
|
|
if (window.location.search.includes("hide")) {
|
|
document.querySelectorAll(".animation-wrapper canvas").forEach(canvas => {
|
|
canvas.style.display = "none";
|
|
});
|
|
}
|
|
|
|
// --- Additional Page Animations (Not part of the render animation) ---
|
|
const featuresEntry = new ZWA2Animations("section#features");
|
|
|
|
featuresEntry.onEnter(() => {
|
|
featuresEntry.el.querySelector(".waves-wrapper svg").style.setProperty("--enter", 1);
|
|
});
|
|
|
|
featuresEntry.onLeave(() => {
|
|
featuresEntry.el.querySelector(".waves-wrapper svg").style.setProperty("--enter", 0);
|
|
});
|
|
|
|
const configCards = document.querySelector("section#plug-and-play .config-cards");
|
|
const configCardItems = configCards.querySelectorAll('.config-card');
|
|
|
|
// Use IntersectionObserver to trigger animation when top center enters viewport
|
|
if (configCards) {
|
|
const observer = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
// Only fire once
|
|
configCardItems.forEach((configCardItem, index) => {
|
|
setTimeout(() => {
|
|
configCardItem.style.setProperty("--enter", 1);
|
|
}, 400 * index);
|
|
});
|
|
observer.disconnect();
|
|
}
|
|
});
|
|
}, {
|
|
root: null,
|
|
threshold: 0.5,
|
|
rootMargin: "0px 0px -20% 0px" // Top center
|
|
});
|
|
observer.observe(configCards);
|
|
}
|
|
|
|
const longRange = document.querySelector("section#long-range svg.range-waves");
|
|
const devices = document.querySelectorAll("section#long-range .devices .device");
|
|
|
|
// Use IntersectionObserver to trigger animation when the top of the element is 20% into the viewport
|
|
if (longRange) {
|
|
const observer1 = new IntersectionObserver((entries, obs) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
// Only fire once
|
|
longRange.style.setProperty("--enter", 1);
|
|
devices.forEach(d => {
|
|
setTimeout(() => {
|
|
d.style.opacity = 1;
|
|
}, 500 + Math.floor(Math.random() * 1000));
|
|
});
|
|
obs.disconnect();
|
|
}
|
|
});
|
|
}, {
|
|
root: null,
|
|
threshold: 0, // trigger as soon as it enters the adjusted root area
|
|
// Shrink the root rectangle from the top by 20% of the viewport height so
|
|
// intersection occurs when the element's top has moved 20% into view.
|
|
rootMargin: "-50% 0px 0px 0px"
|
|
});
|
|
observer1.observe(longRange);
|
|
}
|
|
|
|
// --- Event Listeners ---
|
|
|
|
// Attempt to load animations on initial page load.
|
|
|
|
maybeLoadAnimations();
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
let simon = new Simon(document.querySelectorAll("section#long-range .devices .device:not(.device--sprinkler)"));
|
|
maybeLoadAnimations();
|
|
featuresEntry.checkInViewOnLoad();
|
|
console.log("Repeat after me. Let's see how *long* you can last. -Darren");
|
|
});
|
|
// Also check on resize, in case the user rotates a tablet or resizes a browser window.
|
|
window.addEventListener('resize', maybeLoadAnimations);
|