#!/usr/bin/env python3
"""Builds the graphic LifeWave backgrounds as SVG and renders them to JPEG.
Square 2048 px, so `background-size: cover` crops them evenly in portrait and
landscape. Palette: ink #292a2b / #1a1b1c, gold #f5c26b / #d59040, paper
#f7f3ea. Deterministic: same seed, same file.
Usage: python3 build_graphic.py [out_dir] (default dev/backgrounds/graphic)
"""
import os, random, subprocess, sys
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, 'dev', 'backgrounds', 'graphic')
S = 2048
GRAIN = '''
'''
def svg(defs, body):
return f'\n'
def ink():
defs = '''
''' + GRAIN.format(alpha=0.06)
body = f''
return svg(defs, body)
def gold_dust():
rnd = random.Random(2026)
specks = []
for _ in range(5200):
# Denser and brighter towards the bottom; the middle band stays calm.
y = S * (1 - rnd.random() ** 0.45)
x = rnd.random() * S
r = rnd.choice([1.0, 1.3, 1.6, 2.0, 2.6, 3.4, 4.2])
depth = y / S
o = min(0.95, (0.08 + 1.1 * depth ** 1.6) * rnd.uniform(0.35, 1.0))
c = '#f5c26b' if rnd.random() < 0.7 else '#d59040'
specks.append(f'')
defs = '''
''' + GRAIN.format(alpha=0.05)
body = (f''
f''
f'{"".join(specks[:2200])}{"".join(specks[2200:])}'
f'')
return svg(defs, body)
def horizon():
defs = '''
''' + GRAIN.format(alpha=0.05)
body = (f''
f''
f''
f'')
return svg(defs, body)
def paper():
defs = '''
''' + GRAIN.format(alpha=0.05)
body = (f''
f''
f'')
return svg(defs, body)
def charcoal():
rnd = random.Random(11)
smudges = ''.join(
f''
for _ in range(9))
defs = '''
''' + GRAIN.format(alpha=0.08)
body = (f''
f''
f'{smudges}'
f'')
return svg(defs, body)
BACKGROUNDS = [
('01-tinte', ink), ('02-goldstaub', gold_dust), ('03-horizont', horizon),
('04-papier', paper), ('07-kohle', charcoal),
]
if __name__ == '__main__':
os.makedirs(OUT, exist_ok=True)
for name, fn in BACKGROUNDS:
svg_path = os.path.join(OUT, f'{name}.svg')
png_path = os.path.join(OUT, f'{name}.png')
jpg_path = os.path.join(OUT, f'{name}.jpg')
open(svg_path, 'w').write(fn())
subprocess.run(['rsvg-convert', '-w', str(S), '-h', str(S), svg_path, '-o', png_path], check=True)
subprocess.run(['ffmpeg', '-v', 'error', '-y', '-i', png_path, '-q:v', '3', jpg_path], check=True)
os.remove(png_path)
print(jpg_path, os.path.getsize(jpg_path) // 1024, 'KB')