<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="csrf-token" content="<?php echo e(csrf_token()); ?>">
<meta name="theme-color" content="#F3EEFF">
<title><?php echo e(config('mexco.apps.pass.name')); ?> · <?php echo e(config('mexco.apps.pass.tagline')); ?></title>
<?php echo $__env->make('partials.mexco-favicon', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" rel="stylesheet">
<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=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsQR/1.4.0/jsQR.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<link rel="stylesheet" href="<?php echo e(asset('css/mexco-qrcode.css')); ?>">
<script>window.MEETUP_APP_ICON = <?php echo json_encode(asset(config('mexco.logos.app_icon')), 15, 512) ?>;</script>
<script src="<?php echo e(asset('js/mexco-qrcode.js')); ?>"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<style>
  :root{
    --bs-primary:#4B23D4;--bs-primary-rgb:75,35,212;
    --deep:#3A17B0;--soft:#EFECFF;--ink:#171A24;--muted:#6B7280;
    --mexco-purple:#8E24AA;--mexco-blue:#1E88E5;--mexco-teal:#00ACC1;
    --mexco-bg:#EDE9FE;
    --mexco-bg-soft:#F8F9FF;
    --mexco-sky:#E0F2FF;
    --bs-body-font-family:"Plus Jakarta Sans",system-ui,sans-serif;
    --bs-body-color:#171A24;--bs-body-bg:#F4F5FA;--ok:#12855C;--warn:#B4690E;
  }
  body{background:var(--bs-body-bg);-webkit-tap-highlight-color:transparent}
  .app-shell{max-width:480px;margin:0 auto;min-height:100vh;background:#fff;
    box-shadow:0 0 60px rgba(23,26,36,.08);display:flex;flex-direction:column;position:relative}
  body.org-mode .app-shell{max-width:1180px}
  .ic{width:1.05em;height:1.05em;vertical-align:-.16em;fill:none;stroke:currentColor;
    stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;flex:none}
  .mono{font-family:"JetBrains Mono",ui-monospace,monospace}
  .fw-800{font-weight:800;letter-spacing:-.02em}
  .eyebrow{font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:var(--muted)}
  .btn{border-radius:.85rem;font-weight:700}
  .btn-lg{padding:.9rem 1rem;font-size:1rem}
  .btn-primary{--bs-btn-bg:#4B23D4;--bs-btn-border-color:#4B23D4;--bs-btn-hover-bg:#3F1DB8;
    --bs-btn-hover-border-color:#3F1DB8;--bs-btn-active-bg:#361699;--bs-btn-disabled-bg:#4B23D4;--bs-btn-disabled-border-color:#4B23D4}
  .btn-outline-primary{--bs-btn-color:#4B23D4;--bs-btn-border-color:#CFC6FA;--bs-btn-hover-bg:#4B23D4;--bs-btn-hover-border-color:#4B23D4}
  .btn-light-primary{background:var(--soft);color:#4B23D4;border:0}
  .form-control,.form-select{border-radius:.85rem;padding:.8rem .9rem;border-color:#E3E5EE}
  .form-control:focus,.form-select:focus{border-color:#A697F2;box-shadow:0 0 0 .22rem rgba(75,35,212,.14)}
  .card{border:1px solid #EAEBF2;border-radius:1.15rem;box-shadow:0 1px 2px rgba(23,26,36,.04)}
  .soft-card{background:#F7F6FF;border:1px solid #E8E4FB;border-radius:1.15rem}
  .badge-role{font-size:.68rem;font-weight:800;letter-spacing:.06em;padding:.36rem .6rem;border-radius:99px}
  .progress{height:.45rem;border-radius:99px;background:#E9EAF2}
  .progress-bar{background:#4B23D4}
  .avatar{width:52px;height:52px;border-radius:50%;background:var(--soft);display:grid;place-items:center;color:#4B23D4;flex:none;overflow:hidden}

  /* hero (welcome) */
  .hero{background:linear-gradient(160deg,#4B23D4,#2E0F8E 70%);color:#fff;flex:1;display:flex;
    flex-direction:column;justify-content:center;padding:2rem 1.5rem calc(2rem + env(safe-area-inset-bottom))}
  .hero .mark{width:74px;height:74px;border-radius:24px;background:rgba(255,255,255,.14);display:grid;place-items:center;margin:0 auto 1.4rem}
  /* splash + login shared backdrop */
  .mexco-backdrop{
    background:linear-gradient(180deg,#FAFBFF 0%,#F3EEFF 42%,#E8F4FF 100%);
    position:relative;overflow:hidden;
  }
  .mexco-backdrop::before,.mexco-backdrop::after{
    content:"";position:absolute;pointer-events:none;border-radius:50%;opacity:.55;
  }
  .mexco-backdrop::before{
    width:280px;height:280px;left:-80px;bottom:18%;
    background:radial-gradient(circle,rgba(142,36,170,.08) 0%,transparent 70%);
  }
  .mexco-backdrop::after{
    width:240px;height:240px;right:-60px;top:12%;
    background:radial-gradient(circle,rgba(0,172,193,.1) 0%,transparent 70%);
  }
  .mexco-deco-layer{
    position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:0;
  }
  .mexco-deco{
    position:absolute;inset:0;overflow:hidden;
  }
  .mexco-deco svg{width:100%;height:100%;opacity:.45;display:block}
  .mexco-deco-dots{
    position:absolute;width:88px;height:88px;opacity:.35;pointer-events:none;
    background-image:radial-gradient(circle,#C4B5FD 1.2px,transparent 1.2px);
    background-size:10px 10px;
  }
  .mexco-deco-dots--tl{top:1.25rem;left:.75rem}
  .mexco-deco-dots--br{bottom:5.5rem;right:.5rem}

  #view-splash{
    position:fixed;inset:0;z-index:9999;max-width:480px;margin:0 auto;
    display:flex;align-items:center;justify-content:center;
    transition:opacity .45s ease,visibility .45s ease;
  }
  #view-splash.out{opacity:0;visibility:hidden;pointer-events:none}
  @keyframes splashIn{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:scale(1)}}
  #view-splash .pass-logo-full{animation:splashIn .75s ease both;position:relative;z-index:1}

  /* login (welcome) */
  .login-screen{
    flex:1;display:none;flex-direction:column;justify-content:flex-start;
    min-height:100vh;padding:0 1.15rem calc(1.25rem + env(safe-area-inset-bottom));
    padding-top:max(1rem,calc(22vh - 1.35rem));
  }
  .login-screen.on{display:flex}
  .login-top,.login-body{position:relative;z-index:1}
  .login-top{text-align:center;padding:0 0 0;flex:none;margin-bottom:1.35rem}
  .pass-logo-full{display:block;width:min(220px,70vw);height:auto;margin:0 auto;background:transparent;object-fit:contain}
  .pass-logo-full--splash{width:min(250px,74vw)}
  .login-body{
    flex:0 0 auto;display:flex;flex-direction:column;justify-content:flex-start;
    gap:1rem;max-width:min(100%,320px);margin:0 auto;width:100%;
  }
  .login-card{
    background:#fff;border-radius:1.1rem;padding:1rem .95rem .95rem;
    box-shadow:0 8px 28px rgba(88,63,188,.07),0 2px 6px rgba(15,23,42,.04);
    border:1px solid rgba(255,255,255,.9);
  }
  .login-field{margin-bottom:.75rem}
  .login-field:last-of-type{margin-bottom:.25rem}
  .login-field label{
    display:block;font-size:.78rem;font-weight:700;color:#374151;margin-bottom:.35rem;
  }
  .login-input-wrap{
    display:flex;align-items:center;gap:.5rem;
    border:1px solid #E5E7EB;border-radius:.75rem;background:#fff;
    padding:.58rem .7rem;transition:border-color .15s,box-shadow .15s;
  }
  .login-input-wrap:focus-within{
    border-color:#A78BFA;box-shadow:0 0 0 2px rgba(139,92,246,.12);
  }
  .login-input-wrap .field-icon{
    width:1rem;height:1rem;color:#8B5CF6;flex:none;
  }
  .login-input-wrap input{
    border:0;outline:0;width:100%;background:transparent;
    font-size:.86rem;color:var(--ink);padding:0;
  }
  .login-input-wrap input::placeholder{color:#9CA3AF;font-weight:500;font-size:.84rem}
  .login-input-wrap input.mono{font-family:"JetBrains Mono",ui-monospace,monospace;letter-spacing:.02em}
  .login-err{min-height:1rem;font-size:.72rem;font-weight:700;color:#DC2626;margin:.25rem 0 .5rem}
  .login-card .btn-mexco{
    display:inline-flex;align-items:center;justify-content:center;gap:.4rem;
    background:linear-gradient(90deg,#7B1FA2 0%,#5B21B6 28%,#2563EB 58%,#0891B2 100%);
    border:0;color:#fff;font-weight:800;border-radius:999px;
    font-size:.9rem;padding:.72rem .85rem;
    box-shadow:0 6px 18px rgba(91,33,182,.24);
  }
  .login-card .btn-mexco:hover,.login-card .btn-mexco:focus{
    background:linear-gradient(90deg,#6A1B9A 0%,#4C1D95 28%,#1D4ED8 58%,#0E7490 100%);
    color:#fff;
  }
  .btn-mexco .arrow{font-size:1rem;line-height:1;margin-top:-1px}
  .login-footer{
    text-align:center;padding:.25rem .5rem 0;
  }
  .login-footer-inner{
    display:inline-flex;flex-direction:column;align-items:center;gap:.45rem;max-width:17rem;
  }
  .login-footer-icon{
    width:2rem;height:2rem;border-radius:50%;background:rgba(139,92,246,.12);
    display:grid;place-items:center;color:#7C3AED;
  }
  .login-footer-icon svg{width:1.05rem;height:1.05rem}
  .login-footer-title{font-size:.84rem;font-weight:800;color:#374151;line-height:1.35}
  .login-footer-sub{font-size:.78rem;color:#6B7280;line-height:1.4}
  .login-demo{
    margin-top:.15rem;padding:.75rem .8rem;border-radius:.85rem;
    background:linear-gradient(180deg,#F5F3FF 0%,#fff 70%);
    border:1px solid #DDD6FE;text-align:left;
  }
  .login-demo-kicker{
    font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
    color:#7C3AED;margin-bottom:.35rem;
  }
  .login-demo-row{
    display:flex;justify-content:space-between;gap:.75rem;align-items:baseline;
    font-size:.74rem;line-height:1.45;padding:.12rem 0;color:#64748B;
  }
  .login-demo-row code{
    font-family:"JetBrains Mono",ui-monospace,monospace;font-size:.72rem;font-weight:700;
    color:#1E1B4B;word-break:break-all;text-align:right;
  }
  .login-demo-btn{
    margin-top:.55rem;width:100%;border:1px solid #DDD6FE;background:#fff;
    color:#5B21B6;font-size:.76rem;font-weight:800;border-radius:.65rem;
    padding:.45rem .6rem;cursor:pointer;
  }
  .login-demo-btn:hover{background:#F5F3FF}
  .login-featured{display:none}
  .app-logo-horizontal{display:block;height:44px;width:auto;max-width:100%;background:transparent;object-fit:contain}
  .app-logo-horizontal--bar{height:40px;width:auto;background:transparent;object-fit:contain}

  /* ===== Home hub (post-login) ===== */
  .hub-screen{background:#F7F8FC;min-height:100vh}
  .hub-topbar{
    display:flex;align-items:center;justify-content:space-between;
    padding:.85rem 1rem .45rem;background:#F7F8FC;flex:none;
  }
  .hub-brand{display:flex;align-items:center;gap:.45rem;min-width:0}
  .hub-brand-logo{height:32px;width:32px;object-fit:contain;background:transparent;border-radius:8px}
  .hub-brand-name{font-weight:800;font-size:1.05rem;color:#1E1B4B;letter-spacing:-.02em}
  .hub-top-actions{display:flex;align-items:center;gap:.55rem}
  .hub-icon-btn{
    width:38px;height:38px;border-radius:50%;border:0;background:#fff;color:#4B5563;
    display:grid;place-items:center;padding:0;box-shadow:0 1px 4px rgba(15,23,42,.06);
  }
  .hub-icon-btn svg{width:18px;height:18px}
  .hub-avatar{
    width:38px;height:38px;border-radius:50%;border:0;padding:0;
    background:linear-gradient(135deg,#7C3AED,#2563EB);color:#fff;
    font-weight:800;font-size:.78rem;display:grid;place-items:center;
  }
  .hub-avatar--lg{width:72px;height:72px;font-size:1.25rem;margin:0 auto}
  .hub-profile-card .hub-avatar{margin:0 auto}
  .hub-event-feed{min-height:4rem}
  .hub-scroll{
    flex:1;overflow-y:auto;padding:.65rem 1rem calc(1.25rem + env(safe-area-inset-bottom));
  }
  .hub-welcome{
    margin:0 0 .9rem;padding:1rem 1.05rem;border-radius:1.25rem;
    background:linear-gradient(135deg,#6D28D9 0%,#4F46E5 48%,#2563EB 100%);
    color:#fff;display:flex;align-items:center;justify-content:space-between;gap:1rem;
    box-shadow:0 12px 28px rgba(79,70,229,.28);position:relative;overflow:hidden;
  }
  .hub-welcome::after{
    content:"";position:absolute;right:-24px;top:-30px;width:130px;height:130px;border-radius:50%;
    background:rgba(255,255,255,.12);pointer-events:none;
  }
  .hub-welcome-text{min-width:0;position:relative;z-index:1}
  .hub-welcome-kicker{font-size:.68rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;opacity:.85;margin-bottom:.3rem}
  .hub-hello{font-size:1.2rem;font-weight:800;line-height:1.2;letter-spacing:-.02em}
  .hub-sub{font-size:.8rem;opacity:.9;margin-top:.35rem;font-weight:600;line-height:1.35}
  .hub-welcome-badge{
    flex:none;width:56px;height:56px;border-radius:1rem;background:rgba(255,255,255,.16);
    display:grid;place-items:center;position:relative;z-index:1;
  }
  .hub-welcome-badge svg{width:26px;height:26px}
  .hub-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:.55rem;margin-bottom:.9rem}
  .hub-stat{
    background:#fff;border:1px solid #EEF0F6;border-radius:1.1rem;padding:.75rem .55rem .7rem;
    text-align:center;box-shadow:0 1px 4px rgba(15,23,42,.03);
  }
  .hub-stat.on{border-color:#DDD6FE;box-shadow:0 0 0 2px rgba(109,40,217,.12)}
  .hub-stat-icon{
    width:30px;height:30px;border-radius:10px;display:grid;place-items:center;margin:0 auto .35rem;
  }
  .hub-stat-icon svg{width:15px;height:15px}
  .hub-stat-n{font-size:1.1rem;font-weight:800;color:#1E1B4B;line-height:1}
  .hub-stat-l{font-size:.6rem;color:#6B7280;font-weight:700;line-height:1.25;margin-top:.25rem}
  .hub-segs{
    display:grid;grid-template-columns:repeat(3,1fr);gap:.35rem;
    margin:0 0 .9rem;padding:.3rem;background:#fff;border:1px solid #EEF0F6;border-radius:999px;
  }
  .hub-tab{
    border:0;background:transparent;border-radius:999px;padding:.5rem .3rem;
    font-size:.72rem;font-weight:800;color:#6B7280;
  }
  .hub-tab.on{background:#6D28D9;color:#fff;box-shadow:0 4px 12px rgba(109,40,217,.28)}
  .hub-section{margin-bottom:1.15rem}
  .hub-section-head{
    display:flex;align-items:center;justify-content:space-between;margin-bottom:.65rem;
  }
  .hub-section-title{font-size:.92rem;font-weight:800;color:#1E1B4B}
  .hub-section-link{font-size:.75rem;font-weight:700;color:#7C3AED;background:none;border:0;padding:0}
  .hub-event{
    width:100%;text-align:left;background:#fff;border:1px solid #EEF0F6;border-radius:1.2rem;
    padding:.75rem;margin-bottom:.7rem;display:flex;gap:.75rem;align-items:stretch;
    box-shadow:0 1px 4px rgba(15,23,42,.04);
  }
  .hub-event:disabled{opacity:.72}
  .hub-event-thumb{
    width:76px;height:76px;border-radius:14px;flex:none;overflow:hidden;
    display:grid;place-items:center;color:#fff;font-weight:800;font-size:.85rem;
    background:linear-gradient(135deg,#7C3AED,#2563EB);position:relative;
  }
  .hub-event-thumb img{width:100%;height:100%;object-fit:cover;display:block;position:absolute;inset:0}
  .hub-event-thumb.has-img .hub-event-thumb-mark{display:none}
  .hub-event-body{min-width:0;flex:1;display:flex;flex-direction:column;justify-content:center}
  .hub-event-status{
    display:inline-flex;align-self:flex-start;align-items:center;font-size:.6rem;font-weight:800;letter-spacing:.05em;
    padding:.22rem .5rem;border-radius:999px;margin-bottom:.35rem;text-transform:uppercase;
  }
  .hub-event-title{font-size:.95rem;font-weight:800;color:#1E1B4B;line-height:1.25}
  .hub-event-meta{font-size:.72rem;color:#6B7280;margin-top:.25rem;display:flex;align-items:center;gap:.3rem;font-weight:600}
  .hub-event-meta svg{width:12px;height:12px;flex:none;opacity:.75}
  .hub-event-tags{display:flex;flex-wrap:wrap;gap:.3rem;margin-top:.45rem}
  .hub-pill{
    font-size:.62rem;font-weight:700;padding:.18rem .45rem;border-radius:999px;
  }
  .hub-event-chev{align-self:center;color:#C4C9D4;font-size:1.2rem;flex:none;padding-left:.1rem}
  .hub-empty{
    text-align:center;color:#6B7280;font-size:.88rem;font-weight:600;padding:2.2rem 1rem;
    background:#fff;border:1px dashed #E5E7EB;border-radius:1.2rem;
  }
  .hub-tip{
    display:flex;align-items:flex-start;gap:.65rem;
    background:linear-gradient(100deg,#F3E8FF,#EDE9FE);color:#5B21B6;
    border-radius:1.15rem;padding:.9rem 1rem;font-size:.8rem;font-weight:600;line-height:1.4;
    margin-top:.35rem;
  }
  .hub-tip-icon{
    width:32px;height:32px;border-radius:50%;background:#fff;color:#7C3AED;
    display:grid;place-items:center;flex:none;
  }
  .hub-tip-icon svg{width:16px;height:16px}
  .hub-profile-card{
    background:#fff;border-radius:1.35rem;border:1px solid #EEF0F6;padding:1.75rem 1.25rem;
    text-align:center;margin-top:.35rem;box-shadow:0 1px 4px rgba(15,23,42,.04);
  }
  .hub-profile-card .btn-signout{
    margin-top:1.25rem;width:100%;border:0;border-radius:1rem;padding:.85rem 1rem;font-weight:800;
    background:linear-gradient(135deg,#7C3AED,#4F46E5);color:#fff;
  }
  .hub-profile-card .btn-back{
    margin-top:.45rem;width:100%;border:0;background:none;color:#6B7280;font-weight:700;padding:.55rem;
  }
  .event-backbar{display:none} /* legacy; back control lives in edash-event-card */
  .edash-top{
    display:flex;align-items:center;justify-content:space-between;
    padding:.75rem 1rem .35rem;background:#F7F8FC;flex:none;
  }
  .edash-brand{display:flex;align-items:center;gap:.4rem}
  .edash-brand img{height:28px;width:28px;object-fit:contain;border-radius:7px}
  .edash-brand span{font-weight:800;font-size:1rem;color:#1E1B4B}
  .edash-top-actions{display:flex;align-items:center;gap:.5rem}
  .edash-icon-btn{
    width:36px;height:36px;border-radius:50%;border:0;background:#fff;color:#4B5563;
    display:grid;place-items:center;box-shadow:0 1px 4px rgba(15,23,42,.06);
  }
  .edash-icon-btn svg{width:17px;height:17px}
  .edash-avatar{
    width:36px;height:36px;border-radius:50%;border:0;
    background:linear-gradient(135deg,#7C3AED,#2563EB);color:#fff;
    font-weight:800;font-size:.72rem;display:grid;place-items:center;
  }
  #tab-home{background:#F7F8FC}
  #tab-home > .scroll{padding-top:.5rem}
  .edash-banner{
    margin:0 1rem .9rem;border-radius:1.15rem;overflow:hidden;position:relative;
    background:linear-gradient(120deg,#F3E8FF,#E0E7FF);min-height:110px;
    box-shadow:0 8px 22px rgba(79,70,229,.12);
  }
  .edash-banner img{
    display:block;width:100%;height:132px;object-fit:cover;
  }
  .edash-banner-fade{
    position:absolute;inset:auto 0 0 0;height:42%;
    background:linear-gradient(transparent, rgba(30,27,75,.35));
    pointer-events:none;
  }
  .edash-banner-caption{
    position:absolute;left:.9rem;right:.9rem;bottom:.7rem;z-index:1;
    color:#fff;font-size:.72rem;font-weight:800;letter-spacing:.04em;text-transform:uppercase;
    text-shadow:0 1px 8px rgba(0,0,0,.35);
  }
  .edash-event-card{
    margin:0 1rem .9rem;padding:.85rem 1rem;border-radius:1.15rem;
    background:linear-gradient(120deg,#F3E8FF 0%,#EDE9FE 55%,#E0E7FF 100%);
    display:flex;align-items:center;gap:.75rem;position:relative;overflow:hidden;
  }
  .edash-event-card::after{
    content:"";position:absolute;right:-20px;top:-30px;width:120px;height:120px;border-radius:50%;
    background:rgba(255,255,255,.35);pointer-events:none;
  }
  .edash-back{
    width:38px;height:38px;border-radius:50%;border:0;background:#fff;color:#4B5563;
    display:grid;place-items:center;flex:none;box-shadow:0 2px 8px rgba(15,23,42,.08);z-index:1;
  }
  .edash-back svg{width:16px;height:16px}
  .edash-event-text{min-width:0;flex:1;z-index:1}
  .edash-event-name{font-size:1.05rem;font-weight:800;color:#1E1B4B;line-height:1.15}
  .edash-event-sub{font-size:.72rem;color:#6B7280;font-weight:600;margin-top:.1rem}
  .edash-role{
    z-index:1;flex:none;font-size:.65rem;font-weight:800;letter-spacing:.04em;
    padding:.35rem .65rem;border-radius:999px;background:#6D28D9;color:#fff;
  }
  .edash-hello{
    display:flex;align-items:center;justify-content:space-between;gap:.75rem;
    margin:0 1rem 1rem;
  }
  .edash-hello-left{display:flex;align-items:center;gap:.65rem;min-width:0}
  .edash-hello .avatar{width:52px;height:52px;font-size:.95rem}
  .edash-greet{font-size:.78rem;color:#6B7280;font-weight:600}
  .edash-uname{font-size:1rem;font-weight:800;color:#1E1B4B;line-height:1.2}
  .edash-welcome{font-size:.75rem;color:#6B7280;margin-top:.1rem}
  .edash-pass-btn{
    flex:none;background:linear-gradient(135deg,#7C3AED,#5B21B6);color:#fff;border:0;
    border-radius:1rem;padding:.65rem .7rem;min-width:72px;text-align:center;
    box-shadow:0 8px 18px rgba(91,33,182,.28);
  }
  .edash-pass-btn .ic{width:1.25rem;height:1.25rem;display:block;margin:0 auto .15rem}
  .edash-pass-btn .t{font-size:.68rem;font-weight:800;line-height:1.1}
  .edash-pass-btn .s{font-size:.58rem;opacity:.85}
  .edash-section{margin:0 1rem 1rem}
  .edash-section-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.65rem}
  .edash-section-title{font-size:.95rem;font-weight:800;color:#1E1B4B}
  .edash-link{font-size:.75rem;font-weight:700;color:#7C3AED;background:none;border:0;padding:0}
  .edash-verify{
    display:inline-flex;align-items:center;gap:.3rem;font-size:.68rem;font-weight:700;
    padding:.28rem .55rem;border-radius:999px;
  }
  .edash-verify.warn{background:#FEF3C7;color:#B45309}
  .edash-verify.ok{background:#DCFCE7;color:#15803D}
  .edash-stats{display:grid;grid-template-columns:repeat(2,1fr);gap:.55rem}
  .edash-stat{
    background:#fff;border-radius:1rem;padding:.75rem .55rem .65rem;text-align:center;
    border:1px solid #EEF0F6;box-shadow:0 1px 4px rgba(15,23,42,.03);
  }
  button.edash-stat{
    width:100%;cursor:pointer;appearance:none;-webkit-appearance:none;
    font:inherit;color:inherit;display:block;
  }
  button.edash-stat:active{transform:scale(.98)}
  .edash-stat-icon{
    width:30px;height:30px;border-radius:10px;display:grid;place-items:center;margin:0 auto .35rem;
  }
  .edash-stat-icon svg{width:15px;height:15px}
  .edash-stat .n{font-size:.95rem;font-weight:800;color:#1E1B4B;line-height:1.1}
  .edash-stat .l{font-size:.62rem;color:#6B7280;font-weight:600;margin-top:.2rem}
  .edash-stat .progress{height:.3rem;margin-top:.45rem;background:#F1F5F9;border-radius:99px}
  .edash-stat .edash-stat-go{font-size:.62rem;font-weight:700;color:#16A34A;margin-top:.45rem}
  .edash-next{
    display:block;width:calc(100% - 2rem);margin:0 1rem .75rem;
    background:linear-gradient(145deg,#6D28D9 0%,#4F46E5 45%,#2563EB 100%);
    color:#fff;border-radius:1.15rem;padding:.95rem 1rem;border:0;text-align:left;
    box-shadow:0 10px 24px rgba(79,70,229,.28);
  }
  .edash-next-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.55rem}
  .edash-next-label{display:flex;align-items:center;gap:.35rem;font-size:.72rem;font-weight:700;opacity:.92}
  .edash-next-label svg{width:14px;height:14px}
  .edash-next-go{
    width:26px;height:26px;border-radius:50%;background:rgba(255,255,255,.2);
    display:grid;place-items:center;font-size:.9rem;line-height:1;
  }
  .edash-next-when{font-size:.7rem;opacity:.9;margin-bottom:.35rem}
  .edash-next-title{font-size:.95rem;font-weight:800;line-height:1.3;margin-bottom:.35rem}
  .edash-next-meta{font-size:.72rem;opacity:.88;line-height:1.4}
  .edash-next-venue{margin-top:.15rem}
  .edash-cert{
    background:#ECFDF5;border-radius:1.15rem;padding:.9rem 1rem;border:1px solid #D1FAE5;
    display:flex;flex-direction:column;margin:0 1rem 1rem;
  }
  .edash-cert .eyebrow{color:#059669;font-size:.68rem;margin-bottom:.45rem}
  .edash-cert-status{display:flex;align-items:center;gap:.4rem;margin-bottom:.35rem}
  .edash-cert-status .ic{width:1.35rem;height:1.35rem;color:#059669}
  .edash-cert-label{font-size:.9rem;font-weight:800;color:#065F46}
  .edash-cert-hint{font-size:.7rem;color:#047857;line-height:1.35;flex:1;margin-bottom:.65rem}
  .edash-cert-btn{
    border:0;border-radius:.75rem;padding:.55rem .5rem;font-size:.72rem;font-weight:800;
    background:#059669;color:#fff;width:100%;
  }
  .edash-cert-btn:disabled{opacity:.55}
  .edash-cert-btn.ready{background:#4B23D4}

  .topbar{background:#fff;border-bottom:1px solid #EEF0F6;padding:.85rem 1rem;display:flex;
    align-items:center;gap:.75rem;position:sticky;top:0;z-index:20}
  main.scroll{flex:1;overflow-y:auto;padding:1rem 1rem 6.5rem}
  .tabbar{position:fixed;left:50%;transform:translateX(-50%);bottom:0;width:100%;max-width:480px;
    background:#fff;border-top:1px solid #EEF0F6;display:flex;z-index:30;
    padding-bottom:env(safe-area-inset-bottom)}
  body.org-mode .tabbar{display:none}
  .tabbar button{flex:1;background:none;border:0;padding:.55rem 0 .5rem;color:#9AA0AE;font-size:.66rem;font-weight:700}
  .tabbar button .ic{display:block;margin:0 auto .18rem;width:1.35rem;height:1.35rem;stroke-width:1.6}
  .tabbar button.on{color:#4B23D4}
  .tabbar .center{position:relative}
  .tabbar .center .ring{width:48px;height:48px;border-radius:50%;background:#4B23D4;color:#fff;
    display:grid;place-items:center;margin:-1.2rem auto .1rem;box-shadow:0 8px 18px rgba(75,35,212,.35)}
  .tabbar .center .ring .ic{margin:0;width:1.4rem;height:1.4rem}

  /* stat tiles */
  .stat{flex:1;background:#fff;border:1px solid #EEF0F6;border-radius:1rem;padding:.75rem .6rem;text-align:center}
  .stat .n{font-weight:800;font-size:1rem}
  .stat .l{font-size:.66rem;color:var(--muted);font-weight:600}

  /* stamps */
  .slot{aspect-ratio:1;border-radius:50%;border:1.5px dashed #D8DAE5;background:#FAFAFE;
    display:grid;place-items:center;text-align:center;padding:.4rem}
  .slot.filled{border:0;background:transparent}
  .slot-label{font-size:.64rem;color:var(--muted);font-weight:600;text-align:center;margin-top:.3rem}
  .stamp-svg{width:100%;max-width:98px;color:#12855C;opacity:.95;mix-blend-mode:multiply;filter:url(#inkRough)}
  .stamp-svg text{font-family:"JetBrains Mono",monospace;font-weight:700;fill:currentColor}
  .stamp-arc{font-size:15px;letter-spacing:.07em}.stamp-date{font-size:13px}
  .stamp-short{font-family:"Plus Jakarta Sans",sans-serif!important;font-weight:800;font-size:52px;text-anchor:middle}

  #tab-passport{background:#F7F8FC}
  #tab-passport > .scroll{padding-top:.65rem;padding-bottom:7.5rem}
  .pp-top{
    display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem;
    padding:.75rem 1rem .35rem;background:#F7F8FC;flex:none;
  }
  .pp-top-title{font-size:1.15rem;font-weight:800;color:#1E1B4B;letter-spacing:-.02em;line-height:1.15}
  .pp-top-sub{font-size:.75rem;color:#6B7280;font-weight:600;margin-top:.15rem}
  .pp-top-count{
    flex:none;background:#fff;border:1px solid #EEF0F6;border-radius:1rem;padding:.45rem .7rem;text-align:center;
    box-shadow:0 1px 4px rgba(15,23,42,.04);
  }
  .pp-top-count .n{font-size:1rem;font-weight:800;color:#6D28D9;line-height:1}
  .pp-top-count .l{font-size:.58rem;font-weight:700;color:#6B7280;margin-top:.15rem;text-transform:uppercase;letter-spacing:.04em}
  .pp-hero{
    margin:0 1rem .85rem;padding:.95rem 1rem;border-radius:1.15rem;
    background:linear-gradient(120deg,#F3E8FF 0%,#EDE9FE 55%,#E0E7FF 100%);
    position:relative;overflow:hidden;
  }
  .pp-hero::after{
    content:"";position:absolute;right:-16px;bottom:-28px;width:110px;height:110px;border-radius:50%;
    background:rgba(255,255,255,.4);pointer-events:none;
  }
  .pp-hero-label{font-size:.68rem;font-weight:700;color:#6D28D9;letter-spacing:.06em;text-transform:uppercase;margin-bottom:.25rem}
  .pp-hero-text{font-size:.92rem;font-weight:800;color:#1E1B4B;line-height:1.3;position:relative;z-index:1}
  .pp-hero-meta{font-size:.72rem;color:#6B7280;font-weight:600;margin-top:.3rem;position:relative;z-index:1}
  .pp-progress{
    margin-top:.75rem;height:.4rem;background:rgba(255,255,255,.7);border-radius:99px;overflow:hidden;position:relative;z-index:1;
  }
  .pp-progress > span{display:block;height:100%;background:linear-gradient(90deg,#7C3AED,#4F46E5);border-radius:99px}
  .pp-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:.55rem;margin:0 1rem .85rem}
  .pp-stat{
    background:#fff;border:1px solid #EEF0F6;border-radius:1rem;padding:.7rem .5rem;text-align:center;
    box-shadow:0 1px 4px rgba(15,23,42,.03);
  }
  .pp-stat .n{font-size:.95rem;font-weight:800;color:#1E1B4B;line-height:1.1}
  .pp-stat .l{font-size:.6rem;color:#6B7280;font-weight:700;margin-top:.2rem}
  .pp-segs{
    display:grid;grid-template-columns:repeat(3,1fr);gap:.35rem;
    margin:0 1rem .85rem;padding:.3rem;background:#fff;border:1px solid #EEF0F6;border-radius:999px;
  }
  .pp-seg{
    border:0;background:transparent;border-radius:999px;padding:.5rem .35rem;
    font-size:.72rem;font-weight:800;color:#6B7280;
  }
  .pp-seg.on{background:#6D28D9;color:#fff;box-shadow:0 4px 12px rgba(109,40,217,.28)}
  .pp-grid{
    display:grid;grid-template-columns:repeat(3,1fr);gap:.65rem;
    padding:0 1rem 1rem;
  }
  .pp-card{
    background:#fff;border:1px solid #EEF0F6;border-radius:1.1rem;padding:.7rem .5rem .65rem;
    text-align:center;box-shadow:0 1px 4px rgba(15,23,42,.03);
  }
  .pp-card.filled{border-color:#DDD6FE;background:linear-gradient(180deg,#F5F3FF,#fff 60%)}
  .pp-card.reward{border-style:dashed}
  .pp-card.reward.ready{border-style:solid;border-color:#C4B5FD;background:#F5F3FF}
  .pp-slot{
    width:100%;aspect-ratio:1;max-width:88px;margin:0 auto .45rem;border-radius:50%;
    border:1.5px dashed #D8DAE5;background:#FAFAFE;display:grid;place-items:center;padding:.35rem;
  }
  .pp-card.filled .pp-slot{border:0;background:transparent;padding:0}
  .pp-card .pp-slot .ic{width:1.35rem;height:1.35rem;color:#9CA3AF}
  .pp-card .pp-slot .stamp-svg{max-width:100%;width:100%;color:#6D28D9}
  .pp-card-name{font-size:.68rem;font-weight:700;color:#4B5563;line-height:1.25;min-height:2em}
  .pp-card-code{font-size:.58rem;font-weight:800;color:#6D28D9;letter-spacing:.06em;margin-top:.15rem}
  .pp-empty{
    grid-column:1/-1;text-align:center;padding:2.2rem 1rem;color:#6B7280;font-size:.88rem;font-weight:600;
    background:#fff;border:1px dashed #E5E7EB;border-radius:1.15rem;
  }
  .pp-tip{
    margin:0 1rem 1rem;padding:.85rem 1rem;border-radius:1.15rem;
    background:linear-gradient(100deg,#F3E8FF,#EDE9FE);
    display:flex;align-items:flex-start;gap:.65rem;
  }
  .pp-tip-icon{
    width:32px;height:32px;border-radius:50%;background:#fff;color:#7C3AED;
    display:grid;place-items:center;flex:none;
  }
  .pp-tip-icon svg{width:16px;height:16px}
  .pp-tip-text{font-size:.78rem;font-weight:600;color:#5B21B6;line-height:1.4}
  .pp-sync{text-align:center;font-size:.72rem;font-weight:700;color:#7C3AED;margin:0 1rem .75rem}
  .pp-cta{
    position:fixed;left:50%;transform:translateX(-50%);bottom:calc(4.4rem + env(safe-area-inset-bottom));
    width:calc(100% - 2rem);max-width:calc(480px - 2rem);z-index:25;
  }
  .pp-cta .btn{
    width:100%;border:0;border-radius:1rem;padding:.95rem 1rem;font-weight:800;
    background:linear-gradient(135deg,#7C3AED,#4F46E5);color:#fff;
    box-shadow:0 10px 24px rgba(79,70,229,.28);
  }
  body.org-mode .pp-cta{display:none}

  /* sessions / schedule */
  .chip{border:1px solid #E3E5EE;background:#fff;border-radius:.7rem;padding:.4rem .7rem;font-size:.75rem;
    font-weight:700;color:#5A6172;white-space:nowrap}
  .chip.on{background:#4B23D4;border-color:#4B23D4;color:#fff}
  .sess{padding:.85rem 0;border-bottom:1px solid #F0F1F6}
  .sess:last-child{border-bottom:0}
  .locked{opacity:.62}

  #tab-schedule{background:#F7F8FC}
  #tab-schedule > .scroll{padding-top:.65rem}
  .sched-top{
    display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem;
    padding:.75rem 1rem .35rem;background:#F7F8FC;flex:none;
  }
  .sched-top-title{font-size:1.15rem;font-weight:800;color:#1E1B4B;letter-spacing:-.02em;line-height:1.15}
  .sched-top-sub{font-size:.75rem;color:#6B7280;font-weight:600;margin-top:.15rem}
  .sched-top-count{
    flex:none;background:#fff;border:1px solid #EEF0F6;border-radius:1rem;padding:.45rem .7rem;text-align:center;
    box-shadow:0 1px 4px rgba(15,23,42,.04);
  }
  .sched-top-count .n{font-size:1rem;font-weight:800;color:#6D28D9;line-height:1}
  .sched-top-count .l{font-size:.58rem;font-weight:700;color:#6B7280;margin-top:.15rem;text-transform:uppercase;letter-spacing:.04em}
  .sched-hero{
    margin:0 1rem .85rem;padding:.9rem 1rem;border-radius:1.15rem;
    background:linear-gradient(120deg,#F3E8FF 0%,#EDE9FE 55%,#E0E7FF 100%);
    position:relative;overflow:hidden;
  }
  .sched-hero::after{
    content:"";position:absolute;right:-18px;top:-24px;width:100px;height:100px;border-radius:50%;
    background:rgba(255,255,255,.4);pointer-events:none;
  }
  .sched-hero-label{font-size:.68rem;font-weight:700;color:#6D28D9;letter-spacing:.06em;text-transform:uppercase;margin-bottom:.25rem}
  .sched-hero-text{font-size:.88rem;font-weight:700;color:#1E1B4B;line-height:1.35;position:relative;z-index:1}
  .sched-hero-meta{font-size:.72rem;color:#6B7280;font-weight:600;margin-top:.25rem;position:relative;z-index:1}
  .sched-segs{
    display:grid;grid-template-columns:repeat(3,1fr);gap:.35rem;
    margin:0 1rem .75rem;padding:.3rem;background:#fff;border:1px solid #EEF0F6;border-radius:999px;
  }
  .sched-seg{
    border:0;background:transparent;border-radius:999px;padding:.5rem .35rem;
    font-size:.72rem;font-weight:800;color:#6B7280;
  }
  .sched-seg.on{background:#6D28D9;color:#fff;box-shadow:0 4px 12px rgba(109,40,217,.28)}
  .sched-days{
    display:flex;gap:.45rem;overflow-x:auto;padding:0 1rem .85rem;
    -webkit-overflow-scrolling:touch;scrollbar-width:none;
  }
  .sched-days::-webkit-scrollbar{display:none}
  .sched-day{
    flex:none;border:1px solid #EEF0F6;background:#fff;border-radius:1rem;
    padding:.55rem .75rem;min-width:4.6rem;text-align:center;
  }
  .sched-day .d{font-size:.95rem;font-weight:800;color:#1E1B4B;line-height:1}
  .sched-day .m{font-size:.62rem;font-weight:700;color:#6B7280;margin-top:.2rem;text-transform:uppercase}
  .sched-day.on{background:#6D28D9;border-color:#6D28D9;color:#fff}
  .sched-day.on .d,.sched-day.on .m{color:#fff}
  .sched-list{padding:0 1rem 1rem}
  .sched-limit{
    background:#FEF3C7;color:#92400E;border-radius:1rem;padding:.7rem .85rem;
    font-size:.78rem;font-weight:700;margin-bottom:.75rem;
  }
  .sched-card{
    background:#fff;border:1px solid #EEF0F6;border-radius:1.15rem;padding:.9rem .95rem;
    margin-bottom:.65rem;display:flex;gap:.8rem;align-items:stretch;
    box-shadow:0 1px 4px rgba(15,23,42,.03);
  }
  .sched-card.joined{border-color:#BBF7D0;background:linear-gradient(180deg,#F0FDF4,#fff 55%)}
  .sched-time{
    flex:none;width:3.4rem;display:flex;flex-direction:column;align-items:center;justify-content:center;
    background:#F3E8FF;color:#6D28D9;border-radius:.9rem;padding:.55rem .3rem;text-align:center;
  }
  .sched-card.joined .sched-time{background:#DCFCE7;color:#15803D}
  .sched-time .h{font-size:.82rem;font-weight:800;line-height:1.1}
  .sched-time .to{font-size:.55rem;font-weight:700;opacity:.7;margin:.15rem 0}
  .sched-body{min-width:0;flex:1}
  .sched-status{
    display:inline-flex;align-items:center;gap:.25rem;font-size:.62rem;font-weight:800;
    padding:.2rem .5rem;border-radius:999px;margin-bottom:.35rem;
  }
  .sched-status.todo{background:#F3E8FF;color:#6D28D9}
  .sched-status.done{background:#DCFCE7;color:#15803D}
  .sched-title{font-size:.9rem;font-weight:800;color:#1E1B4B;line-height:1.3}
  .sched-meta{font-size:.72rem;color:#6B7280;font-weight:600;margin-top:.3rem;line-height:1.35}
  .sched-meta svg{width:12px;height:12px;vertical-align:-2px;margin-right:.2rem}
  .sched-empty{
    text-align:center;padding:2.5rem 1rem;color:#6B7280;font-size:.88rem;font-weight:600;
    background:#fff;border:1px dashed #E5E7EB;border-radius:1.15rem;
  }
  .sched-tip{
    margin:0 1rem 1rem;padding:.85rem 1rem;border-radius:1.15rem;
    background:linear-gradient(100deg,#F3E8FF,#EDE9FE);
    display:flex;align-items:flex-start;gap:.65rem;
  }
  .sched-tip-icon{
    width:32px;height:32px;border-radius:50%;background:#fff;color:#7C3AED;
    display:grid;place-items:center;flex:none;
  }
  .sched-tip-icon svg{width:16px;height:16px}
  .sched-tip-text{font-size:.78rem;font-weight:600;color:#5B21B6;line-height:1.4}

  /* scanner */
  #scanModal .modal-dialog{max-width:480px;margin-left:auto;margin-right:auto}
  #scanModal .modal-content{
    background:linear-gradient(180deg,#1E1B4B 0%,#312E81 42%,#1E1B4B 100%);
    color:#fff;border:0;min-height:100%;
  }
  .scan-shell{display:flex;flex-direction:column;min-height:100dvh}
  .scan-head{
    display:flex;align-items:center;justify-content:space-between;gap:.75rem;
    padding:.9rem 1rem calc(.55rem + env(safe-area-inset-top));
  }
  .scan-head-text{min-width:0}
  .scan-kicker{
    display:inline-flex;align-items:center;gap:.3rem;font-size:.65rem;font-weight:800;
    letter-spacing:.06em;text-transform:uppercase;color:#C4B5FD;margin-bottom:.2rem;
  }
  .scan-title{font-size:1.15rem;font-weight:800;letter-spacing:-.02em;line-height:1.15;margin:0}
  .scan-sub{font-size:.75rem;color:rgba(255,255,255,.7);font-weight:600;margin-top:.15rem}
  .scan-close{
    width:40px;height:40px;border-radius:50%;border:0;background:rgba(255,255,255,.12);
    color:#fff;display:grid;place-items:center;flex:none;
  }
  .scan-close svg{width:18px;height:18px}
  .scan-body{flex:1;display:flex;flex-direction:column;padding:0 1rem 1rem;min-height:0}
  #scanStage{
    position:relative;flex:1;background:#0B1020;overflow:hidden;min-height:48vh;
    border-radius:1.35rem;border:1px solid rgba(196,181,253,.35);
    box-shadow:0 16px 40px rgba(0,0,0,.35);
  }
  #scanVideo{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
  .viewfinder{
    position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:68%;aspect-ratio:1;max-width:280px;pointer-events:none;
    box-shadow:0 0 0 9999px rgba(15,23,42,.38);border-radius:1.1rem;
  }
  .viewfinder i{position:absolute;width:28px;height:28px;border:3px solid #A78BFA;border-radius:8px}
  .viewfinder i:nth-child(1){top:-2px;left:-2px;border-right:0;border-bottom:0}
  .viewfinder i:nth-child(2){top:-2px;right:-2px;border-left:0;border-bottom:0}
  .viewfinder i:nth-child(3){bottom:-2px;left:-2px;border-right:0;border-top:0}
  .viewfinder i:nth-child(4){bottom:-2px;right:-2px;border-left:0;border-top:0}
  .scan-pulse{
    position:absolute;left:12%;right:12%;top:50%;height:2px;
    background:linear-gradient(90deg,transparent,#C4B5FD,transparent);
    animation:scanPulse 2s ease-in-out infinite;pointer-events:none;opacity:.85;
  }
  @keyframes scanPulse{
    0%{transform:translateY(-42%);opacity:.35}
    50%{transform:translateY(42%);opacity:1}
    100%{transform:translateY(-42%);opacity:.35}
  }
  .scan-panel{padding:.95rem 0 calc(.35rem + env(safe-area-inset-bottom));flex:none}
  .scan-status{
    text-align:center;font-size:.84rem;font-weight:700;color:rgba(255,255,255,.88);
    min-height:2.5rem;margin:0 0 .85rem;line-height:1.35;
  }
  .scan-recent-label{
    font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
    color:#C4B5FD;margin-bottom:.45rem;
  }
  .scan-recent-item{
    display:flex;justify-content:space-between;align-items:center;gap:.75rem;
    background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1);
    border-radius:1rem;padding:.7rem .85rem;margin-bottom:.45rem;
  }
  .scan-recent-item .n{font-size:.85rem;font-weight:700}
  .scan-recent-item .t{font-size:.72rem;color:rgba(255,255,255,.65);font-weight:600;display:flex;align-items:center;gap:.3rem}
  .scan-recent-item .t svg{width:14px;height:14px;color:#A78BFA}
  .scan-actions{display:grid;gap:.55rem;margin-top:.75rem}
  .scan-btn{
    border:0;border-radius:1rem;padding:.9rem 1rem;font-weight:800;font-size:.9rem;
  }
  .scan-btn.primary{
    background:linear-gradient(135deg,#7C3AED,#4F46E5);color:#fff;
    box-shadow:0 10px 24px rgba(79,70,229,.35);
  }
  .scan-btn.ghost{
    background:rgba(255,255,255,.1);color:#fff;border:1px solid rgba(255,255,255,.16);
  }
  .collect-sheet .modal-content{
    border:0;border-radius:1.4rem;overflow:hidden;
  }
  .collect-sheet .modal-header{
    background:linear-gradient(120deg,#F3E8FF,#EDE9FE);border:0;padding:1rem 1.15rem .35rem;
  }
  .collect-sheet .modal-title{font-weight:800;color:#1E1B4B}
  .collect-sheet .modal-body{padding:1rem 1.15rem 1.25rem}
  .collect-sheet .collect-hint{font-size:.84rem;color:#6B7280;font-weight:600;margin-bottom:.85rem}
  .collect-sheet #boothCode{
    border-radius:1rem;border-color:#E9D5FF;background:#FAF5FF;
    letter-spacing:.3em;font-size:1.3rem;text-align:center;text-transform:uppercase;
    padding:.95rem .75rem;
  }
  .collect-sheet #boothCode:focus{
    border-color:#7C3AED;box-shadow:0 0 0 3px rgba(124,58,237,.18);
  }
  .collect-sheet .btn-collect{
    margin-top:.85rem;border:0;border-radius:1rem;padding:.9rem 1rem;font-weight:800;width:100%;
    background:linear-gradient(135deg,#7C3AED,#4F46E5);color:#fff;
    box-shadow:0 8px 18px rgba(79,70,229,.28);
  }
  .booth-dir-top{
    display:flex;align-items:center;gap:.65rem;
    padding:.75rem 1rem .45rem;background:#F7F8FC;flex:none;
  }
  .booth-dir-back{
    width:38px;height:38px;border-radius:50%;border:0;background:#fff;color:#4B5563;
    display:grid;place-items:center;flex:none;box-shadow:0 1px 4px rgba(15,23,42,.06);
  }
  .booth-dir-back svg{width:16px;height:16px}
  .booth-dir-title{font-size:1.1rem;font-weight:800;color:#1E1B4B;line-height:1.15}
  .booth-dir-sub{font-size:.72rem;color:#6B7280;font-weight:600;margin-top:.1rem}
  #tab-booths{background:#F7F8FC}
  #tab-booths > .scroll{padding-top:.55rem}
  .booth-dir-hero{
    margin:0 1rem .85rem;padding:.9rem 1rem;border-radius:1.15rem;
    background:linear-gradient(120deg,#F3E8FF 0%,#EDE9FE 55%,#E0E7FF 100%);
  }
  .booth-dir-hero-label{font-size:.68rem;font-weight:700;color:#6D28D9;letter-spacing:.06em;text-transform:uppercase;margin-bottom:.25rem}
  .booth-dir-hero-text{font-size:.9rem;font-weight:800;color:#1E1B4B;line-height:1.3}
  .booth-dir-hero-meta{font-size:.72rem;color:#6B7280;font-weight:600;margin-top:.25rem}
  .booth-floor-btn{
    display:none;width:100%;margin:.75rem 0 0;border:0;border-radius:.9rem;padding:.7rem .9rem;
    font-size:.8rem;font-weight:800;color:#fff;
    background:linear-gradient(135deg,#0F766E,#0D9488);
    box-shadow:0 6px 14px rgba(13,148,136,.22);
    align-items:center;justify-content:center;gap:.45rem;
  }
  .booth-floor-btn.on{display:inline-flex}
  .booth-floor-btn svg{width:16px;height:16px}
  .booth-dir-list{padding:0 1rem 1rem}
  .booth-card{
    background:#fff;border:1px solid #EEF0F6;border-radius:1.2rem;padding:0;margin-bottom:.75rem;
    overflow:hidden;box-shadow:0 1px 4px rgba(15,23,42,.04);
  }
  .booth-card-head{
    display:flex;gap:.75rem;align-items:flex-start;padding:.9rem .95rem .7rem;
  }
  .booth-card-logo{
    width:52px;height:52px;border-radius:14px;flex:none;overflow:hidden;
    background:linear-gradient(135deg,#7C3AED,#4F46E5);color:#fff;
    display:grid;place-items:center;font-weight:800;font-size:.8rem;
  }
  .booth-card-logo img{width:100%;height:100%;object-fit:cover;display:block}
  .booth-card-main{min-width:0;flex:1}
  .booth-card-name{font-size:.98rem;font-weight:800;color:#1E1B4B;line-height:1.25}
  .booth-card-code{font-size:.72rem;font-weight:700;color:#7C3AED;margin-top:.2rem;letter-spacing:.04em}
  .booth-card-badge{
    flex:none;font-size:.62rem;font-weight:800;padding:.25rem .55rem;border-radius:999px;
  }
  .booth-card-badge.open{background:#F3E8FF;color:#6D28D9}
  .booth-card-badge.done{background:#EDE9FE;color:#5B21B6}
  .booth-card-badge.off{background:#F3F4F6;color:#9CA3AF}
  .booth-card-body{padding:0 .95rem .95rem}
  .booth-card-desc{
    font-size:.8rem;color:#4B5563;font-weight:600;line-height:1.45;margin:0 0 .65rem;
  }
  .booth-card-meta{display:flex;flex-wrap:wrap;gap:.4rem}
  .booth-chip{
    display:inline-flex;align-items:center;gap:.3rem;font-size:.68rem;font-weight:700;
    padding:.28rem .55rem;border-radius:999px;background:#F8FAFC;color:#4B5563;border:1px solid #EEF0F6;
  }
  .booth-chip svg{width:12px;height:12px;opacity:.8}
  .booth-card-actions{display:flex;gap:.5rem;margin-top:.75rem}
  .booth-vote-btn{
    flex:1;border:0;border-radius:.85rem;padding:.65rem .9rem;font-size:.8rem;font-weight:800;
    background:linear-gradient(135deg,#7C3AED,#4F46E5);color:#fff;
    box-shadow:0 6px 14px rgba(79,70,229,.22);
  }
  .booth-vote-btn:disabled{opacity:.55;box-shadow:none}
  .booth-vote-done{
    flex:1;text-align:center;border-radius:.85rem;padding:.65rem .9rem;font-size:.8rem;font-weight:800;
    background:#EDE9FE;color:#5B21B6;
  }
  .booth-vote-locked{
    flex:1;text-align:center;border-radius:.85rem;padding:.65rem .9rem;font-size:.8rem;font-weight:700;
    background:#F3F4F6;color:#9CA3AF;
  }
  .booth-dir-empty{
    text-align:center;padding:2.2rem 1rem;color:#6B7280;font-size:.88rem;font-weight:600;
    background:#fff;border:1px dashed #E5E7EB;border-radius:1.15rem;
  }

  .floor-plan-lightbox{
    display:none;position:fixed;inset:0;z-index:20000;
    background:rgba(15,23,42,.72);padding:1rem;
    align-items:center;justify-content:center;
  }
  .floor-plan-lightbox.on{display:flex}
  .floor-plan-lightbox-card{
    width:min(400px,92vw);max-height:78vh;background:#fff;border-radius:1.1rem;
    overflow:hidden;box-shadow:0 20px 50px rgba(15,23,42,.35);
    display:flex;flex-direction:column;
  }
  .floor-plan-lightbox-head{
    display:flex;align-items:center;justify-content:space-between;gap:.75rem;
    padding:.75rem .9rem;border-bottom:1px solid #EEF0F6;flex:none;
  }
  .floor-plan-lightbox-head strong{font-size:.9rem;font-weight:800;color:#1E1B4B}
  .floor-plan-lightbox-close{
    width:34px;height:34px;border:0;border-radius:50%;background:#F3F4F6;color:#4B5563;
    display:grid;place-items:center;flex:none;
  }
  .floor-plan-lightbox-close svg{width:16px;height:16px}
  .floor-plan-lightbox-body{
    padding:.75rem;overflow:auto;-webkit-overflow-scrolling:touch;background:#F8FAFC;
    display:grid;place-items:center;min-height:0;
  }
  .floor-plan-lightbox-body img{
    width:100%;max-width:100%;height:auto;max-height:58vh;
    object-fit:contain;display:block;border-radius:.5rem;background:#fff;
  }
  #landingModal .stamp-svg{max-width:220px;animation:slam .5s cubic-bezier(.2,1.4,.4,1) both}
  @keyframes slam{0%{transform:scale(2.5) rotate(-16deg);opacity:0}55%{transform:scale(.94) rotate(var(--rot,-6deg));opacity:1}
    72%{transform:scale(1.05) rotate(var(--rot,-6deg))}100%{transform:scale(1) rotate(var(--rot,-6deg));opacity:1}}
  #passQR .mexco-qr-canvas img,
  #passQR .mexco-qr-canvas canvas,
  #myPassQR .mexco-qr-canvas img,
  #myPassQR .mexco-qr-canvas canvas{width:210px!important;height:210px!important}
  #passQR .mexco-qr-logo,
  #myPassQR .mexco-qr-logo{
    width:46px!important;height:46px!important;max-width:22%!important;max-height:22%!important;
  }
  .qr-card{border:1.5px solid #171A24;border-radius:.9rem;background:#fff;padding:.7rem .5rem;text-align:center;break-inside:avoid;page-break-inside:avoid}
  .qr-card .mexco-qr-wrap .mexco-qr-canvas{padding:6px}
  .qr-card .booth-print-name{font-size:.82rem;font-weight:800;line-height:1.25;margin-top:.65rem;min-height:2.4em}
  .qr-card .booth-print-numbers{display:flex;align-items:center;justify-content:center;gap:.55rem;margin-top:.45rem}
  .qr-card .booth-print-code{font-family:var(--bs-font-monospace);font-weight:800;font-size:.9rem;color:#4B23D4;letter-spacing:.1em}
  .qr-card .booth-print-mark{font-family:var(--bs-font-monospace);font-weight:800;font-size:1.05rem;border:1.5px solid #171A24;border-radius:6px;padding:.08rem .35rem;letter-spacing:.08em}

  /* certificate A4 */
  .cert-stage{width:100%;overflow:hidden}
  #certScaler{transform-origin:top left}
  .cert-page{width:297mm;height:210mm;box-sizing:border-box;background:#fff;position:relative;
    padding:0;display:block;overflow:hidden;
    border:1.6mm solid #171A24;box-shadow:0 6px 30px rgba(23,26,36,.14)}
  .cert-page::after{content:"";position:absolute;inset:4mm;border:.4mm solid #8C7BFF;pointer-events:none;z-index:2}
  .cert-page.has-bg{border:none;box-shadow:0 6px 30px rgba(23,26,36,.14)}
  .cert-page.has-bg::after{display:none}
  .cert-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;display:none;z-index:0}
  .cert-page.has-bg .cert-bg{display:block}
  .cert-content{position:relative;z-index:2;width:100%;height:100%;box-sizing:border-box;
    padding:36mm 28mm 24mm;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;text-align:center}
  /* Layout: title → lead → name → IC → body → event → date; footer pinned below */
  .cert-title{font-size:28pt;font-weight:800;letter-spacing:.06em;text-transform:uppercase;line-height:1.05;margin:0 0 2mm;color:#1A237E}
  .cert-lead{font-size:9.5pt;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:#1A237E;margin:4mm 0 3mm}
  .cert-name{font-size:30pt;font-weight:700;font-family:Georgia,"Times New Roman",serif;font-style:italic;line-height:1.15;margin:0 0 2mm;color:#171A24}
  .cert-rule{width:70mm;height:0;border-top:.45mm solid #C9A227;margin:2mm auto 3mm;position:relative}
  .cert-rule::before{content:"";position:absolute;left:50%;top:-1.6mm;transform:translateX(-50%);width:10mm;height:2.2mm;
    background:radial-gradient(circle closest-side,#C9A227 70%,transparent 71%) 0/3.2mm 2.2mm,
               radial-gradient(circle closest-side,#C9A227 70%,transparent 71%) 3.4mm/3.2mm 2.2mm,
               radial-gradient(circle closest-side,#C9A227 70%,transparent 71%) 6.8mm/3.2mm 2.2mm;background-repeat:no-repeat}
  .cert-ic{font-size:11pt;font-weight:600;letter-spacing:.04em;color:#1A237E;margin:1mm 0}
  .cert-connector,.cert-body{font-size:9pt;font-style:italic;color:#5A6A9A;margin:2mm 0 1mm}
  .cert-event-name{font-size:13pt;font-weight:700;color:#171A24;line-height:1.3;max-width:180mm;margin:0 0 1mm}
  .cert-date{font-size:12pt;font-weight:700;color:#1A237E;margin:0}
  .cert-footer{position:absolute;left:10mm;right:10mm;bottom:7mm;z-index:3;width:auto;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-end;gap:1.2mm;
    font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8pt;color:#6B7280;letter-spacing:.02em;margin:0;padding:0}
  .cert-issued{text-align:left}
  .cert-code{text-align:left;font-weight:700;color:#374151}
  /* Background image sits under all form text - every field overlays the image */
  .cert-page.has-bg .cert-content{padding:36mm 32mm 26mm}
  .cert-page.has-bg .cert-title,
  .cert-page.has-bg .cert-lead,
  .cert-page.has-bg .cert-name,
  .cert-page.has-bg .cert-ic,
  .cert-page.has-bg .cert-body,
  .cert-page.has-bg .cert-connector,
  .cert-page.has-bg .cert-event-name,
  .cert-page.has-bg .cert-date{
    text-shadow:0 1px 2px rgba(255,255,255,.55)
  }
  .cert-page.has-bg .cert-footer{left:8%;right:8%;bottom:5.5%;color:#4B5563}

  /* organiser */
  .org-side{background:#171A24;color:#C9CDD8;border-radius:1rem;padding:.6rem}
  .org-side button{width:100%;text-align:left;background:none;border:0;color:#C9CDD8;padding:.6rem .8rem;
    border-radius:.7rem;font-size:.85rem;font-weight:600}
  .org-side button.on{background:#4B23D4;color:#fff}
  table.tbl{width:100%;border-collapse:collapse;font-size:.82rem}
  table.tbl th{font-size:.68rem;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);
    text-align:left;padding:.6rem .5rem;border-bottom:1px solid #EEF0F6;white-space:nowrap}
  table.tbl td{padding:.6rem .5rem;border-bottom:1px solid #F3F4F9;vertical-align:middle}

  @media print{
    body{background:#fff}.app-shell{max-width:100%;box-shadow:none;min-height:0}
    .topbar,.tabbar,.no-print,#view-splash,#view-welcome,#view-events,#app-tabs,#view-org,.modal,.modal-backdrop{display:none!important}
    #view-cert{display:block!important;padding:0!important}
    .cert-stage{overflow:visible!important;height:auto!important}#certScaler{transform:none!important}
    .cert-page{box-shadow:none;margin:0}
    body.printing-qr #view-cert{display:none!important}
    body.printing-qr #view-org{display:block!important}
    body.printing-qr .topbar,body.printing-qr .org-side,body.printing-qr .org-pane:not(#pane-booths),
    body.printing-qr #pane-booths .no-print,body.printing-qr #pane-booths textarea,
    body.printing-qr #pane-booths .d-flex.gap-2{display:none!important}
    body.printing-qr #pane-booths h5,body.printing-qr #pane-booths>p{display:none!important}
    body.printing-qr #qrGrid{display:grid!important;grid-template-columns:repeat(3,1fr);gap:12px;padding:12px}
    body.printing-qr .qr-card{border:1.5px solid #171A24}
  }
  @media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;transition-duration:.05ms!important}}
</style>
</head>
<body>
<svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs>
  <g id="i-logo"><path d="M3 16V6.5c0-1.4 1.7-2 2.6-1L10 10l4.4-4.5c.9-1 2.6-.4 2.6 1V16"/></g>
  <g id="i-mail"><rect x="2.5" y="4.5" width="15" height="11" rx="2"/><path d="M3 6l7 5 7-5"/></g>
  <g id="i-id"><rect x="2.5" y="4.5" width="15" height="11" rx="2"/><circle cx="7" cy="9.5" r="1.8"/><path d="M4.4 13.6c.5-1.3 1.4-2 2.6-2s2.1.7 2.6 2M12 8.5h3.5M12 11.5h3.5"/></g>
  <g id="i-search"><circle cx="9" cy="9" r="5.5"/><path d="M13.2 13.2L17 17"/></g>
  <g id="i-check"><path d="M4.5 10.5l3.5 3.5 7.5-8"/></g>
  <g id="i-check-circle"><circle cx="10" cy="10" r="7.5"/><path d="M6.5 10.2l2.6 2.6 4.6-5"/></g>
  <g id="i-x"><path d="M5 5l10 10M15 5L5 15"/></g>
  <g id="i-back"><path d="M11.5 4.5L6 10l5.5 5.5"/></g>
  <g id="i-home"><path d="M3.5 9L10 3.6 16.5 9"/><path d="M5.2 8.4v7.9h9.6V8.4"/></g>
  <g id="i-passport"><rect x="4" y="2.8" width="12" height="14.4" rx="2"/><circle cx="10" cy="8.4" r="2.4"/><path d="M7.6 13.6h4.8"/></g>
  <g id="i-scan"><path d="M3 7V4.5A1.5 1.5 0 014.5 3H7M13 3h2.5A1.5 1.5 0 0117 4.5V7M17 13v2.5a1.5 1.5 0 01-1.5 1.5H13M7 17H4.5A1.5 1.5 0 013 15.5V13"/><path d="M6.5 10h7"/></g>
  <g id="i-calendar"><rect x="3" y="4.5" width="14" height="12.5" rx="2"/><path d="M3 8.2h14M7 3v3M13 3v3"/></g>
  <g id="i-user"><circle cx="10" cy="7" r="3.2"/><path d="M4 16.5c.9-3 3.2-4.5 6-4.5s5.1 1.5 6 4.5"/></g>
  <g id="i-people"><circle cx="8" cy="7.5" r="2.8"/><path d="M3 16c.8-2.6 2.7-3.9 5-3.9s4.2 1.3 5 3.9"/><path d="M13.5 5.2a2.8 2.8 0 010 5.4M14.5 12.6c1.6.4 2.7 1.6 3.2 3.4"/></g>
  <g id="i-booth"><path d="M3 8.5h14V16H3z"/><path d="M3.6 4.2h12.8L17 8.5H3z"/><path d="M8 16v-4h4v4"/></g>
  <g id="i-award"><circle cx="10" cy="8" r="4.6"/><path d="M7.2 12.2L6 17.5l4-2 4 2-1.2-5.3"/></g>
  <g id="i-gift"><rect x="3" y="8" width="14" height="8.5" rx="1.5"/><path d="M2.4 8h15.2M10 8v8.5M10 8S8.6 3.5 6.8 4.5 8.6 8 10 8zM10 8s1.4-4.5 3.2-3.5S11.4 8 10 8z"/></g>
  <g id="i-qr"><rect x="3" y="3" width="5.5" height="5.5" rx="1"/><rect x="11.5" y="3" width="5.5" height="5.5" rx="1"/><rect x="3" y="11.5" width="5.5" height="5.5" rx="1"/><path d="M11.5 11.5h2.5v2.5h-2.5zM17 17h-2.5M17 11.5v2.5"/></g>
  <g id="i-lock"><rect x="4.5" y="8.6" width="11" height="8" rx="2"/><path d="M7 8.6V6.4a3 3 0 016 0v2.2"/></g>
  <g id="i-download"><path d="M10 3v9M6.5 8.8l3.5 3.5 3.5-3.5"/><path d="M3.5 15.5h13"/></g>
  <g id="i-printer"><path d="M6 7.5V3.5h8v4"/><rect x="3" y="7.5" width="14" height="6" rx="1.6"/><path d="M6 11.5h8v5H6z"/></g>
  <g id="i-chat"><path d="M17 9.5c0 3.4-3.1 6.2-7 6.2-.9 0-1.7-.1-2.5-.4L3.5 17l1.2-3.2A5.9 5.9 0 013 9.5c0-3.4 3.1-6.2 7-6.2s7 2.8 7 6.2z"/></g>
  <g id="i-refresh"><path d="M16.5 10a6.5 6.5 0 11-2-4.7"/><path d="M16.8 3.2v3.4h-3.4"/></g>
  <g id="i-gear"><circle cx="10" cy="10" r="2.6"/><path d="M10 2.6v2M10 15.4v2M17.4 10h-2M4.6 10h-2M15.2 4.8l-1.4 1.4M6.2 13.8l-1.4 1.4M15.2 15.2l-1.4-1.4M6.2 6.2L4.8 4.8"/></g>
  <g id="i-dots"><circle cx="5" cy="10" r="1.2"/><circle cx="10" cy="10" r="1.2"/><circle cx="15" cy="10" r="1.2"/></g>
  <g id="i-out"><path d="M9 3.5H4.5v13H9"/><path d="M12.5 6.8l3.2 3.2-3.2 3.2M15.5 10H8"/></g>
  <g id="i-info"><circle cx="10" cy="10" r="7.5"/><path d="M10 9.2v4.4M10 6.6v.6"/></g>
  <g id="i-report"><path d="M5 3.5h7l3 3v10H5z"/><path d="M7.5 11v3M10 9v5M12.5 12v2"/></g>
  <g id="i-headset"><path d="M4 11.5a6 6 0 0112 0"/><path d="M4 11.5v2.8a2 2 0 002 2h1.6v-4.8H5.2a1.2 1.2 0 00-1.2 1.2zM16 11.5v2.8a2 2 0 01-2 2h-1.6v-4.8h1.6a1.2 1.2 0 011.2 1.2z"/><path d="M10 17.8v1.7M8.2 19.5h3.6"/></g>
</defs></svg>
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
  <filter id="inkRough"><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="3" result="n"/>
  <feDisplacementMap in="SourceGraphic" in2="n" scale="2" xChannelSelector="R" yChannelSelector="G"/></filter>
</svg>
<div class="app-shell" id="shell">

  <!-- ============ SPLASH ============ -->
  <section id="view-splash" class="mexco-backdrop" aria-label="Loading">
    <?php echo $__env->make('partials.mexco-pass-deco', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>
    <img src="<?php echo e(asset(config('mexco.logos.main'))); ?>" alt="<?php echo e(config('mexco.apps.pass.name')); ?>" class="pass-logo-full pass-logo-full--splash">
  </section>

  <!-- ============ 0. HOME HUB (after login) ============ -->
  <section id="view-events" class="d-none flex-column flex-grow-1 hub-screen">
    <header class="hub-topbar">
      <div class="hub-brand">
        <img src="<?php echo e(asset(config('mexco.logos.app_icon'))); ?>" alt="MeXCo" class="hub-brand-logo">
        <span class="hub-brand-name">MeXCo</span>
      </div>
      <div class="hub-top-actions">
        <button type="button" class="hub-avatar" id="evProfileBtn" aria-label="Profile" title="Profile">SI</button>
      </div>
    </header>

    <main class="hub-scroll" id="hubHomePane">
      <div class="hub-welcome">
        <div class="hub-welcome-text">
          <div class="hub-welcome-kicker">MeXCo Pass</div>
          <div class="hub-hello" id="evGreet">Hello</div>
          <div class="hub-sub" id="evLead">Choose an event to open your passport.</div>
        </div>
        <div class="hub-welcome-badge" aria-hidden="true">
          <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><rect x="5" y="3" width="14" height="18" rx="2"/><circle cx="12" cy="10" r="3"/><path d="M8 17h8"/></svg>
        </div>
      </div>

      <div class="hub-stats" id="hubStats">
        <button type="button" class="hub-stat" data-hub-tab="upcoming">
          <span class="hub-stat-icon" style="background:#F3E8FF;color:#7C3AED">
            <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg>
          </span>
          <span class="hub-stat-n" id="statUpcoming">0</span>
          <span class="hub-stat-l">Upcoming</span>
        </button>
        <button type="button" class="hub-stat" data-hub-tab="ongoing">
          <span class="hub-stat-icon" style="background:#EDE9FE;color:#6D28D9">
            <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M8.5 12.5l2.5 2.5 4.5-5"/></svg>
          </span>
          <span class="hub-stat-n" id="statOngoing">0</span>
          <span class="hub-stat-l">Ongoing</span>
        </button>
        <button type="button" class="hub-stat" data-hub-tab="past">
          <span class="hub-stat-icon" style="background:#DBEAFE;color:#2563EB">
            <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M8 7V4h8v3"/><path d="M4 7h16v12a2 2 0 01-2 2H6a2 2 0 01-2-2V7z"/><path d="M9 12h6"/></svg>
          </span>
          <span class="hub-stat-n" id="statPast">0</span>
          <span class="hub-stat-l">Past</span>
        </button>
      </div>

      <div class="hub-segs" id="hubTabs" role="tablist">
        <button type="button" class="hub-tab on" data-hub-tab="upcoming" role="tab">Upcoming</button>
        <button type="button" class="hub-tab" data-hub-tab="ongoing" role="tab">Ongoing</button>
        <button type="button" class="hub-tab" data-hub-tab="past" role="tab">Past</button>
      </div>

      <div id="eventList" class="hub-event-feed"></div>

      <div class="hub-tip" id="evFoot">
        <div class="hub-tip-icon" aria-hidden="true">
          <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v5"/><circle cx="12" cy="16.5" r=".8" fill="currentColor"/></svg>
        </div>
        <span>Tap an event to open your digital passport and start exploring.</span>
      </div>
    </main>

    <main class="hub-scroll d-none" id="hubProfilePane">
      <div class="hub-profile-card">
        <div class="hub-avatar hub-avatar--lg" id="hubProfileAvatar">?</div>
        <div class="fw-800 mt-2" id="hubProfileName" style="font-size:1.1rem"></div>
        <div class="text-secondary" id="hubProfileEmail" style="font-size:.85rem"></div>
        <button type="button" class="btn-signout" id="evSignOut">Sign out</button>
        <button type="button" class="btn-back" id="evBack">Back to home</button>
      </div>
    </main>

  </section>

  <!-- ============ 1. LOGIN / WELCOME ============ -->
  <section id="view-welcome" class="login-screen mexco-backdrop">
    <?php echo $__env->make('partials.mexco-pass-deco', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>
    <div class="login-top">
      <img src="<?php echo e(asset(config('mexco.logos.main'))); ?>" alt="<?php echo e(config('mexco.apps.pass.name')); ?>" class="pass-logo-full">
      <div class="login-featured" id="heroEvent" aria-hidden="true"></div>
    </div>
    <div class="login-body">
      <div class="login-card">
        <div class="login-field">
          <label for="loginEmail">Email address</label>
          <div class="login-input-wrap">
            <svg class="field-icon" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>
            <input id="loginEmail" type="email" inputmode="email" placeholder="name@email.com" autocomplete="username">
          </div>
        </div>
        <div class="login-field">
          <label for="loginIc">IC number</label>
          <div class="login-input-wrap">
            <svg class="field-icon" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="11" r="2"/><path d="M6 15c.8-1.6 1.8-2.4 3-2.4s2.2.8 3 2.4M15 9h4M15 12h4"/></svg>
            <input id="loginIc" type="text" inputmode="numeric" class="mono" placeholder="Your IC number" autocomplete="current-password">
          </div>
        </div>
        <div class="login-err" id="loginErr"></div>
        <button type="button" class="btn btn-mexco w-100" id="btnLogin">Sign In <span class="arrow" aria-hidden="true">→</span></button>
      </div>
      <?php
        $passDemo = config('mexco.demo_sandbox');
        $passVisitor = collect($passDemo['participants'] ?? [])->firstWhere('role', 'VISITOR')
          ?? collect($passDemo['participants'] ?? [])->first();
      ?>
      <?php if($passVisitor): ?>
        <div class="login-demo" id="loginDemoBox">
          <div class="login-demo-kicker">Demo account</div>
          <div class="login-demo-row"><span>Email</span><code><?php echo e($passVisitor['email']); ?></code></div>
          <div class="login-demo-row"><span>IC / ID</span><code><?php echo e($passVisitor['ic']); ?></code></div>
          <div class="login-demo-row"><span>Event</span><code><?php echo e($passDemo['event_id'] ?? 'mexcodemo'); ?></code></div>
          <button type="button" class="login-demo-btn" id="btnUseDemoAccount"
            data-email="<?php echo e($passVisitor['email']); ?>"
            data-ic="<?php echo e($passVisitor['ic']); ?>">
            Use demo visitor account
          </button>
        </div>
      <?php endif; ?>
      <div class="login-footer">
        <div class="login-footer-inner">
          <div class="login-footer-icon">
            <svg fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" viewBox="0 0 24 24"><path d="M4 12a8 8 0 0116 0"/><path d="M4 12v2a2 2 0 002 2h1.5V10H6a2 2 0 00-2 2zM20 12v2a2 2 0 01-2 2h-1.5V10H18a2 2 0 012 2z"/><path d="M10 18.5v1.5M8 20h4"/></svg>
          </div>
          <div class="login-footer-title">Don't have access yet?</div>
          <div class="login-footer-sub">Please contact the organiser.</div>
        </div>
      </div>
    </div>
  </section>

  <!-- ============ 2. EVENT PASSPORT (inside an event) ============ -->
  <div id="app-tabs" class="d-none flex-column flex-grow-1">
    <!-- HOME -->
    <section id="tab-home" class="flex-column flex-grow-1 d-none">
      <div class="edash-top no-print">
        <div class="edash-brand">
          <img src="<?php echo e(asset(config('mexco.logos.app_icon'))); ?>" alt="MeXCo">
          <span>MeXCo</span>
        </div>
        <div class="edash-top-actions">
          <button type="button" class="edash-avatar" id="edashTopAvatar" aria-label="Profile">?</button>
        </div>
      </div>
      <main class="scroll">
        <div class="edash-event-card">
          <button type="button" class="edash-back" id="btnBackDashboard" aria-label="Back to dashboard">
            <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6"/></svg>
          </button>
          <div class="edash-event-text">
            <div class="edash-event-name" id="eventBackName">Event</div>
            <div class="edash-event-sub">Event Dashboard</div>
          </div>
          <span class="edash-role" id="hmRole">VISITOR</span>
        </div>
        <div class="edash-banner d-none" id="edashBanner">
          <img id="edashBannerImg" alt="Event banner">
          <div class="edash-banner-fade"></div>
          <div class="edash-banner-caption" id="edashBannerCaption"></div>
        </div>

        <div class="edash-hello">
          <div class="edash-hello-left">
            <div class="avatar" id="hmAvatar"></div>
            <div class="min-w-0">
              <div class="edash-greet" id="hmGreet">Good evening,</div>
              <div class="edash-uname text-truncate" id="hmName">Participant</div>
              <div class="edash-welcome text-truncate" id="hmWelcome">Welcome</div>
            </div>
          </div>
          <button type="button" class="edash-pass-btn" id="btnMyPass">
            <svg class="ic"><use href="#i-qr"/></svg>
            <div class="t">My Pass</div>
            <div class="s">Tap to view ›</div>
          </button>
        </div>
        <div class="d-none" id="hmEvent"></div>

        <div class="edash-section">
          <div class="edash-section-head">
            <div class="edash-section-title">Today's Progress</div>
            <span class="edash-verify warn" id="verifyPill">Not verified</span>
          </div>
          <div class="edash-stats">
            <button type="button" class="edash-stat" id="btnProgressBooths" aria-label="Open booths">
              <div class="edash-stat-icon" style="background:#DCFCE7;color:#16A34A"><svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M3 9l2-5h14l2 5"/><path d="M3 9h18v10H3z"/></svg></div>
              <div class="n" id="stBooths">0</div>
              <div class="l">Booths</div>
              <div class="edash-stat-go">View directory ›</div>
            </button>
            <div class="edash-stat">
              <div class="edash-stat-icon" style="background:#FEF3C7;color:#D97706"><svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><circle cx="12" cy="9" r="4.5"/><path d="M8.5 13.5L7 20l5-2.5L17 20l-1.5-6.5"/></svg></div>
              <div class="n" id="stStamps">0</div>
              <div class="l" id="stStampsLabel">Stamps Required</div>
              <div class="progress"><div class="progress-bar" id="pbStamps" style="width:0%;background:#D97706"></div></div>
            </div>
          </div>
        </div>

        <button type="button" class="edash-next" id="btnNextSession">
          <div class="edash-next-head">
            <div class="edash-next-label">
              <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18"/></svg>
              Next Session
            </div>
            <span class="edash-next-go">›</span>
          </div>
          <div id="nextSession"></div>
        </button>

        <div class="edash-cert" id="edashCertBlock">
          <div class="eyebrow" id="edashCertEyebrow">My Certificate</div>
          <div class="edash-cert-status">
            <svg class="ic" id="certIcon"><use href="#i-lock"/></svg>
            <div class="edash-cert-label" id="certLabel">Locked</div>
          </div>
          <div class="edash-cert-hint" id="certHint">Verify your attendance first to unlock your certificate.</div>
          <button type="button" class="edash-cert-btn" id="btnCertOpen">Verify Attendance</button>
        </div>

      </main>
    </section>

    <!-- BOOTHS DIRECTORY -->
    <section id="tab-booths" class="flex-column flex-grow-1 d-none">
      <div class="booth-dir-top">
        <button type="button" class="booth-dir-back" id="btnBoothsBack" aria-label="Back to home">
          <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6"/></svg>
        </button>
        <div class="min-w-0">
          <div class="booth-dir-title">Booths</div>
          <div class="booth-dir-sub text-truncate" id="bbEvent">Event</div>
        </div>
      </div>
      <main class="scroll">
        <div class="booth-dir-hero">
          <div class="booth-dir-hero-label">Booth directory</div>
          <div class="booth-dir-hero-text" id="bbHeroText">Explore booths at this event</div>
          <div class="booth-dir-hero-meta" id="bbHeroMeta">Tap Scan on Passport to collect stamps</div>
          <button type="button" class="booth-floor-btn" id="btnFloorPlan">
            <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M4 6h16v12H4z"/><path d="M8 10h3v3H8zM13 10h3v3h-3zM8 15h8"/></svg>
            View floor plan
          </button>
        </div>
        <div class="booth-dir-list" id="boothDirList"></div>
      </main>
    </section>

    <!-- PASSPORT -->
    <section id="tab-passport" class="flex-column flex-grow-1 d-none">
      <div class="pp-top">
        <div class="min-w-0">
          <div class="pp-top-title">Booth Passport</div>
          <div class="pp-top-sub text-truncate" id="ppEvent">Event</div>
        </div>
        <div class="pp-top-count">
          <div class="n" id="ppCount">0/0</div>
          <div class="l">Stamps</div>
        </div>
      </div>
      <main class="scroll">
        <div class="pp-hero">
          <div class="pp-hero-label">Stamp progress</div>
          <div class="pp-hero-text" id="ppLead">Visit booths to collect stamps</div>
          <div class="pp-hero-meta" id="ppHeroMeta">Scan a booth QR to add a stamp</div>
          <div class="pp-progress" aria-hidden="true"><span id="ppProgressBar" style="width:0%"></span></div>
        </div>

        <div class="pp-stats">
          <div class="pp-stat">
            <div class="n" id="ppStatCollected">0</div>
            <div class="l">Collected</div>
          </div>
          <div class="pp-stat">
            <div class="n" id="ppStatRequired">0</div>
            <div class="l">Required</div>
          </div>
          <div class="pp-stat">
            <div class="n" id="ppStatLeft">0</div>
            <div class="l">Remaining</div>
          </div>
        </div>

        <div class="pp-segs" id="ppFilters">
          <button type="button" class="pp-seg on" data-pp="all">All</button>
          <button type="button" class="pp-seg" data-pp="got">Collected</button>
          <button type="button" class="pp-seg" data-pp="todo">Remaining</button>
        </div>

        <div class="pp-grid" id="grid"></div>

        <div class="pp-tip">
          <div class="pp-tip-icon" aria-hidden="true">
            <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v5"/><circle cx="12" cy="16.5" r=".8" fill="currentColor"/></svg>
          </div>
          <div class="pp-tip-text" id="ppNote">Visit booths and scan their QR codes to fill your passport.</div>
        </div>
        <div class="pp-sync" id="storeNote"></div>
      </main>
      <div class="pp-cta no-print">
        <button type="button" class="btn" id="btnCollect">
          <svg class="ic me-1"><use href="#i-scan"/></svg> Scan to collect
        </button>
      </div>
    </section>

    <!-- SCHEDULE -->
    <section id="tab-schedule" class="flex-column flex-grow-1 d-none">
      <div class="sched-top">
        <div class="min-w-0">
          <div class="sched-top-title">Schedule</div>
          <div class="sched-top-sub text-truncate" id="ssEvent">Event</div>
        </div>
        <div class="sched-top-count">
          <div class="n" id="ssCount">0</div>
          <div class="l">Sessions</div>
        </div>
      </div>
      <main class="scroll">
        <div class="sched-hero">
          <div class="sched-hero-label">Your timetable</div>
          <div class="sched-hero-text" id="ssHeroText">Sessions open to your role</div>
          <div class="sched-hero-meta" id="ssHeroMeta">Scan the room QR to record attendance</div>
        </div>

        <div class="sched-segs" id="sessFilters">
          <button type="button" class="sched-seg on" data-f="all">All</button>
          <button type="button" class="sched-seg" data-f="todo">Upcoming</button>
          <button type="button" class="sched-seg" data-f="done">Attended</button>
        </div>

        <div class="sched-days" id="dayChips"></div>

        <div class="sched-list" id="sessList"></div>

        <div class="sched-tip">
          <div class="sched-tip-icon" aria-hidden="true">
            <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v5"/><circle cx="12" cy="16.5" r=".8" fill="currentColor"/></svg>
          </div>
          <div class="sched-tip-text">Only sessions open to your role are listed. Scan the QR at the session room to mark attendance.</div>
        </div>
      </main>
    </section>

    <!-- PROFILE -->
    <section id="tab-profile" class="flex-column flex-grow-1 d-none">
      <div class="topbar">
        <div class="fw-800">Profile</div>
        <span class="ms-auto fw-800 text-truncate" id="prEvent" style="font-size:.78rem;max-width:8rem;color:#4B23D4"></span>
      </div>
      <main class="scroll">
        <div class="card p-3 mb-3">
          <div class="d-flex gap-3 align-items-center">
            <div class="avatar" id="prAvatar"></div>
            <div class="min-w-0"><div class="fw-800" id="prName"></div>
              <div class="text-secondary text-break" style="font-size:.8rem" id="prEmail"></div>
              <span class="badge-role mt-1 d-inline-block" id="prRole"></span></div>
          </div>
          <hr class="my-3">
          <div class="row g-2" style="font-size:.82rem">
            <div class="col-6"><div class="eyebrow">Organisation</div><div id="prOrg"></div></div>
            <div class="col-6"><div class="eyebrow">IC number</div><div class="mono" id="prIC"></div></div>
            <div class="col-6 mt-2"><div class="eyebrow">UserID</div><div class="mono fw-800" id="prUserId" style="color:#5B21B6"></div></div>
            <div class="col-6 mt-2"><div class="eyebrow">WhatsApp</div><div class="mono" id="prPhone"></div></div>
            <div class="col-6 mt-2"><div class="eyebrow">Attendance</div><div id="prVerify"></div></div>
            <div class="col-12 mt-2"><div class="eyebrow">My access</div><div id="prAccess"></div></div>
          </div>
        </div>
        <div class="d-grid gap-2 mb-3">
          <button class="btn btn-outline-primary" id="btnMyPass2"><svg class="ic me-1"><use href="#i-qr"/></svg> Show my pass</button>
        </div>
        <div class="card p-3 mb-3 d-none" id="prCertBlock">
          <div class="d-flex justify-content-between align-items-start gap-2 mb-2">
            <div>
              <div class="eyebrow mb-1">My Certificate</div>
              <div class="fw-800" id="prCertName" style="font-size:.95rem;line-height:1.25">Certificate</div>
            </div>
            <span class="badge rounded-pill" id="prCertStatus">Locked</span>
          </div>
          <div class="text-secondary" style="font-size:.8rem" id="prCertHint"></div>
          <button class="btn btn-outline-primary w-100 mt-3" id="btnCertOpen2"><svg class="ic me-1"><use href="#i-award"/></svg> Open certificate</button>
        </div>
        <div class="d-grid gap-2">
          <button class="btn btn-link text-secondary text-decoration-none" id="btnSignout"><svg class="ic me-1"><use href="#i-out"/></svg> Sign out</button>
        </div>
      </main>
    </section>

    <nav class="tabbar" id="tabbar">
      <button data-tab="home" class="on"><svg class="ic"><use href="#i-home"/></svg>Home</button>
      <button data-tab="passport"><svg class="ic"><use href="#i-passport"/></svg>Passport</button>
      <button data-tab="scan" class="center"><span class="ring"><svg class="ic"><use href="#i-scan"/></svg></span>Scan</button>
      <button data-tab="schedule"><svg class="ic"><use href="#i-calendar"/></svg>Schedule</button>
      <button data-tab="profile"><svg class="ic"><use href="#i-user"/></svg>Profile</button>
    </nav>
  </div>
  <!-- ============ CERTIFICATE ============ -->
  <section id="view-cert" class="d-none flex-column flex-grow-1">
    <div class="topbar no-print"><button class="btn btn-sm btn-light rounded-circle" id="btnCertBack"><svg class="ic"><use href="#i-back"/></svg></button>
      <div class="fw-800">Certificate</div></div>
    <main class="scroll">
      <div class="cert-stage" id="certStage">
        <div id="certScaler">
          <div class="cert-page" id="certPaper">
            <img class="cert-bg" id="certBg" alt="">
            <div class="cert-content" id="certContent">
              <div class="cert-static cert-title" id="certTitle">Certificate of Participation</div>
              <div class="cert-static cert-lead" id="certLead">This certificate is awarded to</div>
              <div class="cert-name" id="certName"></div>
              <div class="cert-static cert-rule"></div>
              <div class="cert-ic" id="certIC"></div>
              <div class="cert-connector cert-body" id="certBody"></div>
              <div class="cert-event-name" id="certEventName"></div>
              <div class="cert-static cert-connector">on</div>
              <div class="cert-date" id="certDate"></div>
              <div class="cert-footer">
                <div class="cert-issued" id="certIssued"></div>
                <div class="cert-code" id="certCode"></div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="d-grid gap-2 mt-3 no-print">
        <button class="btn btn-primary btn-lg" id="btnCertPDF"><svg class="ic me-1"><use href="#i-download"/></svg> Download PDF (A4)</button>
        <button class="btn btn-outline-primary btn-lg" id="btnCertPrint"><svg class="ic me-1"><use href="#i-printer"/></svg> Send to printer</button>
        <div class="form-text text-center mt-0" style="margin-top:-.25rem">A4 landscape · opens your printer or Save as PDF</div>
        <hr class="my-1">
        <button class="btn btn-outline-primary" id="btnCertMail"><svg class="ic me-1"><use href="#i-mail"/></svg> Send to email</button>
        <button class="btn btn-outline-success" id="btnCertWA"><svg class="ic me-1"><use href="#i-chat"/></svg> Send to WhatsApp</button>
      </div>
    </main>
  </section>

  <!-- ============ ORGANISER ============ -->
  <section id="view-org" class="d-none flex-column flex-grow-1">
    <div class="topbar">
      <img src="<?php echo e(asset(config('mexco.logos.main'))); ?>" alt="<?php echo e(config('mexco.apps.pass.name')); ?>" class="app-logo-horizontal app-logo-horizontal--bar">
      <span class="ms-auto fw-800 text-truncate me-2" id="orgEvent" style="font-size:.78rem;max-width:8rem;color:#4B23D4"></span>
      <button class="btn btn-sm btn-light rounded-circle" id="orgClose"><svg class="ic"><use href="#i-x"/></svg></button>
    </div>
    <main class="scroll">
      <div class="row g-3">
        <div class="col-12 col-lg-3">
          <div class="org-side d-flex flex-lg-column gap-1 overflow-auto">
            <button class="on" data-pane="dash"><svg class="ic me-2"><use href="#i-report"/></svg>Dashboard</button>
            <button data-pane="people"><svg class="ic me-2"><use href="#i-people"/></svg>Participants</button>
            <button data-pane="roles"><svg class="ic me-2"><use href="#i-id"/></svg>Roles &amp; Access</button>
            <button data-pane="sessions"><svg class="ic me-2"><use href="#i-calendar"/></svg>Sessions</button>
            <button data-pane="booths"><svg class="ic me-2"><use href="#i-booth"/></svg>Booths</button>
            <button data-pane="certs"><svg class="ic me-2"><use href="#i-award"/></svg>Certificates</button>
            <button data-pane="settings"><svg class="ic me-2"><use href="#i-gear"/></svg>Settings</button>
          </div>
        </div>
        <div class="col-12 col-lg-9">

          <div class="org-pane" id="pane-dash">
            <h5 class="fw-800 mb-1">Dashboard</h5>
            <p class="text-secondary" style="font-size:.85rem">Live counts from participant records.</p>
            <div class="row g-2 mb-3" id="dashStats"></div>
            <div class="card p-3"><div class="fw-800 mb-2" style="font-size:.9rem">By role</div><div id="dashRoles"></div></div>
          </div>

          <div class="org-pane d-none" id="pane-people">
            <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-2">
              <div><h5 class="fw-800 mb-0">Participants</h5>
                <div class="text-secondary" style="font-size:.82rem">Manage participants, roles and access rights.</div></div>
              <div class="d-flex gap-2">
                <button class="btn btn-sm btn-outline-secondary" id="btnRefresh"><svg class="ic"><use href="#i-refresh"/></svg></button>
                <button class="btn btn-sm btn-outline-primary" id="btnCSV"><svg class="ic me-1"><use href="#i-download"/></svg>Export</button>
                <button class="btn btn-sm btn-outline-primary" id="btnImport">Import list</button>
                <button class="btn btn-sm btn-primary" id="btnAddPax">+ Add participant</button>
              </div>
            </div>
            <div class="d-flex gap-2 mb-2">
              <input id="findIn" type="search" class="form-control" placeholder="Search by name, email or IC…" autocomplete="off">
              <select id="roleFilter" class="form-select" style="max-width:170px"></select>
            </div>
            <div class="card p-2"><div id="peopleTable" class="table-responsive"></div></div>
            <button class="btn btn-outline-primary w-100 mt-2" id="btnVerifyScan"><svg class="ic me-1"><use href="#i-scan"/></svg> Scan a participant's pass to verify</button>
            <textarea id="csvOut" class="form-control mono mt-2 d-none" rows="5" readonly style="font-size:.7rem"></textarea>
          </div>

          <div class="org-pane d-none" id="pane-roles">
            <h5 class="fw-800 mb-1">Roles &amp; Access</h5>
            <p class="text-secondary" style="font-size:.85rem">Session limits and stamps required per role. Blank limit = unlimited.</p>
            <div class="card p-2"><div class="table-responsive"><table class="tbl" id="roleTable"></table></div></div>
            <button class="btn btn-primary w-100 mt-2" id="btnSaveRoles"><svg class="ic me-1"><use href="#i-check"/></svg> Save roles</button>
          </div>

          <div class="org-pane d-none" id="pane-sessions">
            <h5 class="fw-800 mb-1">Sessions</h5>
            <p class="text-secondary" style="font-size:.85rem">One per line:
              <span class="mono">YYYY-MM-DD | 09:00-10:00 | Title | Speaker | Venue | ROLES</span>.
              Leave ROLES blank for everyone, or list them comma-separated.</p>
            <textarea id="cfgSessions" class="form-control mono" rows="10" spellcheck="false" style="font-size:.78rem"></textarea>
            <button class="btn btn-primary w-100 mt-2" id="btnSaveSessions"><svg class="ic me-1"><use href="#i-check"/></svg> Save sessions</button>
          </div>

          <div class="org-pane d-none" id="pane-booths">
            <h5 class="fw-800 mb-1">Booths</h5>
            <p class="text-secondary no-print" style="font-size:.85rem">One per line: <span class="mono">CODE | Booth name | 2-letter mark</span></p>
            <textarea id="cfgBooths" class="form-control mono no-print" rows="8" spellcheck="false" style="font-size:.8rem"></textarea>
            <div class="d-flex gap-2 mt-2 no-print">
              <button class="btn btn-primary flex-fill" id="btnSaveBooths"><svg class="ic me-1"><use href="#i-check"/></svg> Save booths</button>
              <button class="btn btn-outline-primary flex-fill" id="btnQR"><svg class="ic me-1"><use href="#i-qr"/></svg> QR codes</button>
              <button class="btn btn-outline-primary flex-fill" id="btnPrint"><svg class="ic me-1"><use href="#i-printer"/></svg> Print</button>
            </div>
            <div class="row g-3 mt-1" id="qrGrid"></div>
          </div>

          <div class="org-pane d-none" id="pane-certs">
            <h5 class="fw-800 mb-1">Certificates</h5>
            <p class="text-secondary" style="font-size:.85rem">Eligible once the participant is verified and has the stamps their role requires.</p>
            <div class="card p-2"><div id="certTable" class="table-responsive"></div></div>
          </div>

          <div class="org-pane d-none" id="pane-settings">
            <h5 class="fw-800 mb-1">Settings</h5>
            <div class="card p-3">
              <div class="mb-3"><label class="form-label fw-semibold" for="cfgName">Event name</label>
                <input id="cfgName" class="form-control"></div>
              <div class="mb-3"><label class="form-label fw-semibold" for="cfgSigner">Certificate signed by</label>
                <input id="cfgSigner" class="form-control">
                <input id="cfgSignerTitle" class="form-control mt-2" placeholder="Title / position"></div>
              <div class="mb-3"><label class="form-label fw-semibold" for="cfgMail">Email / WhatsApp service URL</label>
                <input id="cfgMail" type="url" class="form-control" placeholder="https://script.google.com/macros/s/.../exec">
                <div class="form-text">Google Apps Script web app: emails the PDF and creates the WhatsApp link.</div></div>
              <div class="soft-card p-3 mb-3" style="font-size:.82rem">
                <div class="fw-800 mb-1">Event dashboard banner</div>
                <div class="text-secondary">Upload or change the Pass event banner in <b>MeXCo Organizer → Settings → General</b>.</div>
                <div class="mt-2" id="orgBannerPreview"></div>
              </div>
              <button class="btn btn-primary w-100" id="btnSaveSettings"><svg class="ic me-1"><use href="#i-check"/></svg> Save settings</button>
            </div>
          </div>

        </div>
      </div>
    </main>
  </section>
</div>

<!-- ============ MODALS ============ -->
<div class="modal fade" id="importModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
  <div class="modal-content" style="border-radius:1.4rem"><div class="modal-header border-0 pb-0">
    <h5 class="modal-title fw-800">Import participants</h5><button class="btn-close" data-bs-dismiss="modal"></button></div>
  <div class="modal-body">
    <div class="d-flex gap-2 mb-2">
      <label class="btn btn-primary flex-fill mb-0" for="csvFile"><svg class="ic me-1"><use href="#i-download"/></svg> Upload CSV</label>
      <input type="file" id="csvFile" accept=".csv,text/csv,text/plain" class="d-none">
      <button class="btn btn-outline-secondary flex-fill" id="btnTemplate">CSV template</button>
    </div>
    <div class="form-check mb-2">
      <input class="form-check-input" type="checkbox" id="csvAppend" checked>
      <label class="form-check-label" for="csvAppend" style="font-size:.82rem">Add to the rows already listed</label>
    </div>
    <div class="soft-card p-2 mb-3 d-none" id="csvInfo" style="font-size:.8rem"></div>
    <div class="form-text mb-2">Or type them in, one per line:<br><span class="mono">email | Full name | Organization | IC | WhatsApp | ROLE</span></div>
    <textarea id="cfgReg" class="form-control mono" rows="10" spellcheck="false" style="font-size:.76rem"
      placeholder="siti@ump.edu.my | Siti Normaziah Ihsan | UMPSA | 900101123456 | 0123456789 | PRESENTER"></textarea>
    <button class="btn btn-primary w-100 mt-3" id="btnSaveReg">Save list</button>
  </div></div></div></div>

<div class="modal fade" id="paxModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
  <div class="modal-content" style="border-radius:1.4rem"><div class="modal-header border-0 pb-0">
    <h5 class="modal-title fw-800" id="paxTitle">Add participant</h5><button class="btn-close" data-bs-dismiss="modal"></button></div>
  <div class="modal-body">
    <div class="mb-2"><label class="form-label fw-semibold" for="paxName">Full name</label>
      <input id="paxName" class="form-control" placeholder="Siti Normaziah Ihsan"></div>
    <div class="mb-2"><label class="form-label fw-semibold" for="paxEmail">Email address</label>
      <input id="paxEmail" type="email" inputmode="email" class="form-control" placeholder="name@email.com"></div>
    <div class="mb-2"><label class="form-label fw-semibold" for="paxOrg">Organisation</label>
      <input id="paxOrg" class="form-control" placeholder="UMPSA"></div>
    <div class="row g-2">
      <div class="col-6 mb-2"><label class="form-label fw-semibold" for="paxIC">IC number</label>
        <input id="paxIC" class="form-control mono" inputmode="numeric" placeholder="900101123456"></div>
      <div class="col-6 mb-2"><label class="form-label fw-semibold" for="paxPhone">WhatsApp</label>
        <input id="paxPhone" class="form-control mono" inputmode="tel" placeholder="0123456789"></div>
    </div>
    <div class="mb-2"><label class="form-label fw-semibold" for="paxRole">Role</label>
      <select id="paxRole" class="form-select"></select>
      <div class="form-text" id="paxRoleHint"></div></div>
    <div class="text-danger small fw-semibold mt-2" id="paxErr" style="min-height:1.2rem"></div>
    <button class="btn btn-primary w-100" id="btnPaxSave">Save participant</button>
    <button class="btn btn-outline-danger w-100 mt-2 d-none" id="btnPaxDelete">Remove participant</button>
  </div></div></div></div>

<div class="modal fade" id="passModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered">
  <div class="modal-content" style="border-radius:1.4rem"><div class="modal-header border-0 pb-0">
    <h5 class="modal-title fw-800"><?php echo e(config('mexco.apps.organizer.name')); ?></h5><button class="btn-close" data-bs-dismiss="modal"></button></div>
  <div class="modal-body"><p class="text-secondary" style="font-size:.9rem">Enter the organiser password for this event.</p>
    <input id="passIn" type="password" class="form-control" autocomplete="current-password">
    <div class="text-danger small fw-semibold mt-2" id="passErr" style="min-height:1.2rem"></div>
    <button class="btn btn-primary w-100" id="btnPassGo">Continue</button></div></div></div></div>

<div class="modal fade" id="myPassModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered">
  <div class="modal-content text-center" style="border-radius:1.4rem"><div class="modal-header border-0 pb-0">
    <h5 class="modal-title fw-800">My Pass</h5><button class="btn-close" data-bs-dismiss="modal"></button></div>
  <div class="modal-body">
    <div id="myPassQR" class="d-inline-block p-2 border rounded-3"></div>
    <div class="mono fw-800 mt-3" id="mpUserId" style="font-size:.95rem;letter-spacing:.06em;color:#5B21B6"></div>
    <div class="fw-800 mt-2" id="mpName"></div>
    <div class="text-secondary" style="font-size:.82rem" id="mpOrg"></div>
    <span class="badge-role mt-2 d-inline-block" id="mpRole"></span>
    <div class="text-secondary mt-3" style="font-size:.8rem">Show this UserID QR to the committee to verify your attendance.</div>
  </div></div></div></div>

<div class="modal fade collect-sheet" id="collectModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered">
  <div class="modal-content"><div class="modal-header">
    <h5 class="modal-title">Enter booth code</h5><button class="btn-close" data-bs-dismiss="modal"></button></div>
  <div class="modal-body">
    <p class="collect-hint">Type the booth code shown at the counter if the camera can’t read the QR.</p>
    <input id="boothCode" class="form-control mono" maxlength="10" autocapitalize="characters" placeholder="••••" autocomplete="off">
    <div class="text-danger small fw-semibold mt-2" id="collectErr" style="min-height:1.2rem"></div>
    <button class="btn-collect" id="btnStamp">Collect stamp</button>
  </div></div></div></div>

<div class="modal fade" id="scanModal" tabindex="-1"><div class="modal-dialog modal-fullscreen">
  <div class="modal-content">
    <div class="scan-shell">
      <div class="scan-head">
        <div class="scan-head-text">
          <div class="scan-kicker" id="scanKicker">MeXCo Pass</div>
          <h5 class="scan-title" id="scanTitle">Scan QR Code</h5>
          <div class="scan-sub" id="scanSub">Booths &amp; session rooms</div>
        </div>
        <button type="button" class="scan-close" data-bs-dismiss="modal" aria-label="Close">
          <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></svg>
        </button>
      </div>
      <div class="scan-body">
        <div id="scanStage">
          <video id="scanVideo" playsinline muted></video>
          <div class="viewfinder"><i></i><i></i><i></i><i></i></div>
          <div class="scan-pulse" aria-hidden="true"></div>
        </div>
        <div class="scan-panel">
          <p class="scan-status" id="scanMsg">Starting camera…</p>
          <div id="recentScans"></div>
          <div class="scan-actions">
            <button type="button" class="scan-btn primary" id="btnManual">Enter code manually</button>
            <button type="button" class="scan-btn ghost" data-bs-dismiss="modal">Close</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</div></div>

<div class="modal fade" id="waModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered">
  <div class="modal-content" style="border-radius:1.4rem"><div class="modal-header border-0 pb-0">
    <h5 class="modal-title fw-800">Send via WhatsApp</h5><button class="btn-close" data-bs-dismiss="modal"></button></div>
  <div class="modal-body"><label class="form-label fw-semibold" for="waPhone">WhatsApp number</label>
    <input id="waPhone" type="tel" class="form-control mono" placeholder="0123456789">
    <div class="form-text mb-3">Numbers starting with 0 become +60 automatically.</div>
    <div class="d-grid gap-2">
      <button class="btn btn-success" id="btnWaSend">Send the PDF file</button>
      <div class="form-text mt-0" style="margin-top:-.25rem">Opens your phone's share sheet. Pick WhatsApp and the contact. They receive the actual PDF.</div>
      <button class="btn btn-outline-secondary mt-2" id="btnWaChat">Send a download link</button>
      <div class="form-text mt-0" style="margin-top:-.25rem">Uploads the PDF and puts a download link in the chat. Needs the service URL in Settings.</div>
    </div>
    <div class="text-secondary mt-3" style="font-size:.78rem" id="waNote"></div></div></div></div>

<div class="modal fade" id="landingModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered">
  <div class="modal-content text-center"><div id="landingStamp" class="d-flex justify-content-center"></div>
    <h4 class="fw-800 text-white mt-4 mb-1" id="landingTitle"></h4>
    <div class="text-white-50" id="landingSub"></div></div></div></div>

<div class="floor-plan-lightbox" id="floorPlanLightbox" aria-hidden="true">
  <div class="floor-plan-lightbox-card" role="dialog" aria-modal="true" aria-labelledby="floorPlanTitle">
    <div class="floor-plan-lightbox-head">
      <strong id="floorPlanTitle">Floor plan</strong>
      <button type="button" class="floor-plan-lightbox-close" id="btnFloorPlanClose" aria-label="Close">
        <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></svg>
      </button>
    </div>
    <div class="floor-plan-lightbox-body">
      <img id="floorPlanImg" src="" alt="Event floor plan">
    </div>
  </div>
</div>

<div class="toast-container position-fixed bottom-0 start-50 translate-middle-x mb-5 p-3" style="z-index:9999">
  <div id="toast" class="toast align-items-center text-bg-dark border-0"><div class="d-flex">
    <div class="toast-body" id="toastBody"></div>
    <button class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js"></script>
<?php echo $__env->make('partials.mexco-head', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>
<script>
/* =====================================================================
   MeXCo Pass - Participant experience
   storage: config:v1 · registry:v1 · pax:<slug>
   ===================================================================== */
const MEXCO_PASS = window.MEXCO?.apps?.pass?.name || "MeXCo Pass";
const MEXCO_PASS_TAGLINE = window.MEXCO?.apps?.pass?.tagline || "Participant experience";
const ROLE_COLORS = {
  VISITOR:"#2563EB", AUTHOR:"#12855C", PRESENTER:"#4B23D4", "KEYNOTE SPEAKER":"#D97706",
  STUDENT:"#0E7490", EXHIBITOR:"#EA580C", VIP:"#171A24", ORGANISER:"#7C3AED"
};
const DEFAULT_CONFIG = {
  eventName:"ICAIIA 2026",
  shortName:"ICAIIA",
  bannerUrl:null,
  floorPlanUrl:null,
  allowFloorPlan:false,
  signer:"Organising Chairperson", signerTitle:"", mailEndpoint:"",
  allowVoting:false,
  maxVotesPerParticipant:3,
  roles:{
    "VISITOR":        { sessions:5,    stamps:5, note:"Up to 5 sessions" },
    "AUTHOR":         { sessions:10,   stamps:8, note:"Up to 10 sessions" },
    "PRESENTER":      { sessions:null, stamps:8, note:"Unlimited sessions" },
    "KEYNOTE SPEAKER":{ sessions:null, stamps:0, note:"Unlimited sessions" },
    "STUDENT":        { sessions:5,    stamps:5, note:"Up to 5 sessions" },
    "EXHIBITOR":      { sessions:null, stamps:0, note:"Selected sessions only" },
    "VIP":            { sessions:null, stamps:0, note:"Unlimited sessions" }
  },
  booths:[
    {code:"B01",name:"Booth 01",mark:"01"},{code:"B02",name:"Booth 02",mark:"02"},
    {code:"B03",name:"Booth 03",mark:"03"},{code:"B04",name:"Booth 04",mark:"04"},
    {code:"B05",name:"Booth 05",mark:"05"},{code:"B06",name:"Booth 06",mark:"06"},
    {code:"B07",name:"Booth 07",mark:"07"},{code:"B08",name:"Booth 08",mark:"08"}
  ],
  sessions:[
    {id:"S1",day:"2026-10-08",time:"09:00-10:00",title:"Keynote: AI for a Better Future",speaker:"Prof. Dr. Rui Martins",venue:"Main Hall",roles:[]},
    {id:"S2",day:"2026-10-08",time:"10:30-11:30",title:"AI for Healthcare: Opportunities and Challenges",speaker:"Dr. Aina Rahim",venue:"Main Hall",roles:[]},
    {id:"S3",day:"2026-10-08",time:"13:30-14:30",title:"Data Science in Action",speaker:"Ir. Tan Wei",venue:"Hall B",roles:["PRESENTER","AUTHOR","KEYNOTE SPEAKER","VIP"]},
    {id:"S4",day:"2026-10-09",time:"09:00-10:00",title:"Human-Centred Design for Serious Games",speaker:"Dr. Lim Mei",venue:"Hall A",roles:[]},
    {id:"S5",day:"2026-10-09",time:"11:00-12:00",title:"Exhibitor Networking Lunch",speaker:"Organising Committee",venue:"Foyer",roles:["EXHIBITOR","VIP"]},
    {id:"S6",day:"2026-10-10",time:"09:30-10:30",title:"Closing Panel: The Next Decade",speaker:"Panel",venue:"Main Hall",roles:[]}
  ],
  certificateTemplates:{
    attendance:{
      key:"attendance", name:"Certificate of Attendance", description:"For visitors and general participants",
      roles:["VISITOR","STUDENT"], title:"Certificate of Participation", lead:"This certificate is awarded to",
      background_url:null,
      body:"in attendance of"
    },
    participation:{
      key:"participation", name:"Certificate of Participation", description:"For authors and contributors",
      roles:["AUTHOR"], title:"Certificate of Participation", lead:"This certificate is awarded to",
      background_url:null,
      body:"in attendance of"
    },
    presentation:{
      key:"presentation", name:"Certificate of Presentation", description:"For keynote speakers and presenters",
      roles:["PRESENTER","KEYNOTE SPEAKER"], title:"Certificate of Presentation", lead:"This certificate is awarded to",
      background_url:null,
      body:"in attendance of"
    }
  }
};

const DEFAULT_EVENTS = [
  { id:"icaiia2026", name:"ICAIIA 2026", venue:"Putrajaya, MY", dates:"8 – 10 October 2026" }
];
let events = [], publicEvents = [], EV = null;
const KEY = { cfg:id=>"config:"+id, reg:id=>"registry:"+id, pax:(id,slug)=>"pax:"+id+":"+slug };
const K = {
  cfg: () => "config:" + EV,
  reg: () => "registry:" + EV,
  paxPrefix: () => "pax:" + EV + ":",
  pax: slug => "pax:" + EV + ":" + slug
};
let config = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
let registry = {}, me = null, pending = null, busy = false;
let scanPurpose = "collect", tab = "home";
let sessFilter = "all", dayFilter = "all", ppFilter = "all", recentScans = [];
let mPass,mMyPass,mCollect,mScan,mLanding,mWa,mImport,mPax,tToast;

const $ = id => document.getElementById(id);
const slugify = e => e.trim().toLowerCase().replace(/[^a-z0-9]/g,"_");
const validEmail = v => /^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i.test(v.trim());
const esc = s => String(s==null?"":s).replace(/[&<>"']/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
const digits = v => String(v||"").replace(/\D/g,"");
const roleOf = r => config.roles[(r||"VISITOR").toUpperCase()] || config.roles.VISITOR || {sessions:null,stamps:0};
function normalizeRoles(raw){
  const out = {};
  if (!raw || typeof raw !== "object") return out;
  Object.entries(raw).forEach(([name, cfg]) => {
    if (!cfg || typeof cfg !== "object") return;
    const key = String(name).toUpperCase();
    const sessions = cfg.sessions ?? cfg.sessions_limit;
    const stamps = cfg.stamps ?? cfg.stamps_required ?? 0;
    out[key] = {
      ...cfg,
      sessions: sessions === "" || sessions === undefined ? null : sessions,
      stamps: Math.max(0, parseInt(stamps, 10) || 0),
    };
  });
  return out;
}
const roleColor = r => ROLE_COLORS[(r||"").toUpperCase()] || "#4B23D4";
const initials = n => (n||"?").trim().split(/\s+/).slice(0,2).map(w=>w[0]).join("").toUpperCase();

/* ---------- Laravel API storage ---------- */
const API = <?php echo json_encode(url('/mexco/api'), 15, 512) ?>;
const csrf = document.querySelector('meta[name="csrf-token"]').content;
let storeMode = "server";
async function api(path, opts={}){
  const res = await fetch(API+path, {
    method: opts.method || 'GET',
    credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'X-CSRF-TOKEN': csrf,
      ...(opts.headers||{})
    },
    body: opts.body != null ? JSON.stringify(opts.body) : undefined
  });
  const data = await res.json().catch(()=>({}));
  if (!res.ok) throw Object.assign(new Error(data.error||data.msg||'Request failed'), {data, status:res.status});
  return data;
}
async function sGet(k, shared=true){
  if (k === 'lastEmail') {
    const r = await api('/auth/last-email');
    return r.email ? { email: r.email } : null;
  }
  return null;
}
async function sSet(k,v,shared=true){ return true; }
async function sDel(k, shared=true){ return true; }
async function sList(p, shared=true){ return []; }
function paintStoreNote(){
  const el = $("storeNote"); if (!el) return;
  el.textContent = "Synced with server";
  el.className = "pp-sync";
}
function paintFloorPlanButton(){
  const btn = $("btnFloorPlan");
  if (!btn) return;
  const url = (config.floorPlanUrl || "").trim();
  const allowed = !!config.allowFloorPlan && !!url;
  btn.classList.toggle("on", allowed);
  btn.disabled = !allowed;
}
function closeFloorPlan(){
  const box = $("floorPlanLightbox");
  if (!box) return;
  box.classList.remove("on");
  box.setAttribute("aria-hidden", "true");
  document.body.style.overflow = "";
}
function openFloorPlan(){
  const url = (config.floorPlanUrl || "").trim();
  if (!url){ toast("Floor plan is not available yet."); return; }
  const box = $("floorPlanLightbox");
  const img = $("floorPlanImg");
  if (!box || !img){ toast("Floor plan viewer is unavailable."); return; }
  if (box.parentElement !== document.body) document.body.appendChild(box);
  img.onerror = () => toast("Could not load the floor plan image.");
  img.src = url;
  box.classList.add("on");
  box.setAttribute("aria-hidden", "false");
  document.body.style.overflow = "hidden";
}
function availableBooths(){
  return (config.booths || []).filter(b => {
    const st = String(b.status || "active").toLowerCase();
    return st === "active" || st === "open" || st === "";
  });
}
function maxVotesAllowed(){
  const n = parseInt(config.maxVotesPerParticipant, 10);
  return Number.isFinite(n) && n > 0 ? n : 3;
}
function votesUsed(){
  return (me?.votes || []).length;
}
function hasVoted(code){
  return (me?.votes || []).map(c => String(c).toUpperCase()).includes(String(code || "").toUpperCase());
}
function votesRemaining(){
  return Math.max(0, maxVotesAllowed() - votesUsed());
}
function renderBooths(){
  const list = availableBooths();
  const visited = list.filter(b => me?.stamps?.[b.code]).length;
  const votable = list.filter(b => b.voting_enabled);
  const used = votesUsed();
  const maxV = maxVotesAllowed();
  const atLimit = used >= maxV;
  if ($("bbHeroText")) {
    $("bbHeroText").textContent = list.length
      ? (list.length + " booth" + (list.length === 1 ? "" : "s") + " available")
      : "No booths are open yet";
  }
  if ($("bbHeroMeta")) {
    let meta = list.length
      ? (visited + " visited · scan QR codes to collect stamps")
      : "Check back when booths are published";
    if (votable.length) {
      meta += " · votes " + used + "/" + maxV + (atLimit ? " · limit reached" : "");
    }
    $("bbHeroMeta").textContent = meta;
  }
  paintFloorPlanButton();
  const box = $("boothDirList");
  if (!box) return;
  if (!list.length) {
    box.innerHTML = `<div class="booth-dir-empty">No available booths in this event yet.</div>`;
    return;
  }
  box.innerHTML = list.map(b => {
    const got = !!(me?.stamps?.[b.code]);
    const mark = (b.mark || b.code || "?").toString().slice(0, 3).toUpperCase();
    const logo = b.logo_url || b.logoUrl || "";
    const desc = (b.description || "").trim();
    const type = (b.booth_type || b.boothType || "").trim();
    const loc = (b.location || "").trim();
    const badge = got
      ? `<span class="booth-card-badge done">Visited</span>`
      : `<span class="booth-card-badge open">Open</span>`;
    const chips = [];
    chips.push(`<span class="booth-chip"><svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M7 8h10v8H7z"/><path d="M3 7V5a2 2 0 012-2h2"/></svg>Code ${esc(b.code)}</span>`);
    if (loc) chips.push(`<span class="booth-chip"><svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M12 21s7-5.2 7-11a7 7 0 10-14 0c0 5.8 7 11 7 11z"/><circle cx="12" cy="10" r="2.5"/></svg>${esc(loc)}</span>`);
    if (type) chips.push(`<span class="booth-chip">${esc(type)}</span>`);
    if (b.voting_enabled) chips.push(`<span class="booth-chip">${hasVoted(b.code) ? "You voted" : "Voting open"}</span>`);
    let voteAction = "";
    if (b.voting_enabled) {
      if (hasVoted(b.code)) {
        voteAction = `<div class="booth-card-actions"><div class="booth-vote-done">Voted</div></div>`;
      } else if (atLimit) {
        voteAction = `<div class="booth-card-actions"><div class="booth-vote-locked">Vote limit reached (${maxV})</div></div>`;
      } else {
        voteAction = `<div class="booth-card-actions"><button type="button" class="booth-vote-btn" data-vote="${esc(b.code)}">Vote for this booth (${votesRemaining()} left)</button></div>`;
      }
    }
    return `<article class="booth-card">
      <div class="booth-card-head">
        <div class="booth-card-logo">${logo ? `<img src="${esc(logo)}" alt="">` : esc(mark)}</div>
        <div class="booth-card-main">
          <div class="booth-card-name">${esc(b.name)}</div>
          <div class="booth-card-code">${esc(mark)} · ${esc(b.code)}</div>
        </div>
        ${badge}
      </div>
      <div class="booth-card-body">
        ${desc ? `<p class="booth-card-desc">${esc(desc)}</p>` : `<p class="booth-card-desc" style="color:#9CA3AF">No description provided.</p>`}
        <div class="booth-card-meta">${chips.join("")}</div>
        ${voteAction}
      </div>
    </article>`;
  }).join("");
  box.querySelectorAll("[data-vote]").forEach(btn => {
    btn.onclick = () => castBoothVote(btn.getAttribute("data-vote"), btn);
  });
}
async function castBoothVote(code, btn){
  if (busy) return;
  const c = String(code || "").trim().toUpperCase();
  if (!c) return;
  if (hasVoted(c)) { toast("You already voted for this booth."); return; }
  if (votesRemaining() <= 0) {
    toast("You can vote for up to " + maxVotesAllowed() + " booths only.");
    renderBooths();
    return;
  }
  busy = true;
  if (btn) { btn.disabled = true; btn.textContent = "Voting…"; }
  try {
    const r = await api('/events/'+EV+'/vote', { method:'POST', body:{ code:c } });
    busy = false;
    if (!r.ok) {
      toast(r.msg || "Could not cast vote.");
      if (btn) { btn.disabled = false; btn.textContent = "Vote for this booth (" + votesRemaining() + " left)"; }
      renderBooths();
      return;
    }
    me.votes = r.participant?.votes || me.votes || [];
    if (!hasVoted(c)) me.votes = [...(me.votes || []), c];
    toast(r.msg || "Vote recorded.");
    renderBooths();
  } catch(e) {
    busy = false;
    toast(e.data?.msg || e.message || "Could not cast vote.");
    if (btn) { btn.disabled = false; btn.textContent = "Vote for this booth (" + votesRemaining() + " left)"; }
  }
}

/* ---------- navigation ---------- */
const AUTH_VIEWS = ["view-events","view-welcome","app-tabs","view-cert","view-org"];
function show(v){
  if (v !== "view-splash") {
    const splash = $("view-splash");
    if (splash) { splash.classList.add("out"); splash.classList.add("d-none"); }
  }
  AUTH_VIEWS.forEach(x=>{
    const el=$(x); el.classList.toggle("d-none", x!==v);
    el.classList.toggle("d-flex", x===v && x!=="view-welcome");
  });
  $("view-welcome").classList.toggle("on", v==="view-welcome");
  $("view-events").classList.toggle("d-flex", v==="view-events");
  document.body.classList.toggle("org-mode", v==="view-org");
  $("tabbar").style.display = v==="app-tabs" ? "flex" : "none";
  window.scrollTo(0,0);
}
function playSplash(onDone){
  const splash = $("view-splash");
  if (!splash) { onDone(); return; }
  splash.classList.remove("out", "d-none");
  setTimeout(()=>{
    splash.classList.add("out");
    setTimeout(onDone, 450);
  }, 2200);
}
function goTab(t){
  if (t==="scan"){ openScan("collect"); return; }
  tab = t;
  ["home","passport","schedule","profile","booths"].forEach(x=>$("tab-"+x).classList.toggle("d-none", x!==t));
  ["home","passport","schedule","profile","booths"].forEach(x=>$("tab-"+x).classList.toggle("d-flex", x===t));
  document.querySelectorAll(".tabbar button").forEach(b=>b.classList.toggle("on", b.dataset.tab===t));
  if (t==="home") renderHome();
  if (t==="passport") renderPassport();
  if (t==="schedule") renderSessions();
  if (t==="profile") renderProfile();
  if (t==="booths") renderBooths();
  show("app-tabs");
}
function toast(m){ $("toastBody").textContent=m; tToast.show(); }

/* ---------- stamp art ---------- */
let uid=0;
const seededRot = s => { let h=0; for(let i=0;i<s.length;i++) h=(h*31+s.charCodeAt(i))%1000; return (h%23)-11; };
function stampSVG(b, iso){
  const id="s"+(++uid), rot=seededRot(b.code+b.name), d=new Date(iso);
  const date=String(d.getDate()).padStart(2,"0")+"."+String(d.getMonth()+1).padStart(2,"0")+"."+d.getFullYear();
  return `<svg class="stamp-svg" viewBox="0 0 200 200" role="img" aria-label="Stamp ${esc(b.name)}"
    style="transform:rotate(${rot}deg);--rot:${rot}deg">
    <defs><path id="t${id}" d="M100,100 m-74,0 a74,74 0 1,1 148,0" fill="none"/>
    <path id="b${id}" d="M100,100 m-66,0 a66,66 0 1,0 132,0" fill="none"/></defs>
    <circle cx="100" cy="100" r="90" fill="none" stroke="currentColor" stroke-width="5"/>
    <circle cx="100" cy="100" r="81" fill="none" stroke="currentColor" stroke-width="1.6" stroke-dasharray="2 6"/>
    <text class="stamp-arc"><textPath href="#t${id}" startOffset="50%" text-anchor="middle">${esc(b.name.toUpperCase().slice(0,16))}</textPath></text>
    <text class="stamp-date"><textPath href="#b${id}" startOffset="50%" text-anchor="middle">${date}</textPath></text>
    <text class="stamp-short" x="100" y="118">${esc((b.mark||b.name.slice(0,2)).toUpperCase())}</text></svg>`;
}

/* ---------- participant helpers ---------- */
const stampCount = () => config.booths.filter(b=>me.stamps[b.code]).length;
const sessionCount = () => Object.keys(me.sessions||{}).length;
const stampsNeeded = () => {
  const r = roleOf(me?.role);
  return Math.max(0, parseInt(r.stamps ?? r.stamps_required ?? 0, 10) || 0);
};
const sessionLimit = () => roleOf(me.role).sessions;
const certReady = () => !!me.verified && stampCount() >= stampsNeeded();
const saveMe = async () => {
  const rec = await api('/events/'+EV+'/participant', { method:'PUT', body:{
    email:me.email, name:me.name, org:me.org, ic:me.ic, phone:me.phone, role:me.role,
    stamps:me.stamps, sessions:me.sessions, verified:me.verified
  }});
  if (rec.stamps) me.stamps = rec.stamps;
  if (rec.sessions) me.sessions = rec.sessions;
  if (rec.verified !== undefined) me.verified = rec.verified;
};

function roleBadge(el, role){
  const e = typeof el === "string" ? $(el) : el;
  e.textContent = (role||"VISITOR").toUpperCase();
  e.style.background = roleColor(role); e.style.color = "#fff";
}

/* ---------- HOME ---------- */
function shortEventName(){
  return config.shortName || config.eventName || "";
}
function applyEventBrand(){
  const short = shortEventName();
  const hero = $("heroEvent");
  if (hero) hero.textContent = short ? short : "";
  const hm = $("hmEvent"); if (hm) hm.textContent = short;
  const pp = $("ppEvent"); if (pp) pp.textContent = short;
  const ss = $("ssEvent"); if (ss) ss.textContent = short;
  const bb = $("bbEvent"); if (bb) bb.textContent = short;
  const pr = $("prEvent"); if (pr) pr.textContent = short;
  const org = $("orgEvent"); if (org) org.textContent = short;
  const back = $("eventBackName"); if (back) back.textContent = short || "Event";
  paintEventBanner();
  document.title = short ? (short + " - " + MEXCO_PASS) : (MEXCO_PASS + " - " + MEXCO_PASS_TAGLINE);
}
function paintEventBanner(){
  const wrap = $("edashBanner");
  const img = $("edashBannerImg");
  const cap = $("edashBannerCaption");
  if (!wrap || !img) return;
  const url = config.bannerUrl || config.banner_url || "";
  const short = shortEventName();
  if (url) {
    img.src = url;
    img.alt = (short || "Event") + " banner";
    if (cap) cap.textContent = short || "";
    wrap.classList.remove("d-none");
  } else {
    img.removeAttribute("src");
    if (cap) cap.textContent = "";
    wrap.classList.add("d-none");
  }
}
function renderHome(){
  const h = new Date().getHours();
  const short = shortEventName();
  const part = h<12?"morning":h<18?"afternoon":"evening";
  $("hmGreet").textContent = "Good " + part + ",";
  $("hmName").textContent = me.name;
  $("hmWelcome").textContent = short ? ("Welcome to " + short) : "Welcome";
  applyEventBrand();
  const ini = initials(me.name);
  $("hmAvatar").innerHTML = `<span class="fw-800">${esc(ini)}</span>`;
  const topAv = $("edashTopAvatar");
  if (topAv) topAv.textContent = ini;
  roleBadge("hmRole", me.role);

  const vp = $("verifyPill");
  vp.textContent = me.verified ? "Verified" : "Not verified";
  vp.className = "edash-verify " + (me.verified ? "ok" : "warn");

  const sc = stampCount(), need = stampsNeeded();
  $("stStamps").textContent = need > 0 ? (sc + " / " + need) : String(sc);
  $("pbStamps").style.width = (need ? Math.min(100, sc/need*100) : (sc ? 100 : 0)) + "%";
  if ($("stStampsLabel")) {
    $("stStampsLabel").textContent = need
      ? ("Stamps required · " + (me.role || "VISITOR").toUpperCase())
      : ("No stamps required · " + (me.role || "VISITOR").toUpperCase());
  }
  if ($("stBooths")) $("stBooths").textContent = String((config.booths || []).length);

  const nxt = allowedSessions().filter(s=>!me.sessions[s.id])[0];
  $("nextSession").innerHTML = nxt
    ? `<div class="edash-next-when">${esc(nxt.day)} · ${esc(nxt.time)}</div>
       <div class="edash-next-title">${esc(nxt.title)}</div>
       <div class="edash-next-meta">${esc(nxt.speaker||"")}</div>
       ${nxt.venue ? `<div class="edash-next-meta edash-next-venue">${esc(nxt.venue)}</div>` : ""}`
    : `<div class="edash-next-meta">Nothing else scheduled for your role.</div>`;

  const ready = certReady();
  const tpl = assignedCertTemplate(me.role);
  const typeName = tpl ? (tpl.name || tpl.title || "Certificate") : "Certificate";
  $("certIcon").innerHTML = `<use href="#i-${ready?"award":"lock"}"/>`;
  $("certLabel").textContent = ready ? "Ready" : "Locked";
  $("certHint").textContent = ready
    ? ("Your " + typeName.toLowerCase() + " is unlocked. Open it anytime.")
    : !me.verified
      ? "Verify your attendance first to unlock your certificate."
      : ("Collect " + (need - sc) + " more stamp" + (need-sc===1?"":"s") + " to unlock.");
  const btn = $("btnCertOpen");
  btn.disabled = false;
  btn.classList.toggle("ready", ready);
  btn.textContent = ready ? "Open Certificate" : (!me.verified ? "Verify Attendance" : "Collect Stamps");
  paintCertAvailability();
}

/* ---------- PASSPORT ---------- */
function renderPassport(){
  const sc = stampCount(), need = stampsNeeded(), total = (config.booths || []).length;
  const leftNeed = need ? Math.max(0, need - sc) : 0;
  const pct = need ? Math.min(100, Math.round(sc / need * 100)) : (total ? Math.min(100, Math.round(sc / total * 100)) : 0);

  if ($("ppCount")) $("ppCount").textContent = sc + "/" + total;
  if ($("ppStatCollected")) $("ppStatCollected").textContent = String(sc);
  if ($("ppStatRequired")) $("ppStatRequired").textContent = need ? String(need) : "-";
  if ($("ppStatLeft")) $("ppStatLeft").textContent = need ? String(leftNeed) : String(Math.max(0, total - sc));
  if ($("ppProgressBar")) $("ppProgressBar").style.width = pct + "%";

  if ($("ppLead")) {
    $("ppLead").textContent = need
      ? (sc >= need ? "Stamp goal reached" : ("Collect " + leftNeed + " more stamp" + (leftNeed === 1 ? "" : "s")))
      : "Collect stamps as you explore the booths";
  }
  if ($("ppHeroMeta")) {
    $("ppHeroMeta").textContent = need
      ? (sc + " of " + need + " required for your role")
      : ("Your role doesn't need stamps · " + sc + " collected");
  }
  if ($("ppNote")) {
    $("ppNote").textContent = need
      ? (sc >= need
          ? "Stamp goal reached. Your certificate unlocks once attendance is verified."
          : "Visit booths and scan their QR codes to fill your passport.")
      : ("No stamps required for " + ((me.role || "your role").toUpperCase()) + " - collect them for fun.");
  }

  let booths = (config.booths || []).slice();
  if (ppFilter === "got") booths = booths.filter(b => !!me.stamps[b.code]);
  if (ppFilter === "todo") booths = booths.filter(b => !me.stamps[b.code]);

  const cells = booths.map(b => {
    const at = me.stamps[b.code];
    return `<div class="pp-card ${at ? "filled" : ""}">
      <div class="pp-slot">${at ? stampSVG(b, at)
        : `<svg class="ic"><use href="#i-booth"/></svg>`}</div>
      <div class="pp-card-name">${esc(b.name)}</div>
      ${at ? `<div class="pp-card-code">${esc(b.code)}</div>` : ""}
    </div>`;
  });

  if (ppFilter === "all") {
    const ready = need ? sc >= need : true;
    cells.push(`<div class="pp-card reward ${ready ? "ready" : ""}">
      <div class="pp-slot" style="border-style:solid;border-color:${ready ? "#C4B5FD" : "#D8DAE5"}">
        <svg class="ic" style="color:${ready ? "#6D28D9" : "#9CA3AF"}"><use href="#i-gift"/></svg>
      </div>
      <div class="pp-card-name">${ready ? "Reward unlocked" : "Reward"}</div>
    </div>`);
  }

  $("grid").innerHTML = cells.length
    ? cells.join("")
    : `<div class="pp-empty">${ppFilter === "got" ? "No stamps collected yet." : (ppFilter === "todo" ? "All booths stamped - nice work!" : "No booths in this event yet.")}</div>`;
  paintStoreNote();
}

/* ---------- SESSIONS ---------- */
function allowedSessions(){
  const r = (me.role||"VISITOR").toUpperCase();
  return config.sessions.filter(s => !s.roles || !s.roles.length || s.roles.map(x=>x.toUpperCase()).includes(r));
}
function canJoin(s){
  const r = (me.role||"VISITOR").toUpperCase();
  if (s.roles && s.roles.length && !s.roles.map(x=>x.toUpperCase()).includes(r))
    return { ok:false, why:"Not allowed" };
  const lim = sessionLimit();
  if (lim != null && sessionCount() >= lim && !me.sessions[s.id])
    return { ok:false, why:"Limit reached" };
  return { ok:true };
}
function formatSessionTime(time){
  const raw = String(time || "").trim();
  const parts = raw.split(/\s*-\s*/);
  if (parts.length >= 2) return { start: parts[0], end: parts[1] };
  return { start: raw || "-", end: "" };
}
function formatSessionDay(day){
  const dt = new Date(day + "T00:00:00");
  if (Number.isNaN(dt.getTime())) return { d: day, m: "", label: day };
  return {
    d: String(dt.getDate()),
    m: dt.toLocaleDateString("en-GB", { month: "short" }),
    label: dt.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short" }),
  };
}
function renderSessions(){
  const mine = allowedSessions();
  const days = [...new Set(mine.map(s=>s.day))].sort();
  if (dayFilter !== "all" && !days.includes(dayFilter)) dayFilter = "all";

  const joinedCount = mine.filter(s => !!me.sessions[s.id]).length;
  if ($("ssCount")) $("ssCount").textContent = String(mine.length);
  if ($("ssHeroText")) {
    $("ssHeroText").textContent = mine.length
      ? (mine.length + " session" + (mine.length === 1 ? "" : "s") + " for " + ((me.role || "VISITOR").toUpperCase()))
      : "No sessions assigned to your role yet";
  }
  if ($("ssHeroMeta")) {
    const lim = sessionLimit();
    $("ssHeroMeta").textContent = lim == null
      ? (joinedCount + " attended · unlimited access")
      : (joinedCount + " / " + lim + " attended · role limit");
  }

  $("dayChips").innerHTML = [`<button type="button" class="sched-day ${dayFilter==="all"?"on":""}" data-day="all"><div class="d">All</div><div class="m">Days</div></button>`]
    .concat(days.map(d=>{
      const f = formatSessionDay(d);
      return `<button type="button" class="sched-day ${dayFilter===d?"on":""}" data-day="${esc(d)}"><div class="d">${esc(f.d)}</div><div class="m">${esc(f.m)}</div></button>`;
    })).join("");

  let list = mine.slice().sort((a,b)=> (a.day+a.time).localeCompare(b.day+b.time));
  if (dayFilter!=="all") list = list.filter(s=>s.day===dayFilter);
  if (sessFilter==="todo") list = list.filter(s=>!me.sessions[s.id]);
  if (sessFilter==="done") list = list.filter(s=>me.sessions[s.id]);

  const lim = sessionLimit(), full = lim != null && sessionCount() >= lim;
  $("sessList").innerHTML = (full ? `<div class="sched-limit">You've reached your role limit of ${lim} sessions.</div>` : "") +
    (list.length ? list.map(s=>{
      const joined = !!me.sessions[s.id];
      const t = formatSessionTime(s.time);
      const day = formatSessionDay(s.day);
      const tag = joined
        ? `<span class="sched-status done">Attended</span>`
        : `<span class="sched-status todo">Scan at door</span>`;
      return `<article class="sched-card ${joined?"joined":""}">
        <div class="sched-time">
          <div class="h">${esc(t.start)}</div>
          ${t.end ? `<div class="to">to</div><div class="h">${esc(t.end)}</div>` : ""}
        </div>
        <div class="sched-body">
          ${tag}
          <div class="sched-title">${esc(s.title)}</div>
          <div class="sched-meta">${esc(day.label)}${s.speaker ? " · " + esc(s.speaker) : ""}</div>
          ${s.venue ? `<div class="sched-meta"><svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M12 21s7-5.2 7-11a7 7 0 10-14 0c0 5.8 7 11 7 11z"/><circle cx="12" cy="10" r="2.5"/></svg>${esc(s.venue)}</div>` : ""}
        </div>
      </article>`;
    }).join("") : `<div class="sched-empty">No sessions in this view yet.</div>`);
}
async function joinSession(id){
  const s = config.sessions.find(x=>x.id===id); if (!s) return;
  if (me.sessions[id]){ toast("Attendance already recorded for this session."); return; }
  const c = canJoin(s);
  if (!c.ok){ toast(c.why==="Limit reached"
    ? "Your role allows " + sessionLimit() + " sessions." : "This session isn't open to your role."); return; }
  try {
    const r = await api('/events/'+EV+'/sessions/'+id+'/join', { method:'POST' });
    if (!r.ok){ toast(r.msg||"Could not record attendance."); return; }
    me.sessions = r.participant.sessions;
    toast("Attendance recorded: " + s.title);
    renderSessions(); if (tab==="home") renderHome();
  } catch(e){ toast(e.message||"Could not record attendance."); }
}

/* ---------- PROFILE ---------- */
function renderProfile(){
  $("prAvatar").innerHTML = `<span class="fw-800">${esc(initials(me.name))}</span>`;
  $("prName").textContent = me.name; $("prEmail").textContent = me.email;
  roleBadge("prRole", me.role);
  $("prOrg").textContent = me.org || "-";
  $("prIC").textContent = me.ic ? "••••••" + digits(me.ic).slice(-6) : "-";
  if ($("prUserId")) $("prUserId").textContent = me.user_id || "-";
  $("prPhone").textContent = me.phone || "-";
  $("prVerify").innerHTML = me.verified
    ? '<span class="badge text-bg-success">Verified</span>' : '<span class="badge text-bg-warning">Pending</span>';
  const r = roleOf(me.role);
  $("prAccess").textContent = (r.sessions==null ? "Unlimited sessions" : "Up to " + r.sessions + " sessions") +
    " · " + (r.stamps ? r.stamps + " stamps required" : "No stamps required");
  paintCertAvailability();
}
function passQrPayload(){
  if (me?.user_id) return "UID:" + me.user_id;
  return "PASS:" + (me?.slug || "");
}
function showMyPass(){
  const box = $("myPassQR"); box.innerHTML = "";
  const payload = passQrPayload();
  if (typeof MexcoQR === "object")
    MexcoQR.render(box, payload, 380, { colorDark: "#5B21B6", logoScale: 0.18 });
  else if (typeof QRCode === "function") {
    const wrap = document.createElement("div");
    wrap.className = "mexco-qr-wrap";
    const host = document.createElement("div");
    host.className = "mexco-qr-canvas";
    wrap.appendChild(host);
    const logo = document.createElement("img");
    logo.className = "mexco-qr-logo";
    logo.src = window.MEETUP_APP_ICON || "<?php echo e(asset(config('mexco.logos.app_icon'))); ?>";
    logo.alt = "MeXCo";
    wrap.appendChild(logo);
    box.appendChild(wrap);
    new QRCode(host, { text:payload, width:380, height:380, colorDark:"#5B21B6", colorLight:"#fff", correctLevel:QRCode.CorrectLevel.H });
  }
  if ($("mpUserId")) $("mpUserId").textContent = me.user_id ? ("UserID  " + me.user_id) : ("Pass  " + me.slug);
  $("mpName").textContent = me.name; $("mpOrg").textContent = me.org || "";
  roleBadge("mpRole", me.role);
  mMyPass.show();
}

/* ---------- events ---------- */
let evMode = "participant";   // participant | organiser
let hubTab = "upcoming";
const HUB_THUMBS = [
  "linear-gradient(135deg,#7C3AED,#2563EB)",
  "linear-gradient(135deg,#0EA5E9,#06B6D4)",
  "linear-gradient(135deg,#EC4899,#8B5CF6)",
  "linear-gradient(135deg,#F59E0B,#EF4444)",
  "linear-gradient(135deg,#10B981,#3B82F6)",
];
const eventsToText = l => l.map(e=>[e.id,e.name,e.venue,e.dates].join(" | ")).join("\n");
function textToEvents(t){
  return t.split("\n").map(l=>l.trim()).filter(Boolean).map(l=>{
    const p = l.split("|").map(x=>x.trim());
    const id = (p[0]||p[1]||"event").toLowerCase().replace(/[^a-z0-9]/g,"");
    return { id, name:p[1]||p[0], venue:p[2]||"", dates:p[3]||"" };
  }).filter(e=>e.id && e.name);
}
function eventAvailabilityLabel(status){
  return ({ upcoming:"Upcoming", live:"Ongoing", ended:"Past Event", open:"Ongoing" })[status] || "Upcoming";
}
function eventAvailabilityStyle(status){
  return ({
    upcoming:{ bg:"#EDE9FE", color:"#6D28D9" },
    live:{ bg:"#DCFCE7", color:"#15803D" },
    ended:{ bg:"#F1F5F9", color:"#64748B" },
    open:{ bg:"#DCFCE7", color:"#15803D" }
  })[status] || { bg:"#EDE9FE", color:"#6D28D9" };
}
function hubBucket(avail){
  if (avail === "ended") return "past";
  if (avail === "live" || avail === "open") return "ongoing";
  return "upcoming";
}
function participantEventList(){
  if (evMode === "organiser") return events.map(e=>[e,null]);
  if (!pending?.matches?.length) return [];
  return pending.matches.map(m=>{
    const ev = events.find(e=>e.id===m.eventId) || publicEvents.find(e=>e.id===m.eventId);
    if (!ev) {
      return [{
        id: m.eventId,
        name: m.eventName || m.shortName || "Event",
        shortName: m.shortName || m.eventName || "Event",
        eventName: m.eventName || m.shortName || "Event",
        venue: "",
        availability: "upcoming",
        isVirtual: false,
        durationLabel: "",
      }, m.role];
    }
    return [Object.assign({}, ev, {
      shortName: m.shortName || ev.shortName,
      eventName: m.eventName || ev.eventName,
    }), m.role];
  }).filter(Boolean);
}
function eventCard(e, role, idx=0){
  const type = e.isVirtual ? "Virtual" : "Face-to-face";
  const typeStyle = e.isVirtual ? { bg:"#DBEAFE", color:"#1D4ED8" } : { bg:"#EDE9FE", color:"#6D28D9" };
  const avail = e.availability || "open";
  const availStyle = eventAvailabilityStyle(avail);
  const duration = e.durationLabel || e.dates || "";
  const ended = avail === "ended";
  const title = e.shortName || e.eventName || e.name || "Event";
  const thumb = HUB_THUMBS[idx % HUB_THUMBS.length];
  const mark = String(title).replace(/[^A-Za-z0-9]/g,"").slice(0,4).toUpperCase() || "EV";
  const banner = e.bannerUrl || e.banner_url || "";
  const roleStyle = role ? { bg: (roleColor(role)||"#7C3AED")+"22", color: roleColor(role)||"#7C3AED" } : null;
  return `<button type="button" class="hub-event" data-ev="${esc(e.id)}" ${ended && evMode!=="organiser" ? "" : ""}>
    <div class="hub-event-thumb ${banner ? "has-img" : ""}" style="background:${thumb}">
      ${banner ? `<img src="${esc(banner)}" alt="">` : ""}
      <span class="hub-event-thumb-mark">${esc(mark)}</span>
    </div>
    <div class="hub-event-body">
      <span class="hub-event-status" style="background:${availStyle.bg};color:${availStyle.color}">${eventAvailabilityLabel(avail)}</span>
      <div class="hub-event-title">${esc(title)}</div>
      <div class="hub-event-meta">
        <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M12 21s7-5.2 7-11a7 7 0 10-14 0c0 5.8 7 11 7 11z"/><circle cx="12" cy="10" r="2.5"/></svg>
        ${esc(e.venue || (e.isVirtual ? "Online" : "TBA"))}
      </div>
      ${duration ? `<div class="hub-event-meta">
        <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg>
        ${esc(duration)}
      </div>` : ""}
      <div class="hub-event-tags">
        <span class="hub-pill" style="background:${typeStyle.bg};color:${typeStyle.color}">${type}</span>
        ${role && roleStyle ? `<span class="hub-pill" style="background:${roleStyle.bg};color:${roleStyle.color}">${esc(role)}</span>` : ""}
      </div>
    </div>
    <span class="hub-event-chev" aria-hidden="true">›</span>
  </button>`;
}
function hubSectionHtml(title, items, key){
  if (!items.length) return "";
  return `<section class="hub-section" data-hub-section="${key}">
    <div class="hub-section-head">
      <div class="hub-section-title">${title}</div>
      <button type="button" class="hub-section-link" data-hub-tab="${key}">View all ›</button>
    </div>
    ${items.map(([e,role],i)=>eventCard(e,role,i)).join("")}
  </section>`;
}
function setHubTab(tab){
  hubTab = tab || "upcoming";
  document.querySelectorAll(".hub-tab").forEach(el=>el.classList.toggle("on", el.dataset.hubTab === hubTab));
  document.querySelectorAll("#hubStats .hub-stat").forEach(el=>el.classList.toggle("on", el.dataset.hubTab === hubTab));
  renderEvents();
  const feed = $("eventList");
  if (feed) feed.scrollIntoView({ behavior:"smooth", block:"start" });
}
function showHubHome(){
  $("hubHomePane")?.classList.remove("d-none");
  $("hubProfilePane")?.classList.add("d-none");
}
function showHubProfile(){
  const name = pending?.name || me?.name || "Participant";
  const email = pending?.email || me?.email || "";
  if ($("hubProfileAvatar")) $("hubProfileAvatar").textContent = initials(name);
  if ($("evProfileBtn")) $("evProfileBtn").textContent = initials(name);
  if ($("hubProfileName")) $("hubProfileName").textContent = name;
  if ($("hubProfileEmail")) $("hubProfileEmail").textContent = email;
  $("hubHomePane")?.classList.add("d-none");
  $("hubProfilePane")?.classList.remove("d-none");
}
function renderEvents(){
  const org = evMode === "organiser";
  const name = pending?.name || "";
  if ($("evGreet")) $("evGreet").textContent = org ? "Organiser events" : (name ? ("Hello, " + name.split(" ")[0]) : "Hello");
  if ($("evLead")) $("evLead").textContent = org
    ? "Select an event to manage."
    : "Choose an event to open your passport.";
  if ($("evProfileBtn") && name) $("evProfileBtn").textContent = initials(name);

  const list = participantEventList();
  const upcoming = list.filter(([e])=>hubBucket(e.availability||"open")==="upcoming");
  const ongoing = list.filter(([e])=>hubBucket(e.availability||"open")==="ongoing");
  const past = list.filter(([e])=>hubBucket(e.availability||"open")==="past");

  if ($("statUpcoming")) $("statUpcoming").textContent = upcoming.length;
  if ($("statOngoing")) $("statOngoing").textContent = ongoing.length;
  if ($("statPast")) $("statPast").textContent = past.length;

  const feed = $("eventList");
  if (!feed) return;

  if (org) {
    feed.innerHTML = list.length
      ? `<section class="hub-section"><div class="hub-section-head"><div class="hub-section-title">All events</div></div>${list.map(([e,role],i)=>eventCard(e,role,i)).join("")}</section>`
      : '<div class="hub-empty">No events available.</div>';
    return;
  }

  if (!list.length) {
    feed.innerHTML = '<div class="hub-empty">No registered events found for this account.</div>';
    return;
  }

  // Show active tab section first, then the others (like the mockup feed)
  const order = hubTab === "ongoing"
    ? [["Ongoing", ongoing, "ongoing"], ["Upcoming", upcoming, "upcoming"], ["Past Events", past, "past"]]
    : hubTab === "past"
      ? [["Past Events", past, "past"], ["Upcoming", upcoming, "upcoming"], ["Ongoing", ongoing, "ongoing"]]
      : [["Upcoming", upcoming, "upcoming"], ["Ongoing", ongoing, "ongoing"], ["Past Events", past, "past"]];

  const html = order.map(([title, items, key])=>hubSectionHtml(title, items, key)).join("");
  feed.innerHTML = html || '<div class="hub-empty">No events in this category.</div>';
}
async function loadPublicEvents(){
  try {
    publicEvents = await api('/events?public=1');
  } catch(e) {
    publicEvents = events.filter(e => (e.availability || "open") !== "ended");
  }
  const featured = publicEvents.find(e => (e.availability || "open") === "live")
    || publicEvents.find(e => (e.availability || "open") !== "ended")
    || publicEvents[0];
  if (featured && $("heroEvent")) {
    const label = featured.shortName || featured.eventName || featured.name;
    $("heroEvent").textContent = label ? label : "";
  }
}
async function loadEventConfig(id){
  const data = await api('/events/'+id+'/config');
  const c = data.config || {};
  const ev = events.find(e=>e.id===id) || { name:"Event" };
  const serverRoles = normalizeRoles(c.roles);
  config = {
    ...JSON.parse(JSON.stringify(DEFAULT_CONFIG)),
    ...c,
    roles: { ...normalizeRoles(DEFAULT_CONFIG.roles), ...serverRoles },
    booths: Array.isArray(c.booths) ? c.booths : [],
    sessions: Array.isArray(c.sessions) ? c.sessions : [],
    certificateTemplates: c.certificateTemplates || DEFAULT_CONFIG.certificateTemplates,
  };
  config.eventName = c.eventName || ev.eventName || ev.name;
  config.shortName = c.shortName || ev.shortName || config.eventName;
  registry = (data.registry && typeof data.registry === "object") ? data.registry : {};
  applyEventBrand();
}
async function pickEvent(id){
  EV = id;
  const picked = events.find(e=>e.id===id) || publicEvents.find(e=>e.id===id);
  if (!evMode && picked && picked.availability === "ended"){
    toast("This event has ended.", true);
    return;
  }
  await loadEventConfig(id);
  if (evMode === "organiser"){ $("passErr").textContent=""; $("passIn").value=""; mPass.show(); return; }
  const m = pending.matches.find(x=>x.eventId===id) || {};
  const data = await api('/events/'+id+'/enter', { method:'POST' });
  const rec = data.participant || {};
  me = { email:pending.email, slug:pending.slug, user_id:rec.user_id||null, key:K.pax(pending.slug),
    name:rec.name||m.name||pending.name, org:rec.org||m.org||"", ic:rec.ic||m.ic||pending.ic, phone:rec.phone||m.phone||"",
    role:(rec.role||m.role||"VISITOR").toUpperCase(),
    stamps:rec.stamps||{}, votes:rec.votes||[], sessions:rec.sessions||{}, verified:rec.verified||null };
  goTab("home");
}

/* ---------- sign in ---------- */
async function login(){
  const email = $("loginEmail").value.trim();
  const ic = digits($("loginIc").value);
  const err = $("loginErr");
  err.textContent = "";
  if (!validEmail(email)){ err.textContent = "Enter a valid email address."; return; }
  if (ic.length < 6){ err.textContent = "Enter your IC number."; return; }

  let result;
  try {
    result = await api('/auth/login', { method:'POST', body:{ email, ic } });
  } catch(e){
    err.textContent = e.data?.error || e.message || "Sign in failed. Please contact the organiser.";
    return;
  }

  pending = { email:result.email, slug:result.slug, name:result.name, ic:result.ic, matches:result.matches };
  evMode = "participant";
  toast("Welcome, " + pending.name);
  showHubHome();
  renderEvents();
  show("view-events");
}
async function restoreSession(){
  try {
    const r = await api('/auth/session');
    if (!r.ok || !r.matches?.length) return false;
    pending = { email:r.email, slug:r.slug, name:r.name, ic:"", matches:r.matches };
    evMode = "participant";
    if ($("loginEmail") && r.email) $("loginEmail").value = r.email;
    showHubHome();
    renderEvents();
    show("view-events");
    return true;
  } catch {
    return false;
  }
}
async function signOutToLogin(){
  try { await api('/auth/signout', { method:'POST' }); } catch {}
  me = null; pending = null; EV = null;
  $("loginIc").value = "";
  $("loginErr").textContent = "";
  show("view-welcome");
}

/* ---------- stamps ---------- */
function codeFromScan(t){
  t = String(t||"").trim();
  const link = t.match(/[?&]code=([A-Za-z0-9_-]+)/); if (link) return link[1].toUpperCase();
  const tag = t.match(/^booth\s*[:=]\s*([A-Za-z0-9_-]+)$/i); if (tag) return tag[1].toUpperCase();
  return t.toUpperCase();
}
async function applyStamp(raw){
  if (busy) return { ok:false, msg:"One moment…" };
  const code = String(raw||"").trim().toUpperCase();
  if (!code) return { ok:false, msg:"Key in the booth code." };
  busy = true;
  try {
    const r = await api('/events/'+EV+'/stamp', { method:'POST', body:{ code } });
    busy = false;
    if (!r.ok) return r;
    const booth = r.booth;
    me.stamps = r.participant.stamps;
    if (r.participant.votes) me.votes = r.participant.votes;
    recentScans.unshift({ label:booth.name, at:r.at }); recentScans = recentScans.slice(0,3);
    $("boothCode").value = ""; mCollect.hide();
    renderPassport(); if (tab==="home") renderHome(); if (tab==="booths") renderBooths();
    $("landingStamp").innerHTML = stampSVG(booth, r.at);
    $("landingTitle").textContent = booth.name + " stamped";
    const left = Math.max(0, stampsNeeded()-stampCount());
    let sub = stampsNeeded()===0 ? "No stamps required for your role"
      : left ? left + (left===1?" more stamp":" more stamps") + " for your certificate" : "Stamp goal reached";
    if (booth.voting_enabled && !hasVoted(booth.code) && votesRemaining() > 0) {
      sub = "Stamp collected · you can vote for up to " + maxVotesAllowed() + " booths";
    }
    $("landingSub").textContent = sub;
    mLanding.show(); setTimeout(()=>mLanding.hide(), 1800);
    return { ok:true };
  } catch(e) {
    busy = false;
    return { ok:false, msg: e.data?.msg || e.message || "Could not collect stamp." };
  }
}
async function doStamp(){
  $("collectErr").textContent = "";
  const r = await applyStamp($("boothCode").value);
  if (!r.ok) $("collectErr").textContent = r.msg;
}

/* ---------- verification ---------- */
async function markVerified(slug){
  try {
    const r = await api('/events/'+EV+'/verify/'+slug, { method:'POST' });
    if (!r.ok) return r;
    if (me && me.slug===slug){ me.verified = r.rec.verified; if (tab==="home") renderHome(); }
    return { ok:true, already:r.already, rec:r.rec, msg:r.msg };
  } catch(e) {
    return { ok:false, msg: e.data?.msg || e.message || "Verification failed." };
  }
}

/* ---------- scanning ---------- */
let scanStream=null, scanTimer=null, detector=null, cvs=null, ctx=null;
function openScan(purpose){
  scanPurpose = purpose || "collect";
  const verify = scanPurpose === "verify";
  $("scanTitle").textContent = verify ? "Verify participant" : "Scan QR Code";
  if ($("scanKicker")) $("scanKicker").textContent = verify ? "MeXCo Attend" : "MeXCo Pass";
  if ($("scanSub")) $("scanSub").textContent = verify
    ? "Point at the participant UserID QR"
    : "Booths & session rooms";
  $("btnManual").classList.toggle("d-none", verify);
  mScan.show();
}
function paintRecent(){
  $("recentScans").innerHTML = recentScans.length
    ? '<div class="scan-recent-label">Recent scans</div>' + recentScans.map(r=>
      `<div class="scan-recent-item">
        <span class="n">${esc(r.label)}</span>
        <span class="t">${new Date(r.at).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"})}
        <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg></span>
      </div>`).join("")
    : "";
}
async function startScan(){
  const msg = $("scanMsg"); paintRecent();
  msg.textContent = "Starting the camera…";
  if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia){ msg.textContent = "This browser can't open the camera."; return; }
  try { scanStream = await navigator.mediaDevices.getUserMedia({ video:{ facingMode:{ ideal:"environment" } }, audio:false }); }
  catch(e){ msg.textContent = e && e.name==="NotAllowedError"
      ? "Camera access was blocked. Allow the camera in your browser settings."
      : "No camera available on this device."; return; }
  const v = $("scanVideo"); v.srcObject = scanStream; try{ await v.play(); }catch{}
  if ("BarcodeDetector" in window){ try{ detector = new window.BarcodeDetector({formats:["qr_code"]}); }catch{ detector=null; } }
  if (!cvs){ cvs = document.createElement("canvas"); ctx = cvs.getContext("2d",{willReadFrequently:true}); }
  msg.textContent = scanPurpose==="verify" ? "Point at the participant's pass QR."
                                           : "Scan the QR code at booths or session rooms.";
  scanTimer = setInterval(tickScan, 220);
}
async function tickScan(){
  const v = $("scanVideo"); if (!v.videoWidth || busy) return;
  let text = null;
  if (detector){ try{ const f = await detector.detect(v); if (f&&f.length) text=f[0].rawValue; }catch{ detector=null; } }
  if (!text && typeof jsQR==="function"){
    const w=420, h=Math.round(v.videoHeight/v.videoWidth*420);
    cvs.width=w; cvs.height=h; ctx.drawImage(v,0,0,w,h);
    const res = jsQR(ctx.getImageData(0,0,w,h).data,w,h,{inversionAttempts:"dontInvert"});
    if (res && res.data) text = res.data;
  }
  if (!text) return;
  clearInterval(scanTimer); scanTimer = null;
  const again = ms => setTimeout(()=>{ if ($("scanModal").classList.contains("show") && !scanTimer) scanTimer=setInterval(tickScan,220); }, ms);

  if (scanPurpose==="verify"){
    const m = String(text).match(/^(?:PASS|UID|USERID|USER|MEXCO):(.+)$/i);
    const r = m ? await markVerified(m[1].trim()) : { ok:false, msg:"That isn't a participant UserID QR." };
    $("scanMsg").textContent = r.msg;
    if (r.ok){ if (navigator.vibrate) navigator.vibrate([30,60,30]); loadPeople(); }
    again(2000); return;
  }
  const sm = String(text).match(/^SESSION:(.+)$/i);
  if (sm){ await joinSession(sm[1].trim()); mScan.hide(); return; }
  const r = await applyStamp(codeFromScan(text));
  if (r.ok){ mScan.hide(); return; }
  $("scanMsg").textContent = r.msg;
  if (navigator.vibrate) navigator.vibrate(40);
  again(1700);
}
function stopScan(){
  if (scanTimer){ clearInterval(scanTimer); scanTimer=null; }
  if (scanStream){ scanStream.getTracks().forEach(t=>t.stop()); scanStream=null; }
  $("scanVideo").srcObject = null;
}

/* ---------- certificate ---------- */
let certFrom="home", certRec=null;
function serialFor(email, eventId){
  const seed = String(eventId || EV || "") + "|" + String(email || "").toLowerCase().trim();
  let h = 2166136261;
  for (let i = 0; i < seed.length; i++) {
    h ^= seed.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  h = h >>> 0;
  const A = "ACDEFGHJKLMNPQRTUVWXY3479";
  let o = "";
  for (let i = 0; i < 8; i++) {
    o += A[h % A.length];
    h = Math.imul(h ^ (h >>> 7), 2654435761) >>> 0;
  }
  return "CERT-" + o.slice(0, 4) + "-" + o.slice(4);
}
function fitCert(){
  const stage=$("certStage"); if (!stage || stage.offsetParent===null) return;
  const sc = Math.min(1, stage.clientWidth/(297*96/25.4));
  $("certScaler").style.transform = "scale("+sc+")";
  stage.style.height = (210*96/25.4*sc)+"px";
}
window.addEventListener("resize", fitCert);
function resolveCertTemplate(role, assignedOnly){
  const templates = config.certificateTemplates || DEFAULT_CONFIG.certificateTemplates || {};
  const r = (role || "VISITOR").toUpperCase();
  for (const t of Object.values(templates)) {
    if ((t.roles || []).map(x => String(x).toUpperCase()).includes(r)) return t;
  }
  if (assignedOnly) return null;
  return templates.attendance || Object.values(templates)[0] || {
    title: "Certificate of Participation",
    lead: "This certificate is awarded to",
    body: "Participated in {eventName} on {date}."
  };
}
function assignedCertTemplate(role){
  return resolveCertTemplate(role, true);
}
function roleHasCertificate(role){
  return !!assignedCertTemplate(role);
}
function certTypeLabel(role){
  const tpl = assignedCertTemplate(role);
  return (tpl && (tpl.name || tpl.title)) || "Certificate";
}
function paintCertAvailability(){
  const tpl = assignedCertTemplate(me?.role);
  const has = !!tpl;
  const ready = has && certReady();
  const homeBlock = $("edashCertBlock");
  const profileBlock = $("prCertBlock");

  if (homeBlock) homeBlock.classList.toggle("d-none", !has);
  if (profileBlock) profileBlock.classList.toggle("d-none", !has);
  if (!has) return;

  const typeName = tpl.name || tpl.title || "Certificate";
  if ($("edashCertEyebrow")) $("edashCertEyebrow").textContent = typeName;
  if ($("prCertName")) $("prCertName").textContent = typeName;

  const need = stampsNeeded(), sc = stampCount(), left = Math.max(0, need - sc);
  let hint;
  if (ready) hint = "Your " + typeName.toLowerCase() + " is unlocked.";
  else if (!me.verified) hint = "Verify your attendance first to unlock this certificate.";
  else if (left > 0) hint = "Collect " + left + " more stamp" + (left === 1 ? "" : "s") + " to unlock.";
  else hint = "Complete the requirements to unlock.";

  if ($("prCertHint")) $("prCertHint").textContent = hint;
  const st = $("prCertStatus");
  if (st) {
    st.textContent = ready ? "Ready" : "Locked";
    st.className = "badge rounded-pill " + (ready ? "text-bg-success" : "text-bg-warning");
  }
  const btn2 = $("btnCertOpen2");
  if (btn2) {
    btn2.disabled = !ready;
    btn2.innerHTML = ready
      ? '<svg class="ic me-1"><use href="#i-award"/></svg> Open certificate'
      : '<svg class="ic me-1"><use href="#i-lock"/></svg> Locked';
  }
}
function fillCertPlaceholders(text, map){
  return String(text || "").replace(/\{(eventName|date|role|sessions|sessionLabel|stamps|boothCount)\}/g, (_, k) => map[k] ?? "");
}
function buildCertificate(rec, from){
  const r = rec || me; certRec = r; certFrom = from || "home";
  const d = r.verified ? new Date(r.verified) : new Date();
  const date = d.toLocaleDateString("en-GB",{day:"numeric",month:"long",year:"numeric"});
  const role = (r.role||"VISITOR").toUpperCase();
  const tpl = resolveCertTemplate(role, true);
  if (!tpl){ toast("No certificate is assigned for your participant type."); return; }
  $("certTitle").textContent = tpl.title || tpl.name || "Certificate of Participation";
  if ($("certLead")) $("certLead").textContent = tpl.lead || "This certificate is awarded to";
  $("certName").textContent = r.name || "Guest";
  $("certIC").textContent = r.ic ? "IC NO. " + digits(r.ic) : "";
  $("certBody").textContent = fillCertPlaceholders(tpl.body || "in attendance of", {
    eventName: config.eventName || "",
    date,
    role: role.toLowerCase(),
    sessions: String(Object.keys(r.sessions||{}).length),
    sessionLabel: Object.keys(r.sessions||{}).length === 1 ? "session" : "sessions",
    stamps: String(Object.keys(r.stamps||{}).length),
    boothCount: String((config.booths || []).length)
  });
  $("certEventName").textContent = config.eventName || "";
  $("certDate").textContent = date;
  const issued = new Date().toLocaleDateString("en-GB",{day:"numeric",month:"long",year:"numeric"});
  $("certIssued").textContent = "Issued " + issued;
  $("certCode").textContent = serialFor(r.email, EV);
  const paper = $("certPaper"), bg = $("certBg");
  const bgUrl = tpl.background_url || tpl.backgroundUrl || "";
  if (bgUrl) {
    paper.classList.add("has-bg");
    bg.src = bgUrl;
    bg.style.display = "block";
  } else {
    paper.classList.remove("has-bg");
    bg.removeAttribute("src");
    bg.style.display = "none";
  }
  show("view-cert"); requestAnimationFrame(fitCert);
}
const certFileName = () => ("Certificate - "+(certRec.name||"Guest")+" - "+config.eventName).replace(/[\\/:*?"<>|]/g,"")+".pdf";
async function certToPDF(){
  if (typeof html2canvas!=="function" || !window.jspdf) return null;
  const sc=$("certScaler"), st=$("certStage");
  const pt=sc.style.transform, po=st.style.overflow, ph=st.style.height;
  sc.style.transform="none"; st.style.overflow="visible"; st.style.height="auto";
  let pdf=null;
  try{
    const canvas = await html2canvas($("certPaper"),{scale:2,backgroundColor:"#ffffff",useCORS:true,logging:false});
    const { jsPDF } = window.jspdf;
    pdf = new jsPDF({orientation:"landscape",unit:"mm",format:"a4"});
    pdf.addImage(canvas.toDataURL("image/jpeg",0.92),"JPEG",0,0,297,210);
  }catch(e){ pdf=null; }
  sc.style.transform=pt; st.style.overflow=po; st.style.height=ph;
  return pdf;
}
async function downloadCert(){
  toast("Building your PDF…");
  const pdf = await certToPDF();
  if (!pdf){ toast("PDF tools didn't load - using print."); printCert(); return; }
  pdf.save(certFileName()); toast("Certificate downloaded");
}
async function emailCert(){
  const to = certRec.email;
  if (!config.mailEndpoint){
    window.location.href = "mailto:"+to+"?subject="+encodeURIComponent("Certificate - "+config.eventName)+
      "&body="+encodeURIComponent("Hi "+(certRec.name||"")+",\n\nYour certificate for "+config.eventName+
      " is ready.\nSerial: "+serialFor(certRec.email)+"\n\nPlease attach the downloaded PDF before sending.\n");
    toast("No email service set - opening your mail app."); return;
  }
  toast("Sending to "+to+"…");
  const pdf = await certToPDF(); if (!pdf){ toast("Couldn't build the PDF."); return; }
  try{
    const res = await fetch(config.mailEndpoint,{ method:"POST", body:JSON.stringify({
      to, name:certRec.name, event:config.eventName, serial:serialFor(certRec.email),
      filename:certFileName(), pdf:pdf.output("datauristring").split(",")[1] }) });
    const txt = await res.text();
    toast(/ok|sent|success/i.test(txt) ? "Certificate sent to "+to : "Service replied: "+txt.slice(0,60));
  }catch(e){ toast("Couldn't reach the email service."); }
}
function normPhone(v){
  let d = digits(v); if (!d) return "";
  if (d.startsWith("0")) d = "60"+d.slice(1);
  else if (d.startsWith("1") && d.length<=10) d = "60"+d;
  return d;
}
function waMessage(link, attached){
  const head = "Hi " + (certRec.name||"") + ", here is your certificate of attendance for " + config.eventName + ".";
  const body = attached ? "\n\nThe PDF is attached to this message."
             : link     ? "\n\nDownload your PDF here:\n" + link
                        : "\n\nThe PDF will be sent to you separately.";
  return head + body + "\n\nCertificate serial: " + serialFor(certRec.email);
}
async function certLink(){
  if (!config.mailEndpoint) return null;
  const pdf = await certToPDF(); if (!pdf) return null;
  try{
    const res = await fetch(config.mailEndpoint,{ method:"POST", body:JSON.stringify({
      action:"link", name:certRec.name, event:config.eventName, filename:certFileName(),
      pdf:pdf.output("datauristring").split(",")[1] }) });
    const txt = (await res.text()).trim();
    return /^https?:\/\//.test(txt) ? txt : null;
  }catch(e){ return null; }
}
async function waSendFile(){
  toast("Building your PDF…");
  const pdf = await certToPDF(); if (!pdf){ toast("Couldn't build the PDF."); return; }
  const file = new File([pdf.output("blob")], certFileName(), { type:"application/pdf" });
  if (navigator.canShare && navigator.canShare({files:[file]})){
    try{ await navigator.share({files:[file],title:"Certificate",text:waMessage(null,true)}); mWa.hide(); }catch(e){}
    return;
  }
  pdf.save(certFileName());
  toast("Sharing isn't supported here - PDF downloaded, attach it in WhatsApp.");
  waOpenChat("");
}
function waOpenChat(link){
  const num = normPhone($("waPhone").value);
  if (!num){ $("waNote").textContent = "Enter a WhatsApp number first."; return; }
  window.open("https://wa.me/"+num+"?text="+encodeURIComponent(waMessage(link)),"_blank");
  mWa.hide();
}
async function waSendLink(){
  if (!normPhone($("waPhone").value)){ $("waNote").textContent="Enter a WhatsApp number first."; return; }
  if (!config.mailEndpoint){
    $("waNote").innerHTML = "<b>No download link available.</b> Add the service URL in Organiser → Settings, " +
      "or use <b>Send the PDF file</b> above so they receive the certificate itself.";
    return;
  }
  $("waNote").textContent = "Uploading the certificate…";
  const link = await certLink();
  if (!link) $("waNote").textContent = "Upload failed - opening the chat without a link.";
  waOpenChat(link||"");
}
async function printCert(){
  document.body.classList.remove("printing-qr");
  setPageSize("landscape");
  toast("Preparing the A4 page…");
  const pdf = await certToPDF();
  if (pdf){
    // hand the real A4 PDF to the system viewer, which offers Print and Save
    try{
      const url = pdf.output("bloburl");
      const w = window.open(url, "_blank");
      if (w){ try{ w.focus(); }catch(e){} setTimeout(()=>{ try{ w.print(); }catch(e){} }, 900); return; }
    }catch(e){}
    // pop-up blocked: download it so they can print from their files
    pdf.save(certFileName());
    toast("Pop-up blocked - PDF saved instead, open it to print.");
    return;
  }
  window.print();   // last resort: print the page itself
}
function setPageSize(o){
  let el = $("pageRule");
  if (!el){ el = document.createElement("style"); el.id="pageRule"; document.head.appendChild(el); }
  el.textContent = "@page{size:A4 "+o+";margin:0}";
}

/* ---------- organiser ---------- */
const boothsToText = l => l.map(b=>`${b.code} | ${b.name} | ${b.mark||b.name.slice(0,2).toUpperCase()}`).join("\n");
function textToBooths(t){
  return t.split("\n").map(l=>l.trim()).filter(Boolean).map(l=>{
    const p=l.split("|").map(s=>s.trim()); if(!p[0]) return null;
    return { code:p[0].toUpperCase(), name:p[1]||p[0], mark:(p[2]||(p[1]||p[0]).slice(0,2)).toUpperCase().slice(0,3) };
  }).filter(Boolean);
}
const sessionsToText = l => l.map(s=>[s.day,s.time,s.title,s.speaker,s.venue,(s.roles||[]).join(",")].join(" | ")).join("\n");
function textToSessions(t){
  return t.split("\n").map(l=>l.trim()).filter(Boolean).map((l,i)=>{
    const p=l.split("|").map(s=>s.trim());
    return { id:"S"+(i+1), day:p[0]||"", time:p[1]||"", title:p[2]||"Session", speaker:p[3]||"", venue:p[4]||"",
             roles:(p[5]||"").split(",").map(s=>s.trim().toUpperCase()).filter(Boolean) };
  });
}
const regToText = r => Object.values(r).map(v=>[v.email,v.name,v.org,v.ic||"",v.phone||"",v.role||"VISITOR"].join(" | ")).join("\n");
function textToRegistry(t){
  const out={};
  t.split("\n").map(l=>l.trim()).filter(Boolean).forEach(l=>{
    const p=l.split("|").map(s=>s.trim());
    if (!p[0]||!validEmail(p[0])) return;
    out[slugify(p[0])] = { email:p[0].toLowerCase(), name:p[1]||"Guest", org:p[2]||"", ic:p[3]||"",
      phone:p[4]||"", role:(config.roles[(p[5]||"").trim().toUpperCase()] ? p[5].trim().toUpperCase() : "VISITOR") };
  });
  return out;
}
function fillOrgForms(){
  $("cfgName").value = config.eventName;
  $("cfgSigner").value = config.signer;
  $("cfgSignerTitle").value = config.signerTitle||"";
  const preview = $("orgBannerPreview");
  if (preview) {
    const url = config.bannerUrl || "";
    preview.innerHTML = url
      ? `<img src="${esc(url)}" alt="Banner" style="width:100%;max-height:120px;object-fit:cover;border-radius:12px;border:1px solid #EEF0F6">`
      : `<span class="text-secondary">No banner uploaded yet.</span>`;
  }
  $("cfgMail").value = config.mailEndpoint||"";
  $("cfgBooths").value = boothsToText(config.booths);
  $("cfgSessions").value = sessionsToText(config.sessions);
  $("cfgReg").value = regToText(registry);
  $("roleFilter").innerHTML = '<option value="">All roles</option>' +
    Object.keys(config.roles).map(r=>`<option>${esc(r)}</option>`).join("");
  $("roleTable").innerHTML = `<thead><tr><th>Role</th><th>Session limit</th><th>Stamps req.</th></tr></thead><tbody>` +
    Object.entries(config.roles).map(([n,r])=>`<tr>
      <td><span class="badge-role" style="background:${roleColor(n)};color:#fff">${esc(n)}</span></td>
      <td><input class="form-control form-control-sm mono" data-role-s="${esc(n)}" value="${r.sessions==null?"":r.sessions}" placeholder="∞" style="max-width:100px"></td>
      <td><input class="form-control form-control-sm mono" data-role-t="${esc(n)}" value="${r.stamps||0}" style="max-width:100px"></td>
    </tr>`).join("") + `</tbody>`;
}
async function saveSettings(){
  config.eventName = $("cfgName").value.trim() || config.eventName || MEXCO_PASS;
  config.signer = $("cfgSigner").value.trim() || DEFAULT_CONFIG.signer;
  config.signerTitle = $("cfgSignerTitle").value.trim();
  config.mailEndpoint = $("cfgMail").value.trim();
  await api('/events/'+EV+'/settings', { method:'PUT', body:{
    eventName: config.eventName, signer: config.signer, signerTitle: config.signerTitle,
    mailEndpoint: config.mailEndpoint
  }});
  const ev = events.find(e=>e.id===EV);
  if (ev){ ev.name = config.eventName; renderEvents(); }
  $("heroEvent").textContent = config.shortName || config.eventName;
  toast("Settings saved");
}
async function saveRoles(){
  document.querySelectorAll("[data-role-s]").forEach(i=>{
    const n=i.getAttribute("data-role-s"), v=i.value.trim();
    config.roles[n].sessions = v==="" ? null : Math.max(0, parseInt(v,10)||0);
  });
  document.querySelectorAll("[data-role-t]").forEach(i=>{
    const n=i.getAttribute("data-role-t");
    config.roles[n].stamps = Math.max(0, parseInt(i.value,10)||0);
  });
  await api('/events/'+EV+'/roles', { method:'PUT', body:{ roles: config.roles } });
  toast("Roles saved");
}
async function saveBooths(){
  const b = textToBooths($("cfgBooths").value);
  if (!b.length){ toast("Add at least one booth."); return; }
  config.booths = b;
  await api('/events/'+EV+'/booths', { method:'PUT', body:{ booths: b } });
  toast("Booths saved");
}
async function saveSessions(){
  config.sessions = textToSessions($("cfgSessions").value);
  await api('/events/'+EV+'/sessions', { method:'PUT', body:{ sessions: config.sessions } });
  toast(config.sessions.length + " sessions saved");
}
async function saveRegistry(){
  registry = textToRegistry($("cfgReg").value);
  await api('/events/'+EV+'/registry', { method:'PUT', body:{ registry } });
  toast(Object.keys(registry).length + " participants registered");
  mImport.hide(); loadPeople();
}

/* ---------- CSV import ---------- */
const CSV_HEADS = {
  email:["email","e-mail","emel","email address"],
  name :["name","full name","fullname","nama","participant","participant name"],
  org  :["organization","organisation","org","company","institution","institut","syarikat","affiliation"],
  ic   :["ic","ic number","ic no","no ic","nric","mykad","identity card","kad pengenalan"],
  phone:["phone","phone number","whatsapp","whatsapp no","mobile","handphone","hp","tel","no tel","contact"],
  role :["role","category","kategori","access","type"]
};
function sniffDelim(line){
  const c=(line.match(/,/g)||[]).length, sc=(line.match(/;/g)||[]).length, tb=(line.match(/\t/g)||[]).length;
  return tb>c && tb>sc ? "\t" : sc>c ? ";" : ",";
}
function parseCSV(text, delim){
  const rows=[]; let row=[], cur="", q=false;
  for (let i=0;i<text.length;i++){
    const ch = text[i];
    if (q){
      if (ch === '"'){ if (text[i+1] === '"'){ cur += '"'; i++; } else q = false; }
      else cur += ch;
    } else if (ch === '"') q = true;
    else if (ch === delim){ row.push(cur); cur=""; }
    else if (ch === "\n"){ row.push(cur); rows.push(row); row=[]; cur=""; }
    else if (ch !== "\r") cur += ch;
  }
  if (cur.length || row.length){ row.push(cur); rows.push(row); }
  return rows.filter(r => r.some(x => x.trim() !== ""));
}
function mapHeader(cells){
  const map = {}; let found = 0;
  cells.forEach((c,i)=>{
    const key = c.trim().toLowerCase().replace(/[._-]/g," ").replace(/\s+/g," ");
    for (const field in CSV_HEADS){
      if (map[field] != null) continue;
      if (CSV_HEADS[field].includes(key)){ map[field] = i; found++; break; }
    }
  });
  return found >= 2 ? map : null;
}
function csvToLines(text){
  text = text.replace(/^\uFEFF/, "");
  const first = text.split(/\r?\n/)[0] || "";
  const rows = parseCSV(text, sniffDelim(first));
  if (!rows.length) return { lines:[], added:0, skipped:0 };
  let map = mapHeader(rows[0]), body = rows;
  if (map) body = rows.slice(1);
  else map = { email:0, name:1, org:2, ic:3, phone:4, role:5 };   // positional fallback
  const at = (r,f) => map[f] == null ? "" : String(r[map[f]] || "").trim();
  const lines = []; let skipped = 0;
  body.forEach(r=>{
    const email = at(r,"email");
    if (!validEmail(email)){ skipped++; return; }
    const role = (at(r,"role") || "VISITOR").toUpperCase();
    lines.push([email.toLowerCase(), at(r,"name") || "Guest", at(r,"org"),
                at(r,"ic").replace(/\s/g,""), at(r,"phone").replace(/\s/g,""), role].join(" | "));
  });
  return { lines, added:lines.length, skipped, headed:!!mapHeader(rows[0]) };
}
function handleCSVFile(file){
  if (!file) return;
  const reader = new FileReader();
  reader.onload = () => {
    const res = csvToLines(String(reader.result||""));
    const box = $("cfgReg"), info = $("csvInfo");
    if (!res.added){
      info.className = "soft-card p-2 mb-3"; info.style.borderColor = "#F0C9C4";
      info.innerHTML = "Nothing imported - no valid email column found. Try the CSV template.";
      return;
    }
    const keep = $("csvAppend").checked ? box.value.split("\n").map(l=>l.trim()).filter(Boolean) : [];
    const seen = new Set(keep.map(l => l.split("|")[0].trim().toLowerCase()));
    const fresh = res.lines.filter(l => { const e = l.split("|")[0].trim().toLowerCase();
      if (seen.has(e)) return false; seen.add(e); return true; });
    box.value = keep.concat(fresh).join("\n");
    const unknown = [...new Set(fresh.map(l => l.split("|")[5].trim())
      .filter(r => !config.roles[r]))];
    info.className = "soft-card p-2 mb-3";
    info.innerHTML = `<b>${fresh.length}</b> row${fresh.length===1?"":"s"} added` +
      (res.skipped ? ` · ${res.skipped} skipped (no valid email)` : "") +
      (res.lines.length - fresh.length ? ` · ${res.lines.length-fresh.length} duplicate` : "") +
      (unknown.length ? `<br><span class="text-warning-emphasis">Unknown role${unknown.length>1?"s":""}: ${esc(unknown.join(", "))} - these default to VISITOR.</span>` : "") +
      `<br>Review the list below, then press <b>Save list</b>.`;
    toast(fresh.length + " participants ready to save");
  };
  reader.readAsText(file);
}
function downloadTemplate(){
  const csv = "email,name,organization,ic,phone,role\n" +
    "siti@ump.edu.my,Siti Normaziah Ihsan,UMPSA,900101123456,0123456789,PRESENTER\n" +
    "ali@example.com,Ali bin Ahmad,Tech Solutions Sdn Bhd,880212065432,0198887766,EXHIBITOR\n" +
    "maya@company.com,Maya Lee,Company Sdn Bhd,950505101234,0112223344,VISITOR\n";
  const url = URL.createObjectURL(new Blob([csv], { type:"text/csv;charset=utf-8" }));
  const a = document.createElement("a");
  a.href = url; a.download = "mexco-participants-template.csv";
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(()=>URL.revokeObjectURL(url), 1000);
  toast("Template downloaded");
}

/* ---------- add / edit a participant by hand ---------- */
let editingSlug = null, deleteArmed = false;

function openPaxForm(p){
  editingSlug = p ? p.slug : null; deleteArmed = false;
  $("paxTitle").textContent = p ? "Edit participant" : "Add participant";
  $("paxName").value  = p ? (p.name||"") : "";
  $("paxEmail").value = p ? (p.email||"") : "";
  $("paxEmail").disabled = !!p;
  $("paxOrg").value   = p ? (p.org||"") : "";
  $("paxIC").value    = p ? (p.ic||"") : "";
  $("paxPhone").value = p ? (p.phone||"") : "";
  $("paxRole").innerHTML = Object.keys(config.roles).map(r=>`<option>${esc(r)}</option>`).join("");
  $("paxRole").value = p ? ((p.role||"VISITOR").toUpperCase()) : "VISITOR";
  paintRoleHint();
  $("paxErr").textContent = "";
  $("btnPaxSave").textContent = p ? "Save changes" : "Save participant";
  $("btnPaxDelete").textContent = "Remove participant";
  $("btnPaxDelete").classList.toggle("d-none", !p);
  mPax.show();
}
function paintRoleHint(){
  const r = config.roles[$("paxRole").value] || {};
  $("paxRoleHint").textContent =
    (r.sessions==null ? "Unlimited sessions" : "Up to " + r.sessions + " sessions") +
    " · " + (r.stamps ? r.stamps + " stamps required" : "no stamps required");
}
async function savePax(){
  const err = $("paxErr"); err.textContent = "";
  const name  = $("paxName").value.trim();
  const email = $("paxEmail").value.trim().toLowerCase();
  if (!name){ err.textContent = "Enter the participant's full name."; return; }
  if (!validEmail(email)){ err.textContent = "Enter a valid email address."; return; }
  try {
    const r = await api('/events/'+EV+'/participants', { method:'POST', body:{
      editing_slug: editingSlug,
      email, name,
      org: $("paxOrg").value.trim(),
      ic: digits($("paxIC").value),
      phone: $("paxPhone").value.trim().replace(/\s/g,""),
      role: $("paxRole").value.toUpperCase()
    }});
    if (me && me.slug === (editingSlug || slugify(email))) Object.assign(me, r.participant);
    mPax.hide();
    toast(editingSlug ? name + " updated" : name + " added");
    await loadEventConfig(EV);
    $("cfgReg").value = regToText(registry);
    loadPeople();
  } catch(e) {
    err.textContent = e.data?.error || e.message || "Could not save participant.";
  }
}
async function deletePax(){
  if (!editingSlug) return;
  if (!deleteArmed){
    deleteArmed = true;
    $("btnPaxDelete").textContent = "Tap again to remove permanently";
    return;
  }
  const name = registry[editingSlug] ? registry[editingSlug].name : "Participant";
  await api('/events/'+EV+'/participants/'+editingSlug, { method:'DELETE' });
  mPax.hide();
  toast(name + " removed");
  await loadEventConfig(EV);
  $("cfgReg").value = regToText(registry);
  loadPeople();
}

let people = [];
async function loadPeople(){
  $("peopleTable").innerHTML = '<div class="text-center text-secondary py-4"><div class="spinner-border spinner-border-sm"></div></div>';
  people = await api('/events/'+EV+'/people');
  paintDash(); paintPeople(); paintCerts();
}
function eligible(p){
  const need = (config.roles[(p.role||"VISITOR").toUpperCase()]||{}).stamps || 0;
  return !!p.verified && Object.keys(p.stamps||{}).length >= need;
}
function paintDash(){
  const byRole = {};
  people.forEach(p=>{ const r=(p.role||"VISITOR").toUpperCase(); byRole[r]=(byRole[r]||0)+1; });
  const tiles = [["Total participants",people.length],["Verified",people.filter(p=>p.verified).length],
    ["Certificates ready",people.filter(eligible).length],["Sessions",config.sessions.length],["Booths",config.booths.length]];
  $("dashStats").innerHTML = tiles.map(([l,n])=>`<div class="col-6 col-md-4 col-lg"><div class="card p-3">
    <div class="eyebrow">${esc(l)}</div><div class="fw-800" style="font-size:1.5rem">${n}</div></div></div>`).join("");
  $("dashRoles").innerHTML = Object.keys(config.roles).map(r=>{
    const n = byRole[r]||0, pct = people.length ? n/people.length*100 : 0;
    return `<div class="mb-2"><div class="d-flex justify-content-between" style="font-size:.8rem">
      <span class="fw-semibold" style="color:${roleColor(r)}">${esc(r)}</span><span class="mono">${n}</span></div>
      <div class="progress mt-1"><div class="progress-bar" style="width:${pct}%;background:${roleColor(r)}"></div></div></div>`;
  }).join("");
}
function paintPeople(){
  const q = $("findIn").value.trim().toLowerCase(), qd = digits(q), rf = $("roleFilter").value;
  let rows = people.filter(p=>{
    if (rf && (p.role||"VISITOR").toUpperCase()!==rf) return false;
    if (!q) return true;
    return (qd && digits(p.ic).includes(qd)) || (p.name||"").toLowerCase().includes(q) || (p.email||"").toLowerCase().includes(q);
  });
  if (!rows.length){ $("peopleTable").innerHTML = '<div class="text-center text-secondary py-4" style="font-size:.9rem">No participants match.</div>'; return; }
  $("peopleTable").innerHTML = `<table class="tbl"><thead><tr>
    <th>#</th><th>Name</th><th>Role</th><th>Sessions</th><th>Stamps</th><th>Status</th><th>Action</th></tr></thead><tbody>` +
    rows.map((p,i)=>{
      const role=(p.role||"VISITOR").toUpperCase(), need=(config.roles[role]||{}).stamps||0;
      const n=Object.keys(p.stamps||{}).length, sj=Object.keys(p.sessions||{}).length;
      const lim=(config.roles[role]||{}).sessions;
      return `<tr><td class="text-secondary">${i+1}</td>
        <td><div class="fw-semibold">${esc(p.name||"Guest")}</div>
          <div class="text-secondary" style="font-size:.72rem">${esc(p.email)}</div>
          <div class="text-secondary mono" style="font-size:.7rem">${esc(p.ic?("••••"+digits(p.ic).slice(-4)):"")}</div></td>
        <td><span class="badge-role" style="background:${roleColor(role)};color:#fff">${esc(role)}</span></td>
        <td class="mono">${sj}/${lim==null?"∞":lim}</td>
        <td class="mono">${n}/${need||"-"}</td>
        <td>${p.verified?'<span class="badge text-bg-success">Verified</span>':'<span class="badge text-bg-warning">Pending</span>'}</td>
        <td><div class="d-flex gap-1">
          <button class="btn btn-sm ${p.verified?"btn-outline-secondary":"btn-primary"}" data-verify="${esc(p.slug)}" ${p.verified?"disabled":""}>Verify</button>
          <button class="btn btn-sm ${eligible(p)?"btn-success":"btn-outline-secondary"}" data-cert="${esc(p.slug)}" ${eligible(p)?"":"disabled"}>Cert</button>
          <button class="btn btn-sm btn-outline-secondary" data-edit="${esc(p.slug)}" title="Edit">✎</button>
        </div></td></tr>`;
    }).join("") + `</tbody></table>`;
}
function paintCerts(){
  const rows = people.filter(eligible);
  $("certTable").innerHTML = rows.length ? `<table class="tbl"><thead><tr>
    <th>Name</th><th>Role</th><th>Serial</th><th>Action</th></tr></thead><tbody>` + rows.map(p=>`
    <tr><td><div class="fw-semibold">${esc(p.name)}</div><div class="text-secondary" style="font-size:.72rem">${esc(p.email)}</div></td>
      <td><span class="badge-role" style="background:${roleColor(p.role)};color:#fff">${esc((p.role||"VISITOR").toUpperCase())}</span></td>
      <td class="mono" style="font-size:.75rem">${esc(serialFor(p.email))}</td>
      <td><button class="btn btn-sm btn-primary" data-cert="${esc(p.slug)}">Open</button></td></tr>`).join("")+`</tbody></table>`
    : '<div class="text-center text-secondary py-4" style="font-size:.9rem">No one is eligible yet.</div>';
}
async function onOrgClick(e){
  const eb = e.target.closest("[data-edit]");
  if (eb){ const p = people.find(x=>x.slug===eb.getAttribute("data-edit")); if (p) openPaxForm(p); return; }
  const vb = e.target.closest("[data-verify]"), cb = e.target.closest("[data-cert]");
  if (vb){ const r = await markVerified(vb.getAttribute("data-verify")); toast(r.msg); if (r.ok) loadPeople(); return; }
  if (cb){
    const rec = await api('/events/'+EV+'/participants/'+cb.getAttribute("data-cert"));
    if (!rec || !rec.email){ toast("No record for that participant yet."); return; }
    buildCertificate(rec, "org"); return;
  }
}
function exportCSV(){
  if (!people.length){ toast("Nothing to export yet."); return; }
  const cols = config.booths.map(b=>b.code);
  const q = x => '"'+String(x==null?"":x).replace(/"/g,'""')+'"';
  const csv = [["name","ic","organization","email","phone","role","verified_at","sessions","stamps","certificate",...cols].join(",")]
    .concat(people.map(p=>{
      const s=p.stamps||{}, n=Object.keys(s).length;
      return [q(p.name),q(p.ic),q(p.org),q(p.email),q(p.phone),q(p.role),q(p.verified||""),
        Object.keys(p.sessions||{}).length, n, eligible(p)?"eligible":"no", ...cols.map(c=>q(s[c]||""))].join(",");
    })).join("\n");
  const out=$("csvOut"); out.classList.remove("d-none"); out.value=csv; out.select();
  try{ navigator.clipboard.writeText(csv); toast("CSV copied to clipboard"); }catch{ toast("CSV ready below"); }
}
function renderQRCodes(){
  const grid=$("qrGrid"); grid.innerHTML="";
  if (typeof MexcoQR!=="object" && typeof QRCode!=="function"){ toast("QR generator didn't load."); return; }
  config.booths.forEach(b=>{
    const col=document.createElement("div"); col.className="col-6 col-md-4 col-lg-3";
    const card=document.createElement("div"); card.className="qr-card";
    const holder=document.createElement("div"); card.appendChild(holder);
    const mark = (b.mark || b.name || "").slice(0,2).toUpperCase();
    card.insertAdjacentHTML("beforeend",
      `<div class="booth-print-name">${esc(b.name)}</div>
       <div class="booth-print-numbers">
         <span class="booth-print-code">${esc(b.code)}</span>
         <span class="booth-print-mark">${esc(mark)}</span>
       </div>`);
    col.appendChild(card); grid.appendChild(col);
    const size = 220;
    if (typeof MexcoQR === "object")
      MexcoQR.render(holder, "BOOTH:"+b.code, size, { colorDark: "#5B21B6" });
    else
      new QRCode(holder,{ text:"BOOTH:"+b.code, width:size, height:size, colorDark:"#5B21B6", colorLight:"#FFFFFF", correctLevel:QRCode.CorrectLevel.H });
  });
}
function printBoothQRs(){
  if(!$("qrGrid").children.length) renderQRCodes();
  document.body.classList.add("printing-qr");
  setPageSize("portrait");
  setTimeout(()=>{
    window.print();
    setTimeout(()=>document.body.classList.remove("printing-qr"),500);
  },400);
}
function orgPane(name){
  document.querySelectorAll(".org-pane").forEach(p=>p.classList.toggle("d-none", p.id!=="pane-"+name));
  document.querySelectorAll(".org-side button").forEach(b=>b.classList.toggle("on", b.dataset.pane===name));
}

/* ---------- boot ---------- */
document.addEventListener("DOMContentLoaded", async ()=>{
  mPass=new bootstrap.Modal("#passModal"); mMyPass=new bootstrap.Modal("#myPassModal");
  mCollect=new bootstrap.Modal("#collectModal"); mScan=new bootstrap.Modal("#scanModal");
  mLanding=new bootstrap.Modal("#landingModal"); mWa=new bootstrap.Modal("#waModal");
  mImport=new bootstrap.Modal("#importModal"); mPax=new bootstrap.Modal("#paxModal"); tToast=new bootstrap.Toast($("toast"),{delay:2600});

  $("btnLogin").onclick = login;
  $("loginEmail").addEventListener("keydown", e=>{ if(e.key==="Enter") login(); });
  $("loginIc").addEventListener("keydown", e=>{ if(e.key==="Enter") login(); });
  $("btnUseDemoAccount")?.addEventListener("click", ()=>{
    const btn = $("btnUseDemoAccount");
    if ($("loginEmail")) $("loginEmail").value = btn.dataset.email || "";
    if ($("loginIc")) $("loginIc").value = btn.dataset.ic || "";
    if ($("loginErr")) $("loginErr").textContent = "";
    toast("Demo visitor credentials filled - tap Sign In.");
  });
  $("btnSignout").onclick = signOutToLogin;

  document.querySelectorAll(".tabbar button").forEach(b=>b.onclick=()=>goTab(b.dataset.tab));
  $("btnMyPass").onclick = showMyPass; $("btnMyPass2").onclick = showMyPass;
  $("btnCollect").onclick = ()=>openScan("collect");
  $("btnBoothsBack").onclick = ()=>goTab("home");
  $("btnProgressBooths").onclick = ()=>goTab("booths");
  $("btnFloorPlan").onclick = openFloorPlan;
  $("btnFloorPlanClose").onclick = closeFloorPlan;
  $("floorPlanLightbox").addEventListener("click", e=>{
    if (e.target === $("floorPlanLightbox")) closeFloorPlan();
  });
  document.addEventListener("keydown", e=>{
    if (e.key === "Escape" && $("floorPlanLightbox")?.classList.contains("on")) closeFloorPlan();
  });
  $("ppFilters")?.addEventListener("click", e=>{
    const b = e.target.closest("[data-pp]");
    if (!b) return;
    ppFilter = b.dataset.pp;
    document.querySelectorAll("#ppFilters .pp-seg").forEach(c => c.classList.toggle("on", c === b));
    renderPassport();
  });
  $("btnManual").onclick = ()=>{ mScan.hide(); setTimeout(()=>{ $("collectErr").textContent=""; mCollect.show(); },250); };
  $("btnStamp").onclick = doStamp;
  $("boothCode").addEventListener("keydown",e=>{ if(e.key==="Enter") doStamp(); });
  $("scanModal").addEventListener("shown.bs.modal", startScan);
  $("scanModal").addEventListener("hidden.bs.modal", stopScan);

  $("sessFilters").onclick = e=>{ const b=e.target.closest("[data-f]"); if(!b) return;
    sessFilter=b.dataset.f; document.querySelectorAll("#sessFilters .sched-seg").forEach(c=>c.classList.toggle("on",c===b)); renderSessions(); };
  $("dayChips").onclick = e=>{ const b=e.target.closest("[data-day]"); if(!b) return; dayFilter=b.dataset.day; renderSessions(); };

  $("btnCertOpen").onclick = ()=>{
    if (!roleHasCertificate(me.role)){ toast("No certificate is assigned for your participant type."); return; }
    if (certReady()){ buildCertificate(me,"home"); return; }
    if (!me.verified){
      toast("Show your pass to the organiser to verify attendance.");
      showMyPass();
      return;
    }
    toast("Collect " + (stampsNeeded()-stampCount()) + " more stamps to unlock.");
    goTab("passport");
  };
  $("btnCertOpen2").onclick = ()=>{
    if (!roleHasCertificate(me.role)){ toast("No certificate is assigned for your participant type."); return; }
    if(!certReady()){ toast(me.verified? "Collect "+(stampsNeeded()-stampCount())+" more stamps." : "Your attendance isn't verified yet."); return; }
    buildCertificate(me,"profile");
  };
  $("btnNextSession").onclick = ()=>goTab("schedule");
  $("edashTopAvatar").onclick = ()=>goTab("profile");
  $("btnCertBack").onclick = ()=>{ certFrom==="org" ? show("view-org") : goTab(certFrom==="profile"?"profile":"home"); };
  $("btnCertPDF").onclick = downloadCert;
  $("btnCertMail").onclick = emailCert;
  $("btnCertWA").onclick = ()=>{ $("waPhone").value = certRec.phone||""; $("waNote").textContent=""; mWa.show(); };
  $("btnWaSend").onclick = waSendFile; $("btnWaChat").onclick = waSendLink;
  $("btnCertPrint").onclick = printCert;

  $("btnPassGo").onclick = async ()=>{
    $("passErr").textContent = "";
    const password = $("passIn").value.trim();
    if (!password){ $("passErr").textContent = "Enter the organiser password."; return; }
    try {
      await api('/events/'+EV+'/org', { method:'POST', body:{ password } });
      mPass.hide(); evMode="participant"; fillOrgForms(); show("view-org"); orgPane("dash"); loadPeople();
    } catch(e) { $("passErr").textContent = e.data?.error || "Wrong password."; }
  };
  $("passIn").addEventListener("keydown",e=>{ if(e.key==="Enter") $("btnPassGo").click(); });
  $("orgClose").onclick = ()=>{ me ? goTab("home") : show("view-welcome"); };
  document.querySelectorAll(".org-side button").forEach(b=>b.onclick=()=>orgPane(b.dataset.pane));
  $("findIn").addEventListener("input", paintPeople);
  $("roleFilter").addEventListener("change", paintPeople);
  $("peopleTable").addEventListener("click", onOrgClick);
  $("certTable").addEventListener("click", onOrgClick);
  $("btnRefresh").onclick = loadPeople;
  $("btnCSV").onclick = exportCSV;
  $("btnImport").onclick = ()=>mImport.show();
  $("btnAddPax").onclick = ()=>openPaxForm(null);
  $("btnPaxSave").onclick = savePax;
  $("btnPaxDelete").onclick = deletePax;
  $("paxRole").addEventListener("change", paintRoleHint);
  $("paxEmail").addEventListener("keydown", e=>{ if(e.key==="Enter") savePax(); });
  $("btnSaveReg").onclick = saveRegistry;
  $("csvFile").addEventListener("change", e => { handleCSVFile(e.target.files[0]); e.target.value = ""; });
  $("btnTemplate").onclick = downloadTemplate;
  $("btnSaveRoles").onclick = saveRoles;
  $("btnSaveBooths").onclick = saveBooths;
  $("btnSaveSessions").onclick = saveSessions;
  $("btnSaveSettings").onclick = saveSettings;
  $("eventList").onclick = e=>{
    const tabBtn = e.target.closest("[data-hub-tab]");
    if (tabBtn){ setHubTab(tabBtn.dataset.hubTab); return; }
    const b = e.target.closest("[data-ev]");
    if (b && !b.disabled) pickEvent(b.dataset.ev);
  };
  $("hubStats")?.addEventListener("click", e=>{
    const b = e.target.closest("[data-hub-tab]");
    if (b) setHubTab(b.dataset.hubTab);
  });
  $("hubTabs")?.addEventListener("click", e=>{
    const b = e.target.closest("[data-hub-tab]");
    if (b) setHubTab(b.dataset.hubTab);
  });
  $("evProfileBtn")?.addEventListener("click", showHubProfile);
  $("evBack").onclick = ()=>{
    if ($("hubProfilePane") && !$("hubProfilePane").classList.contains("d-none")){
      showHubHome();
      return;
    }
    show(evMode==="organiser" ? "view-welcome" : (me ? "app-tabs" : "view-welcome"));
  };
  $("evSignOut").onclick = signOutToLogin;
  function backToDashboard(){
    evMode = "participant";
    showHubHome();
    renderEvents();
    show("view-events");
  }
  $("btnBackDashboard").onclick = backToDashboard;
  $("btnVerifyScan").onclick = ()=>openScan("verify");
  $("btnQR").onclick = renderQRCodes;
  $("btnPrint").onclick = printBoothQRs;

  events = await api('/events');
  if (!events.length) events = JSON.parse(JSON.stringify(DEFAULT_EVENTS));
  await loadPublicEvents();
  const last = await api('/auth/last-email').catch(()=>({}));
  if (last?.email && $("loginEmail")) $("loginEmail").value = last.email;
  playSplash(async ()=>{
    if (await restoreSession()) return;
    show("view-welcome");
  });
});
</script>
</body>
</html>
<?php /**PATH C:\laragon\www\myapp\resources\views/mexco/index.blade.php ENDPATH**/ ?>