Initial commit: Luftglanz drone website with integrated AI chat assistant
Features: - Complete Luftglanz drone cleaning website - AI chat assistant integrated with OpenAI API - Expert product advice for AGO Quart and Mellerud cleaning products - Formal German language support (Sie form) - Secure PHP backend for API calls - Responsive design with mobile support - Product-specific knowledge base - Safety statements from manufacturers - Multi-page integration (index, products, services, contact) Technical components: - AI chat widget (js/ai-chat.js) - Chat styling (css/components/ai-chat.css) - Backend API (ai-chat-api.php) - Product knowledge base with detailed specifications - Demo and documentation files
This commit is contained in:
268
js/roof-cleaning-animation.js
Normal file
268
js/roof-cleaning-animation.js
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Roof Cleaning Animation
|
||||
* Shows a drone flying over a dirty roof and cleaning it in a continuous loop
|
||||
*/
|
||||
(function() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const heroSection = document.querySelector('.hero');
|
||||
if (heroSection) {
|
||||
initRoofCleaningAnimation(heroSection);
|
||||
}
|
||||
});
|
||||
|
||||
function initRoofCleaningAnimation(container) {
|
||||
// Create canvas container
|
||||
const canvasContainer = document.createElement('div');
|
||||
canvasContainer.className = 'roof-cleaning-animation';
|
||||
canvasContainer.style.position = 'absolute';
|
||||
canvasContainer.style.top = '0';
|
||||
canvasContainer.style.left = '0';
|
||||
canvasContainer.style.width = '100%';
|
||||
canvasContainer.style.height = '100%';
|
||||
canvasContainer.style.zIndex = '1';
|
||||
canvasContainer.style.pointerEvents = 'none';
|
||||
canvasContainer.style.overflow = 'hidden';
|
||||
|
||||
// Create canvas element
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = container.offsetWidth;
|
||||
canvas.height = container.offsetHeight;
|
||||
canvas.style.position = 'absolute';
|
||||
canvas.style.top = '0';
|
||||
canvas.style.left = '0';
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '100%';
|
||||
canvasContainer.appendChild(canvas);
|
||||
|
||||
// Add to container
|
||||
container.appendChild(canvasContainer);
|
||||
|
||||
// Set up the animation
|
||||
setupAnimation(canvas);
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', function() {
|
||||
canvas.width = container.offsetWidth;
|
||||
canvas.height = container.offsetHeight;
|
||||
setupAnimation(canvas);
|
||||
});
|
||||
}
|
||||
|
||||
function setupAnimation(canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
|
||||
// Animation variables
|
||||
let droneX = -100; // Start off-screen
|
||||
const droneY = height * 0.4; // Position drone at 40% of height
|
||||
const droneWidth = 80;
|
||||
const droneHeight = 40;
|
||||
const droneSpeed = 2;
|
||||
const cleaningWidth = 80; // Width of cleaning path
|
||||
|
||||
// Create offscreen canvases for better performance
|
||||
const roofCanvas = createRoofCanvas(width, height);
|
||||
const cleanCanvas = createCleanCanvas(width, height);
|
||||
const maskCanvas = document.createElement('canvas');
|
||||
maskCanvas.width = width;
|
||||
maskCanvas.height = height;
|
||||
const maskCtx = maskCanvas.getContext('2d');
|
||||
|
||||
// Create drone image
|
||||
const drone = new Image();
|
||||
drone.src = 'images/spraying_drone1.png';
|
||||
|
||||
// Droplets array for water spray
|
||||
const droplets = [];
|
||||
|
||||
// Animation loop
|
||||
function animate() {
|
||||
// Clear the canvas
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Draw sky background
|
||||
drawSky(ctx, width, height);
|
||||
|
||||
// Update drone position
|
||||
droneX += droneSpeed;
|
||||
if (droneX > width + 100) {
|
||||
// Reset drone position and partially reset mask for continuous loop
|
||||
droneX = -100;
|
||||
// Reset left side of mask
|
||||
maskCtx.clearRect(0, height * 0.45, width * 0.2, height * 0.55);
|
||||
}
|
||||
|
||||
// Update cleaning mask
|
||||
updateCleaningMask(maskCtx, droneX, droneY, cleaningWidth);
|
||||
|
||||
// Draw dirty roof
|
||||
ctx.drawImage(roofCanvas, 0, 0);
|
||||
|
||||
// Apply cleaning mask to show clean areas
|
||||
ctx.globalCompositeOperation = 'destination-out';
|
||||
ctx.drawImage(maskCanvas, 0, 0);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
|
||||
// Draw clean roof in masked areas
|
||||
ctx.drawImage(cleanCanvas, 0, 0);
|
||||
|
||||
// Generate water droplets
|
||||
if (Math.random() < 0.3) { // 30% chance each frame
|
||||
createDroplet(droplets, droneX + droneWidth/2, droneY + droneHeight/2);
|
||||
}
|
||||
|
||||
// Update and draw water droplets
|
||||
updateDroplets(ctx, droplets);
|
||||
|
||||
// Draw the drone
|
||||
ctx.drawImage(drone, droneX, droneY, droneWidth, droneHeight);
|
||||
|
||||
// Continue animation loop
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// Wait for drone image to load before starting animation
|
||||
drone.onload = function() {
|
||||
animate();
|
||||
};
|
||||
}
|
||||
|
||||
function drawSky(ctx, width, height) {
|
||||
// Create gradient for sky
|
||||
const skyGradient = ctx.createLinearGradient(0, 0, 0, height * 0.45);
|
||||
skyGradient.addColorStop(0, '#87CEEB'); // Sky blue
|
||||
skyGradient.addColorStop(1, '#e6f7ff'); // Light blue
|
||||
|
||||
ctx.fillStyle = skyGradient;
|
||||
ctx.fillRect(0, 0, width, height * 0.45);
|
||||
}
|
||||
|
||||
function createRoofCanvas(width, height) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Draw dirty roof
|
||||
const roofY = height * 0.45; // Start roof at 45% of height
|
||||
const roofHeight = height * 0.55; // Roof takes up 55% of height
|
||||
|
||||
// Base roof color (dirty brownish)
|
||||
ctx.fillStyle = '#8B4513';
|
||||
ctx.fillRect(0, roofY, width, roofHeight);
|
||||
|
||||
// Add roof tiles texture
|
||||
ctx.fillStyle = '#6B3501';
|
||||
for (let y = roofY; y < height; y += 20) {
|
||||
for (let x = 0; x < width; x += 60) {
|
||||
const offset = (Math.floor(y / 20) % 2) * 30;
|
||||
ctx.fillRect(x + offset, y, 30, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// Add dirt and moss spots
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const spotX = Math.random() * width;
|
||||
const spotY = roofY + Math.random() * roofHeight;
|
||||
const spotSize = 3 + Math.random() * 15;
|
||||
|
||||
ctx.fillStyle = Math.random() > 0.5 ?
|
||||
'rgba(50, 30, 0, 0.4)' : // Dirt
|
||||
'rgba(30, 70, 30, 0.3)'; // Moss
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(spotX, spotY, spotSize, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function createCleanCanvas(width, height) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Draw clean roof
|
||||
const roofY = height * 0.45;
|
||||
const roofHeight = height * 0.55;
|
||||
|
||||
// Base clean roof color (reddish-brown)
|
||||
ctx.fillStyle = '#A0522D';
|
||||
ctx.fillRect(0, roofY, width, roofHeight);
|
||||
|
||||
// Add clean roof tiles texture
|
||||
ctx.fillStyle = '#8B4513';
|
||||
for (let y = roofY; y < height; y += 20) {
|
||||
for (let x = 0; x < width; x += 60) {
|
||||
const offset = (Math.floor(y / 20) % 2) * 30;
|
||||
ctx.fillRect(x + offset, y, 30, 10);
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function updateCleaningMask(ctx, droneX, droneY, cleaningWidth) {
|
||||
// Create radial gradient for cleaning effect
|
||||
const gradient = ctx.createRadialGradient(
|
||||
droneX + 40, droneY + 30, 5,
|
||||
droneX + 40, droneY + 30, cleaningWidth
|
||||
);
|
||||
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
|
||||
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(droneX + 40, droneY + 30, cleaningWidth, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
function createDroplet(droplets, x, y) {
|
||||
droplets.push({
|
||||
x: x,
|
||||
y: y,
|
||||
size: 2 + Math.random() * 3,
|
||||
speedX: -1 + Math.random() * 2,
|
||||
speedY: 4 + Math.random() * 2,
|
||||
opacity: 0.7 + Math.random() * 0.3
|
||||
});
|
||||
|
||||
// Limit number of droplets for performance
|
||||
if (droplets.length > 50) {
|
||||
droplets.shift();
|
||||
}
|
||||
}
|
||||
|
||||
function updateDroplets(ctx, droplets) {
|
||||
// Draw water droplets
|
||||
ctx.fillStyle = '#82CAFF';
|
||||
|
||||
for (let i = 0; i < droplets.length; i++) {
|
||||
const droplet = droplets[i];
|
||||
|
||||
// Update position
|
||||
droplet.x += droplet.speedX;
|
||||
droplet.y += droplet.speedY;
|
||||
|
||||
// Reduce opacity as it falls
|
||||
droplet.opacity -= 0.02;
|
||||
|
||||
if (droplet.opacity <= 0) {
|
||||
droplets.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Draw droplet
|
||||
ctx.globalAlpha = droplet.opacity;
|
||||
ctx.beginPath();
|
||||
ctx.arc(droplet.x, droplet.y, droplet.size, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user