feat: tag contenuti programmi e pricing
This commit is contained in:
@@ -5,6 +5,8 @@ import { team } from './team';
|
|||||||
import { methodIntro, methodExtraNote, methodSteps } from './method';
|
import { methodIntro, methodExtraNote, methodSteps } from './method';
|
||||||
import { partners } from './partners';
|
import { partners } from './partners';
|
||||||
import { disciplines, days, slots } from './agenda';
|
import { disciplines, days, slots } from './agenda';
|
||||||
|
import { programs } from './programs';
|
||||||
|
import { performanceClassPricing } from './pricing';
|
||||||
|
|
||||||
export type ContentType = 'text' | 'html' | 'image';
|
export type ContentType = 'text' | 'html' | 'image';
|
||||||
export interface SeedEntry { tag: string; type: ContentType; value: string; styleOptions?: StyleGroup[] }
|
export interface SeedEntry { tag: string; type: ContentType; value: string; styleOptions?: StyleGroup[] }
|
||||||
@@ -221,6 +223,53 @@ export const contentSeed: SeedEntry[] = [
|
|||||||
T('about.timeline.stage2.text', 'Contenuto in arrivo: il cliente fornirà il testo di questa tappa.'),
|
T('about.timeline.stage2.text', 'Contenuto in arrivo: il cliente fornirà il testo di questa tappa.'),
|
||||||
T('about.timeline.stage3.label', 'INSANITYLAB OGGI', []),
|
T('about.timeline.stage3.label', 'INSANITYLAB OGGI', []),
|
||||||
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.'),
|
||||||
|
// --- Programs: lista (/programs) ---
|
||||||
|
T('programs.meta.title', 'Programmi', []),
|
||||||
|
T('programs.meta.description', 'I programmi InsanityLab: Personal Training, Performance Class, Coaching, Pilates Flow, Rehab e Nutrizione.', []),
|
||||||
|
T('programs.hero.title', 'Programmi', []),
|
||||||
|
T('programs.intro.title', 'Programmi'),
|
||||||
|
T('programs.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."),
|
||||||
|
T('programs.card.more-label', 'Per saperne di più →', []),
|
||||||
|
|
||||||
|
// --- Programs: dettaglio (data-driven da programs.ts) ---
|
||||||
|
...programs.flatMap((p) => [
|
||||||
|
T(`program.${p.slug}.title`, p.title),
|
||||||
|
T(`program.${p.slug}.subtitle`, p.subtitle),
|
||||||
|
T(`program.${p.slug}.excerpt`, p.excerpt),
|
||||||
|
T(`program.${p.slug}.long-description`, p.longDescription),
|
||||||
|
...p.features.map((f, i) => T(`program.${p.slug}.feature.${i + 1}`, f)),
|
||||||
|
...p.faq.flatMap((item, i) => [
|
||||||
|
T(`program.${p.slug}.faq.${i + 1}.q`, item.q),
|
||||||
|
T(`program.${p.slug}.faq.${i + 1}.a`, item.a),
|
||||||
|
]),
|
||||||
|
I(`program.${p.slug}.image`),
|
||||||
|
]),
|
||||||
|
|
||||||
|
// --- Programs: dettaglio, template ---
|
||||||
|
T('program.side.categories-label', 'Categorie'),
|
||||||
|
T('program.side.category.1', 'Fitness', []),
|
||||||
|
T('program.side.category.2', 'Salute', []),
|
||||||
|
T('program.side.category.3', 'Stile di vita', []),
|
||||||
|
T('program.side.category.4', 'Nutrizione', []),
|
||||||
|
T('program.side.others-label', 'Altri programmi'),
|
||||||
|
H('program.pricing.empty-note', 'Contattaci per i dettagli di questo programma. <a href="/contact">Scrivici →</a>'),
|
||||||
|
T('program.pricing.per', 'al mese, semestrale', []),
|
||||||
|
T('program.pricing.entries-singular', 'ingresso settimanale', []),
|
||||||
|
T('program.pricing.entries-plural', 'ingressi settimanali', []),
|
||||||
|
T('program.pricing.buy-cta', 'Acquista', []),
|
||||||
|
|
||||||
|
// --- Pricing performance class (data-driven da pricing.ts; monthly/entries restano nel data file) ---
|
||||||
|
T('pricing.heading', performanceClassPricing.heading),
|
||||||
|
T('pricing.intro', performanceClassPricing.intro),
|
||||||
|
...performanceClassPricing.groups.flatMap((g, gi) => [
|
||||||
|
T(`pricing.group.${gi + 1}.label`, g.label),
|
||||||
|
...g.plans.flatMap((plan, pi) => [
|
||||||
|
T(`pricing.group.${gi + 1}.plan.${pi + 1}.note`, plan.note, []),
|
||||||
|
T(`pricing.group.${gi + 1}.plan.${pi + 1}.installments`, plan.installments, []),
|
||||||
|
T(`pricing.group.${gi + 1}.plan.${pi + 1}.best`, plan.best, []),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
|
||||||
// I task di estrazione aggiungono qui le entry di header/footer/pagine/data file.
|
// I task di estrazione aggiungono qui le entry di header/footer/pagine/data file.
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +1,62 @@
|
|||||||
---
|
---
|
||||||
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 { Image } from 'astro:assets';
|
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
import { programs } from '../../data/programs';
|
import { programs } from '../../data/programs';
|
||||||
import { performanceClassPricing } from '../../data/pricing';
|
import { performanceClassPricing } from '../../data/pricing';
|
||||||
import { programIcons } from '../../data/program-icons';
|
import { programIcons } from '../../data/program-icons';
|
||||||
|
import T from '../../components/content/T.astro';
|
||||||
|
import TImg from '../../components/content/TImg.astro';
|
||||||
|
import { t } from '../../lib/content';
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
|
|
||||||
export function getStaticPaths() {
|
export function getStaticPaths() {
|
||||||
return programs.map((p) => ({ params: { slug: p.slug }, props: { program: p } }));
|
return programs.map((p) => ({ params: { slug: p.slug }, props: { program: p } }));
|
||||||
}
|
}
|
||||||
const { program } = Astro.props;
|
const { program } = Astro.props;
|
||||||
|
const slug = program.slug;
|
||||||
const others = programs.filter((p) => p.slug !== program.slug);
|
const others = programs.filter((p) => p.slug !== program.slug);
|
||||||
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(program.slug);
|
const heroImg = imgOf(program.slug);
|
||||||
// Categorie come da Figma (Performance class.pdf), non quelle del blog
|
// Categorie come da Figma (Performance class.pdf), non quelle del blog → tag program.side.category.1-4
|
||||||
const categories = ['Fitness', 'Salute', 'Stile di vita', 'Nutrizione'];
|
const categoryIndexes = [1, 2, 3, 4];
|
||||||
// Ordine card pricing come da Figma: tan, taupe, dark
|
// Ordine card pricing come da Figma: tan, taupe, dark
|
||||||
const tones = ['tan', 'taupe', 'dark'];
|
const tones = ['tan', 'taupe', 'dark'];
|
||||||
---
|
---
|
||||||
<Base title={program.title} description={program.excerpt}>
|
<Base title={t(`program.${slug}.title`)} description={t(`program.${slug}.excerpt`)}>
|
||||||
<PageHero title={program.title.toUpperCase()} bgImage={heroImg} />
|
<PageHero title={t(`program.${slug}.title`).toUpperCase()} bgImage={heroImg} />
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container prog">
|
<div class="container prog">
|
||||||
<div>
|
<div>
|
||||||
<h2>{program.title.toUpperCase()}</h2>
|
<h2>{t(`program.${slug}.title`).toUpperCase()}</h2>
|
||||||
<p>{program.longDescription}</p>
|
<T tag={`program.${slug}.long-description`} as="p" />
|
||||||
<ul class="prog__features">
|
<ul class="prog__features">
|
||||||
{program.features.map((f) => <li>{f}</li>)}
|
{program.features.map((_, i) => <T tag={`program.${slug}.feature.${i + 1}`} as="li" />)}
|
||||||
</ul>
|
</ul>
|
||||||
{heroImg && (
|
{heroImg && (
|
||||||
<Image src={heroImg} alt={program.title} class="prog__img" widths={[600, 1000]} sizes="(max-width:991px) 100vw, 60vw" data-reveal="fade" />
|
<TImg tag={`program.${slug}.image`} src={heroImg} alt={t(`program.${slug}.title`)} class="prog__img" widths={[600, 1000]} sizes="(max-width:991px) 100vw, 60vw" data-reveal="fade" />
|
||||||
)}
|
)}
|
||||||
<div class="prog__faq">
|
<div class="prog__faq">
|
||||||
{program.faq.map((item, i) => (
|
{program.faq.map((_, i) => (
|
||||||
<details open={i === 0}>
|
<details open={i === 0}>
|
||||||
<summary>{item.q}</summary>
|
<summary><T tag={`program.${slug}.faq.${i + 1}.q`} /></summary>
|
||||||
<p>{item.a}</p>
|
<T tag={`program.${slug}.faq.${i + 1}.a`} as="p" />
|
||||||
</details>
|
</details>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<aside class="prog__side">
|
<aside class="prog__side">
|
||||||
<h3>Categorie</h3>
|
<T tag="program.side.categories-label" as="h3" />
|
||||||
{categories.map((c) => <p class="prog__cat">{c}</p>)}
|
{categoryIndexes.map((n) => <T tag={`program.side.category.${n}`} as="p" class="prog__cat" />)}
|
||||||
<h3>Altri programmi</h3>
|
<T tag="program.side.others-label" as="h3" />
|
||||||
{others.map((o) => (
|
{others.map((o) => (
|
||||||
<a class="prog__other" href={`/programs/${o.slug}`}>
|
<a class="prog__other" href={`/programs/${o.slug}`}>
|
||||||
<span class="prog__icon">
|
<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={programIcons[o.slug]} /></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" width="26" height="26" aria-hidden="true"><path d={programIcons[o.slug]} /></svg>
|
||||||
</span>
|
</span>
|
||||||
<span>{o.title}</span>
|
<T tag={`program.${o.slug}.title`} />
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</aside>
|
</aside>
|
||||||
@@ -64,25 +67,25 @@ const tones = ['tan', 'taupe', 'dark'];
|
|||||||
<section class="section section--alt">
|
<section class="section section--alt">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<h2>{performanceClassPricing.heading}</h2>
|
<T tag="pricing.heading" as="h2" />
|
||||||
<p>{performanceClassPricing.intro}</p>
|
<T tag="pricing.intro" as="p" />
|
||||||
</div>
|
</div>
|
||||||
{performanceClassPricing.groups.map((g) => (
|
{performanceClassPricing.groups.map((g, gi) => (
|
||||||
<details class="price__group" open={g.open}>
|
<details class="price__group" open={g.open}>
|
||||||
<summary>{g.open ? '+' : '–'} {g.label}</summary>
|
<summary>{g.open ? '+' : '–'} <T tag={`pricing.group.${gi + 1}.label`} /></summary>
|
||||||
{g.plans.length === 0 ? (
|
{g.plans.length === 0 ? (
|
||||||
<p class="price__empty">Contattaci per i dettagli di questo programma. <a href="/contact">Scrivici →</a></p>
|
<T tag="program.pricing.empty-note" as="p" class="price__empty" />
|
||||||
) : (
|
) : (
|
||||||
<div class="price__row">
|
<div class="price__row">
|
||||||
{g.plans.map((p, i) => (
|
{g.plans.map((p, i) => (
|
||||||
<div class:list={['price__card', `price__card--${tones[i % 3]}`]}>
|
<div class:list={['price__card', `price__card--${tones[i % 3]}`]}>
|
||||||
<p class="price__amount">{p.monthly}€</p>
|
<p class="price__amount">{p.monthly}€</p>
|
||||||
<p class="price__per">al mese, semestrale</p>
|
<T tag="program.pricing.per" as="p" class="price__per" />
|
||||||
<p class="price__entries">{p.entries} {p.entries === 1 ? 'ingresso settimanale' : 'ingressi settimanali'}</p>
|
<p class="price__entries">{p.entries} {p.entries === 1 ? t('program.pricing.entries-singular') : t('program.pricing.entries-plural')}</p>
|
||||||
<p>{p.note}</p>
|
<T tag={`pricing.group.${gi + 1}.plan.${i + 1}.note`} as="p" />
|
||||||
<p>{p.installments}</p>
|
<T tag={`pricing.group.${gi + 1}.plan.${i + 1}.installments`} as="p" />
|
||||||
<p class="price__best">{p.best}</p>
|
<T tag={`pricing.group.${gi + 1}.plan.${i + 1}.best`} as="p" class="price__best" />
|
||||||
<a class="btn btn--light" href="/contact">Acquista</a>
|
<a class="btn btn--light" href="/contact">{t('program.pricing.buy-cta')}</a>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
---
|
---
|
||||||
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 { Image } from 'astro:assets';
|
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
import { programs } from '../../data/programs';
|
import { programs } from '../../data/programs';
|
||||||
|
import T from '../../components/content/T.astro';
|
||||||
|
import TImg from '../../components/content/TImg.astro';
|
||||||
|
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;
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
---
|
---
|
||||||
<Base title="Programmi" description="I programmi InsanityLab: Personal Training, Performance Class, Coaching, Pilates Flow, Rehab e Nutrizione.">
|
<Base title={t('programs.meta.title')} description={t('programs.meta.description')}>
|
||||||
<PageHero title="Programmi" />
|
<PageHero title={t('programs.hero.title')} />
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<h2>Programmi</h2>
|
<T tag="programs.intro.title" as="h2" />
|
||||||
<p>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.</p>
|
<T tag="programs.intro.body" as="p" />
|
||||||
</div>
|
</div>
|
||||||
<div class="plist">
|
<div class="plist">
|
||||||
{programs.map((p, i) => (
|
{programs.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={`/programs/${p.slug}`}>
|
<a class="plist__photo" href={`/programs/${p.slug}`}>
|
||||||
<Image src={imgOf(p.slug)!} alt={p.title} widths={[400, 700]} sizes="(max-width:767px) 100vw, 33vw" />
|
<TImg tag={`program.${p.slug}.image`} src={imgOf(p.slug)!} alt={t(`program.${p.slug}.title`)} widths={[400, 700]} sizes="(max-width:767px) 100vw, 33vw" />
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
<div class="plist__body">
|
<div class="plist__body">
|
||||||
<h3>{p.title}</h3>
|
<T tag={`program.${p.slug}.title`} as="h3" />
|
||||||
<p class="plist__sub">{p.subtitle}</p>
|
<T tag={`program.${p.slug}.subtitle`} as="p" class="plist__sub" />
|
||||||
<p>{p.excerpt}</p>
|
<T tag={`program.${p.slug}.excerpt`} as="p" />
|
||||||
<a class="plist__more" href={`/programs/${p.slug}`}>Per saperne di più →</a>
|
<a class="plist__more" href={`/programs/${p.slug}`}>{t('programs.card.more-label')}</a>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user