Compare commits

..

65 Commits

Author SHA1 Message Date
Adriano 11452ea7a7 Blog: numeri delle tematiche fra parentesi
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 17:21:18 +02:00
Adriano d8862ba8ec Blog: numero di articoli accanto a ogni tematica
I filtri non dicevano dove ci fosse materiale: si sceglieva una categoria
per scoprirla vuota. Ora ognuna porta il proprio conteggio, cestino e
bozze esclusi, e il numero si aggiorna insieme ai filtri quando si cambia
tematica senza ricaricare.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 17:16:39 +02:00
Adriano b7f4a7948c Blog: venti articoli divulgativi tratti dal corso base del Campus
Dieci articoli tecnici a firma Nicola Antonelli e dieci contenuti
educativi a firma Donata Dal Pastro, ricavati dalle lezioni del corso
base: sonno, movimento, proteine, freddo, respirazione, luce, recupero,
dati, alimentazione e salute del cervello. Taglio breve e concreto, per
chi frequenta la palestra e tiene alla salute senza essere del mestiere.

Gli articoli arrivano da uno script versionato invece che dal pannello,
perché il database in produzione è un volume del VPS e non viene toccato
dal deploy: creati a mano in locale sarebbero rimasti in locale. Lo script
salta gli slug già presenti, quindi si può rieseguire senza danni.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 17:10:43 +02:00
Adriano 5590a552cc Campus: allineate le 49 Q&A mancanti e corretto il percorso del sorgente
Il progetto Biohacking Campus è stato spostato in Wasabi-Adp-Work, mentre
sync-campus puntava ancora al vecchio percorso, ormai vuoto: da lì in poi
nessun aggiornamento è più arrivato. Il capitolo Q&A si era fermato alla
numero 35, mentre nel progetto ne esistono 84.

Ora la sezione conta 167 guide invece di 118. L'elenco dei Q&A è
rigenerato per intero dal filesystem, in ordine di numero, così il prossimo
allineamento è una sola riga di script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:53:57 +02:00
Adriano 770073c45c Blog: cambio categoria e pagina senza ricaricare tutto
Ogni clic su una categoria ricaricava l'intera pagina, header e hero
compresi, con lo sfarfallio che ne consegue. Ora si scarica la stessa
pagina e si sostituiscono soltanto la griglia e i filtri, con una breve
dissolvenza; l'indirizzo resta corretto e avanti/indietro funzionano.

I link restano link veri: senza JavaScript, o se la risposta non ha la
forma attesa, la navigazione avviene come prima.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:51:37 +02:00
Adriano 6a97a98325 Articoli: cestino con ripristino, e azioni a icona come in Utenti
Eliminare un articolo non lasciava nulla di recuperabile: ora la riga
finisce nel cestino (colonna deleted_at, aggiunta anche ai database già
esistenti) e sparisce da sito e pannello, ma resta ripristinabile dalla
nuova pagina /admin/cestino, dove sta anche la cancellazione definitiva.
L'API la accetta solo su articoli già cestinati, così il contenuto non si
perde per un clic distratto.

Ogni lettura pubblica — elenco, recenti, archivio per mese, pagina del
singolo articolo — filtra il cestino, e chi non gestisce vede nel cestino
soltanto i propri articoli.

Le azioni di riga diventano icone (matita e cestino) come nell'elenco
utenti, con etichetta accessibile che nomina l'articolo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:47:27 +02:00
Adriano 59d6246c77 Utenti: pannello password, ruolo come pillola e azioni a icona
Le due azioni di riga diventano icone (chiave e cestino) con etichetta
accessibile: pesavano quanto il contenuto della tabella. Il ruolo smette
di essere il menu a tendina di sistema e prende la forma di una pillola
colorata per livello, restando un <select> che salva al cambio.

La chiave apre un pannello dove la password si può scrivere a mano oppure
far generare: l'endpoint accetta ora una password scelta, con lo stesso
minimo di otto caratteri della creazione, e continua a restituirla in
chiaro una volta sola perché nel database ne resta solo l'hash. Il valore
si copia con un pulsante invece che dal prompt del browser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:38:18 +02:00
Adriano 15924544b4 Utenti: creazione su pagina dedicata, come per gli articoli
L'elenco aveva il modulo di creazione appiccicato sopra la tabella: ora
c'è il pulsante "+ Nuovo utente" che porta a /admin/users/new, dove il
modulo ha spazio per etichette vere e per spiegare cosa comporta ogni
ruolo. A creazione avvenuta si torna all'elenco.

Nel menu la voce "Sito" diventa "Vai a sito", più esplicita sul fatto che
porta fuori dal pannello.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:30:53 +02:00
Adriano 03510d3940 Area riservata: menu essenziale e veste grafica del sito
Il menu in alto si riduce alle voci che servono davvero — Articoli,
Contenuti, Utenti, Sito, Esci — con la voce corrente evidenziata; ognuna
resta visibile solo a chi ha il ruolo per aprirla, e in pagina di login la
barra mostra il solo marchio. "Nuovo" esce dal menu perché il pulsante per
creare un articolo è già in cima all'elenco Articoli.

Il pannello smette di essere un foglio di stile a sé: adotta i font e la
palette del sito, come Campus e Stress Index, con tabelle, pulsanti, campi
e pillole di stato riportati allo stesso linguaggio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:28:35 +02:00
Adriano 8207627425 Porta la promo agosto nel branch piattaforme, per vederle insieme in locale
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:24:02 +02:00
Adriano 3cda76b4c4 Stress Index: Analytics e Sport rifatti sulle viste reali
Le due sezioni erano ferme al prototipo locale, più povero della
piattaforma vera. Analytics ha ora l'intervallo di date, i KPI con la
nota sotto il valore, la distribuzione dello score a quattro serie per
fascia, le classifiche per recupero medio col numero di misurazioni e il
confronto segmenti con selettore di dimensione. Sport diventa Dashboard,
Sessioni con filtri atleta/periodo/sport e schede atleta con carico e
tendenza.

I numeri non sono più costanti scritte a mano: si calcolano dai dati nel
periodo scelto. La logica sta in lib/stress-index/analytics.ts e sport.ts,
coperta da test — che hanno subito trovato uno slittamento delle settimane
dovuto alle date costruite in ora locale invece che in UTC.

Le sessioni hanno ora ora di inizio, HR max, RPE e durata al secondo, che
il prototipo non prevedeva.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 16:20:40 +02:00
Adriano c4eb598a3a Piattaforme riservate: ruolo dedicato e port della dashboard Stress Index
Il ruolo `campus` diventa `piattaforme` e non apre più solo la sezione
Biohacking: copre tutte le aree riservate, con migrazione guardata sugli
utenti esistenti. Nell'header la vecchia voce Biohacking lascia il posto a
un menu Piattaforme con i due link, visibile solo a chi ha il ruolo, e
/piattaforme diventa la pagina di atterraggio.

Stress Index è portata dentro il sito: le nove viste dell'area
professionisti diventano pagine Astro sotto /piattaforme/stress-index,
con lo stile del sito al posto del design system del prototipo. React
resta solo dove serve davvero — grafici recharts e scheda cliente — il
resto è Astro con un filo di JS. I dati sono ancora quelli dimostrativi,
raccolti in lib/stress-index/data.ts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:49:47 +02:00
Adriano 78a920b4e2 Promo agosto 2026: nuovi prezzi, validità 1-19 agosto e countdown
Prezzi aggiornati (One to One 200, Small Group 125, Reformer 115,
Holistic 75) con migrazione guardata sul vecchio valore, così le righe
già ritoccate dal pannello non vengono sovrascritte. I barrati restano
invariati. Nota di validità con la finestra 1-19 agosto e countdown alla
scadenza sopra le card: la deadline è un tag editabile, il blocco si
nasconde da solo quando la promo è finita.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:36:27 +02:00
Adriano e8e24e086b Merge: sezione Biohacking riservata agli utenti abilitati
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 17:41:53 +02:00
Adriano 2f0df9c92c Link alla gestione utenti nella sidebar del Biohacking
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 17:21:32 +02:00
Adriano f534263c9c Rimuove il limite di tentativi dal login
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 16:57:59 +02:00
Adriano 6b00b2cb28 Il logout riporta alla home del sito
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 16:47:21 +02:00
Adriano d8579c0a59 Accesso al Biohacking dal menu del sito e dal login pubblico
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 16:43:46 +02:00
Adriano 98beccb0cd Rinomina in Biohacking le etichette visibili della sezione
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 15:47:47 +02:00
Adriano c4107b84a5 Sezione Campus: layout dedicato, route protette e documentazione
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 15:40:07 +02:00
Adriano 7baebebc09 Script sync-campus: importa le guide del corso in campus-content/
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 15:30:59 +02:00
Adriano a7d918d241 Ruolo campus: accesso limitato a /campus, landing dedicata
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 15:26:35 +02:00
Adriano 20f7cf4003 Spec: sezione campus riservata con ruolo dedicato
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 15:25:21 +02:00
Adriano 669fd2978f Hero one-to-one: titolo "Personal Training" separato dall'H2
Campo opzionale Service.heroTitle: l'hero (H1) di /services/one-to-one
mostra "PERSONAL TRAINING" mentre H2/card restano "One to One" (tag
condiviso service.one-to-one.title invariato). Nessuna migrazione DB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 08:54:59 +02:00
Adriano b17b5c831a Fix contatti + rinomina servizi + orario sabato
- Contatti: rate-limit per IP reale (X-Forwarded-For) invece dell'IP del
  proxy Traefik, condiviso da tutti; controllo spostato dopo la validazione
  così bot/honeypot non consumano la quota. Honeypot rinominato da "website"
  a "hp_field" per non farlo riempire dall'autofill del browser (falsi 400).
- Servizi: "Performance Class" -> "Class" (titolo/card/H1/footer); migrazione
  DB invertita e guardata.
- One to One: migrazione titolo prod "Personal training" -> "One to One".
- Footer: orario sabato 8-17 (non 8-19), seed + migrazione guardata.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 17:58:26 +02:00
Adriano 22c9cadb43 Migrazione: rimuove i tag orfani training.performance-class.*
Il riquadro training "performance-class" è stato rimosso dal seed nel commit
9966ec3, ma i suoi 5 tag restavano nei DB esistenti come orfani non
renderizzati (uno con un edit errato dell'editor, "Holistic Class"). Aggiunge
una DELETE idempotente per l'intero gruppo; il seed non li reinserisce
(trainings.ts non ha più questo id). Verificata su copia del DB prod: 5 -> 0
orfani, riquadri validi e 39 edit reali del pannello intatti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 00:16:52 +02:00
Adriano 21d9755e37 Migrazione: corregge il titolo Performance Class troncato a "Class"
Nei DB esistenti (prod + dev) service.performance-class.title era rimasto
"Class", mostrando "CLASS" come H1 della pagina /services/performance-class
e nelle card di /services e home. Il seed è INSERT OR IGNORE e non lo
aggiornava; una nuova installazione era invece corretta.

Aggiunge una migrazione guardata dal vecchio valore (idempotente, non tocca
le modifiche del pannello) che allinea a "Performance Class". Verificata su
copia del DB di produzione: titolo corretto, 40 edit del pannello intatti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 23:41:37 +02:00
Adriano ee54e9a1f4 Migrazione: allinea i bottoni promo col vecchio testo-freccia
Il commit 1522d97 ha spostato la freccia "→" dei bottoni promo nel CSS
(::after) e il maiuscolo in text-transform, cambiando il seed in minuscolo
senza freccia. Ma il seed è INSERT OR IGNORE: sui DB esistenti (produzione)
le righe restavano a "LE NOSTRE PROPOSTE →" / "ACQUISTA →", e il ::after
avrebbe prodotto una doppia freccia.

Aggiunge una migrazione guardata dal vecchio valore (idempotente, non tocca
le righe modificate dal pannello) che le allinea al seed corrente. Verificata
su copia del DB di produzione: frecce corrette e 40 edit del pannello intatti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 22:28:20 +02:00
Adriano 62e0120ea2 Footer: rimuove il form contatti e riorganizza in 3 colonne
- Rimossa la colonna "invia messaggio" (nome, cognome, telefono, mail,
  messaggio) e l'import ContactForm inutilizzato.
- Griglia a 3 colonne con contenuto centrato, logo incluso.
- Training e Servizi allineati alla riga "Promuoviamo il movimento...".
- Altezza del footer ridotta (padding compattato).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:48:49 +02:00
Adriano 2f94fc0373 Allinea test stale e migrazione nav dopo riordino menu
I test erano rimasti indietro rispetto a due modifiche intenzionali:
- size 'xxl' è ora un valore valido (whitelist + CSS): i test lo usavano
  come esempio di valore da rifiutare → sostituito con 'xxxl'.
- il menu è stato riordinato (commit 5ff4771): la voce "Servizi" è passata
  da nav.3 a nav.2. Allineata la voce di migrazione OLD_SECTION_DEFAULTS a
  global.nav.2.label e il relativo test. Impatto nullo sui DB reali (nessuna
  riga ha ancora value 'Programmi'), solo coerenza semantica.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:17:37 +02:00
Adriano e6c493b756 Contatti obbligatori, hero senza foto, menu Promo2 solo admin
- Contatti: nome, cognome, telefono e mail resi obbligatori (validazione
  client + server); il form del footer ora include cognome e telefono.
- Home hero: rimossa la foto (file conservato su disco), fascia ridotta
  da 100vh a padding contenuto, testo allargato.
- Menu: la voce "Promo 2 (test)" è visibile solo agli admin loggati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:06:28 +02:00
Adriano 1522d97f54 promo: freccia bottoni via ::after (modifiche dal canvas Claude Design)
Riporta nel codice le modifiche fatte da Adriano nel canvas di Claude Design: la
freccia dei bottoni diventa decorazione CSS (::after) invece di far parte del testo
del tag. I tag promo.hero.cta e promo.buy-label perdono la freccia e il maiuscolo
forzato (gestiti dal CSS .btn). promo-eventi2 usa la ::after globale di .btn;
promo-eventi (.lp) aggiunge ::after ai suoi bottoni custom per non perdere la freccia.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 00:08:20 +02:00
Adriano cc21cb7627 promo: riabilita i controlli di stile sui badge nel pannello contenuti
promo.hero.badge e promo.launch.badge tornano al set di stili di default
(size/color/weight/style/align): erano stati seedati con styleOptions vuoto,
quindi il pannello mostrava solo il testo senza opzioni di stile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 22:22:37 +02:00
Adriano 27fdf1fc30 promo-eventi2: raffinamento estetico
Claim su due righe pulite (niente più spezzatura del corsivo), titolo FAQ reso
visibile e uppercase come lo stile del sito, card con prezzi più prominenti,
separatore feature, ombra/hover e featured evidenziata, spaziature bilanciate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 17:26:52 +02:00
Adriano ae5eef037b promo-eventi2: adotta i pattern di layout del sito (PageHero, section, sfondo bianco)
La prima versione unificava solo font/colori ma teneva il box centrato su sfondo crema.
Ora la pagina di test usa il banner PageHero a tutta larghezza, sezioni .section/.container
su sfondo bianco e bottoni .btn globali, come Servizi e Training. Aggiunto il tag
promo.pagehero.title per il titolo del banner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 17:11:09 +02:00
Adriano 931c1e43ad Aggiunge pagina di test promo-eventi2 con stili unificati al design system
Variante di /promo-eventi con font Montserrat/Open Sans e palette --c-* al posto di
Inter e dei colori hardcoded; struttura e contenuti identici (stessi tag promo.*).
Root class `.lp2` per isolare gli stili is:global dalla pagina originale. Aggiunta la
voce di menu "Promo 2 (test)" → /promo-eventi2 per il confronto affiancato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 16:51:42 +02:00
Adriano a7b05d703a Promo ed eventi: testi editabili dal pannello con stili is:global
Riprende il tagging della landing (tag promo.* + global.whatsapp centralizzato) e
risolve la regressione visiva: gli elementi editabili sono renderizzati dal componente
<T>, fuori dallo scope della pagina, quindi il CSS scoped non li raggiungeva (font e
allineamenti persi). Lo <style> diventa is:global; ogni regola è già prefissata `.lp`,
quindi resta confinata alla landing senza toccare il resto del sito.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 16:43:40 +02:00
Adriano 4cee8cdd7a Revert "Rende la pagina Promo ed eventi modificabile dal pannello contenuti"
This reverts commit 1742c1d666.
2026-07-13 11:54:10 +02:00
Adriano 1742c1d666 Rende la pagina Promo ed eventi modificabile dal pannello contenuti
Tutti i testi della landing (hero, launch, card con prezzi, nota validità e FAQ)
passano ora dal sistema di content-tag `promo.*`, editabili inline e da pannello.
Il numero WhatsApp è centralizzato nel tag globale `global.whatsapp`: le risposte
FAQ usano il segnaposto {{whatsapp}}, sostituito a runtime, così si aggiorna in un
punto solo. Struttura delle 4 card e link di acquisto bsport restano nel codice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 11:51:21 +02:00
Adriano 0b938d7d09 Aggiunge integrazione Google Tag Manager attivabile via env
GTM viene caricato nel layout Base solo se la variabile GTM_ID è valorizzata.
L'ID è letto a runtime da process.env (non inlinato a build-time), così si
attiva/disattiva senza ricompilare. Con GTM_ID vuoto il sito non carica alcuno
script Google.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 10:59:34 +02:00
Adriano 47a93049bb editor inline: restyle e combo con valore impostato pre-selezionato
- GET /api/admin/content/[tag] restituisce anche gli styles correnti del blocco
- Modal ridisegnato (header con chip tag, input arrotondati, griglia combo)
- Combo etichettate in italiano (Dimensione/Colore/Peso/Stile/Allineamento) con
  valori leggibili; pre-selezione del valore già impostato ed evidenziazione

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:18:08 +02:00
Adriano 5ff47711cd menu: riordina le voci (Training, Servizi, About us, Blog & Edugo, Promo, Contatti)
I label del menu vengono da global.nav.N.label (per posizione): riordinati anche
i blocchi contenuto in produzione per allinearli al nuovo ordine degli href.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:54:55 +02:00
Adriano f48518d8fd footer e contatti: social aggiornati e nome sulla mappa
- Footer: Instagram -> in_sanity_lab, LinkedIn -> in-sanity-lab-srl, rimosso Facebook
- Contatti: mapQuery con nome "IN-SANITY LAB" così la mappa mostra l'etichetta

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:48:58 +02:00
Adriano 30b509436e contenuti: aggiunge size xl/xxl e allineamento (con giustificato)
- style-presets: size += xl, xxl; align += justify
- global.css: .cs-size-xl (1.5em), .cs-size-xxl (2em), .cs-align-justify
- content-seed: 'align' nei default styleOptions dei tag testo/html, così il
  picker orientamento (sx/centrato/dx/giustificato) compare nell'editor
- Base.astro: sincronizza la copia hardcoded degli stili nell'editor inline

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:48:58 +02:00
Adriano 8f47cf9e94 about: rende visibili gli a-capo in about.studios.body3
Il <p> reso da <T> collassava i newline (white-space: normal). Aggiunto
white-space: pre-line al paragrafo, ancorato al contenitore col cid via :global.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:19:29 +02:00
Adriano 9f6cf29708 menu: rinomina voce "Insanitylab" in "About us"
Aggiornato il seed del label nav (site.ts). Il valore renderizzato viene dal
blocco taggato global.nav.1.label, aggiornato anche in produzione.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:15:36 +02:00
Adriano 3183bd795d training: nasconde la sezione Agenda
Rimosso il render di <Agenda /> (e l'import) da /training. Il componente
Agenda.astro resta disponibile per riuso.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:12:39 +02:00
Adriano 1b9a3c1343 Servizi home, testo promo, fix immagini override e rimozione Antonello
- ServicesGrid: filtra grid !== false come /services (via Small Group, Rehab,
  Holistic Class, Reformer Class dalla griglia Servizi in home)
- promo-eventi: badge "solo per apertura nuova sede Settembre 2026"
- MethodSteps e about: .tcard/.method__circle usano :global(img) così gli
  override <img> di <TImg> (senza cid) ricevono border-radius/object-fit
- about: griglia team a flex centrato, ultima riga incompleta non piu a sinistra
- team: rimosso Antonello D'Alessandro (staff about e homeTeam)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:01:52 +02:00
Adriano 3b8983dca5 fix(img): foto Reformer Class in alta risoluzione
Sostituita la foto piccola (770px) con la versione HD della stessa scena
reformer (1600x1066) dagli asset Figma.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 17:41:51 +02:00
Adriano 3b4d38e087 fix(img): foto corretta per Holistic Class
La foto di Holistic Class mostrava macchinari Reformer (era la stessa immagine
di Reformer Class). Sostituita con una foto Pilates/Yoga a corpo libero (due
persone di età diverse, coerente con 'adatta a tutti i livelli'), dagli asset Figma.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 17:32:19 +02:00
Adriano 02b5180227 fix(metodo): allinea i testi degli step del Metodo ai valori voluti
Gli step del blocco Metodo (home) erano stati riscritti dal pannello ma solo nel
DB di sviluppo: il database non è versionato, quindi non erano mai arrivati in
produzione (che mostrava ancora i default del seed). Aggiornato il seed method.ts
(Check-up fisico, Insanity plan, Monitoraggio costante + testi) e aggiunta
migrazione idempotente che allinea le righe di prod ancora ai vecchi default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 16:32:44 +02:00
Adriano c722680590 chore(deploy): vecchio insanitylab.tielogic.xyz → 301 all'apex
Conclusa la migrazione al dominio ufficiale: il sottodominio di transizione
non serve più il sito ma redirige permanentemente a insanitylab.it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 16:25:45 +02:00
Adriano 172cef236b feat(gdpr): conformità privacy — mappa a consenso, pagine legali, titolare
- Mappa Google su /contact ripristinata a click-to-load (consenso esplicito):
  nessun dato inviato a Google prima del click dell'utente
- Nuove pagine /privacy e /cookie-policy (informativa art. 13 GDPR e cookie),
  compilate con i dati del titolare IN-SANITY LAB S.R.L. (P.IVA 03927341200)
- Footer: ragione sociale + P.IVA e link a Privacy e Cookie Policy
- Form contatti: link all'informativa privacy sotto l'invio
- sitemap: aggiunte /privacy e /cookie-policy

Testi legali da far validare; base tecnica conforme (no analytics/tracker,
solo cookie tecnici, font self-hosted).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 16:19:57 +02:00
Adriano 1aa82f5332 docs: README unico e aggiornato; rimozione piani/spec superati
- README riscritto come documentazione viva e completa: stack, funzionalita'
  (contenuti taggati + GUID + modifica inline, 3 ruoli admin/superuser/user,
  blog ownership, gestione utenti), env SMTP Aruba, deploy Docker/Traefik,
  dominio insanitylab.it, migrazioni DB e trappole tecniche note
- rimossi docs/superpowers/plans e docs/superpowers/specs: piani gia' eseguiti
  e spec di design ora accorpati nel README (restano nella storia git)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:50:21 +02:00
Adriano d79aa912b1 feat(deploy): dominio ufficiale insanitylab.it (apex canonico)
- astro.config: site=https://insanitylab.it, allowedDomains apex+www+tielogic
- compose/Traefik: router serve insanitylab.it e (in transizione) tielogic.xyz;
  www.insanitylab.it redirige 301 all'apex. TLS Let's Encrypt via certresolver.
  tielogic resta servito per non avere downtime finché il DNS non propaga;
  il suo redirect all'apex verra' attivato dopo la conferma che il dominio e' live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:20:38 +02:00
Adriano 86d608a929 fix(hero): titolo su 3 righe (PERFORMANCE./BALANCE./LONGEVITY.)
Lo span .hero__line è reso dal componente <T> e non riceve il data-astro-cid
di HeroSlider, quindi le regole scoped .hero__line (display:block e animazioni)
non lo colpivano e le righe restavano inline. Ancorato a .hero__text/h1 (che il
cid ce l'hanno) con :global(.hero__line), come già fatto per .prog__desc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 14:54:50 +02:00
Adriano 708410bbf1 feat(home): rimuove le sezioni Agenda e Team dalla pagina principale
Tolte da index.astro le sezioni <Agenda> e <TeamSection> con i relativi import.
I componenti restano nel codice (riutilizzabili altrove). Modifica isolata su
branch dedicato per valutazione col cliente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 14:41:47 +02:00
Adriano 53af0ca00e feat(home): nuova immagine per lo step "Insanity plan" del Metodo
Grafica di brand (barre di progressione, curva, checklist protocolli) al posto
della vecchia foto, coerente col testo aggiornato dello step 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 14:40:57 +02:00
Adriano 6594f6da53 docs(env): configurazione SMTP Aruba per il form contatti
smtps.aruba.it:465 con casella autenticata del dominio (il mittente deve
coincidere con la casella SMTP). Il dominio invia già la posta via Aruba
(SPF include:aruba.it). Credenziali reali solo nel .env (git-ignored) e sul VPS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 13:41:20 +02:00
Adriano c6de02be83 Contatti, /training e immagini: rifiniture post-merge
- Rimossa la sezione "Prenota la lezione di prova" da /training (markup,
  import, CSS, seed e blocchi DB relativi)
- Titoli dei riquadri Training non più forzati in minuscolo: rispettano il
  testo del DB (One to One, Small Groups, ecc.)
- Footer e pagina Contatti con i due ingressi dello stesso stabile
  (Via Leandro Alberti 76 e Via De Mattiolo 5) più l'email; nuovo tag
  global.contact.map-query e migrazione DB idempotente dell'indirizzo
- Mappa Google in /contact caricata automaticamente e a piena dimensione
  (rimosso il bottone e il relativo script)
- Landing Promo ed eventi: rimossa la barra contatti in fondo
- service-icons allineate agli slug attuali (one-to-one, small-group;
  rimosse le chiavi obsolete personal-training e pilates-flow)
- Immagini: foto Rehab corretta (prima era di osteopatia) su program-rehab
  e training-rehab; bande nere letterbox rimosse dalla foto di osteopatia;
  nuova immagine grafica per Profilazione con grafici HRV (ECG, tacogramma
  R-R, Poincaré plot)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:41:37 +02:00
Adriano 11fc2b3e54 Servizi e landing Promo ed eventi: rinomine, pulizia contenuti e nuova pagina
Servizi:
- Personal Training rinominato "One to One" (slug /services/one-to-one) con
  redirect 301 dal vecchio URL e migrazione DB idempotente dei contenuti taggati
- Nuova pagina "Small Group" (slug /services/small-group, fuori dalla griglia)
- Rimossi i titoli intro di /training e /services e le liste feature di One to
  One, Small Group, Rehab e Osteopatia; la lista feature ora è condizionale
- Fix elenco puntato nel long-description (white-space: pre-line, selettore
  ancorato al container per gli elementi resi dal componente <T>)
- Immagine di Performance Class aggiornata
- Nella griglia Servizi i riquadri "One to One" e "Performance Class" rimandano
  a /training; nella pagina Training i riquadri One to One e Small Group aprono
  le rispettive schede servizio

Landing:
- Pagina "Promo ed eventi" (slug /promo-eventi) integrata nel sito con header e
  footer condivisi, basata sul nuovo mockup di lancio
- Card a 5 incontri con prezzi edizione apertura, FAQ ad accordion e acquisto
  tramite gestionale bsport
- Nuova voce di menu "Promo ed eventi"

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:33:11 +02:00
Adriano 5b34ee1697 Home: bottone Contattaci in fondo alla sezione Metodo
Come da documento cliente. Nuovo tag home.method.cta (auto-seed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rUFjLYZ1TZ7DgxwTR5q9K
2026-07-11 17:34:57 +02:00
Adriano 2468ce95f7 About: testi aggiornati e rimozione sezioni Metodo, Vision, StoryLab
- Testi riscritti da documento cliente: about.about.body, convenzioni.item1/item2
  (rimosso "gratuita"), studios.body1 (versione breve). Seed + migrazione DB
  idempotente (guardia via frammento LIKE, rispetta le modifiche da pannello).
- Rimosse dalla pagina le sezioni marcate "LEVARE" nel documento: Timeline/StoryLab,
  Metodo (presente in home) e Vision. I tag restano nel seed per un eventuale ripristino.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rUFjLYZ1TZ7DgxwTR5q9K
2026-07-11 16:43:50 +02:00
Adriano 9966ec3616 Training: 6 riquadri (Rehab, Holistic Class, Reformer Class, Coaching)
- trainings.ts: da 4 a 6 riquadri (One to One, Small Group max 4, Rehab,
  Holistic Class ex Performance Class, Reformer Class nuova, Coaching ex Fit
  Remote); anche in Homepage. CTA "Scopri" verso le pagine dettaglio.
- services.ts: aggiunte pagine dettaglio rehab/holistic-class/reformer-class con
  flag grid:false (fuori dalla griglia /services ma raggiungibili come
  approfondimento dai riquadri Training). Rehab torna consultabile su /services/rehab.
- index/[slug] servizi filtrano grid!==false; rimosso redirect /services/rehab.
- Footer Training portato a 6 voci con ancore aggiornate; TrainingCards tone-safe.
- Foto Reformer dal documento cliente; card Rehab/Holistic/Coaching con
  immagini placeholder (da confermare). Migrazione DB idempotente per label/meta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rUFjLYZ1TZ7DgxwTR5q9K
2026-07-11 13:26:58 +02:00
Adriano 140146fce1 Servizi: Pilates Flow -> Profilazione, Rehab -> Osteopatia
- services.ts: nuovi servizi profilazione (copy bozza dal metodo ISL) e
  osteopatia (copy dal documento cliente); rehab esce da Servizi (va in Training).
- Redirect 301 dai vecchi slug: pilates-flow -> profilazione, rehab -> training.
- Footer, icone, sitemap e meta aggiornati; migrazione DB idempotente per le
  label footer e la meta description (contenuti live in SQLite).
- Immagine osteopatia riusa la foto rehab (come da mockup cliente).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rUFjLYZ1TZ7DgxwTR5q9K
2026-07-11 13:10:37 +02:00
386 changed files with 46014 additions and 8695 deletions
+14 -5
View File
@@ -1,8 +1,17 @@
SMTP_HOST=smtp.example.com # SMTP Aruba — il dominio insanitylab.it invia la posta via Aruba (SPF: include:aruba.it)
SMTP_PORT=587 # Server posta in uscita autenticato: smtps.aruba.it:465 (SSL). Verificato funzionante.
SMTP_USER=user SMTP_HOST=smtps.aruba.it
SMTP_PASS=pass SMTP_PORT=465
# Casella reale del dominio usata per l'invio autenticato (con la sua password Aruba)
SMTP_USER=postmaster@insanitylab.it
SMTP_PASS=la-password-della-casella
# Aruba impone che il mittente sia la casella autenticata: uguale a SMTP_USER
CONTACT_FROM=postmaster@insanitylab.it
# Dove arrivano i messaggi del form
CONTACT_TO=info@insanitylab.it CONTACT_TO=info@insanitylab.it
CONTACT_FROM=sito@insanitylab.it
DB_PATH=data/insanitylab.db DB_PATH=data/insanitylab.db
UPLOADS_DIR=uploads UPLOADS_DIR=uploads
# Google Tag Manager: ID container preso da tagmanager.google.com (accanto al nome container).
# Formato GTM-XXXXXXX. Lascia vuoto per non caricare GTM (es. in locale/staging).
GTM_ID=GTM-XXXXXXX
+2
View File
@@ -14,6 +14,8 @@ COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist COPY --from=build /app/dist ./dist
COPY scripts ./scripts COPY scripts ./scripts
# Contenuti della sezione Campus (generati con `npm run sync-campus` e committati)
COPY campus-content ./campus-content
ENV HOST=0.0.0.0 \ ENV HOST=0.0.0.0 \
PORT=4321 \ PORT=4321 \
+116 -94
View File
@@ -1,145 +1,167 @@
# InsanityLab Website # InsanityLab Website
Sito vetrina per InsanityLab con blog integrato e form di contatti. Sito ufficiale di InsanityLab: vetrina aziendale, blog integrato, form di contatti e sistema di contenuti modificabili senza toccare il codice. In produzione su **https://insanitylab.it**.
## Descrizione ## Descrizione
Il progetto realizza un sito web per InsanityLab, combinando una vetrina aziendale con un sistema di blog gestito tramite SQLite e un form di contatti funzionante. L'architettura è costruita su **Astro 7** con adapter **Node.js** per deployment su server, garantendo performance ottimali e SEO-friendly pages. Il sito è costruito su **Astro 7** in modalità server (SSR) con adapter **Node.js standalone**, servito in un container Docker dietro **Traefik** (TLS Let's Encrypt automatico). I contenuti testuali e le immagini della parte vetrina sono "taggati" e salvati su **SQLite**: si modificano da un pannello di amministrazione o direttamente in pagina, senza deploy. Il blog è gestito internamente con editor richtext, e il form di contatti invia email tramite SMTP.
## Stack Tecnologico ## Stack tecnologico
- **Framework**: Astro 7 - **Framework**: Astro 7 (output `server`, `prerender = false` sulle pagine dinamiche)
- **Runtime**: Node.js (adapter standalone) - **Runtime**: Node.js 22 (adapter `@astrojs/node` standalone)
- **Database**: SQLite (better-sqlite3) - **Database**: SQLite via `better-sqlite3`
- **Autenticazione**: bcryptjs - **Autenticazione**: sessioni con password hashate (`bcryptjs`)
- **Email**: Nodemailer - **Email**: Nodemailer (SMTP)
- **Markup Editor**: TipTap (rich text) - **Editor articoli**: TipTap (rich text)
- **Font System**: @fontsource (Montserrat, Open Sans) - **Font**: `@fontsource` (Montserrat, Open Sans) — nessun CDN esterno
- **Sitemap**: endpoint runtime `/sitemap.xml` (route vetrina, programmi e articoli pubblicati) - **Sitemap**: endpoint runtime `/sitemap.xml` (route vetrina + articoli pubblicati)
- **Testing**: Vitest - **Test**: Vitest
- **Linguaggio**: TypeScript - **Linguaggio**: TypeScript
- **Deploy**: Docker (multistage, `node:22-slim`) + Traefik su VPS
## Installazione ## Funzionalità principali
- **Vetrina**: home, chi siamo, training, servizi (con schede di dettaglio), blog, contatti, landing "Promo ed eventi".
- **Contenuti taggati**: ogni testo/immagine ha un tag (es. `home.hero.title-line-1`) modificabile dal pannello o in pagina.
- **Modifica inline**: chi ha i permessi può attivare l'overlay dei tag e modificare i contenuti direttamente mentre naviga il sito.
- **Blog** con editor TipTap, immagini caricate, firma autore e proprietà degli articoli per ruolo.
- **Gestione utenti** con quattro ruoli (`admin`, `superuser`, `user`, `campus`).
- **Campus**: sezione riservata con le guide di studio del corso Biohacking Campus, accessibile solo dopo il login e con navigazione autonoma.
- **Form contatti** con validazione, honeypot antispam, ratelimit e invio email via SMTP.
## Installazione (sviluppo)
### Prerequisiti ### Prerequisiti
- Node.js 18+ e npm - Node.js 18+ e npm
### Setup ### Setup
```bash ```bash
# Installa dipendenze
npm install npm install
cp .env.example .env # poi compila i valori reali
# Copia il template di configurazione env npm run create-user -- admin <password> admin
cp .env.example .env npm run dev # http://localhost:4321
# Crea il primo utente amministratore (dopo aver configurato il DB)
npm run create-user -- admin tuapassword
``` ```
Modifica `.env` con le credenziali SMTP, percorsi database e upload directory secondo il tuo ambiente. Il database `data/insanitylab.db` viene creato al primo avvio ed è popolato automaticamente con i contenuti di default (seed) e le migrazioni idempotenti.
## Comandi Disponibili ## Comandi
| Comando | Descrizione | | Comando | Descrizione |
|---------|-------------| |---------|-------------|
| `npm run dev` | Avvia il server di sviluppo (localhost:4321) | | `npm run dev` | Server di sviluppo (localhost:4321) |
| `npm run build` | Compila il progetto per production | | `npm run build` | Build di produzione |
| `npm run preview` | Visualizza l'anteprima della build (locale) | | `npm run preview` | Anteprima locale della build |
| `npm run start` | Avvia il server production (richiede `npm run build` preliminare) | | `npm run start` | Server di produzione (`node --env-file=.env`) |
| `npm run test` | Esegui i test con Vitest | | `npm run test` | Test con Vitest |
| `npm run create-user` | Crea un nuovo utente: `-- <nome> <password> [admin\|editor]` | | `npm run create-user` | Crea/aggiorna utente: `-- <nome> <password> [admin\|superuser\|user\|campus]` |
| `npm run sync-campus` | Rigenera i contenuti del Campus: `-- <path-progetto-biohacking-campus>` |
## Gestione Contenuti ## Gestione contenuti
I testi e le immagini del sito vetrina sono contenuti taggati, modificabili senza toccare il codice tramite il pannello `/admin/content`. Ogni blocco di contenuto è identificato da un tag (ad esempio `home.hero.title`) e può essere aggiornato nel valore, negli stili predefiniti (dimensione, peso, colore) e — per le immagini — sostituito con un upload o riportato all'originale. I testi e le immagini della vetrina sono **blocchi taggati** salvati nella tabella `content_blocks`. Ogni blocco ha un tag stabile (es. `home.hero.title-line-1`), un valore, eventuali stili predefiniti, un tipo (`text` / `html` / `image`) e un **GUID** univoco.
Il pannello raggruppa i tag per pagina, offre una ricerca per tag o contenuto e mostra per ogni blocco l'ultima modifica effettuata. Visitando il sito da utente autenticato con il parametro `?annotate=1` (link "Vedi sito con tag" nel pannello), ogni contenuto taggato viene evidenziato con un badge che rimanda direttamente al form di modifica corrispondente. - **Pannello** `/admin/content`: i blocchi sono raggruppati per pagina, con ricerca per tag o contenuto, upload delle immagini e ripristino all'originale.
- **Modifica inline**: attivando "Mostra tag" dal menu utente, ogni blocco mostra un badge (nome tag + matita) che apre una finestra di modifica direttamente in pagina.
- **Componenti** `<T>` e `<TImg>`: rendono i blocchi con fallback automatico al seed quando un tag non è ancora nel database.
Sono previsti due ruoli utente: `admin` ha accesso completo (articoli del blog, contenuti, upload), mentre `editor` può operare esclusivamente sul pannello contenuti. Gli utenti si creano con `npm run create-user -- <nome> <password> editor`. Il seed dei contenuti è in `src/data/content-seed.ts`; le variazioni sui contenuti già esistenti (rinomine, testi aggiornati) si applicano tramite **migrazioni idempotenti** in `src/lib/db.ts`, così da non sovrascrivere le modifiche fatte dal pannello.
## Struttura Progetto > Nota tecnica: gli elementi resi dai componenti `<T>`/`<TImg>` **non** ereditano il `data-astro-cid` del componente che li usa. Uno stile scoped basato solo sulla classe (es. `.hero__line`) non li raggiunge: va ancorato a un contenitore che ha il cid usando `:global()` (es. `.hero__text :global(.hero__line)`).
## Ruoli e accessi
Quattro ruoli, con autorizzazione a matrice di prefissi in `src/lib/auth.ts`:
| Ruolo | Può fare |
|-------|----------|
| `admin` | Tutto: contenuti, blog, upload, **gestione utenti** (`/admin/users`), Campus |
| `superuser` | Pannello contenuti + blog |
| `user` | Solo i **propri** articoli del blog |
| `campus` | Solo la sezione riservata `/campus`; nessun accesso al pannello |
Login pubblico su `/login` (`?next` sanitizzato contro open redirect). Dopo l'autenticazione ogni ruolo viene indirizzato alla propria pagina iniziale: il pannello per admin e user, i contenuti per superuser, il Campus per il ruolo omonimo. La gestione utenti (`/admin/users`, solo admin) consente creazione, reset password unatantum, cambio ruolo ed eliminazione, con guardie contro l'autoeliminazione e la rimozione dell'ultimo admin.
## Campus (sezione riservata)
La sezione `/campus` raccoglie le guide di studio del corso Biohacking Campus. È protetta dal middleware come il pannello: un visitatore anonimo viene mandato al login, un utente con ruolo non abilitato torna alla propria pagina iniziale. Le pagine non montano l'header e il footer del sito — la navigazione avviene attraverso la barra laterale delle aree, con un unico richiamo per tornare alla home — ma usano i font, i colori e il CSS globale di InsanityLab.
I contenuti non vengono scritti a mano: lo script `npm run sync-campus` legge le guide sorgente (`*.studio.md`) dal progetto Biohacking Campus, le converte in frammenti HTML e produce la cartella `campus-content/` (indice `nav.json`, pagine e immagini delle slide), che va committata perché il deploy la copia nell'immagine Docker. Lo script è idempotente: rigenerarlo dopo l'aggiunta di nuove lezioni aggiorna indice e pagine e rimuove quelle non più presenti. Le nuove guide vanno prima dichiarate nella struttura dei capitoli in `scripts/campus/structure.mjs`.
Le immagini delle slide sono servite dalla route `/campus/assets/...`, anch'essa protetta: non sono raggiungibili senza una sessione valida.
## Struttura del progetto
``` ```
insanitylab-website/ insanitylab-website/
├── src/ ├── src/
│ ├── pages/ # Pagine Astro (routing automatico) │ ├── pages/ # Route Astro (vetrina, /admin, /api, /campus)
│ ├── components/ # Componenti Astro riutilizzabili │ ├── components/ # Componenti (home/, content/, Header, Footer, …)
│ ├── layouts/ # Layout master │ ├── layouts/ # Layout Base, Admin, Campus
│ ├── utils/ # Funzioni di utilità │ ├── data/ # Dati statici + seed contenuti (services, trainings, site, content-seed)
│ ├── env.d.ts # Type definitions globali │ ├── lib/ # db, auth, content, mailer, rate-limit, safe-next, env
── ... ── assets/img/ # Immagini ottimizzate da Astro
├── data/ # Database SQLite e dati persistenti │ └── styles/ # global.css
│ └── insanitylab.db # (generato al primo avvio) ├── data/ # SQLite (volume in produzione)
├── uploads/ # Directory per upload immagini e file ├── uploads/ # Upload immagini (volume in produzione)
├── dist/ # Output build (server) ├── campus-content/ # Contenuti del Campus generati da sync-campus
├── tests/ # Test suite ├── scripts/ # create-user, sync-campus, smoke test
├── astro.config.mjs # Configurazione Astro ├── astro.config.mjs # Config Astro (site, allowedDomains, redirects)
├── tsconfig.json # Configurazione TypeScript ├── compose.yaml # Stack Docker + label Traefik
├── vitest.config.ts # Configurazione test framework ├── Dockerfile # Build multi-stage
├── .env.example # Template variabili di ambiente ├── .env.example # Template variabili d'ambiente
── .env # Variabili di ambiente (non versionato) ── README.md
├── package.json # Dipendenze e script npm
└── README.md # Questo file
``` ```
## Configurazione Ambiente ## Variabili d'ambiente
Il file `.env` richiede le seguenti variabili: Il file `.env` (non versionato) richiede:
- `SMTP_HOST`: Host SMTP per invio email | Variabile | Descrizione |
- `SMTP_PORT`: Porta SMTP (solitamente 587) |-----------|-------------|
- `SMTP_USER`: Utente SMTP | `SMTP_HOST` | Host SMTP (produzione: `smtps.aruba.it`) |
- `SMTP_PASS`: Password SMTP | `SMTP_PORT` | Porta SMTP (`465` con SSL) |
- `CONTACT_TO`: Email di destinazione contatti | `SMTP_USER` | Casella di invio (deve coincidere con `CONTACT_FROM`) |
- `CONTACT_FROM`: Email mittente | `SMTP_PASS` | Password della casella |
- `DB_PATH`: Percorso database SQLite | `CONTACT_FROM` | Mittente delle email (= `SMTP_USER`) |
- `UPLOADS_DIR`: Cartella upload file | `CONTACT_TO` | Destinatario dei messaggi del form |
| `DB_PATH` | Percorso del database SQLite |
| `UPLOADS_DIR` | Cartella degli upload |
| `CAMPUS_DIR` | Cartella dei contenuti del Campus (default `./campus-content`) |
## Development Workflow La posta del dominio `@insanitylab.it` è ospitata su **Aruba**; l'invio del form usa l'SMTP autenticato Aruba (`smtps.aruba.it:465`). Aruba impone che il mittente sia la casella autenticata, quindi `CONTACT_FROM` deve essere uguale a `SMTP_USER`.
1. **Avvia il server locale**: `npm run dev` ## Deploy in produzione
2. **Modifica file** in `src/` (hot reload automatico)
3. **Test**: `npm run test`
4. **Build**: `npm run build` (verifica errori di build)
5. **Commit**: Con prefisso `feat:` (feature) o `chore:` (manutenzione)
## Deployment Il sito gira in Docker su un VPS, dietro Traefik. Lo stack è in `/opt/docker/insanitylab/` (repo in `src/`, volumi `data/` e `uploads/`). Aggiornamento:
```bash ```bash
# Build per production cd /opt/docker/insanitylab/src
npm run build git pull
docker compose up -d --build
# Opzionalmente, preview locale
npm run preview
# In production, il server parte con:
npm run start
# Variabili ambiente in production vanno impostate sull'hosting
# (es. Hostinger VPS, Vercel, etc.)
``` ```
## Riferimenti Tecnici Le migrazioni del database (in `src/lib/db.ts`) sono idempotenti e girano all'avvio del container: aggiornano lo schema e i contenuti senza perdere le modifiche fatte dal pannello.
Per dettagli su architettura, schema database, componenti e specifiche vedi: ### Dominio e DNS
- `/docs/superpowers/specs/2026-07-01-insanitylab-website-design.md`
- `/docs/superpowers/plans/2026-07-01-insanitylab-website.md`
## Problemi Comuni - Dominio ufficiale: **insanitylab.it** (canonico l'apex; `www` fa redirect 301 all'apex tramite Traefik).
- I record **A** di `@` e `www` puntano al VPS; la **posta resta su Aruba** (MX, SPF, webmail non vengono toccati).
- TLS Let's Encrypt automatico via Traefik (certresolver `mytlschallenge`).
### `better-sqlite3` fallisce su npm install > Attenzione dietro Traefik: `allowedDomains` in `astro.config.mjs` deve elencare i domini serviti, altrimenti Astro ignora `X-Forwarded-Proto/Host` e il controllo CSRF respinge i POST con 403.
Se `better-sqlite3` non compila, assicurati di avere build tools installati: ## Problemi comuni
- **Linux**: `sudo apt-get install build-essential python3`
- **macOS**: Xcode Command Line Tools (`xcode-select --install`)
- **Windows**: Visual Studio Build Tools o MinGW
### Database locked **`better-sqlite3` non compila su `npm install`** — servono i build tools:
- Linux: `sudo apt-get install build-essential python3`
- macOS: `xcode-select --install`
- Windows: Visual Studio Build Tools
Se il database è locato durante i test, verifica che non ci siano processi in background che accedono a `data/insanitylab.db`. **Modifiche allo `<style>` scoped di `[slug].astro` non compaiono in dev** — l'HMR di Astro 7 non sempre ricompila lo stile scoped delle route dinamiche: riavviare il dev server (`astro dev stop` + `npm run dev`).
**Database "locked" durante i test** — assicurarsi che nessun processo stia accedendo a `data/insanitylab.db`.
## Licenza ## Licenza
Proprietario InsanityLab. Proprietà di InsanityLab.
+21 -3
View File
@@ -1,18 +1,36 @@
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
import node from '@astrojs/node'; import node from '@astrojs/node';
import react from '@astrojs/react';
export default defineConfig({ export default defineConfig({
site: 'https://insanitylab.tielogic.xyz', site: 'https://insanitylab.it',
output: 'server', output: 'server',
// Dietro Traefik il TLS termina al proxy: senza allowedDomains Astro ignora // Dietro Traefik il TLS termina al proxy: senza allowedDomains Astro ignora
// X-Forwarded-Proto/Host e il checkOrigin CSRF respinge i POST (403). // X-Forwarded-Proto/Host e il checkOrigin CSRF respinge i POST (403).
// Dominio ufficiale insanitylab.it (canonico apex); www e il vecchio
// sottodominio tielogic.xyz redirigono all'apex a livello Traefik.
security: { security: {
allowedDomains: [{ hostname: 'insanitylab.tielogic.xyz', protocol: 'https' }], allowedDomains: [
{ hostname: 'insanitylab.it', protocol: 'https' },
{ hostname: 'www.insanitylab.it', protocol: 'https' },
{ hostname: 'insanitylab.tielogic.xyz', protocol: 'https' },
],
}, },
adapter: node({ mode: 'standalone' }), adapter: node({ mode: 'standalone' }),
// La sezione "Programmi" è stata rinominata "Servizi": redirect permanenti dai vecchi URL. // La sezione "Programmi" è stata rinominata "Servizi": redirect permanenti dai vecchi URL.
redirects: { redirects: {
'/programs': { status: 301, destination: '/services' }, '/programs': { status: 301, destination: '/services' },
'/programs/[slug]': { status: 301, destination: '/services/[slug]' }, '/programs/[slug]': { status: 301, destination: '/services/[slug]' },
// Modifiche 02: "Pilates Flow" → "Profilazione". "Rehab" esce dalla griglia Servizi e
// diventa un riquadro Training, ma la pagina dettaglio resta su /services/rehab.
'/services/pilates-flow': { status: 301, destination: '/services/profilazione' },
// "Personal Training" rinominato "One to One" (nuovo slug); Small Group è una pagina a sé.
'/services/personal-training': { status: 301, destination: '/services/one-to-one' },
}, },
});
integrations: [react()],
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More