5 Commits

Author SHA1 Message Date
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
26 changed files with 453 additions and 1232 deletions
+5
View File
@@ -14,5 +14,10 @@ export default defineConfig({
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' },
}, },
}); });
Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

+4
View File
@@ -25,11 +25,15 @@ const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(
</div> </div>
))} ))}
</div> </div>
<div class="method__cta" data-reveal>
<a class="btn btn--dark" href="/contact"><T tag="home.method.cta" as="span" /></a>
</div>
</div> </div>
</section> </section>
<style> <style>
.method__note { text-align: right; font-size: .8rem; color: var(--c-text-light); max-width: 260px; margin-left: auto; } .method__note { text-align: right; font-size: .8rem; color: var(--c-text-light); max-width: 260px; margin-left: auto; }
.method__cta { text-align: center; margin-top: 48px; }
.method__row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 30px; text-align: center; position: relative; } .method__row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 30px; text-align: center; position: relative; }
.method__circle { position: relative; width: 150px; height: 150px; margin: 0 auto 20px; border: 1px dashed var(--c-accent-dark); border-radius: 50%; padding: 8px; } .method__circle { position: relative; width: 150px; height: 150px; margin: 0 auto 20px; border: 1px dashed var(--c-accent-dark); border-radius: 50%; padding: 8px; }
.method__circle img { border-radius: 50%; width: 100%; height: 100%; object-fit: cover; } .method__circle img { border-radius: 50%; width: 100%; height: 100%; object-fit: cover; }
+1 -1
View File
@@ -14,7 +14,7 @@ const isSwapRow = (i: number) => Math.floor(i / 2) % 2 === 1;
{trainings.map((tr, i) => { {trainings.map((tr, i) => {
const img = imgOf(tr.image); const img = imgOf(tr.image);
const card = ( const card = (
<div class:list={['tcards__cell', `tcards__cell--${tones[i]}`]} data-reveal="fade" style={`--reveal-delay:${(i % 2) * 0.15}s`}> <div class:list={['tcards__cell', `tcards__cell--${tones[i % tones.length]}`]} data-reveal="fade" style={`--reveal-delay:${(i % 2) * 0.15}s`}>
<T tag={`training.${tr.id}.title`} as="h3" /> <T tag={`training.${tr.id}.title`} as="h3" />
<T tag={`training.${tr.id}.caption`} as="p" /> <T tag={`training.${tr.id}.caption`} as="p" />
<a class="tcards__link" href={tr.cta.href}><T tag="home.training-cards.link-label" as="span" /> →</a> <a class="tcards__link" href={tr.cta.href}><T tag="home.training-cards.link-label" as="span" /> →</a>
+9 -8
View File
@@ -49,6 +49,8 @@ export const contentSeed: SeedEntry[] = [
T('footer.training.2.label', site.footerTraining[1].label, []), T('footer.training.2.label', site.footerTraining[1].label, []),
T('footer.training.3.label', site.footerTraining[2].label, []), T('footer.training.3.label', site.footerTraining[2].label, []),
T('footer.training.4.label', site.footerTraining[3].label, []), T('footer.training.4.label', site.footerTraining[3].label, []),
T('footer.training.5.label', site.footerTraining[4].label, []),
T('footer.training.6.label', site.footerTraining[5].label, []),
T('footer.services.1.label', site.footerServices[0].label, []), T('footer.services.1.label', site.footerServices[0].label, []),
T('footer.services.2.label', site.footerServices[1].label, []), T('footer.services.2.label', site.footerServices[1].label, []),
T('footer.services.3.label', site.footerServices[2].label, []), T('footer.services.3.label', site.footerServices[2].label, []),
@@ -137,6 +139,7 @@ export const contentSeed: SeedEntry[] = [
// --- Home: metodo (data-driven da method.ts) --- // --- Home: metodo (data-driven da method.ts) ---
H('home.method.title', 'Il metodo<br />InsanityLab'), H('home.method.title', 'Il metodo<br />InsanityLab'),
T('home.method.cta', 'Contattaci', []),
H('method.intro', methodIntro.replace('Metodo Insanitylab', '<strong>Metodo Insanitylab</strong>')), H('method.intro', methodIntro.replace('Metodo Insanitylab', '<strong>Metodo Insanitylab</strong>')),
T('method.extra-note', methodExtraNote, []), T('method.extra-note', methodExtraNote, []),
...methodSteps.flatMap((s) => [ ...methodSteps.flatMap((s) => [
@@ -177,14 +180,14 @@ export const contentSeed: SeedEntry[] = [
// --- About: about --- // --- About: about ---
T('about.about.title', 'About'), T('about.about.title', 'About'),
T('about.about.body', "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."), T('about.about.body', "InsanityLab nasce dalla visione di creare uno spazio dove il fitness incontrasse la salute in modo reale e profondo, non superficiale. L'idea di partenza era semplice ma rivoluzionaria nel settore: il movimento non dovrebbe essere vissuto come un sacrificio estetico, ma come un atto di cura verso se stessi. Da questa convinzione è nato un laboratorio esclusivo di benessere, pensato da chi conosce il corpo in profondità — non solo come semplici trainer, ma come osteopati e chinesiologi esperti del movimento."),
I('about.about.image'), I('about.about.image'),
// --- About: convenzioni --- // --- About: convenzioni ---
T('about.convenzioni.title', 'Convenzioni'), T('about.convenzioni.title', 'Convenzioni'),
T('about.convenzioni.body', "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."), T('about.convenzioni.body', "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."),
T('about.convenzioni.item1', '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.'), T('about.convenzioni.item1', 'Le realtà convenzionate possono accedere a programmi di welfare aziendale personalizzati.'),
T('about.convenzioni.item2', 'Ogni collaborazione parte da una consulenza gratuita preliminare per analizzare esigenze e obiettivi, così da sviluppare soluzioni efficaci e sostenibili.'), T('about.convenzioni.item2', 'Ogni collaborazione parte da una consulenza preliminare per analizzare esigenze e obiettivi, così da sviluppare soluzioni efficaci e sostenibili.'),
T('about.convenzioni.item3', "Le convenzioni vengono definite su misura insieme a InsanityLab, in base alle caratteristiche e alle necessità dell'azienda partner."), T('about.convenzioni.item3', "Le convenzioni vengono definite su misura insieme a InsanityLab, in base alle caratteristiche e alle necessità dell'azienda partner."),
T('about.convenzioni.cta-text', 'Per ricevere maggiori informazioni o attivare una convenzione:', []), T('about.convenzioni.cta-text', 'Per ricevere maggiori informazioni o attivare una convenzione:', []),
I('about.convenzioni.image'), I('about.convenzioni.image'),
@@ -202,7 +205,7 @@ export const contentSeed: SeedEntry[] = [
// --- About: studios --- // --- About: studios ---
T('about.studios.title', 'In-Sanity Studios'), T('about.studios.title', 'In-Sanity Studios'),
T('about.studios.body1', "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."), T('about.studios.body1', "IN-SANITY STUDIOS è il nuovo spazio professionale nato dall'evoluzione di InsanityLab, dedicato ai professionisti della salute e del benessere."),
T('about.studios.body2', "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."), T('about.studios.body2', "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."),
T('about.studios.body3', '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.'), T('about.studios.body3', '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.'),
I('about.studios.image'), I('about.studios.image'),
@@ -226,9 +229,8 @@ export const contentSeed: SeedEntry[] = [
T('about.timeline.stage3.text', 'Contenuto in arrivo: il cliente fornirà il testo di questa tappa.'), T('about.timeline.stage3.text', 'Contenuto in arrivo: il cliente fornirà il testo di questa tappa.'),
// --- Services: lista (/services) --- // --- Services: lista (/services) ---
T('services.meta.title', 'Servizi', []), T('services.meta.title', 'Servizi', []),
T('services.meta.description', 'I servizi InsanityLab: Personal Training, Performance Class, Coaching, Pilates Flow, Rehab e Nutrizione.', []), T('services.meta.description', 'I servizi InsanityLab: Personal Training, Performance Class, Coaching, Profilazione, Osteopatia e Nutrizione.', []),
T('services.hero.title', 'Servizi', []), T('services.hero.title', 'Servizi', []),
T('services.intro.title', 'Servizi'),
T('services.intro.body', "Questi sono i servizi delle classi di Insanity Lab selezionati per voi. Abbiamo scelto i migliori percorsi per offrirvi un'esperienza di allenamento completa e mirata."), T('services.intro.body', "Questi sono i servizi delle classi di Insanity Lab selezionati per voi. Abbiamo scelto i migliori percorsi per offrirvi un'esperienza di allenamento completa e mirata."),
T('services.card.more-label', 'Per saperne di più →', []), T('services.card.more-label', 'Per saperne di più →', []),
@@ -273,9 +275,8 @@ export const contentSeed: SeedEntry[] = [
// --- Training: meta, hero, intro, trial --- // --- Training: meta, hero, intro, trial ---
T('training.meta.title', 'Training', []), T('training.meta.title', 'Training', []),
T('training.meta.description', 'One to One, Small Groups, Performance Class e Fit Remote: trova il training InsanityLab più adatto a te.', []), T('training.meta.description', 'One to One, Small Group, Rehab, Holistic Class, Reformer Class e Coaching: trova il training InsanityLab più adatto a te.', []),
T('training.hero.title', 'training', []), T('training.hero.title', 'training', []),
T('training.intro.title', 'Training'),
T('training.intro.subtitle', 'Porta i tuoi allenamenti al livello successivo: programmi completi e percorsi su misura. Trova il training più adatto alle tue esigenze.'), T('training.intro.subtitle', 'Porta i tuoi allenamenti al livello successivo: programmi completi e percorsi su misura. Trova il training più adatto alle tue esigenze.'),
T('training.trial.title', 'Prenota la lezione di prova'), T('training.trial.title', 'Prenota la lezione di prova'),
T('training.trial.body', "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."), T('training.trial.body', "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."),
+6
View File
@@ -6,4 +6,10 @@ export const serviceIcons: Record<string, string> = {
'pilates-flow': 'M12 4a2 2 0 110 4 2 2 0 010-4zM6 20c2-6 10-6 12 0M12 8v6', 'pilates-flow': 'M12 4a2 2 0 110 4 2 2 0 010-4zM6 20c2-6 10-6 12 0M12 8v6',
'rehab': 'M12 21C7 17 3 13 3 9a5 5 0 019-3 5 5 0 019 3c0 4-4 8-9 12z', 'rehab': 'M12 21C7 17 3 13 3 9a5 5 0 019-3 5 5 0 019 3c0 4-4 8-9 12z',
'nutrition': 'M12 3c4 0 7 3 7 8s-3 10-7 10-7-5-7-10 3-8 7-8zm0-1v4', 'nutrition': 'M12 3c4 0 7 3 7 8s-3 10-7 10-7-5-7-10 3-8 7-8zm0-1v4',
// Servizi rinominati (modifiche 02): profilazione (grafico/analisi), osteopatia (mani)
'profilazione': 'M4 20V6m5 14V4m5 16v-8m5 8V9M3 20h18',
'osteopatia': 'M8 3v7a4 4 0 008 0V3M6 14c0 4 3 7 6 7s6-3 6-7',
// Riquadri Training con pagina dettaglio
'holistic-class': 'M12 3a3 3 0 110 6 3 3 0 010-6zm0 6v7m-4 3l4-3 4 3',
'reformer-class': 'M3 8h18M3 16h18M6 8v8m12-8v8M9 12h6',
}; };
+90 -24
View File
@@ -7,16 +7,19 @@ export interface Service {
features: string[]; features: string[];
faq: { q: string; a: string }[]; faq: { q: string; a: string }[];
hasPricing: boolean; hasPricing: boolean;
/** false = pagina dettaglio esiste ma la voce non compare nella griglia /services
* (usata come approfondimento dai riquadri Training: Rehab, Holistic, Reformer). */
grid?: boolean;
} }
export const services: Service[] = [ export const services: Service[] = [
{ {
slug: "personal-training", slug: "one-to-one",
title: "Personal Training", title: "One to One",
subtitle: "One to One / Small Group", subtitle: "Allenamento con un trainer dedicato",
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.", excerpt: "Lallenamento One to One di InsanityLab è un percorso 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.", 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.",
features: ["Trainer dedicato", "Percorso su misura", "Valutazione iniziale e monitoraggio", "Formula one to one o small group (max 3)"], features: [],
faq: [ faq: [
{ q: "Come si svolge la prima seduta?", a: "Con una consulenza conoscitiva iniziale: raccogliamo lanamnesi, definiamo gli obiettivi e impostiamo il percorso più adatto." }, { q: "Come si svolge la prima seduta?", a: "Con una consulenza conoscitiva iniziale: raccogliamo lanamnesi, 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: "Con quale frequenza ci si allena?", a: "La frequenza viene definita insieme al trainer in base a obiettivi, disponibilità e livello di partenza." },
@@ -24,6 +27,21 @@ export const services: Service[] = [
], ],
hasPricing: false, hasPricing: false,
}, },
{
slug: "small-group",
title: "Small Group",
subtitle: "Mini gruppi (max 4 persone) con trainer dedicato",
excerpt: "Lo Small Group di InsanityLab è un allenamento in mini gruppi da massimo 4 persone con un trainer dedicato, che unisce attenzione personalizzata, motivazione e condivisione.",
longDescription: "Allenati in mini gruppi da massimo 4 persone con un trainer dedicato. Unesperienza small group che unisce attenzione personalizzata, motivazione e condivisione: programmi mirati e supporto costante per raggiungere i tuoi obiettivi insieme, senza rinunciare alla cura del singolo.",
features: [],
faq: [
{ q: "Quante persone ci sono in un Small Group?", a: "Massimo 4 persone per gruppo, così il trainer può seguire ciascuno con attenzione." },
{ 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 livello dei partecipanti, con progressioni graduali e supervisione costante." },
],
hasPricing: false,
grid: false,
},
{ {
slug: "performance-class", slug: "performance-class",
title: "Performance Class", title: "Performance Class",
@@ -56,30 +74,32 @@ export const services: Service[] = [
hasPricing: false, hasPricing: false,
}, },
{ {
slug: "pilates-flow", // NB: sostituisce "Pilates Flow" (il Pilates confluisce nella Holistic Class in Training).
title: "Pilates Flow", // Copy BOZZA basata sul metodo ISL (profilazione fisica) — da confermare col cliente.
subtitle: "Movimento e controllo", slug: "profilazione",
excerpt: "Il Pilates Flow migliora postura, core e consapevolezza corporea con un movimento fluido, mentre losteopatia supporta recupero, prevenzione e benessere globale integrandosi con lallenamento.", title: "Profilazione",
longDescription: "Il Pilates Flow unisce movimento fluido e controllo: migliora postura, core e consapevolezza corporea. Integrato con losteopatia, supporta recupero, prevenzione e benessere globale allinterno del percorso di allenamento.", subtitle: "Check-up e valutazione fisica",
features: ["Postura e core", "Movimento fluido e controllo", "Integrazione con osteopatia", "Benessere globale"], excerpt: "La profilazione fisica è il punto di partenza del metodo InsanityLab: una valutazione completa che definisce lo stato di salute iniziale e le priorità del percorso.",
longDescription: "Attraverso check-up, plicometria e analisi antropometriche misuriamo composizione corporea, postura e stato di forma. I dati raccolti guidano la costruzione di un programma personalizzato e permettono di monitorare i progressi nel tempo.",
features: ["Check-up fisico iniziale", "Plicometria e analisi antropometriche", "Definizione delle priorità del percorso", "Monitoraggio periodico dei progressi"],
faq: [ faq: [
{ q: "A chi è adatto il Pilates Flow?", a: "A chiunque voglia migliorare postura, mobilità e controllo del movimento, a ogni livello." }, { q: "Quando si fa la profilazione?", a: "Allinizio del percorso e poi periodicamente, per misurare i progressi e aggiornare il programma." },
{ q: "Serve esperienza precedente?", a: "No: le progressioni sono graduali e seguite da professionisti qualificati." }, { q: "Che cosa comprende?", a: "Check-up fisico, plicometria e analisi antropometriche; alcune misurazioni avanzate sono disponibili su richiesta." },
{ q: "Come si integra con losteopatia?", a: "Il lavoro osteopatico supporta recupero e prevenzione, in sinergia con le sessioni di movimento." },
], ],
hasPricing: false, hasPricing: false,
}, },
{ {
slug: "rehab", // NB: sostituisce "Rehab" nella pagina Servizi (il Rehab si sposta in Training).
title: "Rehab", slug: "osteopatia",
subtitle: "Recupero post-infortunio, patologie muscolo-scheletriche", title: "Osteopatia",
excerpt: "Il Rehab InsanityLab è un percorso supervisionato che accompagna dal recupero riabilitativo al ritorno allattività fisica in modo sicuro, progressivo e personalizzato.", subtitle: "Approccio manuale globale non invasivo",
longDescription: "Il Rehab accompagna dal recupero post-infortunio o da patologie muscolo-scheletriche fino al ritorno allattività fisica. Percorso supervisionato da chinesiologi e osteopati, sicuro, progressivo e personalizzato.", excerpt: "Losteopatia è una delle colonne portanti dellapproccio integrato di InsanityLab. Non è un servizio aggiunto: è parte della visione.",
features: ["Recupero post-infortunio", "Patologie muscolo-scheletriche", "Supervisione di chinesiologi e osteopati", "Progressione sicura e personalizzata"], longDescription: "Losteopatia è indicata sia in modo preventivo, per chi vuole mantenere il proprio equilibrio corporeo, sia in modo curativo, per chi sta affrontando un problema specifico. È particolarmente utile in sinergia con lallenamento, perché permette di lavorare su compensi e rigidità che altrimenti limiterebbero i progressi. Nel nostro approccio rientrano anche losteopatia neonatale e pediatrica.",
features: [],
faq: [ faq: [
{ q: "Chi segue il percorso Rehab?", a: "Professionisti specializzati in rieducazione funzionale ed esercizio adattato, in sinergia con larea osteopatica." }, { q: "Losteopatia è preventiva o curativa?", a: "Entrambe: è utile sia per mantenere lequilibrio corporeo sia per affrontare un problema specifico, in sinergia con lallenamento." },
{ q: "Serve una diagnosi medica?", a: "Se disponibile è utile portarla alla prima consulenza; il percorso si integra con le indicazioni dei sanitari." }, { q: "Trattate anche neonati e bambini?", a: "Sì, il nostro approccio comprende anche losteopatia neonatale e pediatrica." },
{ q: "Quanto dura il percorso?", a: "Dipende dalla condizione di partenza: la progressione viene definita e aggiornata durante il percorso." }, { q: "Serve una prescrizione medica?", a: "Non è necessaria; se disponibile, eventuale documentazione clinica è utile alla prima valutazione." },
], ],
hasPricing: false, hasPricing: false,
}, },
@@ -97,6 +117,52 @@ export const services: Service[] = [
], ],
hasPricing: false, hasPricing: false,
}, },
{
// Approfondimento del riquadro Training "Rehab" (fuori dalla griglia /services).
slug: "rehab",
title: "Rehab",
subtitle: "Recupero post-infortunio, patologie muscolo-scheletriche",
excerpt: "Accompagniamo il corpo nel recupero della sua funzionalità attraverso il movimento guidato: il ponte tra la fine del percorso medico-riabilitativo e il ritorno a una vita attiva piena.",
longDescription: "Il Rehab InsanityLab significa accompagnare il corpo nel recupero della sua funzionalità attraverso il movimento guidato. Non è fisioterapia in senso clinico: è il ponte tra la fine del percorso medico-riabilitativo e il ritorno a una vita attiva piena, con la stessa cura scientifica ma in un contesto di allenamento. Il servizio lavora con un ampio spettro di condizioni e ogni situazione viene valutata singolarmente con metodologie evidence based.",
features: [],
faq: [
{ q: "Chi segue il percorso Rehab?", a: "Professionisti specializzati in rieducazione funzionale ed esercizio adattato, in sinergia con medici specialisti, fisioterapisti e osteopati." },
{ 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,
grid: false,
},
{
// Approfondimento del riquadro Training "Holistic Class" (ex Pilates Flow / Performance Class).
slug: "holistic-class",
title: "Holistic Class",
subtitle: "Classi fino a 8 persone con trainer certificato",
excerpt: "Mobilità articolare, respirazione, equilibrio. Da sola o integrata nei tuoi allenamenti, adatta a tutti i livelli. Sessioni da 40 minuti.",
longDescription: "La Holistic Class lavora su mobilità articolare, respirazione ed equilibrio. Puoi seguirla da sola o integrarla nei tuoi allenamenti; è adatta a tutti i livelli e ogni sessione dura 40 minuti. Comprende lavoro di Pilates Flow, Mobility e Yoga.",
features: ["Pilates Flow", "Mobility", "Yoga"],
faq: [
{ q: "A chi è adatta la Holistic Class?", a: "A chiunque voglia migliorare mobilità, respirazione ed equilibrio, a ogni livello di partenza." },
{ q: "Posso integrarla con altri allenamenti?", a: "Sì: funziona sia come percorso a sé sia come complemento al tuo programma di allenamento." },
],
hasPricing: false,
grid: false,
},
{
// Approfondimento del riquadro Training "Reformer Class" (nuova).
slug: "reformer-class",
title: "Reformer Class",
subtitle: "Classi fino a 5 persone con macchinari reformer",
excerpt: "Core, postura e mobilità su macchina reformer. Lavoro a basso impatto sulle articolazioni, adatto a tutti i livelli. Lezioni da 40 minuti.",
longDescription: "La Reformer Class utilizza i macchinari reformer per il pilates in classi fino a 5 persone. Il lavoro è dedicato a core, postura e mobilità, a basso impatto sulle articolazioni e adatto a tutti i livelli. Ogni lezione dura 40 minuti.",
features: ["Macchinari reformer", "Core, postura e mobilità", "Lavoro a basso impatto sulle articolazioni", "Classi fino a 5 persone", "Lezioni da 40 minuti"],
faq: [
{ q: "Serve esperienza con il reformer?", a: "No: le progressioni sono graduali e seguite da trainer certificati, adatte anche a chi inizia." },
{ q: "Quante persone per classe?", a: "Massimo 5, per garantire attenzione e correzione costante." },
],
hasPricing: false,
grid: false,
},
]; ];
export function getService(slug: string): Service | undefined { export function getService(slug: string): Service | undefined {
+8 -6
View File
@@ -16,20 +16,22 @@ export const site = {
{ label: "Servizi", href: "/services" }, { label: "Servizi", href: "/services" },
{ label: "Blog & Edugo", href: "/blog" }, { label: "Blog & Edugo", href: "/blog" },
{ label: "Contatti", href: "/contact" }, { label: "Contatti", href: "/contact" },
{ label: "Pacchetti", href: "/pacchetti" }, { label: "Promo ed eventi", href: "/promo-eventi" },
], ],
footerTraining: [ footerTraining: [
{ label: "One to one", href: "/training#one-to-one" }, { label: "One to one", href: "/training#one-to-one" },
{ label: "Small groups", href: "/training#small-groups" }, { label: "Small group", href: "/training#small-groups" },
{ label: "Performance class", href: "/training#performance-class" }, { label: "Rehab", href: "/training#rehab" },
{ label: "Fit remote", href: "/training#fit-remote" }, { label: "Holistic class", href: "/training#holistic-class" },
{ label: "Reformer class", href: "/training#reformer-class" },
{ label: "Coaching", href: "/training#coaching" },
], ],
footerServices: [ footerServices: [
{ label: "Personal Training", href: "/services/personal-training" }, { label: "Personal Training", href: "/services/personal-training" },
{ label: "Performance Class", href: "/services/performance-class" }, { label: "Performance Class", href: "/services/performance-class" },
{ label: "Coaching", href: "/services/coaching" }, { label: "Coaching", href: "/services/coaching" },
{ label: "Rehab", href: "/services/rehab" }, { label: "Profilazione", href: "/services/profilazione" },
{ label: "Pilates Flow", href: "/services/pilates-flow" }, { label: "Osteopatia", href: "/services/osteopatia" },
{ label: "Nutrizione", href: "/services/nutrition" }, { label: "Nutrizione", href: "/services/nutrition" },
], ],
}; };
+6 -4
View File
@@ -1,6 +1,8 @@
export const trainings = [ 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: "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: "Scopri", href: "/services/one-to-one" } },
{ 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. Unesperienza 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: "small-groups", title: "Small Group", caption: "Mini gruppi (max 4 persone) con trainer dedicato", image: "training-small-groups", description: "Allenati in mini gruppi da massimo 4 persone con un trainer dedicato. Unesperienza small group che unisce attenzione personalizzata, motivazione e condivisione. Programmi mirati e supporto costante per raggiungere i tuoi obiettivi insieme.", cta: { label: "Scopri", href: "/services/small-group" } },
{ 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: "/services/performance-class" } }, { id: "rehab", title: "Rehab", caption: "Recupero funzionale attraverso il movimento guidato", image: "training-rehab", description: "Il Rehab InsanityLab accompagna il corpo nel recupero della sua funzionalità attraverso il movimento guidato: il ponte tra la fine del percorso medico-riabilitativo e il ritorno a una vita attiva piena, con la stessa cura scientifica ma in un contesto di allenamento.", cta: { label: "Scopri", href: "/services/rehab" } },
{ 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" } }, { id: "holistic-class", title: "Holistic Class", caption: "Classi fino a 8 persone con trainer certificato", image: "training-holistic-class", description: "Mobilità articolare, respirazione, equilibrio. Da sola o integrata nei tuoi allenamenti, adatta a tutti i livelli. Sessioni da 40 minuti che uniscono Pilates Flow, Mobility e Yoga.", cta: { label: "Scopri", href: "/services/holistic-class" } },
{ id: "reformer-class", title: "Reformer Class", caption: "Classi fino a 5 persone con macchinari reformer", image: "training-reformer-class", description: "Core, postura e mobilità su macchina reformer. Lavoro a basso impatto sulle articolazioni, adatto a tutti i livelli. Lezioni da 40 minuti in classi fino a 5 persone.", cta: { label: "Scopri", href: "/services/reformer-class" } },
{ id: "coaching", title: "Coaching", caption: "Programma su misura e monitoraggio, anche online", image: "training-coaching", description: "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 (anche online, formula Fit Remote).", cta: { label: "Scopri", href: "/services/coaching" } },
] as const; ] as const;
+53 -1
View File
@@ -98,7 +98,6 @@ export function createDb(path?: string): Database.Database {
['services.meta.title', 'Programmi'], ['services.meta.title', 'Programmi'],
['services.meta.description', 'I programmi InsanityLab: Personal Training, Performance Class, Coaching, Pilates Flow, Rehab e Nutrizione.'], ['services.meta.description', 'I programmi InsanityLab: Personal Training, Performance Class, Coaching, Pilates Flow, Rehab e Nutrizione.'],
['services.hero.title', 'Programmi'], ['services.hero.title', 'Programmi'],
['services.intro.title', 'Programmi'],
["services.intro.body", "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."], ["services.intro.body", "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."],
['service.side.others-label', 'Altri programmi'], ['service.side.others-label', 'Altri programmi'],
['service.pricing.empty-note', 'Contattaci per i dettagli di questo programma. <a href="/contact">Scrivici →</a>'], ['service.pricing.empty-note', 'Contattaci per i dettagli di questo programma. <a href="/contact">Scrivici →</a>'],
@@ -111,9 +110,62 @@ export function createDb(path?: string): Database.Database {
} }
}); });
renameTx(); renameTx();
// Modifiche 03: "Personal Training" → "One to One" (nuovo slug one-to-one). Rinomina i tag
// esistenti prima del seed per preservare le modifiche da pannello; il title di default viene
// aggiornato solo se non modificato. La nuova pagina "Small Group" (slug small-group) è un
// servizio nuovo e viene inserita dal seed più sotto. Idempotente.
const oneToOneTx = db.transaction(() => {
renameUpd.run('service.one-to-one.', 'service.personal-training.'.length + 1, 'service.personal-training.%');
renameDel.run('service.personal-training.%');
db.prepare("UPDATE content_blocks SET value = 'One to One' WHERE tag = 'service.one-to-one.title' AND value = 'Personal Training'").run();
});
oneToOneTx();
const ins = db.prepare('INSERT OR IGNORE INTO content_blocks (tag, type, value, guid) VALUES (?, ?, ?, ?)'); const ins = db.prepare('INSERT OR IGNORE INTO content_blocks (tag, type, value, guid) VALUES (?, ?, ?, ?)');
const syncTx = db.transaction(() => { for (const e of contentSeed) ins.run(e.tag, e.type, e.value, randomUUID()); }); const syncTx = db.transaction(() => { for (const e of contentSeed) ins.run(e.tag, e.type, e.value, randomUUID()); });
syncTx(); syncTx();
// Modifiche 02 (pagina Servizi): "Pilates Flow" → "Profilazione" e "Rehab" → "Osteopatia".
// I nuovi servizi hanno slug nuovi (tag service.profilazione.* / service.osteopatia.*) e
// vengono inseriti dal seed qui sopra. Restano da aggiornare le righe EXISTING il cui valore
// cambia: label del footer e meta description. UPDATE guardato dal vecchio valore → idempotente
// e non sovrascrive eventuali modifiche fatte dal pannello.
const SERVIZI_02: [string, string, string][] = [
['footer.services.4.label', 'Rehab', 'Profilazione'],
['footer.services.5.label', 'Pilates Flow', 'Osteopatia'],
['services.meta.description',
'I servizi InsanityLab: Personal Training, Performance Class, Coaching, Pilates Flow, Rehab e Nutrizione.',
'I servizi InsanityLab: Personal Training, Performance Class, Coaching, Profilazione, Osteopatia e Nutrizione.'],
// Pagina Training: riquadri passati a One to One, Small Group, Rehab, Holistic Class,
// Reformer Class, Coaching. Label footer + meta aggiornate (righe 5-6 sono nuovi tag).
['footer.training.2.label', 'Small groups', 'Small group'],
['footer.training.3.label', 'Performance class', 'Rehab'],
['footer.training.4.label', 'Fit remote', 'Holistic class'],
['training.meta.description',
'One to One, Small Groups, Performance Class e Fit Remote: trova il training InsanityLab più adatto a te.',
'One to One, Small Group, Rehab, Holistic Class, Reformer Class e Coaching: trova il training InsanityLab più adatto a te.'],
];
const upd02 = db.prepare('UPDATE content_blocks SET value = ? WHERE tag = ? AND value = ?');
const upd02Tx = db.transaction(() => { for (const [tag, oldV, newV] of SERVIZI_02) upd02.run(newV, tag, oldV); });
upd02Tx();
// Modifiche 02 (pagina Insanitylab/about): testi riscritti. Guardia via frammento LIKE
// distintivo del vecchio testo → robusto agli apostrofi tipografici, idempotente e
// rispettoso delle eventuali riscritture da pannello (se il frammento non c'è più, salta).
const ABOUT_02: [string, string, string][] = [
['about.about.body', '%unire fitness e salute in un unico percorso%',
"InsanityLab nasce dalla visione di creare uno spazio dove il fitness incontrasse la salute in modo reale e profondo, non superficiale. L'idea di partenza era semplice ma rivoluzionaria nel settore: il movimento non dovrebbe essere vissuto come un sacrificio estetico, ma come un atto di cura verso se stessi. Da questa convinzione è nato un laboratorio esclusivo di benessere, pensato da chi conosce il corpo in profondità — non solo come semplici trainer, ma come osteopati e chinesiologi esperti del movimento."],
['about.convenzioni.item1', '%small group training e classi di gruppo%',
'Le realtà convenzionate possono accedere a programmi di welfare aziendale personalizzati.'],
['about.convenzioni.item2', '%consulenza gratuita preliminare%',
'Ogni collaborazione parte da una consulenza preliminare per analizzare esigenze e obiettivi, così da sviluppare soluzioni efficaci e sostenibili.'],
['about.studios.body1', '%della performance. Più che un semplice insieme%',
"IN-SANITY STUDIOS è il nuovo spazio professionale nato dall'evoluzione di InsanityLab, dedicato ai professionisti della salute e del benessere."],
];
const updAbout = db.prepare('UPDATE content_blocks SET value = ? WHERE tag = ? AND value LIKE ?');
const updAboutTx = db.transaction(() => { for (const [tag, frag, newV] of ABOUT_02) updAbout.run(newV, tag, frag); });
updAboutTx();
// Backfill: righe pre-esistenti (già presenti prima dell'introduzione del guid) restano // Backfill: righe pre-esistenti (già presenti prima dell'introduzione del guid) restano
// ignorate dall'INSERT OR IGNORE e hanno guid nullo → assegna un UUID a ciascuna. // ignorate dall'INSERT OR IGNORE e hanno guid nullo → assegna un UUID a ciascuna.
const missing = db.prepare(`SELECT tag FROM content_blocks WHERE guid IS NULL OR guid = ''`).all() as { tag: string }[]; const missing = db.prepare(`SELECT tag FROM content_blocks WHERE guid IS NULL OR guid = ''`).all() as { tag: string }[];
-38
View File
@@ -2,7 +2,6 @@
import type { ImageMetadata } from 'astro'; import type { ImageMetadata } from 'astro';
import Base from '../layouts/Base.astro'; import Base from '../layouts/Base.astro';
import PageHero from '../components/PageHero.astro'; import PageHero from '../components/PageHero.astro';
import Timeline from '../components/about/Timeline.astro';
import { team } from '../data/team'; import { team } from '../data/team';
import T from '../components/content/T.astro'; import T from '../components/content/T.astro';
import TImg from '../components/content/TImg.astro'; import TImg from '../components/content/TImg.astro';
@@ -63,8 +62,6 @@ export const prerender = false;
</div> </div>
</section> </section>
<Timeline />
<section class="section section--dark"> <section class="section section--dark">
<div class="container"> <div class="container">
<div class="section-heading"> <div class="section-heading">
@@ -121,25 +118,6 @@ export const prerender = false;
</div> </div>
</section> </section>
<section class="section section--alt">
<div class="container">
<div class="section-heading">
<T tag="about.metodo.title" as="h2" />
<T tag="about.metodo.body" as="p" />
</div>
<div class="about-img-row">
<div class="about-img-row__text section-heading">
<T tag="about.metodo.closing" as="p" />
</div>
{imgOf('about-team-group') && (
<div class="round-img">
<TImg tag="about.metodo.image" src={imgOf('about-team-group')!} alt="Il team InsanityLab" widths={[360, 720]} sizes="360px" />
</div>
)}
</div>
</div>
</section>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<div class="about-img-row"> <div class="about-img-row">
@@ -158,22 +136,6 @@ export const prerender = false;
</div> </div>
</section> </section>
<section class="section section--alt">
<div class="container">
<div class="about-img-row">
{imgOf('about-vision') && (
<div class="round-img">
<TImg tag="about.vision.image" src={imgOf('about-vision')!} alt="La comunità InsanityLab" widths={[360, 720]} sizes="360px" />
</div>
)}
<div class="about-img-row__text section-heading">
<T tag="about.vision.title" as="h2" />
<T tag="about.vision.body1" as="p" />
<T tag="about.vision.body2" as="p" />
</div>
</div>
</div>
</section>
</Base> </Base>
<style> <style>
File diff suppressed because it is too large Load Diff
+256
View File
@@ -0,0 +1,256 @@
---
import Base from '../layouts/Base.astro';
// Landing promo/lancio integrata nel sito (header e footer del sito, non standalone).
// I bottoni "Acquista" portano al gestionale bsport: l'utente crea l'account,
// inserisce i dati e paga via Stripe dentro la piattaforma. Link centralizzato qui:
// quando avremo gli ID membership per singolo prodotto basterà mappare BSPORT_URL card per card.
const BSPORT_URL = 'https://backoffice.bsport.io/login/signup?membership=3428';
export const prerender = false;
---
<Base title="Promo ed eventi — InsanityLab" description="Edizione apertura: pacchetti a 5 incontri per One to One, Small Group, Reformer e Holistic Class. Acquisto online, pagamento sicuro via Stripe.">
<div class="lp">
<div class="page">
<section class="hero">
<div class="badge">insanity.lab · bologna savena</div>
<h1>Allenati con metodo.</h1>
<p class="accent">Scegli come</p>
<h1>vuoi farlo.</h1>
<p class="desc">Allenati con flessibilità, senza vincoli, da subito.<br>Acquisto online in pochi secondi, pagamento sicuro via Stripe.</p>
<a class="btn-sand" href="#proposte">LE NOSTRE PROPOSTE →</a>
</section>
<section class="launch">
<div class="badge">solo per il lancio</div>
<h1>Il nostro metodo nasce per durare.</h1>
<p class="accent">Per l'apertura, un'eccezione. Solo per poco.</p>
<p class="desc">In-Sanity Lab lavora per percorsi strutturati — trimestrali, semestrali — perché i risultati veri si costruiscono nel tempo. Per la nuova apertura, ci sarà un'eccezione: 5 incontri pensati come un vero mini-percorso, non semplici lezioni sciolte.</p>
</section>
<section class="why5">
<p>Non è una prova. È un percorso in miniatura — per capire come lavoriamo prima di impegnarti su un programma trimestrale o semestrale.</p>
</section>
<section class="cards-wrap" id="proposte">
<div class="cards">
<div class="card">
<div class="eyebrow">Personal training</div>
<div class="title">One to One</div>
<div class="price-row">
<div class="price">163 €</div>
<div class="price-old">300 €</div>
</div>
<div class="unit">5 incontri</div>
<ul>
<li>Screening iniziale, definizione dei tuoi obiettivi</li>
<li>Check-up con profilazione fisica, il tuo punto di partenza</li>
<li>PT dedicato, presente a ogni sessione</li>
<li>In-sanity plan, il tuo programma personalizzato</li>
</ul>
<a class="buy" href={BSPORT_URL} target="_blank" rel="noopener">ACQUISTA →</a>
</div>
<div class="card featured">
<div class="featured-badge">Più scelto</div>
<div class="eyebrow">Personal training</div>
<div class="title">Small Group</div>
<div class="price-row">
<div class="price">110 €</div>
<div class="price-old">220 €</div>
</div>
<div class="unit">5 incontri</div>
<ul>
<li>Screening iniziale, definizione dei tuoi obiettivi</li>
<li>Check-up con profilazione fisica, il tuo punto di partenza</li>
<li>PT dedicato, presente a ogni sessione</li>
<li>In-sanity plan, il tuo programma personalizzato</li>
</ul>
<a class="buy" href={BSPORT_URL} target="_blank" rel="noopener">ACQUISTA →</a>
</div>
<div class="card">
<div class="eyebrow">Pilates class</div>
<div class="title">Reformer</div>
<div class="price-row">
<div class="price">100 €</div>
<div class="price-old">140 €</div>
</div>
<div class="unit">5 incontri</div>
<ul>
<li>Core, postura e mobilità su macchina Reformer</li>
<li>Lavoro a basso impatto sulle articolazioni</li>
<li>Percorso breve, non lezioni isolate</li>
</ul>
<a class="buy" href={BSPORT_URL} target="_blank" rel="noopener">ACQUISTA →</a>
</div>
<div class="card">
<div class="eyebrow">Holistic class</div>
<div class="title">Pilates Mat, Mobility, Yoga</div>
<div class="price-row">
<div class="price">50 €</div>
<div class="price-old">100 €</div>
</div>
<div class="unit">5 incontri</div>
<ul>
<li>Mobilità articolare, respirazione, equilibrio</li>
<li>Da solo, o integrato nei tuoi allenamenti</li>
<li>Adatto a tutti i livelli</li>
</ul>
<a class="buy" href={BSPORT_URL} target="_blank" rel="noopener">ACQUISTA →</a>
</div>
</div>
<p class="validity-note">Disponibile solo per l'apertura · Valido entro 2 mesi dal primo incontro e comunque non oltre il 1° dicembre 2026</p>
</section>
<section class="faq-wrap">
<h1>Tutto quello che vuoi sapere prima di acquistare.</h1>
<div class="faq-block">
<div class="faq-q"><p>Come funziona il pacchetto a incontri per One to One e Small Group?</p><div class="faq-toggle">+</div></div>
<p class="faq-a">Acquisti il pacchetto online con pagamento sicuro via <span class="hl">Stripe</span>. Ricevi una conferma via email. Poi ci scrivi su <span class="hl">WhatsApp (351 753 5977)</span> per prenotare le singole sessioni in base agli orari disponibili. Verrai ricontattato subito tramite messaggio o chiamata! Gli incontri sono validi 2 mesi dalla data del primo incontro e comunque non oltre il 1° dicembre 2026.</p>
</div>
<div class="faq-block">
<div class="faq-q"><p>Come funziona il percorso Reformer o Holistic Class?</p><div class="faq-toggle">+</div></div>
<p class="faq-a">Acquisti il pacchetto online con pagamento sicuro via <span class="hl">Stripe</span>. Ricevi una conferma via email. Poi vai nella schermata Home, clicca il servizio che hai acquistato e seleziona autonomamente la class nel giorno e nell'orario che desideri. In caso di problemi contattaci su <span class="hl">WhatsApp (351 753 5977)</span>! Gli incontri sono validi 2 mesi dalla data del primo incontro e comunque non oltre il 1° dicembre 2026.</p>
</div>
<div class="faq-block">
<div class="faq-q"><p>Posso usare gli incontri su servizi diversi?</p><div class="faq-toggle">+</div></div>
<p class="faq-a">Ogni pacchetto è dedicato a un singolo servizio (<span class="hl">One to One</span>, <span class="hl">Small Group</span>, <span class="hl">Reformer</span> o <span class="hl">Holistic Class</span>). Se vuoi accedere a più servizi puoi acquistare pacchetti separati per ciascuno.</p>
</div>
<div class="faq-block">
<div class="faq-q"><p>Posso regalare un pacchetto a qualcun altro?</p><div class="faq-toggle">+</div></div>
<p class="faq-a">Sì. Puoi acquistare il pacchetto a tuo nome — per la fatturazione — e farlo utilizzare a un'altra persona: perfetto se vuoi fare un regalo a un amico o un familiare. Ti basterà comunicarci chi userà gli incontri quando ci scriverai su <span class="hl">WhatsApp (351 753 5977)</span> per organizzare le sessioni.</p>
</div>
<div class="faq-block">
<div class="faq-q"><p>Il pagamento è sicuro?</p><div class="faq-toggle">+</div></div>
<p class="faq-a">Sì. Il pagamento avviene tramite <span class="hl">Stripe</span>, una delle piattaforme di pagamento online più sicure al mondo. I tuoi dati bancari non vengono mai condivisi con insanity.lab — tutto è gestito in modo cifrato da Stripe.</p>
</div>
<div class="faq-block">
<div class="faq-q"><p>Posso chiedere un rimborso?</p><div class="faq-toggle">+</div></div>
<p class="faq-a">Per richieste di rimborso o problemi con l'acquisto scrivici su <span class="hl">WhatsApp</span> o via email. Valutiamo ogni situazione in modo diretto e umano.</p>
</div>
</section>
<div class="contact-bar">
<p>Via Leandro Alberti 76 e Via De Mattiolo 5, 40139 Bologna &nbsp;·&nbsp; 351 753 5977 &nbsp;·&nbsp; info@insanitylab.it</p>
</div>
</div>
</div>
</Base>
<script>
// Accordion FAQ: click (o Invio/Spazio) sul quesito apre/chiude la risposta.
document.querySelectorAll('.lp .faq-block .faq-q').forEach((q) => {
const block = q.closest('.faq-block');
const toggle = q.querySelector('.faq-toggle');
q.setAttribute('role', 'button');
q.setAttribute('tabindex', '0');
q.setAttribute('aria-expanded', 'false');
const set = (open: boolean) => {
block?.classList.toggle('is-open', open);
q.setAttribute('aria-expanded', String(open));
if (toggle) toggle.textContent = open ? '' : '+';
};
q.addEventListener('click', () => set(!block?.classList.contains('is-open')));
q.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); set(!block?.classList.contains('is-open')); }
});
});
</script>
<style>
.lp { background: #ECE7DC; }
.lp .page { max-width: 1000px; margin: 0 auto; background: #FFFFFF; font-family: 'Inter', system-ui, Arial, sans-serif; }
/* HERO */
.lp .hero { background: #2D2D2D; padding: 72px 64px 84px; }
.lp .badge {
display: inline-block; padding: 7px 16px; border: 0.5px solid rgba(196,168,130,0.5);
border-radius: 999px; margin-bottom: 24px; font-size: 11px; letter-spacing: 0.08em;
color: #C4A882; font-weight: 500; text-transform: uppercase;
}
.lp .hero h1 { margin: 0; font-size: 44px; line-height: 1.2; font-weight: 700; color: #FFFFFF; }
.lp .hero .accent { margin: 4px 0; font-size: 38px; line-height: 1.3; font-style: italic; color: #C4A882; font-family: Georgia, serif; }
.lp .hero p.desc { margin: 28px 0 32px; font-size: 16px; line-height: 1.7; color: #B8B6AE; max-width: 480px; }
.lp .btn-sand {
display: inline-block; background: #C4A882; color: #2C2C2A; border: none; padding: 15px 30px;
border-radius: 999px; font-size: 13px; font-weight: 700; letter-spacing: 0.04em; cursor: pointer;
text-decoration: none;
}
/* LAUNCH */
.lp .launch { background: #F2ECE1; padding: 72px 64px; }
.lp .launch .badge { border-color: rgba(140,107,55,0.35); color: #8A6E3F; }
.lp .launch h1 { margin: 0; font-size: 40px; line-height: 1.25; font-weight: 700; color: #2D2D2D; }
.lp .launch .accent { margin: 8px 0 28px; font-size: 30px; line-height: 1.35; font-style: italic; color: #8A6E3F; font-family: Georgia, serif; }
.lp .launch p.desc { margin: 0; font-size: 16px; line-height: 1.7; color: #5C564A; max-width: 540px; }
/* PERCHE 5 */
.lp .why5 { padding: 48px 64px 8px; }
.lp .why5 p { margin: 0; font-size: 18px; line-height: 1.7; font-style: italic; color: #2D2D2D; font-family: Georgia, serif; max-width: 620px; }
/* CARDS */
.lp .cards-wrap { padding: 32px 64px 72px; }
.lp .cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
.lp .card { background: #FAF8F3; border: 0.5px solid #E3DCCB; border-radius: 14px; padding: 26px; position: relative; }
.lp .card.featured { border: 2px solid #2D2D2D; }
.lp .featured-badge {
position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: #2D2D2D;
color: #FFFFFF; font-size: 11px; padding: 4px 14px; border-radius: 999px; white-space: nowrap; font-weight: 500;
}
.lp .card .eyebrow { font-size: 11px; letter-spacing: 0.08em; color: #8A8980; margin: 0 0 4px; text-transform: uppercase; font-weight: 700; }
.lp .card .title { font-size: 19px; font-weight: 700; color: #2D2D2D; margin: 0 0 14px; }
.lp .card .price-row { display: flex; align-items: baseline; gap: 10px; margin: 0 0 2px; }
.lp .card .price { font-size: 26px; font-weight: 700; color: #2D2D2D; margin: 0; }
.lp .card .price-old { font-size: 15px; color: #ADABA1; text-decoration: line-through; font-weight: 400; }
.lp .card .unit { font-size: 12px; color: #8A8980; margin: 0 0 18px; }
.lp .card ul { list-style: none; padding: 0; margin: 0 0 20px; display: flex; flex-direction: column; gap: 9px; }
.lp .card li { font-size: 13.5px; line-height: 1.5; color: #5C564A; display: flex; gap: 8px; }
.lp .card li::before { content: "—"; color: #C4A882; font-weight: 700; flex-shrink: 0; }
.lp .card .buy {
display: block; width: 100%; box-sizing: border-box; text-align: center; text-decoration: none;
background: #2D2D2D; color: #FFFFFF; border: none; padding: 13px; border-radius: 999px;
font-size: 12px; font-weight: 700; letter-spacing: 0.03em; cursor: pointer;
}
.lp .card.featured .buy { background: #C4A882; color: #2C2C2A; }
.lp .validity-note { text-align: center; font-size: 12px; color: #8A8980; margin: 22px 0 0; }
/* CONTACT BAR */
.lp .contact-bar { padding: 20px 64px 40px; text-align: center; }
.lp .contact-bar p { margin: 0; font-size: 11.5px; line-height: 1.6; color: #8A8980; }
/* FAQ */
.lp .faq-wrap { padding: 72px 64px; }
.lp .faq-wrap h1 { margin: 0 0 40px; font-size: 34px; line-height: 1.25; font-weight: 700; color: #2D2D2D; max-width: 480px; }
.lp .faq-block { border-top: 0.5px solid #E3DCCB; padding: 22px 0; }
.lp .faq-block:last-child { border-bottom: 0.5px solid #E3DCCB; }
.lp .faq-q { display: flex; justify-content: space-between; align-items: center; gap: 24px; cursor: pointer; }
.lp .faq-q p { margin: 0; font-size: 16px; font-weight: 500; color: #2D2D2D; }
.lp .faq-q:focus-visible { outline: 2px solid #8A6E3F; outline-offset: 4px; }
.lp .faq-toggle {
width: 30px; height: 30px; border-radius: 50%; border: 0.5px solid #C9C3B4; display: flex;
align-items: center; justify-content: center; flex-shrink: 0; font-size: 16px; color: #2D2D2D;
transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.lp .faq-block.is-open .faq-toggle { background: #2D2D2D; color: #FFFFFF; border-color: #2D2D2D; }
.lp .faq-a { display: none; margin: 14px 0 0; font-size: 14.5px; line-height: 1.7; color: #8A8980; max-width: 620px; }
.lp .faq-block.is-open .faq-a { display: block; }
.lp .faq-a .hl { color: #8A6E3F; }
@media (max-width: 700px) {
.lp .hero, .lp .launch, .lp .why5, .lp .cards-wrap, .lp .faq-wrap, .lp .contact-bar { padding-left: 28px; padding-right: 28px; }
.lp .cards { grid-template-columns: 1fr; }
.lp .hero h1 { font-size: 32px; }
.lp .launch h1 { font-size: 28px; }
}
</style>
+5 -2
View File
@@ -13,7 +13,7 @@ export const prerender = false;
const service = getService(Astro.params.slug!); const service = getService(Astro.params.slug!);
if (!service) return new Response(null, { status: 404 }); if (!service) return new Response(null, { status: 404 });
const slug = service.slug; const slug = service.slug;
const others = services.filter((p) => p.slug !== service.slug); const others = services.filter((p) => p.slug !== service.slug && p.grid !== false);
const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/program-*.jpg', { eager: true }); const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/program-*.jpg', { eager: true });
const imgOf = (slug: string) => Object.entries(images).find(([p]) => p.includes(`program-${slug}.`))?.[1]?.default; const imgOf = (slug: string) => Object.entries(images).find(([p]) => p.includes(`program-${slug}.`))?.[1]?.default;
const heroImg = imgOf(service.slug); const heroImg = imgOf(service.slug);
@@ -29,10 +29,12 @@ const tones = ['tan', 'taupe', 'dark'];
<div class="container prog"> <div class="container prog">
<div> <div>
<h2>{t(`service.${slug}.title`).toUpperCase()}</h2> <h2>{t(`service.${slug}.title`).toUpperCase()}</h2>
<T tag={`service.${slug}.long-description`} as="p" /> <T tag={`service.${slug}.long-description`} as="p" class="prog__desc" />
{service.features.length > 0 && (
<ul class="prog__features"> <ul class="prog__features">
{service.features.map((_, i) => <T tag={`service.${slug}.feature.${i + 1}`} as="li" />)} {service.features.map((_, i) => <T tag={`service.${slug}.feature.${i + 1}`} as="li" />)}
</ul> </ul>
)}
{heroImg && ( {heroImg && (
<TImg tag={`service.${slug}.image`} src={heroImg} alt={t(`service.${slug}.title`)} class="prog__img" widths={[600, 1000]} sizes="(max-width:991px) 100vw, 60vw" data-reveal="fade" /> <TImg tag={`service.${slug}.image`} src={heroImg} alt={t(`service.${slug}.title`)} class="prog__img" widths={[600, 1000]} sizes="(max-width:991px) 100vw, 60vw" data-reveal="fade" />
)} )}
@@ -97,6 +99,7 @@ const tones = ['tan', 'taupe', 'dark'];
<style> <style>
.prog { display: grid; grid-template-columns: 2.2fr 1fr; gap: 60px; } .prog { display: grid; grid-template-columns: 2.2fr 1fr; gap: 60px; }
.prog :global(.prog__desc) { white-space: pre-line; }
.prog__features { padding-left: 20px; display: grid; gap: 8px; margin-bottom: 30px; } .prog__features { padding-left: 20px; display: grid; gap: 8px; margin-bottom: 30px; }
.prog__features li::marker { color: var(--c-accent); } .prog__features li::marker { color: var(--c-accent); }
.prog__img { width: 100%; height: auto; margin-bottom: 30px; } .prog__img { width: 100%; height: auto; margin-bottom: 30px; }
+6 -3
View File
@@ -8,6 +8,10 @@ import TImg from '../../components/content/TImg.astro';
import { t } from '../../lib/content'; import { t } from '../../lib/content';
const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/program-*.jpg', { eager: true }); const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/program-*.jpg', { eager: true });
const imgOf = (slug: string) => Object.entries(images).find(([p]) => p.includes(`program-${slug}.`))?.[1]?.default; const imgOf = (slug: string) => Object.entries(images).find(([p]) => p.includes(`program-${slug}.`))?.[1]?.default;
const gridServices = services.filter((p) => p.grid !== false);
// Questi servizi rimandano alla pagina Training invece che alla scheda di dettaglio
const TRAINING_LINK = new Set(['one-to-one', 'performance-class']);
const moreHref = (slug: string) => (TRAINING_LINK.has(slug) ? '/training' : `/services/${slug}`);
export const prerender = false; export const prerender = false;
--- ---
<Base title={t('services.meta.title')} description={t('services.meta.description')}> <Base title={t('services.meta.title')} description={t('services.meta.description')}>
@@ -15,11 +19,10 @@ export const prerender = false;
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<div class="section-heading"> <div class="section-heading">
<T tag="services.intro.title" as="h2" />
<T tag="services.intro.body" as="p" /> <T tag="services.intro.body" as="p" />
</div> </div>
<div class="plist"> <div class="plist">
{services.map((p, i) => ( {gridServices.map((p, i) => (
<article class="plist__card" data-reveal style={`--reveal-delay:${((i % 3) * 0.15).toFixed(2)}s`}> <article class="plist__card" data-reveal style={`--reveal-delay:${((i % 3) * 0.15).toFixed(2)}s`}>
{imgOf(p.slug) && ( {imgOf(p.slug) && (
<a class="plist__photo" href={`/services/${p.slug}`}> <a class="plist__photo" href={`/services/${p.slug}`}>
@@ -30,7 +33,7 @@ export const prerender = false;
<T tag={`service.${p.slug}.title`} as="h3" /> <T tag={`service.${p.slug}.title`} as="h3" />
<T tag={`service.${p.slug}.subtitle`} as="p" class="plist__sub" /> <T tag={`service.${p.slug}.subtitle`} as="p" class="plist__sub" />
<T tag={`service.${p.slug}.excerpt`} as="p" /> <T tag={`service.${p.slug}.excerpt`} as="p" />
<a class="plist__more" href={`/services/${p.slug}`}>{t('services.card.more-label')}</a> <a class="plist__more" href={moreHref(p.slug)}>{t('services.card.more-label')}</a>
</div> </div>
</article> </article>
))} ))}
+1 -1
View File
@@ -3,7 +3,7 @@ import { getDb } from '../lib/db';
import { services } from '../data/services'; import { services } from '../data/services';
export const prerender = false; export const prerender = false;
const STATIC = ['/', '/about', '/contact', '/training', '/services', '/blog', '/pacchetti']; const STATIC = ['/', '/about', '/contact', '/training', '/services', '/blog', '/promo-eventi'];
export const GET: APIRoute = ({ site }) => { export const GET: APIRoute = ({ site }) => {
const base = (site ?? new URL('https://insanitylab.tielogic.xyz')).toString().replace(/\/$/, ''); const base = (site ?? new URL('https://insanitylab.tielogic.xyz')).toString().replace(/\/$/, '');
-1
View File
@@ -18,7 +18,6 @@ export const prerender = false;
<section class="section" style="padding-bottom:0"> <section class="section" style="padding-bottom:0">
<div class="section-heading"> <div class="section-heading">
<T tag="training.intro.title" as="h2" />
<T tag="training.intro.subtitle" as="p" /> <T tag="training.intro.subtitle" as="p" />
</div> </div>
</section> </section>