PageSourceSearch

https://dreamy-truffle-77db6e.netlify.app/farfalle%20cittadini/script.js?v=2026-06-03_2

js dreamy-truffle-77db6e.netlify.app collected 2026-10-03 10:40:15 UTC 109,624 bytes, 2,631 lines download raw bytes

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.