"""Create circular board and static preview from the real JS geometry and PNG art. Preview is a composited illustration, NOT a Vela runtime screenshot. Existing sprite art is nearest-neighbor enlarged to native 32px, not blurred. """ import json from pathlib import Path import subprocess from PIL import Image, ImageDraw, ImageFont ROOT = Path(__file__).resolve().parents[1] a = json.loads(subprocess.check_output(['node', '-p', "JSON.stringify(require('./src/common/snake.js').ARENA)"], cwd=ROOT)) tex = ROOT / 'src/common/tex' for p in tex.glob('*.png'): if p.name != 'board.png': img = Image.open(p).convert('RGBA') if img.size != (a['cell'], a['cell']): img.resize((a['cell'], a['cell']), Image.Resampling.NEAREST).save(p) S=4 size=a['radius']*2 board=Image.new('RGBA',(size*S,size*S),(0,0,0,0)) d=ImageDraw.Draw(board) d.ellipse((0,0,size*S-1,size*S-1),fill='#14251e',outline='#36513c',width=2*S) local=a['offset']-(233-a['radius']) for c in a['cells']: x=(local+c['x']*a['cell'])*S;y=(local+c['y']*a['cell'])*S d.rectangle((x,y,x+a['cell']*S-1,y+a['cell']*S-1),fill=('#1d3227' if (c['x']+c['y'])%2 else '#20362a')) board=board.resize((size,size),Image.Resampling.LANCZOS) board.save(tex/'board.png') # Read actual CSS text placement/colors instead of inventing a separate layout. import re ux=(ROOT/'src/pages/index/index.ux').read_text() def rule(name): body=re.search(r'\.'+name+r'\s*\{([^}]+)\}',ux)[1] return dict(re.findall(r'([\w-]+):\s*([^;]+)',body)) def px(r,key): return int(r[key].removesuffix('px')) font=subprocess.check_output(['fc-match','-f','%{file}','sans'],text=True) canvas=Image.new('RGB',(980,526),'#10131a') for panel,mask in enumerate([False,True]): watch=Image.new('RGBA',(466,466),'black');watch.alpha_composite(board,(233-a['radius'],233-a['radius'])) wd=ImageDraw.Draw(watch) if mask: for c in a['cells']: x=a['offset']+c['x']*a['cell'];y=a['offset']+c['y']*a['cell'] wd.rectangle((x,y,x+a['cell']-1,y+a['cell']-1),outline='#71967a') cells=[{'x':7,'y':4},{'x':6,'y':4},{'x':5,'y':4},{'x':5,'y':5},{'x':4,'y':5},{'x':3,'y':5}] names=json.loads(subprocess.check_output(['node','-p',f"JSON.stringify(require('./src/common/snake.js').renderPieces({json.dumps(cells)},{{x:1,y:0}}))"],cwd=ROOT)) for c,name in zip(cells,names): watch.alpha_composite(Image.open(tex/(name+'.png')),(a['offset']+c['x']*a['cell'],a['offset']+c['y']*a['cell'])) watch.alpha_composite(Image.open(tex/'apple.png'),(a['offset']+8*a['cell'],a['offset']+6*a['cell'])) for parent,entries in [('hud',[('score','SCORE 3'),('best','BEST 253')]),('footer',[('modeLabel','CLASSIC'),('hint','swipe / tap pause')])]: pr=rule(parent); y=px(pr,'top') for cls,text in entries: r=rule(cls); f=ImageFont.truetype(font,px(r,'font-size')) wd.text((233,y+px(r,'height')/2),text,font=f,fill=r['color'],anchor='mm') y+=px(r,'height') circle=Image.new('L',(466,466));ImageDraw.Draw(circle).ellipse((0,0,465,465),fill=255) canvas.paste(watch.convert('RGB'),(12+panel*490,12),circle) cd=ImageDraw.Draw(canvas) cd.text((20,490),'STATIC ART / GEOMETRY COMPOSITE — NOT WATCH RUNTIME',fill='white',font=ImageFont.truetype(font,16)) cd.text((510,490),'Legal-cell overlay: 89 cells, 32px tiles',fill='white',font=ImageFont.truetype(font,16)) canvas.save(ROOT/'tests/preview-0.8-static.png') print(ROOT/'tests/preview-0.8-static.png') # Alpha corners prove the replacement board is circular, not a square frame. assert all(board.getpixel(p)[3]==0 for p in [(0,0),(size-1,0),(0,size-1),(size-1,size-1)]) print('Circular PNG alpha corners verified; native 32px sprites generated.')