Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/panel-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
LiveNewsPanel,
LiveWebcamsPanel,
PinnedWebcamsPanel,
LiveIpCamerasPanel,
CIIPanel,
CascadePanel,
StrategicRiskPanel,
Expand Down Expand Up @@ -750,6 +751,10 @@ export class PanelLayoutManager implements AppModule {
this.ctx.panels['windy-webcams'] = new PinnedWebcamsPanel();
}

if (this.shouldCreatePanel('live-ip-cameras')) {
this.ctx.panels['live-ip-cameras'] = new LiveIpCamerasPanel();
}

this.createPanel('events', () => new TechEventsPanel('events', () => this.ctx.allNews));
this.createPanel('service-status', () => new ServiceStatusPanel());

Expand Down
414 changes: 414 additions & 0 deletions src/components/LiveIpCamerasPanel.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from './GdeltIntelPanel';
export * from './LiveNewsPanel';
export * from './LiveWebcamsPanel';
export * from './PinnedWebcamsPanel';
export * from './LiveIpCamerasPanel';
export * from './CIIPanel';
export * from './CascadePanel';
export * from './StrategicRiskPanel';
Expand Down
5 changes: 4 additions & 1 deletion src/config/panels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const FULL_PANELS: Record<string, PanelConfig> = {
'live-news': { name: 'Live News', enabled: true, priority: 1 },
'live-webcams': { name: 'Live Webcams', enabled: true, priority: 1 },
'windy-webcams': { name: 'Windy Live Webcam', enabled: false, priority: 2 },
'live-ip-cameras': { name: 'Live IP Cameras', enabled: true, priority: 1 },
insights: { name: 'AI Insights', enabled: true, priority: 1 },
'strategic-posture': { name: 'AI Strategic Posture', enabled: true, priority: 1 },
forecast: { name: 'AI Forecasts', enabled: true, priority: 1, ...(_desktop && { premium: 'locked' as const }) }, // trial: unlocked on web, locked on desktop
Expand Down Expand Up @@ -196,6 +197,7 @@ const TECH_PANELS: Record<string, PanelConfig> = {
'live-news': { name: 'Tech Headlines', enabled: true, priority: 1 },
'live-webcams': { name: 'Live Webcams', enabled: true, priority: 2 },
'windy-webcams': { name: 'Windy Live Webcam', enabled: false, priority: 2 },
'live-ip-cameras': { name: 'Live IP Cameras', enabled: false, priority: 2 },
insights: { name: 'AI Insights', enabled: true, priority: 1 },
ai: { name: 'AI/ML News', enabled: true, priority: 1 },
tech: { name: 'Technology', enabled: true, priority: 1 },
Expand Down Expand Up @@ -359,6 +361,7 @@ const FINANCE_PANELS: Record<string, PanelConfig> = {
'live-news': { name: 'Market Headlines', enabled: true, priority: 1 },
'live-webcams': { name: 'Live Webcams', enabled: true, priority: 2 },
'windy-webcams': { name: 'Windy Live Webcam', enabled: false, priority: 2 },
'live-ip-cameras': { name: 'Live IP Cameras', enabled: false, priority: 2 },
insights: { name: 'AI Market Insights', enabled: true, priority: 1 },
markets: { name: 'Live Markets', enabled: true, priority: 1 },
'stock-analysis': { name: 'Premium Stock Analysis', enabled: true, priority: 1, premium: 'locked' },
Expand Down Expand Up @@ -859,7 +862,7 @@ export const PANEL_CATEGORY_MAP: Record<string, { labelKey: string; panelKeys: s
// All variants — essential panels
core: {
labelKey: 'header.panelCatCore',
panelKeys: ['map', 'live-news', 'live-webcams', 'windy-webcams', 'insights', 'strategic-posture'],
panelKeys: ['map', 'live-news', 'live-webcams', 'windy-webcams', 'live-ip-cameras', 'insights', 'strategic-posture'],
},

// Full (geopolitical) variant
Expand Down
1 change: 1 addition & 0 deletions src/config/variants/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const STORAGE_KEYS = {
mapMode: 'worldmonitor-map-mode', // 'flat' | 'globe'
activeChannel: 'worldmonitor-active-channel',
webcamPrefs: 'worldmonitor-webcam-prefs',
ipCamPrefs: 'worldmonitor-ipcam-prefs',
} as const;

// Type definitions for variant configs
Expand Down
13 changes: 13 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
"geoHubs": "Geopolitical Hubs",
"liveWebcams": "Live Webcams",
"windyWebcams": "Windy Live Webcam",
"liveIpCameras": "Live IP Cameras",
"gulfEconomies": "Gulf Economies",
"gulfIndices": "Gulf Indices",
"gulfCurrencies": "Gulf Currencies",
Expand Down Expand Up @@ -695,6 +696,18 @@
"pinnedWebcams": {
"pinFromMap": "Pin a webcam from the map"
},
"ipCameras": {
"paused": "IP Cameras paused",
"offline": "Stream offline",
"regions": {
"all": "ALL",
"americas": "AMERICAS",
"europe": "EUROPE",
"asia": "ASIA",
"middleeast": "MIDEAST",
"africa": "AFRICA"
}
},
"positiveNewsFeed": {
"noStories": "No stories in this category yet"
},
Expand Down
93 changes: 93 additions & 0 deletions src/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -2890,6 +2890,99 @@ body.live-news-fullscreen-active .panels-grid > *:not(.live-news-fullscreen) {
}
}

/* ── Live IP Cameras Panel ──────────────────────────────────────────────── */

.ip-cam-grid {
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr;
}

.ip-cam-cell {
min-height: 140px;
}

.ip-cam-cell--offline .ip-cam-img {
opacity: 0.25;
filter: grayscale(1);
}

.ip-cam-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}

.ip-cam-single .ip-cam-img {
width: 100%;
max-height: 400px;
object-fit: contain;
background: #000;
}

.ip-cam-meta-bar {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: var(--panel-bg);
border-bottom: 1px solid var(--border);
font-size: 11px;
}

.ip-cam-meta-city {
font-weight: 600;
color: var(--text);
}

.ip-cam-meta-country {
color: var(--text-dim);
}

.ip-cam-type-badge {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 1px 5px;
border-radius: 3px;
background: rgba(255, 255, 255, 0.08);
color: var(--text-dim);
border: 1px solid var(--border);
}

.ip-cam-cell-label {
pointer-events: none;
}

.ip-cam-cell-label .ip-cam-type-badge {
pointer-events: none;
}

.ip-cam-offline-badge {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
background: rgba(0, 0, 0, 0.65);
z-index: 3;
font-size: 11px;
color: var(--text-dim);
}

@media (max-width: 768px) {
.ip-cam-grid {
grid-template-columns: 1fr 1fr;
grid-template-rows: auto;
}

.ip-cam-grid .ip-cam-cell:nth-child(n+5) {
display: none;
}
}



/* Mobile: make toolbars swipeable (horizontal scroll) */
Expand Down