Compare commits

...

5 Commits

Author SHA1 Message Date
Adriano Dal Pastro 2b71cbb49e L'inaugurazione e' di sabato, e la fascia morta fra 700 e 701 px
Due rilievi della revisione, uno dei quali fuori dalla promo.

**`eventi.astro`: "venerdi' 19 settembre 2026"**, ma il 19/09/2026 e' **sabato**
(`calendari.ts:136` lo diceva gia' giusto). E' la meta description della pagina
dell'inaugurazione: e' cio' che si legge nell'anteprima di un link su WhatsApp,
che e' il canale con cui questo cliente parla ai suoi. Una riga, ma pubblica e
falsa.

**`.cards-5` fra 700 e 701 px non era coperta da nessuna regola**: la fascia
intermedia aveva `and (min-width: 701px)`, e a larghezze frazionarie - uno zoom
del browser dà 700,5 px - non valeva ne' quella ne' il blocco <=700, quindi le
cinque card tornavano a sei colonne dentro ~570 px. Tolto il `min-width`: ci
pensa il blocco <=700, che viene dopo e a pari specificita' vince.

Tolta anche la riga ridondante `.cards-5 { 1fr }` nel blocco <=700: `.lp .cards`
copre gia' tutte le griglie a una colonna, ed e' lo stesso meccanismo per cui
`.cards-3` funziona senza una regola sua.

Restano non toccati, perche' verificati corretti: la virgola di "composizione
corporea, e analisi HRV" e' **nella creativa** (a fine riga, nel layout su tre
righe), e le code esplicative delle card 1 e 2 vengono dai testi di agosto
gia' approvati dal cliente, non dalle creative compresse per il formato social.

186 test verdi.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPKFABRT3y26dFNMquF3CS
2026-09-04 06:53:30 +00:00
Adriano Dal Pastro f914d0699a 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPKFABRT3y26dFNMquF3CS
2026-09-04 06:43:50 +00:00
AdrianoDev f5fac89c1b Via dal router il vecchio sottodominio, tolto dal DNS
Adriano ha rimosso il record insanitylab.tielogic.xyz il 03/09/2026. Una
regola Traefik che nomina un host inesistente non e' inerte: il router chiede
un certificato per i nomi della sua regola, e la sfida ACME su un nome che
non risolve fallisce a ogni tentativo - proprio cio' che Let's Encrypt limita.

Resta il redirect di www verso l'apex, che e' l'unico ancora vero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
2026-09-03 12:41:52 +00:00
AdrianoDev 665c598462 Il vecchio sottodominio torna al suo redirect
Adriano ha creato piattaforme.tielogic.xyz, che e' il nome giusto per le aree
riservate: il prestito di insanitylab.tielogic.xyz e' durato un'ora e finisce
qui. Il sito torna esattamente come stamattina - apex, www e vecchio
sottodominio - e le piattaforme stanno su un host loro.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
2026-09-03 12:39:29 +00:00
AdrianoDev 4103376eb1 Il vecchio sottodominio esce dal redirect: serve alle aree riservate
insanitylab.tielogic.xyz faceva 308 verso l'apex dal 12/07. Dal 03/09/2026
ospita le aree riservate (Campus, Stress Index, Longevity) mentre si aspetta
il record A di apps.insanitylab.it su Aruba: il dominio del cliente resta
dov'e', le piattaforme partono da un sottodominio nostro.

www.insanitylab.it continua a reindirizzare come prima.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
2026-09-03 12:34:59 +00:00
6 changed files with 173 additions and 13 deletions
+6 -1
View File
@@ -23,7 +23,12 @@ services:
- traefik.http.routers.insanitylab.tls.certresolver=mytlschallenge
- traefik.http.services.insanitylab.loadbalancer.server.port=4321
# Router redirect: www e vecchio sottodominio tielogic.xyz → 301 apex
- traefik.http.routers.insanitylab-redir.rule=Host(`www.insanitylab.it`) || Host(`insanitylab.tielogic.xyz`)
# ⚠️ `insanitylab.tielogic.xyz` tolto dal DNS da Adriano il 03/09/2026: il record non
# esiste piu'. Lasciarlo qui non e' inerte — il router chiede un certificato per i
# nomi della sua regola, e una sfida ACME su un nome che non risolve fallisce a ogni
# tentativo, che e' esattamente cio' che Let's Encrypt limita. Il vecchio dominio ha
# smesso di essere raggiungibile: da qui in poi vale solo il redirect di `www`.
- traefik.http.routers.insanitylab-redir.rule=Host(`www.insanitylab.it`)
- traefik.http.routers.insanitylab-redir.tls=true
- traefik.http.routers.insanitylab-redir.entrypoints=websecure
- traefik.http.routers.insanitylab-redir.tls.certresolver=mytlschallenge
+96
View File
@@ -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 <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.'),
// --- 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.<br>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.<br><br>Una volta terminata l\'iscrizione, riceverai un\'email con le istruzioni per scaricare la nostra app <span class="hl">Bsport</span>. Dopo aver effettuato l\'accesso, entra nella sezione <span class="hl">Club</span>: qui troverai tutti i pacchetti disponibili. Seleziona quello che preferisci e completa l\'acquisto direttamente dall\'app effettuando il pagamento.<br><br>Se hai bisogno di aiuto scrivi su <span class="hl">WhatsApp ({{whatsapp}})</span> 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 <span class="hl">WhatsApp ({{whatsapp}})</span> 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 <span class="hl">Bsport</span> 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 <span class="hl">calendario</span>, che puoi consultare e scaricare in PDF dalla pagina <span class="hl">Servizi</span> del sito. In caso di problemi contattaci su <span class="hl">WhatsApp ({{whatsapp}})</span>! 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 <span class="hl">valutazione singola</span>, 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 <span class="hl">WhatsApp ({{whatsapp}})</span>.'),
T('settembre.faq.3.q', 'Posso usare gli incontri su servizi diversi?'),
H('settembre.faq.3.a', 'Ogni pacchetto è dedicato a un singolo servizio (<span class="hl">One to One</span>, <span class="hl">Personalized Training Group</span>, <span class="hl">Reformer Class</span> o <span class="hl">Pilates Mat, Mobility, Yoga</span>). Se vuoi accedere a più servizi puoi acquistare pacchetti separati per ciascuno. Il <span class="hl">Check-up avanzato</span> è 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 <span class="hl">WhatsApp ({{whatsapp}})</span> per organizzare le sessioni.'),
T('settembre.faq.5.q', 'Il pagamento è sicuro?'),
H('settembre.faq.5.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.'),
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 <span class="hl">WhatsApp</span> o via email. Valutiamo ogni situazione in modo diretto e umano.'),
];
export const seedByTag = new Map(contentSeed.map((e) => [e.tag, e]));
+35 -7
View File
@@ -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`. */
+1 -1
View File
@@ -38,7 +38,7 @@ const pomeriggio: Gruppo[] = [
---
<Base
title="In-Sanity Hour — 19 settembre 2026"
description="L'inaugurazione di InsanityLab: venerdì 19 settembre 2026 a Bologna. Mini class, personal training, check-up e consulenze gratuite, poi dj set e aperitivo per tutti."
description="L'inaugurazione di InsanityLab: sabato 19 settembre 2026 a Bologna. Mini class, personal training, check-up e consulenze gratuite, poi dj set e aperitivo per tutti."
>
<section class="ev-hero">
<div class="ev-hero__inner" data-reveal>
+27 -1
View File
@@ -114,7 +114,7 @@ const countdownDeadline = ed ? t(tg('countdown.deadline')) : '';
{c.question && <T tag={tg(`card.${c.id}.question`)} as="p" class="question" />}
<div class="price-row">
<T tag={tg(`card.${c.id}.price`)} as="div" class="price" />
<T tag={tg(`card.${c.id}.price-old`)} as="div" class="price-old" />
{c.priceOld && <T tag={tg(`card.${c.id}.price-old`)} as="div" class="price-old" />}
</div>
{c.unit && <T tag={tg(`card.${c.id}.unit`)} as="div" class="unit" />}
<ul>
@@ -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,25 @@ 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.
Nessun `min-width` a chiudere la fascia in basso: a viewport frazionari (uno zoom del
browser dà larghezze come 700,5px) fra 700 e 701 non varrebbe nessuna delle due regole e
le card tornerebbero a sei colonne. Ci pensa il blocco ≤700px, che viene dopo e a pari
specificità vince. */
@media (max-width: 1000px) {
.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 .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; }
+8 -3
View File
@@ -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`);