1(function(){ 2 console.log("Script IIFE started"); 3 4 // ========= FILE HANDLING GLOBALS ========= 5 // (currentSelectedFiles è definito globalmente più avanti, riga 1429 circa) 6 7 8 // ========= HELPER FUNCTIONS (UCCELLI PARITY) ========= 9 function readFileAsDataURL(file) { 10 return new Promise((resolve, reject) => { 11 const reader = new FileReader(); 12 reader.onload = () => resolve(reader.result); 13 reader.onerror = () => reject(new Error("Impossibile leggere il file.")); 14 reader.readAsDataURL(file); 15 }); 16 } 17 18 async function convertFile(file) { 19 if (file.type === "image/heic" || file.type === "image/heif" || 20 (file.name && (file.name.toLowerCase().endsWith(".heic") || file.name.toLowerCase().endsWith(".heif")))) { 21 console.log(`Tentativo di conversione del file HEIC/HEIF: ${file.name}`); 22 if (typeof heic2any === "undefined") { 23 console.error("Libreria heic2any non caricata."); 24 // alert("Errore: la libreria di conversione HEIC non è disponibile."); 25 return file; // Fallback to original 26 } 27 try { 28 const jpegBlob = await heic2any({ blob: file, toType: "image/jpeg", quality: 0.9 }); 29 const convertedBlob = Array.isArray(jpegBlob) ? jpegBlob[0] : jpegBlob; 30 const newFileName = (file.name || "immagine").replace(/\.heic|\.heif/i, "") + ".jpg"; 31 return new File([convertedBlob], newFileName, { type: "image/jpeg", lastModified: file.lastModified }); 32 } catch (e) { 33 console.error("Errore durante la conversione HEIC:", e); 34 // alert(`Impossibile convertire il file ${file.name} (formato HEIC/HEIF). Caricare una foto JPG/PNG.`); 35 return file; // Fallback to original 36 } 37 } 38 return file; 39 } 40 41 async function compressImageIfNeeded(file) { 42 try { 43 if (!file || !file.type || !file.type.startsWith("image/")) return file; 44 if (file.size && file.size < 400 * 1024) { 45 console.log(`Skipping compression for ${file.name} (${file.size} bytes)`); 46 return file; 47 } 48 49 console.log(`Compressing ${file.name} (${file.size} bytes)...`); 50 const dataUrl = await readFileAsDataURL(file); 51 const img = await new Promise((resolve, reject) => { 52 const i = new Image(); 53 i.onload = () => resolve(i); 54 i.onerror = () => reject(new Error("Impossibile caricare l'immagine per la compressione.")); 55 i.src = dataUrl; 56 }); 57 const maxSide = 1600; 58 let w = img.naturalWidth || img.width; 59 let h = img.naturalHeight || img.height; 60 const scale = Math.min(1, maxSide / Math.max(w, h)); 61 const tw = Math.round(w * scale); 62 const th = Math.round(h * scale); 63 const canvas = document.createElement("canvas"); 64 canvas.width = tw; 65 canvas.height = th; 66 const ctx = canvas.getContext("2d"); 67 if (!ctx) return file; 68 ctx.drawImage(img, 0, 0, tw, th); 69 const blob = await new Promise((resolve) => { 70 canvas.toBlob((b) => resolve(b), "image/jpeg", 0.8); 71 }); 72 if (!blob) return file; 73 const newName = (file.name || "immagine").replace(/\.(jpe?g|png|webp|heic|heif)$/i, "") + ".jpg"; 74 const newFile = new File([blob], newName, { type: "image/jpeg" }); 75 console.log(`Compressione completata: ${newFile.name} (${newFile.size} bytes)`); 76 return newFile; 77 } catch (e) { 78 console.warn("Compressione immagine fallita, invio originale:", e); 79 return file; 80 } 81 } 82 83 // --- RECUPERO DATI UTENTE --- 84 const SHARED_CITIZEN_PROFILE_KEY = "guardaMI_cittadini_profile_v1"; 85 const nomeInputCache = document.getElementById('nomeRilevatore'); 86 const emailInputCache = document.getElementById('email'); 87 88 if (nomeInputCache && emailInputCache) { 89 const KEY_NOME = 'guardaMI_farfalle_cittadini_nome'; 90 const KEY_EMAIL = 'guardaMI_farfalle_cittadini_email'; 91 const LEGACY_KEY_NOME = 'butterfly_user_name'; 92 const LEGACY_KEY_EMAIL = 'butterfly_user_email'; 93 94 const getShared = () => {
95 try { 96 const raw = localStorage.getItem(SHARED_CITIZEN_PROFILE_KEY); 97 if (!raw) return null; 98 const data = JSON.parse(raw); 99 if (!data || typeof data !== "object") return null; 100 const nome = typeof data.nome === "string" ? data.nome : ""; 101 const mail = typeof data.mail === "string" ? data.mail : ""; 102 return { nome, mail }; 103 } catch (e) { 104 return null; 105 } 106 }; 107 const setShared = (nome, mail) => { 108 try { 109 const payload = { nome: String(nome || ""), mail: String(mail || ""), updatedAt: Date.now() }; 110 localStorage.setItem(SHARED_CITIZEN_PROFILE_KEY, JSON.stringify(payload)); 111 localStorage.setItem("userData", JSON.stringify({ nome: payload.nome, mail: payload.mail })); 112 } catch (e) {} 113 }; 114 const clearShared = () => { 115 try { localStorage.removeItem(SHARED_CITIZEN_PROFILE_KEY); } catch (e) {} 116 try { localStorage.removeItem("userData"); } catch (e) {} 117 }; 118 119 const shared = getShared(); 120 if (shared) { 121 if (!nomeInputCache.value && shared.nome) nomeInputCache.value = shared.nome; 122 if (!emailInputCache.value && shared.mail) emailInputCache.value = shared.mail; 123 } 124 125 // Carica se esistono 126 const savedNome = localStorage.getItem(KEY_NOME) || localStorage.getItem(LEGACY_KEY_NOME); 127 const savedEmail = localStorage.getItem(KEY_EMAIL) || localStorage.getItem(LEGACY_KEY_EMAIL); 128 if (savedNome && !nomeInputCache.value) nomeInputCache.value = savedNome; 129 if (savedEmail && !emailInputCache.value) emailInputCache.value = savedEmail; 130 131 if (!shared) { 132 const n = nomeInputCache.value || ""; 133 const m = emailInputCache.value || ""; 134 if (n || m) setShared(n, m); 135 } 136 137 // Salva al cambiamento 138 nomeInputCache.addEventListener('input', () => { 139 localStorage.setItem(KEY_NOME, nomeInputCache.value); 140 setShared(nomeInputCache.value, emailInputCache.value); 141 }); 142 emailInputCache.addEventListener('input', () => { 143 localStorage.setItem(KEY_EMAIL, emailInputCache.value); 144 setShared(nomeInputCache.value, emailInputCache.value); 145 }); 146 147 // Bottone Dimentica 148 const clearBtn = document.getElementById('clearSavedUserData'); 149 if (clearBtn) { 150 clearBtn.addEventListener('click', () => { 151 if (confirm('Vuoi cancellare i dati salvati (nome ed email) su questo dispositivo?')) { 152 localStorage.removeItem(KEY_NOME); 153 localStorage.removeItem(KEY_EMAIL); 154 localStorage.removeItem(LEGACY_KEY_NOME); 155 localStorage.removeItem(LEGACY_KEY_EMAIL); 156 clearShared(); 157 nomeInputCache.value = ''; 158 emailInputCache.value = ''; 159 alert('Dati cancellati.'); 160 } 161 }); 162 } 163 } 164 165 // ========= GOOGLE MAPS + GEOLOCALIZZAZIONE ========= 166 let map; 167 let marker; 168 window.manualEditMade = false; 169 170 window.initButterflyMap = function() { 171 const mapDiv = document.getElementById('map'); 172 if (!mapDiv || !window.google || !google.maps) return; 173 174 // Centro di default: Milano 175 const defaultPos = { lat: 45.4642, lng: 9.19 }; 176 177 map = new google.maps.Map(mapDiv, { 178 center: defaultPos, 179 zoom: 12, 180 streetViewControl: false, 181 mapTypeControl: false 182 }); 183 184 marker = new google.maps.Marker({ 185 position: defaultPos, 186 map: map, 187 draggable: true 188 }); 189 190 // Rende mappa e marker accessibili globalmente (per lettura EXIF, ecc.) 191 window.map = map; 192 window.marker = marker; 193 194 // FIX: Force resize trigger for iframe/hidden tab scenarios 195 setTimeout(() => { 196 google.maps.event.trigger(map, 'resize'); 197 map.setCenter(defaultPos); 198 }, 500); 199 200 const latInput = document.getElementById('latitudine'); 201 const lngInput = document.getElementById('longitudine'); 202 203 if (latInput && lngInput) { 204 if (!latInput.value) { 205 latInput.value = defaultPos.lat.toFixed(6); 206 lngInput.value = defaultPos.lng.toFixed(6); 207 } 208 } 209 210 marker.addListener('dragend', (e) => { 211 window.manualEditMade = true; 212 const pos = e.latLng; 213 const lat = pos.lat(); 214 const lng = pos.lng(); 215 if (latInput && lngInput) { 216 latInput.value = lat.toFixed(6); 217 lngInput.value = lng.toFixed(6); 218 latInput.dispatchEvent(new Event('change')); 219 lngInput.dispatchEvent(new Event('change')); 220 } 221 }); 222 223 // Autocomplete Search Box (Optional but good for parity) 224 const searchInput = document.getElementById('mapSearch'); 225 if (searchInput && google.maps.places) { 226 const autocomplete = new google.maps.places.Autocomplete(searchInput, { 227 fields: ['geometry', 'formatted_address'], 228 types: ['geocode'] 229 }); 230 autocomplete.bindTo('bounds', map); 231 autocomplete.addListener('place_changed', () => { 232 window.manualEditMade = true; 233 const place = autocomplete.getPlace(); 234 if (!place.geometry || !place.geometry.location) return; 235 236 if (place.geometry.viewport) map.fitBounds(place.geometry.viewport); 237 else { 238 map.setCenter(place.geometry.location); 239 map.setZoom(17); 240 } 241 marker.setPosition(place.geometry.location); 242 243 if (latInput && lngInput) { 244 latInput.value = place.geometry.location.lat().toFixed(6); 245 lngInput.value = place.geometry.location.lng().toFixed(6); 246 latInput.dispatchEvent(new Event('change')); 247 lngInput.dispatchEvent(new Event('change')); 248 } 249 }); 250 } 251 252 chiediGeolocalizzazione(); 253 }; 254 255 window.chiediGeolocalizzazione = function() { 256 if (!navigator.geolocation || !map || !marker) return; 257 258 navigator.geolocation.getCurrentPosition( 259 (pos) => { 260 if (window.manualEditMade) return; // Non sovrascrivere se utente ha editato manualmente 261 262 const lat = pos.coords.latitude; 263 const lng = pos.coords.longitude; 264 const nuovaPos = { lat, lng }; 265 266 map.setCenter(nuovaPos); 267 map.setZoom(15); 268 marker.setPosition(nuovaPos); 269 270 const latInput = document.getElementById('latitudine'); 271 const lngInput = document.getElementById('longitudine'); 272 if (latInput && lngInput) { 273 latInput.value = lat.toFixed(6); 274 lngInput.value = lng.toFixed(6); 275 latInput.dispatchEvent(new Event('change')); 276 lngInput.dispatchEvent(new Event('change')); 277 } 278 }, 279 (err) => { 280 console.warn('Errore geolocalizzazione:', err); 281 // se l'utente rifiuta rimaniamo sulla posizione di default 282 }, 283 { 284 enableHighAccuracy: true, 285 timeout: 10000 286 } 287 ); 288 } 289 290 // ========= EXIF HANDLING ========= 291 const fileInput = document.getElementById('butterflyImage'); 292 const exifWarning = document.getElementById('exifWarningBox') || document.getElementById('exif-warning'); 293 294 if (fileInput) { 295 fileInput.addEventListener('change', async function(e) { 296 if (this.files && this.files.length > 0) { 297 const file = this.files[0]; 298 try { 299 if(!window.exifr) return; 300 await readExifData(file); 301 302 } catch (err) { 303 console.warn('Errore lettura EXIF:', err); 304 if(exifWarning) exifWarning.style.display = 'block'; 305 } 306 } 307 }); 308 } 309 310 // ========= QUI SOTTO C'à TUTTO IL TUO SCRIPT ORIGINALE ========= 311 console.log("Script reaching original logic"); 312 313 document.addEventListener("DOMContentLoaded", () => { 314 // Toggle blocks by "Conosci la specie?" 315 const radios = document.querySelectorAll('input[name="conosciSpecieLocal"]'); 316 const species = document.getElementById('speciesFields'); 317 const colorPanel = document.getElementById('colorPanel'); 318 function updateBlocks(){ 319 const val = (document.querySelector('input[name="conosciSpecieLocal"]:checked')||{}).value; 320 species.style.display = (val === 'si') ? 'block' : 'none'; 321 colorPanel.style.display = (val === 'no') ? 'block' : 'none'; 322 } 323 radios.forEach(r => r.addEventListener('change', updateBlocks)); 324 updateBlocks(); 325 326 327 // Preload di tutte le immagini specie per rendere i cambi colore più fluidi 328 const preloadImages = [ 329 'species/Latonia ali aperte.jpg', 330 'species/Latonia ali aperte.jpg',
331 'species/Coenonympha pamphilus ali aperte.jpg', 332 'species/Coenonympha pamphilus _ali chiuse_jpg.jpg', 333 'species/crocea_aperte.jpg', 334 'species/crocea_chiuse.jpg', 335 'species/Gonepteryx rhamni_ali chiuse.jpg', 336 'species/icaro_ali aperte.jpg', 337 'species/icaro_ali chiuse.jpg', 338 'species/Lasiommata megera_ali aperte.jpg', 339 'species/Lasiommata megera_ali chiuse.jpg', 340 'species/Latonia ali aperte.jpg', 341 'species/Latonia ali aperte.jpg', 342 'species/Lycaena phlaeas_ali aperte.jpg', 343 'species/Lycaena phlaeas_ali chiuse.jpg', 344 'species/macaone_ali aperte.jpg', 345 'species/macaone ali chiuse.jpg', 346 'species/occhio di pavone ali aperte.jpg', 347 'species/occhio di pavone ali chiuse.jpg', 348 'species/pieris_brassicae_aperte.jpg', 349 'species/pieris_brassicae_chiuse.jpg', 350 'species/pieris_napi_aperte.jpg', 351 'species/pieris_napi_chiuse.jpg', 352 'species/pieris_rapae_aperte.jpg', 353 'species/pieris_rapae_chiuse.jpg', 354 'species/Pyrgus_malvoides_aperte.jpg', 355 'species/Pyrgus_malvoides_chiuse.jpg', 356 'species/podalirio ali aperte.jpg', 357 'species/podalirio ali chiuse.jpg', 358 'species/pontia_edusa_aperte.jpg', 359 'species/pontia_edusa_aperte_alt.jpg', 360 'species/pontia_edusa_chiuse.jpg', 361 'species/Virgola ali aperte.jpg', 362 'species/virgola ali chiuse.jpg', 363 'species/Sfinge del galio (Macroglossum stellatarum) ali aperte.jpg', 364 'species/Sfinge del galio (Macroglossum stellatarum).jpg' 365 ]; 366 367 function resolveSpeciesImg(src) { 368 const s = String(src || '').trim(); 369 if (!s) return s; 370 if (/^(https?:)?\/\//i.test(s) || s.startsWith('data:') || s.startsWith('blob:')) return s; 371 if (s.startsWith('/assets/species/')) return s; 372 if (s.startsWith('species/')) { 373 const file = s.slice('species/'.length); 374 const remap = { 375 'Carcharodus alceae ali aperte.jpg': 'Carcharodus alceae ali aperte__farfalle.jpg', 376 'Gonepteryx rhamni_ali chiuse.jpg': 'Gonepteryx rhamni_ali chiuse__farfalle.jpg', 377 }; 378 const finalName = remap[file] || file; 379 return '/assets/species/' + encodeURIComponent(finalName); 380 } 381 return s; 382 } 383 384 const DEFAULT_IMG_CREDIT = ''; 385 function getImageCreditFromResolved(resolvedSrc) { 386 const s = String(resolvedSrc || '').trim(); 387 if (!s.startsWith('/assets/species/')) return ''; 388 return DEFAULT_IMG_CREDIT; 389 } 390 391 function applyImageCredits(root) { 392 const r = root || document; 393 if (!r.querySelectorAll) return; 394 const imgs = r.querySelectorAll('img'); 395 imgs.forEach((img) => { 396 const srcAttr = img.getAttribute('src') || ''; 397 const resolved = resolveSpeciesImg(srcAttr || img.src || ''); 398 const credit = getImageCreditFromResolved(resolved); 399 if (!credit) return; 400 const block = img.closest ? img.closest('.img-block') : null; 401 if (!block) return; 402 const existing = block.querySelector('.img-credit'); 403 if (existing) { 404 if (!existing.textContent || !existing.textContent.trim()) existing.textContent = credit; 405 return; 406 } 407 const el = document.createElement('div'); 408 el.className = 'img-credit'; 409 el.textContent = credit; 410 block.appendChild(el); 411 }); 412 } 413 414 function fixSpeciesImages(root) { 415 const r = root || document; 416 const imgs = r.querySelectorAll ? r.querySelectorAll('img[src^=\"species/\"]') : []; 417 imgs.forEach((img) => { 418 const raw = img.getAttribute('src'); 419 const fixed = resolveSpeciesImg(raw); 420 if (fixed && fixed !== raw) img.setAttribute('src', fixed); 421 }); 422 applyImageCredits(r); 423 } 424 preloadImages.forEach((src) => { 425 const img = new Image(); 426 img.src = resolveSpeciesImg(src); 427 }); 428 429 fixSpeciesImages(document); 430 try { 431 const observer = new MutationObserver((mutations) => { 432 mutations.forEach((m) => { 433 m.addedNodes.forEach((node) => { 434 if (!node || node.nodeType !== 1) return; 435 fixSpeciesImages(node); 436 }); 437 }); 438 }); 439 observer.observe(document.body, { childList: true, subtree: true }); 440 } catch (e) {} 441 442 const phenologyData = { 443 "Aglais io": [5, 6, 7, 8, 9], 444 "Pieris brassicae": [3, 4, 5, 6, 7, 8, 9], 445 "Pieris rapae": [2, 3, 4, 5, 6, 7, 8, 9], 446 "Pieris napi": [2, 3, 4, 5, 6, 7, 8, 9, 10], 447 "Pontia edusa": [5, 6, 7, 8], 448 "Gonepteryx rhamni": [2, 3, 4, 5, 6, 7, 8, 9], 449 "Vanessa atalanta": [3, 4, 5, 6, 7, 8, 9], 450 "Vanessa cardui": [3, 4, 5, 6, 7, 8, 9], 451 "Iphiclides podalirius": [3, 4, 5, 6, 7, 8], 452 "Papilio machaon": [3, 4, 5, 6, 7, 8], 453 "Polygonia c-album": [4, 5, 6, 7, 8, 9], 454 "Lasiommata megera": [3, 4, 5, 6, 7, 8, 9],
455 "Coenonympha pamphilus": [4, 5, 6, 7, 8], 456 "Carcharodus alceae": [4, 5, 6, 7, 8], 457 "Heteropterus morpheus": [2, 3, 4, 5, 6, 7, 8, 9], 458 "Thymelicus lineola": [4, 5, 6, 7, 8], 459 "Thymelicus sylvestris": [4, 5, 6, 7, 8], 460 "Ochlodes sylvanus": [4, 5, 6, 7, 8], 461 "Thymelicus acteon": [4, 5, 6, 7, 8], 462 "Hesperia comma": [4, 5, 6, 7, 8], 463 "Erynnis tages": [4, 5, 6, 7, 8], 464 "Pyrgus armoricanus": [4, 5, 6, 7, 8], 465 "Pyrgus malvoides": [4, 5, 6, 7, 8], 466 "Spialia sertorius": [4, 5, 6, 7, 8], 467 "Polyommatus dorylas": [4, 5, 6, 7, 8], 468 "Polyommatus icarus": [2, 3, 4, 5, 6, 7, 8, 9], 469 "Aricia agestis": [4, 5, 6, 7, 8, 9], 470 "Polyommatus amandus": [4, 5, 6, 7, 8], 471 "Glaucopsyche alexis": [2, 3, 4, 5, 6, 7, 8, 9], 472 "Lysandra bellargus": [4, 5, 6, 7, 8], 473 "Lycaena phlaeas": [2, 3, 4, 5, 6, 7, 8, 9], 474 "Lycaena dispar": [3, 4, 5, 6, 7, 8, 9], 475 "Lycaena tityrus": [2, 3, 4, 5, 6, 7, 8], 476 "Phengaris alcon": [4, 5, 6, 7, 8], 477 "Phengaris teleius": [4, 5, 6, 7, 8], 478 "Cupido minimus": [2, 3, 4, 5, 6, 7, 8, 9], 479 "Celastrina argiolus": [2, 3, 4, 5, 6, 7, 8, 9], 480 "Cupido alcetas": [4, 5, 6, 7, 8], 481 "Cacyreus marshalli": [3, 4, 5, 6, 7, 8, 9], 482 "Lampides boeticus": [3, 4, 5, 6, 7, 8, 9], 483 "Leptotes pirithous": [2, 3, 4, 5, 6, 7, 8, 9], 484 "Plebejus argus": [2, 3, 4, 5, 6, 7, 8, 9], 485 "Cupido argiades": [3, 4, 5, 6, 7, 8, 9], 486 "Plebejus idas": [2, 3, 4, 5, 6, 7, 8, 9], 487 "Plebejus argyrgnomon": [2, 3, 4, 5, 6, 7, 8, 9], 488 "Satyrium acaciae": [3, 4, 5, 6, 7, 8], 489 "Satyrium spini": [2, 3, 4, 5, 6, 7, 8, 9], 490 "Issoria lathonia": [4, 5, 6, 7, 8], 491 "Euphydryas aurinia": [2, 3, 4, 5, 6, 7, 8], 492 "Boloria selene": [4, 5, 6, 7, 8], 493 "Boloria dia": [4, 5, 6, 7, 8], 494 "Boloria euphrosyne": [4, 5, 6, 7, 8], 495 "Satyrium w-album": [4, 5, 6, 7, 8], 496 "Satyrium pruni": [4, 5, 6, 7, 8], 497 "Callophrys rubi": [2, 3, 4, 5, 6, 7, 8, 9], 498 "Iassus orithya": [2, 3, 4, 5, 6, 7, 8, 9], 499 "Colias crocea": [3, 4, 5, 6, 7, 8, 9], 500 "Macroglossum stellatarum": [2, 3, 4, 5, 6, 7, 8, 9, 10] 501 }; 502 503 function renderPhenologyGrid(speciesName) { 504 if (!phenologyData[speciesName]) return ''; 505 const months = ['G', 'F', 'M', 'A', 'M', 'G', 'L', 'A', 'S', 'O', 'N', 'D']; 506 const activeMonths = phenologyData[speciesName]; 507 let gridHtml = '<div class="phenology-container" style="margin-bottom: 2px;">'; 508 gridHtml += '<div style="font-size: 0.8em; margin-bottom: 2px; color: #2c5fa6; font-weight: bold;">Periodo di volo:</div>'; 509 gridHtml += '<div class="phenology-grid" style="display: flex; gap: 2px;">'; 510 months.forEach((m, index) => { 511 const isActive = activeMonths.includes(index); 512 const bg = isActive ? '#4CAF50' : '#e0e0e0'; 513 const color = isActive ? 'white' : '#888'; 514 gridHtml += `<div style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; background-color: ${bg}; color: ${color}; font-size: 10px; border-radius: 2px;">${m}</div>`; 515 }); 516 gridHtml += '</div></div>'; 517 return gridHtml; 518 } 519 520 // Other species text 521 const selectSpecie = document.getElementById('nomeSpecie'); 522 const otherWrap = document.getElementById('otherSpeciesField'); 523 if(selectSpecie){ 524 selectSpecie.addEventListener('change',()=>{ 525 }); 526 } 527 528 // Color selection 529 const grid = document.getElementById('colorGrid'); 530 const hidden = document.getElementById('colorePredominante'); 531 if(grid){ 532 grid.addEventListener('click', (e)=>{ 533 const btn = e.target.closest('.color-btn'); 534 if(!btn) return; 535 grid.querySelectorAll('.color-btn').forEach(b=>b.classList.remove('selected')); 536 btn.classList.add('selected'); 537 hidden.value = btn.getAttribute('data-color') || ''; 538 }); 539 } 540 541 // Consent -> enable submit 542 const form = document.getElementById('butterfly-form'); 543 const submitBtn = document.getElementById('submitButton'); 544 const consentWarning = document.getElementById('consentWarning'); 545 const AGE_GATE_KEY = "guardaMI_age_gate_14_or_parent"; 546 const ageGate = document.getElementById('ageGate14'); 547 try { 548 if (ageGate && localStorage.getItem(AGE_GATE_KEY) === "true") ageGate.checked = true; 549 } catch (e) {} 550 551 const updateSubmitGate = () => { 552 const consent = (form.querySelector('input[name="q266_ilSottoscritto"]:checked')||{}).value; 553 let storedAgeOk = false;
554 try { 555 storedAgeOk = localStorage.getItem(AGE_GATE_KEY) === "true"; 556 } catch (e) { 557 storedAgeOk = false; 558 } 559 const ageOk = (ageGate && ageGate.checked) || storedAgeOk; 560 if (ageGate) { 561 try { 562 localStorage.setItem(AGE_GATE_KEY, ageGate.checked ? "true" : "false"); 563 } catch (e) {} 564 } 565 566 if (submitBtn) submitBtn.disabled = false; 567 if (consentWarning) { 568 consentWarning.style.display = (consent === 'Acconsento' && ageOk) ? 'none' : 'block'; 569 } 570 }; 571 form.addEventListener('change', updateSubmitGate); 572 if (ageGate) ageGate.addEventListener('change', updateSubmitGate); 573 updateSubmitGate(); 574 575 // Sincronizza coordinate con il campo geolocalizzazione di Jotform 576 const latInputJF = document.getElementById('latitudine'); 577 const lngInputJF = document.getElementById('longitudine'); 578 const jotformGeoInput = document.getElementById('jotformGeoloc'); 579 if (latInputJF && lngInputJF && jotformGeoInput && form) { 580 const syncGeoToJotform = () => { 581 const lat = latInputJF.value; 582 const lng = lngInputJF.value; 583 if (lat && lng) { 584 jotformGeoInput.value = lat + ', ' + lng; 585 } else { 586 jotformGeoInput.value = ''; 587 } 588 }; 589 latInputJF.addEventListener('change', syncGeoToJotform); 590 lngInputJF.addEventListener('change', syncGeoToJotform); 591 form.addEventListener('submit', syncGeoToJotform); 592 syncGeoToJotform(); 593 } 594 595// ===== Selezione colore ali e scheda Occhio di pavone ===== 596 const colorButtons = document.querySelectorAll('.color-btn'); 597 const hiddenColorInput = document.getElementById('colorePredominante'); 598 599 const schedaSpecieRow = document.getElementById('schedaSpecieRow'); 600 const schedaSpecie = schedaSpecieRow ? schedaSpecieRow.querySelector('.species-card') : null; 601 const nomeComuneSpecie = document.getElementById('nomeComuneSpecie'); 602 const nomeScientificoSpecie = document.getElementById('nomeScientificoSpecie'); 603 const fotoSpecieAperta = document.getElementById('fotoSpecieAperta'); 604 const fotoSpecieChiusa = document.getElementById('fotoSpecieChiusa'); 605 const testoAmbienteSpecie = document.getElementById('testoAmbienteSpecie'); 606 607 let currentSpecieValue = ''; 608
609 function setNomeSpecieValue(valueOrLabel) { 610 if (!selectSpecie) return; 611 const target = String(valueOrLabel || '').trim(); 612 if (!target) return; 613 614 const options = Array.from(selectSpecie.options || []); 615 const byValueExact = options.find((o) => String(o.value || '').trim() === target); 616 if (byValueExact) { 617 selectSpecie.value = byValueExact.value; 618 selectSpecie.dispatchEvent(new Event('change', { bubbles: true })); 619 return; 620 } 621 622 const norm = (s) => String(s || '').trim().toLowerCase().replace(/\s+/g, ' '); 623 const targetNorm = norm(target); 624 const byValueNorm = options.find((o) => norm(o.value) === targetNorm); 625 if (byValueNorm) { 626 selectSpecie.value = byValueNorm.value; 627 selectSpecie.dispatchEvent(new Event('change', { bubbles: true })); 628 return; 629 } 630 631 const byTextIncludes = options.find((o) => norm(o.textContent).includes(targetNorm)); 632 if (byTextIncludes) { 633 selectSpecie.value = byTextIncludes.value; 634 selectSpecie.dispatchEvent(new Event('change', { bubbles: true })); 635 return; 636 } 637 } 638 639 function resetMultiSpeciesView(){ 640 const pierisContainer = document.getElementById('pierisContainer'); 641 if(pierisContainer){ 642 pierisContainer.innerHTML = ''; 643 } 644 const gialloNeroContainer = document.getElementById('gialloNeroContainer'); 645 if(gialloNeroContainer){ 646 gialloNeroContainer.innerHTML = ''; 647 } 648 const arancioneMarroneContainer = document.getElementById('arancioneMarroneContainer'); 649 if(arancioneMarroneContainer){ 650 arancioneMarroneContainer.innerHTML = ''; 651 } 652 const arancioNeroContainer = document.getElementById('arancioNeroContainer'); 653 if(arancioNeroContainer){ 654 arancioNeroContainer.innerHTML = ''; 655 } 656 const arancioMarroneBiancoContainer = document.getElementById('arancioMarroneBiancoContainer'); 657 if(arancioMarroneBiancoContainer){ 658 arancioMarroneBiancoContainer.innerHTML = ''; 659 } 660 const neroArancioBiancoContainer = document.getElementById('neroArancioBiancoContainer'); 661 if(neroArancioBiancoContainer){ 662 neroArancioBiancoContainer.innerHTML = ''; 663 } 664 const marroneBiancoContainer = document.getElementById('marroneBiancoContainer'); 665 if(marroneBiancoContainer){ 666 marroneBiancoContainer.innerHTML = ''; 667 } 668 if(schedaSpecie){ 669 schedaSpecie.style.display = 'block'; 670 } 671 } 672 673 674 675function mostraOcchioDiPavone(){ 676 if(!schedaSpecieRow) return; 677 resetMultiSpeciesView(); 678 currentSpecieValue = 'Occhio di Pavone'; 679 nomeComuneSpecie.textContent = 'Occhio di pavone '; 680 nomeScientificoSpecie.textContent = '(Aglais io)'; 681 if(fotoSpecieAperta){ 682 fotoSpecieAperta.src = resolveSpeciesImg('species/occhio di pavone ali aperte.jpg'); 683 fotoSpecieAperta.alt = 'Occhio di pavone - ali aperte'; 684 } 685 if(fotoSpecieChiusa){ 686 fotoSpecieChiusa.src = resolveSpeciesImg('species/occhio di pavone ali chiuse.jpg'); 687 fotoSpecieChiusa.alt = 'Occhio di pavone - ali chiuse'; 688 } 689 if(testoAmbienteSpecie){ 690 const phenologyContainer = document.getElementById('phenologyContainer'); 691 if(phenologyContainer){ 692 phenologyContainer.innerHTML = renderPhenologyGrid('Aglais io'); 693 } 694 testoAmbienteSpecie.innerHTML = 695 '<strong>Come riconoscerla</strong><br>Ali arancioni con occhi turchesi a forma di occhio sulla parte superiore. Inferiore grigia con occhi più scuri.<br><br><strong>Habitat</strong><br>' + 696 'à una farfalla che si incontra spesso in giardini, parchi e bordi di sentiero, dove gli adulti visitano i fiori per nutrirsi. ' + 697 'Le larve vivono soprattutto su ortica in zone aperte, ai margini di boschi, siepi e aree agricole.'; 698 } 699 schedaSpecieRow.style.display = 'block'; 700 } 701 function mostraIcaro(){ 702 if(!schedaSpecieRow) return; 703 resetMultiSpeciesView(); 704 currentSpecieValue = 'Icaro'; 705 nomeComuneSpecie.textContent = 'Icaro '; 706 nomeScientificoSpecie.textContent = '(Polyommatus icarus)'; 707 if(fotoSpecieAperta){ 708 fotoSpecieAperta.src = resolveSpeciesImg('species/icaro_ali aperte.jpg'); 709 fotoSpecieAperta.alt = 'Icaro - ali aperte'; 710 } 711 if(fotoSpecieChiusa){ 712 fotoSpecieChiusa.src = resolveSpeciesImg('species/icaro_ali chiu
712se.jpg'); 713 fotoSpecieChiusa.alt = 'Icaro - ali chiuse'; 714 } 715 if(testoAmbienteSpecie){ 716 const phenologyContainer = document.getElementById('phenologyContainer'); 717 if(phenologyContainer){ 718 phenologyContainer.innerHTML = renderPhenologyGrid('Polyommatus icarus'); 719 } 720 testoAmbienteSpecie.innerHTML = '<strong>Come riconoscerla</strong><br>Maschi azzurro brillante, femmine marroni con lunule arancioni. Sotto grigio-chiaro con punti neri cerchiati di bianco.<br><br><strong>Habitat</strong><br>Diffusa in prati aperti, campi e margini erbosi. Le larve si nutrono di leguminose spontanee come trifoglio e ginestrino.'; 721 } 722 schedaSpecieRow.style.display = 'block'; 723 } 724 725 function mostraCarcharodusAlceae(){ 726 if(!schedaSpecieRow) return; 727 resetMultiSpeciesView(); 728 currentSpecieValue = 'Carcharodus alceae'; 729 nomeComuneSpecie.textContent = 'Skipper della malva '; 730 nomeScientificoSpecie.textContent = '(Carcharodus alceae)'; 731 if(fotoSpecieAperta){ 732 fotoSpecieAperta.src = resolveSpeciesImg('species/Carcharodus alceae ali aperte.jpg'); 733 fotoSpecieAperta.alt = 'Carcharodus alceae - ali aperte'; 734 } 735 if(fotoSpecieChiusa){ 736 // al momento non è disponibile una foto ad ali chiuse: riutilizziamo la stessa 737 fotoSpecieChiusa.src = resolveSpeciesImg('species/Carcharodus alceae ali aperte.jpg'); 738 fotoSpecieChiusa.alt = 'Carcharodus alceae - ali chiuse'; 739 if(fotoSpecieChiusa){ fotoSpecieChiusa.style.display='none'; if(fotoSpecieChiusa.parentElement){ fotoSpecieChiusa.parentElement.style.display='none'; }} 740 } 741 if(testoAmbienteSpecie){ 742 const phenologyContainer = document.getElementById('phenologyContainer'); 743 if(phenologyContainer){ 744 phenologyContainer.innerHTML = renderPhenologyGrid('Carcharodus alceae'); 745 } 746 testoAmbienteSpecie.innerHTML = 747 '<strong>Come riconoscerla</strong><br>Piccola farfalla con ali marmorizzate di marrone, grigio e bianco.<br><br><strong>Habitat</strong><br>' + 748 'Legata ad ambienti aperti e soleggiati, ' + 749 'come prati aridi, bordi di sentiero e margini di campi. ' + 750 'Le larve si nutrono soprattutto di malva e altre piante erbacee affini.'; 751 } 752 schedaSpecieRow.style.display = 'block'; 753 } 754 755 756 757 758 759 760 function mostraPierisBrassicae(){ 761 if(!schedaSpecieRow) return; 762 resetMultiSpeciesView(); 763 currentSpecieValue = 'Pieris brassicae'; 764 nomeComuneSpecie.textContent = 'Cavolaia maggiore '; 765 nomeScientificoSpecie.textContent = '(Pieris brassicae)'; 766 if(fotoSpecieAperta){ 767 fotoSpecieAperta.src = resolveSpeciesImg('species/pieris_brassicae_aperte.jpg'); 768 fotoSpecieAperta.alt = 'Pieris brassicae - ali aperte'; 769 } 770 if(fotoSpecieChiusa){ 771 fotoSpecieChiusa.src = resolveSpeciesImg('species/pieris_brassicae_chiuse.jpg'); 772 fotoSpecieChiusa.alt = 'Pieris brassicae - ali chiuse'; 773 } 774 if(testoAmbienteSpecie){ 775 const phenologyContainer = document.getElementById('phenologyContainer'); 776 if(phenologyContainer){ 777 phenologyContainer.innerHTML = renderPhenologyGrid('Pieris brassicae'); 778 } 779 testoAmbienteSpecie.innerHTML = 780 '<strong>Come riconoscerla</strong><br>Ali bianche con macchie nere sul bordo e una macchia nera più grande sul lato anteriore.<br><br><strong>Habitat</strong><br>' + 781 'Specie molto comune in ambienti agricoli, orti, giardini e margini di strada, ' + 782 'le cui larve si nutrono di piante crucifere coltivate e spontanee.'; 783 } 784 schedaSpecieRow.style.display = 'block'; 785 } 786 787 function mostraPierisNapi(){ 788 if(!schedaSpecieRow) return; 789 resetMultiSpeciesView(); 790 currentSpecieValue = 'Pieris napi'; 791 nomeComuneSpecie.textContent = 'Cavolaia verdastra '; 792 nomeScientificoSpecie.textContent = '(Pieris napi)'; 793 if(fotoSpecieAperta){ 794 fotoSpecieAperta.src = resolveSpeciesImg('species/pieris_napi_aperte.jpg'); 795 fotoSpecieAperta.alt = 'Pieris napi - ali aperte'; 796 } 797 if(fotoSpecieChiusa){ 798 fotoSpecieChiusa.src = resolveSpeciesImg('species/pieris_napi_chiuse.jpg');
799 fotoSpecieChiusa.alt = 'Pieris napi - ali chiuse'; 800 } 801 if(testoAmbienteSpecie){ 802 const phenologyContainer = document.getElementById('phenologyContainer'); 803 if(phenologyContainer){ 804 phenologyContainer.innerHTML = renderPhenologyGrid('Pieris napi'); 805 } 806 testoAmbienteSpecie.innerHTML = '<strong>Come riconoscerla</strong><br>Ali bianche con una macchia nera caratteristica sulla parte anteriore delle ali anteriori. Più piccola di Pieris brassicae.<br><br><strong>Habitat</strong><br>Presente in prati freschi, bordi di bosco e zone umide. Riconoscibile per le venature verdastre sulle ali chiuse. Le larve vivono su diverse crucifere spontanee.'; 807 } 808 schedaSpecieRow.style.display = 'block'; 809 } 810 811 function mostraPierisRapae(){ 812 if(!schedaSpecieRow) return; 813 resetMultiSpeciesView(); 814 currentSpecieValue = 'Pieris rapae'; 815 nomeComuneSpecie.textContent = 'Cavolaia minore '; 816 nomeScientificoSpecie.textContent = '(Pieris rapae)'; 817 if(fotoSpecieAperta){ 818 fotoSpecieAperta.src = resolveSpeciesImg('species/pieris_rapae_aperte.jpg'); 819 fotoSpecieAperta.alt = 'Pieris rapae - ali aperte'; 820 } 821 if(fotoSpecieChiusa){ 822 fotoSpecieChiusa.src = resolveSpeciesImg('species/pieris_rapae_chiuse.jpg'); 823 fotoSpecieChiusa.alt = 'Pieris rapae - ali chiuse'; 824 } 825 if(testoAmbienteSpecie){ 826 const phenologyContainer = document.getElementById('phenologyContainer'); 827 if(phenologyContainer){ 828 phenologyContainer.innerHTML = renderPhenologyGrid('Pieris rapae'); 829 } 830 testoAmbienteSpecie.innerHTML = '<strong>Come riconoscerla</strong><br>Ali bianche con piccole macchie nere sulle ali anteriori. Più piccola della cavolaia maggiore.<br><br><strong>Habitat</strong><br>Specie molto comune in ambienti urbani e rurali. Visita spesso i fiori nei giardini e vicino alle abitazioni. Le larve si alimentano di Brassicaceae, inclusi numerosi ortaggi.'; 831 } 832 schedaSpecieRow.style.display = 'block'; 833 } 834 function mostraPierisGroup(){ 835 if(!schedaSpecieRow) return; 836 schedaSpecieRow.style.display = 'block'; 837 if(schedaSpecie){ 838 schedaSpecie.style.display = 'none'; 839 } 840 841 let container = document.getElementById('pierisContainer'); 842 if(!container){ 843 container = document.createElement('div'); 844 container.id = 'pierisContainer'; 845 container.className = 'pieris-cards'; 846 schedaSpecieRow.appendChild(container); 847 } 848 container.innerHTML = ` 849 <div class="species-card"> 850 <div class="help"> 851 <strong>Pieris sp.</strong> 852 <span style="font-style:italic">(specie non precisata)</span> 853 </div> 854 <div class="species-body"> 855 <div class="species-images"> 856 <div class="img-block"> 857 <img src="species/pieris_sp.jpg" alt="Pieris sp."> 858 </div> 859 </div> 860 <div class="species-text"> 861 <p class="help"> 862 Hai avvistato una farfalla bianca e la foto non è nitida da permetterti l'identificazione? Scegli: Pieris sp. 863 </p> 864 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Pieris sp.')"> 865 Seleziona specie 866 </button> 867 </div> 868 </div> 869 </div> 870 871 <div class="species-card"> 872 <div class="help"> 873 <strong>Cavolaia maggiore</strong> 874 <span style="font-style:italic">(Pieris brassicae)</span> 875 </div> 876 <div class="species-body"> 877 <div class="species-images"> 878 <div class="img-block"> 879 <img src="species/pieris_brassicae_aperte.jpg" alt="Pieris brassicae - ali aperte"> 880 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 881 </div> 882 <div class="img-block"> 883 <img src="species/pieris_brassicae_chiuse.jpg" alt="Pieris brassicae - ali chiuse"> 884 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal;
884 color: #777; margin-top: 2px;"></div></div> 885 </div> 886 </div> 887 <div class="species-text"> 888 ${renderPhenologyGrid('Pieris brassicae')} 889 <p class="help"> 890 <strong>Come riconoscerla</strong><br>Ali bianche con macchie nere sul bordo e una macchia nera più grande sul lato anteriore.<br><br><strong>Habitat</strong><br> 891 Frequente in orti, giardini e campi coltivati. 892 Le larve si nutrono avidamente di cavoli e altre crucifere. 893 à una farfalla molto mobile, capace di formare grandi popolazioni. 894 </p> 895 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Pieris brassicae')"> 896 Seleziona specie 897 </button> 898 </div> 899 </div> 900 </div> 901 902 <div class="species-card"> 903 <div class="help"> 904 <strong>Cavolaia verdastra</strong> 905 <span style="font-style:italic">(Pieris napi)</span> 906 </div> 907 <div class="species-body"> 908 <div class="species-images"> 909 <div class="img-block"> 910 <img src="species/pieris_napi_aperte.jpg" alt="Pieris napi - ali aperte"> 911 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 912 </div> 913 <div class="img-block"> 914 <img src="species/pieris_napi_chiuse.jpg" alt="Pieris napi - ali chiuse"> 915 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 916 </div> 917 </div> 918 <div class="species-text"> 919 ${renderPhenologyGrid('Pieris napi')} 920 <p class="help"> 921 <strong>Come riconoscerla</strong><br>Ali bianche con una macchia nera caratteristica sulla parte anteriore delle ali anteriori.<br><br><strong>Habitat</strong><br> 922 Presente in prati freschi, boschi radi e zone umide. 923 Si riconosce per le venature verdastre sulle ali chiuse. 924 Le larve vivono su numerose crucifere spontanee. 925 </p> 926 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Pieris napi')"> 927 Seleziona specie 928 </button> 929 </div> 930 </div> 931 </div> 932 933 <div class="species-card"> 934 <div class="help"> 935 <strong>Cavolaia minore</strong> 936 <span style="font-style:italic">(Pieris rapae)</span> 937 </div> 938 <div class="species-body"> 939 <div class="species-images"> 940 <div class="img-block"> 941 <img src="species/pieris_rapae_aperte.jpg" alt="Pieris rapae - ali aperte"> 942 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 943 </div> 944 <div class="img-block"> 945 <img src="species/pieris_rapae_chiuse.jpg" alt="Pieris rapae - ali chiuse"> 946 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 947 </div> 948 </div> 949 <div class="species-text"> 950 ${renderPhenologyGrid('Pieris rapae')} 951 <p class="help"> 952 <strong>Come riconoscerla</strong><br>Ali bianche con piccole macchie nere sulle ali anteriori. Più piccola della cavolaia maggiore.<br><br><strong>Habitat</strong><br> 953 Specie molto comune in ambienti urbani e agricoli. 954 Visita spesso i fiori nei giardini e nei parchi. 955 Le larve si nutrono di diverse Brassicaceae coltivate. 956 </p> 957 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Pieris rapae')"> 958 Seleziona specie 959 </button> 960 </div> 961 </div> 962 </div> 963 `; 964 } 965 966function mostraNeroArancioBiancoGroup(){ 967 if(!schedaSpecieRow) return; 968 schedaSpecieRow.style.display = 'block'; 969 if(schedaSpecie){ 970 schedaSpecie.style.display = 'none'; 971 } 972
973 let container = document.getElementById('neroArancioBiancoContainer'); 974 if(!container){ 975 container = document.createElement('div'); 976 container.id = 'neroArancioBiancoContainer'; 977 container.className = 'pieris-cards'; 978 schedaSpecieRow.appendChild(container); 979 } 980 981 container.innerHTML = ` 982 <div class="species-card"> 983 <div class="help"> 984 <strong>Atalanta</strong> 985 <span style="font-style:italic">(Vanessa atalanta)</span> 986 </div> 987 <div class="species-body"> 988 <div class="species-images"> 989 <div class="img-block"> 990 <img src="species/vanessa atalanta ali aperte.jpg" alt="Vanessa atalanta - ali aperte"> 991 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 992 </div> 993 <div class="img-block"> 994 <img src="species/vanessa atalanta ali chiuse.jpg" alt="Vanessa atalanta - ali chiuse"> 995 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 996 </div> 997 </div> 998 <div class="species-text"> 999 ${renderPhenologyGrid('Vanessa atalanta')} 1000 <p class="help"> 1001 <strong>Come riconoscerla</strong><br>Ali nere con strisce rosse e macchie bianche sulle punte delle ali anteriori.<br><br><strong>Habitat</strong><br> 1002 Specie comune in giardini, parchi, radure e margini di bosco. 1003 </p> 1004 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Vanessa Atalanta')"> 1005 Seleziona specie 1006 </button> 1007 </div> 1008 </div> 1009 </div> 1010 1011 <div class="species-card"> 1012 <div class="help"> 1013 <strong>Sfinge del galio</strong> 1014 <span style="font-style:italic">(Macroglossum stellatarum)</span> 1015 </div> 1016 <div class="species-body"> 1017 <div class="species-images"> 1018 <div class="img-block"> 1019 <img src="species/Sfinge del galio (Macroglossum stellatarum) ali aperte.jpg" alt="Sfinge del galio - ali aperte"> 1020 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1021 </div> 1022 <div class="img-block"> 1023 <img src="species/Sfinge del galio (Macroglossum stellatarum).jpg" alt="Sfinge del galio - foto"> 1024 <div class="caption"><div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1025 </div> 1026 </div> 1027 <div class="species-text"> 1028 ${renderPhenologyGrid('Macroglossum stellatarum')} 1029 <p class="help"> 1030 <strong>Falena diurna</strong><br>à una falena attiva di giorno: spesso resta ferma in volo davanti ai fiori mentre succhia il nettare, come un âcolibrìâ.<br><br> 1031 <strong>Come riconoscerla</strong><br>Corpo tozzo grigio-bruno, ali anteriori brune, ali posteriori aranciate. Volo velocissimo e ronzante.<br><br> 1032 <strong>Habitat</strong><br>Giardini, balconi, parchi e aree fiorite. 1033 </p> 1034 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Macroglossum stellatarum')"> 1035 Seleziona specie 1036 </button> 1037 </div> 1038 </div> 1039 </div> 1040 `; 1041} 1042 1043 1044function mostraMarroneBiancoGroup(){ 1045 if(!schedaSpecieRow) return; 1046 schedaSpecieRow.style.display = 'block'; 1047 if(schedaSpecie){ 1048 schedaSpecie.style.display = 'none'; 1049 } 1050 1051 let container = document.getElementById('marroneBiancoContainer'); 1052 if(!container){ 1053 container = document.createElement('div'); 1054 container.id = 'marroneBiancoContainer'; 1055 container.className = 'pieris-cards'; 1056 schedaSpecieRow.appendChild(container); 1057 } 1058 1059 container.innerHTML = ` 1060 <div class="species-card"> 1061 <div class="help"> 1062 <strong>Licenide dei gerani</strong>
1063 <span style="font-style:italic">(Cacyreus marshalli)</span> 1064 </div> 1065 <div class="species-body"> 1066 <div class="species-images"> 1067 <div class="img-block"> 1068 <img src="species/licenide_gerani_aperte.jpg" alt="Licenide dei gerani - ali aperte"> 1069 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1070 </div> 1071 <div class="img-block"> 1072 <img src="species/licenide_gerani_chiuse.jpg" alt="Licenide dei gerani - ali chiuse"> 1073 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1074 </div> 1075 </div> 1076 <div class="species-text"> 1077 ${renderPhenologyGrid('Cacyreus marshalli')} 1078 <p class="help"> 1079 <strong>Come riconoscerla</strong><br>Piccola, ali superiori marroni, inferiori con complessi disegni a linee ondulate bianche e beige. Coda corta sulle ali posteriori.<br><br><strong>Habitat</strong><br> 1080 Farfalla di origine africana. 1081 Le larve scavano gallerie nei fusti e nei boccioli dei gerani coltivati. 1082 Gli adulti volano rapidamente attorno alle piante ospiti nei mesi caldi. 1083 </p> 1084 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Licenide dei Gerani')"> 1085 Seleziona specie 1086 </button> 1087 </div> 1088 </div> 1089 </div> 1090 1091 <div class="species-card"> 1092 <div class="help"> 1093 <strong>Esperide della Malva</strong> 1094 <span style="font-style:italic">(Pyrgus malvoides)</span> 1095 </div> 1096 <div class="species-body"> 1097 <div class="species-images"> 1098 <div class="img-block"> 1099 <img src="species/Pyrgus_malvoides_aperte.jpg" alt="Pyrgus malvoides - ali aperte"> 1100 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1101 </div> 1102 <div class="img-block"> 1103 <img src="species/Pyrgus_malvoides_chiuse.jpg" alt="Pyrgus malvoides - ali chiuse"> 1104 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1105 </div> 1106 </div> 1107 <div class="species-text"> 1108 ${renderPhenologyGrid('Pyrgus malvoides')} 1109 <p class="help"> 1110 <strong>Come riconoscerla</strong><br>Marrone scuro con piccole macchie bianche quadrangolari ben definite (a scacchiera).<br><br><strong>Habitat</strong><br> 1111 Frequenta radure, prati aridi e margini di sentiero soleggiati. 1112 Vola raso terra con volo rapido, posandosi spesso su pietre o fiori bassi. 1113 </p> 1114 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Pyrgus malvoides')"> 1115 Seleziona specie 1116 </button> 1117 </div> 1118 </div> 1119 </div> 1120 `; 1121} 1122 1123function mostraGialloNeroGroup(){ 1124 if(!schedaSpecieRow) return; 1125 schedaSpecieRow.style.display = 'block'; 1126 if(schedaSpecie){ 1127 schedaSpecie.style.display = 'none'; 1128 } 1129 1130 let container = document.getElementById('gialloNeroContainer'); 1131 if(!container){ 1132 container = document.createElement('div'); 1133 container.id = 'gialloNeroContainer'; 1134 container.className = 'pieris-cards'; 1135 schedaSpecieRow.appendChild(container); 1136 } 1137 1138 container.innerHTML = ` 1139 <div class="species-card"> 1140 <div class="help"> 1141 <strong>Podalirio</strong> 1142 <span style="font-style:italic">(Iphiclides podalirius)</span> 1143 </div> 1144 <div class="species-body"> 1145 <div class="species-images"> 1146 <div class="img-block"> 1147 <img src="species/podalirio ali aperte.jpg" alt="Podalirio - ali aperte"> 1148 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal;
1148 color: #777; margin-top: 2px;"></div></div> 1149 </div> 1150 <div class="img-block"> 1151 <img src="species/podalirio ali chiuse.jpg" alt="Podalirio - ali chiuse"> 1152 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1153 </div> 1154 </div> 1155 <div class="species-text"> 1156 ${renderPhenologyGrid('Iphiclides podalirius')} 1157 <p class="help"> 1158 <strong>Come riconoscerla</strong><br>Ali bianche con strisce nere orizzontali, due macchie blu sulle ali posteriori, e un tratto di linee eleganti.<br><br><strong>Habitat</strong><br> 1159 Tipica di zone collinari, frutteti e ambienti caldi. 1160 Riconoscibile dalle lunghe codine e dalle bande scure sulle ali. 1161 Le larve vivono su prugnolo, peri e altre rosaceae. 1162 </p> 1163 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Podalirio')"> 1164 Seleziona specie 1165 </button> 1166 </div> 1167 </div> 1168 </div> 1169 1170 <div class="species-card"> 1171 <div class="help"> 1172 <strong>Macaone</strong> 1173 <span style="font-style:italic">(Papilio machaon)</span> 1174 </div> 1175 <div class="species-body"> 1176 <div class="species-images"> 1177 <div class="img-block"> 1178 <img src="species/macaone_ali aperte.jpg" alt="Macaone - ali aperte"> 1179 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1180 </div> 1181 <div class="img-block"> 1182 <img src="species/macaone ali chiuse.jpg" alt="Macaone - ali chiuse"> 1183 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1184 </div> 1185 </div> 1186 <div class="species-text"> 1187 ${renderPhenologyGrid('Papilio machaon')} 1188 <p class="help"> 1189 <strong>Come riconoscerla</strong><br>Ali gialle con strisce nere, bordo esterno rosso e azzurro con grandi "code" sulle ali posteriori.<br><br><strong>Habitat</strong><br> 1190 Farfalla vistosa e diffusa nei prati aperti e soleggiati. 1191 Le larve verdi con anelli neri vivono su finocchio e carota. 1192 Gli adulti visitano grandi fiori ricchi di nettare. 1193 </p> 1194 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Macaone')"> 1195 Seleziona specie 1196 </button> 1197 </div> 1198 </div> 1199 </div> 1200 1201 <div class="species-card"> 1202 <div class="help"> 1203 <strong>Crocea</strong> 1204 <span style="font-style:italic">(Colias crocea)</span> 1205 </div> 1206 <div class="species-body"> 1207 <div class="species-images"> 1208 <div class="img-block"> 1209 <img src="species/crocea_aperte.jpg" alt="Colias crocea - ali aperte"> 1210 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1211 </div> 1212 <div class="img-block"> 1213 <img src="species/crocea_chiuse.jpg" alt="Colias crocea - ali chiuse"> 1214 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1215 </div> 1216 </div> 1217 <div class="species-text"> 1218 ${renderPhenologyGrid('Colias crocea')} 1219 <p class="help"> 1220 <strong>Come riconoscerla</strong><br>Ali giallo-arancio brillante con margine nero sulle ali anteriori, tipica della famiglia delle colie.<br><br><strong>Habitat</strong><br> 1221 Specie migratrice molto mobile, comune in campi e pascoli. 1222 Le ali aranciate con bordo scuro sono ben visibili in volo. 1223 Le larve si nutrono di diverse leguminose spontanee. 1224 </p> 1225 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Crocea')"> 1226 Seleziona specie 1227 </button> 1228 </div> 1229 </div> 1230 </div> 1231 `; 1232 } 1233 1234function mostraArancioNeroGroup(){ 1235 if(!schedaSpecieRow) return; 1236 schedaSpecieRow.style.display = 'block'; 1237 if(schedaSpecie){ 1238 schedaSpecie.style.display = 'none'; 1239 } 1240 let container = document.getElementById('arancioNeroContainer'); 1241 if(!container){ 1242 container = document.createElement('div'); 1243 container.id = 'arancioNeroContainer'; 1244 container.className = 'pieris-cards'; 1245 schedaSpecieRow.appendChild(container); 1246 } 1247 container.innerHTML = ` 1248 <div class="species-card"> 1249 <div class="help"> 1250 <strong>Latonia</strong>
1251 <span style="font-style:italic">(Issoria lathonia)</span> 1252 </div> 1253 <div class="species-body"> 1254 <div class="species-images"> 1255 <div class="img-block"> 1256 <img src="species/Latonia ali aperte.jpg" alt="Latonia - ali aperte"> 1257 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1258 </div> 1259 </div> 1260 <div class="species-text"> 1261 ${renderPhenologyGrid('Issoria lathonia')} 1262 <p class="help"> 1263 <strong>Come riconoscerla</strong><br>Arancio con grandi macchie nere rotondeggianti. Ali inferiori con grandi macchie argentate (madreperlacee).<br><br><strong>Habitat</strong><br> 1264 Comune in prati fioriti e campi incolti. 1265 Gli adulti volano rapidamente e visitano spesso i fiori a fine estate. 1266 Le larve si nutrono di viole e altre piante erbacee. 1267 </p> 1268 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Latonia')"> 1269 Seleziona specie 1270 </button> 1271 </div> 1272 </div> 1273 </div> 1274 `; 1275} 1276 1277function mostraArancioneMarroneGroup(){ 1278 if(!schedaSpecieRow) return; 1279 schedaSpecieRow.style.display = 'block'; 1280 if(schedaSpecie){ 1281 schedaSpecie.style.display = 'none'; 1282 } 1283 1284 let container = document.getElementById('arancioneMarroneContainer'); 1285 if(!container){ 1286 container = document.createElement('div'); 1287 container.id = 'arancioneMarroneContainer'; 1288 container.className = 'pieris-cards'; 1289 schedaSpecieRow.appendChild(container); 1290 } 1291 1292 container.innerHTML = ` 1293 <div class="species-card"> 1294 <div class="help"> 1295 <strong>Virgola</strong> 1296 <span style="font-style:italic">(Polygonia c-album)</span> 1297 </div> 1298 <div class="species-body"> 1299 <div class="species-images"> 1300 <div class="img-block"> 1301 <img src="species/Virgola ali aperte.jpg" alt="Virgola - ali aperte"> 1302 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1303 </div> 1304 <div class="img-block"> 1305 <img src="species/virgola ali chiuse.jpg" alt="Virgola - ali chiuse"> 1306 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1307 </div> 1308 </div> 1309 <div class="species-text"> 1310 ${renderPhenologyGrid('Polygonia c-album')} 1311 <p class="help"> 1312 <strong>Come riconoscerla</strong><br>Ali marroni con forma irregolare e un segno bianco a "C" sulle ali posteriori.<br><br><strong>Habitat</strong><br> 1313 Specie comune in radure, margini di bosco e siepi. 1314 Le ali dentellate e il disegno arancio-marrone la rendono inconfondibile. 1315 Le larve si sviluppano su olmo, ortica e ribes. 1316 </p> 1317 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Virgola')"> 1318 Seleziona specie 1319 </button> 1320 </div> 1321 </div> 1322 </div> 1323 1324 <div class="species-card"> 1325 <div class="help"> 1326 <strong>Megera</strong> 1327 <span style="font-style:italic">(Lasiommata megera)</span> 1328 </div> 1329 <div class="species-body"> 1330 <div class="species-images"> 1331 <div class="img-block"> 1332 <img src="species/Lasiommata megera_ali aperte.jpg" alt="Lasiommata megera - ali aperte"> 1333 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1334 </div> 1335 <div class="img-block"> 1336 <img src="species/Lasiommata megera_ali chiuse.jpg" alt="Lasiommata megera - ali chiuse"> 1337 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal;
1337 color: #777; margin-top: 2px;"></div></div> 1338 </div> 1339 </div> 1340 <div class="species-text"> 1341 ${renderPhenologyGrid('Lasiommata megera')} 1342 <p class="help"> 1343 <strong>Come riconoscerla</strong><br>Ali marroni con una serie di occhi tondi bianchi, caratteristiche macchie arancioni sull'ala inferiore.<br><br><strong>Habitat</strong><br> 1344 Frequente in zone erbose, scarpate stradali e aree rocciose. 1345 Ama posarsi al suolo o su pietre al sole con le ali aperte. 1346 Le larve si nutrono di diverse graminacee spontanee. 1347 </p> 1348 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Lasiommata megera')"> 1349 Seleziona specie 1350 </button> 1351 </div> 1352 </div> 1353 </div> 1354 1355 <div class="species-card"> 1356 <div class="help"> 1357 <strong>Rame comune</strong> 1358 <span style="font-style:italic">(Lycaena phlaeas)</span> 1359 </div> 1360 <div class="species-body"> 1361 <div class="species-images"> 1362 <div class="img-block"> 1363 <img src="species/Lycaena phlaeas_ali aperte.jpg" alt="Lycaena phlaeas - ali aperte"> 1364 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1365 </div> 1366 <div class="img-block"> 1367 <img src="species/Lycaena phlaeas_ali chiuse.jpg" alt="Lycaena phlaeas - ali chiuse"> 1368 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1369 </div> 1370 </div> 1371 <div class="species-text"> 1372 ${renderPhenologyGrid('Lycaena phlaeas')} 1373 <p class="help"> 1374 <strong>Come riconoscerla</strong><br>Piccola farfalla arancio-rame brillante con macchie nere. Ali posteriori marroni con fascia arancione.<br><br><strong>Habitat</strong><br> 1375 Comune in prati e bordi di sentiero. 1376 Gli adulti volano bassi tra le erbe in ambienti aperti e soleggiati. 1377 Le larve si nutrono di romici e altre poligonacee. 1378 </p> 1379 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Lycena phaleas')"> 1380 Seleziona specie 1381 </button> 1382 </div> 1383 </div> 1384 </div> 1385 1386 <div class="species-card"> 1387 <div class="help"> 1388 <strong>Farfalla dei prati</strong> 1389 <span style="font-style:italic">(Coenonympha pamphilus)</span> 1390 </div> 1391 <div class="species-body"> 1392 <div class="species-images"> 1393 <div class="img-block"> 1394 <img src="species/Coenonympha pamphilus ali aperte.jpg" alt="Coenonympha pamphilus - ali aperte"> 1395 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1396 </div> 1397 <div class="img-block"> 1398 <img src="species/Coenonympha pamphilus _ali chiuse_jpg.jpg" alt="Coenonympha pamphilus - ali chiuse"> 1399 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1400 </div> 1401 </div> 1402 <div class="species-text"> 1403 ${renderPhenologyGrid('Coenonympha pamphilus')} 1404 <p class="help"> 1405 <strong>Come riconoscerla</strong><br>Ali color marrone chiaro con un occhio rosso scuro sul bordo delle ali anteriori e posteriori.<br><br><strong>Habitat</strong><br> 1406 Molto frequente in prati, pascoli e margini erbosi. 1407 Vola bassa tra le erbe e spesso tiene le ali chiuse a riposo. 1408 Le larve si nutrono di vari tipi di graminacee. 1409 </p> 1410 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Coenonympha pamphilius')"> 1411 Seleziona specie 1412 </button> 1413 </div> 1414 </div> 1415 </div> 1416 `; 1417 } 1418 1419 1420 1421 1422 1423 1424 1425 1426 function mostraPontiaEdusa(){ 1427 if(!schedaSpecieRow) return; 1428 resetMultiSpeciesView(); 1429 currentSpecieValue = 'Pieris edusa'; 1430 nomeComuneSpecie.textContent = 'Edusa '; 1431 nomeScientificoSpecie.textContent = '(Pieris edusa)'; 1432 if(fotoSpecieAperta){ 1433 fotoSpecieAperta.src = resolveSpeciesImg('species/pontia_edusa_aperte.jpg'); 1434 fotoSpecieAperta.alt = 'Pieris edusa - ali aperte'; 1435 } 1436 if(fotoSpecieChiusa){ 1437 fotoSpecieChiusa.src = resolveSpeciesImg('species/pontia_edusa_chiu
1437se.jpg'); 1438 fotoSpecieChiusa.alt = 'Pieris edusa - ali chiuse'; 1439 } 1440 if(testoAmbienteSpecie){ 1441 const phenologyContainer = document.getElementById('phenologyContainer'); 1442 if(phenologyContainer){ 1443 phenologyContainer.innerHTML = renderPhenologyGrid('Pontia edusa'); 1444 } 1445 testoAmbienteSpecie.innerHTML = 1446 '<strong>Come riconoscerla</strong><br>Ali bianche con macchie verdi e bordi neri, più scura rispetto alle cavolaie.<br><br><strong>Habitat</strong><br>' + 1447 'Frequente in ambienti aridi, prati aperti e terreni incolti. ' + 1448 'Le ali chiuse mostrano un caratteristico disegno marmorizzato verdastro. ' + 1449 'Le larve si nutrono di piante crucifere spontanee in più generazioni annue.'; 1450 } 1451 schedaSpecieRow.style.display = 'block'; 1452 } 1453 1454 function mostraGonepteryxRhamni(){ 1455 if(!schedaSpecieRow) return; 1456 resetMultiSpeciesView(); 1457 currentSpecieValue = 'Gonepteryx rhamni'; 1458 nomeComuneSpecie.textContent = 'Cedronella '; 1459 nomeScientificoSpecie.textContent = '(Gonepteryx rhamni)'; 1460 if(fotoSpecieAperta){ 1461 fotoSpecieAperta.src = resolveSpeciesImg('species/gonepteryx_rhamni_aperte.jpg'); 1462 fotoSpecieAperta.alt = 'Gonepteryx rhamni - ali aperte'; 1463 } 1464 if(fotoSpecieChiusa){ 1465 fotoSpecieChiusa.src = resolveSpeciesImg('species/Gonepteryx rhamni_ali chiuse.jpg'); 1466 fotoSpecieChiusa.alt = 'Gonepteryx rhamni - ali chiuse'; 1467 } 1468 if(testoAmbienteSpecie){ 1469 const phenologyContainer = document.getElementById('phenologyContainer'); 1470 if(phenologyContainer){ 1471 phenologyContainer.innerHTML = renderPhenologyGrid('Gonepteryx rhamni'); 1472 } 1473 testoAmbienteSpecie.innerHTML = 1474 '<strong>Come riconoscerla</strong><br>Ali gialle con una venatura grigia chiara, caratteristico segno a "V" scuro sulle ali anteriori. Tipicamente osservabile in primavera.<br><br><strong>Habitat</strong><br>' + 1475 'Specie tipica di ambienti aperti, margini di bosco e siepi. ' + 1476 'Facilmente riconoscibile per le ali giallo-verdi con contorno a forma di foglia. ' + 1477 'à una delle farfalle più longeve e può svernare come adulto tra l\'edera.'; 1478 } 1479 schedaSpecieRow.style.display = 'block'; 1480 } 1481 1482 1483 function mostraVanessaAtalanta(){ 1484 if(!schedaSpecieRow) return; 1485 resetMultiSpeciesView(); 1486 currentSpecieValue = 'Vanessa Atalanta'; 1487 nomeComuneSpecie.textContent = 'Atalanta'; 1488 nomeScientificoSpecie.textContent = '(Vanessa atalanta)'; 1489 if(fotoSpecieAperta){ 1490 fotoSpecieAperta.src = resolveSpeciesImg('species/vanessa atalanta ali aperte.jpg'); 1491 fotoSpecieAperta.alt = 'Vanessa atalanta - ali aperte'; 1492 } 1493 if(fotoSpecieChiusa){ 1494 fotoSpecieChiusa.src = resolveSpeciesImg('species/vanessa atalanta ali chiu
1494se.jpg'); 1495 fotoSpecieChiusa.alt = 'Vanessa atalanta - ali chiuse'; 1496 } 1497 if(testoAmbienteSpecie){ 1498 const phenologyContainer = document.getElementById('phenologyContainer'); 1499 if(phenologyContainer){ 1500 phenologyContainer.innerHTML = renderPhenologyGrid('Vanessa atalanta'); 1501 } 1502 testoAmbienteSpecie.innerHTML = 1503 '<strong>Come riconoscerla</strong><br>Ali nere con strisce rosse orizzontali e bordi bianchi, distintive per l\'aspetto vivace.<br><br><strong>Habitat</strong><br>' + 1504 'Farfalla migratrice molto comune che frequenta giardini, parchi, radure e margini boschivi. ' + 1505 'Le larve si nutrono soprattutto di ortiche in luoghi freschi e umidi. ' + 1506 'Gli adulti visitano spesso i fiori ricchi di nettare dalla primavera all\'autunno.'; 1507 } 1508 schedaSpecieRow.style.display = 'block'; 1509 } 1510 1511function nascondiSchedaSpecie(){ 1512 if(!schedaSpecieRow) return; 1513 schedaSpecieRow.style.display = 'none'; 1514 resetMultiSpeciesView(); 1515 } 1516 1517 if(colorButtons && colorButtons.length){ 1518 colorButtons.forEach(btn=>{ 1519 btn.addEventListener('click', ()=>{ 1520 const colore = btn.getAttribute('data-color'); 1521 if(hiddenColorInput){ 1522 hiddenColorInput.value = colore; 1523 } 1524 // evidenzia selezionato 1525 colorButtons.forEach(b => b.classList.remove('selected')); 1526 btn.classList.add('selected'); 1527 1528 if(typeof resetMultiSpeciesView === 'function'){ 1529 resetMultiSpeciesView(); 1530 } 1531 1532 if(colore === 'ROSSO/MARRONE'){ 1533 mostraOcchioDiPavone(); 1534 } else if(colore === 'MARRONE/NERO'){ 1535 mostraCarcharodusAlceae(); 1536 } else if(colore === 'NERO'){ mostraOcchioDiPavone(); } else if(colore === 'NERO/ARANCIO/BIANCO'){ 1537 mostraNeroArancioBiancoGroup(); 1538 } else if(colore === 'AZZURRO'){ 1539 mostraIcaro(); 1540 } else if(colore === 'BIANCO' || colore === 'BIANCO/NERO'){ 1541 mostraPierisGroup(); 1542 } else if(colore === 'GIALLO'){ 1543 mostraGonepteryxRhamni(); 1544 } else if(colore === 'GIALLO/NERO'){ 1545 mostraGialloNeroGroup(); 1546 } else if(colore === 'ARANCIO/NERO'){ 1547 mostraArancioNeroGroup(); 1548 } else if(colore === 'ARANCIONE/MARRONE'){ 1549 mostraArancioneMarroneGroup(); 1550 } else if(colore === 'MARRONE/BIANCO'){ 1551 mostraMarroneBiancoGroup(); 1552 } else if(colore === 'ARANCIO/MARRONE/BIANCO'){ 1553 mostraArancioMarroneBiancoGroup(); 1554 } else if(colore === 'BIANCO/NERO/VERDE'){ 1555 mostraPontiaEdusa(); 1556 } else { 1557 nascondiSchedaSpecie(); 1558 } 1559 }); 1560 }); 1561 } 1562 1563 1564 // ===== Pulsante "Seleziona specie" per scheda singola ===== 1565 const selezionaSpecieBtn = document.querySelector('.species-select-btn'); 1566 if (selezionaSpecieBtn && selectSpecie) { 1567 selezionaSpecieBtn.addEventListener('click', () => { 1568 if (!currentSpecieValue) return; 1569 1570 const conosciSi = document.querySelector('input[name="conosciSpecieLocal"][value="si"]'); 1571 if (conosciSi) { 1572 conosciSi.checked = true; 1573 if (typeof updateBlocks === 'function') { 1574 updateBlocks(); 1575 } 1576 } 1577 1578 setNomeSpecieValue(currentSpecieValue); 1579 1580 if (schedaSpecieRow) { 1581 schedaSpecieRow.style.display = 'none'; 1582 } 1583 }); 1584 } 1585 1586 // Funzione globale per la selezione dalle schede multiple (es. Pieris) 1587 window.scegliSpecie = function(specName){ 1588 if(!selectSpecie) return; 1589 const conosciSi = document.querySelector('input[name="conosciSpecieLocal"][value="si"]'); 1590 if(conosciSi){ 1591 conosciSi.checked = true; 1592 if(typeof updateBlocks === 'function'){ 1593 updateBlocks(); 1594 } 1595 } 1596 setNomeSpecieValue(specName); 1597 if(schedaSpecieRow){ 1598 schedaSpecieRow.style.display = 'none'; 1599 } 1600 }; 1601 1602 1603 1604 1605function mostraVanessaDelCardo(){ 1606 if(!schedaSpecieRow) return; 1607 resetMultiSpeciesView(); 1608 currentSpecieValue = 'Vanessa del Cardo'; 1609 nomeComuneSpecie.textContent = 'Vanessa del cardo '; 1610 nomeScientificoSpecie.textContent = '(Vanessa cardui)'; 1611 if(fotoSpecieAperta){ 1612 fotoSpecieAperta.src = resolveSpeciesImg('species/vanessa_del_cardo_aperte.jpg'); 1613 fotoSpecieAperta.alt = 'Vanessa del cardo - ali aperte'; 1614 } 1615 if(fotoSpecieChiusa){ 1616 fotoSpecieChiusa.src = resolveSpeciesImg('species/vanessa_del_cardo_chiuse.jpg');
1617 fotoSpecieChiusa.alt = 'Vanessa del cardo - ali chiuse'; 1618 } 1619 if(testoAmbienteSpecie){ 1620 const phenologyContainer = document.getElementById('phenologyContainer'); 1621 if(phenologyContainer){ 1622 phenologyContainer.innerHTML = renderPhenologyGrid('Vanessa cardui'); 1623 } 1624 testoAmbienteSpecie.innerHTML = 1625 '<strong>Come riconoscerla</strong><br>Ali con arancione, nero e bianco, macchie nere sulle ali anteriori, bordi chiari.<br><br><strong>Habitat</strong><br>' + 1626 'Specie migratrice molto comune in prati fioriti, giardini, coltivi e aree urbane. ' + 1627 'Gli adulti visitano spesso i fiori ricchi di nettare e possono essere osservati in volo per lunghi periodi. ' + 1628 'Le larve si nutrono soprattutto di cardi, ortiche e altre piante erbacee spontanee.'; 1629 } 1630 schedaSpecieRow.style.display='block'; 1631} 1632 1633function mostraArancioMarroneBiancoGroup(){ 1634 if(!schedaSpecieRow) return; 1635 schedaSpecieRow.style.display = 'block'; 1636 if(schedaSpecie){ 1637 schedaSpecie.style.display = 'none'; 1638 } 1639 1640 let container = document.getElementById('arancioMarroneBiancoContainer'); 1641 if(!container){ 1642 container = document.createElement('div'); 1643 container.id = 'arancioMarroneBiancoContainer'; 1644 container.className = 'pieris-cards'; 1645 schedaSpecieRow.appendChild(container); 1646 } 1647 1648 container.innerHTML = ` 1649 <div class="species-card"> 1650 <div class="help"> 1651 <strong>Vanessa del cardo</strong> 1652 <span style="font-style:italic">(Vanessa cardui)</span> 1653 </div> 1654 <div class="species-body"> 1655 <div class="species-images"> 1656 <div class="img-block"> 1657 <img src="species/vanessa_del_cardo_aperte.jpg" alt="Vanessa del cardo - ali aperte"> 1658 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1659 </div> 1660 <div class="img-block"> 1661 <img src="species/vanessa_del_cardo_chiuse.jpg" alt="Vanessa del cardo - ali chiuse"> 1662 <div class="caption">Ali chiuse<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1663 </div> 1664 </div> 1665 <div class="species-text"> 1666 ${renderPhenologyGrid('Vanessa cardui')} 1667 <p class="help"> 1668 <strong>Come riconoscerla</strong><br>Ali con arancione, nero e bianco, macchie nere sulle ali anteriori, bordi chiari.<br><br><strong>Habitat</strong><br> 1669 Specie migratrice molto comune in prati fioriti, giardini, coltivi e aree urbane. 1670 </p> 1671 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Vanessa del Cardo')"> 1672 Seleziona specie 1673 </button> 1674 </div> 1675 </div> 1676 </div> 1677 1678 <div class="species-card"> 1679 <div class="help"> 1680 <strong>Sfinge del galio</strong> 1681 <span style="font-style:italic">(Macroglossum stellatarum)</span> 1682 </div> 1683 <div class="species-body"> 1684 <div class="species-images"> 1685 <div class="img-block"> 1686 <img src="species/Sfinge del galio (Macroglossum stellatarum) ali aperte.jpg" alt="Sfinge del galio - ali aperte"> 1687 <div class="caption">Ali aperte<div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1688 </div> 1689 <div class="img-block"> 1690 <img src="species/Sfinge del galio (Macroglossum stellatarum).jpg" alt="Sfinge del galio - foto"> 1691 <div class="caption"><div class="photo-credit" style="font-size: 0.75em; font-weight: normal; color: #777; margin-top: 2px;"></div></div> 1692 </div> 1693 </div> 1694 <div class="species-text"> 1695 ${renderPhenologyGrid('Macroglossum stellatarum')} 1696 <p class="help"> 1697 <strong>Falena diurna</strong><br>à una falena attiva di giorno: spesso resta ferma in volo davanti ai fiori mentre succhia il nettare, come un âcolibrìâ.<br><br> 1698 <strong>Come riconoscerla</strong><br>Corpo tozzo grigio-bruno, ali anteriori brune, ali posteriori aranciate. Volo velocissimo e ronzante. La lunga spiritromba è ben visibile quando si alimenta.<br><br> 1699 <strong>Habitat</strong><br>Giardini, balconi, parchi, bordi strada e aree fiorite. Frequenta spesso fiori ricchi di nettare e può comparire anche in ambiente urbano.<br><br> 1700 <strong>Periodo di volo</strong><br>Da primavera ad autunno, con più generazioni; nelle giornate miti può comparire anche più tardi nella stagione. 1701 </p> 1702 <button type="button" class="btn species-select-btn" onclick="scegliSpecie('Macroglossum stellatarum')"> 1703 Seleziona specie 1704 </button> 1705 </div> 1706 </div> 1707 </div> 1708 `; 1709} 1710 }); // End of DOMContentLoaded wrap 1711 1712// Expose helpers globally 1713window.convertFile = convertFile; 1714window.compressImageIfNeeded = compressImageIfNeeded; 1715 1716})(); // End of first IIFE 1717 1718 1719// =============================================== 1720// LIGHTBOX - Funzioni e Listener (Copia da Cartella 2) 1721// =============================================== 1722 1723// Funzione per aprire la lightbox (resa globale per essere chiamata da HTML) 1724window.openLightbox = function (src) { 1725 const lightbox = document.getElementById("lightbox"); 1726 const img = document.getElementById("lightboxImage"); 1727 if (!lightbox || !img) return; 1728 const s = String(src || '').trim(); 1729 if (s.startsWith('/assets/species/')) { 1730 img.src = s; 1731 } else if (s.startsWith('species/')) { 1732 const file = s.slice('species/'.length); 1733 const remap = { 1734 'Carcharodus alceae ali aperte.jpg': 'Carcharodus alceae ali aperte__farfalle.jpg',
1735 'Gonepteryx rhamni_ali chiuse.jpg': 'Gonepteryx rhamni_ali chiuse__farfalle.jpg', 1736 }; 1737 img.src = '/assets/species/' + encodeURIComponent(remap[file] || file); 1738 } else { 1739 img.src = s; 1740 } 1741 lightbox.style.display = "flex"; // Mostra la lightbox 1742}; 1743 1744// Funzione per chiudere la lightbox (resa globale per essere chiamata da HTML) 1745window.closeLightbox = function () { 1746 const lightbox = document.getElementById("lightbox"); 1747 if (!lightbox) return; 1748 lightbox.style.display = "none"; // Nasconde la lightbox 1749}; 1750 1751// Aggiungi listener per la chiusura tramite ESC e click sullo sfondo 1752document.addEventListener("DOMContentLoaded", () => { 1753 const lightbox = document.getElementById("lightbox"); 1754 if (lightbox) { 1755 // Chiusura con tasto ESC 1756 document.addEventListener("keydown", (ev) => { 1757 if (ev.key === "Escape" && lightbox.style.display === "flex") { 1758 window.closeLightbox(); 1759 } 1760 }); 1761 1762 // Chiusura cliccando sullo sfondo (backdrop) 1763 lightbox.addEventListener("click", (ev) => { 1764 // Chiude solo se il click è avvenuto
1764sul contenitore della lightbox, non sull'immagine 1765 if (ev.target === lightbox) { 1766 window.closeLightbox(); 1767 } 1768 }); 1769 } 1770}); 1771 1772// =============================================== 1773// FILE HANDLING & EXIF (UCCELLI PARITY) 1774// =============================================== 1775let selectedFiles = []; // Global array for selected files 1776 1777const imageInput = document.getElementById('butterflyImage'); 1778const imagePreviewBox = document.getElementById('imagePreviewBox'); 1779const previewContainer = document.getElementById('previewContainer'); 1780const exifWarningBox = document.getElementById('exifWarningBox') || document.getElementById('exif-warning'); 1781 1782// --- HELPER FUNCTIONS --- 1783function readFileAsDataURL(file) { 1784 return new Promise((resolve, reject) => { 1785 const reader = new FileReader(); 1786 reader.onload = (e) => resolve(e.target.result); 1787 reader.onerror = (e) => reject(e); 1788 reader.readAsDataURL(file); 1789 }); 1790} 1791 1792// Inizializzazione input file 1793if(imageInput) { 1794 imageInput.addEventListener('change', function(e) { 1795 handleFiles(this.files); 1796 }); 1797} 1798 1799async function handleFiles(files) { 1800 if(!files || files.length === 0) return; 1801 1802 // Mostra loading 1803 if(previewContainer) { 1804 const loading = document.createElement('div'); 1805 loading.id = 'temp-loading'; 1806 loading.textContent = 'Elaborazione immagini...'; 1807 loading.style.padding = '10px'; 1808 loading.style.fontSize = '12px'; 1809 loading.style.color = '#666'; 1810 previewContainer.appendChild(loading); 1811 } 1812 1813 if(imagePreviewBox) imagePreviewBox.style.display = 'block'; 1814 1815 for (let i = 0; i < files.length; i++) { 1816 let file = files[i]; 1817 1818 try { 1819 // 1. Conversione HEIC 1820 file = await convertFile(file); 1821 1822 // 2. Compressione (solo se immagine) 1823 if (file.type.startsWith('image/')) { 1824 file = await compressImageIfNeeded(file); 1825 } 1826 1827 // 3. Lettura EXIF (Data e GPS) 1828 // Leggiamo EXIF solo dal primo file aggiunto se non abbiamo già dati, 1829 // oppure aggiorniamo se l'utente carica nuove foto. 1830 // Per semplicità , l'ultima foto caricata con EXIF vince (come in Uccelli) 1831 await readExifData(file); 1832 1833 // Aggiungi all'array 1834 selectedFiles.push({ 1835 file: file, 1836 id: Date.now() + i 1837 }); 1838 1839 } catch (err) { 1840 console.error("Errore elaborazione file:", err); 1841 alert("Errore elaborazione file: " + file.name); 1842 } 1843 } 1844 1845 // Rimuovi loading e aggiorna UI 1846 const loading = document.getElementById('temp-loading'); 1847 if(loading) loading.remove(); 1848 1849 syncInputFiles(); // Sync input (opzionale, per compatibilità form standard) 1850 renderPreview(); 1851} 1852 1853async function readExifData(file) { 1854 if (!window.exifr) return; 1855 try { 1856 const toDecimal = (dms, ref) => { 1857 if (!Array.isArray(dms) || dms.length < 3) return null; 1858 const toNum = (v) => { 1859 if (typeof v === "number") return v; 1860 if (v && typeof v === "object") { 1861 if (typeof v.numerator === "number" && typeof v.denominator === "number" && v.denominator) return v.numerator / v.denominator; 1862 if (typeof v.num === "number" && typeof v.den === "number" && v.den) return v.num / v.den; 1863 if (typeof v.valueOf === "function") { 1864 const n = Number(v.valueOf()); 1865 if (Number.isFinite(n)) return n; 1866 } 1867 } 1868 const n = Number(v); 1869 return Number.isFinite(n) ? n : null; 1870 }; 1871 const deg = toNum(dms[0]); 1872 const min = toNum(dms[1]); 1873 const sec = toNum(dms[2]); 1874 if (!Number.isFinite(deg) || !Number.isFinite(min) || !Number.isFinite(sec)) return null; 1875 let dec = deg + min / 60 + sec / 3600; 1876 const r = String(ref || "").toUpperCase(); 1877 if (r === "S" || r === "W") dec = -dec; 1878 return dec; 1879 }; 1880 1881 const extractCoords = (meta) => { 1882 if (!meta || typeof meta !== "object") return null; 1883 if (typeof meta.latitude === "number" && typeof meta.longitude === "number") return { lat: meta.latitude, lng: meta.longitude }; 1884 const lat = toDecimal(meta.GPSLatitude, meta.GPSLatitudeRef); 1885 const lng = toDecimal(meta.GPSLongitude, meta.GPSLongitudeRef); 1886 if (typeof lat === "number" && typeof lng === "number") return { lat, lng }; 1887 if (typeof meta.gpsLatitude === "number" && typeof meta.gpsLongitude === "number") return { lat: meta.gpsLatitude, lng: meta.gpsLongitude }; 1888 return null; 1889 }; 1890 1891 const pickDate = (meta) => 1892 (meta && (meta.DateTimeOriginal || meta.CreateDate || meta.ModifyDate || meta.DateTimeDigitized || meta.dateTimeOriginal || meta.createDate || meta.modifyDate)) || null; 1893 const parseExifDate = (value) => { 1894 if (!value) return null; 1895 if (value instanceof Date) return isNaN(value.getTime()) ? null : value; 1896 const d = new Date(value); 1897 return isNaN(d.getTime()) ? null : d; 1898 }; 1899 1900 const output = await exifr.parse(file, { gps: true, tiff: true }).catch(() => null); 1901 if (!output) return; 1902 1903 const dateVal = pickDate(output); 1904 const d = parseExifDate(dateVal); 1905 if (d) { 1906 const dateField = document.getElementById('dataAvvistamento'); 1907 if (dateField) { 1908 const yyyy = d.getFullYear(); 1909 const mm = String(d.getMonth() + 1).padStart(2, '0'); 1910 const dd = String(d.getDate()).padStart(2, '0'); 1911 dateField.value = `${yyyy}-${mm}-${dd}`; 1912 } 1913 } 1914 1915 let coords = extractCoords(output); 1916 if (!coords && typeof exifr.gps === "function") { 1917 try { 1918 const gps = await exifr.gps(file); 1919 if (gps && typeof gps.latitude === "number" && typeof gps.longitude === "number") coords = { lat: gps.latitude, lng: gps.longitude }; 1920 } catch (e) {} 1921 } 1922 1923 if (coords) { 1924 const freezeKey = "__guardami_freeze_geo_farfalle_citizen_v1"; 1925 if (window[freezeKey]) return; 1926 if (window.map && window.marker) { 1927 const newPos = { lat: coords.lat, lng: coords.lng }; 1928 window.map.setCenter(newPos); 1929 window.map.setZoom(15); 1930 window.marker.setPosition(newPos); 1931 } 1932 window.manualEditMade = true; 1933 1934 const latInput = document.getElementById('latitudine'); 1935 const lngInput = document.getElementById('longitudine'); 1936 if (latInput && lngInput) { 1937 latInput.value = coords.lat.toFixed(6); 1938 lngInput.value = coords.lng.toFixed(6); 1939 latInput.dispatchEvent(new Event('change')); 1940 lngInput.dispatchEvent(new Event('change')); 1941 } 1942 1943 if (exifWarningBox) exifWarningBox.style.display = 'none'; 1944 } else { 1945 const latInput = document.getElementById('latitudine'); 1946 if (latInput && !latInput.value && exifWarningBox) { 1947 exifWarningBox.style.display = 'block'; 1948 } 1949 } 1950 } catch (e) { 1951 console.warn("EXIF error:", e); 1952 } 1953} 1954 1955function syncInputFiles() { 1956 if (!imageInput) return; 1957 const dt = new DataTransfer();
1958 selectedFiles.forEach(item => dt.items.add(item.file)); 1959 imageInput.files = dt.files; 1960} 1961 1962function renderPreview() { 1963 if (!previewContainer) return; 1964 previewContainer.innerHTML = ''; 1965 1966 if (selectedFiles.length === 0) { 1967 if (imagePreviewBox) imagePreviewBox.style.display = 'none'; 1968 return; 1969 } 1970 1971 if (imagePreviewBox) imagePreviewBox.style.display = 'block'; 1972 1973 selectedFiles.forEach((item, index) => { 1974 const div = document.createElement('div'); 1975 div.className = 'preview-item'; // Assicurarsi che esista CSS o usare stile inline 1976 div.style.position = 'relative'; 1977 div.style.display = 'inline-block'; 1978 div.style.margin = '5px'; 1979 1980 const img = document.createElement('img'); 1981 img.className = 'image-thumbnail'; // Classe esistente 1982 img.style.width = '80px'; 1983 img.style.height = '80px'; 1984 img.style.objectFit = 'cover'; 1985 img.style.borderRadius = '8px'; 1986 1987 // Lettura asincrona per preview 1988 const reader = new FileReader(); 1989 reader.onload = (e) => img.src = e.target.result; 1990 reader.readAsDataURL(item.file); 1991 1992 if (typeof openLightbox === 'function') { 1993 img.style.cursor = 'pointer'; 1994 img.onclick = () => { 1995 // Rileggi per lightbox (o usa src se già caricato) 1996 if(img.src) openLightbox(img.src); 1997 }; 1998 } 1999 2000 const delBtn = document.createElement('button'); 2001 delBtn.textContent = 'Ã'; 2002 delBtn.style.position = 'absolute'; 2003 delBtn.style.top = '-5px'; 2004 delBtn.style.right = '-5px'; 2005 delBtn.style.background = 'red'; 2006 delBtn.style.color = 'white'; 2007 delBtn.style.border = 'none'; 2008 delBtn.style.borderRadius = '50%'; 2009 delBtn.style.width = '20px'; 2010 delBtn.style.height = '20px'; 2011 delBtn.style.cursor = 'pointer'; 2012 delBtn.style.display = 'flex'; 2013 delBtn.style.alignItems = 'center'; 2014 delBtn.style.justifyContent = 'center'; 2015 delBtn.style.fontWeight = 'bold'; 2016 2017 delBtn.onclick = (e) => { 2018 e.stopPropagation(); 2019 removeFile(index); 2020 }; 2021 2022 div.appendChild(img); 2023 div.appendChild(delBtn); 2024 previewContainer.appendChild(div); 2025 }); 2026} 2027 2028window.removeFile = function(index) { 2029 selectedFiles.splice(index, 1); 2030 syncInputFiles(); 2031 renderPreview(); 2032 2033 // RESET LOGIC (Uccelli Parity) 2034 if (selectedFiles.length === 0) { 2035 // Reset Data to Today 2036 const dateField = document.getElementById('dataAvvistamento'); 2037 if (dateField) { 2038 const today = new Date(); 2039 const yyyy = today.getFullYear(); 2040 const mm = String(today.getMonth() + 1).padStart(2, '0'); 2041 const dd = String(today.getDate()).padStart(2, '0'); 2042 dateField.value = `${yyyy}-${mm}-${dd}`; 2043 } 2044 2045 // Reset Geolocation to User Position (if permission granted) 2046 if (typeof chiediGeolocalizzazione === 'function') { 2047 chiediGeolocalizzazione(); 2048 } 2049 2050 if (exifWarningBox) exifWarningBox.style.display = 'none'; 2051 } 2052}; 2053 2054// Initial Setup: Set Date to Today if empty 2055document.addEventListener('DOMContentLoaded', () => { 2056 const istitutoScolastico = document.getElementById('istitutoScolastico'); 2057 if (istitutoScolastico) { 2058 const scuoleList = document.getElementById('scuoleList'); 2059 const normalize = (s) => String(s || '').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim(); 2060 const updateSuggestions = () => { 2061 if (!scuoleList || typeof SCUOLE_MILANO === 'undefined') return; 2062 const q = normalize(istitutoScolastico.value); 2063 scuoleList.innerHTML = ''; 2064 if (!q) return; 2065 let added = 0; 2066 for (const scuola of SCUOLE_MILANO) { 2067 if (normalize(scuola).includes(q)) { 2068 const option = document.createElement('option'); 2069 option.value = scuola; 2070 scuoleList.appendChild(option); 2071 added++; 2072 if (added >= 30) break; 2073 } 2074 } 2075 }; 2076 2077 const savedIstituto = localStorage.getItem('guardami_istituto'); 2078 if (savedIstituto) { 2079 istitutoScolastico.value = savedIstituto; 2080 } 2081 2082 istitutoScolastico.addEventListener('input', () => { 2083 localStorage.setItem('guardami_istituto', istitutoScolastico.value); 2084 updateSuggestions(); 2085 }); 2086 istitutoScolastico.addEventListener('focus', updateSuggestions); 2087 } 2088 2089 const dateField = document.getElementById('dataAvvistamento'); 2090 if (dateField && !dateField.value) { 2091 const today = new Date(); 2092 const yyyy = today.getFullYear(); 2093 const mm = String(today.getMonth() + 1).padStart(2, '0'); 2094 const dd = String(today.getDate()).padStart(2, '0'); 2095 dateField.value = `${yyyy}-${mm}-${dd}`; 2096 } 2097}); 2098 2099 2100// Funzione per aprire/chiudere i box info 2101window.toggleInfo = function(id) { 2102 const box = document.getElementById(id); 2103 if (box) { 2104 box.classList.toggle('visible'); 2105 } 2106}; 2107 2108// =============================================== 2109// INVIO MODULO VIA NETLIFY FUNCTION â JOTFORM (Pacchetto B) 2110// =============================================== 2111document.addEventListener("DOMContentLoaded", () => { 2112 const form = document.getElementById("butterfly-form"); 2113 const fileInput = document.getElementById("butterflyImage"); 2114 const submitBtn = document.getElementById("submitButton"); 2115 2116 if (!form || !fileInput) return; 2117 2118 function readFileAsDataURL(file) { 2119 return new Promise((resolve, reject) => { 2120 const reader = new FileReader(); 2121 reader.onload = () => resolve(reader.result); 2122 reader.onerror = () => reject(new Error("Impossibile leggere il file selezionato."));
2123 reader.readAsDataURL(file); 2124 }); 2125 } 2126 2127 // Funzione di ridimensionamento immagini (Client-Side) 2128 // Riduce drasticamente il payload inviato a Netlify evitando timeout e errori 500 2129 function resizeImage(file, maxWidth = 1600, quality = 0.8) { 2130 return new Promise((resolve, reject) => { 2131 const reader = new FileReader(); 2132 reader.readAsDataURL(file); 2133 reader.onload = (event) => { 2134 const img = new Image(); 2135 img.src = event.target.result; 2136 img.onload = () => { 2137 let width = img.width; 2138 let height = img.height; 2139 2140 if (width > maxWidth) { 2141 height = Math.round(height * (maxWidth / width)); 2142 width = maxWidth; 2143 } 2144 2145 const canvas = document.createElement('canvas'); 2146 canvas.width = width; 2147 canvas.height = height; 2148 const ctx = canvas.getContext('2d'); 2149 ctx.drawImage(img, 0, 0, width, height); 2150 2151 // Returns data:image/jpeg;base64,... 2152 resolve(canvas.toDataURL('image/jpeg', quality)); 2153 }; 2154 img.onerror = (err) => reject(new Error("Errore caricamento immagine per resize")); 2155 }; 2156 reader.onerror = (err) => reject(new Error("Errore lettura file per resize")); 2157 }); 2158 } 2159 2160 form.addEventListener("submit", async (e) => { 2161 e.preventDefault(); 2162 2163 // Controlli base 2164 if (selectedFiles.length === 0) { 2165 alert("Carica almeno una foto prima di inviare."); 2166 return; 2167 } 2168 2169 const emailInput = document.getElementById("email"); 2170 if (!emailInput || !emailInput.value) { 2171 alert("Inserisci un indirizzo email."); 2172 return; 2173 } 2174 2175 const consensoChecked = form.querySelector('input[name="q266_ilSottoscritto"]:checked'); 2176 if (!consensoChecked || consensoChecked.value !== "Acconsento") { 2177 alert("Per inviare il modulo devi selezionare 'Acconsento'."); 2178 return; 2179 } 2180 2181 let storedAgeOk = false; 2182 try { 2183 storedAgeOk = localStorage.getItem(AGE_GATE_KEY) === "true"; 2184 } catch (e0) { 2185 storedAgeOk = false; 2186 } 2187 const ageOk = storedAgeOk || document.getElementById('ageGate14')?.checked; 2188 if (!ageOk) { 2189 alert("Per inviare la segnalazione, conferma: hai almeno 14 anni oppure hai lâautorizzazione di un genitore/tutore."); 2190 return; 2191 } 2192 2193 const freezeKey = "__guardami_freeze_geo_farfalle_citizen_v1"; 2194 window[freezeKey] = true; 2195 let hasCoords = false; 2196 try { 2197 const latInput = document.getElementById('latitudine'); 2198 const lngInput = document.getElementById('longitudine'); 2199 const latV = latInput ? parseFloat(latInput.value) : NaN; 2200 const lngV = lngInput ? parseFloat(lngInput.value) : NaN; 2201 if (Number.isFinite(latV) && Number.isFinite(lngV)) hasCoords = true; 2202 } catch (e) {} 2203 try { 2204 if (!hasCoords && window.marker && typeof window.marker.getPosition === 'function') { 2205 const pos = window.marker.getPosition(); 2206 if (pos && typeof pos.lat === 'function' && typeof pos.lng === 'function') { 2207 const latV = pos.lat(); 2208 const lngV = pos.lng(); 2209 if (Number.isFinite(latV) && Number.isFinite(lngV)) hasCoords = true; 2210 } 2211 } 2212 } catch (e) {} 2213 2214 if (!window.manualEditMade && !hasCoords) { 2215 try { 2216 await new Promise((resolve) => { 2217 if (!navigator.geolocation) return resolve(false); 2218 navigator.geolocation.getCurrentPosition( 2219 (pos) => { 2220 try { 2221 const la = pos && pos.coords ? pos.coords.latitude : null; 2222 const lo = pos && pos.coords ? pos.coords.longitude : null; 2223 if (typeof la !== "number" || typeof lo !== "number" || !isFinite(la) || !isFinite(lo)) return resolve(false); 2224 const latInput = document.getElementById('latitudine'); 2225 const lngInput = document.getElementById('longitudine'); 2226 if (latInput) latInput.value = la.toFixed(6); 2227 if (lngInput) lngInput.value = lo.toFixed(6); 2228 const jot = document.getElementById('jotformGeoloc'); 2229 if (jot) jot.value = la.toFixed(6) + ", " + lo.toFixed(6); 2230 if (window.map && window.marker) { 2231 const p = { lat: la, lng: lo }; 2232 window.map.setCenter(p); 2233 window.map.setZoom(15); 2234 window.marker.setPosition(p); 2235 } 2236 resolve(true); 2237 } catch (e) { resolve(false); } 2238 }, 2239 () => resolve(false), 2240 { enableHighAccuracy: true, maximumAge: 0, timeout: 8000 } 2241 ); 2242 }); 2243 } catch (e) {} 2244 } 2245 2246 const latInput = document.getElementById('latitudine'); 2247 const lngInput = document.getElementById('longitudine'); 2248 let lat = latInput ? parseFloat(latInput.value) : NaN; 2249 let lng = lngInput ? parseFloat(lngInput.value) : NaN; 2250 2251 if (isNaN(lat) || isNaN(lng)) { 2252 const jg = document.getElementById('jotformGeoloc')?.value; 2253 if (jg && jg.includes(',')) { 2254 const parts = jg.split(','); 2255 lat = parseFloat(parts[0]); 2256 lng = parseFloat(parts[1]); 2257 } 2258 } 2259 2260 if (!Number.isFinite(lat) || !Number.isFinite(lng)) { 2261 alert("Posizione mancante: consenti l'accesso al GPS oppure imposta la posizione sulla mappa prima di inviare."); 2262 return; 2263 } 2264 try { 2265 const jg = document.getElementById('jotformGeoloc'); 2266 if (jg) jg.value = `${lat}, ${lng}`; 2267 } catch (e1) {} 2268 2269 if (submitBtn) { 2270 submitBtn.disabled = true; 2271 submitBtn.textContent = "Invio in corsoâ¦"; 2272 } 2273 2274 try { 2275 // --- PREPARAZIONE FILES (UCCELLI PARITY) --- 2276 let filesPayload = []; 2277 for (let item of selectedFiles) { 2278 try { 2279 let dataUrl = await readFileAsDataURL(item.file); 2280 filesPayload.push({ 2281 fileName: item.file.name, 2282 fileType: item.file.type || "image/jpeg", 2283 fileBase64: dataUrl.split(",")[1] 2284 }); 2285 } catch (err) { 2286 console.error("Errore lettura file:", err); 2287 } 2288 } 2289 2290 // --- PREPARAZIONE PAYLOAD ---
2291 const urlParams = new URLSearchParams(window.location.search || ""); 2292 const uidFromQuery = urlParams.get('uid') || ""; 2293 const uidFromAuth = (window.firebaseAuth && window.firebaseAuth.currentUser) ? window.firebaseAuth.currentUser.uid : ""; 2294 const uid = uidFromQuery || uidFromAuth || ""; 2295 2296 const clientRequestKey = "guardami_pending_request_farfalle_cittadini"; 2297 let clientRequestId = ""; 2298 try { 2299 const raw = sessionStorage.getItem(clientRequestKey); 2300 if (raw) { 2301 const parsed = JSON.parse(raw); 2302 if (parsed && parsed.id && parsed.ts && (Date.now() - Number(parsed.ts) < 120000)) { 2303 clientRequestId = String(parsed.id); 2304 } 2305 } 2306 } catch (e) {} 2307 if (!clientRequestId) { 2308 clientRequestId = 2309 (window.crypto && typeof window.crypto.randomUUID === "function") 2310 ? window.crypto.randomUUID() 2311 : `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`; 2312 try { 2313 sessionStorage.setItem(clientRequestKey, JSON.stringify({ id: clientRequestId, ts: Date.now() })); 2314 } catch (e) {} 2315 } 2316 2317 const payload = { 2318 formID: "253145422861353", // Farfalle Form ID 2319 nome: document.getElementById("nomeRilevatore")?.value || "", 2320 email: emailInput.value, 2321 istitutoScolastico: document.getElementById("istitutoScolastico")?.value || "", 2322 dataAvvistamento: document.getElementById("dataAvvistamento")?.value || "", 2323 geoloc: document.getElementById("jotformGeoloc")?.value || "", 2324 2325 // Campi specifici Farfalle 2326 cosaHaiOsservato: (form.querySelector('input[name="q30_scriviUna[]"]:checked') || {}).value || "", 2327 conosciLaSpecie: (form.querySelector('input[name="conosciSpecieLocal"]:checked') || {}).value === 'si' ? 'Si' : 'No', 2328 nomeSpecie: document.getElementById("nomeSpecie")?.value || "", 2329 numeroIndividui: document.getElementById("numeroIndividui")?.value || "", 2330 ambiente: document.getElementById("ambienteCircostante")?.value || "", 2331 ulterioriOsservazioni: document.getElementById("ulterioriOsservazioni")?.value || "", 2332 autorizzazione: consensoChecked.value, 2333 2334 // Files array 2335 files: filesPayload, 2336 2337 // UID 2338 uid, 2339 clientRequestId 2340 }; 2341 2342 console.log("Invio payload...", payload); 2343 2344 // --- INVIO ALLA NETLIFY FUNCTION --- 2345 if (window.location.protocol === 'file:') { 2346 alert("Attenzione: Esecuzione locale non supportata per l'invio (file://)."); 2347 throw new Error("Local file execution not supported."); 2348 } 2349 2350 const controller = new AbortController(); 2351 const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 sec timeout for uploads 2352 2353 let res; 2354 try { 2355 res = await fetch("/.netlify/functions/submit-jotform-farfalle", { 2356 method: "POST", 2357 headers: { "Content-Type": "application/json" }, 2358 body: JSON.stringify(payload), 2359 signal: controller.signal 2360 }); 2361 clearTimeout(timeoutId); 2362 } catch (fetchErr) { 2363 clearTimeout(timeoutId); 2364 throw fetchErr; 2365 } 2366 2367 const text = await res.text(); 2368 let jsonResp; 2369 try { 2370 jsonResp = JSON.parse(text); 2371 } catch(e) { 2372 jsonResp = { message: text }; 2373 } 2374 2375 if (res.ok) { 2376 console.log("Invio riuscito:", jsonResp); 2377 try { sessionStorage.removeItem(clientRequestKey); } catch (e) {} 2378 2379 const lat = parseFloat(document.getElementById("latitudine")?.value || ""); 2380 const lng = parseFloat(document.getElementById("longitudine")?.value || ""); 2381 const geoloc = 2382 Number.isFinite(lat) && Number.isFinite(lng) 2383 ? `${lat.toFixed(6)},${lng.toFixed(6)}` 2384 : String(payload.geoloc || "").trim(); 2385 2386 const isLocal = location.hostname === "localhost" || location.hostname === "127.0.0.1"; 2387 const homeBaseUrl = isLocal ?
2387"../index.html" : (window.location.origin + "/index.html")
2387; 2388 2389 const hashParams = []; 2390 hashParams.push("segnalazione_inviata=true"); 2391 hashParams.push("mapModule=" + encodeURIComponent("Farfalle (Cittadini)")); 2392 2393 if (geoloc) { 2394 hashParams.push("mapGeoloc=" + encodeURIComponent(geoloc)); 2395 } 2396 if (payload.nomeSpecie) { 2397 hashParams.push("mapSpecies=" + encodeURIComponent(payload.nomeSpecie)); 2398 } 2399 if (payload.dataAvvistamento) { 2400 hashParams.push("mapDate=" + encodeURIComponent(payload.dataAvvistamento)); 2401 } 2402 2403 const finalHash = "#" + hashParams.join("&"); 2404 2405 try { 2406 sessionStorage.setItem( 2407 "guardami_last_submission", 2408 JSON.stringify({ 2409 ts: Date.now(), 2410 mapModule: "Farfalle (Cittadini)", 2411 mapGeoloc: geoloc || "", 2412 mapSpecies: payload.nomeSpecie || "", 2413 mapDate: payload.dataAvvistamento || "", 2414 }) 2415 ); 2416 } catch (e) {} 2417 2418 const overlay = document.getElementById("successOverlay"); 2419 if (overlay) { 2420 overlay.style.display = "flex"; 2421 setTimeout(() => { 2422 window.location.href = homeBaseUrl + finalHash; 2423 }, 2200); 2424 } else { 2425 alert("Grazie per la segnalazione! Il file è stato correttamente trasmesso. Il Team di GuardaMI"); 2426 window.location.href = homeBaseUrl + finalHash; 2427 } 2428 } else { 2429 console.error("Errore Jotform:", text); 2430 alert("Errore nell'invio a Jotform:\n" + text); 2431 } 2432 } catch (err) { 2433 console.error("Errore invio:", err); 2434 alert("Errore durante l'invio: " + err.message); 2435 } finally { 2436 if (submitBtn) { 2437 submitBtn.disabled = false; 2438 submitBtn.textContent = "Invia"; 2439 } 2440 } 2441 }); 2442 2443 // ============================================================= 2444 // NUOVA LOGICA MODALE GPS (INIEZIONE DINAMICA) - COPIATO DA FARFALLE RICERCATORI 2445 // ============================================================= 2446 (function() { 2447 function createAndShowModal() { 2448 // 1. Check if already exists (prevent duplicates) 2449 if (document.getElementById('geo-permission-modal-v2')) return; 2450 2451 // 2. Check Session Logic: Show ONLY if not yet answered in this session 2452 try { 2453 const status = sessionStorage.getItem('geoConsentSession_v3'); 2454 if (status === 'granted') { 2455 if (typeof chiediGeolocalizzazione === 'function') chiediGeolocalizzazione(); 2456 return; 2457 } 2458 if (status === 'denied') { 2459 return; // User explicitly said NO in this session 2460 } 2461 } catch(e) { 2462 console.error("SessionStorage unavailable", e); 2463 } 2464 2465 // 3. Create Modal HTML 2466 const modal = document.createElement('div'); 2467 modal.id = 'geo-permission-modal-v2'; 2468 modal.style.cssText = ` 2469 display: flex !important; 2470 position: fixed !important; 2471 top: 0 !important; 2472 left: 0 !important; 2473 width: 100% !important; 2474 height: 100% !important; 2475 background-color: rgba(0,0,0,0.85) !important; 2476 z-index: 2147483647 !important; 2477 align-items: center !important; 2478 justify-content: center !important; 2479 font-family: sans-serif !important; 2480 touch-action: none !important; 2481 opacity: 0; 2482 transition: opacity 0.3s ease-out; 2483 `; 2484 2485 modal.innerHTML = ` 2486 <div style=" 2487 background: white; 2488 padding: 24px; 2489 border-radius: 16px; 2490 text-align: center; 2491 max-width: 320px; 2492 width: 90%; 2493 box-shadow: 0 10px 40px rgba(0,0,0,0.5); 2494 position: relative; 2495 transform: scale(0.9); 2496 transition: transform 0.3s ease-out; 2497 "> 2498 <img src="logo.png" alt="GuardaMI" style=" 2499 width: 80px;
2500 height: 80px; 2501 border-radius: 16px; 2502 margin-bottom: 16px; 2503 box-shadow: 0 4px 12px rgba(0,0,0,0.1); 2504 object-fit: cover; 2505 "> 2506 <h3 style="margin: 0 0 12px; font-size: 18px; color: #111827; font-weight: 700;">GuardaMI ® WebApp</h3> 2507 <p style="margin: 0 0 24px; font-size: 15px; color: #4b5563; line-height: 1.5;"> 2508 vorrebbe utilizzare la tua posizione attuale per localizzare le tue segnalazioni. 2509 </p> 2510 <button id="geo-allow-btn-v2" style=" 2511 background: #1d4ed8; 2512 color: white; 2513 border: none; 2514 padding: 14px 24px; 2515 border-radius: 12px; 2516 font-weight: 600; 2517 font-size: 16px; 2518 width: 100%; 2519 cursor: pointer; 2520 box-shadow: 0 4px 6px rgba(29, 78, 216, 0.2); 2521 -webkit-appearance: none; 2522 margin-bottom: 12px; 2523 ">Consenti</button> 2524 <button id="geo-deny-btn-v2" style=" 2525 background: transparent; 2526 color: #6b7280; 2527 border: none; 2528 padding: 12px; 2529 font-size: 14px; 2530 cursor: pointer; 2531 -webkit-appearance: none; 2532 width: 100%; 2533 ">Non ora</button> 2534 </div> 2535 `; 2536 2537 // 4. Inject into Body 2538 document.body.appendChild(modal); 2539 2540 // 5. Animate In 2541 requestAnimationFrame(() => { 2542 modal.style.opacity = '1'; 2543 const inner = modal.querySelector('div'); 2544 if(inner) inner.style.transform = 'scale(1)'; 2545 }); 2546 2547 // 6. Event Listeners 2548 const btnAllow = document.getElementById('geo-allow-btn-v2'); 2549 const btnDeny = document.getElementById('geo-deny-btn-v2'); 2550 2551 const handleAllow = (e) => { 2552 e.preventDefault(); 2553 e.stopPropagation(); 2554 /* DISABILITATO: Nessun dato salvato in locale. 2555 try { sessionStorage.setItem('geoConsentSession_v3', 'granted'); } catch(e){} 2556 */ 2557 closeModal(); 2558 if (typeof chiediGeolocalizzazione === 'function') { 2559 chiediGeolocalizzazione(); 2560 } 2561 }; 2562 2563 const handleDeny = (e) => { 2564 e.preventDefault(); 2565 e.stopPropagation(); 2566 /* DISABILITATO: Nessun dato salvato in locale. 2567 try { sessionStorage.setItem('geoConsentSession_v3', 'denied'); } catch(e){} 2568 */ 2569 closeModal(); 2570 }; 2571 2572 const closeModal = () => { 2573 modal.style.opacity = '0'; 2574 setTimeout(() => { 2575 if(modal.parentNode) modal.parentNode.removeChild(modal); 2576 }, 300); 2577 }; 2578 2579 if(btnAllow) { 2580 btnAllow.addEventListener('click', handleAllow); 2581 btnAllow.addEventListener('touchstart', handleAllow, {passive: false}); 2582 } 2583 if(btnDeny) { 2584 btnDeny.addEventListener('click', handleDeny); 2585 btnDeny.addEventListener('touchstart', handleDeny, {passive: false}); 2586 } 2587 } 2588 2589 // Run on load 2590 if (document.readyState === 'loading') { 2591 document.addEventListener('DOMContentLoaded', createAndShowModal); 2592 } else { 2593 createAndShowModal(); 2594 } 2595 })(); // End of Modal IIFE 2596 2597 // (Removed invalid IIFE close) 2598 }); // End of Form Submission Listener 2599 2600 2601async function incrementUserScore() { 2602 // 1. Try Modular SDK (window.fs) - PREFERRED 2603 if (window.firebaseAuth && window.firebaseDb && window.fs && window.fs.updateDoc && window.fs.increment) { 2604 const user = window.firebaseAuth.currentUser; 2605 if (!user) { 2606 console.warn("User not logged in (Modular), score not updated."); 2607 return; 2608 } 2609 2610 const userRef = window.fs.doc(window.firebaseDb, "users", user.uid);
2611 try { 2612 await window.fs.updateDoc(userRef, { 2613 points: window.fs.increment(1) 2614 }); 2615 console.log("Score updated successfully +1 (Modular)"); 2616 return; 2617 } catch (e) { 2618 console.error("Score update failed (Modular):", e); 2619 // Retry with setDoc if doc missing 2620 if (e.code === 'not-found' || e.message.includes("No document to update")) { 2621 try { 2622 await window.fs.setDoc(userRef, { points: 1 }, { merge: true }); 2623 console.log("Score set successfully (Modular)"); 2624 } catch (err) { 2625 console.error("Retry set points failed (Modular):", err); 2626 } 2627 } 2628 return; 2629 } 2630 } 2631}
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.