feat: webcam preview images in detail panel + unique layer indicator colors
- Webcam clicks now show the live camera feed image from Windy API - Added img to DOMPurify allowed tags for preview rendering - Road Traffic layer dot → white (#e0e0e0), Webcams → grey (#909090) - All 12 layers now have visually distinct indicator colors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -410,8 +410,8 @@ a { color: var(--accent); text-decoration: none; }
|
||||
.mk-plane:hover { transform: scale(3); }
|
||||
.mk-traffic {
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255,160,0,0.5);
|
||||
background: radial-gradient(circle, rgba(255,160,0,0.5) 30%, rgba(255,160,0,0) 70%);
|
||||
border: 2px solid rgba(224,224,224,0.5);
|
||||
background: radial-gradient(circle, rgba(224,224,224,0.5) 30%, rgba(224,224,224,0) 70%);
|
||||
cursor: pointer; transition: transform 0.15s;
|
||||
}
|
||||
.mk-traffic:hover { transform: scale(1.3); }
|
||||
@@ -426,8 +426,8 @@ a { color: var(--accent); text-decoration: none; }
|
||||
.mk-navwarn:hover { transform: rotate(45deg) scale(1.8); }
|
||||
.mk-cam {
|
||||
width: 7px; height: 7px; border-radius: 50%;
|
||||
background: rgba(120,220,255,0.6);
|
||||
border: 1px solid rgba(120,220,255,0.4);
|
||||
background: rgba(144,144,144,0.7);
|
||||
border: 1px solid rgba(144,144,144,0.5);
|
||||
cursor: pointer; transition: transform 0.15s;
|
||||
}
|
||||
.mk-cam:hover { transform: scale(1.8); }
|
||||
@@ -556,7 +556,7 @@ a { color: var(--accent); text-decoration: none; }
|
||||
<div class="toggle on" style="--lc:#00c8ff"><div class="knob"></div></div>
|
||||
</div>
|
||||
<div class="layer-row" data-layer="traffic">
|
||||
<span class="layer-dot" style="background:#ffa000"></span>
|
||||
<span class="layer-dot" style="background:#e0e0e0"></span>
|
||||
<span class="layer-label">Road Traffic</span>
|
||||
<span class="layer-count" id="cntTraffic">0</span>
|
||||
<div class="toggle on" style="--lc:#ffa000"><div class="knob"></div></div>
|
||||
@@ -568,7 +568,7 @@ a { color: var(--accent); text-decoration: none; }
|
||||
<div class="toggle on" style="--lc:#ffc832"><div class="knob"></div></div>
|
||||
</div>
|
||||
<div class="layer-row" data-layer="webcams">
|
||||
<span class="layer-dot" style="background:#78dcff"></span>
|
||||
<span class="layer-dot" style="background:#909090"></span>
|
||||
<span class="layer-label">Webcams</span>
|
||||
<span class="layer-count" id="cntCams">0</span>
|
||||
<div class="toggle on" style="--lc:#78dcff"><div class="knob"></div></div>
|
||||
@@ -604,7 +604,7 @@ a { color: var(--accent); text-decoration: none; }
|
||||
|
||||
var $ = function(s) { return document.querySelector(s); };
|
||||
var $$ = function(s) { return document.querySelectorAll(s); };
|
||||
var safe = function(h) { return DOMPurify.sanitize(h, {ALLOWED_TAGS: ['tr','td','th','table','thead','tbody','a','span','div','br'], ALLOWED_ATTR: ['class','style','href','data-click','data-external']}); };
|
||||
var safe = function(h) { return DOMPurify.sanitize(h, {ALLOWED_TAGS: ['tr','td','th','table','thead','tbody','a','span','div','br','img'], ALLOWED_ATTR: ['class','style','href','data-click','data-external','src','alt']}); };
|
||||
|
||||
function fmtNum(n, d) { d = d != null ? d : 2; return n == null ? '\u2014' : Number(n).toLocaleString(undefined, {minimumFractionDigits: d, maximumFractionDigits: d}); }
|
||||
function fmtPct(n) { return n == null ? '\u2014' : (n >= 0 ? '+' : '') + fmtNum(n); }
|
||||
@@ -663,7 +663,7 @@ $('#drawerToggle').addEventListener('click', function() {
|
||||
});
|
||||
|
||||
// ════════════ DETAIL MODAL ════════════
|
||||
var COLORS = { earthquake: 'var(--red)', military: 'var(--blue)', conflict: 'var(--amber)', fire: 'var(--gold)', convergence: 'var(--purple)', nuclear: 'var(--green)', military_base: 'var(--teal)', port: 'var(--blue)', nuclear_facility: 'var(--gold)', pipeline: 'var(--purple)', exposure: '#e040fb', waterway: 'var(--accent)', cable: 'var(--blue)', airtraffic: '#00c8ff', traffic_city: '#ffa000', navwarn: '#ffc832', webcam: '#78dcff' };
|
||||
var COLORS = { earthquake: 'var(--red)', military: 'var(--blue)', conflict: 'var(--amber)', fire: 'var(--gold)', convergence: 'var(--purple)', nuclear: 'var(--green)', military_base: 'var(--teal)', port: 'var(--blue)', nuclear_facility: 'var(--gold)', pipeline: 'var(--purple)', exposure: '#e040fb', waterway: 'var(--accent)', cable: 'var(--blue)', airtraffic: '#00c8ff', traffic_city: '#e0e0e0', navwarn: '#ffc832', webcam: '#909090' };
|
||||
var LABELS = { earthquake: 'EARTHQUAKE', military: 'MILITARY AIRCRAFT', conflict: 'CONFLICT EVENT', fire: 'WILDFIRE CLUSTER', convergence: 'SIGNAL CONVERGENCE', nuclear: 'NUCLEAR MONITOR', military_base: 'MILITARY BASE', port: 'STRATEGIC PORT', nuclear_facility: 'NUCLEAR FACILITY', pipeline: 'PIPELINE', exposure: 'POPULATION EXPOSURE', waterway: 'STRATEGIC WATERWAY', cable: 'CABLE CORRIDOR', airtraffic: 'AIRCRAFT', traffic_city: 'TRAFFIC CONGESTION', navwarn: 'NAVIGATION WARNING', webcam: 'WEBCAM' };
|
||||
|
||||
function showDetail(type, d) {
|
||||
@@ -825,7 +825,6 @@ function showDetail(type, d) {
|
||||
title = d.title || 'Camera';
|
||||
subtitle = (d.city || '') + (d.country ? ', ' + d.country : '');
|
||||
fields = [
|
||||
['Title', d.title || '\u2014'],
|
||||
['City', d.city || '\u2014'],
|
||||
['Country', d.country || '\u2014'],
|
||||
['Status', d.status || '\u2014'],
|
||||
@@ -850,7 +849,12 @@ function showDetail(type, d) {
|
||||
'<div class="detail-title">' + esc(title) + '</div>' +
|
||||
(subtitle ? '<div class="detail-subtitle">' + esc(subtitle) + '</div>' : '');
|
||||
|
||||
var body = fields.map(function(f) {
|
||||
var body = '';
|
||||
// Webcam: show preview image at top of detail panel
|
||||
if (type === 'webcam' && d.preview_url) {
|
||||
body += '<img src="' + esc(d.preview_url) + '" style="width:100%;border-radius:6px;margin-bottom:10px;" alt="' + esc(d.title || 'Webcam') + '" onerror="this.style.display=\'none\'">';
|
||||
}
|
||||
body += fields.map(function(f) {
|
||||
return '<div class="df"><span class="df-l">' + esc(f[0]) + '</span><span class="df-v ' + (f[2] || '') + '">' + esc(String(f[1])) + '</span></div>';
|
||||
}).join('');
|
||||
|
||||
@@ -1044,7 +1048,6 @@ function showDrawerDetail(id) {
|
||||
title = d.title || 'Camera';
|
||||
subtitle = (d.city || '') + (d.country ? ', ' + d.country : '');
|
||||
fields = [
|
||||
['Title', d.title || '\u2014'],
|
||||
['City', d.city || '\u2014'],
|
||||
['Country', d.country || '\u2014'],
|
||||
['Status', d.status || '\u2014'],
|
||||
@@ -1072,7 +1075,11 @@ function showDrawerDetail(id) {
|
||||
'<div class="detail-title">' + esc(title) + '</div>' +
|
||||
(subtitle ? '<div class="detail-subtitle">' + esc(subtitle) + '</div>' : '');
|
||||
|
||||
var body = fields.map(function(f) {
|
||||
var body = '';
|
||||
if (type === 'webcam' && d.preview_url) {
|
||||
body += '<img src="' + esc(d.preview_url) + '" style="width:100%;border-radius:6px;margin-bottom:10px;" alt="' + esc(d.title || 'Webcam') + '" onerror="this.style.display=\'none\'">';
|
||||
}
|
||||
body += fields.map(function(f) {
|
||||
return '<div class="df"><span class="df-l">' + esc(f[0]) + '</span><span class="df-v ' + (f[2] || '') + '">' + esc(String(f[1])) + '</span></div>';
|
||||
}).join('');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user