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>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user