feat: rinomina sezione Programmi in Servizi (route, tag, migrazione DB)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 20:23:48 +02:00
parent c975dcac12
commit 8ceea9b16f
20 changed files with 226 additions and 118 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ export const prerender = false;
<div>
<T tag="about.direzione.title" as="h2" />
<T tag="about.direzione.body" as="p" />
<a class="btn" href="/programs">{t('about.direzione.cta')}</a>
<a class="btn" href="/services">{t('about.direzione.cta')}</a>
</div>
<div>
<T tag="about.approccio.title" as="h2" />
+2 -2
View File
@@ -9,13 +9,13 @@ export const prerender = false;
const GROUP_BY_PREFIX: Record<string, string> = {
home: 'Home',
about: 'About', team: 'About', method: 'About',
program: 'Programmi', programs: 'Programmi', pricing: 'Programmi',
service: 'Servizi', services: 'Servizi', pricing: 'Servizi',
training: 'Training',
contact: 'Contatti', form: 'Contatti',
global: 'Globali', header: 'Globali', footer: 'Globali',
agenda: 'Globali', partner: 'Globali', notfound: 'Globali',
};
const GROUP_ORDER = ['Home', 'About', 'Programmi', 'Training', 'Contatti', 'Globali', 'Altro'];
const GROUP_ORDER = ['Home', 'About', 'Servizi', 'Training', 'Contatti', 'Globali', 'Altro'];
interface Row { tag: string; type: string; value: string; styles: string; updated_at: string; updated_by: string | null }
const rows = getDb().prepare(
+2 -2
View File
@@ -5,7 +5,7 @@ import InfoColumns from '../components/home/InfoColumns.astro';
import FeatureBlocks from '../components/home/FeatureBlocks.astro';
import QuoteBanner from '../components/home/QuoteBanner.astro';
import TrainingCards from '../components/home/TrainingCards.astro';
import ProgramsGrid from '../components/home/ProgramsGrid.astro';
import ServicesGrid from '../components/home/ServicesGrid.astro';
import Agenda from '../components/Agenda.astro';
import TeamSection from '../components/home/TeamSection.astro';
import MethodSteps from '../components/home/MethodSteps.astro';
@@ -26,7 +26,7 @@ export const prerender = false;
</div>
</section>
<TrainingCards />
<ProgramsGrid />
<ServicesGrid />
<Agenda />
<TeamSection />
<MethodSteps />
@@ -2,66 +2,66 @@
import Base from '../../layouts/Base.astro';
import PageHero from '../../components/PageHero.astro';
import type { ImageMetadata } from 'astro';
import { programs, getProgram } from '../../data/programs';
import { services, getService } from '../../data/services';
import { performanceClassPricing } from '../../data/pricing';
import { programIcons } from '../../data/program-icons';
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 program = getProgram(Astro.params.slug!);
if (!program) return new Response(null, { status: 404 });
const slug = program.slug;
const others = programs.filter((p) => p.slug !== program.slug);
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);
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(program.slug);
// Categorie come da Figma (Performance class.pdf), non quelle del blog → tag program.side.category.1-4
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(`program.${slug}.title`)} description={t(`program.${slug}.excerpt`)}>
<PageHero title={t(`program.${slug}.title`).toUpperCase()} bgImage={heroImg} />
<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(`program.${slug}.title`).toUpperCase()}</h2>
<T tag={`program.${slug}.long-description`} as="p" />
<h2>{t(`service.${slug}.title`).toUpperCase()}</h2>
<T tag={`service.${slug}.long-description`} as="p" />
<ul class="prog__features">
{program.features.map((_, i) => <T tag={`program.${slug}.feature.${i + 1}`} as="li" />)}
{service.features.map((_, i) => <T tag={`service.${slug}.feature.${i + 1}`} as="li" />)}
</ul>
{heroImg && (
<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" />
<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">
{program.faq.map((_, i) => (
{service.faq.map((_, i) => (
<details open={i === 0}>
<summary><T tag={`program.${slug}.faq.${i + 1}.q`} /></summary>
<T tag={`program.${slug}.faq.${i + 1}.a`} as="p" />
<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="program.side.categories-label" as="h3" />
{categoryIndexes.map((n) => <T tag={`program.side.category.${n}`} as="p" class="prog__cat" />)}
<T tag="program.side.others-label" as="h3" />
<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={`/programs/${o.slug}`}>
<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={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={serviceIcons[o.slug]} /></svg>
</span>
<T tag={`program.${o.slug}.title`} />
<T tag={`service.${o.slug}.title`} />
</a>
))}
</aside>
</div>
</section>
{program.hasPricing && (
{service.hasPricing && (
<section class="section section--alt">
<div class="container">
<div class="section-heading">
@@ -72,18 +72,18 @@ const tones = ['tan', 'taupe', 'dark'];
<details class="price__group" open={g.open}>
<summary>{g.open ? '+' : ''} <T tag={`pricing.group.${gi + 1}.label`} /></summary>
{g.plans.length === 0 ? (
<T tag="program.pricing.empty-note" as="p" class="price__empty" />
<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="program.pricing.per" as="p" class="price__per" />
<p class="price__entries">{p.entries} {p.entries === 1 ? t('program.pricing.entries-singular') : t('program.pricing.entries-plural')}</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('program.pricing.buy-cta')}</a>
<a class="btn btn--light" href="/contact">{t('service.pricing.buy-cta')}</a>
</div>
))}
</div>
@@ -2,7 +2,7 @@
import Base from '../../layouts/Base.astro';
import PageHero from '../../components/PageHero.astro';
import type { ImageMetadata } from 'astro';
import { programs } from '../../data/programs';
import { services } from '../../data/services';
import T from '../../components/content/T.astro';
import TImg from '../../components/content/TImg.astro';
import { t } from '../../lib/content';
@@ -10,27 +10,27 @@ const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/pr
const imgOf = (slug: string) => Object.entries(images).find(([p]) => p.includes(`program-${slug}.`))?.[1]?.default;
export const prerender = false;
---
<Base title={t('programs.meta.title')} description={t('programs.meta.description')}>
<PageHero title={t('programs.hero.title')} />
<Base title={t('services.meta.title')} description={t('services.meta.description')}>
<PageHero title={t('services.hero.title')} />
<section class="section">
<div class="container">
<div class="section-heading">
<T tag="programs.intro.title" as="h2" />
<T tag="programs.intro.body" as="p" />
<T tag="services.intro.title" as="h2" />
<T tag="services.intro.body" as="p" />
</div>
<div class="plist">
{programs.map((p, i) => (
{services.map((p, i) => (
<article class="plist__card" data-reveal style={`--reveal-delay:${((i % 3) * 0.15).toFixed(2)}s`}>
{imgOf(p.slug) && (
<a class="plist__photo" href={`/programs/${p.slug}`}>
<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 class="plist__photo" href={`/services/${p.slug}`}>
<TImg tag={`service.${p.slug}.image`} src={imgOf(p.slug)!} alt={t(`service.${p.slug}.title`)} widths={[400, 700]} sizes="(max-width:767px) 100vw, 33vw" />
</a>
)}
<div class="plist__body">
<T tag={`program.${p.slug}.title`} as="h3" />
<T tag={`program.${p.slug}.subtitle`} as="p" class="plist__sub" />
<T tag={`program.${p.slug}.excerpt`} as="p" />
<a class="plist__more" href={`/programs/${p.slug}`}>{t('programs.card.more-label')}</a>
<T tag={`service.${p.slug}.title`} as="h3" />
<T tag={`service.${p.slug}.subtitle`} as="p" class="plist__sub" />
<T tag={`service.${p.slug}.excerpt`} as="p" />
<a class="plist__more" href={`/services/${p.slug}`}>{t('services.card.more-label')}</a>
</div>
</article>
))}
+3 -3
View File
@@ -1,16 +1,16 @@
import type { APIRoute } from 'astro';
import { getDb } from '../lib/db';
import { programs } from '../data/programs';
import { services } from '../data/services';
export const prerender = false;
const STATIC = ['/', '/about', '/contact', '/training', '/programs', '/blog'];
const STATIC = ['/', '/about', '/contact', '/training', '/services', '/blog'];
export const GET: APIRoute = ({ site }) => {
const base = (site ?? new URL('https://insanitylab.tielogic.xyz')).toString().replace(/\/$/, '');
const posts = getDb().prepare('SELECT slug FROM posts WHERE draft = 0').all() as { slug: string }[];
const urls = [
...STATIC,
...programs.map((p) => `/programs/${p.slug}`),
...services.map((p) => `/services/${p.slug}`),
...posts.map((p) => `/blog/${p.slug}`),
];
const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls.map((u) => ` <url><loc>${base}${u}</loc></url>`).join('\n')}\n</urlset>`;