feat: add California SB 34 PRA plate audit generator, pre-trip commute route scanner, background audio driving guard, corridor density heatmap, and open data export (v26)
This commit is contained in:
+364
@@ -4323,5 +4323,369 @@ body {
|
|||||||
box-shadow: 0 0 8px rgba(245, 158, 11, 0.3);
|
box-shadow: 0 0 8px rgba(245, 158, 11, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
V26 CIVIC UTILITY FEATURES: ROUTE SCANNER, SB 34 PRA, BACKGROUND AUDIO, EXPORT
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/* Pre-Trip Route Scanner */
|
||||||
|
.route-scanner-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-corridor-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: rgba(15, 23, 42, 0.8);
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.3);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-corridor-select:focus {
|
||||||
|
border-color: #38bdf8;
|
||||||
|
box-shadow: 0 0 10px rgba(56, 189, 248, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-scan-result {
|
||||||
|
background: rgba(15, 23, 42, 0.95);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
animation: fadeIn 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-scan-result.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-score-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-score-badge {
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-score-badge.exposure-high {
|
||||||
|
background: rgba(239, 68, 68, 0.2);
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.5);
|
||||||
|
color: #fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-score-badge.exposure-medium {
|
||||||
|
background: rgba(245, 158, 11, 0.2);
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.5);
|
||||||
|
color: #fcd34d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-score-badge.exposure-clean {
|
||||||
|
background: rgba(16, 185, 129, 0.2);
|
||||||
|
border: 1px solid rgba(16, 185, 129, 0.5);
|
||||||
|
color: #6ee7b7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-camera-count {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-summary-text {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-detour-box {
|
||||||
|
background: rgba(16, 185, 129, 0.08);
|
||||||
|
border: 1px solid rgba(16, 185, 129, 0.25);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detour-title {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #34d399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detour-desc {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: #a7f3d0;
|
||||||
|
line-height: 1.35;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-detour-apply {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(16, 185, 129, 0.2);
|
||||||
|
border: 1px solid rgba(16, 185, 129, 0.4);
|
||||||
|
color: #6ee7b7;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.18s ease;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-detour-apply:hover {
|
||||||
|
background: rgba(16, 185, 129, 0.35);
|
||||||
|
color: #ffffff;
|
||||||
|
box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Civic Privacy & Export */
|
||||||
|
.btn-pra-accent {
|
||||||
|
background: rgba(168, 85, 247, 0.15) !important;
|
||||||
|
border-color: rgba(168, 85, 247, 0.35) !important;
|
||||||
|
color: #d8b4fe !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-pra-accent:hover {
|
||||||
|
background: rgba(168, 85, 247, 0.28) !important;
|
||||||
|
border-color: rgba(168, 85, 247, 0.5) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-btn-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-btn-grid .btn-tool-item {
|
||||||
|
padding: 8px 8px;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Background Audio Guard */
|
||||||
|
.btn-bg-audio-active {
|
||||||
|
background: rgba(16, 185, 129, 0.2) !important;
|
||||||
|
border-color: rgba(16, 185, 129, 0.5) !important;
|
||||||
|
color: #34d399 !important;
|
||||||
|
box-shadow: 0 0 12px rgba(16, 185, 129, 0.25) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SB 34 PRA Modal */
|
||||||
|
.pra-dialog {
|
||||||
|
max-width: 780px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-modal-header {
|
||||||
|
border-bottom: 1px solid rgba(168, 85, 247, 0.25);
|
||||||
|
background: linear-gradient(180deg, rgba(20, 15, 35, 0.96) 0%, rgba(10, 15, 29, 0.94) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-pra {
|
||||||
|
background: rgba(168, 85, 247, 0.2);
|
||||||
|
border: 1px solid rgba(168, 85, 247, 0.5);
|
||||||
|
color: #d8b4fe;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.6px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-modal-body {
|
||||||
|
padding: 24px 28px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18px;
|
||||||
|
background: var(--bg-surface-elevated);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-input-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-input-grid .form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-input-grid .form-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-input-grid .form-input,
|
||||||
|
.pra-input-grid .form-select {
|
||||||
|
padding: 9px 12px;
|
||||||
|
background: rgba(15, 23, 42, 0.85);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #f1f5f9;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-input-grid .form-input:focus,
|
||||||
|
.pra-input-grid .form-select:focus {
|
||||||
|
border-color: #a855f7;
|
||||||
|
box-shadow: 0 0 10px rgba(168, 85, 247, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-actions-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-pra-generate {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
background: linear-gradient(135deg, #9333ea, #a855f7);
|
||||||
|
border: 1px solid #c084fc;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 4px 14px rgba(168, 85, 247, 0.35);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-pra-generate:hover {
|
||||||
|
background: linear-gradient(135deg, #a855f7, #c084fc);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 18px rgba(168, 85, 247, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-letter-container {
|
||||||
|
background: rgba(15, 23, 42, 0.95);
|
||||||
|
border: 1px solid rgba(168, 85, 247, 0.25);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-letter-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: rgba(30, 27, 50, 0.8);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-letter-tag {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
color: #c084fc;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-letter-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-letter-action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-letter-action:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.16);
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-letter-email {
|
||||||
|
background: rgba(56, 189, 248, 0.15);
|
||||||
|
border-color: rgba(56, 189, 248, 0.35);
|
||||||
|
color: #38bdf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-letter-email:hover {
|
||||||
|
background: rgba(56, 189, 248, 0.28);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-letter-textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 240px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #f1f5f9;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
resize: vertical;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-legal-notice {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: #94a3b8;
|
||||||
|
background: rgba(245, 158, 11, 0.08);
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.25);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pra-legal-notice strong {
|
||||||
|
color: #fbbf24;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.pra-modal-body {
|
||||||
|
padding: 16px 18px;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
.pra-input-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+140
-6
@@ -54,7 +54,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=25">
|
<link rel="stylesheet" href="css/styles.css?v=26">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -327,6 +327,11 @@
|
|||||||
<span>Satellite / Dark</span>
|
<span>Satellite / Dark</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button id="btn-corridor-heatmap" class="btn-hud-action" title="Toggle Surveillance Density Corridor Heatmap">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v20M2 12h20M4.93 4.93l14.14 14.14M4.93 19.07l14.14-14.14"/></svg>
|
||||||
|
<span>Heatmap</span>
|
||||||
|
</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">
|
||||||
<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>
|
<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>Fullscreen</span>
|
<span>Fullscreen</span>
|
||||||
@@ -401,6 +406,61 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pre-Trip Surveillance Commute Scanner -->
|
||||||
|
<div class="tools-group">
|
||||||
|
<div class="tools-group-label">PRE-TRIP ROUTE SCANNER</div>
|
||||||
|
<div class="route-scanner-container">
|
||||||
|
<select id="route-corridor-select" class="route-corridor-select" title="Select corridor to scan camera exposure">
|
||||||
|
<option value="">Choose Corridor to Scan...</option>
|
||||||
|
<option value="palmdale-blvd">Palmdale Blvd (SR-14 to 50th E) • 10 Cams</option>
|
||||||
|
<option value="10th-st-west">10th St West (Ave S to Ave L) • 8 Cams</option>
|
||||||
|
<option value="sierra-hwy">Sierra Hwy (Palmdale to Lancaster) • 5 Cams</option>
|
||||||
|
<option value="avenue-s">Avenue S (Cross-Town Corridor) • 4 Cams</option>
|
||||||
|
<option value="avenue-r-clean">Avenue R (Surveillance-Free Bypass) • 0 Cams</option>
|
||||||
|
</select>
|
||||||
|
<div id="route-scan-result" class="route-scan-result hidden">
|
||||||
|
<div class="scan-score-header">
|
||||||
|
<span class="scan-score-badge" id="scan-score-badge">EXPOSURE: HIGH</span>
|
||||||
|
<span class="scan-camera-count" id="scan-camera-count">10 Cameras</span>
|
||||||
|
</div>
|
||||||
|
<p class="scan-summary-text" id="scan-summary-text"></p>
|
||||||
|
<div class="scan-detour-box" id="scan-detour-box">
|
||||||
|
<strong class="detour-title">🟢 RECOMMENDED BYPASS:</strong>
|
||||||
|
<p id="scan-detour-text" class="detour-desc"></p>
|
||||||
|
<button type="button" id="btn-apply-route-bypass" class="btn-detour-apply">Show Clean Detour on Map</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Civic Privacy & SB 34 Plate Audit -->
|
||||||
|
<div class="tools-group">
|
||||||
|
<div class="tools-group-label">CIVIC PRIVACY & LEGAL AUDIT</div>
|
||||||
|
<button type="button" id="btn-open-pra-generator" class="btn-hud-action btn-tool-item btn-pra-accent" title="Generate formal California Public Records Act demand for license plate read logs">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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"/><polyline points="10 9 9 9 8 9"/></svg>
|
||||||
|
<span>Audit My Plate (CA SB 34 Request)</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" id="btn-toggle-bg-audio" class="btn-hud-action btn-tool-item" title="Keep radar audio active when using Apple Maps or with locked screen">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 18v-6a9 9 0 0 1 18 0v6"/><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"/></svg>
|
||||||
|
<span id="bg-audio-label">Background Audio Guard: OFF</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Open Data Export -->
|
||||||
|
<div class="tools-group">
|
||||||
|
<div class="tools-group-label">OPEN CIVIC DATA EXPORT</div>
|
||||||
|
<div class="export-btn-grid">
|
||||||
|
<button type="button" id="btn-export-geojson" class="btn-hud-action btn-tool-item" title="Download camera nodes as GeoJSON for Google Earth / QGIS">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||||
|
<span>Export GeoJSON</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" id="btn-export-csv" class="btn-hud-action btn-tool-item" title="Download camera nodes as CSV spreadsheet">
|
||||||
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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"/></svg>
|
||||||
|
<span>Export CSV</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="tools-group">
|
<div class="tools-group">
|
||||||
<div class="tools-group-label">DIAGNOSTICS & SYSTEM</div>
|
<div class="tools-group-label">DIAGNOSTICS & SYSTEM</div>
|
||||||
<button id="btn-test-alert" class="btn-hud-action btn-tool-item" title="Immediately test voice alert and radar siren">
|
<button id="btn-test-alert" class="btn-hud-action btn-tool-item" title="Immediately test voice alert and radar siren">
|
||||||
@@ -1332,6 +1392,7 @@
|
|||||||
<span>Choose Amount</span>
|
<span>Choose Amount</span>
|
||||||
<svg class="flock-svg flock-svg-xs" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
<svg class="flock-svg flock-svg-xs" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Viral Share Alternative -->
|
<!-- Viral Share Alternative -->
|
||||||
@@ -1365,6 +1426,79 @@
|
|||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<!-- ========================================================================
|
||||||
|
MODAL 5: CALIFORNIA SB 34 PUBLIC RECORDS ACT (CPRA) REQUEST GENERATOR
|
||||||
|
======================================================================== -->
|
||||||
|
<dialog id="pra-modal" class="agency-dialog pra-dialog" aria-labelledby="pra-modal-title">
|
||||||
|
<div class="modal-header pra-modal-header">
|
||||||
|
<div class="modal-header-left">
|
||||||
|
<span class="modal-badge badge-pra">CALIFORNIA SB 34 COMPLIANCE</span>
|
||||||
|
<h2 id="pra-modal-title" class="modal-agency-name">License Plate Audit Request Generator</h2>
|
||||||
|
<p class="modal-location-text">Exercise your legal right under Cal. Civil Code § 1798.90.5 & Gov. Code § 7920 to discover all police searches of your vehicle.</p>
|
||||||
|
</div>
|
||||||
|
<button class="modal-close-btn" id="pra-modal-close-btn" aria-label="Close Audit Generator">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body pra-modal-body">
|
||||||
|
<!-- Input Parameters -->
|
||||||
|
<div class="pra-input-grid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pra-plate-input" class="form-label">License Plate #:</label>
|
||||||
|
<input type="text" id="pra-plate-input" class="form-input" placeholder="e.g. 8ABC123" maxlength="8" style="text-transform: uppercase;">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pra-state-input" class="form-label">Registration State:</label>
|
||||||
|
<input type="text" id="pra-state-input" class="form-input" value="CA" maxlength="2" style="text-transform: uppercase;">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pra-agency-select" class="form-label">Target Law Enforcement Agency:</label>
|
||||||
|
<select id="pra-agency-select" class="form-select">
|
||||||
|
<option value="palmdale">City of Palmdale / Los Angeles County Sheriff's Dept (LASD)</option>
|
||||||
|
<option value="lancaster">City of Lancaster / LASD Lancaster Station</option>
|
||||||
|
<option value="bakersfield">Bakersfield Police Department</option>
|
||||||
|
<option value="chp">California Highway Patrol (CHP)</option>
|
||||||
|
<option value="riverside">Riverside County Sheriff / DA</option>
|
||||||
|
<option value="other">Other California Municipal Agency</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pra-requester-name" class="form-label">Your Name / Title:</label>
|
||||||
|
<input type="text" id="pra-requester-name" class="form-input" placeholder="Registered Vehicle Owner">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pra-actions-bar">
|
||||||
|
<button type="button" id="btn-generate-pra" class="btn-pra-generate">
|
||||||
|
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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"/><polyline points="10 9 9 9 8 9"/></svg>
|
||||||
|
<span>Generate Formal CPRA Letter</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Generated Letter Preview Area -->
|
||||||
|
<div class="pra-letter-container" id="pra-letter-container">
|
||||||
|
<div class="pra-letter-header">
|
||||||
|
<span class="pra-letter-tag">FORMAL LEGAL NOTICE</span>
|
||||||
|
<div class="pra-letter-actions">
|
||||||
|
<button type="button" id="btn-copy-pra-letter" class="btn-letter-action" title="Copy letter to clipboard">
|
||||||
|
<svg class="flock-svg flock-svg-xs" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||||||
|
<span>Copy Letter</span>
|
||||||
|
</button>
|
||||||
|
<a id="btn-email-pra-letter" href="#" class="btn-letter-action btn-letter-email" title="Open in your default email app">
|
||||||
|
<svg class="flock-svg flock-svg-xs" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
||||||
|
<span>Email Agency</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea id="pra-letter-text" class="pra-letter-textarea" readonly placeholder="Click 'Generate Formal CPRA Letter' above to view your ready-to-send records request letter..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Legal Summary Callout -->
|
||||||
|
<div class="pra-legal-notice">
|
||||||
|
<strong>LEGAL MANDATE:</strong> Under California Government Code § 7922.535, public agencies must respond within 10 calendar days. Under California SB 34 (Civ. Code § 1798.90.55), agencies are strictly prohibited from selling, sharing, or transferring ALPR surveillance data to out-of-state or federal immigration authorities.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<!-- Hidden Native Camera / File Input for Mobile Evidence Snapping -->
|
<!-- Hidden Native Camera / File Input for Mobile Evidence Snapping -->
|
||||||
<input type="file" id="camera-photo-file-input" accept="image/*" capture="environment" style="display: none;">
|
<input type="file" id="camera-photo-file-input" accept="image/*" capture="environment" style="display: none;">
|
||||||
|
|
||||||
@@ -1375,11 +1509,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=25"></script>
|
<script src="data/portals.js?v=26"></script>
|
||||||
<script src="js/icons.js?v=25"></script>
|
<script src="js/icons.js?v=26"></script>
|
||||||
<script src="js/auth.js?v=25"></script>
|
<script src="js/auth.js?v=26"></script>
|
||||||
<script src="js/radar.js?v=25"></script>
|
<script src="js/radar.js?v=26"></script>
|
||||||
<script src="js/app.js?v=25"></script>
|
<script src="js/app.js?v=26"></script>
|
||||||
|
|
||||||
<!-- Service Worker Instant Cache Invalidation -->
|
<!-- Service Worker Instant Cache Invalidation -->
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
+613
-1
@@ -105,6 +105,159 @@
|
|||||||
let reportPhotoPendingDataUrl = null;
|
let reportPhotoPendingDataUrl = null;
|
||||||
let contestPhotoPendingDataUrl = null;
|
let contestPhotoPendingDataUrl = null;
|
||||||
|
|
||||||
|
// --- V26 CIVIC UTILITY FEATURES STATE & CONSTANTS ---
|
||||||
|
let isCorridorHeatmapActive = false;
|
||||||
|
let heatmapLayerGroup = null;
|
||||||
|
let isBackgroundAudioActive = false;
|
||||||
|
let bgAudioElement = null;
|
||||||
|
let activeDetourLayerGroup = null;
|
||||||
|
|
||||||
|
const SURVEILLANCE_CORRIDORS = {
|
||||||
|
'palmdale-blvd': {
|
||||||
|
name: 'Palmdale Blvd Corridor (SR-14 to 50th St E)',
|
||||||
|
exposure: 'high',
|
||||||
|
exposureLabel: 'EXPOSURE: HIGH',
|
||||||
|
cams: 10,
|
||||||
|
summary: 'Heavy Flock ALPR density every 0.4 miles. Maximum surveillance exposure with real-time LASD hot-list scanning at 5th W, 10th W, 20th E, 30th E, and 47th E.',
|
||||||
|
detourTitle: '🟢 RECOMMENDED BYPASS: AVENUE R',
|
||||||
|
detourDesc: 'Take Avenue R eastbound instead — 0 Flock cameras detected, zero delay, connects directly from SR-14 to 50th St E.',
|
||||||
|
center: [34.5828, -118.0875],
|
||||||
|
zoom: 13,
|
||||||
|
corridorPath: [
|
||||||
|
[34.5828, -118.1525],
|
||||||
|
[34.5828, -118.1484],
|
||||||
|
[34.5828, -118.1360],
|
||||||
|
[34.5828, -118.1150],
|
||||||
|
[34.5828, -118.0900],
|
||||||
|
[34.5828, -118.0350]
|
||||||
|
],
|
||||||
|
bypassPath: [
|
||||||
|
[34.5700, -118.1525],
|
||||||
|
[34.5700, -118.0350]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'10th-st-west': {
|
||||||
|
name: '10th St West (Ave S to Ave L)',
|
||||||
|
exposure: 'high',
|
||||||
|
exposureLabel: 'EXPOSURE: HIGH',
|
||||||
|
cams: 8,
|
||||||
|
summary: 'Primary commercial arterial connecting Palmdale and Lancaster shopping centers. Monitored at Ave S, Rancho Vista Blvd, Technology Dr, Palmdale Blvd, Ave M, and Ave L.',
|
||||||
|
detourTitle: '🟢 RECOMMENDED BYPASS: 15TH ST WEST',
|
||||||
|
detourDesc: 'Use 15th St West for residential bypass or SR-14 Freeway for high-speed transit between Palmdale and Lancaster.',
|
||||||
|
center: [34.6325, -118.1470],
|
||||||
|
zoom: 12,
|
||||||
|
corridorPath: [
|
||||||
|
[34.5680, -118.1470],
|
||||||
|
[34.6019, -118.1470],
|
||||||
|
[34.6380, -118.1470],
|
||||||
|
[34.6970, -118.1470]
|
||||||
|
],
|
||||||
|
bypassPath: [
|
||||||
|
[34.5680, -118.1580],
|
||||||
|
[34.6019, -118.1580],
|
||||||
|
[34.6380, -118.1580],
|
||||||
|
[34.6970, -118.1580]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'sierra-hwy': {
|
||||||
|
name: 'Sierra Highway (Palmdale to Lancaster)',
|
||||||
|
exposure: 'medium',
|
||||||
|
exposureLabel: 'EXPOSURE: MODERATE',
|
||||||
|
cams: 5,
|
||||||
|
summary: 'Historical arterial monitored along railway crossings, Palmdale Blvd, Ave P, and Ave J.',
|
||||||
|
detourTitle: '🟢 RECOMMENDED BYPASS: DIVISION ST / 10TH ST EAST',
|
||||||
|
detourDesc: 'Division Street parallel run has minimal commercial surveillance and zero Flock pole hardware.',
|
||||||
|
center: [34.6325, -118.1250],
|
||||||
|
zoom: 12,
|
||||||
|
corridorPath: [
|
||||||
|
[34.5680, -118.1250],
|
||||||
|
[34.6019, -118.1250],
|
||||||
|
[34.6400, -118.1250],
|
||||||
|
[34.6970, -118.1250]
|
||||||
|
],
|
||||||
|
bypassPath: [
|
||||||
|
[34.5680, -118.1150],
|
||||||
|
[34.6019, -118.1150],
|
||||||
|
[34.6400, -118.1150],
|
||||||
|
[34.6970, -118.1150]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'avenue-s': {
|
||||||
|
name: 'Avenue S (Cross-Town Arterial)',
|
||||||
|
exposure: 'medium',
|
||||||
|
exposureLabel: 'EXPOSURE: MODERATE',
|
||||||
|
cams: 4,
|
||||||
|
summary: 'South Palmdale arterial monitored at 10th St W, 25th St E, and 47th St E.',
|
||||||
|
detourTitle: '🟢 RECOMMENDED BYPASS: AVENUE T / FORT TEJON RD',
|
||||||
|
detourDesc: 'Avenue T provides a scenic, camera-free southern cross-town connector with zero ALPR hardware.',
|
||||||
|
center: [34.5535, -118.1000],
|
||||||
|
zoom: 13,
|
||||||
|
corridorPath: [
|
||||||
|
[34.5535, -118.1700],
|
||||||
|
[34.5535, -118.0350]
|
||||||
|
],
|
||||||
|
bypassPath: [
|
||||||
|
[34.5420, -118.1700],
|
||||||
|
[34.5420, -118.0350]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'avenue-r-clean': {
|
||||||
|
name: 'Avenue R (Surveillance-Free Bypass)',
|
||||||
|
exposure: 'clean',
|
||||||
|
exposureLabel: 'EXPOSURE: CLEAN (0 CAMS)',
|
||||||
|
cams: 0,
|
||||||
|
summary: '🟢 Verified 0 detected Flock cameras. Optimal community-vetted clean civic route between West Palmdale and East Palmdale.',
|
||||||
|
detourTitle: '🟢 OPTIMAL DIRECT ROUTE',
|
||||||
|
detourDesc: 'No detour needed! You are currently viewing the designated surveillance-free bypass corridor.',
|
||||||
|
center: [34.5700, -118.0900],
|
||||||
|
zoom: 13,
|
||||||
|
corridorPath: [
|
||||||
|
[34.5700, -118.1700],
|
||||||
|
[34.5700, -118.0350]
|
||||||
|
],
|
||||||
|
bypassPath: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const AGENCY_PRA_CONTACTS = {
|
||||||
|
palmdale: {
|
||||||
|
name: "City of Palmdale & Los Angeles County Sheriff's Department",
|
||||||
|
recordsDept: "Records & Public Information Officer / City Clerk",
|
||||||
|
email: "cityclerk@cityofpalmdale.org",
|
||||||
|
address: "38300 Sierra Hwy, Palmdale, CA 93550"
|
||||||
|
},
|
||||||
|
lancaster: {
|
||||||
|
name: "City of Lancaster & LASD Lancaster Station",
|
||||||
|
recordsDept: "City Clerk / Public Records Division",
|
||||||
|
email: "cityclerk@cityoflancasterca.gov",
|
||||||
|
address: "44933 Fern Ave, Lancaster, CA 93534"
|
||||||
|
},
|
||||||
|
bakersfield: {
|
||||||
|
name: "Bakersfield Police Department",
|
||||||
|
recordsDept: "BPD Records Management Division",
|
||||||
|
email: "bpdrecords@bakersfieldcity.us",
|
||||||
|
address: "1601 Trujillo Ave, Bakersfield, CA 93301"
|
||||||
|
},
|
||||||
|
chp: {
|
||||||
|
name: "California Highway Patrol (Headquarters)",
|
||||||
|
recordsDept: "Office of Legal Affairs / CPRA Unit",
|
||||||
|
email: "pra@chp.ca.gov",
|
||||||
|
address: "P.O. Box 942898, Sacramento, CA 94298"
|
||||||
|
},
|
||||||
|
riverside: {
|
||||||
|
name: "Riverside County Sheriff's Office",
|
||||||
|
recordsDept: "Sheriff's Public Records Coordinator",
|
||||||
|
email: "sheriffrecords@riversidesheriff.org",
|
||||||
|
address: "4095 Lemon St, Riverside, CA 92501"
|
||||||
|
},
|
||||||
|
other: {
|
||||||
|
name: "Public Law Enforcement Agency",
|
||||||
|
recordsDept: "Public Records Act Officer",
|
||||||
|
email: "records@agency.gov",
|
||||||
|
address: "Municipal Police Department / Sheriff Office"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// --- DOM REFERENCES ---
|
// --- DOM REFERENCES ---
|
||||||
const el = {
|
const el = {
|
||||||
radarView: document.getElementById('radar-view'),
|
radarView: document.getElementById('radar-view'),
|
||||||
@@ -227,7 +380,33 @@
|
|||||||
btnCloseTools: document.getElementById('btn-close-tools'),
|
btnCloseTools: document.getElementById('btn-close-tools'),
|
||||||
toolsOverlay: document.getElementById('tools-overlay'),
|
toolsOverlay: document.getElementById('tools-overlay'),
|
||||||
btnDesktopToolsMenu: document.getElementById('btn-desktop-tools-menu'),
|
btnDesktopToolsMenu: document.getElementById('btn-desktop-tools-menu'),
|
||||||
controlsToolsMenu: document.getElementById('controls-tools-menu')
|
controlsToolsMenu: document.getElementById('controls-tools-menu'),
|
||||||
|
|
||||||
|
// Civic Utility Features (v26)
|
||||||
|
btnCorridorHeatmap: document.getElementById('btn-corridor-heatmap'),
|
||||||
|
routeCorridorSelect: document.getElementById('route-corridor-select'),
|
||||||
|
routeScanResult: document.getElementById('route-scan-result'),
|
||||||
|
scanScoreBadge: document.getElementById('scan-score-badge'),
|
||||||
|
scanCameraCount: document.getElementById('scan-camera-count'),
|
||||||
|
scanSummaryText: document.getElementById('scan-summary-text'),
|
||||||
|
scanDetourBox: document.getElementById('scan-detour-box'),
|
||||||
|
scanDetourText: document.getElementById('scan-detour-text'),
|
||||||
|
btnApplyRouteBypass: document.getElementById('btn-apply-route-bypass'),
|
||||||
|
btnOpenPraGenerator: document.getElementById('btn-open-pra-generator'),
|
||||||
|
btnToggleBgAudio: document.getElementById('btn-toggle-bg-audio'),
|
||||||
|
bgAudioLabel: document.getElementById('bg-audio-label'),
|
||||||
|
btnExportGeoJson: document.getElementById('btn-export-geojson'),
|
||||||
|
btnExportCsv: document.getElementById('btn-export-csv'),
|
||||||
|
praModal: document.getElementById('pra-modal'),
|
||||||
|
praModalCloseBtn: document.getElementById('pra-modal-close-btn'),
|
||||||
|
praPlateInput: document.getElementById('pra-plate-input'),
|
||||||
|
praStateInput: document.getElementById('pra-state-input'),
|
||||||
|
praAgencySelect: document.getElementById('pra-agency-select'),
|
||||||
|
praRequesterName: document.getElementById('pra-requester-name'),
|
||||||
|
btnGeneratePra: document.getElementById('btn-generate-pra'),
|
||||||
|
btnCopyPraLetter: document.getElementById('btn-copy-pra-letter'),
|
||||||
|
btnEmailPraLetter: document.getElementById('btn-email-pra-letter'),
|
||||||
|
praLetterText: document.getElementById('pra-letter-text')
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- INITIALIZATION ---
|
// --- INITIALIZATION ---
|
||||||
@@ -1871,6 +2050,369 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// V26 CIVIC UTILITY FEATURES: SB 34 PRA, COMMUTE SCANNER, AUDIO GUARD, EXPORT
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
// --- SB 34 PUBLIC RECORDS ACT (CPRA) AUDIT GENERATOR ---
|
||||||
|
function openPraModal() {
|
||||||
|
if (!el.praModal) return;
|
||||||
|
generatePraLetter();
|
||||||
|
if (typeof el.praModal.showModal === 'function') {
|
||||||
|
el.praModal.showModal();
|
||||||
|
} else {
|
||||||
|
el.praModal.setAttribute('open', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePraModal() {
|
||||||
|
if (!el.praModal) return;
|
||||||
|
if (typeof el.praModal.close === 'function') {
|
||||||
|
el.praModal.close();
|
||||||
|
} else {
|
||||||
|
el.praModal.removeAttribute('open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePraLetter() {
|
||||||
|
const plate = (el.praPlateInput ? el.praPlateInput.value.trim().toUpperCase() : '') || '[INSERT PLATE #]';
|
||||||
|
const state = (el.praStateInput ? el.praStateInput.value.trim().toUpperCase() : 'CA') || 'CA';
|
||||||
|
const requester = (el.praRequesterName ? el.praRequesterName.value.trim() : '') || 'Registered Vehicle Owner / Concerned Citizen';
|
||||||
|
const agencyKey = el.praAgencySelect ? el.praAgencySelect.value : 'palmdale';
|
||||||
|
const agency = AGENCY_PRA_CONTACTS[agencyKey] || AGENCY_PRA_CONTACTS.palmdale;
|
||||||
|
const today = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||||
|
|
||||||
|
const letter = `Date: ${today}
|
||||||
|
|
||||||
|
TO:
|
||||||
|
${agency.recordsDept}
|
||||||
|
${agency.name}
|
||||||
|
${agency.address}
|
||||||
|
Email: ${agency.email}
|
||||||
|
|
||||||
|
FROM:
|
||||||
|
${requester}
|
||||||
|
Registered Vehicle Owner / Citizen Requester
|
||||||
|
|
||||||
|
SUBJECT: PUBLIC RECORDS ACT DEMAND & SB 34 ALPR AUDIT TRAIL DISCLOSURE
|
||||||
|
STATUTORY AUTHORITIES:
|
||||||
|
- California Public Records Act (Gov. Code § 7920.000 et seq.)
|
||||||
|
- California Automated License Plate Recognition Privacy Act (SB 34 / Civ. Code § 1798.90.5 et seq.)
|
||||||
|
- California Constitution, Article I, Section 3(b)
|
||||||
|
|
||||||
|
Dear Records Custodian,
|
||||||
|
|
||||||
|
Pursuant to the California Public Records Act (Cal. Gov. Code § 7920 et seq.) and California Civil Code § 1798.90.52, I hereby formally request inspection and copies of all public records and automated license plate reader (ALPR) data logs pertaining to the following vehicle:
|
||||||
|
|
||||||
|
VEHICLE LICENSE PLATE: ${plate}
|
||||||
|
REGISTRATION JURISDICTION: ${state}
|
||||||
|
TIME PERIOD: All records within the maximum statutory or contractual retention window (including but not limited to the past 30 to 365 days).
|
||||||
|
|
||||||
|
SPECIFIC RECORDS REQUESTED:
|
||||||
|
1. DETECTIONS & SCANS: All automated license plate reader (ALPR) records, timestamps, GPS coordinates, optical character recognition reads, and contextual camera photographs captured by any fixed pole-mounted or mobile camera operated, contracted, or leased by ${agency.name} (including all cameras manufactured by Flock Group Inc. / Flock Safety) containing the license plate ${plate}.
|
||||||
|
|
||||||
|
2. SYSTEM SEARCH & AUDIT LOGS: Under Cal. Civil Code § 1798.90.52, an ALPR operator MUST maintain a record of all system queries. Please produce the complete, unredacted audit trail and query log indicating:
|
||||||
|
(a) The date and time of every query or hot-list search performed for license plate ${plate};
|
||||||
|
(b) The name, agency, and badge/operator ID of each individual who initiated the search or query;
|
||||||
|
(c) The specific, articulated law enforcement purpose or case number entered by the operator for each search.
|
||||||
|
|
||||||
|
3. DATA SHARING & THIRD-PARTY TRANSFERS: Under California Senate Bill 34 (Civ. Code § 1798.90.55), public agencies are strictly prohibited from selling, sharing, or transferring ALPR data to unauthorized, out-of-state, or federal entities. Please provide all records showing whether reads of plate ${plate} have been shared with, accessed by, or transmitted to any external law enforcement agency, regional intelligence center, federal agency, or out-of-state entity.
|
||||||
|
|
||||||
|
4. ALPR USAGE & PRIVACY POLICY: The current, adopted ALPR usage and privacy policy required under Cal. Civil Code § 1798.90.51, including confirmation of public posting and date of last compliance review.
|
||||||
|
|
||||||
|
STATUTORY TIMELINE:
|
||||||
|
Under California Government Code § 7922.535, you have ten (10) calendar days from receipt of this request to notify me whether this request seeks copies of disclosable public records. If any portion of this request is denied, please cite the specific statutory exemption justifying the withholding and provide all non-exempt portions.
|
||||||
|
|
||||||
|
Please provide all responsive records in electronic format (CSV, PDF, or spreadsheet) to my contact email or mailing address.
|
||||||
|
|
||||||
|
Respectfully submitted,
|
||||||
|
|
||||||
|
${requester}
|
||||||
|
Vehicle Owner / CPRA Requester
|
||||||
|
Generated via FlockRadar Civic Transparency Network (flockradar.org)`;
|
||||||
|
|
||||||
|
if (el.praLetterText) {
|
||||||
|
el.praLetterText.value = letter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.btnEmailPraLetter) {
|
||||||
|
const mailSubject = encodeURIComponent(`CPRA Demand & SB 34 ALPR Audit Request: Plate ${plate}`);
|
||||||
|
const mailBody = encodeURIComponent(letter);
|
||||||
|
el.btnEmailPraLetter.href = `mailto:${agency.email}?subject=${mailSubject}&body=${mailBody}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return letter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- PRE-TRIP ROUTE SCANNER LOGIC ---
|
||||||
|
function handleRouteCorridorChange() {
|
||||||
|
if (!el.routeCorridorSelect || !el.routeScanResult) return;
|
||||||
|
const corridorId = el.routeCorridorSelect.value;
|
||||||
|
if (!corridorId || !SURVEILLANCE_CORRIDORS[corridorId]) {
|
||||||
|
el.routeScanResult.classList.add('hidden');
|
||||||
|
if (activeDetourLayerGroup && map && map.hasLayer(activeDetourLayerGroup)) {
|
||||||
|
map.removeLayer(activeDetourLayerGroup);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const c = SURVEILLANCE_CORRIDORS[corridorId];
|
||||||
|
el.routeScanResult.classList.remove('hidden');
|
||||||
|
|
||||||
|
if (el.scanScoreBadge) {
|
||||||
|
el.scanScoreBadge.className = `scan-score-badge exposure-${c.exposure}`;
|
||||||
|
el.scanScoreBadge.textContent = c.exposureLabel;
|
||||||
|
}
|
||||||
|
if (el.scanCameraCount) {
|
||||||
|
el.scanCameraCount.textContent = `${c.cams} Cameras Detected`;
|
||||||
|
}
|
||||||
|
if (el.scanSummaryText) {
|
||||||
|
el.scanSummaryText.textContent = c.summary;
|
||||||
|
}
|
||||||
|
if (el.scanDetourBox && el.scanDetourText) {
|
||||||
|
el.scanDetourBox.style.display = 'flex';
|
||||||
|
el.scanDetourText.textContent = c.detourDesc;
|
||||||
|
}
|
||||||
|
if (el.btnApplyRouteBypass) {
|
||||||
|
el.btnApplyRouteBypass.textContent = c.bypassPath ? 'Show Clean Bypass on Map' : 'Center Clean Corridor on Map';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyRouteBypassDetour() {
|
||||||
|
if (!el.routeCorridorSelect || !map) return;
|
||||||
|
const corridorId = el.routeCorridorSelect.value;
|
||||||
|
const c = SURVEILLANCE_CORRIDORS[corridorId];
|
||||||
|
if (!c) return;
|
||||||
|
|
||||||
|
if (activeDetourLayerGroup) {
|
||||||
|
map.removeLayer(activeDetourLayerGroup);
|
||||||
|
}
|
||||||
|
activeDetourLayerGroup = L.layerGroup();
|
||||||
|
|
||||||
|
// Red or amber surveillance corridor line
|
||||||
|
const color = c.exposure === 'high' ? '#ef4444' : (c.exposure === 'medium' ? '#f59e0b' : '#10b981');
|
||||||
|
const corridorLine = L.polyline(c.corridorPath, {
|
||||||
|
color: color,
|
||||||
|
weight: 7,
|
||||||
|
opacity: 0.9,
|
||||||
|
lineCap: 'round',
|
||||||
|
lineJoin: 'round',
|
||||||
|
className: c.exposure === 'high' ? 'leaflet-corridor-high' : ''
|
||||||
|
});
|
||||||
|
corridorLine.bindPopup(`<strong>${c.name}</strong><br>${c.exposureLabel}<br>${c.cams} Cameras Monitored`);
|
||||||
|
activeDetourLayerGroup.addLayer(corridorLine);
|
||||||
|
|
||||||
|
// Green clean bypass line
|
||||||
|
if (c.bypassPath) {
|
||||||
|
const bypassLine = L.polyline(c.bypassPath, {
|
||||||
|
color: '#10b981',
|
||||||
|
weight: 6,
|
||||||
|
opacity: 0.95,
|
||||||
|
lineCap: 'round',
|
||||||
|
lineJoin: 'round'
|
||||||
|
});
|
||||||
|
bypassLine.bindPopup(`<strong>${c.detourTitle}</strong><br>${c.detourDesc}`);
|
||||||
|
activeDetourLayerGroup.addLayer(bypassLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
activeDetourLayerGroup.addTo(map);
|
||||||
|
map.flyTo(c.center, c.zoom, { duration: 1.2 });
|
||||||
|
|
||||||
|
// Close tools drawer on mobile
|
||||||
|
if (window.innerWidth <= 900 && el.mapTopControls) {
|
||||||
|
el.mapTopControls.classList.remove('open-drawer');
|
||||||
|
if (el.toolsOverlay) el.toolsOverlay.classList.remove('visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast(`🟢 Route Bypass Map updated for ${c.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- BACKGROUND AUDIO DRIVING GUARD ---
|
||||||
|
function toggleBackgroundAudio() {
|
||||||
|
if (!isBackgroundAudioActive) {
|
||||||
|
if (!bgAudioElement) {
|
||||||
|
bgAudioElement = new Audio();
|
||||||
|
// 1-second inaudible 8kHz silent WAV loop
|
||||||
|
bgAudioElement.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA';
|
||||||
|
bgAudioElement.loop = true;
|
||||||
|
}
|
||||||
|
bgAudioElement.play().then(() => {
|
||||||
|
isBackgroundAudioActive = true;
|
||||||
|
if (el.bgAudioLabel) el.bgAudioLabel.textContent = 'Background Audio Guard: ON';
|
||||||
|
if (el.btnToggleBgAudio) el.btnToggleBgAudio.classList.add('btn-bg-audio-active');
|
||||||
|
|
||||||
|
if ('mediaSession' in navigator) {
|
||||||
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
|
title: 'FlockRadar Driving Guard: Active',
|
||||||
|
artist: 'FlockRadar Civic Radar',
|
||||||
|
album: 'Continuous ALPR Audio Alerts Active'
|
||||||
|
});
|
||||||
|
navigator.mediaSession.playbackState = 'playing';
|
||||||
|
}
|
||||||
|
|
||||||
|
requestWakeLock();
|
||||||
|
speakVoiceAlert("Background driving audio guard engaged. Audible radar alerts active over navigation.");
|
||||||
|
showToast("🟢 Background Audio Guard ON: Radar alerts active over Apple/Google Maps");
|
||||||
|
}).catch(err => {
|
||||||
|
console.warn("Background audio start error:", err);
|
||||||
|
showToast("Audio permission required to start background guard.");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (bgAudioElement) {
|
||||||
|
bgAudioElement.pause();
|
||||||
|
}
|
||||||
|
isBackgroundAudioActive = false;
|
||||||
|
if (el.bgAudioLabel) el.bgAudioLabel.textContent = 'Background Audio Guard: OFF';
|
||||||
|
if (el.btnToggleBgAudio) el.btnToggleBgAudio.classList.remove('btn-bg-audio-active');
|
||||||
|
if ('mediaSession' in navigator) {
|
||||||
|
navigator.mediaSession.playbackState = 'none';
|
||||||
|
}
|
||||||
|
showToast("Background Audio Guard disengaged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CORRIDOR SURVEILLANCE HEATMAP & DENSITY OVERLAY ---
|
||||||
|
function toggleCorridorHeatmap() {
|
||||||
|
if (!map) return;
|
||||||
|
isCorridorHeatmapActive = !isCorridorHeatmapActive;
|
||||||
|
|
||||||
|
if (isCorridorHeatmapActive) {
|
||||||
|
if (!heatmapLayerGroup) {
|
||||||
|
heatmapLayerGroup = L.layerGroup();
|
||||||
|
Object.keys(SURVEILLANCE_CORRIDORS).forEach(key => {
|
||||||
|
const c = SURVEILLANCE_CORRIDORS[key];
|
||||||
|
const color = c.exposure === 'high' ? '#ef4444' : (c.exposure === 'medium' ? '#f59e0b' : '#10b981');
|
||||||
|
const weight = c.exposure === 'high' ? 8 : (c.exposure === 'medium' ? 6 : 5);
|
||||||
|
const opacity = 0.85;
|
||||||
|
|
||||||
|
const polyline = L.polyline(c.corridorPath, {
|
||||||
|
color: color,
|
||||||
|
weight: weight,
|
||||||
|
opacity: opacity,
|
||||||
|
lineCap: 'round',
|
||||||
|
lineJoin: 'round',
|
||||||
|
className: c.exposure === 'high' ? 'leaflet-corridor-high' : ''
|
||||||
|
});
|
||||||
|
|
||||||
|
polyline.bindPopup(`
|
||||||
|
<div style="font-family: inherit; font-size: 0.82rem; color: #0f172a; padding: 4px;">
|
||||||
|
<strong style="color: ${color}; font-size: 0.9rem;">${c.name}</strong><br>
|
||||||
|
<div style="margin-top: 4px; font-weight: 600;">Status: ${c.exposureLabel}</div>
|
||||||
|
<div style="margin-top: 2px; color: #475569;">${c.cams} Flock Cameras Monitored</div>
|
||||||
|
<p style="margin: 6px 0 0; font-size: 0.76rem; color: #334155;">${c.summary}</p>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
heatmapLayerGroup.addLayer(polyline);
|
||||||
|
|
||||||
|
if (c.bypassPath) {
|
||||||
|
const bypassLine = L.polyline(c.bypassPath, {
|
||||||
|
color: '#10b981',
|
||||||
|
weight: 4,
|
||||||
|
opacity: 0.7,
|
||||||
|
dashArray: '5, 8'
|
||||||
|
});
|
||||||
|
bypassLine.bindPopup(`
|
||||||
|
<div style="font-family: inherit; font-size: 0.82rem; color: #0f172a; padding: 4px;">
|
||||||
|
<strong style="color: #10b981;">Clean Bypass Corridor</strong><br>
|
||||||
|
<div style="margin-top: 4px; color: #334155;">${c.detourDesc}</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
heatmapLayerGroup.addLayer(bypassLine);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
heatmapLayerGroup.addTo(map);
|
||||||
|
if (el.btnCorridorHeatmap) el.btnCorridorHeatmap.classList.add('active');
|
||||||
|
showToast("🔥 Surveillance Corridor Density Heatmap: ON");
|
||||||
|
} else {
|
||||||
|
if (heatmapLayerGroup && map.hasLayer(heatmapLayerGroup)) {
|
||||||
|
map.removeLayer(heatmapLayerGroup);
|
||||||
|
}
|
||||||
|
if (el.btnCorridorHeatmap) el.btnCorridorHeatmap.classList.remove('active');
|
||||||
|
showToast("Corridor Density Heatmap: OFF");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- OPEN CIVIC DATA EXPORT (GEOJSON & CSV) ---
|
||||||
|
function exportCameraData(format) {
|
||||||
|
if (!allCameras || allCameras.length === 0) {
|
||||||
|
showToast("No camera data loaded to export!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === 'geojson') {
|
||||||
|
const geojson = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
metadata: {
|
||||||
|
title: "FlockRadar Verified Camera Database",
|
||||||
|
generated: new Date().toISOString(),
|
||||||
|
jurisdiction: "Antelope Valley, California",
|
||||||
|
totalCameras: allCameras.length,
|
||||||
|
license: "Open Civic Data / Public Records Act"
|
||||||
|
},
|
||||||
|
features: allCameras.map(cam => ({
|
||||||
|
type: "Feature",
|
||||||
|
geometry: {
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [cam.lng, cam.lat]
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
id: cam.id,
|
||||||
|
name: cam.name || cam.crossStreets || "Flock Safety ALPR",
|
||||||
|
crossStreets: cam.crossStreets || "",
|
||||||
|
city: cam.city || "Palmdale",
|
||||||
|
state: cam.state || "CA",
|
||||||
|
agency: cam.agency || "City of Palmdale / LASD",
|
||||||
|
mountType: cam.mountType || "Pole Mounted Solar Falcon",
|
||||||
|
status: cam.isContested ? "Contested" : (cam.isCommunity ? "Community Reported" : "Verified Active"),
|
||||||
|
confirmations: cam.confirmedCount || 1,
|
||||||
|
captures30d: cam.captures30d || "N/A",
|
||||||
|
latitude: cam.lat,
|
||||||
|
longitude: cam.lng
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
const blob = new Blob([JSON.stringify(geojson, null, 2)], { type: 'application/geo+json' });
|
||||||
|
downloadBlob(blob, `flockradar_cameras_${new Date().toISOString().slice(0, 10)}.geojson`);
|
||||||
|
showToast(`Exported ${allCameras.length} cameras as GeoJSON!`);
|
||||||
|
} else if (format === 'csv') {
|
||||||
|
const headers = ["Camera_ID", "Location_Name", "Agency", "City", "State", "Latitude", "Longitude", "Mount_Type", "Status", "Confirmations"];
|
||||||
|
const rows = allCameras.map(cam => [
|
||||||
|
`"${(cam.id || '').replace(/"/g, '""')}"`,
|
||||||
|
`"${(cam.name || cam.crossStreets || '').replace(/"/g, '""')}"`,
|
||||||
|
`"${(cam.agency || '').replace(/"/g, '""')}"`,
|
||||||
|
`"${(cam.city || 'Palmdale').replace(/"/g, '""')}"`,
|
||||||
|
`"${(cam.state || 'CA').replace(/"/g, '""')}"`,
|
||||||
|
cam.lat,
|
||||||
|
cam.lng,
|
||||||
|
`"${(cam.mountType || 'Pole Mounted').replace(/"/g, '""')}"`,
|
||||||
|
`"${(cam.isContested ? 'Contested' : 'Verified Active').replace(/"/g, '""')}"`,
|
||||||
|
cam.confirmedCount || 1
|
||||||
|
]);
|
||||||
|
|
||||||
|
const csvContent = [headers.join(','), ...rows.map(r => r.join(','))].join('\r\n');
|
||||||
|
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
downloadBlob(blob, `flockradar_cameras_${new Date().toISOString().slice(0, 10)}.csv`);
|
||||||
|
showToast(`Exported ${allCameras.length} cameras as CSV spreadsheet!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadBlob(blob, filename) {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
// --- SURVEILLANCE ZONE HARDWARE PROOF MODAL ---
|
// --- SURVEILLANCE ZONE HARDWARE PROOF MODAL ---
|
||||||
function openCameraDetailModal(cam) {
|
function openCameraDetailModal(cam) {
|
||||||
if (!cam) return;
|
if (!cam) return;
|
||||||
@@ -2798,6 +3340,76 @@
|
|||||||
openCameraHardwareProof(camId);
|
openCameraHardwareProof(camId);
|
||||||
}, 150);
|
}, 150);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- V26 CIVIC UTILITY EVENT BINDINGS ---
|
||||||
|
// Corridor Heatmap Toggle
|
||||||
|
if (el.btnCorridorHeatmap) {
|
||||||
|
el.btnCorridorHeatmap.addEventListener('click', toggleCorridorHeatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-Trip Route Scanner
|
||||||
|
if (el.routeCorridorSelect) {
|
||||||
|
el.routeCorridorSelect.addEventListener('change', handleRouteCorridorChange);
|
||||||
|
}
|
||||||
|
if (el.btnApplyRouteBypass) {
|
||||||
|
el.btnApplyRouteBypass.addEventListener('click', applyRouteBypassDetour);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background Audio Guard
|
||||||
|
if (el.btnToggleBgAudio) {
|
||||||
|
el.btnToggleBgAudio.addEventListener('click', toggleBackgroundAudio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// California SB 34 PRA Request Generator
|
||||||
|
if (el.btnOpenPraGenerator) {
|
||||||
|
el.btnOpenPraGenerator.addEventListener('click', openPraModal);
|
||||||
|
}
|
||||||
|
if (el.praModalCloseBtn) {
|
||||||
|
el.praModalCloseBtn.addEventListener('click', closePraModal);
|
||||||
|
}
|
||||||
|
if (el.praModal) {
|
||||||
|
el.praModal.addEventListener('click', e => {
|
||||||
|
if (e.target === el.praModal) closePraModal();
|
||||||
|
});
|
||||||
|
el.praModal.addEventListener('cancel', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
closePraModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.btnGeneratePra) el.btnGeneratePra.addEventListener('click', generatePraLetter);
|
||||||
|
if (el.praPlateInput) el.praPlateInput.addEventListener('input', generatePraLetter);
|
||||||
|
if (el.praStateInput) el.praStateInput.addEventListener('input', generatePraLetter);
|
||||||
|
if (el.praAgencySelect) el.praAgencySelect.addEventListener('change', generatePraLetter);
|
||||||
|
if (el.praRequesterName) el.praRequesterName.addEventListener('input', generatePraLetter);
|
||||||
|
|
||||||
|
if (el.btnCopyPraLetter) {
|
||||||
|
el.btnCopyPraLetter.addEventListener('click', () => {
|
||||||
|
const text = el.praLetterText ? el.praLetterText.value : '';
|
||||||
|
if (text) {
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
playLogConfirmationChirp();
|
||||||
|
showToast("📋 Formal CPRA Demand Letter copied to clipboard!");
|
||||||
|
}).catch(() => {
|
||||||
|
showToast("Letter copied!");
|
||||||
|
});
|
||||||
|
} else if (el.praLetterText) {
|
||||||
|
el.praLetterText.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
showToast("📋 Letter copied to clipboard!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open Civic Data Export (GeoJSON & CSV)
|
||||||
|
if (el.btnExportGeoJson) {
|
||||||
|
el.btnExportGeoJson.addEventListener('click', () => exportCameraData('geojson'));
|
||||||
|
}
|
||||||
|
if (el.btnExportCsv) {
|
||||||
|
el.btnExportCsv.addEventListener('click', () => exportCameraData('csv'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
// Service Worker for FlockRadar Offline Driving & Live Updates
|
// Service Worker for FlockRadar Offline Driving & Live Updates
|
||||||
const CACHE_NAME = 'flockradar-v25';
|
const CACHE_NAME = 'flockradar-v26';
|
||||||
const ASSETS_TO_CACHE = [
|
const ASSETS_TO_CACHE = [
|
||||||
'./',
|
'./',
|
||||||
'./index.html',
|
'./index.html',
|
||||||
'./css/styles.css?v=25',
|
'./css/styles.css?v=26',
|
||||||
'./js/icons.js?v=25',
|
'./js/icons.js?v=26',
|
||||||
'./js/app.js?v=25',
|
'./js/app.js?v=26',
|
||||||
'./js/auth.js?v=25',
|
'./js/auth.js?v=26',
|
||||||
'./js/radar.js?v=25',
|
'./js/radar.js?v=26',
|
||||||
'./data/portals.js?v=25',
|
'./data/portals.js?v=26',
|
||||||
'./favicon.svg?v=25',
|
'./favicon.svg?v=26',
|
||||||
'./favicon.ico?v=25',
|
'./favicon.ico?v=26',
|
||||||
'./apple-touch-icon.png?v=25',
|
'./apple-touch-icon.png?v=26',
|
||||||
'./icon-192.png?v=25',
|
'./icon-192.png?v=26',
|
||||||
'./icon-512.png?v=25',
|
'./icon-512.png?v=26',
|
||||||
'./og-image.png?v=25',
|
'./og-image.png?v=26',
|
||||||
'./assets/img/logo.svg',
|
'./assets/img/logo.svg',
|
||||||
'./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