Calendario delle class: popup a pieno schermo dalle pagine Performance e Holistic

Le due griglie fornite dal cliente (1-18 settembre, dal 21 in poi) sono
trascritte come dato e ridisegnate nello stile del sito: si leggono da telefono,
si stampano in PDF e per correggere una cella non serve rifare uno screenshot.

Quale griglia compare lo decide la data, come per le edizioni della promo: quella
in vigore, oppure - se nessuna lo e' ancora - la prossima, dichiarando da quando
entra. Serve perche' il link esiste da subito e ad agosto un "nessun calendario"
non direbbe niente a chi vuole gli orari di settembre.

Il popup e' un <dialog> nativo: fondale, Esc e trappola del focus senza
librerie. "Scarica in PDF" stampa la sola griglia, gia' impaginata in orizzontale
dal CSS di stampa. Le discipline hanno una barra colorata a sinistra oltre al
fondo, cosi' restano distinguibili anche quando il browser stampa senza sfondi.

Quattordici test sul dato: confini al secondo in ora italiana, fasce orarie
uniche e in ordine, nessuna lezione in un giorno di chiusura, e il buco vero fra
il 19 e il 20 settembre resta dichiarato invece che nascosto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 15:58:47 +02:00
parent a18a505254
commit 01f49ab6d2
5 changed files with 453 additions and 0 deletions
+21
View File
@@ -7,12 +7,18 @@ 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 CalendarioModal from '../../components/CalendarioModal.astro';
import { calendarioDaMostrare, SERVIZI_CON_CALENDARIO } from '../../data/calendari';
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;
// Il calendario della sala si apre dalle pagine delle class. La voce compare solo se c'è
// davvero una griglia da mostrare (in vigore o prossima): un link che apre un popup vuoto è
// peggio di nessun link.
const conCalendario = SERVIZI_CON_CALENDARIO.includes(slug) && calendarioDaMostrare(new Date()) !== null;
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;
@@ -59,6 +65,16 @@ const tones = ['tan', 'taupe', 'dark'];
<T tag={`service.${o.slug}.title`} />
</a>
))}
{conCalendario && (
<button type="button" class="prog__other prog__other--calendario" data-apri-calendario>
<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="M4 6h16v14H4zM4 10h16M8 3v4M16 3v4M8 14h3M8 17h3M14 14h2" />
</svg>
</span>
{t('calendario.link-label')}
</button>
)}
</aside>
</div>
</section>
@@ -95,6 +111,8 @@ const tones = ['tan', 'taupe', 'dark'];
</div>
</section>
)}
{conCalendario && <CalendarioModal />}
</Base>
<style>
@@ -109,6 +127,9 @@ const tones = ['tan', 'taupe', 'dark'];
.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); }
/* La voce del calendario apre un popup, quindi è un bottone e non un link: qui gli si
toglie l'aspetto da bottone perché nella colonna sia indistinguibile dalle altre voci. */
.prog__other--calendario { background: none; border: 0; padding: 0; cursor: pointer; width: 100%; text-align: left; }
.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); }