Allinea PageHero, lista programmi e dettaglio programma al design Figma
- PageHero: banner scuro min-height 380px, titolo bianco uppercase ~44px, variante con foto di sfondo e overlay scuro - /programs: card con foto (aspect 4/3, hover zoom), ombra, reveal stagger - Dettaglio programma: hero con foto, immagine grande nel corpo, categorie corrette da Figma, sidebar con icone programmi, pricing con card colorate tan/taupe/dark e prezzo grande - Nuove foto card program-*.jpg e modulo condiviso program-icons.ts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 235 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 350 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 254 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@@ -14,10 +14,12 @@ const bg = bgImage ? await getImage({ src: bgImage, width: 1920 }) : null;
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.phero { background: var(--c-bg-alt); padding-block: 70px; }
|
/* Banner scuro come nel Figma: ~460px, titolo bianco maiuscolo ~44px */
|
||||||
.phero h1 { text-transform: lowercase; font-weight: 500; margin: 0; }
|
.phero { background: var(--c-dark); min-height: 380px; display: flex; align-items: center; }
|
||||||
.phero--photo { position: relative; background-size: cover; background-position: center; padding-block: 110px; }
|
.phero .container { width: 100%; }
|
||||||
|
.phero h1 { text-transform: uppercase; font-weight: 500; color: #fff; font-size: clamp(2.2rem, 4vw, 2.75rem); letter-spacing: .04em; margin: 0; }
|
||||||
|
.phero--photo { position: relative; background-size: cover; background-position: center; }
|
||||||
.phero--photo::before { content: ''; position: absolute; inset: 0; background: rgba(46,32,24,.6); }
|
.phero--photo::before { content: ''; position: absolute; inset: 0; background: rgba(46,32,24,.6); }
|
||||||
.phero--photo .container { position: relative; }
|
.phero--photo .container { position: relative; }
|
||||||
.phero--photo h1 { color: #fff; }
|
@media (max-width: 767px) { .phero { min-height: 240px; } }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { programs } from '../../data/programs';
|
import { programs } from '../../data/programs';
|
||||||
const icons: Record<string, string> = {
|
import { programIcons as icons } from '../../data/program-icons';
|
||||||
'personal-training': 'M4 12h3m10 0h3M9 8v8m6-8v8M7 10v4m10-4v4',
|
|
||||||
'performance-class': 'M13 3l-2 7h4l-6 11 2-8H7l4-10z',
|
|
||||||
'coaching': 'M12 3a4 4 0 110 8 4 4 0 010-8zm-7 18a7 7 0 0114 0',
|
|
||||||
'pilates-flow': 'M12 4a2 2 0 110 4 2 2 0 010-4zM6 20c2-6 10-6 12 0M12 8v6',
|
|
||||||
'rehab': 'M12 21C7 17 3 13 3 9a5 5 0 019-3 5 5 0 019 3c0 4-4 8-9 12z',
|
|
||||||
'nutrition': 'M12 3c4 0 7 3 7 8s-3 10-7 10-7-5-7-10 3-8 7-8zm0-1v4',
|
|
||||||
};
|
|
||||||
---
|
---
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// Path SVG (stroke, viewBox 0 0 24 24) delle icone programmi, condivisi tra griglia home e sidebar
|
||||||
|
export const programIcons: Record<string, string> = {
|
||||||
|
'personal-training': 'M4 12h3m10 0h3M9 8v8m6-8v8M7 10v4m10-4v4',
|
||||||
|
'performance-class': 'M13 3l-2 7h4l-6 11 2-8H7l4-10z',
|
||||||
|
'coaching': 'M12 3a4 4 0 110 8 4 4 0 010-8zm-7 18a7 7 0 0114 0',
|
||||||
|
'pilates-flow': 'M12 4a2 2 0 110 4 2 2 0 010-4zM6 20c2-6 10-6 12 0M12 8v6',
|
||||||
|
'rehab': 'M12 21C7 17 3 13 3 9a5 5 0 019-3 5 5 0 019 3c0 4-4 8-9 12z',
|
||||||
|
'nutrition': 'M12 3c4 0 7 3 7 8s-3 10-7 10-7-5-7-10 3-8 7-8zm0-1v4',
|
||||||
|
};
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
---
|
---
|
||||||
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 { programs } from '../../data/programs';
|
import { programs } from '../../data/programs';
|
||||||
import { performanceClassPricing } from '../../data/pricing';
|
import { performanceClassPricing } from '../../data/pricing';
|
||||||
import { CATEGORIES } from '../../lib/blog-const';
|
import { programIcons } from '../../data/program-icons';
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
|
|
||||||
export function getStaticPaths() {
|
export function getStaticPaths() {
|
||||||
@@ -11,9 +13,16 @@ export function getStaticPaths() {
|
|||||||
}
|
}
|
||||||
const { program } = Astro.props;
|
const { program } = Astro.props;
|
||||||
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 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
|
||||||
|
const categories = ['Fitness', 'Salute', 'Stile di vita', 'Nutrizione'];
|
||||||
|
// Ordine card pricing come da Figma: tan, taupe, dark
|
||||||
|
const tones = ['tan', 'taupe', 'dark'];
|
||||||
---
|
---
|
||||||
<Base title={program.title} description={program.excerpt}>
|
<Base title={program.title} description={program.excerpt}>
|
||||||
<PageHero title={program.title.toUpperCase()} />
|
<PageHero title={program.title.toUpperCase()} bgImage={heroImg} />
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container prog">
|
<div class="container prog">
|
||||||
@@ -23,6 +32,9 @@ const others = programs.filter((p) => p.slug !== program.slug);
|
|||||||
<ul class="prog__features">
|
<ul class="prog__features">
|
||||||
{program.features.map((f) => <li>{f}</li>)}
|
{program.features.map((f) => <li>{f}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
|
{heroImg && (
|
||||||
|
<Image src={heroImg} alt={program.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((item, i) => (
|
||||||
<details open={i === 0}>
|
<details open={i === 0}>
|
||||||
@@ -34,9 +46,16 @@ const others = programs.filter((p) => p.slug !== program.slug);
|
|||||||
</div>
|
</div>
|
||||||
<aside class="prog__side">
|
<aside class="prog__side">
|
||||||
<h3>Categorie</h3>
|
<h3>Categorie</h3>
|
||||||
{CATEGORIES.map((c) => <p><a href={`/blog?categoria=${encodeURIComponent(c)}`}>{c}</a></p>)}
|
{categories.map((c) => <p class="prog__cat">{c}</p>)}
|
||||||
<h3>Altri programmi</h3>
|
<h3>Altri programmi</h3>
|
||||||
{others.map((o) => <p><a href={`/programs/${o.slug}`}>{o.title}</a></p>)}
|
{others.map((o) => (
|
||||||
|
<a class="prog__other" href={`/programs/${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>
|
||||||
|
</span>
|
||||||
|
<span>{o.title}</span>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -55,15 +74,15 @@ const others = programs.filter((p) => p.slug !== program.slug);
|
|||||||
<p class="price__empty">Contattaci per i dettagli di questo programma. <a href="/contact">Scrivici →</a></p>
|
<p class="price__empty">Contattaci per i dettagli di questo programma. <a href="/contact">Scrivici →</a></p>
|
||||||
) : (
|
) : (
|
||||||
<div class="price__row">
|
<div class="price__row">
|
||||||
{g.plans.map((p) => (
|
{g.plans.map((p, i) => (
|
||||||
<div class="price__card">
|
<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>
|
<p class="price__per">al mese, semestrale</p>
|
||||||
<p class="price__entries">{p.entries} {p.entries === 1 ? 'ingresso settimanale' : 'ingressi settimanali'}</p>
|
<p class="price__entries">{p.entries} {p.entries === 1 ? 'ingresso settimanale' : 'ingressi settimanali'}</p>
|
||||||
<p>{p.note}</p>
|
<p>{p.note}</p>
|
||||||
<p>{p.installments}</p>
|
<p>{p.installments}</p>
|
||||||
<p class="price__best">{p.best}</p>
|
<p class="price__best">{p.best}</p>
|
||||||
<a class="btn" href="/contact">Acquista</a>
|
<a class="btn btn--light" href="/contact">Acquista</a>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -79,20 +98,27 @@ const others = programs.filter((p) => p.slug !== program.slug);
|
|||||||
.prog { display: grid; grid-template-columns: 2.2fr 1fr; gap: 60px; }
|
.prog { display: grid; grid-template-columns: 2.2fr 1fr; gap: 60px; }
|
||||||
.prog__features { padding-left: 20px; display: grid; gap: 8px; margin-bottom: 30px; }
|
.prog__features { padding-left: 20px; display: grid; gap: 8px; margin-bottom: 30px; }
|
||||||
.prog__features li::marker { color: var(--c-accent); }
|
.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 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__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__side h3 { font-size: .85rem; text-transform: uppercase; letter-spacing: .16em; margin-top: 30px; }
|
||||||
.prog__side a { text-decoration: none; color: var(--c-text); }
|
.prog__cat { font-family: var(--font-heading); font-size: .72rem; letter-spacing: .18em; text-transform: uppercase; color: var(--c-text); margin-bottom: 10px; }
|
||||||
.prog__side a:hover { color: var(--c-accent-dark); }
|
.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 { 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__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; }
|
.price__row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; margin-top: 30px; }
|
||||||
.price__card { background: #fff; padding: 36px 28px; text-align: center; }
|
/* Card colorate come nel Figma: tan / taupe / dark, testo bianco, prezzo grande */
|
||||||
.price__amount { font-family: var(--font-heading); font-size: 2.6rem; color: var(--c-heading); margin: 0; }
|
.price__card { padding: 44px 28px; text-align: center; color: #fff; }
|
||||||
.price__per { color: var(--c-text-light); font-size: .8rem; }
|
.price__card--tan { background: var(--c-accent); }
|
||||||
.price__entries { font-family: var(--font-heading); text-transform: uppercase; letter-spacing: .1em; font-size: .82rem; color: var(--c-heading); }
|
.price__card--taupe { background: #6b5d5b; }
|
||||||
.price__card p { margin-bottom: 6px; font-size: .85rem; }
|
.price__card--dark { background: var(--c-dark); }
|
||||||
.price__best { font-weight: 600; color: var(--c-accent-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 .btn { margin-top: 16px; }
|
.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; } }
|
@media (max-width: 991px) { .prog { grid-template-columns: 1fr; } .price__row { grid-template-columns: 1fr; } }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
---
|
---
|
||||||
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 { programs } from '../../data/programs';
|
import { programs } from '../../data/programs';
|
||||||
|
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;
|
||||||
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="Programmi" description="I programmi InsanityLab: Personal Training, Performance Class, Coaching, Pilates Flow, Rehab e Nutrizione.">
|
||||||
<PageHero title="programmi" />
|
<PageHero title="Programmi" />
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
@@ -13,12 +17,19 @@ export const prerender = true;
|
|||||||
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div class="plist">
|
<div class="plist">
|
||||||
{programs.map((p) => (
|
{programs.map((p, i) => (
|
||||||
<article class="plist__card">
|
<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}`}>
|
||||||
|
<Image src={imgOf(p.slug)!} alt={p.title} widths={[400, 700]} sizes="(max-width:767px) 100vw, 33vw" />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
<div class="plist__body">
|
||||||
<h3>{p.title}</h3>
|
<h3>{p.title}</h3>
|
||||||
<p class="plist__sub">{p.subtitle}</p>
|
<p class="plist__sub">{p.subtitle}</p>
|
||||||
<p>{p.excerpt}</p>
|
<p>{p.excerpt}</p>
|
||||||
<a class="plist__more" href={`/programs/${p.slug}`}>Per saperne di più →</a>
|
<a class="plist__more" href={`/programs/${p.slug}`}>Per saperne di più →</a>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -27,11 +38,16 @@ export const prerender = true;
|
|||||||
</Base>
|
</Base>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.plist { display: grid; grid-template-columns: repeat(3, 1fr); gap: 50px 40px; }
|
.plist { display: grid; grid-template-columns: repeat(3, 1fr); gap: 40px 30px; }
|
||||||
.plist__card h3 { letter-spacing: .08em; }
|
.plist__card { background: #fff; box-shadow: 0 6px 24px rgba(0,0,0,.08); display: flex; flex-direction: column; }
|
||||||
.plist__sub { font-size: .8rem; text-transform: uppercase; letter-spacing: .12em; color: var(--c-accent-dark); }
|
.plist__photo { display: block; overflow: hidden; }
|
||||||
.plist__card p { font-size: .92rem; }
|
.plist__photo img { aspect-ratio: 4/3; object-fit: cover; width: 100%; transition: transform .5s ease; }
|
||||||
.plist__more { font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); }
|
.plist__card:hover .plist__photo img { transform: scale(1.05); }
|
||||||
|
.plist__body { padding: 26px 24px 30px; display: flex; flex-direction: column; align-items: flex-start; flex: 1; }
|
||||||
|
.plist__card h3 { font-size: 1rem; text-transform: uppercase; letter-spacing: .1em; margin-bottom: .3em; }
|
||||||
|
.plist__sub { font-size: .78rem; text-transform: uppercase; letter-spacing: .12em; color: var(--c-accent-dark); }
|
||||||
|
.plist__body > p:not(.plist__sub) { font-size: .9rem; }
|
||||||
|
.plist__more { font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); margin-top: auto; }
|
||||||
.plist__more:hover { color: var(--c-accent-dark); }
|
.plist__more:hover { color: var(--c-accent-dark); }
|
||||||
@media (max-width: 991px) { .plist { grid-template-columns: 1fr 1fr; } }
|
@media (max-width: 991px) { .plist { grid-template-columns: 1fr 1fr; } }
|
||||||
@media (max-width: 600px) { .plist { grid-template-columns: 1fr; } }
|
@media (max-width: 600px) { .plist { grid-template-columns: 1fr; } }
|
||||||
|
|||||||
Reference in New Issue
Block a user