RVSP

<!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>

  <!--