```
- [ ] **Step 6: aggiorna index placeholder per usare Base**
```astro
---
import Base from '../layouts/Base.astro';
export const prerender = true;
---
Homepage in costruzione
```
- [ ] **Step 7: verifica visiva e build**
Run: `npm run dev` e apri `http://localhost:4321/` — header sticky con nav, footer scuro 4 colonne, pagina 404 su URL inesistente. Poi `npm run build`.
Expected: nessun errore; nav mobile funzionante sotto 992px.
- [ ] **Step 8: commit**
```bash
git add -A && git commit -m "feat: layout base con header, footer e 404"
```
---
### Task 8: Dati statici dei contenuti
**Files:**
- Create: `src/data/site.ts`, `src/data/programs.ts`, `src/data/trainings.ts`, `src/data/agenda.ts`, `src/data/team.ts`, `src/data/method.ts`, `src/data/pricing.ts`, `src/data/partners.ts`
**Interfaces:**
- Produces: export nominati usati dalle pagine. Testi reali dai PDF Figma (riportati sotto). Nessun lorem ipsum salvo dove il Figma stesso è placeholder (agenda, che riusa i nomi del template).
- [ ] **Step 1: site.ts**
```ts
export const site = {
name: 'InsanityLab',
phone: '351 7535977',
email: 'info@insanitylab.it',
address: 'Via Leandro Alberti, 76',
cityLine: '40139 Bologna',
hours: ['Lun – Ven 6.00 – 21.00', 'Sab 8.00 – 19.00 · Dom chiuso'],
socials: [
{ label: 'Instagram', url: 'https://www.instagram.com/insanitylab' },
{ label: 'Facebook', url: 'https://www.facebook.com/insanitylab' },
{ label: 'LinkedIn', url: 'https://www.linkedin.com/company/insanitylab' },
],
navigation: [
{ label: 'Insanitylab', href: '/about' },
{ label: 'Training', href: '/training' },
{ label: 'Programmi', href: '/programs' },
{ label: 'Blog & Edugo', href: '/blog' },
{ label: 'Contatti', href: '/contact' },
],
footerTraining: [
{ label: 'One to one', href: '/training#one-to-one' },
{ label: 'Small groups', href: '/training#small-groups' },
{ label: 'Performance class', href: '/training#performance-class' },
{ label: 'Fit remote', href: '/training#fit-remote' },
],
footerPrograms: [
{ label: 'Personal Training', href: '/programs/personal-training' },
{ label: 'Performance Class', href: '/programs/performance-class' },
{ label: 'Coaching', href: '/programs/coaching' },
{ label: 'Rehab', href: '/programs/rehab' },
{ label: 'Pilates Flow', href: '/programs/pilates-flow' },
{ label: 'Nutrizione', href: '/programs/nutrition' },
],
};
```
- [ ] **Step 2: programs.ts**
Excerpt = testi reali di Programmi.pdf. `longDescription` e `faq`: per `performance-class` dai testi di Performance class.pdf; per gli altri derivati dall'excerpt e da Training.pdf (il cliente li integrerà — testo comunque sensato, niente lorem).
```ts
export interface Program {
slug: string;
title: string;
subtitle: string;
excerpt: string;
longDescription: string;
features: string[];
faq: { q: string; a: string }[];
hasPricing: boolean;
}
export const programs: Program[] = [
{
slug: 'personal-training',
title: 'Personal Training',
subtitle: 'One to One / Small Group',
excerpt: 'Il personal training InsanityLab è un percorso one-to-one su misura, seguito da un professionista dedicato, per raggiungere in modo sicuro ed efficace obiettivi di forma fisica, performance e benessere.',
longDescription: 'Allenati con un trainer dedicato che costruisce un percorso su misura per i tuoi obiettivi. Ogni sessione one to one è pensata per migliorare performance, tecnica e risultati, con supporto costante, attenzione totale e allenamenti personalizzati. Disponibile anche in formula small group (massimo 3 persone) per unire attenzione personalizzata, motivazione e condivisione.',
features: ['Trainer dedicato', 'Percorso su misura', 'Valutazione iniziale e monitoraggio', 'Formula one to one o small group (max 3)'],
faq: [
{ q: 'Come si svolge la prima seduta?', a: 'Con una consulenza conoscitiva iniziale: raccogliamo l’anamnesi, definiamo gli obiettivi e impostiamo il percorso più adatto.' },
{ q: 'Con quale frequenza ci si allena?', a: 'La frequenza viene definita insieme al trainer in base a obiettivi, disponibilità e livello di partenza.' },
{ q: 'È adatto a chi parte da zero?', a: 'Sì: il percorso è costruito sul tuo livello, con progressioni graduali e supervisione costante.' },
],
hasPricing: false,
},
{
slug: 'performance-class',
title: 'Performance Class',
subtitle: 'Gruppo max 8 persone',
excerpt: 'La Performance Class è un allenamento in piccoli gruppi, basato su una programmazione progressiva che combina forza e condizionamento per ottenere risultati concreti e costanti nel tempo.',
longDescription: 'La Performance Class è un allenamento in piccoli gruppi con programmazione progressiva e scientifica, pensato per migliorare forza, composizione corporea e performance attraverso sessioni strutturate che combinano forza, ipertrofia e condizionamento metabolico. Non si improvvisa: ogni sessione ha un posto preciso all’interno di un piano più ampio.',
features: ['Allenamento ibrido', 'Forza, ipertrofia, condizionamento metabolico e lavoro funzionale', 'Sessioni da 50 minuti', 'Fasi di bassa, media e alta intensità', 'Programmazione mensile'],
faq: [
{ q: 'Che cosa si intende per Performance Class?', a: 'Un allenamento in piccoli gruppi (massimo 8 persone) guidato da trainer certificati, con programmazione progressiva mensile: ogni sessione ha un posto preciso all’interno di un piano più ampio.' },
{ q: 'Che tipo di allenamento viene svolto?', a: 'Un allenamento ibrido che alterna forza, ipertrofia, condizionamento metabolico e lavoro funzionale. Le sessioni durano 50 minuti e prevedono fasi di bassa, media e alta intensità, calibrate sulla programmazione mensile.' },
{ q: 'A chi è consigliato questo servizio?', a: 'A chi vuole allenarsi con qualità e continuità in un contesto di gruppo motivante, mantenendo una programmazione seria e misurabile.' },
{ q: 'Qual è la differenza rispetto a una class tradizionale?', a: 'La programmazione: non è una lezione isolata ma parte di un piano mensile progressivo, con carichi e intensità calibrati.' },
{ q: 'Che tipo di risultati o miglioramenti si ottengono?', a: 'Miglioramento di forza, composizione corporea e performance, con risultati concreti e costanti nel tempo.' },
{ q: 'Qual è l’intensità del lavoro?', a: 'Alternata: fasi di bassa, media e alta intensità all’interno della stessa sessione, adattate al livello dei partecipanti.' },
],
hasPricing: true,
},
{
slug: 'coaching',
title: 'Coaching',
subtitle: 'Programma di allenamento e monitoraggio',
excerpt: 'Il coaching InsanityLab è un percorso personalizzato che, con programma su misura e supporto costante, guida la persona verso i propri obiettivi sviluppando autonomia e continuità nell’allenamento.',
longDescription: 'Con il coaching hai un programma di allenamento su misura e un monitoraggio costante, ovunque ti alleni: in palestra, a casa o in viaggio. Il percorso sviluppa autonomia e continuità, adattandosi al tuo stile di vita (formula Fit Remote).',
features: ['Programma su misura', 'Supporto e monitoraggio costanti', 'Ovunque ti alleni (Fit Remote)', 'Sviluppo di autonomia'],
faq: [
{ q: 'Come funziona il monitoraggio?', a: 'Il trainer segue i tuoi progressi con check periodici e adatta il programma in base ai risultati.' },
{ q: 'Serve attrezzatura?', a: 'Il programma viene costruito sull’attrezzatura che hai a disposizione, in palestra o a casa.' },
{ q: 'È adatto se viaggio spesso?', a: 'Sì: la formula Fit Remote è pensata per chi si allena in luoghi diversi.' },
],
hasPricing: false,
},
{
slug: 'pilates-flow',
title: 'Pilates Flow',
subtitle: 'Movimento e controllo',
excerpt: 'Il Pilates Flow migliora postura, core e consapevolezza corporea con un movimento fluido, mentre l’osteopatia supporta recupero, prevenzione e benessere globale integrandosi con l’allenamento.',
longDescription: 'Il Pilates Flow unisce movimento fluido e controllo: migliora postura, core e consapevolezza corporea. Integrato con l’osteopatia, supporta recupero, prevenzione e benessere globale all’interno del percorso di allenamento.',
features: ['Postura e core', 'Movimento fluido e controllo', 'Integrazione con osteopatia', 'Benessere globale'],
faq: [
{ q: 'A chi è adatto il Pilates Flow?', a: 'A chiunque voglia migliorare postura, mobilità e controllo del movimento, a ogni livello.' },
{ q: 'Serve esperienza precedente?', a: 'No: le progressioni sono graduali e seguite da professionisti qualificati.' },
{ q: 'Come si integra con l’osteopatia?', a: 'Il lavoro osteopatico supporta recupero e prevenzione, in sinergia con le sessioni di movimento.' },
],
hasPricing: false,
},
{
slug: 'rehab',
title: 'Rehab',
subtitle: 'Recupero post-infortunio, patologie muscolo-scheletriche',
excerpt: 'Il Rehab InsanityLab è un percorso supervisionato che accompagna dal recupero riabilitativo al ritorno all’attività fisica in modo sicuro, progressivo e personalizzato.',
longDescription: 'Il Rehab accompagna dal recupero post-infortunio o da patologie muscolo-scheletriche fino al ritorno all’attività fisica. Percorso supervisionato da chinesiologi e osteopati, sicuro, progressivo e personalizzato.',
features: ['Recupero post-infortunio', 'Patologie muscolo-scheletriche', 'Supervisione di chinesiologi e osteopati', 'Progressione sicura e personalizzata'],
faq: [
{ q: 'Chi segue il percorso Rehab?', a: 'Professionisti specializzati in rieducazione funzionale ed esercizio adattato, in sinergia con l’area osteopatica.' },
{ q: 'Serve una diagnosi medica?', a: 'Se disponibile è utile portarla alla prima consulenza; il percorso si integra con le indicazioni dei sanitari.' },
{ q: 'Quanto dura il percorso?', a: 'Dipende dalla condizione di partenza: la progressione viene definita e aggiornata durante il percorso.' },
],
hasPricing: false,
},
{
slug: 'nutrition',
title: 'Nutrizione',
subtitle: 'Alimentazione e stile di vita',
excerpt: 'La nutrizione in InsanityLab è un servizio personalizzato che integra alimentazione e allenamento per migliorare energia, performance e benessere con piani su misura e sostenibili.',
longDescription: 'Il servizio di nutrizione integra alimentazione e allenamento: piani su misura e sostenibili per migliorare energia, performance e benessere, costruiti sul tuo stile di vita.',
features: ['Piani alimentari su misura', 'Integrazione con l’allenamento', 'Sostenibilità nel tempo', 'Focus su energia e performance'],
faq: [
{ q: 'Come inizia il percorso di nutrizione?', a: 'Con una valutazione iniziale di abitudini, obiettivi e allenamento, da cui nasce il piano personalizzato.' },
{ q: 'È integrato con il training?', a: 'Sì: alimentazione e allenamento vengono programmati in modo coerente.' },
{ q: 'Sono previsti controlli periodici?', a: 'Sì, con aggiornamento del piano in base ai risultati.' },
],
hasPricing: false,
},
];
export function getProgram(slug: string): Program | undefined {
return programs.find((p) => p.slug === slug);
}
```
- [ ] **Step 3: trainings.ts (4 card Training)**
```ts
export const trainings = [
{ id: 'one-to-one', title: 'One to One', caption: 'Allenamento con un trainer dedicato', image: 'training-one-to-one', description: 'Allenati con un trainer dedicato che costruisce un percorso su misura per i tuoi obiettivi. Ogni sessione one to one è pensata per migliorare performance, tecnica e risultati. Supporto costante, attenzione totale e allenamenti personalizzati per dare il massimo.', cta: { label: 'Contattaci', href: '/contact' } },
{ id: 'small-groups', title: 'Small Groups', caption: 'Mini gruppi (max 3 persone) con trainer dedicato', image: 'training-small-groups', description: 'Allenati in mini gruppi da massimo 3 persone con un trainer dedicato. Un’esperienza small group che unisce attenzione personalizzata, motivazione e condivisione. Programmi mirati e supporto costante per raggiungere i tuoi obiettivi insieme.', cta: { label: 'Contattaci', href: '/contact' } },
{ id: 'performance-class', title: 'Performance Class', caption: 'Classi fino a 8 persone con trainer certificato', image: 'training-performance-class', description: 'Allenati in classi fino a 8 persone guidate da trainer certificati. Sessioni dinamiche ad alta intensità pensate per migliorare forza, resistenza e performance. Energia, motivazione e allenamenti strutturati per dare il massimo in ogni workout.', cta: { label: 'Scopri', href: '/programs/performance-class' } },
{ id: 'fit-remote', title: 'Fit Remote', caption: 'Piano di allenamento su misura, ovunque ti alleni.', image: 'training-fit-remote', description: 'Con Fit Remote hai un piano di allenamento su misura, ovunque ti alleni. Programmi personalizzati creati sui tuoi obiettivi, da seguire in palestra, a casa o in viaggio. Supporto costante e allenamenti pensati per adattarsi al tuo stile di vita.', cta: { label: 'Contattaci', href: '/contact' } },
] as const;
```
- [ ] **Step 4: agenda.ts (orario placeholder dal template, come nel Figma)**
```ts
export const disciplines = ['Events', 'Crossfit', 'Cardio', 'Box', 'Meditation', 'Yoga Classes', 'Body Balance'] as const;
export const days = ['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica'] as const;
export const slots = ['10.00', '12.00', '16.00', '18.00', '20.00'] as const;
export interface AgendaEntry { day: number; slot: string; discipline: string; time: string; trainer: string }
// Placeholder dal template Prowess (come nel Figma); il cliente fornirà l'orario reale.
export const agenda: AgendaEntry[] = [
{ day: 0, slot: '10.00', discipline: 'Crossfit', time: '10.00 - 11.00', trainer: 'Amanda Snow' },
{ day: 0, slot: '12.00', discipline: 'Box', time: '12.00 - 13.00', trainer: 'Matthew Dunn' },
{ day: 0, slot: '18.00', discipline: 'Cardio', time: '18.00 - 19.00', trainer: 'Randy Nelson' },
{ day: 0, slot: '20.00', discipline: 'Crossfit', time: '20.00 - 21.00', trainer: 'Amanda Snow' },
{ day: 1, slot: '10.00', discipline: 'Cardio', time: '10.00 - 11.00', trainer: 'Randy Nelson' },
{ day: 1, slot: '12.00', discipline: 'Crossfit', time: '12.00 - 13.00', trainer: 'Amanda Snow' },
{ day: 1, slot: '16.00', discipline: 'Body Balance', time: '16.00 - 17.00', trainer: 'Barbara Holland' },
{ day: 1, slot: '20.00', discipline: 'Box', time: '20.00 - 21.00', trainer: 'Matthew Dunn' },
{ day: 2, slot: '10.00', discipline: 'Crossfit', time: '10.00 - 11.00', trainer: 'Amanda Snow' },
{ day: 2, slot: '12.00', discipline: 'Meditation', time: '12.00 - 13.00', trainer: 'Nicole Fuller' },
{ day: 2, slot: '16.00', discipline: 'Box', time: '16.00 - 17.00', trainer: 'Matthew Dunn' },
{ day: 2, slot: '20.00', discipline: 'Body Balance', time: '20.00 - 21.00', trainer: 'Barbara Holland' },
{ day: 3, slot: '10.00', discipline: 'Body Balance', time: '10.00 - 11.00', trainer: 'Barbara Holland' },
{ day: 3, slot: '12.00', discipline: 'Box', time: '12.00 - 13.00', trainer: 'Matthew Dunn' },
{ day: 3, slot: '16.00', discipline: 'Crossfit', time: '16.00 - 17.00', trainer: 'Amanda Snow' },
{ day: 3, slot: '20.00', discipline: 'Body Balance', time: '20.00 - 21.00', trainer: 'Randy Nelson' },
{ day: 4, slot: '12.00', discipline: 'Crossfit', time: '12.00 - 13.00', trainer: 'Amanda Snow' },
{ day: 4, slot: '16.00', discipline: 'Body Balance', time: '16.00 - 17.00', trainer: 'Barbara Holland' },
{ day: 4, slot: '18.00', discipline: 'Cardio', time: '18.00 - 19.00', trainer: 'Randy Nelson' },
{ day: 4, slot: '20.00', discipline: 'Yoga Classes', time: '20.00 - 21.00', trainer: 'Amanda Snow' },
{ day: 5, slot: '10.00', discipline: 'Cardio', time: '10.00 - 11.00', trainer: 'Randy Nelson' },
{ day: 5, slot: '12.00', discipline: 'Body Balance', time: '12.00 - 13.00', trainer: 'Barbara Holland' },
{ day: 5, slot: '18.00', discipline: 'Box', time: '18.00 - 19.00', trainer: 'Matthew Dunn' },
{ day: 6, slot: '12.00', discipline: 'Crossfit', time: '12.00 - 13.00', trainer: 'Amanda Snow' },
{ day: 6, slot: '16.00', discipline: 'Cardio', time: '16.00 - 17.00', trainer: 'Randy Nelson' },
{ day: 6, slot: '18.00', discipline: 'Box', time: '18.00 - 19.00', trainer: 'Matthew Dunn' },
{ day: 6, slot: '20.00', discipline: 'Body Balance', time: '20.00 - 21.00', trainer: 'Barbara Holland' },
];
```
- [ ] **Step 5: team.ts**
```ts
export interface Member { name: string; shortName: string; role: string; bio: string; image?: string; founder: boolean }
export const team: Member[] = [
{ name: 'Donata Dal Pastro', shortName: 'Donata', role: 'Chinesiologa e Osteopata', founder: true, image: 'team-donata',
bio: 'Donata Dal Pastro è chinesiologa e osteopata specializzata in rieducazione funzionale, riatletizzazione sportiva ed esercizio adattato. Supervisiona l’area dedicata a prevenzione, recupero e benessere, con competenze specifiche anche in pediatria e gravidanza.' },
{ name: 'Nicola Antonelli', shortName: 'Nicola', role: 'Chinesiologo e Osteopata', founder: true, image: 'team-nicola',
bio: 'Nicola Antonelli è chinesiologo e osteopata specializzato in performance sportiva, functional training e bodybuilding. Coordina lo sviluppo tecnico dei percorsi dedicati alla performance e all’allenamento avanzato.' },
{ name: 'Eva Cassetta', shortName: 'Eva', role: 'Business Manager – Balance & Strategy', founder: true,
bio: 'Eva Cassetta ricopre il ruolo di Business Manager – Balance & Strategy e si occupa della gestione amministrativa, economica e finanziaria del progetto, garantendone solidità, organizzazione e sostenibilità nel tempo.' },
{ name: 'Alessandro Lazzari', shortName: 'Alessandro', role: 'Trainer – Functional & Movement', founder: false,
bio: 'Trainer – Functional & Movement. Laureando in Scienze Motorie, specializzato in running e atletica leggera.' },
{ name: 'Eleonora Cassani', shortName: 'Eleonora', role: 'Trainer – Planning Manager', founder: false, image: 'team-eleonora',
bio: 'Trainer – Planning Manager. Chinesiologa specializzata in attività preventiva e adattata, con focus su allenamento in gravidanza e post-gravidanza.' },
{ name: "Antonello D'Alessandro", shortName: 'Antonello', role: 'Trainer – Exercise & Health', founder: false, image: 'team-antonello',
bio: 'Trainer – Exercise & Health. Chinesiologo specializzato in attività preventiva e adattata, Dottore in Osteopatia.' },
{ name: 'Alice Roda', shortName: 'Alice', role: 'Trainer – Functional & Movement', founder: false,
bio: 'Trainer – Functional & Movement. Laureanda in Scienze Motorie e Preparatore Atletico. Scienza del movimento e massima cura della persona: un approccio al functional training empatico e accogliente, dove l’ascolto umano guida e supporta ogni tuo progresso.' },
{ name: 'Davide Bisagni', shortName: 'Davide', role: 'Trainer – Functional & Movement', founder: false,
bio: 'Trainer – Functional & Movement. Chinesiologo e allenamento funzionale al servizio del corpo: massima cura del gesto atletico e attenzione mirata al recupero per prestazioni sostenibili e durature.' },
{ name: 'Giulia Barbàra', shortName: 'Giulia', role: 'Trainer – Pilates, Yoga & Mindfulness', founder: false,
bio: 'Trainer – Pilates, Yoga & Mindfulness. Laureata in Psicologia Clinica e Riabilitazione, insegnante di Pilates, Yoga e Mindfulness, con approccio orientato al benessere e all’equilibrio mente-corpo.' },
];
export const homeTeam = ['Donata', 'Nicola', 'Eleonora', 'Antonello'];
```
- [ ] **Step 6: method.ts, pricing.ts, partners.ts**
`src/data/method.ts`:
```ts
export const methodIntro = 'Il Metodo Insanitylab si basa su un percorso strutturato in fasi, dove le prime due rappresentano il focus standard del lavoro: valutazione iniziale e avvio dell’allenamento personalizzato. A queste possono essere aggiunti servizi e approfondimenti extra, in base alle esigenze e agli obiettivi della persona.';
export const methodExtraNote = 'Disponibili su richiesta, non incluse nel percorso standard.';
export const methodSteps = [
{ n: 1, title: 'Prima consulenza conoscitiva iniziale', text: 'Dove raccogliamo un’anamnesi iniziale per comprendere il cliente, i suoi obiettivi e guidarlo verso il percorso più adatto.', extra: false, image: 'method-step-1' },
{ n: 2, title: 'Prima seduta operativa', text: 'In cui si effettua la profilazione iniziale e la valutazione sul campo per definire il punto di partenza e impostare un percorso personalizzato.', extra: false, image: 'method-step-2' },
{ n: 3, title: 'Plicometria', text: 'Test che misura il grasso sottocutaneo per stimare la massa grassa, disponibile su richiesta o come servizio aggiuntivo.', extra: true, image: 'method-step-3' },
{ n: 4, title: 'Analisi antropometriche', text: 'Le analisi antropometriche sono misurazioni del corpo utili a valutare la composizione fisica e lo stato di forma.', extra: true, image: 'method-step-4' },
];
```
`src/data/pricing.ts` (prezzi da Performance class.pdf):
```ts
export const performanceClassPricing = {
heading: 'Acquista la tua performance class',
intro: 'Questi sono i programmi delle classi di Insanity Lab selezionati per voi. Abbiamo scelto i migliori percorsi per offrirvi un’esperienza di allenamento completa e mirata.',
groups: [
{
label: 'Programmi performance class semestrale',
open: true,
plans: [
{ monthly: 130, entries: 1, note: 'tot: 780€ · da 23€ lezione singola', installments: '2 rate da 390€ – inizio 60', best: 'best price 720€' },
{ monthly: 234, entries: 2, note: 'tot: 1.400€ · da 23€ lezione singola', installments: '2 rate da 700€ – inizio 60', best: 'best price 1.250€' },
{ monthly: 300, entries: 3, note: 'tot: 1.800€ · da 23€ lezione singola', installments: '2 rate da 900€ – inizio 60', best: 'best price 1.650€' },
],
},
{ label: 'Programmi performance class trimestrale', open: false, plans: [] },
{ label: 'Programmi performance class mensile', open: false, plans: [] },
],
};
```
(I gruppi senza piani mostrano "Contattaci per i dettagli" — nel Figma sono accordion chiusi senza contenuto visibile.)
`src/data/partners.ts`:
```ts
export const partners = [
{ name: 'Ticket Welfare Edenred', image: 'partner-edenred' },
{ name: 'Welbee', image: 'partner-welbee' },
{ name: 'AON', image: 'partner-aon' },
{ name: 'Howden', image: 'partner-howden' },
];
```
- [ ] **Step 7: verifica typecheck e commit**
Run: `npx astro check 2>&1 | tail -5` (o `npx tsc --noEmit`)
Expected: nessun errore sui file dati.
```bash
git add src/data && git commit -m "feat: dati statici contenuti da design Figma"
```
---
### Task 9: Homepage
**Files:**
- Create: `src/components/home/HeroSlider.astro`, `src/components/home/InfoColumns.astro`, `src/components/home/FeatureBlocks.astro`, `src/components/home/QuoteBanner.astro`, `src/components/home/TrainingCards.astro`, `src/components/home/ProgramsGrid.astro`, `src/components/Agenda.astro`, `src/components/home/TeamSection.astro`, `src/components/home/MethodSteps.astro`, `src/components/home/PartnersStrip.astro`
- Modify: `src/pages/index.astro`
**Interfaces:**
- Consumes: dati Task 8, immagini Task 2 (`src/assets/img/*` via `astro:assets`), utility CSS Task 3.
- Produces: `` riusato dal Task 11; `` riusato dal Task 11.
Riferimento visivo: `../FigmaDoc/Homepage.pdf`. Per confronto genera le fette: `pdftoppm -png -r 50 ../FigmaDoc/Homepage.pdf /tmp/ilab-home && convert /tmp/ilab-home-1.png -crop x1800 +repage /tmp/ilab-home-slice_%d.png`.
- [ ] **Step 1: HeroSlider.astro**
Hero fullscreen: titolo `PERFORMANCE.` / `BALANCE.` / `LONGEVITY.` su righe, bottone SCOPRI → `/programs`, immagine hero a destra, pattern di linee orizzontali a sinistra, frecce navigazione. Il Figma mostra una sola slide compiuta: implementare struttura a slide con array (1 slide ora, estendibile).
```astro
---
import { Image } from 'astro:assets';
import hero1 from '../../assets/img/hero-1.jpg';
const slides = [
{ title: ['PERFORMANCE.', 'BALANCE.', 'LONGEVITY.'], image: hero1, alt: 'Atleti InsanityLab' },
];
---
{slides.map((s, i) => (
))}
{slides.length > 1 && (
<>
>
)}
```
- [ ] **Step 2: InfoColumns.astro**
Tre colonne full-width con foto di sfondo e overlay scuro, titoli `PERFORMANCE.` `BALANCE.` `LONGEVITY.`; ogni colonna linka `/programs`.
```astro
---
import infoPerformance from '../../assets/img/info-performance.jpg';
import infoBalance from '../../assets/img/info-balance.jpg';
import infoLongevity from '../../assets/img/info-longevity.jpg';
const cols = [
{ title: 'PERFORMANCE.', image: infoPerformance },
{ title: 'BALANCE.', image: infoBalance },
{ title: 'LONGEVITY.', image: infoLongevity },
];
---
{cols.map((c) => (
{c.title}
))}
```
- [ ] **Step 3: FeatureBlocks.astro**
Tre blocchi alternati testo/immagine (testi reali da Homepage.pdf):
```astro
---
import { Image } from 'astro:assets';
import featPerformance from '../../assets/img/feature-performance.jpg';
import featBalance from '../../assets/img/feature-balance.jpg';
import featLongevity from '../../assets/img/feature-longevity.jpg';
const blocks = [
{ title: 'PERFORMANCE', image: featPerformance, reverse: false,
text: 'Allenamenti progettati su misura per adattarsi ai tuoi obiettivi e al tuo livello. Programmi mirati che sviluppano forza, resistenza e funzionalità in modo equilibrato. Un metodo efficace per migliorare le performance e ottenere risultati concreti e duraturi.' },
{ title: 'BALANCE', image: featBalance, reverse: true,
text: 'L’equilibrio tra corpo e mente è la base di ogni performance duratura. Attraverso movimento, recupero e gestione dello stress, costruiamo stabilità e controllo. Un approccio integrato che sostiene benessere, energia e continuità nel tempo.' },
{ title: 'LONGEVITY', image: featLongevity, reverse: false,
text: 'Benessere completo tra movimento, nutrizione e stile di vita. Un percorso personalizzato che unisce prevenzione, recupero ed equilibrio. Investi nella tua salute e qualità della vita nel tempo.' },
];
---
```
- [ ] **Step 4: QuoteBanner.astro**
Banner scuro con foto di sfondo e citazione (nel Figma non è marquee ma banner statico con virgolette):
```astro
---
import quoteBg from '../../assets/img/quote-bg.jpg';
---
“
PERFORMANCE È SPINGERE OLTRE I PROPRI LIMITI, BALANCE È SAPERLI ASCOLTARE, LONGEVITY È COSTRUIRE NEL TEMPO CIÒ CHE DAVVERO CONTA.
Insanitylab
```
- [ ] **Step 5: TrainingCards.astro**
Griglia 2×4 in alternanza card testo (scura/tan) e card foto, dati da `trainings.ts` (riusato in `/training`):
```astro
---
import { Image } from 'astro:assets';
import { trainings } from '../../data/trainings';
const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/training-*.{jpg,png}', { eager: true });
const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(name))?.[1].default;
const tones = ['dark', 'tan', 'dark', 'tan'];
---
{trainings.map((t, i) => (
<>
Questi sono i programmi delle classi di Insanity Lab selezionati per voi. Abbiamo scelto i migliori percorsi per offrirvi un’esperienza di allenamento completa e mirata.
```
(Se nell'archivio Figma sono presenti le icone originali dei programmi, usale al posto degli SVG inline: mappale nel Task 2 come `icon-.png`.)
- [ ] **Step 7: Agenda.astro (condiviso)**
Sezione scura con tab discipline e griglia settimanale:
```astro
---
import { agenda, disciplines, days, slots } from '../data/agenda';
---
```
`MethodSteps.astro` — intestazione "IL METODO INSANITYLAB" + 4 cerchi numerati con linea tratteggiata; nota extra sopra gli step 3–4:
```astro
---
import { Image } from 'astro:assets';
import { methodIntro, methodExtraNote, methodSteps } from '../../data/method';
const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/method-*.{jpg,png}', { eager: true });
const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(name))?.[1].default;
---
Il metodo InsanityLab
Metodo Insanitylab')} />
{methodExtraNote}
{methodSteps.map((s) => (
{imgOf(s.image) && }
{s.n}
{s.title}
{s.text}
))}
```
`PartnersStrip.astro` — striscia tan con i 4 loghi:
```astro
---
import { Image } from 'astro:assets';
import { partners } from '../../data/partners';
const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/partner-*.{jpg,png}', { eager: true });
const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(name))?.[1].default;
---
{partners.map((p) => imgOf(p.image) && )}
```
- [ ] **Step 9: componi index.astro**
```astro
---
import Base from '../layouts/Base.astro';
import HeroSlider from '../components/home/HeroSlider.astro';
import InfoColumns from '../components/home/InfoColumns.astro';
import FeatureBlocks from '../components/home/FeatureBlocks.astro';
import QuoteBanner from '../components/home/QuoteBanner.astro';
import TrainingCards from '../components/home/TrainingCards.astro';
import ProgramsGrid from '../components/home/ProgramsGrid.astro';
import Agenda from '../components/Agenda.astro';
import TeamSection from '../components/home/TeamSection.astro';
import MethodSteps from '../components/home/MethodSteps.astro';
import PartnersStrip from '../components/home/PartnersStrip.astro';
export const prerender = true;
---
Training
Porta i tuoi allenamenti al livello successivo: trova il percorso più adatto alle tue esigenze.
```
- [ ] **Step 10: confronto visivo con Figma**
Run: `npm run dev`, apri `http://localhost:4321/` e confronta sezione per sezione con `/tmp/ilab-home-slice_*.png`. Correggi spaziature, dimensioni titoli, colori finché la corrispondenza è fedele (non pixel-perfect: stessa gerarchia, palette, proporzioni). Verifica mobile a 375px.
- [ ] **Step 11: build e commit**
Run: `npm run build`
Expected: build ok.
```bash
git add -A && git commit -m "feat: homepage completa da design Figma"
```
---
### Task 10: Pagina About (`/about`)
**Files:**
- Create: `src/pages/about.astro`, `src/components/about/Timeline.astro`, `src/components/PageHero.astro`
**Interfaces:**
- Consumes: `team` (Task 8), immagini `about-*`/`team-*` (Task 2), `site`.
- Produces: `` riusato da training/programs/blog/contact (banner titolo pagina su fondo chiaro con eyebrow).
Riferimento: `../FigmaDoc/Insanitylab.pdf` (generare fette come per la homepage). Sezioni nell'ordine: hero titolo "Insanitylab", Mission, Direzione (+ bottone Scopri), Approccio (+ bottone Contattaci), About, Storylab (timeline), Convenzioni, Team (3 fondatori + 6 trainer), Metodo (testo), In-Sanity Studios, Vision.
- [ ] **Step 1: PageHero.astro**
```astro
---
interface Props { title: string; eyebrow?: string }
const { title, eyebrow } = Astro.props;
---
{eyebrow &&
{eyebrow}
}
{title}
```
- [ ] **Step 2: Timeline.astro (Storylab)**
Blocco 2021-2024 aperto con testo; 2025, 2026 e OGGI come accordion ``:
```astro
---
const openText = `Il percorso di crescita di InsanityLab si è sviluppato attraverso tappe precise e coerenti con la visione iniziale. Nato nel 2021 con un piccolo team e grandi ambizioni, il progetto ha conosciuto una forte evoluzione nel 2024, anno in cui il servizio si è ampliato attraverso nuovi format come gli small group e una sempre maggiore specializzazione nella profilazione fisica del cliente, grazie a sistemi avanzati di valutazione e monitoraggio. Parallelamente, anche l’area dedicata alla riabilitazione e all’esercizio preventivo si è consolidata, ponendo le basi per un importante salto di qualità.`;
const stages = [
{ label: 'INSANITYLAB 2025', text: 'Contenuto in arrivo: il cliente fornirà il testo di questa tappa.' },
{ label: 'INSANITYLAB 2026', text: 'Contenuto in arrivo: il cliente fornirà il testo di questa tappa.' },
{ label: 'INSANITYLAB OGGI', text: 'Contenuto in arrivo: il cliente fornirà il testo di questa tappa.' },
];
---
Storylab
Scopri la nostra storia: come nasce InsanityLab, perché abbiamo deciso di creare qualcosa di diverso e chi sono i visionari che hanno trasformato questa idea in un nuovo modo di vivere benessere, movimento e salute.
INSANITYLAB 2021-2024
{openText}
{stages.map((s) => (
+ {s.label}
{s.text}
))}
```
- [ ] **Step 3: about.astro**
Testi reali da Insanitylab.pdf (già estratti — riportati integralmente qui sotto). Struttura: alternanza di sezioni testo/immagine come nel PDF.
```astro
---
import Base from '../layouts/Base.astro';
import PageHero from '../components/PageHero.astro';
import Timeline from '../components/about/Timeline.astro';
import { Image } from 'astro:assets';
import { team } from '../data/team';
import { site } from '../data/site';
const images = import.meta.glob<{ default: ImageMetadata }>('../assets/img/*.{jpg,png}', { eager: true });
const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(name))?.[1]?.default;
const founders = team.filter((m) => m.founder);
const staff = team.filter((m) => !m.founder);
export const prerender = true;
---
Mission
In InsanityLab crediamo che il movimento non debba essere vissuto come un obbligo o un sacrificio, ma come una forma autentica di cura di sé. La nostra missione è accompagnare ogni persona in un percorso di consapevolezza, aiutandola a riscoprire il benessere fisico e mentale attraverso un approccio sostenibile, concreto e duraturo.
Direzione
Non ci limitiamo ad allenare il corpo: creiamo percorsi personalizzati che uniscono movimento, equilibrio e benessere, attraverso il supporto di professionisti qualificati, programmi su misura e un approccio centrato sulla persona e su risultati duraturi.
InsanityLab si distingue per un approccio serio e personalizzato al benessere, basato su competenze specializzate, costanza e cura quotidiana, rivolto a chiunque voglia migliorare il proprio stile di vita in modo consapevole e duraturo.
InsanityLab nasce con l’obiettivo di unire fitness e salute in un unico percorso dedicato al benessere globale, superando il concetto tradizionale di palestra. Un laboratorio esclusivo, guidato da osteopati e chinesiologi e professionisti specializzati, dove movimento, prevenzione e performance convivono all’interno di un approccio personalizzato e consapevole.
Convenzioni
InsanityLab collabora con aziende, enti e organizzazioni che desiderano investire concretamente nel benessere delle proprie persone, attraverso programmi dedicati alla salute, al movimento e alla qualità della vita. Le convenzioni nascono con l’obiettivo di portare il valore del benessere anche all’interno del contesto professionale, creando percorsi che favoriscano equilibrio fisico, prevenzione e performance quotidiana.
Le realtà convenzionate possono accedere a programmi di welfare aziendale personalizzati, con percorsi individuali, small group training e classi di gruppo seguiti da professionisti qualificati.
Ogni collaborazione parte da una consulenza gratuita preliminare per analizzare esigenze e obiettivi, così da sviluppare soluzioni efficaci e sostenibili.
Le convenzioni vengono definite su misura insieme a InsanityLab, in base alle caratteristiche e alle necessità dell’azienda partner.
Per ricevere maggiori informazioni o attivare una convenzione: {site.email} · {site.phone}
Team
Il team di InsanityLab riunisce professionisti specializzati in allenamento, salute e benessere, con un approccio multidisciplinare basato su competenza, personalizzazione e attenzione alla persona.
Il team InsanityLab lavora con un approccio empatico e personalizzato, seguendo ogni persona in un percorso continuo e su misura. Unisce competenze multidisciplinari in allenamento, prevenzione, riabilitazione e performance, supportate da formazione costante. Il metodo si basa su sinergia, condivisione e qualità, per generare risultati concreti e duraturi.
In poche parole, il team di InsanityLab è composto da professionisti che credono profondamente in ciò che fanno: un gruppo unito da valori condivisi, formato per eccellere e guidato dalla convinzione che il movimento sia uno strumento di benessere, equilibrio e qualità della vita.
In-Sanity Studios
IN-SANITY STUDIOS è il nuovo spazio professionale nato dall’evoluzione di InsanityLab, dedicato ai professionisti della salute, del benessere e della performance. Più che un semplice insieme di studi professionali, rappresenta un ecosistema multidisciplinare pensato per favorire collaborazione, qualità del servizio e integrazione tra competenze diverse.
Il progetto nasce con l’obiettivo di offrire a liberi professionisti uno spazio elegante, riservato e già attrezzato, in cui poter svolgere la propria attività all’interno di un contesto consolidato e altamente qualificato, senza dover affrontare i costi e le complessità di una struttura indipendente.
IN-SANITY STUDIOS si rivolge a figure specializzate nel settore salute e wellness, come fisioterapisti, osteopati, psicologi, nutrizionisti, massaggiatori e altri professionisti dedicati alla cura della persona. Ogni studio è progettato per garantire comfort, professionalità e continuità operativa, all’interno di un ambiente coerente con la filosofia di InsanityLab: mettere il benessere globale della persona al centro del percorso.
Vision
InsanityLab nasce con l’obiettivo di aiutare le persone a ritrovare energia ed equilibrio attraverso il movimento, promuovendo un approccio autentico, scientifico e umano al benessere. La sua visione va oltre l’estetica e la performance: allenarsi significa prendersi cura di sé, costruendo abitudini sane, sostenibili e orientate alla longevità, alla prevenzione e alla qualità della vita.
L’obiettivo è diventare un punto di riferimento capace di lasciare un impatto reale nella comunità, trasformando il movimento in uno strumento di salute, consapevolezza e identità.
```
Nota: se nell'archivio Figma trovi le immagini delle sezioni about (mission, direzione, in-sanity studios), aggiungi accanto ai testi con lo stesso pattern `Image` — confronta col PDF.
- [ ] **Step 2b: i tre accordion Storylab (2025, 2026, OGGI) hanno testo placeholder esplicito "Contenuto in arrivo" — segnala all'operatore nel riepilogo finale del task che servono i testi dal cliente.**
- [ ] **Step 4: verifica e commit**
Run: `npm run dev`, confronta con le fette di `Insanitylab.pdf`; poi `npm run build`.
```bash
git add -A && git commit -m "feat: pagina about"
```
---
### Task 11: Pagina Training (`/training`)
**Files:**
- Create: `src/pages/training.astro`
**Interfaces:**
- Consumes: `trainings` (Task 8), ``, `` (Task 9), `` (Task 10).
Riferimento: `../FigmaDoc/Training.pdf`. Struttura: hero "training", intestazione Training, griglia 4 card (riuso `TrainingCards`), 4 blocchi alternati testo/immagine (one to one, small groups, performance class, fit remote) con ancore `id`, Agenda, CTA "Prenota la lezione di prova".
- [ ] **Step 1: training.astro**
```astro
---
import Base from '../layouts/Base.astro';
import PageHero from '../components/PageHero.astro';
import TrainingCards from '../components/home/TrainingCards.astro';
import Agenda from '../components/Agenda.astro';
import { Image } from 'astro:assets';
import { trainings } from '../data/trainings';
const images = import.meta.glob<{ default: ImageMetadata }>('../assets/img/training-*.{jpg,png}', { eager: true });
const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(name))?.[1]?.default;
export const prerender = true;
---
Training
Porta i tuoi allenamenti al livello successivo: programmi completi e percorsi su misura. Trova il training più adatto alle tue esigenze.
La lezione di prova è l’occasione perfetta per conoscere il metodo, vivere l’esperienza in prima persona e capire il percorso più adatto a te. Inizia oggi il tuo percorso: prenota la tua lezione di prova.
```
- [ ] **Step 2: verifica e commit**
Run: `npm run dev`, confronta con `Training.pdf`; ancore `#one-to-one` ecc. funzionanti dal footer; `npm run build`.
```bash
git add -A && git commit -m "feat: pagina training"
```
---
### Task 12: Pagine Programmi (`/programs`, `/programs/[slug]`)
**Files:**
- Create: `src/pages/programs/index.astro`, `src/pages/programs/[slug].astro`
**Interfaces:**
- Consumes: `programs`, `getProgram` (Task 8), `performanceClassPricing` (Task 8), ``.
- Produces: pagine prerenderizzate via `getStaticPaths`.
Riferimento: `../FigmaDoc/Programmi.pdf` e `../FigmaDoc/Performance class.pdf`.
- [ ] **Step 1: programs/index.astro**
```astro
---
import Base from '../../layouts/Base.astro';
import PageHero from '../../components/PageHero.astro';
import { programs } from '../../data/programs';
export const prerender = true;
---
Programmi
Questi sono i programmi delle classi di Insanity Lab selezionati per voi. Abbiamo scelto i migliori percorsi per offrirvi un’esperienza di allenamento completa e mirata.
Per informazioni o per richiedere una consulenza iniziale, compila il form. Un team specializzato ti accoglierà e ti ricontatterà al più presto, oppure lasciaci il tuo contatto.
```
- [ ] **Step 7: verifica manuale API**
Run (con dev server attivo):
```bash
curl -s -X POST localhost:4321/api/contact -H 'Content-Type: application/json' -d '{"firstName":"Test","email":"t@t.it","message":"ciao"}'
```
Expected: `{"error":"Invio non riuscito..."}` (502, SMTP finto in `.env`) — la catena valida→invia funziona. Con honeypot: `-d '{"firstName":"T","email":"t@t.it","message":"x","website":"spam"}'` → 400.
- [ ] **Step 8: commit**
```bash
git add -A && git commit -m "feat: pagina contatti e api email con validazione testata"
```
---
### Task 14: Blog pubblico (`/blog`, `/blog/[slug]`)
**Files:**
- Create: `src/pages/blog/index.astro`, `src/pages/blog/[slug].astro`, `src/lib/blog-const.ts`, `src/components/blog/Sidebar.astro`
**Interfaces:**
- Consumes: `getDb`, `listPublished`, `getPublishedBySlug`, `listRecent`, `listArchiveMonths` (Task 5), ``.
- Produces: `CATEGORIES: string[]` in `blog-const.ts` (usato anche dall'admin, Task 15).
- [ ] **Step 1: blog-const.ts**
```ts
export const CATEGORIES = [
'Articoli tecnici & scientifici',
'Contenuti educativi',
'LinkedIn / Insight',
'Eventi & Presidi',
] as const;
```
- [ ] **Step 2: blog/index.astro (SSR)**
```astro
---
import Base from '../../layouts/Base.astro';
import PageHero from '../../components/PageHero.astro';
import { getDb } from '../../lib/db';
import { listPublished } from '../../lib/posts';
import { CATEGORIES } from '../../lib/blog-const';
export const prerender = false;
const PER_PAGE = 9;
const url = Astro.url;
const category = url.searchParams.get('categoria') ?? undefined;
const page = Math.max(1, Number(url.searchParams.get('pagina') ?? '1') || 1);
const { items, total } = listPublished(getDb(), {
category: category && (CATEGORIES as readonly string[]).includes(category) ? category : undefined,
page, perPage: PER_PAGE,
});
const pages = Math.max(1, Math.ceil(total / PER_PAGE));
const fmt = (d: string | null) => d ? new Intl.DateTimeFormat('it-IT', { dateStyle: 'long' }).format(new Date(d)) : '';
const linkFor = (cat?: string, p = 1) => {
const q = new URLSearchParams();
if (cat) q.set('categoria', cat);
if (p > 1) q.set('pagina', String(p));
const s = q.toString();
return `/blog${s ? `?${s}` : ''}`;
};
---
Blog & Edugo
Edugo è lo spazio di InsanityLab dedicato alla conoscenza: articoli, guide e approfondimenti su allenamento, nutrizione e salute per capire il corpo e migliorare il proprio benessere. Perché la conoscenza è il primo passo del cambiamento.
{items.length === 0 ? (
Nessun articolo pubblicato{category ? ' in questa categoria' : ''}, torna a trovarci presto.
```
Nota: `body_html` è già sanitizzato al salvataggio (Task 15); `set:html` è accettabile solo per questo campo.
- [ ] **Step 5: verifica con dati di prova**
```bash
sqlite3 data/insanitylab.db "INSERT INTO posts (title, slug, category, excerpt, body_html, draft, published_at) VALUES ('Articolo di prova', 'articolo-di-prova', 'Contenuti educativi', 'Un estratto di prova.', '
Corpo di prova.
', 0, datetime('now'));"
curl -s localhost:4321/blog | grep -o 'Articolo di prova' | head -1
curl -s localhost:4321/blog/articolo-di-prova | grep -o 'Corpo' | head -1
curl -s -o /dev/null -w '%{http_code}' localhost:4321/blog/inesistente
```
Expected: `Articolo di prova`, `Corpo`, redirect/404 per lo slug inesistente. Verifica filtri categoria e paginazione con più insert se serve.
- [ ] **Step 6: commit**
```bash
git add -A && git commit -m "feat: blog pubblico con filtri, paginazione e articolo"
```
---
### Task 15: Area admin — login, lista, editor TipTap, API CRUD e upload (TDD su sanitizzazione)
**Files:**
- Create: `src/lib/sanitize.ts`, `src/lib/post-body.ts`, `src/layouts/Admin.astro`, `src/pages/admin/login.astro`, `src/pages/admin/logout.ts`, `src/pages/admin/index.astro`, `src/pages/admin/new.astro`, `src/pages/admin/edit/[id].astro`, `src/components/admin/PostForm.astro`, `src/pages/api/admin/posts/index.ts`, `src/pages/api/admin/posts/[id].ts`, `src/pages/api/admin/upload.ts`, `src/pages/uploads/[...path].ts`
- Test: `tests/sanitize.test.ts`
**Interfaces:**
- Consumes: auth/middleware (Task 6), posts (Task 5), `slugify` (Task 4), `CATEGORIES` (Task 14), `rateLimit` (Task 4), `getEnv` (Task 4).
- Produces:
- `sanitizeHtml(html: string): string` in `src/lib/sanitize.ts`.
- `POST /api/admin/posts` body `{ title, slug?, category, excerpt, body_html, cover?, draft }` → 201 `{ id, slug }`.
- `PUT /api/admin/posts/[id]` stesso body → 200; `DELETE /api/admin/posts/[id]` → 200.
- `POST /api/admin/upload` (multipart, campo `file`) → 200 `{ url: '/uploads/' }`.
- `GET /uploads/` serve da `UPLOADS_DIR`.
- [ ] **Step 1: test sanitizzazione (falliranno)**
`tests/sanitize.test.ts`:
```ts
import { describe, it, expect } from 'vitest';
import { sanitizeHtml } from '../src/lib/sanitize';
describe('sanitizeHtml', () => {
it('mantiene la formattazione consentita', () => {
const html = '