383 lines
12 KiB
PHP
383 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* openning.php
|
|
* Time-limited door access page (30 seconds)
|
|
* Standalone design (no header/footer)
|
|
* Color scheme matches edit_data.php
|
|
*/
|
|
|
|
$token = isset($_GET['token']) ? trim($_GET['token']) : '';
|
|
$valid = false;
|
|
$errorType = 'invalid'; // 'invalid' or 'expired'
|
|
|
|
if (!empty($token)) {
|
|
$ENCRYPTION_KEY = 'FiberID_DoorAccess_SecretKey_2026!';
|
|
$MANIFEST_PATH = __DIR__ . '/custom_code_tokens.json';
|
|
|
|
if (file_exists($MANIFEST_PATH)) {
|
|
$tokens = json_decode(file_get_contents($MANIFEST_PATH), true) ?: [];
|
|
|
|
if (isset($tokens[$token])) {
|
|
$tokenData = $tokens[$token];
|
|
$now = time();
|
|
|
|
if ($now <= $tokenData['expires_at']) {
|
|
$valid = true;
|
|
$remainingSeconds = $tokenData['expires_at'] - $now;
|
|
|
|
// Mark as used (one-time use)
|
|
unset($tokens[$token]);
|
|
file_put_contents($MANIFEST_PATH, json_encode($tokens, JSON_PRETTY_PRINT));
|
|
} else {
|
|
$errorType = 'expired';
|
|
// Clean up expired token
|
|
unset($tokens[$token]);
|
|
file_put_contents($MANIFEST_PATH, json_encode($tokens, JSON_PRETTY_PRINT));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1, user-scalable=0">
|
|
<title>Door Access - Identia</title>
|
|
<link rel="stylesheet" href="vendors/mdi/css/materialdesignicons.min.css">
|
|
<link rel="shortcut icon" href="images/myn.png" />
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap');
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
background: #f8f9fa;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
/* ─── MAIN CARD ─── */
|
|
.access-card {
|
|
background: #fff;
|
|
border-radius: 24px;
|
|
box-shadow: 0 4px 24px rgba(0,0,0,0.06), 0 1px 4px rgba(0,0,0,0.04);
|
|
padding: 40px 30px;
|
|
max-width: 380px;
|
|
width: 100%;
|
|
text-align: center;
|
|
position: relative;
|
|
overflow: hidden;
|
|
animation: cardSlideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
@keyframes cardSlideUp {
|
|
from { opacity: 0; transform: translateY(30px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
/* Decorative top accent bar */
|
|
.access-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0; left: 0; right: 0;
|
|
height: 4px;
|
|
background: linear-gradient(90deg, #4b49ac, #7c3aed, #4b49ac);
|
|
}
|
|
|
|
/* ─── ICON CIRCLE ─── */
|
|
.icon-circle {
|
|
width: 90px; height: 90px;
|
|
border-radius: 50%;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: 24px;
|
|
position: relative;
|
|
}
|
|
.icon-circle.active {
|
|
background: linear-gradient(135deg, #ede9fe, #e0e7ff);
|
|
border: 2px solid #c4b5fd;
|
|
}
|
|
.icon-circle.active i {
|
|
font-size: 42px;
|
|
color: #4b49ac;
|
|
}
|
|
.icon-circle.active::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -4px;
|
|
border-radius: 50%;
|
|
border: 2px solid rgba(75, 73, 172, 0.15);
|
|
animation: ringPulse 2s ease-in-out infinite;
|
|
}
|
|
@keyframes ringPulse {
|
|
0%, 100% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.08); opacity: 0.5; }
|
|
}
|
|
|
|
.icon-circle.error {
|
|
background: #fef2f2;
|
|
border: 2px solid #fecaca;
|
|
}
|
|
.icon-circle.error i { font-size: 42px; color: #ef4444; }
|
|
.icon-circle.error::after { display: none; }
|
|
|
|
.icon-circle.expired {
|
|
background: #fffbeb;
|
|
border: 2px solid #fde68a;
|
|
}
|
|
.icon-circle.expired i { font-size: 42px; color: #f59e0b; }
|
|
.icon-circle.expired::after { display: none; }
|
|
|
|
/* ─── TEXT ─── */
|
|
.access-title {
|
|
font-size: 22px;
|
|
font-weight: 800;
|
|
color: #1e293b;
|
|
margin-bottom: 6px;
|
|
}
|
|
.access-subtitle {
|
|
font-size: 13px;
|
|
color: #94a3b8;
|
|
margin-bottom: 30px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* ─── TIMER ─── */
|
|
.timer-ring {
|
|
width: 100px; height: 100px;
|
|
position: relative;
|
|
margin: 0 auto 24px;
|
|
}
|
|
.timer-ring svg {
|
|
width: 100%; height: 100%;
|
|
transform: rotate(-90deg);
|
|
}
|
|
.timer-ring .bg-ring {
|
|
fill: none;
|
|
stroke: #e2e8f0;
|
|
stroke-width: 6;
|
|
}
|
|
.timer-ring .progress-ring {
|
|
fill: none;
|
|
stroke: #4b49ac;
|
|
stroke-width: 6;
|
|
stroke-linecap: round;
|
|
transition: stroke-dashoffset 1s linear, stroke 0.5s ease;
|
|
}
|
|
.timer-ring .progress-ring.urgent {
|
|
stroke: #ef4444;
|
|
}
|
|
.timer-text {
|
|
position: absolute;
|
|
top: 50%; left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
font-size: 28px;
|
|
font-weight: 900;
|
|
color: #1e293b;
|
|
transition: color 0.3s ease;
|
|
}
|
|
.timer-text.urgent { color: #ef4444; }
|
|
|
|
/* ─── BUTTON ─── */
|
|
.door-btn {
|
|
width: 100%;
|
|
padding: 18px 24px;
|
|
border: none;
|
|
border-radius: 16px;
|
|
background: linear-gradient(135deg, #4b49ac, #5b52bf);
|
|
color: #fff;
|
|
font-size: 17px;
|
|
font-weight: 800;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 15px rgba(75, 73, 172, 0.35);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.door-btn::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0; left: -100%;
|
|
width: 100%; height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.15), transparent);
|
|
transition: left 0.6s ease;
|
|
}
|
|
.door-btn:hover::before { left: 100%; }
|
|
.door-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 25px rgba(75, 73, 172, 0.45);
|
|
}
|
|
.door-btn:active {
|
|
transform: translateY(0) scale(0.98);
|
|
box-shadow: 0 2px 8px rgba(75, 73, 172, 0.3);
|
|
}
|
|
.door-btn i { font-size: 22px; }
|
|
|
|
.door-btn.clicked {
|
|
background: linear-gradient(135deg, #10b981, #059669);
|
|
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.35);
|
|
}
|
|
|
|
.door-btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none !important;
|
|
}
|
|
|
|
/* ─── EXPIRED OVERLAY ─── */
|
|
.expired-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 999;
|
|
background: #f8f9fa;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
padding: 40px;
|
|
animation: fadeInOverlay 0.4s ease;
|
|
}
|
|
@keyframes fadeInOverlay {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
.expired-overlay .exp-icon {
|
|
width: 80px; height: 80px;
|
|
border-radius: 50%;
|
|
background: #fef2f2;
|
|
border: 1px solid #fecaca;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
.expired-overlay .exp-icon i { font-size: 40px; color: #ef4444; }
|
|
.expired-overlay h3 { font-size: 22px; font-weight: 800; color: #1e293b; margin-bottom: 8px; }
|
|
.expired-overlay p { font-size: 14px; color: #64748b; line-height: 1.6; max-width: 300px; }
|
|
|
|
/* ─── RESPONSIVE ─── */
|
|
@media (max-width: 400px) {
|
|
.access-card { padding: 30px 20px; }
|
|
.access-title { font-size: 20px; }
|
|
.door-btn { font-size: 15px; padding: 16px 20px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<?php if ($valid): ?>
|
|
<!-- ═══ VALID TOKEN ═══ -->
|
|
<div class="access-card" id="accessCard">
|
|
<div class="icon-circle active">
|
|
<i class="mdi mdi-door-open"></i>
|
|
</div>
|
|
|
|
<div class="access-title">Door Access</div>
|
|
<div class="access-subtitle">Your access session is active. Tap the button below to unlock the door.</div>
|
|
|
|
<!-- Countdown timer ring -->
|
|
<div class="timer-ring">
|
|
<svg viewBox="0 0 120 120">
|
|
<circle class="bg-ring" cx="60" cy="60" r="52"/>
|
|
<circle class="progress-ring" id="progressRing" cx="60" cy="60" r="52"
|
|
stroke-dasharray="326.73"
|
|
stroke-dashoffset="0"/>
|
|
</svg>
|
|
<div class="timer-text" id="timerText"><?= $remainingSeconds ?></div>
|
|
</div>
|
|
|
|
<button class="door-btn" id="doorBtn" onclick="openDoor()">
|
|
<i class="mdi mdi-lock-open-variant-outline"></i>
|
|
Open The Door
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Expired overlay -->
|
|
<div class="expired-overlay" id="expiredOverlay">
|
|
<div class="exp-icon"><i class="mdi mdi-timer-off-outline"></i></div>
|
|
<h3>Session Expired</h3>
|
|
<p>Your 30-second access window has ended. Please scan the door code again to get a new session.</p>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
const REMAINING = <?= (int)$remainingSeconds ?>;
|
|
const progressRing = document.getElementById('progressRing');
|
|
const timerText = document.getElementById('timerText');
|
|
const doorBtn = document.getElementById('doorBtn');
|
|
const expiredOverlay = document.getElementById('expiredOverlay');
|
|
const circumference = 326.73; // 2 * π * 52
|
|
|
|
let timeLeft = REMAINING;
|
|
let doorOpened = false;
|
|
|
|
function updateTimer() {
|
|
timeLeft--;
|
|
|
|
if (timeLeft <= 0) {
|
|
// Time's up
|
|
timerText.textContent = '0';
|
|
progressRing.style.strokeDashoffset = circumference;
|
|
expiredOverlay.style.display = 'flex';
|
|
doorBtn.disabled = true;
|
|
|
|
// Just show expired overlay, no redirect
|
|
return;
|
|
}
|
|
|
|
timerText.textContent = timeLeft;
|
|
|
|
// Update progress ring
|
|
const progress = 1 - (timeLeft / REMAINING);
|
|
progressRing.style.strokeDashoffset = circumference * progress;
|
|
|
|
// Urgent state when < 10 seconds
|
|
if (timeLeft <= 10) {
|
|
progressRing.classList.add('urgent');
|
|
timerText.classList.add('urgent');
|
|
}
|
|
|
|
setTimeout(updateTimer, 1000);
|
|
}
|
|
|
|
// Start countdown
|
|
setTimeout(updateTimer, 1000);
|
|
|
|
// Open door handler
|
|
window.openDoor = function() {
|
|
if (doorOpened) return;
|
|
doorOpened = true;
|
|
|
|
doorBtn.innerHTML = '<i class="mdi mdi-check-circle"></i> Door Opened!';
|
|
doorBtn.classList.add('clicked');
|
|
doorBtn.disabled = true;
|
|
|
|
// Vibrate
|
|
if (navigator.vibrate) navigator.vibrate([100, 50, 100]);
|
|
};
|
|
})();
|
|
</script>
|
|
|
|
<?php elseif ($errorType === 'expired'): ?>
|
|
<script>window.location.href = '404.php';</script>
|
|
<?php exit; ?>
|
|
<?php else: ?>
|
|
<script>window.location.href = '404.php';</script>
|
|
<?php exit; ?>
|
|
<?php endif; ?>
|
|
|
|
</body>
|
|
</html>
|