"""Browser edition of the PDF: identical layout, live text, then the asset library."""
from pathlib import Path
from io import BytesIO
from base64 import b64encode
from html import escape
from urllib.parse import quote
import json
from fontTools.ttLib import TTFont
from fontTools.varLib.instancer import instantiateVariableFont
from fontTools import subset
from graphics import ROOT

TITLES=['Identity system','The refined identity','A useful logo family','Colour, with restraint','Space and size','Type that knows its job','Japanese, with a purpose','Social, without starting over','Digital assets','Print that is ready to adapt','Clothing and patches','Patches in context','Handoff and everyday use']

def embedded_fonts(pages):
    faces=[]
    for name,path,weights in [('STF Anton','anton/Anton-Regular.ttf','400'),('STF Epilogue','epilogue/Epilogue[wght].ttf','100 900')]:
        data=b64encode((ROOT/'fonts'/path).read_bytes()).decode()
        faces.append(f'@font-face{{font-family:"{name}";src:url(data:font/ttf;base64,{data}) format("truetype");font-weight:{weights};font-style:normal;font-display:block;}}')
    chars=''.join(run['text'] for p in pages for run in p.get('text_runs',[]) if run['family']=='STF Japanese')
    font=instantiateVariableFont(TTFont(ROOT/'fonts/notosansjp/NotoSansJP[wght].ttf'),{'wght':400},inplace=True)
    opts=subset.Options();opts.hinting=False;opts.layout_features=[];opts.name_IDs=['*'];opts.name_legacy=True
    sub=subset.Subsetter(options=opts);sub.populate(text=chars);sub.subset(font)
    names={1:'STF Guide Japanese',2:'Regular',3:'STFGuideJapanese-Regular 1.0',4:'STF Guide Japanese Regular',6:'STFGuideJapanese-Regular',16:'STF Guide Japanese',17:'Regular'}
    for name_id,value in names.items():
        for platform,encoding,language in [(3,1,0x409),(1,0,0)]:font['name'].setName(value,name_id,platform,encoding,language)
    buf=BytesIO();font.save(buf)
    faces.append('@font-face{font-family:"STF Japanese";src:url(data:font/ttf;base64,'+b64encode(buf.getvalue()).decode()+') format("truetype");font-weight:400;font-style:normal;font-display:block;}')
    return '\n'.join(faces)

def size_label(n):
    return f'{n/1048576:.1f} MB' if n>=1048576 else f'{max(1,round(n/1024))} KB'

def nice_name(stem):
    known={'stf-primary':'Primary bilingual logo','stf-english':'Full English logo','stf-compact':'Compact name logo','stf-emblem':'Throw emblem','stf-wordmark':'Name-only wordmark','stf-n1-balanced-patch':'Balanced bilingual patch','stf-m1-bilingual-patch':'Earlier M1 patch study','stf-m2-bilingual-patch':'Earlier M2 patch study'}
    for prefix,label in known.items():
        if stem.startswith(prefix):return label+' / '+stem[len(prefix):].strip('-').replace('-',' ')
    return stem.removeprefix('stf-').replace('-',' ').replace('_',' ').capitalize()

def catalog():
    folders=[('logos','Logo files','Outlined SVGs, vector PDFs and transparent PNGs. Use the supplied geometry.'),('social','Social media','Avatars and editable square, portrait and story templates.'),('web','Web assets','Banner, icons and design tokens.'),('print','Print','Stationery, class poster templates and process-colour artwork.'),('patches','Patches and clothing','Balanced bilingual patch, earlier studies and garment artwork. All patch files are vector art for supplier digitisation.'),('fonts','Fonts and licences','Original font files, licences and source checksums.'),('guide','Guide','The PDF edition of this browser guide.'),('previews','Context and mockups','Illustrative applications and review images.'),('source','Source files','Rebuild scripts, vector source and the selected reference.')]
    sections=[];total_files=0;total_cards=0;manifest=[]
    for folder,title,description in folders:
        paths=[]
        for path in sorted((ROOT/folder).rglob('*')):
            if not path.is_file() or '__pycache__' in path.parts:continue
            rel=path.relative_to(ROOT)
            if path.name in ['.DS_Store','web-guide.json','asset-catalog.json'] or path.name.startswith('qa-guide-'):continue
            if folder=='previews' and ('guide-pages' in path.parts or path.name.startswith(('export-checks','html-'))):continue
            paths.append(path)
        groups={}
        for p in paths:groups.setdefault(p.with_suffix(''),[]).append(p)
        cards=[]
        for stem,variants in groups.items():
            name=nice_name(stem.name)
            if folder=='fonts':name=stem.parent.name+' / '+stem.name
            image=next((p for p in variants if p.suffix.lower()=='.png'),None)
            if image is None:image=next((p for p in variants if p.suffix.lower()=='.svg'),None)
            preview_path=next((p for p in variants if p.suffix.lower()=='.pdf'),variants[0])
            theme=' dark-preview' if any(v in stem.name for v in ['-white','-reverse','avatar-dark']) else ''
            if image:
                visual=f'<a class="asset-visual{theme}" href="{quote(str(image.relative_to(ROOT)))}" target="_blank" rel="noopener" aria-label="Preview {escape(name,quote=True)}"><img src="{quote(str(image.relative_to(ROOT)))}" loading="lazy" alt="{escape(name,quote=True)}"></a>'
            else:
                badge=variants[0].suffix[1:].upper() or 'FILE'
                visual=f'<a class="asset-visual file-preview" href="{quote(str(preview_path.relative_to(ROOT)))}" target="_blank" rel="noopener"><span>{badge}</span></a>'
            buttons=[]
            for p in sorted(variants,key=lambda p:({'.svg':0,'.pdf':1,'.png':2}.get(p.suffix.lower(),3),p.name)):
                rel=str(p.relative_to(ROOT));n=p.stat().st_size;total_files+=1
                buttons.append(f'<a download href="{quote(rel)}" title="{escape(rel,quote=True)}">{p.suffix[1:].upper()}<span>{size_label(n)}</span></a>')
                manifest.append({'file':rel,'bytes':n,'category':folder})
            search=' '.join([name,folder,*[str(p.relative_to(ROOT)) for p in variants]]).lower()
            cards.append(f'<article class="asset-card" data-search="{escape(search,quote=True)}">{visual}<div class="asset-info"><h4>{escape(name)}</h4><p>{escape(str(stem.relative_to(ROOT)))}</p><div class="file-links">{"".join(buttons)}</div></div></article>')
        if not cards:continue
        total_cards+=len(cards)
        sections.append(f'<section class="asset-group" id="assets-{folder}" data-category="{folder}" aria-labelledby="asset-title-{folder}"><div class="group-heading"><h3 id="asset-title-{folder}">{title}</h3><p>{description}</p></div><div class="asset-grid">{"".join(cards)}</div></section>')
    (ROOT/'guide/asset-catalog.json').write_text(json.dumps(manifest,indent=2)+'\n')
    return ''.join(sections),total_files,total_cards,folders

def write_html(scenes=None):
    cache=ROOT/'guide/web-guide.json'
    if scenes is not None:
        pages=[{'title':TITLES[i],'svg':s.svg(live_text=True),'text_runs':s.text_runs} for i,s in enumerate(scenes)]
        cache.write_text(json.dumps(pages,ensure_ascii=False,separators=(',',':')))
    else:
        if not cache.exists():raise RuntimeError('Build the guide first to create the shared browser layout source.')
        pages=json.loads(cache.read_text())
    fonts=embedded_fonts(pages)
    sheets=[]
    for i,p in enumerate(pages,1):
        svg=p['svg'].replace('role="img"','role="group"').replace('aria-labelledby="title"',f'aria-labelledby="guide-{i:02d}-svg-title"').replace('id="title"',f'id="guide-{i:02d}-svg-title"')
        sheets.append(f'<section class="guide-page" id="guide-{i:02d}" aria-labelledby="guide-{i:02d}-heading"><h2 class="sr-only" id="guide-{i:02d}-heading">{i:02d}. {escape(p["title"])}</h2><div class="sheet">{svg}</div><div class="page-caption"><a href="#guide-{i:02d}">{i:02d} / {len(pages):02d} &nbsp; {escape(p["title"])}</a><a href="#assets">Asset library ↓</a></div></section>')
    sections,total_files,total_cards,folders=catalog()
    toc=''.join(f'<a href="#guide-{i:02d}"><span>{i:02d}</span>{escape(p["title"])}</a>' for i,p in enumerate(pages,1))
    filters='<button type="button" data-filter="all" aria-pressed="true">All assets</button>'+''.join(f'<button type="button" data-filter="{key}" aria-pressed="false">{escape(title)}</button>' for key,title,_ in folders)
    css='''
*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:90px}body{margin:0;background:#e7e3dc;color:#111;font-family:"STF Epilogue",sans-serif;font-size:15px;line-height:1.6}a{color:inherit}button,input{font:inherit}button,a,input{touch-action:manipulation}a:focus-visible,button:focus-visible,input:focus-visible,summary:focus-visible{outline:3px solid #c81d25;outline-offset:4px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skip{position:fixed;left:16px;top:-80px;background:#111;color:white;padding:14px 20px;z-index:100}.skip:focus{top:10px}
.topbar{position:sticky;top:0;z-index:30;background:#f4f1ebf5;border-bottom:1px solid #d2ccc2;backdrop-filter:blur(16px)}.topbar-inner{max-width:1488px;margin:auto;padding:16px 24px;display:flex;align-items:center;justify-content:space-between;gap:24px}.brand{font-family:"STF Anton",sans-serif;font-size:24px;text-decoration:none;line-height:1;white-space:nowrap}.brand small{display:block;font-family:"STF Epilogue",sans-serif;font-size:9px;font-weight:400;letter-spacing:.17em;margin-top:7px}.toolbar{display:flex;align-items:center;gap:16px}.toolbar>a,.toolbar button,.contents summary{font-size:12px;font-weight:600;cursor:pointer;text-decoration:none;background:none;border:0;padding:7px 0}.toolbar .pdf-link{background:#111;color:#fff;padding:9px 14px}.contents{position:relative}.contents summary{list-style:none}.contents summary::-webkit-details-marker{display:none}.contents[open] summary{color:#c81d25}.toc{position:absolute;right:0;top:44px;width:350px;max-height:75vh;overflow:auto;background:#f4f1eb;border:1px solid #d2ccc2;box-shadow:0 16px 40px #0002;padding:14px}.toc a{display:flex;gap:15px;padding:9px 8px;text-decoration:none;font-size:12px}.toc a:hover{background:#e7e3dc}.toc span{color:#8a8278}.guide{max-width:1488px;margin:0 auto;padding:28px 24px 60px}.guide-page{margin:0 0 32px;scroll-margin-top:90px}.sheet{overflow-x:auto;box-shadow:0 2px 14px #11111112;background:#f4f1eb}.sheet svg{display:block;width:100%;height:auto}.sheet text{user-select:text;cursor:text}.reading-size .sheet svg{min-width:1100px}.page-caption{display:flex;justify-content:space-between;gap:16px;padding:10px 0;color:#66625c;font-size:11px}.page-caption a{text-decoration:none}.page-caption a:hover{color:#c81d25}
.library{background:#f4f1eb;border-top:1px solid #d2ccc2;padding:72px 24px 80px}.library-inner{max-width:1392px;margin:auto}.eyebrow{font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:#66625c}.library h2{font-family:"STF Anton",sans-serif;font-weight:400;font-size:clamp(48px,7vw,86px);line-height:1.1;text-transform:uppercase;margin:12px 0 20px}.library-intro{max-width:830px;color:#55514c;margin:0 0 32px}.library .quick-links{display:flex;flex-wrap:wrap;gap:12px;margin:20px 0 40px}.quick-links a{border:1px solid #c8c0b4;padding:10px 14px;text-decoration:none;font-size:12px}.library-tools{position:sticky;top:79px;z-index:10;background:#f4f1ebf5;backdrop-filter:blur(12px);padding:18px 0 14px;border-top:1px solid #d6d1c8;border-bottom:1px solid #d6d1c8}.search-row{display:flex;align-items:center;gap:20px;justify-content:space-between}.search-wrap{flex:1;max-width:650px;position:relative}.search-wrap input{width:100%;border:1px solid #c8c0b4;background:white;color:#111;border-radius:0;padding:12px 48px 12px 14px;font-size:14px}.clear-search{position:absolute;right:12px;top:11px;background:none;border:0;cursor:pointer;font-size:20px;line-height:1}.result-count{font-size:11px;color:#66625c;white-space:nowrap}.filters{display:flex;flex-wrap:wrap;gap:7px;margin-top:13px}.filters button{border:1px solid #d6d1c8;background:transparent;padding:7px 11px;cursor:pointer;font-size:11px;color:#55514c}.filters button[aria-pressed="true"]{background:#111;color:white;border-color:#111}.asset-group{padding-top:42px;scroll-margin-top:240px}.group-heading{display:flex;align-items:baseline;gap:30px;justify-content:space-between;margin-bottom:20px}.group-heading h3{font-family:"STF Anton",sans-serif;font-weight:400;font-size:34px;line-height:1.1;margin:0;text-transform:uppercase}.group-heading p{font-size:12px;color:#66625c;margin:0;max-width:760px}.asset-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px}.asset-card{min-width:0;border:1px solid #d6d1c8;background:white;display:flex;flex-direction:column}.asset-visual{height:232px;display:flex;align-items:center;justify-content:center;background:#eeebe5;text-decoration:none;overflow:hidden;border-bottom:1px solid #ddd8cf}.asset-visual img{display:block;width:100%;height:100%;padding:18px;object-fit:contain}.asset-visual.dark-preview{background:#111}.file-preview{height:100px;background:#e9e4da;color:#847c70}.file-preview span{font-family:"STF Anton",sans-serif;font-size:30px}.asset-info{padding:18px;display:flex;flex-direction:column;flex:1}.asset-info h4{font-size:13px;font-weight:600;line-height:1.5;margin:0 0 7px;overflow-wrap:anywhere}.asset-info p{font-size:10px;line-height:1.5;overflow-wrap:anywhere;color:#81796d;margin:0 0 17px}.file-links{display:flex;flex-wrap:wrap;gap:7px;margin-top:auto}.file-links a{display:flex;align-items:center;gap:8px;text-decoration:none;font-size:10px;font-weight:600;border:1px solid #d2ccc2;padding:7px 9px}.file-links a:hover{border-color:#c81d25;color:#c81d25}.file-links span{font-weight:400;color:#8a8278;font-size:9px}.library-note{margin-top:50px;padding-top:24px;border-top:1px solid #d6d1c8;max-width:940px;font-size:12px;color:#66625c}.empty-state{padding:50px 0;font-size:16px}.footer{background:#111;color:#bdb7ad;padding:30px 24px;font-size:11px}.footer-inner{max-width:1392px;margin:auto;display:flex;justify-content:space-between;gap:24px}.footer a{color:white} [hidden]{display:none!important}
@media(max-width:1000px){.asset-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.group-heading{display:block}.group-heading p{margin-top:12px}.topbar-inner{padding:14px 18px}.toolbar{gap:12px}.brand{font-size:20px}.guide{padding:18px 12px 32px}.library{padding:45px 18px}.library-tools{top:76px}}
@media(max-width:600px){.topbar-inner{display:block}.toolbar{margin-top:12px;justify-content:space-between}.guide-page{scroll-margin-top:135px}.toolbar .pdf-link{display:none}.toolbar{gap:18px}.brand{font-size:18px}.brand small{font-size:8px}.contents .toc{position:fixed;top:120px;right:10px;left:10px;width:auto}.asset-grid{grid-template-columns:1fr}.asset-visual{height:250px}.search-row{display:block}.result-count{display:block;margin-top:10px}.filters{gap:5px}.filters button{font-size:10px;padding:7px 8px}.page-caption{font-size:9px}.footer-inner{display:block}.library-tools{position:relative;top:auto}.library h2{font-size:54px}}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
@page{size:A4 landscape;margin:0}@media print{html,body{background:white!important;margin:0!important;padding:0!important}.topbar,.page-caption,.library,.footer,.skip{display:none!important}.guide{max-width:none;margin:0;padding:0}.guide-page{margin:0;break-after:page;page-break-after:always;width:297mm;height:210mm;overflow:hidden}.guide-page:last-child{break-after:auto;page-break-after:auto}.sheet{box-shadow:none;overflow:visible;width:297mm;height:210mm}.sheet svg,.reading-size .sheet svg{width:297mm!important;height:210mm!important;min-width:0!important}*{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}}
'''
    js='''
const search=document.querySelector('#asset-search'), count=document.querySelector('#result-count');
const cards=[...document.querySelectorAll('.asset-card')], groups=[...document.querySelectorAll('.asset-group')], filters=[...document.querySelectorAll('[data-filter]')];
let selected='all';
function filterAssets(){const q=search.value.trim().toLowerCase();let visible=0;for(const group of groups){let matches=0;for(const card of group.querySelectorAll('.asset-card')){const show=(selected==='all'||selected===group.dataset.category)&&card.dataset.search.includes(q);card.hidden=!show;if(show){visible++;matches++;}}group.hidden=!matches;}count.textContent=visible+' of '+cards.length+' asset sets';document.querySelector('#empty-state').hidden=visible>0;document.querySelector('#clear-search').hidden=!q;}
search.addEventListener('input',filterAssets);document.querySelector('#clear-search').addEventListener('click',()=>{search.value='';filterAssets();search.focus();});for(const button of filters)button.addEventListener('click',()=>{selected=button.dataset.filter;for(const b of filters)b.setAttribute('aria-pressed',b===button?'true':'false');filterAssets();});
document.querySelector('#reading-toggle').addEventListener('click',function(){const expanded=document.body.classList.toggle('reading-size');this.setAttribute('aria-pressed',expanded?'true':'false');this.textContent=expanded?'Fit pages':'Reading size';});
for(const link of document.querySelectorAll('.toc a'))link.addEventListener('click',()=>document.querySelector('.contents').removeAttribute('open'));
document.addEventListener('keydown',e=>{if(e.key==='Escape')document.querySelector('.contents').removeAttribute('open');});
filterAssets();
'''
    doc='<!doctype html>\n<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="Stand The Fight brand guide and complete asset library."><link rel="icon" href="web/favicon.ico"><title>Stand The Fight - Brand guide and assets</title><style>'+fonts+css+'</style></head><body><a class="skip" href="#guide-01">Skip to brand guide</a><header class="topbar"><div class="topbar-inner"><a class="brand" href="#guide-01">STAND THE FIGHT<small>BRAND GUIDE / 1.2</small></a><nav class="toolbar" aria-label="Guide navigation"><details class="contents"><summary>Contents +</summary><div class="toc">'+toc+'<a href="#assets"><span>↓</span>Asset library</a></div></details><button id="reading-toggle" type="button" aria-pressed="false">Reading size</button><a href="#assets">Assets ↓</a><a class="pdf-link" href="guide/stand-the-fight-brand-guide-v1.pdf" download>Download PDF</a></nav></div></header><main><div class="guide" aria-label="Thirteen-page brand guide">'+''.join(sheets)+'</div><section class="library" id="assets" aria-labelledby="assets-heading"><div class="library-inner"><span class="eyebrow">Everything in one place</span><h2 id="assets-heading">Asset library</h2><p class="library-intro">Download the outlined logos, application templates and supplier artwork below. The guide above uses the same layout source as the PDF. SVG, PDF and PNG versions are grouped together so you can choose the format you need.</p><div class="quick-links"><a href="guide/stand-the-fight-brand-guide-v1.pdf" download>Brand guide PDF</a><a href="patches/balanced-circular/circular-patch-balance-comparison.png" target="_blank" rel="noopener">Latest circular patch refinement</a><a href="README.md">File and production notes</a></div><div class="library-tools"><div class="search-row"><div class="search-wrap"><label class="sr-only" for="asset-search">Search asset names and filenames</label><input id="asset-search" type="search" placeholder="Search logos, templates, patches…" autocomplete="off"><button id="clear-search" class="clear-search" type="button" aria-label="Clear asset search" hidden>×</button></div><span class="result-count" id="result-count" aria-live="polite">'+str(total_cards)+' asset sets / '+str(total_files)+' files</span></div><div class="filters" role="group" aria-label="Asset category">'+filters+'</div></div><p id="empty-state" class="empty-state" hidden>No assets match. Try another name or category.</p>'+sections+'<div class="library-note"><strong>Working files.</strong> Logos are outlined and need no installed fonts. Editable social and print SVG templates contain labelled text fields; install the supplied fonts and replace those fields before use. White and reverse logos are shown on dark backgrounds here. Patch files are actual-size vector artwork for supplier digitisation and a sew-out. The embroidered photographs are illustrative mockups, not physical samples. Font licences are included with each family; the guide embeds a renamed Japanese font subset for offline viewing.</div></div></section></main><footer class="footer"><div class="footer-inner"><span>STAND THE FIGHT / TRADITIONAL JAPANESE JIU-JITSU</span><a href="#guide-01">Back to guide ↑</a></div></footer><script>'+js+'</script></body></html>'
    (ROOT/'index.html').write_text(doc)
    print(f'HTML guide: {len(pages)} faithful pages, {total_cards} asset sets, {total_files} downloads.')
