Files
OrbitTv/design/create-orboffice.py
pauljdandClaude Opus 5.5 934f82d805
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 1m48s
pauljd/davidson-ci: Davidson CI / ai-review (push) Successful in 49s
Orbit TV at 0.16.0-tuning-screen
First commit, imported from the working folder: Android TV app, loopback
guide server, design sources and per-release verification notes.

Includes the 0.16 tuning screen, built and tested but not yet confirmed
on the box.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-01 17:25:30 +00:00

66 lines
4.9 KiB
Python

from pathlib import Path
from html import escape
from PIL import Image, ImageDraw, ImageFont
p=Path(__file__).parent
N='#071632';P='#12356b';Y='#ffde59';M='#b7cbe8';W='#ffffff';C='#f7eac8'
def build(section):
a=['<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080">']
def rect(x,y,w,h,c,stroke=None):a.append(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" fill="{c}"'+(f' stroke="{stroke}" stroke-width="2"' if stroke else '')+'/>')
def text(x,y,s,size=28,c=W,bold=False,font='Barlow'):
a.append(f'<text x="{x}" y="{y}" font-family="{font}" font-size="{size}" fill="{c}" font-weight="{600 if bold else 400}">{escape(s)}</text>')
rect(0,0,1920,1080,N)
a.append(f'<circle cx="106" cy="87" r="32" fill="none" stroke="{Y}" stroke-width="7"/><ellipse cx="106" cy="87" rx="51" ry="14" transform="rotate(-25 106 87)" fill="none" stroke="white" stroke-width="4"/>')
text(175,108,'orbit',68,bold=True);text(360,103,'/ Classic',34,M)
text(1440,101,'Saturday 31 October',26);text(1770,101,'21:30',28,Y,True)
for x,label,active in [(64,'1 Menu',section=='menu'),(412,'2 TV Guide',False),(760,'3 Box Office',section=='office')]:
rect(x,150,340,64,'#194c91' if active else P,'#38629a');text(x+24,192,label,29,bold=active)
if section=='office':
text(64,296,'OrbOffice',68,Y,font='Barlow Condensed');text(370,288,'Your front-row seat.',32,M)
tx=64;ty=340;tw=1070;th=590;seam=tx+tw*.76
rect(tx,ty,tw,th,C)
a.append(f'<line x1="{seam}" y1="{ty}" x2="{seam}" y2="{ty+th}" stroke="{N}" stroke-opacity=".5" stroke-width="2" stroke-dasharray="10 10"/>')
for i in range(12):
for x in [tx,tx+tw]:a.append(f'<circle cx="{x}" cy="{ty+(i+.5)*th/12}" r="10" fill="{N}"/>')
for y in [ty,ty+th]:a.append(f'<circle cx="{seam}" cy="{y}" r="18" fill="{N}"/>')
text(110,408,'OrbOffice',48,N,font='Barlow Condensed');text(110,450,'Orbit premieres',26,N)
text(110,550,'The Last',72,N,font='Barlow Condensed');text(110,629,'Shuttle Home',72,N,font='Barlow Condensed')
text(110,745,'Tonight at 21:30',36,N,True);text(110,787,'One universe. Best seat in the house.',27,N)
rect(110,824,236,66,Y);text(137,870,'Admit one',39,N,font='Barlow Condensed')
text(seam+60,420,'orbit',42,N,True);text(seam+50,465,'Front row',32,N,font='Barlow Condensed')
text(seam+77,658,'01',105,N,font='Barlow Condensed');text(seam+84,700,'Seat',25,N)
text(seam+66,845,'ORB-001',30,N,font='Barlow Condensed');text(seam+75,883,'Preview',23,N)
text(64,976,'Preview ticket • Fictional event • Included in your collection',26,M)
text(1210,320,'Special transmissions',40,bold=True)
rect(1210,356,646,77,Y,W);text(1238,407,'Orbit premieres',32,N,True)
rect(1210,445,646,77,P,'#38629a');text(1238,496,'After dark',32)
for y,s in [(590,'One final journey brings an unlikely'),(634,'crew together. A fictional premiere'),(678,'for your own front-row seat.')]:text(1210,y,s,32,M)
text(1210,800,'Event playback comes with the',28,M);text(1210,838,'Emby connection.',28,M)
rect(1210,874,646,66,P,'#38629a');text(1240,918,'Return to TV Guide',29)
else:
rect(64,258,1792,690,P)
text(112,365,'Your evening starts here.',61,Y,True)
for y,s in [(455,'Your collection.'),(515,'Your channels.'),(575,'Your television.')]:text(112,y,s,46)
text(112,824,'Orbit Classic',48,M,font='Barlow Condensed');text(112,873,'The satellite spirit, in your universe.',30,M)
for y,s in [(400,"TV Guide / What's on now"),(510,'OrbOffice / Special transmissions'),(620,'Services / Orbit Interactive')]:
rect(1030,y,760,84,Y if y==400 else '#194c91',W if y==400 else '#38629a');text(1058,y+55,s,30,N if y==400 else W,y==400)
text(64,1030,'Arrows Browse OK Select Back Return to guide',25,M)
a.append('</svg>');(p/('orboffice.svg' if section=='office' else 'orbit-menu.svg')).write_text('\n'.join(a))
for section in ['office','menu']:build(section)
# A font comparison rendered from the actual local TTFs, without installing system fonts.
base=p/'fonts'
im=Image.new('RGB',(1600,1100),N);d=ImageDraw.Draw(im)
def line(x,y,s,path,size,color=W):d.text((x,y),s,font=ImageFont.truetype(str(path),size),fill=color)
def font(name):return base/name
line(65,45,'Orbit / type study',font('barlow-semibold.ttf'),60)
line(65,123,'Actual fonts from your Documents collection',font('barlow-regular.ttf'),28,M)
for i,(name,path,description) in enumerate([
('Barlow',font('barlow-regular.ttf'),'Selected for the new menu and event descriptions'),
('Barlow Condensed',font('barlow-condensed-medium.ttf'),'Selected for OrbOffice titles and ticket lettering'),
('Instrument Sans',font('instrument-sans.ttf'),'Alternative: a quieter contemporary UI')]):
y=215+i*285;d.rectangle((64,y,1536,y+245),fill=P)
line(90,y+23,name,path,36,Y)
line(90,y+82,'The Last Shuttle Home',path,62)
line(90,y+169,'101 Orbit One Tonight 21:30 Admit one',path,27)
line(90,y+210,description,font('barlow-regular.ttf'),22,M)
im.save(p/'font-comparison.png')