🌿 GROWLINGER 3.5 PRO
📸 Zeitraffer & Foto-Dokumentation
📷
Kamera starten um Fotos aufzunehmen
📷 Foto-Galerie (0 Fotos)
🧮 Nährstoff-Rechner
📊 pH & EC Management
📊 3D Grow Room
🎮 Erfolge & Fortschritt
🏅 Erfolge
📝
📊
Dash
📸
Foto
🧬
DNA
🧮
Calc
📊
3D
`);
},// STRAIN FUNCTIONS
loadStrains() {
this.strains = [...this.strainDatabase];
this.renderStrains();
},searchStrains(query) {
const filtered = this.strains.filter(s =>
s.name.toLowerCase().includes(query.toLowerCase()) ||
s.type.toLowerCase().includes(query.toLowerCase())
);
this.renderStrains(filtered);
},renderStrains(list = this.strains) {
const container = document.getElementById('strainList');
if (list.length === 0) {
container.innerHTML = '
Keine Strains gefunden
';
return;
}
container.innerHTML = list.map(s => `
THC: ${s.thc}%
CBD: ${s.cbd}%
Blüte: ${s.flowering}d
Ertrag: ${s.yield}g/m²
`).join('');
},selectStrain(name) {
const strain = this.strains.find(s => s.name === name);
if (!strain) return;
const confirm = window.confirm(`🧬 ${strain.name} auswählen?\n\n${strain.desc}\n\nTyp: ${strain.type}\nTHC: ${strain.thc}%\nBlütezeit: ${strain.flowering} Tage`);
if (confirm) {
this.currentStrain = strain;
this.save();
this.addXP(50, `🧬 Strain gewählt: ${strain.name}`);
alert(`✅ ${strain.name} als aktueller Strain gesetzt!`);
}
},addCustomStrain() {
const name = document.getElementById('customStrainName').value.trim();
const type = document.getElementById('customType').value;
const thc = parseFloat(document.getElementById('customThc').value) || 0;
if (!name) {
alert('❌ Bitte Strain-Namen eingeben!');
return;
}
const strain = {
name,
type,
thc,
cbd: 0.1,
flowering: 60,
yield: 400,
desc: 'Custom Strain',
custom: true
};
this.strains.push(strain);
this.renderStrains();
this.save();
this.addXP(30, '➕ Eigener Strain hinzugefügt');
document.getElementById('customStrainName').value = '';
document.getElementById('customThc').value = '';
alert(`✅ ${name} zur Datenbank hinzugefügt!`);
},// NUTRIENT FUNCTIONS
calculateNutrients() {
const water = parseFloat(document.getElementById('waterAmount').value) || 10;
const phase = document.getElementById('nutrientPhase').value;
const brand = document.getElementById('nutrientBrand').value;
const recipe = this.nutrientRecipes[brand][phase];
let html = '
';
html += `
📋 ${brand.toUpperCase()} - ${phase.toUpperCase()}
`;
html += `
`;
Object.keys(recipe).forEach(nutrient => {
const mlPerLiter = recipe[nutrient];
const totalMl = (mlPerLiter * water).toFixed(1);
html += `
`;
html += `${nutrient}:`;
html += `${totalMl} ml (${mlPerLiter} ml/L)`;
html += `
`;
});
html += `
`;
html += `
`;
html += `💡 Tipp: Nährstoffe in der Reihenfolge zugeben: Mikro → Gro → Bloom`;
html += `
`;
html += `
`;
document.getElementById('nutrientResult').innerHTML = html;
this.addXP(15, '🧪 Nährstoffe berechnet');
},checkPhEc() {
const ph = parseFloat(document.getElementById('currentPh').value);
const ec = parseFloat(document.getElementById('currentEc').value);
if (!ph || !ec) {
alert('❌ Bitte beide Werte eingeben!');
return;
}
let html = '';
// pH Check
if (ph < 5.5) {
html += `
⚠️ pH zu niedrig (${ph})! Ziel: 5.8-6.5
➜ pH+ hinzufügen
`;
} else if (ph > 6.8) {
html += `
⚠️ pH zu hoch (${ph})! Ziel: 5.8-6.5
➜ pH- hinzufügen
`;
} else if (ph >= 5.8 && ph <= 6.5) {
html += `
✅ pH optimal (${ph})
`;
} else {
html += `
⚠️ pH suboptimal (${ph}). Ziel: 5.8-6.5
`;
}
// EC Check
if (ec < 0.8) {
html += `
⚠️ EC niedrig (${ec} mS/cm)
➜ Mehr Nährstoffe
`;
} else if (ec > 2.5) {
html += `
⚠️ EC zu hoch (${ec} mS/cm)!
➜ Mit Wasser verdünnen
`;
} else if (ec >= 1.0 && ec <= 2.0) {
html += `
✅ EC optimal (${ec} mS/cm)
`;
} else {
html += `
ℹ️ EC: ${ec} mS/cm (akzeptabel)
`;
}
document.getElementById('phEcResult').innerHTML = html;
this.addXP(10, '📊 pH/EC geprüft');
},// COST FUNCTIONS
addCost() {
const category = document.getElementById('costCategory').value;
const amount = parseFloat(document.getElementById('costAmount').value);
const description = document.getElementById('costDescription').value.trim();
if (!amount || amount <= 0) {
alert('❌ Bitte gültigen Betrag eingeben!');
return;
}
const cost = {
id: Date.now(),
category,
amount,
description,
date: new Date().toISOString()
};
this.costs.push(cost);
this.renderCosts();
this.save();
this.addXP(5, '💰 Kosten eingetragen');
document.getElementById('costAmount').value = '';
document.getElementById('costDescription').value = '';
alert(`✅ ${amount.toFixed(2)}€ eingetragen!`);
},renderCosts() {
const summary = document.getElementById('costSummary');
const list = document.getElementById('costList');
const total = this.costs.reduce((sum, c) => sum + c.amount, 0);
const categories = {
electricity: { name: 'Strom', icon: '💡', total: 0 },
water: { name: 'Wasser', icon: '💧', total: 0 },
nutrients: { name: 'Nährstoffe', icon: '🌿', total: 0 },
substrate: { name: 'Substrate', icon: '🌱', total: 0 },
equipment: { name: 'Equipment', icon: '🔧', total: 0 },
seeds: { name: 'Seeds', icon: '🌰', total: 0 },
other: { name: 'Sonstiges', icon: '📦', total: 0 }
};
this.costs.forEach(c => {
if (categories[c.category]) {
categories[c.category].total += c.amount;
}
});
summary.innerHTML = Object.values(categories).map(cat => `
${cat.icon} ${cat.name}
${cat.total.toFixed(2)}€
`).join('');
if (this.costs.length === 0) {
list.innerHTML = '
Keine Kosten eingetragen
';
return;
}
list.innerHTML = `
Gesamtkosten
${total.toFixed(2)}€
${this.costs.slice().reverse().slice(0, 10).map(c => `
${categories[c.category]?.name || c.category}
${c.description ? `
${c.description}
` : ''}
${new Date(c.date).toLocaleDateString('de-DE')}
${c.amount.toFixed(2)}€
`).join('')}
`;
},calculateROI() {
const yieldG = parseFloat(document.getElementById('expectedYield').value);
const pricePerG = parseFloat(document.getElementById('streetPrice').value);
if (!yieldG || !pricePerG) {
alert('❌ Bitte alle Felder ausfüllen!');
return;
}
const totalCosts = this.costs.reduce((sum, c) => sum + c.amount, 0);
const revenue = yieldG * pricePerG;
const profit = revenue - totalCosts;
const roi = totalCosts > 0 ? (profit / totalCosts * 100) : 0;
const html = `
💎 ROI Analyse
Kosten
${totalCosts.toFixed(2)}€
Wert
${revenue.toFixed(2)}€
Gewinn / ROI
${profit > 0 ? '+' : ''}${profit.toFixed(2)}€
${roi > 0 ? '+' : ''}${roi.toFixed(1)}%
${yieldG}g × ${pricePerG}€/g = ${revenue.toFixed(2)}€
Kosten pro Gramm: ${(totalCosts / yieldG).toFixed(2)}€/g
`;
document.getElementById('roiResult').innerHTML = html;
this.addXP(20, '💰 ROI berechnet');
},// 3D VIEW FUNCTIONS
init3DView() {
if (this.renderer3d) return; // Already initialized
const container = document.getElementById('threejs-container');
const canvas = document.getElementById('canvas3d');
// Scene
this.scene3d = new THREE.Scene();
this.scene3d.background = new THREE.Color(0x0a0f1a);
// Camera
this.camera3d = new THREE.PerspectiveCamera(75, container.offsetWidth / container.offsetHeight, 0.1, 1000);
this.camera3d.position.set(5, 5, 5);
this.camera3d.lookAt(0, 0, 0);
// Renderer
this.renderer3d = new THREE.WebGLRenderer({ canvas, antialias: true });
this.renderer3d.setSize(container.offsetWidth, container.offsetHeight);
// Lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
this.scene3d.add(ambientLight);
this.growLight = new THREE.PointLight(0x22c55e, 1, 100);
this.growLight.position.set(0, 5, 0);
this.scene3d.add(this.growLight);
// Grow Tent (Room)
const roomGeometry = new THREE.BoxGeometry(8, 6, 8);
const roomMaterial = new THREE.MeshBasicMaterial({
color: 0x1e293b,
wireframe: true,
transparent: true,
opacity: 0.3
});
const room = new THREE.Mesh(roomGeometry, roomMaterial);
room.position.y = 3;
this.scene3d.add(room);
// Floor
const floorGeometry = new THREE.PlaneGeometry(8, 8);
const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x334155 });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
this.scene3d.add(floor);
// Plants
for (let i = 0; i < 4; i++) {
const plantGroup = new THREE.Group();
// Pot
const potGeometry = new THREE.CylinderGeometry(0.3, 0.2, 0.4, 8);
const potMaterial = new THREE.MeshStandardMaterial({ color: 0x8b4513 });
const pot = new THREE.Mesh(potGeometry, potMaterial);
pot.position.y = 0.2;
plantGroup.add(pot);
// Plant
const plantGeometry = new THREE.SphereGeometry(0.4, 8, 8);
const plantMaterial = new THREE.MeshStandardMaterial({ color: 0x22c55e });
const plant = new THREE.Mesh(plantGeometry, plantMaterial);
plant.position.y = 0.8;
plant.scale.y = 1.5;
plantGroup.add(plant);
// Position plants in grid
const x = (i % 2) * 2 - 1;
const z = Math.floor(i / 2) * 2 - 1;
plantGroup.position.set(x * 1.5, 0, z * 1.5);
this.scene3d.add(plantGroup);
}
// Sensor
const sensorGeometry = new THREE.BoxGeometry(0.2, 0.2, 0.1);
const sensorMaterial = new THREE.MeshStandardMaterial({ color: 0x3b82f6 });
this.sensor3d = new THREE.Mesh(sensorGeometry, sensorMaterial);
this.sensor3d.position.set(2, 1, 2);
this.scene3d.add(this.sensor3d);
// Animation
this.animate3D();
// Handle resize
window.addEventListener('resize', () => {
if (this.camera3d && this.renderer3d) {
this.camera3d.aspect = container.offsetWidth / container.offsetHeight;
this.camera3d.updateProjectionMatrix();
this.renderer3d.setSize(container.offsetWidth, container.offsetHeight);
}
});
this.rotation3DActive = true;
},animate3D() {
if (!this.renderer3d) return;
requestAnimationFrame(() => this.animate3D());
if (this.rotation3DActive) {
this.camera3d.position.x = Math.cos(Date.now() * 0.0005) * 7;
this.camera3d.position.z = Math.sin(Date.now() * 0.0005) * 7;
this.camera3d.lookAt(0, 2, 0);
}
// Pulsing sensor
if (this.sensor3d) {
this.sensor3d.material.emissive = new THREE.Color(0x3b82f6);
this.sensor3d.material.emissiveIntensity = 0.5 + Math.sin(Date.now() * 0.005) * 0.5;
}
this.renderer3d.render(this.scene3d, this.camera3d);
},toggle3DRotation() {
this.rotation3DActive = !this.rotation3DActive;
const btn = document.getElementById('rotate3dBtn');
btn.textContent = this.rotation3DActive ? '⏸️ Pause' : '▶️ Rotation';
},toggle3DLight() {
if (!this.growLight) return;
this.growLight.visible = !this.growLight.visible;
const btn = document.getElementById('toggle3dLightBtn');
btn.textContent = this.growLight.visible ? '💡 Licht aus' : '🌑 Licht an';
},// ACHIEVEMENT SYSTEM
initAchievements() {
this.achievements = [
{ id: 'first_photo', name: 'Erster Schnappschuss', desc: 'Erstes Foto aufgenommen', icon: '📸', unlocked: false, xp: 50 },
{ id: 'sensor_master', name: 'Sensor-Meister', desc: '100 Datenpunkte gesammelt', icon: '🛰️', unlocked: false, xp: 100 },
{ id: 'strain_collector', name: 'Strain-Sammler', desc: '5 Strains in Datenbank', icon: '🧬', unlocked: false, xp: 75 },
{ id: 'cost_tracker', name: 'Buchhalter', desc: '10 Kosten eingetragen', icon: '💰', unlocked: false, xp: 50 },
{ id: 'week_streak', name: 'Wöchentlicher Check', desc: '7 Tage Streak', icon: '🔥', unlocked: false, xp: 150 },
{ id: 'ml_expert', name: 'KI-Experte', desc: '10 ML Predictions', icon: '🤖', unlocked: false, xp: 200 },
{ id: 'photo_album', name: 'Fotograf', desc: '25 Fotos aufgenommen', icon: '📷', unlocked: false, xp: 150 },
{ id: 'nutrient_pro', name: 'Nährstoff-Profi', desc: '20 Berechnungen', icon: '🧪', unlocked: false, xp: 100 }
];
this.renderAchievements();
this.updateXPDisplay();
},renderAchievements() {
const container = document.getElementById('achievementsList');
container.innerHTML = this.achievements.map(a => `
${a.icon}
${a.unlocked ? '
✅ Freigeschaltet
' : '
🔒 Gesperrt
'}
+${a.xp} XP
`).join('');
},addXP(amount, reason) {
this.xp += amount;
const oldLevel = this.level;
this.level = Math.floor(1 + this.xp / 500);
this.updateXPDisplay();
this.save();
if (this.level > oldLevel) {
this.showNotification(`🎉 Level ${this.level} erreicht!`, 'success');
}
// Check achievements
this.checkAchievements();
},updateXPDisplay() {
document.getElementById('totalXP').textContent = this.xp + ' XP';
document.getElementById('userLevel').textContent = this.level;
const xpForNextLevel = this.level * 500;
const xpInCurrentLevel = this.xp % 500;
const progress = (xpInCurrentLevel / 500) * 100;
document.getElementById('xpBar').style.width = progress + '%';
},checkAchievements() {
// First photo
if (!this.achievements[0].unlocked && this.photos.length >= 1) {
this.unlockAchievement('first_photo');
}
// Sensor master
if (!this.achievements[1].unlocked && this.sensorData.temps.length >= 100) {
this.unlockAchievement('sensor_master');
}
// Strain collector
if (!this.achievements[2].unlocked && this.strains.filter(s => s.custom).length >= 5) {
this.unlockAchievement('strain_collector');
}
// Cost tracker
if (!this.achievements[3].unlocked && this.costs.length >= 10) {
this.unlockAchievement('cost_tracker');
}
// Photo album
if (!this.achievements[6].unlocked && this.photos.length >= 25) {
this.unlockAchievement('photo_album');
}
},unlockAchievement(id) {
const achievement = this.achievements.find(a => a.id === id);
if (!achievement || achievement.unlocked) return;
achievement.unlocked = true;
this.xp += achievement.xp;
this.updateXPDisplay();
this.renderAchievements();
this.save();
this.showNotification(`🏆 Erfolg freigeschaltet: ${achievement.name}!`, 'success');
},checkDailyStreak() {
const lastVisit = localStorage.getItem('growlinger_last_visit');
const today = new Date().toDateString();
if (lastVisit !== today) {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
if (lastVisit === yesterday.toDateString()) {
this.streak++;
this.addXP(10, 'Täglicher Besuch');
} else {
this.streak = 1;
}
localStorage.setItem('growlinger_last_visit', today);
this.save();
}
},showNotification(message, type = 'info') {
const div = document.createElement('div');
div.className = `alert alert-${type}`;
div.textContent = message;
div.style.position = 'fixed';
div.style.top = '20px';
div.style.right = '20px';
div.style.zIndex = '9999';
div.style.animation = 'slideIn 0.3s ease-out';
document.body.appendChild(div);
setTimeout(() => {
div.style.animation = 'slideOut 0.3s ease-in';
setTimeout(() => div.remove(), 300);
}, 3000);
},fabMenu() {
const actions = ['📸 Schnellfoto', '📝 Notiz', '💧 Gießen', '🌿 Düngen', '⚠️ Problem'];
const choice = prompt(`Schnellaktionen:\n\n${actions.map((a,i) => `${i+1}. ${a}`).join('\n')}\n\nWähle (1-5):`);
if (choice && choice >= 1 && choice <= 5) {
const action = actions[parseInt(choice)-1];
if (choice == 1) {
document.querySelector('[data-tab="camera"]').click();
} else {
this.addXP(5, action);
alert(`✅ ${action}\n\n📅 ${new Date().toLocaleString('de-DE')}`);
}
}
},save() {
try {
const data = {
sensorData: this.sensorData,
photos: this.photos.slice(-50).map(p => ({ ...p, data: p.data?.substring(0, 50000) })),
costs: this.costs,
strains: this.strains.filter(s => s.custom),
achievements: this.achievements,
xp: this.xp,
level: this.level,
streak: this.streak,
currentStrain: this.currentStrain,
timestamp: Date.now()
};
localStorage.setItem('growlinger_data', JSON.stringify(data));
} catch(e) {
console.warn('Save error (quota?):', e);
}
},loadStorage() {
try {
const stored = localStorage.getItem('growlinger_data');
if (stored) {
const data = JSON.parse(stored);
this.sensorData = data.sensorData || this.sensorData;
this.photos = data.photos || [];
this.costs = data.costs || [];
this.achievements = data.achievements || this.achievements;
this.xp = data.xp || 0;
this.level = data.level || 1;
this.streak = data.streak || 0;
this.currentStrain = data.currentStrain;
if (data.strains && data.strains.length > 0) {
this.strains = [...this.strainDatabase, ...data.strains];
}
console.log(`✅ Loaded: ${this.photos.length} photos, ${this.costs.length} costs`);
this.renderPhotos();
this.renderCosts();
}
} catch(e) {
console.warn('Load error:', e);
}
}
};// START APP
document.addEventListener('DOMContentLoaded', () => {
app.init();
setInterval(() => app.save(), 30000);
});