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:
claude-dev
2026-03-08 13:18:04 +01:00
Ursprung 7734eefd35
Commit bcad3e9f3c
4 geänderte Dateien mit 234 neuen und 13 gelöschten Zeilen

Datei anzeigen

@@ -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;
}

Datei anzeigen

@@ -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">&#9788;</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>

Datei anzeigen

@@ -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');
}
};

Datei anzeigen

@@ -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 = '&copy; <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.
*/