Porta la promo agosto nel branch piattaforme, per vederle insieme in locale
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -320,7 +320,7 @@ export const contentSeed: SeedEntry[] = [
|
|||||||
// Card 1 — One to One
|
// Card 1 — One to One
|
||||||
T('promo.card.1.eyebrow', 'Personal training', []),
|
T('promo.card.1.eyebrow', 'Personal training', []),
|
||||||
T('promo.card.1.title', 'One to One'),
|
T('promo.card.1.title', 'One to One'),
|
||||||
T('promo.card.1.price', '163 €', []),
|
T('promo.card.1.price', '200 €', []),
|
||||||
T('promo.card.1.price-old', '300 €', []),
|
T('promo.card.1.price-old', '300 €', []),
|
||||||
T('promo.card.1.unit', '5 incontri', []),
|
T('promo.card.1.unit', '5 incontri', []),
|
||||||
T('promo.card.1.feature.1', 'Screening iniziale, definizione dei tuoi obiettivi'),
|
T('promo.card.1.feature.1', 'Screening iniziale, definizione dei tuoi obiettivi'),
|
||||||
@@ -331,7 +331,7 @@ export const contentSeed: SeedEntry[] = [
|
|||||||
T('promo.card.2.badge', 'Più scelto', []),
|
T('promo.card.2.badge', 'Più scelto', []),
|
||||||
T('promo.card.2.eyebrow', 'Personal training', []),
|
T('promo.card.2.eyebrow', 'Personal training', []),
|
||||||
T('promo.card.2.title', 'Small Group'),
|
T('promo.card.2.title', 'Small Group'),
|
||||||
T('promo.card.2.price', '110 €', []),
|
T('promo.card.2.price', '125 €', []),
|
||||||
T('promo.card.2.price-old', '220 €', []),
|
T('promo.card.2.price-old', '220 €', []),
|
||||||
T('promo.card.2.unit', '5 incontri', []),
|
T('promo.card.2.unit', '5 incontri', []),
|
||||||
T('promo.card.2.feature.1', 'Screening iniziale, definizione dei tuoi obiettivi'),
|
T('promo.card.2.feature.1', 'Screening iniziale, definizione dei tuoi obiettivi'),
|
||||||
@@ -341,7 +341,7 @@ export const contentSeed: SeedEntry[] = [
|
|||||||
// Card 3 — Reformer
|
// Card 3 — Reformer
|
||||||
T('promo.card.3.eyebrow', 'Pilates class', []),
|
T('promo.card.3.eyebrow', 'Pilates class', []),
|
||||||
T('promo.card.3.title', 'Reformer'),
|
T('promo.card.3.title', 'Reformer'),
|
||||||
T('promo.card.3.price', '100 €', []),
|
T('promo.card.3.price', '115 €', []),
|
||||||
T('promo.card.3.price-old', '140 €', []),
|
T('promo.card.3.price-old', '140 €', []),
|
||||||
T('promo.card.3.unit', '5 incontri', []),
|
T('promo.card.3.unit', '5 incontri', []),
|
||||||
T('promo.card.3.feature.1', 'Core, postura e mobilità su macchina Reformer'),
|
T('promo.card.3.feature.1', 'Core, postura e mobilità su macchina Reformer'),
|
||||||
@@ -350,14 +350,23 @@ export const contentSeed: SeedEntry[] = [
|
|||||||
// Card 4 — Holistic class
|
// Card 4 — Holistic class
|
||||||
T('promo.card.4.eyebrow', 'Holistic class', []),
|
T('promo.card.4.eyebrow', 'Holistic class', []),
|
||||||
T('promo.card.4.title', 'Pilates Mat, Mobility, Yoga'),
|
T('promo.card.4.title', 'Pilates Mat, Mobility, Yoga'),
|
||||||
T('promo.card.4.price', '50 €', []),
|
T('promo.card.4.price', '75 €', []),
|
||||||
T('promo.card.4.price-old', '100 €', []),
|
T('promo.card.4.price-old', '100 €', []),
|
||||||
T('promo.card.4.unit', '5 incontri', []),
|
T('promo.card.4.unit', '5 incontri', []),
|
||||||
T('promo.card.4.feature.1', 'Mobilità articolare, respirazione, equilibrio'),
|
T('promo.card.4.feature.1', 'Mobilità articolare, respirazione, equilibrio'),
|
||||||
T('promo.card.4.feature.2', 'Da solo, o integrato nei tuoi allenamenti'),
|
T('promo.card.4.feature.2', 'Da solo, o integrato nei tuoi allenamenti'),
|
||||||
T('promo.card.4.feature.3', 'Adatto a tutti i livelli'),
|
T('promo.card.4.feature.3', 'Adatto a tutti i livelli'),
|
||||||
|
// Countdown alla scadenza della promo. `deadline` è una data ISO con offset: la pagina la
|
||||||
|
// legge, la confronta col tempo del browser e nasconde tutto il blocco quando è passata
|
||||||
|
// (o se il valore non è una data valida). Editabile dal pannello per la finestra successiva.
|
||||||
|
T('promo.countdown.label', 'La promo scade tra', []),
|
||||||
|
T('promo.countdown.deadline', '2026-08-19T23:59:59+02:00', []),
|
||||||
|
T('promo.countdown.unit.days', 'giorni', []),
|
||||||
|
T('promo.countdown.unit.hours', 'ore', []),
|
||||||
|
T('promo.countdown.unit.minutes', 'minuti', []),
|
||||||
|
T('promo.countdown.unit.seconds', 'secondi', []),
|
||||||
// Nota validità
|
// Nota validità
|
||||||
T('promo.validity-note', 'Disponibile solo per l\'apertura · Valido entro 2 mesi dal primo incontro e comunque non oltre il 1° dicembre 2026', []),
|
T('promo.validity-note', 'Promo valida dal 1° al 19 agosto 2026 · Incontri validi entro 2 mesi dal primo incontro e comunque non oltre il 1° dicembre 2026', []),
|
||||||
// FAQ — le risposte usano il segnaposto {{whatsapp}}, sostituito a runtime con global.whatsapp
|
// FAQ — le risposte usano il segnaposto {{whatsapp}}, sostituito a runtime con global.whatsapp
|
||||||
T('promo.faq.title', 'Tutto quello che vuoi sapere prima di acquistare.'),
|
T('promo.faq.title', 'Tutto quello che vuoi sapere prima di acquistare.'),
|
||||||
T('promo.faq.1.q', 'Come funziona il pacchetto a incontri per One to One e Small Group?'),
|
T('promo.faq.1.q', 'Come funziona il pacchetto a incontri per One to One e Small Group?'),
|
||||||
|
|||||||
@@ -177,6 +177,21 @@ export function createDb(path?: string): Database.Database {
|
|||||||
const updPromoTx = db.transaction(() => { for (const [tag, oldV, newV] of PROMO_FRECCE) upd02.run(newV, tag, oldV); });
|
const updPromoTx = db.transaction(() => { for (const [tag, oldV, newV] of PROMO_FRECCE) upd02.run(newV, tag, oldV); });
|
||||||
updPromoTx();
|
updPromoTx();
|
||||||
|
|
||||||
|
// Listino promo agosto 2026 (finestra 1–19 agosto): nuovi prezzi comunicati dal cliente e nota
|
||||||
|
// di validità con le date. I prezzi barrati (price-old) restano invariati. UPDATE guardato dal
|
||||||
|
// vecchio valore → idempotente e non sovrascrive eventuali ritocchi fatti dal pannello.
|
||||||
|
const PROMO_AGOSTO_2026: [string, string, string][] = [
|
||||||
|
['promo.card.1.price', '163 €', '200 €'],
|
||||||
|
['promo.card.2.price', '110 €', '125 €'],
|
||||||
|
['promo.card.3.price', '100 €', '115 €'],
|
||||||
|
['promo.card.4.price', '50 €', '75 €'],
|
||||||
|
['promo.validity-note',
|
||||||
|
"Disponibile solo per l'apertura · Valido entro 2 mesi dal primo incontro e comunque non oltre il 1° dicembre 2026",
|
||||||
|
'Promo valida dal 1° al 19 agosto 2026 · Incontri validi entro 2 mesi dal primo incontro e comunque non oltre il 1° dicembre 2026'],
|
||||||
|
];
|
||||||
|
const updAgostoTx = db.transaction(() => { for (const [tag, oldV, newV] of PROMO_AGOSTO_2026) upd02.run(newV, tag, oldV); });
|
||||||
|
updAgostoTx();
|
||||||
|
|
||||||
// Il servizio "Performance Class" torna a chiamarsi solo "Class" (titolo card /services,
|
// Il servizio "Performance Class" torna a chiamarsi solo "Class" (titolo card /services,
|
||||||
// H1 pagina dettaglio, link footer). Inverte la precedente normalizzazione Class→Performance
|
// H1 pagina dettaglio, link footer). Inverte la precedente normalizzazione Class→Performance
|
||||||
// Class. Guardato dal vecchio valore → idempotente, non tocca eventuali modifiche del pannello.
|
// Class. Guardato dal vecchio valore → idempotente, non tocca eventuali modifiche del pannello.
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ const faqs = [1, 2, 3, 4, 5, 6];
|
|||||||
// Numero WhatsApp centralizzato: le risposte FAQ contengono il segnaposto {{whatsapp}}
|
// Numero WhatsApp centralizzato: le risposte FAQ contengono il segnaposto {{whatsapp}}
|
||||||
// che qui viene sostituito col valore del tag globale, così si aggiorna in un punto solo.
|
// che qui viene sostituito col valore del tag globale, così si aggiorna in un punto solo.
|
||||||
const wa = t('global.whatsapp');
|
const wa = t('global.whatsapp');
|
||||||
|
// Scadenza della promo (tag editabile dal pannello, formato ISO con offset). Viene solo passata
|
||||||
|
// al client: è il JS a decidere se mostrare il countdown, perché il confronto va fatto col tempo
|
||||||
|
// del visitatore e non con quello del render (la pagina è SSR ma può essere servita da cache).
|
||||||
|
const countdownDeadline = t('promo.countdown.deadline');
|
||||||
---
|
---
|
||||||
<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.">
|
<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="lp">
|
||||||
@@ -47,6 +51,15 @@ const wa = t('global.whatsapp');
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="cards-wrap" id="proposte">
|
<section class="cards-wrap" id="proposte">
|
||||||
|
<div class="countdown" data-deadline={countdownDeadline} hidden>
|
||||||
|
<T tag="promo.countdown.label" as="div" class="cd-label" />
|
||||||
|
<div class="cd-boxes">
|
||||||
|
<div class="cd-box"><span class="cd-num" data-cd="days">–</span><T tag="promo.countdown.unit.days" as="span" class="cd-unit" /></div>
|
||||||
|
<div class="cd-box"><span class="cd-num" data-cd="hours">–</span><T tag="promo.countdown.unit.hours" as="span" class="cd-unit" /></div>
|
||||||
|
<div class="cd-box"><span class="cd-num" data-cd="minutes">–</span><T tag="promo.countdown.unit.minutes" as="span" class="cd-unit" /></div>
|
||||||
|
<div class="cd-box"><span class="cd-num" data-cd="seconds">–</span><T tag="promo.countdown.unit.seconds" as="span" class="cd-unit" /></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="cards">
|
<div class="cards">
|
||||||
{cards.map((c) => (
|
{cards.map((c) => (
|
||||||
<div class:list={['card', c.featured && 'featured']}>
|
<div class:list={['card', c.featured && 'featured']}>
|
||||||
@@ -88,6 +101,32 @@ const wa = t('global.whatsapp');
|
|||||||
</Base>
|
</Base>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// Countdown alla scadenza della promo. Parte nascosto (attributo `hidden` nel markup) e viene
|
||||||
|
// mostrato solo se la deadline è una data valida ancora futura: così, a promo finita, il blocco
|
||||||
|
// semplicemente non compare — nessun messaggio, nessun salto di layout.
|
||||||
|
const cd = document.querySelector<HTMLElement>('.lp .countdown');
|
||||||
|
if (cd) {
|
||||||
|
const end = Date.parse(cd.dataset.deadline || '');
|
||||||
|
const nums = new Map<string, HTMLElement>();
|
||||||
|
cd.querySelectorAll<HTMLElement>('.cd-num').forEach((n) => nums.set(n.dataset.cd!, n));
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0');
|
||||||
|
// Dichiarato prima di `tick` perché la prima chiamata è sincrona: se la promo è già scaduta
|
||||||
|
// il timer va fermato subito, e leggerlo dopo il `setInterval` darebbe un errore di TDZ.
|
||||||
|
let timer = 0;
|
||||||
|
const tick = () => {
|
||||||
|
const left = end - Date.now();
|
||||||
|
if (Number.isNaN(end) || left <= 0) { cd.hidden = true; clearInterval(timer); return; }
|
||||||
|
const s = Math.floor(left / 1000);
|
||||||
|
nums.get('days')!.textContent = String(Math.floor(s / 86400));
|
||||||
|
nums.get('hours')!.textContent = pad(Math.floor(s / 3600) % 24);
|
||||||
|
nums.get('minutes')!.textContent = pad(Math.floor(s / 60) % 60);
|
||||||
|
nums.get('seconds')!.textContent = pad(s % 60);
|
||||||
|
cd.hidden = false;
|
||||||
|
};
|
||||||
|
tick();
|
||||||
|
timer = setInterval(tick, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
// Accordion FAQ: click (o Invio/Spazio) sul quesito apre/chiude la risposta.
|
// Accordion FAQ: click (o Invio/Spazio) sul quesito apre/chiude la risposta.
|
||||||
document.querySelectorAll('.lp .faq-block .faq-q').forEach((q) => {
|
document.querySelectorAll('.lp .faq-block .faq-q').forEach((q) => {
|
||||||
const block = q.closest('.faq-block');
|
const block = q.closest('.faq-block');
|
||||||
@@ -144,6 +183,25 @@ const wa = t('global.whatsapp');
|
|||||||
.lp .why5 { padding: 48px 64px 8px; }
|
.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; }
|
.lp .why5 p { margin: 0; font-size: 18px; line-height: 1.7; font-style: italic; color: #2D2D2D; font-family: Georgia, serif; max-width: 620px; }
|
||||||
|
|
||||||
|
/* COUNTDOWN */
|
||||||
|
/* `hidden` va forzato: i display flex qui sotto avrebbero comunque la meglio sul default UA. */
|
||||||
|
.lp .countdown[hidden] { display: none !important; }
|
||||||
|
.lp .countdown {
|
||||||
|
display: flex; flex-direction: column; align-items: center; gap: 14px;
|
||||||
|
background: #2D2D2D; border-radius: 14px; padding: 26px 24px; margin: 0 0 28px;
|
||||||
|
}
|
||||||
|
.lp .countdown .cd-label {
|
||||||
|
font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700; color: #C4A882;
|
||||||
|
}
|
||||||
|
.lp .countdown .cd-boxes { display: flex; gap: 10px; }
|
||||||
|
.lp .countdown .cd-box {
|
||||||
|
display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 74px;
|
||||||
|
background: rgba(255,255,255,0.06); border: 0.5px solid rgba(196,168,130,0.35);
|
||||||
|
border-radius: 10px; padding: 12px 10px;
|
||||||
|
}
|
||||||
|
.lp .countdown .cd-num { font-size: 30px; font-weight: 700; line-height: 1; color: #FFFFFF; font-variant-numeric: tabular-nums; }
|
||||||
|
.lp .countdown .cd-unit { font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: #B8B6AE; }
|
||||||
|
|
||||||
/* CARDS */
|
/* CARDS */
|
||||||
.lp .cards-wrap { padding: 32px 64px 72px; }
|
.lp .cards-wrap { padding: 32px 64px 72px; }
|
||||||
.lp .cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
|
.lp .cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
|
||||||
@@ -192,6 +250,10 @@ const wa = t('global.whatsapp');
|
|||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
.lp .hero, .lp .launch, .lp .why5, .lp .cards-wrap, .lp .faq-wrap { padding-left: 28px; padding-right: 28px; }
|
.lp .hero, .lp .launch, .lp .why5, .lp .cards-wrap, .lp .faq-wrap { padding-left: 28px; padding-right: 28px; }
|
||||||
.lp .cards { grid-template-columns: 1fr; }
|
.lp .cards { grid-template-columns: 1fr; }
|
||||||
|
.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; }
|
||||||
|
.lp .countdown .cd-unit { font-size: 9.5px; }
|
||||||
.lp .hero h1 { font-size: 32px; }
|
.lp .hero h1 { font-size: 32px; }
|
||||||
.lp .launch h1 { font-size: 28px; }
|
.lp .launch h1 { font-size: 28px; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user