"""JamPulse Frame – Benutzeroberfläche (Web)."""

from __future__ import annotations

import json
import os
import re
import urllib.error
import urllib.request
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
SECRETS = ROOT / "data" / "jami-secrets.local.json"

PROMPT_IDEAS = [
    "Jami Dating – Sarah & Laura, Wochenend-Date, 3D Trickfilm Dialog",
    "Neon-Stadt bei Nacht, Regen auf Asphalt, cinematic dolly shot",
    "Handwerker am Werk, warmes Licht, professionell, 9:16",
    "Dating-App auf dem Handy, moderne UI, pink-lila Glow",
    "Website-Launch, Laptop im Café, KI Web Studio Werbung",
    "Drone shot über Alpen bei Sonnenaufgang, 4K",
    "Social Media Creator filmt mit Smartphone, Ringlicht",
]


def _openai_key() -> str:
    key = os.environ.get("OPENAI_API_KEY", "").strip()
    if key:
        return key
    if SECRETS.is_file():
        try:
            data = json.loads(SECRETS.read_text(encoding="utf-8"))
            return str(data.get("openai_api_key") or "").strip()
        except (json.JSONDecodeError, OSError):
            pass
    return ""


def enhance_prompt(text: str) -> str:
    t = text.strip()
    if not t:
        return ""
    key = _openai_key()
    if key:
        payload = {
            "model": "gpt-4o-mini",
            "messages": [
                {
                    "role": "system",
                    "content": (
                        "Verbessere den Prompt für KI-Video-Generierung auf Englisch. "
                        "Kurz, visuell, cinematic. Max 120 Wörter. Nur der Prompt, kein Kommentar."
                    ),
                },
                {"role": "user", "content": t},
            ],
            "max_tokens": 180,
            "temperature": 0.65,
        }
        try:
            req = urllib.request.Request(
                "https://api.openai.com/v1/chat/completions",
                data=json.dumps(payload).encode(),
                headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
                method="POST",
            )
            with urllib.request.urlopen(req, timeout=25) as resp:
                out = json.loads(resp.read().decode())["choices"][0]["message"]["content"].strip()
                if out:
                    return out
        except (urllib.error.URLError, json.JSONDecodeError, KeyError, OSError):
            pass
    extra = ", cinematic lighting, smooth camera motion, shallow depth of field, high detail"
    if not re.search(r"cinematic|4k|camera|lighting", t, re.I):
        return t.rstrip("., ") + extra
    return t


def render_studio_html() -> str:
    from jam.jami_promo_banner import jami_promo_banner_css, jami_promo_banner_html, jami_promo_banner_script
    from jam.clipwelle_legal import legal_footer_links
    from jam.jampulse_brand import PRODUCT_BRAND, PRODUCT_TAGLINE, SINGLE_VIDEO_OFFER, SUBSCRIPTION_OFFER

    single_eur = f"{float(SINGLE_VIDEO_OFFER['price_eur']):.2f}".replace(".", ",")
    sub_monthly = f"{float(SUBSCRIPTION_OFFER['monthly_eur']):.2f}".replace(".", ",")
    sub_yearly = f"{float(SUBSCRIPTION_OFFER['yearly_eur']):.2f}".replace(".", ",")
    single_eur_num = float(SINGLE_VIDEO_OFFER["price_eur"])
    from jam.jampulse_templates import INDUSTRY_TEMPLATES
    from jam.vidpuls_showcase import studio_gallery_css

    jami_banner = jami_promo_banner_html(variant="compact", utm_source="vidpuls", utm_medium="studio")
    jami_css = jami_promo_banner_css(variant="compact")
    jami_script = jami_promo_banner_script()
    legal_foot = legal_footer_links(prefix="")

    ideas = [t["prompt"] for t in INDUSTRY_TEMPLATES] + PROMPT_IDEAS
    ideas_json = json.dumps(ideas, ensure_ascii=False)
    templates_json = json.dumps(INDUSTRY_TEMPLATES, ensure_ascii=False)
    return f"""<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"/>
<meta name="theme-color" content="#050508"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<title>{PRODUCT_BRAND} – KI Video Studio</title>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
<style>
:root{{
  --bg:#050508;--surface:#0e0c14;--surface2:#16131f;--line:rgba(255,255,255,.08);
  --text:#f4f0ff;--muted:#8b8fa3;--accent:#a855f7;--accent2:#ec4899;--mint:#34d399;
  --warn:#fbbf24;--err:#f87171;--radius:14px;--shadow:0 24px 80px rgba(0,0,0,.55);
}}
*{{box-sizing:border-box;margin:0;padding:0}}
html,body{{height:100%}}
body{{
  font-family:Inter,Segoe UI,system-ui,sans-serif;background:var(--bg);color:var(--text);
  background-image:
    radial-gradient(1200px 600px at 10% -10%, rgba(168,85,247,.22), transparent 55%),
    radial-gradient(900px 500px at 100% 0%, rgba(236,72,153,.14), transparent 50%);
}}
.app{{display:grid;grid-template-columns:240px 1fr;min-height:100dvh}}
@media(max-width:900px){{.app{{grid-template-columns:1fr}} .sidebar{{display:none}} .mobile-nav{{display:flex}}}}
.sidebar{{
  border-right:1px solid var(--line);padding:1.25rem 1rem;background:rgba(8,6,12,.85);
  backdrop-filter:blur(12px);display:flex;flex-direction:column;gap:1rem;
}}
.brand{{display:flex;align-items:center;gap:.75rem;padding:.25rem .5rem}}
.brand-mark{{
  width:44px;height:44px;border-radius:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));
  display:grid;place-items:center;font-weight:800;font-size:.95rem;box-shadow:0 8px 24px rgba(168,85,247,.35);
}}
.brand h1{{font-size:1.05rem;font-weight:800;line-height:1.2}}
.brand p{{font-size:.72rem;color:var(--muted)}}
.nav{{display:flex;flex-direction:column;gap:.35rem}}
.nav button{{
  text-align:left;padding:.65rem .85rem;border-radius:12px;border:1px solid transparent;
  background:transparent;color:var(--muted);font:inherit;font-weight:600;cursor:pointer;
}}
.nav button.active,.nav button:hover{{background:rgba(168,85,247,.12);color:var(--text);border-color:rgba(168,85,247,.25)}}
.engine-pill{{
  margin-top:auto;padding:.65rem .85rem;border-radius:12px;background:var(--surface2);
  border:1px solid var(--line);font-size:.78rem;color:var(--muted);line-height:1.45;
}}
.engine-pill strong{{color:var(--mint);display:block;margin-bottom:.2rem}}
.main{{padding:1rem 1.25rem 2rem;max-width:1200px;width:100%;margin:0 auto}}
.mobile-nav{{display:none;gap:.5rem;margin-bottom:1rem}}
.mobile-nav button{{flex:1;padding:.55rem;border-radius:10px;border:1px solid var(--line);background:var(--surface2);color:var(--text);font:inherit;font-weight:600}}
.layout{{display:grid;grid-template-columns:1fr 1fr;gap:1rem;align-items:start}}
@media(max-width:900px){{.layout{{grid-template-columns:1fr}}}}
.panel{{
  background:rgba(14,12,20,.92);border:1px solid var(--line);border-radius:var(--radius);
  padding:1.15rem;box-shadow:var(--shadow);
}}
.panel h2{{font-size:.95rem;font-weight:700;margin-bottom:.85rem;display:flex;align-items:center;gap:.5rem}}
.panel h2 span{{font-size:1.1rem}}
.mode-tabs{{display:flex;gap:.45rem;margin-bottom:1rem;background:var(--surface2);padding:.35rem;border-radius:12px}}
.mode-tabs button{{
  flex:1;border:none;border-radius:10px;padding:.55rem .5rem;font:inherit;font-weight:600;
  cursor:pointer;background:transparent;color:var(--muted);
}}
.mode-tabs button.active{{background:linear-gradient(135deg,rgba(168,85,247,.45),rgba(236,72,153,.25));color:#fff}}
.dropzone{{
  display:none;border:2px dashed rgba(168,85,247,.4);border-radius:12px;padding:1.25rem;
  text-align:center;color:var(--muted);margin-bottom:1rem;cursor:pointer;transition:.2s;
}}
.dropzone.active{{display:block}}
.dropzone.drag{{border-color:var(--accent2);background:rgba(168,85,247,.08)}}
.dropzone img{{max-height:120px;border-radius:10px;margin-top:.5rem}}
.field{{margin-bottom:.85rem}}
.field label{{display:block;font-size:.72rem;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:.4rem}}
textarea{{
  width:100%;min-height:120px;resize:vertical;border-radius:12px;border:1px solid var(--line);
  background:#0a0810;color:var(--text);padding:.85rem 1rem;font:inherit;font-size:.95rem;line-height:1.5;
}}
textarea:focus{{outline:2px solid rgba(168,85,247,.5);border-color:transparent}}
.field-meta{{display:flex;justify-content:space-between;align-items:center;margin-top:.35rem;font-size:.75rem;color:var(--muted)}}
.chips{{display:flex;flex-wrap:wrap;gap:.4rem;margin-bottom:.85rem}}
.chip{{
  border:1px solid var(--line);background:var(--surface2);color:var(--muted);border-radius:999px;
  padding:.35rem .65rem;font-size:.72rem;cursor:pointer;max-width:100%;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;
}}
.chip:hover{{border-color:var(--accent);color:var(--text)}}
.grid{{display:grid;grid-template-columns:repeat(2,1fr);gap:.65rem}}
select{{
  width:100%;padding:.55rem .65rem;border-radius:10px;border:1px solid var(--line);
  background:#0a0810;color:var(--text);font:inherit;
}}
.row{{display:flex;gap:.5rem;flex-wrap:wrap;margin:.85rem 0}}
.btn{{
  border:none;border-radius:12px;padding:.75rem 1rem;font:inherit;font-weight:700;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;gap:.45rem;
}}
.btn-primary{{
  flex:1;color:#fff;background:linear-gradient(135deg,var(--accent),var(--accent2));
  box-shadow:0 10px 30px rgba(168,85,247,.35);
}}
.btn-primary:disabled{{opacity:.45;cursor:not-allowed;box-shadow:none}}
.btn-ghost{{background:transparent;border:1px solid var(--line);color:var(--text)}}
.btn-ghost:hover{{border-color:var(--accent)}}
.check{{display:flex;align-items:center;gap:.5rem;font-size:.85rem;color:var(--muted);margin:.5rem 0}}
.preview-wrap{{
  aspect-ratio:9/16;max-height:min(70vh,640px);margin:0 auto;background:#000;border-radius:12px;
  border:1px solid var(--line);overflow:hidden;position:relative;display:grid;place-items:center;
}}
.preview-wrap.landscape{{aspect-ratio:16/9;max-height:360px}}
.preview-empty{{text-align:center;padding:1.5rem;color:var(--muted);font-size:.88rem;line-height:1.5}}
.preview-empty .icon{{font-size:2.5rem;margin-bottom:.5rem;opacity:.7}}
video{{width:100%;height:100%;object-fit:contain;background:#000}}
.progress{{
  position:absolute;inset:0;background:rgba(5,5,8,.82);display:none;flex-direction:column;
  align-items:center;justify-content:center;gap:1rem;padding:1.5rem;text-align:center;
}}
.progress.show{{display:flex}}
.spinner{{
  width:48px;height:48px;border-radius:50%;border:3px solid rgba(255,255,255,.12);
  border-top-color:var(--accent);animation:spin .9s linear infinite;
}}
@keyframes spin{{to{{transform:rotate(360deg)}}}}
.steps{{display:flex;gap:.35rem;margin-top:.25rem}}
.step{{
  font-size:.65rem;padding:.25rem .45rem;border-radius:999px;background:rgba(255,255,255,.06);color:var(--muted);
}}
.step.on{{background:rgba(168,85,247,.25);color:#e9d5ff}}
.step.done{{background:rgba(52,211,153,.2);color:var(--mint)}}
.status-bar{{
  margin-top:.85rem;padding:.65rem .85rem;border-radius:10px;background:var(--surface2);
  border:1px solid var(--line);font-size:.82rem;color:var(--muted);min-height:2.4rem;
}}
.status-bar.ok{{border-color:rgba(52,211,153,.35);color:var(--mint)}}
.status-bar.err{{border-color:rgba(248,113,113,.35);color:var(--err)}}
.actions{{display:flex;gap:.5rem;margin-top:.75rem;flex-wrap:wrap}}
.actions .btn{{flex:1;min-width:120px}}
.gallery-grid{{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:.75rem}}
.g-card{{
  border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--surface2);
  cursor:pointer;transition:transform .15s,border-color .15s;
}}
.g-card:hover{{transform:translateY(-2px);border-color:rgba(168,85,247,.4)}}
.g-thumb{{
  aspect-ratio:9/16;background:linear-gradient(160deg,#1a1030,#0a0810);
  display:grid;place-items:center;font-size:1.5rem;position:relative;
}}
.g-thumb .badge{{
  position:absolute;top:6px;right:6px;font-size:.6rem;padding:.15rem .4rem;border-radius:6px;
  background:rgba(0,0,0,.55);color:var(--mint);
}}
.g-body{{padding:.55rem .65rem .65rem}}
.g-body p{{font-size:.72rem;color:var(--muted);line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}}
.view{{display:none}}.view.active{{display:block}}
.toast{{
  position:fixed;bottom:1.25rem;left:50%;transform:translateX(-50%) translateY(120%);
  background:#1e1b28;border:1px solid var(--line);padding:.65rem 1rem;border-radius:999px;
  font-size:.85rem;transition:.3s;z-index:99;box-shadow:var(--shadow);
}}
.toast.show{{transform:translateX(-50%) translateY(0)}}
.hidden{{display:none!important}}
.sidebar .jami-ad-embed {{ margin-top: .75rem; display: block; }}
.sidebar .jami-ad-embed-inner {{ padding: .65rem .7rem .75rem; }}
.sidebar .jami-ad-embed-head h3 {{ font-size: .78rem; }}
.sidebar .jami-ad-embed-head p {{ font-size: .68rem; margin-bottom: .35rem; }}
.sidebar .jami-ad-stat {{ font-size: .62rem; margin-bottom: .4rem; }}
.sidebar .jami-ad-cta {{ font-size: .72rem; padding: .45rem .55rem; }}
.studio-legal a, .legal-main a {{ color: var(--accent); text-decoration: none; }}
.studio-legal a:hover {{ text-decoration: underline; }}
{studio_gallery_css()}
{jami_css}
</style>
</head>
<body>
<div class="app">
  <aside class="sidebar">
    <div class="brand">
      <div class="brand-mark">VP</div>
      <div>
        <h1>{PRODUCT_BRAND}</h1>
        <p>{PRODUCT_TAGLINE}</p>
      </div>
    </div>
    <nav class="nav">
      <button type="button" class="active" data-view="create">Erstellen</button>
      <button type="button" data-view="templates">Vorlagen</button>
      <button type="button" data-view="calendar">Kalender</button>
      <button type="button" data-view="pricing">Preise</button>
      <button type="button" data-view="gallery">Galerie</button>
      <button type="button" data-view="help">Hilfe</button>
    </nav>
    {jami_banner}
    <div class="engine-pill">
      <strong id="engineLabel">Engine …</strong>
      <span id="engineHint">PixVerse API oder Fallback</span>
      <div id="licensePill" style="font-size:.72rem;color:var(--mint);margin-top:.35rem"></div>
    </div>
  </aside>

  <main class="main">
    <div class="mobile-nav">
      <button type="button" data-view="create">Erstellen</button>
      <button type="button" data-view="gallery">Galerie</button>
    </div>

    <section id="view-create" class="view active">
      <div class="layout">
        <div class="panel">
          <h2><span>✨</span> Neues Video</h2>
          <div class="mode-tabs">
            <button type="button" data-mode="text">Text → Video</button>
            <button type="button" data-mode="image">Bild → Video</button>
            <button type="button" data-mode="artist_dialog">Artist · Dialog</button>
            <button type="button" class="active" data-mode="minute_hd">1–2 Min · 4K</button>
          </div>
          <div id="hdBanner" style="margin-bottom:.85rem;padding:.65rem .85rem;border-radius:10px;background:rgba(52,211,153,.12);border:1px solid rgba(52,211,153,.35);font-size:.82rem;color:var(--mint)">
            <strong>Gratis:</strong> 1 Video mit Wasserzeichen · <strong>{single_eur} €</strong> ohne WM · Pro {sub_monthly} €/Mo
          </div>
          <div id="dropzone" class="dropzone">
            <div id="dropLabel">Bild hierher ziehen oder klicken</div>
            <img id="imgPreview" class="hidden" alt=""/>
            <input type="file" id="fileIn" accept="image/*" class="hidden"/>
          </div>
          <div class="chips" id="chips"></div>
          <div class="field">
            <label for="prompt">Prompt</label>
            <textarea id="prompt" placeholder="Beschreibe dein Video – Szene, Stimmung, Kamerabewegung…"></textarea>
            <div class="field-meta">
              <span><kbd style="opacity:.7">Strg+Enter</kbd> generieren</span>
              <span id="charCount">0 / 5000</span>
            </div>
          </div>
          <div class="grid" id="pixverseOpts">
            <div class="field"><label>Format</label>
              <select id="aspect"><option value="9:16" selected>9:16 TikTok</option><option value="16:9">16:9 YouTube</option><option value="1:1">1:1 Feed</option><option value="3:4">3:4 Portrait</option></select></div>
            <div class="field" id="durationField"><label>Dauer</label>
              <select id="duration"><option value="5" selected>5 Sek.</option><option value="8">8 Sek.</option><option value="10">10 Sek.</option></select></div>
            <div class="field" id="qualityField"><label>Qualität</label>
              <select id="quality"><option value="540p" selected>540p</option><option value="720p">720p</option><option value="1080p">1080p HD</option></select></div>
            <div class="field" id="modelField"><label>Modell</label>
              <select id="model"><option value="v5.5" selected>v5.5</option><option value="v5.6">v5.6</option><option value="v6">v6</option></select></div>
          </div>
          <label class="check" id="audioRow"><input type="checkbox" id="audio"/> KI-Audio (PixVerse v5.5+)</label>
          <div id="autoPublishRow" style="margin:.5rem 0;padding:.65rem;border:1px solid var(--line);border-radius:10px;background:var(--surface2)">
            <div style="font-size:.72rem;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:.45rem">Auto-Post nach Fertigstellung</div>
            <label class="check"><input type="checkbox" id="pubTiktok" checked/> TikTok – Upload öffnen, Video + Caption bereit</label>
            <label class="check"><input type="checkbox" id="pubYoutube" checked/> YouTube – Studio öffnen, Titel + Text bereit</label>
            <label class="check"><input type="checkbox" id="pubInstagram" checked/> Instagram Reels – App/Web öffnen</label>
          </div>
          <div class="row">
            <button type="button" class="btn btn-ghost" id="enhance">Prompt verbessern</button>
            <button type="button" class="btn btn-primary" id="go">Video generieren</button>
          </div>
          <div class="status-bar" id="status">Bereit – Prompt eingeben und starten.</div>
        </div>

        <div class="panel">
          <h2><span>▶</span> Vorschau</h2>
          <div class="preview-wrap" id="previewBox">
            <div class="preview-empty" id="previewEmpty">
              <div class="icon">🎬</div>
              Dein Video erscheint hier<br/><span style="font-size:.8rem;opacity:.8" id="previewHint">Meist 1–3 Minuten Generierungszeit</span>
            </div>
            <video id="player" controls playsinline class="hidden"></video>
            <div class="progress" id="progress">
              <div class="spinner"></div>
              <div>
                <strong id="progressTitle">KI generiert…</strong>
                <p id="progressSub" style="color:var(--muted);font-size:.85rem;margin-top:.35rem">Bitte Tab offen lassen</p>
              </div>
              <div class="steps">
                <span class="step on" data-step="queue">Warteschlange</span>
                <span class="step" data-step="gen">Generierung</span>
                <span class="step" data-step="done">Fertig</span>
              </div>
            </div>
          </div>
          <div class="actions">
            <a id="dl" class="btn btn-ghost hidden" download>MP4 laden</a>
            <button type="button" class="btn btn-ghost hidden" id="copyCap">Caption kopieren</button>
            <button type="button" class="btn btn-ghost" id="openFolder" title="Videos-Ordner">Ordner öffnen</button>
          </div>
        </div>
      </div>
    </section>

    <section id="view-templates" class="view">
      <div class="panel">
        <h2><span>📋</span> Branchen-Vorlagen</h2>
        <p style="color:var(--muted);font-size:.88rem;margin-bottom:1rem">Klick = Prompt übernehmen → Tab Erstellen → Video generieren</p>
        <div class="gallery-grid" id="templateGrid"></div>
      </div>
    </section>

    <section id="view-calendar" class="view">
      <div class="panel">
        <h2><span>📅</span> 30-Tage Content-Kalender</h2>
        <p style="color:var(--muted);font-size:.88rem;margin-bottom:1rem">Automatische Themen – Mo–So Posts, TikTok + YouTube + Instagram</p>
        <div class="row" style="margin-bottom:1rem">
          <button type="button" class="btn btn-primary" id="genCal">Kalender erstellen</button>
          <button type="button" class="btn btn-ghost" id="runDueCal">Fällige jetzt starten</button>
        </div>
        <div id="calendarList" style="font-size:.85rem;line-height:1.6;color:var(--muted)"></div>
      </div>
    </section>

    <section id="view-pricing" class="view">
      <div class="panel">
        <h2><span>💳</span> Preise &amp; Abo</h2>
        <div id="pricingBox" style="line-height:1.7;color:var(--muted);font-size:.9rem"></div>
      </div>
    </section>

    <section id="view-gallery" class="view">
      <div class="panel gal-panel">
        <h2><span>🎞</span> Video-Galerie</h2>
        <p class="gal-sub">Beispiel-Prompts in 4K – links &amp; rechts animiert · Klick = Vorschau laden</p>
        <div class="gal-stage">
          <div class="gal-col gal-col-l"><div class="gal-track" id="galColL"></div></div>
          <div class="gal-col gal-col-r"><div class="gal-track" id="galColR"></div></div>
        </div>
        <div class="gal-mine-wrap hidden" id="galMineWrap">
          <div class="gal-mine-title">Deine Videos</div>
          <div class="gal-mine-scroll"><div class="gal-mine-track" id="galMine"></div></div>
        </div>
        <p id="galleryEmpty" class="preview-empty" style="padding:2rem">Galerie wird geladen…</p>
      </div>
    </section>

    <section id="view-help" class="view">
      <div class="panel" style="line-height:1.65;color:var(--muted);font-size:.9rem">
        <h2 style="color:var(--text);margin-bottom:.75rem"><span>💡</span> Kurzanleitung</h2>
        <p><strong style="color:var(--text)">1.</strong> Prompt schreiben oder Idee-Chip klicken.</p>
        <p style="margin-top:.5rem"><strong style="color:var(--text)">2.</strong> Format wählen (9:16 für TikTok/Reels).</p>
        <p style="margin-top:.5rem"><strong style="color:var(--text)">3.</strong> „Video generieren“ – warten bis Vorschau erscheint.</p>
        <p style="margin-top:.5rem"><strong style="color:var(--text)">Gratis:</strong> 1× Video mit <strong>VidPuls-Wasserzeichen</strong> (60–120 Sek.).</p>
        <p style="margin-top:.5rem"><strong style="color:var(--text)">Ohne Wasserzeichen:</strong> Einzelvideo <strong>{single_eur} €</strong> · Pro <strong>{sub_monthly} €/Monat</strong> · Jahr <strong>{sub_yearly} €</strong>.</p>
        <p style="margin-top:.5rem"><strong style="color:var(--text)">Jahresabo:</strong> <strong>167,92 €/Jahr</strong> statt 239,88 € – <strong>30&nbsp;% günstiger</strong> (eff. 13,99 €/Monat, spart 71,96 €).</p>
        <p style="margin-top:.5rem"><strong style="color:var(--text)">Text/Bild → Video:</strong> PixVerse Kurz-Clips (max. ~10 Sek.) für KI-Szenen.</p>
        <p style="margin-top:.5rem"><strong style="color:var(--text)">Auto-Post:</strong> Nach dem Rendern öffnet {PRODUCT_BRAND} TikTok + YouTube Studio, legt das Video ab und kopiert Caption/Text – du klickst nur noch <em>Veröffentlichen</em>.</p>
        <p style="margin-top:.5rem"><strong style="color:var(--text)">Einmalig:</strong> In TikTok/YouTube im Browser einloggen. Config: <code>data/social-publish-config.json</code></p>
        <p style="margin-top:1rem"><strong style="color:var(--text)">Videos:</strong> Desktop → VidPuls → Videos → Frame</p>
        <p style="margin-top:1.25rem;padding-top:1rem;border-top:1px solid var(--line);font-size:.82rem">
          {legal_foot}
        </p>
      </div>
    </section>
  </main>
  <footer class="studio-legal" style="padding:.65rem 1rem;font-size:.72rem;color:var(--muted);border-top:1px solid var(--line);text-align:center">
    {legal_foot}
  </footer>
</div>
<div class="toast" id="toast"></div>
<script>
const IDEAS = {ideas_json};
const TEMPLATES = {templates_json};
let mode = 'minute_hd', imageFile = null, pollTimer = null, lastCaption = '';
const $ = id => document.getElementById(id);

function toast(msg) {{
  const t = $('toast'); t.textContent = msg; t.classList.add('show');
  setTimeout(() => t.classList.remove('show'), 2600);
}}

function setView(name) {{
  document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
  document.querySelectorAll('.nav button, .mobile-nav button').forEach(b => {{
    b.classList.toggle('active', b.dataset.view === name);
  }});
  $('view-' + name).classList.add('active');
  if (name === 'calendar') loadCalendar();
  if (name === 'pricing') loadPricing();
  if (name === 'gallery') loadGallery();
}}

document.querySelectorAll('[data-view]').forEach(b => {{
  b.onclick = () => setView(b.dataset.view);
}});

document.querySelectorAll('.mode-tabs button').forEach(t => {{
  t.onclick = () => {{
    document.querySelectorAll('.mode-tabs button').forEach(x => x.classList.remove('active'));
    t.classList.add('active');
    mode = t.dataset.mode;
    syncModeUI();
  }};
}});

const PIXVERSE_DURATION_HTML = '<option value="5" selected>5 Sek.</option><option value="8">8 Sek.</option><option value="10">10 Sek.</option>';
const PIXVERSE_QUALITY_HTML = '<option value="540p" selected>540p</option><option value="720p">720p</option><option value="1080p">1080p HD</option>';
const MINUTE_DURATION_HTML = '<option value="60">1 Minute</option><option value="90">1:30 Min.</option><option value="120" selected>2 Minuten</option>';
const MINUTE_QUALITY_HTML = '<option value="1080p">1080p Full HD</option><option value="4k" selected>4K UHD</option>';
let licenseState = {{ watermark_mode: false }};

function applyLicenseUI() {{
  if (mode !== 'minute_hd' && mode !== 'artist_dialog') return;
  const lic = licenseState;
  const kind = lic.generation_kind || (lic.watermark_mode ? 'free' : lic.pro ? 'pro' : 'none');
  $('duration').innerHTML = MINUTE_DURATION_HTML;
  $('quality').innerHTML = MINUTE_QUALITY_HTML;
  $('qualityField').classList.remove('hidden');

  if (lic.owner || kind === 'owner') {{
    $('autoPublishRow').classList.remove('hidden');
    $('hdBanner').innerHTML = '<strong>Owner</strong> – kostenlos · unbegrenzt · ohne Wasserzeichen · 4K · Auto-Post';
    $('previewHint').textContent = '60–120 Sek. · 4K · ohne Wasserzeichen';
    $('go').textContent = 'Promo-Video erstellen';
    $('go').disabled = false;
    $('engineHint').textContent = 'Owner · kostenlos';
  }} else if (kind === 'free' || lic.watermark_mode) {{
    $('autoPublishRow').classList.add('hidden');
    $('hdBanner').innerHTML = '<strong>Gratis-Video:</strong> 1× mit <strong>VidPuls-Wasserzeichen</strong> · 60–120 Sek. · kein Auto-Post';
    $('previewHint').textContent = 'Gratis · Wasserzeichen · ca. 3–8 Min. Generierung';
    $('go').textContent = 'Gratis-Video erstellen (mit Wasserzeichen)';
    $('go').disabled = false;
    $('engineHint').textContent = 'Gratis · mit Wasserzeichen';
  }} else if (lic.pro) {{
    $('autoPublishRow').classList.remove('hidden');
    $('hdBanner').innerHTML = '<strong>Pro-Abo aktiv</strong> – unbegrenzt · ohne Wasserzeichen · 4K · Auto-Post';
    $('previewHint').textContent = '60–120 Sek. · 4K · ohne Wasserzeichen';
    $('go').textContent = 'Promo-Video erstellen';
    $('go').disabled = false;
    $('engineHint').textContent = 'Pro · ohne Wasserzeichen';
  }} else if ((lic.videos_remaining || 0) > 0) {{
    $('autoPublishRow').classList.remove('hidden');
    $('hdBanner').innerHTML = '<strong>Einzelvideo</strong> – ohne Wasserzeichen · ' + lic.videos_remaining + ' übrig';
    $('previewHint').textContent = 'Bezahlt · ohne Wasserzeichen';
    $('go').textContent = 'Promo-Video erstellen';
    $('go').disabled = false;
    $('engineHint').textContent = 'Einzelvideo · ohne Wasserzeichen';
  }} else {{
    $('autoPublishRow').classList.add('hidden');
    $('hdBanner').innerHTML = '<strong>Gratis verbraucht</strong> – <a href="#" id="goPricing" style="color:var(--mint)">{single_eur} €/Video oder Pro-Abo</a>';
    $('previewHint').textContent = 'Einzelvideo oder Abo nötig';
    $('go').textContent = 'Preise ansehen';
    $('go').disabled = false;
    $('engineHint').textContent = 'Zahlung erforderlich';
    const gp = $('goPricing');
    if (gp) gp.onclick = e => {{ e.preventDefault(); setView('pricing'); }};
  }}
}}

function syncModeUI() {{
  const hd = mode === 'minute_hd' || mode === 'artist_dialog';
  const artist = mode === 'artist_dialog';
  $('dropzone').classList.toggle('active', mode === 'image');
  $('hdBanner').classList.toggle('hidden', !hd);
  $('durationField').classList.toggle('hidden', artist);
  $('qualityField').classList.toggle('hidden', false);
  $('modelField').classList.toggle('hidden', hd);
  $('audioRow').classList.toggle('hidden', hd);
  $('autoPublishRow').classList.toggle('hidden', !hd);
  if (artist) {{
    $('duration').innerHTML = '<option value="60" selected>1 Minute Dialog</option>';
    $('quality').innerHTML = MINUTE_QUALITY_HTML;
    $('aspect').value = '9:16';
    $('hdBanner').innerHTML = '<strong>Cinema3D</strong> – echtes 3D · Lip-Sync · PixVerse-KI · besser als Artist-Tools';
    $('previewHint').textContent = 'Cinema3D · 3D-Rendering · ca. 8–20 Min.';
    $('go').textContent = 'Cinema3D-Dialog generieren';
    $('engineHint').textContent = '3D-Engine + Rhubarb Lip-Sync + PixVerse';
    $('go').disabled = false;
  }} else if (hd) {{
    $('duration').innerHTML = MINUTE_DURATION_HTML;
    $('quality').innerHTML = MINUTE_QUALITY_HTML;
    $('aspect').value = '9:16';
    applyLicenseUI();
  }} else {{
    $('duration').innerHTML = PIXVERSE_DURATION_HTML;
    $('quality').innerHTML = PIXVERSE_QUALITY_HTML;
    $('go').textContent = 'Video generieren';
    $('previewHint').textContent = 'Kurz-Clip · max. 10 Sek. (PixVerse)';
  }}
  updateAspectPreview();
  refreshEngine();
}}
syncModeUI();

IDEAS.forEach(text => {{
  const c = document.createElement('button');
  c.type = 'button'; c.className = 'chip'; c.textContent = text.slice(0, 42) + (text.length > 42 ? '…' : '');
  c.title = text;
  c.onclick = () => {{ $('prompt').value = text; updateCount(); }};
  $('chips').appendChild(c);
}});

function updateCount() {{
  $('charCount').textContent = $('prompt').value.length + ' / 5000';
}}
$('prompt').oninput = updateCount;
updateCount();

const dz = $('dropzone');
dz.onclick = e => {{ if (e.target !== $('imgPreview')) $('fileIn').click(); }};
$('fileIn').onchange = e => setImage(e.target.files[0]);
dz.ondragover = e => {{ e.preventDefault(); dz.classList.add('drag'); }};
dz.ondragleave = () => dz.classList.remove('drag');
dz.ondrop = e => {{
  e.preventDefault(); dz.classList.remove('drag');
  if (e.dataTransfer.files[0]) setImage(e.dataTransfer.files[0]);
}};
function setImage(f) {{
  if (!f || !f.type.startsWith('image/')) return;
  imageFile = f;
  $('dropLabel').textContent = f.name;
  const url = URL.createObjectURL(f);
  const img = $('imgPreview');
  img.src = url; img.classList.remove('hidden');
}}

async function api(path, opts = {{}}) {{
  const r = await fetch(path, opts);
  return r.json();
}}

async function refreshEngine() {{
  const h = await api('/api/health');
  const lic = await api('/api/license');
  licenseState = lic;
  $('licensePill').textContent = lic.message || '';
  if (mode === 'minute_hd' || mode === 'artist_dialog') {{
    $('engineLabel').textContent = mode === 'artist_dialog' ? 'Artist Pipeline' : '{PRODUCT_BRAND}';
    applyLicenseUI();
    if (mode === 'artist_dialog') {{
      $('engineHint').textContent = 'Charaktere · PixVerse-Clips · ElevenLabs';
    }}
  }} else if (h.pixverse) {{
    $('engineLabel').textContent = 'PixVerse API aktiv';
    $('engineHint').textContent = 'Kurz-Clips bis 10 Sek.';
  }} else {{
    $('engineLabel').textContent = 'Fallback (IA 1 Min)';
    $('engineHint').textContent = 'Key optional für PixVerse';
  }}
}}

function setStatus(msg, ok) {{
  const s = $('status');
  s.textContent = msg;
  s.className = 'status-bar' + (ok === true ? ' ok' : ok === false ? ' err' : '');
}}

function setProgress(show, step) {{
  $('progress').classList.toggle('show', show);
  $('previewEmpty').classList.toggle('hidden', show || !$('player').classList.contains('hidden'));
  document.querySelectorAll('.step').forEach(el => {{
    const n = el.dataset.step;
    el.classList.remove('on', 'done');
    if (!show) return;
    if (n === step) el.classList.add('on');
    if ((step === 'gen' && n === 'queue') || (step === 'done' && n !== 'done')) el.classList.add('done');
    if (step === 'done' && n === 'done') el.classList.add('done');
  }});
}}

function updateAspectPreview() {{
  const box = $('previewBox');
  box.classList.toggle('landscape', $('aspect').value === '16:9');
}}
$('aspect').onchange = updateAspectPreview;
updateAspectPreview();

async function loadGallery() {{
  const d = await api('/api/gallery');
  const demos = d.demos || [];
  const mine = (d.mine || []).filter(j => j.video_url);
  const esc = s => String(s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;');

  function galCard(j) {{
    const url = j.video_url || '';
    const has = j.status === 'done' && url;
    const label = j.label ? `${{j.icon || '🎬'}} ${{esc(j.label)}}` : (j.icon || '🎬');
    const prompt = esc((j.prompt || '').slice(0, 90)) + ((j.prompt || '').length > 90 ? '…' : '');
    const badge = j.resolution || (j.demo ? '4K' : '');
    const inner = has
      ? `<video src="${{url}}" autoplay muted loop playsinline preload="metadata"></video>`
      : `<div class="gal-ph">${{j.status === 'error' ? '⚠' : '⏳'}}</div>`;
    return `<article class="gal-card" data-gal-id="${{esc(j.id)}}">${{inner}}
      ${{badge ? `<span class="gal-badge">${{esc(badge)}}</span>` : ''}}
      <div class="gal-cap"><strong>${{label}}</strong>${{prompt}}</div></article>`;
  }}

  function bindCards(root, items) {{
    root.querySelectorAll('.gal-card').forEach((el, i) => {{
      el.onclick = () => showJob(items[i % items.length]);
    }});
  }}

  const colL = $('galColL');
  const colR = $('galColR');
  const left = demos.filter((_, i) => i % 2 === 0);
  const right = demos.filter((_, i) => i % 2 === 1);
  const leftItems = left.length ? left : demos;
  const rightItems = right.length ? right : demos;
  const leftHtml = (leftItems.length ? leftItems.concat(leftItems) : []).map(galCard).join('');
  const rightHtml = (rightItems.length ? rightItems.concat(rightItems) : []).map(galCard).join('');
  colL.innerHTML = leftHtml;
  colR.innerHTML = rightHtml;
  bindCards(colL, leftItems);
  bindCards(colR, rightItems);

  const mineEl = $('galMine');
  const mineWrap = $('galMineWrap');
  if (mine.length) {{
    const doubled = mine.concat(mine);
    mineEl.innerHTML = doubled.map(galCard).join('');
    bindCards(mineEl, doubled);
    mineWrap.classList.remove('hidden');
  }} else {{
    mineEl.innerHTML = '';
    mineWrap.classList.add('hidden');
  }}

  $('galleryEmpty').classList.toggle('hidden', demos.length > 0 || mine.length > 0);
  if (!demos.length && !mine.length) {{
    $('galleryEmpty').textContent = 'Noch keine Videos – erstelle deins unter Erstellen.';
  }}
}}

function showJob(j) {{
  $('prompt').value = j.prompt || '';
  updateCount();
  const vurl = j.video_url || (j.local_video && j.id ? '/api/video/' + j.id : '');
  if (vurl && (j.status === 'done' || j.demo)) {{
    $('player').src = vurl + (vurl.includes('?') ? '&' : '?') + 't=' + Date.now();
    $('player').classList.remove('hidden');
    $('previewEmpty').classList.add('hidden');
    if (!j.demo && j.id) {{
      $('dl').href = '/api/video/' + j.id;
      $('dl').classList.remove('hidden');
    }}
    setProgress(false);
    setStatus((j.label || 'Video') + ' geladen' + (j.demo ? ' (Beispiel)' : ''), true);
    setView('create');
  }} else if (j.status === 'error') {{
    setView('create');
    setStatus(j.error || 'Fehler', false);
    setProgress(false);
  }} else {{
    setView('create');
    toast('Video noch nicht fertig');
  }}
}}

async function pollJob(id) {{
  clearInterval(pollTimer);
  setProgress(true, 'queue');
  pollTimer = setInterval(async () => {{
    const j = await api('/api/job/' + id);
    if (j.status === 'generating' || j.status === 'processing') setProgress(true, 'gen');
    if (j.status === 'done') {{
      clearInterval(pollTimer);
      setProgress(true, 'done');
      setTimeout(() => setProgress(false), 800);
      $('player').src = '/api/video/' + id + '?t=' + Date.now();
      $('player').classList.remove('hidden');
      $('previewEmpty').classList.add('hidden');
      $('dl').href = '/api/video/' + id;
      $('dl').classList.remove('hidden');
      setStatus('Fertig! ' + (j.duration_sec ? j.duration_sec + 's' : 'Video bereit.'), true);
      if (j.publish_queued) toast('TikTok/YouTube Upload vorbereitet – Browser prüfen');
      else if (j.publish_error) toast('Auto-Post Fehler: ' + j.publish_error);
      toast('Video fertig');
      loadGallery();
    }} else if (j.status === 'error') {{
      clearInterval(pollTimer);
      setProgress(false);
      setStatus(j.error || 'Generierung fehlgeschlagen', false);
    }} else {{
      setStatus('Generiere… (' + (j.status || '…') + ') – 1–3 Min.');
    }}
  }}, 3500);
}}

async function generate() {{
  const prompt = $('prompt').value.trim();
  if (!prompt) {{ setStatus('Bitte Prompt eingeben', false); return; }}
  if ((mode === 'minute_hd' || mode === 'artist_dialog') && !licenseState.ok) {{
    setView('pricing');
    toast('Gratis-Video verbraucht – Einzelvideo oder Abo wählen');
    return;
  }}
  $('go').disabled = true;
  setStatus('Starte…');
  setProgress(true, 'queue');
  $('player').classList.add('hidden');
  $('dl').classList.add('hidden');
  try {{
    let r;
    if (mode === 'image' && imageFile) {{
      const b64 = await new Promise((res, rej) => {{
        const fr = new FileReader();
        fr.onload = () => res(String(fr.result).split(',')[1] || '');
        fr.onerror = rej;
        fr.readAsDataURL(imageFile);
      }});
      r = await api('/api/generate/image', {{
        method: 'POST',
        headers: {{ 'Content-Type': 'application/json' }},
        body: JSON.stringify({{
          prompt, filename: imageFile.name, image_base64: b64,
          aspect_ratio: $('aspect').value, duration: +$('duration').value,
          quality: $('quality').value, model: $('model').value
        }})
      }});
    }} else {{
      const payload = {{
        prompt, aspect_ratio: $('aspect').value,
        duration: +$('duration').value,
        quality: $('quality').value,
        model: $('model').value, audio: $('audio').checked
      }};
      if (mode === 'minute_hd') {{
        payload.mode = 'minute_hd';
        payload.publish_tiktok = $('pubTiktok').checked;
        payload.publish_youtube = $('pubYoutube').checked;
        payload.publish_instagram = $('pubInstagram').checked;
        payload.auto_publish = payload.publish_tiktok || payload.publish_youtube || payload.publish_instagram;
      }} else if (mode === 'artist_dialog') {{
        payload.mode = 'artist_dialog';
        payload.dialogue = true;
        payload.visual_style = '3d';
        payload.title = 'Das Wochenend-Date · Artist';
        payload.duration = 60;
        payload.quality = $('quality').value || '4k';
        payload.publish_tiktok = $('pubTiktok').checked;
        payload.publish_youtube = $('pubYoutube').checked;
        payload.publish_instagram = $('pubInstagram').checked;
        payload.auto_publish = payload.publish_tiktok || payload.publish_youtube || payload.publish_instagram;
      }} else payload.mode = 'text';
      r = await api('/api/generate/text', {{
        method: 'POST',
        headers: {{ 'Content-Type': 'application/json' }},
        body: JSON.stringify(payload)
      }});
    }}
    if (!r.ok) throw new Error(r.error || 'Start fehlgeschlagen');
    setStatus('Job ' + r.jobId + ' (' + r.engine + ')');
    pollJob(r.jobId);
  }} catch (e) {{
    setProgress(false);
    setStatus(String(e.message || e), false);
  }} finally {{
    $('go').disabled = false;
  }}
}}

$('go').onclick = generate;
document.addEventListener('keydown', e => {{
  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {{ e.preventDefault(); generate(); }}
}});

$('enhance').onclick = async () => {{
  const t = $('prompt').value.trim();
  if (!t) {{ toast('Erst Prompt eingeben'); return; }}
  $('enhance').disabled = true;
  setStatus('Verbessere Prompt…');
  try {{
    const r = await api('/api/enhance-prompt', {{
      method: 'POST',
      headers: {{ 'Content-Type': 'application/json' }},
      body: JSON.stringify({{ prompt: t }})
    }});
    if (r.prompt) {{
      $('prompt').value = r.prompt;
      updateCount();
      toast('Prompt verbessert');
      setStatus('Prompt optimiert – jetzt generieren', true);
    }}
  }} catch (e) {{ setStatus('Enhance fehlgeschlagen', false); }}
  finally {{ $('enhance').disabled = false; }}
}};

$('copyCap').onclick = async () => {{
  if (!lastCaption) {{ toast('Keine Caption'); return; }}
  try {{
    await navigator.clipboard.writeText(lastCaption);
    toast('Caption kopiert');
  }} catch (e) {{ toast('Kopieren fehlgeschlagen'); }}
}};

$('openFolder').onclick = () => toast('Videos: Desktop → VidPuls → Videos → Frame');

const tg = $('templateGrid');
TEMPLATES.forEach(t => {{
  const el = document.createElement('div');
  el.className = 'g-card';
  el.innerHTML = `<div class="g-thumb">${{t.icon}}<span class="badge">${{t.label}}</span></div><div class="g-body"><p>${{t.prompt.slice(0, 90)}}…</p></div>`;
  el.onclick = () => {{ $('prompt').value = t.prompt; updateCount(); setView('create'); toast(t.label); }};
  tg.appendChild(el);
}});

async function loadCalendar() {{
  const d = await api('/api/calendar');
  const list = $('calendarList');
  const entries = d.entries || [];
  if (!entries.length) {{
    list.innerHTML = '<p>Noch kein Kalender – „Kalender erstellen“ klicken.</p>';
    return;
  }}
  list.innerHTML = entries.map(e => {{
    const dt = (e.date || '').slice(0, 16).replace('T', ' ');
    const st = e.status || 'planned';
    return `<div style="padding:.5rem 0;border-bottom:1px solid var(--line)">
      <strong style="color:var(--text)">${{dt}}</strong> · ${{e.label}} · ${{st}}
      <button type="button" class="chip" style="margin-left:.5rem" data-eid="${{e.id}}">Start</button>
    </div>`;
  }}).join('');
  list.querySelectorAll('[data-eid]').forEach(btn => {{
    btn.onclick = async () => {{
      const r = await api('/api/calendar/run', {{
        method: 'POST', headers: {{ 'Content-Type': 'application/json' }},
        body: JSON.stringify({{ entryId: btn.dataset.eid }})
      }});
      toast(r.message || (r.ok ? 'Gestartet' : r.error));
      loadCalendar();
    }};
  }});
}}

async function loadPricing() {{
  const p = await api('/api/pricing');
  const tiers = p.products || {{}};
  const sub = p.subscription || {{}};
  const methods = (p.paymentMethods || []).slice(0, 6).join(' · ');
  const sepa = p.sepa;
  const monthly = tiers.pro_monthly || {{}};
  const yearly = tiers.pro_yearly || {{}};
  const single = tiers.video_1 || {{}};
  const sPrice = Number(single.price_eur || {single_eur_num}).toFixed(2).replace('.', ',');
  const mPrice = Number(monthly.price_eur || sub.monthly_eur || 19.99).toFixed(2).replace('.', ',');
  const yPrice = Number(yearly.price_eur || sub.yearly_eur || 167.92).toFixed(2).replace('.', ',');
  const sepaHtml = sepa ? `<p style="margin-top:.75rem;font-size:.82rem"><strong style="color:var(--text)">Überweisung:</strong> ${{sepa.holder}} · IBAN ${{sepa.iban}} · ${{sepa.note}}</p>` : '';
  $('pricingBox').innerHTML = `
    <p><strong style="color:var(--text)">Gratis</strong> – 1 Video mit VidPuls-Wasserzeichen</p>
    <div class="row" style="align-items:center;margin:.75rem 0;flex-wrap:wrap;gap:.35rem">
      <span style="flex:1;min-width:140px;color:var(--text)">1 Video ohne Wasserzeichen</span>
      <strong>${{sPrice}} €</strong>
      <button type="button" class="btn buy-btn" data-pid="video_1">Stripe</button>
      ${{single.paypal_url ? `<a class="btn btn-ghost" href="${{single.paypal_url}}" target="_blank" rel="noopener" style="text-decoration:none">PayPal</a>` : ''}}
    </div>
    <div class="row" style="align-items:center;margin:.75rem 0;flex-wrap:wrap;gap:.35rem">
      <span style="flex:1;min-width:140px;color:var(--text)">VidPuls Pro · Monat</span>
      <strong>${{mPrice}} €/Monat</strong>
      <button type="button" class="btn buy-btn" data-pid="pro_monthly">Stripe</button>
      ${{monthly.paypal_url ? `<a class="btn btn-ghost" href="${{monthly.paypal_url}}" target="_blank" rel="noopener" style="text-decoration:none">PayPal</a>` : ''}}
    </div>
    <div class="row" style="align-items:center;margin:.75rem 0;flex-wrap:wrap;gap:.35rem">
      <span style="flex:1;min-width:140px;color:var(--text)">VidPuls Pro · Jahr (−30 %)</span>
      <strong>${{yPrice}} €/Jahr</strong>
      <button type="button" class="btn buy-btn" data-pid="pro_yearly">Stripe</button>
      ${{yearly.paypal_url ? `<a class="btn btn-ghost" href="${{yearly.paypal_url}}" target="_blank" rel="noopener" style="text-decoration:none">PayPal</a>` : ''}}
    </div>
    <p style="font-size:.82rem;margin-top:.5rem">${{sub.price_note || ''}}</p>
    <p style="margin-top:.85rem;font-size:.82rem;color:var(--mint)"><strong>Zahlung:</strong> ${{methods || 'Karte · PayPal · Klarna · Apple/Google Pay · SEPA'}}</p>
    <p style="font-size:.82rem">Pro &amp; Einzelvideo: ohne Wasserzeichen · unbegrenzt nur mit Pro</p>${{sepaHtml}}`;
  document.querySelectorAll('.buy-btn').forEach(b => {{
    b.onclick = async () => {{
      const r = await api('/api/checkout', {{
        method: 'POST', headers: {{ 'Content-Type': 'application/json' }},
        body: JSON.stringify({{ productId: b.dataset.pid }})
      }});
      if (r.url) window.location.href = r.url;
      else toast(r.error || 'Checkout fehlgeschlagen');
    }};
  }});
}}

$('genCal').onclick = async () => {{
  const r = await api('/api/calendar/generate', {{ method: 'POST', headers: {{ 'Content-Type': 'application/json' }}, body: '{{}}' }});
  toast((r.entries || []).length + ' Termine geplant');
  loadCalendar();
}};
$('runDueCal').onclick = async () => {{
  const r = await api('/api/calendar/run', {{ method: 'POST', headers: {{ 'Content-Type': 'application/json' }}, body: JSON.stringify({{ limit: 1 }}) }});
  toast('Fällige Jobs: ' + ((r.results || []).length));
  loadCalendar();
}};

const qsView = new URLSearchParams(location.search).get('view');
const VIEWS = ['create', 'templates', 'calendar', 'gallery', 'help', 'pricing'];
if (qsView && qsView !== 'pricing' && VIEWS.includes(qsView)) setView(qsView);
else setView('create');
if (location.search) history.replaceState(null, '', location.pathname);

refreshEngine();
loadGallery();
{jami_script}
</script>
</body>
</html>"""
