Refine landing layout: calendar actions and editorial gift section

This commit is contained in:
bisco
2026-03-25 19:37:04 +01:00
parent 8f580745d8
commit eb12dd880d
3 changed files with 484 additions and 18 deletions

View File

@ -17,23 +17,8 @@
<div class="hero__overlay"></div>
<div class="hero__content container">
<!-- <p class="eyebrow">Save the date</p> -->
<h1 class="hero__title">Hey, ci sono i 40 anni del Gianca!</h1>
<div class="hero__underline" aria-hidden="true"></div>
<br />
<div class="event-tag__date">Save the date</div>
<!-- <div class="event-tag event-tag--icon" aria-label="Informazioni evento">
<div class="event-tag__icon">
<span class="party-popper" aria-hidden="true"></span>
</div>
<div class="event-tag__content">
<div class="event-tag__date">25 aprile 2026</div>
<div class="event-tag__place">Lost Paradise Beach Club</div>
<div class="event-tag__divider"></div>
<div class="event-tag__meta">Bacoli • ore 12.00</div>
</div>
</div> -->
</div>
</header>
@ -52,9 +37,26 @@
</div>
<div class="details-card">
<div class="details-card__item">
<div class="details-card__item details-card__item--calendar">
<span class="details-card__label">Quando</span>
<strong>Sabato 25 aprile 2026</strong>
<div class="calendar-tools">
<a
class="calendar-tools__action"
id="google-calendar-link"
target="_blank"
rel="noopener noreferrer"
>
Google Calendar
</a>
<button
class="calendar-tools__action calendar-tools__action--button"
type="button"
id="ical-download"
>
iCal / Apple Calendar
</button>
</div>
</div>
<div class="details-card__item">
<span class="details-card__label">Dove</span>
@ -69,6 +71,52 @@
</div>
</section>
<section class="section gift" id="regalo">
<div class="container gift__shell">
<div class="gift__layout" aria-labelledby="gift-title">
<h3 class="gift__title" id="gift-title">Dettagli regalo</h3>
<div class="gift__col gift__col--left">
<p class="section-kicker gift__kicker">Regalo / quota viaggio</p>
<h2 class="gift__heading">Un piccolo gesto, con il sorriso.</h2>
<p class="gift__lead">
Se ti fa piacere contribuire al regalo o alla quota viaggio, qui trovi i riferimenti.
</p>
<p class="gift__note" id="gift-note">Nota facoltativa: un pensiero semplice è più che sufficiente.</p>
</div>
<div class="gift__col gift__col--right">
<span class="gift__accent" aria-hidden="true"></span>
<!-- Dati contributo: modifica rapidamente intestatario, IBAN, causale e nota in script.js (costante GIFT_DETAILS). -->
<dl class="gift__details">
<div class="gift__row">
<dt>Intestatario</dt>
<dd id="gift-holder">Giancarlo ...</dd>
</div>
<div class="gift__row gift__row--iban">
<dt>IBAN</dt>
<dd id="gift-iban">IT00X0000000000000000000000</dd>
<div class="gift__iban-actions">
<button
class="button button--secondary button--compact gift__copy"
type="button"
id="copy-iban"
data-copy-target="gift-iban"
>
Copia IBAN
</button>
<p class="gift__feedback" id="iban-feedback" aria-live="polite"></p>
</div>
</div>
<div class="gift__row">
<dt>Causale consigliata</dt>
<dd id="gift-causal">Regalo 40 anni Gianca</dd>
</div>
</dl>
</div>
</div>
</div>
</section>
<section class="section rsvp" id="rsvp">
<div class="container rsvp__grid">
<div class="rsvp__copy">
@ -175,7 +223,7 @@
<footer class="footer">
<div class="container footer__inner">
<p>Ci vediamo a Bacoli.</p>
<p>Sviluppato con ♥ da bisco con Codex</p>
</div>
</footer>
</div>

138
script.js
View File

@ -5,6 +5,33 @@ const attendanceInputs = Array.from(document.querySelectorAll('input[name="atten
const successModal = document.getElementById('success-modal');
const successOkButton = document.getElementById('success-ok');
const closeModalNodes = Array.from(document.querySelectorAll('[data-close-modal]'));
const googleCalendarLink = document.getElementById('google-calendar-link');
const iCalDownloadButton = document.getElementById('ical-download');
const copyIbanButton = document.getElementById('copy-iban');
const ibanFeedback = document.getElementById('iban-feedback');
const giftHolder = document.getElementById('gift-holder');
const giftIban = document.getElementById('gift-iban');
const giftCausal = document.getElementById('gift-causal');
const giftNote = document.getElementById('gift-note');
// Modifica qui i dati del calendario evento.
const EVENT_CALENDAR = {
title: 'I 40 anni del Gianca',
location: 'Lost Paradise Beach Club, Bacoli',
description: 'Conferma la tua presenza per i 40 anni del Gianca.',
date: '2026-04-25',
startTime: '12:00',
durationHours: 6,
timezone: 'Europe/Rome'
};
// Modifica qui i dati regalo / quota viaggio.
const GIFT_DETAILS = {
holder: 'Giancarlo ...',
iban: 'IT00X0000000000000000000000',
causal: 'Regalo 40 anni Gianca',
note: 'Nota facoltativa: un pensiero semplice e sempre gradito.'
};
function updateAttendanceFields() {
const selected = document.querySelector('input[name="attendance"]:checked');
@ -34,15 +61,126 @@ function closeSuccessModal() {
document.body.classList.remove('modal-open');
}
function pad(value) {
return String(value).padStart(2, '0');
}
function createFloatingDateTime(date, time, plusHours = 0) {
const [year, month, day] = date.split('-').map(Number);
const [hours, minutes] = time.split(':').map(Number);
const normalizedDate = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0));
normalizedDate.setUTCHours(normalizedDate.getUTCHours() + plusHours);
return `${normalizedDate.getUTCFullYear()}${pad(normalizedDate.getUTCMonth() + 1)}${pad(normalizedDate.getUTCDate())}T${pad(normalizedDate.getUTCHours())}${pad(normalizedDate.getUTCMinutes())}00`;
}
function buildGoogleCalendarUrl(eventData) {
const start = createFloatingDateTime(eventData.date, eventData.startTime, 0);
const end = createFloatingDateTime(eventData.date, eventData.startTime, eventData.durationHours);
const params = new URLSearchParams({
action: 'TEMPLATE',
text: eventData.title,
details: eventData.description,
location: eventData.location,
ctz: eventData.timezone,
dates: `${start}/${end}`
});
return `https://calendar.google.com/calendar/render?${params.toString()}`;
}
function escapeIcsText(value) {
return value
.replace(/\\/g, '\\\\')
.replace(/\n/g, '\\n')
.replace(/,/g, '\\,')
.replace(/;/g, '\\;');
}
function buildIcsContent(eventData) {
const start = createFloatingDateTime(eventData.date, eventData.startTime, 0);
const end = createFloatingDateTime(eventData.date, eventData.startTime, eventData.durationHours);
const stamp = `${createFloatingDateTime(eventData.date, eventData.startTime, 0)}Z`;
const uid = `gianca-40-${start}@gianca-event`;
return [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//Gianca40//Event Landing//IT',
'CALSCALE:GREGORIAN',
'BEGIN:VEVENT',
`UID:${uid}`,
`DTSTAMP:${stamp}`,
`DTSTART;TZID=${eventData.timezone}:${start}`,
`DTEND;TZID=${eventData.timezone}:${end}`,
`SUMMARY:${escapeIcsText(eventData.title)}`,
`LOCATION:${escapeIcsText(eventData.location)}`,
`DESCRIPTION:${escapeIcsText(eventData.description)}`,
'END:VEVENT',
'END:VCALENDAR'
].join('\r\n');
}
function triggerIcsDownload() {
const content = buildIcsContent(EVENT_CALENDAR);
const blob = new Blob([content], { type: 'text/calendar;charset=utf-8' });
const downloadUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = 'i-40-anni-del-gianca.ics';
document.body.append(link);
link.click();
link.remove();
URL.revokeObjectURL(downloadUrl);
}
function setIbanFeedback(message) {
if (!ibanFeedback) return;
ibanFeedback.textContent = message;
}
async function copyIbanToClipboard() {
if (!copyIbanButton) return;
const ibanNodeId = copyIbanButton.dataset.copyTarget;
const ibanText = document.getElementById(ibanNodeId || '')?.textContent?.trim();
if (!ibanText) return;
try {
await navigator.clipboard.writeText(ibanText);
setIbanFeedback('IBAN copiato negli appunti.');
} catch (error) {
const fallbackField = document.createElement('input');
fallbackField.value = ibanText;
document.body.append(fallbackField);
fallbackField.select();
const copied = document.execCommand('copy');
fallbackField.remove();
setIbanFeedback(copied ? 'IBAN copiato negli appunti.' : 'Copia non riuscita. Riprova.');
}
}
attendanceInputs.forEach((input) => input.addEventListener('change', updateAttendanceFields));
closeModalNodes.forEach((node) => node.addEventListener('click', closeSuccessModal));
successOkButton?.addEventListener('click', closeSuccessModal);
iCalDownloadButton?.addEventListener('click', triggerIcsDownload);
copyIbanButton?.addEventListener('click', async () => {
await copyIbanToClipboard();
window.clearTimeout(copyIbanButton.dataset.feedbackTimeoutId);
const timeoutId = window.setTimeout(() => setIbanFeedback(''), 2500);
copyIbanButton.dataset.feedbackTimeoutId = String(timeoutId);
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && successModal?.classList.contains('success-modal--open')) {
closeSuccessModal();
}
});
updateAttendanceFields();
if (googleCalendarLink) {
googleCalendarLink.href = buildGoogleCalendarUrl(EVENT_CALENDAR);
}
if (giftHolder) giftHolder.textContent = GIFT_DETAILS.holder;
if (giftIban) giftIban.textContent = GIFT_DETAILS.iban;
if (giftCausal) giftCausal.textContent = GIFT_DETAILS.causal;
if (giftNote) giftNote.textContent = GIFT_DETAILS.note;
function encode(data) {
return new URLSearchParams(data).toString();

282
style.css
View File

@ -286,6 +286,11 @@ img {
border: 1px solid rgba(40, 89, 138, 0.08);
}
.details-card__item--calendar {
display: grid;
gap: 0.7rem;
}
.details-card__item strong,
.details-card__item span {
display: block;
@ -300,6 +305,42 @@ img {
text-transform: uppercase;
}
.calendar-tools {
margin-top: 0.3rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.calendar-tools__action {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 40px;
width: auto;
border-radius: 999px;
border: 1px solid rgba(30, 87, 138, 0.28);
background: rgba(255, 255, 255, 0.86);
color: var(--sea-dark);
font-size: 0.88rem;
font-weight: 700;
padding: 0.42rem 0.82rem;
text-decoration: none;
cursor: pointer;
transition: background .18s ease, border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.calendar-tools__action:hover {
background: rgba(30, 87, 138, 0.1);
border-color: rgba(30, 87, 138, 0.5);
transform: translateY(-1px);
box-shadow: 0 8px 16px rgba(21, 60, 96, 0.1);
}
.calendar-tools__action--button {
font: inherit;
}
.form-card {
padding: 1.8rem;
}
@ -419,6 +460,205 @@ textarea:focus {
cursor: wait;
}
.button--secondary {
color: var(--sea-dark);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(241, 246, 252, 0.92) 100%);
border: 1px solid rgba(30, 87, 138, 0.3);
box-shadow: 0 10px 20px rgba(21, 60, 96, 0.12);
}
.button--compact {
min-height: 44px;
padding: 0.55rem 1rem;
font-size: 0.93rem;
}
.gift {
padding-top: 1.3rem;
}
.gift__shell {
display: block;
}
.gift__title {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.gift__layout {
position: relative;
display: grid;
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
column-gap: clamp(1.4rem, 3.2vw, 2.7rem);
row-gap: 0;
align-items: start;
}
.gift__layout::before {
content: "";
position: absolute;
left: 0;
top: -0.68rem;
width: min(360px, 44%);
height: 1px;
border-radius: 999px;
background: linear-gradient(90deg, rgba(30, 87, 138, 0.38), rgba(30, 87, 138, 0.05));
}
.gift__layout::after {
content: "";
position: absolute;
right: 0;
bottom: -0.4rem;
width: min(190px, 26%);
height: 1px;
border-radius: 999px;
background: linear-gradient(90deg, rgba(30, 87, 138, 0.04), rgba(30, 87, 138, 0.22));
}
.gift__col {
min-width: 0;
}
.gift__col--left {
display: grid;
gap: 0.55rem;
align-content: start;
}
.gift__kicker {
margin: 0 0 0.2rem;
}
.gift__heading {
max-width: 17ch;
margin: 0;
font-size: clamp(1.72rem, 3.25vw, 2.55rem);
line-height: 1.06;
letter-spacing: -0.02em;
}
.gift__lead {
margin: 0;
color: var(--ink);
max-width: 56ch;
font-size: 0.99rem;
line-height: 1.68;
}
.gift__note {
margin: 0;
color: var(--ink-soft);
line-height: 1.58;
font-size: 0.92rem;
max-width: 44ch;
}
.gift__col--right {
position: relative;
align-self: start;
padding-left: clamp(1rem, 1.9vw, 1.3rem);
}
.gift__accent {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
border-radius: 999px;
background: linear-gradient(180deg, rgba(30, 87, 138, 0.62), rgba(30, 87, 138, 0.16));
}
.gift__accent::after {
content: "";
position: absolute;
left: 50%;
bottom: -3px;
width: 5px;
height: 5px;
border-radius: 50%;
transform: translateX(-50%);
background: rgba(30, 87, 138, 0.24);
}
.gift__details {
margin: 0;
display: grid;
gap: 0.74rem;
}
.gift__row {
margin: 0;
padding: 0 0 0.64rem;
border-bottom: 1px solid rgba(30, 87, 138, 0.18);
}
.gift__row:last-child {
border-bottom: 0;
padding-bottom: 0.1rem;
}
.gift__row dt {
color: var(--sea);
font-size: 0.78rem;
font-weight: 800;
letter-spacing: 0.16em;
text-transform: uppercase;
margin-bottom: 0.22rem;
}
.gift__row dd {
margin: 0;
color: var(--ink);
font-weight: 700;
line-height: 1.5;
word-break: break-word;
}
.gift__row--iban dd {
font-size: clamp(1.15rem, 2.3vw, 1.42rem);
letter-spacing: 0.038em;
color: var(--sea-dark);
margin-bottom: 0.38rem;
font-weight: 800;
line-height: 1.35;
}
.gift__row--iban {
padding-top: 0.04rem;
padding-bottom: 0.82rem;
border-bottom-color: rgba(30, 87, 138, 0.22);
}
.gift__copy {
width: auto;
}
.gift__iban-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.55rem 0.75rem;
}
.gift__feedback {
min-height: 1.15rem;
margin: 0;
color: var(--sea-dark);
font-size: 0.84rem;
font-weight: 700;
opacity: 0.88;
}
.form-status {
min-height: 1.4rem;
margin: 0;
@ -482,6 +722,24 @@ textarea:focus {
grid-template-columns: 1fr;
}
.gift__layout {
grid-template-columns: 1fr;
row-gap: 0.88rem;
}
.gift__layout::before,
.gift__layout::after {
width: min(320px, 100%);
}
.gift__col--right {
padding-left: 1.15rem;
}
.gift__heading {
max-width: 100%;
}
.section {
padding: 4rem 0;
}
@ -538,7 +796,8 @@ textarea:focus {
}
.form-card,
.details-card {
.details-card,
.gift__layout {
padding: 1.2rem;
}
@ -550,6 +809,27 @@ textarea:focus {
width: 100%;
justify-content: center;
}
.calendar-tools__action {
width: 100%;
}
.gift__layout {
padding: 0;
}
.gift__copy {
width: 100%;
}
.gift__iban-actions {
align-items: stretch;
gap: 0.45rem;
}
.gift__feedback {
width: 100%;
}
}