diff --git a/App/sims/klima/game-3d.html b/App/sims/klima/game-3d.html
index 2fd2287..8abc903 100644
--- a/App/sims/klima/game-3d.html
+++ b/App/sims/klima/game-3d.html
@@ -161,6 +161,52 @@
.kw-place-hint.visible { opacity: 1; }
.kw-place-hint.invalid { background: rgba(176,74,58,0.92); }
+/* Zentrales Pause-Zeichen über der Szene — erscheint bei Speed 0 */
+.kw-pause-center {
+ position: absolute;
+ top: 50%; left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 5;
+ pointer-events: auto;
+ cursor: pointer;
+ user-select: none;
+ text-align: center;
+ animation: kw-pause-fade-in 0.2s ease-out;
+}
+.kw-pause-btn {
+ width: 110px; height: 110px;
+ border-radius: 50%;
+ background: rgba(30, 40, 50, 0.58);
+ backdrop-filter: blur(10px) saturate(140%);
+ -webkit-backdrop-filter: blur(10px) saturate(140%);
+ color: #fff;
+ font-size: 56px;
+ line-height: 110px;
+ border: 3px solid rgba(255,255,255,0.78);
+ box-shadow: 0 6px 20px rgba(0,0,0,0.28);
+ transition: transform 0.15s var(--ggs-ease), background 0.18s;
+ text-indent: 8px; /* optische Mitte für die ▶-Spitze */
+}
+.kw-pause-center:hover .kw-pause-btn {
+ transform: scale(1.08);
+ background: rgba(74, 124, 138, 0.75);
+}
+.kw-pause-label {
+ margin-top: 10px;
+ display: inline-block;
+ background: rgba(30, 40, 50, 0.72);
+ color: #fff;
+ padding: 5px 12px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0.02em;
+}
+@keyframes kw-pause-fade-in {
+ from { opacity: 0; transform: translate(-50%, -50%) scale(0.85); }
+ to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
+}
+
/* „Bau beenden"-Karte — kompakt, kontrastfarben, nur während Placement */
.kw-exit-card {
display: flex;
@@ -579,6 +625,10 @@
von Seitenrand zu Seitenrand — Panels schweben als Glasflächen drüber) -->
Klicke zum Platzieren
+
+
▶
+
Pause — klicken zum Fortsetzen
+
@@ -2058,6 +2108,7 @@ function renderUI() {
renderGraphs();
renderAchievements();
renderTimeline();
+ if (typeof updatePauseCenter === 'function') updatePauseCenter();
// Szene wird aus der Frame-Loop gezeichnet (sceneRender), hier nicht
// nötig — anders als im 2D-Canvas-Renderer, der jedes State-Update
// synchron neu zeichnen musste.
@@ -2781,18 +2832,19 @@ function makeCoalPlant() {
function makeAirport() {
const g = new THREE.Group();
// Plateau-Sockel: hebt den Flughafen sichtbar aus dem Terrain und
- // verhindert, dass Teile am Hang im Berg verschwinden. Zwei-Schichten-
- // Look (Unterkante etwas breiter, Oberkante heller).
+ // verhindert, dass Teile am Hang im Berg verschwinden. Länge bleibt
+ // knapp innerhalb der Runway (2.4), Z-Richtung ist nach +Z versetzt,
+ // um Terminal + Heli-Pad abzudecken (sitzen beide bei z≈0.55).
const plateauBase = new THREE.Mesh(
- new THREE.BoxGeometry(3.4, 0.20, 1.55),
+ new THREE.BoxGeometry(2.55, 0.20, 1.25),
new THREE.MeshStandardMaterial({ color: 0x50555a, roughness: 1 })
);
- plateauBase.position.y = 0.10; g.add(plateauBase);
+ plateauBase.position.set(0, 0.10, 0.28); g.add(plateauBase);
const plateauTop = new THREE.Mesh(
- new THREE.BoxGeometry(3.3, 0.04, 1.48),
+ new THREE.BoxGeometry(2.48, 0.04, 1.18),
new THREE.MeshStandardMaterial({ color: 0x787d82, roughness: 1 })
);
- plateauTop.position.y = 0.22; g.add(plateauTop);
+ plateauTop.position.set(0, 0.22, 0.28); g.add(plateauTop);
// Alle Airport-Strukturen um LIFT anheben (sitzen jetzt auf der Plateau-Kante)
const LIFT = 0.24;
@@ -4053,11 +4105,65 @@ function syncCitizenBeach() {
}
}
+// --- Wolken am Himmel ---
+// Ziehen als Dunst-Gruppen quer über die Insel, eher Richtung Vulkan
+// (z-Drift nach hinten). Low-Poly-Sphären, transluzent, ohne Schatten —
+// pro Wolke eigener Zyklus und eigenes Höhenlevel.
+function makeCloud() {
+ const g = new THREE.Group();
+ const mat = new THREE.MeshBasicMaterial({
+ color: 0xffffff, transparent: true, opacity: 0.72, depthWrite: false
+ });
+ // Drei bis fünf gestapelte Kugeln für Dunst-Silhouette
+ const bumps = 3 + Math.floor(Math.random() * 3);
+ for (let i = 0; i < bumps; i++) {
+ const r = 0.9 + Math.random() * 0.9;
+ const sphere = new THREE.Mesh(new THREE.SphereGeometry(r, 8, 6), mat);
+ sphere.position.set(
+ (i - bumps / 2) * 1.1 + (Math.random() - 0.5) * 0.4,
+ (Math.random() - 0.5) * 0.4,
+ (Math.random() - 0.5) * 0.4
+ );
+ g.add(sphere);
+ }
+ return g;
+}
+
+// Vier Wolken, jede mit eigener Phase, Höhe und Flugdauer.
+const CLOUD_CYCLE = 60; // Sekunden pro Überquerung (Basis)
+const clouds = [];
+for (let i = 0; i < 4; i++) {
+ const c = makeCloud();
+ c.userData.phase = Math.random(); // 0..1 Startverschiebung
+ c.userData.height = 13 + Math.random() * 4; // y
+ c.userData.zStart = 4 + Math.random() * 6; // Insel-Vorder-Bereich
+ c.userData.zEnd = -16 - Math.random() * 4; // hinterm Vulkan
+ c.userData.scale = 0.8 + Math.random() * 0.5;
+ c.userData.speed = 0.8 + Math.random() * 0.6;
+ c.scale.setScalar(c.userData.scale);
+ ambientGroup.add(c);
+ clouds.push(c);
+}
+
+function updateClouds(tNow) {
+ for (const c of clouds) {
+ const cycle = CLOUD_CYCLE / c.userData.speed;
+ const u = ((tNow / cycle) + c.userData.phase) % 1; // 0..1 entlang der Bahn
+ // Von +X nach -X, Z interpoliert von zStart zu zEnd (Drift Richtung Vulkan)
+ c.position.set(
+ 50 - u * 100,
+ c.userData.height,
+ c.userData.zStart + (c.userData.zEnd - c.userData.zStart) * u
+ );
+ }
+}
+
// Zentrale Ambient-Update-Funktion (aus sceneRender pro Frame)
function updateAmbient(tNow, dt) {
updateBoats(tNow);
updateHeli(tNow);
updateVillage(tNow, dt);
+ updateClouds(tNow);
}
/* ============================================================
@@ -4108,9 +4214,26 @@ $$('.ggs-speed-btn').forEach(btn => {
const header = $('#header');
if (sp === 0) { state.phase = state.phase === 'playing' ? 'paused' : state.phase; header.classList.add('paused'); }
else { if (state.phase === 'paused') state.phase = 'playing'; header.classList.remove('paused'); state.lastTickMs = 0; }
+ updatePauseCenter();
});
});
+// Pause-Zeichen zentral in der Szene: sichtbar bei speed=0, sonst aus.
+// Klick entspricht dem Druck auf den 1×-Speed-Button.
+const pauseCenter = $('#pause-center');
+function updatePauseCenter() {
+ if (!pauseCenter) return;
+ const visible = state && state.speed === 0
+ && (state.phase === 'playing' || state.phase === 'paused' || state.phase === 'tutorial');
+ pauseCenter.style.display = visible ? '' : 'none';
+}
+if (pauseCenter) {
+ pauseCenter.addEventListener('click', () => {
+ const btn = document.querySelector('[data-speed="1"]');
+ if (btn) btn.click();
+ });
+}
+
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (e.key === 'p' || e.key === 'P') document.querySelector('[data-speed="0"]').click();