1/** 2 * Assistive Calculator Module 3 * Handles the assistive (simplified) calculator functionality. 4 * This handles a step-by-step calculator experience with audio guidance. 5 */ 6document.addEventListener("DOMContentLoaded", function() { 7 // Add custom appliance counter at the top with other state variables 8 let customApplianceCount = 0; 9 10 // ----------- LANGUAGE SYSTEM ----------- 11 12 // Default language is English - always 13 let currentLanguage = 'en'; 14 15 // Language translations 16 const translations = { 17 en: { 18 // Header and navigation 19 'main-title': 'Solar Calculator - Assistive Mode', 20 'main-subtitle': 'Find the right solar system for your home', 21 'main-title-bar': 'Solar Calculator - Assistive Mode', 22 'main-subtitle-bar': 'Find the right solar system for your home', 23 'listen-text': 'Listen to Instructions', 24 'stop-text': 'Stop Audio', 25 'step1-label': '1. Select Home Type', 26 'step2-label': '2. Select Appliances', 27 'step3-label': '3. Get Results', 28 'next-button-text': 'Next', 29 'back-button-text': 'Back', 30 'next-button-text-2': 'Next', 31 'back-button-text-2': 'Back', 32 'print-button-text': 'Print Results', 33 'template-title': 'Quick Templates', 34 'template-subtitle': 'Select a template based on home size', 35 'template-1bed': '1 Bedroom', 36 'template-2bed': '2 Bedrooms', 37 'template-3bed': '3 Bedrooms', 38 'template-4bed': '4+ Bedrooms', 39 40 // Step 1 - Home Type 41 'home-type-question': 'What type of home do you have?', 42 'home-type-subtitle': 'Select the option that best matches your home', 43 'bedroom1-label': '1 Bedroom', 44 'bedroom2-label': '2 Bedrooms', 45 'bedroom3-label': '3 Bedrooms', 46 'bedroom4-label': '4+ Bedrooms', 47 'sunlight-question': 'How much sunlight do you get?', 48 'less-sun-label': 'Less Sun', 49 'average-sun-label': 'Average Sun', 50 'more-sun-label': 'More Sun', 51 'backup-question': 'How long should backup power last?', 52 'half-day-label': 'Half Day', 53 'one-day-label': '1 Day', 54 'two-days-label': '2 Days', 55 56 // Step 2 - Appliances 57 'appliances-title': 'Select your appliances', 58 'appliances-subtitle': 'Tap on the appliances you use regularly', 59 'category-all': 'All', 60 'category-lighting': 'Lights', 61 'category-kitchen': 'Kitchen', 62 'category-cooling': 'Cooling', 63 'category-entertainment': 'Entertainment', 64 'category-work-leisure': 'Work & Leisure', 65 'category-laundry': 'Laundry', 66 'category-general': 'General', 67 68 // Lighting 69 'appliance-led-lights': 'LED Lights (10x)', 70 'appliance-lights': 'Lights', 71 72 // Kitchen 73 'appliance-fridge-freezer-large': 'Fridge/Freezer - Large', 74 'appliance-freezer-chest': 'Freezer - Chest', 75 'appliance-microwave': 'Microwave', 76 'appliance-dishwasher': 'Dishwasher', 77 'appliance-electric-oven': 'Electric Oven', 78 'appliance-gas-oven': 'Gas Oven', 79 'appliance-kettle': 'Kettle', 80 'appliance-toaster': 'Toaster', 81 82 // Cooling 83 'appliance-fan-ceiling': 'Fan - Ceiling', 84 'appliance-air-conditioner-large': 'Air Conditioner - Large', 85 'appliance-air-conditioner-small': 'Air Conditioner - Small', 86 87 // Entertainment 88 'appliance-tv-led': 'TV (LED)', 89 'appliance-gaming': 'Gaming Console', 90 91 // Work & Leisure 92 'appliance-laptop-computers': 'Laptop / Computers', 93 'appliance-coffee-machine': 'Coffee Machine', 94 'appliance-electric-stovetop-large': 'Electric Stovetop - Large', 95 'appliance-electric-stovetop-small': 'Electric Stovetop - Small', 96 'appliance-modem-internet': 'Modem/Internet', 97 98 // Laundry 99 'appliance-washing-machine': 'Washing Machine', 100 'appliance-dryer': 'Dryer', 101 'appliance-hair-dryer': 'Hair Dryer', 102 103 // General 104 'appliance-power-tools-charging': 'Charging for Common Power Tools', 105 'appliance-pressure-pump': 'Pressure Pump', 106 'appliance-custom': 'Custom Appliance', 107 108 // Step 3 - Results 109 'results-title': 'Your Solar Power Results', 110 'results-subtitle': 'Here\'s what you need for your home', 111 'result-solar-panels': 'Solar Panels', 112 'solar-panel-desc': 'These capture sunlight to power your home', 113 'result-battery': 'Battery', 114 'battery-desc': 'Stores energy for use when there\'s no sun', 115 'result-system-size': 'System Size', 116 'system-size-desc': 'Powers all your selected appliances', 117 'selected-title': 'Your Selected Appliances:', 118 'total-title': 'Total Daily Energy Use:', 119 'template-applied': 'Template Applied', 120 'warning-text': 'These estimates may not be exact. Actual energy usage can vary based on specific models and usage patterns.', 121 122 // New audio guide translations 123 'voice-guide-title': 'Voice Guide Available', 124 'voice-guide-message': 'Each step has voice instructions to help you', 125 'step1-voice': 'Step 1: Home Type', 126 'step2-voice': 'Step 2: Appliances', 127 'step3-voice': 'Step 3: Results', 128 129 // Audio instructions (kept the same) 130 'home-audio': 'Welcome to the Solar Calculator. In this first step, select your home type, how much sunlight you get, and how long you need backup power to last.',
131 'appliances-audio': 'Now, select the appliances you use in your home. Tap the plus button to add appliances, or minus to remove them.', 132 'results-audio': 'Here are your results. This shows the solar panels, battery, and system size you need based on your selections.', 133 134 'energy-legend-title': 'Energy Consumption Guide', 135 'energy-low-text': 'Low Energy: Efficient appliances', 136 'energy-medium-text': 'Medium Energy: Standard consumption', 137 'energy-high-text': 'High Energy: Power-intensive appliances', 138 'legend-show-text': 'Show Energy Guide', 139 'legend-hide-text': 'Hide Energy Guide' 140 }, 141 tok: { 142 // Header and navigation 143 'main-title': 'Solar Kalkulator - Isi Moud', 144 'main-subtitle': 'Painim gutpela solar sistem bilong haus bilong yu', 145 'main-title-bar': 'Solar Kalkulator - Isi Moud', 146 'main-subtitle-bar': 'Painim gutpela solar sistem bilong haus bilong yu', 147 'listen-text': 'Harim Toktok', 148 'stop-text': 'Stopim Toktok', 149 'step1-label': '1. Makim Haus Kaen', 150 'step2-label': '2. Makim Ol Applaens', 151 'step3-label': '3. Kisim Risalts', 152 'next-button-text': 'Go het', 153 'back-button-text': 'Go bek', 154 'next-button-text-2': 'Go het', 155 'back-button-text-2': 'Go bek', 156 'print-button-text': 'Printim Result', 157 'template-title': 'Kwik Tamplat', 158 'template-subtitle': 'Makim tamplat bilong haus bikpela', 159 'template-1bed': '1 Rum', 160 'template-2bed': '2 Rum', 161 'template-3bed': '3 Rum', 162 'template-4bed': '4+ Rum', 163 164 // Step 1 - Home Type 165 'home-type-question': 'Wanem kain haus yu gat?', 166 'home-type-subtitle': 'Makim haus we em i wankain olsem haus bilong yu', 167 'bedroom1-label': '1 Rum', 168 'bedroom2-label': '2 Rum', 169 'bedroom3-label': '3 Rum', 170 'bedroom4-label': '4+ Rum', 171 'sunlight-question': 'Hamas san yu save kisim?', 172 'less-sun-label': 'Liklik San', 173 'average-sun-label': 'Namel San', 174 'more-sun-label': 'Planti San', 175 'backup-question': 'Hamas taim yu laikim pawa i stap?', 176 'half-day-label': 'Hap De', 177 'one-day-label': '1 De', 178 'two-days-label': '2 De', 179 180 // Step 2 - Appliances 181 'appliances-title': 'Makim ol samting lektrik', 182 'appliances-subtitle': 'Paitim ol samting lektrik yu save yusim long haus', 183 'category-all': 'Olgeta', 184 'category-lighting': 'Lait', 185 'category-kitchen': 'Kuk Haus', 186 'category-cooling': 'Kol Samting', 187 'category-entertainment': 'Lukluk Samting', 188 'category-work-leisure': 'Wok & Malolo', 189 'category-laundry': 'Wasim Klos', 190 'category-general': 'Narapela Samting', 191 192 // Lighting 193 'appliance-led-lights': 'LED Lait (10x)', 194 'appliance-lights': 'Lait', 195 196 // Kitchen 197 'appliance-fridge-freezer-large': 'Bikpela Fris/Frisa', 198 'appliance-freezer-chest': 'Bokis Frisa', 199 'appliance-microwave': 'Maekroweiv', 200 'appliance-dishwasher': 'Wasim Plet Masin', 201 'appliance-electric-oven': 'Oven Lektrik', 202 'appliance-gas-oven': 'Oven Ges', 203 'appliance-kettle': 'Ketol', 204 'appliance-toaster': 'Tosta', 205 206 // Cooling 207 'appliance-fan-ceiling': 'Fan Bilong Ruf', 208 'appliance-air-conditioner-large': 'Bikpela Ea Kondisena', 209 'appliance-air-conditioner-small': 'Liklik Ea Kondisena', 210 211 // Entertainment 212 'appliance-tv-led': 'TV (LED)', 213 'appliance-gaming': 'Pilai Gem', 214 215 // Work & Leisure 216 'appliance-laptop-computers': 'Lepkop / Kompyuta', 217 'appliance-coffee-machine': 'Masin Kopi', 218 'appliance-electric-stovetop-large': 'Bikpela Stov Lektrik', 219 'appliance-electric-stovetop-small': 'Liklik Stov Lektrik', 220 'appliance-modem-internet': 'Modem/Intanet', 221 222 // Laundry 223 'appliance-washing-machine': 'Wasim Masin', 224 'appliance-dryer': 'Draia', 225 'appliance-hair-dryer': 'Draia Bilong Gras', 226 227 // General 228 'appliance-power-tools-charging': 'Sasim Tul Bilong Wok', 229 'appliance-pressure-pump': 'Pam Bilong Wara', 230 'appliance-custom': 'Narapela Samting Lektrik', 231 232 // Step 3 - Results 233 'results-title': 'Solar Pawa Result bilong Yu', 234 'results-subtitle': 'Dispela em samting yu nidim bilong haus bilong yu', 235 'result-solar-panels': 'Solar Panel', 236 'solar-panel-desc': 'Ol i kisim san na tanim i go long pawa', 237 'result-battery': 'Betri', 238 'battery-desc': 'Holim pawa bilong yusim taim i nogat san', 239 'result-system-size': 'Bikpela bilong Sistem', 240 'system-size-desc': 'Givim pawa long ol samting lektrik yu makim', 241 'selected-title': 'Ol Samting Lektrik Yu Makim:', 242 'total-title': 'Bikpela Pawa Yusim long Wanpela De:', 243 'template-applied': 'Samting Lektrik I Redi Nau', 244 'warning-text': 'Ol dispela namba em i no stret olgeta. Tru pawa yus bilong yu inap long senis long wanem kain masin yu gat na olsem wanem yu save yusim.', 245 246 // New audio guide translations 247 'voice-guide-title': 'Gaid Bilong Harim i Stap', 248 'voice-guide-message': 'Olgeta step i gat toktok bilong helpim yu', 249 'step1-voice': 'Step 1: Makim Haus Kaen', 250 'step2-voice': 'Step 2: Makim Ol Applaens', 251 'step3-voice': 'Step 3: Kisim Risalts', 252 253 // Audio instructions (kept the same) 254 'home-audio': 'Welkam long Solar Kalkulator. Long dispela hap, makim wanem kain haus bilong yu, hamas
254san yu kisim, na hamas taim yu laikim pawa i stap.', 255 'appliances-audio': 'Nau, makim ol samting lektrik yu save yusim long haus. Paitim plus botun bilong putim samting, o minus long rausim.', 256 'results-audio': 'Dispela em result bilong yu. Em i soim ol solar panel, betri, na bikpela bilong sistem yu nidim long ol samting yu makim.', 257 258 // Energy indicator translations 259 'energy-legend-title': 'Gaid bilong Pawa Yus', 260 'energy-low-text': 'Liklik Pawa: Gut samting lektrik', 261 'energy-medium-text': 'Namel Pawa: Nomol samting lektrik', 262 'energy-high-text': 'Bikpela Pawa: Samting i yusim planti pawa', 263 'legend-show-text': 'Soim Pawa Gaid', 264 'legend-hide-text': 'Haitim Pawa Gaid' 265 } 266 }; 267 268 // Set the UI language 269 const setLanguage = (lang) => { 270 currentLanguage = lang; 271 272 // Update all text elements with translations 273 Object.keys(translations[lang]).forEach(key => { 274 const element = document.getElementById(key); 275 if (element) { 276 element.textContent = translations[lang][key]; 277 } 278 }); 279 280 // Save language preference 281 localStorage.setItem('preferredLanguage', lang); 282 283 // Update active language button 284 document.querySelectorAll('.lang-btn').forEach(btn => { 285 btn.classList.remove('active'); 286 if (btn.dataset.lang === lang) { 287 btn.classList.add('active'); 288 } 289 }); 290 }; 291 292 // Initialize language based on saved preference or default to English 293 const initializeLanguage = () => { 294 const savedLanguage = localStorage.getItem('preferredLanguage'); 295 if (savedLanguage) { 296 setLanguage(savedLanguage); 297 } else { 298 setLanguage('en'); // Default to English 299 } 300 }; 301 302 // Setup language buttons 303 const langButtons = document.querySelectorAll('.lang-btn'); 304 langButtons.forEach(button => { 305 button.addEventListener('click', function() { 306 const lang = this.dataset.lang; 307 setLanguage(lang); 308 }); 309 }); 310 311 // ----------- AUDIO SYSTEM ----------- 312 313 let audioPlaying = false; 314 315 // Create a visual indicator for when audio is playing 316 function createAudioStatusIndicator() { 317 // Create the indicator element if it doesn't exist 318 let audioIndicator = document.getElementById('audio-status-indicator'); 319 if (!audioIndicator) { 320 audioIndicator = document.createElement('div'); 321 audioIndicator.id = 'audio-status-indicator'; 322 audioIndicator.className = 'audio-status-indicator'; 323 document.body.appendChild(audioIndicator); 324 325 // Add styles for the audio indicator 326 const style = document.createElement('style'); 327 style.textContent = ` 328 .audio-status-indicator { 329 position: fixed; 330 bottom: 20px; 331 right: 20px; 332 background-color: rgba(52, 152, 219, 0.9); 333 color: white; 334 padding: 12px 20px; 335 border-radius: 8px; 336 box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); 337 z-index: 1000; 338 display: flex; 339 align-items: center; 340 gap: 10px; 341 transform: translateY(100px); 342 transition: transform 0.3s ease; 343 } 344 345 .audio-status-indicator.visible { 346 transform: translateY(0); 347 } 348 349 .audio-status-indicator .wave { 350 display: flex; 351 align-items: center; 352 height: 20px; 353 } 354 355 .audio-status-indicator .wave .bar { 356 width: 3px; 357 margin: 0 2px; 358 background-color: white; 359 animation: wave 1s infinite ease-in-out; 360 } 361 362 .audio-status-indicator .wave .bar:nth-child(2) { 363 animation-delay: 0.2s; 364 } 365 366 .audio-status-indicator .wave .bar:nth-child(3) { 367 animation-delay: 0.4s; 368 } 369 370 .audio-status-indicator .wave .bar:nth-child(4) { 371 animation-delay: 0.6s; 372 } 373 374 @keyframes wave { 375 0%, 100% { 376 height: 6px; 377 } 378 50% { 379 height: 18px; 380 } 381 } 382 `; 383 document.head.appendChild(style); 384 } 385
386 return audioIndicator; 387 } 388 389 // Show the audio indicator with step-specific message 390 function showAudioStatusIndicator(step) { 391 const audioIndicator = createAudioStatusIndicator(); 392 393 // Determine the message based on the step 394 let message = translations[currentLanguage][`step${step}-voice`] || 'Voice guide playing'; 395 396 audioIndicator.innerHTML = ` 397 <div class="wave"> 398 <div class="bar"></div> 399 <div class="bar"></div> 400 <div class="bar"></div> 401 <div class="bar"></div> 402 </div> 403 <span>${message}</span> 404 `; 405 406 // Show the indicator 407 setTimeout(() => { 408 audioIndicator.classList.add('visible'); 409 }, 10); 410 } 411 412 // Hide the audio indicator 413 function hideAudioStatusIndicator() { 414 const audioIndicator = document.getElementById('audio-status-indicator'); 415 if (audioIndicator) { 416 audioIndicator.classList.remove('visible'); 417 } 418 } 419 420 // Enhanced playAudio function with step detection 421 const playAudio = (audioKey) => { 422 // Stop any currently playing audio 423 stopAudio(); 424 425 // Determine which step audio is being played 426 let step = null; 427 if (audioKey === 'home-audio') { 428 step = 1; 429 } else if (audioKey === 'appliances-audio') { 430 step = 2; 431 } else if (audioKey === 'results-audio') { 432 step = 3; 433 } 434 435 // Show the audio indicator 436 if (step) { 437 showAudioStatusIndicator(step); 438 } 439 440 // In a real implementation, you would have actual audio files 441 // For demo, we'll use the Web Speech API 442 443 const message = translations[currentLanguage][audioKey]; 444 // Check if browser supports speech synthesis 445 if ('speechSynthesis' in window) { 446 // Create a new speech synthesis utterance 447 const utterance = new SpeechSynthesisUtterance(message); 448 449 // Set the language based on current selection 450 if (currentLanguage === 'en') { 451 utterance.lang = 'en-US'; 452 } else if (currentLanguage === 'tok') { 453 utterance.lang = 'en-US'; // Fallback since Tok Pisin not widely supported 454 } else { 455 utterance.lang = 'en-US'; // Fallback 456 } 457 458 // Set flag when audio starts 459 utterance.onstart = () => { 460 audioPlaying = true; 461 }; 462 463 // Set flag when audio ends 464 utterance.onend = () => { 465 audioPlaying = false; 466 // Hide the audio indicator when audio ends 467 hideAudioStatusIndicator(); 468 }; 469 470 // Speak the message 471 speechSynthesis.cancel(); // Cancel any ongoing speech 472 speechSynthesis.speak(utterance); 473 } else { 474 console.log('Speech synthesis not supported in this browser'); 475 // Fallback alert for browsers without speech synthesis 476 alert(message); 477 } 478 }; 479 480 // Enhanced stopAudio function 481 const stopAudio = () => { 482 if ('speechSynthesis' in window) { 483 speechSynthesis.cancel(); 484 audioPlaying = false; 485 // Hide the audio indicator 486 hideAudioStatusIndicator(); 487 } 488 }; 489 490 // Listen button functionality 491 const listenButton = document.getElementById('listen-button'); 492 if (listenButton) { 493 listenButton.addEventListener('click', function() { 494 // Play the appropriate audio based on current step 495 if (currentStep === 1) { 496 playAudio('home-audio'); 497 } else if (currentStep === 2) { 498 playAudio('appliances-audio'); 499 } else if (currentStep === 3) { 500 playAudio('results-audio'); 501 } 502 }); 503 } 504 505 // Stop audio button functionality 506 const stopAudioButton = document.getElementById('stop-audio-button'); 507 if (stopAudioButton) { 508 stopAudioButton.addEventListener('click', stopAudio); 509 } 510 511 // Add click event listeners to all step audio buttons 512 const stepAudioButtons = document.querySelectorAll('.step-audio-btn'); 513 stepAudioButtons.forEach(button => { 514 button.addEventListener('click', function(e) { 515 e.preventDefault(); 516 e.stopPropagation(); 517 518 const audioKey = this.dataset.audio; 519 // Call the existing playAudio function with the specific audio key 520 playAudio(audioKey); 521 }); 522 }); 523 524 const legendToggleBtn = document.getElementById('legend-toggle-btn'); 525 const energyLegend = document.querySelector('.energy-indicator-legend'); 526 527 if (legendToggleBtn && energyLegend) { 528 legendToggleBtn.addEventListener('click', function() { 529 energyLegend.classList.toggle('expanded'); 530 531 // Update the toggle button text 532 const toggleText = document.getElementById('legend-toggle-text'); 533 if (toggleText) { 534 if (energyLegend.classList.contains('expanded')) { 535 toggleText.textContent = translations[currentLanguage]['legend-hide-text'] || 'Hide Energy Guide'; 536 } else { 537 toggleText.textContent = translations[currentLanguage]['legend-show-text'] || 'Show Energy Guide'; 538 } 539 } 540 }); 541 } 542 543 function autoExpandEnergyLegend() { 544 const energyLegend = document.querySelector('.energy-indicator-legend'); 545 if (energyLegend) { 546 if (window.innerWidth <= 768) { 547 energyLegend.classList.add('expanded'); 548 } else { 549 energyLegend.classList.remove('expanded'); 550 } 551 } 552 } 553 554 autoExpandEnergyLegend(); 555 556 window.addEventListener('resize', autoExpandEnergyLegend); 557 558 // Function to initialize energy indicator tooltips 559 function initializeEnergyTooltips() { 560 // Add tooltip functionality to all energy indicators 561 const energyIndicators = document.querySelectorAll('.energy-indicator'); 562
563 energyIndicators.forEach(indicator => { 564 // Get the parent appliance card 565 const applianceCard = indicator.closest('.appliance-card'); 566 if (!applianceCard) return; 567 568 // Create tooltip element if it doesn't exist 569 let tooltip = applianceCard.querySelector('.energy-tooltip'); 570 if (!tooltip) { 571 tooltip = document.createElement('div'); 572 tooltip.className = 'energy-tooltip'; 573 applianceCard.appendChild(tooltip); 574 } 575 576 // Determine energy level from class 577 let energyLevel = ''; 578 if (indicator.classList.contains('energy-low')) { 579 energyLevel = translations[currentLanguage]['energy-low-text'] || 'Low Energy: Efficient appliance'; 580 } else if (indicator.classList.contains('energy-medium')) { 581 energyLevel = translations[currentLanguage]['energy-medium-text'] || 'Medium Energy: Standard consumption'; 582 } else if (indicator.classList.contains('energy-high')) { 583 energyLevel = translations[currentLanguage]['energy-high-text'] || 'High Energy: Power-intensive appliance'; 584 } 585 586 // Set tooltip text 587 tooltip.textContent = energyLevel; 588 589 // Show tooltip on hover 590 indicator.addEventListener('mouseenter', () => { 591 tooltip.style.display = 'block'; 592 593 // Position the tooltip 594 const applianceRect = applianceCard.getBoundingClientRect(); 595 tooltip.style.top = '10px'; 596 tooltip.style.right = '10px'; 597 598 // Ensure tooltip is visible within viewport 599 setTimeout(() => { 600 const tooltipRect = tooltip.getBoundingClientRect(); 601 if (tooltipRect.right > window.innerWidth) { 602 tooltip.style.right = 'auto'; 603 tooltip.style.left = '10px'; 604 } 605 }, 0); 606 }); 607 608 // Hide tooltip when not hovering 609 indicator.addEventListener('mouseleave', () => { 610 tooltip.style.display = 'none'; 611 }); 612 }); 613 } 614 615 // Add CSS for tooltips 616 const styleElement = document.createElement('style'); 617 styleElement.textContent = ` 618 .energy-tooltip { 619 position: absolute; 620 background-color: rgba(0, 0, 0, 0.8); 621 color: white; 622 padding: 5px 10px; 623 border-radius: 4px; 624 font-size: 12px; 625 z-index: 100; 626 display: none; 627 white-space: nowrap; 628 pointer-events: none; 629 top: 10px; 630 right: 10px; 631 animation: fadeIn 0.2s; 632 } 633 634 .energy-tooltip::after { 635 content: ''; 636 position: absolute; 637 top: -5px; 638 right: 10px; 639 border-width: 0 5px 5px; 640 border-style: solid; 641 border-color: transparent transparent rgba(0, 0, 0, 0.8); 642 } 643 644 @keyframes fadeIn { 645 from { opacity: 0; } 646 to { opacity: 1; } 647 } 648 649 /* Make the energy indicator have a slightly larger hit area for better UX */ 650 .energy-indicator { 651 cursor: pointer; 652 } 653 `; 654 document.head.appendChild(styleElement); 655 656 // Call this function after appliances are loaded 657 document.addEventListener('DOMContentLoaded', function() { 658 // Wait a short time to ensure all appliances are loaded 659 setTimeout(initializeEnergyTooltips, 500); 660 661 // Also initialize tooltips when category is changed since this can reload appliances 662 const categories = document.querySelectorAll('.category'); 663 categories.forEach(category => { 664 category.addEventListener('click', function() { 665 setTimeout(initializeEnergyTooltips, 300); 666 }); 667 }); 668 }); 669 670 // ----------- CALCULATOR FUNCTIONALITY ----------- 671 672 // Cache DOM elements 673 const steps = document.querySelectorAll('.step'); 674 const stepContents = document.querySelectorAll('.step-content'); 675 const homeOptions = document.querySelectorAll('.home-option'); 676 const sunlightOptions = document.querySelectorAll('.sunlight-option'); 677 const backupOptions = document.querySelectorAll('.backup-option'); 678 const categories = document.querySelectorAll('.category'); 679 680 // Navigation buttons 681 const toStep2Button = document.getElementById('to-step-2'); 682 const backToStep1Button = document.getElementById('to-step-1'); 683 const toStep3Button = document.getElementById('to-step-3'); 684 const backToStep2Button = document.getElementById('back-to-step-2'); 685 const printResultsButton = document.getElementById('print-results'); 686 687 // State variables 688 let currentStep = 1; 689 let selectedHomeType = null; 690 let selectedSunlight = 5; // Default: Average Sun 691 let selectedBackup = 1; // Default: 1 Day 692 693 // Show loading spinner 694 function showLoadingSpinner(message = 'Calculating...') { 695 document.getElementById('loading-message').textContent = message; 696 document.getElementById('loading-overlay').style.display = 'flex'; 697 } 698 699 // Hide loading spinner 700 function hideLoadingSpinner() { 701 document.getElementById('loading-overlay').style.display = 'none'; 702 } 703 704 // Show notification 705 function showNotification(message) { 706 // Create notification element if it doesn't exist 707 let notification = document.getElementById('notification'); 708 if (!notification) { 709 notification = document.createElement('div'); 710 notification.id = 'notification'; 711 notification.className = 'notification'; 712 document.body.appendChild(notification); 713 714 // Add styles for notification 715 const style = document.createElement('style'); 716 style.textContent = ` 717 .notification { 718 position: fixed; 719 top: 20px; 720 right: 20px; 721 background-color: #3498db; 722 color: white; 723 padding: 15px 25px; 724 border-radius: 8px; 725 box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); 726 z-index: 1000; 727 transition: all 0.3s ease; 728 opacity: 0; 729 transform: translateY(-20px); 730 } 731 .notification.show { 732 opacity: 1; 733 transform: translateY(0); 734 } 735 `; 736 document.head.appendChild(style); 737 } 738 739 // Set message and show notification 740 notification.textContent = message; 741 notification.classList.add('show'); 742 743 // Hide notification after 3 seconds 744 setTimeout(() => { 745 notification.classList.remove('show'); 746 }, 3000); 747 } 748 749 // Navigate between steps 750 function navigateToStep(stepNumber) { 751 // Hide all step contents
752 stepContents.forEach(content => { 753 content.classList.remove('active'); 754 }); 755 756 // Show the selected step content 757 document.getElementById(`step-${stepNumber}`).classList.add('active'); 758 759 // Update step indicators 760 steps.forEach(step => { 761 const stepNum = parseInt(step.dataset.step); 762 step.classList.remove('active', 'completed'); 763 764 if (stepNum === stepNumber) { 765 step.classList.add('active'); 766 } else if (stepNum < stepNumber) { 767 step.classList.add('completed'); 768 } 769 }); 770 771 // Update current step 772 currentStep = stepNumber; 773 774 // Note: Removed automatic audio playback 775 776 // Scroll to the active step section 777 const activeStepElement = document.querySelector(`.step[data-step="${stepNumber}"]`); 778 if (activeStepElement) { 779 // Smooth scroll to the step with some offset to account for fixed header 780 const headerOffset = 300; // Adjust this value based on your header height 781 const elementPosition = activeStepElement.getBoundingClientRect().top; 782 const offsetPosition = elementPosition + window.pageYOffset - headerOffset; 783 784 window.scrollTo({ 785 top: offsetPosition, 786 behavior: 'smooth' 787 }); 788 } 789 } 790 791 // Calculate total energy usage 792 function calculateTotalEnergy() { 793 let totalEnergy = 0; 794 795 // Loop through all appliance cards 796 const applianceCards = document.querySelectorAll('.appliance-card:not(.custom-template)'); 797 applianceCards.forEach(card => { 798 const quantityEl = card.querySelector('.quantity-value'); 799 if (!quantityEl) return; 800 801 const quantity = parseInt(quantityEl.textContent); 802 if (quantity > 0) { 803 // For custom appliances with editable fields, get values from inputs 804 let wattage, hours; 805 806 if (card.classList.contains('custom-appliance')) { 807 wattage = parseInt(card.querySelector('.custom-wattage').value) || 1; 808 hours = parseFloat(card.querySelector('.custom-hours').value) || 1; 809 } else { 810 wattage = parseInt(card.dataset.wattage) || 1; 811 hours = parseFloat(card.dataset.hours) || 1; 812 } 813 814 // Calculate kWh for this appliance 815 const kwh = (wattage * quantity * hours) / 1000; 816 totalEnergy += kwh; 817 } 818 }); 819 820 return totalEnergy; 821 } 822 823 // Get selected appliances 824 function getSelectedAppliances() { 825 const appliances = []; 826 const applianceCards = document.querySelectorAll('.appliance-card:not(.custom-template)'); 827 828 applianceCards.forEach(card => { 829 const quantityEl = card.querySelector('.quantity-value'); 830 if (!quantityEl) return; 831 832 const quantity = parseInt(quantityEl.textContent); 833 if (quantity > 0) { 834 let name, wattage, hours; 835 836 if (card.classList.contains('custom-appliance')) { 837 const nameEl = card.querySelector('.editable-appliance-name'); 838 name = nameEl ? nameEl.value : 'Custom Appliance'; 839 wattage = parseInt(card.querySelector('.custom-wattage').value) || 1; 840 hours = parseFloat(card.querySelector('.custom-hours').value) || 1; 841 } else { 842 const nameEl = card.querySelector('.appliance-name'); 843 name = nameEl ? nameEl.textContent : card.dataset.title; 844 wattage = parseInt(card.dataset.wattage) || 1; 845 hours = parseFloat(card.dataset.hours) || 1; 846 } 847 848 appliances.push({ 849 name: name, 850 quantity: quantity, 851 wattage: wattage, 852 hours: hours, 853 kwh: (wattage * quantity * hours) / 1000 854 }); 855 } 856 }); 857 858 return appliances; 859 } 860 861 // Calculate final results 862 function calculateResults() { 863 showLoadingSpinner('Calculating your solar system...'); 864 865 setTimeout(() => { 866 // Get total energy usage 867 const totalEnergy = calculateTotalEnergy(); 868 document.getElementById('total-kwh-value').textContent = totalEnergy.toFixed(1); 869 870 // Get selected appliances 871 const appliances = getSelectedAppliances(); 872 873 // Clear previous selected appliances 874 const selectedAppliancesList = document.getElementById('selected-appliances-list'); 875 selectedAppliancesList.innerHTML = ''; 876 877 // Add selected appliances to the list
878 appliances.forEach(appliance => { 879 const applianceItem = document.createElement('div'); 880 applianceItem.className = 'appliance-item'; 881 882 // Find the matching card to get the image source 883 let imgSrc = 'assets/custom.png'; 884 if (!appliance.name.includes('Custom')) { 885 const matchingCard = Array.from(document.querySelectorAll('.appliance-card:not(.custom-appliance)')).find(card => { 886 const nameEl = card.querySelector('.appliance-name'); 887 return nameEl && nameEl.textContent === appliance.name; 888 }); 889 890 if (matchingCard) { 891 const iconEl = matchingCard.querySelector('.appliance-icon'); 892 if (iconEl) { 893 imgSrc = iconEl.src; 894 } 895 } 896 } 897 898 applianceItem.innerHTML = ` 899 <img src="${imgSrc}" alt="${appliance.name}" class="item-icon"> 900 <div class="item-details"> 901 <div class="item-name">${appliance.name}</div> 902 <div class="item-quantity">Quantity: ${appliance.quantity}</div> 903 </div> 904 `; 905 906 selectedAppliancesList.appendChild(applianceItem); 907 }); 908 909 // Calculate solar panel size based on total energy and sunlight hours 910 const solarPanelSize = totalEnergy / selectedSunlight; 911 912 // Calculate battery capacity based on total energy and backup days 913 const batteryCapacity = totalEnergy * selectedBackup; 914 915 // Calculate system size (inverter) based on home type 916 let systemSize = selectedHomeType; 917 918 // Determine appropriate solar panel recommendation 919 let solarText = ''; 920 if (solarPanelSize <= 0.5) { 921 solarText = '1 - 2 panels'; 922 } else if (solarPanelSize <= 1) { 923 solarText = '3 - 4 panels'; 924 } else if (solarPanelSize <= 2) { 925 solarText = '5 - 6 panels'; 926 } else if (solarPanelSize <= 3) { 927 solarText = '7 - 9 panels'; 928 } else if (solarPanelSize <= 4) { 929 solarText = '10 - 12 panels'; 930 } else if (solarPanelSize <= 5) { 931 solarText = '13 - 15 panels'; 932 } else { 933 solarText = '16 - 20+ panels'; 934 } 935 936 // Determine appropriate battery recommendation 937 let batteryText = ''; 938 if (batteryCapacity <= 1) { 939 batteryText = 'Small (0.5 - 1kWh)'; 940 } else if (batteryCapacity <= 2) { 941 batteryText = 'Small (1.5 - 2kWh)'; 942 } else if (batteryCapacity <= 5) { 943 batteryText = 'Medium (3 - 5kWh)'; 944 } else if (batteryCapacity <= 10) { 945 batteryText = 'Medium (7 - 10kWh)'; 946 } else if (batteryCapacity <= 15) { 947 batteryText = 'Large (12 - 15kWh)'; 948 } else { 949 batteryText = 'Very Large (15 - 20kWh+)'; 950 } 951 952 // Determine appropriate system size recommendation 953 let systemText = ''; 954 if (systemSize === 30) { 955 systemText = 'Small (2 - 3kVA)'; 956 } else if (systemSize === 50) { 957 systemText = 'Medium (4 - 5kVA)'; 958 } else if (systemSize === 70) { 959 systemText = 'Large (8 - 10kVA)'; 960 } else if (systemSize === 90) { 961 systemText = 'Extra Large (12 - 15kVA)'; 962 } else { 963 systemText = 'Medium (4 - 5kVA)'; 964 } 965 966 // Update result values 967 document.getElementById('solar-panel-value').querySelector('.result-value-text').innerHTML = solarText; 968 document.getElementById('battery-value').querySelector('.result-value-text').innerHTML = batteryText.replace(/\(([^)]+)\)/, '<br>($1)'); 969 document.getElementById('system-size-value').querySelector('.result-value-text').innerHTML = systemText.replace(/\(([^)]+)\)/, '<br>($1)'); 970 971 hideLoadingSpinner(); 972 }, 1500); 973 } 974 975 // Initialize option selections 976 977 // Home type selection based on bedroom count 978 homeOptions.forEach(option => { 979 option.addEventListener('click', function() { 980 homeOptions.forEach(opt => opt.classList.remove('selected')); 981 this.classList.add('selected'); 982 selectedHomeType = parseInt(this.dataset.usage); 983 984 // Auto-apply template based on bedroom selection 985 const homeType = this.dataset.homeType; 986 if (homeType === 'bedroom1') { 987 applyTemplate(1); 988 } else if (homeType === 'bedroom2') { 989 applyTemplate(2); 990 } else if (homeType === 'bedroom3') { 991 applyTemplate(3); 992 } else if (homeType === 'bedroom4') { 993 applyTemplate(4); 994 } 995 }); 996 }); 997 998 // Sunlight selection 999 sunlightOptions.forEach(option => { 1000 option.addEventListener('click', function() { 1001 sunlightOptions.forEach(opt => opt.classList.remove('selected')); 1002 this.classList.add('selected'); 1003 selectedSunlight = parseFloat(this.dataset.sun); 1004 }); 1005 }); 1006 1007 // Backup selection 1008 backupOptions.forEach(option => { 1009 option.addEventListener('click', function() { 1010 backupOptions.forEach(opt => opt.classList.remove('selected')); 1011 this.classList.add('selected'); 1012 selectedBackup = parseFloat(this.dataset.backup); 1013 }); 1014 }); 1015 1016 // Default selections 1017 sunlightOptions[1].classList.add('selected'); // Average sun 1018 backupOptions[1].classList.add('selected'); // 1 day 1019 1020 // Category filtering
1021 categories.forEach(category => { 1022 category.addEventListener('click', function() { 1023 categories.forEach(cat => cat.classList.remove('active')); 1024 this.classList.add('active'); 1025 1026 const selectedCategory = this.dataset.category; 1027 const applianceCards = document.querySelectorAll('.appliance-card'); 1028 1029 applianceCards.forEach(card => { 1030 if (selectedCategory === 'all' || card.dataset.category === selectedCategory) { 1031 card.style.display = ''; 1032 } else { 1033 card.style.display = 'none'; 1034 } 1035 }); 1036 }); 1037 }); 1038 1039 // Apply Template for Room Sizes 1040 function applyTemplate(bedrooms) { 1041 // Show loading spinner 1042 showLoadingSpinner(`Loading template for ${bedrooms} bedroom(s)...`); 1043 1044 // Reset all appliance quantities to 0 1045 document.querySelectorAll('.appliance-card:not(.custom-template)').forEach(card => { 1046 // Remove any existing custom appliances 1047 if (card.classList.contains('custom-appliance')) { 1048 card.remove(); 1049 } else { 1050 // Reset quantity for standard appliances 1051 const quantityEl = card.querySelector('.quantity-value'); 1052 if (quantityEl) { 1053 quantityEl.textContent = '0'; 1054 } 1055 card.classList.remove('selected'); 1056 } 1057 }); 1058 1059 // Reset custom appliance counter 1060 customApplianceCount = 0; 1061 1062 setTimeout(() => { 1063 // Define appliance templates based on bedrooms 1064 const applianceTemplates = { 1065 1: [ 1066 { title: "Lights", quantity: 3 }, 1067 { title: "Fridge/Freezer - Large", quantity: 1 }, 1068 { title: "Fan - Ceiling", quantity: 1 }, 1069 { title: "TV (LED)", quantity: 1 }, 1070 { title: "Air Conditioner - Small", quantity: 1 }, 1071 { title: "Microwave", quantity: 1 }, 1072 { title: "Modem/Internet", quantity: 1 }, 1073 { title: "Washing Machine", quantity: 1 } 1074 ], 1075 2: [ 1076 { title: "Lights", quantity: 5 }, 1077 { title: "Fridge/Freezer - Large", quantity: 1 }, 1078 { title: "Fan - Ceiling", quantity: 2 }, 1079 { title: "TV (LED)", quantity: 1 }, 1080 { title: "Air Conditioner - Small", quantity: 1 }, 1081 { title: "Microwave", quantity: 1 }, 1082 { title: "Modem/Internet", quantity: 1 }, 1083 { title: "Laptop / Computers", quantity: 1 }, 1084 { title: "Washing Machine", quantity: 1 } 1085 ], 1086 3: [ 1087 { title: "Lights", quantity: 8 }, 1088 { title: "Fridge/Freezer - Large", quantity: 1 }, 1089 { title: "Fan - Ceiling", quantity: 3 }, 1090 { title: "TV (LED)", quantity: 1 }, 1091 { title: "Air Conditioner - Large", quantity: 1 }, 1092 { title: "Microwave", quantity: 1 }, 1093 { title: "Kettle", quantity: 1 }, 1094 { title: "Toaster", quantity: 1 }, 1095 { title: "Modem/Internet", quantity: 1 }, 1096 { title: "Laptop / Computers", quantity: 2 }, 1097 { title: "Washing Machine", quantity: 1 } 1098 ], 1099 4: [ 1100 { title: "Lights", quantity: 12 }, 1101 { title: "Fridge/Freezer - Large", quantity: 1 }, 1102 { title: "Fan - Ceiling", quantity: 4 }, 1103 { title: "TV (LED)", quantity: 2 }, 1104 { title: "Air Conditioner - Large", quantity: 1 }, 1105 { title: "Air Conditioner - Small", quantity: 1 }, 1106 { title: "Microwave", quantity: 1 }, 1107 { title: "Kettle", quantity: 1 }, 1108 { title: "Toaster", quantity: 1 }, 1109 { title: "Modem/Internet", quantity: 1 }, 1110 { title: "Laptop / Computers", quantity: 2 }, 1111 { title: "Washing Machine", quantity: 1 }, 1112 { title: "Dryer", quantity: 1 }, 1113 { title: "Gaming Console", quantity: 1 }] 1114 }; 1115 1116 // Apply the template 1117 const template = applianceTemplates[bedrooms] || applianceTemplates[4]; // Default to 4+ for any larger number 1118 1119 // Find and update each appliance card with the correct quantity using data-title instead of name 1120 template.forEach(item => { 1121 const cards = document.querySelectorAll('.appliance-card:not(.custom-template)'); 1122 cards.forEach(card => { 1123 // Use data-title attribute to match appliances instead of innerText 1124 if (card.dataset.title === item.title) { 1125 const quantityEl = card.querySelector('.quantity-value'); 1126 if (quantityEl) { 1127 quantityEl.textContent = item.quantity.toString(); 1128 if (item.quantity > 0) { 1129 card.classList.add('selected'); 1130 } 1131 } 1132 } 1133 }); 1134 }); 1135 1136 // Show notification instead of navigating to next step 1137 hideLoadingSpinner(); 1138 showNotification(translations[currentLanguage]['template-applied']); 1139 }, 1000); 1140 } 1141 1142 // Navigation button events 1143 toStep2Button.addEventListener('click', function() { 1144 // Validate home type selection 1145 if (selectedHomeType === null) { 1146 alert(translations[currentLanguage]['home-type-question']); 1147 return; 1148 } 1149 navigateToStep(2); 1150 }); 1151 1152 backToStep1Button.addEventListener('click', function() { 1153 navigateToStep(1); 1154 }); 1155 1156 toStep3Button.addEventListener('click', function() {
1157 // Check if at least one appliance is selected 1158 const hasSelectedAppliances = Array.from(document.querySelectorAll('.appliance-card:not(.custom-template)')).some(card => { 1159 const quantityEl = card.querySelector('.quantity-value'); 1160 return quantityEl && parseInt(quantityEl.textContent) > 0; 1161 }); 1162 1163 if (!hasSelectedAppliances) { 1164 alert(translations[currentLanguage]['appliances-title']); 1165 return; 1166 } 1167 1168 navigateToStep(3); 1169 calculateResults(); // Calculate results when navigating to step 3 1170 }); 1171 1172 function setupEnhancedPrinting() { 1173 // Get the print button element 1174 const printButton = document.getElementById('print-results'); 1175 1176 if (printButton) { 1177 // Replace the current click handler with our enhanced version 1178 printButton.removeEventListener('click', window.print); 1179 1180 printButton.addEventListener('click', function() { 1181 // Show loading spinner while preparing print layout 1182 showLoadingSpinner('Preparing print layout...'); 1183 1184 // Create a new window for the print layout 1185 const printWindow = window.open('', '_blank', 'width=800,height=600'); 1186 1187 if (!printWindow) { 1188 hideLoadingSpinner(); 1189 alert('Please allow popup windows to print your results.'); 1190 return; 1191 } 1192 1193 // Get selected appliances and results data 1194 const appliances = getSelectedAppliances(); 1195 const totalEnergy = calculateTotalEnergy(); 1196 1197 // Get result values, ensuring we get the text content even with the new HTML structure 1198 const solarPanelEl = document.getElementById('solar-panel-value'); 1199 const batteryEl = document.getElementById('battery-value'); 1200 const systemSizeEl = document.getElementById('system-size-value'); 1201 1202 const solarPanelValue = solarPanelEl.querySelector('.result-value-text') ? 1203 solarPanelEl.querySelector('.result-value-text').textContent : 1204 solarPanelEl.textContent; 1205 1206 const batteryValue = batteryEl.querySelector('.result-value-text') ? 1207 batteryEl.querySelector('.result-value-text').textContent : 1208 batteryEl.textContent; 1209 1210 const systemSizeValue = systemSizeEl.querySelector('.result-value-text') ? 1211 systemSizeEl.querySelector('.result-value-text').textContent : 1212 systemSizeEl.textContent; 1213 1214 // Get date information for the printed report 1215 const currentDate = new Date(); 1216 const formattedDate = currentDate.toLocaleDateString(); 1217 const formattedTime = currentDate.toLocaleTimeString(); 1218 1219 // Determine home type selection 1220 let homeType = 'Unknown'; 1221 document.querySelectorAll('.home-option.selected').forEach(option => { 1222 const label = option.querySelector('.home-label'); 1223 if (label) { 1224 homeType = label.textContent; 1225 } 1226 }); 1227 1228 // Determine sunlight selection 1229 let sunlight = 'Average'; 1230 document.querySelectorAll('.sunlight-option.selected').forEach(option => { 1231 const label = option.querySelector('.sunlight-label'); 1232 if (label) { 1233 sunlight = label.textContent; 1234 } 1235 }); 1236 1237 // Determine backup selection 1238 let backup = '1 Day'; 1239 document.querySelectorAll('.backup-option.selected').forEach(option => { 1240 const label = option.querySelector('.backup-label'); 1241 if (label) { 1242 backup = label.textContent; 1243 } 1244 }); 1245 1246 // Path to TEPNG logo (update this with the actual path to your logo) 1247 const logoPath = 'assets/logo/logo.png'; 1248 1249 // Write the print layout HTML with space for the logo 1250 printWindow.document.write(` 1251 <!DOCTYPE html> 1252 <html> 1253 <head> 1254 <title>Solar Calculator Results - ${formattedDate}</title> 1255 <meta charset="UTF-8"> 1256 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 1257 <style> 1258 body { 1259 font-family: Arial, sans-serif; 1260 color: #333; 1261 line-height: 1.5; 1262 margin: 0; 1263 padding: 20px; 1264 } 1265 1266 .print-container { 1267 max-width: 800px; 1268 margin: 0 auto; 1269 } 1270 1271 .print-header { 1272 padding-bottom: 20px; 1273 border-bottom: 2px solid #3498db; 1274 margin-bottom: 20px; 1275 display: flex; 1276 align-items: center; 1277 justify-content: space-between; 1278 } 1279 1280 .header-logo { 1281 flex: 0 0 150px; 1282 height: 80px; 1283 display: flex; 1284 align-items: center; 1285 justify-content: center; 1286 margin-right: 20px; 1287 } 1288 1289 .header-logo img { 1290 max-width: 100%; 1291 max-height: 100%; 1292 object-fit: contain; 1293 } 1294 1295 .header-content { 1296 flex: 1; 1297 margin-left: 50px; 1298 text-align: left; 1299 } 1300 1301 .print-logo { 1302 font-size: 24px; 1303 font-weight: bold; 1304 color: #1a3755; 1305 margin-bottom: 5px; 1306 } 1307 1308 .print-subtitle { 1309 color: #666; 1310 font-size: 16px; 1311 } 1312 1313 .print-date { 1314 color: #777; 1315 font-size: 14px; 1316 margin-top: 10px; 1317 } 1318 1319 .print-section { 1320 margin-bottom: 30px; 1321 } 1322 1323 .section-title { 1324 color: #1a3755; 1325 border-bottom: 1px solid #ddd; 1326 padding-bottom: 5px; 1327 font-size: 18px; 1328 margin-bottom: 15px; 1329 } 1330 1331 .inputs-section { 1332 display: flex; 1333 flex-wrap: wrap; 1334 gap: 15px; 1335 margin-bottom: 20px; 1336 } 1337 1338 .input-item { 1339 flex: 1; 1340 min-width: 180px; 1341 background-color: #f9f9f9; 1342 padding: 10px 15px; 1343 border-radius: 8px; 1344 border-left: 4px solid #3498db; 1345 } 1346 1347 .input-label { 1348 font-weight: bold; 1349 margin-bottom: 5px; 1350 color: #555; 1351 } 1352 1353 .input-value { 1354 font-size: 16px; 1355 } 1356 1357 .results-grid { 1358 display: flex; 1359 flex-wrap: wrap; 1360 gap: 20px; 1361 margin-bottom: 25px; 1362 } 1363 1364 .result-card { 1365 flex: 1; 1366 min-width: 200px; 1367 border: 1px solid #ddd; 1368 padding: 15px; 1369 border-radius: 8px; 1370 background-color: #f5f9ff; 1371 text-align: center; 1372 } 1373 1374 .result-name { 1375 font-weight: bold; 1376 color: #1a3755; 1377 margin-bottom: 10px; 1378 font-size: 16px; 1379 } 1380 1381 .result-value { 1382 font-size: 22px; 1383 color: #3498db; 1384 margin-bottom: 5px; 1385 font-weight: bold; 1386 } 1387 1388 .result-description { 1389 color: #777; 1390 font-size: 13px; 1391 } 1392 1393 .accuracy-note { 1394 display: flex; 1395 align-items: center; 1396 gap: 10px; 1397 background-color: #fff3cd; 1398 border: 1px solid #ffeeba; 1399 color: #856404; 1400 padding: 10px 15px; 1401 border-radius: 6px; 1402 margin: 15px 0; 1403 font-size: 14px; 1404 } 1405 1406 .note-icon { 1407 color: #f39c12; 1408 font-size: 18px; 1409 flex-shrink: 0; 1410 } 1411 1412 .total-energy { 1413 background-color: #1a3755; 1414 color: white; 1415 padding: 15px; 1416 border-radius: 8px; 1417 text-align: center; 1418 margin-bottom: 20px; 1419 } 1420 1421 .total-title { 1422 font-size: 16px; 1423 margin-bottom: 5px; 1424 } 1425 1426 .total-value { 1427 font-size: 22px; 1428 font-weight: bold; 1429 } 1430 1431 .appliances-list { 1432 border: 1px solid #ddd; 1433 border-radius: 8px; 1434 overflow: hidden; 1435 } 1436 1437 .appliances-header { 1438 background-color: #f1f1f1; 1439 padding: 10px 15px; 1440 font-weight: bold; 1441 display: grid; 1442 grid-template-columns: 3fr 1fr 1fr 1fr 1fr; 1443 border-bottom: 1px solid #ddd; 1444 } 1445 1446 .appliance-item { 1447 padding: 10px 15px; 1448 display: grid; 1449 grid-template-columns: 3fr 1fr 1fr 1fr 1fr; 1450 border-bottom: 1px solid #eee; 1451 } 1452 1453 .appliance-item:last-child { 1454 border-bottom: none; 1455 } 1456 1457 .appliance-item:nth-child(even) { 1458 background-color: #f9f9f9; 1459 } 1460 1461 .print-footer { 1462 margin-top: 30px; 1463 text-align: center; 1464 font-size: 14px; 1465 color: #777; 1466 padding-top: 15px; 1467 border-top: 1px solid #ddd; 1468 } 1469 1470 .print-actions { 1471 text-align: center; 1472 margin-top: 30px; 1473 } 1474 1475 .print-button { 1476 background-color: #3498db; 1477 color: white; 1478 border: none; 1479 padding: 10px 20px; 1480 font-size: 16px; 1481 border-radius: 5px; 1482 cursor: pointer; 1483 } 1484 1485 .print-button:hover { 1486 background-color: #2980b9; 1487 } 1488 1489 .disclaimer { 1490 font-size: 12px; 1491 color: #777; 1492 font-style: italic; 1493 margin-top: 20px; 1494 } 1495 1496 @media print { 1497 .print-actions { 1498 display: none; 1499 } 1500 1501 body { 1502 padding: 0; 1503 margin: 0; 1504 } 1505 1506 .print-container { 1507 max-width: 100%; 1508 } 1509 } 1510 </style> 1511 </head> 1512 <body> 1513 <div class="print-container"> 1514 <div class="print-header"> 1515 <div class="header-logo"> 1516 <img src="${logoPath}" alt="TEPNG Logo" onerror="this.src='data:image/svg+xml;utf8,<svg xmlns=\\'http://www.w3.org/2000/svg\\' width=\\'150\\' height=\\'80\\'><rect width=\\'150\\' height=\\'80\\' fill=\\'%23f8f8f8\\'/><text x=\\'75\\' y=\\'40\\' font-family=\\'Arial\\' font-size=\\'12\\' fill=\\'%23666\\' text-anchor=\\'middle\\' dominant-baseline=\\'middle\\'>TEPNG LOGO</text></svg>';"> 1517 </div> 1518 <div class="header-content"> 1519 <div class="print-logo">Solar Power Calculator</div> 1520 <div class="print-subtitle">Your Personalized Solar System Recommendation</div> 1521 <div class="print-date">Generated on ${formattedDate} at ${formattedTime}</div> 1522 </div> 1523 </div> 1524 1525 <div class="print-section"> 1526 <div class="section-title">Your Inputs</div> 1527 <div class="inputs-section"> 1528 <div class="input-item"> 1529 <div class="input-label">Home Type</div> 1530 <div class="input-value">${homeType}</div> 1531 </div> 1532 <div class="input-item"> 1533 <div class="input-label">Sunlight Level</div> 1534 <div class="input-value">${sunlight}</div> 1535 </div> 1536 <div class="input-item"> 1537 <div class="input-label">Backup Duration</div> 1538 <div class="input-value">${backup}</div> 1539 </div> 1540 </div> 1541 </div> 1542 1543 <div class="print-section"> 1544 <div class="section-title">Recommended Solar System</div> 1545 1546 <div class="results-grid"> 1547 <div class="result-card"> 1548 <div class="result-name">Solar Panels</div> 1549 <div class="result-value">${solarPanelValue}</div> 1550 <div class="result-description">Capture sunlight to power your home</div> 1551 </div> 1552 1553 <div class="result-card"> 1554 <div class="result-name">Battery Capacity</div> 1555 <div class="result-value">${batteryValue}</div> 1556 <div class="result-description">Stores energy for use when there's no sun</div> 1557 </div> 1558 1559 <div class="result-card"> 1560 <div class="result-name">System Size</div> 1561 <div class="result-value">${systemSizeValue}</div> 1562 <div class="result-description">Powers all your selected appliances</div> 1563 </div> 1564 </div> 1565 1566 <div class="accuracy-note"> 1567 <div class="note-icon"><i class="fas fa-exclamation-triangle"></i></div> 1568 <div class="note-text">These estimates may not be exact. Actual energy usage can vary based on specific models and usage patterns.</div> 1569 </div> 1570 1571 <div class="total-energy"> 1572 <div class="total-title">Total Daily Energy Use</div> 1573 <div class="total-value">${totalEnergy.toFixed(1)} kWh</div> 1574 </div> 1575 </div> 1576 1577 <div class="print-section"> 1578 <div class="section-title">
1578Selected Appliances (${appliances.length})</div> 1579 1580 <div class="appliances-list"> 1581 <div class="appliances-header"> 1582 <div>Appliance</div> 1583 <div style="text-align:center">Quantity</div> 1584 <div style="text-align:center">Wattage</div> 1585 <div style="text-align:center">Hours/Day</div> 1586 <div style="text-align:center">kWh/Day</div> 1587 </div> 1588 1589 ${appliances.map(app => ` 1590 <div class="appliance-item"> 1591 <div>${app.name}</div> 1592 <div style="text-align:center">${app.quantity}</div> 1593 <div style="text-align:center">${app.wattage}W</div> 1594 <div style="text-align:center">${app.hours}</div> 1595 <div style="text-align:center">${app.kwh.toFixed(2)}</div> 1596 </div> 1597 `).join('')} 1598 </div> 1599 </div> 1600 1601 <div class="print-footer"> 1602 This solar system recommendation is based on the appliances and options you selected. 1603 For a detailed professional assessment, please contact a solar installation expert. 1604 </div> 1605 1606 <div class="disclaimer"> 1607 Note: Actual energy usage may vary based on specific product models, usage patterns, and environmental factors. 1608 </div> 1609 1610 <div class="print-actions"> 1611 <button class="print-button" onclick="window.print();">Print This Page</button> 1612 </div> 1613 </div> 1614 1615 <script> 1616 // Auto-print once everything is loaded 1617 window.onload = function() { 1618 // Small delay to ensure styles are applied 1619 setTimeout(function() { 1620 // Notify the opener window that printing is ready 1621 window.opener.postMessage('print_ready', '*'); 1622 }, 500); 1623 } 1624 </script> 1625 </body> 1626 </html> 1627 `); 1628 1629 // Close the document to finish loading 1630 printWindow.document.close(); 1631 1632 // Set up message listener for when printing is ready 1633 window.addEventListener('message', function messageHandler(e) { 1634 if (e.data === 'print_ready') { 1635 // Remove the event listener to prevent multiple calls 1636 window.removeEventListener('message', messageHandler); 1637 1638 // Hide the loading spinner 1639 hideLoadingSpinner(); 1640 } 1641 }); 1642 }); 1643 } 1644 } 1645 1646 backToStep2Button.addEventListener('click', function() { 1647 navigateToStep(2); 1648 }); 1649 1650 setupEnhancedPrinting(); 1651 1652 1653 // ----------- INITIALIZATION ----------- 1654 1655 // Load shared appliances grid 1656 if (window.AppliancesModule) { 1657 // Set global synchronization between modules 1658 window.customApplianceCounter = 0; 1659 window.AppliancesModule.loadAppliancesGrid('simple', 'appliance-grid-container'); 1660 } else { 1661 console.error('Appliances module not loaded!'); 1662 } 1663 1664 // Initialize language 1665 initializeLanguage(); 1666 });
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.