Files
InsanityLab-web/src/pages/services/[slug].astro
T
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

129 lines
7.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
import Base from '../../layouts/Base.astro';
import PageHero from '../../components/PageHero.astro';
import type { ImageMetadata } from 'astro';
import { services, getService } from '../../data/services';
import { performanceClassPricing } from '../../data/pricing';
import { serviceIcons } from '../../data/service-icons';
import T from '../../components/content/T.astro';
import TImg from '../../components/content/TImg.astro';
import { t } from '../../lib/content';
export const prerender = false;
const service = getService(Astro.params.slug!);
if (!service) return new Response(null, { status: 404 });
const 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 imgOf = (slug: string) => Object.entries(images).find(([p]) => p.includes(`program-${slug}.`))?.[1]?.default;
const heroImg = imgOf(service.slug);
// Categorie come da Figma (Performance class.pdf), non quelle del blog → tag service.side.category.1-4
const categoryIndexes = [1, 2, 3, 4];
// Ordine card pricing come da Figma: tan, taupe, dark
const tones = ['tan', 'taupe', 'dark'];
---
<Base title={t(`service.${slug}.title`)} description={t(`service.${slug}.excerpt`)}>
<PageHero title={t(`service.${slug}.title`).toUpperCase()} bgImage={heroImg} />
<section class="section">
<div class="container prog">
<div>
<h2>{t(`service.${slug}.title`).toUpperCase()}</h2>
<T tag={`service.${slug}.long-description`} as="p" class="prog__desc" />
{service.features.length > 0 && (
<ul class="prog__features">
{service.features.map((_, i) => <T tag={`service.${slug}.feature.${i + 1}`} as="li" />)}
</ul>
)}
{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" />
)}
<div class="prog__faq">
{service.faq.map((_, i) => (
<details open={i === 0}>
<summary><T tag={`service.${slug}.faq.${i + 1}.q`} /></summary>
<T tag={`service.${slug}.faq.${i + 1}.a`} as="p" />
</details>
))}
</div>
</div>
<aside class="prog__side">
<T tag="service.side.categories-label" as="h3" />
{categoryIndexes.map((n) => <T tag={`service.side.category.${n}`} as="p" class="prog__cat" />)}
<T tag="service.side.others-label" as="h3" />
{others.map((o) => (
<a class="prog__other" href={`/services/${o.slug}`}>
<span class="prog__icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" width="26" height="26" aria-hidden="true"><path d={serviceIcons[o.slug]} /></svg>
</span>
<T tag={`service.${o.slug}.title`} />
</a>
))}
</aside>
</div>
</section>
{service.hasPricing && (
<section class="section section--alt">
<div class="container">
<div class="section-heading">
<T tag="pricing.heading" as="h2" />
<T tag="pricing.intro" as="p" />
</div>
{performanceClassPricing.groups.map((g, gi) => (
<details class="price__group" open={g.open}>
<summary>{g.open ? '+' : ''} <T tag={`pricing.group.${gi + 1}.label`} /></summary>
{g.plans.length === 0 ? (
<T tag="service.pricing.empty-note" as="p" class="price__empty" />
) : (
<div class="price__row">
{g.plans.map((p, i) => (
<div class:list={['price__card', `price__card--${tones[i % 3]}`]}>
<p class="price__amount">{p.monthly}€</p>
<T tag="service.pricing.per" as="p" class="price__per" />
<p class="price__entries">{p.entries} {p.entries === 1 ? t('service.pricing.entries-singular') : t('service.pricing.entries-plural')}</p>
<T tag={`pricing.group.${gi + 1}.plan.${i + 1}.note`} as="p" />
<T tag={`pricing.group.${gi + 1}.plan.${i + 1}.installments`} as="p" />
<T tag={`pricing.group.${gi + 1}.plan.${i + 1}.best`} as="p" class="price__best" />
<a class="btn btn--light" href="/contact">{t('service.pricing.buy-cta')}</a>
</div>
))}
</div>
)}
</details>
))}
</div>
</section>
)}
</Base>
<style>
.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 li::marker { color: var(--c-accent); }
.prog__img { width: 100%; height: auto; margin-bottom: 30px; }
.prog__faq details { border-top: 1px solid #e4ded6; padding-block: 14px; }
.prog__faq summary { cursor: pointer; font-family: var(--font-heading); font-size: .85rem; letter-spacing: .08em; color: var(--c-heading); }
.prog__side h3 { font-size: .85rem; text-transform: uppercase; letter-spacing: .16em; margin-top: 30px; }
.prog__cat { font-family: var(--font-heading); font-size: .72rem; letter-spacing: .18em; text-transform: uppercase; color: var(--c-text); margin-bottom: 10px; }
.prog__other { display: flex; align-items: center; gap: 14px; text-decoration: none; color: var(--c-text); margin-bottom: 14px; font-family: var(--font-heading); font-size: .78rem; letter-spacing: .14em; text-transform: uppercase; }
.prog__other:hover { color: var(--c-accent-dark); }
.prog__icon { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: var(--c-accent); color: #fff; flex-shrink: 0; }
.price__group { border-top: 1px solid #ddd5ca; padding-block: 18px; }
.price__group summary { cursor: pointer; font-family: var(--font-heading); letter-spacing: .12em; text-transform: uppercase; font-size: .85rem; color: var(--c-heading); }
.price__row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; margin-top: 30px; }
/* Card colorate come nel Figma: tan / taupe / dark, testo bianco, prezzo grande */
.price__card { padding: 44px 28px; text-align: center; color: #fff; }
.price__card--tan { background: var(--c-accent); }
.price__card--taupe { background: #6b5d5b; }
.price__card--dark { background: var(--c-dark); }
.price__card .price__amount { font-family: var(--font-heading); font-size: clamp(2.6rem, 4.7vw, 4rem); font-weight: 500; color: #fff; margin: 0; line-height: 1.1; }
.price__card .price__per { font-family: var(--font-heading); text-transform: uppercase; letter-spacing: .18em; font-size: .68rem; opacity: .85; margin-bottom: 22px; }
.price__card .price__entries { font-family: var(--font-heading); text-transform: uppercase; letter-spacing: .1em; font-size: .82rem; border-top: 1px solid rgba(255,255,255,.25); padding-top: 18px; }
.price__card p { margin-bottom: 6px; font-size: .82rem; }
.price__card .price__best { font-weight: 700; text-transform: uppercase; letter-spacing: .08em; font-size: .8rem; }
.price__card .btn { margin-top: 18px; }
@media (max-width: 991px) { .prog { grid-template-columns: 1fr; } .price__row { grid-template-columns: 1fr; } }
</style>