← zurück zur Toolbox

og_editor_web_v1.py · Quelltext

9342 Bytes · SHA-256: 307c8847c4cb397d93ebbf531e3019fad5be521c0bbcf6c673c38b5863edf6e9

⤓ Download
#!/usr/home/jozapf/public_html/toolenv/bin/python
# -*- coding: utf-8 -*-
# TOOLBOX-TILE: {"title": "OG Editor", "desc": "Interaktiver OG-Bild-Editor: Text mit der Maus ziehen, eigenes Hintergrundbild, Farben & Abdunkeln live — PNG direkt im Browser. Alles läuft lokal, nichts verlässt den Rechner.", "icon": "🎨", "type": "web", "example": "?title=Dein+Titel&subtitle=Kurzer+Untertitel", "order": 26}
"""
OG Editor Web — interactive, client-side Open Graph image editor.
Version: 1.0.2
Date: 2026-07-18

This CGI is a THIN shell: it serves a self-contained HTML + <canvas> app (so the
toolbox tile-discovery, which scans *.py, lists it). Drag-to-place text, background
upload, live darkening / colours / accent, PNG export — all in the browser. Nothing
is uploaded.

1.0.1: the JS lives in a SEPARATE same-origin file (og_editor_web_v1.js). The
toolbox sets a strict `Content-Security-Policy: script-src 'self'`, which blocks
inline <script>; an external same-origin script is allowed. Editor defaults are read
from the query string client-side (title/subtitle/site/url).
"""

import sys


def main():
    sys.stdout.write("Content-Type: text/html; charset=utf-8\n\n")
    sys.stdout.write(PAGE)


PAGE = r"""<!DOCTYPE html>
<!-- jozapf.de toolbox · rev jzt-7c3f9a2e -->
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Jo Zapf Toolbox – OG Editor</title>
<link rel="preconnect" href="https://assets.jozapf.de" crossorigin>
<link rel="stylesheet" href="https://assets.jozapf.de/css/fonts.css">
<style>
:root{--bg:#0f172a;--bg2:#1e293b;--tx:#f1f5f9;--tx2:#94a3b8;--accent:#3b82f6;
--accent2:#8b5cf6;--bd:#334155;--code:#0f172a;}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Montserrat',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
background:var(--bg);color:var(--tx);line-height:1.6;min-height:100vh;padding:2rem}
.container{max-width:1000px;margin:0 auto}
h1{font-size:2rem;font-weight:700;margin-bottom:.4rem;
background:linear-gradient(135deg,var(--accent),var(--accent2));
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.subtitle{color:var(--tx2);margin-bottom:1.5rem}
h2{font-size:1.2rem;margin:0 0 1rem}
h2 .sub{font-size:.85rem;color:var(--tx2);font-weight:400}
.card{background:var(--bg2);border:1px solid var(--bd);border-radius:12px;padding:1.5rem;margin-bottom:1.25rem}
canvas{width:100%;height:auto;border-radius:10px;border:1px solid var(--bd);display:block;touch-action:none;cursor:default}
.tip{font-size:.85rem;color:var(--tx2);margin-top:.75rem}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-top:1rem}
label{display:block;font-size:.85rem;color:var(--tx2);margin:0 0 .35rem}
input[type=text],select,input[type=file]{width:100%;padding:.6rem .8rem;border-radius:8px;border:1px solid var(--bd);
background:var(--bg);color:var(--tx);font-size:.95rem;font-family:inherit}
input[type=range]{width:100%}
input[type=color]{width:100%;height:2.6rem;border:1px solid var(--bd);border-radius:8px;background:var(--bg);padding:.2rem}
input:focus,select:focus{outline:none;border-color:var(--accent)}
.full{grid-column:1 / -1}
.ghead{grid-column:1 / -1;font-size:.72rem;text-transform:uppercase;letter-spacing:.08em;
color:var(--tx2);font-weight:700;margin-top:.6rem;padding-top:.85rem;border-top:1px solid var(--bd)}
.ghead:first-child{margin-top:0;padding-top:0;border-top:none}
.row{display:flex;align-items:center;gap:.5rem}
.row input[type=checkbox]{width:1.1rem;height:1.1rem}
.btns{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:1rem}
button{padding:.7rem 1.3rem;border-radius:8px;border:none;cursor:pointer;
background:var(--accent);color:#fff;font-size:.95rem;font-weight:600;font-family:inherit}
button.ghost{background:transparent;border:1px solid var(--bd);color:var(--tx2)}
button:hover{filter:brightness(1.1)}
code{font-family:'JetBrains Mono',Consolas,monospace;font-size:.85em;background:var(--code);
color:#e879f9;padding:.15em .4em;border-radius:4px}
pre{white-space:pre-wrap;word-break:break-all;background:var(--bg);color:var(--tx2);
padding:1rem;border-radius:8px;border:1px solid var(--bd);font-size:.82rem;
font-family:'JetBrains Mono',Consolas,monospace;max-height:300px;overflow:auto}
footer{text-align:center;margin-top:1.75rem;font-size:.85rem;color:var(--tx2)}
footer a{color:var(--accent);text-decoration:none;font-weight:500}
@media(max-width:640px){body{padding:1rem}h1{font-size:1.5rem}.grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="container">
<h1>toolbox.jozapf.de | OG Editor</h1>
<p class="subtitle">Interaktiver Open-Graph-Editor — Text mit der Maus ziehen, Hintergrund & Farben live. Ein Hintergrundbild wird dabei nur in deinem Browser geöffnet — es wird <strong>nicht an einen Server gesendet und nirgends gespeichert</strong>. Auch das fertige OG-Bild entsteht direkt auf deinem Gerät.</p>

<div class="card">
  <canvas id="cv" width="1200" height="630"></canvas>
  <p class="tip">Tipp: <strong>Titel, Untertitel und Footer</strong> mit der Maus (oder Touch) verschieben. Ein <strong>Hintergrundbild</strong> ziehst du auf freier Fläche zurecht; den Ausschnitt regelt der <strong>Bild-Zoom</strong>.</p>
</div>

<div class="card">
  <h2>Inhalt &amp; Stil</h2>
  <div class="grid">
    <div class="ghead">Texte</div>
    <div class="full"><label>Titel</label><input type="text" id="t_title"></div>
    <div class="full"><label>Untertitel</label><input type="text" id="t_sub"></div>
    <div class="full"><label>Footer / Site</label><input type="text" id="t_site"></div>

    <div class="ghead">Schriftgrößen</div>
    <div><label>Titel <span id="l_ts" class="sub"></span></label><input type="range" id="r_ts" min="40" max="120" value="76"></div>
    <div><label>Untertitel <span id="l_ss" class="sub"></span></label><input type="range" id="r_ss" min="18" max="80" value="34"></div>
    <div><label>Footer <span id="l_fs" class="sub"></span></label><input type="range" id="r_fs" min="16" max="60" value="30"></div>

    <div class="ghead">Schriftfarben</div>
    <div><label>Titel</label><input type="color" id="c_ct" value="#f1f5f9"></div>
    <div><label>Untertitel</label><input type="color" id="c_cs" value="#f1f5f9"></div>
    <div><label>Footer</label><input type="color" id="c_cf" value="#f1f5f9"></div>

    <div class="ghead">Footer-Icon</div>
    <div><label>Form</label>
      <select id="s_icon"><option value="dot">● Punkt</option><option value="ring">○ Ring</option><option value="square">■ Quadrat</option><option value="diamond">◆ Raute</option><option value="triangle">▲ Dreieck</option><option value="star">★ Stern</option><option value="chevron">› Pfeil</option></select></div>
    <div><label>&nbsp;</label><label class="row"><input type="checkbox" id="chk_icon" checked> Icon anzeigen</label></div>

    <div class="ghead">Akzentbalken</div>
    <div><label>Farbe <span class="sub">(auch Icon)</span></label><input type="color" id="c_ac" value="#38e07b"></div>
    <div><label>Breite <span id="l_acw" class="sub"></span></label><input type="range" id="r_acw" min="4" max="60" value="14"></div>
    <div><label>Position</label><select id="s_acpos"><option value="left">links</option><option value="right">rechts</option><option value="top">oben</option><option value="bottom">unten</option></select></div>
    <div><label>&nbsp;</label><label class="row"><input type="checkbox" id="chk_ac" checked> Balken anzeigen</label></div>

    <div class="ghead">Hintergrund</div>
    <div><label>Farbschema</label>
      <select id="s_theme"><option value="slate">slate</option><option value="ocean">ocean</option><option value="violet">violet</option><option value="light">light</option></select></div>
    <div><label>Hintergrundbild <span class="sub">(max. 12 MB)</span></label><input type="file" id="f_bg" accept="image/png,image/jpeg,image/webp,image/gif,image/avif"><span id="bg_msg" style="display:block;font-size:.8rem;color:#f87171;margin-top:.35rem"></span></div>
    <div><label>Abdunkeln <span id="l_dk" class="sub"></span></label><input type="range" id="r_dk" min="0" max="90" value="45"></div>
    <div><label>Bild-Zoom / Ausschnitt <span id="l_zm" class="sub"></span></label><input type="range" id="r_zm" min="100" max="300" value="100"></div>
  </div>
  <div class="btns">
    <button id="b_dl">PNG herunterladen</button>
    <button class="ghost" id="b_clear">Hintergrundbild entfernen</button>
    <button class="ghost" id="b_reset">Positionen zurücksetzen</button>
  </div>
</div>

<div class="card">
  <h2>Meta-Tags <span class="sub">(in den &lt;head&gt; kopieren)</span></h2>
  <p class="tip">Ziel-URL deiner Seite eingeben; lade das PNG herunter und lege es als <code>/og-image.png</code> auf <strong>deiner</strong> Domain ab.</p>
  <label>Ziel-URL</label><input type="text" id="pageurl" placeholder="https://deine-domain.de/seite">
  <pre id="tags" style="margin-top:1rem"></pre>
</div>

<footer>Lieber ein individuelles OG-Bild oder Design vom Grafiker? <a href="https://jozapf.de" target="_blank" rel="noopener">jozapf.de</a> · kein Tracking · Bild bleibt auf deinem Gerät · läuft komplett im Browser</footer>
</div>
<script src="og_editor_web_v1.js?v=3"></script>
</body>
</html>
"""


if __name__ == "__main__":
    main()