Theme-Toggle als Schalter + Karten-Vollbild + Zoom-Begrenzung
- Theme-Toggle: Button durch Sun/Moon-Slider ersetzt (Dark Mode Standard) - Karte: Fullscreen-Overlay mit Expand-Icon, Escape zum Schließen - Karte: Zoom-Limits (minZoom:2, maxBounds, noWrap) - Karte: Button 'Orte erkennen' -> 'Orte einlesen', rechts ausgerichtet Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -3620,17 +3620,58 @@ a:hover {
|
||||
}
|
||||
|
||||
/* === Theme Toggle Button === */
|
||||
.theme-toggle-btn {
|
||||
font-size: 18px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
.theme-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.theme-switch-icon {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
opacity: 0.4;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.theme-switch.dark .theme-switch-moon,
|
||||
.theme-switch.light .theme-switch-sun {
|
||||
opacity: 1;
|
||||
}
|
||||
.theme-switch-track {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 22px;
|
||||
border-radius: 11px;
|
||||
background: var(--bg-tertiary, #1A2440);
|
||||
border: 1px solid var(--border, #1E2D45);
|
||||
transition: background 0.3s, border-color 0.3s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.theme-switch-knob {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent, #C8A851);
|
||||
box-shadow: 0 0 8px rgba(200, 168, 81, 0.3);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s;
|
||||
}
|
||||
/* Dark mode: knob right */
|
||||
.theme-switch.dark .theme-switch-knob {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
/* Light mode: knob left */
|
||||
.theme-switch.light .theme-switch-knob {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.theme-switch:hover .theme-switch-track {
|
||||
border-color: var(--accent, #C8A851);
|
||||
}
|
||||
.theme-switch:hover .theme-switch-knob {
|
||||
box-shadow: 0 0 12px rgba(200, 168, 81, 0.5);
|
||||
}
|
||||
|
||||
/* === Light Theme Sonderregeln === */
|
||||
@@ -4234,6 +4275,16 @@ select:focus-visible, textarea:focus-visible,
|
||||
}
|
||||
.map-card .card-header {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.card-header-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.map-stats {
|
||||
font-size: 12px;
|
||||
@@ -4405,3 +4456,90 @@ a.map-popup-article:hover {
|
||||
[data-theme="light"] .map-cluster span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Karten-Legende */
|
||||
.map-legend-ctrl {
|
||||
background: var(--bg-card);
|
||||
padding: 10px 14px;
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-md);
|
||||
font-size: 12px;
|
||||
font-family: var(--font-body);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.map-legend-ctrl strong {
|
||||
font-family: var(--font-title);
|
||||
font-size: 13px;
|
||||
}
|
||||
[data-theme="light"] .map-legend-ctrl {
|
||||
background: #fff;
|
||||
border-color: #ddd;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* SVG-Marker: kein Default-divIcon-Styling */
|
||||
.map-marker-svg {
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.map-marker-svg svg {
|
||||
filter: drop-shadow(1px 2px 3px rgba(0,0,0,0.35));
|
||||
}
|
||||
|
||||
/* Map Expand Button */
|
||||
.map-expand-btn {
|
||||
margin-left: auto;
|
||||
width: 32px;
|
||||
min-height: 32px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.map-expand-btn:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Map Fullscreen Overlay */
|
||||
.map-fullscreen-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10000;
|
||||
background: var(--bg-primary);
|
||||
flex-direction: column;
|
||||
}
|
||||
.map-fullscreen-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
.map-fullscreen-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
background: var(--bg-card);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.map-fullscreen-title {
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.map-fullscreen-stats {
|
||||
flex: 1;
|
||||
}
|
||||
.map-fullscreen-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
.map-fullscreen-container .leaflet-container {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,13 @@
|
||||
<h1 class="sr-only">AegisSight Monitor Dashboard</h1>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<button class="btn btn-secondary btn-small theme-toggle-btn" id="theme-toggle" onclick="ThemeManager.toggle()" title="Theme wechseln" aria-label="Theme wechseln">☼</button>
|
||||
<div class="theme-switch" id="theme-toggle" onclick="ThemeManager.toggle()" role="switch" aria-checked="true" aria-label="Dark Mode" title="Theme wechseln">
|
||||
<span class="theme-switch-icon theme-switch-sun">☀︎</span>
|
||||
<div class="theme-switch-track">
|
||||
<div class="theme-switch-knob"></div>
|
||||
</div>
|
||||
<span class="theme-switch-icon theme-switch-moon">☽</span>
|
||||
</div>
|
||||
<div class="header-user-info">
|
||||
<button class="header-user-btn" id="header-user-btn" aria-expanded="false" aria-haspopup="true">
|
||||
<span class="header-user" id="header-user"></span>
|
||||
@@ -275,7 +281,12 @@
|
||||
<div class="card-header">
|
||||
<div class="card-title">Geografische Verteilung</div>
|
||||
<span class="map-stats" id="map-stats"></span>
|
||||
<button class="btn btn-secondary btn-small" id="geoparse-btn" onclick="App.triggerGeoparse()" title="Orte aus Artikeln erkennen">Orte erkennen</button>
|
||||
<div class="card-header-actions">
|
||||
<button class="btn btn-secondary btn-small" id="geoparse-btn" onclick="App.triggerGeoparse()" title="Orte aus Artikeln einlesen">Orte einlesen</button>
|
||||
<button class="btn btn-secondary btn-small map-expand-btn" id="map-expand-btn" onclick="UI.toggleMapFullscreen()" title="Vollbild" aria-label="Karte im Vollbild anzeigen">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="map-container" id="map-container">
|
||||
<div class="map-empty" id="map-empty">Keine Orte erkannt</div>
|
||||
@@ -576,5 +587,17 @@
|
||||
<script src="/static/js/components.js?v=20260304h"></script>
|
||||
<script src="/static/js/layout.js?v=20260304h"></script>
|
||||
<script src="/static/js/app.js?v=20260304h"></script>
|
||||
|
||||
<!-- Map Fullscreen Overlay -->
|
||||
<div class="map-fullscreen-overlay" id="map-fullscreen-overlay">
|
||||
<div class="map-fullscreen-header">
|
||||
<div class="map-fullscreen-title">Geografische Verteilung</div>
|
||||
<span class="map-stats map-fullscreen-stats" id="map-fullscreen-stats"></span>
|
||||
<button class="btn btn-secondary btn-small" onclick="UI.toggleMapFullscreen()" title="Vollbild beenden" aria-label="Vollbild beenden">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" y1="10" x2="21" y2="3"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="map-fullscreen-container" id="map-fullscreen-container"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -36,8 +36,11 @@ const ThemeManager = {
|
||||
UI.updateMapTheme();
|
||||
},
|
||||
_updateIcon(theme) {
|
||||
const btn = document.getElementById('theme-toggle');
|
||||
if (btn) btn.textContent = theme === 'dark' ? '\u2600' : '\u263D';
|
||||
const el = document.getElementById('theme-toggle');
|
||||
if (!el) return;
|
||||
el.classList.remove('dark', 'light');
|
||||
el.classList.add(theme);
|
||||
el.setAttribute('aria-checked', theme === 'dark' ? 'true' : 'false');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -717,6 +717,9 @@ const UI = {
|
||||
this._map = L.map(container, {
|
||||
zoomControl: true,
|
||||
attributionControl: true,
|
||||
minZoom: 2,
|
||||
maxBounds: [[-85, -180], [85, 180]],
|
||||
maxBoundsViscosity: 1.0,
|
||||
}).setView([51.1657, 10.4515], 5); // Deutschland-Zentrum
|
||||
|
||||
this._applyMapTiles();
|
||||
@@ -839,7 +842,7 @@ const UI = {
|
||||
const tileUrl = 'https://tile.openstreetmap.de/{z}/{x}/{y}.png';
|
||||
const attribution = '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>';
|
||||
|
||||
L.tileLayer(tileUrl, { attribution, maxZoom: 18 }).addTo(this._map);
|
||||
L.tileLayer(tileUrl, { attribution, maxZoom: 18, noWrap: true }).addTo(this._map);
|
||||
},
|
||||
|
||||
updateMapTheme() {
|
||||
@@ -858,6 +861,60 @@ const UI = {
|
||||
}
|
||||
},
|
||||
|
||||
_mapFullscreen: false,
|
||||
_mapOriginalParent: null,
|
||||
|
||||
toggleMapFullscreen() {
|
||||
const overlay = document.getElementById('map-fullscreen-overlay');
|
||||
const fsContainer = document.getElementById('map-fullscreen-container');
|
||||
const mapContainer = document.getElementById('map-container');
|
||||
const statsEl = document.getElementById('map-stats');
|
||||
const fsStatsEl = document.getElementById('map-fullscreen-stats');
|
||||
|
||||
if (!this._mapFullscreen) {
|
||||
// Save original parent and height
|
||||
this._mapOriginalParent = mapContainer.parentElement;
|
||||
this._savedMapHeight = mapContainer.style.height || mapContainer.offsetHeight + 'px';
|
||||
|
||||
// Move entire map-container into fullscreen overlay
|
||||
fsContainer.appendChild(mapContainer);
|
||||
mapContainer.style.height = '100%';
|
||||
|
||||
if (statsEl && fsStatsEl) {
|
||||
fsStatsEl.textContent = statsEl.textContent;
|
||||
}
|
||||
overlay.classList.add('active');
|
||||
this._mapFullscreen = true;
|
||||
|
||||
// Escape key to close
|
||||
this._mapFsKeyHandler = (e) => { if (e.key === 'Escape') this.toggleMapFullscreen(); };
|
||||
document.addEventListener('keydown', this._mapFsKeyHandler);
|
||||
|
||||
setTimeout(() => { if (this._map) this._map.invalidateSize(); }, 100);
|
||||
} else {
|
||||
// Exit fullscreen: move map-container back to original parent
|
||||
overlay.classList.remove('active');
|
||||
if (this._mapOriginalParent) {
|
||||
this._mapOriginalParent.appendChild(mapContainer);
|
||||
}
|
||||
// Restore saved height
|
||||
mapContainer.style.height = this._savedMapHeight || '';
|
||||
|
||||
this._mapFullscreen = false;
|
||||
if (this._mapFsKeyHandler) {
|
||||
document.removeEventListener('keydown', this._mapFsKeyHandler);
|
||||
this._mapFsKeyHandler = null;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
[100, 300, 600].forEach(delay => {
|
||||
setTimeout(() => { if (self._map) self._map.invalidateSize(); }, delay);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_mapFsKeyHandler: null,
|
||||
|
||||
/**
|
||||
* HTML escapen.
|
||||
*/
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren