feat(ui): replace all emojis with professional vector SVG icons suite
This commit is contained in:
+67
-7
@@ -1343,8 +1343,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dir-cam-placeholder-icon {
|
.dir-cam-placeholder-icon {
|
||||||
font-size: 2.2rem;
|
width: 48px;
|
||||||
opacity: 0.8;
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(6, 182, 212, 0.1);
|
||||||
|
border: 1px solid rgba(6, 182, 212, 0.25);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #38bdf8;
|
||||||
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dir-cam-badge {
|
.dir-cam-badge {
|
||||||
@@ -1357,6 +1365,9 @@ body {
|
|||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dir-cam-badge.verified {
|
.dir-cam-badge.verified {
|
||||||
@@ -2857,18 +2868,67 @@ body {
|
|||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Professional Vector SVG Icon System */
|
||||||
|
.flock-svg {
|
||||||
|
width: 1.15em;
|
||||||
|
height: 1.15em;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
fill: none;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flock-svg-sm {
|
||||||
|
width: 0.9em;
|
||||||
|
height: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flock-svg-md {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flock-svg-lg {
|
||||||
|
width: 1.8rem;
|
||||||
|
height: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flock-svg-xl {
|
||||||
|
width: 2.2rem;
|
||||||
|
height: 2.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flock-svg-hero {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Empty Photo Prompt State */
|
/* Empty Photo Prompt State */
|
||||||
.proof-empty-box {
|
.proof-empty-box {
|
||||||
padding: 24px 20px;
|
padding: 26px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border: 1px dashed rgba(148, 163, 184, 0.25);
|
border: 1px dashed rgba(6, 182, 212, 0.35);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
background: rgba(15, 23, 42, 0.5);
|
background: rgba(15, 23, 42, 0.65);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.proof-empty-icon {
|
.proof-empty-icon {
|
||||||
font-size: 2.2rem;
|
width: 58px;
|
||||||
margin-bottom: 8px;
|
height: 58px;
|
||||||
|
margin: 0 auto 12px auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(6, 182, 212, 0.12);
|
||||||
|
border: 1px solid rgba(6, 182, 212, 0.35);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #38bdf8;
|
||||||
|
box-shadow: 0 0 20px rgba(6, 182, 212, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.proof-empty-title {
|
.proof-empty-title {
|
||||||
|
|||||||
+141
-62
@@ -28,7 +28,7 @@
|
|||||||
<meta name="apple-mobile-web-app-title" content="FlockRadar">
|
<meta name="apple-mobile-web-app-title" content="FlockRadar">
|
||||||
|
|
||||||
<!-- Main Stylesheet -->
|
<!-- Main Stylesheet -->
|
||||||
<link rel="stylesheet" href="css/styles.css?v=12">
|
<link rel="stylesheet" href="css/styles.css?v=13">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -55,12 +55,29 @@
|
|||||||
<!-- View Mode Switcher Pills -->
|
<!-- View Mode Switcher Pills -->
|
||||||
<div class="nav-mode-switcher" role="tablist">
|
<div class="nav-mode-switcher" role="tablist">
|
||||||
<button id="nav-map-btn" class="nav-mode-btn active" role="tab" aria-selected="true">
|
<button id="nav-map-btn" class="nav-mode-btn active" role="tab" aria-selected="true">
|
||||||
<span class="mode-icon">🗺️</span>
|
<span class="mode-icon">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="9"/>
|
||||||
|
<circle cx="12" cy="12" r="5"/>
|
||||||
|
<path d="M12 3v9l5.5 3.5"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
<span class="nav-text-desktop">Live Radar Map</span>
|
<span class="nav-text-desktop">Live Radar Map</span>
|
||||||
<span class="nav-text-mobile">Radar</span>
|
<span class="nav-text-mobile">Radar</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="nav-dir-btn" class="nav-mode-btn" role="tab" aria-selected="false">
|
<button id="nav-dir-btn" class="nav-mode-btn" role="tab" aria-selected="false">
|
||||||
<span class="mode-icon">📋</span>
|
<span class="mode-icon">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="4" y="2" width="16" height="20" rx="2" ry="2"/>
|
||||||
|
<line x1="9" y1="22" x2="9" y2="18"/>
|
||||||
|
<line x1="15" y1="22" x2="15" y2="18"/>
|
||||||
|
<line x1="9" y1="18" x2="15" y2="18"/>
|
||||||
|
<line x1="8" y1="6" x2="8.01" y2="6"/>
|
||||||
|
<line x1="16" y1="6" x2="16.01" y2="6"/>
|
||||||
|
<line x1="8" y1="10" x2="8.01" y2="10"/>
|
||||||
|
<line x1="16" y1="10" x2="16.01" y2="10"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
<span class="nav-text-desktop">National Directory (1,439)</span>
|
<span class="nav-text-desktop">National Directory (1,439)</span>
|
||||||
<span class="nav-text-mobile">Directory</span>
|
<span class="nav-text-mobile">Directory</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -76,7 +93,12 @@
|
|||||||
<!-- Driver Profile / Login Button -->
|
<!-- Driver Profile / Login Button -->
|
||||||
<button id="driver-profile-btn" class="user-profile-pill" title="Driver Profile & Settings">
|
<button id="driver-profile-btn" class="user-profile-pill" title="Driver Profile & Settings">
|
||||||
<span id="user-profile-pill">
|
<span id="user-profile-pill">
|
||||||
<span class="avatar-dot">👤</span>
|
<span class="avatar-dot">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||||
|
<circle cx="12" cy="7" r="4"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
<span>Driver Login</span>
|
<span>Driver Login</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -123,11 +145,13 @@
|
|||||||
<!-- Mobile Header Quick Triggers -->
|
<!-- Mobile Header Quick Triggers -->
|
||||||
<div class="hud-capsule-actions">
|
<div class="hud-capsule-actions">
|
||||||
<button type="button" id="btn-toggle-tools" class="btn-capsule-trigger" title="Open Driving Controls Menu">
|
<button type="button" id="btn-toggle-tools" class="btn-capsule-trigger" title="Open Driving Controls Menu">
|
||||||
<span>⚙️</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
||||||
<span class="capsule-btn-text">Tools</span>
|
<span class="capsule-btn-text">Tools</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" id="btn-toggle-hud-details" class="btn-capsule-trigger btn-expand-details" title="Toggle Search & Locations">
|
<button type="button" id="btn-toggle-hud-details" class="btn-capsule-trigger btn-expand-details" title="Toggle Search & Locations">
|
||||||
<span id="hud-details-arrow">🔍</span>
|
<span id="hud-details-arrow">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -144,7 +168,9 @@
|
|||||||
|
|
||||||
<!-- High-Priority Pulsing Warning Banner -->
|
<!-- High-Priority Pulsing Warning Banner -->
|
||||||
<div id="hud-banner-alert" class="hud-banner-alert">
|
<div id="hud-banner-alert" class="hud-banner-alert">
|
||||||
<span class="banner-pulse-icon">⚠️</span>
|
<span class="banner-pulse-icon">
|
||||||
|
<svg class="flock-svg flock-svg-lg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
||||||
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
|
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
|
||||||
<p>Vehicle Profile & Plate Logging Active</p>
|
<p>Vehicle Profile & Plate Logging Active</p>
|
||||||
@@ -172,14 +198,20 @@
|
|||||||
<div class="hud-sensitivity-row">
|
<div class="hud-sensitivity-row">
|
||||||
<span class="sensitivity-label">RADAR RANGE:</span>
|
<span class="sensitivity-label">RADAR RANGE:</span>
|
||||||
<div class="sensitivity-toggle-group">
|
<div class="sensitivity-toggle-group">
|
||||||
<button id="btn-sens-city" class="sens-btn active" title="Alert within 1,000 ft (Optimal for city driving)">🏙️ City (1k ft)</button>
|
<button id="btn-sens-city" class="sens-btn active" title="Alert within 1,000 ft (Optimal for city driving)">
|
||||||
<button id="btn-sens-hwy" class="sens-btn" title="Alert within 2,500 ft (Optimal for freeway/SR-14 speeds)">🛣️ Highway (2.5k ft)</button>
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="2" width="16" height="20" rx="2" ry="2"/><line x1="9" y1="22" x2="9" y2="18"/><line x1="15" y1="22" x2="15" y2="18"/><line x1="9" y1="18" x2="15" y2="18"/><line x1="8" y1="6" x2="8.01" y2="6"/><line x1="16" y1="6" x2="16.01" y2="6"/><line x1="8" y1="10" x2="8.01" y2="10"/><line x1="16" y1="10" x2="16.01" y2="10"/><line x1="8" y1="14" x2="8.01" y2="14"/><line x1="16" y1="14" x2="16.01" y2="14"/></svg>
|
||||||
|
<span>City (1k ft)</span>
|
||||||
|
</button>
|
||||||
|
<button id="btn-sens-hwy" class="sens-btn" title="Alert within 2,500 ft (Optimal for freeway/SR-14 speeds)">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="21" x2="8" y2="3"/><line x1="20" y1="21" x2="16" y2="3"/><line x1="12" y1="5" x2="12" y2="8"/><line x1="12" y1="12" x2="12" y2="15"/><line x1="12" y1="19" x2="12" y2="21"/></svg>
|
||||||
|
<span>Highway (2.5k ft)</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Live Auto-Spotted Status Badge -->
|
<!-- Live Auto-Spotted Status Badge -->
|
||||||
<div id="hud-spotted-badge" class="hud-spotted-badge" title="Location auto-detected via network & GPS">
|
<div id="hud-spotted-badge" class="hud-spotted-badge" title="Location auto-detected via network & GPS">
|
||||||
<span>🛰️</span>
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 10a7.33 7.33 0 0 0 10 10l1-3.5L8.5 10z"/><path d="M21 3l-8.5 8.5"/><path d="M15 6l2 2"/></svg>
|
||||||
<span id="hud-spotted-text">Spotting Antelope Valley Location...</span>
|
<span id="hud-spotted-text">Spotting Antelope Valley Location...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -187,26 +219,32 @@
|
|||||||
<div class="hud-location-selector">
|
<div class="hud-location-selector">
|
||||||
<div class="location-quick-chips">
|
<div class="location-quick-chips">
|
||||||
<button id="btn-locate-me" class="loc-chip active" title="Re-detect your location via IP & GPS">
|
<button id="btn-locate-me" class="loc-chip active" title="Re-detect your location via IP & GPS">
|
||||||
<span>🎯</span> Auto-Detect (GPS)
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
|
||||||
|
<span>Auto-Detect (GPS)</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="btn-loc-palmdale" class="loc-chip" title="Jump to Palmdale Blvd & 10th St W">
|
<button id="btn-loc-palmdale" class="loc-chip" title="Jump to Palmdale Blvd & 10th St W">
|
||||||
<span>📍</span> Palmdale
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||||||
|
<span>Palmdale</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="btn-loc-lancaster" class="loc-chip" title="Jump to Lancaster ALPR Grid">
|
<button id="btn-loc-lancaster" class="loc-chip" title="Jump to Lancaster ALPR Grid">
|
||||||
<span>📍</span> Lancaster
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||||||
|
<span>Lancaster</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="btn-loc-palmdale-east" class="loc-chip" title="Jump to East Palmdale">
|
<button id="btn-loc-palmdale-east" class="loc-chip" title="Jump to East Palmdale">
|
||||||
<span>📍</span> East AV
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||||||
|
<span>East AV</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="btn-loc-palmdale-south" class="loc-chip" title="Jump to South Palmdale">
|
<button id="btn-loc-palmdale-south" class="loc-chip" title="Jump to South Palmdale">
|
||||||
<span>📍</span> South AV
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||||||
|
<span>South AV</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="btn-loc-rancho" class="loc-chip" title="Jump to West AV">
|
<button id="btn-loc-rancho" class="loc-chip" title="Jump to West AV">
|
||||||
<span>📍</span> West AV
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||||||
|
<span>West AV</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="map-search-form" class="map-search-box">
|
<form id="map-search-form" class="map-search-box">
|
||||||
<input type="text" id="map-search-input" placeholder="🔍 Search Antelope Valley street or address..." autocomplete="off">
|
<input type="text" id="map-search-input" placeholder="Search Antelope Valley street or address..." autocomplete="off">
|
||||||
<button type="submit" id="btn-search-go">Jump</button>
|
<button type="submit" id="btn-search-go">Jump</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -216,62 +254,67 @@
|
|||||||
<!-- Floating Navigation & Driving Controls (Drawer on Mobile, Top-Right on Desktop) -->
|
<!-- Floating Navigation & Driving Controls (Drawer on Mobile, Top-Right on Desktop) -->
|
||||||
<div class="map-top-controls" id="map-top-controls">
|
<div class="map-top-controls" id="map-top-controls">
|
||||||
<div class="drawer-header" id="drawer-header">
|
<div class="drawer-header" id="drawer-header">
|
||||||
<span class="drawer-title">⚙️ Driving Controls & Settings</span>
|
<span class="drawer-title" style="display: flex; align-items: center; gap: 8px;">
|
||||||
<button type="button" id="btn-close-tools" class="btn-close-drawer" aria-label="Close controls">✕</button>
|
<svg class="flock-svg flock-svg-md" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
||||||
|
Driving Controls & Settings
|
||||||
|
</span>
|
||||||
|
<button type="button" id="btn-close-tools" class="btn-close-drawer" aria-label="Close controls">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="btn-follow-car" class="btn-hud-action active" title="Automatically keep map centered on your vehicle">
|
<button id="btn-follow-car" class="btn-hud-action active" title="Automatically keep map centered on your vehicle">
|
||||||
<span>🎯</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
|
||||||
<span>Follow Car: ON</span>
|
<span>Follow Car: ON</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-live-gps" class="btn-hud-action" title="Start continuous real-time GPS tracking on your device">
|
<button id="btn-live-gps" class="btn-hud-action" title="Start continuous real-time GPS tracking on your device">
|
||||||
<span>📡</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="2"/><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"/></svg>
|
||||||
<span>Live GPS Drive</span>
|
<span>Live GPS Drive</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-map-layer" class="btn-hud-action" title="Toggle between Dark Canvas and Satellite View">
|
<button id="btn-map-layer" class="btn-hud-action" title="Toggle between Dark Canvas and Satellite View">
|
||||||
<span>🛰️</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg>
|
||||||
<span>Satellite / Dark</span>
|
<span>Satellite / Dark</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-fullscreen-mount" class="btn-hud-action" title="Toggle Fullscreen Dashboard Mount Mode">
|
<button id="btn-fullscreen-mount" class="btn-hud-action" title="Toggle Fullscreen Dashboard Mount Mode">
|
||||||
<span>⛶</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>
|
||||||
<span>Dashboard Fullscreen</span>
|
<span>Dashboard Fullscreen</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-test-camera" class="btn-hud-action" title="Drop a test camera 400ft ahead of your current location to test proximity alerts">
|
<button id="btn-test-camera" class="btn-hud-action" title="Drop a test camera 400ft ahead of your current location to test proximity alerts">
|
||||||
<span>⚡</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||||
<span>Place Camera 400ft Ahead</span>
|
<span>Place Camera 400ft Ahead</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-admin-manage-pins" class="btn-hud-action" title="Admin: Manage and remove cameras you placed (the 1,439 official municipal portals are protected)">
|
<button id="btn-admin-manage-pins" class="btn-hud-action" title="Admin: Manage and remove cameras you placed (the 1,439 official municipal portals are protected)">
|
||||||
<span>🗑️</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
|
||||||
<span>Admin: Placed Pins</span>
|
<span>Admin: Placed Pins</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-test-alert" class="btn-hud-action" title="Immediately test voice alert and radar siren">
|
<button id="btn-test-alert" class="btn-hud-action" title="Immediately test voice alert and radar siren">
|
||||||
<span>🔊</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
|
||||||
<span>Test Alert Voice</span>
|
<span>Test Alert Voice</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-sim-drive" class="btn-hud-action" title="Simulate driving through Palmdale camera corridors">
|
<button id="btn-sim-drive" class="btn-hud-action" title="Simulate driving through Palmdale camera corridors">
|
||||||
<span>▶</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||||
<span>Simulate Palmdale Drive</span>
|
<span>Simulate Palmdale Drive</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-reset-sim" class="btn-hud-action" title="Reset drive position">
|
<button id="btn-reset-sim" class="btn-hud-action" title="Reset drive position">
|
||||||
<span>↺</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
||||||
<span>Reset Car</span>
|
<span>Reset Car</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-mute-sound" class="btn-hud-action" title="Toggle audio alert chime and voice">
|
<button id="btn-mute-sound" class="btn-hud-action" title="Toggle audio alert chime and voice">
|
||||||
<span>🔔</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
|
||||||
<span>Sound: ON</span>
|
<span>Sound: ON</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-mobile-connect" class="btn-hud-action" title="Show mobile network link to open on phone in car">
|
<button id="btn-mobile-connect" class="btn-hud-action" title="Show mobile network link to open on phone in car">
|
||||||
<span>📱</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
|
||||||
<span>Phone Link</span>
|
<span>Phone Link</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -303,14 +346,16 @@
|
|||||||
|
|
||||||
<!-- Floating Recenter Pill (Appears when map is dragged away from car) -->
|
<!-- Floating Recenter Pill (Appears when map is dragged away from car) -->
|
||||||
<button id="btn-recenter-car" class="btn-recenter-pill hidden" title="Re-center map on your vehicle">
|
<button id="btn-recenter-car" class="btn-recenter-pill hidden" title="Re-center map on your vehicle">
|
||||||
<span>🎯</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="22" y1="12" x2="18" y2="12"/><line x1="6" y1="12" x2="2" y2="12"/><line x1="12" y1="6" x2="12" y2="2"/><line x1="12" y1="22" x2="12" y2="18"/></svg>
|
||||||
<span>Recenter on Vehicle</span>
|
<span>Recenter on Vehicle</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Giant 1-Tap Floating Action Button: Snap & Log Flock Camera (Bottom-Right) -->
|
<!-- Giant 1-Tap Floating Action Button: Snap & Log Flock Camera (Bottom-Right) -->
|
||||||
<div class="driver-fab-container">
|
<div class="driver-fab-container">
|
||||||
<button id="btn-quick-log-cam" class="btn-driver-fab" title="Snap camera photo on pole & log at GPS location">
|
<button id="btn-quick-log-cam" class="btn-driver-fab" title="Snap camera photo on pole & log at GPS location">
|
||||||
<div class="fab-icon-glow">📸</div>
|
<div class="fab-icon-glow">
|
||||||
|
<svg class="flock-svg flock-svg-lg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/></svg>
|
||||||
|
</div>
|
||||||
<div class="fab-text-group">
|
<div class="fab-text-group">
|
||||||
<span class="fab-main-title">+ SNAP & LOG CAMERA</span>
|
<span class="fab-main-title">+ SNAP & LOG CAMERA</span>
|
||||||
<span class="fab-sub-title">Opens Camera for Photo Proof</span>
|
<span class="fab-sub-title">Opens Camera for Photo Proof</span>
|
||||||
@@ -405,7 +450,9 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
>
|
>
|
||||||
<button id="clear-search-btn" class="clear-search-btn" title="Clear search">✕</button>
|
<button id="clear-search-btn" class="clear-search-btn" title="Clear search">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
<span class="search-shortcut">/</span>
|
<span class="search-shortcut">/</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -491,7 +538,9 @@
|
|||||||
<section id="directory-cameras-section" class="directory-cameras-section" style="display: none; margin-bottom: 36px;">
|
<section id="directory-cameras-section" class="directory-cameras-section" style="display: none; margin-bottom: 36px;">
|
||||||
<div class="dir-cam-header" style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; padding: 14px 18px; background: rgba(6, 182, 212, 0.08); border: 1px solid rgba(6, 182, 212, 0.25); border-radius: 14px;">
|
<div class="dir-cam-header" style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; padding: 14px 18px; background: rgba(6, 182, 212, 0.08); border: 1px solid rgba(6, 182, 212, 0.25); border-radius: 14px;">
|
||||||
<div style="display: flex; align-items: center; gap: 10px;">
|
<div style="display: flex; align-items: center; gap: 10px;">
|
||||||
<span style="font-size: 1.4rem;">📸</span>
|
<span class="dir-cam-header-icon-box" style="display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 8px; background: rgba(6, 182, 212, 0.15); color: #38bdf8;">
|
||||||
|
<svg class="flock-svg flock-svg-md" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/></svg>
|
||||||
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<h3 style="margin: 0; font-size: 1.1rem; color: #38bdf8; font-family: var(--font-heading);">
|
<h3 style="margin: 0; font-size: 1.1rem; color: #38bdf8; font-family: var(--font-heading);">
|
||||||
Identified Camera Nodes in <span id="dir-cam-query-title">City</span>
|
Identified Camera Nodes in <span id="dir-cam-query-title">City</span>
|
||||||
@@ -634,7 +683,9 @@
|
|||||||
<span id="modal-agency-slug" style="font-family: var(--font-mono); color: var(--accent-cyan);">slug</span>
|
<span id="modal-agency-slug" style="font-family: var(--font-mono); color: var(--accent-cyan);">slug</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="modal-close-btn" class="close-modal-btn" aria-label="Close modal">✕</button>
|
<button id="modal-close-btn" class="close-modal-btn" aria-label="Close modal">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@@ -714,7 +765,9 @@
|
|||||||
<h2 id="login-modal-title" class="modal-agency-name">Driver Profile & Scout Login</h2>
|
<h2 id="login-modal-title" class="modal-agency-name">Driver Profile & Scout Login</h2>
|
||||||
<div class="modal-agency-meta">Join the community awareness grid to contribute camera sightings</div>
|
<div class="modal-agency-meta">Join the community awareness grid to contribute camera sightings</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="login-close-btn" class="close-modal-btn" aria-label="Close modal">✕</button>
|
<button id="login-close-btn" class="close-modal-btn" aria-label="Close modal">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="login-form">
|
<form id="login-form">
|
||||||
@@ -749,7 +802,7 @@
|
|||||||
<div class="admin-profile-section" style="margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--border-subtle);">
|
<div class="admin-profile-section" style="margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--border-subtle);">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||||
<h4 style="font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 6px;">
|
<h4 style="font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 6px;">
|
||||||
<span>🛡️</span>
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||||
<span>Admin: Manage Placed Cameras</span>
|
<span>Admin: Manage Placed Cameras</span>
|
||||||
</h4>
|
</h4>
|
||||||
<span id="admin-placed-count" style="font-size: 0.75rem; color: #38bdf8; background: rgba(56, 189, 248, 0.15); padding: 2px 8px; border-radius: 10px;">0 Placed</span>
|
<span id="admin-placed-count" style="font-size: 0.75rem; color: #38bdf8; background: rgba(56, 189, 248, 0.15); padding: 2px 8px; border-radius: 10px;">0 Placed</span>
|
||||||
@@ -760,8 +813,9 @@
|
|||||||
<div id="admin-placed-cams-list" style="max-height: 160px; overflow-y: auto; margin-bottom: 12px; display: flex; flex-direction: column; gap: 6px;">
|
<div id="admin-placed-cams-list" style="max-height: 160px; overflow-y: auto; margin-bottom: 12px; display: flex; flex-direction: column; gap: 6px;">
|
||||||
<!-- Rendered via JS -->
|
<!-- Rendered via JS -->
|
||||||
</div>
|
</div>
|
||||||
<button type="button" id="btn-admin-clear-all-placed" class="btn-secondary" style="width: 100%; padding: 10px; color: #f87171; border-color: rgba(239, 68, 68, 0.4); justify-content: center;">
|
<button type="button" id="btn-admin-clear-all-placed" class="btn-secondary" style="width: 100%; padding: 10px; color: #f87171; border-color: rgba(239, 68, 68, 0.4); justify-content: center; display: flex; align-items: center; gap: 8px;">
|
||||||
🗑️ Clear All Placed Pins (Keep Real Locations)
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
|
||||||
|
<span>Clear All Placed Pins (Keep Real Locations)</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -777,7 +831,9 @@
|
|||||||
<h2 id="report-modal-title" class="modal-agency-name">Report Flock Camera Sighting</h2>
|
<h2 id="report-modal-title" class="modal-agency-name">Report Flock Camera Sighting</h2>
|
||||||
<div class="modal-agency-meta">Help alert the driver network to new ALPR surveillance installations</div>
|
<div class="modal-agency-meta">Help alert the driver network to new ALPR surveillance installations</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="report-close-btn" class="close-modal-btn" aria-label="Close modal">✕</button>
|
<button id="report-close-btn" class="close-modal-btn" aria-label="Close modal">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="report-form">
|
<form id="report-form">
|
||||||
@@ -806,12 +862,14 @@
|
|||||||
<label class="control-label" style="display:block; margin-bottom: 6px;">Physical Hardware Photo Proof (Recommended)</label>
|
<label class="control-label" style="display:block; margin-bottom: 6px;">Physical Hardware Photo Proof (Recommended)</label>
|
||||||
<div class="photo-proof-upload-zone" id="report-photo-zone">
|
<div class="photo-proof-upload-zone" id="report-photo-zone">
|
||||||
<button type="button" id="btn-report-add-photo" class="btn-secondary" style="width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px;">
|
<button type="button" id="btn-report-add-photo" class="btn-secondary" style="width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px;">
|
||||||
<span>📸</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/></svg>
|
||||||
<span id="report-photo-btn-text">Snap Camera on Pole (+100 pts)</span>
|
<span id="report-photo-btn-text">Snap Camera on Pole (+100 pts)</span>
|
||||||
</button>
|
</button>
|
||||||
<div id="report-photo-preview-box" class="photo-preview-box hidden" style="margin-top: 10px; position: relative;">
|
<div id="report-photo-preview-box" class="photo-preview-box hidden" style="margin-top: 10px; position: relative;">
|
||||||
<img id="report-photo-preview-img" src="" alt="Camera preview" style="width: 100%; max-height: 180px; object-fit: cover; border-radius: var(--radius-md); border: 1px solid var(--accent-cyan);">
|
<img id="report-photo-preview-img" src="" alt="Camera preview" style="width: 100%; max-height: 180px; object-fit: cover; border-radius: var(--radius-md); border: 1px solid var(--accent-cyan);">
|
||||||
<button type="button" id="btn-report-remove-photo" style="position: absolute; top: 6px; right: 6px; background: rgba(0,0,0,0.7); color: #fff; border: none; border-radius: 50%; width: 26px; height: 26px; cursor: pointer;">✕</button>
|
<button type="button" id="btn-report-remove-photo" style="position: absolute; top: 6px; right: 6px; background: rgba(0,0,0,0.7); color: #fff; border: none; border-radius: 50%; width: 26px; height: 26px; cursor: pointer; display: flex; align-items: center; justify-content: center;">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -847,7 +905,9 @@
|
|||||||
<h2 id="cam-detail-title" class="modal-agency-name" style="margin-top: 6px;">Flock Camera Location</h2>
|
<h2 id="cam-detail-title" class="modal-agency-name" style="margin-top: 6px;">Flock Camera Location</h2>
|
||||||
<div class="modal-agency-meta" id="cam-detail-meta">Managing Agency & Hardware Registry</div>
|
<div class="modal-agency-meta" id="cam-detail-meta">Managing Agency & Hardware Registry</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="cam-detail-close-btn" class="close-modal-btn" aria-label="Close modal">✕</button>
|
<button id="cam-detail-close-btn" class="close-modal-btn" aria-label="Close modal">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<!-- Hardware Photo Proof Area -->
|
<!-- Hardware Photo Proof Area -->
|
||||||
@@ -880,7 +940,9 @@
|
|||||||
|
|
||||||
<!-- Community Consensus & Anti-Sabotage Rule Banner -->
|
<!-- Community Consensus & Anti-Sabotage Rule Banner -->
|
||||||
<div class="proof-consensus-banner">
|
<div class="proof-consensus-banner">
|
||||||
<div class="consensus-icon">🛡️</div>
|
<div class="consensus-icon">
|
||||||
|
<svg class="flock-svg flock-svg-md" style="color: #38bdf8;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||||
|
</div>
|
||||||
<div class="consensus-text">
|
<div class="consensus-text">
|
||||||
<strong>Democratic Checks & Balances Active</strong>
|
<strong>Democratic Checks & Balances Active</strong>
|
||||||
<p>Flock cameras are permanent municipal installations. To prevent law enforcement or bad actors from deleting active surveillance zones, locations are protected by community consensus.</p>
|
<p>Flock cameras are permanent municipal installations. To prevent law enforcement or bad actors from deleting active surveillance zones, locations are protected by community consensus.</p>
|
||||||
@@ -890,22 +952,30 @@
|
|||||||
<!-- Voting & Action Bar -->
|
<!-- Voting & Action Bar -->
|
||||||
<div class="proof-actions-row">
|
<div class="proof-actions-row">
|
||||||
<button id="btn-vote-confirm-cam" class="btn-proof-action btn-confirm-vote" title="Confirm camera is still bolted to pole">
|
<button id="btn-vote-confirm-cam" class="btn-proof-action btn-confirm-vote" title="Confirm camera is still bolted to pole">
|
||||||
<span class="btn-icon">👍</span>
|
<span class="btn-icon">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h0a3.13 3.13 0 0 1 3 3.88Z"/></svg>
|
||||||
|
</span>
|
||||||
<span id="btn-vote-confirm-text">Confirm Active (1)</span>
|
<span id="btn-vote-confirm-text">Confirm Active (1)</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-proof-add-photo" class="btn-proof-action btn-add-photo" title="Snap or upload a photo of the camera on the pole">
|
<button id="btn-proof-add-photo" class="btn-proof-action btn-add-photo" title="Snap or upload a photo of the camera on the pole">
|
||||||
<span class="btn-icon">📸</span>
|
<span class="btn-icon">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/></svg>
|
||||||
|
</span>
|
||||||
<span id="btn-proof-photo-text">Add Photo Proof</span>
|
<span id="btn-proof-photo-text">Add Photo Proof</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="btn-proof-contest" class="btn-proof-action btn-contest-vote" title="Report camera removed or inaccurate">
|
<button id="btn-proof-contest" class="btn-proof-action btn-contest-vote" title="Report camera removed or inaccurate">
|
||||||
<span class="btn-icon">⚠️</span>
|
<span class="btn-icon">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
||||||
|
</span>
|
||||||
<span>Contest</span>
|
<span>Contest</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a id="btn-proof-navigate" href="#" target="_blank" rel="noopener noreferrer" class="btn-proof-action btn-nav-maps" title="Open navigation in Apple/Google Maps">
|
<a id="btn-proof-navigate" href="#" target="_blank" rel="noopener noreferrer" class="btn-proof-action btn-nav-maps" title="Open navigation in Apple/Google Maps">
|
||||||
<span class="btn-icon">🧭</span>
|
<span class="btn-icon">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="3 11 22 2 13 21 11 13 3 11"/></svg>
|
||||||
|
</span>
|
||||||
<span>Maps ↗</span>
|
<span>Maps ↗</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -914,7 +984,7 @@
|
|||||||
<div class="modal-section proof-directory-records-section" id="proof-directory-records-section" style="margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border-subtle);">
|
<div class="modal-section proof-directory-records-section" id="proof-directory-records-section" style="margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border-subtle);">
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
|
||||||
<h3 class="modal-section-title" style="margin: 0; font-size: 0.95rem; color: #38bdf8; display: flex; align-items: center; gap: 6px;">
|
<h3 class="modal-section-title" style="margin: 0; font-size: 0.95rem; color: #38bdf8; display: flex; align-items: center; gap: 6px;">
|
||||||
<span>📋</span>
|
<svg class="flock-svg flock-svg-md" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/></svg>
|
||||||
<span>Agency Surveillance & Policy Records</span>
|
<span>Agency Surveillance & Policy Records</span>
|
||||||
</h3>
|
</h3>
|
||||||
<span id="proof-dir-source-badge" style="font-size: 0.72rem; padding: 2px 8px; border-radius: 10px; background: rgba(56, 189, 248, 0.15); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.3);">
|
<span id="proof-dir-source-badge" style="font-size: 0.72rem; padding: 2px 8px; border-radius: 10px; background: rgba(56, 189, 248, 0.15); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.3);">
|
||||||
@@ -946,7 +1016,8 @@
|
|||||||
<div class="proof-sharing-section" style="margin-bottom: 14px; background: rgba(15, 23, 42, 0.6); border: 1px solid rgba(148, 163, 184, 0.15); border-radius: 12px; padding: 12px 14px;">
|
<div class="proof-sharing-section" style="margin-bottom: 14px; background: rgba(15, 23, 42, 0.6); border: 1px solid rgba(148, 163, 184, 0.15); border-radius: 12px; padding: 12px 14px;">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
||||||
<strong style="font-size: 0.8rem; color: #e2e8f0; display: flex; align-items: center; gap: 6px;">
|
<strong style="font-size: 0.8rem; color: #e2e8f0; display: flex; align-items: center; gap: 6px;">
|
||||||
<span>🔗</span> Inter-Agency Surveillance Sharing:
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
||||||
|
<span>Inter-Agency Surveillance Sharing:</span>
|
||||||
</strong>
|
</strong>
|
||||||
<span id="proof-dir-sharing-count" style="font-size: 0.75rem; color: #38bdf8; font-weight: 700;">Regional Law Enforcement</span>
|
<span id="proof-dir-sharing-count" style="font-size: 0.75rem; color: #38bdf8; font-weight: 700;">Regional Law Enforcement</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -960,8 +1031,9 @@
|
|||||||
|
|
||||||
<!-- Top Search Justifications -->
|
<!-- Top Search Justifications -->
|
||||||
<div id="proof-dir-reasons-box" style="margin-bottom: 14px; background: rgba(15, 23, 42, 0.6); border: 1px solid rgba(148, 163, 184, 0.15); border-radius: 12px; padding: 12px 14px;">
|
<div id="proof-dir-reasons-box" style="margin-bottom: 14px; background: rgba(15, 23, 42, 0.6); border: 1px solid rgba(148, 163, 184, 0.15); border-radius: 12px; padding: 12px 14px;">
|
||||||
<strong style="font-size: 0.8rem; color: #e2e8f0; display: block; margin-bottom: 6px;">
|
<strong style="font-size: 0.8rem; color: #e2e8f0; display: flex; align-items: center; gap: 6px; margin-bottom: 6px;">
|
||||||
🔍 Primary Search Audit Reasons:
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||||
|
<span>Primary Search Audit Reasons:</span>
|
||||||
</strong>
|
</strong>
|
||||||
<div id="proof-dir-reasons-list" style="display: flex; flex-direction: column; gap: 4px; font-size: 0.75rem; color: #cbd5e1;">
|
<div id="proof-dir-reasons-list" style="display: flex; flex-direction: column; gap: 4px; font-size: 0.75rem; color: #cbd5e1;">
|
||||||
<!-- Rendered dynamically -->
|
<!-- Rendered dynamically -->
|
||||||
@@ -971,7 +1043,7 @@
|
|||||||
<!-- Official City Portal Link (Opens in New Tab - Never leaves the radar map) -->
|
<!-- Official City Portal Link (Opens in New Tab - Never leaves the radar map) -->
|
||||||
<div id="proof-portal-link-box" style="margin-top: 12px;">
|
<div id="proof-portal-link-box" style="margin-top: 12px;">
|
||||||
<a id="btn-proof-official-portal" href="#" target="_blank" rel="noopener noreferrer" class="btn-secondary" style="width: 100%; padding: 12px; display: flex; align-items: center; justify-content: center; gap: 8px; text-decoration: none; border-color: #06b6d4; color: #38bdf8; font-weight: 600;">
|
<a id="btn-proof-official-portal" href="#" target="_blank" rel="noopener noreferrer" class="btn-secondary" style="width: 100%; padding: 12px; display: flex; align-items: center; justify-content: center; gap: 8px; text-decoration: none; border-color: #06b6d4; color: #38bdf8; font-weight: 600;">
|
||||||
<span>🌐</span>
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
||||||
<span id="btn-proof-portal-text">Open Official Transparency Portal ↗</span>
|
<span id="btn-proof-portal-text">Open Official Transparency Portal ↗</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -980,7 +1052,7 @@
|
|||||||
<!-- Admin Removal Control (Only Active for Custom Placed Pins) -->
|
<!-- Admin Removal Control (Only Active for Custom Placed Pins) -->
|
||||||
<div id="cam-admin-control-box" class="admin-control-box" style="margin-top: 16px; padding-top: 14px; border-top: 1px dashed rgba(239, 68, 68, 0.3); text-align: center; display: none;">
|
<div id="cam-admin-control-box" class="admin-control-box" style="margin-top: 16px; padding-top: 14px; border-top: 1px dashed rgba(239, 68, 68, 0.3); text-align: center; display: none;">
|
||||||
<button type="button" id="btn-admin-delete-cam" class="btn-admin-delete" style="width: 100%; padding: 11px; background: rgba(239, 68, 68, 0.15); border: 1px solid #ef4444; color: #f87171; border-radius: var(--radius-md); font-weight: 600; font-size: 0.82rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.2s ease;">
|
<button type="button" id="btn-admin-delete-cam" class="btn-admin-delete" style="width: 100%; padding: 11px; background: rgba(239, 68, 68, 0.15); border: 1px solid #ef4444; color: #f87171; border-radius: var(--radius-md); font-weight: 600; font-size: 0.82rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.2s ease;">
|
||||||
<span>🗑️</span>
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
|
||||||
<span>Admin: Delete This Placed Camera</span>
|
<span>Admin: Delete This Placed Camera</span>
|
||||||
</button>
|
</button>
|
||||||
<span style="display: block; font-size: 0.7rem; color: #94a3b8; margin-top: 5px;">
|
<span style="display: block; font-size: 0.7rem; color: #94a3b8; margin-top: 5px;">
|
||||||
@@ -999,7 +1071,9 @@
|
|||||||
<h2 id="contest-modal-title" class="modal-agency-name">Contest Camera Location</h2>
|
<h2 id="contest-modal-title" class="modal-agency-name">Contest Camera Location</h2>
|
||||||
<div class="modal-agency-meta">Community Checks & Balances to Prevent Misinformation</div>
|
<div class="modal-agency-meta">Community Checks & Balances to Prevent Misinformation</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="contest-close-btn" class="close-modal-btn" aria-label="Close modal">✕</button>
|
<button id="contest-close-btn" class="close-modal-btn" aria-label="Close modal">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="contest-form">
|
<form id="contest-form">
|
||||||
@@ -1021,17 +1095,21 @@
|
|||||||
<div class="form-group" style="margin-bottom: 20px;">
|
<div class="form-group" style="margin-bottom: 20px;">
|
||||||
<label class="control-label" style="display:block; margin-bottom: 6px;">Photo Proof of Empty Pole (Optional)</label>
|
<label class="control-label" style="display:block; margin-bottom: 6px;">Photo Proof of Empty Pole (Optional)</label>
|
||||||
<button type="button" id="btn-contest-add-photo" class="btn-secondary" style="width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px;">
|
<button type="button" id="btn-contest-add-photo" class="btn-secondary" style="width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px;">
|
||||||
<span>📷</span>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/></svg>
|
||||||
<span id="contest-photo-btn-text">Snap Empty Pole</span>
|
<span id="contest-photo-btn-text">Snap Empty Pole</span>
|
||||||
</button>
|
</button>
|
||||||
<div id="contest-photo-preview-box" class="photo-preview-box hidden" style="margin-top: 10px; position: relative;">
|
<div id="contest-photo-preview-box" class="photo-preview-box hidden" style="margin-top: 10px; position: relative;">
|
||||||
<img id="contest-photo-preview-img" src="" alt="Contest photo" style="width: 100%; max-height: 160px; object-fit: cover; border-radius: var(--radius-md); border: 1px solid var(--accent-amber);">
|
<img id="contest-photo-preview-img" src="" alt="Contest photo" style="width: 100%; max-height: 160px; object-fit: cover; border-radius: var(--radius-md); border: 1px solid var(--accent-amber);">
|
||||||
<button type="button" id="btn-contest-remove-photo" style="position: absolute; top: 6px; right: 6px; background: rgba(0,0,0,0.7); color: #fff; border: none; border-radius: 50%; width: 26px; height: 26px; cursor: pointer;">✕</button>
|
<button type="button" id="btn-contest-remove-photo" style="position: absolute; top: 6px; right: 6px; background: rgba(0,0,0,0.7); color: #fff; border: none; border-radius: 50%; width: 26px; height: 26px; cursor: pointer; display: flex; align-items: center; justify-content: center;">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="proof-consensus-banner" style="margin-bottom: 20px;">
|
<div class="proof-consensus-banner" style="margin-bottom: 20px;">
|
||||||
<div class="consensus-icon">⚖️</div>
|
<div class="consensus-icon">
|
||||||
|
<svg class="flock-svg flock-svg-md" style="color: #f59e0b;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="M7 21h10"/><path d="M12 3v18"/><path d="M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2"/></svg>
|
||||||
|
</div>
|
||||||
<div class="consensus-text">
|
<div class="consensus-text">
|
||||||
<strong>Consensus Safeguard:</strong>
|
<strong>Consensus Safeguard:</strong>
|
||||||
<p>A single report does not delete a verified camera. Once 3 trusted scouts flag a location or photo proof of an empty pole is verified, the camera is archived.</p>
|
<p>A single report does not delete a verified camera. Once 3 trusted scouts flag a location or photo proof of an empty pole is verified, the camera is archived.</p>
|
||||||
@@ -1055,10 +1133,11 @@
|
|||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||||
|
|
||||||
<!-- Data & Application Logic with Cache Busting -->
|
<!-- Data & Application Logic with Cache Busting -->
|
||||||
<script src="data/portals.js?v=12"></script>
|
<script src="data/portals.js?v=13"></script>
|
||||||
<script src="js/auth.js?v=12"></script>
|
<script src="js/icons.js?v=13"></script>
|
||||||
<script src="js/radar.js?v=12"></script>
|
<script src="js/auth.js?v=13"></script>
|
||||||
<script src="js/app.js?v=12"></script>
|
<script src="js/radar.js?v=13"></script>
|
||||||
|
<script src="js/app.js?v=13"></script>
|
||||||
|
|
||||||
<!-- Service Worker Instant Cache Invalidation -->
|
<!-- Service Worker Instant Cache Invalidation -->
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -428,22 +428,30 @@
|
|||||||
const crossStreets = cam.crossStreets || (cam.name ? cam.name.split('(')[0].replace(/^.*?-\s*/, '').trim() : 'Intersection Camera');
|
const crossStreets = cam.crossStreets || (cam.name ? cam.name.split('(')[0].replace(/^.*?-\s*/, '').trim() : 'Intersection Camera');
|
||||||
|
|
||||||
const badgeHtml = meta.isContested
|
const badgeHtml = meta.isContested
|
||||||
? `<span class="dir-cam-badge unverified">🟠 CONTESTED</span>`
|
? `<span class="dir-cam-badge unverified">${window.FlockIcons ? window.FlockIcons.alertTriangle('flock-svg-sm') : ''} CONTESTED</span>`
|
||||||
: (hasPhoto ? `<span class="dir-cam-badge verified">🟢 HARDWARE PHOTO VERIFIED</span>` : `<span class="dir-cam-badge unverified">⚠️ SIGHTING RECORDED</span>`);
|
: (hasPhoto
|
||||||
|
? `<span class="dir-cam-badge verified">${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''} PHOTO VERIFIED</span>`
|
||||||
|
: `<span class="dir-cam-badge unverified">${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''} SIGHTING RECORDED</span>`);
|
||||||
|
|
||||||
const imageContainerHtml = hasPhoto
|
const imageContainerHtml = hasPhoto
|
||||||
? `<div class="dir-cam-image-container">
|
? `<div class="dir-cam-image-container">
|
||||||
<img src="${photoUrl}" class="dir-cam-img" alt="Camera at ${escapeHtml(crossStreets)}" onclick="window.open('${photoUrl}', '_blank')" title="Click to view full photo proof in high resolution">
|
<img src="${photoUrl}" class="dir-cam-img" alt="Camera at ${escapeHtml(crossStreets)}" onclick="window.open('${photoUrl}', '_blank')" title="Click to view full photo proof in high resolution">
|
||||||
${badgeHtml}
|
${badgeHtml}
|
||||||
<div class="dir-cam-img-hover-hint">🔍 Tap to inspect photo proof</div>
|
<div class="dir-cam-img-hover-hint">
|
||||||
|
${window.FlockIcons ? window.FlockIcons.search('flock-svg-sm') : ''}
|
||||||
|
<span>Tap to inspect photo proof</span>
|
||||||
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
: `<div class="dir-cam-image-container pending">
|
: `<div class="dir-cam-image-container pending">
|
||||||
<div class="dir-cam-placeholder">
|
<div class="dir-cam-placeholder">
|
||||||
<span class="dir-cam-placeholder-icon">📸</span>
|
<div class="dir-cam-placeholder-icon">
|
||||||
|
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-lg') : ''}
|
||||||
|
</div>
|
||||||
<strong style="font-size: 0.84rem; color: #cbd5e1;">Hardware Photo Pending</strong>
|
<strong style="font-size: 0.84rem; color: #cbd5e1;">Hardware Photo Pending</strong>
|
||||||
<span style="font-size: 0.72rem; color: #94a3b8;">Mounted on utility / signal pole</span>
|
<span style="font-size: 0.72rem; color: #94a3b8;">Mounted on utility / signal pole</span>
|
||||||
<button type="button" class="btn-dir-cam-upload-trigger" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
<button type="button" class="btn-dir-cam-upload-trigger" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
||||||
<span>+ Upload Photo Proof</span>
|
${window.FlockIcons ? window.FlockIcons.cameraPlus('flock-svg-sm') : ''}
|
||||||
|
<span>Upload Photo Proof</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
${badgeHtml}
|
${badgeHtml}
|
||||||
@@ -456,7 +464,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<!-- Cross Streets Prominent Box -->
|
<!-- Cross Streets Prominent Box -->
|
||||||
<div class="dir-cam-cross-box">
|
<div class="dir-cam-cross-box">
|
||||||
<span class="dir-cam-cross-icon">📍</span>
|
<span class="dir-cam-cross-icon">
|
||||||
|
${window.FlockIcons ? window.FlockIcons.mapPin('flock-svg-md') : ''}
|
||||||
|
</span>
|
||||||
<div class="dir-cam-cross-content">
|
<div class="dir-cam-cross-content">
|
||||||
<span class="dir-cam-cross-label">CROSS STREETS</span>
|
<span class="dir-cam-cross-label">CROSS STREETS</span>
|
||||||
<strong class="dir-cam-cross-value">${escapeHtml(crossStreets)}</strong>
|
<strong class="dir-cam-cross-value">${escapeHtml(crossStreets)}</strong>
|
||||||
@@ -465,8 +475,8 @@
|
|||||||
|
|
||||||
<h4 class="dir-cam-intersection">${escapeHtml(cam.name)}</h4>
|
<h4 class="dir-cam-intersection">${escapeHtml(cam.name)}</h4>
|
||||||
<div class="dir-cam-agency-meta">
|
<div class="dir-cam-agency-meta">
|
||||||
<span>🏛️ ${escapeHtml(cam.agency || 'Local Law Enforcement')}</span>
|
<span>${window.FlockIcons ? window.FlockIcons.building('flock-svg-sm') : ''} ${escapeHtml(cam.agency || 'Local Law Enforcement')}</span>
|
||||||
${cam.city ? `<span class="dir-cam-city-tag">🗺️ ${escapeHtml(cam.city)}, ${escapeHtml(cam.state || 'CA')}</span>` : ''}
|
${cam.city ? `<span class="dir-cam-city-tag">${window.FlockIcons ? window.FlockIcons.map('flock-svg-sm') : ''} ${escapeHtml(cam.city)}, ${escapeHtml(cam.state || 'CA')}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dir-cam-specs-row">
|
<div class="dir-cam-specs-row">
|
||||||
@@ -484,18 +494,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dir-cam-spec-item">
|
<div class="dir-cam-spec-item">
|
||||||
<span class="dir-cam-spec-label">CONSENSUS</span>
|
<span class="dir-cam-spec-label">CONSENSUS</span>
|
||||||
<span class="dir-cam-spec-val" style="color: #38bdf8;">👍 ${meta.confirmations || 1} Confirmed</span>
|
<span class="dir-cam-spec-val" style="color: #38bdf8; display: inline-flex; align-items: center; gap: 4px;">
|
||||||
|
${window.FlockIcons ? window.FlockIcons.thumbUp('flock-svg-sm') : ''} ${meta.confirmations || 1} Confirmed
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dir-cam-actions">
|
<div class="dir-cam-actions">
|
||||||
<button type="button" class="btn-dir-cam-jump" onclick="window.jumpToRadarCamera('${escapeHtml(cam.id)}')">
|
<button type="button" class="btn-dir-cam-jump" onclick="window.jumpToRadarCamera('${escapeHtml(cam.id)}')">
|
||||||
<span>🗺️</span>
|
${window.FlockIcons ? window.FlockIcons.radar('flock-svg-sm') : ''}
|
||||||
<span>View on Live Radar</span>
|
<span>View on Live Radar</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn-dir-cam-details" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
<button type="button" class="btn-dir-cam-details" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
||||||
<span>📸</span>
|
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}
|
||||||
<span>Camera Info</span>
|
<span>Camera Info</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -910,13 +922,22 @@
|
|||||||
const toast = document.createElement('div');
|
const toast = document.createElement('div');
|
||||||
toast.className = 'toast';
|
toast.className = 'toast';
|
||||||
|
|
||||||
let icon = 'ℹ️';
|
let iconSvg = window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-sm') : '';
|
||||||
if (type === 'success') icon = '✓';
|
if (type === 'success') {
|
||||||
if (type === 'error') icon = '⚠️';
|
iconSvg = window.FlockIcons ? FlockIcons.checkCircle('flock-svg-sm') : '';
|
||||||
if (type === 'warning') icon = '⚡';
|
} else if (type === 'error') {
|
||||||
|
iconSvg = window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-sm') : '';
|
||||||
|
} else if (type === 'warning') {
|
||||||
|
iconSvg = window.FlockIcons ? FlockIcons.zap('flock-svg-sm') : '';
|
||||||
|
} else {
|
||||||
|
iconSvg = window.FlockIcons ? FlockIcons.radar('flock-svg-sm') : '';
|
||||||
|
}
|
||||||
|
|
||||||
toast.innerHTML = `<span>${icon}</span><span>${escapeHtml(message)}</span>`;
|
// Clean any leading emoji characters from message
|
||||||
el.toastContainer.appendChild(toast);
|
const cleanMsg = String(message).replace(/^[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2300}-\u{23FF}\s]+/u, '').trim();
|
||||||
|
|
||||||
|
toast.innerHTML = `<span class="toast-icon">${iconSvg}</span><span>${escapeHtml(cleanMsg || message)}</span>`;
|
||||||
|
if (el.toastContainer) el.toastContainer.appendChild(toast);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast.style.opacity = '0';
|
toast.style.opacity = '0';
|
||||||
|
|||||||
+366
@@ -0,0 +1,366 @@
|
|||||||
|
// FlockRadar Professional SVG Vector Icon Suite
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function createSvg(innerPath, className, viewBox, strokeWidth) {
|
||||||
|
const cls = className ? `flock-svg ${className}` : 'flock-svg';
|
||||||
|
const vb = viewBox || '0 0 24 24';
|
||||||
|
const sw = strokeWidth || 2;
|
||||||
|
return `<svg class="${cls}" viewBox="${vb}" fill="none" stroke="currentColor" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${innerPath}</svg>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FlockIcons = {
|
||||||
|
camera: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/>` +
|
||||||
|
`<circle cx="12" cy="13" r="3"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
cameraPlus: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/>` +
|
||||||
|
`<circle cx="12" cy="13" r="3"/>` +
|
||||||
|
`<line x1="12" y1="10" x2="12" y2="16"/>` +
|
||||||
|
`<line x1="9" y1="13" x2="15" y2="13"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
mapPin: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>` +
|
||||||
|
`<circle cx="12" cy="10" r="3"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
radar: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<circle cx="12" cy="12" r="9"/>` +
|
||||||
|
`<circle cx="12" cy="12" r="5"/>` +
|
||||||
|
`<circle cx="12" cy="12" r="1" fill="currentColor"/>` +
|
||||||
|
`<path d="M12 3v9l5.5 3.5"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
building: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<rect x="4" y="2" width="16" height="20" rx="2" ry="2"/>` +
|
||||||
|
`<line x1="9" y1="22" x2="9" y2="18"/>` +
|
||||||
|
`<line x1="15" y1="22" x2="15" y2="18"/>` +
|
||||||
|
`<line x1="9" y1="18" x2="15" y2="18"/>` +
|
||||||
|
`<line x1="8" y1="6" x2="8.01" y2="6"/>` +
|
||||||
|
`<line x1="16" y1="6" x2="16.01" y2="6"/>` +
|
||||||
|
`<line x1="8" y1="10" x2="8.01" y2="10"/>` +
|
||||||
|
`<line x1="16" y1="10" x2="16.01" y2="10"/>` +
|
||||||
|
`<line x1="8" y1="14" x2="8.01" y2="14"/>` +
|
||||||
|
`<line x1="16" y1="14" x2="16.01" y2="14"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
shield: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
shieldCheck: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>` +
|
||||||
|
`<polyline points="9 12 11 14 15 10"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
thumbUp: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M7 10v12"/>` +
|
||||||
|
`<path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h0a3.13 3.13 0 0 1 3 3.88Z"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
alertTriangle: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/>` +
|
||||||
|
`<line x1="12" y1="9" x2="12" y2="13"/>` +
|
||||||
|
`<line x1="12" y1="17" x2="12.01" y2="17"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
navigation: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polygon points="3 11 22 2 13 21 11 13 3 11"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
map: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/>` +
|
||||||
|
`<line x1="8" y1="2" x2="8" y2="18"/>` +
|
||||||
|
`<line x1="16" y1="6" x2="16" y2="22"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
trophy: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/>` +
|
||||||
|
`<path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/>` +
|
||||||
|
`<path d="M4 22h16"/>` +
|
||||||
|
`<path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/>` +
|
||||||
|
`<path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/>` +
|
||||||
|
`<path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
chart: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<line x1="18" y1="20" x2="18" y2="10"/>` +
|
||||||
|
`<line x1="12" y1="20" x2="12" y2="4"/>` +
|
||||||
|
`<line x1="6" y1="20" x2="6" y2="14"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
volume: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>` +
|
||||||
|
`<path d="M19.07 4.93a10 10 0 0 1 0 14.14"/>` +
|
||||||
|
`<path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
volumeX: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>` +
|
||||||
|
`<line x1="23" y1="9" x2="17" y2="15"/>` +
|
||||||
|
`<line x1="17" y1="9" x2="23" y2="15"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
crosshair: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<circle cx="12" cy="12" r="10"/>` +
|
||||||
|
`<line x1="22" y1="12" x2="18" y2="12"/>` +
|
||||||
|
`<line x1="6" y1="12" x2="2" y2="12"/>` +
|
||||||
|
`<line x1="12" y1="6" x2="12" y2="2"/>` +
|
||||||
|
`<line x1="12" y1="22" x2="12" y2="18"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
smartphone: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>` +
|
||||||
|
`<line x1="12" y1="18" x2="12.01" y2="18"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
search: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<circle cx="11" cy="11" r="8"/>` +
|
||||||
|
`<line x1="21" y1="21" x2="16.65" y2="16.65"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
fileText: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>` +
|
||||||
|
`<polyline points="14 2 14 8 20 8"/>` +
|
||||||
|
`<line x1="16" y1="13" x2="8" y2="13"/>` +
|
||||||
|
`<line x1="16" y1="17" x2="8" y2="17"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
checkCircle: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>` +
|
||||||
|
`<polyline points="22 4 12 14.01 9 11.01"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
check: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polyline points="20 6 9 17 4 12"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
externalLink: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>` +
|
||||||
|
`<polyline points="15 3 21 3 21 9"/>` +
|
||||||
|
`<line x1="10" y1="14" x2="21" y2="3"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
car: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9C2.1 11 2 11.3 2 11.7V16c0 .6.4 1 1 1h2"/>` +
|
||||||
|
`<circle cx="7" cy="17" r="2"/>` +
|
||||||
|
`<path d="M9 17h6"/>` +
|
||||||
|
`<circle cx="17" cy="17" r="2"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<circle cx="12" cy="12" r="3"/>` +
|
||||||
|
`<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
trash: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polyline points="3 6 5 6 21 6"/>` +
|
||||||
|
`<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>` +
|
||||||
|
`<line x1="10" y1="11" x2="10" y2="17"/>` +
|
||||||
|
`<line x1="14" y1="11" x2="14" y2="17"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
target: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<circle cx="12" cy="12" r="10"/>` +
|
||||||
|
`<circle cx="12" cy="12" r="6"/>` +
|
||||||
|
`<circle cx="12" cy="12" r="2"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
satellite: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M4 10a7.33 7.33 0 0 0 10 10l1-3.5L8.5 10z"/>` +
|
||||||
|
`<path d="M21 3l-8.5 8.5"/>` +
|
||||||
|
`<path d="M15 6l2 2"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
radio: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<circle cx="12" cy="12" r="2"/>` +
|
||||||
|
`<path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
layers: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polygon points="12 2 2 7 12 12 22 7 12 2"/>` +
|
||||||
|
`<polyline points="2 17 12 22 22 17"/>` +
|
||||||
|
`<polyline points="2 12 12 17 22 12"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
maximize: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
zap: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
link: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>` +
|
||||||
|
`<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
globe: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<circle cx="12" cy="12" r="10"/>` +
|
||||||
|
`<line x1="2" y1="12" x2="22" y2="12"/>` +
|
||||||
|
`<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
scale: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/>` +
|
||||||
|
`<path d="m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/>` +
|
||||||
|
`<path d="M7 21h10"/>` +
|
||||||
|
`<path d="M12 3v18"/>` +
|
||||||
|
`<path d="M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
clipboard: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>` +
|
||||||
|
`<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
chevronDown: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polyline points="6 9 12 15 18 9"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
x: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<line x1="18" y1="6" x2="6" y2="18"/>` +
|
||||||
|
`<line x1="6" y1="6" x2="18" y2="18"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
pause: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<rect x="6" y="4" width="4" height="16"/>` +
|
||||||
|
`<rect x="14" y="4" width="4" height="16"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
play: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<polygon points="5 3 19 12 5 21 5 3"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
stopCircle: function(className, strokeWidth) {
|
||||||
|
return createSvg(
|
||||||
|
`<circle cx="12" cy="12" r="10"/>` +
|
||||||
|
`<rect x="9" y="9" width="6" height="6"/>`,
|
||||||
|
className, '0 0 24 24', strokeWidth
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.FlockIcons = FlockIcons;
|
||||||
|
})();
|
||||||
+105
-75
@@ -395,7 +395,7 @@
|
|||||||
<circle cx="12" cy="13" r="4"></circle>
|
<circle cx="12" cy="13" r="4"></circle>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
${hasPhoto ? '<div class="pin-photo-dot" title="Photo Evidence Verified">📸</div>' : ''}
|
${hasPhoto ? `<div class="pin-photo-dot" title="Photo Evidence Verified">${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -410,8 +410,8 @@
|
|||||||
|
|
||||||
// Popup details
|
// Popup details
|
||||||
const badgeText = meta.isContested
|
const badgeText = meta.isContested
|
||||||
? '🟠 CONTESTED SIGHTING'
|
? `${window.FlockIcons ? window.FlockIcons.alertTriangle('flock-svg-sm') : ''} CONTESTED SIGHTING`
|
||||||
: (hasPhoto ? '🟢 PHOTO VERIFIED HARDWARE' : (cam.isCommunity ? 'COMMUNITY SIGHTING' : 'FLOCK RECORDED ZONE'));
|
: (hasPhoto ? `${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''} PHOTO VERIFIED HARDWARE` : (cam.isCommunity ? 'COMMUNITY SIGHTING' : 'FLOCK RECORDED ZONE'));
|
||||||
|
|
||||||
const popupHtml = `
|
const popupHtml = `
|
||||||
<div class="radar-popup-card">
|
<div class="radar-popup-card">
|
||||||
@@ -419,17 +419,18 @@
|
|||||||
<h4 class="popup-title">${escapeHtml(cam.name)}</h4>
|
<h4 class="popup-title">${escapeHtml(cam.name)}</h4>
|
||||||
<div class="popup-meta">
|
<div class="popup-meta">
|
||||||
<span><strong>Agency:</strong> ${escapeHtml(cam.agency)}</span><br>
|
<span><strong>Agency:</strong> ${escapeHtml(cam.agency)}</span><br>
|
||||||
<span><strong>Consensus:</strong> 👍 ${meta.confirmations || 1} Confirmations</span><br>
|
<span style="display: inline-flex; align-items: center; gap: 4px;"><strong>Consensus:</strong> ${window.FlockIcons ? window.FlockIcons.thumbUp('flock-svg-sm') : ''} ${meta.confirmations || 1} Confirmations</span><br>
|
||||||
<span><strong>Photo Proof:</strong> ${hasPhoto ? '🟢 Verified on Pole' : '⚠️ No Photo Yet (Needs Driver Sighting)'}</span>
|
<span style="display: inline-flex; align-items: center; gap: 4px;"><strong>Photo Proof:</strong> ${hasPhoto ? `${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''} Verified on Pole` : `${window.FlockIcons ? window.FlockIcons.alertTriangle('flock-svg-sm') : ''} No Photo Yet`}</span>
|
||||||
${cam.notes ? `<br><span><em>${escapeHtml(cam.notes)}</em></span>` : ''}
|
${cam.notes ? `<br><span><em>${escapeHtml(cam.notes)}</em></span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; flex-direction: column; gap: 6px; margin-top: 10px;">
|
<div style="display: flex; flex-direction: column; gap: 6px; margin-top: 10px;">
|
||||||
<button class="popup-inspect-btn" style="background: linear-gradient(135deg, #06b6d4, #0284c7); color: #fff; font-weight: 700; width: 100%; border: none; border-radius: 6px; padding: 10px; cursor: pointer; box-shadow: 0 2px 8px rgba(6, 182, 212, 0.4);" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
<button class="popup-inspect-btn" style="background: linear-gradient(135deg, #06b6d4, #0284c7); color: #fff; font-weight: 700; width: 100%; border: none; border-radius: 6px; padding: 10px; cursor: pointer; box-shadow: 0 2px 8px rgba(6, 182, 212, 0.4); display: flex; align-items: center; justify-content: center; gap: 8px;" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
||||||
📸 View Camera Info & Agency Records ↗
|
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}
|
||||||
|
<span>View Camera Info & Agency Records ↗</span>
|
||||||
</button>
|
</button>
|
||||||
${(!cam.id.startsWith('portal-') && (cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-') || cam.notes === '1-Tap Mobile Driver Pin' || cam.agency === 'Crowdsourced Report' || cam.name.includes('Community Report'))) ? `
|
${(!cam.id.startsWith('portal-') && (cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-') || cam.notes === '1-Tap Mobile Driver Pin' || cam.agency === 'Crowdsourced Report' || cam.name.includes('Community Report'))) ? `
|
||||||
<button class="popup-inspect-btn popup-delete-btn" style="background: #dc2626 !important; color: #ffffff !important; font-weight: 800 !important; font-size: 0.88rem !important; padding: 10px 14px !important; border-radius: 6px !important; margin-top: 6px !important; display: flex !important; align-items: center !important; justify-content: center !important; gap: 8px !important; border: none !important; cursor: pointer !important; box-shadow: 0 3px 10px rgba(220, 38, 38, 0.45) !important;" onclick="window.adminDeleteCamera('${escapeHtml(cam.id)}')">
|
<button class="popup-inspect-btn popup-delete-btn" style="background: #dc2626 !important; color: #ffffff !important; font-weight: 800 !important; font-size: 0.88rem !important; padding: 10px 14px !important; border-radius: 6px !important; margin-top: 6px !important; display: flex !important; align-items: center !important; justify-content: center !important; gap: 8px !important; border: none !important; cursor: pointer !important; box-shadow: 0 3px 10px rgba(220, 38, 38, 0.45) !important;" onclick="window.adminDeleteCamera('${escapeHtml(cam.id)}')">
|
||||||
<span style="font-size: 1rem;">🗑️</span>
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||||
<span>REMOVE THIS PIN (ADMIN)</span>
|
<span>REMOVE THIS PIN (ADMIN)</span>
|
||||||
</button>
|
</button>
|
||||||
` : ''}
|
` : ''}
|
||||||
@@ -531,7 +532,7 @@
|
|||||||
el.hudZoneBadge.innerHTML = `<span class="badge-dot dot-danger"></span> IN RECORDED ZONE`;
|
el.hudZoneBadge.innerHTML = `<span class="badge-dot dot-danger"></span> IN RECORDED ZONE`;
|
||||||
el.hudBannerAlert.classList.add('visible');
|
el.hudBannerAlert.classList.add('visible');
|
||||||
el.hudBannerAlert.innerHTML = `
|
el.hudBannerAlert.innerHTML = `
|
||||||
<span class="banner-pulse-icon">⚠️</span>
|
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}</span>
|
||||||
<div>
|
<div>
|
||||||
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
|
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
|
||||||
<p>${escapeHtml(closestCam.agency)} • Vehicle Profile Logging</p>
|
<p>${escapeHtml(closestCam.agency)} • Vehicle Profile Logging</p>
|
||||||
@@ -688,9 +689,10 @@
|
|||||||
function updateFollowCarUI() {
|
function updateFollowCarUI() {
|
||||||
if (el.btnFollowCar) {
|
if (el.btnFollowCar) {
|
||||||
el.btnFollowCar.classList.toggle('active', isFollowCarLocked);
|
el.btnFollowCar.classList.toggle('active', isFollowCarLocked);
|
||||||
|
const icon = window.FlockIcons ? FlockIcons.target() : '';
|
||||||
el.btnFollowCar.innerHTML = isFollowCarLocked
|
el.btnFollowCar.innerHTML = isFollowCarLocked
|
||||||
? `<span>🎯</span><span>Follow Car: ON</span>`
|
? `${icon}<span>Follow Car: ON</span>`
|
||||||
: `<span>🎯</span><span>Follow Car: OFF</span>`;
|
: `${icon}<span>Follow Car: OFF</span>`;
|
||||||
}
|
}
|
||||||
if (el.btnRecenterCar) {
|
if (el.btnRecenterCar) {
|
||||||
el.btnRecenterCar.classList.toggle('hidden', isFollowCarLocked);
|
el.btnRecenterCar.classList.toggle('hidden', isFollowCarLocked);
|
||||||
@@ -703,7 +705,7 @@
|
|||||||
if (el.btnSensCity) el.btnSensCity.classList.toggle('active', mode === 'city');
|
if (el.btnSensCity) el.btnSensCity.classList.toggle('active', mode === 'city');
|
||||||
if (el.btnSensHwy) el.btnSensHwy.classList.toggle('active', mode === 'highway');
|
if (el.btnSensHwy) el.btnSensHwy.classList.toggle('active', mode === 'highway');
|
||||||
playCautionBeep();
|
playCautionBeep();
|
||||||
showToast(mode === 'highway' ? '🛣️ Highway Range Active (2,500 ft advance alert)' : '🏙️ City Range Active (1,000 ft alert)');
|
showToast(mode === 'highway' ? 'Highway Range Active (2,500 ft advance alert)' : 'City Range Active (1,000 ft alert)');
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- DASHBOARD FULLSCREEN MOUNT MODE ---
|
// --- DASHBOARD FULLSCREEN MOUNT MODE ---
|
||||||
@@ -711,7 +713,7 @@
|
|||||||
if (!document.fullscreenElement) {
|
if (!document.fullscreenElement) {
|
||||||
document.documentElement.requestFullscreen().then(() => {
|
document.documentElement.requestFullscreen().then(() => {
|
||||||
if (el.btnFullscreenMount) el.btnFullscreenMount.classList.add('active');
|
if (el.btnFullscreenMount) el.btnFullscreenMount.classList.add('active');
|
||||||
showToast("📱 Fullscreen Dashboard Mode Active");
|
showToast("Fullscreen Dashboard Mode Active");
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.warn('Fullscreen request failed:', err);
|
console.warn('Fullscreen request failed:', err);
|
||||||
});
|
});
|
||||||
@@ -790,7 +792,7 @@
|
|||||||
speakVoiceAlert("Warning. Flock camera active zone ahead. You are being recorded.");
|
speakVoiceAlert("Warning. Flock camera active zone ahead. You are being recorded.");
|
||||||
el.hudBannerAlert.classList.add('visible');
|
el.hudBannerAlert.classList.add('visible');
|
||||||
el.hudBannerAlert.innerHTML = `
|
el.hudBannerAlert.innerHTML = `
|
||||||
<span class="banner-pulse-icon">⚠️</span>
|
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}</span>
|
||||||
<div>
|
<div>
|
||||||
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
|
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
|
||||||
<p>Test Alert • Vehicle Profile Logging Active</p>
|
<p>Test Alert • Vehicle Profile Logging Active</p>
|
||||||
@@ -824,7 +826,7 @@
|
|||||||
|
|
||||||
playRadarChime();
|
playRadarChime();
|
||||||
speakVoiceAlert('Test camera placed 400 feet ahead. Move forward into the red circle to test the alert.');
|
speakVoiceAlert('Test camera placed 400 feet ahead. Move forward into the red circle to test the alert.');
|
||||||
alert('📍 Test Flock camera placed 400 feet ahead of you!\n\nWalk or drive forward into the red circle zone to trigger the live warning chime and voice alert.');
|
alert('Test Flock camera placed 400 feet ahead of you!\n\nWalk or drive forward into the red circle zone to trigger the live warning chime and voice alert.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- LIVE GPS CONTINUOUS TRACKING ---
|
// --- LIVE GPS CONTINUOUS TRACKING ---
|
||||||
@@ -840,7 +842,8 @@
|
|||||||
navigator.geolocation.clearWatch(liveGpsWatchId);
|
navigator.geolocation.clearWatch(liveGpsWatchId);
|
||||||
liveGpsWatchId = null;
|
liveGpsWatchId = null;
|
||||||
el.btnLiveGps.classList.remove('active');
|
el.btnLiveGps.classList.remove('active');
|
||||||
el.btnLiveGps.innerHTML = `<span>🎯</span><span>Live GPS Drive Mode</span>`;
|
const radioIcon = window.FlockIcons ? FlockIcons.radio() : '';
|
||||||
|
el.btnLiveGps.innerHTML = `${radioIcon}<span>Live GPS Drive Mode</span>`;
|
||||||
el.hudSpeedVal.textContent = '0';
|
el.hudSpeedVal.textContent = '0';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -848,7 +851,8 @@
|
|||||||
if (isSimulating) stopDriveSimulation();
|
if (isSimulating) stopDriveSimulation();
|
||||||
|
|
||||||
el.btnLiveGps.classList.add('active');
|
el.btnLiveGps.classList.add('active');
|
||||||
el.btnLiveGps.innerHTML = `<span>🛑</span><span>Stop Live GPS</span>`;
|
const stopIcon = window.FlockIcons ? FlockIcons.stopCircle() : '';
|
||||||
|
el.btnLiveGps.innerHTML = `${stopIcon}<span>Stop Live GPS</span>`;
|
||||||
speakVoiceAlert('Live GPS drive mode activated. Monitoring for Flock cameras.');
|
speakVoiceAlert('Live GPS drive mode activated. Monitoring for Flock cameras.');
|
||||||
|
|
||||||
liveGpsWatchId = navigator.geolocation.watchPosition(
|
liveGpsWatchId = navigator.geolocation.watchPosition(
|
||||||
@@ -898,7 +902,7 @@
|
|||||||
const httpUrl = 'http://192.168.1.249:8085';
|
const httpUrl = 'http://192.168.1.249:8085';
|
||||||
const httpsUrl = 'https://192.168.1.249:8443';
|
const httpsUrl = 'https://192.168.1.249:8443';
|
||||||
alert(
|
alert(
|
||||||
"📱 HOW TO USE FLOCKRADAR ON YOUR PHONE IN ANTELOPE VALLEY:\n\n" +
|
"HOW TO USE FLOCKRADAR ON YOUR PHONE IN ANTELOPE VALLEY:\n\n" +
|
||||||
"1. Connect your phone to your Wi-Fi or hotspot.\n\n" +
|
"1. Connect your phone to your Wi-Fi or hotspot.\n\n" +
|
||||||
"2. FOR CONTINUOUS DRIVING GPS (iOS Safari & Android Chrome):\n" +
|
"2. FOR CONTINUOUS DRIVING GPS (iOS Safari & Android Chrome):\n" +
|
||||||
" Open: " + httpsUrl + "\n" +
|
" Open: " + httpsUrl + "\n" +
|
||||||
@@ -919,7 +923,8 @@
|
|||||||
|
|
||||||
function startDriveSimulation() {
|
function startDriveSimulation() {
|
||||||
isSimulating = true;
|
isSimulating = true;
|
||||||
el.btnSimDrive.innerHTML = `<span>⏸</span> Pause Simulation`;
|
const pauseIcon = window.FlockIcons ? FlockIcons.pause('flock-svg-sm') : '';
|
||||||
|
el.btnSimDrive.innerHTML = `${pauseIcon}<span>Pause Simulation</span>`;
|
||||||
el.btnSimDrive.classList.add('active');
|
el.btnSimDrive.classList.add('active');
|
||||||
|
|
||||||
simIndex = 0;
|
simIndex = 0;
|
||||||
@@ -932,7 +937,8 @@
|
|||||||
|
|
||||||
function stopDriveSimulation() {
|
function stopDriveSimulation() {
|
||||||
isSimulating = false;
|
isSimulating = false;
|
||||||
el.btnSimDrive.innerHTML = `<span>▶</span> Simulate Drive`;
|
const playIcon = window.FlockIcons ? FlockIcons.play('flock-svg-sm') : '';
|
||||||
|
el.btnSimDrive.innerHTML = `${playIcon}<span>Simulate Drive</span>`;
|
||||||
el.btnSimDrive.classList.remove('active');
|
el.btnSimDrive.classList.remove('active');
|
||||||
if (simAnimId) cancelAnimationFrame(simAnimId);
|
if (simAnimId) cancelAnimationFrame(simAnimId);
|
||||||
}
|
}
|
||||||
@@ -1028,7 +1034,7 @@
|
|||||||
|
|
||||||
// --- MULTI-TIER AUTO-LOCATION RESOLVER (ANTELOPE VALLEY & BEYOND) ---
|
// --- MULTI-TIER AUTO-LOCATION RESOLVER (ANTELOPE VALLEY & BEYOND) ---
|
||||||
async function attemptAutoLocate() {
|
async function attemptAutoLocate() {
|
||||||
updateSpottedBadge('🛰️ Spotting Antelope Valley Location...');
|
updateSpottedBadge('Spotting Antelope Valley Location...');
|
||||||
|
|
||||||
// Tier 1: Instant IP-based Network Geolocation
|
// Tier 1: Instant IP-based Network Geolocation
|
||||||
// Works 100% reliably even when browser GPS is blocked, disabled, or accessed over HTTP
|
// Works 100% reliably even when browser GPS is blocked, disabled, or accessed over HTTP
|
||||||
@@ -1059,7 +1065,7 @@
|
|||||||
|
|
||||||
updateUserCoordinates(targetLat, targetLng, 0, 0);
|
updateUserCoordinates(targetLat, targetLng, 0, 0);
|
||||||
map.flyTo([targetLat, targetLng], 14, { duration: 1.2 });
|
map.flyTo([targetLat, targetLng], 14, { duration: 1.2 });
|
||||||
updateSpottedBadge(`📍 Spotted: ${city}, Antelope Valley (Network)`);
|
updateSpottedBadge(`Spotted: ${city}, Antelope Valley (Network)`);
|
||||||
updateHudLocationStatus(`Monitoring ${city} / Antelope Valley Corridors`);
|
updateHudLocationStatus(`Monitoring ${city} / Antelope Valley Corridors`);
|
||||||
speakVoiceAlert(`Location spotted: Antelope Valley, California.`);
|
speakVoiceAlert(`Location spotted: Antelope Valley, California.`);
|
||||||
checkProximityToCameras();
|
checkProximityToCameras();
|
||||||
@@ -1073,7 +1079,7 @@
|
|||||||
if (ipData2 && ipData2.success && ipData2.latitude) {
|
if (ipData2 && ipData2.success && ipData2.latitude) {
|
||||||
updateUserCoordinates(34.60194, -118.14847, 0, 0);
|
updateUserCoordinates(34.60194, -118.14847, 0, 0);
|
||||||
map.flyTo([34.60194, -118.14847], 14, { duration: 1.2 });
|
map.flyTo([34.60194, -118.14847], 14, { duration: 1.2 });
|
||||||
updateSpottedBadge(`📍 Spotted: ${ipData2.city || 'Antelope Valley'} (Network)`);
|
updateSpottedBadge(`Spotted: ${ipData2.city || 'Antelope Valley'} (Network)`);
|
||||||
checkProximityToCameras();
|
checkProximityToCameras();
|
||||||
ipFound = true;
|
ipFound = true;
|
||||||
}
|
}
|
||||||
@@ -1083,7 +1089,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ipFound) {
|
if (!ipFound) {
|
||||||
updateSpottedBadge('📍 Antelope Valley: Palmdale, CA (Default)');
|
updateSpottedBadge('Antelope Valley: Palmdale, CA (Default)');
|
||||||
updateHudLocationStatus('Monitoring Antelope Valley Corridors');
|
updateHudLocationStatus('Monitoring Antelope Valley Corridors');
|
||||||
checkProximityToCameras();
|
checkProximityToCameras();
|
||||||
}
|
}
|
||||||
@@ -1098,8 +1104,8 @@
|
|||||||
console.log('Hardware GPS lock acquired:', lat, lng, '±' + acc + 'm');
|
console.log('Hardware GPS lock acquired:', lat, lng, '±' + acc + 'm');
|
||||||
updateUserCoordinates(lat, lng, pos.coords.speed, pos.coords.heading);
|
updateUserCoordinates(lat, lng, pos.coords.speed, pos.coords.heading);
|
||||||
map.flyTo([lat, lng], 15, { duration: 1.2 });
|
map.flyTo([lat, lng], 15, { duration: 1.2 });
|
||||||
updateSpottedBadge(`🎯 High-Precision GPS (±${acc}m) • Antelope Valley`);
|
updateSpottedBadge(`High-Precision GPS (±${acc}m) • Antelope Valley`);
|
||||||
updateHudLocationStatus('📍 Live Driving GPS Active');
|
updateHudLocationStatus('Live Driving GPS Active');
|
||||||
checkProximityToCameras();
|
checkProximityToCameras();
|
||||||
},
|
},
|
||||||
gpsErr => {
|
gpsErr => {
|
||||||
@@ -1147,7 +1153,7 @@
|
|||||||
async function useLiveGps() {
|
async function useLiveGps() {
|
||||||
if (isSimulating) stopDriveSimulation();
|
if (isSimulating) stopDriveSimulation();
|
||||||
|
|
||||||
updateSpottedBadge('🛰️ Re-detecting location in Antelope Valley...');
|
updateSpottedBadge('Re-detecting location in Antelope Valley...');
|
||||||
updateHudLocationStatus('Acquiring high-precision location...');
|
updateHudLocationStatus('Acquiring high-precision location...');
|
||||||
|
|
||||||
// 1. First ensure IP location is refreshed
|
// 1. First ensure IP location is refreshed
|
||||||
@@ -1157,7 +1163,7 @@
|
|||||||
if (ipData && ipData.latitude) {
|
if (ipData && ipData.latitude) {
|
||||||
const city = ipData.city || 'Palmdale';
|
const city = ipData.city || 'Palmdale';
|
||||||
jumpToLocation(34.60194, -118.14847, `${city} (Antelope Valley)`);
|
jumpToLocation(34.60194, -118.14847, `${city} (Antelope Valley)`);
|
||||||
updateSpottedBadge(`📍 Spotted: ${city}, Antelope Valley`);
|
updateSpottedBadge(`Spotted: ${city}, Antelope Valley`);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
@@ -1168,8 +1174,8 @@
|
|||||||
updateUserCoordinates(pos.coords.latitude, pos.coords.longitude, pos.coords.speed, pos.coords.heading);
|
updateUserCoordinates(pos.coords.latitude, pos.coords.longitude, pos.coords.speed, pos.coords.heading);
|
||||||
map.flyTo([pos.coords.latitude, pos.coords.longitude], 16, { duration: 1.2 });
|
map.flyTo([pos.coords.latitude, pos.coords.longitude], 16, { duration: 1.2 });
|
||||||
const acc = Math.round(pos.coords.accuracy || 10);
|
const acc = Math.round(pos.coords.accuracy || 10);
|
||||||
updateSpottedBadge(`🎯 Live GPS Active (±${acc}m)`);
|
updateSpottedBadge(`Live GPS Active (±${acc}m)`);
|
||||||
updateHudLocationStatus('📍 High-Precision GPS Locked');
|
updateHudLocationStatus('High-Precision GPS Locked');
|
||||||
playRadarChime();
|
playRadarChime();
|
||||||
speakVoiceAlert('GPS position locked in Antelope Valley.');
|
speakVoiceAlert('GPS position locked in Antelope Valley.');
|
||||||
checkProximityToCameras();
|
checkProximityToCameras();
|
||||||
@@ -1180,10 +1186,10 @@
|
|||||||
if (err.code === 1) { // PERMISSION_DENIED
|
if (err.code === 1) { // PERMISSION_DENIED
|
||||||
el.hudBannerAlert.classList.add('visible');
|
el.hudBannerAlert.classList.add('visible');
|
||||||
el.hudBannerAlert.innerHTML = `
|
el.hudBannerAlert.innerHTML = `
|
||||||
<span class="banner-pulse-icon">ℹ️</span>
|
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.radar('flock-svg-lg') : ''}</span>
|
||||||
<div>
|
<div>
|
||||||
<strong>ANTELOPE VALLEY NETWORK LOCATION ACTIVE</strong>
|
<strong>ANTELOPE VALLEY NETWORK LOCATION ACTIVE</strong>
|
||||||
<p>To enable turn-by-turn driving GPS, click the lock icon 🔒 in your browser address bar and select 'Allow Location'.</p>
|
<p>To enable turn-by-turn driving GPS, click the permissions icon in your browser address bar and select 'Allow Location'.</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -1201,8 +1207,8 @@
|
|||||||
if (isSimulating) stopDriveSimulation();
|
if (isSimulating) stopDriveSimulation();
|
||||||
updateUserCoordinates(lat, lng, 0, 0);
|
updateUserCoordinates(lat, lng, 0, 0);
|
||||||
map.flyTo([lat, lng], zoom, { duration: 1.2 });
|
map.flyTo([lat, lng], zoom, { duration: 1.2 });
|
||||||
updateHudLocationStatus(`📍 ${label}`);
|
updateHudLocationStatus(label);
|
||||||
updateSpottedBadge(`📍 Set to: ${label}`);
|
updateSpottedBadge(`Set to: ${label}`);
|
||||||
checkProximityToCameras();
|
checkProximityToCameras();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1237,13 +1243,15 @@
|
|||||||
if (darkTileLayer && map.hasLayer(darkTileLayer)) map.removeLayer(darkTileLayer);
|
if (darkTileLayer && map.hasLayer(darkTileLayer)) map.removeLayer(darkTileLayer);
|
||||||
if (satelliteLayer) satelliteLayer.addTo(map);
|
if (satelliteLayer) satelliteLayer.addTo(map);
|
||||||
if (satelliteLabelsLayer) satelliteLabelsLayer.addTo(map);
|
if (satelliteLabelsLayer) satelliteLabelsLayer.addTo(map);
|
||||||
el.btnMapLayer.innerHTML = `<span>🗺️</span><span>Dark Canvas</span>`;
|
const mapIcon = window.FlockIcons ? FlockIcons.map() : '';
|
||||||
|
el.btnMapLayer.innerHTML = `${mapIcon}<span>Dark Canvas</span>`;
|
||||||
el.btnMapLayer.classList.add('active');
|
el.btnMapLayer.classList.add('active');
|
||||||
} else {
|
} else {
|
||||||
if (satelliteLayer && map.hasLayer(satelliteLayer)) map.removeLayer(satelliteLayer);
|
if (satelliteLayer && map.hasLayer(satelliteLayer)) map.removeLayer(satelliteLayer);
|
||||||
if (satelliteLabelsLayer && map.hasLayer(satelliteLabelsLayer)) map.removeLayer(satelliteLabelsLayer);
|
if (satelliteLabelsLayer && map.hasLayer(satelliteLabelsLayer)) map.removeLayer(satelliteLabelsLayer);
|
||||||
if (darkTileLayer) darkTileLayer.addTo(map);
|
if (darkTileLayer) darkTileLayer.addTo(map);
|
||||||
el.btnMapLayer.innerHTML = `<span>🛰️</span><span>Satellite / Dark</span>`;
|
const layersIcon = window.FlockIcons ? FlockIcons.layers() : '';
|
||||||
|
el.btnMapLayer.innerHTML = `${layersIcon}<span>Satellite / Dark</span>`;
|
||||||
el.btnMapLayer.classList.remove('active');
|
el.btnMapLayer.classList.remove('active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1252,14 +1260,16 @@
|
|||||||
function updateProfileDisplay() {
|
function updateProfileDisplay() {
|
||||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||||
if (user && user.isLoggedIn) {
|
if (user && user.isLoggedIn) {
|
||||||
|
const carSvg = window.FlockIcons ? FlockIcons.car('flock-svg-sm') : '';
|
||||||
el.userProfilePill.innerHTML = `
|
el.userProfilePill.innerHTML = `
|
||||||
<span class="avatar-dot">🚘</span>
|
<span class="avatar-dot">${carSvg}</span>
|
||||||
<span>${escapeHtml(user.username)}</span>
|
<span>${escapeHtml(user.username)}</span>
|
||||||
<span class="points-badge">${user.points} pts</span>
|
<span class="points-badge">${user.points} pts</span>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
|
const userSvg = `<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
|
||||||
el.userProfilePill.innerHTML = `
|
el.userProfilePill.innerHTML = `
|
||||||
<span class="avatar-dot">👤</span>
|
<span class="avatar-dot">${userSvg}</span>
|
||||||
<span>Driver Login</span>
|
<span>Driver Login</span>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -1326,13 +1336,13 @@
|
|||||||
if (el.camDetailVerifyBadge && el.camDetailVerifyText) {
|
if (el.camDetailVerifyBadge && el.camDetailVerifyText) {
|
||||||
if (meta.isContested) {
|
if (meta.isContested) {
|
||||||
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-contested';
|
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-contested';
|
||||||
el.camDetailVerifyText.textContent = `🟠 CONTESTED SIGHTING (${meta.contests ? meta.contests.length : 1} REPORTS)`;
|
el.camDetailVerifyText.innerHTML = `${window.FlockIcons ? window.FlockIcons.alertTriangle('flock-svg-sm') : ''} <span>CONTESTED LOCATION • RE-VERIFICATION PENDING</span>`;
|
||||||
} else if (hasPhoto) {
|
} else if (hasPhoto) {
|
||||||
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-verified';
|
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-verified';
|
||||||
el.camDetailVerifyText.textContent = `🟢 VERIFIED PHOTO PROOF • ${meta.confirmations || 1} CONFIRMATIONS`;
|
el.camDetailVerifyText.innerHTML = `${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''} <span>VERIFIED PHOTO PROOF • ${meta.confirmations || 1} CONFIRMATIONS</span>`;
|
||||||
} else {
|
} else {
|
||||||
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-unverified';
|
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-unverified';
|
||||||
el.camDetailVerifyText.textContent = `🟡 UNVERIFIED PIN • NEEDS PHOTO PROOF (${meta.confirmations || 1} CONFIRMED)`;
|
el.camDetailVerifyText.innerHTML = `${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''} <span>UNVERIFIED PIN • NEEDS PHOTO PROOF (${meta.confirmations || 1} CONFIRMED)</span>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1344,10 +1354,16 @@
|
|||||||
<img src="${meta.photoUrl}" class="proof-photo-img" alt="Flock camera mounted on pole" onclick="window.open('${meta.photoUrl}', '_blank')" title="Click to view full size">
|
<img src="${meta.photoUrl}" class="proof-photo-img" alt="Flock camera mounted on pole" onclick="window.open('${meta.photoUrl}', '_blank')" title="Click to view full size">
|
||||||
<div class="proof-photo-overlay">
|
<div class="proof-photo-overlay">
|
||||||
<div>
|
<div>
|
||||||
<span class="proof-photo-author">📸 Scout: ${escapeHtml(meta.photoAuthor || 'Active Driver')}</span><br>
|
<span class="proof-photo-author" style="display: inline-flex; align-items: center; gap: 5px;">
|
||||||
|
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}
|
||||||
|
<span>Scout: ${escapeHtml(meta.photoAuthor || 'Active Driver')}</span>
|
||||||
|
</span><br>
|
||||||
<span class="proof-photo-date">Logged: ${new Date(meta.photoDate || Date.now()).toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' })}</span>
|
<span class="proof-photo-date">Logged: ${new Date(meta.photoDate || Date.now()).toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' })}</span>
|
||||||
</div>
|
</div>
|
||||||
<span style="background: rgba(16,185,129,0.25); border: 1px solid #10b981; padding: 3px 8px; border-radius: 12px; font-size: 0.7rem; font-weight: 700; color: #34d399;">VERIFIED HARDWARE</span>
|
<span style="background: rgba(16,185,129,0.25); border: 1px solid #10b981; padding: 3px 8px; border-radius: 12px; font-size: 0.7rem; font-weight: 700; color: #34d399; display: inline-flex; align-items: center; gap: 4px;">
|
||||||
|
${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''}
|
||||||
|
<span>VERIFIED HARDWARE</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -1355,13 +1371,15 @@
|
|||||||
} else {
|
} else {
|
||||||
el.camProofContainer.innerHTML = `
|
el.camProofContainer.innerHTML = `
|
||||||
<div class="proof-empty-box">
|
<div class="proof-empty-box">
|
||||||
<div class="proof-empty-icon">📸</div>
|
<div class="proof-empty-icon">
|
||||||
|
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-hero') : ''}
|
||||||
|
</div>
|
||||||
<div class="proof-empty-title">No Physical Hardware Photo Yet</div>
|
<div class="proof-empty-title">No Physical Hardware Photo Yet</div>
|
||||||
<div class="proof-empty-desc">
|
<div class="proof-empty-desc">
|
||||||
Flock cameras stay permanently bolted to utility poles and traffic signals with small solar panels. Snap a photo of the pole to lock this camera into the permanent verified registry.
|
Flock cameras stay permanently bolted to utility poles and traffic signals with small solar panels. Snap a photo of the pole to lock this camera into the permanent verified registry.
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-proof-snap-cta" onclick="window.triggerPhotoCaptureForActiveCam()">
|
<button type="button" class="btn-proof-snap-cta" onclick="window.triggerPhotoCaptureForActiveCam()">
|
||||||
<span>📸</span>
|
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}
|
||||||
<span>Snap Camera on Pole (+100 pts)</span>
|
<span>Snap Camera on Pole (+100 pts)</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1381,10 +1399,10 @@
|
|||||||
if (el.btnVoteConfirmCam && el.btnVoteConfirmText) {
|
if (el.btnVoteConfirmCam && el.btnVoteConfirmText) {
|
||||||
if (userConfirmed) {
|
if (userConfirmed) {
|
||||||
el.btnVoteConfirmCam.classList.add('voted');
|
el.btnVoteConfirmCam.classList.add('voted');
|
||||||
el.btnVoteConfirmText.textContent = `👍 Confirmed (${meta.confirmations || 1})`;
|
el.btnVoteConfirmText.innerHTML = `${window.FlockIcons ? window.FlockIcons.check('flock-svg-sm') : ''} Confirmed (${meta.confirmations || 1})`;
|
||||||
} else {
|
} else {
|
||||||
el.btnVoteConfirmCam.classList.remove('voted');
|
el.btnVoteConfirmCam.classList.remove('voted');
|
||||||
el.btnVoteConfirmText.textContent = `Confirm Active (${meta.confirmations || 1})`;
|
el.btnVoteConfirmText.innerHTML = `${window.FlockIcons ? window.FlockIcons.thumbUp('flock-svg-sm') : ''} Confirm Active (${meta.confirmations || 1})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1592,15 +1610,16 @@
|
|||||||
${r.photoUrl ? `
|
${r.photoUrl ? `
|
||||||
<img src="${r.photoUrl}" alt="Thumbnail" style="width: 34px; height: 34px; object-fit: cover; border-radius: 4px; border: 1px solid var(--accent-cyan); flex-shrink: 0;">
|
<img src="${r.photoUrl}" alt="Thumbnail" style="width: 34px; height: 34px; object-fit: cover; border-radius: 4px; border: 1px solid var(--accent-cyan); flex-shrink: 0;">
|
||||||
` : `
|
` : `
|
||||||
<span style="font-size: 1.1rem; flex-shrink: 0;">📍</span>
|
<span style="display: flex; align-items: center; color: var(--accent-cyan); flex-shrink: 0;">${window.FlockIcons ? FlockIcons.mapPin('flock-svg-sm') : ''}</span>
|
||||||
`}
|
`}
|
||||||
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||||
<strong style="color: #f8fafc; display: block; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(r.name)}</strong>
|
<strong style="color: #f8fafc; display: block; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(r.name)}</strong>
|
||||||
<span style="color: #94a3b8; font-size: 0.7rem;">${r.lat.toFixed(4)}, ${r.lng.toFixed(4)} • ${r.dateStr}</span>
|
<span style="color: #94a3b8; font-size: 0.7rem;">${r.lat.toFixed(4)}, ${r.lng.toFixed(4)} • ${r.dateStr}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-secondary" style="padding: 5px 10px; font-size: 0.74rem; color: #f87171; border-color: rgba(239,68,68,0.3); cursor: pointer; flex-shrink: 0;" onclick="window.adminDeleteCamera('${escapeHtml(r.id)}')">
|
<button type="button" class="btn-secondary" style="padding: 5px 10px; font-size: 0.74rem; color: #f87171; border-color: rgba(239,68,68,0.3); cursor: pointer; flex-shrink: 0; display: flex; align-items: center; gap: 5px;" onclick="window.adminDeleteCamera('${escapeHtml(r.id)}')">
|
||||||
🗑️ Delete
|
${window.FlockIcons ? FlockIcons.trash('flock-svg-sm') : ''}
|
||||||
|
<span>Delete</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -1630,7 +1649,7 @@
|
|||||||
closeCameraDetailModal();
|
closeCameraDetailModal();
|
||||||
playLogConfirmationChirp();
|
playLogConfirmationChirp();
|
||||||
speakVoiceAlert("Placed camera deleted from radar.");
|
speakVoiceAlert("Placed camera deleted from radar.");
|
||||||
showToast("🗑️ Placed camera permanently removed.");
|
showToast("Placed camera permanently removed.");
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- CONTEST CAMERA MODAL ---
|
// --- CONTEST CAMERA MODAL ---
|
||||||
@@ -1693,7 +1712,7 @@
|
|||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
showToast("📸 Processing & compressing photo proof...");
|
showToast("Processing & compressing photo proof...");
|
||||||
const compressedDataUrl = await window.FlockAuth.compressImage(file, 1000, 0.72);
|
const compressedDataUrl = await window.FlockAuth.compressImage(file, 1000, 0.72);
|
||||||
|
|
||||||
if (photoUploadTarget === 'quicklog') {
|
if (photoUploadTarget === 'quicklog') {
|
||||||
@@ -1712,7 +1731,7 @@
|
|||||||
updateProfileDisplay();
|
updateProfileDisplay();
|
||||||
playLogConfirmationChirp();
|
playLogConfirmationChirp();
|
||||||
speakVoiceAlert("Camera photo proof verified and logged to radar grid. +150 Scout Points!");
|
speakVoiceAlert("Camera photo proof verified and logged to radar grid. +150 Scout Points!");
|
||||||
showToast("📸 Camera photo proof verified & logged! +150 Scout Points!");
|
showToast("Camera photo proof verified & logged! +150 Scout Points!");
|
||||||
|
|
||||||
const cam = allCameras.find(c => c.id === newRpt.id);
|
const cam = allCameras.find(c => c.id === newRpt.id);
|
||||||
if (cam) openCameraDetailModal(cam);
|
if (cam) openCameraDetailModal(cam);
|
||||||
@@ -1722,19 +1741,19 @@
|
|||||||
renderCameraZones();
|
renderCameraZones();
|
||||||
playLogConfirmationChirp();
|
playLogConfirmationChirp();
|
||||||
speakVoiceAlert("Photo proof verified and locked into radar database. +100 Scout Points awarded!");
|
speakVoiceAlert("Photo proof verified and locked into radar database. +100 Scout Points awarded!");
|
||||||
showToast("📸 Photo proof verified & locked! +100 Scout Points!");
|
showToast("Photo proof verified & locked! +100 Scout Points!");
|
||||||
} else if (photoUploadTarget === 'report') {
|
} else if (photoUploadTarget === 'report') {
|
||||||
reportPhotoPendingDataUrl = compressedDataUrl;
|
reportPhotoPendingDataUrl = compressedDataUrl;
|
||||||
if (el.reportPhotoPreviewImg) el.reportPhotoPreviewImg.src = compressedDataUrl;
|
if (el.reportPhotoPreviewImg) el.reportPhotoPreviewImg.src = compressedDataUrl;
|
||||||
if (el.reportPhotoPreviewBox) el.reportPhotoPreviewBox.classList.remove('hidden');
|
if (el.reportPhotoPreviewBox) el.reportPhotoPreviewBox.classList.remove('hidden');
|
||||||
if (el.reportPhotoBtnText) el.reportPhotoBtnText.textContent = 'Change Photo Proof';
|
if (el.reportPhotoBtnText) el.reportPhotoBtnText.textContent = 'Change Photo Proof';
|
||||||
showToast("📸 Photo proof attached to camera report!");
|
showToast("Photo proof attached to camera report!");
|
||||||
} else if (photoUploadTarget === 'contest') {
|
} else if (photoUploadTarget === 'contest') {
|
||||||
contestPhotoPendingDataUrl = compressedDataUrl;
|
contestPhotoPendingDataUrl = compressedDataUrl;
|
||||||
if (el.contestPhotoPreviewImg) el.contestPhotoPreviewImg.src = compressedDataUrl;
|
if (el.contestPhotoPreviewImg) el.contestPhotoPreviewImg.src = compressedDataUrl;
|
||||||
if (el.contestPhotoPreviewBox) el.contestPhotoPreviewBox.classList.remove('hidden');
|
if (el.contestPhotoPreviewBox) el.contestPhotoPreviewBox.classList.remove('hidden');
|
||||||
if (el.contestPhotoBtnText) el.contestPhotoBtnText.textContent = 'Change Empty Pole Photo';
|
if (el.contestPhotoBtnText) el.contestPhotoBtnText.textContent = 'Change Empty Pole Photo';
|
||||||
showToast("📸 Empty pole photo attached to contest report!");
|
showToast("Empty pole photo attached to contest report!");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Photo processing error:', err);
|
console.error('Photo processing error:', err);
|
||||||
@@ -1820,7 +1839,7 @@
|
|||||||
if (res.success) {
|
if (res.success) {
|
||||||
playLogConfirmationChirp();
|
playLogConfirmationChirp();
|
||||||
speakVoiceAlert("Camera confirmed active on pole. +25 Scout Points!");
|
speakVoiceAlert("Camera confirmed active on pole. +25 Scout Points!");
|
||||||
showToast(`👍 Confirmed active on pole! Total: ${res.count} confirmations.`);
|
showToast(`Confirmed active on pole! Total: ${res.count} confirmations.`);
|
||||||
openCameraDetailModal(activeModalCamera);
|
openCameraDetailModal(activeModalCamera);
|
||||||
renderCameraZones();
|
renderCameraZones();
|
||||||
updateProfileDisplay();
|
updateProfileDisplay();
|
||||||
@@ -1876,9 +1895,9 @@
|
|||||||
if (res.success) {
|
if (res.success) {
|
||||||
if (res.isContested) {
|
if (res.isContested) {
|
||||||
speakVoiceAlert("Camera flagged as contested by community consensus.");
|
speakVoiceAlert("Camera flagged as contested by community consensus.");
|
||||||
showToast("⚠️ Location marked Contested by community consensus.");
|
showToast("Location marked Contested by community consensus.");
|
||||||
} else {
|
} else {
|
||||||
showToast(`⚠️ Contest submitted (${res.contestCount}/3 flags for consensus review).`);
|
showToast(`Contest submitted (${res.contestCount}/3 flags for consensus review).`);
|
||||||
}
|
}
|
||||||
renderCameraZones();
|
renderCameraZones();
|
||||||
} else {
|
} else {
|
||||||
@@ -1926,7 +1945,10 @@
|
|||||||
if (user) {
|
if (user) {
|
||||||
user.soundEnabled = !user.soundEnabled;
|
user.soundEnabled = !user.soundEnabled;
|
||||||
window.FlockAuth.saveUser();
|
window.FlockAuth.saveUser();
|
||||||
el.btnMuteSound.innerHTML = user.soundEnabled ? '🔔 Sound: ON' : '🔕 Sound: OFF';
|
const volIcon = user.soundEnabled
|
||||||
|
? (window.FlockIcons ? FlockIcons.volume('flock-svg-sm') : '')
|
||||||
|
: (window.FlockIcons ? FlockIcons.volumeX('flock-svg-sm') : '');
|
||||||
|
el.btnMuteSound.innerHTML = `${volIcon}<span>Sound: ${user.soundEnabled ? 'ON' : 'OFF'}</span>`;
|
||||||
el.btnMuteSound.classList.toggle('active', !user.soundEnabled);
|
el.btnMuteSound.classList.toggle('active', !user.soundEnabled);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1942,23 +1964,27 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const username = document.getElementById('login-username').value.trim();
|
const username = document.getElementById('login-username').value.trim();
|
||||||
const vehicle = document.getElementById('login-vehicle').value.trim();
|
const vehicle = document.getElementById('login-vehicle').value.trim();
|
||||||
const soundEnabled = document.getElementById('login-sound').checked;
|
const sound = document.getElementById('login-sound').checked;
|
||||||
|
|
||||||
if (window.FlockAuth) {
|
window.FlockAuth.login(username, vehicle, sound);
|
||||||
window.FlockAuth.login(username, vehicle, soundEnabled);
|
updateProfileDisplay();
|
||||||
updateProfileDisplay();
|
|
||||||
}
|
|
||||||
closeLoginModal();
|
closeLoginModal();
|
||||||
|
playLogConfirmationChirp();
|
||||||
|
speakVoiceAlert(`Welcome to the grid, scout ${username}.`);
|
||||||
|
showToast(`Welcome back, Scout ${username}!`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Login as Guest button
|
// Guest scout button
|
||||||
el.loginGuestBtn.addEventListener('click', () => {
|
if (el.loginGuestBtn) {
|
||||||
if (window.FlockAuth) {
|
el.loginGuestBtn.addEventListener('click', () => {
|
||||||
window.FlockAuth.login('Guest Scout', 'Unspecified Vehicle', true);
|
if (window.FlockAuth) {
|
||||||
updateProfileDisplay();
|
window.FlockAuth.login('Guest Scout', 'Unspecified Vehicle', true);
|
||||||
}
|
updateProfileDisplay();
|
||||||
closeLoginModal();
|
}
|
||||||
});
|
closeLoginModal();
|
||||||
|
showToast("Continuing as Anonymous Scout");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Admin Clear All Placed Pins Button (Keeps 1,439 real municipal portals)
|
// Admin Clear All Placed Pins Button (Keeps 1,439 real municipal portals)
|
||||||
if (el.btnAdminClearAllPlaced) {
|
if (el.btnAdminClearAllPlaced) {
|
||||||
@@ -1974,7 +2000,7 @@
|
|||||||
renderAdminPlacedList();
|
renderAdminPlacedList();
|
||||||
playLogConfirmationChirp();
|
playLogConfirmationChirp();
|
||||||
speakVoiceAlert("All custom placed pins cleared from radar.");
|
speakVoiceAlert("All custom placed pins cleared from radar.");
|
||||||
showToast("🗑️ All custom placed pins cleared.");
|
showToast("All custom placed pins cleared.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2031,7 +2057,11 @@
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (el.driverHud) {
|
if (el.driverHud) {
|
||||||
const isExpanded = el.driverHud.classList.toggle('is-expanded');
|
const isExpanded = el.driverHud.classList.toggle('is-expanded');
|
||||||
if (el.hudDetailsArrow) el.hudDetailsArrow.textContent = isExpanded ? '▲' : '🔍';
|
if (el.hudDetailsArrow) {
|
||||||
|
el.hudDetailsArrow.innerHTML = isExpanded
|
||||||
|
? (window.FlockIcons ? FlockIcons.chevronDown('flock-svg-sm') : '▲')
|
||||||
|
: (window.FlockIcons ? FlockIcons.search('flock-svg-sm') : '▼');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
// Service Worker for FlockRadar Offline Driving & Live Updates
|
// Service Worker for FlockRadar Offline Driving & Live Updates
|
||||||
const CACHE_NAME = 'flockradar-v12';
|
const CACHE_NAME = 'flockradar-v13';
|
||||||
const ASSETS_TO_CACHE = [
|
const ASSETS_TO_CACHE = [
|
||||||
'./',
|
'./',
|
||||||
'./index.html',
|
'./index.html',
|
||||||
'./css/styles.css?v=12',
|
'./css/styles.css?v=13',
|
||||||
'./js/app.js?v=12',
|
'./js/icons.js?v=13',
|
||||||
'./js/auth.js?v=12',
|
'./js/app.js?v=13',
|
||||||
'./js/radar.js?v=12',
|
'./js/auth.js?v=13',
|
||||||
'./data/portals.js?v=12',
|
'./js/radar.js?v=13',
|
||||||
|
'./data/portals.js?v=13',
|
||||||
'./assets/img/flock_pole_cam_palmdale.jpg',
|
'./assets/img/flock_pole_cam_palmdale.jpg',
|
||||||
'./manifest.json'
|
'./manifest.json'
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user