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;
+}
+
+