<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>RSVP — Ons Huwelijk</title>
<meta name="description" content="RSVP voor onze bruiloft" />
<style>
:root{
--bg: #0b0c10;
--card: #111318;
--muted: #9aa3b2;
--text: #eef2f7;
--accent: #6ee7b7; /* mint */
--accent-2:#60a5fa; /* cornflower */
--danger:#f87171;
--ring: rgba(110,231,183,0.25);
--radius: 16px;
}
html,body{height:100%}
body{
margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
color:var(--text); background: radial-gradient(1200px 800px at 10% -10%, #1a1f2a 0, transparent 60%),
radial-gradient(800px 600px at 120% 30%, #0d1b2a 0, transparent 60%),
var(--bg);
line-height:1.5;
}
.wrap{ max-width: 860px; margin: clamp(16px,4vw,48px) auto; padding: 0 16px; }
header{ text-align:center; margin-bottom: clamp(20px,3vw,40px); }
.badge{ display:inline-block; padding:6px 10px; border:1px solid #2a3140; border-radius:999px; color:var(--muted); font-size:12px; letter-spacing:.03em }
h1{ font-size: clamp(28px, 4.5vw, 44px); margin: 10px 0 6px; line-height:1.1 }
p.sub{ color: var(--muted); margin: 0 }
.card{ background: linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.02));
border:1px solid #1e2633; border-radius: var(--radius); padding: clamp(16px,3.5vw,28px);
box-shadow: 0 10px 30px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.03); }
form{ display:grid; gap:16px }
fieldset{ border:0; padding:0; margin:0 }
.grid{ display:grid; gap:12px }
.grid.two{ grid-template-columns: 1fr 1fr }
@media (max-width:700px){ .grid.two{ grid-template-columns: 1fr } }
label{ display:block; font-weight:600; margin-bottom:6px }
.hint{ color:var(--muted); font-size:12px; margin-top:4px }
input[type="text"], input[type="email"], input[type="tel"], select, textarea, input[type="number"]{
width:100%; box-sizing:border-box; background:#0f131a; border:1px solid #273043; color:var(--text);
padding:12px 14px; border-radius:10px; outline:none; transition:.2s border-color, .2s box-shadow;
}
input:focus, select:focus, textarea:focus{
border-color: var(--accent); box-shadow: 0 0 0 6px var(--ring);
}
textarea{ min-height:110px; resize: vertical }
.radio-row{ display:flex; gap:14px; flex-wrap:wrap }
.radio{
display:flex; align-items:center; gap:8px; padding:10px 12px; border:1px solid #273043; border-radius:999px;
cursor:pointer; user-select:none; background:#0f131a;
}
.radio input{ accent-color: var(--accent) }
.row{ display:flex; gap:12px; align-items:center }
.btn{
appearance:none; border:0; background:linear-gradient(135deg, var(--accent), var(--accent-2));
color:#0c111b; padding:12px 16px; font-weight:700; border-radius:12px; cursor:pointer;
transition: transform .05s ease-in-out, box-shadow .2s ease; box-shadow: 0 10px 22px rgba(96,165,250,.25);
}
.btn:hover{ transform: translateY(-1px) }
.btn:active{ transform: translateY(0) scale(.99) }
.inline{ display:flex; align-items:center; gap:8px; color:var(--muted) }
.divider{ height:1px; background:#1e2633; margin:8px 0 }
.guest-block{ padding:12px; border:1px dashed #2b3548; border-radius:12px; background: #0b0f16 }
.visually-hidden{ position:absolute !important; height:1px; width:1px; overflow:hidden; clip:rect(1px,1px,1px,1px); white-space:nowrap; }
.required{ color: var(--accent); font-weight:700 }
.error{ color: var(--danger); font-size: 14px }
.success{
border:1px solid #163b2c; background: linear-gradient(180deg, rgba(110,231,183,.1), rgba(110,231,183,.05));
color:#d1fae5; padding:12px 14px; border-radius:12px; display:none
}
footer{ text-align:center; color:var(--muted); margin-top: 18px; font-size: 12px }
.heart{ filter: drop-shadow(0 0 6px rgba(110,231,183,.45)) }
</style>
</head>
<body>
<div class="wrap">
<header>
<span class="badge">RSVP</span>
<h1>We gaan trouwen! 🎉</h1>
<p class="sub">Laat even weten of je erbij bent — dat scheelt ons Excel-trauma's.</p>
</header>
<section class="card" aria-labelledby="rsvp-title">
<h2 id="rsvp-title" class="visually-hidden">RSVP-formulier</h2>
<div id="alert" class="success" role="status" aria-live="polite"></div>
<form id="rsvp-form" novalidate>
<fieldset class="grid two">
<div>
<label for="naam">Jouw naam <span class="required" aria-hidden="true">*</span></label>
<input id="naam" name="naam" type="text" autocomplete="name" required placeholder="Voor- en achternaam" />
<p class="hint">Bijv. "Sam de Vries"</p>
</div>
<div>
<label for="email">E‑mail <span class="required" aria-hidden="true">*</span></label>
<input id="email" name="email" type="email" autocomplete="email" required placeholder="jij@voorbeeld.nl" />
</div>
</fieldset>
<fieldset>
<label>Aanwezig? <span class="required" aria-hidden="true">*</span></label>
<div class="radio-row" role="radiogroup" aria-label="Aanwezig">
<label class="radio"><input type="radio" name="aanwezig" value="ja" required /> Ja, ik ben erbij</label>
<label class="radio"><input type="radio" name="aanwezig" value="nee" /> Helaas, ik kan niet</label>
</div>
</fieldset>
<div id="presence-block" style="display:none">
<div class="grid two">
<div>
<label for="aantal">Met hoeveel personen kom je? <span class="required" aria-hidden="true">*</span></label>
<input id="aantal" name="aantal" type="number" min="1" max="5" value="1" required />
<p class="hint">Inclusief jezelf (max 5). Voor meer personen: zet het in het bericht hieronder.</p>
</div>
<div>
<label for="tel">Telefoon (optioneel)</label>
<input id="tel" name="tel" type="tel" autocomplete="tel" placeholder="06 12 34 56 78" />
</div>
</div>
<div id="guests" class="grid" aria-live="polite"></div>
<fieldset>
<label for="allergie">Allergieën / dieetwensen</label>
<textarea id="allergie" name="allergie" placeholder="Bijv. notenallergie, vegetarisch, halal…"></textarea>
</fieldset>
<fieldset>
<label for="song">Verzoeknummer voor de DJ</label>
<input id="song" name="song" type="text" placeholder="Artist – Titel" />
</fieldset>
</div>
<fieldset>
<label for="bericht">Bericht aan het bruidspaar</label>
<textarea id="bericht" name="bericht" placeholder="Verras ons 🤍"></textarea>
</fieldset>
<div class="divider"></div>
<div class="row" style="justify-content:space-between; flex-wrap:wrap; gap:12px">
<div class="inline">
<input type="checkbox" id="privacy" name="privacy" required />
<label for="privacy">Ik ga akkoord dat mijn gegevens alleen voor de bruiloft worden gebruikt.</label>
</div>
<button class="btn" type="submit">Versturen</button>
</div>
<p id="form-error" class="error" role="alert" aria-live="assertive" style="display:none"></p>
</form>
<footer>
Gemaakt met ♥︎ en veel koffie. <span class="heart">☕</span>
</footer>
</section>
</div>
<script>
// --- Helpers ---
const el = (sel, ctx=document) => ctx.querySelector(sel);
const els = (sel, ctx=document) => Array.from(ctx.querySelectorAll(sel));
// Render guest sub-forms based on count
function renderGuests(count){
const wrap = el('#guests');
wrap.innerHTML = '';
for(let i=1;i<=count;i++){
const block = document.createElement('div');
block.className = 'guest-block';
block.innerHTML = `
<div class="grid two">
<div>
<label for="gast_${i}_naam">Naam gast ${i} <span class="required" aria-hidden="true">*</span></label>
<input id="gast_${i}_naam" name="gast_${i}_naam" type="text" required placeholder="Voor- en achternaam" />
</div>
<div>
<label for="gast_${i}_menu">Menukeuze</label>
<select id="gast_${i}_menu" name="gast_${i}_menu">
<option value="geen-voorkeur">Geen voorkeur</option>
<option value="vlees">Vlees</option>
<option value="vis">Vis</option>
<option value="vegetarisch">Vegetarisch</option>
<option value="vegan">Vegan</option>
</select>
<p class="hint">Definitief menu volgt later — dit helpt de chef alvast.</p>
</div>
</div>`;
wrap.appendChild(block);
}
}
function togglePresence(show){
el('#presence-block').style.display = show ? '' : 'none';
}
// --- Init ---
(function(){
const form = el('#rsvp-form');
// Show/hide presence-dependent block
els('input[name="aanwezig"]').forEach(r => r.addEventListener('change', e => {
togglePresence(e.target.value === 'ja');
}));
// Default: render 1 guest block
renderGuests(1);
// Change guest count
el('#aantal').addEventListener('input', e => {
let c = parseInt(e.target.value || '1', 10);
c = Math.max(1, Math.min(5, c));
e.target.value = c;
renderGuests(c);
});
// Submit handler (demo)
form.addEventListener('submit', async (e) => {
e.preventDefault();
const error = el('#form-error');
error.style.display = 'none';
error.textContent = '';
// Basic HTML5 validation first
if(!form.checkValidity()){
// Find first invalid
const firstInvalid = form.querySelector(':invalid');
if(firstInvalid){ firstInvalid.focus(); }
error.textContent = 'Controleer de verplichte velden.';
error.style.display = 'block';
return;
}
// Collect data
const fd = new FormData(form);
const data = Object.fromEntries(fd.entries());
// Demo: pretend to send
try{
// Replace with your endpoint (see instructions below)
// Example using a static form service:
// await fetch('https://formspree.io/f/XXXXXXXX', { method:'POST', body: fd, headers: { 'Accept':'application/json' } });
await new Promise(r => setTimeout(r, 500)); // fake latency
el('#alert').textContent = 'Bedankt! Je RSVP is ontvangen. We sturen je snel meer info.';
el('#alert').style.display = 'block';
form.reset();
togglePresence(false);
el('#guests').innerHTML = '';
}catch(err){
error.textContent = 'Oeps, verzenden lukte niet. Probeer later opnieuw of stuur ons een mailtje.';
error.style.display = 'block';
}
});
})();
</script>
<!--