From f914d0699aed083a2fd8294dbc89020a4c2a57c6 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Fri, 4 Sep 2026 06:43:50 +0000 Subject: [PATCH] La promo di settembre subentra ai bundle il 5, cinque pacchetti Gli Opening Bundles scadono il 04/09 alle 23:59:59 e dopo di loro non c'era nessuna edizione: dal 5 il sito avrebbe mostrato "Nessuna promozione in corso" mentre le promo erano vive. Adriano ha consegnato le sei creative il 03/09. Edizione `settembre-2026` (5 -> 19 settembre, chiude il giorno dell'inaugurazione): tornano i pacchetti da 5 incontri, con in piu' il Check-up avanzato, che e' una valutazione singola e per questo dice "1 valutazione" invece di "5 incontri". Prezzi 250/150/130/80/70. Due cose che la struttura non prevedeva: - **Nessun prezzo barrato.** Le creative danno il solo prezzo promo, e i listini pieni di agosto (300/220/140/100) sono numeri che nessuno ha riconfermato - riportarli accanto ai nuovi sarebbe stato inventarli. `price-old` diventa opzionale nella CardSpec (`priceOld`) invece di essere un tag vuoto: cosi' la spec dichiara che quella card non ne ha, e il test lo pretende solo dove c'e'. Agosto e i bundle sono marcati `priceOld: true`, quindi restano sotto controllo come prima. - **Cinque card non stanno in una griglia da due o da tre** senza lasciare un buco in fondo. `.cards-5` va a sei colonne con span due, cosi' le ultime due si spostano al centro; a due card per riga sotto i 1000px, a una sotto i 700. Testi ripresi dall'edizione di agosto, gia' approvata dal cliente, cambiando solo cio' che le date rendevano falso: la FAQ delle class diceva "gli orari saranno pubblicati entro Settembre" e adesso e' settembre, quindi rimanda al calendario che esiste. La FAQ 7 sul Check-up e' nuova e va fatta leggere a Nicola prima della pubblicazione. 186 test verdi, `astro check` invariato (i 3 errori della pagina promo sono preesistenti e non toccati). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FPKFABRT3y26dFNMquF3CS --- src/data/content-seed.ts | 96 ++++++++++++++++++++++++++++++ src/data/promo-edizioni.ts | 42 ++++++++++--- src/pages/promo-eventi/promo.astro | 25 +++++++- tests/promo-edizioni.test.ts | 11 +++- 4 files changed, 163 insertions(+), 11 deletions(-) diff --git a/src/data/content-seed.ts b/src/data/content-seed.ts index 9ce5522..2cecbd9 100644 --- a/src/data/content-seed.ts +++ b/src/data/content-seed.ts @@ -503,6 +503,102 @@ export const contentSeed: SeedEntry[] = [ T('bundle.faq.4.q', 'Il pagamento è sicuro?'), H('bundle.faq.4.a', 'Sì. Il pagamento avviene tramite Stripe, 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.'), + // --- Promo settembre (edizione `settembre-2026`, 5 → 19 settembre) --- + // Testi ripresi dall'edizione di agosto, che il cliente ha già approvato: cambia solo ciò che + // le date o le nuove creative rendono falso. I prezzi e le voci degli elenchi vengono dalle + // sei creative consegnate il 03/09. + T('settembre.hero.badge', 'insanity.lab · bologna savena'), + T('settembre.hero.title1', 'Allenati con metodo.'), + T('settembre.hero.accent', 'Scegli come'), + T('settembre.hero.title2', 'vuoi farlo.'), + H('settembre.hero.desc', 'Allenati con flessibilità, senza vincoli, da subito.
Acquisto online in pochi secondi, pagamento sicuro via Stripe.'), + T('settembre.hero.cta', 'Le nostre proposte', []), + + T('settembre.launch.badge', 'solo per apertura nuova sede Settembre 2026'), + T('settembre.launch.title', 'Il nostro metodo nasce per durare.'), + T('settembre.launch.accent', "Per l'apertura, un'eccezione. Solo per poco."), + T('settembre.launch.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 — e una valutazione singola, per chi vuole partire dai propri numeri."), + + T('settembre.why5.text', 'Non è una prova. È un percorso in miniatura — per capire come lavoriamo prima di impegnarti su un programma trimestrale o semestrale.'), + + T('settembre.buy-label', 'Acquista', []), + + T('settembre.card.1.eyebrow', 'Personal training', []), + T('settembre.card.1.title', 'One to One'), + T('settembre.card.1.price', '250 €', []), + T('settembre.card.1.unit', '5 incontri', []), + T('settembre.card.1.feature.1', 'Screening iniziale, definizione dei tuoi obiettivi'), + T('settembre.card.1.feature.2', 'Check-up con profilazione fisica, il tuo punto di partenza'), + T('settembre.card.1.feature.3', 'PT dedicato, presente a ogni sessione'), + T('settembre.card.1.feature.4', 'In-sanity plan, il tuo programma personalizzato'), + + T('settembre.card.2.eyebrow', 'Personal training', []), + T('settembre.card.2.title', 'Personalized Training Group'), + T('settembre.card.2.price', '150 €', []), + T('settembre.card.2.unit', '5 incontri', []), + T('settembre.card.2.feature.1', 'Screening iniziale, definizione dei tuoi obiettivi'), + T('settembre.card.2.feature.2', 'Check-up con profilazione fisica, il tuo punto di partenza'), + T('settembre.card.2.feature.3', 'PT dedicato, presente a ogni sessione'), + T('settembre.card.2.feature.4', 'In-sanity plan, il tuo programma personalizzato'), + + T('settembre.card.3.eyebrow', 'Pilates class', []), + T('settembre.card.3.title', 'Reformer Class'), + T('settembre.card.3.price', '130 €', []), + T('settembre.card.3.unit', '5 incontri', []), + T('settembre.card.3.feature.1', 'Core, postura e mobilità su macchina Reformer'), + T('settembre.card.3.feature.2', '40 minuti, massimo 5 utenti'), + T('settembre.card.3.feature.3', 'Lavoro a basso impatto sulle articolazioni'), + + T('settembre.card.4.eyebrow', 'Holistic class', []), + T('settembre.card.4.title', 'Pilates Mat, Mobility, Yoga'), + T('settembre.card.4.price', '80 €', []), + T('settembre.card.4.unit', '5 incontri', []), + T('settembre.card.4.feature.1', 'Mobilità articolare, respirazione, equilibrio'), + T('settembre.card.4.feature.2', 'Da solo, o integrato nei tuoi allenamenti'), + T('settembre.card.4.feature.3', 'Adatto a tutti i livelli'), + + // Il Check-up avanzato non è un percorso: si compra una volta e si fa in un appuntamento. + // Per questo l'unità dice "1 valutazione" e non "5 incontri". + T('settembre.card.5.eyebrow', 'Valutazione', []), + T('settembre.card.5.title', 'Check-up avanzato'), + T('settembre.card.5.price', '70 €', []), + T('settembre.card.5.unit', '1 valutazione', []), + T('settembre.card.5.feature.1', 'Analisi completa di composizione corporea, e analisi HRV'), + T('settembre.card.5.feature.2', 'Spirometria e scansione posturale 3D'), + T('settembre.card.5.feature.3', 'Report dettagliato e consigli personalizzati'), + + // La deadline è lo stesso istante in cui finisce l'edizione in `promo-edizioni.ts`: un test + // lo verifica, perché un countdown che scade a un'ora diversa dallo scambio fa contraddire + // il sito. + T('settembre.countdown.label', 'La promo scade tra', []), + T('settembre.countdown.deadline', '2026-09-19T23:59:59+02:00', []), + T('settembre.countdown.unit.days', 'giorni', []), + T('settembre.countdown.unit.hours', 'ore', []), + T('settembre.countdown.unit.minutes', 'minuti', []), + T('settembre.countdown.unit.seconds', 'secondi', []), + + T('settembre.validity-note', 'Promo valida dal 5 al 19 settembre 2026 · Incontri validi entro 2 mesi dal primo incontro e comunque non oltre il 1° dicembre 2026', []), + + T('settembre.faq.title', 'Tutto quello che vuoi sapere prima di acquistare.'), + T('settembre.faq.0.q', 'Come faccio ad acquistare i pacchetti?'), + H('settembre.faq.0.a', 'Clicca sull\'icona del pacchetto che desideri acquistare e compila tutti i campi richiesti per completare la registrazione.

Una volta terminata l\'iscrizione, riceverai un\'email con le istruzioni per scaricare la nostra app Bsport. Dopo aver effettuato l\'accesso, entra nella sezione Club: qui troverai tutti i pacchetti disponibili. Seleziona quello che preferisci e completa l\'acquisto direttamente dall\'app effettuando il pagamento.

Se hai bisogno di aiuto scrivi su WhatsApp ({{whatsapp}}) e sarai presto ricontattato!'), + T('settembre.faq.1.q', 'Come funziona il pacchetto a incontri per One to One e Personalized Training Group?'), + H('settembre.faq.1.a', 'Per acquistare il pacchetto online segui le istruzioni riportate nella risposta della prima domanda. Una volta avvenuto con successo l\'acquisto, potrai contattarci scrivendoci su WhatsApp ({{whatsapp}}) per prenotare lo screening iniziale ovvero il colloquio conoscitivo per la definizione dei tuoi obiettivi. 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.'), + // Ad agosto qui c'era scritto "gli orari saranno pubblicati entro Settembre": adesso è + // settembre e il calendario esiste, quindi la frase andava riscritta o diventava falsa. + T('settembre.faq.2.q', 'Come funziona il percorso Reformer Class o Holistic Class?'), + H('settembre.faq.2.a', 'Per acquistare il pacchetto online segui le istruzioni riportate nella risposta della prima domanda. Una volta scaricata l\'app Bsport e acquistato il pacchetto potrai procedere a selezionare autonomamente la class che preferisci nel giorno e nell\'orario che desideri. Gli orari delle class li trovi nel calendario, che puoi consultare e scaricare in PDF dalla pagina Servizi del sito. In caso di problemi contattaci su WhatsApp ({{whatsapp}})! Gli incontri sono validi 2 mesi dalla data del primo incontro e comunque non oltre il 1° dicembre 2026.'), + T('settembre.faq.7.q', "Che cos'è il Check-up avanzato?"), + H('settembre.faq.7.a', 'È una valutazione singola, non un percorso a incontri: analisi completa della composizione corporea e analisi HRV, spirometria e scansione posturale 3D. Al termine ricevi un report dettagliato con i consigli che ne derivano. Puoi farlo da solo, oppure come punto di partenza prima di scegliere un percorso. Per fissare l\'appuntamento scrivici su WhatsApp ({{whatsapp}}).'), + T('settembre.faq.3.q', 'Posso usare gli incontri su servizi diversi?'), + H('settembre.faq.3.a', 'Ogni pacchetto è dedicato a un singolo servizio (One to One, Personalized Training Group, Reformer Class o Pilates Mat, Mobility, Yoga). Se vuoi accedere a più servizi puoi acquistare pacchetti separati per ciascuno. Il Check-up avanzato è a sé: è una valutazione singola e non un pacchetto di incontri.'), + T('settembre.faq.4.q', 'Posso regalare un pacchetto a qualcun altro?'), + H('settembre.faq.4.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 WhatsApp ({{whatsapp}}) per organizzare le sessioni.'), + T('settembre.faq.5.q', 'Il pagamento è sicuro?'), + H('settembre.faq.5.a', 'Sì. Il pagamento avviene tramite Stripe, 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.'), + T('settembre.faq.6.q', 'Posso chiedere un rimborso?'), + H('settembre.faq.6.a', 'Per richieste di rimborso o problemi con l\'acquisto scrivici su WhatsApp o via email. Valutiamo ogni situazione in modo diretto e umano.'), + ]; export const seedByTag = new Map(contentSeed.map((e) => [e.tag, e])); diff --git a/src/data/promo-edizioni.ts b/src/data/promo-edizioni.ts index 8119db0..661d5fe 100644 --- a/src/data/promo-edizioni.ts +++ b/src/data/promo-edizioni.ts @@ -20,6 +20,9 @@ export interface CardSpec { featured?: boolean; /** Riga sotto il prezzo, es. "5 incontri". */ unit?: boolean; + /** Prezzo pieno barrato accanto a quello promo. Senza, la card mostra il solo prezzo di + * vendita: un listino barrato non dichiarato dal cliente sarebbe un numero inventato. */ + priceOld?: boolean; /** Domanda in testa alla card, es. "Ti alleni ma non vedi risultati?". */ question?: boolean; /** Paragrafo descrittivo sotto l'elenco. */ @@ -52,10 +55,10 @@ export const edizioni: Edizione[] = [ to: '2026-08-19T23:59:59+02:00', launch: true, cards: [ - { id: 1, features: 4, unit: true }, - { id: 2, features: 4, unit: true, featured: true }, - { id: 3, features: 3, unit: true }, - { id: 4, features: 3, unit: true }, + { id: 1, features: 4, unit: true, priceOld: true }, + { id: 2, features: 4, unit: true, priceOld: true, featured: true }, + { id: 3, features: 3, unit: true, priceOld: true }, + { id: 4, features: 3, unit: true, priceOld: true }, ], faq: [0, 1, 2, 3, 4, 5, 6], }, @@ -67,14 +70,39 @@ export const edizioni: Edizione[] = [ from: '2026-08-20T00:00:00+02:00', to: '2026-09-04T23:59:59+02:00', cards: [ - { id: 1, features: 3, question: true, desc: true, note: true }, - { id: 2, features: 2, question: true, desc: true, note: true }, + { id: 1, features: 3, question: true, desc: true, note: true, priceOld: true }, + { id: 2, features: 2, question: true, desc: true, note: true, priceOld: true }, // Nessuna card "in evidenza": sono tre percorsi per esigenze diverse, non tre livelli // dello stesso prodotto, e un "più scelto" su un'offerta appena nata sarebbe inventato. - { id: 3, features: 3, question: true, desc: true, note: true }, + { id: 3, features: 3, question: true, desc: true, note: true, priceOld: true }, ], faq: [0, 1, 2, 3, 4], }, + { + // Promo di settembre: subentra ai bundle il 5 e chiude il 19, il giorno dell'inaugurazione. + // Torna la forma dei pacchetti da 5 incontri, con in più il Check-up avanzato, che è una + // valutazione singola e non un percorso — per questo ha "1 valutazione" al posto di + // "5 incontri". + // Nessun prezzo barrato: le creative del cliente danno il solo prezzo promo, e un listino + // pieno ripreso dall'edizione di agosto sarebbe un numero che nessuno ha confermato. + id: 'settembre-2026', + prefix: 'settembre', + from: '2026-09-05T00:00:00+02:00', + to: '2026-09-19T23:59:59+02:00', + launch: true, + cards: [ + { id: 1, features: 4, unit: true }, + { id: 2, features: 4, unit: true }, + { id: 3, features: 3, unit: true }, + { id: 4, features: 3, unit: true }, + // Nessuna card "in evidenza": cinque proposte per esigenze diverse, non cinque livelli + // dello stesso prodotto. + { id: 5, features: 3, unit: true }, + ], + // La 7 è la domanda nuova sul Check-up: va letta dopo le altre due "come funziona", non + // in fondo dopo il rimborso, quindi l'ordine di comparsa non segue la numerazione. + faq: [0, 1, 2, 7, 3, 4, 5, 6], + }, ]; /** Tag di contenuto di un'edizione: `tagEdizione(e, 'card.1.price')` → `bundle.card.1.price`. */ diff --git a/src/pages/promo-eventi/promo.astro b/src/pages/promo-eventi/promo.astro index bf2ccc5..f08fc00 100644 --- a/src/pages/promo-eventi/promo.astro +++ b/src/pages/promo-eventi/promo.astro @@ -114,7 +114,7 @@ const countdownDeadline = ed ? t(tg('countdown.deadline')) : ''; {c.question && }
- + {c.priceOld && }
{c.unit && }
    @@ -296,6 +296,16 @@ const countdownDeadline = ed ? t(tg('countdown.deadline')) : ''; .lp .cards-3 { grid-template-columns: repeat(3, 1fr); } .lp .cards-3 .card { display: flex; flex-direction: column; } .lp .cards-3 .card .buy { margin-top: auto; } + + /* CARD DELLE EDIZIONI A CINQUE (promo settembre). Cinque non si divide in una griglia a due + o a tre colonne senza lasciare un buco in fondo: la griglia è a sei colonne e ogni card ne + occupa due, così le ultime due si spostano al centro invece di restare allineate a sinistra + con un vuoto accanto. */ + .lp .cards-5 { grid-template-columns: repeat(6, 1fr); } + .lp .cards-5 .card { grid-column: span 2; display: flex; flex-direction: column; } + .lp .cards-5 .card .buy { margin-top: auto; } + .lp .cards-5 .card:nth-child(4) { grid-column: 2 / span 2; } + .lp .cards-5 .card:nth-child(5) { grid-column: 4 / span 2; } .lp .card .question { margin: 0 0 14px; font-size: 15px; line-height: 1.4; font-weight: 700; color: #2D2D2D; } @@ -343,9 +353,22 @@ const countdownDeadline = ed ? t(tg('countdown.deadline')) : ''; .lp .faq-block.is-open .faq-a { display: block; } .lp .faq-a .hl { color: #8A6E3F; } + /* Sotto i 1000px tre colonne diventano troppo strette per un prezzo e un elenco: le cinque + card passano a due per riga (griglia a quattro, span due), con la quinta al centro. */ + @media (max-width: 1000px) and (min-width: 701px) { + .lp .cards-5 { grid-template-columns: repeat(4, 1fr); } + .lp .cards-5 .card:nth-child(4) { grid-column: span 2; } + .lp .cards-5 .card:nth-child(5) { grid-column: 2 / span 2; } + } + @media (max-width: 700px) { .lp .hero, .lp .launch, .lp .why5, .lp .cards-wrap, .lp .faq-wrap { padding-left: 28px; padding-right: 28px; } .lp .cards { grid-template-columns: 1fr; } + /* A una colonna gli span e le collocazioni delle ultime due card lascerebbero righe vuote. */ + .lp .cards-5 { grid-template-columns: 1fr; } + .lp .cards-5 .card, + .lp .cards-5 .card:nth-child(4), + .lp .cards-5 .card:nth-child(5) { grid-column: auto; } .lp .countdown .cd-boxes { gap: 6px; } .lp .countdown .cd-box { min-width: 0; flex: 1; padding: 10px 4px; } .lp .countdown .cd-num { font-size: 22px; } diff --git a/tests/promo-edizioni.test.ts b/tests/promo-edizioni.test.ts index 82a6d14..df5bf9b 100644 --- a/tests/promo-edizioni.test.ts +++ b/tests/promo-edizioni.test.ts @@ -8,11 +8,12 @@ describe('edizioneAttiva', () => { it('sceglie l\'edizione la cui finestra contiene l\'istante', () => { expect(edizioneAttiva(d('2026-08-15T12:00:00+02:00'))?.id).toBe('agosto-2026'); expect(edizioneAttiva(d('2026-08-25T12:00:00+02:00'))?.id).toBe('bundle-apertura'); + expect(edizioneAttiva(d('2026-09-10T12:00:00+02:00'))?.id).toBe('settembre-2026'); }); it('fuori da ogni finestra non c\'è edizione attiva', () => { expect(edizioneAttiva(d('2026-07-15T12:00:00+02:00'))).toBeNull(); - expect(edizioneAttiva(d('2026-09-10T12:00:00+02:00'))).toBeNull(); + expect(edizioneAttiva(d('2026-09-25T12:00:00+02:00'))).toBeNull(); }); // Il passaggio è il momento in cui il sito cambia da solo: va provato al secondo, e in ora @@ -22,13 +23,16 @@ describe('edizioneAttiva', () => { expect(edizioneAttiva(d('2026-08-19T23:59:59+02:00'))?.id).toBe('agosto-2026'); expect(edizioneAttiva(d('2026-08-20T00:00:00+02:00'))?.id).toBe('bundle-apertura'); expect(edizioneAttiva(d('2026-09-04T23:59:59+02:00'))?.id).toBe('bundle-apertura'); - expect(edizioneAttiva(d('2026-09-05T00:00:00+02:00'))).toBeNull(); + expect(edizioneAttiva(d('2026-09-05T00:00:00+02:00'))?.id).toBe('settembre-2026'); + expect(edizioneAttiva(d('2026-09-19T23:59:59+02:00'))?.id).toBe('settembre-2026'); + expect(edizioneAttiva(d('2026-09-20T00:00:00+02:00'))).toBeNull(); }); it('l\'ordine dell\'elenco non cambia il risultato', () => { const rovescio = [...edizioni].reverse(); expect(edizioneAttiva(d('2026-08-15T12:00:00+02:00'), rovescio)?.id).toBe('agosto-2026'); expect(edizioneAttiva(d('2026-08-25T12:00:00+02:00'), rovescio)?.id).toBe('bundle-apertura'); + expect(edizioneAttiva(d('2026-09-10T12:00:00+02:00'), rovescio)?.id).toBe('settembre-2026'); }); }); @@ -80,7 +84,8 @@ describe('i testi di ogni edizione esistono', () => { 'countdown.unit.days', 'countdown.unit.hours', 'countdown.unit.minutes', 'countdown.unit.seconds']; if (e.launch) base.push('launch.badge', 'launch.title', 'launch.accent', 'launch.desc', 'why5.text'); for (const c of e.cards) { - base.push(`card.${c.id}.eyebrow`, `card.${c.id}.title`, `card.${c.id}.price`, `card.${c.id}.price-old`); + base.push(`card.${c.id}.eyebrow`, `card.${c.id}.title`, `card.${c.id}.price`); + if (c.priceOld) base.push(`card.${c.id}.price-old`); if (c.featured) base.push(`card.${c.id}.badge`); if (c.unit) base.push(`card.${c.id}.unit`); if (c.question) base.push(`card.${c.id}.question`);