diff --git a/css/base.css b/css/base.css new file mode 100644 index 0000000..a5fd45d --- /dev/null +++ b/css/base.css @@ -0,0 +1,17 @@ +body { + font-family: sans-serif; + margin: 0; + padding: 0; + background: #fafafa; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 4px; +} diff --git a/css/bottomSheet.css b/css/bottomSheet.css new file mode 100644 index 0000000..e3b0bb0 --- /dev/null +++ b/css/bottomSheet.css @@ -0,0 +1,100 @@ +/* =============================== + BOTTOM SHEET (menu ⋮) + =============================== */ + +.bottom-sheet { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + + /* 🔥 Altezza dinamica: abbastanza grande per mostrare tutto */ + height: 60vh; + max-height: 80vh; + + background: rgba(255,255,255,0.95); + backdrop-filter: blur(6px); + border-top: 1px solid #ddd; + box-shadow: 0 -2px 10px rgba(0,0,0,0.15); + + display: none; + flex-direction: column; + z-index: 9999; + + overflow-y: auto; /* 🔥 Scroll verticale */ +} + +.bottom-sheet.open { + display: flex; +} + +/* Header con la "maniglia" */ +.sheet-header { + height: 16px; + display: flex; + justify-content: center; + align-items: center; +} + +.sheet-header::before { + content: ""; + width: 40px; + height: 4px; + background: #bbb; + border-radius: 4px; +} + +/* Contenuto del menu (ordinamento, filtri, ecc.) */ +.sheet-content { + padding: 20px; +} + +/* Miniature (usate nel bottom sheet delle foto, non in quello delle opzioni) */ +.sheet-gallery { + display: flex; + flex-direction: row; + overflow-x: auto; + padding: 10px; + gap: 10px; +} + +.sheet-item { + width: 90px; + height: 90px; + border-radius: 10px; + overflow: hidden; + flex-shrink: 0; + box-shadow: 0 2px 6px rgba(0,0,0,0.25); + background: #eee; +} + +.sheet-item img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Pulsanti del menu ⋮ */ +.sheet-btn { + width: 100%; + padding: 12px; + margin-bottom: 8px; + text-align: left; + background: #f5f5f5; + border: none; + border-radius: 8px; + font-size: 15px; + cursor: pointer; +} + +.sheet-btn:hover { + background: #e8e8e8; +} + +/* Titoli delle sezioni */ +#optionsSheet h3 { + margin-top: 20px; + margin-bottom: 10px; + font-size: 16px; + color: #444; +} diff --git a/css/gallery.css b/css/gallery.css new file mode 100644 index 0000000..ecb3e1d --- /dev/null +++ b/css/gallery.css @@ -0,0 +1,46 @@ +.gallery { + display: block; + padding: 6px; /* più stretto */ +} + +.gallery-section-title { + font-size: 18px; + font-weight: 600; + margin: 18px 6px 6px; /* più compatto */ + color: #444; +} + +.gallery-section { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); /* leggermente più piccole */ + gap: 6px; /* SPACING RIDOTTO */ + padding: 0 6px; +} + +.thumb { + width: 100%; + aspect-ratio: 1 / 1; + border-radius: 8px; /* più compatto */ + overflow: hidden; + background: white; + box-shadow: 0 1px 3px rgba(0,0,0,0.12); /* più leggero */ + position: relative; + cursor: pointer; +} + +.thumb img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.play-icon { + position: absolute; + bottom: 6px; + right: 6px; + background: rgba(0,0,0,0.55); + color: white; + padding: 3px 5px; + border-radius: 4px; + font-size: 12px; +} diff --git a/css/header.css b/css/header.css new file mode 100644 index 0000000..a57f0c2 --- /dev/null +++ b/css/header.css @@ -0,0 +1,36 @@ +header { + padding: 10px 15px; + background: #333; + color: white; + display: flex; + justify-content: space-between; + align-items: center; +} + +.top-buttons { + display: flex; + gap: 10px; +} + +.icon-btn { + background: none; + border: none; + font-size: 22px; + padding: 6px 10px; + cursor: pointer; + border-radius: 6px; +} + +.icon-btn:hover { + background: rgba(255,255,255,0.15); +} + +.icon-btn { + background: none; + border: none; + font-size: 22px; + padding: 6px 10px; + cursor: pointer; + border-radius: 6px; + color: white; /* 🔥 questo mancava */ +} diff --git a/css/infoPanel.css b/css/infoPanel.css new file mode 100644 index 0000000..cb2d743 --- /dev/null +++ b/css/infoPanel.css @@ -0,0 +1,40 @@ +.info-panel { + position: fixed; + top: 0; + right: 0; + width: 320px; + height: 100%; + background: #fff; + padding: 16px; + box-shadow: -2px 0 6px rgba(0,0,0,0.25); + overflow-y: auto; + z-index: 2000; + transform: translateX(100%); + transition: transform 0.3s ease; +} + +.info-panel.open { + transform: translateX(0); +} + +.info-panel h3 { + margin-top: 0; +} + +.info-row { + margin-bottom: 10px; +} + +.info-row b { + display: inline-block; + width: 110px; +} + +.info-map { + width: 100%; + height: 250px; + margin-top: 15px; + border-radius: 6px; + overflow: hidden; + border: 1px solid #ccc; +} diff --git a/css/map.css b/css/map.css new file mode 100644 index 0000000..4d9fcab --- /dev/null +++ b/css/map.css @@ -0,0 +1,58 @@ +.global-map { + display: none; + width: 100%; + height: calc(100vh - 60px); + z-index: 900; +} + +.global-map.open { + display: block; +} + +.photo-marker { + width: 48px; + height: 48px; + border-radius: 10px; + overflow: hidden; + position: relative; + box-shadow: 0 2px 6px rgba(0,0,0,0.25); + background: #fff; +} + +.photo-marker img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.photo-cluster { + width: 56px; + height: 56px; + position: relative; + border-radius: 12px; + overflow: visible; +} + +.cluster-back { + position: absolute; + top: 6px; + left: 6px; + width: 48px; + height: 48px; + border-radius: 10px; + object-fit: cover; + opacity: 0.5; + filter: blur(1px); + transform: scale(0.95); +} + +.cluster-front { + position: absolute; + top: 0; + left: 0; + width: 48px; + height: 48px; + border-radius: 10px; + object-fit: cover; + box-shadow: 0 2px 6px rgba(0,0,0,0.35); +} diff --git a/css/modal.css b/css/modal.css new file mode 100644 index 0000000..0274319 --- /dev/null +++ b/css/modal.css @@ -0,0 +1,70 @@ +.modal { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.8); + display: none; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal.open { + display: flex; +} + +.modal-content { + width: 90vw; + height: 90vh; + position: relative; + display: flex; + justify-content: center; + align-items: center; +} + +#modalMediaContainer { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +#modalMediaContainer img, +#modalMediaContainer video { + width: 100%; + height: 100%; + object-fit: contain; +} + +.modal-close { + position: absolute; + top: -10px; + right: -10px; + background: #fff; + border-radius: 50%; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-weight: bold; + border: 1px solid #ccc; +} + +.modal-info-btn { + position: absolute; + bottom: -10px; + right: 40px; + background: #fff; + border-radius: 50%; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border: 1px solid #ccc; + font-size: 16px; + z-index: 1500; +} diff --git a/css/optionsSheet.css b/css/optionsSheet.css new file mode 100644 index 0000000..378d064 --- /dev/null +++ b/css/optionsSheet.css @@ -0,0 +1,26 @@ +#optionsSheet .sheet-content { + padding: 20px; +} + +#optionsSheet h3 { + margin-top: 20px; + margin-bottom: 10px; + font-size: 16px; + color: #444; +} + +.sheet-btn { + width: 100%; + padding: 12px; + margin-bottom: 8px; + text-align: left; + background: #f5f5f5; + border: none; + border-radius: 8px; + font-size: 15px; + cursor: pointer; +} + +.sheet-btn:hover { + background: #e8e8e8; +} diff --git a/css/utils.css b/css/utils.css new file mode 100644 index 0000000..d88c06f --- /dev/null +++ b/css/utils.css @@ -0,0 +1,24 @@ +.hidden { + display: none !important; +} + +.rounded { + border-radius: 10px; +} + +.shadow-sm { + box-shadow: 0 1px 3px rgba(0,0,0,0.15); +} + +.shadow-md { + box-shadow: 0 2px 6px rgba(0,0,0,0.25); +} + +.fade-in { + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} diff --git a/index.html b/index.html index f51306f..56b94f9 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,16 @@ Galleria Foto - + + + + + + + + + + @@ -17,9 +26,17 @@ + + +

Galleria Foto

- + +
+ + + +
@@ -44,12 +61,38 @@
- +
+ + + +
+
+ +
+ +

Ordinamento

+ + + +

Raggruppamento

+ + + + + +

Filtri

+ + + + +
+
+ diff --git a/js/data.js b/js/data.js index 14c0af2..713e899 100644 --- a/js/data.js +++ b/js/data.js @@ -22,5 +22,8 @@ async function loadPhotos() { return; } - renderGallery(photosData); + // 🔥 IMPORTANTE: + // Ora la galleria deve essere costruita tramite refreshGallery(), + // che applica filtri, ordinamento e raggruppamento. + refreshGallery(); } diff --git a/js/gallery.js b/js/gallery.js index 298b585..92862ea 100644 --- a/js/gallery.js +++ b/js/gallery.js @@ -1,40 +1,149 @@ // =============================== -// RENDER GALLERIA +// ORDINAMENTO // =============================== -function renderGallery(photos) { - const gallery = document.getElementById('gallery'); - gallery.innerHTML = ''; +function sortByDate(photos, direction = "desc") { + return photos.slice().sort((a, b) => { + const da = new Date(a.taken_at); + const db = new Date(b.taken_at); - photos.forEach(photo => { - const thumbDiv = document.createElement('div'); - thumbDiv.className = 'thumb'; - - const img = document.createElement('img'); - img.src = `https://prova.patachina.it/${photo.thub1}`; - img.alt = photo.name || ''; - img.loading = "lazy"; - - thumbDiv.appendChild(img); - - if (photo.mime_type.startsWith("video/")) { - const play = document.createElement("div"); - play.className = "play-icon"; - play.textContent = "▶"; - thumbDiv.appendChild(play); - } - - const preview = photo.thub2 - ? `https://prova.patachina.it/${photo.thub2}` - : `https://prova.patachina.it/${photo.thub1}`; - - thumbDiv.addEventListener('click', () => { - openModal( - `https://prova.patachina.it/${photo.path}`, - preview, - photo - ); - }); - - gallery.appendChild(thumbDiv); + return direction === "asc" ? da - db : db - da; + }); +} + +// =============================== +// FILTRI (base, estendibili) +// =============================== +function applyFilters(photos) { + if (!currentFilter) return photos; + + switch (currentFilter) { + case "folder": + return photos.filter(p => p.folder); // da migliorare + case "location": + return photos.filter(p => p.gps && p.gps.lat); + case "type": + return photos.filter(p => p.mime_type.startsWith("image/")); + default: + return photos; + } +} + +// =============================== +// RAGGRUPPAMENTO STILE GOOGLE PHOTOS +// =============================== +function groupByDate(photos, mode = "auto") { + const sections = []; + const now = new Date(); + + function getLabel(photo) { + const date = new Date(photo.taken_at); + const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24)); + + if (mode === "day") return formatDay(date); + if (mode === "month") return formatMonth(date); + if (mode === "year") return date.getFullYear().toString(); + + // modalità AUTO (Google Photos) + if (diffDays === 0) return "Oggi"; + if (diffDays === 1) return "Ieri"; + if (diffDays <= 7) return "Questa settimana"; + if (diffDays <= 14) return "La settimana scorsa"; + if (diffDays <= 30) return "Questo mese"; + if (diffDays <= 60) return "Mese scorso"; + + // stesso anno → raggruppa per mese + if (date.getFullYear() === now.getFullYear()) { + return formatMonth(date); + } + + // anni precedenti → raggruppa per anno + return date.getFullYear().toString(); + } + + photos.forEach(photo => { + const label = getLabel(photo); + + let section = sections.find(s => s.label === label); + if (!section) { + section = { label, photos: [] }; + sections.push(section); + } + + section.photos.push(photo); + }); + + return sections; +} + +// =============================== +// FORMATTATORI +// =============================== +function formatDay(date) { + return date.toLocaleDateString("it-IT", { + weekday: "long", + day: "numeric", + month: "long" + }); +} + +function formatMonth(date) { + return date.toLocaleDateString("it-IT", { + month: "long", + year: "numeric" + }); +} + +// =============================== +// RENDER GALLERIA A SEZIONI +// =============================== +function renderGallery(sections) { + const gallery = document.getElementById("gallery"); + gallery.innerHTML = ""; + + sections.forEach(section => { + // HEADER DELLA SEZIONE + const h = document.createElement("h2"); + h.className = "gallery-section-title"; + h.textContent = section.label; + gallery.appendChild(h); + + // CONTENITORE DELLE FOTO + const container = document.createElement("div"); + container.className = "gallery-section"; + + section.photos.forEach(photo => { + const thumbDiv = document.createElement("div"); + thumbDiv.className = "thumb"; + + const img = document.createElement("img"); + img.src = `https://prova.patachina.it/${photo.thub1}`; + img.alt = photo.name || ""; + img.loading = "lazy"; + + thumbDiv.appendChild(img); + + if (photo.mime_type.startsWith("video/")) { + const play = document.createElement("div"); + play.className = "play-icon"; + play.textContent = "▶"; + thumbDiv.appendChild(play); + } + + const preview = photo.thub2 + ? `https://prova.patachina.it/${photo.thub2}` + : `https://prova.patachina.it/${photo.thub1}`; + + thumbDiv.addEventListener("click", () => { + openModal( + `https://prova.patachina.it/${photo.path}`, + preview, + photo + ); + }); + + container.appendChild(thumbDiv); + }); + + gallery.appendChild(container); }); } diff --git a/js/main.js b/js/main.js index 1f5ae9b..1d64f75 100644 --- a/js/main.js +++ b/js/main.js @@ -2,4 +2,76 @@ // AVVIO // =============================== console.log("main.js avviato"); + +// Carica le foto iniziali loadPhotos(); + +// =============================== +// VARIABILI GLOBALI PER LE OPZIONI +// =============================== +let currentSort = "desc"; // "desc" = più recenti prima +let currentGroup = "auto"; // auto = Oggi, Ieri, Settimana... +let currentFilter = null; // folder / location / type + +// =============================== +// APERTURA / CHIUSURA OPTIONS SHEET +// =============================== +const optionsBtn = document.getElementById("optionsBtn"); +const optionsSheet = document.getElementById("optionsSheet"); + +optionsBtn.addEventListener("click", () => { + optionsSheet.classList.add("open"); +}); + +function closeOptionsSheet() { + optionsSheet.classList.remove("open"); +} + +// Chiudi se clicchi fuori +optionsSheet.addEventListener("click", (e) => { + if (e.target === optionsSheet) closeOptionsSheet(); +}); + +// =============================== +// GESTIONE PULSANTI DEL BOTTOM SHEET OPZIONI +// =============================== +document.querySelectorAll("#optionsSheet .sheet-btn").forEach(btn => { + btn.addEventListener("click", () => { + + if (btn.dataset.sort) { + currentSort = btn.dataset.sort; + } + + if (btn.dataset.group) { + currentGroup = btn.dataset.group; + } + + if (btn.dataset.filter) { + currentFilter = btn.dataset.filter; + } + + closeOptionsSheet(); + refreshGallery(); + }); +}); + +// =============================== +// FUNZIONE CENTRALE DI AGGIORNAMENTO GALLERIA +// =============================== +function refreshGallery() { + console.log("Aggiornamento galleria..."); + + let photos = [...photosData]; + + // 1) Filtri + photos = applyFilters(photos); + + // 2) Ordinamento + photos = sortByDate(photos, currentSort); + + // 3) Raggruppamento + const sections = groupByDate(photos, currentGroup); + + // 4) Rendering + renderGallery(sections); +} diff --git a/style.css b/style.css index cad5927..f928901 100644 --- a/style.css +++ b/style.css @@ -341,3 +341,85 @@ header { height: 100%; object-fit: cover; } + +.top-buttons { + display: flex; + gap: 10px; +} + +.icon-btn { + background: none; + border: none; + font-size: 22px; + padding: 6px 10px; + cursor: pointer; + border-radius: 6px; +} + +.icon-btn:hover { + background: rgba(0,0,0,0.1); +} + +#optionsSheet .sheet-content { + padding: 20px; +} + +#optionsSheet h3 { + margin-top: 20px; + margin-bottom: 10px; + font-size: 16px; + color: #444; +} + +.sheet-btn { + width: 100%; + padding: 12px; + margin-bottom: 8px; + text-align: left; + background: #f5f5f5; + border: none; + border-radius: 8px; + font-size: 15px; + cursor: pointer; +} + +.sheet-btn:hover { + background: #e8e8e8; +} + +.gallery-section-title { + font-size: 20px; + font-weight: 600; + margin: 20px 10px 10px; + color: #444; +} + +.gallery-section { + column-count: 3; + column-gap: 10px; + padding: 0 10px; +} + +@media (max-width: 900px) { + .gallery-section { column-count: 2; } +} + +@media (max-width: 600px) { + .gallery-section { column-count: 1; } +} + +.thumb { + break-inside: avoid; + margin-bottom: 10px; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 2px 6px rgba(0,0,0,0.15); +} + +.thumb img { + width: 100%; + display: block; + object-fit: cover; +} + +