diff --git a/resources/views/operator/dashboard.blade.php b/resources/views/operator/dashboard.blade.php
index e89e2cb..ec65aa4 100644
--- a/resources/views/operator/dashboard.blade.php
+++ b/resources/views/operator/dashboard.blade.php
@@ -29,11 +29,7 @@
transition:opacity .18s;
border-radius:2px 0 0 2px;
}
- .lot-picker-card:hover {
- border-color:#a5b4fc;
- box-shadow:0 4px 20px rgba(99,102,241,.12);
- transform:translateY(-2px);
- }
+ .lot-picker-card:hover { border-color:#a5b4fc; box-shadow:0 4px 20px rgba(99,102,241,.12); transform:translateY(-2px); }
.lot-picker-card:hover::before { opacity:1; }
.lot-picker-card.highlighted { border-color:#6366f1; box-shadow:0 0 0 3px rgba(99,102,241,.15); }
.lot-picker-card.highlighted::before { opacity:1; }
@@ -70,28 +66,71 @@
margin-bottom:1.5rem;
}
- /* ── Tabs ─────────────────────────────────────────────────────────── */
- .op-tabs { display:flex; gap:.375rem; margin-bottom:1.25rem; border-bottom:2px solid #e2e8f0; padding-bottom:0; }
- .op-tab {
- padding:.55rem 1.125rem;
+ /* ── Booking cards ───────────────────────────────────────────────── */
+ .booking-card {
+ background:#fff;
border:none;
- border-bottom:3px solid transparent;
- margin-bottom:-2px;
- background:none;
- font-family:'Cairo',sans-serif;
- font-weight:600;
- font-size:.875rem;
+ border-radius:1rem;
+ box-shadow:0 4px 16px rgba(0,0,0,.06);
+ overflow:hidden;
+ transition:transform .2s, box-shadow .2s;
+ height:100%;
+ }
+ .booking-card:hover {
+ transform:translateY(-5px);
+ box-shadow:0 10px 28px rgba(0,0,0,.1);
+ }
+ .booking-card-header {
+ padding:.75rem 1rem;
+ font-weight:700;
+ font-size:.8rem;
+ color:#fff;
+ text-align:center;
+ letter-spacing:.02em;
+ }
+ .booking-card-header.reservation { background:linear-gradient(90deg,#1e1b4b,#4338ca); }
+ .booking-card-header.walkin { background:linear-gradient(90deg,#064e3b,#059669); }
+
+ .plate-display {
+ font-size:1.6rem;
+ font-weight:900;
+ letter-spacing:.12em;
+ color:#0f172a;
+ background:#f8fafc;
+ border:2px solid #e2e8f0;
+ border-radius:.625rem;
+ padding:.6rem 1rem;
+ text-align:center;
+ margin:.875rem;
+ font-family:monospace;
+ direction:ltr;
+ }
+
+ .booking-card-body {
+ padding:0 .875rem .875rem;
+ }
+
+ .booking-card-body .customer-name {
+ text-align:center;
color:#64748b;
- cursor:pointer;
- border-radius:.5rem .5rem 0 0;
- transition:color .15s, border-color .15s;
+ font-size:.82rem;
+ margin-bottom:.5rem;
+ }
+
+ .time-badge {
display:flex;
align-items:center;
- gap:.4rem;
+ justify-content:center;
+ gap:.375rem;
+ background:#f1f5f9;
+ border-radius:.5rem;
+ padding:.35rem .625rem;
+ font-size:.78rem;
+ color:#475569;
+ margin-bottom:.75rem;
}
- .op-tab:hover { color:#0f172a; }
- .op-tab.active { color:#6366f1; border-bottom-color:#6366f1; }
- .op-tab .badge { font-size:.68rem; padding:.2em .5em; }
+
+ .booking-card-divider { border:none; border-top:1px dashed #e2e8f0; margin:.625rem 0; }
/* ── Receipt modal ────────────────────────────────────────────────── */
.fee-row { display:flex; justify-content:space-between; padding:.35rem 0; border-bottom:1px dashed #f1f5f9; font-size:.875rem; }
@@ -101,6 +140,10 @@
.pay-method:hover { border-color:#a5b4fc; }
.pay-method.selected { border-color:#6366f1; background:#f0f4ff; }
.pay-method i { font-size:1.5rem; display:block; margin-bottom:.25rem; }
+
+ /* Empty state */
+ .empty-state { text-align:center; padding:3rem 1rem; color:#94a3b8; }
+ .empty-state i { font-size:3rem; display:block; margin-bottom:.75rem; opacity:.3; }
@endpush
@@ -154,9 +197,7 @@
-
- {{ $lot['name'] }}
-
+
{{ $lot['name'] }}
{{ $lot['address'] }}
@@ -228,6 +269,7 @@
@php
$pct = $selectedLot->usage_percentage;
$barColor = $pct >= 90 ? '#ef4444' : ($pct >= 60 ? '#f59e0b' : '#10b981');
+ $totalCards = $activeCars->count() + $reservations->count();
@endphp
{{-- ── Selected lot header ─────────────────────────────────────────── --}}
@@ -243,7 +285,6 @@
- {{-- Live stats --}}
{{ $selectedLot->available_spaces }}
@@ -261,7 +302,6 @@
- {{-- Capacity bar (inline) --}}
الإشغال{{ round($pct) }}%
@@ -271,7 +311,6 @@
- {{-- Change lot --}}
@@ -280,241 +319,249 @@
+{{-- ── Toolbar: search + new entry ─────────────────────────────────── --}}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $reservations->count() }} حجز مسبق
+
+
+ {{ $activeCars->count() }} داخل الآن
+
+
+
+
+
+
+{{-- ── Booking cards grid ───────────────────────────────────────────── --}}
+
+
+ {{-- Reservation cards (not yet arrived) --}}
+ @foreach($reservations as $res)
+
+
+
+
+
{{ $res->vehicle_plate ?? '—' }}
+
+
+
+
{{ $res->customer_name ?? 'غير محدد' }}
+ @if($res->phone)
+
{{ $res->phone }}
+ @endif
+
+
+
+
+ {{ $res->start_time->format('H:i') }} — {{ $res->end_time->format('H:i') }}
+ {{ $res->start_time->format('d/m') }}
+
+
+
+
+
+
+
+
+
+ @endforeach
+
+ {{-- Walk-in cards (already inside) --}}
+ @foreach($activeCars as $car)
+ @php
+ $elapsedMin = $car->start_time->diffInMinutes(now());
+ $remainMins = now()->diffInMinutes($car->end_time, false);
+ $isOverdue = $remainMins < 0;
+ @endphp
+
+
+
+
+
{{ $car->vehicle_plate ?? '—' }}
+
+
+
+
{{ $car->customer_name ?? 'غير محدد' }}
+ @if($car->phone)
+
{{ $car->phone }}
+ @endif
+
+
+
+
+ دخل {{ $car->start_time->format('H:i') }}
+ ·
+ {{ floor($elapsedMin/60) }}س {{ $elapsedMin%60 }}د
+
+
+ @if($isOverdue)
+
+
+
+ تجاوز {{ floor(abs($remainMins)/60) }}س {{ abs($remainMins)%60 }}د
+
+
+ @else
+
+
+ متبقي {{ floor($remainMins/60) }}س {{ $remainMins%60 }}د
+
+
+ @endif
+
+
+
+
+
+
+
+ @endforeach
+
+ {{-- Empty state --}}
+ @if($totalCards === 0)
+
+
+
+
لا توجد سيارات أو حجوزات نشطة
+
سجّل دخول سيارة جديدة للبدء
+
+
+
+ @endif
+
+ {{-- No search results --}}
+
+
+
+
لا توجد نتائج مطابقة
+
+
+
+
{{-- Auto-refresh countdown --}}
-
+
—
-{{-- ── Tabs ───────────────────────────────────────────────────────────── --}}
-
-
-
-
-
-
-{{-- ─────────────────────────────────────────────────────────────────────
- TAB 1: Active walk-in cars
-──────────────────────────────────────────────────────────────────────── --}}
-
-
-
-
- @if($activeCars->isEmpty())
-
-
-
لا توجد سيارات داخل الموقف
-
-
- @else
-
-
-
-
- | رقم اللوحة |
- السائق |
- وقت الدخول |
- المدة |
- المتبقي |
- إجراء |
-
-
-
- @foreach($activeCars as $car)
- @php
- $elapsedMin = $car->start_time->diffInMinutes(now());
- $remainMins = now()->diffInMinutes($car->end_time, false);
- $isOverdue = $remainMins < 0;
- @endphp
-
- |
-
- {{ $car->vehicle_plate ?? '—' }}
-
- |
-
- {{ $car->customer_name ?? 'غير محدد' }}
- @if($car->phone)
- {{ $car->phone }}
- @endif
- |
-
- {{ $car->start_time->format('H:i') }}
- {{ $car->start_time->format('Y/m/d') }}
- |
-
-
- {{ floor($elapsedMin/60) }}س {{ $elapsedMin%60 }}د
-
- |
-
- @if($isOverdue)
-
- تجاوز {{ floor(abs($remainMins)/60) }}س {{ abs($remainMins)%60 }}د
-
- @else
-
- {{ floor($remainMins/60) }}س {{ $remainMins%60 }}د
-
- @endif
- |
-
-
- |
-
- @endforeach
-
-
-
- @endif
-
-
-
-{{-- ─────────────────────────────────────────────────────────────────────
- TAB 2: Manual check-in form
-──────────────────────────────────────────────────────────────────────── --}}
-
-
-{{-- ─────────────────────────────────────────────────────────────────────
- TAB 3: Pre-reservations
-──────────────────────────────────────────────────────────────────────── --}}
-
-
-
-
- @if($reservations->isEmpty())
-
-
-
لا توجد حجوزات مسبقة لهذا الموقف
-
- @else
-
-
-
-
- | # |
- العميل |
- الهاتف |
- وقت البدء |
- وقت الانتهاء |
- إجراء |
-
-
-
- @foreach($reservations as $res)
-
- | {{ $res->id }} |
- {{ $res->customer_name ?? '—' }} |
- {{ $res->phone ?? '—' }} |
-
- {{ $res->start_time->format('H:i') }}
- {{ $res->start_time->format('Y/m/d') }}
- |
-
- {{ $res->end_time->format('H:i') }}
- {{ $res->end_time->format('Y/m/d') }}
- |
-
-
- |
-
- @endforeach
-
-
-
- @endif
-
-
-
@endif {{-- end selectedLot --}}
+{{-- ══════════════════════════════════════════════════════════════════════
+ DIRECT ENTRY MODAL
+══════════════════════════════════════════════════════════════════════ --}}
+
+
+
{{-- ══════════════════════════════════════════════════════════════════════
RECEIPT & PAYMENT MODAL
══════════════════════════════════════════════════════════════════════ --}}
@@ -540,7 +587,7 @@
@@ -599,7 +646,6 @@
- {{-- File upload (hidden until upload selected) --}}
toJson() !!};
const csrf = document.querySelector('meta[name="csrf-token"]').content;
-// ── Navigate to lot ──────────────────────────────────────────────────────────
function selectLot(id) { window.location = '/operator/dashboard?lot_id=' + id; }
@if(!$selectedLot)
@@ -638,7 +683,6 @@ function selectLot(id) { window.location = '/operator/dashboard?lot_id=' + id; }
// LOT PICKER SCRIPTS
// ════════════════════════════════════════════════════════════════════════════
-// ── Map ──────────────────────────────────────────────────────────────────────
const map = L.map('opMap').setView([33.5138, 36.2765], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution:'© OpenStreetMap' }).addTo(map);
const markers = {};
@@ -670,7 +714,6 @@ lots.forEach(l => {
`);
-
m.on('click', () => highlightCard(l.id));
});
@@ -679,21 +722,16 @@ if (lots.length) {
map.fitBounds(g.getBounds().pad(.15));
}
-// ── Card highlight ───────────────────────────────────────────────────────────
let hlId = null;
function highlightCard(id) {
if (hlId) document.getElementById('lcard-' + hlId)?.classList.remove('highlighted');
hlId = id;
const card = document.getElementById('lcard-' + id);
- if (card) {
- card.classList.add('highlighted');
- card.scrollIntoView({ behavior:'smooth', block:'nearest' });
- }
+ if (card) { card.classList.add('highlighted'); card.scrollIntoView({ behavior:'smooth', block:'nearest' }); }
const l = lots.find(x => x.id === id);
if (l) map.setView([l.lat, l.lng], 15, { animate:true });
}
-// ── Search ───────────────────────────────────────────────────────────────────
const searchEl = document.getElementById('lotSearch');
const clearBtn = document.getElementById('clearSearch');
const noResults = document.getElementById('noResults');
@@ -716,7 +754,6 @@ clearBtn.addEventListener('click', () => {
searchEl.value = ''; searchEl.dispatchEvent(new Event('input')); searchEl.focus();
});
-// ── Mobile tab ───────────────────────────────────────────────────────────────
function mobileTab(tab) {
const cc = document.getElementById('cardsCol');
const mc = document.getElementById('mapCol');
@@ -737,56 +774,81 @@ if (window.innerWidth < 992) {
document.getElementById('mapCol').style.display = 'none';
}
-// Highlight duration radio default
-document.querySelectorAll('input[name="duration_hours"]').forEach(r => {
- if (r.checked) r.closest('label').style.cssText='border:2px solid #6366f1;cursor:pointer;font-size:.82rem;font-weight:700;color:#4338ca;background:#f0f4ff;transition:all .15s;';
-});
-
@else
// ════════════════════════════════════════════════════════════════════════════
// OPERATOR PANEL SCRIPTS
// ════════════════════════════════════════════════════════════════════════════
-// ── Tabs ─────────────────────────────────────────────────────────────────────
-function switchTab(name) {
- ['active','checkin','reservations'].forEach(t => {
- document.getElementById('panel-' + t).style.display = t === name ? '' : 'none';
- document.getElementById('tab-' + t).classList.toggle('active', t === name);
- });
-}
+// ── Card search ───────────────────────────────────────────────────────────────
+const cardSearch = document.getElementById('cardSearch');
+const clearCardSearch = document.getElementById('clearCardSearch');
-// ── Check-in form ─────────────────────────────────────────────────────────────
-// Default duration highlight
-document.querySelectorAll('input[name="duration_hours"]').forEach(r => {
- if (r.checked) r.closest('label').style.cssText='border:2px solid #6366f1;cursor:pointer;font-size:.82rem;font-weight:700;color:#4338ca;background:#f0f4ff;transition:all .15s;';
- r.addEventListener('change', () => {
- document.querySelectorAll('input[name="duration_hours"]').forEach(x =>
- x.closest('label').style.cssText='border:2px solid #e2e8f0;cursor:pointer;font-size:.82rem;font-weight:600;color:#475569;transition:all .15s;'
- );
- r.closest('label').style.cssText='border:2px solid #6366f1;cursor:pointer;font-size:.82rem;font-weight:700;color:#4338ca;background:#f0f4ff;transition:all .15s;';
+cardSearch.addEventListener('input', () => {
+ const q = cardSearch.value.trim().toLowerCase();
+ clearCardSearch.style.display = q ? 'block' : 'none';
+ let vis = 0;
+ document.querySelectorAll('.booking-card-wrap').forEach(w => {
+ const match = !q || w.dataset.plate.includes(q) || w.dataset.name.includes(q);
+ w.style.display = match ? '' : 'none';
+ if (match) vis++;
});
+ document.getElementById('noCardResults').style.display =
+ (vis === 0 && q) ? 'block' : 'none';
+});
+clearCardSearch.addEventListener('click', () => {
+ cardSearch.value = ''; cardSearch.dispatchEvent(new Event('input')); cardSearch.focus();
});
-document.getElementById('checkInForm')?.addEventListener('submit', async e => {
- e.preventDefault();
- const btn = document.getElementById('checkInBtn');
+// ── Duration tile highlight ───────────────────────────────────────────────────
+function initDurationTiles() {
+ document.querySelectorAll('.duration-tile').forEach(label => {
+ const radio = label.querySelector('input[type="radio"]');
+ if (radio.checked) label.style.cssText = 'border:2px solid #10b981;cursor:pointer;font-size:.82rem;font-weight:700;color:#059669;background:#f0fdf4;transition:all .15s;border-radius:.75rem;';
+ radio.addEventListener('change', () => {
+ document.querySelectorAll('.duration-tile').forEach(l =>
+ l.style.cssText = 'border:2px solid #e2e8f0;cursor:pointer;font-size:.82rem;font-weight:600;color:#475569;transition:all .15s;border-radius:.75rem;'
+ );
+ label.style.cssText = 'border:2px solid #10b981;cursor:pointer;font-size:.82rem;font-weight:700;color:#059669;background:#f0fdf4;transition:all .15s;border-radius:.75rem;';
+ });
+ });
+}
+initDurationTiles();
+
+// ── Check-in form submit ──────────────────────────────────────────────────────
+async function submitCheckIn() {
+ const form = document.getElementById('checkInForm');
+ const btn = document.getElementById('checkInBtn');
+ if (!form.vehicle_plate.value.trim()) {
+ form.vehicle_plate.focus(); return;
+ }
btn.disabled = true;
btn.innerHTML = 'جاري التسجيل...';
try {
- const res = await fetch('/operator/check-in', { method:'POST', body: new FormData(e.target) });
+ const res = await fetch('/operator/check-in', { method:'POST', body: new FormData(form) });
const data = await res.json();
if (data.success) {
btn.style.background = '#059669';
- btn.innerHTML = 'تم التسجيل — يتم التحديث...';
- setTimeout(() => location.reload(), 900);
+ btn.innerHTML = 'تم التسجيل!';
+ setTimeout(() => location.reload(), 700);
} else {
- alert(data.message || 'حدث خطأ'); resetCheckInBtn();
+ alert(data.message || 'حدث خطأ');
+ btn.disabled = false;
+ btn.innerHTML = 'تأكيد الدخول';
}
- } catch { alert('خطأ في الاتصال'); resetCheckInBtn(); }
- function resetCheckInBtn() {
+ } catch {
+ alert('خطأ في الاتصال');
btn.disabled = false;
- btn.innerHTML = 'تسجيل الدخول';
+ btn.innerHTML = 'تأكيد الدخول';
}
+}
+
+// Reset modal form when closed
+document.getElementById('newEntryModal').addEventListener('hidden.bs.modal', () => {
+ document.getElementById('checkInForm').reset();
+ document.getElementById('checkInBtn').disabled = false;
+ document.getElementById('checkInBtn').innerHTML = 'تأكيد الدخول';
+ document.getElementById('checkInBtn').style.background = '#10b981';
+ initDurationTiles();
});
// ── Activate reservation ──────────────────────────────────────────────────────
@@ -801,15 +863,30 @@ async function activateRes(id, btn) {
});
const data = await res.json();
if (data.success) {
- document.getElementById('res-' + id)?.remove();
- alert(data.message);
- location.reload();
- } else { alert(data.message || 'حدث خطأ'); btn.innerHTML=orig; btn.disabled=false; }
- } catch { alert('خطأ في الاتصال'); btn.innerHTML=orig; btn.disabled=false; }
+ // swap check-in button to success state, enable checkout
+ btn.style.background = '#059669';
+ btn.innerHTML = 'تم الدخول';
+ const checkoutBtn = document.getElementById('checkout-res-' + id);
+ if (checkoutBtn) {
+ checkoutBtn.disabled = false;
+ checkoutBtn.style.opacity = '1';
+ checkoutBtn.style.cursor = 'pointer';
+ checkoutBtn.onclick = () => openReceipt(id);
+ }
+ } else {
+ alert(data.message || 'حدث خطأ');
+ btn.innerHTML = orig;
+ btn.disabled = false;
+ }
+ } catch {
+ alert('خطأ في الاتصال');
+ btn.innerHTML = orig;
+ btn.disabled = false;
+ }
}
// ── Receipt modal ─────────────────────────────────────────────────────────────
-let receiptModal = null;
+let receiptModal = null;
let currentBookingId = null;
let selectedPayment = 'cash';
@@ -823,15 +900,12 @@ async function openReceipt(id) {
selectedPayment = 'cash';
selectPayment('cash');
- // Reset breakdown
document.getElementById('rcpt-breakdown').innerHTML =
'
';
- document.getElementById('rcpt-plate').textContent = '—';
- document.getElementById('rcpt-name').textContent = '—';
- document.getElementById('rcpt-duration').textContent = '—';
- document.getElementById('rcpt-entry').textContent = '—';
- document.getElementById('rcpt-exit').textContent = '—';
- document.getElementById('rcpt-total').textContent = '—';
+ ['rcpt-plate','rcpt-name','rcpt-duration','rcpt-entry','rcpt-exit','rcpt-total'].forEach(i =>
+ document.getElementById(i).textContent = '—'
+ );
+ document.getElementById('rcpt-lot').textContent = '';
getModal().show();
@@ -842,33 +916,31 @@ async function openReceipt(id) {
const d = data.data;
document.getElementById('rcpt-lot').textContent = d.lot_name;
- document.getElementById('rcpt-plate').textContent = d.plate || '—';
+ document.getElementById('rcpt-plate').textContent = d.plate || '—';
document.getElementById('rcpt-name').textContent = d.customer_name || 'غير محدد';
document.getElementById('rcpt-duration').textContent = d.duration_label;
document.getElementById('rcpt-entry').textContent = d.entry_time;
document.getElementById('rcpt-exit').textContent = d.exit_time;
document.getElementById('rcpt-total').textContent = Number(d.total_fee).toLocaleString('ar-SA') + ' ل.س';
- // Breakdown table
const rows = d.fee_details.map(r => `
${r.day} ${r.date}
${r.hours}س × ${Number(r.rate).toLocaleString('ar-SA')}
${Number(r.subtotal).toLocaleString('ar-SA')} ل.س
`).join('');
- document.getElementById('rcpt-breakdown').innerHTML = rows || 'لا تفاصيل
';
+ document.getElementById('rcpt-breakdown').innerHTML =
+ rows || 'لا تفاصيل
';
} catch { alert('تعذّر تحميل بيانات الفاتورة'); }
}
-// Payment method toggle
function selectPayment(method) {
selectedPayment = method;
- document.getElementById('pm-cash').classList.toggle('selected', method === 'cash');
+ document.getElementById('pm-cash').classList.toggle('selected', method === 'cash');
document.getElementById('pm-upload').classList.toggle('selected', method === 'upload');
document.getElementById('uploadArea').style.display = method === 'upload' ? 'block' : 'none';
}
-// Confirm payment
document.getElementById('confirmPayBtn').addEventListener('click', async () => {
if (!currentBookingId) return;
if (selectedPayment === 'upload' && !document.getElementById('paymentProofFile').files.length) {
@@ -879,7 +951,6 @@ document.getElementById('confirmPayBtn').addEventListener('click', async () => {
btn.innerHTML = 'جاري المعالجة...';
const fd = new FormData();
- fd.append('_method', 'POST');
fd.append('payment_method', selectedPayment);
if (selectedPayment === 'upload') {
fd.append('payment_proof', document.getElementById('paymentProofFile').files[0]);
@@ -887,29 +958,33 @@ document.getElementById('confirmPayBtn').addEventListener('click', async () => {
try {
const res = await fetch(`/operator/${currentBookingId}/payment`, {
- method:'POST',
- headers:{'X-CSRF-TOKEN':csrf},
- body: fd
+ method:'POST', headers:{'X-CSRF-TOKEN':csrf}, body: fd
});
const data = await res.json();
if (data.success) {
getModal().hide();
- document.getElementById('row-' + currentBookingId)?.remove();
- const row = document.getElementById('row-' + currentBookingId);
- if (row) { row.style.transition='opacity .4s'; row.style.opacity='0'; setTimeout(()=>row.remove(),400); }
+ const card = document.getElementById('card-' + currentBookingId);
+ if (card) { card.style.transition='opacity .4s'; card.style.opacity='0'; setTimeout(()=>card.remove(),400); }
setTimeout(() => location.reload(), 600);
} else {
alert(data.message || 'حدث خطأ');
- btn.disabled = false;
+ btn.disabled = false;
btn.innerHTML = 'تأكيد الدفع وإغلاق البوابة';
}
} catch {
alert('خطأ في الاتصال');
- btn.disabled = false;
+ btn.disabled = false;
btn.innerHTML = 'تأكيد الدفع وإغلاق البوابة';
}
});
+// Reset receipt modal on close
+document.getElementById('receiptModal').addEventListener('hidden.bs.modal', () => {
+ document.getElementById('confirmPayBtn').disabled = false;
+ document.getElementById('confirmPayBtn').innerHTML = 'تأكيد الدفع وإغلاق البوابة';
+ document.getElementById('paymentProofFile').value = '';
+});
+
// ── Auto-refresh countdown ────────────────────────────────────────────────────
let t = 30;
const badge = document.getElementById('refresh-badge');