123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
#!/usr/bin/env python3
"""nb-bridge hero build - adapted from pup's canonical build_hero.py (same constants).
Vitrine = REAL nb window capture (untouched, banner incl.) + 68px taskbar band with
chrome/chrome/edge/edge/badged-nb tiles, active = the badged nb one."""
import os
from PIL import Image, ImageDraw, ImageFilter, ImageFont
HERE=os.path.dirname(os.path.abspath(__file__))
A=lambda *p: os.path.join(HERE,'assets',*p)
import argparse
ap=argparse.ArgumentParser(description="nb hero build - see SKILL.md for the capture recipe")
ap.add_argument("--window",required=True,help="RAW desktop_screenshot_window capture of a real nb window (banner incl., untouched)")
ap.add_argument("--out",default="hero_candidate.png")
args=ap.parse_args()
WIN=args.window
STRIP=A("taskbar_strip.png")
W,H=2000,1250; CQ=W/100.0
bg=Image.new('RGB',(W,H),(13,17,23))
glow=Image.new('L',(W,H),0)
ImageDraw.Draw(glow).ellipse([W*0.45,-H*0.3,W*1.25,H*0.9],fill=90)
glow=glow.filter(ImageFilter.GaussianBlur(240))
bg=Image.composite(Image.new('RGB',(W,H),(18,52,58)),bg,glow).convert('RGBA')
# vitrine geometry - pup's exact numbers
vy=140; vw=1062; vx=(W-int(2.6*CQ))-vw; VH=1050; TB=68
win_h=VH-TB
# panel: REAL window capture, width-fit, top-aligned; band at bottom; gap = panel bg
win=Image.open(WIN).convert('RGB')
s=vw/win.width
win=win.resize((vw,int(win.height*s)),Image.LANCZOS)
panel=Image.new('RGBA',(vw,VH),(19,22,27,255))
panel.paste(win.convert('RGBA'),(0,0))
# fade the window's bottom edge into the panel so the gap reads intentional
fh=40
al=Image.linear_gradient('L').resize((vw,fh))
sol=Image.new('RGBA',(vw,fh),(19,22,27,255)); sol.putalpha(al)
panel.alpha_composite(sol,(0,win.height-fh))
# taskbar band: 5 tiles left-aligned, active = badged nb (underline)
dp=ImageDraw.Draw(panel); tb_y=VH-TB
dp.rectangle([0,tb_y,vw,VH],fill=(24,26,31,255))
dp.line([0,tb_y,vw,tb_y],fill=(44,50,60,255))
strip=Image.open(STRIP).convert('RGB').crop((0,20,1568,63))
def tile(a,b):
c=strip.crop((a,0,b,strip.height))
s=54/c.height
return c.resize((int(c.width*s),54),Image.LANCZOS).convert('RGBA')
tiles=[tile(499,523),tile(697,728),tile(576,606),tile(576,606),tile(899,934)]
bh,gap,x=54,14,14
for i,t in enumerate(tiles):
by=tb_y+(TB-bh)//2
panel.alpha_composite(t,(x,by))
if i==4: dp.line([x+4,VH-4,x+t.width-4,VH-4],fill=(120,200,255),width=4) # active = badged nb
x+=t.width+gap
# rounded corners + drop shadow
R=22
mask=Image.new('L',(vw,VH),0); ImageDraw.Draw(mask).rounded_rectangle([0,0,vw,VH],R,fill=255)
sh=Image.new('RGBA',(W,H),(0,0,0,0)); shm=Image.new('L',(W,H),0)
ImageDraw.Draw(shm).rounded_rectangle([vx+10,vy+16,vx+vw+10,vy+VH+16],R,fill=150)
shm=shm.filter(ImageFilter.GaussianBlur(30)); sh.putalpha(shm)
bg=Image.alpha_composite(bg,sh); bg.paste(panel,(vx,vy),mask)
# left: logo, title (2 lines, max size that clears the vitrine), subtitle
d=ImageDraw.Draw(bg); inset=int(4.5*CQ)
logo=Image.open(A('adom_logo_white.png')).convert('RGBA'); lw=int(11.8*CQ)
logo=logo.resize((lw,int(logo.height*lw/logo.width)),Image.LANCZOS)
bg.alpha_composite(logo,(inset,int(3.2*CQ)))
maxw=vx-inset-46 # clear the vitrine with a real gutter
size=int(12*CQ)
while size>100:
f=ImageFont.truetype(A('familjen-700.ttf'),size)
if max(d.textlength(w,font=f) for w in ("Native","Browser"))<=maxw: break
size-=4
f_title=ImageFont.truetype(A('familjen-700.ttf'),size)
ty=int(13.15*CQ)
def ink_dx(txt,font):
# render on scratch, measure where ink ACTUALLY starts vs the draw x
t=Image.new('L',(1400,int(font.size*1.6)),0)
ImageDraw.Draw(t).text((100,10),txt,font=font,fill=255)
p=t.load()
for x in range(100,1400):
if any(p[x,y]>60 for y in range(0,t.height,2)): return x-100
return 0
bbN=d.textbbox((0,0),"Native",font=f_title)
lh=int(size*0.92)
d.text((inset-ink_dx("Native",f_title),ty-bbN[1]),"Native",font=f_title,fill=(230,237,243,255))
d.text((inset-ink_dx("Browser",f_title),ty-bbN[1]+lh),"Browser",font=f_title,fill=(230,237,243,255))
f_sub=ImageFont.truetype(A('satoshi-500.ttf'),50)
BLU,WHT=(74,168,255,255),(232,238,244,255)
bb2=d.textbbox((0,0),'Browser',font=f_title)
sy=ty+lh+(bb2[3]-bb2[1])+54
sx=inset-ink_dx("Your real",f_sub)
d.text((sx,sy),"Your real",font=f_sub,fill=BLU)
w1=d.textlength("Your real",font=f_sub)
d.text((sx+w1,sy)," browser,",font=f_sub,fill=WHT)
d.text((inset-ink_dx("driven",f_sub),sy+int(50*1.35)),"driven by AI.",font=f_sub,fill=WHT)
# BRIDGE pill - pup's exact
pf=ImageFont.truetype(A('familjen-700.ttf'),26); txt="BRIDGE"
tb2=d.textbbox((0,0),txt,font=pf); tw,th=tb2[2]-tb2[0],tb2[3]-tb2[1]
padx,pady=22,12; pw2,ph2=tw+padx*2,th+pady*2
px_=W-int(2.6*CQ)-pw2; py_=int(2.6*CQ)
d.rounded_rectangle([px_,py_,px_+pw2,py_+ph2],radius=ph2//2,fill=(0,206,194,255))
d.text((px_+padx-tb2[0],py_+pady-tb2[1]),txt,font=pf,fill=(9,20,24,255))
bg.convert('RGB').save(args.out)
print(f"saved {args.out}. title {size}px, vitrine right {vx+vw} = pill right {W-int(2.6*CQ)}, window {win.size}, gap {win_h-win.height}px above band")