#!/usr/bin/env python3
"""
Server HTTP puro (no dipendenze esterne).
- Serve file statici da /var/www/dashboard
- GET  /reset-kit?printer_id=xxx&date=yyy&auth=PASS
- POST /save-costs       {auth, costs}
- POST /add-expense      {auth, spesa:{...}}
- POST /delete-expense   {auth, id}
- POST /save-presence    {auth, presenza:{...}}
- POST /delete-presence  {auth, id}
- POST /save-user        {auth, user:{username,password,nome,strutture:[],can_input_presenze:bool}}
- POST /delete-user      {auth, username}
- POST /log-access       {username, nome}   (no auth — chiamato dal browser al login)
"""
from http.server import HTTPServer, SimpleHTTPRequestHandler
import json, os, subprocess, urllib.parse, datetime
from pathlib import Path

BASE = Path("/var/www/dashboard")
PASS = os.getenv("DASH_PASS", "CreativaPro2025!")

class Handler(SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(BASE), **kwargs)

    def do_OPTIONS(self):
        self.send_response(200)
        self._cors()
        self.send_header("Content-Length", "0")
        self.end_headers()

    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        params = dict(urllib.parse.parse_qsl(parsed.query))

        if parsed.path == "/spectre-alerts":
            af = BASE / "spectre_alerts.json"
            alerts = json.loads(af.read_text()) if af.exists() else {"alerts": []}
            self._json(200, alerts); return

        if parsed.path == "/spectre-exif-data":
            ef = BASE / "exif_data.json"
            exif = json.loads(ef.read_text()) if ef.exists() else {"sessioni": [], "cameras": {}}
            self._json(200, exif); return

        # ── Landing page ─────────────────────────────────────────────────────
        if parsed.path == "/landing":
            struttura = params.get("s", "").strip()
            if not struttura:
                self._json(400, {"error": "Parametro s mancante"}); return
            self._serve_landing(struttura); return

        # ── Slot disponibili (pubblico) ───────────────────────────────────────
        if parsed.path == "/get-slots":
            struttura = params.get("s", "").strip()
            sf = BASE / f"slots_{struttura}.json"
            sd = json.loads(sf.read_text()) if sf.exists() else {"whatsapp": "", "slots": [], "note": ""}
            self._json(200, sd); return

        # ── Statistiche landing (auth admin) ─────────────────────────────────
        if parsed.path == "/landing-stats":
            if params.get("auth") != PASS:
                self._json(401, {"error": "Non autorizzato"}); return
            lf = BASE / "landing_stats.json"
            stats = json.loads(lf.read_text()) if lf.exists() else {"visits": [], "whatsapp_clicks": []}
            self._json(200, stats); return

        # ── Config landing page (auth admin) ─────────────────────────────────
        if parsed.path == "/get-landing-config":
            if params.get("auth") != PASS:
                self._json(401, {"error": "Non autorizzato"}); return
            struttura = params.get("struttura", "").strip()
            if not struttura:
                self._json(400, {"error": "Struttura mancante"}); return
            cf = BASE / f"landing_config_{struttura}.json"
            config = json.loads(cf.read_text()) if cf.exists() else {}
            self._json(200, config); return

        if parsed.path == "/orari-pc":
            hs_file = BASE / "heartbeat_history.json"
            hs = json.loads(hs_file.read_text()) if hs_file.exists() else {}
            self._json(200, hs); return

        if parsed.path == "/postazioni-stato":
            hf = BASE / "heartbeat.json"
            cf = BASE / "postazioni_config.json"
            sf = BASE / "spectre_data.json"
            hb = json.loads(hf.read_text()) if hf.exists() else {}
            pc = json.loads(cf.read_text()) if cf.exists() else {}
            sd = json.loads(sf.read_text()) if sf.exists() else {"postazioni": {}}
            result = {}
            # Unione di tutte le postazioni conosciute
            tutti = set(list(hb.keys()) + list(sd.get("postazioni", {}).keys()) + list(pc.keys()))
            for p in tutti:
                result[p] = {
                    "stato":            pc.get(p, {}).get("stato", "attiva"),
                    "exif_monitoring":  pc.get(p, {}).get("exif_monitoring", False),
                    "ultimo_heartbeat": hb.get(p),
                    "ultimo_sync":      sd.get("postazioni", {}).get(p, {}).get("timestamp")
                }
            self._json(200, {"postazioni": result}); return

        if parsed.path == "/reset-kit":
            if params.get("auth") != PASS:
                self._json(401, {"error": "Non autorizzato"}); return
            pid  = params.get("printer_id", "").strip()
            date = params.get("date", "").strip()
            if not pid or not date:
                self._json(400, {"error": "printer_id e date obbligatori"}); return
            resets_file = BASE / "kit_resets.json"
            resets = json.loads(resets_file.read_text()) if resets_file.exists() else {"resets": {}}
            resets["resets"][pid] = date
            resets_file.write_text(json.dumps(resets, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True, "printer_id": pid, "reset_date": date})
            return

        super().do_GET()

    def do_POST(self):
        parsed = urllib.parse.urlparse(self.path)
        length = int(self.headers.get("Content-Length", 0))
        body   = self.rfile.read(length)
        try:
            data = json.loads(body)
        except Exception:
            self._json(400, {"error": "JSON non valido"}); return

        # ── Sync dati Spectre dalle postazioni (no auth admin) ───────────────
        if parsed.path == "/spectre-sync":
            if data.get("token") != "SPECTRE_BRIDGE_2025":
                self._json(401, {"error": "Token non valido"}); return
            postazione = data.get("postazione", "sconosciuto")
            sessioni   = data.get("sessioni", [])
            ts         = data.get("timestamp", "")
            sf = BASE / "spectre_data.json"
            sd = json.loads(sf.read_text()) if sf.exists() else {"postazioni": {}}
            cartelle = data.get("cartelle", [])
            stats    = data.get("stats", {})
            sd["postazioni"][postazione] = {
                "timestamp": ts,
                "cartelle":  cartelle,
                "stats":     stats
            }
            sf.write_text(json.dumps(sd, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True, "postazione": postazione,
                             "cartelle_ricevute": len(cartelle)}); return

        # ── Heartbeat watcher postazioni (no auth admin) ─────────────────────
        if parsed.path == "/spectre-heartbeat":
            if data.get("token") != "SPECTRE_BRIDGE_2025":
                self._json(401, {"error": "Token non valido"}); return
            postazione = data.get("postazione", "?")
            ts         = data.get("timestamp", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
            hf = BASE / "heartbeat.json"
            hb = json.loads(hf.read_text()) if hf.exists() else {}
            hb[postazione] = ts
            hf.write_text(json.dumps(hb, indent=2, ensure_ascii=False))
            # Salva storico sessioni per orari accensione/spegnimento
            hs_file = BASE / "heartbeat_history.json"
            hs = json.loads(hs_file.read_text()) if hs_file.exists() else {}
            oggi = ts[:10]
            if postazione not in hs:
                hs[postazione] = {}
            if oggi not in hs[postazione]:
                hs[postazione][oggi] = [{"acceso": ts, "ultimo": ts}]
            else:
                sessioni = hs[postazione][oggi]
                ultimo_hb = sessioni[-1]["ultimo"]
                import datetime as dt2
                fmt = "%Y-%m-%d %H:%M:%S"
                diff = (dt2.datetime.strptime(ts, fmt) - dt2.datetime.strptime(ultimo_hb, fmt)).total_seconds()
                if diff > 600:
                    sessioni.append({"acceso": ts, "ultimo": ts})
                else:
                    sessioni[-1]["ultimo"] = ts
                hs[postazione][oggi] = sessioni
            hs_file.write_text(json.dumps(hs, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        # ── Dati EXIF scheda SD (no auth admin) ──────────────────────────────
        if parsed.path == "/spectre-exif":
            if data.get("token") != "SPECTRE_BRIDGE_2025":
                self._json(401, {"error": "Token non valido"}); return
            ef = BASE / "exif_data.json"
            exif = json.loads(ef.read_text()) if ef.exists() else {"sessioni": [], "cameras": {}}
            postazione   = data.get("postazione", "?")
            ts           = data.get("timestamp", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
            foto_count   = data.get("foto_sulla_scheda", 0)
            shutter      = data.get("shutter_count")   # può essere None
            make         = data.get("camera_make", "Sconosciuta")
            model        = data.get("camera_model", data.get("modello_camera", "Sconosciuto"))
            serial       = data.get("camera_serial", "")
            fotografo    = data.get("fotografo", "")
            cam_key      = serial if serial else f"{make}_{model}"
            # Calcola delta shutter rispetto all'ultima sessione della stessa fotocamera
            delta = None
            if shutter is not None and cam_key in exif.get("cameras", {}):
                prev = exif["cameras"][cam_key].get("ultimo_shutter")
                if prev is not None and shutter >= prev:
                    delta = shutter - prev
            # Aggiorna storico fotocamera
            if "cameras" not in exif: exif["cameras"] = {}
            exif["cameras"][cam_key] = {
                "make": make, "model": model, "serial": serial,
                "ultimo_shutter": shutter, "ultima_sessione": ts,
                "postazione": postazione
            }
            # Aggiunge sessione
            sessione = {
                "postazione": postazione, "timestamp": ts,
                "drive": data.get("drive", fotografo),
                "fotografo": fotografo,
                "camera_make": make, "camera_model": model, "camera_serial": serial,
                "cam_key": cam_key,
                "foto_sulla_scheda": foto_count,
                "shutter_count": shutter,
                "shutter_delta": delta,
                "shutter_baseline": data.get("shutter_baseline"),
                "scatti_da_baseline": data.get("scatti_da_baseline"),
                "foto_importate_spectre": data.get("foto_importate_spectre"),
                "dispersione_perc": data.get("dispersione_perc"),
                "sospetto": False
            }
            # Segna sospetto se delta > foto importate * 1.20 (calcolato dopo dal confronto)
            exif["sessioni"].append(sessione)
            exif["sessioni"] = exif["sessioni"][-500:]
            ef.write_text(json.dumps(exif, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True, "delta": delta}); return

        # ── Alert real-time da watcher postazioni (no auth admin) ────────────
        if parsed.path == "/spectre-alert":
            if data.get("token") != "SPECTRE_BRIDGE_2025":
                self._json(401, {"error": "Token non valido"}); return
            af = BASE / "spectre_alerts.json"
            alerts = json.loads(af.read_text()) if af.exists() else {"alerts": []}
            alerts["alerts"].append({
                "postazione": data.get("postazione", "?"),
                "tipo":       data.get("tipo", "?"),
                "percorso":   data.get("percorso", ""),
                "timestamp":  data.get("timestamp", ""),
                "letto":      False
            })
            alerts["alerts"] = alerts["alerts"][-200:]
            af.write_text(json.dumps(alerts, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        if data.get("auth") != PASS:
            self._json(401, {"error": "Non autorizzato"}); return

        # ── Salva config landing page ────────────────────────────────────────
        if parsed.path == "/save-landing-config":
            struttura = data.get("struttura", "").strip()
            if not struttura:
                self._json(400, {"error": "Struttura mancante"}); return
            config = data.get("config", {})
            cf = BASE / f"landing_config_{struttura}.json"
            cf.write_text(json.dumps(config, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        # ── Salva costi ──────────────────────────────────────────────────────
        if parsed.path == "/save-costs":
            costs = data.get("costs")
            if not costs:
                self._json(400, {"error": "Campo costs mancante"}); return
            (BASE / "costs.json").write_text(
                json.dumps(costs, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True}); return

        # ── Aggiungi spesa ───────────────────────────────────────────────────
        if parsed.path == "/add-expense":
            spesa = data.get("spesa")
            if not spesa:
                self._json(400, {"error": "Campo spesa mancante"}); return
            ef = BASE / "expenses.json"
            exp = json.loads(ef.read_text()) if ef.exists() else {"spese": []}
            exp["spese"].append(spesa)
            ef.write_text(json.dumps(exp, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True}); return

        # ── Elimina spesa ────────────────────────────────────────────────────
        if parsed.path == "/delete-expense":
            eid = data.get("id")
            if not eid:
                self._json(400, {"error": "Campo id mancante"}); return
            ef = BASE / "expenses.json"
            exp = json.loads(ef.read_text()) if ef.exists() else {"spese": []}
            exp["spese"] = [s for s in exp["spese"] if s.get("id") != eid]
            ef.write_text(json.dumps(exp, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True}); return

        # ── Salva presenza settimanale ────────────────────────────────────────
        if parsed.path == "/save-presence":
            presenza = data.get("presenza")
            if not presenza or not presenza.get("id"):
                self._json(400, {"error": "Campo presenza mancante"}); return
            pf = BASE / "presences.json"
            pres = json.loads(pf.read_text()) if pf.exists() else {"presenze": []}
            # sostituisce se già esiste stesso id, altrimenti aggiunge
            pres["presenze"] = [p for p in pres["presenze"] if p.get("id") != presenza["id"]]
            pres["presenze"].append(presenza)
            pres["presenze"].sort(key=lambda p: p.get("lun", ""))
            pf.write_text(json.dumps(pres, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True}); return

        # ── Elimina presenza ─────────────────────────────────────────────────
        if parsed.path == "/delete-presence":
            pid = data.get("id")
            if not pid:
                self._json(400, {"error": "Campo id mancante"}); return
            pf = BASE / "presences.json"
            pres = json.loads(pf.read_text()) if pf.exists() else {"presenze": []}
            pres["presenze"] = [p for p in pres["presenze"] if p.get("id") != pid]
            pf.write_text(json.dumps(pres, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True}); return

        # ── Salva utente ─────────────────────────────────────────────────────
        if parsed.path == "/save-user":
            user = data.get("user")
            if not user or not user.get("username"):
                self._json(400, {"error": "Campo user mancante"}); return
            uf = BASE / "users.json"
            users = json.loads(uf.read_text()) if uf.exists() else {"users": []}
            users["users"] = [u for u in users["users"] if u.get("username") != user["username"]]
            users["users"].append(user)
            uf.write_text(json.dumps(users, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True}); return

        # ── Elimina utente ───────────────────────────────────────────────────
        if parsed.path == "/delete-user":
            username = data.get("username")
            if not username:
                self._json(400, {"error": "Campo username mancante"}); return
            uf = BASE / "users.json"
            users = json.loads(uf.read_text()) if uf.exists() else {"users": []}
            users["users"] = [u for u in users["users"] if u.get("username") != username]
            uf.write_text(json.dumps(users, indent=2, ensure_ascii=False))
            # Elimina la dashboard dell'utente
            dash = BASE / f"dashboard_{username}.html"
            if dash.exists(): dash.unlink()
            self._regen()
            self._json(200, {"ok": True}); return

        # ── Salva presenza (utente limitato) ─────────────────────────────────
        if parsed.path == "/save-presence-user":
            # Autenticazione con password utente
            uf = BASE / "users.json"
            users_data = json.loads(uf.read_text()) if uf.exists() else {"users": []}
            uname = data.get("username","")
            upass = data.get("password","")
            user_rec = next((u for u in users_data["users"]
                             if u["username"]==uname and u["password"]==upass), None)
            if not user_rec:
                self._json(401, {"error": "Non autorizzato"}); return
            presenza = data.get("presenza")
            if not presenza or not presenza.get("id"):
                self._json(400, {"error": "Campo presenza mancante"}); return
            pf = BASE / "presences.json"
            pres = json.loads(pf.read_text()) if pf.exists() else {"presenze": []}
            pres["presenze"] = [p for p in pres["presenze"] if p.get("id") != presenza["id"]]
            pres["presenze"].append(presenza)
            pres["presenze"].sort(key=lambda p: p.get("lun", ""))
            pf.write_text(json.dumps(pres, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True}); return

        # ── Elimina presenza (utente limitato) ───────────────────────────────
        if parsed.path == "/delete-presence-user":
            uf = BASE / "users.json"
            users_data = json.loads(uf.read_text()) if uf.exists() else {"users": []}
            uname = data.get("username","")
            upass = data.get("password","")
            user_rec = next((u for u in users_data["users"]
                             if u["username"]==uname and u["password"]==upass), None)
            if not user_rec:
                self._json(401, {"error": "Non autorizzato"}); return
            pid = data.get("id")
            if not pid:
                self._json(400, {"error": "Campo id mancante"}); return
            pf = BASE / "presences.json"
            pres = json.loads(pf.read_text()) if pf.exists() else {"presenze": []}
            pres["presenze"] = [p for p in pres["presenze"] if p.get("id") != pid]
            pf.write_text(json.dumps(pres, indent=2, ensure_ascii=False))
            self._regen()
            self._json(200, {"ok": True}); return

        # ── Segna alert come letto (no auth — operazione non distruttiva) ───────
        if parsed.path == "/segna-alert-letto":
            idx = data.get("idx")
            af = BASE / "spectre_alerts.json"
            alerts = json.loads(af.read_text()) if af.exists() else {"alerts": []}
            if idx is not None and 0 <= idx < len(alerts["alerts"]):
                alerts["alerts"][idx]["letto"] = True
            af.write_text(json.dumps(alerts, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        # ── Segna TUTTI gli alert come letti (no auth — non distruttivo) ────────
        if parsed.path == "/segna-tutti-letti":
            af = BASE / "spectre_alerts.json"
            alerts = json.loads(af.read_text()) if af.exists() else {"alerts": []}
            for a in alerts["alerts"]:
                a["letto"] = True
            af.write_text(json.dumps(alerts, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        # ── Imposta stato postazione (solo admin) ─────────────────────────────
        if parsed.path == "/set-postazione-stato":
            if data.get("auth") != PASS:
                self._json(401, {"error": "Non autorizzato"}); return
            postazione = data.get("postazione", "").strip()
            stato      = data.get("stato", "attiva")
            if not postazione or stato not in ("attiva", "pausa"):
                self._json(400, {"error": "Parametri non validi"}); return
            cf = BASE / "postazioni_config.json"
            pc = json.loads(cf.read_text()) if cf.exists() else {}
            if postazione not in pc: pc[postazione] = {}
            pc[postazione]["stato"] = stato
            cf.write_text(json.dumps(pc, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True, "postazione": postazione, "stato": stato}); return

        # ── Attiva/disattiva monitoraggio EXIF per postazione ────────────────
        if parsed.path == "/set-exif-monitoring":
            if data.get("auth") != PASS:
                self._json(401, {"error": "Non autorizzato"}); return
            postazione = data.get("postazione", "").strip()
            abilitato  = bool(data.get("abilitato", False))
            if not postazione:
                self._json(400, {"error": "Parametri non validi"}); return
            cf = BASE / "postazioni_config.json"
            pc = json.loads(cf.read_text()) if cf.exists() else {}
            if postazione not in pc: pc[postazione] = {}
            pc[postazione]["exif_monitoring"] = abilitato
            cf.write_text(json.dumps(pc, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True, "postazione": postazione, "exif_monitoring": abilitato}); return

        # ── Pulisci tutti gli alert (solo admin) ──────────────────────────────
        if parsed.path == "/pulisci-alerts":
            if data.get("auth") != PASS:
                self._json(401, {"error": "Non autorizzato"}); return
            af = BASE / "spectre_alerts.json"
            af.write_text(json.dumps({"alerts": []}, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        # ── GET alerts (polling dashboard) ───────────────────────────────────
        # ── Tracking visita landing (pubblico) ───────────────────────────────
        if parsed.path == "/track-visit":
            struttura = data.get("struttura", "?")
            lang      = data.get("lang", "it")
            ts        = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            lf = BASE / "landing_stats.json"
            stats = json.loads(lf.read_text()) if lf.exists() else {"visits": [], "whatsapp_clicks": []}
            stats["visits"].append({"struttura": struttura, "timestamp": ts, "lang": lang})
            stats["visits"] = stats["visits"][-5000:]
            lf.write_text(json.dumps(stats, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        # ── Tracking click WhatsApp (pubblico) ────────────────────────────────
        if parsed.path == "/track-whatsapp":
            struttura = data.get("struttura", "?")
            ts        = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            lf = BASE / "landing_stats.json"
            stats = json.loads(lf.read_text()) if lf.exists() else {"visits": [], "whatsapp_clicks": []}
            stats["whatsapp_clicks"].append({"struttura": struttura, "timestamp": ts})
            stats["whatsapp_clicks"] = stats["whatsapp_clicks"][-5000:]
            lf.write_text(json.dumps(stats, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        # ── Salva slot fotografo (auth utente o admin) ────────────────────────
        if parsed.path == "/save-slots":
            struttura = data.get("struttura", "").strip()
            slots     = data.get("slots", [])
            whatsapp  = data.get("whatsapp", "")
            note      = data.get("note", "")
            if not struttura:
                self._json(400, {"error": "Struttura mancante"}); return
            # Auth: accetta admin PASS oppure qualsiasi struttura valida nel sistema
            uf = BASE / "users.json"
            users = json.loads(uf.read_text()) if uf.exists() else {"users": []}
            auth = data.get("auth", "")
            is_admin = (auth == PASS)
            strutture_valide = {s for u in users.get("users", []) for s in u.get("strutture", [])}
            is_user = struttura in strutture_valide
            if not is_admin and not is_user:
                self._json(401, {"error": "Non autorizzato"}); return
            sf = BASE / f"slots_{struttura}.json"
            sd = json.loads(sf.read_text()) if sf.exists() else {}
            sd["struttura"] = struttura
            sd["whatsapp"]  = whatsapp
            sd["note"]      = note
            sd["slots"]     = slots
            sf.write_text(json.dumps(sd, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        # ── Log accesso utente ───────────────────────────────────────────────
        if parsed.path == "/log-access":
            # Nessuna autenticazione richiesta — il browser lo chiama al login
            uname = data.get("username", "unknown")
            nome  = data.get("nome", uname)
            ip    = self.client_address[0]
            ts    = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            lf = BASE / "access_log.json"
            log = json.loads(lf.read_text()) if lf.exists() else {"accessi": []}
            log["accessi"].append({"username": uname, "nome": nome, "ip": ip, "ts": ts})
            # Mantieni solo gli ultimi 500 accessi
            log["accessi"] = log["accessi"][-500:]
            lf.write_text(json.dumps(log, indent=2, ensure_ascii=False))
            self._json(200, {"ok": True}); return

        self._json(404, {"error": "Not found"})

    def _serve_landing(self, struttura):
        sf = BASE / f"slots_{struttura}.json"
        sd = json.loads(sf.read_text()) if sf.exists() else {"whatsapp": "", "slots": [], "note": ""}
        cf = BASE / f"landing_config_{struttura}.json"
        landing_config = json.loads(cf.read_text()) if cf.exists() else {}
        html = LANDING_HTML \
            .replace("__STRUTTURA__", struttura) \
            .replace("__WHATSAPP__",  sd.get("whatsapp", "")) \
            .replace("__NOTE__",      sd.get("note", "")) \
            .replace("__SLOTS_JSON__", json.dumps(sd.get("slots", []), ensure_ascii=False)) \
            .replace("__LANDING_CONFIG__", json.dumps(landing_config, ensure_ascii=False))
        body = html.encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type",   "text/html; charset=utf-8")
        self.send_header("Content-Length", len(body))
        self.end_headers()
        self.wfile.write(body)

    def _regen(self):
        subprocess.Popen(
            ["python3", str(BASE / "export_dashboard.py")],
            cwd=str(BASE), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    def _cors(self):
        self.send_header("Access-Control-Allow-Origin",  "*")
        self.send_header("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")

    def _json(self, code, data):
        body = json.dumps(data, ensure_ascii=False).encode()
        self.send_response(code)
        self._cors()
        self.send_header("Content-Type",   "application/json")
        self.send_header("Content-Length", len(body))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, fmt, *args):
        pass

LANDING_HTML = """<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>__STRUTTURA__ · Fotografia</title>
<meta name="description" content="Prenota il tuo shooting fotografico professionale"/>
<style>
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;background:#F8F6F3;color:#1C1918;overflow-x:hidden}
a{text-decoration:none;color:inherit}
/* NAV */
nav{position:fixed;top:0;left:0;right:0;z-index:999;background:rgba(248,246,243,.96);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
    border-bottom:1px solid #EAE4DC;padding:0 28px;height:60px;display:flex;align-items:center;justify-content:space-between}
.nav-logo{font-size:.92rem;font-weight:600;color:#1C1918;display:flex;align-items:center;gap:9px;letter-spacing:-.1px}
.nav-logo svg{color:#B8935A;flex-shrink:0}
.nav-logo span{color:#B8935A;font-weight:700}
.lang-bar{display:flex;gap:2px}
.lang-btn{background:none;border:none;border-radius:4px;padding:5px 9px;font-size:.7rem;font-weight:600;
           cursor:pointer;color:#A09890;transition:.15s;letter-spacing:.5px}
.lang-btn:hover{color:#1C1918}
.lang-btn.on{color:#1C1918;background:#EDE8E0}
/* HERO */
.hero{min-height:100vh;background:#1A1714;
      display:flex;flex-direction:column;align-items:center;justify-content:center;
      text-align:center;padding:80px 24px 60px;position:relative;overflow:hidden}
.hero::before{content:'';position:absolute;inset:0;
  background:radial-gradient(ellipse 75% 55% at 50% 38%,rgba(184,147,90,.13) 0%,transparent 68%)}
.hero-badge{display:inline-flex;align-items:center;gap:7px;
             border:1px solid rgba(184,147,90,.38);border-radius:2px;
             padding:7px 18px;font-size:.68rem;font-weight:600;color:#C8A97A;
             letter-spacing:1.4px;text-transform:uppercase;margin-bottom:30px}
.hero h1{font-size:clamp(2.2rem,5.5vw,4rem);font-weight:700;color:#F5F0E8;line-height:1.06;
          margin-bottom:20px;letter-spacing:-.6px}
.hero h1 em{color:#C8A97A;font-style:normal}
.hero p{font-size:clamp(.92rem,2vw,1.1rem);color:rgba(245,240,232,.58);max-width:460px;
         line-height:1.75;margin-bottom:42px}
.hero-btns{display:flex;gap:12px;flex-wrap:wrap;justify-content:center}
.btn-wa{background:#22C55E;color:#fff;border:none;border-radius:5px;padding:13px 26px;
         font-size:.88rem;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;gap:8px;
         letter-spacing:.1px;transition:.18s;min-height:48px}
.btn-wa:hover{background:#16A34A}
.btn-sec{background:transparent;color:rgba(245,240,232,.82);
          border:1px solid rgba(245,240,232,.28);
          border-radius:5px;padding:13px 26px;font-size:.88rem;font-weight:500;cursor:pointer;
          letter-spacing:.1px;transition:.18s;min-height:48px;display:inline-flex;align-items:center;gap:8px}
.btn-sec:hover{border-color:rgba(245,240,232,.55);color:#F5F0E8}
.scroll-hint{position:absolute;bottom:28px;left:50%;transform:translateX(-50%);
              color:rgba(245,240,232,.28);font-size:.65rem;letter-spacing:1px;text-transform:uppercase;
              display:flex;flex-direction:column;align-items:center;gap:8px}
.scroll-hint svg{animation:bob .7s ease-in-out infinite alternate}
@keyframes bob{from{transform:translateY(0)}to{transform:translateY(5px)}}
/* SECTIONS */
section{padding:80px 24px;max-width:920px;margin:0 auto}
.sec-label{font-size:.63rem;font-weight:700;letter-spacing:2.2px;text-transform:uppercase;
            color:#B8935A;margin-bottom:12px}
.sec-title{font-size:clamp(1.55rem,3.5vw,2.35rem);font-weight:700;color:#1C1918;
            margin-bottom:12px;line-height:1.12;letter-spacing:-.35px}
.sec-sub{font-size:.92rem;color:#7A706A;max-width:500px;line-height:1.72;margin-bottom:44px}
/* SERVICES */
.services-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:1px;
               background:#EAE4DC;border:1px solid #EAE4DC;border-radius:12px;overflow:hidden}
.svc-card{background:#FDFBF8;padding:32px 28px;transition:.18s}
.svc-card:hover{background:#fff}
.svc-icon{width:42px;height:42px;border-radius:10px;background:#F0E9DF;
           display:flex;align-items:center;justify-content:center;margin-bottom:18px;color:#B8935A}
.svc-name{font-size:.98rem;font-weight:600;color:#1C1918;margin-bottom:8px;letter-spacing:-.1px}
.svc-desc{font-size:.83rem;color:#7A706A;line-height:1.68}
/* GALLERY */
.gallery-wrap{overflow-x:auto;padding-bottom:8px;-webkit-overflow-scrolling:touch;
               scrollbar-width:thin;scrollbar-color:#E0D8CE transparent}
.gallery-wrap::-webkit-scrollbar{height:3px}
.gallery-wrap::-webkit-scrollbar-thumb{background:#DDD5C8;border-radius:4px}
.gallery-strip{display:flex;gap:12px;padding:4px 0}
.gallery-card{flex-shrink:0;width:232px;height:300px;border-radius:10px;overflow:hidden;
               background:#26221E;position:relative;cursor:pointer}
.gallery-card:nth-child(1){background:linear-gradient(158deg,#1C2C3C,#2E4860)}
.gallery-card:nth-child(2){background:linear-gradient(158deg,#2A1F18,#58362A)}
.gallery-card:nth-child(3){background:linear-gradient(158deg,#1A2A1C,#2E4830)}
.gallery-card:nth-child(4){background:linear-gradient(158deg,#2A281A,#4A4020)}
.gallery-card:nth-child(5){background:linear-gradient(158deg,#1C1A28,#342E4A)}
.gallery-card-overlay{position:absolute;bottom:0;left:0;right:0;height:90px;
                       background:linear-gradient(transparent,rgba(8,6,4,.72))}
.gallery-card-label{position:absolute;bottom:14px;left:16px;right:12px;
                      color:rgba(255,255,255,.82);font-size:.75rem;font-weight:500;letter-spacing:.1px}
/* PRICING */
.pricing-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:16px}
.price-card{background:#fff;border-radius:10px;padding:30px 24px;
             border:1px solid #E8E2D8;transition:.18s;position:relative}
.price-card:hover:not(.featured){border-color:#C8A97A;box-shadow:0 4px 24px rgba(184,147,90,.08)}
.price-card.featured{background:#1C1918;border-color:#1C1918;color:#F5F0E8}
.price-card.featured .price-name{color:#B8935A}
.price-card.featured .price-val{color:#F5F0E8}
.price-card.featured .price-unit{color:rgba(245,240,232,.45)}
.price-card.featured .price-features li{color:rgba(245,240,232,.7);border-bottom-color:rgba(255,255,255,.07)}
.price-card.featured .price-features li::before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none' stroke='%23B8935A' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 7.5l3 3 6-6'/%3E%3C/svg%3E");background-color:rgba(184,147,90,.2)}
.price-popular{position:absolute;top:-1px;left:50%;transform:translateX(-50%);
               background:#B8935A;color:#fff;font-size:.6rem;font-weight:700;
               padding:3px 14px;border-radius:0 0 6px 6px;letter-spacing:.8px;
               text-transform:uppercase;white-space:nowrap}
.price-name{font-size:.67rem;font-weight:700;text-transform:uppercase;
             letter-spacing:1.4px;color:#B8935A;margin-bottom:14px}
.price-val{font-size:2.6rem;font-weight:700;color:#1C1918;letter-spacing:-1.5px;margin-bottom:2px}
.price-unit{font-size:.76rem;color:#A09890;margin-bottom:20px}
.price-features{text-align:left;list-style:none}
.price-features li{font-size:.82rem;color:#5A5250;padding:6px 0;
                    border-bottom:1px solid #F0E8DE;display:flex;align-items:center;gap:9px}
.price-features li:last-child{border-bottom:none}
.price-features li::before{content:'';width:15px;height:15px;flex-shrink:0;border-radius:50%;
  background:#F0E8DE url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none' stroke='%23B8935A' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 7.5l3 3 6-6'/%3E%3C/svg%3E") center/cover}
/* AGENDA */
#agenda{background:#fff;border-radius:12px;padding:44px 36px;
         border:1px solid #E8E2D8;box-shadow:0 2px 20px rgba(28,25,24,.04);
         max-width:920px;margin:0 auto 72px}
.week-nav{display:flex;align-items:center;gap:12px;margin-bottom:24px}
.week-nav button{background:#F8F6F3;border:1px solid #E8E2D8;border-radius:6px;
                  width:36px;height:36px;cursor:pointer;font-size:.9rem;
                  display:flex;align-items:center;justify-content:center;
                  transition:.15s;color:#1C1918}
.week-nav button:hover{background:#1C1918;border-color:#1C1918;color:#fff}
.week-label{font-weight:600;color:#1C1918;font-size:.9rem;flex:1;text-align:center;letter-spacing:-.1px}
.slots-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px}
.slot-chip{background:#F8F6F3;border:1px solid #E8E2D8;border-radius:8px;padding:12px 10px;
            text-align:center;cursor:pointer;transition:.18s;user-select:none}
.slot-chip:hover{border-color:#B8935A;background:#FDFAF6}
.slot-chip.selected{background:#F5EDE0;border-color:#B8935A;
                     box-shadow:0 0 0 2px rgba(184,147,90,.18)}
.slot-chip.booked{background:#F5F4F2;border-color:#EAE6E2;cursor:default;opacity:.42}
.slot-date{font-size:.63rem;color:#A09890;font-weight:600;text-transform:uppercase;letter-spacing:.6px}
.slot-time{font-size:1.05rem;font-weight:700;color:#1C1918;margin:3px 0}
.slot-status{font-size:.62rem;color:#B8935A;font-weight:600;letter-spacing:.3px}
.slot-chip.booked .slot-status{color:#BDB8B5}
.no-slots{text-align:center;padding:36px;color:#A09890;font-size:.88rem;line-height:1.7}
/* CTA */
.cta-box{background:#1A1714;border-radius:12px;padding:60px 40px;text-align:center;
          color:#F5F0E8;margin:0 auto 80px;max-width:920px;position:relative;overflow:hidden}
.cta-box::before{content:'';position:absolute;top:-40%;right:-8%;width:380px;height:380px;
  border-radius:50%;background:radial-gradient(circle,rgba(184,147,90,.14) 0%,transparent 68%);pointer-events:none}
.cta-box h2{font-size:clamp(1.5rem,3.5vw,2.2rem);font-weight:700;margin-bottom:12px;letter-spacing:-.35px}
.cta-box p{color:rgba(245,240,232,.55);font-size:.92rem;margin-bottom:36px;line-height:1.75;
            max-width:400px;margin-left:auto;margin-right:auto}
.slot-selected-info{background:rgba(184,147,90,.13);border:1px solid rgba(184,147,90,.28);
                     border-radius:6px;padding:10px 20px;font-size:.83rem;color:#C8A97A;
                     margin-bottom:24px;display:none;letter-spacing:.1px}
/* FOOTER */
footer{background:#F0EAE0;color:#A09890;text-align:center;padding:24px;
        font-size:.73rem;letter-spacing:.2px;border-top:1px solid #E8E0D4}
footer strong{color:#1C1918;font-weight:600}
@media(max-width:600px){
  section{padding:60px 18px}
  #agenda{padding:28px 18px;margin-bottom:48px}
  .cta-box{padding:40px 22px;margin-bottom:48px}
  .gallery-card{width:188px;height:248px}
  .hero-btns{flex-direction:column;align-items:stretch}
  .btn-wa,.btn-sec{justify-content:center}
}
</style>
</head>
<body>

<nav>
  <div class="nav-logo">
    <svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/><circle cx="12" cy="13" r="4"/></svg>
    <span>__STRUTTURA__</span>
  </div>
  <div class="lang-bar">
    <button class="lang-btn on" onclick="setLang('it')">IT</button>
    <button class="lang-btn" onclick="setLang('en')">EN</button>
    <button class="lang-btn" onclick="setLang('fr')">FR</button>
    <button class="lang-btn" onclick="setLang('de')">DE</button>
  </div>
</nav>

<!-- HERO -->
<div class="hero" id="top">
  <div class="hero-badge">
    <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></svg>
    <span data-i18n="badge">Fotografia Professionale</span>
  </div>
  <h1 data-i18n="hero_h1">I tuoi ricordi<br><em>immortalati per sempre</em></h1>
  <p data-i18n="hero_p">Shooting fotografici professionali in spiaggia, all'aperto e nei villaggi. Stampe di qualità, ricordi unici.</p>
  <div class="hero-btns">
    <button class="btn-wa" onclick="contattaWA()">
      <svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg>
      <span data-i18n="btn_wa">Contatta il fotografo</span>
    </button>
    <button class="btn-sec" onclick="document.getElementById('agenda-sec').scrollIntoView({behavior:'smooth'})">
      <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
      <span data-i18n="btn_agenda">Vedi le disponibilità</span>
    </button>
  </div>
  <div class="scroll-hint">
    <span data-i18n="scroll_hint">Scorri per scoprire</span>
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
  </div>
</div>

<!-- SERVICES -->
<section>
  <div class="sec-label" data-i18n="svc_label">I NOSTRI SERVIZI</div>
  <div class="sec-title" data-i18n="svc_title">Cosa offriamo</div>
  <div class="sec-sub" data-i18n="svc_sub">Ogni momento merita di essere ricordato. Scegliamo insieme il tipo di shooting più adatto a te.</div>
  <div class="services-grid">
    <div class="svc-card">
      <div class="svc-icon">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12c2-2.5 4-4 6-4s4 3 6 3 4-4 6-4"/><path d="M2 17c2-2.5 4-4 6-4s4 3 6 3 4-4 6-4"/></svg>
      </div>
      <div class="svc-name" data-i18n="s1_name">Spiaggia &amp; Mare</div>
      <div class="svc-desc" data-i18n="s1_desc">Ritratti naturali con la luce del sole, l'acqua e la sabbia come sfondo.</div>
    </div>
    <div class="svc-card">
      <div class="svc-icon">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
      </div>
      <div class="svc-name" data-i18n="s2_name">Ritratti di Famiglia</div>
      <div class="svc-desc" data-i18n="s2_desc">Sessioni rilassate per immortalare i momenti più preziosi con i tuoi cari.</div>
    </div>
    <div class="svc-card">
      <div class="svc-icon">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
      </div>
      <div class="svc-name" data-i18n="s3_name">Tramonto d'Oro</div>
      <div class="svc-desc" data-i18n="s3_desc">Le ore d'oro regalano luci spettacolari. Un'esperienza fotografica unica.</div>
    </div>
  </div>
</section>

<!-- GALLERY -->
<section>
  <div class="sec-label" data-i18n="gal_label">GALLERIA</div>
  <div class="sec-title" data-i18n="gal_title">I nostri scatti</div>
  <div class="gallery-wrap">
    <div class="gallery-strip">
      <div class="gallery-card"><div class="gallery-card-overlay"></div><div class="gallery-card-label" data-i18n="gal_1">Famiglia in spiaggia</div></div>
      <div class="gallery-card"><div class="gallery-card-overlay"></div><div class="gallery-card-label" data-i18n="gal_2">Ritratto al tramonto</div></div>
      <div class="gallery-card"><div class="gallery-card-overlay"></div><div class="gallery-card-label" data-i18n="gal_3">Coppia nel parco</div></div>
      <div class="gallery-card"><div class="gallery-card-overlay"></div><div class="gallery-card-label" data-i18n="gal_4">Avventura estiva</div></div>
      <div class="gallery-card"><div class="gallery-card-overlay"></div><div class="gallery-card-label" data-i18n="gal_5">Momenti speciali</div></div>
    </div>
  </div>
</section>

<!-- PRICING -->
<section>
  <div class="sec-label" data-i18n="price_label">LISTINO PREZZI</div>
  <div class="sec-title" data-i18n="price_title">Scegli il tuo pacchetto</div>
  <div class="sec-sub" data-i18n="price_sub">Stampe di alta qualità e file digitali. Tutto incluso nel prezzo.</div>
  <div class="pricing-grid">
    <div class="price-card">
      <div class="price-name" data-i18n="p1_name">MINI</div>
      <div class="price-val">€20</div>
      <div class="price-unit" data-i18n="p1_unit">1 foto stampata</div>
      <ul class="price-features">
        <li data-i18n="p1_f1">1 foto formato 15×20</li>
        <li data-i18n="p1_f2">Ritiro immediato</li>
        <li data-i18n="p1_f3">Stampa professionale</li>
      </ul>
    </div>
    <div class="price-card featured">
      <div class="price-popular">Più popolare</div>
      <div class="price-name" data-i18n="p2_name">FAMILY</div>
      <div class="price-val">€50</div>
      <div class="price-unit" data-i18n="p2_unit">3 foto stampate</div>
      <ul class="price-features">
        <li data-i18n="p2_f1">3 foto formato 15×20</li>
        <li data-i18n="p2_f2">Shooting 20 minuti</li>
        <li data-i18n="p2_f3">File digitali inclusi</li>
        <li data-i18n="p2_f4">Scelta delle pose</li>
      </ul>
    </div>
    <div class="price-card">
      <div class="price-name" data-i18n="p3_name">PREMIUM</div>
      <div class="price-val">€80</div>
      <div class="price-unit" data-i18n="p3_unit">5 foto stampate</div>
      <ul class="price-features">
        <li data-i18n="p3_f1">5 foto formato 15×20</li>
        <li data-i18n="p3_f2">Shooting 45 minuti</li>
        <li data-i18n="p3_f3">File digitali inclusi</li>
        <li data-i18n="p3_f4">Foto grande 20×30</li>
      </ul>
    </div>
  </div>
</section>

<!-- AGENDA -->
<div id="agenda-sec" style="padding:0 24px">
<div id="agenda">
  <div class="sec-label" data-i18n="ag_label">DISPONIBILITÀ</div>
  <div class="sec-title" data-i18n="ag_title">Prenota il tuo shooting</div>
  <div class="sec-sub" data-i18n="ag_sub" style="margin-bottom:20px">Seleziona una slot e poi contattaci su WhatsApp.</div>
  <div class="week-nav">
    <button onclick="cambiaSettimana(-1)">&#8592;</button>
    <div class="week-label" id="weekLabel">—</div>
    <button onclick="cambiaSettimana(1)">&#8594;</button>
  </div>
  <div class="slots-grid" id="slotsGrid"></div>
</div>
</div>

<!-- CTA -->
<div style="padding:0 24px">
<div class="cta-box">
  <h2 data-i18n="cta_title">Pronto per il tuo shooting?</h2>
  <p data-i18n="cta_p">Contattaci su WhatsApp — risponderemo entro pochi minuti per confermare la tua prenotazione.</p>
  <div class="slot-selected-info" id="slotInfo"></div>
  <button class="btn-wa" onclick="contattaWA()" style="margin:0 auto;font-size:.92rem;padding:14px 32px">
    <svg width="19" height="19" viewBox="0 0 24 24" fill="currentColor"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg>
    <span data-i18n="cta_btn">Contatta su WhatsApp</span>
  </button>
</div>
</div>

<footer>
  <strong>CreativaPro</strong> &nbsp;&middot;&nbsp; __STRUTTURA__ &nbsp;&middot;&nbsp; <span data-i18n="footer_txt">Fotografia professionale in vacanza</span>
</footer>

<script>
const STRUTTURA      = '__STRUTTURA__';
const WA_NUMBER      = '__WHATSAPP__'.replace(/[^0-9+]/g,'');
const ALL_SLOTS      = __SLOTS_JSON__;
const NOTE           = '__NOTE__';
const LANDING_CONFIG = __LANDING_CONFIG__;
let selectedSlot  = null;
let weekOffset    = 0;
let currentLang   = navigator.language.slice(0,2);
if (!['it','en','fr','de'].includes(currentLang)) currentLang = 'it';

// ── Traduzioni ────────────────────────────────────────────────────────────────
const T = {
  it: {
    badge:'📍 Fotografia Professionale',
    hero_h1:'I tuoi ricordi<br><em>immortalati per sempre</em>',
    hero_p:'Shooting fotografici professionali in spiaggia, all\'aperto e nei villaggi. Stampe di qualità, ricordi unici.',
    btn_wa:'Contatta il fotografo', btn_agenda:'📅 Vedi le disponibilità',
    scroll_hint:'Scorri per scoprire',
    svc_label:'I NOSTRI SERVIZI', svc_title:'Cosa offriamo',
    svc_sub:'Ogni momento merita di essere ricordato. Scegliamo insieme il tipo di shooting più adatto a te.',
    s1_name:'Spiaggia & Mare', s1_desc:'Ritratti naturali con la luce del sole, l\'acqua e la sabbia come sfondo.',
    s2_name:'Ritratti di Famiglia', s2_desc:'Sessioni rilassate per immortalare i momenti più preziosi con i tuoi cari.',
    s3_name:'Tramonto d\'Oro', s3_desc:'Le ore d\'oro regalano luci spettacolari. Un\'esperienza fotografica unica.',
    gal_label:'GALLERIA', gal_title:'I nostri scatti',
    gal_1:'Famiglia in spiaggia', gal_2:'Ritratto al tramonto', gal_3:'Coppia nel parco',
    gal_4:'Avventura estiva', gal_5:'Momenti speciali',
    price_label:'LISTINO PREZZI', price_title:'Scegli il tuo pacchetto',
    price_sub:'Stampe di alta qualità e file digitali. Tutto incluso nel prezzo.',
    p1_name:'MINI', p1_unit:'1 foto stampata', p1_f1:'1 foto formato 15×20', p1_f2:'Ritiro immediato', p1_f3:'Stampa professionale',
    p2_name:'FAMILY', p2_unit:'3 foto stampate', p2_f1:'3 foto formato 15×20', p2_f2:'Shooting 20 minuti', p2_f3:'File digitali inclusi', p2_f4:'Scelta delle pose',
    p3_name:'PREMIUM', p3_unit:'5 foto stampate', p3_f1:'5 foto formato 15×20', p3_f2:'Shooting 45 minuti', p3_f3:'File digitali inclusi', p3_f4:'Foto grande 20×30',
    ag_label:'DISPONIBILITÀ', ag_title:'Prenota il tuo shooting',
    ag_sub:'Seleziona una slot e poi contattaci su WhatsApp.',
    slot_avail:'Disponibile', slot_booked:'Occupato', slot_sel:'✓ Selezionato',
    no_slots:'Nessuna disponibilità per questa settimana.<br>Contattaci direttamente su WhatsApp!',
    cta_title:'Pronto per il tuo shooting? 📸',
    cta_p:'Contattaci su WhatsApp — risponderemo entro pochi minuti.',
    cta_btn:'Contatta su WhatsApp',
    slot_info:'📅 Slot selezionata:',
    footer_txt:'Fotografia professionale in vacanza'
  },
  en: {
    badge:'📍 Professional Photography',
    hero_h1:'Your memories<br><em>captured forever</em>',
    hero_p:'Professional photo shoots on the beach, outdoors and in holiday villages. Quality prints, unique memories.',
    btn_wa:'Contact the photographer', btn_agenda:'📅 See availability',
    scroll_hint:'Scroll to discover',
    svc_label:'OUR SERVICES', svc_title:'What we offer',
    svc_sub:'Every moment deserves to be remembered. Let\'s choose the perfect type of shoot together.',
    s1_name:'Beach & Sea', s1_desc:'Natural portraits with sunlight, water and sand as your backdrop.',
    s2_name:'Family Portraits', s2_desc:'Relaxed sessions to capture the most precious moments with your loved ones.',
    s3_name:'Golden Hour', s3_desc:'The golden hour offers spectacular light. A unique and romantic photography experience.',
    gal_label:'GALLERY', gal_title:'Our shots',
    gal_1:'Family on the beach', gal_2:'Sunset portrait', gal_3:'Couple in the park',
    gal_4:'Summer adventure', gal_5:'Special moments',
    price_label:'PRICE LIST', price_title:'Choose your package',
    price_sub:'High quality prints and digital files. All included in the price.',
    p1_name:'MINI', p1_unit:'1 printed photo', p1_f1:'1 photo 15×20 format', p1_f2:'Immediate pickup', p1_f3:'Professional print',
    p2_name:'FAMILY', p2_unit:'3 printed photos', p2_f1:'3 photos 15×20 format', p2_f2:'20-minute shoot', p2_f3:'Digital files included', p2_f4:'Choice of poses',
    p3_name:'PREMIUM', p3_unit:'5 printed photos', p3_f1:'5 photos 15×20 format', p3_f2:'45-minute shoot', p3_f3:'Digital files included', p3_f4:'Large 20×30 photo',
    ag_label:'AVAILABILITY', ag_title:'Book your shoot',
    ag_sub:'Select a slot then contact us on WhatsApp.',
    slot_avail:'Available', slot_booked:'Booked', slot_sel:'✓ Selected',
    no_slots:'No availability this week.<br>Contact us directly on WhatsApp!',
    cta_title:'Ready for your shoot? 📸',
    cta_p:'Contact us on WhatsApp — we\'ll reply within minutes.',
    cta_btn:'Contact on WhatsApp',
    slot_info:'📅 Selected slot:',
    footer_txt:'Professional holiday photography'
  },
  fr: {
    badge:'📍 Photographie Professionnelle',
    hero_h1:'Vos souvenirs<br><em>immortalisés pour toujours</em>',
    hero_p:'Séances photo professionnelles sur la plage, en plein air et dans les villages de vacances.',
    btn_wa:'Contacter le photographe', btn_agenda:'📅 Voir les disponibilités',
    scroll_hint:'Faites défiler pour découvrir',
    svc_label:'NOS SERVICES', svc_title:'Ce que nous offrons',
    svc_sub:'Chaque moment mérite d\'être immortalisé. Choisissons ensemble le type de séance idéal.',
    s1_name:'Plage & Mer', s1_desc:'Portraits naturels avec la lumière du soleil, l\'eau et le sable en toile de fond.',
    s2_name:'Portraits de Famille', s2_desc:'Séances décontractées pour capturer les moments les plus précieux avec vos proches.',
    s3_name:'Heure Dorée', s3_desc:'L\'heure dorée offre une lumière spectaculaire. Une expérience photographique unique.',
    gal_label:'GALERIE', gal_title:'Nos clichés',
    gal_1:'Famille à la plage', gal_2:'Portrait au coucher du soleil', gal_3:'Couple dans le parc',
    gal_4:'Aventure estivale', gal_5:'Moments spéciaux',
    price_label:'TARIFS', price_title:'Choisissez votre forfait',
    price_sub:'Tirages de haute qualité et fichiers numériques. Tout inclus.',
    p1_name:'MINI', p1_unit:'1 photo imprimée', p1_f1:'1 photo format 15×20', p1_f2:'Retrait immédiat', p1_f3:'Impression professionnelle',
    p2_name:'FAMILLE', p2_unit:'3 photos imprimées', p2_f1:'3 photos format 15×20', p2_f2:'Séance de 20 minutes', p2_f3:'Fichiers numériques inclus', p2_f4:'Choix des poses',
    p3_name:'PREMIUM', p3_unit:'5 photos imprimées', p3_f1:'5 photos format 15×20', p3_f2:'Séance de 45 minutes', p3_f3:'Fichiers numériques inclus', p3_f4:'Grande photo 20×30',
    ag_label:'DISPONIBILITÉS', ag_title:'Réservez votre séance',
    ag_sub:'Sélectionnez un créneau puis contactez-nous sur WhatsApp.',
    slot_avail:'Disponible', slot_booked:'Réservé', slot_sel:'✓ Sélectionné',
    no_slots:'Aucune disponibilité cette semaine.<br>Contactez-nous directement sur WhatsApp !',
    cta_title:'Prêt pour votre séance ? 📸',
    cta_p:'Contactez-nous sur WhatsApp — nous répondons en quelques minutes.',
    cta_btn:'Contacter sur WhatsApp',
    slot_info:'📅 Créneau sélectionné :',
    footer_txt:'Photographie professionnelle en vacances'
  },
  de: {
    badge:'📍 Professionelle Fotografie',
    hero_h1:'Ihre Erinnerungen<br><em>für immer festgehalten</em>',
    hero_p:'Professionelle Fotoshootings am Strand, im Freien und in Ferienvillen. Hochwertige Drucke, einzigartige Erinnerungen.',
    btn_wa:'Fotografen kontaktieren', btn_agenda:'📅 Verfügbarkeit anzeigen',
    scroll_hint:'Scrollen um zu entdecken',
    svc_label:'UNSERE DIENSTE', svc_title:'Was wir anbieten',
    svc_sub:'Jeder Moment verdient es, erinnert zu werden. Gemeinsam wählen wir die perfekte Art des Shootings.',
    s1_name:'Strand & Meer', s1_desc:'Natürliche Porträts mit Sonnenlicht, Wasser und Sand als Hintergrund.',
    s2_name:'Familienporträts', s2_desc:'Entspannte Sessions, um die wertvollsten Momente mit Ihren Lieben festzuhalten.',
    s3_name:'Goldene Stunde', s3_desc:'Die goldene Stunde bietet spektakuläres Licht. Ein einzigartiges Fotografieerlebnis.',
    gal_label:'GALERIE', gal_title:'Unsere Aufnahmen',
    gal_1:'Familie am Strand', gal_2:'Porträt beim Sonnenuntergang', gal_3:'Pärchen im Park',
    gal_4:'Sommerabenteuer', gal_5:'Besondere Momente',
    price_label:'PREISLISTE', price_title:'Wählen Sie Ihr Paket',
    price_sub:'Hochwertige Drucke und digitale Dateien. Alles inklusive.',
    p1_name:'MINI', p1_unit:'1 gedrucktes Foto', p1_f1:'1 Foto Format 15×20', p1_f2:'Sofortige Abholung', p1_f3:'Professioneller Druck',
    p2_name:'FAMILIE', p2_unit:'3 gedruckte Fotos', p2_f1:'3 Fotos Format 15×20', p2_f2:'20-minütiges Shooting', p2_f3:'Digitale Dateien inklusive', p2_f4:'Posenauswahl',
    p3_name:'PREMIUM', p3_unit:'5 gedruckte Fotos', p3_f1:'5 Fotos Format 15×20', p3_f2:'45-minütiges Shooting', p3_f3:'Digitale Dateien inklusive', p3_f4:'Großes Foto 20×30',
    ag_label:'VERFÜGBARKEIT', ag_title:'Ihr Shooting buchen',
    ag_sub:'Wählen Sie einen Slot und kontaktieren Sie uns dann auf WhatsApp.',
    slot_avail:'Verfügbar', slot_booked:'Gebucht', slot_sel:'✓ Ausgewählt',
    no_slots:'Keine Verfügbarkeit diese Woche.<br>Kontaktieren Sie uns direkt auf WhatsApp!',
    cta_title:'Bereit für Ihr Shooting? 📸',
    cta_p:'Kontaktieren Sie uns auf WhatsApp — wir antworten innerhalb weniger Minuten.',
    cta_btn:'Auf WhatsApp kontaktieren',
    slot_info:'📅 Ausgewählter Slot:',
    footer_txt:'Professionelle Urlaubsfotografie'
  }
};

function t(k){ return (T[currentLang]||T['it'])[k] || k; }

function setLang(l){
  currentLang = l;
  document.querySelectorAll('.lang-btn').forEach(b => b.classList.toggle('on', b.textContent === l.toUpperCase()));
  document.querySelectorAll('[data-i18n]').forEach(el => {
    const k = el.getAttribute('data-i18n');
    if(T[l] && T[l][k]) el.innerHTML = T[l][k];
  });
  renderSlots();
}

// ── Agenda ────────────────────────────────────────────────────────────────────
function getMondayOfWeek(offset){
  const d = new Date();
  const day = d.getDay() || 7;
  d.setDate(d.getDate() - day + 1 + offset * 7);
  d.setHours(0,0,0,0);
  return d;
}
function fmtDate(d){ return d.toISOString().slice(0,10); }
function fmtLabel(d){
  return d.toLocaleDateString(currentLang === 'it' ? 'it-IT' : currentLang === 'de' ? 'de-DE' : currentLang === 'fr' ? 'fr-FR' : 'en-GB',
    {day:'2-digit', month:'short'});
}

function cambiaSettimana(dir){ weekOffset += dir; renderSlots(); }

function renderSlots(){
  const mon = getMondayOfWeek(weekOffset);
  const sun = new Date(mon); sun.setDate(mon.getDate()+6);
  document.getElementById('weekLabel').textContent = fmtLabel(mon) + ' – ' + fmtLabel(sun);
  const grid = document.getElementById('slotsGrid');
  const weekSlots = ALL_SLOTS.filter(s => {
    const d = new Date(s.data);
    return d >= mon && d <= sun;
  }).sort((a,b) => (a.data+a.orario).localeCompare(b.data+b.orario));
  if(!weekSlots.length){
    grid.innerHTML = '<div class="no-slots">' + t('no_slots') + '</div>';
    return;
  }
  grid.innerHTML = weekSlots.map((s,i) => {
    const d = new Date(s.data);
    const isSelected = selectedSlot && selectedSlot.data === s.data && selectedSlot.orario === s.orario;
    const isBooked = !s.disponibile;
    const cls = isBooked ? 'slot-chip booked' : isSelected ? 'slot-chip selected' : 'slot-chip';
    const statusTxt = isBooked ? t('slot_booked') : isSelected ? t('slot_sel') : t('slot_avail');
    return '<div class="' + cls + '" ' + (isBooked ? '' : 'onclick="selezionaSlot(' + i + ')"') + '>' +
      '<div class="slot-date">' + fmtLabel(d) + '</div>' +
      '<div class="slot-time">' + s.orario + '</div>' +
      '<div class="slot-status">' + statusTxt + '</div></div>';
  }).join('');
}

function selezionaSlot(idx){
  const mon = getMondayOfWeek(weekOffset);
  const sun = new Date(mon); sun.setDate(mon.getDate()+6);
  const weekSlots = ALL_SLOTS.filter(s => {
    const d = new Date(s.data); return d >= mon && d <= sun && s.disponibile;
  }).sort((a,b) => (a.data+a.orario).localeCompare(b.data+b.orario));
  const s = weekSlots[idx];
  if(!s) return;
  selectedSlot = (selectedSlot && selectedSlot.data === s.data && selectedSlot.orario === s.orario) ? null : s;
  const info = document.getElementById('slotInfo');
  if(selectedSlot){
    const d = new Date(selectedSlot.data);
    info.innerHTML = t('slot_info') + ' <strong>' + fmtLabel(d) + ' ore ' + selectedSlot.orario + '</strong>';
    info.style.display = 'block';
    document.getElementById('agenda-sec').scrollIntoView({behavior:'smooth'});
  } else {
    info.style.display = 'none';
  }
  renderSlots();
}

// ── WhatsApp ──────────────────────────────────────────────────────────────────
function contattaWA(){
  fetch('/track-whatsapp', {method:'POST',headers:{'Content-Type':'application/json'},
    body: JSON.stringify({struttura: STRUTTURA})}).catch(()=>{});
  if(!WA_NUMBER){ alert('Numero WhatsApp non configurato.'); return; }
  window.open('https://wa.me/' + WA_NUMBER.replace('+',''), '_blank');
}

// ── Applica config personalizzata landing ─────────────────────────────────────
(function applyLandingConfig(){
  if(!LANDING_CONFIG || !Object.keys(LANDING_CONFIG).length) return;
  // Override traduzioni
  ['it','en','fr','de'].forEach(function(lang){
    if(LANDING_CONFIG[lang]) Object.assign(T[lang], LANDING_CONFIG[lang]);
  });
  // Override prezzi
  if(LANDING_CONFIG.prezzi){
    var p = LANDING_CONFIG.prezzi;
    var vals = document.querySelectorAll('.price-val');
    if(vals[0] && p.p1) vals[0].textContent = '\u20ac' + p.p1;
    if(vals[1] && p.p2) vals[1].textContent = '\u20ac' + p.p2;
    if(vals[2] && p.p3) vals[2].textContent = '\u20ac' + p.p3;
  }
  // Override gallery con immagini reali
  if(LANDING_CONFIG.gallery && LANDING_CONFIG.gallery.length){
    var cards = document.querySelectorAll('.gallery-card');
    LANDING_CONFIG.gallery.forEach(function(url, i){
      if(url && cards[i]){
        cards[i].style.backgroundImage = 'url(' + url + ')';
        cards[i].style.backgroundSize = 'cover';
        cards[i].style.backgroundPosition = 'center';
        var span = cards[i].querySelector('span');
        if(span) span.style.display = 'none';
      }
    });
  }
  // Override display name in nav / title
  if(LANDING_CONFIG.display_name){
    var logo = document.querySelector('.nav-logo span');
    if(logo) logo.textContent = LANDING_CONFIG.display_name;
    document.title = LANDING_CONFIG.display_name + ' \u00b7 Fotografia';
  }
})();

// ── Init ──────────────────────────────────────────────────────────────────────
setLang(currentLang);
renderSlots();

// Traccia visita
fetch('/track-visit', {method:'POST',headers:{'Content-Type':'application/json'},
  body: JSON.stringify({struttura: STRUTTURA, lang: currentLang})}).catch(()=>{});
</script>
</body></html>"""

if __name__ == "__main__":
    os.chdir(BASE)
    httpd = HTTPServer(("0.0.0.0", 3000), Handler)
    print("Dashboard in ascolto su :3000")
    httpd.serve_forever()
