<?php
session_start();
include('../config/db.php');
require_once '../includes/csrf.php';
require_once '../includes/voucher_helpers.php';
csrf_generate();
if(!isset($_SESSION['user'])){header('Location:../index.php');exit();}
$user=$_SESSION['user'];
$s=$conn->prepare('SELECT id,name,phone FROM customers WHERE phone=?');
$s->bind_param('s',$user['phone']);$s->execute();
$cust=$s->get_result()->fetch_assoc();$s->close();

// FIX: if user was deleted from DB while session was active, log them out cleanly
if (!$cust) {
    session_destroy();
    header('Location:../index.php');
    exit();
}

$uid=(int)$cust['id'];

// ── AJAX: redemption status check ─────────────────────────────────────────────
if (isset($_GET['action']) && $_GET['action'] === 'status') {
    header('Content-Type: application/json');
    $code = trim($_GET['code'] ?? '');
    $claimId = (int)($_GET['claim_id'] ?? 0);
    if ($claimId > 0) {
        $st = $conn->prepare("SELECT vc.status FROM voucher_claims vc JOIN vouchers v ON vc.voucher_id=v.id WHERE vc.id=? AND v.code=? AND vc.customer_id=? LIMIT 1");
        $st->bind_param('isi', $claimId, $code, $uid);
    } else {
        $st = $conn->prepare("SELECT vc.status FROM voucher_claims vc JOIN vouchers v ON vc.voucher_id=v.id WHERE v.code=? AND vc.customer_id=? ORDER BY vc.id DESC LIMIT 1");
        $st->bind_param('si', $code, $uid);
    }
    $st->execute();
    $row = $st->get_result()->fetch_assoc();
    echo json_encode(['redeemed' => $row && $row['status'] === 'redeemed']);
    exit();
}

// ── AJAX: auto-claim a public voucher with a unique code on first open ─────────
if (isset($_GET['action']) && $_GET['action'] === 'autoclaim') {
    header('Content-Type: application/json');
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        http_response_code(405);
        echo json_encode(['ok'=>false,'error'=>'Method not allowed']);
        exit();
    }
    csrf_verify();
    $voucherId = (int)($_POST['voucher_id'] ?? 0);

    // Get the template. Public vouchers can be claimed by anyone; private
    // vouchers can claim the next use only after admin has released it once.
    $vq = $conn->prepare('SELECT * FROM vouchers WHERE id=? AND parent_voucher_id IS NULL LIMIT 1');
    $vq->bind_param('i', $voucherId);
    $vq->execute();
    $v = $vq->get_result()->fetch_assoc();
    $vq->close();

    if (!$v) { echo json_encode(['ok'=>false,'error'=>'Voucher not found']); exit(); }

    // Block claim if the template is hidden (is_public=0) regardless of prior claims
    // This is the core fix: hiding a voucher means NO ONE can claim it, even existing users
    if (!(int)$v['is_public']) {
        // Check if user already has a pending/active claim — if so, still show it but no new claims
        $existing2 = $conn->prepare("
            SELECT vc.id, cv.code, vc.status
            FROM voucher_claims vc
            JOIN vouchers cv ON vc.voucher_id=cv.id
            WHERE vc.customer_id=?
              AND (cv.id=? OR cv.parent_voucher_id=?)
              AND vc.status='claimed'
              AND (cv.status IS NULL OR cv.status != 'revoked')
            ORDER BY vc.id ASC LIMIT 1
        ");
        $existing2->bind_param('iii', $uid, $voucherId, $voucherId);
        $existing2->execute();
        $existingClaim = $existing2->get_result()->fetch_assoc();
        $existing2->close();
        if ($existingClaim) {
            // They have it claimed already — return it (they can still use/redeem it)
            echo json_encode(['ok'=>true, 'code'=>$existingClaim['code'], 'claim_id'=>(int)$existingClaim['id']]);
        } else {
            // Voucher is hidden — cannot claim new
            echo json_encode(['ok'=>false,'error'=>'This voucher is not available']);
        }
        exit();
    }

    $window = voucher_window_status($v);
    if ($window === 'expired') {
        echo json_encode(['ok'=>false,'error'=>'Voucher expired']);
        exit();
    }
    if ($window === 'not_started') {
        echo json_encode(['ok'=>false,'error'=>'Voucher is not valid yet']);
        exit();
    }

    $pending = $conn->prepare("
        SELECT vc.id, cv.code
        FROM voucher_claims vc
        JOIN vouchers cv ON vc.voucher_id=cv.id
        WHERE vc.customer_id=?
          AND (cv.id=? OR cv.parent_voucher_id=?)
          AND vc.status='claimed'
        ORDER BY vc.id ASC
        LIMIT 1
    ");
    $pending->bind_param('iii', $uid, $voucherId, $voucherId);
    $pending->execute();
    $existing = $pending->get_result()->fetch_assoc();
    $pending->close();

    if ($existing) {
        echo json_encode(['ok'=>true, 'code'=>$existing['code'], 'claim_id'=>(int)$existing['id']]);
        exit();
    }

    $cnt = $conn->prepare("
        SELECT COUNT(*) c
        FROM voucher_claims vc
        JOIN vouchers cv ON vc.voucher_id=cv.id
        WHERE vc.customer_id=?
          AND (cv.id=? OR cv.parent_voucher_id=?)
    ");
    $cnt->bind_param('iii', $uid, $voucherId, $voucherId);
    $cnt->execute();
    $usedCount = (int)$cnt->get_result()->fetch_assoc()['c'];
    $cnt->close();

    // Private voucher mode check: type starts with "private:"
    // Private = exclusive, only users admin has released it to can claim subsequent uses
    $isPrivateMode = str_starts_with($v['type'] ?? '', 'private');
    if ($isPrivateMode && $usedCount <= 0) {
        echo json_encode(['ok'=>false,'error'=>'This voucher is exclusive — contact staff to get access']);
        exit();
    }

    if ($usedCount >= max(1, (int)$v['max_per_user'])) {
        echo json_encode(['ok'=>false,'error'=>'Claim limit reached']);
        exit();
    }

    // Generate unique code and clone the voucher for this user
    $code = '';
    for ($t = 0; $t < 10; $t++) {
        $candidate = 'ICE-' . strtoupper(bin2hex(random_bytes(4)));
        $c2 = $conn->prepare('SELECT id FROM vouchers WHERE code=? LIMIT 1');
        $c2->bind_param('s', $candidate); $c2->execute();
        if ($c2->get_result()->num_rows === 0) { $code = $candidate; break; }
        $c2->close();
    }
    if (!$code) { echo json_encode(['ok'=>false,'error'=>'Code generation failed']); exit(); }

    $conn->begin_transaction();
    try {
        $ins = $conn->prepare('INSERT INTO vouchers (parent_voucher_id,code,event_name,start_date,end_date,details,type,image,max_per_user,is_public) VALUES (?,?,?,?,?,?,?,?,?,0)');
        $ins->bind_param('isssssssi', $voucherId, $code, $v['event_name'], $v['start_date'], $v['end_date'], $v['details'], $v['type'], $v['image'], $v['max_per_user']);
        $ins->execute();
        $newVoucherId = $conn->insert_id;
        $ins->close();

        $cl = $conn->prepare('INSERT INTO voucher_claims (customer_id, voucher_id, status) VALUES (?,?,"claimed")');
        $cl->bind_param('ii', $uid, $newVoucherId);
        $cl->execute();
        $claimId = $conn->insert_id;
        $cl->close();

        $conn->commit();
        echo json_encode(['ok'=>true, 'code'=>$code, 'claim_id'=>(int)$claimId]);
    } catch (Exception $e) {
        $conn->rollback();
        error_log('autoclaim error: ' . $e->getMessage());
        echo json_encode(['ok'=>false,'error'=>'Could not claim voucher. Please try again.']);
    }
    exit();
}

$uname=htmlspecialchars($cust['name']);
$uphone=htmlspecialchars($cust['phone']);

// Load custom branding saved by admin
$brandingFile = __DIR__ . '/../admin/user_branding.json';
$branding = file_exists($brandingFile) ? json_decode(file_get_contents($brandingFile), true) : [];
$brandTitle  = htmlspecialchars($branding['title']  ?? 'My Vouchers');
$brandColor  = htmlspecialchars($branding['color']  ?? '#d32f2f');
$brandHeader = $branding['header_img'] ?? '';
$brandFooter = htmlspecialchars($branding['footer_text'] ?? '');

// Fetch: public templates + private templates released to this user.
// FIX: Exclude revoked child clones AND hidden parent templates from display.
// If a voucher is hidden (is_public=0) and the user has no active claim, it disappears.
// If they already claimed it, it still shows so they can use/redeem their existing claim.
$s=$conn->prepare("
  SELECT tpl.*,
         COUNT(vc.id) AS times_claimed,
         SUM(vc.status='redeemed') AS times_redeemed,
         SUM(vc.status='claimed') AS times_pending
  FROM voucher_claims vc
  INNER JOIN vouchers cv ON vc.voucher_id=cv.id
  INNER JOIN vouchers tpl ON tpl.id=COALESCE(cv.parent_voucher_id, cv.id)
  WHERE vc.customer_id=?
    AND (cv.status IS NULL OR cv.status != 'revoked')
  GROUP BY tpl.id
  HAVING times_claimed > 0
  UNION
  SELECT v.*,
         0 AS times_claimed,
         0 AS times_redeemed,
         0 AS times_pending
  FROM vouchers v
  WHERE v.is_public=1
    AND v.parent_voucher_id IS NULL
    AND (v.status IS NULL OR v.status != 'revoked')
    AND NOT EXISTS (
      SELECT 1
      FROM voucher_claims vc2
      JOIN vouchers cv2 ON vc2.voucher_id=cv2.id
      WHERE vc2.customer_id=?
        AND (cv2.id=v.id OR cv2.parent_voucher_id=v.id)
    )
  ORDER BY id DESC
");
$s->bind_param('ii',$uid,$uid);$s->execute();
$rows=$s->get_result();
$vouchers=[];$today=date('Y-m-d');
while($r=$rows->fetch_assoc()){
  $r['times_claimed']  = (int)$r['times_claimed'];
  $r['times_redeemed'] = (int)$r['times_redeemed'];
  $r['times_pending']  = (int)$r['times_pending'];
  $r['max_per_user']   = max(1,(int)$r['max_per_user']);
  $r['remaining_uses'] = max(0, $r['max_per_user'] - $r['times_claimed']);

  $isExpired = $r['end_date'] && $r['end_date'] !== '0000-00-00' && $r['end_date'] < $today;
  if($isExpired)                                                           $r['_s']='expired';
  elseif($r['times_pending'] > 0)                                          $r['_s']='valid';
  elseif($r['remaining_uses'] > 0)                                         $r['_s']='valid';
  elseif($r['times_redeemed'] > 0)                                         $r['_s']='used';
  else                                                                     $r['_s']='valid';
  $vouchers[]=$r;
}
$s->close();
$order=['valid'=>0,'used'=>1,'expired'=>2];
usort($vouchers, function($a, $b) use ($order) {
    // First sort by status (valid first, used second, expired last)
    $statusCmp = $order[$a['_s']] <=> $order[$b['_s']];
    if ($statusCmp !== 0) return $statusCmp;
    // Within same status, sort by start_date ascending (soonest event first)
    $aDate = $a['start_date'] && $a['start_date'] !== '0000-00-00' ? $a['start_date'] : '9999-12-31';
    $bDate = $b['start_date'] && $b['start_date'] !== '0000-00-00' ? $b['start_date'] : '9999-12-31';
    return $aDate <=> $bDate;
});
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover">
<?php csrf_meta(); ?>
<title><?= $brandTitle ?></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800;900&family=Nunito:wght@400;600;700;800&family=Luckiest+Guy&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
  --brand:<?= $brandColor ?>;
  --brand2:color-mix(in srgb,<?= $brandColor ?> 80%,#000);
  --dark:#111;--cream:#f4f5f7;--warm:#f0e6d3;--border:#e4e6ea;
  --text:#1a1a1a;--muted:#888;--white:#fff;
  --green:#1b8c5a;--font:'Nunito',sans-serif;--cond:'DM Sans',sans-serif;
}
body{font-family:var(--font);background:var(--cream);color:var(--text);min-height:100vh;padding-bottom:calc(40px + env(safe-area-inset-bottom,0px))}

/* ── Topbar ── */
.topbar{background:var(--dark);height:56px;display:grid;grid-template-columns:56px 1fr auto;align-items:center;position:sticky;top:0;z-index:200;border-bottom:3px solid var(--brand);padding:0 4px}
.tb-left{display:flex;align-items:center;justify-content:flex-start;padding-left:8px}
.tb-center{text-align:center}
.tb-right-cell{display:flex;align-items:center;justify-content:flex-end;padding-right:12px}
.tb-brand-name{font-family:var(--cond);font-weight:900;font-size:18px;color:#fff;letter-spacing:1px;text-transform:uppercase}
.tb-avatar{width:32px;height:32px;background:var(--brand);border-radius:50%;display:grid;place-items:center;font-family:var(--cond);font-weight:900;font-size:14px;color:#fff}
.tb-logout{font-size:11px;color:rgba(255,255,255,.5);font-weight:700;letter-spacing:.4px;text-transform:uppercase;text-decoration:none;transition:color .15s;white-space:nowrap}
.tb-logout:hover{color:#fff}

/* ── Hero ── */
.hero{background:var(--brand);padding:24px 20px 30px;text-align:center;position:relative;overflow:hidden}
.hero-bg-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;opacity:.25}
.hero::before{content:'';position:absolute;top:-50px;left:-50px;width:200px;height:200px;border-radius:50%;background:rgba(255,255,255,.05)}
.hero::after{content:'';position:absolute;bottom:-60px;right:-30px;width:180px;height:180px;border-radius:50%;background:rgba(0,0,0,.07)}
.hero-eyebrow{font-size:10px;font-weight:800;letter-spacing:2.5px;text-transform:uppercase;color:rgba(255,255,255,.55);margin-bottom:4px;position:relative;z-index:1}
.hero-name{font-family:'Luckiest Guy',cursive;font-size:48px;color:#fff;line-height:1;letter-spacing:2px;text-transform:uppercase;position:relative;z-index:1;text-shadow:0 2px 8px rgba(0,0,0,.25)}
.hero-phone{font-size:15px;color:rgba(255,255,255,.9);margin-top:8px;font-weight:600;font-family:'DM Sans',sans-serif;letter-spacing:3px;position:relative;z-index:1;display:inline-flex;align-items:center;gap:6px;background:rgba(0,0,0,.2);padding:5px 14px;border-radius:99px;backdrop-filter:blur(4px)}

/* ── Filter tabs ── */
.filter-bar{padding:16px 14px 8px;display:flex;gap:7px;overflow-x:auto;scrollbar-width:none;background:#fff;border-bottom:1px solid var(--border)}
.filter-bar::-webkit-scrollbar{display:none}
.ftab{flex-shrink:0;border:1.5px solid var(--border);background:transparent;color:var(--muted);padding:7px 18px;border-radius:100px;font-size:13px;font-weight:700;cursor:pointer;transition:all .15s;white-space:nowrap;font-family:var(--font)}
.ftab.active{background:var(--dark);border-color:var(--dark);color:#fff}

/* ── Voucher list ── */
.v-list{padding:14px 14px 0;display:flex;flex-direction:column;gap:12px}

/* ── Card ── */
.vcard{position:relative;border-radius:16px;overflow:hidden;box-shadow:0 1px 6px rgba(0,0,0,.08);cursor:pointer;transition:box-shadow .22s,transform .22s;background:var(--white);display:flex;flex-direction:column;border:1px solid var(--border)}
.vcard:hover{box-shadow:0 6px 28px rgba(0,0,0,.14);transform:translateY(-2px)}
.vcard:active{transform:scale(.98)}
.vcard.disabled{opacity:.6;filter:grayscale(.55)}
.vcard[data-s="expired"]{pointer-events:none;opacity:.45}

/* Image area */
.vc-img-wrap{width:100%;aspect-ratio:16/7;position:relative;overflow:hidden;background:#1a1a2e;flex-shrink:0}
.vc-img-wrap img{width:100%;height:100%;object-fit:cover;object-position:center;display:block;transition:transform .4s ease}
.vcard:hover .vc-img-wrap img{transform:scale(1.04)}
.vc-img-ph{width:100%;height:100%;background:linear-gradient(135deg,#1c1c2e 0%,#16213e 50%,#0f3460 100%);display:grid;place-items:center}
.vc-img-ph i{font-size:44px;color:rgba(255,255,255,.12)}

/* Status pill */
.vc-ribbon{position:absolute;top:10px;right:10px;z-index:3;font-size:9px;font-weight:800;letter-spacing:.8px;text-transform:uppercase;padding:4px 10px;border-radius:100px;backdrop-filter:blur(8px)}
.vc-ribbon.valid{background:rgba(27,140,90,.92);color:#fff}
.vc-ribbon.expired{background:rgba(160,20,20,.88);color:#fff}
.vc-ribbon.used{display:none}

/* Redeemed stamp overlay */
.vc-stamp{
  position:absolute;inset:0;z-index:4;
  display:flex;align-items:center;justify-content:center;
  pointer-events:none;
}
.vc-stamp-inner{
  transform:rotate(-25deg);
  border:3px solid rgba(180,40,40,.7);
  border-radius:6px;
  padding:5px 14px;
  font-size:13px;font-weight:900;letter-spacing:3px;
  text-transform:uppercase;
  color:rgba(180,40,40,.85);
  background:rgba(255,255,255,.08);
  backdrop-filter:blur(2px);
  white-space:nowrap;
  box-shadow:0 0 0 2px rgba(180,40,40,.15);
}
.vcard.disabled .vc-img-wrap{filter:grayscale(.5);opacity:.75}

/* Private / Exclusive badge */
.vc-private-badge{
  position:absolute;top:10px;left:10px;z-index:3;
  font-size:9.5px;font-weight:800;letter-spacing:1.2px;text-transform:uppercase;
  padding:5px 11px;border-radius:4px;
  background:linear-gradient(135deg,#1a1a2e 0%,#16213e 50%,#0f3460 100%);
  color:#e2b96f;
  border:1px solid rgba(226,185,111,.35);
  backdrop-filter:blur(8px);
  display:flex;align-items:center;gap:5px;
  box-shadow:0 2px 8px rgba(0,0,0,.4), inset 0 1px 0 rgba(226,185,111,.15);
}

/* Bottom label */
.vc-label{padding:13px 15px 15px;background:var(--white);display:flex;justify-content:space-between;align-items:flex-end}
.vc-event-name{font-family:var(--cond);font-size:16px;font-weight:800;line-height:1.2;color:var(--text);letter-spacing:.3px}
.vc-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;align-items:center;gap:5px}
.vc-uses-badge{flex-shrink:0;background:color-mix(in srgb,var(--brand) 12%,transparent);color:var(--brand);border:1px solid color-mix(in srgb,var(--brand) 25%,transparent);border-radius:8px;padding:5px 10px;text-align:center}
.vc-uses-num{font-family:var(--cond);font-size:20px;font-weight:900;line-height:1}
.vc-uses-lbl{font-size:9px;font-weight:800;letter-spacing:.4px;text-transform:uppercase;color:var(--muted)}

/* ── Empty ── */
.empty{text-align:center;padding:64px 24px}
.empty-icon{font-size:52px;opacity:.15;margin-bottom:12px}
.empty h5{font-family:var(--cond);font-size:22px;font-weight:900;text-transform:uppercase;margin-bottom:4px}
.empty p{font-size:13px;color:var(--muted);font-weight:600}

/* ── Footer ── */
.user-footer{
  padding:28px 20px;
  text-align:center;
  border-top:1px solid var(--border);
  margin-top:24px;
}
.user-footer-brand{
  font-family:var(--cond);font-size:16px;font-weight:900;
  letter-spacing:1px;text-transform:uppercase;
  color:var(--brand);margin-bottom:4px;
}
.user-footer-copy{
  font-size:11px;color:var(--muted);font-weight:600;
  letter-spacing:.3px;
}
.user-footer-divider{
  width:32px;height:2px;
  background:linear-gradient(90deg,transparent,var(--brand),transparent);
  margin:10px auto;border-radius:2px;
}

/* ── Full-screen detail overlay ── */
.overlay{position:fixed;inset:0;z-index:300;background:#000;display:none;flex-direction:column;overflow-y:auto}
.overlay.open{display:flex}
.sheet-close{position:absolute;top:calc(14px + env(safe-area-inset-top,0px));left:14px;width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,.48);backdrop-filter:blur(6px);border:none;color:#fff;font-size:20px;display:grid;place-items:center;cursor:pointer;z-index:10;transition:background .15s}
.sheet-close:hover{background:rgba(0,0,0,.72)}
.detail-img-wrap{width:100%;aspect-ratio:4/3;position:relative;flex-shrink:0;background:#111}
.detail-img-wrap img{width:100%;height:100%;object-fit:cover;display:block}
.detail-img-ph{width:100%;height:100%;background:linear-gradient(135deg,#1c1c2e,#0f3460);display:grid;place-items:center}
.detail-img-ph i{font-size:72px;color:rgba(255,255,255,.1)}
.detail-body{background:#fff;border-radius:24px 24px 0 0;margin-top:-24px;flex:1;padding:26px 22px max(36px,env(safe-area-inset-bottom,36px));position:relative}
.detail-handle{width:40px;height:4px;background:#e0e0e0;border-radius:2px;margin:0 auto 20px}
.detail-type{font-size:10px;font-weight:800;letter-spacing:1.8px;text-transform:uppercase;color:var(--brand);margin-bottom:5px}
.detail-title{font-family:var(--cond);font-size:32px;font-weight:900;line-height:1.05;color:var(--text);margin-bottom:10px}
.detail-date-row{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:700;color:var(--muted);margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--border)}
.detail-desc{font-size:14px;color:#555;font-weight:600;line-height:1.6;margin-bottom:20px}
.detail-section-label{font-size:10px;font-weight:800;letter-spacing:1.4px;text-transform:uppercase;color:var(--muted);margin-bottom:10px}
.detail-qr-box{background:#fafafa;border:1.5px solid var(--border);border-radius:14px;padding:20px;text-align:center;margin-bottom:14px}
.detail-qr-box canvas,.detail-qr-box img{width:180px;height:180px;margin:0 auto 10px;display:block;border-radius:8px}
.detail-qr-hint{font-size:12px;font-weight:700;color:var(--muted)}
.detail-code-wrap{background:#fafafa;border:1.5px dashed var(--border);border-radius:12px;padding:14px 18px;text-align:center;margin-bottom:20px}
.detail-code-label{font-size:10px;font-weight:800;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
.detail-code-val{font-family:monospace;font-size:20px;font-weight:900;letter-spacing:5px;color:var(--dark)}

/* Multi-claim list */
.claim-list{display:flex;flex-direction:column;gap:8px;margin-bottom:18px}
.claim-item{background:#f9f9fb;border:1.5px solid var(--border);border-radius:12px;padding:14px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px}
.claim-item.redeemed{opacity:.55;filter:grayscale(.4)}
.claim-qr-wrap{width:60px;height:60px;flex-shrink:0;background:#fff;border:1px solid var(--border);border-radius:8px;overflow:hidden;display:grid;place-items:center}
.claim-qr-wrap img{width:60px;height:60px;border-radius:7px}
.claim-info{flex:1;min-width:0}
.claim-num{font-family:var(--cond);font-size:15px;font-weight:800;color:var(--dark)}
.claim-status{font-size:11px;font-weight:700}
.claim-status.pending{color:var(--green)}
.claim-status.redeemed{color:var(--muted)}
.claim-expand-btn{background:none;border:1.5px solid var(--border);border-radius:8px;color:var(--dark);font-size:12px;font-weight:700;padding:6px 12px;cursor:pointer;transition:all .15s;font-family:var(--font)}
.claim-expand-btn:hover{border-color:var(--brand);color:var(--brand)}

.btn-save{display:block;width:100%;background:var(--brand);color:#fff;border:none;border-radius:14px;padding:15px;font-size:15px;font-weight:800;font-family:var(--font);text-align:center;text-decoration:none;cursor:pointer;transition:background .15s;letter-spacing:.3px}
.btn-save:hover{background:var(--brand2);color:#fff}

/* ── Expiry countdown ── */
.expiry-timer{display:inline-flex;align-items:center;gap:5px;background:rgba(180,20,20,.1);border:1px solid rgba(180,20,20,.25);border-radius:8px;padding:6px 12px;font-size:12px;font-weight:700;color:#c0392b;margin-bottom:14px}
.expiry-timer i{font-size:13px}

</style>
</head>
<body>

<div class="topbar">
  <div class="tb-left">
    <div class="tb-avatar"><?= strtoupper(substr($cust['name'],0,1)) ?></div>
  </div>

  <div class="tb-center">
    <span class="tb-brand-name"><?= $brandTitle ?></span>
  </div>
  <div class="tb-right-cell">
    <a href="../logout.php" class="tb-logout"><i class="bi bi-box-arrow-left"></i> Out</a>
  </div>
</div>

<div class="hero">
  <?php if ($brandHeader && file_exists(__DIR__.'/../uploads/'.$brandHeader)): ?>
  <img class="hero-bg-img" src="../uploads/<?= htmlspecialchars($brandHeader) ?>" alt="">
  <?php endif; ?>
  <div class="hero-eyebrow">Welcome</div>
  <div class="hero-name"><?= strtoupper($uname) ?></div>
  <div class="hero-phone"><i class="bi bi-telephone-fill" style="opacity:.7"></i><?= $uphone ?></div>
</div>

<?php
$totalV  = count($vouchers);
$validV  = 0;
$usedV   = 0;
$expV    = 0;
foreach ($vouchers as $v) {
    if ($v['_s'] === 'valid')   { $validV++; }
    elseif ($v['_s'] === 'used')   { $usedV++; }
    elseif ($v['_s'] === 'expired') { $expV++; }
}
?>

<div class="filter-bar">
  <button class="ftab active" onclick="filt('all',this)">All <span id="cnt-all">(<?= $totalV ?>)</span></button>
  <button class="ftab" onclick="filt('valid',this)">Valid <span id="cnt-valid">(<?= $validV ?>)</span></button>
  <button class="ftab" onclick="filt('used',this)">Used <span id="cnt-used">(<?= $usedV ?>)</span></button>
  <button class="ftab" onclick="filt('expired',this)">Expired <span id="cnt-exp">(<?= $expV ?>)</span></button>
</div>

<div class="v-list" id="vlist">

<?php if(empty($vouchers)): ?>
<div class="empty">
  <div class="empty-icon"><i class="bi bi-ticket-perforated"></i></div>
  <h5>No Vouchers Yet</h5>
  <p>Your exclusive vouchers will appear here.<br>Hang tight — the organiser will release them soon!</p>
  <div style="margin-top:20px;font-size:12px;color:var(--muted);background:rgba(0,0,0,.04);padding:12px 16px;border-radius:10px;display:inline-block;line-height:1.6">
    <i class="bi bi-info-circle"></i>&nbsp; Got a private voucher? Ask your organiser to release it to your account.
  </div>
</div>

<?php else: foreach($vouchers as $row):
  $isV   = $row['_s']==='valid';
  $img   = !empty($row['image']) ? '../uploads/'.htmlspecialchars($row['image']) : '';
  $hasImg= !empty($img);
  // isPrivate = true only when voucher MODE is private (type starts with "private")
  // NOT based on is_public (that's just visibility/live status, not exclusivity)
  $isPrivate = str_starts_with($row['type'] ?? '', 'private');

  // Detect if this is a public-clone (auto-generated copy of a public voucher for this user)
  $isClone = !empty($row['parent_voucher_id']);
  // Build array of claims for this voucher
  $claimsForVoucher = [];
  if ($row['times_claimed'] > 0) {
    $cq = $conn->prepare("
      SELECT vc.id, vc.status, vc.claimed_at, cv.code
      FROM voucher_claims vc
      JOIN vouchers cv ON vc.voucher_id=cv.id
      WHERE vc.customer_id=?
        AND (cv.id=? OR cv.parent_voucher_id=?)
      ORDER BY vc.id ASC
    ");
    $cq->bind_param('iii', $uid, $row['id'], $row['id']);
    $cq->execute();
    $cqr = $cq->get_result();
    while ($cl = $cqr->fetch_assoc()) $claimsForVoucher[] = $cl;
    $cq->close();
  }
  $d = htmlspecialchars(json_encode([
    'type'     => $row['type']??'',
    'title'    => $row['event_name'],
    'desc'     => $row['details'],
    'img'      => $img,
    'start'    => $row['start_date'],
    'end'      => $row['end_date'],
    'code'     => $row['code'],
    'status'   => $row['_s'],
    'uid'      => $uid,
    'claims'   => $claimsForVoucher,
    'maxUses'  => $row['max_per_user'],
    'remainingUses' => $row['remaining_uses'],
    'canClaimNext' => (bool)(!str_starts_with($row['type']??'','private') || $row['times_claimed'] > 0),
    'endDate'  => $row['end_date'],
    'isPublic' => !str_starts_with($row['type']??'','private'),
    'vid'      => (int)$row['id'],
  ]));
?>

<div class="vcard <?= !$isV?'disabled':'' ?>"
     data-s="<?= $row['_s'] ?>"
     <?= $isV ? "onclick='openDetail(this)' data-v='{$d}'" : "onclick='openDetailReadonly(this)' data-v='{$d}'" ?>>

  <div class="vc-img-wrap">
    <?php if($hasImg): ?>
    <img src="<?= $img ?>" alt="<?= htmlspecialchars($row['event_name']) ?>" loading="lazy">
    <?php else: ?>
    <div class="vc-img-ph"><i class="bi bi-ticket-perforated"></i></div>
    <?php endif; ?>
    <div class="vc-ribbon <?= $row['_s'] ?>">
      <?php if($row['_s']==='valid'): ?><i class="bi bi-check-circle-fill"></i> Valid
      <?php elseif($row['_s']==='expired'): ?><i class="bi bi-clock"></i> Expired
      <?php else: ?><i class="bi bi-check2-all"></i> Used<?php endif; ?>
    </div>
    <?php if($isPrivate && !$isClone): ?>
    <div class="vc-private-badge"><i class="bi bi-stars"></i> Exclusive</div>
    <?php endif; ?>
    <?php if($row['_s']==='used'): ?>
    <div class="vc-stamp"><div class="vc-stamp-inner">Redeemed</div></div>
    <?php endif; ?>
  </div>

  <div class="vc-label">
    <div>
      <div class="vc-event-name"><?= htmlspecialchars($row['event_name']) ?></div>
      <?php if($row['type']): ?>
      <?php
        $typeLabel = $row['type'] ?? '';
        if (str_contains($typeLabel, ':')) $typeLabel = explode(':', $typeLabel, 2)[1];
      ?>
      <?php if($typeLabel): ?>
      <div class="vc-meta"><i class="bi bi-tag-fill" style="color:var(--brand)"></i><?= htmlspecialchars($typeLabel) ?></div>
      <?php endif; ?>
      <?php endif; ?>
      <?php if($row['end_date'] && $row['end_date'] !== '0000-00-00'): ?>
      <div class="vc-meta"><i class="bi bi-calendar3"></i>Expires <?= date('d M Y', strtotime($row['end_date'])) ?></div>
      <?php endif; ?>
    </div>
    <?php if($row['_s']==='valid' && $row['max_per_user'] > 1): ?>
    <div class="vc-uses-badge">
      <div class="vc-uses-num"><?= $row['remaining_uses'] ?></div>
      <div class="vc-uses-lbl">of <?= $row['max_per_user'] ?><br>left</div>
    </div>
    <?php elseif($row['_s']==='valid'): ?>
    <div class="vc-uses-badge">
      <div class="vc-uses-num">1</div>
      <div class="vc-uses-lbl">of 1<br>left</div>
    </div>
    <?php endif; ?>
  </div>

</div>
<?php endforeach; endif; ?>

<div id="empty-filter" style="display:none" class="empty">
  <div class="empty-icon"><i class="bi bi-funnel"></i></div>
  <h5>Nothing Here</h5><p>No vouchers in this category.</p>
</div>
</div>

<?php if($brandFooter): ?>
<div class="user-footer">
  <div class="user-footer-brand"><?= $brandFooter ?></div>
  <div class="user-footer-divider"></div>
  <div class="user-footer-copy">Copyright &copy; <?= date('Y') ?> Perna Events. All Rights Reserved.</div>
</div>
<?php else: ?>
<div class="user-footer">
  <div class="user-footer-divider"></div>
  <div class="user-footer-copy">Copyright &copy; <?= date('Y') ?> Perna Events. All Rights Reserved.</div>
</div>
<?php endif; ?>

<!-- ── DETAIL OVERLAY ── -->
<div class="overlay" id="overlay">
  <div class="detail-img-wrap">
    <button class="sheet-close" onclick="closeDetail()"><i class="bi bi-arrow-left"></i></button>
    <img id="d-img" src="" alt="" style="display:none">
    <div id="d-img-ph" class="detail-img-ph"><i class="bi bi-image"></i></div>
  </div>
  <div class="detail-body">
    <div class="detail-handle"></div>
    <div class="detail-type" id="d-type"></div>
    <div class="detail-title" id="d-title"></div>
    <div class="detail-date-row">
      <i class="bi bi-calendar-range" style="color:var(--brand)"></i>
      <span id="d-dates"></span>
    </div>
    <div class="detail-desc" id="d-desc"></div>
    <div id="d-qr-section">
      <div class="detail-section-label"><i class="bi bi-qr-code"></i> Your QR Code(s)</div>
      <div id="d-claims-list" class="claim-list"></div>
    </div>
    <div class="detail-code-wrap">
      <div id="d-expiry-timer" class="expiry-timer" style="display:none"><i class="bi bi-hourglass-split"></i> <span id="d-expiry-countdown"></span></div>
      <div class="detail-code-label"><i class="bi bi-keyboard"></i> Manual code</div>
      <div class="detail-code-val" id="d-code"></div>
    </div>
    <a id="d-dl" href="" download="voucher-qr.png" class="btn-save" style="display:none">
      <i class="bi bi-download"></i> Save QR Code
    </a>
  </div>
</div>

<script>
function filt(type,btn){
  const cards=document.querySelectorAll('.vcard');
  const ef=document.getElementById('empty-filter');
  let vis=0;
  cards.forEach(c=>{
    const show=type==='all'||c.dataset.s===type;
    c.style.display=show?'flex':'none';
    if(show)vis++;
  });
  document.querySelectorAll('.ftab').forEach(b=>b.classList.remove('active'));
  btn.classList.add('active');
  if(ef)ef.style.display=vis===0?'block':'none';
}

function fmt(s){
  if(!s||s==='0000-00-00') return '—';
  const [y,m,d]=s.split('-');
  const months=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  return parseInt(d)+' '+months[parseInt(m)-1]+' '+y;
}

const CSRF = document.querySelector('meta[name="csrf-token"]')?.content ?? '';

function buildQRUrl(code, claimId = 0) {
  // uid is now resolved server-side from session — no longer passed in URL
  let url = '../admin/qr.php?code=' + encodeURIComponent(code);
  if (claimId) url += '&claim_id=' + encodeURIComponent(claimId);
  return url;
}

// ── Expiry countdown timer ──
let expiryInterval = null;
let _expiryCard    = null; // reference to the card DOM element being shown

function updateFilterCounts() {
  const cards  = document.querySelectorAll('.vcard');
  let total = 0, valid = 0, used = 0, exp = 0;
  cards.forEach(c => {
    if (c.style.display === 'none') return; // already hidden by filt()
    if (c.dataset.hidden === '1') return;   // hidden by expiry removal
    total++;
    if (c.dataset.s === 'valid')   valid++;
    if (c.dataset.s === 'used')    used++;
    if (c.dataset.s === 'expired') exp++;
  });
  const el = id => document.getElementById(id);
  if (el('cnt-all'))   el('cnt-all').textContent   = `(${total})`;
  if (el('cnt-valid')) el('cnt-valid').textContent = `(${valid})`;
  if (el('cnt-used'))  el('cnt-used').textContent  = `(${used})`;
  if (el('cnt-exp'))   el('cnt-exp').textContent   = `(${exp})`;
}

function removeExpiredCard(card) {
  if (!card) return;
  card.style.transition = 'opacity .4s, max-height .5s, margin .4s, padding .4s';
  card.style.opacity    = '0';
  card.style.maxHeight  = card.offsetHeight + 'px';
  // animate collapse
  requestAnimationFrame(() => {
    card.style.maxHeight = '0';
    card.style.marginBottom = '0';
    card.style.overflow = 'hidden';
  });
  setTimeout(() => {
    card.dataset.hidden = '1';
    card.style.display  = 'none';
    updateFilterCounts();
  }, 500);
}

function startExpiryCountdown(endDateStr, card) {
  clearInterval(expiryInterval);
  _expiryCard = card || null;
  const timerEl = document.getElementById('d-expiry-timer');
  const countEl = document.getElementById('d-expiry-countdown');
  if (!endDateStr || endDateStr === '0000-00-00') { timerEl.style.display='none'; return; }

  // endDate at end of day (23:59:59)
  const endDate = new Date(endDateStr + 'T23:59:59');
  const now     = new Date();
  // Only show countdown if expired within last 7 days
  const diffMs  = now - endDate;
  if (diffMs < 0 || diffMs > 7 * 24 * 3600 * 1000) { timerEl.style.display='none'; return; }

  timerEl.style.display = 'inline-flex';
  const REMOVAL_GRACE = 7 * 24 * 3600 * 1000; // 7 days grace

  function tick() {
    const remaining = REMOVAL_GRACE - (new Date() - endDate);
    if (remaining <= 0) {
      countEl.textContent = 'Removing from your list…';
      clearInterval(expiryInterval);
      // Close overlay and remove the card from the list
      closeDetail();
      removeExpiredCard(_expiryCard);
      return;
    }
    const d = Math.floor(remaining / 86400000);
    const h = Math.floor((remaining % 86400000) / 3600000);
    const m = Math.floor((remaining % 3600000) / 60000);
    const s = Math.floor((remaining % 60000) / 1000);
    countEl.textContent = `Removing in ${d}d ${h}h ${m}m ${s}s`;
  }
  tick();
  expiryInterval = setInterval(tick, 1000);
}

function openDetail(card){ _openDetail(card, false); }
function openDetailReadonly(card){ _openDetail(card, true); }

let detailHistoryActive = false;

function _openDetail(card, readonly){
  const d=JSON.parse(card.getAttribute('data-v'));

  // Image
  const img=document.getElementById('d-img');
  const ph=document.getElementById('d-img-ph');
  if(d.img){img.src=d.img;img.style.display='block';ph.style.display='none';}
  else{img.style.display='none';ph.style.display='grid';}

  const rawType = d.type || '';
  const dispType = rawType.includes(':') ? rawType.split(':')[1] : rawType;
  document.getElementById('d-type').textContent  = dispType || 'VOUCHER';
  document.getElementById('d-title').textContent = d.title;
  document.getElementById('d-desc').textContent  = d.desc||'';
  document.getElementById('d-code').textContent  = d.code||'';

  // Hide code + download only if every allowed use has been redeemed
  const allRedeemed = d.claims && d.claims.length > 0 && d.claims.every(c => c.status === 'redeemed');
  const hasPendingClaim = d.claims && d.claims.some(c => c.status === 'claimed');
  const codeWrap = document.querySelector('.detail-code-wrap');
  if (allRedeemed && (d.remainingUses || 0) <= 0) {
    if (codeWrap) codeWrap.style.display = 'none';
  } else {
    if (codeWrap) codeWrap.style.display = '';
  }
  document.getElementById('d-dates').textContent = fmt(d.start) + ' – ' + fmt(d.end);

  // Expiry countdown (only for expired vouchers)
  if (d.status === 'expired') {
    startExpiryCountdown(d.endDate || d.end, card);
  } else {
    clearInterval(expiryInterval);
    document.getElementById('d-expiry-timer').style.display = 'none';
  }

  // Build claims list
  const claimsList = document.getElementById('d-claims-list');
  const qrSection  = document.getElementById('d-qr-section');
  const dlBtn      = document.getElementById('d-dl');
  claimsList.innerHTML = '';

  // Voucher with available uses and no pending QR — claim the next unique code
  if (!readonly && d.canClaimNext && !hasPendingClaim && (d.remainingUses || 0) > 0 && d.status === 'valid') {
    qrSection.style.display = 'block';
    claimsList.innerHTML = `<div class="detail-qr-box" style="opacity:.5"><div style="text-align:center;padding:20px;color:var(--muted)"><i class="bi bi-qr-code" style="font-size:36px;display:block;margin-bottom:8px"></i>Getting your unique code…</div></div>`;
    dlBtn.style.display = 'none';

    fetch('?action=autoclaim', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': CSRF },
      body: 'voucher_id=' + encodeURIComponent(d.vid)
    })
      .then(r => r.json())
      .then(res => {
        if (res.ok) {
          // Got unique code — update card data and show QR
          d.code = res.code;
          d.claims = (d.claims || []).filter(c => String(c.id) !== String(res.claim_id));
          d.claims.push({ id: res.claim_id, status: 'claimed', code: res.code });
          d.remainingUses = Math.max(0, (d.remainingUses || 1) - 1);
          card.setAttribute('data-v', JSON.stringify(d));
          document.getElementById('d-code').textContent = res.code;
          const qrUrl = buildQRUrl(res.code, res.claim_id);
          claimsList.innerHTML = `<div class="detail-qr-box"><img src="${qrUrl}" alt="QR Code"><div class="detail-qr-hint"><i class="bi bi-camera"></i> Show this to staff to redeem</div></div>`;
          dlBtn.href = qrUrl; dlBtn.style.display = 'block';
          window._needsReload = true;
          // Start polling now that we have the real unique code
          startRedeemPolling(res.code, res.claim_id, card);
        } else {
          claimsList.innerHTML = `<div style="text-align:center;padding:20px;color:#e05252"><i class="bi bi-exclamation-circle"></i> Could not load voucher. Please refresh.</div>`;
        }
      })
      .catch(() => {
        claimsList.innerHTML = `<div style="text-align:center;padding:20px;color:#e05252"><i class="bi bi-wifi-off"></i> Network error. Please try again.</div>`;
      });

  } else if (readonly || !d.claims || d.claims.length === 0) {
    qrSection.style.display = d.status === 'valid' && (!d.claims || d.claims.length === 0) ? 'block' : 'none';
    if (d.status === 'valid' && (!d.claims || d.claims.length === 0)) {
      const claimId = d.claims && d.claims[0] ? d.claims[0].id : 0;
      const qrUrl = buildQRUrl(d.code, claimId);
      claimsList.innerHTML = `<div class="detail-qr-box"><img src="${qrUrl}" alt="QR Code"><div class="detail-qr-hint"><i class="bi bi-camera"></i> Show this to staff to redeem</div></div>`;
      dlBtn.href = qrUrl; dlBtn.style.display = 'block';
    } else {
      dlBtn.style.display = 'none';
    }
  } else {
    qrSection.style.display = 'block';
    dlBtn.style.display = 'none';
    const pending = d.claims.filter(c=>c.status==='claimed');
    const redeemed = d.claims.filter(c=>c.status==='redeemed');
    const allClaims = [...pending, ...redeemed];
    allClaims.forEach((cl, i) => {
      const claimCode = cl.code || d.code;
      const qrUrl = buildQRUrl(claimCode, cl.id);
      const isRed = cl.status === 'redeemed';
      const item = document.createElement('div');
      item.className = 'claim-item' + (isRed?' redeemed':'');
      if (isRed) {
        item.innerHTML = `
          <div style="width:100%;text-align:center;padding:24px 16px">
            <div style="font-size:36px;margin-bottom:8px">🎟️</div>
            <div style="font-size:14px;font-weight:700;color:var(--muted);letter-spacing:1px;text-transform:uppercase">Voucher Redeemed</div>
            <div style="font-size:12px;color:var(--muted);margin-top:6px;opacity:.7">This voucher has already been used</div>
          </div>`;
      } else {
        item.innerHTML = `
          <div class="claim-qr-wrap"><img src="${qrUrl}" alt="QR ${i+1}" loading="lazy"></div>
          <div class="claim-info">
            <div class="claim-num">Use #${i+1}</div>
            <div class="claim-status ${cl.status}"><i class="bi bi-qr-code"></i> Ready to use</div>
          </div>
          <button class="claim-expand-btn" onclick="expandQR('${qrUrl}',${i+1})"><i class="bi bi-arrows-fullscreen"></i> Show</button>`;
      }
      claimsList.appendChild(item);
    });
  }

  const ov=document.getElementById('overlay');
  ov.classList.add('open');
  ov.scrollTop=0;
  document.body.style.overflow='hidden';
  if (!detailHistoryActive) {
    history.pushState({ voucherOverlay: true }, '', '#voucher');
    detailHistoryActive = true;
  }

  // Start polling if voucher has pending claims (not yet redeemed)
  // For public vouchers with no claims yet, polling is started inside the autoclaim callback
  if (d.claims && d.claims.some(c => c.status === 'claimed')) {
    const firstPending = d.claims.find(c => c.status === 'claimed');
    startRedeemPolling(firstPending?.code || d.code, firstPending ? firstPending.id : 0, card);
  } else if (!d.isPublic || (d.claims && d.claims.length > 0)) {
    clearInterval(redeemPollInterval);
  }
  // If isPublic with no claims — polling started inside autoclaim callback above
}

function expandQR(url, num) {
  const box = document.createElement('div');
  box.style.cssText='position:fixed;inset:0;z-index:500;background:rgba(0,0,0,.92);display:grid;place-items:center;cursor:pointer';
  box.innerHTML=`<div style="text-align:center"><img src="${url}" style="width:260px;height:260px;border-radius:12px;display:block;margin:0 auto 16px"><div style="color:#fff;font-size:14px;font-weight:700">Use #${num} — Show to staff</div><div style="color:rgba(255,255,255,.45);font-size:12px;margin-top:6px">Tap anywhere to close</div></div>`;
  box.onclick = () => document.body.removeChild(box);
  document.body.appendChild(box);
}

function closeDetail(fromPop = false){
  clearInterval(expiryInterval);
  clearInterval(redeemPollInterval);
  const ov = document.getElementById('overlay');
  const wasOpen = ov.classList.contains('open');
  const shouldReload = !!window._needsReload;
  ov.classList.remove('open');
  document.body.style.overflow='';
  if (detailHistoryActive) {
    detailHistoryActive = false;
    if (!fromPop && wasOpen && !shouldReload) {
      history.back();
    }
  }
  if (shouldReload) {
    window._needsReload = false;
    window.location.href = window.location.pathname + window.location.search;
  }
}
window.addEventListener('popstate', () => {
  if (document.getElementById('overlay').classList.contains('open')) {
    closeDetail(true);
  }
});

// Poll for redemption status every 8 seconds while overlay is open
let redeemPollInterval = null;
function startRedeemPolling(code, claimId = 0, card = null) {
  clearInterval(redeemPollInterval);
  redeemPollInterval = setInterval(async () => {
    if (!document.getElementById('overlay').classList.contains('open')) {
      clearInterval(redeemPollInterval); return;
    }
    try {
      let statusUrl = '?action=status&code=' + encodeURIComponent(code);
      if (claimId) statusUrl += '&claim_id=' + encodeURIComponent(claimId);
      const res  = await fetch(statusUrl);
      const data = await res.json();
      if (data.redeemed) {
        clearInterval(redeemPollInterval);

        // 🎮 LEVEL UP — Web Audio API generated sound, no file needed
        try {
          const ctx = new (window.AudioContext || window.webkitAudioContext)();
          const master = ctx.createGain();
          master.gain.setValueAtTime(0.5, ctx.currentTime);
          master.connect(ctx.destination);

          const t = (f, s, d, v=0.4, type='square') => {
            const osc = ctx.createOscillator();
            const g   = ctx.createGain();
            osc.type = type;
            osc.frequency.setValueAtTime(f, ctx.currentTime + s);
            g.gain.setValueAtTime(0, ctx.currentTime + s);
            g.gain.linearRampToValueAtTime(v, ctx.currentTime + s + 0.01);
            g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + s + d);
            osc.connect(g); g.connect(master);
            osc.start(ctx.currentTime + s);
            osc.stop(ctx.currentTime + s + d + 0.05);
          };

          // Ascending scale run then triumphant chord
          [262, 330, 392, 523, 659, 784, 1047].forEach((f, i) => t(f, i * 0.08, 0.15, 0.25));
          t(1047, 0.56, 0.5, 0.4, 'sine');
          t(1319, 0.58, 0.5, 0.35, 'sine');
          t(1568, 0.60, 0.6, 0.4, 'sine');
        } catch(e) {}

        // 🎉 Celebration flash animation on overlay
        const overlay = document.getElementById('overlay');
        const flash = document.createElement('div');
        flash.style.cssText = 'position:fixed;inset:0;z-index:999;pointer-events:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px';
        flash.innerHTML = `
          <div style="font-size:72px;animation:kachingSpin .5s ease-out">🎉</div>
          <div style="font-size:28px;font-weight:900;color:#fff;text-shadow:0 2px 12px rgba(0,0,0,.5);letter-spacing:2px;animation:kachingPop .4s ease-out">REDEEMED!</div>
          <div style="font-size:15px;color:rgba(255,255,255,.8);font-weight:600">✓ Voucher successfully used</div>`;
        overlay.appendChild(flash);

        // Add keyframes if not already added
        if (!document.getElementById('kaching-style')) {
          const style = document.createElement('style');
          style.id = 'kaching-style';
          style.textContent = `
            @keyframes kachingSpin { 0%{transform:scale(0) rotate(-180deg);opacity:0} 60%{transform:scale(1.3) rotate(10deg)} 100%{transform:scale(1) rotate(0)} }
            @keyframes kachingPop  { 0%{transform:scale(0);opacity:0} 60%{transform:scale(1.15)} 100%{transform:scale(1);opacity:1} }
          `;
          document.head.appendChild(style);
        }

        setTimeout(() => flash.remove(), 2500);

        // Update UI after animation
        setTimeout(() => {
          document.getElementById('d-claims-list').querySelectorAll('.claim-status').forEach(el => {
            el.className = 'claim-status redeemed';
            el.innerHTML = '<i class="bi bi-check-circle-fill"></i> Redeemed';
          });
          document.getElementById('d-claims-list').querySelectorAll('.claim-item').forEach(el => el.classList.add('redeemed'));
          document.getElementById('d-claims-list').querySelectorAll('.claim-expand-btn').forEach(el => el.remove());
          document.getElementById('d-qr-section').style.display = 'none';
          // Add stamp to the card behind the overlay
          if (card) {
            const imgWrap = card.querySelector('.vc-img-wrap');
            if (imgWrap && !imgWrap.querySelector('.vc-stamp')) {
              imgWrap.insertAdjacentHTML('beforeend', '<div class="vc-stamp"><div class="vc-stamp-inner">Redeemed</div></div>');
            }
            card.classList.add('disabled');
            card.setAttribute('data-s', 'used');
            card.setAttribute('onclick', 'openDetailReadonly(this)');
            const ribbon = card.querySelector('.vc-ribbon');
            if (ribbon) ribbon.style.display = 'none';
          }
          // Hide code + download button
          const codeWrap = document.querySelector('.detail-code-wrap');
          if (codeWrap) codeWrap.style.display = 'none';
          document.getElementById('d-dl').style.display = 'none';
          window._needsReload = true;
        }, 600);
      }
    } catch(e) {}
  }, 3000);
}
</script>
</body></html>
