Files
project-lyra/lyra/web/gen_icons.py
T
serversdown fa168271e1 feat(web): iPhone PWA fixes (M1) + warm RTO redesign (M2)
M1 — PWA mechanics:
- Generate real app icons (apple-touch-icon + manifest 192/512/maskable)
  via pure-stdlib gen_icons.py; iOS uses apple-touch-icon, not manifest icons.
- viewport-fit=cover + env(safe-area-inset-*) on header/input/menu so content
  clears the notch and home indicator.
- Dynamic height pinned to the VisualViewport (height + offsetTop, re-measured
  across the keyboard animation) so the input stays above the iOS keyboard;
  100dvh fallback. Kills the squish/gap bugs in standalone mode.
- overscroll containment; flesh out manifest (scope, portrait, maskable).

M2 — visual redesign:
- Realign style.css to the warm low-glow RTO palette already used by the
  standalone pages (#0e0e0e panels, #2a1d12 borders); remove the neon
  saturated-orange borders and ~15 glow shadows.
- Reserve filled accent for one element (Send); glow only on status pulse +
  input focus. Flat warm message bubbles with tail corners.
- Reclaim the mobile header into [≡] Lyra · [status dot]; drop the redundant
  status bar (relay status now the header dot, updated in checkHealth).
- prefers-reduced-motion support; fix undefined var(--text); real light-mode tokens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 23:20:11 +00:00

76 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""Generate Lyra PWA icons with no third-party deps (pure stdlib PNG writer).
Design: RTO warm/low-glow — near-black field, a soft orange ambient glow, and a
luminous gold-orange ring (the "orb/portal"). iOS masks corners itself, so icons
are full-bleed squares. Run from anywhere; writes PNGs into ./static.
"""
import math
import os
import struct
import zlib
HERE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
BG = (7, 7, 7) # #070707
ORANGE = (255, 122, 0) # #ff7a00 accent
GOLD = (255, 179, 71) # #ffb347 hot core
def _png(width, height, rgb_rows):
def chunk(tag, data):
return (struct.pack(">I", len(data)) + tag + data
+ struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF))
raw = bytearray()
for row in rgb_rows:
raw.append(0) # filter type 0 (None)
raw.extend(row)
ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0) # 8-bit RGB
return (b"\x89PNG\r\n\x1a\n"
+ chunk(b"IHDR", ihdr)
+ chunk(b"IDAT", zlib.compress(bytes(raw), 9))
+ chunk(b"IEND", b""))
def render(n):
c = (n - 1) / 2.0
sigma_glow = n * 0.30
ring_r = n * 0.30
ring_w = n * 0.050
core_sigma = n * 0.11
rows = []
for y in range(n):
row = bytearray()
for x in range(n):
dx, dy = x - c, y - c
d = math.hypot(dx, dy)
r, g, b = BG
# ambient orange glow
glow = math.exp(-(d * d) / (2 * sigma_glow * sigma_glow)) * 0.50
# soft hot core
core = math.exp(-(d * d) / (2 * core_sigma * core_sigma)) * 0.45
# luminous ring
rr = d - ring_r
ring = math.exp(-(rr * rr) / (2 * ring_w * ring_w))
r += ORANGE[0] * glow + GOLD[0] * (ring + core)
g += ORANGE[1] * glow + GOLD[1] * (ring + core)
b += ORANGE[2] * glow + GOLD[2] * (ring + core)
row += bytes((min(255, int(r)), min(255, int(g)), min(255, int(b))))
rows.append(row)
return rows
def write(name, n):
rows = render(n)
with open(os.path.join(HERE, name), "wb") as f:
f.write(_png(n, n, rows))
print(f"wrote {name} ({n}x{n})")
if __name__ == "__main__":
write("icon-512.png", 512)
write("icon-192.png", 192)
write("apple-touch-icon.png", 180)
write("icon-maskable-512.png", 512)