Sedang semak...

Taikun Tyre Logo

SPIN & WIN

Masukkan nombor telefon yang didaftarkan dalam borang untuk mula spin!

Nombor telefon mesti sama dengan yang diisi dalam borang pendaftaran.
Setiap pelanggan hanya layak 1 spin sahaja.

🎉 SPIN & WIN TAIKUN 🎉

Tahniah! Anda layak spin. 1 peluang sahaja.

SPIN
NOW

BAKI HADIAH


Telefon
Nama
Status
Layak Spin ✓

Result disimpan automatik ke Google Sheets

⚙️ ADMIN PANEL

Ubah nama dan kuantiti hadiah. Tekan Simpan untuk aktifkan.
const SCRIPT_URL = 'https://script.google.com/macros/s/AKfycbwPfvkjQC6m02sKONkPp7z1pysd55C19GgN6L0t9mjwovv3Nm1VYpphlfzLGrWumLAJcg/exec'; const HADIAH_DEFAULT = { 'FREE 38 Checkpoint': 30, 'Mesin Goyang': 3, 'Basic Aircond Service': 3, 'Payung': 10, 'Voucher RM10 (min. spend RM250)': 30, 'Voucher RM30 (min. spend RM350)': 20, 'Voucher RM50 (min. spend RM500)': 10 }; // Warna wheel — gelap sikit untuk match tema hitam emas const COLORS = ['#d4af37','#c0392b','#2980b9','#27ae60','#8e44ad','#e67e22','#16a085','#e74c3c']; const STOCK_KEY = 'taikun_stock_v6'; const ROT_KEY = 'taikun_rot_v6'; // Per-session state — TIDAK disimpan dalam localStorage // Reset setiap kali goBack() dipanggil let spinning = false; let spun = false; let rot = parseFloat(localStorage.getItem(ROT_KEY) || '0'); let cust = { phone: '', nama: '', promosi: '' }; const canvas = document.getElementById('wheelCanvas'); const ctx = canvas.getContext('2d'); // ── UI ─────────────────────────────────── function showScreen(id) { document.querySelectorAll('.screen').forEach(s => s.classList.remove('active')); document.getElementById(id).classList.add('active'); } function overlayOn(msg) { document.getElementById('overlay-msg').textContent = msg; document.getElementById('overlay').classList.add('on'); } function overlayOff() { document.getElementById('overlay').classList.remove('on'); } function setMsg(msg, err) { const el = document.getElementById('entry-msg'); el.textContent = msg; el.className = 'entry-msg' + (err ? ' err' : ''); } // ── BACK — reset penuh untuk pelanggan baru ── function goBack() { spinning = false; spun = false; cust = { phone: '', nama: '', promosi: '' }; document.getElementById('inp-phone').value = ''; document.getElementById('inp-nama').value = ''; document.getElementById('enterBtn').disabled = false; setMsg(''); document.getElementById('spinResult').textContent = ''; document.getElementById('spinStatus').textContent = ''; document.getElementById('spinBtn').disabled = false; document.getElementById('spinBtn').textContent = '🎰 SPIN SEKARANG'; document.getElementById('c-status').textContent = 'Layak Spin ✓'; document.getElementById('c-status').style.color = '#86efac'; showScreen('screen-entry'); } // ── ENTRY ──────────────────────────────── async function enterSpin() { const rawPhone = document.getElementById('inp-phone').value.trim(); // Normalise: buang spaces, dashes const phone = rawPhone.replace(/[\s\-]/g, ''); const nama = document.getElementById('inp-nama').value.trim(); if (!phone || phone.length < 9) { setMsg('Sila masukkan nombor telefon yang sah (min 9 digit).', true); return; } const btn = document.getElementById('enterBtn'); btn.disabled = true; setMsg(''); overlayOn('Mengesahkan kelayakan...'); try { const res = await fetch(`${SCRIPT_URL}?action=checkPhone&phone=${encodeURIComponent(phone)}`); const json = await res.json(); overlayOff(); if (json.error) { // Script error — boleh spin (jangan block customer) console.warn('Script error (offline fallback):', json.error); cust = { phone, nama: nama || phone, promosi: '' }; goToWheel(); btn.disabled = false; return; } if (json.hasSpun) { setMsg('❌ Nombor ' + phone + ' sudah pernah spin. Setiap pelanggan hanya 1 spin sahaja.', true); btn.disabled = false; return; } // ✅ Belum spin cust = { phone, nama: json.nama || nama || phone, promosi: json.promosi || '' }; goToWheel(); } catch (err) { overlayOff(); console.warn('Network error, offline fallback:', err); cust = { phone, nama: nama || phone, promosi: '' }; setMsg('⚠️ Tidak dapat sambung — spin dibenarkan.', false); setTimeout(goToWheel, 600); } btn.disabled = false; } function goToWheel() { // Reset spin state untuk user baru spinning = false; spun = false; document.getElementById('c-phone').textContent = cust.phone; document.getElementById('c-nama').textContent = cust.nama || '—'; document.getElementById('c-status').textContent = 'Layak Spin ✓'; document.getElementById('c-status').style.color = '#86efac'; document.getElementById('spinResult').textContent = ''; document.getElementById('spinStatus').textContent = ''; document.getElementById('spinBtn').disabled = false; document.getElementById('spinBtn').textContent = '🎰 SPIN SEKARANG'; renderStock(); draw(); showScreen('screen-spin'); } document.getElementById('inp-phone').addEventListener('keydown', e => { if (e.key === 'Enter') enterSpin(); }); // ── STOCK ──────────────────────────────── function getStock() { try { const s = localStorage.getItem(STOCK_KEY); return s ? JSON.parse(s) : { ...HADIAH_DEFAULT }; } catch { return { ...HADIAH_DEFAULT }; } } function setStock(d) { localStorage.setItem(STOCK_KEY, JSON.stringify(d)); } function availNames(d) { return Object.keys(d).filter(k => d[k] > 0); } function weightedPool(d) { const arr = []; Object.entries(d).forEach(([n,q]) => { for(let i=0;i { total += q; const row = document.createElement('div'); row.className = 'stock-row'; row.innerHTML = '' + n + '' + q + ''; list.appendChild(row); }); if (total <= 0 && !spun) { document.getElementById('spinBtn').disabled = true; document.getElementById('spinResult').textContent = '❌ Semua hadiah habis.'; } } // Shorten label for wheel display — nama penuh tetap disimpan function shortLabel(n) { const map = { 'Voucher RM10 (min. spend RM250)': 'Voucher RM10', 'Voucher RM30 (min. spend RM350)': 'Voucher RM30', 'Voucher RM50 (min. spend RM500)': 'Voucher RM50', }; const s = map[n] || n; return s.length > 20 ? s.slice(0, 20) + '…' : s; } // ── DRAW WHEEL ─────────────────────────── function draw(r) { if (r === undefined) r = rot; const d = getStock(), names = availNames(d); ctx.clearRect(0, 0, 480, 480); const cx = 240, cy = 240, radius = 228; if (!names.length) { ctx.fillStyle = '#d4af37'; ctx.font = 'bold 24px Arial'; ctx.textAlign = 'center'; ctx.fillText('Hadiah Habis', cx, cy); return; } const segAng = (Math.PI*2) / names.length; const rotRad = r * Math.PI / 180; names.forEach((n, i) => { const s = -Math.PI/2 + rotRad + i*segAng; const e = s + segAng; ctx.beginPath(); ctx.moveTo(cx,cy); ctx.arc(cx,cy,radius,s,e); ctx.closePath(); ctx.fillStyle = COLORS[i % COLORS.length]; ctx.fill(); // Gold border ctx.strokeStyle = 'rgba(212,175,55,0.7)'; ctx.lineWidth = 2.5; ctx.stroke(); ctx.save(); ctx.translate(cx, cy); ctx.rotate(s + segAng/2); ctx.textAlign = 'right'; ctx.fillStyle = 'white'; ctx.shadowColor = 'rgba(0,0,0,0.8)'; ctx.shadowBlur = 4; ctx.font = 'bold 13px Arial'; ctx.fillText(shortLabel(n), radius-16, 6); ctx.shadowBlur = 0; ctx.restore(); }); // Centre circle — gold const grad = ctx.createRadialGradient(cx,cy,20,cx,cy,68); grad.addColorStop(0, '#f5e07a'); grad.addColorStop(1, '#c9a227'); ctx.beginPath(); ctx.arc(cx,cy,70,0,Math.PI*2); ctx.fillStyle = grad; ctx.fill(); ctx.lineWidth = 4; ctx.strokeStyle = 'rgba(255,255,255,0.6)'; ctx.stroke(); } // ── SPIN ───────────────────────────────── function doSpin() { if (spinning || spun) return; const d = getStock(); const wp = weightedPool(d); const av = availNames(d); if (!wp.length) { document.getElementById('spinResult').textContent='❌ Hadiah habis.'; return; } spinning = true; document.getElementById('spinBtn').disabled = true; document.getElementById('spinResult').textContent = '🎰 Sedang spin...'; document.getElementById('spinStatus').textContent = ''; const winner = wp[Math.floor(Math.random()*wp.length)]; const idx = av.indexOf(winner); const segDeg = 360 / av.length; const target = idx * segDeg + segDeg / 2; const endRot = rot + 360*(5 + Math.floor(Math.random()*3)) + (360 - target); const startRot = rot; const dur = 4800, t0 = performance.now(); function frame(now) { const t = Math.min((now-t0)/dur, 1); const ease = 1 - Math.pow(1-t, 4); rot = startRot + (endRot-startRot)*ease; draw(rot); if (t < 1) { requestAnimationFrame(frame); return; } rot = endRot % 360; localStorage.setItem(ROT_KEY, rot); const latest = getStock(); if (latest[winner] > 0) latest[winner]--; setStock(latest); spinning = false; spun = true; document.getElementById('spinBtn').disabled = true; document.getElementById('spinBtn').textContent = '✅ SUDAH SPIN'; document.getElementById('c-status').innerHTML = 'Menang: ' + winner + ' 🎁'; document.getElementById('spinResult').innerHTML = '🎁 TAHNIAH! ANDA MENANG:
' + winner + '
'; renderStock(); draw(rot); saveResult(winner); } requestAnimationFrame(frame); } // ── SAVE ───────────────────────────────── async function saveResult(prize) { document.getElementById('spinStatus').textContent = '💾 Menyimpan result...'; try { const r = await fetch(SCRIPT_URL, { method: 'POST', headers: { 'Content-Type': 'text/plain;charset=utf-8' }, body: JSON.stringify({ phone: cust.phone, nama: cust.nama, promosi: cust.promosi, result: prize, timestamp: new Date().toISOString() }) }); const j = await r.json(); document.getElementById('spinStatus').textContent = j.success ? '✅ Result disimpan! Kolum RESULT HADIAH dikemaskini.' : '⚠️ ' + (j.error||'Ralat simpan'); } catch(e) { document.getElementById('spinStatus').textContent = '❌ Gagal simpan. Catat manual: ' + prize; } } // ── ADMIN RESET (spin screen) ───────────── function resetStock() { if (!confirm('Reset semua stok hadiah ke asal?')) return; setStock({ ...HADIAH_DEFAULT }); rot = 0; localStorage.setItem(ROT_KEY, '0'); document.getElementById('spinResult').textContent = '✅ Stok direset.'; document.getElementById('spinStatus').textContent = ''; renderStock(); draw(0); } // ══════════════════════════════════════════ // ADMIN PANEL // Akses: klik butang Admin Panel → masuk password // Atau URL: ?admin=7269 // ══════════════════════════════════════════ const ADMIN_PASS = '7269'; function openAdmin() { const pass = prompt('Masukkan password admin:'); if (pass === ADMIN_PASS) { switchTab('hadiah'); renderAdminPanel(); showScreen('screen-admin'); } else if (pass !== null) { alert('❌ Password salah.'); } } // ── Tab switching ───────────────────────── function switchTab(tab) { document.getElementById('panel-hadiah').style.display = tab === 'hadiah' ? 'block' : 'none'; document.getElementById('panel-records').style.display = tab === 'records' ? 'block' : 'none'; const activeStyle = 'flex:1;padding:11px;border:none;border-radius:10px;cursor:pointer;font-size:14px;font-weight:800;background:linear-gradient(135deg,#d4af37,#c9a227);color:#1a1a1a;'; const inactiveStyle = 'flex:1;padding:11px;border:1px solid rgba(255,255,255,0.15);border-radius:10px;cursor:pointer;font-size:14px;font-weight:800;background:rgba(255,255,255,0.08);color:white;'; document.getElementById('tab-hadiah').style.cssText = tab === 'hadiah' ? activeStyle : inactiveStyle; document.getElementById('tab-records').style.cssText = tab === 'records' ? activeStyle : inactiveStyle; if (tab === 'records') loadRecords(); } // ── TAB 1: Urus Hadiah ──────────────────── function renderAdminPanel() { const d = getStock(); const list = document.getElementById('adminHadiahList'); list.innerHTML = ''; Object.entries(d).forEach(([name, qty]) => list.appendChild(makeAdminRow(name, qty))); } function makeAdminRow(name, qty) { const row = document.createElement('div'); row.style.cssText = 'display:grid;grid-template-columns:1fr 90px 36px;gap:8px;align-items:center;'; row.innerHTML = '' + '' + ''; return row; } function addHadiahRow() { const list = document.getElementById('adminHadiahList'); list.appendChild(makeAdminRow('', 0)); list.lastChild.querySelector('input[type="text"]').focus(); } function saveAdminHadiah() { const rows = document.querySelectorAll('#adminHadiahList > div'); const newStock = {}; let hasEmpty = false; rows.forEach(row => { const inputs = row.querySelectorAll('input'); const name = inputs[0].value.trim(); const qty = parseInt(inputs[1].value) || 0; if (name) newStock[name] = qty; else hasEmpty = true; }); const msg = document.getElementById('adminMsg'); if (hasEmpty) { msg.textContent='⚠️ Ada nama hadiah kosong.'; msg.style.color='#fca5a5'; return; } if (!Object.keys(newStock).length) { msg.textContent='⚠️ Tiada hadiah!'; msg.style.color='#fca5a5'; return; } setStock(newStock); renderStock(); draw(); msg.style.color = '#86efac'; msg.textContent = '✅ Hadiah berjaya disimpan dan diaktifkan!'; setTimeout(() => { msg.textContent = ''; }, 3000); } function resetToDefault() { if (!confirm('Reset ke hadiah default asal?')) return; setStock({ ...HADIAH_DEFAULT }); renderAdminPanel(); const msg = document.getElementById('adminMsg'); msg.style.color='#86efac'; msg.textContent='✅ Reset berjaya.'; setTimeout(()=>{ msg.textContent=''; }, 2000); } // ── TAB 2: Rekod Spin ───────────────────── async function loadRecords() { const statusEl = document.getElementById('recordsStatus'); const tableEl = document.getElementById('recordsTable'); const countEl = document.getElementById('recordsCount'); statusEl.textContent = '⏳ Memuatkan rekod...'; tableEl.innerHTML = ''; countEl.textContent = ''; try { const res = await fetch(SCRIPT_URL + '?action=getRecords'); const json = await res.json(); if (json.error) throw new Error(json.error); if (!json.records || !json.records.length) { statusEl.textContent = 'Tiada rekod lagi.'; return; } statusEl.textContent = ''; countEl.textContent = json.records.length + ' rekod'; renderRecordsTable(json.records); } catch(err) { statusEl.textContent = '❌ Gagal muatkan rekod: ' + err.message; } } function renderRecordsTable(records) { const tableEl = document.getElementById('recordsTable'); let html = ''; html += ''; ['Masa','Nama','Telefon','Promosi','Hadiah',''].forEach(h => { html += ''; }); html += ''; records.forEach((r, i) => { const bg = i % 2 === 0 ? 'rgba(255,255,255,0.03)' : 'transparent'; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; }); html += '
' + h + '
' + (r.timestamp||'—') + '' + (r.nama||'—') + '' + (r.phone||'—') + '' + (r.promosi||'—') + '' + (r.result||'—') + ''; if (r.result && r.result !== '—') { html += ''; } html += '
'; tableEl.innerHTML = html; } async function deleteRecord(rowIndex, btn) { if (!confirm('Padam rekod ini? Ini akan kosongkan RESULT HADIAH untuk nombor tersebut — pelanggan boleh spin semula.')) return; btn.disabled = true; btn.textContent = '...'; try { const res = await fetch(SCRIPT_URL, { method: 'POST', headers: { 'Content-Type': 'text/plain;charset=utf-8' }, body: JSON.stringify({ action: 'deleteRecord', rowIndex }) }); const json = await res.json(); if (json.success) { btn.closest('tr').style.opacity = '0.3'; btn.textContent = '✓ Dipadam'; document.getElementById('recordsCount').textContent = (parseInt(document.getElementById('recordsCount').textContent) - 1) + ' rekod'; } else { throw new Error(json.error || 'Unknown'); } } catch(err) { btn.disabled = false; btn.textContent = '🗑 Padam'; alert('❌ Gagal padam: ' + err.message); } } // ── AUTO URL PARAMS ─────────────────────── (function() { const p = new URLSearchParams(location.search); const phone = p.get('phone') || ''; const nama = p.get('nama') || p.get('name') || ''; const admin = p.get('admin') || ''; if (admin === ADMIN_PASS) { switchTab('hadiah'); renderAdminPanel(); showScreen('screen-admin'); return; } if (phone) { document.getElementById('inp-phone').value = phone; if (nama) document.getElementById('inp-nama').value = nama; setTimeout(enterSpin, 300); } })(); draw();