Klima: iPad-Polish korrigiert — schmale Spalte mit Toggle (Modal raus)

- Default-Spalte 76 px, nur Icons 60x60 + grüner Ring für owned
- ℹ-Toggle oben → Spalte schiebt sich als Absolute-Overlay auf 280 px
  (Desktop-Card-Variante mit Titel/Desc/Badges)
- Tap auf Karte: Bau-Flow + Auto-Collapse zur Icon-Spalte
- Querformat-Hinweis bei Portrait + Touch (📱 Bitte drehen)
- kw-card-detail-overlay (Center-Modal) entfernt — Thomas wollte das nicht
- viewport-fit=cover + dvh + safe-area schon vorher drin
This commit is contained in:
2026-05-02 15:54:57 +02:00
parent d6d646ff05
commit 9c8d12c3cd
3 changed files with 335 additions and 251 deletions
@@ -0,0 +1,64 @@
---
von: klima
an: logistik
datum: 2026-05-02 17:00
status: neu
betrifft: iPad-Polish KORRIGIERT — schmale Spalte + Toggle-Expand (kein Modal)
---
# Korrektur
Hi Logistik,
erste Iteration war falsch verstanden: Ich hatte ein zentriertes Detail-
Modal eingebaut. Thomas wollte stattdessen ein **Inline-Expand des
rechten Panels**. Jetzt korrekt:
## Wie's jetzt funktioniert (iPad/Touch)
1. **Default:** Bau-Spalte ist nur 76 px breit, zeigt ausschließlich
Icons (60×60) plus oben einen -Toggle-Button.
2. **Tap auf :** Spalte schiebt sich als Overlay über den Canvas auf
280 px Breite und zeigt die volle Desktop-Card-Variante (Titel,
Beschreibung, Effekt-Badges).
3. **Tap auf eine Karte (auch in der Expand-Ansicht):** löst den Bau-
Flow aus (in 3D: Placement-Modus; in 2D: direkter Kauf) und klappt
die Spalte automatisch zur Icon-Variante zurück.
4. **Tap auf während Expand:** klappt manuell zurück.
5. **Bereits gebaute Maßnahmen** behalten den grünen Ring
(`kw-card-owned`).
KEIN zentriertes Detail-Modal mehr.
## Querformat erzwungen
Browser können Orientation nicht hart sperren, aber bei
`@media (orientation: portrait) and (pointer: coarse)` blendet sich
jetzt eine Vollbild-Karte mit „📱 Bitte Gerät drehen — Klimawächter
funktioniert im Querformat" ein. Sobald der/die Schüler:in dreht,
verschwindet sie automatisch.
## Layout-Trick
Das Expand-Panel ist `position: absolute` über der Sim-Layout-Grid-Zelle
(grid bleibt schmal). So muss der Canvas nicht erneut umgerechnet werden,
wenn der Spieler ein-/ausklappt. Animation: 180 ms Slide-in von rechts.
## Bonus
Glossar-Tote-Links (`ppm`, `megawatt`) waren noch offen vom letzten
Commit, schon gefixt. Atlas-Mail (15:45) raus mit Bitte um DB-Eintrag
+ Anker-Logik in `pages/glossar.php?term=...`.
## Wann fertig?
Jetzt — Logistik kann deployen. Wenn Thomas weitere Feinheiten am
iPad findet (Schmal-Spalte zu schmal, Expand-Breite, Toggle-Icon-
Wechsel), Bescheid sagen.
— Klima
## Bestätigen
- status: gelesen
- nach Deploy keine Rückmeldung nötig
+142 -126
View File
@@ -438,17 +438,19 @@
}
/* ====================================================================
iPad / Touch-Polish — alle Spezial-Regeln, die nur bei pointer:coarse
greifen. Atlas-Header bleibt unangetastet (Atlas-Territorium); hier
nur Klima-lokale Overrides.
iPad / Touch-Polish (überarbeitet 2026-05-02 16:50):
Schmale Bau-Spalte als Default, Expand-Toggle (-Icon) ganz oben.
Expand-Modus zeigt die volle Desktop-Card-Variante; sobald gebaut/
getippt wird, klappt's wieder zurück.
KEIN Center-Modal mehr (Vor-Version war falsch interpretiert).
Querformat-Hinweis bei Portrait, weil das Spiel im Portrait nicht
funktioniert.
==================================================================== */
@media (pointer: coarse) {
/* Auftrag #2: Top-Bar slim — Plattform-Logo + Schriftzug + Trenner raus */
.ggs-header-logo-text { display: none !important; }
.ggs-header-logo-icon { height: 32px !important; }
.ggs-header-separator { display: none !important; }
/* Auftrag #5: Sim-Titel kleiner + einzeilig (kein Wrap mehr) */
.ggs-header-module {
font-size: 13px !important;
white-space: nowrap;
@@ -461,7 +463,6 @@
white-space: nowrap;
}
/* Auftrag #4: Toaster ~50% kleiner (Container + Schrift) */
.ggs-event-notification {
padding: 4px 10px !important;
font-size: 11.5px !important;
@@ -472,18 +473,45 @@
margin-right: 4px !important;
}
/* Auftrag #3: Playlist als Icon-only — Select auf 38 px stauchen, das
Track-Label (Emoji + Text) wird abgeschnitten, das führende Emoji
bleibt sichtbar. Native Tap öffnet das Dropdown mit voller Beschriftung. */
.ggs-music-sel {
width: 38px !important;
padding-left: 4px !important;
padding-right: 0 !important;
}
/* Auftrag #1: Bau-Karten als kompakte Icon-Spalte. Zeigt nur Emoji +
count-Badge; Tap → Detail-Modal mit voller Info und Bauen-Button. */
#measures-container { padding: 6px 4px !important; }
/* Schmale Bau-Spalte: Grid-Spalte 3 wird auf 76 px gedrückt, ein
Toggle-Button oben klappt sie temporär zur Desktop-Variante aus.
Im expand-Modus liegt die Spalte als Overlay über dem Canvas, damit
Karte und Status nicht erneut umsortiert werden. */
.ggs-sim-layout {
grid-template-columns: 215px 1fr 76px !important;
position: relative; /* Ankerpunkt für das Expand-Overlay */
}
.ggs-zone-actions { padding: 4px !important; overflow-x: hidden; }
.ggs-zone-actions .ggs-panel { padding: 0 !important; }
.ggs-zone-actions .ggs-panel-title { display: none; }
#measures-container { padding: 0 !important; }
/* Toggle-Button () ganz oben */
.kw-build-toggle {
display: flex !important;
align-items: center; justify-content: center;
width: 60px; height: 44px; margin: 4px auto 8px;
border-radius: 12px;
border: 1.5px solid var(--ggs-border);
background: var(--ggs-bg-dark);
font-size: 18px;
cursor: pointer;
user-select: none;
}
.kw-build-toggle.is-open {
background: var(--ggs-fjord-light);
border-color: var(--ggs-fjord);
color: var(--ggs-fjord-dark);
}
/* Default: Karten kompakt (nur Emoji + count) */
.ggs-card {
display: flex !important;
flex-direction: column;
@@ -509,75 +537,77 @@
font-size: 10px;
padding: 1px 5px;
}
/* Bereits gebaute Maßnahmen: dezenter grüner Ring (kumulativ) */
.ggs-card.kw-card-owned {
border-color: var(--ggs-moss) !important;
box-shadow: 0 0 0 2px rgba(106, 155, 86, 0.30);
}
/* Expand-Modus: Spalte als Overlay über dem Canvas, volle Desktop-Card */
.ggs-zone-actions.kw-build-expanded {
position: absolute !important;
top: 0; right: 0; bottom: 0;
width: 280px !important;
background: var(--ggs-bg);
z-index: 50;
box-shadow: -6px 0 18px rgba(0,0,0,0.18);
padding: 8px !important;
overflow-y: auto;
animation: kw-build-slidein 0.18s ease-out;
}
.ggs-zone-actions.kw-build-expanded .ggs-panel-title { display: block; }
.ggs-zone-actions.kw-build-expanded #measures-container { padding: 0 6px 16px !important; }
.ggs-zone-actions.kw-build-expanded .kw-build-toggle { width: 100%; }
/* Im Expand-Modus rendern wir die Karten wie Desktop */
.ggs-zone-actions.kw-build-expanded .ggs-card {
display: block !important;
width: auto !important;
height: auto !important;
margin: 0 0 6px !important;
padding: 10px 12px !important;
border-radius: var(--ggs-radius-md) !important;
}
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-header { gap: var(--ggs-gap-sm) !important; }
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-icon { font-size: 20px !important; }
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-title,
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-desc,
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-badges,
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-demolish { display: revert !important; }
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-count { position: static; }
}
/* Detail-Modal für Bau-Karten (Tap-to-Expand auf Touch-Geräten). */
.kw-card-detail-overlay {
@keyframes kw-build-slidein {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Querformat erzwingen: bei Portrait + Touch ein blockierender Hinweis */
.kw-rotate-hint {
display: none;
position: fixed; inset: 0;
background: rgba(20, 30, 40, 0.55);
backdrop-filter: blur(4px);
display: flex; align-items: center; justify-content: center;
z-index: 10000;
padding: max(16px, env(safe-area-inset-bottom));
animation: ggs-overlay-in 0.18s ease-out;
background: var(--ggs-bg);
z-index: 99999;
align-items: center; justify-content: center;
flex-direction: column;
text-align: center;
padding: 24px;
}
.kw-card-detail-card {
background: var(--ggs-white);
border-radius: var(--ggs-radius-lg);
box-shadow: var(--ggs-shadow-lg);
padding: 18px 20px 14px;
max-width: 360px;
width: 100%;
max-height: 90dvh;
overflow-y: auto;
position: relative;
.kw-rotate-hint .kw-rh-icon {
font-size: 72px; margin-bottom: 16px;
animation: kw-rotate-pulse 1.6s ease-in-out infinite;
}
.kw-card-detail-card .kw-cd-close {
position: absolute; top: 8px; right: 8px;
width: 36px; height: 36px;
border-radius: 50%;
border: none; background: var(--ggs-bg-dark);
font-size: 16px; cursor: pointer;
.kw-rotate-hint h2 { font-size: 22px; margin-bottom: 8px; color: var(--ggs-fjord-dark); }
.kw-rotate-hint p { font-size: 15px; color: var(--ggs-text-muted); max-width: 320px; }
@media (orientation: portrait) and (pointer: coarse) {
.kw-rotate-hint { display: flex; }
}
.kw-card-detail-card .kw-cd-head {
display: flex; align-items: center; gap: 12px;
margin-bottom: 10px; padding-right: 36px;
}
.kw-card-detail-card .kw-cd-icon {
font-size: 36px; width: 56px; height: 56px;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
background: var(--ggs-fjord-light);
}
.kw-card-detail-card .kw-cd-title {
font-size: 18px; font-weight: 800; color: var(--ggs-fjord-dark);
}
.kw-card-detail-card .kw-cd-desc {
font-size: 14px; color: var(--ggs-text);
line-height: 1.55; margin-bottom: 12px;
}
.kw-card-detail-card .kw-cd-badges {
display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px;
}
.kw-card-detail-card .kw-cd-actions {
display: flex; gap: 8px;
position: sticky; bottom: 0;
background: linear-gradient(to top, var(--ggs-white) 60%, transparent);
padding-top: 8px;
}
.kw-card-detail-card .kw-cd-buy {
flex: 1;
min-height: 44px;
@keyframes kw-rotate-pulse {
0%, 100% { transform: rotate(-90deg); }
50% { transform: rotate(0deg); }
}
/* iPad-Modal-Pattern (Logistik-Konvention 15:05): klima-lokale Sicherung
für lange Overlays — dvh statt vh, safe-area-inset-bottom, scrollbar.
Zentrale Atlas-Komponente bleibt unangetastet. */
für lange Overlays — dvh statt vh, safe-area-inset-bottom, scrollbar. */
.ggs-overlay-card {
max-height: 92dvh;
overflow-y: auto;
@@ -589,6 +619,13 @@
</head>
<body>
<!-- Querformat-Hinweis (zeigt sich nur bei iPad/Touch in Portrait) -->
<div class="kw-rotate-hint" aria-live="polite">
<div class="kw-rh-icon">📱</div>
<h2>Bitte Gerät drehen</h2>
<p>Klimawächter funktioniert im Querformat. Dreh dein Tablet horizontal — dann geht's weiter.</p>
</div>
<!-- ============================================================
LOADING SCREEN
============================================================ -->
@@ -753,8 +790,10 @@
<!-- RECHTS: Maßnahmen ------------------------------------------- -->
<aside class="ggs-zone-actions">
<aside class="ggs-zone-actions" id="zone-actions">
<div class="ggs-panel">
<button class="kw-build-toggle" id="kw-build-toggle"
title="Details ein-/ausklappen" style="display:none"></button>
<div class="ggs-panel-title">Maßnahmen kaufen</div>
<div id="measures-container"></div>
</div>
@@ -1731,13 +1770,10 @@ function renderMeasures() {
card.addEventListener('click', (e) => {
if (e.target.closest('[data-demolish]')) return;
const isTouch = window.matchMedia && window.matchMedia('(pointer: coarse)').matches;
if (isTouch) {
// iPad-Pattern: Detail-Modal aufmachen, Bauen erst per Bestätigung
showCardDetail(m, canAfford, cost);
return;
}
if (!canAfford) { showEvent('💸', 'Zu teuer — spare Budget oder baue weniger.', 'bad'); return; }
// Touch-Spalte ist im Expand-Modus → Bau direkt auslösen, Spalte
// klappt automatisch zurück zur Icon-Variante.
collapseBuildPanel();
card.classList.add('kw-card-selected');
setTimeout(() => card.classList.remove('kw-card-selected'), 350);
buyMeasure(m.id);
@@ -1753,58 +1789,38 @@ function renderMeasures() {
});
}
/**
* showCardDetail(m, canAfford, cost) — Touch-Pattern für die Bau-Karten:
* Tap auf das Icon öffnet ein Modal mit Beschreibung + „Bauen"-Button.
* Beim Bestätigen läuft der normale buyMeasure-Flow, das Modal schließt
* automatisch. Wer „zu teuer" tappt, bekommt den Hinweis im Modal.
*/
function showCardDetail(m, canAfford, cost) {
// Existiert bereits ein Detail? Schließen, dann neues anzeigen.
const existing = document.querySelector('.kw-card-detail-overlay');
if (existing) existing.remove();
const ov = document.createElement('div');
ov.className = 'kw-card-detail-overlay';
const card = document.createElement('div');
card.className = 'kw-card-detail-card';
const badges = [];
badges.push('<span class="ggs-badge cost">💰 '+cost+' Mio</span>');
if (m.co2Reduction > 0) badges.push('<span class="ggs-badge effect">🍃 '+m.co2Reduction.toFixed(2)+' ppm/J</span>');
if (m.co2Reduction < 0) badges.push('<span class="ggs-badge danger">💨 +'+Math.abs(m.co2Reduction).toFixed(2)+' ppm/J</span>');
if (m.protection > 0) badges.push('<span class="ggs-badge protect">🛡️ +'+m.protection+' cm</span>');
if (m.powerOutput) badges.push('<span class="ggs-badge protect">⚡ +'+m.powerOutput+' MW</span>');
if (m.upkeep > 0) badges.push('<span class="ggs-badge cost">⚙ '+m.upkeep+' Mio/J</span>');
const buyLabel = canAfford ? 'Bauen (' + cost + ' Mio €)' : 'Zu teuer';
card.innerHTML =
'<button class="kw-cd-close" aria-label="Schließen">✕</button>'
+ '<div class="kw-cd-head">'
+ '<div class="kw-cd-icon">'+m.emoji+'</div>'
+ '<div class="kw-cd-title">'+m.name+'</div>'
+ '</div>'
+ '<div class="kw-cd-desc">'+m.description+'</div>'
+ '<div class="kw-cd-badges">'+badges.join('')+'</div>'
+ '<div class="kw-cd-actions">'
+ '<button class="ggs-btn ggs-btn-secondary" data-action="cancel">Abbrechen</button>'
+ '<button class="ggs-btn ggs-btn-primary kw-cd-buy" data-action="buy"'+(canAfford ? '' : ' disabled')+'>'+buyLabel+'</button>'
+ '</div>';
ov.appendChild(card);
document.body.appendChild(ov);
function close() { if (ov.parentNode) ov.parentNode.removeChild(ov); }
ov.addEventListener('click', (e) => { if (e.target === ov) close(); });
card.querySelector('.kw-cd-close').addEventListener('click', close);
card.querySelector('[data-action="cancel"]').addEventListener('click', close);
const buyBtn = card.querySelector('[data-action="buy"]');
buyBtn.addEventListener('click', () => {
if (!canAfford) return;
close();
buyMeasure(m.id);
});
/* Expand/Collapse-Logik der iPad-Bau-Spalte. Default: schmal mit Icons.
Tap auf den -Toggle oben → Spalte schiebt sich als Overlay über den
Canvas und zeigt die volle Desktop-Card-Variante. Tap auf eine Karte
(Bauen) oder erneuter Toggle-Tap → klappt wieder zur Icon-Spalte. */
function expandBuildPanel() {
const z = $('#zone-actions'); if (!z) return;
z.classList.add('kw-build-expanded');
const t = $('#kw-build-toggle'); if (t) { t.classList.add('is-open'); t.textContent = '×'; }
}
function collapseBuildPanel() {
const z = $('#zone-actions'); if (!z) return;
z.classList.remove('kw-build-expanded');
const t = $('#kw-build-toggle'); if (t) { t.classList.remove('is-open'); t.textContent = ''; }
}
(function setupBuildToggle() {
const t = document.getElementById('kw-build-toggle');
if (!t) return;
// Toggle nur auf Touch sichtbar; Desktop sieht ihn nie.
function refresh() {
const isTouch = window.matchMedia && window.matchMedia('(pointer: coarse)').matches;
t.style.display = isTouch ? '' : 'none';
}
refresh();
if (window.matchMedia) {
window.matchMedia('(pointer: coarse)').addEventListener('change', refresh);
}
t.addEventListener('click', () => {
const z = document.getElementById('zone-actions');
if (z && z.classList.contains('kw-build-expanded')) collapseBuildPanel();
else expandBuildPanel();
});
})();
function renderStatus() {
// 3 Hauptwerte (CO₂/Temp/Meer stehen NUR in den Graphen)
+129 -125
View File
@@ -576,17 +576,18 @@
}
/* ====================================================================
iPad / Touch-Polish — alle Spezial-Regeln, die nur bei pointer:coarse
greifen. Atlas-Header bleibt unangetastet (Atlas-Territorium); hier
nur Klima-lokale Overrides.
iPad / Touch-Polish (überarbeitet 2026-05-02 16:50):
Schmale Bau-Spalte als Default, Expand-Toggle (-Icon) ganz oben.
Expand-Modus zeigt die volle Desktop-Card-Variante; sobald gebaut/
getippt wird, klappt's wieder zurück.
KEIN Center-Modal mehr.
Querformat-Hinweis bei Portrait.
==================================================================== */
@media (pointer: coarse) {
/* Auftrag #2: Top-Bar slim — Plattform-Logo + Schriftzug + Trenner raus */
.ggs-header-logo-text { display: none !important; }
.ggs-header-logo-icon { height: 32px !important; }
.ggs-header-separator { display: none !important; }
/* Auftrag #5: Sim-Titel kleiner + einzeilig (kein Wrap mehr) */
.ggs-header-module {
font-size: 13px !important;
white-space: nowrap;
@@ -599,7 +600,6 @@
white-space: nowrap;
}
/* Auftrag #4: Toaster ~50% kleiner (Container + Schrift) */
.ggs-event-notification {
padding: 4px 10px !important;
font-size: 11.5px !important;
@@ -610,18 +610,38 @@
margin-right: 4px !important;
}
/* Auftrag #3: Playlist als Icon-only — Select auf 38 px stauchen, das
Track-Label (Emoji + Text) wird abgeschnitten, das führende Emoji
bleibt sichtbar. Native Tap öffnet das Dropdown mit voller Beschriftung. */
.ggs-music-sel {
width: 38px !important;
padding-left: 4px !important;
padding-right: 0 !important;
}
/* Auftrag #1: Bau-Karten als kompakte Icon-Spalte. Zeigt nur Emoji +
count-Badge; Tap → Detail-Modal mit voller Info und Bauen-Button. */
#measures-container { padding: 6px 4px !important; }
.ggs-sim-layout {
grid-template-columns: 215px 1fr 76px !important;
position: relative; /* Ankerpunkt für das Expand-Overlay */
}
.ggs-zone-actions { padding: 4px !important; overflow-x: hidden; }
.ggs-zone-actions .ggs-panel { padding: 0 !important; }
.ggs-zone-actions .ggs-panel-title { display: none; }
#measures-container { padding: 0 !important; }
.kw-build-toggle {
display: flex !important;
align-items: center; justify-content: center;
width: 60px; height: 44px; margin: 4px auto 8px;
border-radius: 12px;
border: 1.5px solid var(--ggs-border);
background: var(--ggs-bg-dark);
font-size: 18px;
cursor: pointer;
user-select: none;
}
.kw-build-toggle.is-open {
background: var(--ggs-fjord-light);
border-color: var(--ggs-fjord);
color: var(--ggs-fjord-dark);
}
.ggs-card {
display: flex !important;
flex-direction: column;
@@ -647,75 +667,72 @@
font-size: 10px;
padding: 1px 5px;
}
/* Bereits gebaute Maßnahmen: dezenter grüner Ring (kumulativ) */
.ggs-card.kw-card-owned {
border-color: var(--ggs-moss) !important;
box-shadow: 0 0 0 2px rgba(106, 155, 86, 0.30);
}
.ggs-zone-actions.kw-build-expanded {
position: absolute !important;
top: 0; right: 0; bottom: 0;
width: 280px !important;
background: var(--ggs-bg);
z-index: 50;
box-shadow: -6px 0 18px rgba(0,0,0,0.18);
padding: 8px !important;
overflow-y: auto;
animation: kw-build-slidein 0.18s ease-out;
}
.ggs-zone-actions.kw-build-expanded .ggs-panel-title { display: block; }
.ggs-zone-actions.kw-build-expanded #measures-container { padding: 0 6px 16px !important; }
.ggs-zone-actions.kw-build-expanded .kw-build-toggle { width: 100%; }
.ggs-zone-actions.kw-build-expanded .ggs-card {
display: block !important;
width: auto !important;
height: auto !important;
margin: 0 0 6px !important;
padding: 10px 12px !important;
border-radius: var(--ggs-radius-md) !important;
}
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-header { gap: var(--ggs-gap-sm) !important; }
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-icon { font-size: 20px !important; }
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-title,
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-desc,
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-badges,
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-demolish { display: revert !important; }
.ggs-zone-actions.kw-build-expanded .ggs-card .ggs-card-count { position: static; }
}
/* Detail-Modal für Bau-Karten (Tap-to-Expand auf Touch-Geräten). */
.kw-card-detail-overlay {
@keyframes kw-build-slidein {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.kw-rotate-hint {
display: none;
position: fixed; inset: 0;
background: rgba(20, 30, 40, 0.55);
backdrop-filter: blur(4px);
display: flex; align-items: center; justify-content: center;
z-index: 10000;
padding: max(16px, env(safe-area-inset-bottom));
animation: ggs-overlay-in 0.18s ease-out;
background: var(--ggs-bg);
z-index: 99999;
align-items: center; justify-content: center;
flex-direction: column;
text-align: center;
padding: 24px;
}
.kw-card-detail-card {
background: var(--ggs-white);
border-radius: var(--ggs-radius-lg);
box-shadow: var(--ggs-shadow-lg);
padding: 18px 20px 14px;
max-width: 360px;
width: 100%;
max-height: 90dvh;
overflow-y: auto;
position: relative;
.kw-rotate-hint .kw-rh-icon {
font-size: 72px; margin-bottom: 16px;
animation: kw-rotate-pulse 1.6s ease-in-out infinite;
}
.kw-card-detail-card .kw-cd-close {
position: absolute; top: 8px; right: 8px;
width: 36px; height: 36px;
border-radius: 50%;
border: none; background: var(--ggs-bg-dark);
font-size: 16px; cursor: pointer;
.kw-rotate-hint h2 { font-size: 22px; margin-bottom: 8px; color: var(--ggs-fjord-dark); }
.kw-rotate-hint p { font-size: 15px; color: var(--ggs-text-muted); max-width: 320px; }
@media (orientation: portrait) and (pointer: coarse) {
.kw-rotate-hint { display: flex; }
}
.kw-card-detail-card .kw-cd-head {
display: flex; align-items: center; gap: 12px;
margin-bottom: 10px; padding-right: 36px;
}
.kw-card-detail-card .kw-cd-icon {
font-size: 36px; width: 56px; height: 56px;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
background: var(--ggs-fjord-light);
}
.kw-card-detail-card .kw-cd-title {
font-size: 18px; font-weight: 800; color: var(--ggs-fjord-dark);
}
.kw-card-detail-card .kw-cd-desc {
font-size: 14px; color: var(--ggs-text);
line-height: 1.55; margin-bottom: 12px;
}
.kw-card-detail-card .kw-cd-badges {
display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px;
}
.kw-card-detail-card .kw-cd-actions {
display: flex; gap: 8px;
position: sticky; bottom: 0;
background: linear-gradient(to top, var(--ggs-white) 60%, transparent);
padding-top: 8px;
}
.kw-card-detail-card .kw-cd-buy {
flex: 1;
min-height: 44px;
@keyframes kw-rotate-pulse {
0%, 100% { transform: rotate(-90deg); }
50% { transform: rotate(0deg); }
}
/* iPad-Modal-Pattern (Logistik-Konvention 15:05): klima-lokale Sicherung
für lange Overlays — dvh statt vh, safe-area-inset-bottom, scrollbar.
Zentrale Atlas-Komponente bleibt unangetastet. */
.ggs-overlay-card {
max-height: 92dvh;
overflow-y: auto;
@@ -728,6 +745,13 @@
</head>
<body>
<!-- Querformat-Hinweis (zeigt sich nur bei iPad/Touch in Portrait) -->
<div class="kw-rotate-hint" aria-live="polite">
<div class="kw-rh-icon">📱</div>
<h2>Bitte Gerät drehen</h2>
<p>Klimawächter funktioniert im Querformat. Dreh dein Tablet horizontal — dann geht's weiter.</p>
</div>
<!-- ============================================================
LOADING SCREEN
============================================================ -->
@@ -902,8 +926,10 @@
<!-- RECHTS: Maßnahmen ------------------------------------------- -->
<aside class="ggs-zone-actions">
<aside class="ggs-zone-actions" id="zone-actions">
<div class="ggs-panel">
<button class="kw-build-toggle" id="kw-build-toggle"
title="Details ein-/ausklappen" style="display:none"></button>
<div class="ggs-panel-title">Maßnahmen kaufen</div>
<div id="measures-container"></div>
</div>
@@ -2066,13 +2092,10 @@ function renderMeasures() {
card.addEventListener('click', (e) => {
if (e.target.closest('[data-demolish]')) return;
const isTouch = window.matchMedia && window.matchMedia('(pointer: coarse)').matches;
if (isTouch) {
// iPad-Pattern: Detail-Modal öffnet sich, Bauen erst per Bestätigung.
showCardDetail(m, canAfford, cost);
return;
}
if (!canAfford) { showEvent('💸', 'Zu teuer — spare Budget oder baue weniger.', 'bad'); return; }
// Tap auf eine Karte beendet ggf. den Expand-Modus und löst dann
// den normalen Bau-Flow aus (in 3D: Placement-Modus starten).
collapseBuildPanel();
buyMeasure(m.id);
});
host.appendChild(card);
@@ -2086,56 +2109,37 @@ function renderMeasures() {
});
}
/**
* showCardDetail(m, canAfford, cost) — Touch-Pattern für die Bau-Karten:
* Tap auf das Icon öffnet ein Modal mit Beschreibung + „Bauen"-Button.
* In 3D startet „Bauen" den Placement-Modus; das Modal schließt vorher.
*/
function showCardDetail(m, canAfford, cost) {
const existing = document.querySelector('.kw-card-detail-overlay');
if (existing) existing.remove();
const ov = document.createElement('div');
ov.className = 'kw-card-detail-overlay';
const card = document.createElement('div');
card.className = 'kw-card-detail-card';
const badges = [];
badges.push('<span class="ggs-badge cost">💰 '+cost+' Mio</span>');
if (m.co2Reduction > 0) badges.push('<span class="ggs-badge effect">🍃 '+m.co2Reduction.toFixed(2)+' ppm/J</span>');
if (m.co2Reduction < 0) badges.push('<span class="ggs-badge danger">💨 +'+Math.abs(m.co2Reduction).toFixed(2)+' ppm/J</span>');
if (m.protection > 0) badges.push('<span class="ggs-badge protect">🛡️ +'+m.protection+' cm</span>');
if (m.powerOutput) badges.push('<span class="ggs-badge protect">⚡ +'+m.powerOutput+' MW</span>');
if (m.upkeep > 0) badges.push('<span class="ggs-badge cost">⚙ '+m.upkeep+' Mio/J</span>');
const buyLabel = canAfford ? 'Bauen (' + cost + ' Mio €)' : 'Zu teuer';
card.innerHTML =
'<button class="kw-cd-close" aria-label="Schließen">✕</button>'
+ '<div class="kw-cd-head">'
+ '<div class="kw-cd-icon">'+m.emoji+'</div>'
+ '<div class="kw-cd-title">'+m.name+'</div>'
+ '</div>'
+ '<div class="kw-cd-desc">'+m.description+'</div>'
+ '<div class="kw-cd-badges">'+badges.join('')+'</div>'
+ '<div class="kw-cd-actions">'
+ '<button class="ggs-btn ggs-btn-secondary" data-action="cancel">Abbrechen</button>'
+ '<button class="ggs-btn ggs-btn-primary kw-cd-buy" data-action="buy"'+(canAfford ? '' : ' disabled')+'>'+buyLabel+'</button>'
+ '</div>';
ov.appendChild(card);
document.body.appendChild(ov);
function close() { if (ov.parentNode) ov.parentNode.removeChild(ov); }
ov.addEventListener('click', (e) => { if (e.target === ov) close(); });
card.querySelector('.kw-cd-close').addEventListener('click', close);
card.querySelector('[data-action="cancel"]').addEventListener('click', close);
const buyBtn = card.querySelector('[data-action="buy"]');
buyBtn.addEventListener('click', () => {
if (!canAfford) return;
close();
buyMeasure(m.id);
});
/* Expand/Collapse-Logik der iPad-Bau-Spalte. Default: schmal mit Icons.
Tap auf den -Toggle oben → Spalte schiebt sich als Overlay über die
3D-Szene und zeigt die volle Desktop-Card-Variante. Tap auf eine Karte
(= Bauen) oder erneuter Toggle-Tap → klappt zur Icon-Spalte zurück. */
function expandBuildPanel() {
const z = $('#zone-actions'); if (!z) return;
z.classList.add('kw-build-expanded');
const t = $('#kw-build-toggle'); if (t) { t.classList.add('is-open'); t.textContent = '×'; }
}
function collapseBuildPanel() {
const z = $('#zone-actions'); if (!z) return;
z.classList.remove('kw-build-expanded');
const t = $('#kw-build-toggle'); if (t) { t.classList.remove('is-open'); t.textContent = ''; }
}
(function setupBuildToggle() {
const t = document.getElementById('kw-build-toggle');
if (!t) return;
function refresh() {
const isTouch = window.matchMedia && window.matchMedia('(pointer: coarse)').matches;
t.style.display = isTouch ? '' : 'none';
}
refresh();
if (window.matchMedia) {
window.matchMedia('(pointer: coarse)').addEventListener('change', refresh);
}
t.addEventListener('click', () => {
const z = document.getElementById('zone-actions');
if (z && z.classList.contains('kw-build-expanded')) collapseBuildPanel();
else expandBuildPanel();
});
})();
function renderStatus() {
// 3 Hauptwerte (CO₂/Temp/Meer stehen NUR in den Graphen)