<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>VYDENCE 2026 — Сканер гостей</title>
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, Arial, sans-serif; background: #0f1923; color: #fff; min-height: 100vh; }
.header { background: #1A3A5F; padding: 16px 20px; text-align: center; }
.header h1 { font-size: 15px; font-weight: 600; color: #fff; letter-spacing: 0.5px; }
.header p { font-size: 12px; color: #5BB8D4; margin-top: 2px; }
.scanner-wrap { padding: 20px; }
#reader { width: 100%; border-radius: 12px; overflow: hidden; background: #000; }
#reader video { width: 100% !important; border-radius: 12px; }
.hint { text-align: center; color: #8899aa; font-size: 13px; margin-top: 12px; }
.overlay { display: none; position: fixed; inset: 0; z-index: 100; align-items: center; justify-content: center; padding: 24px; }
.overlay.show { display: flex; }
.overlay-bg { position: absolute; inset: 0; }
.overlay-bg.success { background: rgba(13, 110, 60, 0.97); }
.overlay-bg.error { background: rgba(140, 30, 30, 0.97); }
.overlay-bg.already { background: rgba(140, 100, 0, 0.97); }
.result-card { position: relative; z-index: 1; background: rgba(255,255,255,0.1); border-radius: 16px; padding: 28px 24px; text-align: center; width: 100%; max-width: 340px; border: 1px solid rgba(255,255,255,0.2); }
.result-icon { font-size: 64px; margin-bottom: 16px; }
.result-title { font-size: 22px; font-weight: 700; margin-bottom: 8px; }
.result-name { font-size: 18px; font-weight: 600; margin-bottom: 4px; }
.result-org { font-size: 14px; opacity: 0.8; margin-bottom: 4px; }
.result-email { font-size: 13px; opacity: 0.6; margin-bottom: 20px; }
.btn-next { background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.4); color: #fff; font-size: 16px; font-weight: 600; padding: 14px 32px; border-radius: 10px; cursor: pointer; width: 100%; }
.btn-next:active { opacity: 0.7; }
.stats { display: flex; gap: 10px; padding: 0 20px 20px; }
.stat { flex: 1; background: #1a2a3a; border-radius: 10px; padding: 12px; text-align: center; }
.stat-num { font-size: 24px; font-weight: 700; color: #5BB8D4; }
.stat-label { font-size: 11px; color: #8899aa; margin-top: 2px; }
.webhook-setup { margin: 0 20px 20px; background: #1a2a3a; border-radius: 10px; padding: 16px; }
.webhook-setup label { font-size: 12px; color: #8899aa; display: block; margin-bottom: 6px; }
.webhook-setup input { width: 100%; background: #0f1923; border: 1px solid #2a4060; border-radius: 8px; padding: 10px 12px; color: #fff; font-size: 13px; }
.webhook-setup input::placeholder { color: #4a6080; }
.save-btn { margin-top: 8px; background: #1A3A5F; border: none; color: #5BB8D4; font-size: 13px; padding: 8px 16px; border-radius: 8px; cursor: pointer; width: 100%; }
</style>
</head>
<body>
<div class="header">
<h1>САММИТ VYDENCE 2026</h1>
<p>Сканер регистрации гостей</p>
</div>
<div class="stats">
<div class="stat">
<div class="stat-num" id="cnt-ok">0</div>
<div class="stat-label">Отмечено</div>
</div>
<div class="stat">
<div class="stat-num" id="cnt-already">0</div>
<div class="stat-label">Повторных</div>
</div>
<div class="stat">
<div class="stat-num" id="cnt-err">0</div>
<div class="stat-label">Не найдено</div>
</div>
</div>
<div class="webhook-setup">
<label>URL вашего n8n Webhook (вставьте один раз):</label>
<input type="text" id="webhook-url" placeholder="https://your-n8n.com/webhook/vydence-scan" />
<button class="save-btn" onclick="saveWebhook()">Сохранить</button>
</div>
<div class="scanner-wrap">
<div id="reader"></div>
<p class="hint">Наведите камеру на QR-код гостя</p>
</div>
<div class="overlay" id="overlay">
<div class="overlay-bg" id="overlay-bg"></div>
<div class="result-card">
<div class="result-icon" id="res-icon"></div>
<div class="result-title" id="res-title"></div>
<div class="result-name" id="res-name"></div>
<div class="result-org" id="res-org"></div>
<div class="result-email" id="res-email"></div>
<button class="btn-next" onclick="closeOverlay()">Следующий гость →</button>
</div>
</div>
<script>
let scanning = true;
let cntOk = 0, cntAlready = 0, cntErr = 0;
let lastScanned = '';
let webhookUrl = localStorage.getItem('vydence_webhook') || '';
document.getElementById('webhook-url').value = webhookUrl;
function saveWebhook() {
webhookUrl = document.getElementById('webhook-url').value.trim();
localStorage.setItem('vydence_webhook', webhookUrl);
alert('Сохранено!');
}
function parseQR(text) {
const lines = text.split('\n');
const data = { raw: text };
lines.forEach(line => {
if (line.startsWith('Участник:')) data.name = line.replace('Участник:', '').trim();
else if (line.startsWith('Email:')) data.email = line.replace('Email:', '').trim();
else if (line.startsWith('Клиника:')) data.org = line.replace('Клиника:', '').trim();
else if (line.startsWith('Город:')) data.city = line.replace('Город:', '').trim();
else if (line.startsWith('Тел:')) data.phone = line.replace('Тел:', '').trim();
});
return data;
}
function showResult(type, guest) {
const bg = document.getElementById('overlay-bg');
bg.className = 'overlay-bg ' + type;
const icons = { success: '✅', error: '❌', already: '⚠️' };
const titles = { success: 'Добро пожаловать!', error: 'Не найден', already: 'Уже отмечен' };
document.getElementById('res-icon').textContent = icons[type];
document.getElementById('res-title').textContent = titles[type];
document.getElementById('res-name').textContent = guest.name || '—';
document.getElementById('res-org').textContent = guest.org || '';
document.getElementById('res-email').textContent = guest.email || '';
document.getElementById('overlay').classList.add('show');
}
function closeOverlay() {
document.getElementById('overlay').classList.remove('show');
scanning = true;
lastScanned = '';
}
async function onScan(text) {
if (!scanning || text === lastScanned) return;
scanning = false;
lastScanned = text;
const guest = parseQR(text);
if (!guest.email) {
cntErr++;
document.getElementById('cnt-err').textContent = cntErr;
showResult('error', { name: 'Неизвестный QR', org: '', email: text.substring(0, 40) });
return;
}
if (!webhookUrl) {
cntOk++;
document.getElementById('cnt-ok').textContent = cntOk;
showResult('success', guest);
return;
}
try {
const resp = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: guest.email, name: guest.name, org: guest.org, city: guest.city, action: 'checkin' })
});
const result = await resp.json();
if (result.status === 'already') {
cntAlready++;
document.getElementById('cnt-already').textContent = cntAlready;
showResult('already', guest);
} else if (result.status === 'not_found') {
cntErr++;
document.getElementById('cnt-err').textContent = cntErr;
showResult('error', guest);
} else {
cntOk++;
document.getElementById('cnt-ok').textContent = cntOk;
showResult('success', guest);
}
} catch(e) {
cntOk++;
document.getElementById('cnt-ok').textContent = cntOk;
showResult('success', guest);
}
}
const scanner = new Html5Qrcode("reader");
scanner.start(
{ facingMode: "environment" },
{ fps: 10, qrbox: { width: 280, height: 280 } },
onScan,
() => {}
).catch(() => {
document.getElementById('reader').innerHTML = '<p style="color:#f88;padding:20px;text-align:center">Нет доступа к камере.<br>Разрешите доступ в настройках браузера.</p>';
});
</script>
</body>
</html>