E-Rechnung/intro_overlay.py

137 lines
5.9 KiB
Python

import streamlit.components.v1 as components
# Sicherheitsnetz-Timer (ms): das Ausblenden geschieht primaer ueber das 'ended'-Event des
# Videos (exakt am Ende). Dieser Timer greift nur, falls 'ended' mal nicht feuert
# (z. B. Autoplay blockiert). Grosszuegig ueber der Videolaenge (~15,8 s) waehlen.
INTRO_FALLBACK_MS = 20000
# Statische URL (Streamlit Static Serving). Beim Live-Test ggf. auf "./static/intro.mp4"
# oder "/static/intro.mp4" korrigieren, falls "app/static/..." 404 liefert.
_INTRO_SRC = "app/static/intro.mp4"
_SEEN_KEY = "einrechnung_intro_seen"
def render_intro_overlay(fallback_ms: int = INTRO_FALLBACK_MS,
intro_src: str = _INTRO_SRC,
seen_key: str = _SEEN_KEY) -> None:
"""Injiziert ein einmaliges Intro-Overlay (MP4) in die Streamlit-Elternseite (Ansatz A).
Ein unsichtbarer components.html-Baustein (Hoehe 0) fuehrt JS aus, das ein
position:fixed-Overlay in window.parent.document einfuegt - ein echtes Overlay ueber der
Seite. Die Animation ist ein <video> (autoplay, muted, ohne Steuerleiste, ohne Loop):
Ausblenden exakt am Ende via 'ended'-Event (Timer nur als Sicherheitsnetz). Autoplay nur
einmal pro Browser (localStorage), "Ueberspringen"-Button + Replay-Button oben rechts
(Replay aendert den "gesehen"-Merker nicht).
"""
html = f"""
<script>
(function() {{
var doc = window.parent.document;
if (window.parent.__introInstalled) return; // Rerun-Schutz: nur einmal pro Seitenladen
window.parent.__introInstalled = true;
var FALLBACK = {fallback_ms};
var SRC = {intro_src!r};
var SEEN_KEY = {seen_key!r};
var style = doc.createElement('style');
style.textContent = `
#eir-backdrop {{
position: fixed; inset: 0; z-index: 2147483646;
display: flex; align-items: center; justify-content: center;
background: rgba(8,8,10,0.82); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
opacity: 0; transition: opacity .45s ease; pointer-events: none;
}}
#eir-backdrop.eir-show {{ opacity: 1; pointer-events: auto; }}
#eir-panel {{
position: relative; width: min(50vw, 1100px); aspect-ratio: 16 / 9;
max-width: 94vw; max-height: 88vh;
background: #0a0a0a; border-radius: 16px; overflow: hidden;
box-shadow: 0 24px 80px rgba(0,0,0,0.6), 0 0 0 1px rgba(245,126,26,0.25);
}}
@media (max-width: 820px) {{ #eir-panel {{ width: 92vw; }} }}
#eir-video {{ width: 100%; height: 100%; display: block; object-fit: contain; background: #0a0a0a; }}
#eir-skip {{
position: absolute; top: 10px; right: 12px; z-index: 2;
font: 600 13px/1 -apple-system, BlinkMacSystemFont, sans-serif;
color: #eee; background: rgba(0,0,0,0.45); border: 1px solid rgba(255,255,255,0.18);
padding: 7px 12px; border-radius: 999px; cursor: pointer; user-select: none;
}}
#eir-skip:hover {{ background: rgba(0,0,0,0.7); }}
#eir-replay {{
position: fixed; top: 12px; right: 72px; z-index: 2147483647;
width: 40px; height: 40px; border-radius: 50%;
display: flex; align-items: center; justify-content: center; cursor: pointer;
background: #17171a; border: 1px solid rgba(245,126,26,0.55);
box-shadow: 0 2px 10px rgba(0,0,0,0.35);
}}
#eir-replay:hover {{ background: #202024; }}
#eir-replay svg {{ width: 16px; height: 16px; }}
`;
doc.head.appendChild(style);
var replay = doc.createElement('div');
replay.id = 'eir-replay';
replay.title = 'Intro erneut abspielen';
replay.innerHTML = '<svg viewBox="0 0 24 24" fill="#F57E1A"><path d="M8 5v14l11-7z"/></svg>';
doc.body.appendChild(replay);
// Den ▶-Button links NEBEN Streamlits Menue-Leiste (oben rechts) platzieren, statt darueber.
// Die Toolbar (Deploy + ⋮-Menue) rendert asynchron -> mehrfach nachjustieren + bei Resize.
function positionReplay() {{
var win = window.parent, gap = 12, fallback = 72;
var tb = doc.querySelector('[data-testid="stToolbar"]');
var rightPx = fallback;
if (tb) {{
var cluster = win.innerWidth - tb.getBoundingClientRect().left; // Breite des Menue-Clusters
if (cluster > 8 && cluster < win.innerWidth * 0.6) rightPx = Math.round(cluster) + gap;
}}
replay.style.right = rightPx + 'px';
}}
positionReplay();
[150, 500, 1200].forEach(function(t) {{ setTimeout(positionReplay, t); }});
window.parent.addEventListener('resize', positionReplay);
var backdrop = doc.createElement('div');
backdrop.id = 'eir-backdrop';
backdrop.innerHTML =
'<div id="eir-panel">' +
'<div id="eir-skip">Überspringen ✕</div>' +
'<video id="eir-video" muted playsinline preload="none" src="' + SRC + '"></video>' +
'</div>';
doc.body.appendChild(backdrop);
var video = doc.getElementById('eir-video');
var skip = doc.getElementById('eir-skip');
var hideTimer = null;
function hide() {{
clearTimeout(hideTimer);
backdrop.classList.remove('eir-show');
setTimeout(function() {{ try {{ video.pause(); }} catch (e) {{}} }}, 500);
}}
function show() {{
backdrop.classList.add('eir-show');
try {{ video.currentTime = 0; }} catch (e) {{}} // Neustart von vorn (auch bei Replay)
var p = video.play();
if (p && p.catch) p.catch(function() {{}}); // Autoplay-Ablehnung ignorieren
clearTimeout(hideTimer);
hideTimer = setTimeout(hide, FALLBACK); // Sicherheitsnetz, falls 'ended' ausbleibt
}}
video.addEventListener('ended', hide); // exaktes Ausblenden am Videoende
skip.addEventListener('click', hide);
backdrop.addEventListener('click', function(e) {{ if (e.target === backdrop) hide(); }});
replay.addEventListener('click', show);
var seen;
try {{ seen = window.parent.localStorage.getItem(SEEN_KEY); }} catch (e) {{ seen = null; }}
if (seen !== '1') {{
try {{ window.parent.localStorage.setItem(SEEN_KEY, '1'); }} catch (e) {{}}
show();
}}
}})();
</script>
"""
components.html(html, height=0)