MatteoScript commited on
Commit
c8ecae0
·
verified ·
1 Parent(s): 2c3eed5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +675 -322
index.html CHANGED
@@ -8,7 +8,7 @@
8
 
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800&family=Great+Vibes&display=swap" rel="stylesheet">
12
 
13
  <style>
14
  @font-face {
@@ -25,23 +25,31 @@
25
 
26
  :root {
27
  --blue: #254B6B;
 
28
  --blue-soft: #8FA3B3;
29
  --olive: #939675;
 
30
  --sage: #C2C5B2;
31
  --coral: #D08A7A;
 
32
  --rose: #EDD3CD;
33
  --cream: #F3EFE9;
34
  --paper: #FFFDF8;
35
- --border: rgba(147, 150, 117, .22);
36
- --shadow: 0 16px 38px rgba(37, 75, 107, .10);
 
 
37
  --font-main: "Montserrat", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
 
38
  --font-script: "Amsterdam One", "Great Vibes", "Snell Roundhand", cursive;
 
 
 
39
  }
40
 
41
  * { box-sizing: border-box; }
42
 
43
- html,
44
- body {
45
  width: 100%;
46
  height: 100%;
47
  margin: 0;
@@ -49,13 +57,60 @@
49
  font-family: var(--font-main);
50
  color: var(--blue);
51
  background: var(--cream);
 
 
52
  }
53
 
54
  body {
55
  min-height: 100dvh;
56
- background: linear-gradient(180deg, #fffdf8 0%, #f7f2ec 100%);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  }
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  .screen {
60
  position: relative;
61
  width: 100%;
@@ -64,48 +119,78 @@
64
  display: flex;
65
  justify-content: center;
66
  overflow: hidden;
67
- }
68
-
69
- .screen::before,
70
- .reveal::before {
71
- content: "";
72
- position: fixed;
73
- inset: 16px;
74
- border: 1px solid var(--border);
75
- border-radius: 28px;
76
- pointer-events: none;
77
- z-index: 0;
78
  }
79
 
80
  .page {
81
  position: relative;
82
- z-index: 1;
83
  width: min(100%, 500px);
84
  height: 100dvh;
85
- padding: max(20px, env(safe-area-inset-top)) 28px max(18px, env(safe-area-inset-bottom));
86
  display: grid;
87
- grid-template-rows: 58px minmax(0, 1fr) auto;
88
- gap: 10px;
89
  overflow: hidden;
90
  }
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  .top-mark {
93
  display: flex;
 
94
  align-items: center;
95
  justify-content: center;
 
 
96
  }
97
 
98
  .logo {
99
- width: 54px;
100
- height: 54px;
101
  display: block;
102
  object-fit: contain;
103
  background: transparent;
104
- opacity: .96;
105
- mix-blend-mode: normal;
106
- filter: none;
107
  }
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  .hero {
110
  min-height: 0;
111
  display: flex;
@@ -113,98 +198,159 @@
113
  align-items: center;
114
  justify-content: center;
115
  text-align: center;
116
- padding: 2px 0 8px;
 
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
  .script-title {
120
  margin: 0 auto;
121
  color: var(--blue);
122
  font-family: var(--font-script);
123
- font-size: clamp(50px, 13vw, 78px);
124
  font-weight: 400;
125
- line-height: .82;
126
  letter-spacing: -.025em;
127
  text-wrap: balance;
128
  }
129
 
130
- .script-title span {
131
- display: block;
132
- }
133
 
134
- .script-title span:first-child {
135
- transform: translateX(-.04em);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  }
137
 
138
- .script-title span:last-child {
139
- transform: translateX(.05em);
140
- margin-top: -.02em;
141
  }
142
 
143
- .island-line {
144
- width: min(34vw, 150px);
145
- max-width: 150px;
146
- height: 118px;
147
- margin: clamp(16px, 3dvh, 26px) auto 0;
148
  display: flex;
149
  align-items: center;
150
  justify-content: center;
151
- overflow: hidden;
 
 
 
 
 
 
152
  }
153
-
154
- .island-img {
155
- max-width: 100%;
156
- max-height: 100%;
157
- width: auto;
158
- height: auto;
159
- display: block;
160
- object-fit: contain;
161
- opacity: 1;
162
- background: transparent;
163
- mix-blend-mode: normal;
164
- filter: none;
165
- transform: none;
166
- animation: none;
167
  }
168
 
169
- .form-area {
170
- padding-bottom: clamp(2px, 1.6dvh, 16px);
171
  }
172
 
173
- .field-label {
174
- display: block;
175
- margin: 0 0 10px;
176
- color: rgba(37, 75, 107, .74);
177
- font-size: 11px;
178
- font-weight: 800;
179
- letter-spacing: .15em;
180
- text-transform: uppercase;
 
 
 
 
 
181
  }
182
 
183
  input {
184
  width: 100%;
185
  height: 60px;
186
- border: 1px solid rgba(147, 150, 117, .30);
187
  outline: 0;
188
  border-radius: 999px;
189
- padding: 0 22px;
190
  color: var(--blue);
191
  background: rgba(255, 253, 248, .92);
192
  font-family: var(--font-main);
193
- font-size: 16px;
194
  font-weight: 600;
195
- box-shadow: 0 10px 26px rgba(37, 75, 107, .07);
196
- transition: border-color .18s ease, box-shadow .18s ease, background .18s ease;
 
197
  }
198
 
199
- input::placeholder {
200
- color: rgba(37, 75, 107, .34);
201
- font-weight: 600;
202
- }
203
 
204
  input:focus {
205
- border-color: rgba(208, 138, 122, .70);
206
  background: var(--paper);
207
- box-shadow: 0 0 0 5px rgba(237, 211, 205, .28), 0 12px 30px rgba(37, 75, 107, .08);
 
 
 
208
  }
209
 
210
  .discover {
@@ -217,87 +363,144 @@
217
  cursor: pointer;
218
  overflow: hidden;
219
  color: var(--paper);
220
- background: var(--blue);
221
  font-family: var(--font-main);
222
- font-size: 15px;
223
- font-weight: 800;
224
- letter-spacing: .02em;
225
- box-shadow: 0 14px 32px rgba(37, 75, 107, .17);
226
- transition: transform .16s ease, background .16s ease, box-shadow .16s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  }
228
 
229
  .discover::after {
230
  content: "";
231
  position: absolute;
232
  left: 50%;
233
- bottom: 9px;
234
- width: 54px;
235
- height: 2px;
236
  border-radius: 999px;
237
  background: var(--coral);
238
  transform: translateX(-50%);
 
239
  }
240
 
 
 
 
 
 
 
 
241
  .discover:active {
242
  transform: translateY(2px);
243
- background: #1f405b;
244
- box-shadow: 0 8px 22px rgba(37, 75, 107, .14);
 
245
  }
246
 
247
  .message {
248
  min-height: 0;
249
- margin-top: 10px;
250
  opacity: 0;
251
  transform: translateY(8px);
252
- transition: opacity .22s ease, transform .22s ease;
253
- }
254
-
255
- .message.show {
256
- opacity: 1;
257
- transform: translateY(0);
258
  }
 
259
 
260
  .message-card {
261
- border-radius: 18px;
262
- padding: 13px 15px;
263
- color: rgba(37, 75, 107, .78);
264
- background: rgba(255, 253, 248, .90);
265
- border: 1px solid rgba(147, 150, 117, .22);
 
266
  text-align: center;
267
- font-size: 13px;
268
- line-height: 1.35;
269
- font-weight: 600;
270
- box-shadow: 0 10px 26px rgba(37, 75, 107, .06);
 
 
 
 
 
 
 
 
 
 
 
 
271
  }
272
 
 
273
  .reveal {
274
  position: fixed;
275
  inset: 0;
276
  z-index: 20;
277
  display: grid;
278
  place-items: center;
279
- padding: max(24px, env(safe-area-inset-top)) 24px max(22px, env(safe-area-inset-bottom));
280
  color: var(--blue);
281
- background: linear-gradient(180deg, #fffdf8 0%, #f7f2ec 100%);
 
 
 
 
282
  opacity: 0;
283
  visibility: hidden;
284
  pointer-events: none;
285
- transform: scale(1.01);
286
- transition: opacity .30s ease, transform .30s ease, visibility 0s linear .30s;
287
  overflow: hidden;
288
  }
289
-
 
 
 
 
 
 
 
 
 
290
  .reveal.show {
291
  opacity: 1;
292
  visibility: visible;
293
  pointer-events: auto;
294
  transform: scale(1);
295
- transition: opacity .30s ease, transform .30s ease, visibility 0s;
 
 
 
 
 
 
 
 
 
296
  }
297
 
298
  .reveal-page {
299
  position: relative;
300
- z-index: 1;
301
  width: min(100%, 560px);
302
  height: 100%;
303
  display: flex;
@@ -307,84 +510,175 @@
307
  overflow: hidden;
308
  }
309
 
 
 
 
 
 
 
 
 
310
  .hello {
311
- margin: 0 0 clamp(20px, 4dvh, 34px);
312
- color: rgba(37, 75, 107, .66);
313
- font-size: clamp(15px, 4vw, 19px);
314
- font-weight: 500;
315
- letter-spacing: .02em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  }
317
 
318
  .table-kicker {
319
- margin: 0 0 8px;
320
- color: var(--coral);
321
- font-size: 12px;
322
- font-weight: 800;
323
- letter-spacing: .22em;
324
  text-transform: uppercase;
325
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
  .table-name {
328
  margin: 0 auto;
329
  max-width: 11ch;
330
  color: var(--blue);
331
  font-family: var(--font-script);
332
- font-size: clamp(72px, 22vw, 138px);
333
  font-weight: 400;
334
  line-height: .76;
335
  letter-spacing: -.035em;
336
  text-wrap: balance;
337
  }
338
-
339
  .table-name[data-long="true"] {
340
  max-width: 12ch;
341
- font-size: clamp(54px, 17vw, 104px);
342
  }
343
 
344
- .reveal-island {
345
- width: min(30vw, 132px);
346
- height: 100px;
347
- margin: clamp(18px, 3.2dvh, 26px) auto 0;
348
  display: flex;
349
  align-items: center;
350
  justify-content: center;
351
- overflow: hidden;
352
- opacity: .34;
353
  }
354
-
355
- .reveal-line {
356
- width: 82px;
357
- height: 2px;
358
- margin: clamp(14px, 2.8dvh, 24px) auto 0;
359
- border-radius: 999px;
360
  background: var(--olive);
361
- opacity: .58;
 
 
 
 
 
 
 
362
  }
363
 
364
  .reveal-note {
365
- margin: 14px auto 0;
366
- max-width: 28ch;
367
- color: rgba(37, 75, 107, .66);
368
- font-size: 15px;
369
- line-height: 1.42;
370
- font-weight: 500;
 
 
 
371
  }
372
 
373
  .again {
374
  align-self: center;
375
- height: 52px;
376
- margin-top: clamp(20px, 4dvh, 30px);
377
- border: 1px solid rgba(37, 75, 107, .14);
378
  border-radius: 999px;
379
- padding: 0 24px;
380
  color: var(--blue);
381
- background: rgba(255, 253, 248, .90);
382
- box-shadow: 0 14px 32px rgba(37, 75, 107, .08);
383
  font-family: var(--font-main);
384
- font-size: 13px;
385
- font-weight: 800;
386
- letter-spacing: .03em;
 
387
  cursor: pointer;
 
 
 
 
 
 
388
  }
389
 
390
  .status {
@@ -405,79 +699,217 @@
405
  pointer-events: none;
406
  }
407
 
 
 
 
 
 
 
 
408
  @media (max-width: 380px) {
409
- .screen::before, .reveal::before { inset: 12px; border-radius: 24px; }
410
- .page { padding-left: 22px; padding-right: 22px; grid-template-rows: 52px minmax(0, 1fr) auto; }
411
- .logo { width: 48px; height: 48px; }
412
- .script-title { font-size: clamp(48px, 14vw, 70px); }
413
- .island-line { width: min(32vw, 125px); height: 96px; margin-top: 14px; }
 
 
 
 
 
414
  input, .discover { height: 56px; }
415
- .table-name { font-size: clamp(62px, 20vw, 106px); }
416
- .table-name[data-long="true"] { font-size: clamp(50px, 16vw, 88px); }
417
- .reveal-island { width: min(28vw, 112px); height: 84px; }
 
 
 
 
418
  }
419
  </style>
420
  </head>
421
  <body>
422
  <canvas id="confetti"></canvas>
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  <main class="screen">
425
  <div class="page">
426
- <div class="top-mark">
427
- <img class="logo clean-bg" src="logo.png" alt="Logo matrimonio" />
 
428
  </div>
429
 
430
  <section class="hero" aria-label="Trova la tua cala">
431
- <h1 class="script-title">
 
432
  <span>Trova la</span>
433
  <span>tua Cala</span>
434
  </h1>
435
 
436
- <div class="island-line" aria-hidden="true">
437
- <img class="island-img clean-bg" src="isola.png" alt="" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  </div>
439
  </section>
440
 
441
- <section class="form-area" aria-label="Ricerca invitato">
442
- <label class="field-label" for="guestName">Nome e cognome</label>
443
- <input id="guestName" autocomplete="name" inputmode="text" placeholder="Es. Gaia Pollastrini" />
444
- <button class="discover" id="discover" type="button">Scopri il Tavolo</button>
 
 
 
 
445
  <div id="message" class="message" aria-live="polite"></div>
446
  <div id="status" class="status" aria-live="polite"></div>
447
  </section>
448
  </div>
449
  </main>
450
 
 
451
  <section id="reveal" class="reveal" aria-live="assertive" aria-hidden="true">
 
 
452
  <div class="reveal-page">
453
- <p class="hello" id="hello"></p>
454
- <p class="table-kicker">Il tuo tavolo è</p>
455
- <h2 class="table-name" id="tableName"></h2>
456
- <div class="reveal-island" aria-hidden="true">
457
- <img class="island-img clean-bg" src="isola.png" alt="" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  </div>
459
- <div class="reveal-line" aria-hidden="true"></div>
460
- <p class="reveal-note">In che cala cenerai? Lhai appena scoperto.</p>
461
- <button class="again" id="again" type="button">Cerca un altro nome</button>
 
462
  </div>
463
  </section>
464
 
465
  <script>
466
- const DATA_URL = "";
467
-
468
- const SAMPLE_CSV = `
469
- Nome Cognome,Nome del tavolo
470
- Gaia Pollastrini,Cala Matano
471
- Matteo Bergamelli,Cala delle Arene
472
- Luca Bianchi,Cala Tramontana
473
- Sara Conti,Cala Spido
474
- Marco De Santis,Cala Zio Cesare
475
- Elena Ferri,Cala degli Inglesi
476
- Davide Greco,Cala Tonda
477
- Chiara Romano,Cala dei Benedettini
478
- Alessandro Rinaldi,Cala Pietre di Fucile
479
- Martina Esposito,Cala Sorrentino
480
- `;
481
 
482
  const els = {
483
  input: document.getElementById("guestName"),
@@ -561,21 +993,14 @@ Martina Esposito,Cala Sorrentino
561
  }
562
 
563
  async function loadGuests() {
564
- const params = new URLSearchParams(window.location.search);
565
- const url = params.get("data") || DATA_URL;
566
-
567
  try {
568
- if (url && url.trim()) {
569
- const response = await fetch(url.trim(), { cache: "no-store" });
570
- if (!response.ok) throw new Error("Lista non disponibile");
571
- guests = tableFromCSV(await response.text());
572
- } else {
573
- guests = tableFromCSV(SAMPLE_CSV);
574
- }
575
  els.status.textContent = "Lista invitati pronta.";
576
  } catch (error) {
577
- guests = tableFromCSV(SAMPLE_CSV);
578
- els.status.textContent = "Lista invitati pronta.";
579
  }
580
  }
581
 
@@ -601,7 +1026,7 @@ Martina Esposito,Cala Sorrentino
601
  function showReveal(guest) {
602
  clearTimeout(hideTimer);
603
  const longName = guest.tableName.length > 14;
604
- els.hello.textContent = `Ciao ${firstName(guest.fullName)}`;
605
  els.tableName.textContent = guest.tableName;
606
  els.tableName.dataset.long = longName ? "true" : "false";
607
  document.body.classList.add("reveal-open");
@@ -619,7 +1044,7 @@ Martina Esposito,Cala Sorrentino
619
  hideTimer = window.setTimeout(() => {
620
  els.input.value = "";
621
  els.message.classList.remove("show");
622
- }, 330);
623
  }
624
 
625
  function discoverTable() {
@@ -631,6 +1056,11 @@ Martina Esposito,Cala Sorrentino
631
  return;
632
  }
633
 
 
 
 
 
 
634
  const guest = findGuest(typed);
635
  if (!guest) {
636
  showMessage("Nome non trovato. Controlla di aver scritto nome e cognome corretti.");
@@ -649,86 +1079,6 @@ Martina Esposito,Cala Sorrentino
649
  .replaceAll("'", "&#039;");
650
  }
651
 
652
- // Rimuove fondi bianchi/grigi/quadrettati collegati ai bordi del PNG.
653
- // Serve quando un file sembra trasparente ma nel browser mostra ancora il checkerboard.
654
- function cleanTransparentImage(img) {
655
- if (img.dataset.cleaned === "true" || !img.complete || !img.naturalWidth) return;
656
- img.dataset.cleaned = "true";
657
-
658
- const width = img.naturalWidth;
659
- const height = img.naturalHeight;
660
- const canvas = document.createElement("canvas");
661
- const context = canvas.getContext("2d");
662
- canvas.width = width;
663
- canvas.height = height;
664
-
665
- try {
666
- context.drawImage(img, 0, 0);
667
- const imageData = context.getImageData(0, 0, width, height);
668
- const data = imageData.data;
669
- const visited = new Uint8Array(width * height);
670
- const queue = [];
671
-
672
- function indexOf(x, y) { return y * width + x; }
673
-
674
- function isBackgroundPixel(i) {
675
- const p = i * 4;
676
- const r = data[p];
677
- const g = data[p + 1];
678
- const b = data[p + 2];
679
- const a = data[p + 3];
680
- if (a < 20) return true;
681
-
682
- const max = Math.max(r, g, b);
683
- const min = Math.min(r, g, b);
684
- const avg = (r + g + b) / 3;
685
- const lowColor = (max - min) < 34;
686
-
687
- return lowColor && avg > 202;
688
- }
689
-
690
- function addIfBackground(x, y) {
691
- if (x < 0 || y < 0 || x >= width || y >= height) return;
692
- const i = indexOf(x, y);
693
- if (visited[i] || !isBackgroundPixel(i)) return;
694
- visited[i] = 1;
695
- queue.push(i);
696
- }
697
-
698
- for (let x = 0; x < width; x++) {
699
- addIfBackground(x, 0);
700
- addIfBackground(x, height - 1);
701
- }
702
- for (let y = 0; y < height; y++) {
703
- addIfBackground(0, y);
704
- addIfBackground(width - 1, y);
705
- }
706
-
707
- for (let q = 0; q < queue.length; q++) {
708
- const i = queue[q];
709
- const x = i % width;
710
- const y = Math.floor(i / width);
711
- data[i * 4 + 3] = 0;
712
- addIfBackground(x + 1, y);
713
- addIfBackground(x - 1, y);
714
- addIfBackground(x, y + 1);
715
- addIfBackground(x, y - 1);
716
- }
717
-
718
- context.putImageData(imageData, 0, 0);
719
- img.src = canvas.toDataURL("image/png");
720
- } catch (error) {
721
- // Se il browser blocca il canvas per sicurezza, lasciamo l'immagine originale.
722
- }
723
- }
724
-
725
- function cleanAllImages() {
726
- document.querySelectorAll("img.clean-bg").forEach(img => {
727
- if (img.complete) cleanTransparentImage(img);
728
- else img.addEventListener("load", () => cleanTransparentImage(img), { once: true });
729
- });
730
- }
731
-
732
  els.discover.addEventListener("click", discoverTable);
733
  els.again.addEventListener("click", hideReveal);
734
  els.input.addEventListener("keydown", event => {
@@ -738,8 +1088,9 @@ Martina Esposito,Cala Sorrentino
738
  if (event.key === "Escape" && els.reveal.classList.contains("show")) hideReveal();
739
  });
740
 
 
741
  const ctx = els.canvas.getContext("2d");
742
- let confetti = [];
743
  let rafId = null;
744
 
745
  function resizeCanvas() {
@@ -751,62 +1102,65 @@ Martina Esposito,Cala Sorrentino
751
 
752
  function celebrate() {
753
  resizeCanvas();
754
- const colors = ["#254B6B", "#8FA3B3", "#939675", "#C2C5B2", "#D08A7A", "#EDD3CD"];
755
- const centerX = window.innerWidth / 2;
756
- const centerY = window.innerHeight * .36;
757
 
758
- confetti = Array.from({ length: 80 }, () => {
759
- const angle = Math.random() * Math.PI * 2;
760
- const speed = 3 + Math.random() * 6;
761
  return {
762
- x: centerX,
763
- y: centerY,
764
- size: 4 + Math.random() * 7,
765
- vx: Math.cos(angle) * speed,
766
- vy: Math.sin(angle) * speed - 4,
767
- gravity: .15 + Math.random() * .10,
768
  rotation: Math.random() * Math.PI,
769
- spin: (Math.random() - .5) * .24,
 
 
 
770
  color: colors[Math.floor(Math.random() * colors.length)],
771
- life: 82 + Math.random() * 34
 
772
  };
773
  });
774
 
775
  if (rafId) cancelAnimationFrame(rafId);
776
- animateConfetti();
777
  }
778
 
779
  function stopConfetti() {
780
  if (rafId) cancelAnimationFrame(rafId);
781
  rafId = null;
782
- confetti = [];
783
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
784
  }
785
 
786
- function animateConfetti() {
787
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
 
788
 
789
- confetti.forEach(piece => {
790
- piece.x += piece.vx;
791
- piece.y += piece.vy;
792
- piece.vy += piece.gravity;
793
- piece.vx *= .988;
794
- piece.rotation += piece.spin;
795
- piece.life -= 1;
796
 
797
  ctx.save();
798
- ctx.translate(piece.x, piece.y);
799
- ctx.rotate(piece.rotation);
800
- ctx.globalAlpha = Math.max(piece.life / 100, 0);
801
- ctx.fillStyle = piece.color;
802
- ctx.fillRect(-piece.size / 2, -piece.size / 2, piece.size, piece.size * .58);
 
 
803
  ctx.restore();
804
  });
805
 
806
- confetti = confetti.filter(piece => piece.life > 0 && piece.y < window.innerHeight + 80);
807
 
808
- if (confetti.length) {
809
- rafId = requestAnimationFrame(animateConfetti);
810
  } else {
811
  rafId = null;
812
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
@@ -814,8 +1168,7 @@ Martina Esposito,Cala Sorrentino
814
  }
815
 
816
  window.addEventListener("resize", resizeCanvas);
817
- cleanAllImages();
818
  loadGuests();
819
  </script>
820
  </body>
821
- </html>
 
8
 
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800&family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400&family=Great+Vibes&display=swap" rel="stylesheet">
12
 
13
  <style>
14
  @font-face {
 
25
 
26
  :root {
27
  --blue: #254B6B;
28
+ --blue-deep: #1B3A55;
29
  --blue-soft: #8FA3B3;
30
  --olive: #939675;
31
+ --olive-deep: #6F7257;
32
  --sage: #C2C5B2;
33
  --coral: #D08A7A;
34
+ --coral-deep: #B8705F;
35
  --rose: #EDD3CD;
36
  --cream: #F3EFE9;
37
  --paper: #FFFDF8;
38
+ --ink: rgba(37, 75, 107, .82);
39
+ --ink-soft: rgba(37, 75, 107, .56);
40
+ --ink-faint: rgba(37, 75, 107, .34);
41
+ --hair: rgba(147, 150, 117, .26);
42
  --font-main: "Montserrat", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
43
+ --font-serif: "Cormorant Garamond", "Cormorant", Georgia, serif;
44
  --font-script: "Amsterdam One", "Great Vibes", "Snell Roundhand", cursive;
45
+
46
+ --grain:
47
+ url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.86' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.145 0 0 0 0 0.294 0 0 0 0 0.420 0 0 0 0.045 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
48
  }
49
 
50
  * { box-sizing: border-box; }
51
 
52
+ html, body {
 
53
  width: 100%;
54
  height: 100%;
55
  margin: 0;
 
57
  font-family: var(--font-main);
58
  color: var(--blue);
59
  background: var(--cream);
60
+ -webkit-font-smoothing: antialiased;
61
+ -moz-osx-font-smoothing: grayscale;
62
  }
63
 
64
  body {
65
  min-height: 100dvh;
66
+ background-color: var(--cream);
67
+ background-image:
68
+ radial-gradient(ellipse 70% 55% at 50% 38%, rgba(237, 211, 205, .26) 0%, rgba(237, 211, 205, 0) 60%),
69
+ radial-gradient(ellipse 90% 60% at 50% 105%, rgba(143, 163, 179, .14) 0%, rgba(143, 163, 179, 0) 65%),
70
+ linear-gradient(180deg, #fffdf8 0%, #f5efe7 100%);
71
+ position: relative;
72
+ }
73
+
74
+ body::before {
75
+ content: "";
76
+ position: fixed;
77
+ inset: 0;
78
+ background-image: var(--grain);
79
+ background-size: 220px 220px;
80
+ opacity: .42;
81
+ mix-blend-mode: multiply;
82
+ pointer-events: none;
83
+ z-index: 0;
84
  }
85
 
86
+ body.reveal-open { overflow: hidden; }
87
+
88
+ /* ---------- decorative frame + corners ---------- */
89
+ .frame {
90
+ position: fixed;
91
+ inset: 18px;
92
+ border: 1px solid var(--hair);
93
+ border-radius: 30px;
94
+ pointer-events: none;
95
+ z-index: 1;
96
+ }
97
+
98
+ .corner {
99
+ position: fixed;
100
+ width: 64px;
101
+ height: 64px;
102
+ color: var(--olive);
103
+ opacity: .55;
104
+ pointer-events: none;
105
+ z-index: 2;
106
+ }
107
+ .corner svg { width: 100%; height: 100%; display: block; overflow: visible; }
108
+ .corner-tl { top: 26px; left: 26px; }
109
+ .corner-tr { top: 26px; right: 26px; transform: scaleX(-1); }
110
+ .corner-bl { bottom: 26px; left: 26px; transform: scaleY(-1); }
111
+ .corner-br { bottom: 26px; right: 26px; transform: scale(-1, -1); }
112
+
113
+ /* ---------- screen layout ---------- */
114
  .screen {
115
  position: relative;
116
  width: 100%;
 
119
  display: flex;
120
  justify-content: center;
121
  overflow: hidden;
122
+ z-index: 3;
 
 
 
 
 
 
 
 
 
 
123
  }
124
 
125
  .page {
126
  position: relative;
 
127
  width: min(100%, 500px);
128
  height: 100dvh;
129
+ padding: max(20px, env(safe-area-inset-top)) 30px max(20px, env(safe-area-inset-bottom));
130
  display: grid;
131
+ grid-template-rows: auto minmax(0, 1fr) auto;
132
+ gap: 6px;
133
  overflow: hidden;
134
  }
135
 
136
+ /* ---------- staggered entrance ---------- */
137
+ @keyframes rise {
138
+ from { opacity: 0; transform: translateY(14px); }
139
+ to { opacity: 1; transform: translateY(0); }
140
+ }
141
+ @keyframes fadeIn {
142
+ from { opacity: 0; }
143
+ to { opacity: 1; }
144
+ }
145
+ @keyframes float {
146
+ 0%, 100% { transform: translateY(0); }
147
+ 50% { transform: translateY(-6px); }
148
+ }
149
+
150
+ .anim { opacity: 0; animation: rise .7s cubic-bezier(.2,.7,.2,1) forwards; }
151
+ .anim-1 { animation-delay: .05s; }
152
+ .anim-2 { animation-delay: .18s; }
153
+ .anim-3 { animation-delay: .32s; }
154
+ .anim-4 { animation-delay: .46s; }
155
+ .anim-5 { animation-delay: .60s; }
156
+
157
+ /* ---------- top mark ---------- */
158
  .top-mark {
159
  display: flex;
160
+ flex-direction: column;
161
  align-items: center;
162
  justify-content: center;
163
+ padding: 6px 0 0;
164
+ gap: 8px;
165
  }
166
 
167
  .logo {
168
+ width: 76px;
169
+ height: 76px;
170
  display: block;
171
  object-fit: contain;
172
  background: transparent;
 
 
 
173
  }
174
 
175
+ .top-eyebrow {
176
+ display: flex;
177
+ align-items: center;
178
+ gap: 10px;
179
+ color: var(--ink-faint);
180
+ font-size: 9.5px;
181
+ font-weight: 700;
182
+ letter-spacing: .38em;
183
+ text-transform: uppercase;
184
+ }
185
+ .top-eyebrow::before,
186
+ .top-eyebrow::after {
187
+ content: "";
188
+ width: 22px;
189
+ height: 1px;
190
+ background: var(--hair);
191
+ }
192
+
193
+ /* ---------- hero ---------- */
194
  .hero {
195
  min-height: 0;
196
  display: flex;
 
198
  align-items: center;
199
  justify-content: center;
200
  text-align: center;
201
+ padding: 0 0 4px;
202
+ }
203
+
204
+ .hero-tag {
205
+ margin: 0 0 8px;
206
+ color: var(--coral-deep);
207
+ font-family: var(--font-serif);
208
+ font-style: italic;
209
+ font-size: 16px;
210
+ font-weight: 400;
211
+ letter-spacing: .02em;
212
  }
213
 
214
  .script-title {
215
  margin: 0 auto;
216
  color: var(--blue);
217
  font-family: var(--font-script);
218
+ font-size: clamp(60px, 16vw, 96px);
219
  font-weight: 400;
220
+ line-height: .78;
221
  letter-spacing: -.025em;
222
  text-wrap: balance;
223
  }
224
 
225
+ .script-title span { display: block; }
226
+ .script-title span:first-child { transform: translateX(-.04em); }
227
+ .script-title span:last-child { transform: translateX(.05em); margin-top: -.02em; }
228
 
229
+ /* ---------- coastline illustration ---------- */
230
+ .coast {
231
+ width: min(78vw, 320px);
232
+ height: 158px;
233
+ margin: clamp(14px, 3dvh, 26px) auto 0;
234
+ animation: float 7s ease-in-out infinite;
235
+ }
236
+ .coast svg { width: 100%; height: 100%; display: block; overflow: visible; }
237
+
238
+ .sea-wave {
239
+ fill: none;
240
+ stroke: var(--blue-soft);
241
+ stroke-width: 1.1;
242
+ stroke-linecap: round;
243
+ opacity: .42;
244
+ vector-effect: non-scaling-stroke;
245
+ }
246
+ .sea-wave-soft {
247
+ fill: none;
248
+ stroke: var(--sage);
249
+ stroke-width: 1;
250
+ stroke-linecap: round;
251
+ opacity: .55;
252
+ vector-effect: non-scaling-stroke;
253
+ }
254
+ .sun {
255
+ fill: none;
256
+ stroke: var(--coral);
257
+ stroke-width: 1.4;
258
+ opacity: .72;
259
+ vector-effect: non-scaling-stroke;
260
+ }
261
+ .island-shape {
262
+ fill: rgba(147, 150, 117, .10);
263
+ stroke: var(--olive-deep);
264
+ stroke-width: 1.6;
265
+ stroke-linecap: round;
266
+ stroke-linejoin: round;
267
+ vector-effect: non-scaling-stroke;
268
+ }
269
+ .cypress {
270
+ fill: var(--olive-deep);
271
+ opacity: .82;
272
+ }
273
+ .cala-dot {
274
+ fill: var(--coral);
275
+ }
276
+ .cala-ring {
277
+ fill: none;
278
+ stroke: var(--coral);
279
+ stroke-width: 1.2;
280
+ opacity: .35;
281
+ vector-effect: non-scaling-stroke;
282
  }
283
 
284
+ /* ---------- form ---------- */
285
+ .form-area {
286
+ padding-bottom: clamp(2px, 1.6dvh, 14px);
287
  }
288
 
289
+ .field-label {
 
 
 
 
290
  display: flex;
291
  align-items: center;
292
  justify-content: center;
293
+ gap: 10px;
294
+ margin: 0 0 12px;
295
+ color: var(--ink-soft);
296
+ font-size: 10.5px;
297
+ font-weight: 700;
298
+ letter-spacing: .26em;
299
+ text-transform: uppercase;
300
  }
301
+ .field-label::before,
302
+ .field-label::after {
303
+ content: "";
304
+ flex: 0 0 18px;
305
+ height: 1px;
306
+ background: var(--hair);
 
 
 
 
 
 
 
 
307
  }
308
 
309
+ .input-wrap {
310
+ position: relative;
311
  }
312
 
313
+ .input-wrap::before {
314
+ content: "";
315
+ position: absolute;
316
+ left: 22px;
317
+ top: 50%;
318
+ width: 14px;
319
+ height: 14px;
320
+ transform: translateY(-50%);
321
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23254B6B' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round' opacity='0.42'><circle cx='11' cy='11' r='7'/><path d='m20 20-3.5-3.5'/></svg>");
322
+ background-repeat: no-repeat;
323
+ background-position: center;
324
+ background-size: contain;
325
+ pointer-events: none;
326
  }
327
 
328
  input {
329
  width: 100%;
330
  height: 60px;
331
+ border: 1px solid rgba(147, 150, 117, .32);
332
  outline: 0;
333
  border-radius: 999px;
334
+ padding: 0 22px 0 46px;
335
  color: var(--blue);
336
  background: rgba(255, 253, 248, .92);
337
  font-family: var(--font-main);
338
+ font-size: 15.5px;
339
  font-weight: 600;
340
+ letter-spacing: .005em;
341
+ box-shadow: 0 8px 22px rgba(37, 75, 107, .06), inset 0 1px 0 rgba(255, 255, 255, .5);
342
+ transition: border-color .22s ease, box-shadow .22s ease, background .22s ease;
343
  }
344
 
345
+ input::placeholder { color: var(--ink-faint); font-weight: 500; font-style: italic; }
 
 
 
346
 
347
  input:focus {
348
+ border-color: rgba(208, 138, 122, .65);
349
  background: var(--paper);
350
+ box-shadow:
351
+ 0 0 0 5px rgba(237, 211, 205, .32),
352
+ 0 12px 28px rgba(37, 75, 107, .08),
353
+ inset 0 1px 0 rgba(255, 255, 255, .6);
354
  }
355
 
356
  .discover {
 
363
  cursor: pointer;
364
  overflow: hidden;
365
  color: var(--paper);
366
+ background: linear-gradient(180deg, #2a557a 0%, #1f405b 100%);
367
  font-family: var(--font-main);
368
+ font-size: 13.5px;
369
+ font-weight: 700;
370
+ letter-spacing: .22em;
371
+ text-transform: uppercase;
372
+ box-shadow:
373
+ 0 14px 30px rgba(37, 75, 107, .22),
374
+ inset 0 1px 0 rgba(255, 255, 255, .12);
375
+ transition: transform .18s ease, box-shadow .18s ease, filter .18s ease;
376
+ }
377
+
378
+ .discover-inner {
379
+ display: inline-flex;
380
+ align-items: center;
381
+ gap: 14px;
382
+ }
383
+ .discover-inner::before,
384
+ .discover-inner::after {
385
+ content: "";
386
+ width: 16px;
387
+ height: 1px;
388
+ background: rgba(237, 211, 205, .55);
389
  }
390
 
391
  .discover::after {
392
  content: "";
393
  position: absolute;
394
  left: 50%;
395
+ bottom: 8px;
396
+ width: 6px;
397
+ height: 6px;
398
  border-radius: 999px;
399
  background: var(--coral);
400
  transform: translateX(-50%);
401
+ box-shadow: 0 0 0 3px rgba(208, 138, 122, .22);
402
  }
403
 
404
+ .discover:hover {
405
+ transform: translateY(-1px);
406
+ filter: brightness(1.04);
407
+ box-shadow:
408
+ 0 18px 36px rgba(37, 75, 107, .26),
409
+ inset 0 1px 0 rgba(255, 255, 255, .14);
410
+ }
411
  .discover:active {
412
  transform: translateY(2px);
413
+ box-shadow:
414
+ 0 8px 18px rgba(37, 75, 107, .14),
415
+ inset 0 1px 0 rgba(255, 255, 255, .10);
416
  }
417
 
418
  .message {
419
  min-height: 0;
420
+ margin-top: 12px;
421
  opacity: 0;
422
  transform: translateY(8px);
423
+ transition: opacity .26s ease, transform .26s ease;
 
 
 
 
 
424
  }
425
+ .message.show { opacity: 1; transform: translateY(0); }
426
 
427
  .message-card {
428
+ position: relative;
429
+ border-radius: 16px;
430
+ padding: 14px 16px;
431
+ color: var(--ink);
432
+ background: rgba(255, 253, 248, .92);
433
+ border: 1px solid var(--hair);
434
  text-align: center;
435
+ font-size: 12.5px;
436
+ line-height: 1.42;
437
+ font-weight: 500;
438
+ letter-spacing: .005em;
439
+ box-shadow: 0 10px 24px rgba(37, 75, 107, .05);
440
+ }
441
+ .message-card::before {
442
+ content: "";
443
+ position: absolute;
444
+ top: -1px;
445
+ left: 50%;
446
+ width: 36px;
447
+ height: 2px;
448
+ background: var(--coral);
449
+ border-radius: 999px;
450
+ transform: translateX(-50%);
451
  }
452
 
453
+ /* ---------- reveal screen ---------- */
454
  .reveal {
455
  position: fixed;
456
  inset: 0;
457
  z-index: 20;
458
  display: grid;
459
  place-items: center;
460
+ padding: max(28px, env(safe-area-inset-top)) 28px max(24px, env(safe-area-inset-bottom));
461
  color: var(--blue);
462
+ background-color: var(--cream);
463
+ background-image:
464
+ radial-gradient(ellipse 75% 60% at 50% 38%, rgba(237, 211, 205, .32) 0%, rgba(237, 211, 205, 0) 60%),
465
+ radial-gradient(ellipse 90% 55% at 50% 110%, rgba(143, 163, 179, .18) 0%, rgba(143, 163, 179, 0) 65%),
466
+ linear-gradient(180deg, #fffdf8 0%, #f4ede4 100%);
467
  opacity: 0;
468
  visibility: hidden;
469
  pointer-events: none;
470
+ transform: scale(1.015);
471
+ transition: opacity .36s ease, transform .36s ease, visibility 0s linear .36s;
472
  overflow: hidden;
473
  }
474
+ .reveal::after {
475
+ content: "";
476
+ position: fixed;
477
+ inset: 0;
478
+ background-image: var(--grain);
479
+ background-size: 220px 220px;
480
+ opacity: .42;
481
+ mix-blend-mode: multiply;
482
+ pointer-events: none;
483
+ }
484
  .reveal.show {
485
  opacity: 1;
486
  visibility: visible;
487
  pointer-events: auto;
488
  transform: scale(1);
489
+ transition: opacity .36s ease, transform .36s ease, visibility 0s;
490
+ }
491
+
492
+ .reveal-frame {
493
+ position: fixed;
494
+ inset: 18px;
495
+ border: 1px solid var(--hair);
496
+ border-radius: 30px;
497
+ pointer-events: none;
498
+ z-index: 1;
499
  }
500
 
501
  .reveal-page {
502
  position: relative;
503
+ z-index: 2;
504
  width: min(100%, 560px);
505
  height: 100%;
506
  display: flex;
 
510
  overflow: hidden;
511
  }
512
 
513
+ .reveal.show .reveal-step { animation: rise .8s cubic-bezier(.2,.7,.2,1) both; }
514
+ .reveal.show .step-1 { animation-delay: .08s; }
515
+ .reveal.show .step-2 { animation-delay: .22s; }
516
+ .reveal.show .step-3 { animation-delay: .38s; }
517
+ .reveal.show .step-4 { animation-delay: .56s; }
518
+ .reveal.show .step-5 { animation-delay: .72s; }
519
+ .reveal.show .step-6 { animation-delay: .86s; }
520
+
521
  .hello {
522
+ margin: 0 0 6px;
523
+ color: var(--ink-soft);
524
+ font-family: var(--font-serif);
525
+ font-style: italic;
526
+ font-size: clamp(17px, 4.4vw, 22px);
527
+ font-weight: 400;
528
+ letter-spacing: .005em;
529
+ }
530
+
531
+ .hello-divider {
532
+ width: 28px;
533
+ height: 1px;
534
+ margin: 0 auto clamp(18px, 3.6dvh, 28px);
535
+ background: var(--hair);
536
+ position: relative;
537
+ }
538
+ .hello-divider::before {
539
+ content: "";
540
+ position: absolute;
541
+ left: 50%;
542
+ top: 50%;
543
+ width: 5px;
544
+ height: 5px;
545
+ border-radius: 999px;
546
+ background: var(--coral);
547
+ transform: translate(-50%, -50%);
548
  }
549
 
550
  .table-kicker {
551
+ margin: 0 0 12px;
552
+ color: var(--coral-deep);
553
+ font-size: 11px;
554
+ font-weight: 700;
555
+ letter-spacing: .42em;
556
  text-transform: uppercase;
557
  }
558
+ .table-kicker span {
559
+ display: inline-flex;
560
+ align-items: center;
561
+ gap: 14px;
562
+ }
563
+ .table-kicker span::before,
564
+ .table-kicker span::after {
565
+ content: "";
566
+ width: 24px;
567
+ height: 1px;
568
+ background: var(--coral);
569
+ opacity: .55;
570
+ }
571
+
572
+ .table-name-wrap {
573
+ position: relative;
574
+ display: inline-block;
575
+ margin: 0 auto;
576
+ padding: 0 clamp(28px, 8vw, 56px);
577
+ }
578
+
579
+ .branch {
580
+ position: absolute;
581
+ top: 50%;
582
+ width: clamp(36px, 9vw, 56px);
583
+ height: clamp(72px, 18vw, 110px);
584
+ transform: translateY(-50%);
585
+ color: var(--olive);
586
+ opacity: .68;
587
+ pointer-events: none;
588
+ }
589
+ .branch svg { width: 100%; height: 100%; display: block; overflow: visible; }
590
+ .branch-l { left: -8px; }
591
+ .branch-r { right: -8px; transform: translateY(-50%) scaleX(-1); }
592
+
593
+ .branch-stem {
594
+ fill: none;
595
+ stroke: var(--olive-deep);
596
+ stroke-width: 1.4;
597
+ stroke-linecap: round;
598
+ vector-effect: non-scaling-stroke;
599
+ }
600
+ .branch-leaf {
601
+ fill: var(--olive);
602
+ opacity: .85;
603
+ }
604
+ .branch-leaf-soft {
605
+ fill: var(--sage);
606
+ opacity: .9;
607
+ }
608
 
609
  .table-name {
610
  margin: 0 auto;
611
  max-width: 11ch;
612
  color: var(--blue);
613
  font-family: var(--font-script);
614
+ font-size: clamp(76px, 23vw, 144px);
615
  font-weight: 400;
616
  line-height: .76;
617
  letter-spacing: -.035em;
618
  text-wrap: balance;
619
  }
 
620
  .table-name[data-long="true"] {
621
  max-width: 12ch;
622
+ font-size: clamp(56px, 17vw, 108px);
623
  }
624
 
625
+ .reveal-divider {
 
 
 
626
  display: flex;
627
  align-items: center;
628
  justify-content: center;
629
+ gap: 10px;
630
+ margin: clamp(20px, 4dvh, 32px) auto 0;
631
  }
632
+ .reveal-divider::before,
633
+ .reveal-divider::after {
634
+ content: "";
635
+ width: 38px;
636
+ height: 1px;
 
637
  background: var(--olive);
638
+ opacity: .55;
639
+ }
640
+ .reveal-divider .dot {
641
+ width: 6px;
642
+ height: 6px;
643
+ border-radius: 999px;
644
+ background: var(--coral);
645
+ box-shadow: 0 0 0 4px rgba(208, 138, 122, .18);
646
  }
647
 
648
  .reveal-note {
649
+ margin: 16px auto 0;
650
+ max-width: 32ch;
651
+ color: var(--ink-soft);
652
+ font-family: var(--font-serif);
653
+ font-style: italic;
654
+ font-size: clamp(15px, 4vw, 18px);
655
+ font-weight: 400;
656
+ line-height: 1.45;
657
+ letter-spacing: .003em;
658
  }
659
 
660
  .again {
661
  align-self: center;
662
+ height: 50px;
663
+ margin-top: clamp(22px, 4.4dvh, 32px);
664
+ border: 1px solid rgba(37, 75, 107, .16);
665
  border-radius: 999px;
666
+ padding: 0 26px;
667
  color: var(--blue);
668
+ background: rgba(255, 253, 248, .92);
669
+ box-shadow: 0 12px 26px rgba(37, 75, 107, .07);
670
  font-family: var(--font-main);
671
+ font-size: 11px;
672
+ font-weight: 700;
673
+ letter-spacing: .26em;
674
+ text-transform: uppercase;
675
  cursor: pointer;
676
+ transition: transform .18s ease, box-shadow .18s ease, background .18s ease;
677
+ }
678
+ .again:hover {
679
+ transform: translateY(-1px);
680
+ background: var(--paper);
681
+ box-shadow: 0 16px 30px rgba(37, 75, 107, .10);
682
  }
683
 
684
  .status {
 
699
  pointer-events: none;
700
  }
701
 
702
+ /* ---------- responsive tightening ---------- */
703
+ @media (max-height: 720px) {
704
+ .logo { width: 64px; height: 64px; }
705
+ .coast { height: 134px; margin-top: 10px; }
706
+ .script-title { font-size: clamp(54px, 14vw, 84px); }
707
+ }
708
+
709
  @media (max-width: 380px) {
710
+ .frame, .reveal-frame { inset: 12px; border-radius: 24px; }
711
+ .corner { width: 52px; height: 52px; }
712
+ .corner-tl, .corner-tr { top: 20px; }
713
+ .corner-bl, .corner-br { bottom: 20px; }
714
+ .corner-tl, .corner-bl { left: 20px; }
715
+ .corner-tr, .corner-br { right: 20px; }
716
+ .page { padding-left: 22px; padding-right: 22px; }
717
+ .logo { width: 64px; height: 64px; }
718
+ .script-title { font-size: clamp(50px, 14vw, 76px); }
719
+ .coast { width: min(72vw, 270px); height: 130px; }
720
  input, .discover { height: 56px; }
721
+ .table-name { font-size: clamp(64px, 21vw, 108px); }
722
+ .table-name[data-long="true"] { font-size: clamp(48px, 16vw, 88px); }
723
+ }
724
+
725
+ @media (prefers-reduced-motion: reduce) {
726
+ .anim, .reveal.show .reveal-step { animation: none; opacity: 1; transform: none; }
727
+ .coast { animation: none; }
728
  }
729
  </style>
730
  </head>
731
  <body>
732
  <canvas id="confetti"></canvas>
733
 
734
+ <!-- decorative frame & corner ornaments -->
735
+ <div class="frame" aria-hidden="true"></div>
736
+
737
+ <div class="corner corner-tl" aria-hidden="true">
738
+ <svg viewBox="0 0 80 80">
739
+ <path d="M8 8 L8 30 M8 8 L30 8" fill="none" stroke="currentColor" stroke-width="1" opacity=".55" stroke-linecap="round"/>
740
+ <path d="M14 14 C 22 16, 30 22, 36 32" fill="none" stroke="currentColor" stroke-width="1.1" opacity=".7" stroke-linecap="round"/>
741
+ <ellipse cx="20" cy="18" rx="3" ry="1.4" fill="currentColor" opacity=".75" transform="rotate(35 20 18)"/>
742
+ <ellipse cx="26" cy="22" rx="3.4" ry="1.5" fill="currentColor" opacity=".6" transform="rotate(45 26 22)"/>
743
+ <ellipse cx="31" cy="28" rx="3" ry="1.4" fill="currentColor" opacity=".75" transform="rotate(55 31 28)"/>
744
+ <ellipse cx="34" cy="33" rx="2.4" ry="1.2" fill="currentColor" opacity=".55" transform="rotate(60 34 33)"/>
745
+ </svg>
746
+ </div>
747
+ <div class="corner corner-tr" aria-hidden="true">
748
+ <svg viewBox="0 0 80 80">
749
+ <path d="M8 8 L8 30 M8 8 L30 8" fill="none" stroke="currentColor" stroke-width="1" opacity=".55" stroke-linecap="round"/>
750
+ <path d="M14 14 C 22 16, 30 22, 36 32" fill="none" stroke="currentColor" stroke-width="1.1" opacity=".7" stroke-linecap="round"/>
751
+ <ellipse cx="20" cy="18" rx="3" ry="1.4" fill="currentColor" opacity=".75" transform="rotate(35 20 18)"/>
752
+ <ellipse cx="26" cy="22" rx="3.4" ry="1.5" fill="currentColor" opacity=".6" transform="rotate(45 26 22)"/>
753
+ <ellipse cx="31" cy="28" rx="3" ry="1.4" fill="currentColor" opacity=".75" transform="rotate(55 31 28)"/>
754
+ <ellipse cx="34" cy="33" rx="2.4" ry="1.2" fill="currentColor" opacity=".55" transform="rotate(60 34 33)"/>
755
+ </svg>
756
+ </div>
757
+ <div class="corner corner-bl" aria-hidden="true">
758
+ <svg viewBox="0 0 80 80">
759
+ <path d="M8 8 L8 30 M8 8 L30 8" fill="none" stroke="currentColor" stroke-width="1" opacity=".55" stroke-linecap="round"/>
760
+ <path d="M14 14 C 22 16, 30 22, 36 32" fill="none" stroke="currentColor" stroke-width="1.1" opacity=".7" stroke-linecap="round"/>
761
+ <ellipse cx="20" cy="18" rx="3" ry="1.4" fill="currentColor" opacity=".75" transform="rotate(35 20 18)"/>
762
+ <ellipse cx="26" cy="22" rx="3.4" ry="1.5" fill="currentColor" opacity=".6" transform="rotate(45 26 22)"/>
763
+ <ellipse cx="31" cy="28" rx="3" ry="1.4" fill="currentColor" opacity=".75" transform="rotate(55 31 28)"/>
764
+ <ellipse cx="34" cy="33" rx="2.4" ry="1.2" fill="currentColor" opacity=".55" transform="rotate(60 34 33)"/>
765
+ </svg>
766
+ </div>
767
+ <div class="corner corner-br" aria-hidden="true">
768
+ <svg viewBox="0 0 80 80">
769
+ <path d="M8 8 L8 30 M8 8 L30 8" fill="none" stroke="currentColor" stroke-width="1" opacity=".55" stroke-linecap="round"/>
770
+ <path d="M14 14 C 22 16, 30 22, 36 32" fill="none" stroke="currentColor" stroke-width="1.1" opacity=".7" stroke-linecap="round"/>
771
+ <ellipse cx="20" cy="18" rx="3" ry="1.4" fill="currentColor" opacity=".75" transform="rotate(35 20 18)"/>
772
+ <ellipse cx="26" cy="22" rx="3.4" ry="1.5" fill="currentColor" opacity=".6" transform="rotate(45 26 22)"/>
773
+ <ellipse cx="31" cy="28" rx="3" ry="1.4" fill="currentColor" opacity=".75" transform="rotate(55 31 28)"/>
774
+ <ellipse cx="34" cy="33" rx="2.4" ry="1.2" fill="currentColor" opacity=".55" transform="rotate(60 34 33)"/>
775
+ </svg>
776
+ </div>
777
+
778
  <main class="screen">
779
  <div class="page">
780
+ <div class="top-mark anim anim-1">
781
+ <img class="logo" src="logo.png" alt="Logo matrimonio" />
782
+ <div class="top-eyebrow">Mise en place</div>
783
  </div>
784
 
785
  <section class="hero" aria-label="Trova la tua cala">
786
+ <p class="hero-tag anim anim-2">a tavola, in riva al mare</p>
787
+ <h1 class="script-title anim anim-3">
788
  <span>Trova la</span>
789
  <span>tua Cala</span>
790
  </h1>
791
 
792
+ <div class="coast anim anim-4" aria-hidden="true">
793
+ <svg viewBox="0 0 420 220" role="img">
794
+ <!-- soft sea waves behind -->
795
+ <path class="sea-wave-soft" d="M30 168 Q 80 162 130 168 T 230 168 T 330 168 T 410 168" />
796
+ <path class="sea-wave-soft" d="M30 184 Q 80 178 130 184 T 230 184 T 330 184 T 410 184" />
797
+ <path class="sea-wave" d="M30 200 Q 80 194 130 200 T 230 200 T 330 200 T 410 200" />
798
+
799
+ <!-- sun -->
800
+ <circle class="sun" cx="338" cy="56" r="22" />
801
+
802
+ <!-- island -->
803
+ <path class="island-shape" d="M62 152
804
+ C 78 130, 102 116, 134 116
805
+ C 158 116, 178 124, 196 116
806
+ C 218 106, 238 110, 256 122
807
+ C 278 110, 304 114, 322 130
808
+ C 340 144, 348 156, 354 162
809
+ L 62 162 Z" />
810
+
811
+ <!-- cypresses on island -->
812
+ <path class="cypress" d="M148 116 q 1.6 -16 0 -28 q -1.6 12 0 28 z" />
813
+ <path class="cypress" d="M210 110 q 1.4 -14 0 -24 q -1.4 10 0 24 z" />
814
+ <path class="cypress" d="M278 116 q 1.5 -15 0 -26 q -1.5 11 0 26 z" />
815
+
816
+ <!-- cala dots with rings -->
817
+ <circle class="cala-ring" cx="96" cy="146" r="6" />
818
+ <circle class="cala-dot" cx="96" cy="146" r="2.6" />
819
+
820
+ <circle class="cala-ring" cx="178" cy="138" r="6" />
821
+ <circle class="cala-dot" cx="178" cy="138" r="2.6" />
822
+
823
+ <circle class="cala-ring" cx="244" cy="142" r="6" />
824
+ <circle class="cala-dot" cx="244" cy="142" r="2.6" />
825
+
826
+ <circle class="cala-ring" cx="316" cy="146" r="6" />
827
+ <circle class="cala-dot" cx="316" cy="146" r="2.6" />
828
+
829
+ <!-- sailboat -->
830
+ <g opacity=".75">
831
+ <path d="M70 188 L 88 188 L 84 194 L 74 194 Z" fill="#6F7257"/>
832
+ <path d="M79 188 L 79 172 L 90 188 Z" fill="#6F7257"/>
833
+ </g>
834
+ </svg>
835
  </div>
836
  </section>
837
 
838
+ <section class="form-area anim anim-5" aria-label="Ricerca invitato">
839
+ <label class="field-label" for="guestName">Nome &amp; Cognome</label>
840
+ <div class="input-wrap">
841
+ <input id="guestName" autocomplete="name" inputmode="text" placeholder="Es. Gaia Pollastrini" />
842
+ </div>
843
+ <button class="discover" id="discover" type="button">
844
+ <span class="discover-inner">Scopri il tavolo</span>
845
+ </button>
846
  <div id="message" class="message" aria-live="polite"></div>
847
  <div id="status" class="status" aria-live="polite"></div>
848
  </section>
849
  </div>
850
  </main>
851
 
852
+ <!-- ===================== REVEAL ===================== -->
853
  <section id="reveal" class="reveal" aria-live="assertive" aria-hidden="true">
854
+ <div class="reveal-frame" aria-hidden="true"></div>
855
+
856
  <div class="reveal-page">
857
+ <p class="hello reveal-step step-1" id="hello"></p>
858
+ <div class="hello-divider reveal-step step-2" aria-hidden="true"></div>
859
+
860
+ <p class="table-kicker reveal-step step-3"><span>Il tuo tavolo è</span></p>
861
+
862
+ <div class="table-name-wrap reveal-step step-4">
863
+ <!-- left olive branch -->
864
+ <div class="branch branch-l" aria-hidden="true">
865
+ <svg viewBox="0 0 60 120">
866
+ <path class="branch-stem" d="M50 6 C 32 36, 22 70, 30 116" />
867
+ <ellipse class="branch-leaf" cx="44" cy="20" rx="6" ry="2.4" transform="rotate(-25 44 20)"/>
868
+ <ellipse class="branch-leaf-soft" cx="36" cy="32" rx="6.5" ry="2.6" transform="rotate(-30 36 32)"/>
869
+ <ellipse class="branch-leaf" cx="32" cy="46" rx="7" ry="2.7" transform="rotate(-38 32 46)"/>
870
+ <ellipse class="branch-leaf-soft" cx="28" cy="60" rx="7" ry="2.7" transform="rotate(-46 28 60)"/>
871
+ <ellipse class="branch-leaf" cx="26" cy="74" rx="7" ry="2.7" transform="rotate(-54 26 74)"/>
872
+ <ellipse class="branch-leaf-soft" cx="26" cy="88" rx="6.5" ry="2.6" transform="rotate(-62 26 88)"/>
873
+ <ellipse class="branch-leaf" cx="28" cy="102" rx="5.6" ry="2.3" transform="rotate(-70 28 102)"/>
874
+ <!-- tiny olives -->
875
+ <circle cx="40" cy="26" r="1.6" fill="var(--olive-deep)" opacity=".75"/>
876
+ <circle cx="30" cy="54" r="1.6" fill="var(--olive-deep)" opacity=".75"/>
877
+ <circle cx="27" cy="82" r="1.6" fill="var(--olive-deep)" opacity=".75"/>
878
+ </svg>
879
+ </div>
880
+
881
+ <h2 class="table-name" id="tableName"></h2>
882
+
883
+ <!-- right olive branch (mirrored via CSS) -->
884
+ <div class="branch branch-r" aria-hidden="true">
885
+ <svg viewBox="0 0 60 120">
886
+ <path class="branch-stem" d="M50 6 C 32 36, 22 70, 30 116" />
887
+ <ellipse class="branch-leaf" cx="44" cy="20" rx="6" ry="2.4" transform="rotate(-25 44 20)"/>
888
+ <ellipse class="branch-leaf-soft" cx="36" cy="32" rx="6.5" ry="2.6" transform="rotate(-30 36 32)"/>
889
+ <ellipse class="branch-leaf" cx="32" cy="46" rx="7" ry="2.7" transform="rotate(-38 32 46)"/>
890
+ <ellipse class="branch-leaf-soft" cx="28" cy="60" rx="7" ry="2.7" transform="rotate(-46 28 60)"/>
891
+ <ellipse class="branch-leaf" cx="26" cy="74" rx="7" ry="2.7" transform="rotate(-54 26 74)"/>
892
+ <ellipse class="branch-leaf-soft" cx="26" cy="88" rx="6.5" ry="2.6" transform="rotate(-62 26 88)"/>
893
+ <ellipse class="branch-leaf" cx="28" cy="102" rx="5.6" ry="2.3" transform="rotate(-70 28 102)"/>
894
+ <circle cx="40" cy="26" r="1.6" fill="var(--olive-deep)" opacity=".75"/>
895
+ <circle cx="30" cy="54" r="1.6" fill="var(--olive-deep)" opacity=".75"/>
896
+ <circle cx="27" cy="82" r="1.6" fill="var(--olive-deep)" opacity=".75"/>
897
+ </svg>
898
+ </div>
899
+ </div>
900
+
901
+ <div class="reveal-divider reveal-step step-5" aria-hidden="true">
902
+ <span class="dot"></span>
903
  </div>
904
+
905
+ <p class="reveal-note reveal-step step-5">In che cala cenerai? <br/>L'hai appena scoperto.</p>
906
+
907
+ <button class="again reveal-step step-6" id="again" type="button">Cerca un altro nome</button>
908
  </div>
909
  </section>
910
 
911
  <script>
912
+ const DATA_URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSlb-0cSIFaGN_BWrn_S9tQoQsdqGH7qYRYQGC3_3wRB-rGzwZoPoxIBNr9l6NmxW9Ont82YKIdGKIR/pub?output=csv";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
 
914
  const els = {
915
  input: document.getElementById("guestName"),
 
993
  }
994
 
995
  async function loadGuests() {
 
 
 
996
  try {
997
+ const response = await fetch(DATA_URL, { cache: "no-store" });
998
+ if (!response.ok) throw new Error("Lista non disponibile");
999
+ guests = tableFromCSV(await response.text());
 
 
 
 
1000
  els.status.textContent = "Lista invitati pronta.";
1001
  } catch (error) {
1002
+ guests = [];
1003
+ els.status.textContent = "Lista invitati non disponibile.";
1004
  }
1005
  }
1006
 
 
1026
  function showReveal(guest) {
1027
  clearTimeout(hideTimer);
1028
  const longName = guest.tableName.length > 14;
1029
+ els.hello.textContent = `Ciao, ${firstName(guest.fullName)}`;
1030
  els.tableName.textContent = guest.tableName;
1031
  els.tableName.dataset.long = longName ? "true" : "false";
1032
  document.body.classList.add("reveal-open");
 
1044
  hideTimer = window.setTimeout(() => {
1045
  els.input.value = "";
1046
  els.message.classList.remove("show");
1047
+ }, 360);
1048
  }
1049
 
1050
  function discoverTable() {
 
1056
  return;
1057
  }
1058
 
1059
+ if (!guests.length) {
1060
+ showMessage("Lista invitati non ancora disponibile. Riprova tra qualche secondo.");
1061
+ return;
1062
+ }
1063
+
1064
  const guest = findGuest(typed);
1065
  if (!guest) {
1066
  showMessage("Nome non trovato. Controlla di aver scritto nome e cognome corretti.");
 
1079
  .replaceAll("'", "&#039;");
1080
  }
1081
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1082
  els.discover.addEventListener("click", discoverTable);
1083
  els.again.addEventListener("click", hideReveal);
1084
  els.input.addEventListener("keydown", event => {
 
1088
  if (event.key === "Escape" && els.reveal.classList.contains("show")) hideReveal();
1089
  });
1090
 
1091
+ /* ============= petali (ex-confetti) ============= */
1092
  const ctx = els.canvas.getContext("2d");
1093
+ let petals = [];
1094
  let rafId = null;
1095
 
1096
  function resizeCanvas() {
 
1102
 
1103
  function celebrate() {
1104
  resizeCanvas();
1105
+ const colors = ["#EDD3CD", "#D08A7A", "#C2C5B2", "#8FA3B3", "#F3EFE9", "#EDD3CD", "#D08A7A"];
1106
+ const w = window.innerWidth;
 
1107
 
1108
+ petals = Array.from({ length: 56 }, () => {
 
 
1109
  return {
1110
+ x: Math.random() * w,
1111
+ y: -20 - Math.random() * 200,
1112
+ rx: 5 + Math.random() * 5,
1113
+ ry: 2.4 + Math.random() * 1.6,
1114
+ vy: 1.0 + Math.random() * 1.6,
1115
+ vx: (Math.random() - .5) * .6,
1116
  rotation: Math.random() * Math.PI,
1117
+ spin: (Math.random() - .5) * .04,
1118
+ sway: Math.random() * Math.PI * 2,
1119
+ swaySpeed: .015 + Math.random() * .02,
1120
+ swayAmp: .4 + Math.random() * .9,
1121
  color: colors[Math.floor(Math.random() * colors.length)],
1122
+ alpha: .75 + Math.random() * .25,
1123
+ life: 360 + Math.random() * 200
1124
  };
1125
  });
1126
 
1127
  if (rafId) cancelAnimationFrame(rafId);
1128
+ animatePetals();
1129
  }
1130
 
1131
  function stopConfetti() {
1132
  if (rafId) cancelAnimationFrame(rafId);
1133
  rafId = null;
1134
+ petals = [];
1135
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
1136
  }
1137
 
1138
+ function animatePetals() {
1139
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
1140
+ const h = window.innerHeight;
1141
 
1142
+ petals.forEach(p => {
1143
+ p.sway += p.swaySpeed;
1144
+ p.x += p.vx + Math.sin(p.sway) * p.swayAmp;
1145
+ p.y += p.vy;
1146
+ p.rotation += p.spin;
1147
+ p.life -= 1;
 
1148
 
1149
  ctx.save();
1150
+ ctx.translate(p.x, p.y);
1151
+ ctx.rotate(p.rotation);
1152
+ ctx.globalAlpha = Math.min(p.alpha, Math.max(p.life / 100, 0));
1153
+ ctx.fillStyle = p.color;
1154
+ ctx.beginPath();
1155
+ ctx.ellipse(0, 0, p.rx, p.ry, 0, 0, Math.PI * 2);
1156
+ ctx.fill();
1157
  ctx.restore();
1158
  });
1159
 
1160
+ petals = petals.filter(p => p.life > 0 && p.y < h + 40);
1161
 
1162
+ if (petals.length) {
1163
+ rafId = requestAnimationFrame(animatePetals);
1164
  } else {
1165
  rafId = null;
1166
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
 
1168
  }
1169
 
1170
  window.addEventListener("resize", resizeCanvas);
 
1171
  loadGuests();
1172
  </script>
1173
  </body>
1174
+ </html>