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:
427
js/ai-chat.js
Normal file
427
js/ai-chat.js
Normal 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
334
js/background-patterns.js
Normal 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
139
js/bright-theme.js
Normal 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
39
js/contact-form.js
Normal 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
86
js/dark-mode.js
Normal 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
154
js/drone-animation.js
Normal 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
486
js/drone-image-animation.js
Normal 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
66
js/language-manager.js
Normal 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
19
js/main.js
Normal 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
45
js/mobile-menu.js
Normal 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
28
js/navigation.js
Normal 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');
|
||||
}
|
||||
});
|
||||
});
|
||||
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;
|
||||
}
|
||||
})();
|
||||
94
js/star-background.js
Normal file
94
js/star-background.js
Normal 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
267
js/translations.js
Normal 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 roof’s 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;
|
||||
}
|
||||
Reference in New Issue
Block a user