TKK_E32230273/openning.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>