Klima: iPad-Polish (5 Aufträge) + Glossar-Tote-Links gefixt

- Bau-Karten als Icon-Spalte am iPad mit Tap-to-Expand (kw-card-detail-overlay)
- Top-Bar slim: geograsim-Schriftzug ausgeblendet, Logo 32 px
- Sim-Titel einzeilig (font-size 13 px, ellipsis, max-width 200 px)
- Toaster-Cards ~50% kleiner am iPad
- Playlist als Icon-only (Select auf 38 px gestaucht)
- ggs-overlay-card lokal mit max-height 92dvh + safe-area-padding
- Owned-Cards bekommen grünen Ring (kw-card-owned)
- GLOSSAR ergänzt: ppm + megawatt (waren tote Links im Tutorial)
- Atlas-Mail raus für DB-Eintrag + glossar.php Anker-Logik
- Quittung an Logistik (kann deployen)
This commit is contained in:
2026-05-02 15:25:37 +02:00
parent e61af43f9b
commit d6d646ff05
7 changed files with 828 additions and 155 deletions
+230 -3
View File
@@ -299,6 +299,18 @@
}
.ggs-card.unaffordable .ggs-badge.cost { background: var(--ggs-coral-light); color: var(--ggs-coral); }
/* Bau-Karte aktiv ausgewählt — kräftiges Grün (analog zu 3D-Placement-Modus).
In 2D ist der Kauf zwar einmalig, der grüne Blitz für ~350 ms gibt aber das
gleiche visuelle Feedback wie in 3D, wo die Auswahl bis zur Platzierung
bleibt. */
.ggs-card.kw-card-selected {
border-color: var(--ggs-moss) !important;
background: var(--ggs-moss-light) !important;
box-shadow: 0 0 0 3px rgba(106, 155, 86, 0.35), var(--ggs-shadow-md) !important;
transform: translateY(-1px);
}
.ggs-card.kw-card-selected .ggs-card-title { color: var(--ggs-moss-dark); }
.ggs-card-count {
margin-left: auto;
font-size: 11px;
@@ -424,6 +436,153 @@
font-size: 12px;
font-weight: 600;
}
/* ====================================================================
iPad / Touch-Polish — alle Spezial-Regeln, die nur bei pointer:coarse
greifen. Atlas-Header bleibt unangetastet (Atlas-Territorium); hier
nur Klima-lokale Overrides.
==================================================================== */
@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;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
.ggs-header-badge {
font-size: 10px !important;
white-space: nowrap;
}
/* Auftrag #4: Toaster ~50% kleiner (Container + Schrift) */
.ggs-event-notification {
padding: 4px 10px !important;
font-size: 11.5px !important;
line-height: 1.25 !important;
}
.ggs-event-notification .ev-icon {
font-size: 14px !important;
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-card {
display: flex !important;
flex-direction: column;
align-items: center;
justify-content: center;
width: 60px !important;
height: 60px !important;
margin: 0 auto 8px !important;
padding: 4px !important;
position: relative;
border-radius: 12px !important;
}
.ggs-card .ggs-card-header { gap: 0 !important; margin: 0 !important; }
.ggs-card .ggs-card-icon { font-size: 28px !important; }
.ggs-card .ggs-card-title,
.ggs-card .ggs-card-desc,
.ggs-card .ggs-card-badges,
.ggs-card .ggs-card-demolish { display: none !important; }
.ggs-card .ggs-card-count {
position: absolute;
top: 2px; right: 2px;
margin: 0 !important;
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);
}
}
/* Detail-Modal für Bau-Karten (Tap-to-Expand auf Touch-Geräten). */
.kw-card-detail-overlay {
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;
}
.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-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-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;
}
/* 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;
padding-bottom: max(var(--ggs-gap-xl), env(safe-area-inset-bottom));
}
</style>
<script src="engine.js"></script>
<script src="audio.js"></script>
@@ -839,7 +998,9 @@ const GLOSSAR = {
bevoelkerung: { title:'Bevölkerung', text:'Die Menschen deiner Inselstadt. Sie zahlen Steuern (Einnahmen) und brauchen Strom. Wenn die Insel unbewohnbar wird (Überflutung, Stromausfall), wandern sie ab.', unit:'in Personen' },
strom: { title:'Stromversorgung', text:'Dein Strombedarf wächst mit der Bevölkerung: ca. 1 MW pro 1.000 Einwohner. Fehlt Strom, wird es kalt — die Leute fällen Bäume und verbrennen Holz, was CO₂ freisetzt.', unit:'Leistung in MW (Megawatt)' },
co2: { title:'CO₂ (Kohlendioxid)', text:'Ein farbloses Gas, das beim Verbrennen von Kohle, Öl und Gas entsteht. Es verstärkt den Treibhauseffekt — der wichtigste Treiber des Klimawandels.', unit:'in ppm (parts per million)', link:'https://de.wikipedia.org/wiki/Kohlenstoffdioxid' },
ppm: { title:'ppm (parts per million)', text:'Anteilseinheit: 1 ppm bedeutet ein Teilchen pro Million. Bei CO₂ misst man, wie viele CO₂-Moleküle pro Million Luftmoleküle in der Atmosphäre sind. Vorindustriell ~280 ppm, heute ~425 ppm.', unit:'1 ppm = 1 Teilchen / 1.000.000', link:'https://de.wikipedia.org/wiki/Parts_per_million' },
megatonne: { title:'Megatonne (Mt)', text:'Eine Million Tonnen. Österreich stößt etwa 70 Mt CO₂ pro Jahr aus.', unit:'1 Mt = 1.000.000 Tonnen' },
megawatt: { title:'Megawatt (MW)', text:'Einheit der elektrischen Leistung. 1 MW = 1 Million Watt. Reicht für ca. 1.000 Haushalte, je nach Verbrauch. Ein modernes Windrad liefert 35 MW, eine große Solaranlage 1 MW oder mehr.', unit:'1 MW = 1.000.000 Watt', link:'https://de.wikipedia.org/wiki/Watt_(Einheit)' },
temperatur: { title:'Globale Durchschnittstemperatur', text:'Seit der Industrialisierung (~1850) ist sie um etwa 1,2 °C gestiegen. Das Pariser Abkommen will den Anstieg auf 1,5 °C begrenzen.', unit:'°C', link:'https://de.wikipedia.org/wiki/Globale_Erwärmung' },
meeresspiegel:{ title:'Meeresspiegel', text:'Steigt durch schmelzendes Eis und die Ausdehnung warmen Wassers. Seit 1900 rund 20 cm. Bis 2100 möglicherweise über 1 m.', unit:'in cm über Startniveau', link:'https://de.wikipedia.org/wiki/Meeresspiegelanstieg' },
ueberflutung: { title:'Überflutete Fläche', text:'Prozentanteil der Siedlungen, die unter Wasser stehen, sobald der Meeresspiegel den Schutz (Deiche etc.) überschreitet.', unit:'in %' },
@@ -1160,9 +1321,11 @@ function showEventUI(ev) {
n.addEventListener('click', () => openInfoOverlay(infoKey));
}
vp.appendChild(n);
// Stapel-Obergrenze: maximal 6 sichtbare Toasts. Kommt ein siebter, fliegt
// der älteste sofort raus, damit die Canvas-Szene nicht zugedeckt wird.
const MAX_TOASTS = 6;
// Stapel-Obergrenze: 3 auf Desktop, 1 auf Touch-Geräten (iPad). Sobald ein
// weiterer Toast kommt, fliegt der älteste raus, damit die Canvas-Szene
// nicht zugedeckt wird.
const isTouch = window.matchMedia && window.matchMedia('(pointer: coarse)').matches;
const MAX_TOASTS = isTouch ? 1 : 3;
while (vp.childElementCount > MAX_TOASTS) {
const oldest = vp.firstElementChild;
if (!oldest) break;
@@ -1563,9 +1726,20 @@ function renderMeasures() {
+ '<div class="ggs-card-desc">'+m.description+'</div>'
+ '<div class="ggs-card-badges">'+badges.join('')+'</div>';
// Bereits gebaute Maßnahmen: grüner Ring (auf iPad sichtbarer Marker)
if (count > 0) card.classList.add('kw-card-owned');
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; }
card.classList.add('kw-card-selected');
setTimeout(() => card.classList.remove('kw-card-selected'), 350);
buyMeasure(m.id);
});
host.appendChild(card);
@@ -1579,6 +1753,59 @@ 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);
});
}
function renderStatus() {
// 3 Hauptwerte (CO₂/Temp/Meer stehen NUR in den Graphen)
document.querySelector('[data-stat="budget"]').textContent = fmtMoney(state.budget);