Tutorial: Resize per CSS-Scale statt width/height, OSM-Tiles wie echte App

Resize-Demo (Schritt 25):
- Nutzt jetzt CSS transform:scale() statt width/height-Aenderung
- GridStack wird gar nicht beruehrt, Kachel bleibt nach Demo
  exakt in Originalgroesse (kein Schrumpfen mehr)

Karte (Schritt 23):
- Verwendet jetzt tile.openstreetmap.de (gleiche Quelle wie echte App)
- Kein Dark/Light-Tile-Unterschied mehr (App nutzt auch nur einen Server)
- Tiles laden jetzt korrekt statt grauem Hintergrund

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Dev
2026-03-16 17:09:57 +01:00
Ursprung c2d08f460d
Commit a0f0315768
2 geänderte Dateien mit 27 neuen und 34 gelöschten Zeilen

Datei anzeigen

@@ -764,7 +764,7 @@
<script src="/static/js/api_network.js?v=20260316a"></script> <script src="/static/js/api_network.js?v=20260316a"></script>
<script src="/static/js/network-graph.js?v=20260316a"></script> <script src="/static/js/network-graph.js?v=20260316a"></script>
<script src="/static/js/app_network.js?v=20260316a"></script> <script src="/static/js/app_network.js?v=20260316a"></script>
<script src="/static/js/tutorial.js?v=20260316q"></script> <script src="/static/js/tutorial.js?v=20260316r"></script>
<script src="/static/js/chat.js?v=20260316f"></script> <script src="/static/js/chat.js?v=20260316f"></script>
<script>document.addEventListener("DOMContentLoaded",function(){Chat.init();Tutorial.init()});</script> <script>document.addEventListener("DOMContentLoaded",function(){Chat.init();Tutorial.init()});</script>

Datei anzeigen

@@ -374,14 +374,14 @@ const Tutorial = {
}).setView([53.545, 9.98], 13); }).setView([53.545, 9.98], 13);
// Tile-Layer (Theme-abhängig) // Tile-Layer (Theme-abhängig)
var isDark = !document.documentElement.getAttribute('data-theme') || document.documentElement.getAttribute('data-theme') !== 'light'; var isDark = document.documentElement.getAttribute('data-theme') !== 'light';
if (isDark) { if (isDark) {
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', {
attribution: '\u00a9 OpenStreetMap, \u00a9 CARTO', attribution: '\u00a9 OpenStreetMap, \u00a9 CARTO',
maxZoom: 19, maxZoom: 19,
}).addTo(this._demoMap); }).addTo(this._demoMap);
} else { } else {
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
attribution: '\u00a9 OpenStreetMap, \u00a9 CARTO', attribution: '\u00a9 OpenStreetMap, \u00a9 CARTO',
maxZoom: 19, maxZoom: 19,
}).addTo(this._demoMap); }).addTo(this._demoMap);
@@ -1519,19 +1519,17 @@ const Tutorial = {
mapDiv.style.cssText = 'width:100%;height:100%;min-height:400px;'; mapDiv.style.cssText = 'width:100%;height:100%;min-height:400px;';
fsContainer.appendChild(mapDiv); fsContainer.appendChild(mapDiv);
var isDark = !document.documentElement.getAttribute('data-theme') || document.documentElement.getAttribute('data-theme') !== 'light';
// Start weit herausgezoomt (Europa) // Start weit herausgezoomt (Europa)
this._demoMap = L.map(mapDiv, { this._demoMap = L.map(mapDiv, {
zoomControl: true, zoomControl: true,
attributionControl: true, attributionControl: true,
}).setView([51.0, 10.0], 5); }).setView([51.0, 10.0], 5);
var tileUrl = isDark // Gleiche Tile-Quelle wie die echte App (deutsche OSM-Kacheln)
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png' L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', {
: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'; attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
L.tileLayer(tileUrl, { maxZoom: 18,
attribution: '\u00a9 OpenStreetMap, \u00a9 CARTO', noWrap: true,
maxZoom: 19,
}).addTo(this._demoMap); }).addTo(this._demoMap);
// Marker (aber noch nicht sichtbar bei Zoom 5) // Marker (aber noch nicht sichtbar bei Zoom 5)
@@ -2013,12 +2011,14 @@ const Tutorial = {
this._els.cursor.classList.add('tutorial-cursor-resize'); this._els.cursor.classList.add('tutorial-cursor-resize');
await this._wait(200); await this._wait(200);
// Kachel vergrößern (visuell) // Rein visuelle Vergrößerung per CSS transform (kein width/height!)
// So bleibt GridStack komplett unberührt
tile.style.transition = 'none'; tile.style.transition = 'none';
tile.style.zIndex = '9002'; tile.style.zIndex = '9002';
var origW = tile.offsetWidth; tile.style.transformOrigin = 'top left';
var origH = tile.offsetHeight;
var self = this; var self = this;
var origW = rect.width;
var origH = rect.height;
var start = null; var start = null;
await new Promise(function(resolve) { await new Promise(function(resolve) {
@@ -2027,12 +2027,11 @@ const Tutorial = {
if (!start) start = ts; if (!start) start = ts;
var progress = Math.min((ts - start) / 1000, 1); var progress = Math.min((ts - start) / 1000, 1);
var eased = progress < 0.5 ? 4*progress*progress*progress : 1 - Math.pow(-2*progress+2,3)/2; var eased = progress < 0.5 ? 4*progress*progress*progress : 1 - Math.pow(-2*progress+2,3)/2;
var dx = expandX * eased; var scaleX = 1 + (expandX / origW) * eased;
var dy = expandY * eased; var scaleY = 1 + (expandY / origH) * eased;
tile.style.width = (origW + dx) + 'px'; tile.style.transform = 'scale(' + scaleX + ', ' + scaleY + ')';
tile.style.height = (origH + dy) + 'px'; self._els.cursor.style.left = (startX + expandX * eased) + 'px';
self._els.cursor.style.left = (startX + dx) + 'px'; self._els.cursor.style.top = (startY + expandY * eased) + 'px';
self._els.cursor.style.top = (startY + dy) + 'px';
if (progress < 1) requestAnimationFrame(frame); else resolve(); if (progress < 1) requestAnimationFrame(frame); else resolve();
} }
requestAnimationFrame(frame); requestAnimationFrame(frame);
@@ -2047,27 +2046,21 @@ const Tutorial = {
if (!start) start = ts; if (!start) start = ts;
var progress = Math.min((ts - start) / 700, 1); var progress = Math.min((ts - start) / 700, 1);
var eased = progress < 0.5 ? 4*progress*progress*progress : 1 - Math.pow(-2*progress+2,3)/2; var eased = progress < 0.5 ? 4*progress*progress*progress : 1 - Math.pow(-2*progress+2,3)/2;
var dx = expandX * (1 - eased); var scaleX = 1 + (expandX / origW) * (1 - eased);
var dy = expandY * (1 - eased); var scaleY = 1 + (expandY / origH) * (1 - eased);
tile.style.width = (origW + dx) + 'px'; tile.style.transform = 'scale(' + scaleX + ', ' + scaleY + ')';
tile.style.height = (origH + dy) + 'px'; self._els.cursor.style.left = (startX + expandX * (1 - eased)) + 'px';
self._els.cursor.style.left = (startX + dx) + 'px'; self._els.cursor.style.top = (startY + expandY * (1 - eased)) + 'px';
self._els.cursor.style.top = (startY + dy) + 'px';
if (progress < 1) requestAnimationFrame(frame); else resolve(); if (progress < 1) requestAnimationFrame(frame); else resolve();
} }
requestAnimationFrame(frame); requestAnimationFrame(frame);
}); });
// Aufräumen - exakte Originalgröße wiederherstellen // Aufräumen
tile.style.width = origW + 'px'; tile.style.transform = '';
tile.style.height = origH + 'px'; tile.style.transformOrigin = '';
tile.style.transition = ''; tile.style.transition = '';
tile.style.zIndex = ''; tile.style.zIndex = '';
// Nach kurzer Verzögerung CSS-Werte entfernen damit GridStack wieder übernimmt
setTimeout(function() {
tile.style.width = '';
tile.style.height = '';
}, 100);
this._hideCursor(); this._hideCursor();
await this._wait(200); await this._wait(200);