Contatti, /training e immagini: rifiniture post-merge

- Rimossa la sezione "Prenota la lezione di prova" da /training (markup,
  import, CSS, seed e blocchi DB relativi)
- Titoli dei riquadri Training non più forzati in minuscolo: rispettano il
  testo del DB (One to One, Small Groups, ecc.)
- Footer e pagina Contatti con i due ingressi dello stesso stabile
  (Via Leandro Alberti 76 e Via De Mattiolo 5) più l'email; nuovo tag
  global.contact.map-query e migrazione DB idempotente dell'indirizzo
- Mappa Google in /contact caricata automaticamente e a piena dimensione
  (rimosso il bottone e il relativo script)
- Landing Promo ed eventi: rimossa la barra contatti in fondo
- service-icons allineate agli slug attuali (one-to-one, small-group;
  rimosse le chiavi obsolete personal-training e pilates-flow)
- Immagini: foto Rehab corretta (prima era di osteopatia) su program-rehab
  e training-rehab; bande nere letterbox rimosse dalla foto di osteopatia;
  nuova immagine grafica per Profilazione con grafici HRV (ECG, tacogramma
  R-R, Poincaré plot)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 12:41:37 +02:00
parent 11fc2b3e54
commit c6de02be83
13 changed files with 19 additions and 48 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 192 KiB

+1 -1
View File
@@ -11,7 +11,7 @@ const year = new Date().getFullYear();
<img src={contentImageUrl('footer.logo.image', '/img/logo-light.png')} alt="InsanityLab" width="140" loading="lazy" /> <img src={contentImageUrl('footer.logo.image', '/img/logo-light.png')} alt="InsanityLab" width="140" loading="lazy" />
<T tag="footer.tagline" as="p" class="ftr__tag" /> <T tag="footer.tagline" as="p" class="ftr__tag" />
<T tag="footer.about" as="p" /> <T tag="footer.about" as="p" />
<p>{t('global.contact.phone')}<br />{t('global.contact.address')}, {t('global.contact.city-line')}</p> <p>{t('global.contact.address')}, {t('global.contact.city-line')}<br />{t('global.contact.phone')}<br /><a href={`mailto:${t('global.contact.email')}`}>{t('global.contact.email')}</a></p>
<p class="ftr__hours">{t('global.hours.1')}</p> <p class="ftr__hours">{t('global.hours.1')}</p>
<p class="ftr__hours">{t('global.hours.2')}</p> <p class="ftr__hours">{t('global.hours.2')}</p>
</div> </div>
+1 -4
View File
@@ -23,6 +23,7 @@ export const contentSeed: SeedEntry[] = [
T('global.contact.email', site.email, []), T('global.contact.email', site.email, []),
T('global.contact.address', site.address, []), T('global.contact.address', site.address, []),
T('global.contact.city-line', site.cityLine, []), T('global.contact.city-line', site.cityLine, []),
T('global.contact.map-query', site.mapQuery, []),
T('global.hours.1', site.hours[0], []), T('global.hours.1', site.hours[0], []),
T('global.hours.2', site.hours[1], []), T('global.hours.2', site.hours[1], []),
T('global.nav.1.label', site.navigation[0].label, []), T('global.nav.1.label', site.navigation[0].label, []),
@@ -278,10 +279,6 @@ export const contentSeed: SeedEntry[] = [
T('training.meta.description', 'One to One, Small Group, Rehab, Holistic Class, Reformer Class e Coaching: trova il training InsanityLab più adatto a te.', []), T('training.meta.description', 'One to One, Small Group, Rehab, Holistic Class, Reformer Class e Coaching: trova il training InsanityLab più adatto a te.', []),
T('training.hero.title', 'training', []), T('training.hero.title', 'training', []),
T('training.intro.subtitle', 'Porta i tuoi allenamenti al livello successivo: programmi completi e percorsi su misura. Trova il training più adatto alle tue esigenze.'), T('training.intro.subtitle', 'Porta i tuoi allenamenti al livello successivo: programmi completi e percorsi su misura. Trova il training più adatto alle tue esigenze.'),
T('training.trial.title', 'Prenota la lezione di prova'),
T('training.trial.body', "La lezione di prova è l'occasione perfetta per conoscere il metodo, vivere l'esperienza in prima persona e capire il percorso più adatto a te. Inizia oggi il tuo percorso: prenota la tua lezione di prova."),
T('training.trial.cta', 'Prenota', []),
I('training.trial.image'),
// --- Contact --- // --- Contact ---
T('contact.meta.title', 'Contatti', []), T('contact.meta.title', 'Contatti', []),
+2 -2
View File
@@ -1,9 +1,9 @@
// Path SVG (stroke, viewBox 0 0 24 24) delle icone servizi, condivisi tra griglia home e sidebar // Path SVG (stroke, viewBox 0 0 24 24) delle icone servizi, condivisi tra griglia home e sidebar
export const serviceIcons: Record<string, string> = { export const serviceIcons: Record<string, string> = {
'personal-training': 'M4 12h3m10 0h3M9 8v8m6-8v8M7 10v4m10-4v4', 'one-to-one': 'M4 12h3m10 0h3M9 8v8m6-8v8M7 10v4m10-4v4',
'small-group': 'M4 12h3m10 0h3M9 8v8m6-8v8M7 10v4m10-4v4',
'performance-class': 'M13 3l-2 7h4l-6 11 2-8H7l4-10z', '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', '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', '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', 'nutrition': 'M12 3c4 0 7 3 7 8s-3 10-7 10-7-5-7-10 3-8 7-8zm0-1v4',
// Servizi rinominati (modifiche 02): profilazione (grafico/analisi), osteopatia (mani) // Servizi rinominati (modifiche 02): profilazione (grafico/analisi), osteopatia (mani)
+3 -1
View File
@@ -2,8 +2,10 @@ export const site = {
name: "InsanityLab", name: "InsanityLab",
phone: "351 7535977", phone: "351 7535977",
email: "info@insanitylab.it", email: "info@insanitylab.it",
address: "Via Leandro Alberti, 76", // Due ingressi dello stesso stabile. address = testo mostrato; mapQuery = punto geolocalizzabile per Google Maps.
address: "Via Leandro Alberti 76 e Via De Mattiolo 5",
cityLine: "40139 Bologna", cityLine: "40139 Bologna",
mapQuery: "Via Leandro Alberti 76, 40139 Bologna",
hours: ["Lun Ven 6.00 21.00", "Sab 8.00 19.00 · Dom chiuso"], hours: ["Lun Ven 6.00 21.00", "Sab 8.00 19.00 · Dom chiuso"],
socials: [ socials: [
{ label: "Instagram", url: "https://www.instagram.com/insanitylab" }, { label: "Instagram", url: "https://www.instagram.com/insanitylab" },
+5
View File
@@ -150,6 +150,11 @@ export function createDb(path?: string): Database.Database {
const upd02Tx = db.transaction(() => { for (const [tag, oldV, newV] of SERVIZI_02) upd02.run(newV, tag, oldV); }); const upd02Tx = db.transaction(() => { for (const [tag, oldV, newV] of SERVIZI_02) upd02.run(newV, tag, oldV); });
upd02Tx(); upd02Tx();
// Modifiche 03: due ingressi dello stesso stabile → indirizzo esteso in footer e contatti.
// Il tag global.contact.map-query è nuovo (inserito dal seed); qui si aggiorna solo l'indirizzo
// già esistente, se non modificato dal pannello. Idempotente.
db.prepare("UPDATE content_blocks SET value = 'Via Leandro Alberti 76 e Via De Mattiolo 5' WHERE tag = 'global.contact.address' AND value = 'Via Leandro Alberti, 76'").run();
// Modifiche 02 (pagina Insanitylab/about): testi riscritti. Guardia via frammento LIKE // Modifiche 02 (pagina Insanitylab/about): testi riscritti. Guardia via frammento LIKE
// distintivo del vecchio testo → robusto agli apostrofi tipografici, idempotente e // distintivo del vecchio testo → robusto agli apostrofi tipografici, idempotente e
// rispettoso delle eventuali riscritture da pannello (se il frammento non c'è più, salta). // rispettoso delle eventuali riscritture da pannello (se il frammento non c'è più, salta).
+5 -14
View File
@@ -6,15 +6,14 @@ import contactHero from '../assets/img/contact-hero.jpg';
import T from '../components/content/T.astro'; import T from '../components/content/T.astro';
import { t, getImageOverride } from '../lib/content'; import { t, getImageOverride } from '../lib/content';
export const prerender = false; export const prerender = false;
const mapsQuery = encodeURIComponent(`${t('global.contact.address')}, ${t('global.contact.city-line')}`); const mapsQuery = encodeURIComponent(t('global.contact.map-query'));
--- ---
<Base title={t('contact.meta.title')} description={t('contact.meta.description')}> <Base title={t('contact.meta.title')} description={t('contact.meta.description')}>
<PageHero title={t('contact.hero.title')} bgImage={getImageOverride('contact.hero.image') ?? contactHero} /> <PageHero title={t('contact.hero.title')} bgImage={getImageOverride('contact.hero.image') ?? contactHero} />
<section class="section"> <section class="section">
<div class="container cgrid"> <div class="container cgrid">
<div class="cmap" id="map-box"> <div class="cmap">
<button class="btn btn--dark" id="map-load">{t('contact.map.button')}</button> <iframe src={`https://www.google.com/maps?q=${mapsQuery}&output=embed`} loading="lazy" title="Mappa InsanityLab" referrerpolicy="no-referrer-when-downgrade"></iframe>
<p class="cmap__note">{t('contact.map.note')}</p>
</div> </div>
<ContactForm variant="full" /> <ContactForm variant="full" />
</div> </div>
@@ -40,18 +39,10 @@ const mapsQuery = encodeURIComponent(`${t('global.contact.address')}, ${t('globa
<style> <style>
.cgrid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 50px; margin-bottom: 50px; } .cgrid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 50px; margin-bottom: 50px; }
.cmap { background: var(--c-bg-alt); min-height: 420px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; } .cmap { background: var(--c-bg-alt); min-height: 420px; }
.cmap iframe { width: 100%; height: 100%; border: 0; min-height: 420px; } .cmap iframe { display: block; width: 100%; height: 100%; min-height: 420px; border: 0; }
.cmap__note { font-size: .8rem; color: var(--c-text-light); }
.cinfo { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; margin-bottom: 30px; } .cinfo { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; margin-bottom: 30px; }
.cinfo h3 { font-size: .85rem; text-transform: uppercase; letter-spacing: .16em; } .cinfo h3 { font-size: .85rem; text-transform: uppercase; letter-spacing: .16em; }
.cinvite { max-width: 640px; } .cinvite { max-width: 640px; }
@media (max-width: 991px) { .cgrid, .cinfo { grid-template-columns: 1fr; } } @media (max-width: 991px) { .cgrid, .cinfo { grid-template-columns: 1fr; } }
</style> </style>
<script define:vars={{ mapsQuery }}>
document.getElementById('map-load')?.addEventListener('click', () => {
const box = document.getElementById('map-box');
box.innerHTML = `<iframe src="https://www.google.com/maps?q=${mapsQuery}&output=embed" loading="lazy" title="Mappa InsanityLab" referrerpolicy="no-referrer-when-downgrade"></iframe>`;
});
</script>
+1 -9
View File
@@ -139,10 +139,6 @@ export const prerender = false;
</div> </div>
</section> </section>
<div class="contact-bar">
<p>Via Leandro Alberti 76 e Via De Mattiolo 5, 40139 Bologna &nbsp;·&nbsp; 351 753 5977 &nbsp;·&nbsp; info@insanitylab.it</p>
</div>
</div> </div>
</div> </div>
</Base> </Base>
@@ -225,10 +221,6 @@ export const prerender = false;
.lp .validity-note { text-align: center; font-size: 12px; color: #8A8980; margin: 22px 0 0; } .lp .validity-note { text-align: center; font-size: 12px; color: #8A8980; margin: 22px 0 0; }
/* CONTACT BAR */
.lp .contact-bar { padding: 20px 64px 40px; text-align: center; }
.lp .contact-bar p { margin: 0; font-size: 11.5px; line-height: 1.6; color: #8A8980; }
/* FAQ */ /* FAQ */
.lp .faq-wrap { padding: 72px 64px; } .lp .faq-wrap { padding: 72px 64px; }
.lp .faq-wrap h1 { margin: 0 0 40px; font-size: 34px; line-height: 1.25; font-weight: 700; color: #2D2D2D; max-width: 480px; } .lp .faq-wrap h1 { margin: 0 0 40px; font-size: 34px; line-height: 1.25; font-weight: 700; color: #2D2D2D; max-width: 480px; }
@@ -248,7 +240,7 @@ export const prerender = false;
.lp .faq-a .hl { color: #8A6E3F; } .lp .faq-a .hl { color: #8A6E3F; }
@media (max-width: 700px) { @media (max-width: 700px) {
.lp .hero, .lp .launch, .lp .why5, .lp .cards-wrap, .lp .faq-wrap, .lp .contact-bar { 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 .hero h1 { font-size: 32px; } .lp .hero h1 { font-size: 32px; }
.lp .launch h1 { font-size: 28px; } .lp .launch h1 { font-size: 28px; }
+1 -17
View File
@@ -5,9 +5,7 @@ import TrainingCards from '../components/home/TrainingCards.astro';
import Agenda from '../components/Agenda.astro'; import Agenda from '../components/Agenda.astro';
import { Image } from 'astro:assets'; import { Image } from 'astro:assets';
import { trainings } from '../data/trainings'; import { trainings } from '../data/trainings';
import trialImg from '../assets/img/trial-class.jpg';
import T from '../components/content/T.astro'; import T from '../components/content/T.astro';
import TImg from '../components/content/TImg.astro';
import { t } from '../lib/content'; import { t } from '../lib/content';
const images = import.meta.glob<{ default: ImageMetadata }>('../assets/img/training-*.{jpg,png}', { eager: true }); const images = import.meta.glob<{ default: ImageMetadata }>('../assets/img/training-*.{jpg,png}', { eager: true });
const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(name))?.[1]?.default; const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(name))?.[1]?.default;
@@ -29,7 +27,7 @@ export const prerender = false;
<div class:list={['tblock', { 'tblock--reverse': i % 2 === 1 }]} id={tr.id}> <div class:list={['tblock', { 'tblock--reverse': i % 2 === 1 }]} id={tr.id}>
{imgOf(tr.image) && <Image src={imgOf(tr.image)!} alt={t(`training.${tr.id}.title`)} widths={[500, 900]} sizes="(max-width:767px) 100vw, 45vw" data-reveal={i % 2 === 1 ? 'right' : 'left'} />} {imgOf(tr.image) && <Image src={imgOf(tr.image)!} alt={t(`training.${tr.id}.title`)} widths={[500, 900]} sizes="(max-width:767px) 100vw, 45vw" data-reveal={i % 2 === 1 ? 'right' : 'left'} />}
<div data-reveal={i % 2 === 1 ? 'left' : 'right'}> <div data-reveal={i % 2 === 1 ? 'left' : 'right'}>
<h2 data-tag={`training.${tr.id}.title`}>{t(`training.${tr.id}.title`).toLowerCase()}</h2> <h2 data-tag={`training.${tr.id}.title`}>{t(`training.${tr.id}.title`)}</h2>
<T tag={`training.${tr.id}.description`} as="p" /> <T tag={`training.${tr.id}.description`} as="p" />
<a class="btn" href={tr.cta.href}><T tag={`training.${tr.id}.cta-label`} as="span" /></a> <a class="btn" href={tr.cta.href}><T tag={`training.${tr.id}.cta-label`} as="span" /></a>
</div> </div>
@@ -39,25 +37,11 @@ export const prerender = false;
</section> </section>
<Agenda /> <Agenda />
<section class="section section--alt">
<div class="container trial">
<TImg tag="training.trial.image" src={trialImg} alt="Lezione di prova InsanityLab" widths={[500, 900]} sizes="(max-width:767px) 100vw, 45vw" data-reveal="left" />
<div data-reveal="right">
<T tag="training.trial.title" as="h2" />
<T tag="training.trial.body" as="p" />
<a class="btn btn--dark" href="/contact">{t('training.trial.cta')}</a>
</div>
</div>
</section>
</Base> </Base>
<style> <style>
.tblock { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: center; padding-block: 40px; scroll-margin-top: calc(var(--header-h) + 20px); } .tblock { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: center; padding-block: 40px; scroll-margin-top: calc(var(--header-h) + 20px); }
.tblock--reverse img { order: 2; } .tblock--reverse img { order: 2; }
.tblock h2 { letter-spacing: .1em; } .tblock h2 { letter-spacing: .1em; }
.trial { display: grid; grid-template-columns: 1fr 1.2fr; gap: 60px; align-items: center; }
.trial img { width: 100%; height: auto; max-height: 460px; object-fit: cover; }
@media (max-width: 767px) { .trial { grid-template-columns: 1fr; gap: 24px; } }
@media (max-width: 767px) { .tblock { grid-template-columns: 1fr; gap: 24px; } .tblock--reverse img { order: 0; } } @media (max-width: 767px) { .tblock { grid-template-columns: 1fr; gap: 24px; } .tblock--reverse img { order: 0; } }
</style> </style>