Stand 16.10.2026. Enthält das Marketing-Dashboard, die Daten in daten/, die Produktionsvorgaben, Reels und das Webinar mit Skript, Sprechzettel und Präsentation (HTML, PowerPoint, PDF). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
3.6 KiB
Python
88 lines
3.6 KiB
Python
"""Wabenmuster für Slide 2 (noah) und Slide 3 (problem).
|
|
|
|
Aufruf: python3 waben.py <Ordner mit den Slide-Dateien>, zum Beispiel quelle/slides.
|
|
|
|
Ein Raster für beide Slides. Auf Slide 2 liegt es um Noahs Foto und läuft
|
|
unten bündig aus der Slide heraus. Slide 3 setzt dieselben Reihen oben fort
|
|
(y minus 1080) und blendet nach unten aus. Die Reihe auf y = 1080 wird von
|
|
der Kante halbiert: obere Hälfte auf Slide 2, untere auf Slide 3.
|
|
"""
|
|
import math, re, sys
|
|
from pathlib import Path
|
|
|
|
P = Path(sys.argv[1])
|
|
X0, W = 960, 960
|
|
R = 45
|
|
DX, DY = math.sqrt(3) * R, 1.5 * R
|
|
RAHMEN = (1164, 199, 1716, 881) # Foto-Rahmen auf Slide 2
|
|
MX, MY = 1440, 540
|
|
SEITLICH = 175 # Ausblenden nach links und rechts
|
|
UNTEN3 = 330 # Ausblenden nach unten auf Slide 3
|
|
|
|
def seitlich(x):
|
|
l, r = RAHMEN[0], RAHMEN[2]
|
|
d = max(l - x, 0, x - r)
|
|
return max(0.0, 1 - d / SEITLICH)
|
|
|
|
def stark(op):
|
|
return op ** 1.4
|
|
|
|
def punkte(x, y):
|
|
return " ".join(f"{x - X0 + R * math.cos(math.radians(a)):.1f},{y + R * math.sin(math.radians(a)):.1f}"
|
|
for a in (-90, -30, 30, 90, 150, 210))
|
|
|
|
def zelle(x, y, op, gelb=False):
|
|
if gelb:
|
|
return f'<polygon points="{punkte(x, y)}" fill="#F1B729"/>'
|
|
return (f'<polygon points="{punkte(x, y)}" fill="#FFFFFF" fill-opacity="{op:.2f}" '
|
|
f'stroke="#D6D6D9" stroke-opacity="{op:.2f}" stroke-width="1.5"/>')
|
|
|
|
def raster():
|
|
for j in range(-10, 14):
|
|
for i in range(-8, 9):
|
|
yield MX + i * DX + (DX / 2 if j % 2 else 0), MY + j * DY
|
|
|
|
# Slide 2: Abstand zum Rahmen, der nach unten bis über die Kante verlängert ist.
|
|
def slide2():
|
|
l, o, r, u = RAHMEN
|
|
zellen = []
|
|
for x, y in raster():
|
|
if not (X0 - R < x < X0 + W + R and -R < y < 1080 + R): continue
|
|
if l <= x <= r and o <= y <= u: continue # hinter dem Foto
|
|
d = math.hypot(max(l - x, 0, x - r), max(o - y, 0))
|
|
op = stark(max(0.0, 1 - d / SEITLICH))
|
|
if op >= 0.04: zellen.append((x, y, d, op))
|
|
def naechste(zx, zy):
|
|
return min((z for z in zellen if 20 < z[2] < 90 or (z[1] > u and z[2] < 5)),
|
|
key=lambda z: math.hypot(z[0] - zx, z[1] - zy))
|
|
gelb = {naechste(1120, 300)[:2], naechste(1770, 560)[:2], naechste(1250, 945)[:2]}
|
|
return "".join(zelle(x, y, op, (x, y) in gelb) for x, y, d, op in zellen), 1080
|
|
|
|
# Slide 3: dieselben Spalten, Reihen um 1080 nach oben verschoben.
|
|
def slide3():
|
|
teile = []
|
|
for x, y in raster():
|
|
y3 = y - 1080
|
|
if not (X0 - R < x < X0 + W + R and -R < y3 < UNTEN3 + R): continue
|
|
op = stark(seitlich(x) * max(0.0, 1 - max(y3, 0) / UNTEN3))
|
|
if op >= 0.04: teile.append(zelle(x, y3, op))
|
|
return "".join(teile), UNTEN3 + R
|
|
|
|
def einsetzen(datei, svg_inhalt, hoehe, vor):
|
|
s = (P / datei).read_text()
|
|
s = re.sub(r'<svg aria-label="" data-waben="1".*?</svg>\n', "", s, flags=re.S)
|
|
s = re.sub(r'<svg aria-label="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 1080".*?</svg>\n', "", s, flags=re.S)
|
|
svg = (f'<svg aria-label="" data-waben="1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {hoehe}" width="{W}" height="{hoehe}" '
|
|
f'style="position:absolute;left:{X0}px;top:0px;width:{W}px;height:{hoehe}px">{svg_inhalt}</svg>\n')
|
|
i = s.index(vor)
|
|
s = s[:i] + svg + s[i:]
|
|
(P / datei).write_text(s)
|
|
|
|
inhalt, h = slide2()
|
|
einsetzen("noah.html", inhalt, h, '<svg aria-label="Rahmen um das Foto"')
|
|
inhalt, h = slide3()
|
|
s = (P / "problem.html").read_text()
|
|
erstes_kind = s.index(">", s.index("<section")) + 2 # direkt nach dem Section-Tag
|
|
einsetzen("problem.html", inhalt, h, s[erstes_kind:erstes_kind + 40])
|
|
print("ok")
|