Tutorial: Spotlight und Bubble auf sichtbaren Viewport-Bereich beschraenken
- Spotlight wird auf den sichtbaren Teil des Elements geclippt statt ueber den Viewport hinauszuragen - Bubble-Position nutzt sichtbaren Elementbereich statt voller Rect - Demo-Summary-Text gekuerzt, damit er in die Lagebild-Kachel passt - Behebt das Problem, dass bei grossen Kacheln (Lagebild, Timeline, Karte) die Sprechblase ausserhalb des sichtbaren Bereichs landete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -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=20260316c"></script>
|
<script src="/static/js/tutorial.js?v=20260316d"></script>
|
||||||
<script src="/static/js/chat.js?v=20260316e"></script>
|
<script src="/static/js/chat.js?v=20260316e"></script>
|
||||||
<script>document.addEventListener("DOMContentLoaded",function(){Chat.init();Tutorial.init()});</script>
|
<script>document.addEventListener("DOMContentLoaded",function(){Chat.init();Tutorial.init()});</script>
|
||||||
|
|
||||||
|
|||||||
@@ -49,19 +49,18 @@ const Tutorial = {
|
|||||||
+ '</div>'
|
+ '</div>'
|
||||||
+ '</div>',
|
+ '</div>',
|
||||||
|
|
||||||
_DEMO_SUMMARY: '<p>Am Morgen des 16. März 2026 kam es im Hamburger Hafen zu einer schweren Explosion '
|
_DEMO_SUMMARY: '<p>Am 16. März 2026 kam es im Hamburger Hafen zu einer Explosion '
|
||||||
+ 'in einem Containerterminal am Burchardkai. Nach übereinstimmenden Berichten von '
|
+ 'im Containerterminal Burchardkai '
|
||||||
+ '<span class="source-ref" data-index="1" title="dpa">dpa [1]</span>, '
|
+ '<span class="source-ref" data-index="1" title="dpa">[1]</span> '
|
||||||
+ '<span class="source-ref" data-index="2" title="Reuters">Reuters [2]</span> und '
|
+ '<span class="source-ref" data-index="2" title="Reuters">[2]</span>. '
|
||||||
+ '<span class="source-ref" data-index="3" title="NDR">NDR [3]</span> '
|
+ 'Die Detonation ereignete sich gegen 06:45 Uhr in einem Gefahrgutbereich. '
|
||||||
+ 'ereignete sich die Detonation gegen 06:45 Uhr Ortszeit in einem Lagerbereich für Gefahrgut.</p>'
|
+ 'Mindestens 12 Personen wurden verletzt '
|
||||||
+ '<p>Die Hamburger Feuerwehr ist mit einem Großaufgebot vor Ort. Laut '
|
+ '<span class="source-ref" data-index="3" title="NDR">[3]</span>.</p>'
|
||||||
+ '<span class="source-ref" data-index="4" title="Hamburger Abendblatt">Hamburger Abendblatt [4]</span> '
|
+ '<p>Die Feuerwehr ist mit einem Großaufgebot vor Ort. Der Hafenbetrieb im betroffenen Terminal wurde '
|
||||||
+ 'wurden mindestens 12 Personen verletzt, davon 3 schwer. Die Ursache der Explosion ist noch unklar. '
|
+ 'vorübergehend eingestellt '
|
||||||
+ 'Die Polizei Hamburg hat den Bereich weiträumig abgesperrt.</p>'
|
+ '<span class="source-ref" data-index="4" title="HPA">[4]</span>. '
|
||||||
+ '<p>Der Hamburger Hafen, Europas drittgrößter Seehafen, hat den Betrieb im betroffenen Terminal vorübergehend '
|
+ 'Die Ursache ist noch unklar '
|
||||||
+ 'eingestellt. Auswirkungen auf den Schiffsverkehr werden derzeit geprüft '
|
+ '<span class="source-ref" data-index="5" title="Hamburger Abendblatt">[5]</span>.</p>',
|
||||||
+ '<span class="source-ref" data-index="5" title="Hamburg Port Authority">HPA [5]</span>.</p>',
|
|
||||||
|
|
||||||
_DEMO_FACTCHECKS: [
|
_DEMO_FACTCHECKS: [
|
||||||
{ status: 'confirmed', icon: '✓', claim: 'Eine Explosion ereignete sich am 16.03.2026 gegen 06:45 Uhr im Hamburger Hafen.', sources: 5 },
|
{ status: 'confirmed', icon: '✓', claim: 'Eine Explosion ereignete sich am 16.03.2026 gegen 06:45 Uhr im Hamburger Hafen.', sources: 5 },
|
||||||
@@ -901,13 +900,21 @@ const Tutorial = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rect = el.getBoundingClientRect();
|
var rect = el.getBoundingClientRect();
|
||||||
|
var vw = window.innerWidth;
|
||||||
|
var vh = window.innerHeight;
|
||||||
var pad = 8;
|
var pad = 8;
|
||||||
var s = this._els.spotlight.style;
|
|
||||||
|
|
||||||
s.top = (rect.top - pad) + 'px';
|
// Sichtbaren Bereich berechnen (auf Viewport beschränkt)
|
||||||
s.left = (rect.left - pad) + 'px';
|
var top = Math.max(rect.top, 0) - pad;
|
||||||
s.width = (rect.width + pad * 2) + 'px';
|
var left = Math.max(rect.left, 0) - pad;
|
||||||
s.height = (rect.height + pad * 2) + 'px';
|
var bottom = Math.min(rect.bottom, vh) + pad;
|
||||||
|
var right = Math.min(rect.right, vw) + pad;
|
||||||
|
|
||||||
|
var s = this._els.spotlight.style;
|
||||||
|
s.top = top + 'px';
|
||||||
|
s.left = left + 'px';
|
||||||
|
s.width = (right - left) + 'px';
|
||||||
|
s.height = (bottom - top) + 'px';
|
||||||
s.borderRadius = '8px';
|
s.borderRadius = '8px';
|
||||||
s.opacity = '1';
|
s.opacity = '1';
|
||||||
},
|
},
|
||||||
@@ -988,6 +995,14 @@ const Tutorial = {
|
|||||||
var vh = window.innerHeight;
|
var vh = window.innerHeight;
|
||||||
var gap = 16;
|
var gap = 16;
|
||||||
|
|
||||||
|
// Sichtbaren Bereich des Elements berechnen (auf Viewport beschraenkt)
|
||||||
|
var visTop = Math.max(rect.top, 0);
|
||||||
|
var visBottom = Math.min(rect.bottom, vh);
|
||||||
|
var visLeft = Math.max(rect.left, 0);
|
||||||
|
var visRight = Math.min(rect.right, vw);
|
||||||
|
var visCenterY = (visTop + visBottom) / 2;
|
||||||
|
var visCenterX = (visLeft + visRight) / 2;
|
||||||
|
|
||||||
// Reset transform
|
// Reset transform
|
||||||
bubble.style.transform = '';
|
bubble.style.transform = '';
|
||||||
bubble.style.width = bw + 'px';
|
bubble.style.width = bw + 'px';
|
||||||
@@ -996,30 +1011,33 @@ const Tutorial = {
|
|||||||
var pos = step.position || 'bottom';
|
var pos = step.position || 'bottom';
|
||||||
var bubbleHeight = 300;
|
var bubbleHeight = 300;
|
||||||
|
|
||||||
if (pos === 'bottom' && (rect.bottom + gap + bubbleHeight > vh)) pos = 'top';
|
if (pos === 'bottom' && (visBottom + gap + bubbleHeight > vh)) pos = 'top';
|
||||||
if (pos === 'top' && (rect.top - gap - bubbleHeight < 0)) pos = 'bottom';
|
if (pos === 'top' && (visTop - gap - bubbleHeight < 0)) pos = 'bottom';
|
||||||
if (pos === 'right' && (rect.right + gap + bw > vw)) pos = 'left';
|
if (pos === 'right' && (visRight + gap + bw > vw)) pos = 'left';
|
||||||
if (pos === 'left' && (rect.left - gap - bw < 0)) pos = 'right';
|
if (pos === 'left' && (visLeft - gap - bw < 0)) pos = 'right';
|
||||||
|
|
||||||
bubble.className = 'tutorial-bubble visible tutorial-pos-' + pos;
|
bubble.className = 'tutorial-bubble visible tutorial-pos-' + pos;
|
||||||
|
|
||||||
|
// Bubble-Top immer im sichtbaren Bereich halten
|
||||||
|
var clampTop = function(t) { return Math.max(8, Math.min(t, vh - bubbleHeight - 8)); };
|
||||||
|
|
||||||
switch (pos) {
|
switch (pos) {
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
bubble.style.top = (rect.bottom + gap) + 'px';
|
bubble.style.top = Math.min(visBottom + gap, vh - bubbleHeight - 8) + 'px';
|
||||||
bubble.style.left = Math.max(8, Math.min(rect.left + rect.width / 2 - bw / 2, vw - bw - 8)) + 'px';
|
bubble.style.left = Math.max(8, Math.min(visCenterX - bw / 2, vw - bw - 8)) + 'px';
|
||||||
break;
|
break;
|
||||||
case 'top':
|
case 'top':
|
||||||
bubble.style.top = (rect.top - gap) + 'px';
|
bubble.style.top = Math.max(visTop - gap, 8) + 'px';
|
||||||
bubble.style.left = Math.max(8, Math.min(rect.left + rect.width / 2 - bw / 2, vw - bw - 8)) + 'px';
|
bubble.style.left = Math.max(8, Math.min(visCenterX - bw / 2, vw - bw - 8)) + 'px';
|
||||||
bubble.style.transform = 'translateY(-100%)';
|
bubble.style.transform = 'translateY(-100%)';
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case 'right':
|
||||||
bubble.style.top = Math.max(8, rect.top + rect.height / 2 - bubbleHeight / 2) + 'px';
|
bubble.style.top = clampTop(visCenterY - bubbleHeight / 2) + 'px';
|
||||||
bubble.style.left = (rect.right + gap) + 'px';
|
bubble.style.left = Math.min(visRight + gap, vw - bw - 8) + 'px';
|
||||||
break;
|
break;
|
||||||
case 'left':
|
case 'left':
|
||||||
bubble.style.top = Math.max(8, rect.top + rect.height / 2 - bubbleHeight / 2) + 'px';
|
bubble.style.top = clampTop(visCenterY - bubbleHeight / 2) + 'px';
|
||||||
bubble.style.left = (rect.left - gap - bw) + 'px';
|
bubble.style.left = Math.max(visLeft - gap - bw, 8) + 'px';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren