2269 lines
70 KiB
HTML
2269 lines
70 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Siro Driver - Ride Lifecycle Simulator</title>
|
|
<!-- Google Fonts: Outfit -->
|
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<!-- Lucide Icons -->
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<style>
|
|
:root {
|
|
--bg-dark: #080b11;
|
|
--card-bg: rgba(13, 18, 29, 0.75);
|
|
--card-border: rgba(255, 255, 255, 0.08);
|
|
--accent-gold: #f59e0b;
|
|
--accent-gold-rgb: 245, 158, 11;
|
|
--accent-blue: #3b82f6;
|
|
--accent-emerald: #10b981;
|
|
--accent-emerald-rgb: 16, 185, 129;
|
|
--accent-rose: #f43f5e;
|
|
--text-main: #f3f4f6;
|
|
--text-muted: #9ca3af;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
font-family: 'Outfit', sans-serif;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
body {
|
|
background-color: var(--bg-dark);
|
|
color: var(--text-main);
|
|
overflow-x: hidden;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* Premium Header */
|
|
header {
|
|
background: linear-gradient(to bottom, rgba(8, 11, 17, 0.95), rgba(8, 11, 17, 0));
|
|
padding: 1.5rem 2rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
border-bottom: 1px solid var(--card-border);
|
|
backdrop-filter: blur(10px);
|
|
z-index: 100;
|
|
}
|
|
|
|
.logo-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.logo-badge {
|
|
background: linear-gradient(135deg, var(--accent-gold), #d97706);
|
|
color: #000;
|
|
padding: 0.4rem 0.8rem;
|
|
border-radius: 8px;
|
|
font-weight: 800;
|
|
font-size: 1.1rem;
|
|
letter-spacing: 1px;
|
|
box-shadow: 0 0 15px rgba(245, 158, 11, 0.3);
|
|
}
|
|
|
|
.logo-title {
|
|
font-size: 1.3rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.logo-title span {
|
|
color: var(--accent-gold);
|
|
}
|
|
|
|
.system-status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1.5rem;
|
|
font-size: 0.9rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.status-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background-color: var(--accent-emerald);
|
|
box-shadow: 0 0 8px var(--accent-emerald);
|
|
}
|
|
|
|
/* Dashboard Layout */
|
|
.dashboard {
|
|
flex: 1;
|
|
display: grid;
|
|
grid-template-columns: 380px 1fr;
|
|
gap: 1.5rem;
|
|
padding: 1.5rem 2rem;
|
|
max-width: 1800px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
}
|
|
|
|
/* Simulation Control Panel */
|
|
.control-panel {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 20px;
|
|
padding: 1.5rem;
|
|
backdrop-filter: blur(16px) saturate(180%);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.25rem;
|
|
height: fit-content;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.panel-section {
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
padding-bottom: 1.25rem;
|
|
}
|
|
|
|
.panel-section:last-child {
|
|
border: none;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: var(--accent-gold);
|
|
margin-bottom: 0.75rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.section-desc {
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted);
|
|
margin-bottom: 1rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* Buttons & Controls */
|
|
.btn {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
color: var(--text-main);
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
font-size: 0.9rem;
|
|
transition: all 0.2s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
width: 100%;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.btn:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.btn-gold {
|
|
background: var(--accent-gold);
|
|
color: #000;
|
|
font-weight: 600;
|
|
border: none;
|
|
}
|
|
|
|
.btn-gold:hover {
|
|
background: #e08e0b;
|
|
box-shadow: 0 0 15px rgba(245, 158, 11, 0.4);
|
|
}
|
|
|
|
.btn-rose {
|
|
background: rgba(244, 63, 94, 0.15);
|
|
border-color: rgba(244, 63, 94, 0.3);
|
|
color: var(--accent-rose);
|
|
}
|
|
|
|
.btn-rose:hover {
|
|
background: rgba(244, 63, 94, 0.25);
|
|
}
|
|
|
|
.switch-container {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.switch-label {
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.switch {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 44px;
|
|
height: 24px;
|
|
}
|
|
|
|
.switch input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.slider {
|
|
position: absolute;
|
|
cursor: pointer;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
transition: .4s;
|
|
border-radius: 24px;
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.slider:before {
|
|
position: absolute;
|
|
content: "";
|
|
height: 16px;
|
|
width: 16px;
|
|
left: 3px;
|
|
bottom: 3px;
|
|
background-color: var(--text-main);
|
|
transition: .4s;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
input:checked + .slider {
|
|
background-color: var(--accent-gold);
|
|
}
|
|
|
|
input:checked + .slider:before {
|
|
transform: translateX(20px);
|
|
background-color: #000;
|
|
}
|
|
|
|
/* Info Badge / Indicator */
|
|
.info-badge {
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
padding: 0.5rem 0.75rem;
|
|
font-size: 0.85rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.info-val {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.info-val.emerald { color: var(--accent-emerald); }
|
|
.info-val.gold { color: var(--accent-gold); }
|
|
.info-val.rose { color: var(--accent-rose); }
|
|
|
|
/* Main Simulator Space */
|
|
.simulator-workspace {
|
|
display: grid;
|
|
grid-template-columns: 1fr 380px;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
/* Map Mock Component */
|
|
.map-container {
|
|
background: #0f1322;
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 20px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
min-height: 550px;
|
|
box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.8), 0 10px 30px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
/* Map Elements */
|
|
.map-grid {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-size: 40px 40px;
|
|
background-image:
|
|
linear-gradient(to right, rgba(255, 255, 255, 0.015) 1px, transparent 1px),
|
|
linear-gradient(to bottom, rgba(255, 255, 255, 0.015) 1px, transparent 1px);
|
|
}
|
|
|
|
.map-road {
|
|
position: absolute;
|
|
background-color: rgba(255, 255, 255, 0.04);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.road-h { height: 16px; width: 120%; left: -10%; }
|
|
.road-v { width: 16px; height: 120%; top: -10%; }
|
|
|
|
/* Heatmap Grids overlay */
|
|
.heatmap-layer {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: grid;
|
|
grid-template-columns: repeat(10, 1fr);
|
|
grid-template-rows: repeat(10, 1fr);
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.5s ease;
|
|
}
|
|
|
|
.heatmap-grid {
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
.heatmap-grid.high {
|
|
background-color: rgba(239, 68, 68, 0.25);
|
|
border-color: rgba(239, 68, 68, 0.6);
|
|
}
|
|
|
|
.heatmap-grid.med {
|
|
background-color: rgba(245, 158, 11, 0.25);
|
|
border-color: rgba(245, 158, 11, 0.6);
|
|
}
|
|
|
|
.heatmap-grid.low {
|
|
background-color: rgba(234, 179, 8, 0.15);
|
|
border-color: rgba(234, 179, 8, 0.4);
|
|
}
|
|
|
|
/* Route Lines drawing */
|
|
.route-canvas {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Markers */
|
|
.marker {
|
|
position: absolute;
|
|
transform: translate(-50%, -50%);
|
|
transition: top 0.5s cubic-bezier(0.25, 1, 0.5, 1), left 0.5s cubic-bezier(0.25, 1, 0.5, 1), transform 0.5s ease;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
z-index: 10;
|
|
}
|
|
|
|
.marker-dot {
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 0 15px rgba(0, 0, 0, 0.6);
|
|
}
|
|
|
|
.marker-car {
|
|
background: var(--accent-emerald);
|
|
border: 2px solid #fff;
|
|
}
|
|
|
|
.marker-pax {
|
|
background: var(--accent-gold);
|
|
border: 2px solid #000;
|
|
}
|
|
|
|
.marker-dest {
|
|
background: var(--accent-rose);
|
|
border: 2px solid #fff;
|
|
}
|
|
|
|
.marker-label {
|
|
background: rgba(0, 0, 0, 0.85);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
padding: 0.2rem 0.5rem;
|
|
border-radius: 6px;
|
|
font-size: 0.75rem;
|
|
margin-bottom: 4px;
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Pulsing radar for online waiting */
|
|
.radar-pulse {
|
|
position: absolute;
|
|
width: 120px;
|
|
height: 120px;
|
|
border-radius: 50%;
|
|
background: rgba(16, 185, 129, 0.05);
|
|
border: 1px solid rgba(16, 185, 129, 0.3);
|
|
transform: translate(-50%, -50%);
|
|
animation: pulse 2s infinite linear;
|
|
pointer-events: none;
|
|
display: none;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% { transform: translate(-50%, -50%) scale(0.3); opacity: 1; }
|
|
100% { transform: translate(-50%, -50%) scale(1.5); opacity: 0; }
|
|
}
|
|
|
|
/* Mock Driver App Phone UI */
|
|
.phone-container {
|
|
background: #0b0e14;
|
|
border: 6px solid #1f2937;
|
|
border-radius: 36px;
|
|
height: 550px;
|
|
width: 100%;
|
|
position: relative;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
|
|
}
|
|
|
|
/* Phone Notch */
|
|
.phone-notch {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 130px;
|
|
height: 18px;
|
|
background: #1f2937;
|
|
border-bottom-left-radius: 12px;
|
|
border-bottom-right-radius: 12px;
|
|
z-index: 50;
|
|
}
|
|
|
|
/* App StatusBar */
|
|
.app-statusbar {
|
|
height: 28px;
|
|
background: rgba(11, 14, 20, 0.9);
|
|
padding: 0 1.25rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
color: #9ca3af;
|
|
z-index: 40;
|
|
}
|
|
|
|
/* App Content Pane */
|
|
.app-screen {
|
|
flex: 1;
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* App Active Header Card */
|
|
.app-header-card {
|
|
background: linear-gradient(180deg, rgba(13, 18, 29, 0.95) 0%, rgba(13, 18, 29, 0.85) 100%);
|
|
border-bottom: 1px solid var(--card-border);
|
|
padding: 1rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
z-index: 20;
|
|
}
|
|
|
|
.driver-profile {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.driver-avatar {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--accent-gold), #d97706);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 700;
|
|
color: #000;
|
|
}
|
|
|
|
.driver-info-text h4 {
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.driver-info-text span {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.app-earnings {
|
|
text-align: right;
|
|
}
|
|
|
|
.app-earnings span {
|
|
font-size: 0.7rem;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.app-earnings h3 {
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
color: var(--accent-gold);
|
|
}
|
|
|
|
/* Bottom Control panel of App */
|
|
.app-bottom-panel {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
background: rgba(13, 18, 29, 0.95);
|
|
border-top: 1px solid var(--card-border);
|
|
backdrop-filter: blur(20px);
|
|
padding: 1.25rem;
|
|
border-top-left-radius: 20px;
|
|
border-top-right-radius: 20px;
|
|
z-index: 30;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.offline-view {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.offline-title {
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
color: var(--text-muted);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
/* Slider Button (Go Online gesture) */
|
|
.slider-btn-container {
|
|
width: 100%;
|
|
height: 52px;
|
|
background: rgba(255, 255, 255, 0.04);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 26px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.slider-text {
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
color: var(--text-muted);
|
|
letter-spacing: 0.5px;
|
|
pointer-events: none;
|
|
z-index: 2;
|
|
}
|
|
|
|
.slider-handle {
|
|
position: absolute;
|
|
left: 3px;
|
|
top: 3px;
|
|
width: 46px;
|
|
height: 46px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--accent-gold), #d97706);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #000;
|
|
box-shadow: 0 0 10px rgba(245, 158, 11, 0.3);
|
|
cursor: grab;
|
|
z-index: 3;
|
|
}
|
|
|
|
.slider-bg-fill {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
height: 100%;
|
|
width: 0;
|
|
background: rgba(245, 158, 11, 0.15);
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Online Waiting View */
|
|
.online-view {
|
|
display: none;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.online-pulse-circle {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
background: rgba(16, 185, 129, 0.1);
|
|
border: 2px solid var(--accent-emerald);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--accent-emerald);
|
|
animation: float 2s infinite ease-in-out;
|
|
}
|
|
|
|
@keyframes float {
|
|
0% { transform: translateY(0px); }
|
|
50% { transform: translateY(-6px); }
|
|
100% { transform: translateY(0px); }
|
|
}
|
|
|
|
.online-status-title {
|
|
font-size: 1.05rem;
|
|
font-weight: 700;
|
|
color: var(--accent-emerald);
|
|
}
|
|
|
|
.online-status-subtitle {
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* New Order Modal (Overlay Mode) */
|
|
.order-overlay-modal {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(8, 11, 17, 0.95);
|
|
z-index: 60;
|
|
display: none;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
padding: 2rem 1.5rem 1.5rem;
|
|
}
|
|
|
|
.order-overlay-header {
|
|
text-align: center;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.order-badge-container {
|
|
background: rgba(245, 158, 11, 0.1);
|
|
border: 1px solid var(--accent-gold);
|
|
color: var(--accent-gold);
|
|
padding: 0.35rem 0.8rem;
|
|
border-radius: 20px;
|
|
font-size: 0.8rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.5px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
.order-timer-circle {
|
|
position: relative;
|
|
width: 90px;
|
|
height: 90px;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.timer-svg {
|
|
transform: rotate(-90deg);
|
|
width: 90px;
|
|
height: 90px;
|
|
}
|
|
|
|
.timer-track {
|
|
fill: none;
|
|
stroke: rgba(255, 255, 255, 0.05);
|
|
stroke-width: 6;
|
|
}
|
|
|
|
.timer-fill {
|
|
fill: none;
|
|
stroke: var(--accent-gold);
|
|
stroke-width: 6;
|
|
stroke-linecap: round;
|
|
stroke-dasharray: 251.2;
|
|
stroke-dashoffset: 0;
|
|
transition: stroke-dashoffset 1s linear;
|
|
}
|
|
|
|
.timer-text {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
font-size: 1.8rem;
|
|
font-weight: 800;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
/* Details Card */
|
|
.order-details-card {
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 16px;
|
|
padding: 1rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.detail-label {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.detail-val {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.overlay-accept-btn {
|
|
height: 52px;
|
|
background: linear-gradient(135deg, var(--accent-emerald), #059669);
|
|
color: #000;
|
|
font-weight: 700;
|
|
font-size: 1.05rem;
|
|
border: none;
|
|
border-radius: 26px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
box-shadow: 0 0 15px rgba(16, 185, 129, 0.3);
|
|
transition: transform 0.1s ease;
|
|
}
|
|
|
|
.overlay-accept-btn:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
/* Ride Screen (Pickup & Trip) */
|
|
.ride-info-view {
|
|
display: none;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.ride-status-badge {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.badge-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.badge-indicator.pickup { color: var(--accent-gold); }
|
|
.badge-indicator.trip { color: var(--accent-blue); }
|
|
|
|
.passenger-strip {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 12px;
|
|
padding: 0.65rem 0.75rem;
|
|
}
|
|
|
|
.passenger-strip-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.pax-avatar {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
background: #374151;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.pax-name {
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.pax-rating {
|
|
font-size: 0.75rem;
|
|
color: var(--accent-gold);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.1rem;
|
|
}
|
|
|
|
.pax-action-icons {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.circle-icon-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
background: rgba(255, 255, 255, 0.06);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--text-main);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.circle-icon-btn:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.trip-metrics-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.metric-box {
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 10px;
|
|
padding: 0.5rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.metric-label {
|
|
font-size: 0.7rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.metric-val {
|
|
font-size: 1rem;
|
|
font-weight: 700;
|
|
color: var(--accent-gold);
|
|
}
|
|
|
|
/* Turn-by-Turn Instruction Panel */
|
|
.tbt-panel {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 10px;
|
|
right: 10px;
|
|
background: rgba(13, 18, 29, 0.95);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 12px;
|
|
padding: 0.75rem;
|
|
display: none;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
z-index: 25;
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.tbt-icon-box {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 8px;
|
|
background: var(--accent-blue);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #fff;
|
|
}
|
|
|
|
.tbt-info {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.15rem;
|
|
}
|
|
|
|
.tbt-instruction {
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tbt-dist {
|
|
font-size: 0.7rem;
|
|
color: var(--accent-blue);
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Swipe gesture for Start/End Ride */
|
|
.swipe-container {
|
|
width: 100%;
|
|
height: 52px;
|
|
background: rgba(255, 255, 255, 0.04);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 26px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.swipe-text {
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
color: var(--text-muted);
|
|
pointer-events: none;
|
|
z-index: 2;
|
|
}
|
|
|
|
.swipe-handle {
|
|
position: absolute;
|
|
left: 3px;
|
|
top: 3px;
|
|
width: 46px;
|
|
height: 46px;
|
|
border-radius: 50%;
|
|
background: var(--accent-blue);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #fff;
|
|
z-index: 3;
|
|
cursor: grab;
|
|
}
|
|
|
|
.swipe-handle.rose {
|
|
background: var(--accent-rose);
|
|
}
|
|
|
|
/* Star rating widget */
|
|
.rating-view {
|
|
display: none;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.stars-row {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.star-icon {
|
|
color: #374151;
|
|
cursor: pointer;
|
|
transition: color 0.15s ease;
|
|
}
|
|
|
|
.star-icon.filled {
|
|
color: var(--accent-gold);
|
|
}
|
|
|
|
/* Stats sidebar / logs */
|
|
.stats-sidebar {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 20px;
|
|
padding: 1.5rem;
|
|
backdrop-filter: blur(16px) saturate(180%);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 12px;
|
|
padding: 0.75rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.stat-val {
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.stat-val.emerald { color: var(--accent-emerald); }
|
|
.stat-val.gold { color: var(--accent-gold); }
|
|
.stat-val.blue { color: var(--accent-blue); }
|
|
|
|
/* Performance widgets */
|
|
.perf-bar-bg {
|
|
width: 100%;
|
|
height: 6px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.perf-bar-fill {
|
|
height: 100%;
|
|
background-color: var(--accent-emerald);
|
|
width: 10%;
|
|
transition: width 0.5s ease, background-color 0.5s ease;
|
|
}
|
|
|
|
/* Console Logger */
|
|
.console-logs {
|
|
background: rgba(0, 0, 0, 0.6);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 12px;
|
|
padding: 0.75rem;
|
|
font-family: monospace;
|
|
font-size: 0.75rem;
|
|
color: #a7f3d0;
|
|
height: 160px;
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
.log-item {
|
|
line-height: 1.3;
|
|
border-left: 2px solid var(--accent-gold);
|
|
padding-left: 0.5rem;
|
|
}
|
|
|
|
.log-item.error {
|
|
border-color: var(--accent-rose);
|
|
color: #fca5a5;
|
|
}
|
|
|
|
.log-item.info {
|
|
border-color: var(--accent-blue);
|
|
color: #93c5fd;
|
|
}
|
|
|
|
/* Arabic Notification overlay popup styling */
|
|
.dialog-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.75);
|
|
backdrop-filter: blur(5px);
|
|
z-index: 1000;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.dialog-box {
|
|
background: #0d121d;
|
|
border: 1px solid var(--accent-rose);
|
|
border-radius: 16px;
|
|
padding: 1.5rem;
|
|
width: 320px;
|
|
text-align: center;
|
|
box-shadow: 0 10px 25px rgba(244, 63, 148, 0.15);
|
|
}
|
|
|
|
.dialog-icon {
|
|
font-size: 2.5rem;
|
|
color: var(--accent-rose);
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.dialog-title {
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.dialog-body {
|
|
font-size: 0.85rem;
|
|
color: var(--text-muted);
|
|
margin-bottom: 1.25rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.dialog-btn {
|
|
padding: 0.65rem 1.25rem;
|
|
background: var(--accent-rose);
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Header -->
|
|
<header>
|
|
<div class="logo-container">
|
|
<div class="logo-badge">SIRO</div>
|
|
<div class="logo-title">Captain <span>Ride Simulator</span></div>
|
|
</div>
|
|
<div class="system-status">
|
|
<div class="status-item">
|
|
<div class="status-dot"></div>
|
|
<span>MapLibre Engine: Active</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<i data-lucide="cpu" style="width: 16px; height: 16px;"></i>
|
|
<span>Optimized Mode</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Dashboard -->
|
|
<div class="dashboard">
|
|
|
|
<!-- Control Panel -->
|
|
<div class="control-panel">
|
|
|
|
<!-- Driver state overrides -->
|
|
<div class="panel-section">
|
|
<div class="section-title">
|
|
<i data-lucide="sliders" style="width: 18px; height: 18px;"></i>
|
|
<span>Simulation Overrides</span>
|
|
</div>
|
|
<p class="section-desc">Alter driver variables to trigger penalties, fatigue checks, or same-device warnings.</p>
|
|
|
|
<div class="switch-container">
|
|
<span class="switch-label">Restrict Points (-201 Points)</span>
|
|
<label class="switch">
|
|
<input type="checkbox" id="chk-points">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="switch-container">
|
|
<span class="switch-label">Exceed Fatigue (12h limit)</span>
|
|
<label class="switch">
|
|
<input type="checkbox" id="chk-fatigue">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="switch-container">
|
|
<span class="switch-label">Cancellation Limit Block (3x)</span>
|
|
<label class="switch">
|
|
<input type="checkbox" id="chk-block">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Trigger Events -->
|
|
<div class="panel-section">
|
|
<div class="section-title">
|
|
<i data-lucide="radio" style="width: 18px; height: 18px;"></i>
|
|
<span>Lifecycle Controls</span>
|
|
</div>
|
|
<p class="section-desc">Simulate backend and device lifecycle events during different ride stages.</p>
|
|
|
|
<button class="btn btn-gold" id="btn-trigger-order">
|
|
<i data-lucide="bell" style="width: 16px; height: 16px;"></i>
|
|
<span>Simulate Incoming Order</span>
|
|
</button>
|
|
|
|
<button class="btn" id="btn-move-closer" disabled>
|
|
<i data-lucide="navigation" style="width: 16px; height: 16px;"></i>
|
|
<span>Drive Closer (< 100m)</span>
|
|
</button>
|
|
|
|
<button class="btn" id="btn-same-device" disabled>
|
|
<i data-lucide="smartphone" style="width: 16px; height: 16px;"></i>
|
|
<span>Simulate Same Device</span>
|
|
</button>
|
|
|
|
<button class="btn btn-rose" id="btn-simulate-cancel" disabled>
|
|
<i data-lucide="user-minus" style="width: 16px; height: 16px;"></i>
|
|
<span>Simulate Rider Cancellation</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Device Stats -->
|
|
<div class="panel-section">
|
|
<div class="section-title">
|
|
<i data-lucide="activity" style="width: 18px; height: 18px;"></i>
|
|
<span>System Optimization Status</span>
|
|
</div>
|
|
<div style="display: flex; flex-direction: column; gap: 0.6rem;">
|
|
<div>
|
|
<div style="display: flex; justify-content: space-between; font-size: 0.75rem;">
|
|
<span style="color: var(--text-muted);">GPS Stream Pull Rate</span>
|
|
<span class="info-val emerald" id="txt-gps-rate">Centralized (1 Stream)</span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div style="display: flex; justify-content: space-between; font-size: 0.75rem;">
|
|
<span style="color: var(--text-muted);">Camera Follow Interval</span>
|
|
<span class="info-val emerald" id="txt-cam-interval">Throttled (8s / 15m)</span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div style="display: flex; justify-content: space-between; font-size: 0.75rem;">
|
|
<span style="color: var(--text-muted);">GPS Jitter Noise Filter</span>
|
|
<span class="info-val emerald">Active (> 3 meters)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Main Simulator Workspace -->
|
|
<div class="simulator-workspace">
|
|
|
|
<!-- Map Visualiser -->
|
|
<div class="map-container">
|
|
<div class="map-grid"></div>
|
|
|
|
<!-- Standard mock roads -->
|
|
<div class="map-road road-h" style="top: 250px;"></div>
|
|
<div class="map-road road-v" style="left: 450px;"></div>
|
|
<div class="map-road road-h" style="top: 450px;"></div>
|
|
<div class="map-road road-v" style="left: 150px;"></div>
|
|
|
|
<!-- Heatmap Overlay Layer -->
|
|
<div class="heatmap-layer" id="heatmap-layer">
|
|
<div class="heatmap-grid high" style="grid-area: 3/4/5/6;"></div>
|
|
<div class="heatmap-grid med" style="grid-area: 2/5/3/6;"></div>
|
|
<div class="heatmap-grid med" style="grid-area: 3/6/5/7;"></div>
|
|
<div class="heatmap-grid low" style="grid-area: 5/5/7/7;"></div>
|
|
<div class="heatmap-grid high" style="grid-area: 7/2/9/4;"></div>
|
|
<div class="heatmap-grid med" style="grid-area: 6/3/7/4;"></div>
|
|
</div>
|
|
|
|
<!-- Canvas for routes -->
|
|
<canvas class="route-canvas" id="route-canvas" width="800" height="550"></canvas>
|
|
|
|
<!-- Radar pulse for searching -->
|
|
<div class="radar-pulse" id="radar-pulse"></div>
|
|
|
|
<!-- Map Markers -->
|
|
<!-- Driver Car Marker -->
|
|
<div class="marker" id="marker-driver" style="top: 250px; left: 150px; transform: translate(-50%, -50%) rotate(0deg);">
|
|
<div class="marker-label">Damascus Driver (You)</div>
|
|
<div class="marker-dot marker-car">
|
|
<i data-lucide="car" style="width: 14px; height: 14px; color: #000;"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Passenger Marker -->
|
|
<div class="marker" id="marker-pax" style="top: 250px; left: 450px; display: none;">
|
|
<div class="marker-label">Mahmoud (Pickup)</div>
|
|
<div class="marker-dot marker-pax">
|
|
<i data-lucide="user" style="width: 14px; height: 14px; color: #000;"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Destination Marker -->
|
|
<div class="marker" id="marker-dest" style="top: 450px; left: 450px; display: none;">
|
|
<div class="marker-label">Mezzeh (Destination)</div>
|
|
<div class="marker-dot marker-dest">
|
|
<i data-lucide="flag" style="width: 14px; height: 14px; color: #fff;"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Turn-by-Turn Instruction Panel -->
|
|
<div class="tbt-panel" id="tbt-panel">
|
|
<div class="tbt-icon-box" id="tbt-icon">
|
|
<i data-lucide="arrow-up-right" style="width: 20px; height: 20px;"></i>
|
|
</div>
|
|
<div class="tbt-info">
|
|
<span class="tbt-instruction" id="tbt-text">Drive straight on Al-Jalaa St</span>
|
|
<span class="tbt-dist" id="tbt-dist">500 m</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- App Interface Wrapper -->
|
|
<div>
|
|
<div class="phone-container">
|
|
<div class="phone-notch"></div>
|
|
|
|
<!-- App Status bar -->
|
|
<div class="app-statusbar">
|
|
<span id="txt-app-time">01:31</span>
|
|
<div style="display: flex; align-items: center; gap: 0.35rem;">
|
|
<i data-lucide="wifi" style="width: 12px; height: 12px;"></i>
|
|
<i data-lucide="battery" style="width: 14px; height: 14px;"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- App Screen -->
|
|
<div class="app-screen">
|
|
|
|
<!-- Active Header -->
|
|
<div class="app-header-card">
|
|
<div class="driver-profile">
|
|
<div class="driver-avatar">C</div>
|
|
<div class="driver-info-text">
|
|
<h4>Capt. Hamza</h4>
|
|
<span id="txt-points-badge"><i data-lucide="award" style="width: 12px; height: 12px; color: var(--accent-gold);"></i> 180 Points</span>
|
|
</div>
|
|
</div>
|
|
<div class="app-earnings">
|
|
<span>Today's Cash</span>
|
|
<h3 id="txt-app-earnings">0 S.P</h3>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- New Order Modal overlay inside the screen -->
|
|
<div class="order-overlay-modal" id="order-overlay">
|
|
<div class="order-overlay-header">
|
|
<div class="order-badge-container">
|
|
<i data-lucide="zap" style="width: 12px; height: 12px;"></i>
|
|
<span id="txt-ride-type">COMFORT REQUEST</span>
|
|
</div>
|
|
<div class="order-timer-circle">
|
|
<svg class="timer-svg">
|
|
<circle class="timer-track" cx="45" cy="45" r="40"></circle>
|
|
<circle class="timer-fill" id="timer-fill" cx="45" cy="45" r="40"></circle>
|
|
</svg>
|
|
<div class="timer-text" id="txt-overlay-timer">15</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="order-details-card">
|
|
<div class="detail-row">
|
|
<span class="detail-label">Passenger:</span>
|
|
<span class="detail-val">Mahmoud K. (★ 4.9)</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Pickup Distance:</span>
|
|
<span class="detail-val" id="txt-overlay-pickup">2.1 km away</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Trip Length:</span>
|
|
<span class="detail-val" id="txt-overlay-length">12.5 km</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Est. Price:</span>
|
|
<span class="detail-val" style="color: var(--accent-gold);" id="txt-overlay-price">28,000 S.P.</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="overlay-accept-btn" id="btn-accept-order">
|
|
<i data-lucide="check-circle" style="width: 20px; height: 20px;"></i>
|
|
<span>Accept Order (قبول الطلب)</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Bottom Dashboard View (Switches based on state) -->
|
|
<div class="app-bottom-panel" id="app-bottom-panel">
|
|
|
|
<!-- Offline Panel -->
|
|
<div class="offline-view" id="panel-offline">
|
|
<div class="offline-title">
|
|
<i data-lucide="cloud-off" style="width: 20px; height: 20px;"></i>
|
|
<span>You are Offline</span>
|
|
</div>
|
|
<div class="slider-btn-container" id="online-slider-container">
|
|
<div class="slider-bg-fill" id="online-slider-fill"></div>
|
|
<div class="slider-handle" id="online-slider-handle">
|
|
<i data-lucide="chevrons-right" style="width: 18px; height: 18px;"></i>
|
|
</div>
|
|
<span class="slider-text" id="online-slider-text">SWIPE GO ONLINE</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Online Waiting Panel -->
|
|
<div class="online-view" id="panel-online">
|
|
<div class="online-pulse-circle">
|
|
<i data-lucide="radio" style="width: 20px; height: 20px;"></i>
|
|
</div>
|
|
<div class="online-status-title">Looking for Rides...</div>
|
|
<div class="online-status-subtitle">Damascus demand heatmap sync active</div>
|
|
<div style="display: flex; gap: 0.5rem; width: 100%; margin-top: 0.5rem;">
|
|
<button class="btn" id="btn-toggle-heatmap" style="flex: 1; font-size: 0.8rem; margin: 0; padding: 0.5rem;">
|
|
<i data-lucide="map" style="width: 14px; height: 14px;"></i>
|
|
<span>Toggle Heatmap</span>
|
|
</button>
|
|
<button class="btn btn-rose" id="btn-go-offline" style="flex: 1; font-size: 0.8rem; margin: 0; padding: 0.5rem;">
|
|
<span>Go Offline</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active Ride Panel -->
|
|
<div class="ride-info-view" id="panel-ride-active">
|
|
<div class="ride-status-badge">
|
|
<span>RIDE LIFECYCLE</span>
|
|
<div class="badge-indicator pickup" id="txt-lifecycle-phase">
|
|
<div class="status-dot" style="background-color: var(--accent-gold); box-shadow: 0 0 8px var(--accent-gold);"></div>
|
|
<span>Heading to Pickup</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="passenger-strip">
|
|
<div class="passenger-strip-left">
|
|
<div class="pax-avatar">M</div>
|
|
<div>
|
|
<div class="pax-name">Mahmoud K.</div>
|
|
<div class="pax-rating">
|
|
<i data-lucide="star" style="width: 12px; height: 12px; fill: var(--accent-gold); color: transparent;"></i>
|
|
<span>4.9</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="pax-action-icons">
|
|
<div class="circle-icon-btn" id="btn-pax-call">
|
|
<i data-lucide="phone" style="width: 14px; height: 14px;"></i>
|
|
</div>
|
|
<div class="circle-icon-btn">
|
|
<i data-lucide="message-square" style="width: 14px; height: 14px;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="trip-metrics-grid">
|
|
<div class="metric-box">
|
|
<span class="metric-label" id="txt-metric-top-label">Pickup Dist</span>
|
|
<span class="metric-val" id="txt-metric-top-val">2.1 km</span>
|
|
</div>
|
|
<div class="metric-box">
|
|
<span class="metric-label" id="txt-metric-bottom-label">Est. Time</span>
|
|
<span class="metric-val" id="txt-metric-bottom-val">5 min</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Swipe to start/finish -->
|
|
<div class="swipe-container" id="ride-swipe-container" style="display: none;">
|
|
<div class="slider-bg-fill" id="ride-swipe-fill" style="background: rgba(59, 130, 246, 0.15);"></div>
|
|
<div class="swipe-handle" id="ride-swipe-handle">
|
|
<i data-lucide="chevrons-right" style="width: 18px; height: 18px;"></i>
|
|
</div>
|
|
<span class="swipe-text" id="ride-swipe-text">SWIPE START RIDE</span>
|
|
</div>
|
|
|
|
<!-- Arrived Action button -->
|
|
<button class="btn btn-gold" id="btn-arrive-action" style="margin: 0; font-weight: 700; height: 50px; border-radius: 25px;">
|
|
<span>MARK ARRIVED (وصلت)</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Rating View -->
|
|
<div class="rating-view" id="panel-rating">
|
|
<i data-lucide="check-check" style="width: 36px; height: 36px; color: var(--accent-emerald);"></i>
|
|
<h4 style="font-size: 1rem; font-weight: 700;">Ride Completed Successfully!</h4>
|
|
<p style="font-size: 0.8rem; color: var(--text-muted);">Please rate passenger Mahmoud K.</p>
|
|
<div class="stars-row">
|
|
<i data-lucide="star" class="star-icon" data-idx="1" style="width: 24px; height: 24px;"></i>
|
|
<i data-lucide="star" class="star-icon" data-idx="2" style="width: 24px; height: 24px;"></i>
|
|
<i data-lucide="star" class="star-icon" data-idx="3" style="width: 24px; height: 24px;"></i>
|
|
<i data-lucide="star" class="star-icon" data-idx="4" style="width: 24px; height: 24px;"></i>
|
|
<i data-lucide="star" class="star-icon" data-idx="5" style="width: 24px; height: 24px;"></i>
|
|
</div>
|
|
<button class="btn btn-gold" id="btn-submit-rating" style="margin: 0;">Submit Rating</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Simulation Logs & Info -->
|
|
<div class="stats-sidebar" style="margin-top: 1.5rem;">
|
|
<div class="section-title">
|
|
<i data-lucide="terminal" style="width: 18px; height: 18px;"></i>
|
|
<span>Real-time Execution Log</span>
|
|
</div>
|
|
<div class="console-logs" id="console-logs">
|
|
<div class="log-item info">System initialized. Driver is Offline.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Custom Alert overlay dialog -->
|
|
<div class="dialog-overlay" id="dialog-overlay">
|
|
<div class="dialog-box">
|
|
<div class="dialog-icon">⛔</div>
|
|
<div class="dialog-title" id="dialog-title">Blocked</div>
|
|
<div class="dialog-body" id="dialog-body">You have exceeded the cancellation limit.</div>
|
|
<button class="dialog-btn" id="dialog-btn">OK</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// System variables
|
|
let driverState = 'OFFLINE'; // OFFLINE, ONLINE, NEW_ORDER, ACCEPTED, ARRIVED, EN_ROUTE, COMPLETED
|
|
let driverPoints = 180;
|
|
let driverCoins = 0;
|
|
let isBlocked = false;
|
|
let isFatigued = false;
|
|
let isPointsRestricted = false;
|
|
let blockExpiryTime = null;
|
|
let totalTimeOnlineToday = 0; // seconds
|
|
let onlineTimer = null;
|
|
|
|
// Map geometry
|
|
let driverPos = { x: 150, y: 250 }; // Pixels
|
|
let paxPos = { x: 450, y: 250 };
|
|
let destPos = { x: 450, y: 450 };
|
|
let carRotation = 0;
|
|
|
|
// Simulation variables
|
|
let rideId = 1059;
|
|
let priceCalculated = 28000;
|
|
let distanceCoveredKm = 0.0;
|
|
let rideSecondsElapsed = 0;
|
|
let rideCostTimer = null;
|
|
let waitTimer = null;
|
|
let waitSecondsRemaining = 300;
|
|
let routeCanvas = document.getElementById('route-canvas');
|
|
let ctx = routeCanvas.getContext('2d');
|
|
|
|
// UI Selectors
|
|
const consoleLogs = document.getElementById('console-logs');
|
|
|
|
function addLog(text, type = 'default') {
|
|
const log = document.createElement('div');
|
|
log.className = `log-item ${type}`;
|
|
log.textContent = `[${new Date().toLocaleTimeString()}] ${text}`;
|
|
consoleLogs.appendChild(log);
|
|
consoleLogs.scrollTop = consoleLogs.scrollHeight;
|
|
}
|
|
|
|
// Initialize icons
|
|
lucide.createIcons();
|
|
|
|
// System Alert dialog helper
|
|
function showSystemDialog(title, body, isBlockDialog = true) {
|
|
document.getElementById('dialog-title').innerText = title;
|
|
document.getElementById('dialog-body').innerText = body;
|
|
document.getElementById('dialog-overlay').style.display = 'flex';
|
|
|
|
const btn = document.getElementById('dialog-btn');
|
|
btn.onclick = () => {
|
|
document.getElementById('dialog-overlay').style.display = 'none';
|
|
};
|
|
}
|
|
|
|
// Swipe Go Online controller gesture
|
|
const handle = document.getElementById('online-slider-handle');
|
|
const container = document.getElementById('online-slider-container');
|
|
const fill = document.getElementById('online-slider-fill');
|
|
let isDragging = false;
|
|
let startX = 0;
|
|
let containerWidth = container.clientWidth;
|
|
|
|
handle.addEventListener('mousedown', startDrag);
|
|
handle.addEventListener('touchstart', startDrag);
|
|
|
|
function startDrag(e) {
|
|
if (driverState !== 'OFFLINE') return;
|
|
isDragging = true;
|
|
startX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
|
|
document.addEventListener('mousemove', onDrag);
|
|
document.addEventListener('touchmove', onDrag);
|
|
document.addEventListener('mouseup', stopDrag);
|
|
document.addEventListener('touchend', stopDrag);
|
|
handle.style.cursor = 'grabbing';
|
|
}
|
|
|
|
function onDrag(e) {
|
|
if (!isDragging) return;
|
|
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
|
|
let deltaX = clientX - startX;
|
|
let limit = container.clientWidth - handle.clientWidth - 6;
|
|
if (deltaX < 0) deltaX = 0;
|
|
if (deltaX > limit) deltaX = limit;
|
|
|
|
handle.style.left = (deltaX + 3) + 'px';
|
|
fill.style.width = (deltaX + 23) + 'px';
|
|
|
|
// Check threshold (swipe confirmation)
|
|
if (deltaX >= limit * 0.95) {
|
|
// Trigger Go Online
|
|
confirmGoOnline();
|
|
stopDrag();
|
|
}
|
|
}
|
|
|
|
function stopDrag() {
|
|
if (!isDragging) return;
|
|
isDragging = false;
|
|
document.removeEventListener('mousemove', onDrag);
|
|
document.removeEventListener('touchmove', onDrag);
|
|
handle.style.cursor = 'grab';
|
|
|
|
if (driverState === 'OFFLINE') {
|
|
// Revert handle position
|
|
handle.style.transition = 'left 0.2s ease';
|
|
fill.style.transition = 'width 0.2s ease';
|
|
handle.style.left = '3px';
|
|
fill.style.width = '0px';
|
|
setTimeout(() => {
|
|
handle.style.transition = '';
|
|
fill.style.transition = '';
|
|
}, 200);
|
|
}
|
|
}
|
|
|
|
// Go Online Check Logic
|
|
function confirmGoOnline() {
|
|
// 1. Check points restriction
|
|
if (isPointsRestricted) {
|
|
addLog("Go Online Rejected: Account points below Syria threshold (-200 points).", "error");
|
|
showSystemDialog("Stopped due to low points! ⛔", "Your account points (-201) are below the Damascus region minimum threshold (-200). Please recharge your wallet to proceed.");
|
|
resetOnlineSlider();
|
|
return;
|
|
}
|
|
|
|
// 2. Check cancellation block limit
|
|
if (isBlocked) {
|
|
addLog("Go Online Rejected: Temporarily blocked due to excessive cancellations.", "error");
|
|
showSystemDialog("Account restricted! ⛔", "You have exceeded the cancellation limit (3 times). You cannot go online for the next 4 hours.");
|
|
resetOnlineSlider();
|
|
return;
|
|
}
|
|
|
|
// 3. Check fatigue (12 hours)
|
|
if (isFatigued) {
|
|
addLog("Go Online Rejected: Driving hours exceeded 12-hour safety limit.", "error");
|
|
showSystemDialog("Safety break required! 🛑", "You have been driving for 12 hours today. For your safety, you must take a continuous 6-hour break.");
|
|
resetOnlineSlider();
|
|
return;
|
|
}
|
|
|
|
// Successful online state
|
|
setOnlineState();
|
|
}
|
|
|
|
function resetOnlineSlider() {
|
|
handle.style.left = '3px';
|
|
fill.style.width = '0px';
|
|
}
|
|
|
|
function setOnlineState() {
|
|
driverState = 'ONLINE';
|
|
addLog("Driver went Online. Throttled GPS listener active.", "info");
|
|
document.getElementById('panel-offline').style.display = 'none';
|
|
document.getElementById('panel-online').style.display = 'flex';
|
|
document.getElementById('radar-pulse').style.display = 'block';
|
|
document.getElementById('radar-pulse').style.left = driverPos.x + 'px';
|
|
document.getElementById('radar-pulse').style.top = driverPos.y + 'px';
|
|
|
|
// Start online duration accumulator to monitor fatigue
|
|
onlineTimer = setInterval(() => {
|
|
totalTimeOnlineToday++;
|
|
if (totalTimeOnlineToday >= 60) {
|
|
// Accelerate simulation: 1 min online acts as 12 hours fatigue trigger
|
|
isFatigued = true;
|
|
clearInterval(onlineTimer);
|
|
addLog("Simulation: 12-hour online driving fatigue threshold reached. Forcing offline.", "error");
|
|
goOffline();
|
|
showSystemDialog("Safety limit reached! 🛑", "You have driven for 12 hours today. To prevent fatigue-related hazards, you are forced offline for a 6-hour break.");
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function goOffline() {
|
|
driverState = 'OFFLINE';
|
|
clearInterval(onlineTimer);
|
|
addLog("Driver went Offline.", "info");
|
|
document.getElementById('panel-online').style.display = 'none';
|
|
document.getElementById('panel-offline').style.display = 'flex';
|
|
document.getElementById('radar-pulse').style.display = 'none';
|
|
resetOnlineSlider();
|
|
}
|
|
|
|
document.getElementById('btn-go-offline').onclick = goOffline;
|
|
|
|
// Toggle Heatmap Layer
|
|
let heatmapActive = false;
|
|
function toggleHeatmap() {
|
|
heatmapActive = !heatmapActive;
|
|
const layer = document.getElementById('heatmap-layer');
|
|
if (heatmapActive) {
|
|
layer.style.opacity = '1';
|
|
addLog("Demand Heatmap Synced periodic interval set to 15 min (Optimized).", "info");
|
|
} else {
|
|
layer.style.opacity = '0';
|
|
addLog("Demand Heatmap Layer hidden.", "info");
|
|
}
|
|
}
|
|
document.getElementById('btn-toggle-heatmap').onclick = toggleHeatmap;
|
|
|
|
// Trigger Incoming Order (FCM/Socket mockup)
|
|
let orderOverlay = document.getElementById('order-overlay');
|
|
let overlayTimer = 15;
|
|
let overlayInterval = null;
|
|
|
|
document.getElementById('btn-trigger-order').onclick = () => {
|
|
if (driverState !== 'ONLINE') {
|
|
addLog("Cannot trigger order. Driver must be Online first.", "error");
|
|
return;
|
|
}
|
|
triggerOrder();
|
|
};
|
|
|
|
function triggerOrder() {
|
|
driverState = 'NEW_ORDER';
|
|
addLog("New Order received via Firebase Socket (ride_id: " + rideId + ")", "info");
|
|
orderOverlay.style.display = 'flex';
|
|
document.getElementById('btn-simulate-cancel').removeAttribute('disabled');
|
|
|
|
// Sound alert (Beep mockup)
|
|
playBeepAlert();
|
|
|
|
overlayTimer = 15;
|
|
const fillEl = document.getElementById('timer-fill');
|
|
fillEl.style.strokeDashoffset = '0';
|
|
document.getElementById('txt-overlay-timer').innerText = overlayTimer;
|
|
|
|
overlayInterval = setInterval(() => {
|
|
overlayTimer--;
|
|
document.getElementById('txt-overlay-timer').innerText = overlayTimer;
|
|
|
|
// Circular dash animation
|
|
let percent = overlayTimer / 15;
|
|
let offset = 251.2 * (1 - percent);
|
|
fillEl.style.strokeDashoffset = offset;
|
|
|
|
if (overlayTimer <= 0) {
|
|
clearInterval(overlayInterval);
|
|
addLog("Order Request timed out. Missed count incremented.", "error");
|
|
orderOverlay.style.display = 'none';
|
|
driverState = 'ONLINE';
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function playBeepAlert() {
|
|
// Basic HTML5 beep generator
|
|
try {
|
|
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
const oscillator = audioCtx.createOscillator();
|
|
const gainNode = audioCtx.createGain();
|
|
oscillator.connect(gainNode);
|
|
gainNode.connect(audioCtx.destination);
|
|
oscillator.type = 'sine';
|
|
oscillator.frequency.setValueAtTime(880, audioCtx.currentTime); // A5 note
|
|
gainNode.gain.setValueAtTime(0.1, audioCtx.currentTime);
|
|
oscillator.start();
|
|
setTimeout(() => oscillator.stop(), 300);
|
|
} catch (e) {}
|
|
}
|
|
|
|
// Handle same-device check warning simulation
|
|
document.getElementById('btn-same-device').onclick = () => {
|
|
addLog("GPS warning: Driver & Passenger distance < 10m. Fit bounds domain_error avoided.", "error");
|
|
showSystemDialog("Same device detected! ⚠️", "Rider and Captain locations are extremely close (under 10 meters). To prevent MapLibre fitBounds domains error crash, map will transition to approximate padded safe zoom.");
|
|
};
|
|
|
|
// Accept Order Logic
|
|
document.getElementById('btn-accept-order').onclick = () => {
|
|
acceptOrder();
|
|
};
|
|
|
|
function acceptOrder() {
|
|
clearInterval(overlayInterval);
|
|
driverState = 'ACCEPTED';
|
|
addLog("Order accepted. Transitioning to Passenger Location Map.", "info");
|
|
orderOverlay.style.display = 'none';
|
|
document.getElementById('btn-trigger-order').setAttribute('disabled', 'true');
|
|
document.getElementById('btn-move-closer').removeAttribute('disabled');
|
|
document.getElementById('btn-same-device').removeAttribute('disabled');
|
|
|
|
// Setup UI for accepted state (Heading to pickup)
|
|
document.getElementById('panel-online').style.display = 'none';
|
|
document.getElementById('panel-ride-active').style.display = 'flex';
|
|
|
|
document.getElementById('marker-pax').style.display = 'flex';
|
|
document.getElementById('marker-dest').style.display = 'flex';
|
|
document.getElementById('radar-pulse').style.display = 'none';
|
|
|
|
// Draw route on map
|
|
drawRoute(driverPos, paxPos, 'yellow');
|
|
|
|
// Update TBT instructions Panel
|
|
document.getElementById('tbt-panel').style.display = 'flex';
|
|
document.getElementById('tbt-text').innerText = "Drive east toward Mahmoud's pickup point";
|
|
document.getElementById('tbt-dist').innerText = "2.1 km";
|
|
}
|
|
|
|
// Draw lines on canvas
|
|
function drawRoute(start, end, type = 'yellow') {
|
|
ctx.clearRect(0, 0, routeCanvas.width, routeCanvas.height);
|
|
ctx.beginPath();
|
|
ctx.lineWidth = 6;
|
|
ctx.strokeStyle = type === 'yellow' ? '#eab308' : '#3b82f6';
|
|
|
|
// Connect start to end through mockup nodes to look like roads
|
|
ctx.moveTo(start.x, start.y);
|
|
if (start.x !== end.x && start.y !== end.y) {
|
|
ctx.lineTo(end.x, start.y); // Corner bend
|
|
}
|
|
ctx.lineTo(end.x, end.y);
|
|
ctx.stroke();
|
|
|
|
// If heading to pickup, draw dotted walk line from road (corner) to actual off-road position
|
|
if (type === 'yellow') {
|
|
ctx.beginPath();
|
|
ctx.lineWidth = 3;
|
|
ctx.strokeStyle = '#94a3b8'; // Grey/Blue dashes
|
|
ctx.setLineDash([6, 6]);
|
|
ctx.moveTo(end.x, start.y);
|
|
ctx.lineTo(end.x + 20, end.y - 30); // offroad displacement mockup
|
|
ctx.stroke();
|
|
ctx.setLineDash([]); // Reset
|
|
}
|
|
}
|
|
|
|
// Simulate moving closer to pickup
|
|
document.getElementById('btn-move-closer').onclick = () => {
|
|
simulateMoveCloser();
|
|
};
|
|
|
|
function simulateMoveCloser() {
|
|
addLog("GPS update: Captain driving. Centralized polling interval tick.", "info");
|
|
|
|
// Animate driver marker moving to passenger location on map
|
|
let marker = document.getElementById('marker-driver');
|
|
driverPos = { x: 440, y: 250 }; // Closer to passenger (450, 250)
|
|
|
|
marker.style.left = driverPos.x + 'px';
|
|
marker.style.top = driverPos.y + 'px';
|
|
marker.style.transform = 'translate(-50%, -50%) rotate(90deg)';
|
|
|
|
// Draw updated route
|
|
drawRoute(driverPos, paxPos, 'yellow');
|
|
|
|
// Update UI
|
|
document.getElementById('txt-metric-top-val').innerText = "80 m";
|
|
document.getElementById('txt-metric-bottom-val').innerText = "0 min";
|
|
document.getElementById('btn-move-closer').setAttribute('disabled', 'true');
|
|
document.getElementById('btn-arrive-action').removeAttribute('disabled');
|
|
addLog("Captain is close to passenger location (< 100m). 'Mark Arrived' unlocked.", "info");
|
|
}
|
|
|
|
// Mark Arrived Action
|
|
document.getElementById('btn-arrive-action').onclick = () => {
|
|
markArrived();
|
|
};
|
|
|
|
function markArrived() {
|
|
driverState = 'ARRIVED';
|
|
addLog("Driver marked Arrived. Waiting compensation timer started.", "info");
|
|
document.getElementById('btn-arrive-action').style.display = 'none';
|
|
|
|
// Update badge
|
|
document.getElementById('txt-lifecycle-phase').innerHTML = `
|
|
<div class="status-dot" style="background-color: var(--accent-emerald); box-shadow: 0 0 8px var(--accent-emerald);"></div>
|
|
<span>Waiting for Passenger</span>
|
|
`;
|
|
document.getElementById('txt-lifecycle-phase').className = "badge-indicator emerald";
|
|
|
|
// Setup Waiting Duration Timer
|
|
waitSecondsRemaining = 300;
|
|
document.getElementById('txt-metric-top-label').innerText = "Wait Time";
|
|
document.getElementById('txt-metric-top-val').innerText = "05:00";
|
|
document.getElementById('txt-metric-bottom-label').innerText = "Status";
|
|
document.getElementById('txt-metric-bottom-val').innerText = "Arrived";
|
|
|
|
waitTimer = setInterval(() => {
|
|
waitSecondsRemaining--;
|
|
let min = Math.floor(waitSecondsRemaining / 60);
|
|
let sec = waitSecondsRemaining % 60;
|
|
document.getElementById('txt-metric-top-val').innerText = `${min.toString().padLeft(2, '0')}:${sec.toString().padLeft(2, '0')}`;
|
|
|
|
if (waitSecondsRemaining <= 0) {
|
|
clearInterval(waitTimer);
|
|
addLog("Waiting limit of 5 min exceeded. Paid cancellation unlocked.", "rose");
|
|
document.getElementById('txt-metric-bottom-val').innerText = "No-Show";
|
|
document.getElementById('btn-simulate-cancel').innerText = "Cancel Paid (عدم صعود)";
|
|
}
|
|
}, 1000);
|
|
|
|
// Reveal Swipe to Start Ride
|
|
document.getElementById('ride-swipe-container').style.display = 'flex';
|
|
setupSwipeControl();
|
|
}
|
|
|
|
// Swipe Start/End Controller Logic
|
|
let rideSwipeHandle = document.getElementById('ride-swipe-handle');
|
|
let rideSwipeContainer = document.getElementById('ride-swipe-container');
|
|
let rideSwipeFill = document.getElementById('ride-swipe-fill');
|
|
let isSwipeActive = false;
|
|
let swipeStartX = 0;
|
|
|
|
function setupSwipeControl() {
|
|
rideSwipeHandle.addEventListener('mousedown', startSwipe);
|
|
rideSwipeHandle.addEventListener('touchstart', startSwipe);
|
|
}
|
|
|
|
function startSwipe(e) {
|
|
isSwipeActive = true;
|
|
swipeStartX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
|
|
document.addEventListener('mousemove', onSwipe);
|
|
document.addEventListener('touchmove', onSwipe);
|
|
document.addEventListener('mouseup', stopSwipe);
|
|
document.addEventListener('touchend', stopSwipe);
|
|
rideSwipeHandle.style.cursor = 'grabbing';
|
|
}
|
|
|
|
function onSwipe(e) {
|
|
if (!isSwipeActive) return;
|
|
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
|
|
let deltaX = clientX - swipeStartX;
|
|
let limit = rideSwipeContainer.clientWidth - rideSwipeHandle.clientWidth - 6;
|
|
if (deltaX < 0) deltaX = 0;
|
|
if (deltaX > limit) deltaX = limit;
|
|
|
|
rideSwipeHandle.style.left = (deltaX + 3) + 'px';
|
|
rideSwipeFill.style.width = (deltaX + 23) + 'px';
|
|
|
|
if (deltaX >= limit * 0.95) {
|
|
if (driverState === 'ARRIVED') {
|
|
startRide();
|
|
} else if (driverState === 'EN_ROUTE') {
|
|
endRideAttempt();
|
|
}
|
|
stopSwipe();
|
|
}
|
|
}
|
|
|
|
function stopSwipe() {
|
|
if (!isSwipeActive) return;
|
|
isSwipeActive = false;
|
|
document.removeEventListener('mousemove', onSwipe);
|
|
document.removeEventListener('touchmove', onSwipe);
|
|
rideSwipeHandle.style.cursor = 'grab';
|
|
|
|
// Reset swipe positions
|
|
rideSwipeHandle.style.transition = 'left 0.2s ease';
|
|
rideSwipeFill.style.transition = 'width 0.2s ease';
|
|
rideSwipeHandle.style.left = '3px';
|
|
rideSwipeFill.style.width = '0px';
|
|
setTimeout(() => {
|
|
rideSwipeHandle.style.transition = '';
|
|
rideSwipeFill.style.transition = '';
|
|
}, 200);
|
|
}
|
|
|
|
// Start Ride Logic
|
|
function startRide() {
|
|
clearInterval(waitTimer);
|
|
driverState = 'EN_ROUTE';
|
|
addLog("Ride Started. Solid navigation route updated. Voice recording initiated.", "info");
|
|
|
|
// Animate driver to original road
|
|
driverPos = { x: 450, y: 250 };
|
|
document.getElementById('marker-driver').style.left = driverPos.x + 'px';
|
|
document.getElementById('marker-driver').style.top = driverPos.y + 'px';
|
|
|
|
// Draw Trip Route (solid blue/black)
|
|
drawRoute(driverPos, destPos, 'blue');
|
|
|
|
// Update TBT instructions Panel
|
|
document.getElementById('tbt-text').innerText = "Drive south on Mezzeh Highway";
|
|
document.getElementById('tbt-dist').innerText = "12.5 km";
|
|
|
|
// Setup UI
|
|
document.getElementById('txt-lifecycle-phase').innerHTML = `
|
|
<div class="status-dot" style="background-color: var(--accent-blue); box-shadow: 0 0 8px var(--accent-blue);"></div>
|
|
<span>En Route to Mezzeh</span>
|
|
`;
|
|
document.getElementById('txt-lifecycle-phase').className = "badge-indicator trip";
|
|
|
|
document.getElementById('txt-metric-top-label').innerText = "Distance Covered";
|
|
document.getElementById('txt-metric-top-val').innerText = "0.0 km";
|
|
document.getElementById('txt-metric-bottom-label').innerText = "Current Cost";
|
|
document.getElementById('txt-metric-bottom-val').innerText = "28,000 S.P.";
|
|
|
|
document.getElementById('ride-swipe-text').innerText = "SWIPE END RIDE";
|
|
rideSwipeHandle.classList.add('rose');
|
|
|
|
// Start dynamic pricing and mock navigation updates
|
|
rideSecondsElapsed = 0;
|
|
distanceCoveredKm = 0.0;
|
|
|
|
rideCostTimer = setInterval(() => {
|
|
rideSecondsElapsed += 15; // Accelerate time
|
|
distanceCoveredKm += 0.25; // Accelerate distance
|
|
|
|
if (distanceCoveredKm > 12.5) distanceCoveredKm = 12.5;
|
|
|
|
// Dynamic pricing: Base (28000) + time increments + distance increments
|
|
// Using Damascus Comfort pricing: ~800 S.P/km comfort base
|
|
let updatedPrice = 28000 + (distanceCoveredKm * 800) + (rideSecondsElapsed / 60 * 150);
|
|
priceCalculated = Math.round(updatedPrice);
|
|
|
|
document.getElementById('txt-metric-top-val').innerText = `${distanceCoveredKm.toFixed(2)} km`;
|
|
document.getElementById('txt-metric-bottom-val').innerText = `${priceCalculated.toLocaleString()} S.P.`;
|
|
|
|
// Update driver position slowly on map
|
|
let fraction = distanceCoveredKm / 12.5;
|
|
let x = driverPos.x;
|
|
let y = driverPos.y + (destPos.y - driverPos.y) * fraction;
|
|
document.getElementById('marker-driver').style.top = y + 'px';
|
|
document.getElementById('marker-driver').style.transform = 'translate(-50%, -50%) rotate(180deg)';
|
|
|
|
// TBT Update
|
|
let remainingDist = (12.5 - distanceCoveredKm).toFixed(1);
|
|
document.getElementById('tbt-dist').innerText = `${remainingDist} km`;
|
|
|
|
if (distanceCoveredKm >= 12.5) {
|
|
document.getElementById('tbt-text').innerText = "Arriving at Mezzeh. End the ride.";
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
// End Ride Attempt with Displacement Check (Anti-Fraud)
|
|
function endRideAttempt() {
|
|
// Planned trip distance: 12.5 km.
|
|
// Minimum displacement threshold: (1/5) = 2.5 km.
|
|
const minThreshold = 2.5;
|
|
|
|
if (distanceCoveredKm < minThreshold) {
|
|
// Anti-fraud validation failed
|
|
addLog("Anti-Fraud Violation: Attempted early exit at " + distanceCoveredKm.toFixed(2) + " km. Displ. under 1/5.", "error");
|
|
|
|
// Trigger Speech synthesis warning (matching Dart TTS code)
|
|
speakTTS("You haven't moved sufficiently! Please complete more distance before ending.");
|
|
|
|
showSystemDialog("Warning: Insufficient Distance! 🛑", "You haven't moved sufficiently! You must complete at least 1/5 of the total trip distance (2.5 km) before ending the ride.");
|
|
return;
|
|
}
|
|
|
|
// Successful ride completion
|
|
completeRide();
|
|
}
|
|
|
|
function speakTTS(text) {
|
|
if ('speechSynthesis' in window) {
|
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
utterance.lang = 'en-US';
|
|
utterance.rate = 1.0;
|
|
window.speechSynthesis.speak(utterance);
|
|
}
|
|
}
|
|
|
|
function completeRide() {
|
|
clearInterval(rideCostTimer);
|
|
driverState = 'COMPLETED';
|
|
addLog("Ride completed successfully. Wallet transaction completed on server.", "info");
|
|
|
|
// Update profile wallet amount
|
|
driverCoins += priceCalculated;
|
|
document.getElementById('txt-app-earnings').innerText = `${driverCoins.toLocaleString()} S.P`;
|
|
|
|
document.getElementById('tbt-panel').style.display = 'none';
|
|
document.getElementById('panel-ride-active').style.display = 'none';
|
|
document.getElementById('panel-rating').style.display = 'flex';
|
|
}
|
|
|
|
// Star rating animation
|
|
const stars = document.querySelectorAll('.star-icon');
|
|
let ratedValue = 5;
|
|
|
|
stars.forEach(star => {
|
|
star.onclick = () => {
|
|
let idx = parseInt(star.getAttribute('data-idx'));
|
|
ratedValue = idx;
|
|
stars.forEach((s, i) => {
|
|
if (i < idx) {
|
|
s.classList.add('filled');
|
|
} else {
|
|
s.classList.remove('filled');
|
|
}
|
|
});
|
|
};
|
|
});
|
|
|
|
document.getElementById('btn-submit-rating').onclick = () => {
|
|
addLog("Rating submitted: Mahmoud K. rated " + ratedValue + " stars.", "info");
|
|
// Reset back to Online
|
|
document.getElementById('panel-rating').style.display = 'none';
|
|
document.getElementById('panel-online').style.display = 'flex';
|
|
document.getElementById('radar-pulse').style.display = 'block';
|
|
driverState = 'ONLINE';
|
|
|
|
// Reset map markers
|
|
document.getElementById('marker-pax').style.display = 'none';
|
|
document.getElementById('marker-dest').style.display = 'none';
|
|
|
|
// Reset driver position
|
|
driverPos = { x: 150, y: 250 };
|
|
document.getElementById('marker-driver').style.left = driverPos.x + 'px';
|
|
document.getElementById('marker-driver').style.top = driverPos.y + 'px';
|
|
document.getElementById('marker-driver').style.transform = 'translate(-50%, -50%) rotate(0deg)';
|
|
|
|
// Reset route
|
|
ctx.clearRect(0, 0, routeCanvas.width, routeCanvas.height);
|
|
document.getElementById('btn-trigger-order').removeAttribute('disabled');
|
|
document.getElementById('btn-arrive-action').style.display = 'block';
|
|
document.getElementById('btn-arrive-action').setAttribute('disabled', 'true');
|
|
document.getElementById('ride-swipe-container').style.display = 'none';
|
|
rideSwipeHandle.classList.remove('rose');
|
|
document.getElementById('ride-swipe-text').innerText = "SWIPE START RIDE";
|
|
};
|
|
|
|
// Override switches
|
|
document.getElementById('chk-points').onchange = (e) => {
|
|
isPointsRestricted = e.target.checked;
|
|
if (isPointsRestricted) {
|
|
driverPoints = -201;
|
|
document.getElementById('txt-points-badge').innerHTML = `<i data-lucide="award" style="width: 12px; height: 12px; color: var(--accent-rose);"></i> -201 Points`;
|
|
addLog("Override: Points restricted set to -201.", "rose");
|
|
} else {
|
|
driverPoints = 180;
|
|
document.getElementById('txt-points-badge').innerHTML = `<i data-lucide="award" style="width: 12px; height: 12px; color: var(--accent-gold);"></i> 180 Points`;
|
|
addLog("Override: Points restored to 180.", "info");
|
|
}
|
|
lucide.createIcons();
|
|
};
|
|
|
|
document.getElementById('chk-fatigue').onchange = (e) => {
|
|
isFatigued = e.target.checked;
|
|
if (isFatigued) {
|
|
addLog("Override: Simulated driving fatigue limit reached.", "rose");
|
|
} else {
|
|
totalTimeOnlineToday = 0;
|
|
addLog("Override: Fatigue limits reset.", "info");
|
|
}
|
|
};
|
|
|
|
document.getElementById('chk-block').onchange = (e) => {
|
|
isBlocked = e.target.checked;
|
|
if (isBlocked) {
|
|
addLog("Override: Driver is blocked due to exceeding cancellations today.", "rose");
|
|
} else {
|
|
addLog("Override: Cancellation block removed.", "info");
|
|
}
|
|
};
|
|
|
|
// Handle rider cancellation simulation
|
|
document.getElementById('btn-simulate-cancel').onclick = () => {
|
|
simulateRiderCancel();
|
|
};
|
|
|
|
function simulateRiderCancel() {
|
|
addLog("Rider Mahmoud cancelled the ride. Socket channel closed.", "rose");
|
|
|
|
// Clean timers
|
|
clearInterval(waitTimer);
|
|
clearInterval(rideCostTimer);
|
|
clearInterval(overlayInterval);
|
|
|
|
// Show cancel dialog
|
|
showSystemDialog("Order Cancelled 🚫", "Rider Mahmoud K. has cancelled the request. Reason: Changed plans. All navigation timers and routing have been cleaned.");
|
|
|
|
// Revert UI to Online
|
|
document.getElementById('panel-ride-active').style.display = 'none';
|
|
document.getElementById('panel-online').style.display = 'flex';
|
|
document.getElementById('radar-pulse').style.display = 'block';
|
|
document.getElementById('order-overlay').style.display = 'none';
|
|
driverState = 'ONLINE';
|
|
|
|
// Reset map markers
|
|
document.getElementById('marker-pax').style.display = 'none';
|
|
document.getElementById('marker-dest').style.display = 'none';
|
|
|
|
// Reset driver position
|
|
driverPos = { x: 150, y: 250 };
|
|
document.getElementById('marker-driver').style.left = driverPos.x + 'px';
|
|
document.getElementById('marker-driver').style.top = driverPos.y + 'px';
|
|
document.getElementById('marker-driver').style.transform = 'translate(-50%, -50%) rotate(0deg)';
|
|
|
|
// Reset route
|
|
ctx.clearRect(0, 0, routeCanvas.width, routeCanvas.height);
|
|
document.getElementById('btn-trigger-order').removeAttribute('disabled');
|
|
document.getElementById('btn-arrive-action').style.display = 'block';
|
|
document.getElementById('btn-arrive-action').setAttribute('disabled', 'true');
|
|
document.getElementById('ride-swipe-container').style.display = 'none';
|
|
rideSwipeHandle.classList.remove('rose');
|
|
document.getElementById('ride-swipe-text').innerText = "SWIPE START RIDE";
|
|
document.getElementById('btn-simulate-cancel').setAttribute('disabled', 'true');
|
|
}
|
|
|
|
// Centralized custom string padding
|
|
String.prototype.padLeft = function (length, character) {
|
|
return this.length >= length ? this : (character + this).slice(-length);
|
|
};
|
|
|
|
// Simulated Central Clock
|
|
setInterval(() => {
|
|
let now = new Date();
|
|
let hour = now.getHours().toString().padStart(2, '0');
|
|
let min = now.getMinutes().toString().padStart(2, '0');
|
|
document.getElementById('txt-app-time').innerText = `${hour}:${min}`;
|
|
}, 1000);
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|