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:
Luftglanz
2025-07-08 11:54:37 +02:00
commit ac7088c5ca
72 changed files with 7936 additions and 0 deletions

427
js/ai-chat.js Normal file
View File

@@ -0,0 +1,427 @@
// AI Chat System for Luftglanz Website
class AIChat {
constructor() {
this.apiKey = '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';
this.isOpen = false;
this.conversationHistory = [];
this.isTyping = false;
// Product knowledge base
this.productKnowledge = `
LUFTGLANZ PRODUCT KNOWLEDGE BASE:
1. COMPANY: Luftglanz - Professional drone-based roof and surface cleaning services
2. MAIN PRODUCTS:
A) AGO QUART GRÜNBELAGENTFERNER (Green algae and lichen remover)
PRODUCT DETAILS:
- Name: AGO Quart Algenentferner, Grünbelag- und Flechtenentferner
- Function: Highly effective against algae, green deposits, and lichens
- Active period: Up to 18 months continuous protection
- Application: Apply only on dry surfaces, no rain for 8 hours after application
- Coverage: 0.5L bottle covers approximately 100m² surface area
- Price: 19,90 € (39,80 € per liter)
MIXING RATIOS:
- Light deposits: 1:20 with water
- Heavy deposits (black algae, lichens): 1:10 with water
INGREDIENTS: Water, Benzalkonium-Chlorides, Tideceth-8, Cocamidopropyl-Betaine, Isopropyl-Alcohol
APPLICATION PROCESS:
1. Apply only on dry surfaces
2. Ensure no rain for minimum 8 hours after application
3. Works on surface level, not suitable for moss removal in joints/cracks
4. Cleaning duration depends on contamination severity
5. Active for up to 18 months, provides thorough cleaning and prevents recontamination
6. Never rinse with water after application - product must bind with surface
EFFECTIVENESS TIMELINE:
- Green deposits: Disappear within days
- Red/black algae: Several months for significant improvement
- Heavy lichen contamination: Up to 2 years for complete removal
SUITABLE SURFACES: All waterproof surfaces (roofs, terraces, facades, etc.)
IMPORTANT: AGO Quart is also SAFE for solar panels! According to the manufacturer: "Sie können die Solar-Platten bedenkenlos einsprühen. Unser AGO Quart greift dies nicht an. Es wird auch sehr erfolgreich von Firmen eingesetzt, die Solaranlagen reinigen."
BENEFITS:
- No high-pressure cleaning needed
- No scrubbing required
- Chlorine and acid-free
- Gentle cleaning without material damage
- Long-term protection against recontamination
- Self-cleaning effect through weather interaction
B) MELLERUD PHOTOVOLTAIK & SOLARANLAGEN REINIGER KONZENTRAT
PRODUCT DETAILS:
- Name: Mellerud Photovoltaik & Solaranlagen Reiniger Konzentrat
- Size: 0.5L concentrate
- Price: 8,99 € (17,98 € per liter)
- Function: Gentle and effective cleaning solution specifically for solar panels and photovoltaic systems
- Removes: Dust, general dirt, soot residues, fine dust, and pollen
- Special features: Streak-free, residue-free cleaning with water-repellent protection
MIXING RATIO:
- Standard cleaning: 1:20 with water (ideally distilled water)
INGREDIENTS & SPECS:
- Composition: Aqueous mixture of surfactants, complexing agents, and effectiveness enhancers
- pH value: 5.70 - 6.50
- Density: 1.020 kg/l
APPLICATION PROCESS:
1. Remove coarse dirt, leaves, and debris by lightly sweeping (avoid intensive scrubbing)
2. Mix concentrate 1:20 with water (preferably distilled water)
3. Apply carefully with specialized solar/PV equipment, soft sponge, cloth, or squeegee
4. Allow to work briefly
5. Rinse thoroughly with clear water (preferably distilled water)
SUITABLE EQUIPMENT:
- Specialized equipment for solar and PV systems
- Particularly soft sponges
- Soft cloths or squeegees
- Avoid intensive scrubbing to prevent damage
BENEFITS:
- Improves performance and lifespan of solar systems
- Creates sustainable protection against re-contamination through water-repellent effect
- Gentle cleaning without damage to sensitive modules
- Streak-free and residue-free results
- Suitable for all types of solar and PV panels
SAFETY INFORMATION:
- Causes severe eye irritation
- Keep out of reach of children
- Wash hands thoroughly after use
- In case of eye contact: rinse gently with water for several minutes
- Remove contact lenses if present and continue rinsing
- If eye irritation persists: seek medical advice
WHY DISTILLED WATER?
- Minimizes residues and promotes streak-free cleaning
- Supports optimal care of PV and solar systems
DRONE SERVICES:
- Professional application using drones for hard-to-reach areas
- Precise, even distribution of both AGO Quart and Mellerud products
- Safe application on steep roofs and high buildings
- Cost-effective compared to traditional scaffolding methods
- Specialized equipment for solar panel cleaning via drone
SAFETY INFORMATION:
- Use biocides safely
- Always read labeling and product information
- AGO Quart: Causes skin irritation (H315), serious eye damage (H318), very toxic to aquatic organisms (H410)
- Mellerud: Causes severe eye irritation, keep away from children
CONTACT INFORMATION:
AGO: AGO Sauberheit für Haus und Garten! GmbH & Co. KG, Brilloner Str. 39, 59909 Bestwig
Phone: 02904 / 98 98 98-0, Email: info@agoshop.de
Mellerud: MELLERUD CHEMIE GMBH, Bernhard-Röttgen-Waldweg 20, 41379 Brüggen
Phone: +49 (0) 2163 / 950 90 999, Email: shop@mellerud.de, experten-service@mellerud.de
`;
this.init();
}
init() {
this.createChatWidget();
this.attachEventListeners();
this.loadConversationHistory();
}
createChatWidget() {
// Chat toggle button
const chatButton = document.createElement('div');
chatButton.id = 'ai-chat-toggle';
chatButton.innerHTML = `
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4v3c0 .6.4 1 1 1 .2 0 .5-.1.7-.3L14.6 18H20c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z"/>
</svg>
<span>KI-Assistent</span>
`;
// Chat window
const chatWindow = document.createElement('div');
chatWindow.id = 'ai-chat-window';
chatWindow.innerHTML = `
<div class="chat-header">
<h3>🤖 Luftglanz KI-Assistent</h3>
<button id="chat-close">×</button>
</div>
<div class="chat-messages" id="chat-messages">
<div class="message bot-message">
<div class="message-content">
Hallo! Ich bin Ihr KI-Assistent für Fragen zu unseren Drohnen-Reinigungsservices und AGO Quart Produkten. Wie kann ich Ihnen helfen?
</div>
</div>
</div>
<div class="chat-input-container">
<input type="text" id="chat-input" placeholder="Stellen Sie Ihre Frage..." />
<button id="chat-send">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
</svg>
</button>
</div>
`;
document.body.appendChild(chatButton);
document.body.appendChild(chatWindow);
}
attachEventListeners() {
const toggleButton = document.getElementById('ai-chat-toggle');
const closeButton = document.getElementById('chat-close');
const sendButton = document.getElementById('chat-send');
const chatInput = document.getElementById('chat-input');
toggleButton.addEventListener('click', () => this.toggleChat());
closeButton.addEventListener('click', () => this.closeChat());
sendButton.addEventListener('click', () => this.sendMessage());
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
}
toggleChat() {
this.isOpen = !this.isOpen;
const chatWindow = document.getElementById('ai-chat-window');
const toggleButton = document.getElementById('ai-chat-toggle');
if (this.isOpen) {
chatWindow.classList.add('open');
toggleButton.classList.add('active');
} else {
chatWindow.classList.remove('open');
toggleButton.classList.remove('active');
}
}
closeChat() {
this.isOpen = false;
const chatWindow = document.getElementById('ai-chat-window');
const toggleButton = document.getElementById('ai-chat-toggle');
chatWindow.classList.remove('open');
toggleButton.classList.remove('active');
}
async sendMessage() {
const input = document.getElementById('chat-input');
const message = input.value.trim();
if (!message || this.isTyping) return;
// Add user message
this.addMessage(message, 'user');
input.value = '';
// Show typing indicator
this.showTypingIndicator();
try {
// Send to OpenAI
const response = await this.callOpenAI(message);
this.hideTypingIndicator();
this.addMessage(response, 'bot');
} catch (error) {
this.hideTypingIndicator();
this.addMessage('Entschuldigung, es gab einen Fehler bei der Kommunikation mit dem KI-System. Bitte versuchen Sie es erneut.', 'bot');
console.error('OpenAI API Error:', error);
}
}
async callOpenAI(userMessage) {
// Try PHP backend first (more secure), fallback to direct API call
try {
return await this.callPHPBackend(userMessage);
} catch (error) {
console.warn('PHP backend failed, trying direct API call:', error);
return await this.callDirectAPI(userMessage);
}
}
async callPHPBackend(userMessage) {
const response = await fetch('ai-chat-api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: userMessage,
history: this.conversationHistory.slice(-10) // Keep last 10 for context
})
});
if (!response.ok) {
throw new Error(`PHP Backend error: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Backend error');
}
// Update conversation history
this.conversationHistory.push(
{ role: 'user', content: userMessage },
{ role: 'assistant', content: data.message }
);
this.saveConversationHistory();
return data.message;
}
async callDirectAPI(userMessage) {
const systemPrompt = `Du bist ein hilfsreicher KI-Assistent für Luftglanz, ein Unternehmen für professionelle Drohnen-basierte Dach- und Oberflächenreinigung.
WICHTIGE ANWEISUNGEN:
- Antworte immer auf Deutsch
- Verwende IMMER die höfliche Anrede "Sie" (niemals "du" oder "dir")
- Sei freundlich, professionell und hilfsbereit
- Nutze AUSSCHLIESSLICH die bereitgestellten Produktinformationen für präzise Antworten
- Gib konkrete Produktdetails, Preise und Anwendungshinweise aus der Wissensbasis
- Bei Fragen zu Solaranlagen: Erkläre dass SOWOHL AGO Quart ALS AUCH Mellerud geeignet sind
- AGO Quart: Laut Hersteller sicher für Solar-Platten, wird von Reinigungsfirmen eingesetzt
- Mellerud: Speziell für Solaranlagen entwickelt mit schonender Formel
- Bei Fragen zu Algen/Grünbelägen auf Dächern, empfehle AGO Quart
- Erwähne die Vorteile der Drohnen-Anwendung wenn relevant
- Erkläre Unterschiede zwischen den Produkten detailliert basierend auf der Wissensbasis
- Gib konkrete Mischungsverhältnisse und Preise an
WICHTIG: Du kennst diese BEIDEN spezifischen Produkte sehr genau:
1. AGO Quart (19,90€) - für Algen, Grünbeläge, Flechten auf Dächern/Terrassen
2. Mellerud Photovoltaik Reiniger (8,99€) - speziell für Solaranlagen
PRODUKTWISSEN: ${this.productKnowledge}
Beantworte Fragen zu:
- Unterschiede zwischen AGO Quart und Mellerud Produkten
- Spezifische Anwendung für Solaranlagen vs. Dächer
- Konkrete Preise, Mischungsverhältnisse und Reichweiten
- Drohnen-Anwendung beider Produkte
- Sicherheitshinweise für beide Produkte
- Warum destilliertes Wasser bei Mellerud wichtig ist
- Kontaktdaten beider Hersteller`;
const messages = [
{ role: 'system', content: systemPrompt },
...this.conversationHistory.slice(-10), // Keep last 10 messages for context
{ role: 'user', content: userMessage }
];
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: messages,
max_tokens: 500,
temperature: 0.7,
presence_penalty: 0.1,
frequency_penalty: 0.1
})
});
if (!response.ok) {
throw new Error(`OpenAI API error: ${response.status}`);
}
const data = await response.json();
const assistantMessage = data.choices[0].message.content;
// Update conversation history
this.conversationHistory.push(
{ role: 'user', content: userMessage },
{ role: 'assistant', content: assistantMessage }
);
this.saveConversationHistory();
return assistantMessage;
}
addMessage(content, sender) {
const messagesContainer = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}-message`;
const messageContent = document.createElement('div');
messageContent.className = 'message-content';
messageContent.textContent = content;
messageDiv.appendChild(messageContent);
messagesContainer.appendChild(messageDiv);
// Auto-scroll to bottom
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
showTypingIndicator() {
this.isTyping = true;
const messagesContainer = document.getElementById('chat-messages');
const typingDiv = document.createElement('div');
typingDiv.id = 'typing-indicator';
typingDiv.className = 'message bot-message typing';
typingDiv.innerHTML = `
<div class="message-content">
<div class="typing-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
`;
messagesContainer.appendChild(typingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
hideTypingIndicator() {
this.isTyping = false;
const typingIndicator = document.getElementById('typing-indicator');
if (typingIndicator) {
typingIndicator.remove();
}
}
saveConversationHistory() {
try {
localStorage.setItem('luftglanz_chat_history', JSON.stringify(this.conversationHistory));
} catch (error) {
console.warn('Could not save conversation history:', error);
}
}
loadConversationHistory() {
try {
const saved = localStorage.getItem('luftglanz_chat_history');
if (saved) {
this.conversationHistory = JSON.parse(saved);
}
} catch (error) {
console.warn('Could not load conversation history:', error);
this.conversationHistory = [];
}
}
}
// Initialize chat when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.aiChat = new AIChat();
});

334
js/background-patterns.js Normal file
View File

@@ -0,0 +1,334 @@
/**
* Roof Cleaning Drone Background Animations
* Creates animated drones with water spray effects
*/
(function() {
document.addEventListener('DOMContentLoaded', function() {
// Apply the background to the entire site
applyCleanBackground();
// Add animated drones to hero section
enhanceHeroSection();
// Add water droplet effects to service cards
enhanceServiceCards();
});
function applyCleanBackground() {
// Remove any existing star/cosmic backgrounds
const existingStars = document.querySelector('.stars');
if (existingStars) {
existingStars.remove();
}
// Add clean background to body
document.body.style.background = '#f8f9fa';
document.body.style.backgroundImage = `
linear-gradient(120deg, rgba(99, 183, 241, 0.1) 0%, transparent 40%),
linear-gradient(240deg, rgba(220, 237, 249, 0.2) 0%, transparent 40%)
`;
// Add subtle diagonal pattern
const pattern = document.createElement('div');
pattern.className = 'clean-pattern';
pattern.style.position = 'fixed';
pattern.style.top = '0';
pattern.style.left = '0';
pattern.style.width = '100%';
pattern.style.height = '100%';
pattern.style.pointerEvents = 'none';
pattern.style.opacity = '0.03';
pattern.style.zIndex = '-1';
pattern.style.backgroundImage = `
repeating-linear-gradient(
45deg,
#3498db,
#3498db 2px,
transparent 2px,
transparent 10px
)
`;
document.body.appendChild(pattern);
// Update text colors for better contrast on light background
document.querySelectorAll('p, h1, h2, h3, h4, h5, h6').forEach(el => {
// Only change if still using the dark theme colors
if (getComputedStyle(el).color === 'rgb(216, 216, 255)' ||
getComputedStyle(el).color === 'rgb(143, 143, 183)') {
el.style.color = '#505050';
}
});
}
function enhanceHeroSection() {
const heroSection = document.querySelector('.hero');
if (!heroSection) return;
// Remove any existing background
if (heroSection.style.backgroundImage.includes('stars-bg.jpg')) {
heroSection.style.backgroundImage = 'none';
}
// Create a sky gradient background
heroSection.style.background = 'linear-gradient(to bottom, #87CEEB 0%, #e6f7ff 100%)';
heroSection.style.position = 'relative';
heroSection.style.overflow = 'hidden';
// Create animation container
const animationContainer = document.createElement('div');
animationContainer.className = 'drone-animation-container';
animationContainer.style.position = 'absolute';
animationContainer.style.top = '0';
animationContainer.style.left = '0';
animationContainer.style.width = '100%';
animationContainer.style.height = '100%';
animationContainer.style.pointerEvents = 'none';
animationContainer.style.zIndex = '1';
heroSection.appendChild(animationContainer);
// Add roof silhouette
const roofSilhouette = document.createElement('div');
roofSilhouette.className = 'roof-silhouette';
roofSilhouette.style.position = 'absolute';
roofSilhouette.style.bottom = '0';
roofSilhouette.style.left = '0';
roofSilhouette.style.width = '100%';
roofSilhouette.style.height = '20%';
roofSilhouette.style.background = 'url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAwIDIwMCI+PHBhdGggZD0iTTAgMjAwVjE1MEg1MEwxMDAgMTAwTDE1MCAxNTBIMjAwTDI1MCAxMDBMMzAwIDE1MEgzNTBMNDAwIDUwTDQ1MCAxNTBINTAwTDU1MCAxMDBMNjAwIDE1MEg2NTBMNzAwIDEwMEw3NTAgMTUwSDgwMEw4NTAgNTBMOTAwIDE1MEg5NTBMMTAwMCAxMDBMMTA1MCAxNTBIMTEwMEwxMTUwIDEwMEwxMjAwIDE1MFYyMDBIMFoiIGZpbGw9IiNlMGUwZTAiIGZpbGwtb3BhY2l0eT0iMC40Ii8+PC9zdmc+")';
roofSilhouette.style.backgroundSize = 'cover';
roofSilhouette.style.zIndex = '0';
animationContainer.appendChild(roofSilhouette);
// Create multiple drones with spray effects
createCleaningDrone(animationContainer, 20, 30, -1, 20);
createCleaningDrone(animationContainer, 60, 20, 1, 25);
createCleaningDrone(animationContainer, 40, 50, -1, 30);
// Add keyframes for animations
addDroneAnimationStyles();
}
function createCleaningDrone(container, startX, startY, direction, duration) {
// Create drone container
const droneElement = document.createElement('div');
droneElement.className = 'cleaning-drone';
droneElement.style.position = 'absolute';
droneElement.style.left = `${startX}%`;
droneElement.style.top = `${startY}%`;
droneElement.style.width = '60px';
droneElement.style.height = '60px';
droneElement.style.zIndex = '2';
// Calculate unique animation name
const animationName = `drone-path-${startX}-${startY}`;
droneElement.style.animation = `${animationName} ${duration}s linear infinite`;
// Create drone body
const droneBody = document.createElement('div');
droneBody.className = 'drone-body';
droneBody.style.position = 'relative';
droneBody.style.width = '40px';
droneBody.style.height = '40px';
droneBody.style.borderRadius = '50%';
droneBody.style.backgroundColor = '#3498db';
droneBody.style.border = '2px solid #2980b9';
droneBody.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.2)';
droneBody.style.margin = '0 auto';
droneBody.style.zIndex = '2';
droneElement.appendChild(droneBody);
// Add central hub
const droneHub = document.createElement('div');
droneHub.className = 'drone-hub';
droneHub.style.position = 'absolute';
droneHub.style.top = '50%';
droneHub.style.left = '50%';
droneHub.style.width = '15px';
droneHub.style.height = '15px';
droneHub.style.borderRadius = '50%';
droneHub.style.backgroundColor = '#fff';
droneHub.style.transform = 'translate(-50%, -50%)';
droneHub.style.zIndex = '3';
droneBody.appendChild(droneHub);
// Add propellers
for (let i = 0; i < 4; i++) {
const propContainer = document.createElement('div');
propContainer.className = 'propeller-container';
propContainer.style.position = 'absolute';
propContainer.style.width = '15px';
propContainer.style.height = '15px';
// Position propellers around drone
switch(i) {
case 0: // top left
propContainer.style.top = '-10px';
propContainer.style.left = '-10px';
break;
case 1: // top right
propContainer.style.top = '-10px';
propContainer.style.right = '-10px';
break;
case 2: // bottom left
propContainer.style.bottom = '-10px';
propContainer.style.left = '-10px';
break;
case 3: // bottom right
propContainer.style.bottom = '-10px';
propContainer.style.right = '-10px';
break;
}
// Create propeller
const propeller = document.createElement('div');
propeller.className = 'propeller';
propeller.style.width = '100%';
propeller.style.height = '100%';
propeller.style.borderRadius = '50%';
propeller.style.borderTop = '2px solid rgba(255,255,255,0.8)';
propeller.style.borderLeft = '2px solid rgba(255,255,255,0.6)';
propeller.style.animation = 'spin-propeller 0.15s linear infinite';
propContainer.appendChild(propeller);
droneBody.appendChild(propContainer);
}
// Create water spray effect
const waterSpray = document.createElement('div');
waterSpray.className = 'water-spray';
waterSpray.style.position = 'absolute';
waterSpray.style.bottom = '-20px';
waterSpray.style.left = '50%';
waterSpray.style.transform = 'translateX(-50%)';
waterSpray.style.width = '30px';
waterSpray.style.height = '50px';
waterSpray.style.zIndex = '1';
droneElement.appendChild(waterSpray);
// Add water droplets to the spray
for (let i = 0; i < 12; i++) {
const droplet = document.createElement('div');
droplet.className = 'water-droplet';
droplet.style.position = 'absolute';
droplet.style.width = `${3 + Math.random() * 3}px`;
droplet.style.height = `${5 + Math.random() * 7}px`;
droplet.style.backgroundColor = 'rgba(173, 216, 230, 0.7)';
droplet.style.borderRadius = '50%';
// Randomize position within spray
const angle = Math.random() * 30 - 15; // -15 to 15 degrees
const distance = 5 + Math.random() * 45; // 5 to 50px
droplet.style.top = `${distance}px`;
droplet.style.left = '50%';
droplet.style.transform = `translateX(-50%) rotate(${angle}deg)`;
// Different animation duration for each droplet
const fallDuration = 0.5 + Math.random() * 0.5;
droplet.style.animation = `fall-droplet ${fallDuration}s infinite linear`;
waterSpray.appendChild(droplet);
}
// Create path animation for this specific drone
const keyframesStyle = document.createElement('style');
keyframesStyle.innerHTML = `
@keyframes ${animationName} {
0% {
transform: translateX(${direction > 0 ? '-100px' : '100vw'}) translateY(0px) rotate(${direction > 0 ? '0deg' : '180deg'});
}
25% {
transform: translateX(${direction > 0 ? 'calc(20vw)' : 'calc(80vw - 100px)'}) translateY(-20px) rotate(${direction > 0 ? '0deg' : '180deg'});
}
50% {
transform: translateX(${direction > 0 ? 'calc(40vw)' : 'calc(60vw - 100px)'}) translateY(30px) rotate(${direction > 0 ? '0deg' : '180deg'});
}
75% {
transform: translateX(${direction > 0 ? 'calc(60vw)' : 'calc(40vw - 100px)'}) translateY(-15px) rotate(${direction > 0 ? '0deg' : '180deg'});
}
100% {
transform: translateX(${direction > 0 ? '100vw' : '-100px'}) translateY(10px) rotate(${direction > 0 ? '0deg' : '180deg'});
}
}
`;
document.head.appendChild(keyframesStyle);
container.appendChild(droneElement);
}
function addDroneAnimationStyles() {
const styleSheet = document.createElement('style');
styleSheet.innerHTML = `
@keyframes spin-propeller {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes fall-droplet {
0% {
opacity: 0.8;
transform: translateX(-50%) translateY(0) scale(1);
}
100% {
opacity: 0;
transform: translateX(-50%) translateY(30px) scale(0.5);
}
}
`;
document.head.appendChild(styleSheet);
}
function enhanceServiceCards() {
const serviceCards = document.querySelectorAll('.service-card');
if (!serviceCards.length) return;
serviceCards.forEach(card => {
// Update card styling to match clean theme
card.style.backgroundColor = 'white';
card.style.borderRadius = '10px';
card.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.1)';
card.style.border = '1px solid rgba(0, 0, 0, 0.05)';
card.style.overflow = 'hidden';
// Add water droplet accent at the top
const waterAccent = document.createElement('div');
waterAccent.className = 'water-accent';
waterAccent.style.height = '5px';
waterAccent.style.width = '100%';
waterAccent.style.position = 'absolute';
waterAccent.style.top = '0';
waterAccent.style.left = '0';
waterAccent.style.background = 'linear-gradient(90deg, #3498db, #00c2cb)';
card.prepend(waterAccent);
// Add subtle cleaning pattern to each card
addCleaningIconToCard(card);
});
}
function addCleaningIconToCard(card) {
// Create a container for the icon
const iconContainer = document.createElement('div');
iconContainer.className = 'cleaning-icon';
iconContainer.style.position = 'absolute';
iconContainer.style.top = '15px';
iconContainer.style.right = '15px';
iconContainer.style.width = '30px';
iconContainer.style.height = '30px';
iconContainer.style.opacity = '0.1';
// Randomly choose between different cleaning icons
const icons = [
// Water droplet
`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 20C16.4183 20 20 16.4183 20 12C20 7.58172 12 3 12 3C12 3 4 7.58172 4 12C4 16.4183 7.58172 20 12 20Z" fill="#3498db"/></svg>`,
// Spray bottle
`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8 3H11V5H13V3H16V5H14V7H17L19 10V21H6V10L8 7H11V5H8V3ZM8 10V19H17V10L15.8 8H9.2L8 10Z" fill="#3498db"/></svg>`,
// Drone
`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 8C13.1046 8 14 7.10457 14 6C14 4.89543 13.1046 4 12 4C10.8954 4 10 4.89543 10 6C10 7.10457 10.8954 8 12 8Z" fill="#3498db"/><path d="M4 10C5.10457 10 6 9.10457 6 8C6 6.89543 5.10457 6 4 6C2.89543 6 2 6.89543 2 8C2 9.10457 2.89543 10 4 10Z" fill="#3498db"/><path d="M20 10C21.1046 10 22 9.10457 22 8C22 6.89543 21.1046 6 20 6C18.8954 6 18 6.89543 18 8C18 9.10457 18.8954 10 20 10Z" fill="#3498db"/><path d="M12 20C13.1046 20 14 19.1046 14 18C14 16.8954 13.1046 16 12 16C10.8954 16 10 16.8954 10 18C10 19.1046 10.8954 20 12 20Z" fill="#3498db"/><path d="M12 8V16M4 10L10 16M20 10L14 16" stroke="#3498db" stroke-width="2" stroke-linecap="round"/></svg>`
];
// Choose a random icon
iconContainer.innerHTML = icons[Math.floor(Math.random() * icons.length)];
card.appendChild(iconContainer);
}
})();

139
js/bright-theme.js Normal file
View File

@@ -0,0 +1,139 @@
/**
* Bright Theme Elements and Visual Enhancements
*/
(function() {
document.addEventListener('DOMContentLoaded', function() {
// Add clean sky background to hero section
enhanceHeroSection();
// Add clean visual elements to services section
enhanceServicesSection();
// Add bright UI enhancements
applyBrightUIEnhancements();
});
function enhanceHeroSection() {
const heroSection = document.querySelector('.hero');
if (!heroSection) return;
// Create and add sky background with subtle clouds
heroSection.style.backgroundImage = 'linear-gradient(to bottom, #e6f3ff 0%, #ffffff 100%)';
heroSection.style.position = 'relative';
heroSection.style.overflow = 'hidden';
// Add subtle pattern
const pattern = document.createElement('div');
pattern.className = 'hero-pattern';
pattern.style.position = 'absolute';
pattern.style.top = 0;
pattern.style.left = 0;
pattern.style.right = 0;
pattern.style.bottom = 0;
pattern.style.opacity = 0.3;
pattern.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg width=\'100\' height=\'100\' viewBox=\'0 0 100 100\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath d=\'M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z\' fill=\'%230078d4\' fill-opacity=\'0.1\' fill-rule=\'evenodd\'/%3E%3C/svg%3E")';
heroSection.insertBefore(pattern, heroSection.firstChild);
// Add animated simple drone icon
const droneContainer = document.createElement('div');
droneContainer.className = 'drone-animation';
droneContainer.style.position = 'absolute';
droneContainer.style.top = '20%';
droneContainer.style.right = '10%';
droneContainer.style.width = '80px';
droneContainer.style.height = '80px';
droneContainer.style.zIndex = '1';
droneContainer.style.animation = 'float 6s ease-in-out infinite';
// Create drone SVG
droneContainer.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 8C13.1046 8 14 7.10457 14 6C14 4.89543 13.1046 4 12 4C10.8954 4 10 4.89543 10 6C10 7.10457 10.8954 8 12 8Z" fill="#0078d4"/>
<path d="M4 10C5.10457 10 6 9.10457 6 8C6 6.89543 5.10457 6 4 6C2.89543 6 2 6.89543 2 8C2 9.10457 2.89543 10 4 10Z" fill="#0078d4"/>
<path d="M20 10C21.1046 10 22 9.10457 22 8C22 6.89543 21.1046 6 20 6C18.8954 6 18 6.89543 18 8C18 9.10457 18.8954 10 20 10Z" fill="#0078d4"/>
<path d="M12 20C13.1046 20 14 19.1046 14 18C14 16.8954 13.1046 16 12 16C10.8954 16 10 16.8954 10 18C10 19.1046 10.8954 20 12 20Z" fill="#0078d4"/>
<path d="M12 8V16M4 10L10 16M20 10L14 16" stroke="#0078d4" stroke-width="2" stroke-linecap="round"/>
<animate attributeName="opacity" values="0.7;1;0.7" dur="2s" repeatCount="indefinite" />
</svg>`;
heroSection.appendChild(droneContainer);
// Add the floating animation
const style = document.createElement('style');
style.textContent = `
@keyframes float {
0% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-15px) rotate(2deg); }
100% { transform: translateY(0px) rotate(0deg); }
}
`;
document.head.appendChild(style);
}
function enhanceServicesSection() {
const servicesSection = document.getElementById('services');
if (!servicesSection) return;
// Add subtle pattern background
servicesSection.style.backgroundImage = 'linear-gradient(120deg, #ffffff 0%, #f8f9fa 100%)';
servicesSection.style.position = 'relative';
// Add cleaning icons to service cards
const serviceCards = document.querySelectorAll('.service-card');
const icons = [
'<svg width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 21L12 12M12 12L21 3M12 12L3 3M12 12L21 21" stroke="#0078d4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'<svg width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="9" stroke="#0078d4" stroke-width="2"/><path d="M12 7V12L15 15" stroke="#0078d4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'<svg width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 3v18M3 12h18" stroke="#0078d4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
];
serviceCards.forEach((card, index) => {
const icon = document.createElement('div');
icon.className = 'service-icon';
icon.style.marginBottom = '1.5rem';
icon.innerHTML = icons[index % icons.length];
card.insertBefore(icon, card.firstChild);
});
}
function applyBrightUIEnhancements() {
// Enhance buttons
const buttons = document.querySelectorAll('.btn-primary');
buttons.forEach(button => {
button.style.background = 'var(--primary-color)';
button.style.boxShadow = '0 4px 12px rgba(0, 120, 212, 0.2)';
button.style.border = 'none';
// Add hover effect
button.addEventListener('mouseenter', function() {
this.style.background = 'var(--gradient-primary)';
this.style.transform = 'translateY(-2px)';
});
button.addEventListener('mouseleave', function() {
this.style.background = 'var(--primary-color)';
this.style.transform = 'translateY(0)';
});
});
// Add roof-related decorative elements to sections
const sections = document.querySelectorAll('.section-container');
sections.forEach(section => {
// Add subtle top decoration
const decoration = document.createElement('div');
decoration.className = 'section-decoration';
decoration.style.height = '4px';
decoration.style.width = '100px';
decoration.style.background = 'var(--gradient-primary)';
decoration.style.borderRadius = '2px';
decoration.style.marginBottom = '2rem';
const title = section.querySelector('.section-title');
if (title) {
title.style.display = 'flex';
title.style.flexDirection = 'column';
title.style.alignItems = 'center';
title.style.marginBottom = '3rem';
title.appendChild(decoration);
}
});
}
})();

39
js/contact-form.js Normal file
View File

@@ -0,0 +1,39 @@
document.addEventListener('DOMContentLoaded', function() {
const contactForm = document.getElementById('contact-form');
const nameInput = document.getElementById('name');
const emailInput = document.getElementById('email');
const messageInput = document.getElementById('message');
const submitButton = document.getElementById('submit-button');
contactForm.addEventListener('submit', function(event) {
event.preventDefault();
const name = nameInput.value.trim();
const email = emailInput.value.trim();
const message = messageInput.value.trim();
if (validateForm(name, email, message)) {
// Simulate form submission
console.log('Form submitted:', { name, email, message });
alert('Thank you for your message! We will get back to you soon.');
contactForm.reset();
}
});
function validateForm(name, email, message) {
if (!name || !email || !message) {
alert('Please fill in all fields.');
return false;
}
if (!validateEmail(email)) {
alert('Please enter a valid email address.');
return false;
}
return true;
}
function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(String(email).toLowerCase());
}
});

86
js/dark-mode.js Normal file
View File

@@ -0,0 +1,86 @@
/**
* Enhanced Styling Controller for Balancer.fi-inspired Theme
*/
(function() {
// Run on page load and after small delay to ensure all elements are available
document.addEventListener('DOMContentLoaded', function() {
enhanceCosmicStyles();
// Apply once more after a slight delay to handle dynamically loaded elements
setTimeout(enhanceCosmicStyles, 500);
});
function enhanceCosmicStyles() {
// Add subtle animations to cards
const cards = document.querySelectorAll('.service-card, .step-card, .contact-method, .gallery-item');
cards.forEach(function(card) {
card.style.transition = 'all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1)';
// Add hover effects if not already present
card.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-10px)';
this.style.boxShadow = '0 15px 30px rgba(0, 0, 0, 0.4), 0 0 15px rgba(0, 194, 255, 0.5)';
});
card.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
this.style.boxShadow = '0 5px 15px rgba(0, 0, 0, 0.2)';
});
});
// Enhance form inputs for cosmic theme
const formElements = document.querySelectorAll('input:not(#chatInput), textarea, select');
formElements.forEach(function(element) {
element.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';
element.style.color = 'var(--text-light)';
element.style.border = '1px solid rgba(255, 255, 255, 0.1)';
element.style.borderRadius = '6px';
element.style.padding = '1rem';
// Add focus effects
element.addEventListener('focus', function() {
this.style.borderColor = 'var(--accent-color)';
this.style.boxShadow = '0 0 0 2px rgba(0, 194, 255, 0.2)';
});
element.addEventListener('blur', function() {
this.style.borderColor = 'rgba(255, 255, 255, 0.1)';
this.style.boxShadow = 'none';
});
});
// Enhance buttons
const buttons = document.querySelectorAll('button[type="submit"], .btn-primary, .cta-button');
buttons.forEach(function(button) {
// Only apply if not already styled
if (!button.hasAttribute('data-cosmic-styled')) {
button.setAttribute('data-cosmic-styled', 'true');
// Apply gradient background if not already set
if (!button.style.background.includes('gradient')) {
button.style.background = 'linear-gradient(90deg, #5846f9, #00c2ff)';
}
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '6px';
button.style.fontWeight = '600';
button.style.padding = '0.8rem 1.5rem';
button.style.cursor = 'pointer';
button.style.transition = 'all 0.3s ease';
button.style.boxShadow = '0 0 15px rgba(0, 194, 255, 0.5)';
// Add hover effects
button.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-2px)';
this.style.boxShadow = '0 0 25px rgba(0, 194, 255, 0.7)';
});
button.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
this.style.boxShadow = '0 0 15px rgba(0, 194, 255, 0.5)';
});
}
});
}
})();

154
js/drone-animation.js Normal file
View File

@@ -0,0 +1,154 @@
/**
* Drone Animation Controller
* Creates floating drone elements with cosmic glow effects
*/
(function() {
document.addEventListener('DOMContentLoaded', function() {
// Only run on the homepage
if (window.location.pathname.endsWith('index.html') || window.location.pathname.endsWith('/')) {
createDroneElements();
}
});
function createDroneElements() {
const heroSection = document.querySelector('.hero');
const servicesSection = document.getElementById('services');
if (heroSection) {
// Create a container for the animated elements
const animationContainer = document.createElement('div');
animationContainer.className = 'animation-container';
animationContainer.style.position = 'absolute';
animationContainer.style.top = '0';
animationContainer.style.left = '0';
animationContainer.style.width = '100%';
animationContainer.style.height = '100%';
animationContainer.style.overflow = 'hidden';
animationContainer.style.pointerEvents = 'none';
animationContainer.style.zIndex = '1';
heroSection.appendChild(animationContainer);
// Create floating drone elements
createFloatingDrone(animationContainer, 'right', '15%', '20%');
createFloatingDrone(animationContainer, 'left', '70%', '60%');
// Create floating particles
for (let i = 0; i < 15; i++) {
createGlowingParticle(animationContainer);
}
}
if (servicesSection) {
// Create subtle background animations for services section
const servicesBg = document.createElement('div');
servicesBg.className = 'services-bg';
servicesBg.style.position = 'absolute';
servicesBg.style.top = '0';
servicesBg.style.left = '0';
servicesBg.style.width = '100%';
servicesBg.style.height = '100%';
servicesBg.style.overflow = 'hidden';
servicesBg.style.pointerEvents = 'none';
servicesBg.style.zIndex = '0';
servicesSection.style.position = 'relative';
servicesSection.insertBefore(servicesBg, servicesSection.firstChild);
// Create subtle floating particles
for (let i = 0; i < 10; i++) {
createGlowingParticle(servicesBg, true);
}
}
}
function createFloatingDrone(container, direction, startX, startY) {
const drone = document.createElement('div');
drone.className = 'floating-drone';
drone.style.position = 'absolute';
drone.style.width = '80px';
drone.style.height = '80px';
drone.style.borderRadius = '50%';
drone.style.background = 'radial-gradient(circle at center, rgba(88, 70, 249, 0.7), rgba(0, 194, 255, 0.4))';
drone.style.boxShadow = '0 0 30px rgba(0, 194, 255, 0.6)';
drone.style.left = startX;
drone.style.top = startY;
drone.style.filter = 'blur(5px)';
// Create propeller effects
const propeller = document.createElement('div');
propeller.className = 'propeller';
propeller.style.position = 'absolute';
propeller.style.width = '100px';
propeller.style.height = '100px';
propeller.style.left = '-10px';
propeller.style.top = '-10px';
propeller.style.borderRadius = '50%';
propeller.style.border = '2px solid rgba(255, 255, 255, 0.2)';
propeller.style.animation = 'spin 2s linear infinite';
drone.appendChild(propeller);
// Add animation properties
drone.style.animation = `float-${direction} 15s ease-in-out infinite`;
container.appendChild(drone);
// Add keyframes for floating animation
const styleSheet = document.createElement('style');
styleSheet.type = 'text/css';
styleSheet.innerHTML = `
@keyframes float-${direction} {
0% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(${direction === 'left' ? '-' : ''}80px, 40px) rotate(${direction === 'left' ? '-' : ''}5deg); }
50% { transform: translate(${direction === 'left' ? '-' : ''}60px, -30px) rotate(${direction === 'left' ? '-' : ''}10deg); }
75% { transform: translate(${direction === 'left' ? '-' : ''}100px, 50px) rotate(${direction === 'left' ? '-' : ''}3deg); }
100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(styleSheet);
}
function createGlowingParticle(container, subtle = false) {
const particle = document.createElement('div');
particle.className = 'glowing-particle';
// Set particle style
const size = subtle ? Math.random() * 5 + 2 : Math.random() * 10 + 5;
const opacity = subtle ? Math.random() * 0.3 + 0.1 : Math.random() * 0.5 + 0.2;
particle.style.position = 'absolute';
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
particle.style.borderRadius = '50%';
particle.style.background = 'radial-gradient(circle at center, rgba(0, 194, 255, 0.8), rgba(88, 70, 249, 0.4))';
particle.style.boxShadow = `0 0 ${size * 2}px rgba(0, 194, 255, ${opacity})`;
// Random position
particle.style.left = `${Math.random() * 100}%`;
particle.style.top = `${Math.random() * 100}%`;
// Add animation with random duration
const duration = Math.random() * 20 + 10;
particle.style.animation = `float-particle ${duration}s ease-in-out infinite`;
container.appendChild(particle);
// Add keyframes for particle animation if not already added
if (!document.querySelector('style[data-animation="particle-float"]')) {
const styleSheet = document.createElement('style');
styleSheet.type = 'text/css';
styleSheet.setAttribute('data-animation', 'particle-float');
styleSheet.innerHTML = `
@keyframes float-particle {
0% { transform: translate(0, 0); }
25% { transform: translate(${Math.random() * 100 - 50}px, ${Math.random() * 100 - 50}px); }
50% { transform: translate(${Math.random() * 100 - 50}px, ${Math.random() * 100 - 50}px); }
75% { transform: translate(${Math.random() * 100 - 50}px, ${Math.random() * 100 - 50}px); }
100% { transform: translate(0, 0); }
}
`;
document.head.appendChild(styleSheet);
}
}
})();

486
js/drone-image-animation.js Normal file
View File

@@ -0,0 +1,486 @@
/**
* Drone Image Animation with New Background
* Uses a single image background with the drone flying over the roof
*/
(function() {
document.addEventListener('DOMContentLoaded', function() {
// Only run on pages that have a hero section
const heroSection = document.querySelector('.hero');
if (heroSection) {
// Remove any existing backgrounds or animations
if (heroSection.style.backgroundImage) {
heroSection.style.backgroundImage = 'none';
}
// Remove any existing animation containers
const existingAnimations = heroSection.querySelectorAll('.drone-animation-container, .animation-container, .roof-cleaning-animation');
existingAnimations.forEach(el => el.remove());
// Create our animation with the new background
createAnimationWithSingleBackground(heroSection);
}
});
function createAnimationWithSingleBackground(container) {
// Create animation container with more aggressive full-width styling
const animationContainer = document.createElement('div');
animationContainer.className = 'drone-animation-container';
// Position absolutely with negative margins to escape any parent padding
animationContainer.style.position = 'absolute';
animationContainer.style.top = '0';
animationContainer.style.left = '0';
animationContainer.style.right = '0'; // Add right: 0 to ensure full width
animationContainer.style.width = '100vw'; // Use viewport width
animationContainer.style.maxWidth = '100vw'; // Override any max-width restrictions
animationContainer.style.marginLeft = 'calc(50% - 50vw)'; // Center regardless of parent padding
animationContainer.style.marginRight = 'calc(50% - 50vw)'; // Center regardless of parent padding
animationContainer.style.height = '100%';
animationContainer.style.overflow = 'hidden';
animationContainer.style.pointerEvents = 'none';
animationContainer.style.zIndex = '1';
// Add CSS to fix any scrollbar issues
const styleFixForScrollbar = document.createElement('style');
styleFixForScrollbar.textContent = `
html, body {
overflow-x: hidden;
width: 100%;
position: relative;
}
.hero {
width: 100%;
max-width: 100%;
overflow: hidden;
padding-left: 0 !important;
padding-right: 0 !important;
}
`;
document.head.appendChild(styleFixForScrollbar);
// Fix mobile white space by adjusting the parent container on mobile
if (window.innerWidth <= 768) {
// Remove potential white space in parent container
container.style.paddingBottom = '0';
container.style.marginBottom = '0';
}
// Create background using the new image
createFullBackground(animationContainer);
// Create drone elements that will fly over the roof
createFlyingDrones(animationContainer);
// Add everything to the container
container.appendChild(animationContainer);
// Add animation styles
addAnimationStyles();
// Add resize listener to adjust for screen size changes
window.addEventListener('resize', function() {
adjustForScreenSize(animationContainer);
});
// Initial adjustment for screen size
adjustForScreenSize(animationContainer);
// Position the hero content (text) in the sky area
positionHeroContent(container);
}
function createFullBackground(container) {
// Create the full background with enhanced full-width styling
const backgroundElement = document.createElement('div');
backgroundElement.className = 'animation-background';
backgroundElement.style.position = 'absolute';
backgroundElement.style.top = '0';
backgroundElement.style.left = '0';
backgroundElement.style.right = '0'; // Add right: 0
backgroundElement.style.width = '100vw'; // Use viewport width
backgroundElement.style.maxWidth = 'none'; // No max width restrictions
backgroundElement.style.height = '100%';
backgroundElement.style.backgroundImage = 'url("images/animation_background1.png")';
backgroundElement.style.backgroundSize = 'cover';
backgroundElement.style.backgroundPosition = 'center center';
backgroundElement.style.backgroundRepeat = 'no-repeat';
backgroundElement.style.backgroundColor = '#75b0cc';
backgroundElement.style.zIndex = '1';
// Add subtle overlay to improve text visibility
const overlay = document.createElement('div');
overlay.style.position = 'absolute';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.1)'; /* Subtle darkening */
overlay.style.zIndex = '2';
backgroundElement.appendChild(overlay);
container.appendChild(backgroundElement);
// Add resize event listener for responsive adjustments
window.addEventListener('resize', function() {
adjustBackground(backgroundElement);
});
// Initial adjustment
adjustBackground(backgroundElement);
}
function adjustBackground(backgroundElement) {
const isMobile = window.innerWidth <= 768;
const isPortrait = window.innerHeight > window.innerWidth;
if (isMobile) {
if (isPortrait) {
// Portrait mobile - fix grey area by using cover and adjusting transform
backgroundElement.style.backgroundSize = 'cover';
backgroundElement.style.transform = 'translateY(-15%)'; // Move image up instead of using background-position
backgroundElement.style.height = '100%'; // Ensure full height
} else {
// Landscape mobile - also fix using transform
backgroundElement.style.backgroundSize = 'cover';
backgroundElement.style.transform = 'translateY(-10%)';
backgroundElement.style.height = '100%';
}
} else {
// Desktop - standard positioning
backgroundElement.style.backgroundSize = 'cover';
backgroundElement.style.transform = 'none';
backgroundElement.style.backgroundPosition = 'center center';
}
}
function positionHeroContent(container) {
// Get the hero content (text)
const heroContent = container.querySelector('.hero-content');
if (heroContent) {
// Split the content: Title in sky, rest at top of roof
const headline = heroContent.querySelector('h2');
const otherContent = document.createElement('div');
otherContent.className = 'hero-secondary-content';
// Get all other elements except the headline
const subheading = heroContent.querySelector('p');
const ctaButton = heroContent.querySelector('.btn-primary');
// Set styles for the main container
heroContent.style.position = 'relative';
heroContent.style.zIndex = '5';
heroContent.style.display = 'flex';
heroContent.style.flexDirection = 'column';
heroContent.style.alignItems = 'center';
heroContent.style.justifyContent = 'space-between';
heroContent.style.height = '100%';
heroContent.style.textShadow = '0 2px 4px rgba(0, 0, 0, 0.3)';
// Style headline to be in sky - make consistent across all views
if (headline) {
headline.style.color = '#ffffff';
headline.style.margin = '0 auto 2rem';
headline.style.maxWidth = '100%'; // Changed from 90% to 100% for better text wrapping
headline.style.textAlign = 'center';
headline.style.position = 'relative';
headline.style.zIndex = '6';
headline.style.fontSize = window.innerWidth <= 480 ? '1.5rem' : (window.innerWidth <= 768 ? '1.8rem' : '3rem'); // Reduced from 1.7rem to 1.5rem for smallest screens
headline.style.lineHeight = '1.3'; // Add line height for better text spacing
headline.style.width = '100%'; // Ensure full width
headline.style.wordWrap = 'break-word'; // Improve word wrapping
headline.style.hyphens = 'none'; // Changed from 'auto' to 'none'
headline.style.wordBreak = 'keep-all'; // Prevent words from breaking
headline.style.whiteSpace = 'normal'; // Allow normal wrapping but prevent word splitting
// More mobile-friendly initial positioning
headline.style.marginTop = window.innerWidth <= 768 ? '-3rem' : '-8rem';
}
// Move other content to a separate container
if (subheading && ctaButton) {
// Remove elements from their current position
subheading.remove();
ctaButton.remove();
// Add them to the new container
otherContent.appendChild(subheading);
otherContent.appendChild(ctaButton);
// Style the subheading
subheading.style.color = '#ffffff';
subheading.style.marginBottom = '1.5rem';
// Style the container for other content
otherContent.style.display = 'flex';
otherContent.style.flexDirection = 'column';
otherContent.style.alignItems = 'center';
otherContent.style.position = 'relative';
otherContent.style.zIndex = '6';
// Add the container to the hero content
heroContent.appendChild(otherContent);
}
// Adjust positions for different screen sizes
adjustHeroContentPositioning(heroContent, headline, otherContent);
window.addEventListener('resize', function() {
adjustHeroContentPositioning(heroContent, headline, otherContent);
});
}
}
function adjustHeroContentPositioning(heroContent, headline, otherContent) {
const isMobile = window.innerWidth <= 768;
const isPortrait = window.innerHeight > window.innerWidth;
const isSmallPhone = window.innerWidth <= 480;
// Apply different positioning based on device type and size
if (headline) {
if (isMobile) {
if (isPortrait) {
// Portrait mobile - adjust based on device height
const viewportHeight = window.innerHeight;
if (viewportHeight <= 600) {
// Very small device
headline.style.marginTop = '-1rem';
} else if (viewportHeight <= 800) {
// Medium device
headline.style.marginTop = '-2rem';
} else {
// Larger device
headline.style.marginTop = '-3rem';
}
} else {
// Landscape mobile
headline.style.marginTop = '-2rem';
}
} else {
// Desktop - keep as is
headline.style.marginTop = '-8rem';
}
}
if (otherContent) {
otherContent.style.marginTop = 'auto';
if (isMobile) {
if (isPortrait) {
// Portrait mobile - adjust based on device height
const viewportHeight = window.innerHeight;
if (viewportHeight <= 600) {
// More compact spacing for small devices
otherContent.style.marginBottom = '3rem';
} else if (viewportHeight <= 800) {
otherContent.style.marginBottom = '4rem';
} else {
otherContent.style.marginBottom = '5rem';
}
// Adjust spacing between elements for small devices
if (isSmallPhone) {
const subheading = otherContent.querySelector('p');
if (subheading) {
subheading.style.marginBottom = '1rem';
}
}
} else {
// Landscape mobile
otherContent.style.marginBottom = '3rem';
}
} else {
// Desktop
otherContent.style.marginBottom = '5rem';
}
}
}
function adjustHeroContent(heroContent) {
// This function is replaced by the more specific positioning function above
// Keep it for compatibility but it no longer needs to do anything
}
function createFlyingDrones(container) {
// Create drone path container
const dronePathContainer = document.createElement('div');
dronePathContainer.className = 'drone-path-container';
dronePathContainer.style.position = 'absolute';
dronePathContainer.style.top = '0';
dronePathContainer.style.left = '0';
dronePathContainer.style.width = '100%';
dronePathContainer.style.height = '100%';
dronePathContainer.style.zIndex = '10';
container.appendChild(dronePathContainer);
// Create drone with responsive sizing
createDrone(dronePathContainer, 'drone-path-1', 0);
}
function createDrone(container, pathClass, startDelay = 0, duration = 30) {
// Create drone element using the specified drone image
const droneElement = document.createElement('div');
droneElement.className = `animated-drone ${pathClass}`;
droneElement.style.position = 'absolute';
droneElement.style.width = '440px'; // Will be adjusted for mobile
droneElement.style.height = '300px'; // Will be adjusted for mobile
droneElement.style.top = '45%'; // Position for new background
droneElement.style.left = '-450px';
droneElement.style.zIndex = '15'; // Above all other elements
droneElement.style.animation = `${pathClass} ${duration}s linear infinite`;
droneElement.style.animationDelay = `${startDelay}s`;
// Add the drone image
const droneImage = document.createElement('img');
droneImage.src = 'images/spraying_drone4.png';
droneImage.alt = 'Dachreinigungsdrohne';
droneImage.style.width = '100%';
droneImage.style.height = 'auto';
droneImage.style.filter = 'drop-shadow(0 15px 30px rgba(0,0,0,0.25))';
droneElement.appendChild(droneImage);
container.appendChild(droneElement);
}
function adjustForScreenSize(container) {
const isMobile = window.innerWidth <= 768;
const isPortrait = window.innerHeight > window.innerWidth;
// Fix white space issue on mobile
if (isMobile) {
// Adjust parent container (hero section)
const heroSection = container.closest('.hero');
if (heroSection) {
heroSection.style.paddingBottom = '0';
heroSection.style.marginBottom = '0';
// Ensure full height to bottom of viewport on mobile
container.style.minHeight = '100%';
}
// Adjust the main background element to extend fully
const backgroundEl = container.querySelector('.animation-background');
if (backgroundEl) {
backgroundEl.style.bottom = '0';
backgroundEl.style.height = '100%';
// Add extra height to cover any potential gaps
backgroundEl.style.marginBottom = '-2px';
}
}
const drones = container.querySelectorAll('.animated-drone');
// Adjust drone sizes
drones.forEach(drone => {
if (isMobile) {
if (isPortrait) {
// Portrait mobile - smaller drone
drone.style.width = '150px';
drone.style.height = '100px';
} else {
// Landscape mobile
drone.style.width = '180px';
drone.style.height = '120px';
}
} else {
// Desktop size
drone.style.width = '440px';
drone.style.height = '300px';
}
});
// Adjust background
const background = container.querySelector('.animation-background');
if (background) {
adjustBackground(background);
}
// Add headline adjustment on resize
const headline = document.querySelector('.hero-content h2');
if (headline) {
// Adjust font size based on screen width for better text wrapping
if (window.innerWidth <= 480) {
headline.style.fontSize = '1.5rem';
headline.style.lineHeight = '1.3';
headline.style.maxWidth = '100%';
headline.style.padding = '0 0.5rem'; // Add padding for smallest screens
headline.style.hyphens = 'none';
headline.style.wordBreak = 'keep-all';
headline.style.whiteSpace = 'normal';
} else if (window.innerWidth <= 768) {
headline.style.fontSize = '1.8rem';
headline.style.lineHeight = '1.3';
headline.style.maxWidth = '100%';
headline.style.hyphens = 'none';
headline.style.wordBreak = 'keep-all';
headline.style.whiteSpace = 'normal';
} else {
headline.style.fontSize = '3rem';
headline.style.lineHeight = '1.2';
headline.style.maxWidth = '90%';
headline.style.hyphens = 'none';
headline.style.wordBreak = 'keep-all';
headline.style.whiteSpace = 'normal';
}
}
}
// Add a resize event listener to handle orientation changes
window.addEventListener('resize', function() {
const animationContainer = document.querySelector('.drone-animation-container');
if (animationContainer) {
adjustForScreenSize(animationContainer);
}
});
function addAnimationStyles() {
// Create style element if it doesn't exist
if (!document.getElementById('drone-animation-styles')) {
const styleElement = document.createElement('style');
styleElement.id = 'drone-animation-styles';
// Add responsive animations with adjusted drone path to match new background
styleElement.innerHTML = `
@keyframes drone-path-1 {
/* First half - path adjusted for new background */
0% { left: -450px; top: 45%; transform: rotate(0deg); }
5% { left: 10%; top: 43%; transform: rotate(-5deg); }
10% { left: 20%; top: 45%; transform: rotate(0deg); }
15% { left: 30%; top: 47%; transform: rotate(5deg); }
20% { left: 40%; top: 45%; transform: rotate(0deg); }
25% { left: 50%; top: 48%; transform: rotate(-3deg); }
30% { left: 60%; top: 50%; transform: rotate(0deg); }
35% { left: 70%; top: 52%; transform: rotate(3deg); }
40% { left: 80%; top: 50%; transform: rotate(0deg); }
45% { left: 90%; top: 48%; transform: rotate(-5deg); }
50% { left: 100%; top: 45%; transform: rotate(-10deg); }
/* Second half - return path */
55% { left: 90%; top: 48%; transform: rotate(-5deg) scaleX(-1); }
60% { left: 80%; top: 50%; transform: rotate(0deg) scaleX(-1); }
65% { left: 70%; top: 52%; transform: rotate(3deg) scaleX(-1); }
70% { left: 60%; top: 50%; transform: rotate(0deg) scaleX(-1); }
75% { left: 50%; top: 48%; transform: rotate(-3deg) scaleX(-1); }
80% { left: 40%; top: 45%; transform: rotate(0deg) scaleX(-1); }
85% { left: 30%; top: 47%; transform: rotate(5deg) scaleX(-1); }
90% { left: 20%; top: 45%; transform: rotate(0deg) scaleX(-1); }
95% { left: 10%; top: 43%; transform: rotate(-5deg) scaleX(-1); }
100% { left: -450px; top: 45%; transform: rotate(0deg) scaleX(-1); }
}
/* Mobile-specific animations */
@media (max-width: 768px) {
@keyframes drone-path-1 {
/* Adjusted path for mobile - fly higher to match the shifted background */
0% { left: -220px; top: 45%; transform: rotate(0deg); }
20% { left: 30%; top: 43%; transform: rotate(-5deg); }
40% { left: 70%; top: 47%; transform: rotate(5deg); }
50% { left: 100%; top: 45%; transform: rotate(-5deg); }
60% { left: 70%; top: 47%; transform: rotate(5deg) scaleX(-1); }
80% { left: 30%; top: 43%; transform: rotate(-5deg) scaleX(-1); }
100% { left: -220px; top: 45%; transform: rotate(0deg) scaleX(-1); }
}
}
`;
document.head.appendChild(styleElement);
}
}
})();

66
js/language-manager.js Normal file
View File

@@ -0,0 +1,66 @@
/**
* Language Manager
* Simplified to use only German language
*/
const languageManager = {
currentLang: 'de', // Default language set to German
init: function() {
console.log('Initializing Language Manager');
// Always use German as the language
this.currentLang = 'de';
localStorage.setItem('preferredLanguage', 'de');
// Apply translations
this.applyTranslations();
// Update document language attribute
document.documentElement.lang = this.currentLang;
console.log(`Language Manager initialized with language: ${this.currentLang}`);
},
applyTranslations: function() {
if (typeof translations === 'undefined' || !translations['de']) {
console.error('German translations not available');
return;
}
const elements = document.querySelectorAll('[data-i18n]');
elements.forEach(element => {
const key = element.getAttribute('data-i18n');
const translation = translations['de'][key];
if (translation) {
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
if (element.hasAttribute('placeholder')) {
element.setAttribute('placeholder', translation);
} else {
element.value = translation;
}
} else {
element.textContent = translation;
}
} else {
console.warn(`Missing translation for key: ${key}`);
}
});
const altElements = document.querySelectorAll('[data-i18n-alt]');
altElements.forEach(element => {
const key = element.getAttribute('data-i18n-alt');
const translation = translations['de'][key];
if (translation) {
element.setAttribute('alt', translation);
}
});
console.log(`Applied German translations`);
}
};
// Initialize the language manager when the DOM is ready
document.addEventListener('DOMContentLoaded', () => {
languageManager.init();
});

19
js/main.js Normal file
View File

@@ -0,0 +1,19 @@
// This file contains the main JavaScript code for the website, handling general interactivity and functionality.
document.addEventListener('DOMContentLoaded', () => {
// Initialize any necessary components or features
console.log('Roof Drone Cleaning Website Loaded');
// Example: Smooth scroll for anchor links
const scrollLinks = document.querySelectorAll('a[href^="#"]');
scrollLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href');
const targetElement = document.querySelector(targetId);
targetElement.scrollIntoView({ behavior: 'smooth' });
});
});
// Additional functionality can be added here
});

45
js/mobile-menu.js Normal file
View File

@@ -0,0 +1,45 @@
/**
* Mobile navigation functionality
* This script handles the mobile hamburger menu behavior
*/
document.addEventListener('DOMContentLoaded', function() {
// Mobile navigation toggle
const mobileToggle = document.getElementById('mobileToggle');
const mainNav = document.getElementById('mainNav');
if(mobileToggle && mainNav) {
mobileToggle.addEventListener('click', function() {
mainNav.classList.toggle('active');
});
}
// Close mobile menu when a link is clicked
const mobileNavLinks = document.querySelectorAll('.main-nav a');
mobileNavLinks.forEach(link => {
link.addEventListener('click', function() {
if (window.innerWidth <= 768) {
mainNav.classList.remove('active');
}
});
});
// Close menu when clicking outside
document.addEventListener('click', function(event) {
if (window.innerWidth <= 768 &&
!event.target.closest('.main-nav') &&
!event.target.closest('.mobile-toggle') &&
mainNav.classList.contains('active')) {
mainNav.classList.remove('active');
}
});
// Ensure copyright year is 2025 on mobile
const footerText = document.querySelector('footer p[data-i18n="footer_text"]');
if (footerText) {
// Force update the copyright year to 2025 for mobile and desktop
const currentText = footerText.innerHTML;
const updatedText = currentText.replace(/© \d{4}/, '© 2025');
footerText.innerHTML = updatedText;
}
});

28
js/navigation.js Normal file
View File

@@ -0,0 +1,28 @@
document.addEventListener('DOMContentLoaded', function() {
// Get current page path
const currentPath = window.location.pathname;
const isInPagesDirectory = currentPath.includes('/pages/');
// Handle paths based on directory
const navLinks = document.querySelectorAll('#mainNav a');
navLinks.forEach(link => {
// Get href attribute
let href = link.getAttribute('href');
// Fix paths if in pages directory
if (isInPagesDirectory && href.startsWith('index.html')) {
link.setAttribute('href', '../' + href);
} else if (isInPagesDirectory && href.startsWith('#')) {
link.setAttribute('href', '../index.html' + href);
} else if (!isInPagesDirectory && href.startsWith('pages/')) {
// No change needed for root directory links to pages
}
// Set active class based on current page
if ((currentPath.endsWith('/index.html') || currentPath.endsWith('/')) && href === 'index.html') {
link.classList.add('active');
} else if (currentPath.includes(href) && href !== 'index.html') {
link.classList.add('active');
}
});
});

View 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;
}
})();

94
js/star-background.js Normal file
View File

@@ -0,0 +1,94 @@
/**
* Dynamic Star Background Generator
* Creates an animated cosmic background with stars and nebulae
*/
(function() {
document.addEventListener('DOMContentLoaded', function() {
// Create canvas for star background if it doesn't exist
if (!document.getElementById('stars-canvas')) {
createStarCanvas();
}
});
function createStarCanvas() {
const canvas = document.createElement('canvas');
canvas.id = 'stars-canvas';
canvas.style.position = 'fixed';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.zIndex = '-1';
canvas.style.pointerEvents = 'none';
document.body.prepend(canvas);
// Make the canvas responsive
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Redraw on window resize
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
drawStarBackground(canvas);
});
// Draw initial star background
drawStarBackground(canvas);
}
function drawStarBackground(canvas) {
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
// Clear canvas
ctx.clearRect(0, 0, width, height);
// Create gradient background
const bgGradient = ctx.createLinearGradient(0, 0, 0, height);
bgGradient.addColorStop(0, '#070b1a');
bgGradient.addColorStop(1, '#0c1426');
ctx.fillStyle = bgGradient;
ctx.fillRect(0, 0, width, height);
// Draw nebulae
drawNebula(ctx, width * 0.2, height * 0.3, width * 0.4, height * 0.4, '#5846f9', 0.05);
drawNebula(ctx, width * 0.7, height * 0.7, width * 0.5, height * 0.5, '#00c2ff', 0.05);
// Draw stars
const starCount = Math.floor((width * height) / 1000); // Adaptive star count
for (let i = 0; i < starCount; i++) {
const x = Math.random() * width;
const y = Math.random() * height;
const size = Math.random() * 2;
const opacity = Math.random() * 0.8 + 0.2;
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`;
ctx.fill();
// Add glow to some stars
if (Math.random() > 0.95) {
ctx.beginPath();
ctx.arc(x, y, size * 4, 0, Math.PI * 2);
const glowColor = Math.random() > 0.5 ? '0, 194, 255' : '88, 70, 249';
const glow = ctx.createRadialGradient(x, y, size, x, y, size * 4);
glow.addColorStop(0, `rgba(${glowColor}, 0.3)`);
glow.addColorStop(1, `rgba(${glowColor}, 0)`);
ctx.fillStyle = glow;
ctx.fill();
}
}
}
function drawNebula(ctx, x, y, width, height, color, opacity) {
const nebulaGradient = ctx.createRadialGradient(x, y, 10, x, y, Math.max(width, height));
nebulaGradient.addColorStop(0, `${color}${Math.floor(opacity * 255).toString(16).padStart(2, '0')}`);
nebulaGradient.addColorStop(1, 'transparent');
ctx.fillStyle = nebulaGradient;
ctx.fillRect(x - width / 2, y - height / 2, width, height);
}
})();

267
js/translations.js Normal file
View File

@@ -0,0 +1,267 @@
const translations = {
'de': {
// Header
'header_title': 'Willkommen bei Luftglanz',
'roof_drone': 'Luftglanz',
// Navigation
'nav_home': 'Startseite',
'nav_services': 'Leistungen',
'nav_how_it_works': 'Ablauf',
'nav_faq': 'FAQ',
'nav_contact': 'Kontakt',
'nav_about': 'Über Uns',
'nav_gallery': 'Galerie',
'nav_products': 'Produkte',
// Hero section
'hero_title': 'Revolutionäre Dachreinigung mit Drohnentechnologie',
'hero_subtitle': 'Schnelle, effiziente und unkomplizierte Reinigungslösungen für Ihr Dach.',
// Services section
'services_title': 'Unsere Leistungen',
'services_description': 'Erleben Sie modernste Drohnentechnologie für schnelle und effektive Dachreinigung und Wartung.',
'service_standard_title': 'Umfassende Dachreinigung',
'service_standard_description': 'Unser fortschrittlicher Reinigungsservice entfernt Schmutz, Moos und Algen.',
'service_inspection_title': 'Detaillierte Dachinspektion',
'service_inspection_description': 'Unsere Drohnen bieten hochauflösende Inspektionen, um potenzielle Probleme frühzeitig zu erkennen.',
'service_solar_title': 'Solaranlagen-Reinigung',
'service_solar_description': 'Maximieren Sie die Effizienz Ihrer Solarmodule mit unserem spezialisierten Reinigungsservice.',
// How it works section
'how_it_works_title': 'So funktioniert es',
'how_it_works_description': 'Unser optimierter Prozess sorgt für eine schnelle, effektive und stressfreie Dachreinigung.',
'how_it_works_step1': 'Inspektion vereinbaren',
'how_it_works_step2': 'Drohnenreinigung in Aktion',
'how_it_works_step3': 'Genießen Sie ein sauberes Dach',
'how_it_works_step1_desc': 'Füllen Sie unser einfaches Formular aus, um ein Angebot zu erhalten und Ihre Inspektion zu planen.',
'how_it_works_step2_desc': 'Unsere Techniker kommen mit speziellen Drohnen und Ausrüstung, um Ihr Dach zu reinigen.',
'how_it_works_step3_desc': 'Bewundern Sie Ihr sauberes Dach und genießen Sie die verlängerte Lebensdauer Ihres Dachmaterials.',
// FAQ section
'faq_title': 'Häufig gestellte Fragen',
'faq_q1': 'Wie lange dauert die Reinigung?',
'faq_a1': 'Die meisten Dächer werden innerhalb von 2-4 Stunden gereinigt, abhängig von Größe und Komplexität.',
'faq_q2': 'Ist die Drohnenreinigung sicher für mein Dach?',
'faq_a2': 'Ja, unsere Drohnen verwenden schonende, nicht-invasive Methoden, die Ihr Dach schützen.',
'faq_description': 'Finden Sie Antworten auf häufig gestellte Fragen zu unseren Drohnen-Dachreinigungsdiensten',
// Contact form
'contact_title': 'Kontaktieren Sie uns',
'contact_name': 'Ihr Name',
'contact_email': 'Ihre E-Mail',
'contact_phone': 'Telefonnummer',
'contact_address': 'Ihre Adresse',
'contact_message': 'Ihre Nachricht',
'contact_reset': 'Formular löschen',
'contact_send': 'Absenden',
'contact_description': 'Kontaktieren Sie uns für ein kostenloses Angebot oder um mehr über unsere Dienstleistungen zu erfahren',
// Footer
'footer_text': '© 2025 Luftglanz. Alle Rechte vorbehalten.',
// Language selection
'language_select': 'DE',
// Gallery page
'gallery_title': 'Unsere Arbeit in Aktion',
'gallery_description': 'Entdecken Sie unsere Galerie und sehen Sie, wie unsere Drohnen außergewöhnliche Reinigungsergebnisse liefern.',
'gallery_prev': 'Zurück',
'gallery_next': 'Weiter',
'gallery_image1_alt': 'Drohne reinigt ein Wohnhausdach',
'gallery_image2_alt': 'Drohne führt Dachinspektion durch',
'gallery_image3_alt': 'Drohne reinigt Solarmodule',
'gallery_image4_alt': 'Nahaufnahme des Drohnenreinigungsprozesses',
// Chatbot
'chatbot_title': 'Dachdrohnen-Assistent',
'chatbot_greeting': 'Hallo! Wie kann ich Ihnen heute bei Ihren Dachreinigungsbedürfnissen helfen?',
'chatbot_placeholder': 'Geben Sie hier Ihre Frage ein...',
'chatbot_offline_notice': 'Derzeit offline. Bitte nutzen Sie unser Kontaktformular für Unterstützung.',
// Company name variations
'roof_drone': 'Luftglanz',
'roof_drone_cleaning': 'Luftglanz',
// Additional translations for missing elements
'get_quote': 'Kostenloses Angebot',
// Services page
'services_hero_title': 'Professionelle Drohnen-Dachreinigung',
'services_hero_subtitle': 'Unsere spezialisierten Drohnen sorgen für schnelle und gründliche Reinigung mit fortschrittlicher Technologie',
'service_drone_title': 'Drohnen-Dachreinigung',
'service_drone_description': 'Unsere Drohnen sind mit speziellen Sprühsystemen ausgestattet, die effektiv Schmutz, Moos und Algen entfernen. Dieser kontaktlose Ansatz beseitigt das Risiko von Ziegelschäden und Arbeiterunfällen, die mit herkömmlichen Dachreinigungsmethoden verbunden sind.',
'service_inspection_detailed_title': 'Inspektionsdienstleistungen',
'service_inspection_detailed_description': 'Zusätzlich zur Reinigung können unsere Drohnen detaillierte Inspektionen Ihres Daches durchführen und potenzielle Probleme identifizieren, bevor sie zu kostspieligen Reparaturen werden. Unsere hochauflösenden Kameras erfassen jedes Detail und ermöglichen es uns, fehlende Schindeln, beschädigte Verkleidungen oder andere Probleme zu erkennen, die mit bloßem Auge übersehen werden könnten.',
'service_maintenance_title': 'Wartungspakete',
'service_maintenance_description': 'Wir bieten verschiedene Wartungspakete an, um Ihr Dach das ganze Jahr über in bestem Zustand zu halten und so Langlebigkeit und Ästhetik zu gewährleisten. Regelmäßige Wartung kann die Lebensdauer Ihres Daches um bis zu 25% verlängern und kostspielige Reparaturen verhindern. Unsere Pakete können an Ihre spezifischen Dachanforderungen und Ihr Budget angepasst werden.',
'premium_services_title': 'Premium-Zusatzleistungen',
'premium_services_description': 'Neben unseren Kerndienstleistungen bieten wir spezialisierte Behandlungen an, darunter Abdichtungsanwendungen, UV-Schutzbeschichtungen und Wärmescans zur Identifizierung potenzieller Wärmelecks. Diese zusätzlichen Dienstleistungen tragen dazu bei, die Leistung und Energieeffizienz Ihres Daches zu maximieren und seine Gesamtlebensdauer zu verlängern.',
'environmental_approach_title': 'Unser Effizienzkonzept',
'environmental_approach_description': 'Unsere präzise Drohnenanwendungsmethode reduziert den Wasserverbrauch im Vergleich zu herkömmlichen Hochdruckreinigungstechniken um bis zu 60%. Dies bedeutet eine schnellere, sauberere Reinigung in weniger Zeit.',
// Contact page
'contact_page_description': 'Haben Sie Fragen zu unseren Dienstleistungen oder möchten Sie eine Reinigung planen? Kontaktieren Sie uns mit einer der folgenden Methoden.',
'contact_phone_title': 'Telefon',
'contact_email_title': 'E-Mail',
'contact_address_title': 'Adresse',
// About page
'about_title': 'Über unser Unternehmen',
'about_description': 'Bei Dachdrohnen-Reinigung revolutionieren wir die Dachreinigungsbranche mit modernster Drohnentechnologie. Unser Unternehmen wurde 2020 gegründet und unsere Mission ist es, sichere, schnelle und gründliche Reinigungsdienstleistungen ohne die Risiken traditioneller Dachreinigungsmethoden anzubieten.',
'our_story_title': 'Unsere Geschichte',
'our_story_text': 'Die Idee für Dachdrohnen-Reinigung entstand, als unser Gründer die Gefahren erkannte, denen traditionelle Dachreiniger täglich ausgesetzt sind. Durch die Kombination von Fachwissen in Drohnentechnologie und Reinigungslösungen haben wir eine sicherere und effizientere Methode zur Dachwartung geschaffen.',
'our_team_caption': 'Das professionelle Dachdrohnen-Reinigungsteam',
'our_team_title': 'Unser Team',
'our_team_text': 'Unser Team besteht aus zertifizierten Drohnenbetreibern, Dachwartungsspezialisten und Kundendienstexperten, die sich dafür einsetzen, Ihnen das bestmögliche Erlebnis zu bieten.',
'our_technology_title': 'Unsere Technologie',
'our_technology_text': 'Wir verwenden speziell entwickelte Drohnen, die mit hochmoderner Reinigungsausrüstung ausgestattet sind, um sicherzustellen, dass Ihr Dach schnell und gründlich gereinigt wird.',
// Products page
'products_title': 'Hochwertige Reinigungsprodukte',
'products_subtitle': 'Entdecken Sie unsere professionellen Produkte für eine optimale Dach- und Oberflächenreinigung',
'product_details': 'Details ansehen',
'product_buy': 'Beim Hersteller kaufen',
'application_title': 'Anwendungshinweise',
'application_subtitle': 'So erzielen Sie die besten Ergebnisse mit unseren Produkten',
'ago_quart_alt': 'AGO Quart Grünbelagentferner',
'ago_quart_1l_alt': 'AGO Quart 1 Liter Flasche',
'ago_quart_5l_alt': 'AGO Quart 5 Liter Kanister',
'ago_sprayer_alt': 'AGO Drucksprüher 5 Liter'
},
'en': {
// Header
'header_title': 'Welcome to Luftglanz',
'roof_drone': 'Luftglanz',
// Navigation
'nav_home': 'Home',
'nav_services': 'Services',
'nav_how_it_works': 'How It Works',
'nav_faq': 'FAQ',
'nav_contact': 'Contact',
'nav_about': 'About Us',
'nav_gallery': 'Gallery',
'nav_products': 'Products',
// Hero section
'hero_title': 'Revolutionary Roof Cleaning with Drone Technology',
'hero_subtitle': 'Fast, efficient, and hassle-free cleaning solutions for your roof.',
// Services section
'services_title': 'Our Services',
'services_description': 'Experience cutting-edge drone technology for fast and effective roof cleaning and maintenance.',
'service_standard_title': 'Comprehensive Roof Cleaning',
'service_standard_description': 'Our advanced cleaning service removes dirt, moss, and algae, restoring your roof to its original condition.',
'service_inspection_title': 'Detailed Roof Inspection',
'service_inspection_description': 'Our drones provide high-resolution inspections to identify potential issues early.',
'service_solar_title': 'Solar Panel Cleaning',
'service_solar_description': 'Maximize the efficiency of your solar panels with our specialized cleaning service.',
// How it works section
'how_it_works_title': 'How It Works',
'how_it_works_description': 'Our streamlined process ensures fast, effective, and stress-free roof cleaning.',
'how_it_works_step1': 'Schedule an Inspection',
'how_it_works_step2': 'Drone Cleaning in Action',
'how_it_works_step3': 'Enjoy a Clean Roof',
'how_it_works_step1_desc': 'Fill out our simple form to get a quote and schedule your inspection.',
'how_it_works_step2_desc': 'Our technicians arrive with specialized drones and equipment to clean your roof.',
'how_it_works_step3_desc': 'Admire your clean roof and enjoy the extended lifespan of your roofing materials.',
// FAQ section
'faq_title': 'Frequently Asked Questions',
'faq_q1': 'How long does the cleaning take?',
'faq_a1': 'Most roofs are cleaned within 2-4 hours, depending on size and complexity.',
'faq_q2': 'Is drone cleaning safe for my roof?',
'faq_a2': 'Yes, our drones use gentle, non-invasive methods that protect your roof.',
'faq_description': 'Find answers to common questions about our drone roof cleaning services',
// Contact form
'contact_title': 'Contact Us',
'contact_name': 'Your Name',
'contact_email': 'Your Email',
'contact_phone': 'Phone Number',
'contact_address': 'Your Address',
'contact_message': 'Your Message',
'contact_reset': 'Reset Form',
'contact_send': 'Send',
'contact_description': 'Contact us for a free quote or to learn more about our services',
// Footer
'footer_text': '© 2025 Luftglanz. All rights reserved.',
// Language selection
'language_select': 'EN',
// Gallery page
'gallery_title': 'Our Work in Action',
'gallery_description': 'Explore our gallery and see how our drones deliver exceptional cleaning results.',
'gallery_prev': 'Previous',
'gallery_next': 'Next',
'gallery_image1_alt': 'Drone cleaning a residential roof',
'gallery_image2_alt': 'Drone performing roof inspection',
'gallery_image3_alt': 'Drone cleaning solar panels',
'gallery_image4_alt': 'Close-up of drone cleaning process',
// Chatbot
'chatbot_title': 'Roof Drone Assistant',
'chatbot_greeting': 'Hello! How can I assist you today with your roof cleaning needs?',
'chatbot_placeholder': 'Type your question here...',
'chatbot_offline_notice': 'Currently offline. Please use our contact form for assistance.',
// Company name variations
'roof_drone': 'Luftglanz',
'roof_drone_cleaning': 'Luftglanz',
// Additional translations for missing elements
'get_quote': 'Get a Free Quote',
// Services page
'services_hero_title': 'Professional Drone Roof Cleaning',
'services_hero_subtitle': 'Our specialized drones ensure fast and thorough cleaning with advanced technology',
'service_drone_title': 'Drone Roof Cleaning',
'service_drone_description': 'Our drones are equipped with specialized spray systems that effectively remove dirt, moss, and algae. This contactless approach eliminates the risk of tile damage and worker accidents associated with traditional roof cleaning methods.',
'service_inspection_detailed_title': 'Inspection Services',
'service_inspection_detailed_description': 'In addition to cleaning, our drones can perform detailed inspections of your roof, identifying potential issues before they become costly repairs. Our high-resolution cameras capture every detail, allowing us to spot missing shingles, damaged flashing, or other problems that might be overlooked with the naked eye.',
'service_maintenance_title': 'Maintenance Packages',
'service_maintenance_description': 'We offer various maintenance packages to keep your roof in top condition year-round, ensuring longevity and aesthetics. Regular maintenance can extend the lifespan of your roof by up to 25% and prevent costly repairs. Our packages can be tailored to your specific roof requirements and budget.',
'premium_services_title': 'Premium Add-On Services',
'premium_services_description': 'In addition to our core services, we offer specialized treatments, including sealing applications, UV protection coatings, and thermal scans to identify potential heat leaks. These additional services help maximize your roofs performance and energy efficiency while extending its overall lifespan.',
'environmental_approach_title': 'Our Efficiency Concept',
'environmental_approach_description': 'Our precise drone application method reduces water usage by up to 60% compared to traditional pressure washing techniques. This means faster, cleaner results in less time.',
// Contact page
'contact_page_description': 'Have questions about our services or want to schedule a cleaning? Contact us using one of the following methods.',
'contact_phone_title': 'Phone',
'contact_email_title': 'Email',
'contact_address_title': 'Address',
// About page
'about_title': 'About Our Company',
'about_description': 'At Roof Drone Cleaning, we are revolutionizing the roof cleaning industry with cutting-edge drone technology. Founded in 2020, our mission is to provide safe, fast, and thorough cleaning services without the risks of traditional roof cleaning methods.',
'our_story_title': 'Our Story',
'our_story_text': 'The idea for Roof Drone Cleaning came when our founder recognized the dangers traditional roof cleaners face daily. By combining expertise in drone technology and cleaning solutions, we have created a safer and more efficient method for roof maintenance.',
'our_team_caption': 'The Professional Roof Drone Cleaning Team',
'our_team_title': 'Our Team',
'our_team_text': 'Our team consists of certified drone operators, roof maintenance specialists, and customer service experts dedicated to providing you with the best experience possible.',
'our_technology_title': 'Our Technology',
'our_technology_text': 'We use specially designed drones equipped with state-of-the-art cleaning equipment to ensure your roof is cleaned quickly and thoroughly.',
// Products page
'products_title': 'High-Quality Cleaning Products',
'products_subtitle': 'Discover our professional products for optimal roof and surface cleaning',
'product_details': 'View Details',
'product_buy': 'Buy from Manufacturer',
'application_title': 'Application Instructions',
'application_subtitle': 'Achieve the best results with our products',
'ago_quart_alt': 'AGO Quart Green Stain Remover',
'ago_quart_1l_alt': 'AGO Quart 1 Liter Bottle',
'ago_quart_5l_alt': 'AGO Quart 5 Liter Canister',
'ago_sprayer_alt': 'AGO Pressure Sprayer 5 Liter'
}
};
// Export the translations object
if (typeof module !== 'undefined' && module.exports) {
module.exports = translations;
}