<?php
// =====================
// Single-file QR Registration (Frontend + Backend API)
// =====================
// Konfigurasi database MySQL
$DB_HOST = 'localhost';
$DB_NAME = 'mdcc5937_mdc';
$DB_USER = 'mdcc5937_mdc';
$DB_PASS = 'mdcc5937_mdc';
$DB_CHARSET = 'utf8mb4';

// ===== Backend API =====
if (isset($_GET['api']) && $_GET['api'] === 'register') {
  header('Content-Type: application/json');
  // Hanya izinkan POST
  if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['message' => 'Method Not Allowed']);
    exit;
  }

  // Baca body JSON
  $raw = file_get_contents('php://input');
  $input = json_decode($raw, true) ?: [];

  // Helper: normalisasi nomor HP ke +62
  $normalizePhone = function($v){
    $v = trim($v ?? '');
    if ($v === '') return '';
    if (preg_match('/^0\d{8,15}$/', $v)) return '+62' . substr($v, 1);
    if (preg_match('/^\+?62\d{7,15}$/', $v)) return ($v[0] === '+') ? $v : ('+' . $v);
    return $v; // biarin, nanti divalidasi regex berikutnya
  };

  $nama   = trim($input['nama']   ?? '');
  $wa     = $normalizePhone($input['whatsapp'] ?? '');
  $gender = trim($input['gender'] ?? '');
  $gereja = trim($input['gereja'] ?? '');
  $alamat = trim($input['alamat'] ?? '');

  // Validasi server-side
  $errors = [];
  if ($nama === '') $errors[] = 'Nama kosong';
  if ($wa === '' || !preg_match('/^\+62\d{7,15}$/', $wa)) $errors[] = 'WA tidak valid (08 / 62 / +62)';
  if ($gender === '') $errors[] = 'Pilih gender';
  if ($gereja === '') $errors[] = 'Pilih gereja';
  if ($alamat === '') $errors[] = 'Alamat wajib diisi';

  if ($errors) {
    http_response_code(422);
    echo json_encode(['message' => 'Validasi gagal', 'errors' => $errors]);
    exit;
  }

  // Konek DB
  $dsn = "mysql:host={$DB_HOST};dbname={$DB_NAME};charset={$DB_CHARSET}";
  try {
    $pdo = new PDO($dsn, $DB_USER, $DB_PASS, [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
  } catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(['message' => 'Gagal koneksi database', 'error' => $e->getMessage()]);
    exit;
  }

  // (Opsional) Buat tabel jika belum ada (aman dieksekusi berkali-kali)
  $sqlCreate = <<<SQL
  CREATE TABLE IF NOT EXISTS youth_registrations (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    nama VARCHAR(255) NOT NULL,
    whatsapp VARCHAR(20) NOT NULL,
    gender VARCHAR(10) NOT NULL,
    gereja VARCHAR(100) NOT NULL,
    alamat TEXT NOT NULL,
    registered_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY uniq_whatsapp (whatsapp)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  SQL;

  try { $pdo->exec($sqlCreate); } catch (Throwable $e) { /* abaikan jika tidak boleh create */ }

  // Insert baru, atau jika sudah ada (whatsapp unik) → HANYA perbarui registered_at
  $sql = "INSERT INTO youth_registrations (nama, whatsapp, gender, gereja, alamat) VALUES (:nama, :whatsapp, :gender, :gereja, :alamat)
          ON DUPLICATE KEY UPDATE registered_at = CURRENT_TIMESTAMP";

  try {
    $stmt = $pdo->prepare($sql);
    $stmt->execute([
      ':nama' => $nama,
      ':whatsapp' => $wa,
      ':gender' => $gender,
      ':gereja' => $gereja,
      ':alamat' => $alamat,
    ]);

    // Cek apakah baris diupdate atau dibuat (affected rows = 1 insert, =2 duplicate key update tergantung mode)
    $status = ($stmt->rowCount() >= 2) ? 'updated' : 'created';

    echo json_encode(['ok' => true, 'status' => $status]);
    exit;
  } catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(['message' => 'Gagal simpan', 'error' => $e->getMessage()]);
    exit;
  }
}
?><!DOCTYPE html><html lang="id"><head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>QR Registration</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
  <!-- QRCode.js -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" defer></script>
  <style>
    :root{
      --bg1:#0f1220; --bg2:#121826;
      --accent:#4f9ef8; --accent2:#9b8afc;
      --glass: rgba(255,255,255,.08); --glass-border: rgba(255,255,255,.22);
      --txt:#e8ebf1; --txt-dim:#b7c0d1; --ok:#22c55e; --err:#ef4444;
    }
    *{box-sizing:border-box}
    html,body{
      height:100%; margin:0;
      font-family:Inter,system-ui,Segoe UI,Roboto,Helvetica,Arial,"Apple Color Emoji","Segoe UI Emoji";
      color:var(--txt);
      background:
        radial-gradient(1200px 800px at 20% -10%, rgba(79,158,248,.28), transparent 60%),
        radial-gradient(1200px 800px at 110% 10%, rgba(155,138,252,.22), transparent 60%),
        linear-gradient(180deg, var(--bg1), var(--bg2));
      overflow-x:hidden;
    }
    .blob{position:fixed;filter:blur(60px);opacity:.55;pointer-events:none;mix-blend-mode:screen}
    .b1{width:500px;height:500px;background:conic-gradient(from 90deg at 50% 50%, #3b82f6,#8b5cf6,#06b6d4,#3b82f6);left:-150px;top:-120px;animation:drift 18s ease-in-out infinite}
    .b2{width:420px;height:420px;background:radial-gradient(circle at 30% 30%, #22d3ee, transparent 60%), radial-gradient(circle at 70% 70%, #818cf8, transparent 60%);right:-120px;bottom:-160px;animation:drift2 22s ease-in-out infinite}
    @keyframes drift{0%,100%{transform:translate(0,0) rotate(0)}50%{transform:translate(40px,30px) rotate(20deg)}}
    @keyframes drift2{0%,100%{transform:translate(0,0)}50%{transform:translate(-30px,20px)}}.welcome{position:fixed;inset:0;display:grid;place-items:center;background:rgba(5,7,13,.55);backdrop-filter:blur(6px);z-index:50;transition:opacity .6s ease,visibility .6s ease}
.welcome.hidden{opacity:0;visibility:hidden}
.welcome-card{padding:32px 28px;border-radius:28px;background:linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,.06));border:1px solid var(--glass-border);box-shadow:0 20px 60px rgba(0,0,0,.45), inset 0 1px rgba(255,255,255,.2);text-align:center;max-width:560px}
.welcome h1{margin:0 0 8px;font-weight:800;letter-spacing:.2px;font-size:clamp(28px,6vw,40px)}
.welcome p{margin:0 0 20px;color:var(--txt-dim)}
.btn{appearance:none;border:0;border-radius:16px;padding:14px 18px;font-weight:700;letter-spacing:.2px;cursor:pointer;color:white;background:linear-gradient(135deg,var(--accent),var(--accent2));box-shadow:0 10px 24px rgba(79,158,248,.35);transition:transform .08s ease,box-shadow .2s ease}
.btn:active{transform:translateY(1px);box-shadow:0 8px 16px rgba(79,158,248,.25)}
.btn[disabled]{opacity:.6;cursor:not-allowed;filter:grayscale(.1)}

.container{max-width:1040px;margin:90px auto;padding:0 20px}
.glass{background:linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,.05));border:1px solid var(--glass-border);border-radius:28px;backdrop-filter:blur(16px) saturate(120%);box-shadow:0 20px 60px rgba(0,0,0,.4), inset 0 .5px rgba(255,255,255,.2)}
.grid{display:grid;grid-template-columns:1.1fr .9fr;gap:22px}
@media (max-width:920px){.grid{grid-template-columns:1fr;gap:16px}}
.card{padding:22px}
.card h2{margin:0 0 10px;font-size:24px}
.muted{color:var(--txt-dim);font-size:14px;margin-bottom:14px}
label{display:block;font-size:13px;color:var(--txt-dim);margin:12px 0 6px}
input,select,textarea{width:100%;padding:12px 14px;border-radius:14px;border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.06);color:var(--txt);outline:none;box-shadow:inset 0 1px rgba(255,255,255,.18)}
input::placeholder,textarea::placeholder{color:#9aa3b6}
textarea{min-height:88px;resize:vertical}
.row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.actions{display:flex;gap:10px;align-items:center;margin-top:18px}
.chip{font-size:12px;padding:6px 10px;border-radius:999px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.14);color:var(--txt-dim)}
.status{font-size:13px}
.preview{display:flex;align-items:center;justify-content:center;min-height:460px;position:relative}
.pass{position:relative;width:min(420px,92%);aspect-ratio:3/4;border-radius:28px;overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.5)}
.pass .bg{position:absolute;inset:0;background:radial-gradient(600px 400px at 20% 10%, rgba(79,158,248,.25), transparent 60%), radial-gradient(600px 400px at 100% 0%, rgba(155,138,252,.2), transparent 60%), linear-gradient(180deg,#111729,#0b1020)}
.pass .noise{position:absolute;inset:0;opacity:.15;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="140" height="140" viewBox="0 0 140 140"><filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="2" stitchTiles="stitch"/></filter><rect width="100%" height="100%" filter="url(%23n)" opacity="0.5"/></svg>');mix-blend-mode:overlay}
.pass .glass-top{position:absolute;left:16px;right:16px;top:16px;height:74px;border-radius:18px;background:linear-gradient(180deg, rgba(255,255,255,.22), rgba(255,255,255,.06));border:1px solid rgba(255,255,255,.25);backdrop-filter:blur(10px)}
.pass .title{position:absolute;top:28px;left:32px;right:32px;display:flex;justify-content:space-between;align-items:center;font-weight:700;letter-spacing:.4px}
.pass .qr-wrap{position:absolute;inset:auto 32px 110px;height:240px;display:grid;place-items:center;border-radius:20px;background:linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,.04));border:1px solid rgba(255,255,255,.16)}
#qr{width:200px;height:200px}
.pass .meta{position:absolute;left:24px;right:24px;bottom:18px;display:grid;gap:6px;font-size:14px;color:var(--txt-dim)}
.pass .meta strong{color:var(--txt)}
.footer{opacity:.7;text-align:center;font-size:12px;margin:26px 0 50px}
a{color:#c7d7ff}
.hidden{display:none}
.snow{position:fixed;inset:0;pointer-events:none;z-index:40}

  </style>
</head>
<body>
  <div class="blob b1"></div>
  <div class="blob b2"></div>
  <!-- Snow -->
  <canvas id="snow" class="snow"></canvas>  <!-- Welcome -->  <div class="welcome" id="welcome">
    <div class="welcome-card glass">
      <h1>Welcome 👋</h1>
      <p>Isi data registrasi singkat lalu dapatkan <strong>QR Pass</strong> di buat dengan rasa kecewa.</p>
      <button class="btn" id="startBtn">Mulai</button>
    </div>
  </div>  <!-- Musik otomatis dari folder --><audio id="bgMusic" src="music/natal.mp3" preload="auto"></audio>

  <div class="container">
    <div class="grid">
      <!-- Form -->
      <div class="card glass">
        <h2>Registrasi Natal Youth</h2>
        <div class="muted">Lengkapi semua kolom untuk mendaftar.</div>
        <form id="regForm" autocomplete="on">
          <label for="nama">Nama Lengkap</label>
          <input id="nama" name="nama" type="text" placeholder=" Yohanes Pratama" required /><div class="row">
        <div>
          <label for="wa">Nomor WhatsApp</label>
          <input id="wa" name="wa" type="tel" inputmode="numeric" placeholder="08xxxx" required />
        </div>
        <div>
          <label for="gender">Jenis Kelamin</label>
          <select id="gender" name="gender" required>
            <option value="" disabled selected>Pilih...</option>
            <option>Pria</option>
            <option>Wanita</option>
          </select>
        </div>
      </div>

      <label for="gereja">Gereja</label>
      <select id="gereja" name="gereja" required>
        <option value="" disabled selected>Pilih gereja...</option>
        <option>GKPB MDC Jombang</option>
        <option>GKPB</option>
        <option>GKI</option>
        <option>GPIB</option>
        <option>GSJA</option>
        <option>Lainnya</option>
      </select>

      <label for="alamat">Alamat</label>
      <textarea id="alamat" name="alamat" placeholder="Alamat" required></textarea>

      <div class="actions">
        <button class="btn" type="submit">Daftar</button>
        <span id="status" class="status chip">Menunggu kepastian…</span>
      </div>
    </form>
  </div>

  <!-- Preview -->
  <div class="card glass">
    <h2>Data Anda</h2>
    <div class="muted">“Karena begitu besar kasih Allah...” (Yohanes 3:16)</div>
    <div class="preview">
      <div class="pass" id="pass">
        <div class="bg"></div>
        <div class="noise"></div>
        <div class="glass-top"></div>
        <div class="title">
          <div>Registration Pass</div>
          <div class="chip"></div>
        </div>
        <div class="qr-wrap"><div id="qr"></div></div>
        <div class="meta" id="meta">
          <div>Nama: <strong id="mNama">—</strong></div>
          <div>WA: <strong id="mWa">—</strong></div>
          <div>Gereja: <strong id="mGereja">—</strong></div>
          <div>Gender: <strong id="mGender">—</strong></div>
        </div>
      </div>
    </div>
    <div class="actions">
      <button id="dlBtn" class="btn hidden">Unduh Pass</button>
    </div>
  </div>
</div>

<div class="footer">Dibuat dengan ❤️ · By SCARD-PROJECT.</div>

  </div><script>
  // Welcome + auto music
  const welcome = document.getElementById('welcome');
  const startBtn = document.getElementById('startBtn');
  const music = document.getElementById('bgMusic');
  startBtn.addEventListener('click', ()=>{
    welcome.classList.add('hidden');
    music.volume = 0.6; music.loop = true;
    music.play().catch(()=>{});
  });

  // Snow
  (function(){
    const cnv = document.getElementById('snow'); const ctx = cnv.getContext('2d');
    let W=0,H=0,flakes=[];
    function resize(){
      const dpr = Math.max(1, window.devicePixelRatio||1);
      cnv.width=Math.floor(innerWidth*dpr); cnv.height=Math.floor(innerHeight*dpr);
      cnv.style.width=innerWidth+'px'; cnv.style.height=innerHeight+'px';
      ctx.setTransform(dpr,0,0,dpr,0,0);
      W=innerWidth; H=innerHeight; spawn();
    }
    function spawn(){
      const density=Math.min(220,Math.floor(W/6));
      flakes=new Array(density).fill(0).map(()=>({x:Math.random()*W,y:Math.random()*H,r:1+Math.random()*2.2,s:0.6+Math.random()*1.2,w:(Math.random()*0.8-0.4),p:Math.random()*Math.PI*2}));
    }
    function step(){
      ctx.clearRect(0,0,W,H); ctx.fillStyle='rgba(255,255,255,0.9)';
      for(const f of flakes){
        f.p+=0.01; f.y+=f.s; f.x+=f.w+Math.sin(f.p)*0.3;
        if(f.y>H+6){f.y=-6;f.x=Math.random()*W}
        if(f.x>W+6){f.x=-6} if(f.x<-6){f.x=W+6}
        ctx.beginPath(); ctx.arc(f.x,f.y,f.r,0,Math.PI*2); ctx.fill();
      }
      requestAnimationFrame(step);
    }
    addEventListener('resize',resize); resize(); step();
  })();

  // Helpers
  const $ = sel => document.querySelector(sel);
  const statusEl = $('#status');
  const form = $('#regForm');
  const qrBox = $('#qr');
  let qrInstance = null;

  function normalizePhone(v){
    v = (v||'').trim();
    if(!v) return '';
    if(/^0\d{8,15}$/.test(v)) return '+62' + v.slice(1);         // 08.. -> +62..
    if(/^\+?62\d{7,15}$/.test(v)) return v.startsWith('+') ? v : ('+'+v); // 62.. -> +62..
    return v;
  }

  function validate(){
    const nama = $('#nama').value.trim();
    const wa = normalizePhone($('#wa').value);
    const gender = $('#gender').value;
    const gereja = $('#gereja').value;
    const alamat = $('#alamat').value.trim();

    let ok=true, msg=[];
    if(!nama){ok=false;msg.push('Nama kosong')}
    if(!wa || !(/^\+62\d{7,15}$/.test(wa))){ok=false;msg.push('WA tidak valid (08 / 62 / +62)')}
    if(!gender){ok=false;msg.push('Pilih gender')}
    if(!gereja){ok=false;msg.push('Pilih gereja')}
    if(!alamat){ok=false;msg.push('Alamat wajib diisi')}
    statusEl.textContent = ok ? 'Siap membuat QR' : 'Perlu perbaikan: '+msg.join(', ');
    statusEl.style.color = ok ? 'var(--ok)' : 'var(--err)';
    return ok ? {nama,wa,gender,gereja,alamat} : null;
  }

  function updatePreview(d){
    $('#mNama').textContent=d.nama; $('#mWa').textContent=d.wa;
    $('#mGereja').textContent=d.gereja; $('#mGender').textContent=d.gender;
  }
  function buildPayload(d){ return JSON.stringify({type:'registration',version:1,ts:new Date().toISOString(),...d}); }
  function ensureQR(text){
    if(qrInstance){ qrBox.innerHTML=''; qrInstance=null; }
    qrInstance = new QRCode(qrBox, {text, width:200, height:200, correctLevel: QRCode.CorrectLevel.H});
  }

  form.addEventListener('input', ()=> validate());

  form.addEventListener('submit', async (e)=>{
    e.preventDefault();
    const data = validate();
    if(!data) return;

    // 1) Buat QR dulu
    updatePreview(data);
    ensureQR(buildPayload(data));

    // Pastikan QR benar-benar ada
    const qrEl = qrBox.querySelector('canvas, img');
    if(!qrEl){
      statusEl.textContent = 'Gagal membuat QR';
      statusEl.style.color = 'var(--err)';
      return;
    }

    // 2) Kunci tombol submit
    const submitBtn = form.querySelector('button[type="submit"]');
    const defaultLabel = submitBtn.textContent;
    submitBtn.disabled = true;
    submitBtn.textContent = 'Memproses…';

    statusEl.textContent = 'QR berhasil dibuat';
    statusEl.style.color = 'var(--ok)';
    document.getElementById('dlBtn').classList.remove('hidden');

    // 3) Simpan ke DB langsung ke file ini (tanpa save.php)
    try{
      const resp = await fetch(window.location.href.replace(/#.*$/,'') + (window.location.search ? '&' : '?') + 'api=register', {
        method:'POST',
        headers:{'Content-Type':'application/json'},
        body: JSON.stringify({
          nama: data.nama,
          whatsapp: data.wa,
          gender: data.gender,
          gereja: data.gereja,
          alamat: data.alamat
        })
      });
      const res = await resp.json();
      if(!resp.ok){
        // Simpan gagal -> tombol aktif lagi
        submitBtn.disabled = false;
        submitBtn.textContent = defaultLabel;
        throw new Error(res.message || 'Gagal simpan');
      }

      // Sukses simpan -> tombol permanen non-aktif
      submitBtn.textContent = 'Sudah Terdaftar';
      statusEl.textContent = (res.status === 'updated')
        ? 'Data diperbarui (tgl pendaftaran di-update)'
        : 'Data tersimpan';
      statusEl.style.color = 'var(--ok)';
    }catch(err){
      console.error(err);
      statusEl.textContent = 'Gagal simpan ke database';
      statusEl.style.color = 'var(--err)';
      // tombol sudah di-enable kembali di atas
    }
  });

  // Download pass
  document.getElementById('dlBtn').addEventListener('click', async ()=>{
    const scale=3, W=360*scale, H=480*scale, pad=20*scale;
    const canvas=document.createElement('canvas'); canvas.width=W; canvas.height=H;
    const ctx=canvas.getContext('2d');
    const g1=ctx.createLinearGradient(0,0,0,H); g1.addColorStop(0,'#111729'); g1.addColorStop(1,'#0b1020');
    ctx.fillStyle=g1; ctx.fillRect(0,0,W,H);
    function blob(cx,cy,r,c){const g=ctx.createRadialGradient(cx,cy,r*0.1,cx,cy,r); g.addColorStop(0,c); g.addColorStop(1,'rgba(0,0,0,0)'); ctx.fillStyle=g; ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.fill();}
    blob(W*0.2,H*0.18,220*scale,'rgba(79,158,248,0.25)'); blob(W*0.9,H*0.12,260*scale,'rgba(155,138,252,0.22)');
    const radius=24*scale; const cardX=pad, cardY=pad, cardW=W-pad*2, cardH=H-pad*2;
    ctx.save(); roundRect(ctx,cardX,cardY,cardW,cardH,radius); ctx.clip();
    const gGlass=ctx.createLinearGradient(0,cardY,0,cardY+cardH); gGlass.addColorStop(0,'rgba(255,255,255,0.18)'); gGlass.addColorStop(1,'rgba(255,255,255,0.06)');
    ctx.fillStyle=gGlass; ctx.fillRect(cardX,cardY,cardW,cardH);
    ctx.restore(); ctx.lineWidth=2; ctx.strokeStyle='rgba(255,255,255,0.28)'; roundRect(ctx,cardX,cardY,cardW,cardH,radius,true);
    const topH=60*scale, topR=16*scale, topPad=14*scale; const topX=cardX+16*scale, topY=cardY+16*scale, topW=cardW-32*scale;
    ctx.fillStyle='rgba(255,255,255,0.16)'; roundRect(ctx,topX,topY,topW,topH,topR); ctx.fill(); ctx.strokeStyle='rgba(255,255,255,0.25)'; ctx.lineWidth=1.6; roundRect(ctx,topX,topY,topW,topH,topR,true);
    ctx.fillStyle='#e8ebf1'; ctx.font=`${18*scale}px Inter, Arial`; ctx.textBaseline='middle'; ctx.fillText('QR Registration Pass', topX+topPad, topY+topH/2);
    const qrEl=document.querySelector('#qr canvas, #qr img'); if(!qrEl){alert('QR belum dibuat.'); return;}
    const qrSide=220*scale, qrWrapW=qrSide+40*scale, qrWrapH=qrSide+40*scale, qrWrapX=cardX+(cardW-qrWrapW)/2, qrWrapY=topY+topH+20*scale;
    ctx.fillStyle='rgba(255,255,255,0.12)'; roundRect(ctx,qrWrapX,qrWrapY,qrWrapW,qrWrapH,16*scale); ctx.fill();
    ctx.strokeStyle='rgba(255,255,255,0.18)'; ctx.lineWidth=1.4; roundRect(ctx,qrWrapX,qrWrapY,qrWrapW,qrWrapH,16*scale,true);
    const qrx=qrWrapX+(qrWrapW-qrSide)/2, qry=qrWrapY+(qrWrapH-qrSide)/2; await drawDomImage(ctx, qrEl, qrx, qry, qrSide, qrSide);
    const metaX=cardX+24*scale, metaY=cardY+cardH-90*scale; ctx.fillStyle='#b7c0d1'; ctx.font=`${14*scale}px Inter, Arial`;
    const nama=$('#mNama').textContent, wa=$('#mWa').textContent, gereja=$('#mGereja').textContent, gender=$('#mGender').textContent;
    ctx.fillText(nama, metaX, metaY); ctx.fillText(`WA: ${wa}`, metaX, metaY+22*scale); ctx.fillText(`Gereja: ${gereja}`, metaX, metaY+44*scale); ctx.fillText(`Gender: ${gender}`, metaX, metaY+66*scale);
    const url=canvas.toDataURL('image/png'); const a=document.createElement('a'); const safe=(nama||'qr-pass').toLowerCase().replace(/[^a-z0-9-_]+/g,'-').replace(/-+/g,'-'); a.download=`qr-pass-${safe}.png`; a.href=url; a.click();
  });

  function roundRect(ctx,x,y,w,h,r,strokeOnly){ctx.beginPath();ctx.moveTo(x+r,y);ctx.arcTo(x+w,y,x+w,y+h,r);ctx.arcTo(x+w,y+h,x,y+h,r);ctx.arcTo(x,y+h,x,y,r);ctx.arcTo(x,y,x+w,y,r);strokeOnly?ctx.stroke():ctx.fill();}
  async function drawDomImage(ctx,el,x,y,w,h){
    let src; if(el.tagName.toLowerCase()==='img') src=el.src; else src=el.toDataURL('image/png');
    await new Promise(res=>{const img=new Image(); img.onload=()=>{ctx.imageSmoothingEnabled=false;ctx.drawImage(img,x,y,w,h);res();}; img.src=src;});
  }
</script></body>
</html>