"""Rebuild the portable TNT identity study. All delivery SVG lettering is outlined."""
from pathlib import Path
from html import escape
import json, math
from fontTools.ttLib import TTFont
from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.varLib.instancer import instantiateVariableFont

ROOT=Path(__file__).resolve().parent
C=dict(ink='#20282C',paper='#FAF7F0',primary='#BE3D2A',accent='#DDE7EA',signal='#FF754B',muted='#59646A')
F={
 'head':TTFont(ROOT/'fonts/BarlowCondensed-ExtraBold.ttf'),
 'sub':TTFont(ROOT/'fonts/BarlowCondensed-SemiBold.ttf'),
 'body':instantiateVariableFont(TTFont(ROOT/'fonts/PublicSans-Variable.ttf'),{'wght':400}),
 'bold':instantiateVariableFont(TTFont(ROOT/'fonts/PublicSans-Variable.ttf'),{'wght':600})}
for key,font in F.items():
    out=ROOT/'fonts'/f'{key}.woff2'
    font.flavor='woff2';font.save(out);font.flavor=None

def rect(x,y,w,h,c,**kw):
    return f'<rect x="{x}" y="{y}" width="{w}" height="{h}" fill="{c}" '+ ' '.join(f'{k}="{v}"' for k,v in kw.items())+'/>'
def path(d,c):return f'<path fill="{c}" d="{d}"/>'
def line(x1,y1,x2,y2,c,w=2):return f'<path d="M{x1} {y1} L{x2} {y2}" fill="none" stroke="{c}" stroke-width="{w}"/>'
def txt(s,x,y,size,font='body',c=None,spacing=0,maxw=None,align='left'):
    c=c or C['ink'];f=F[font];gs=f.getGlyphSet();cm=f.getBestCmap();units=f['head'].unitsPerEm
    widths=[f['hmtx'][cm[ord(ch)]][0] for ch in s]
    scale=size/units;total=sum(widths)*scale+max(0,len(s)-1)*spacing
    sx=min(1,maxw/total) if maxw else 1
    if align=='center':x-=total*sx/2
    if align=='right':x-=total*sx
    parts=[];cursor=0
    for ch,adv in zip(s,widths):
        pen=SVGPathPen(gs);gs[cm[ord(ch)]].draw(pen)
        if pen.getCommands():parts.append(f'<path transform="translate({cursor:.3f} 0)" d="{pen.getCommands()}"/>')
        cursor+=adv+spacing/scale
    return f'<g aria-label="{escape(s,quote=True)}" fill="{c}" transform="translate({x:.3f} {y}) scale({scale*sx:.6f} {-scale:.6f})">'+''.join(parts)+'</g>'
def svg(w,h,body,title,physical=None):
    dims=f'width="{physical[0]}in" height="{physical[1]}in"' if physical else f'width="{w}" height="{h}"'
    return f'<svg xmlns="http://www.w3.org/2000/svg" {dims} viewBox="0 0 {w} {h}" role="img"><title>{escape(title)}</title>{body}</svg>'
def save(name,w,h,body,title,physical=None):
    (ROOT/name).write_text(svg(w,h,body,title,physical),encoding='utf-8')
def group(body,x=0,y=0,s=1):return f'<g transform="translate({x} {y}) scale({s})">{body}</g>'
def spark(c):
    return path('M0 29 L12 16 L25 29 L12 42 Z M6 0 H18 V12 H6 Z M30 8 L39 17 L29 27 L20 18 Z M30 34 H44 V46 H30 Z',c)
def tnt(c):
    return path('M0 0 H124 V37 H81 V160 H43 V37 H0 Z M146 160 V0 H183 L236 92 V0 H274 V160 H237 L184 68 V160 Z M296 0 H420 V37 H377 V160 H339 V37 H296 Z',c)
def stencil_presents(c):
    # Open P/R counters: bridge the negative center to the outside at right.
    letters={
    'P':'M0 0 H23 L33 9 V11 H25 L20 7 H7 V18 H20 L25 14 H33 V16 L23 25 H7 V40 H0 Z',
    'R':'M0 0 H23 L33 9 V11 H25 L20 7 H7 V18 H20 L25 14 H33 V16 L23 25 H7 V40 H0 Z M15 25 H23 L33 40 H24 Z',
    'E':'M0 0 H30 V7 H7 V16 H25 V23 H7 V33 H30 V40 H0 Z',
    'S':'M0 0 H30 V7 H7 V16 H30 V40 H0 V33 H23 V23 H0 Z',
    'N':'M0 40 V0 H7 L23 26 V0 H30 V40 H23 L7 14 V40 Z',
    'T':'M0 0 H32 V7 H20 V40 H12 V7 H0 Z'}
    return ''.join(group(path(letters[ch],c),i*53) for i,ch in enumerate('PRESENTS'))
def fuse_mark(c):
    return path('M0 108 V48 H50 V14 H78 V76 H28 V108 Z',c)+group(spark(c),78,-16,1.15)
def logo(i,c=None):
    c=c or C['ink']
    if i==1:
        return group(tnt(c),100,130,1.65)+group(stencil_presents(c),100,425,.99)+group(fuse_mark(c),825,70,1.0)
    if i==2:
        mark=path('M20 0 H155 V28 H101 V77 H74 V28 H20 Z M0 68 H48 V96 H28 V185 H146 V96 H125 V68 H174 V213 H0 Z',c)
        return group(mark,65,110,1.1)+group(tnt(c),315,112,1.38)+group(stencil_presents(c),315,365,1.05)
    if i==3:
        m=path('M0 38 L37 24 L87 160 L50 174 Z M55 10 L96 4 L122 149 L81 156 Z M116 4 H157 V151 H116 Z M38 180 H165 V211 H38 Z',c)+group(spark(c),135,-58,.85)
        return group(m,90,125,1.2)+txt('TNT',360,325,270,'head',c)+txt('PRESENTS',367,393,66,'sub',c,6)
    if i==4:
        return txt('TNT',65,335,380,'head',c,2,maxw=410)+txt('PRESENTS',530,330,125,'head',c,1,maxw=400)
    if i==5:
        m=path('M0 220 V0 H178 V30 H30 V220 Z M208 65 H238 V220 H208 Z',c)+group(spark(c),185,-30,1.0)
        return group(m,67,120,1.08)+txt('TNT',390,320,267,'head',c)+txt('PRESENTS',394,388,68,'sub',c,7)
    if i==6:
        m=path('M0 35 H120 V0 L190 65 L120 130 V95 H60 V185 H0 Z M80 150 H140 V195 H208 V160 L270 225 L208 290 V255 H80 Z',c)
        return group(m,65,102,.86)+txt('TNT',385,323,270,'head',c)+txt('PRESENTS',390,390,66,'sub',c,6)
    if i==7:
        m=''
        for deg in [0,90,180,270]:m+=f'<g transform="rotate({deg} 105 105)">'+path('M84 0 H126 L116 66 H94 Z',c)+'</g>'
        m+=path('M105 78 L132 105 L105 132 L78 105 Z',c)
        return group(m,75,122,1.15)+txt('TNT',385,323,270,'head',c)+txt('PRESENTS',390,390,66,'sub',c,6)
    if i==8:
        return txt('TNT',95,307,298,'head',c,maxw=350)+txt('PRESENTS',505,302,96,'sub',c,2,maxw=370)+path('M95 345 H885 V220 H913 V373 H95 Z',c)+group(spark(c),878,150,.95)
    if i==9:
        return path('M90 80 H370 V104 H114 V185 H90 Z M910 80 H630 V104 H886 V185 H910 Z M90 450 V295 H114 V426 H170 V450 Z M910 450 H800 V426 H886 V295 H910 Z',c)+group(tnt(c),206,145,1.4)+group(stencil_presents(c),209,408,.90)

names=['set-the-fuse','the-plunger','street-charge','full-shift','open-door','relay','controlled-blast','live-line','road-case']
titles=['Set the fuse','The plunger','Street charge','Full shift','Open door','Relay','Controlled blast','Live line','Road case']
descs=['A custom open-counter TNT and a fuse ready to fire. Recommended.','A working plunger gives the crew a compact, ownable tool.','Three charges gathered into one traveling crew.','Pure type, with the producer credit given a full-size voice.','A door left open for the next business on the block.','A connected pair of moves: load in, then move on.','One contained four-way blast, without party scatter.','A live cable carries the name from one room to the next.','The corner hardware of a road case, built around TNT.']
for i,name in enumerate(names,1):
    save(f'logos/tnt-{i:02}-{name}.svg',1000,500,logo(i),f'TNT Presents — {titles[i-1]}')
save('logos/tnt-primary-ink.svg',1000,500,logo(1),'TNT Presents primary ink')
save('logos/tnt-primary-paper.svg',1000,500,logo(1,C['paper']),'TNT Presents primary reversed')
save('logos/tnt-primary-vermilion.svg',1000,500,logo(1,C['primary']),'TNT Presents primary vermilion')
save('logos/tnt-mark-ink.svg',150,150,group(fuse_mark(C['ink']),8,27),'TNT fuse mark')
save('logos/tnt-mark-paper.svg',150,150,group(fuse_mark(C['paper']),8,27),'TNT fuse mark reversed')
save('logos/tnt-micro-ink.svg',448,188,group(tnt(C['ink']),14,14),'TNT micro mark, no descriptor')

# Contact sheet: all directions have the same optical territory.
b=rect(0,0,1800,1660,C['paper'])+txt('NINE WAYS TO SHOW UP.',60,108,83,'head')+txt('TNT PRESENTS / IDENTITY STUDY / 12 SEPTEMBER 2026',62,156,20,'bold',C['muted'],1)
for k in range(9):
    x=40+(k%3)*580;y=218+(k//3)*460
    b+=rect(x,y,560,427,'#FFFFFF')+group(logo(k+1),x+10,y+10,.54)
    b+=txt(f'{k+1:02}  {titles[k].upper()}',x+24,y+333,31,'head',C['primary'] if k==0 else C['ink'])
    # short purpose in two designed lines
    words=descs[k].split();lines=['']
    for w in words:
        if len(lines[-1])+len(w)>48:lines.append('')
        lines[-1]+=(' ' if lines[-1] else '')+w
    for n,l in enumerate(lines):b+=txt(l,x+24,y+370+n*26,18,'body')
save('artifacts/logo-directions.svg',1800,1660,b,'Nine TNT Presents logo directions')

# Permanent, one-ink window vinyl. The SVG has no opaque background.
b=group(logo(1),90,2,1.02)+line(85,500,1115,500,C['ink'],5)
b+=txt('WE SET IT UP.',85,607,110,'head')+txt('WE RUN IT. WE PACK IT OUT.',85,721,93,'head',maxw=1025)
b+=txt('EVENT PRODUCTION + STAFFING',85,807,33,'bold',spacing=1)+txt('FORT COLLINS, COLORADO',85,854,23,'body',spacing=1)
save('artifacts/tnt-window-sign-24x18.svg',1200,900,b,'TNT Presents window sign — 24 by 18 inch one-color vinyl',(24,18))
save('artifacts/tnt-window-sign-24x18-reversed.svg',1200,900,b.replace(C['ink'],C['paper']),'TNT Presents window sign — reversed one-color vinyl',(24,18))

# The partner board: 36 equal cells around a deliberate standing/photo zone.
b=rect(0,0,1600,1600,C['paper'])+rect(0,0,1600,18,C['primary'])
b+=txt('THE HALLOWEEN HUNT',52,191,180,'head',maxw=1496)+txt('OLD TOWN FORT COLLINS',55,269,44,'sub',spacing=3)+txt('SAT / OCT 31 / 2026',1545,269,44,'sub',align='right')
b+=line(50,315,1550,315,C['ink'],3)
positions=[(40+i*190,350) for i in range(8)]
for row in range(5):
    for col in [0,1,6,7]:positions.append((40+col*190,510+row*158))
positions += [(40+i*190,1300) for i in range(8)]
slots=[]
for n,(x,y) in enumerate(positions,1):
    slots.append(dict(id=f'partner-{n:02}',x=x,y=y,width=180,height=140,safe_inset=18))
    b+=f'<g id="partner-{n:02}">'+rect(x,y,180,140,'#FFFFFF')+rect(x+10,y+10,160,120,'none',stroke=C['accent'])+txt(f'{n:02}',x+90,y+53,19,'bold',C['muted'],align='center')+txt('YOUR LOGO HERE',x+90,y+84,12,'bold',C['muted'],align='center')+'</g>'
b+=txt('OLD TOWN',800,670,101,'head',align='center')+txt('LOOKS GOOD',800,783,101,'head',align='center')+txt('ON YOU.',800,896,101,'head',C['primary'],align='center')
b+=txt('COSTUME ON. PHOTO. POST.',800,982,25,'bold',align='center')+group(fuse_mark(C['primary']),724,1040,1)
b+=line(50,1470,1550,1470,C['ink'],2)+txt('PRODUCED BY',55,1512,15,'bold',C['muted'])+group(logo(1),38,1502,.18)
b+=txt('PRESENTING SPONSORS',452,1512,15,'bold',C['muted'],1)
b+=txt('Glacier Ice Cream',452,1562,29,'sub')+txt('The Whiskey',891,1562,29,'sub')+txt('The Elizabeth',1250,1562,29,'sub')
save('artifacts/halloween-partner-board-96x96.svg',1600,1600,b,'Halloween Hunt — 36 partner slots, draft board, 96 by 96 inches',(96,96))
(ROOT/'artifacts/board-slots.json').write_text(json.dumps({'canvas':[1600,1600],'size_inches':[96,96],'slots':slots,'photo_zone':{'x':440,'y':510,'width':720,'height':770},'sponsor_names_are_typeset_placeholders':True},indent=2))

# Campaign motif: shop doors connected by a fuse, with a single controlled spark.
def doors():
    d=''
    for x,y,w,h in [(0,74,205,244),(287,15,240,303),(610,92,215,226)]:
        d+=rect(x,y,w,h,C['paper'])+rect(x+25,y+29,w-50,h-29,C['ink'])
        d+=path(f'M{x+25} {y+29} L{x+w-39} {y+51} V{y+h-24} L{x+25} {y+h} Z',C['accent'])
        d+=rect(x+w-65,y+h/2,12,12,C['primary'])
    d+=path('M45 348 H876 V232 H852 V324 H45 Z',C['paper'])+group(spark(C['paper']),840,161,1.2)
    # Found gift card, tied visually to the doorway.
    d+=f'<g transform="translate(492 222) rotate(-13)">'+rect(0,0,115,77,C['signal'])+rect(15,34,85,9,C['ink'])+rect(52,10,10,58,C['ink'])+'</g>'
    return d
b=rect(0,0,1200,1800,C['paper'])+rect(0,0,1200,23,C['primary'])
b+=txt('OLD TOWN FORT COLLINS',55,85,27,'bold',spacing=2)+group(logo(1),924,20,.23)
b+=txt('THE HALLOWEEN',52,285,198,'head',maxw=1096)+txt('HUNT',50,528,285,'head')
b+=txt('YOUR COSTUME.',710,397,54,'head')+txt('YOUR NEIGHBORS.',710,459,54,'head',maxw=430)+txt('YOUR OLD TOWN.',710,521,54,'head',maxw=430)
b+=rect(0,572,1200,450,C['primary'])+group(doors(),120,619,1.02)
b+=rect(0,1022,1200,115,C['accent'])+txt('SATURDAY, OCTOBER 31, 2026',55,1100,67,'head',maxw=1090)
b+=txt('COSTUMES ON. COME FIND SOMETHING.',55,1220,63,'head',maxw=1090)
b+=txt('Find gift cards hidden inside Old Town businesses.',55,1282,29)+txt('Take a costume photo at a board and post it.',55,1328,29)
b+=line(55,1373,1145,1373,C['ink'],2)
b+=txt('THE NEIGHBORHOOD HUNT',55,1435,35,'head')+txt('Costumes, photos + gift cards.',55,1480,25)
b+=txt('THE BAR CRAWL / 21+',685,1435,35,'head')+txt('A separate grown-up stop.',685,1480,25)
b+=txt('HALLOWEEN NIGHT: JUDGED COSTUME CONTEST',55,1557,42,'head',maxw=1090)
b+=txt('Ask this business how to take part.',55,1620,32,'bold')
b+=line(55,1664,1145,1664,C['ink'],2)+txt('PRESENTING SPONSORS',55,1704,17,'bold',C['muted'],1)
b+=txt('Glacier Ice Cream',55,1760,32,'sub')+txt('The Whiskey',503,1760,32,'sub')+txt('The Elizabeth',916,1760,32,'sub')
save('artifacts/halloween-hunt-poster-24x36.svg',1200,1800,b,'The Halloween Hunt — draft campaign poster, 24 by 36 inches',(24,36))

# Palette, neighbor adjacency and actual type specimen.
b=rect(0,0,1600,1200,C['paper'])+txt('A WORKING COLOR. ROOM FOR EVERYONE.',55,110,79,'head',maxw=1490)
for i,(role,col) in enumerate(C.items()):
    x=55+i*250;b+=rect(x,165,240,155,col)
    b+=txt(role.upper(),x,363,28,'head')+txt(col,x,402,22,'body')
b+=txt('THREE NAMES. THREE DISTINCT VOICES.',55,505,48,'head')
neighbors=[('TNT PRESENTS',[C['primary'],C['paper'],C['accent'],C['ink']]),('GLACIER ICE CREAM',['#9002AF','#F5E6C8','#4A0E6B','#1A1A1A']),('THE FORT LIST',['#173F35','#F2EEE4','#C49348','#0C1016'])]
for j,(name,cols) in enumerate(neighbors):
    x=55+j*510;b+=txt(name,x,560,29,'head')
    for k,col in enumerate(cols):b+=rect(x+k*119,583,115,65,col)
b+=txt('BRING THE IDEA. WE’LL BRING THE CREW.',55,799,86,'head',maxw=1490)
b+=txt('From the first table to the last load out.',55,880,43,'sub')
b+=txt('You know the people. You have a date in mind. Tell us what you want to put on.',55,953,29)
b+=txt('We’ll work through the setup, the people and the pack out with you.',55,1000,29)
b+=txt('BARLOW CONDENSED 800 / 600     +     PUBLIC SANS 400 / 600',55,1110,23,'bold',C['muted'],1)
save('artifacts/color-and-type.svg',1600,1200,b,'TNT palette, neighboring brand comparison, and typography specimen')

# Exact WCAG relative luminance calculations for the finite allowed text system.
def lum(h):
    v=[int(h[i:i+2],16)/255 for i in [1,3,5]]
    v=[x/12.92 if x<=.04045 else ((x+.055)/1.055)**2.4 for x in v]
    return sum(x*w for x,w in zip(v,[.2126,.7152,.0722]))
def ratio(a,b):
    a,b=sorted([lum(a),lum(b)]);return (b+.05)/(a+.05)
pairs=[('ink','paper'),('muted','paper'),('primary','paper'),('paper','ink'),('paper','primary'),('ink','accent'),('muted','accent'),('ink','signal'),('ink','white'),('muted','white'),('primary','white')]
colors={**C,'white':'#FFFFFF'}
contrasts=[dict(text=a,background=b,ratio=round(ratio(colors[a],colors[b]),3),normal_text_AA=ratio(colors[a],colors[b])>=4.5) for a,b in pairs]
(ROOT/'contrast.json').write_text(json.dumps(contrasts,indent=2))
port=dict(status='proposed_identity_study_not_owner_approved',concept='Bring the idea. We bring the crew.',palette=C,substrate={'partner_logo_well':'#FFFFFF'},fonts={'heading':{'family':'Barlow Condensed','weights':[600,800],'fallback':'"Barlow Condensed", "Arial Narrow", Arial, sans-serif'},'body':{'family':'Public Sans','weights':[400,600],'fallback':'"Public Sans", system-ui, -apple-system, "Segoe UI", sans-serif'}},recommended_direction='01-set-the-fuse',logos={f'direction_{i:02}':f'tnt-{i:02}-{n}.svg' for i,n in enumerate(names,1)},production_variants=['tnt-primary-ink.svg','tnt-primary-paper.svg','tnt-primary-vermilion.svg','tnt-mark-ink.svg','tnt-mark-paper.svg','tnt-micro-ink.svg'])
(ROOT/'port-sheet.json').write_text(json.dumps(port,indent=2))
print(json.dumps(contrasts,indent=2))
