Files
cyberusgate/app/index.html
T
Guillaume-Sanchez 47dd7f4c4d Refonte total
2026-06-29 23:12:26 +02:00

845 lines
49 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#1565C0">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="SafeAccess">
<title>SafeAccess</title>
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<style>
:root {
--blue-dark: #0D2B5E;
--blue-main: #1565C0;
--blue-light: #1E88E5;
--blue-pale: #E3F2FD;
--green: #2E7D32;
--green-light: #E8F5E9;
--red: #C62828;
--red-light: #FFEBEE;
--orange: #E65100;
--gray-1: #F5F6FA;
--gray-2: #E8ECF1;
--gray-3: #B0BEC5;
--gray-4: #607D8B;
--gray-5: #37474F;
--white: #FFFFFF;
--text-main: #1A2332;
--text-sub: #546E7A;
--radius: 14px;
--radius-sm: 8px;
--shadow: 0 2px 16px rgba(0,0,0,0.10);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.16);
}
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--gray-1); color: var(--text-main); min-height: 100vh; overflow-x: hidden; }
/* SCREENS */
.screen { display: none; min-height: 100vh; flex-direction: column; }
.screen.active { display: flex; }
/* ======= SPLASH / LOGIN ======= */
#screen-login { background: linear-gradient(145deg, #0D2B5E 0%, #1565C0 60%, #1E88E5 100%); align-items: center; justify-content: center; padding: 40px 24px; }
.login-logo { width: 72px; height: 72px; background: rgba(255,255,255,0.15); border-radius: 20px; display: flex; align-items: center; justify-content: center; margin-bottom: 16px; border: 2px solid rgba(255,255,255,0.3); }
.login-logo svg { width: 40px; height: 40px; fill: white; }
.login-title { font-size: 28px; font-weight: 700; color: white; letter-spacing: -0.5px; margin-bottom: 4px; }
.login-sub { font-size: 14px; color: rgba(255,255,255,0.7); margin-bottom: 40px; }
.login-card { background: white; border-radius: 20px; padding: 28px 24px; width: 100%; max-width: 380px; box-shadow: var(--shadow-lg); }
.login-card h2 { font-size: 18px; font-weight: 600; color: var(--text-main); margin-bottom: 4px; }
.login-card p { font-size: 13px; color: var(--text-sub); margin-bottom: 24px; }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-size: 12px; font-weight: 600; color: var(--gray-4); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
.form-group input { width: 100%; padding: 12px 14px; border: 1.5px solid var(--gray-2); border-radius: var(--radius-sm); font-size: 15px; color: var(--text-main); background: var(--gray-1); outline: none; transition: border-color 0.2s; }
.form-group input:focus { border-color: var(--blue-main); background: white; }
.btn-primary { width: 100%; padding: 14px; background: var(--blue-main); color: white; border: none; border-radius: var(--radius-sm); font-size: 16px; font-weight: 600; cursor: pointer; transition: background 0.2s, transform 0.1s; }
.btn-primary:active { background: var(--blue-dark); transform: scale(0.98); }
.btn-outline { width: 100%; padding: 12px; background: transparent; color: var(--blue-main); border: 1.5px solid var(--blue-main); border-radius: var(--radius-sm); font-size: 15px; font-weight: 600; cursor: pointer; margin-top: 10px; transition: all 0.2s; }
.btn-outline:active { background: var(--blue-pale); }
/* ======= 2FA ======= */
#screen-2fa { background: var(--gray-1); align-items: center; justify-content: center; padding: 40px 24px; }
.mfa-card { background: white; border-radius: 20px; padding: 32px 24px; width: 100%; max-width: 380px; box-shadow: var(--shadow-lg); text-align: center; }
.mfa-icon { width: 64px; height: 64px; border-radius: 50%; background: var(--blue-pale); display: flex; align-items: center; justify-content: center; margin: 0 auto 16px; }
.mfa-icon svg { width: 32px; height: 32px; }
.mfa-card h2 { font-size: 20px; font-weight: 700; margin-bottom: 6px; }
.mfa-card p { font-size: 14px; color: var(--text-sub); margin-bottom: 8px; }
.mfa-code-display { font-size: 36px; font-weight: 800; letter-spacing: 8px; color: var(--blue-main); margin: 20px 0; font-variant-numeric: tabular-nums; }
.mfa-progress { height: 4px; background: var(--gray-2); border-radius: 2px; overflow: hidden; margin-bottom: 20px; }
.mfa-progress-bar { height: 100%; background: var(--blue-main); border-radius: 2px; transition: width 1s linear; }
.mfa-input-row { display: flex; gap: 8px; justify-content: center; margin-bottom: 20px; }
.mfa-digit { width: 44px; height: 52px; border: 2px solid var(--gray-2); border-radius: 10px; font-size: 22px; font-weight: 700; text-align: center; color: var(--text-main); outline: none; transition: border-color 0.2s; }
.mfa-digit:focus { border-color: var(--blue-main); }
.mfa-digit.filled { border-color: var(--blue-light); background: var(--blue-pale); }
.mfa-method-row { display: flex; gap: 8px; margin-top: 12px; }
.mfa-method-btn { flex: 1; padding: 10px 8px; border: 1.5px solid var(--gray-2); border-radius: var(--radius-sm); font-size: 12px; font-weight: 600; color: var(--text-sub); background: white; cursor: pointer; transition: all 0.2s; }
.mfa-method-btn.active { border-color: var(--blue-main); color: var(--blue-main); background: var(--blue-pale); }
/* ======= MAIN APP ======= */
#screen-app { background: var(--gray-1); }
.app-header { background: var(--blue-dark); padding: 48px 20px 20px; display: flex; align-items: center; justify-content: space-between; }
.app-header-left { display: flex; align-items: center; gap: 12px; }
.app-header-avatar { width: 40px; height: 40px; border-radius: 50%; border: 2px solid rgba(255,255,255,0.3); overflow: hidden; background: var(--blue-light); display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: 700; color: white; }
.app-header-info h1 { font-size: 16px; font-weight: 700; color: white; }
.app-header-info p { font-size: 12px; color: rgba(255,255,255,0.65); }
.app-header-right { display: flex; gap: 10px; }
.icon-btn { width: 38px; height: 38px; border-radius: 50%; background: rgba(255,255,255,0.12); border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.icon-btn svg { width: 20px; height: 20px; stroke: white; fill: none; stroke-width: 2; }
/* NAV TABS */
.nav-tabs { display: flex; background: white; border-bottom: 1px solid var(--gray-2); position: sticky; top: 0; z-index: 100; }
.nav-tab { flex: 1; padding: 12px 4px; display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 10px; font-weight: 600; color: var(--gray-3); text-transform: uppercase; letter-spacing: 0.3px; border: none; background: transparent; cursor: pointer; position: relative; transition: color 0.2s; }
.nav-tab.active { color: var(--blue-main); }
.nav-tab.active::after { content: ''; position: absolute; bottom: 0; left: 20%; right: 20%; height: 2px; background: var(--blue-main); border-radius: 2px 2px 0 0; }
.nav-tab svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 2; }
/* TAB CONTENT */
.tab-pane { display: none; padding: 16px; flex-direction: column; gap: 14px; }
.tab-pane.active { display: flex; }
/* BADGE CARD */
.badge-card { background: white; border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.badge-card-header { background: linear-gradient(135deg, var(--blue-dark), var(--blue-main)); padding: 20px; display: flex; align-items: center; gap: 14px; }
.badge-avatar { width: 56px; height: 56px; border-radius: 50%; background: rgba(255,255,255,0.2); border: 2px solid rgba(255,255,255,0.4); display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 700; color: white; flex-shrink: 0; }
.badge-info h2 { font-size: 18px; font-weight: 700; color: white; }
.badge-info p { font-size: 13px; color: rgba(255,255,255,0.7); }
.badge-id { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 2px; }
.badge-status { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border-radius: 20px; font-size: 12px; font-weight: 700; margin-top: 14px; }
.badge-status.ready { background: rgba(46,125,50,0.2); color: #A5D6A7; border: 1px solid rgba(165,214,167,0.3); }
.badge-status.offline { background: rgba(198,40,40,0.2); color: #EF9A9A; border: 1px solid rgba(239,154,154,0.3); }
.badge-status-dot { width: 8px; height: 8px; border-radius: 50%; background: #69F0AE; animation: pulse 2s infinite; }
@keyframes pulse { 0%,100%{opacity:1;} 50%{opacity:0.4;} }
/* QR ZONE */
.qr-zone { padding: 20px; display: flex; flex-direction: column; align-items: center; gap: 12px; }
.qr-box { width: 170px; height: 170px; border: 3px solid var(--blue-main); border-radius: 12px; padding: 8px; position: relative; }
.qr-corner { position: absolute; width: 18px; height: 18px; border-color: var(--blue-dark); border-style: solid; }
.qr-corner.tl { top: -2px; left: -2px; border-width: 3px 0 0 3px; border-radius: 4px 0 0 0; }
.qr-corner.tr { top: -2px; right: -2px; border-width: 3px 3px 0 0; border-radius: 0 4px 0 0; }
.qr-corner.bl { bottom: -2px; left: -2px; border-width: 0 0 3px 3px; border-radius: 0 0 0 4px; }
.qr-corner.br { bottom: -2px; right: -2px; border-width: 0 3px 3px 0; border-radius: 0 0 4px 0; }
.qr-canvas { width: 100%; height: 100%; }
.badge-system-tag { font-size: 11px; color: var(--gray-4); font-weight: 500; }
.badge-actions { padding: 0 16px 16px; display: flex; flex-direction: column; gap: 10px; }
.btn-action { padding: 13px; border-radius: var(--radius-sm); font-size: 15px; font-weight: 600; cursor: pointer; border: none; transition: all 0.15s; text-align: center; }
.btn-blue { background: var(--blue-main); color: white; }
.btn-blue:active { background: var(--blue-dark); transform: scale(0.97); }
.btn-ghost { background: transparent; color: var(--blue-main); border: 1.5px solid var(--blue-main); }
.btn-ghost:active { background: var(--blue-pale); }
/* NFC ZONE */
.nfc-area { padding: 20px; display: flex; flex-direction: column; align-items: center; }
.nfc-ring { width: 120px; height: 120px; border-radius: 50%; border: 3px solid var(--blue-light); display: flex; align-items: center; justify-content: center; position: relative; margin-bottom: 14px; animation: nfc-pulse 2.5s ease-in-out infinite; }
@keyframes nfc-pulse { 0%,100%{box-shadow:0 0 0 0 rgba(21,101,192,0.3);} 50%{box-shadow:0 0 0 18px rgba(21,101,192,0);} }
.nfc-ring svg { width: 52px; height: 52px; stroke: var(--blue-main); fill: none; stroke-width: 1.5; }
.nfc-status { font-size: 14px; font-weight: 600; color: var(--blue-main); }
.nfc-sub { font-size: 12px; color: var(--text-sub); text-align: center; margin-top: 4px; }
/* BADGES LIST */
.section-title { font-size: 13px; font-weight: 700; color: var(--gray-4); text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 2px; }
.badge-list-item { display: flex; align-items: center; gap: 12px; padding: 14px; background: white; border-radius: var(--radius); box-shadow: var(--shadow); cursor: pointer; transition: transform 0.15s; }
.badge-list-item:active { transform: scale(0.98); }
.badge-icon { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.badge-icon svg { width: 24px; height: 24px; }
.badge-icon.nfc { background: #E3F2FD; }
.badge-icon.nfc svg { stroke: var(--blue-main); fill: none; stroke-width: 2; }
.badge-icon.rfid { background: #F3E5F5; }
.badge-icon.rfid svg { stroke: #7B1FA2; fill: none; stroke-width: 2; }
.badge-icon.qr { background: #E8F5E9; }
.badge-icon.qr svg { stroke: var(--green); fill: none; stroke-width: 2; }
.badge-icon.bt { background: #FFF3E0; }
.badge-icon.bt svg { stroke: #E65100; fill: none; stroke-width: 2; }
.badge-list-info { flex: 1; min-width: 0; }
.badge-list-name { font-size: 15px; font-weight: 600; color: var(--text-main); }
.badge-list-sub { font-size: 12px; color: var(--text-sub); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.badge-list-right { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.tech-chip { font-size: 10px; font-weight: 700; padding: 3px 8px; border-radius: 10px; text-transform: uppercase; }
.tech-chip.nfc { background: var(--blue-pale); color: var(--blue-main); }
.tech-chip.rfid { background: #F3E5F5; color: #7B1FA2; }
.tech-chip.qr { background: var(--green-light); color: var(--green); }
.tech-chip.bt { background: #FFF3E0; color: var(--orange); }
.active-dot { width: 8px; height: 8px; border-radius: 50%; }
.active-dot.on { background: #4CAF50; }
.active-dot.off { background: var(--gray-3); }
/* HISTORY */
.history-search { display: flex; align-items: center; gap: 10px; background: white; border-radius: var(--radius-sm); padding: 10px 14px; box-shadow: var(--shadow); }
.history-search svg { width: 18px; height: 18px; stroke: var(--gray-3); fill: none; stroke-width: 2; flex-shrink: 0; }
.history-search input { flex: 1; border: none; outline: none; font-size: 14px; color: var(--text-main); background: transparent; }
.history-group-label { font-size: 12px; font-weight: 700; color: var(--gray-4); text-transform: uppercase; letter-spacing: 0.5px; padding: 4px 0; }
.history-item { display: flex; align-items: center; gap: 12px; padding: 12px 14px; background: white; border-radius: var(--radius-sm); box-shadow: 0 1px 6px rgba(0,0,0,0.06); }
.history-icon { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.history-icon.in { background: var(--green-light); }
.history-icon.in svg { stroke: var(--green); }
.history-icon.out { background: #E3F2FD; }
.history-icon.out svg { stroke: var(--blue-main); }
.history-icon.refused { background: var(--red-light); }
.history-icon.refused svg { stroke: var(--red); }
.history-icon svg { width: 18px; height: 18px; fill: none; stroke-width: 2.5; }
.history-info { flex: 1; }
.history-info .loc { font-size: 14px; font-weight: 600; color: var(--text-main); }
.history-info .type { font-size: 12px; color: var(--text-sub); }
.history-right { text-align: right; }
.history-right .status { font-size: 11px; font-weight: 700; padding: 3px 8px; border-radius: 10px; }
.status.ok { background: var(--green-light); color: var(--green); }
.status.refused { background: var(--red-light); color: var(--red); }
.history-right .time { font-size: 11px; color: var(--gray-4); margin-top: 3px; }
/* DASHBOARD */
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.stat-card { background: white; border-radius: var(--radius); padding: 16px; box-shadow: var(--shadow); }
.stat-card-label { font-size: 11px; font-weight: 600; color: var(--text-sub); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }
.stat-card-value { font-size: 28px; font-weight: 800; color: var(--text-main); line-height: 1; }
.stat-card-sub { font-size: 12px; color: var(--text-sub); margin-top: 4px; }
.stat-card.blue .stat-card-value { color: var(--blue-main); }
.stat-card.green .stat-card-value { color: var(--green); }
.stat-card.red .stat-card-value { color: var(--red); }
.stat-card.orange .stat-card-value { color: var(--orange); }
.zone-list-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px; background: white; border-radius: var(--radius-sm); box-shadow: 0 1px 6px rgba(0,0,0,0.06); }
.zone-info .zone-name { font-size: 14px; font-weight: 600; }
.zone-info .zone-level { font-size: 12px; color: var(--text-sub); }
.zone-badge { font-size: 11px; font-weight: 700; padding: 4px 10px; border-radius: 10px; }
.zone-badge.high { background: var(--red-light); color: var(--red); }
.zone-badge.medium { background: #FFF3E0; color: var(--orange); }
.zone-badge.low { background: var(--green-light); color: var(--green); }
/* SETTINGS */
.settings-section { background: white; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
.settings-item { display: flex; align-items: center; gap: 14px; padding: 14px 16px; border-bottom: 1px solid var(--gray-2); cursor: pointer; transition: background 0.15s; }
.settings-item:last-child { border-bottom: none; }
.settings-item:active { background: var(--gray-1); }
.settings-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.settings-icon svg { width: 20px; height: 20px; }
.settings-icon.blue { background: var(--blue-pale); }
.settings-icon.blue svg { stroke: var(--blue-main); fill: none; stroke-width: 2; }
.settings-icon.green { background: var(--green-light); }
.settings-icon.green svg { stroke: var(--green); fill: none; stroke-width: 2; }
.settings-icon.red { background: var(--red-light); }
.settings-icon.red svg { stroke: var(--red); fill: none; stroke-width: 2; }
.settings-icon.orange { background: #FFF3E0; }
.settings-icon.orange svg { stroke: var(--orange); fill: none; stroke-width: 2; }
.settings-text { flex: 1; }
.settings-text .s-title { font-size: 15px; font-weight: 600; }
.settings-text .s-sub { font-size: 12px; color: var(--text-sub); }
.settings-arrow { color: var(--gray-3); }
.toggle { width: 44px; height: 24px; background: var(--gray-3); border-radius: 12px; position: relative; cursor: pointer; transition: background 0.2s; }
.toggle.on { background: var(--blue-main); }
.toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: white; transition: left 0.2s; box-shadow: 0 1px 4px rgba(0,0,0,0.2); }
.toggle.on::after { left: 22px; }
/* NOTIFICATIONS */
.notif-banner { background: var(--blue-pale); border-left: 3px solid var(--blue-main); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; padding: 12px 14px; display: flex; gap: 10px; align-items: flex-start; }
.notif-banner.warning { background: #FFF8E1; border-color: #FBC02D; }
.notif-banner.success { background: var(--green-light); border-color: var(--green); }
.notif-icon { width: 20px; height: 20px; flex-shrink: 0; margin-top: 1px; }
.notif-icon svg { width: 20px; height: 20px; fill: none; stroke-width: 2; }
.notif-banner .n-title { font-size: 13px; font-weight: 700; color: var(--text-main); }
.notif-banner .n-sub { font-size: 12px; color: var(--text-sub); }
/* MODAL */
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000; align-items: flex-end; }
.modal-overlay.open { display: flex; }
.modal-sheet { background: white; border-radius: 20px 20px 0 0; padding: 8px 20px 32px; width: 100%; max-height: 80vh; overflow-y: auto; }
.modal-handle { width: 36px; height: 4px; background: var(--gray-2); border-radius: 2px; margin: 10px auto 20px; }
.modal-title { font-size: 18px; font-weight: 700; margin-bottom: 16px; }
.add-badge-option { display: flex; align-items: center; gap: 14px; padding: 14px; border-radius: var(--radius-sm); border: 1.5px solid var(--gray-2); margin-bottom: 10px; cursor: pointer; transition: all 0.15s; }
.add-badge-option:active { background: var(--gray-1); border-color: var(--blue-main); }
.add-badge-opt-icon { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.add-badge-opt-icon svg { width: 24px; height: 24px; fill: none; stroke-width: 2; }
.add-badge-opt-text .t { font-size: 15px; font-weight: 600; }
.add-badge-opt-text .s { font-size: 12px; color: var(--text-sub); }
/* TOAST */
.toast { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%) translateY(20px); background: #263238; color: white; padding: 10px 20px; border-radius: 24px; font-size: 14px; font-weight: 500; opacity: 0; transition: all 0.3s; z-index: 9999; white-space: nowrap; pointer-events: none; }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* FAB */
.fab { position: fixed; bottom: 80px; right: 20px; width: 52px; height: 52px; border-radius: 50%; background: var(--blue-main); border: none; box-shadow: 0 4px 16px rgba(21,101,192,0.4); cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 200; transition: transform 0.15s; }
.fab:active { transform: scale(0.9); }
.fab svg { width: 26px; height: 26px; stroke: white; fill: none; stroke-width: 2; }
/* CHIP ROW */
.chip-row { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: none; }
.chip-row::-webkit-scrollbar { display: none; }
.chip { padding: 6px 14px; border-radius: 20px; font-size: 13px; font-weight: 600; cursor: pointer; border: 1.5px solid var(--gray-2); color: var(--text-sub); background: white; white-space: nowrap; transition: all 0.15s; }
.chip.active { background: var(--blue-main); color: white; border-color: var(--blue-main); }
</style>
</head>
<body>
<!-- ===== LOGIN ===== -->
<div id="screen-login" class="screen active">
<div style="text-align:center;">
<div class="login-logo" style="margin:0 auto 16px;">
<svg viewBox="0 0 40 40"><path d="M20 4L6 12v16l14 8 14-8V12L20 4z" fill="white" opacity="0.9"/><circle cx="20" cy="20" r="6" fill="#1E88E5"/></svg>
</div>
<div class="login-title">SafeAccess</div>
<div class="login-sub">Gestion de badges d'accès sécurisé</div>
</div>
<div class="login-card">
<h2>Connexion</h2>
<p>Entrez vos identifiants pour continuer</p>
<div class="form-group">
<label>Identifiant</label>
<input type="email" id="login-email" placeholder="alice.dupont@société.fr" value="alice.dupont@societe.fr">
</div>
<div class="form-group">
<label>Mot de passe</label>
<input type="password" id="login-pass" placeholder="••••••••" value="password123">
</div>
<button class="btn-primary" onclick="goTo2FA()">Se connecter →</button>
<button class="btn-outline" onclick="showToast('Contactez votre administrateur')">Mot de passe oublié</button>
</div>
</div>
<!-- ===== 2FA ===== -->
<div id="screen-2fa" class="screen">
<div class="mfa-card">
<div class="mfa-icon">
<svg viewBox="0 0 32 32" fill="none" stroke="#1565C0" stroke-width="2"><rect x="8" y="14" width="16" height="12" rx="3"/><path d="M11 14v-4a5 5 0 0110 0v4"/><circle cx="16" cy="21" r="2" fill="#1565C0" stroke="none"/></svg>
</div>
<h2>Vérification 2FA</h2>
<p>Code TOTP (renouvelle toutes les 30s)</p>
<div class="mfa-code-display" id="totp-display">------</div>
<div class="mfa-progress"><div class="mfa-progress-bar" id="totp-bar" style="width:100%"></div></div>
<p style="font-size:13px;color:var(--text-sub);margin-bottom:12px;">Entrez le code de votre application d'authentification :</p>
<div class="mfa-input-row" id="mfa-inputs"></div>
<div class="mfa-method-row">
<button class="mfa-method-btn active" onclick="selectMFA(this,'totp')">📱 TOTP</button>
<button class="mfa-method-btn" onclick="selectMFA(this,'sms')">💬 SMS</button>
<button class="mfa-method-btn" onclick="selectMFA(this,'email')">📧 Email</button>
</div>
<button class="btn-primary" style="margin-top:20px;" onclick="verifyMFA()">Vérifier →</button>
<button class="btn-outline" style="margin-top:8px;" onclick="goTo('screen-login')">← Retour</button>
</div>
</div>
<!-- ===== MAIN APP ===== -->
<div id="screen-app" class="screen">
<div class="app-header">
<div class="app-header-left">
<div class="app-header-avatar">AD</div>
<div class="app-header-info">
<h1>Alice Dupont</h1>
<p>AC-0012 · Niveau 3</p>
</div>
</div>
<div class="app-header-right">
<button class="icon-btn" onclick="showToast('Notifications')">
<svg viewBox="0 0 24 24"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
</button>
<button class="icon-btn" onclick="showToast('Paramètres rapides')">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
</button>
</div>
</div>
<nav class="nav-tabs">
<button class="nav-tab active" onclick="switchTab('badge',this)">
<svg viewBox="0 0 24 24"><rect x="2" y="5" width="20" height="14" rx="3"/><path d="M8 12h.01M12 12h.01M16 12h.01"/></svg>
Badge
</button>
<button class="nav-tab" onclick="switchTab('mes-badges',this)">
<svg viewBox="0 0 24 24"><path d="M20 7H4a2 2 0 00-2 2v10a2 2 0 002 2h16a2 2 0 002-2V9a2 2 0 00-2-2z"/><path d="M16 21V5a2 2 0 00-2-2h-4a2 2 0 00-2 2v16"/></svg>
Mes badges
</button>
<button class="nav-tab" onclick="switchTab('historique',this)">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
Historique
</button>
<button class="nav-tab" onclick="switchTab('dashboard',this)">
<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
Tableau
</button>
<button class="nav-tab" onclick="switchTab('settings',this)">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
Réglages
</button>
</nav>
<!-- TAB: MON E-BADGE -->
<div id="tab-badge" class="tab-pane active">
<div class="notif-banner success">
<div class="notif-icon"><svg viewBox="0 0 20 20" fill="none" stroke="#2E7D32" stroke-width="2"><path d="M9 12l2 2 4-4"/><path d="M4 6a8 8 0 1016 0A8 8 0 004 6"/></svg></div>
<div><div class="n-title">NFC disponible</div><div class="n-sub">Votre téléphone est détecté compatible NFC</div></div>
</div>
<div class="badge-card">
<div class="badge-card-header">
<div class="badge-avatar">AD</div>
<div class="badge-info">
<h2>Alice Dupont</h2>
<p>Technicien Réseau Senior</p>
<div class="badge-id">ID: AC-0012 · Émis le 01/01/2025</div>
<div class="badge-status ready">
<div class="badge-status-dot"></div>
PRÊT À SCANNER
</div>
</div>
</div>
<div class="qr-zone">
<div class="qr-box">
<canvas id="qr-canvas" class="qr-canvas" width="154" height="154"></canvas>
<div class="qr-corner tl"></div><div class="qr-corner tr"></div>
<div class="qr-corner bl"></div><div class="qr-corner br"></div>
</div>
<div class="badge-system-tag">SafeAccess system · NFC/QR</div>
</div>
<div class="nfc-area">
<div class="nfc-ring" id="nfc-ring">
<svg viewBox="0 0 52 52"><path d="M26 8C16 8 8 16 8 26s8 18 18 18 18-8 18-18"/><path d="M26 14c-7 0-12 5-12 12s5 12 12 12 12-5 12-12"/><path d="M26 20c-3 0-6 3-6 6s3 6 6 6 6-3 6-6"/><circle cx="26" cy="26" r="3" fill="#1565C0"/></svg>
</div>
<div class="nfc-status" id="nfc-status-text">En attente d'un lecteur NFC</div>
<div class="nfc-sub">Approchez votre téléphone du lecteur</div>
</div>
<div class="badge-actions">
<button class="btn-action btn-blue" onclick="simulateNFC()">📡 SIMULER LECTURE NFC</button>
<button class="btn-action btn-ghost" onclick="switchTab('historique', document.querySelectorAll('.nav-tab')[2])">HISTORIQUE D'ACCÈS</button>
</div>
</div>
</div>
<!-- TAB: MES BADGES -->
<div id="tab-mes-badges" class="tab-pane">
<div class="chip-row">
<div class="chip active">Tous</div>
<div class="chip">NFC</div>
<div class="chip">RFID</div>
<div class="chip">QR Code</div>
<div class="chip">Bluetooth</div>
</div>
<div class="section-title">Mes badges (4)</div>
<div id="badge-list"></div>
</div>
<!-- TAB: HISTORIQUE -->
<div id="tab-historique" class="tab-pane">
<div class="history-search">
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
<input type="text" placeholder="Rechercher un accès..." id="history-search">
</div>
<div id="history-list"></div>
</div>
<!-- TAB: DASHBOARD -->
<div id="tab-dashboard" class="tab-pane">
<div class="section-title">Tableau de bord</div>
<div class="stat-grid">
<div class="stat-card blue"><div class="stat-card-label">Utilisateurs</div><div class="stat-card-value">17</div><div class="stat-card-sub">actifs ce mois</div></div>
<div class="stat-card"><div class="stat-card-label">Zones</div><div class="stat-card-value">16</div><div class="stat-card-sub">zones actives</div></div>
<div class="stat-card green"><div class="stat-card-label">Accès aujourd'hui</div><div class="stat-card-value">143</div><div class="stat-card-sub">autorisés</div></div>
<div class="stat-card red"><div class="stat-card-label">Alertes</div><div class="stat-card-value">0</div><div class="stat-card-sub">incidents actifs</div></div>
</div>
<div class="section-title" style="margin-top:4px;">Gestion des zones</div>
<div id="zone-list"></div>
<div class="section-title" style="margin-top:4px;">Logs récents</div>
<div id="dash-logs"></div>
</div>
<!-- TAB: RÉGLAGES -->
<div id="tab-settings" class="tab-pane">
<div class="section-title">Sécurité</div>
<div class="settings-section">
<div class="settings-item">
<div class="settings-icon blue"><svg viewBox="0 0 24 24"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 018 0v4"/><circle cx="12" cy="16" r="1" fill="#1565C0"/></svg></div>
<div class="settings-text"><div class="s-title">Double authentification (2FA)</div><div class="s-sub">TOTP activé · App Authenticator</div></div>
<div class="toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="settings-item">
<div class="settings-icon blue"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></div>
<div class="settings-text"><div class="s-title">Biométrie</div><div class="s-sub">Empreinte / Face ID</div></div>
<div class="toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="settings-item" onclick="showToast('PIN modifié')">
<div class="settings-icon orange"><svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg></div>
<div class="settings-text"><div class="s-title">Code PIN</div><div class="s-sub">Modifier le code PIN de secours</div></div>
<div class="settings-arrow"></div>
</div>
</div>
<div class="section-title" style="margin-top:4px;">NFC & Technologies</div>
<div class="settings-section">
<div class="settings-item">
<div class="settings-icon blue"><svg viewBox="0 0 24 24"><path d="M12 4C8 4 4 8 4 12s4 8 8 8 8-4 8-8"/><path d="M12 7c-2.5 0-5 2.5-5 5s2.5 5 5 5 5-2.5 5-5"/><circle cx="12" cy="12" r="2" fill="#1565C0"/></svg></div>
<div class="settings-text"><div class="s-title">NFC actif</div><div class="s-sub">Lecture/écriture NFC activée</div></div>
<div class="toggle on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="settings-item">
<div class="settings-icon orange"><svg viewBox="0 0 24 24"><polyline points="6.5 6.5 17.5 12 6.5 17.5 6.5 6.5"/></svg></div>
<div class="settings-text"><div class="s-title">Bluetooth BLE</div><div class="s-sub">Badges longue portée</div></div>
<div class="toggle" onclick="this.classList.toggle('on')"></div>
</div>
<div class="settings-item">
<div class="settings-icon green"><svg viewBox="0 0 24 24"><path d="M3 3h18v18H3z"/><path d="M9 9h2v2H9zM13 9h2v2h-2zM9 13h2v2H9z"/></svg></div>
<div class="settings-text"><div class="s-title">QR Code dynamique</div><div class="s-sub">Régénération toutes les 60s</div></div>
<div class="toggle on" onclick="this.classList.toggle('on')"></div>
</div>
</div>
<div class="section-title" style="margin-top:4px;">Compte</div>
<div class="settings-section">
<div class="settings-item" onclick="showToast('Profil ouvert')">
<div class="settings-icon blue"><svg viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div>
<div class="settings-text"><div class="s-title">Mon profil</div><div class="s-sub">Alice Dupont · AC-0012</div></div>
<div class="settings-arrow"></div>
</div>
<div class="settings-item" onclick="goTo('screen-login')">
<div class="settings-icon red"><svg viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg></div>
<div class="settings-text"><div class="s-title">Déconnexion</div><div class="s-sub">alice.dupont@societe.fr</div></div>
<div class="settings-arrow"></div>
</div>
</div>
<div style="text-align:center;padding:20px 0;font-size:11px;color:var(--gray-3);">SafeAccess v2.4.1 · Build 2025.06</div>
</div>
</div>
<!-- FAB -->
<button class="fab" id="fab-btn" style="display:none;" onclick="showAddBadgeModal()">
<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<!-- MODAL -->
<div class="modal-overlay" id="modal-add">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-title">Ajouter un badge</div>
<div class="add-badge-option" onclick="addBadge('NFC')">
<div class="add-badge-opt-icon" style="background:#E3F2FD;"><svg viewBox="0 0 24 24" fill="none" stroke="#1565C0" stroke-width="2"><path d="M12 4C8 4 4 8 4 12s4 8 8 8 8-4 8-8"/><circle cx="12" cy="12" r="2" fill="#1565C0"/></svg></div>
<div class="add-badge-opt-text"><div class="t">Badge NFC</div><div class="s">ISO 14443 · Mifare · NFC Type A/B</div></div>
</div>
<div class="add-badge-option" onclick="addBadge('RFID')">
<div class="add-badge-opt-icon" style="background:#F3E5F5;"><svg viewBox="0 0 24 24" fill="none" stroke="#7B1FA2" stroke-width="2"><path d="M5 12.5c0-3.9 3.1-7 7-7s7 3.1 7 7"/><path d="M8 12.5c0-2.2 1.8-4 4-4s4 1.8 4 4"/><circle cx="12" cy="12.5" r="2"/></svg></div>
<div class="add-badge-opt-text"><div class="t">Badge RFID</div><div class="s">125kHz · HID · Em-Marine · Wiegand</div></div>
</div>
<div class="add-badge-option" onclick="addBadge('QR Code')">
<div class="add-badge-opt-icon" style="background:#E8F5E9;"><svg viewBox="0 0 24 24" fill="none" stroke="#2E7D32" stroke-width="2"><path d="M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM17 14h.01M14 14h.01M14 17h.01M17 17h4M17 20v1M21 14v3"/></svg></div>
<div class="add-badge-opt-text"><div class="t">QR Code dynamique</div><div class="s">Rotation toutes les 60s · HMAC signé</div></div>
</div>
<div class="add-badge-option" onclick="addBadge('Bluetooth')">
<div class="add-badge-opt-icon" style="background:#FFF3E0;"><svg viewBox="0 0 24 24" fill="none" stroke="#E65100" stroke-width="2"><polyline points="6.5 6.5 17.5 12 6.5 17.5 6.5 6.5"/><polyline points="17.5 12 6.5 17.5"/></svg></div>
<div class="add-badge-opt-text"><div class="t">Badge Bluetooth BLE</div><div class="s">Longue portée · iBeacon · Eddystone</div></div>
</div>
<button class="btn-outline" onclick="closeModal()" style="margin-top:8px;">Annuler</button>
</div>
</div>
<!-- TOAST -->
<div class="toast" id="toast"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script>
// ===== STATE =====
let currentTab = 'badge';
let mfaEntered = '';
let totpTimer = 30;
let totpInterval = null;
const correctCode = '472851';
const badges = [
{ id: 'B001', name: 'Badge Principal', tech: 'nfc', uid: 'A3:F2:1B:CC', zones: ['Bureau Principal', 'Parking', 'Cafétéria'], active: true },
{ id: 'B002', name: 'Badge Labo', tech: 'rfid', uid: '125K-88F2A1', zones: ['Labo A', 'Labo B'], active: true },
{ id: 'B003', name: 'QR Code', tech: 'qr', uid: 'QR-DYN-2025', zones: ['Bureau Principal'], active: true },
{ id: 'B004', name: 'Badge BLE', tech: 'bt', uid: 'BT:A4:CC:12:FF', zones: ['Parking'], active: false },
];
const history = [
{ date: "Aujourd'hui, 09:30", type: 'in', loc: 'Bureau Principal', sub: '· NFC · B001', status: 'ok' },
{ date: "Hier, 17:15", type: 'out', loc: 'Parking', sub: '· RFID · B002', status: 'ok' },
{ date: "Mercredi, 14:05", type: 'in', loc: 'Labo A', sub: '· RFID · B002', status: 'refused' },
{ date: "Mercredi, 08:50", type: 'in', loc: 'Bureau Principal', sub: '· NFC · B001', status: 'ok' },
{ date: "Mardi, 18:20", type: 'out', loc: 'Bureau Principal', sub: '· QR · B003', status: 'ok' },
{ date: "Mardi, 09:00", type: 'in', loc: 'Cafétéria', sub: '· NFC · B001', status: 'ok' },
];
const zones = [
{ name: 'Bureau Principal', level: 'Niveau 1', risk: 'low' },
{ name: 'Labo A', level: 'Niveau 3 — Restreint', risk: 'high' },
{ name: 'Parking', level: 'Niveau 1', risk: 'low' },
{ name: 'Salle Serveurs', level: 'Niveau 4 — Critique', risk: 'high' },
{ name: 'Cafétéria', level: 'Niveau 0 — Public', risk: 'low' },
{ name: 'Direction', level: 'Niveau 2', risk: 'medium' },
];
// ===== NAVIGATION =====
function goTo(screenId) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(screenId).classList.add('active');
document.getElementById('fab-btn').style.display = screenId === 'screen-app' ? 'flex' : 'none';
}
function switchTab(tab, btn) {
currentTab = tab;
document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.nav-tab').forEach(b => b.classList.remove('active'));
document.getElementById('tab-' + tab).classList.add('active');
if(btn) btn.classList.add('active');
if(tab === 'mes-badges') renderBadgeList();
if(tab === 'historique') renderHistory();
if(tab === 'dashboard') renderDashboard();
}
// ===== LOGIN / 2FA =====
function goTo2FA() {
const e = document.getElementById('login-email').value;
const p = document.getElementById('login-pass').value;
if(!e || !p) { showToast('Renseignez vos identifiants'); return; }
goTo('screen-2fa');
initTOTP();
buildMFAInputs();
}
function initTOTP() {
if(totpInterval) clearInterval(totpInterval);
totpTimer = 30;
updateTOTP();
totpInterval = setInterval(() => {
totpTimer--;
if(totpTimer <= 0) { totpTimer = 30; generateNewCode(); }
updateTOTP();
}, 1000);
}
let currentCode = correctCode;
function generateNewCode() {
currentCode = String(Math.floor(100000 + Math.random() * 900000));
document.getElementById('totp-display').textContent = currentCode;
}
function updateTOTP() {
document.getElementById('totp-display').textContent = currentCode;
document.getElementById('totp-bar').style.width = (totpTimer / 30 * 100) + '%';
}
function buildMFAInputs() {
const row = document.getElementById('mfa-inputs');
row.innerHTML = '';
mfaEntered = '';
for(let i = 0; i < 6; i++) {
const inp = document.createElement('input');
inp.className = 'mfa-digit';
inp.maxLength = 1;
inp.inputMode = 'numeric';
inp.pattern = '[0-9]';
inp.dataset.idx = i;
inp.addEventListener('input', e => {
if(/[0-9]/.test(e.target.value)) {
e.target.classList.add('filled');
if(i < 5) row.children[i+1].focus();
} else { e.target.value = ''; e.target.classList.remove('filled'); }
});
inp.addEventListener('keydown', e => { if(e.key === 'Backspace' && !inp.value && i > 0) row.children[i-1].focus(); });
row.appendChild(inp);
}
setTimeout(() => row.children[0].focus(), 100);
}
function selectMFA(btn, method) {
document.querySelectorAll('.mfa-method-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
if(method === 'sms') showToast('SMS envoyé au +33 6 ** ** ** 42');
if(method === 'email') showToast('Email envoyé à a.dupont@****');
}
function verifyMFA() {
const inputs = document.querySelectorAll('.mfa-digit');
let code = '';
inputs.forEach(i => code += i.value);
if(code.length < 6) { showToast('Entrez les 6 chiffres'); return; }
if(code === currentCode || code === correctCode) {
if(totpInterval) clearInterval(totpInterval);
showToast('✓ Authentification réussie !');
setTimeout(() => {
goTo('screen-app');
renderQR();
renderBadgeList();
renderHistory();
renderDashboard();
}, 800);
} else {
showToast('❌ Code incorrect, réessayez');
buildMFAInputs();
}
}
// ===== QR CODE =====
function renderQR() {
const canvas = document.getElementById('qr-canvas');
const ctx = canvas.getContext('2d');
const size = 154;
const data = 'SAFEACCESS:AC-0012:' + Date.now();
drawQR(ctx, data, size);
setInterval(() => {
const newData = 'SAFEACCESS:AC-0012:' + Date.now();
drawQR(ctx, newData, size);
}, 60000);
}
function drawQR(ctx, data, size) {
ctx.clearRect(0, 0, size, size);
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, size, size);
const cells = 25;
const cellSize = Math.floor(size / cells);
const hash = Array.from(data).reduce((a, c) => ((a << 5) - a + c.charCodeAt(0)) | 0, 0);
for(let r = 0; r < cells; r++) {
for(let c = 0; c < cells; c++) {
const seed = (r * 31 + c * 17 + hash + r*c) % 100;
const shouldFill = seed < 45 || (r < 7 && c < 7) || (r < 7 && c > cells-8) || (r > cells-8 && c < 7);
if(shouldFill) {
ctx.fillStyle = '#0D2B5E';
ctx.fillRect(c * cellSize + 1, r * cellSize + 1, cellSize - 1, cellSize - 1);
}
}
}
// Finder patterns
[[0,0],[0,cells-7],[cells-7,0]].forEach(([row,col]) => {
ctx.strokeStyle = '#0D2B5E'; ctx.lineWidth = 2;
ctx.strokeRect(col*cellSize+1, row*cellSize+1, 6*cellSize, 6*cellSize);
ctx.fillStyle = '#0D2B5E';
ctx.fillRect((col+2)*cellSize, (row+2)*cellSize, 3*cellSize, 3*cellSize);
});
}
// ===== NFC SIMULATION =====
function simulateNFC() {
const ring = document.getElementById('nfc-ring');
const status = document.getElementById('nfc-status-text');
ring.style.borderColor = '#FBC02D';
ring.style.animation = 'nfc-pulse 0.8s ease-in-out infinite';
status.textContent = 'Lecture NFC en cours...';
status.style.color = '#E65100';
setTimeout(() => {
ring.style.borderColor = '#4CAF50';
ring.style.animation = 'none';
status.textContent = '✓ Accès autorisé — Bureau Principal';
status.style.color = '#2E7D32';
showToast('✓ Accès autorisé — Bureau Principal');
setTimeout(() => {
ring.style.borderColor = 'var(--blue-light)';
ring.style.animation = 'nfc-pulse 2.5s ease-in-out infinite';
status.textContent = 'En attente d\'un lecteur NFC';
status.style.color = 'var(--blue-main)';
}, 3000);
}, 1800);
}
// ===== BADGE LIST =====
const techIcons = {
nfc: `<svg viewBox="0 0 24 24" fill="none" stroke="#1565C0" stroke-width="2"><path d="M12 4C8 4 4 8 4 12s4 8 8 8 8-4 8-8"/><circle cx="12" cy="12" r="2" fill="#1565C0"/></svg>`,
rfid: `<svg viewBox="0 0 24 24" fill="none" stroke="#7B1FA2" stroke-width="2"><path d="M5 12.5c0-3.9 3.1-7 7-7s7 3.1 7 7"/><path d="M8 12.5c0-2.2 1.8-4 4-4s4 1.8 4 4"/><circle cx="12" cy="12.5" r="2"/></svg>`,
qr: `<svg viewBox="0 0 24 24" fill="none" stroke="#2E7D32" stroke-width="2"><path d="M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3z"/></svg>`,
bt: `<svg viewBox="0 0 24 24" fill="none" stroke="#E65100" stroke-width="2"><polyline points="6.5 6.5 17.5 12 6.5 17.5 6.5 6.5"/></svg>`,
};
function renderBadgeList() {
const list = document.getElementById('badge-list');
list.innerHTML = badges.map(b => `
<div class="badge-list-item" onclick="showToast('Badge ${b.name} sélectionné')">
<div class="badge-icon ${b.tech}">${techIcons[b.tech]}</div>
<div class="badge-list-info">
<div class="badge-list-name">${b.name}</div>
<div class="badge-list-sub">${b.uid} · ${b.zones.join(', ')}</div>
</div>
<div class="badge-list-right">
<span class="tech-chip ${b.tech}">${b.tech.toUpperCase()}</span>
<div class="active-dot ${b.active ? 'on' : 'off'}"></div>
</div>
</div>
`).join('');
}
// ===== HISTORY =====
const inIcon = `<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>`;
const outIcon = `<svg viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>`;
const refIcon = `<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
function renderHistory(filter = '') {
const list = document.getElementById('history-list');
const filtered = history.filter(h => !filter || h.loc.toLowerCase().includes(filter.toLowerCase()));
let html = '';
let lastDate = '';
filtered.forEach(h => {
if(h.date !== lastDate) {
html += `<div class="history-group-label">${h.date}</div>`;
lastDate = h.date;
}
const iconType = h.status === 'refused' ? 'refused' : h.type === 'in' ? 'in' : 'out';
const icon = h.status === 'refused' ? refIcon : h.type === 'in' ? inIcon : outIcon;
const typeLabel = h.type === 'in' ? 'Entrée' : 'Sortie';
html += `
<div class="history-item">
<div class="history-icon ${iconType}">${icon}</div>
<div class="history-info">
<div class="loc">${typeLabel} · ${h.loc}</div>
<div class="type">${h.sub}</div>
</div>
<div class="history-right">
<span class="status ${h.status === 'ok' ? 'ok' : 'refused'}">${h.status === 'ok' ? 'AUTORISÉ' : 'REFUSÉ'}</span>
<div class="time">${h.date.split(',')[1] || ''}</div>
</div>
</div>`;
});
list.innerHTML = html || '<div style="text-align:center;padding:20px;color:var(--gray-4);">Aucun résultat</div>';
}
document.getElementById('history-search').addEventListener('input', e => renderHistory(e.target.value));
// ===== DASHBOARD =====
function renderDashboard() {
const zl = document.getElementById('zone-list');
zl.innerHTML = zones.map(z => `
<div class="zone-list-item">
<div class="zone-info"><div class="zone-name">${z.name}</div><div class="zone-level">${z.level}</div></div>
<span class="zone-badge ${z.risk}">${z.risk === 'high' ? 'Restreint' : z.risk === 'medium' ? 'Modéré' : 'Public'}</span>
</div>`).join('');
const dl = document.getElementById('dash-logs');
dl.innerHTML = history.slice(0,4).map(h => `
<div class="history-item" style="margin-bottom:8px;">
<div class="history-icon ${h.status === 'refused' ? 'refused' : h.type === 'in' ? 'in' : 'out'}">${h.status === 'refused' ? refIcon : h.type === 'in' ? inIcon : outIcon}</div>
<div class="history-info"><div class="loc">${h.loc}</div><div class="type">${h.sub}</div></div>
<div class="history-right"><span class="status ${h.status === 'ok' ? 'ok' : 'refused'}">${h.status === 'ok' ? 'OK' : 'REFUSÉ'}</span></div>
</div>`).join('');
}
// ===== ADD BADGE MODAL =====
function showAddBadgeModal() {
document.getElementById('modal-add').classList.add('open');
}
function closeModal() {
document.getElementById('modal-add').classList.remove('open');
}
function addBadge(tech) {
closeModal();
const newBadge = {
id: 'B00' + (badges.length + 1),
name: 'Nouveau badge ' + tech,
tech: tech.toLowerCase() === 'nfc' ? 'nfc' : tech.toLowerCase() === 'rfid' ? 'rfid' : tech.toLowerCase() === 'qr code' ? 'qr' : 'bt',
uid: 'NEW-' + Math.random().toString(36).substr(2,8).toUpperCase(),
zones: ['Bureau Principal'],
active: true,
};
badges.push(newBadge);
if(currentTab === 'mes-badges') renderBadgeList();
showToast('✓ Badge ' + tech + ' ajouté');
}
// ===== TOAST =====
function showToast(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2500);
}
// ===== INIT =====
document.getElementById('fab-btn').style.display = 'none';
</script>
</body>
</html>