from pathlib import Path
from collections import defaultdict
import re, json, html, hashlib, shutil, base64, argparse, zipfile
from parse_substellar_blocks import blocks_from_text
from planning_hub import render_planning
from illustrated_companion import IllustratedCompanion, read_records
from edition05_companion import CurrentDirectionEdition, NEW_SOURCES, NEW_KEYS

ROOT=Path(__file__).resolve().parent
PORTABLE=(ROOT.name=='sources' and (ROOT.parent/'index.html').exists())
OUT=ROOT.parent if PORTABLE else ROOT/'substellar'
DIAGRAM_ROOT=OUT/'images/diagrams' if PORTABLE else ROOT/'diagrams'
DIAGRAM_META=ROOT/'overview-diagrams.json' if PORTABLE else ROOT/'diagrams/overview-diagrams.json'
def copy_file(source,dest):
    source,dest=Path(source),Path(dest)
    if source.resolve()!=dest.resolve():shutil.copy2(source,dest)
SOURCES=[('approved','complete_text_for_typesetting.txt','Approved text'),('industry','industry_progression_expansion.txt','Industry expansion'),('moon-mars','moon_mars_expansion.txt','Destination expansion'),('tier1','tier1_space_expansion.txt','Tier 1 expansion'),('first-playable','first_playable_expansion.txt','First Playable planning'),('tier1-models','tier1_model_specifications.txt','Tier 1 model specifications'),('illustrated','illustrated_companion.txt','Illustrated reading companion')]+NEW_SOURCES
PLANNING_HTML=''
COMPANION=None
CURRENT=None
ART={
 'EAR01_H': {'source':'illustrated_assets/EAR01_H_Overworld.png','name':'Overworld — home and the first descent','caption':'The familiar surface remains the emotional center: blue water, a readable quarry, warm shelter, and a route into the Earth. Existing concept image, reused in this edition.','kind':'Existing concept'},
 'EAR03_H': {'source':'illustrated_assets/EAR03_H_Deepwilds.png','name':'Deepwilds — a living underground landscape','caption':'A shared atmosphere reference for vegetation, water, player scale, and warm maintained refuges. The complete written atlas retains quiet, sparse, wet, and dry variations beyond this image.','kind':'Existing concept'},
 'EAR05_H': {'source':'illustrated_assets/EAR05_H_Abyss.png','name':'Abyss — a refuge against the deep','caption':'Warm shelter and a maintained crossing establish a route through immense columns, dark water, and exposed ground. The other Abyss experiences remain individually named in the text and artwork backlog.','kind':'Existing concept'},
 'OV-EARTH': {'source':'generated_images/exec-df67088b-7ab2-4654-9a6d-675b8a07f4f3.png','name':'Earth — four more realm identities','caption':'Shallows, Underreach, Mantle, and Core receive an overview panel each. These are concept moods and example activities; the seven-realm diagram defines the relationships, and the complete text defines all 35 regional experiences.','kind':'New overview'},
 'OV-MOON': {'source':'generated_images/exec-116b91aa-0ecc-4ad8-b0e9-d3e3e2920654.png','name':'Moon — from the surface to Glassfall Vaults','caption':'A first foothold on a crater rim, a tethered shallow descent, a sheltered gallery habitat, and a deep glass survey. Surface and cave profiles are fictional game regions, with no implied breathable lunar caves.','kind':'New overview'},
 'OV-MARS': {'source':'generated_images/exec-74e1393b-d7ac-448c-b59f-d74b71dd5eef.png','name':'Mars — a frontier above and below ground','caption':'Windcut terrain and old drainage lead into Ochre Galleries, Brine Scarps, and Rootstone Chambers. Imported equipment supports the first expedition; local materials improve subsequent operations.','kind':'New overview'}
}
if (ROOT/'overview_generated_assets.json').exists():
    for k,v in json.loads((ROOT/'overview_generated_assets.json').read_text()).items():
        ART.setdefault(k,{}).update(v)

def esc(s):return html.escape(str(s),quote=True)
def slug(s):
    return re.sub(r'[^a-z0-9]+','-',s.lower()).strip('-')
def inline(s):
    # Preserve words and punctuation while interpreting the draft's exact inline markup.
    tokens=re.split(r'(\*\*.*?\*\*|`[^`]+`|https?://[^\s<>]+)',str(s))
    parts=[]
    for t in tokens:
        if t.startswith('**') and t.endswith('**'):parts.append('<strong>'+esc(t[2:-2])+'</strong>')
        elif t.startswith('`') and t.endswith('`'):parts.append('<code>'+esc(t[1:-1])+'</code>')
        elif re.match(r'https?://',t):
            url=t.rstrip('.,;)');suffix=t[len(url):]
            parts.append('<a href="'+esc(url)+'" target="_blank" rel="noopener noreferrer">'+esc(url)+'</a>'+esc(suffix))
        else:parts.append(re.sub(r'\bREF(\d{2})\b',lambda m:'<a href="#ref-ref'+m[1]+'">'+m[0]+'</a>',esc(t)))
    return ''.join(parts)
def plain(s):return html.unescape(re.sub(r'<[^>]*>','',inline(s)))
def words(s):return len(re.findall(r"\b[\w]+(?:[’'-][\w]+)*\b",re.sub(r'[#*`|]',' ',s)))
def badge(label,kind=''):return '<span class="pill '+kind+'">'+esc(label)+'</span>'

CHAPTERS=[];SOURCE_BLOCKS={};TITLE_ANCHORS={};ID_ANCHORS={};SEARCH=[]
for key,filename,label in SOURCES:
    path=ROOT/filename
    if not path.exists():raise RuntimeError('Missing complete source: '+filename)
    blocks=blocks_from_text(path.read_text());SOURCE_BLOCKS[key]=blocks;chapter=None;ci=0
    for bi,b in enumerate(blocks):
        if b[0]=='heading' and b[1]==1:
            ci+=1;cid=('ch' if key=='approved' else key)+'-'+str(ci).zfill(2)+'-'+slug(b[2])
            chapter={'id':cid,'title':b[2],'source':key,'label':label,'index':ci,'blocks':[],'subheads':[]}
            CHAPTERS.append(chapter);TITLE_ANCHORS[b[2]]=cid
        if chapter is None:raise RuntimeError('Source must begin with a chapter: '+filename)
        blockid=f'{key}-{bi:04d}';anchor=chapter['id'] if b[0]=='heading' and b[1]==1 else blockid
        if b[0]=='heading' and b[1]>1:
            anchor=chapter['id']+'--'+slug(b[2]);
            if any(x.get('anchor')==anchor for x in chapter['blocks']):anchor+='-'+str(bi)
            chapter['subheads'].append({'anchor':anchor,'level':b[1],'title':b[2]})
            TITLE_ANCHORS.setdefault(b[2],anchor)
        text=' '.join(' '.join(row) for row in b[1]) if b[0]=='table' else b[2] if b[0]=='heading' else b[1]
        for ident in re.findall(r'\b(?:T1-[A-Z]+-?\d+|FP-(?:PLAN-\d+|B\d+|ART\d+|C\d+|R\d+)|MO-PLAN-\d+|MA-PLAN-\d+|IND-M\d+|IP\d+|MAT\d+|PM\d+|EQ\d+|TEC\d+|SUPPLY\d+|BIO\d+|FL\d+|HUL\d+|CEC\d+|SPX\d+|UX\d+|EAR\d+|SET\d+|DST\d+|ENC\d+|MO-[SL]\d|MA-[SL]\d)\b',text):
            if b[0]=='heading' and key not in ('first-playable','tier1-models') and key not in NEW_KEYS:ID_ANCHORS[ident]=anchor
            else:ID_ANCHORS.setdefault(ident,anchor)
        if key in NEW_KEYS:
            for ident in re.findall(r'\b(?:CD05-(?:T\d+|[AD]\d+)|WP05-(?:PLAN-|B|AC)?\d+|LE05-(?:PLAN-|AC)?\d+|OS05-\d+)\b',text):
                if b[0]=='heading':ID_ANCHORS[ident]=anchor
                else:ID_ANCHORS.setdefault(ident,anchor)
        chapter['blocks'].append({'block':b,'index':bi,'id':blockid,'anchor':anchor})
        if b[0]=='heading':chapter['current_heading']=b[2]
        SEARCH.append({'id':anchor,'chapter':chapter['title'],'title':chapter.get('current_heading',chapter['title']),'text':plain(text),'source':key})

# Realm and destination IDs open their actual chapter, while order/recipe IDs
# retain the exact source table in which they are defined.
for prefix,titles in [('EAR',['Overworld','Shallows','Deepwilds','Underreach','Abyss','Mantle','Core']),('DST',['Moon','Mars','Ceres','Europa','Io','Titan','Pluto','Cairn','Morrow'])]:
    for number,title in enumerate(titles,1):
        ID_ANCHORS[prefix+str(number).zfill(2)]=TITLE_ANCHORS[title]

def anchor(title):
    if title in TITLE_ANCHORS:return TITLE_ANCHORS[title]
    if title in ID_ANCHORS:return ID_ANCHORS[title]
    matches=[v for k,v in TITLE_ANCHORS.items() if title.lower() in k.lower()]
    return matches[0] if matches else 'complete-text'
def link(title,label=None):return '<a href="#'+esc(anchor(title))+'">'+esc(label or title)+'</a>'
def idlink(ident):return '<a href="#'+esc(ID_ANCHORS.get(ident,'complete-text'))+'">'+esc(ident)+'</a>'
def artpath(ident):return 'images/'+slug(ident)+'.webp'
def originalpath(ident):return 'images/originals/'+slug(ident)+'.png'

REFS=json.loads((ROOT/'reference_catalogue.json').read_text())
for r in REFS['records']:
    ART[r['id']]={'source':r['file_path'],'name':r['title'],'caption':'; '.join(r['appearance_notes'][:2]),'kind':'Confirmed reference','record':r}
for r in read_records(ROOT/'illustrated_03_boards.json'):
    path=ROOT/'illustrated_03_assets'/Path(r['file']).name
    if PORTABLE or path.is_file():
        ART[r['id']]={'source':str(path),'name':r['title'],'caption':r.get('caption',r.get('alt','')),'kind':'Illustrated overview concept','record':r}
if PORTABLE:
    for ident in ART:ART[ident]['source']=str(OUT/originalpath(ident))

def plate(ident,caption=None):
    x=ART.get(ident)
    if not x:raise RuntimeError('Missing required overview art '+ident)
    name=x['name'];cap=caption or x['caption']
    return f'''<figure class="plate" id="art-{slug(ident)}"><button type="button" data-image="{artpath(ident)}" data-original="{originalpath(ident)}" data-caption="{esc(name)}" aria-label="Enlarge {esc(name)}"><img src="{artpath(ident)}" alt="{esc(name+' — '+cap)}" loading="lazy" decoding="async"></button><figcaption><div class="plate-meta"><strong>{esc(name)}</strong><span>{esc(ident)} · {esc(x['kind'])}</span></div>{esc(cap)}</figcaption></figure>'''

DIAGRAMS=json.loads(DIAGRAM_META.read_text())['diagrams']
def diagram(name):
    rec=next(x for x in DIAGRAMS if x['file']==name+'.svg')
    svg=(DIAGRAM_ROOT/rec['file']).read_text()
    svg=re.sub(r'<\?xml[^>]*>','',svg)
    # Namespacing protects markers and accessibility references when all seven SVGs are inline.
    svgids=re.findall(r'\bid="([^"]+)"',svg)
    for sid in sorted(svgids,key=len,reverse=True):
        newid=name+'-'+sid
        svg=svg.replace('id="'+sid+'"','id="'+newid+'"').replace('url(#'+sid+')','url(#'+newid+')').replace('href="#'+sid+'"','href="#'+newid+'"')
        svg=re.sub(r'(aria-labelledby=")([^"]+)(")',lambda m:m[1]+' '.join(newid if v==sid else v for v in m[2].split())+m[3],svg)
    return f'<figure class="diagram" id="diagram-{name}">{svg}<figcaption>{esc(rec["alt"])} <a href="images/diagrams/{name}.svg" target="_blank">Open full-size diagram</a></figcaption></figure>'

def sectionhead(num,title,desc=''):
    return '<div class="section-head"><div><span class="eyebrow">'+esc(num)+'</span><h2>'+esc(title)+'</h2></div>'+('<p>'+esc(desc)+'</p>' if desc else '')+'</div>'

def card(title,desc,href=None,ident=None,status=None,extra='',imageid=None):
    im=f'<div class="card-art"><button type="button" data-image="{artpath(imageid)}" data-original="{originalpath(imageid)}" data-caption="{esc(ART[imageid]["name"])}" aria-label="Enlarge {esc(ART[imageid]["name"])}"><img src="{artpath(imageid)}" alt="{esc(title)}" loading="lazy"></button></div>' if imageid else ''
    image_anchor=' id="art-'+slug(imageid)+'"' if imageid in ('EAR01_H','EAR03_H','EAR05_H') else ''
    return '<article class="card"'+image_anchor+'>'+im+('<span class="card-id">'+esc(ident)+'</span>' if ident else '')+'<h3>'+esc(title)+'</h3>'+(''.join(badge(*s) for s in status) if status else '')+'<p>'+esc(desc)+'</p>'+extra+(f'<div class="links"><a href="#{esc(href)}">Read the complete direction →</a></div>' if href else '')+'</article>'

def render_overview():
    original_words=words((ROOT/SOURCES[0][1]).read_text())
    add_words=sum(words((ROOT/f).read_text()) for k,f,l in SOURCES[1:])
    h=f'''<section class="hero" id="overview"><div class="hero-top"><div class="eyebrow">Creative &amp; product direction · Illustrated overview 03</div><h1 class="hero-title">Every world<br>goes <em>deeper.</em></h1><p class="hero-desc">A complete reference for SUBSTELLAR: the Earth beneath home, the worlds beyond it, and the industry, equipment, settlements, and journeys that connect them.</p><div class="hero-links"><a class="button primary" href="#illustrated-overview">Open the illustrated guide</a><a class="button" href="#world-atlas">Explore the atlas</a><a class="button" href="#complete-text">Read the complete text</a><a class="button" href="#tier1-overview">Tier 1 space systems</a></div></div><figure class="hero-art"><img src="{artpath('REF03')}" alt="Confirmed Explorer rocket and astronaut beside a warm lunar outpost, with Earth overhead."><figcaption class="hero-caption"><div><span>PALE HORIZON / CONFIRMED REFERENCE 03</span><strong>A first room beneath an unfamiliar sky.</strong></div><span>The Explorer and its supporting systems establish the first complete Earth–Moon–Earth journey.</span></figcaption></figure><div class="edition-strip"><div><b>{original_words:,}</b><span>Approved words preserved</span></div><div><b>{add_words:,}</b><span>Words of additional direction</span></div><div><b>15</b><span>Confirmed reference sheets</span></div><div><b>7 + 5</b><span>Original diagrams + world boards</span></div></div></section>'''
    if CURRENT:
        h=h.replace('Creative &amp; product direction · Illustrated overview 03','Creative &amp; product direction · Current edition 05').replace('href="#illustrated-overview">Open the illustrated guide','href="#current-edition">Open the current direction').replace('<b>7 + 5</b><span>Original diagrams + world boards</span>','<b>59</b><span>Concepts, references and diagrams</span>')
        h+=CURRENT.hub()
        h+=render_chapters([c for c in CHAPTERS if c['source'] in NEW_KEYS], source_band=False)
        h+=CURRENT.review_and_maps()
    if COMPANION:
        h+='<div class="reader-band" id="retained-overview-03"><div class="eyebrow">Retained illustrated overview 03</div><h2>The complete high-level atlas and reading guides</h2><p>The ten guides and their original eight-board production record are preserved below. Their edition-specific counts describe that earlier overview. The visual directory includes the three additional concept boards and six diagrams from edition 05.</p></div>'
        h+=COMPANION.intro()+COMPANION.coverage()+COMPANION.chapter_directory()+COMPANION.visual_directory()
    h+='<details class="historical-edition"><summary>Earlier planning edition 02 — specifications and original image briefs</summary><p class="intro-note">This retained planning record predates the illustrated overview. Its zero-generation statement and two pending briefs describe that earlier delivery. The current visual catalogue records the completed overview boards and diagrams.</p>'+PLANNING_HTML+'</details>'
    h+='<p class="endnote edition-history">The original five world overview boards and their earlier edition labels are retained. Current illustrated coverage is recorded in the ten reading guides and visual catalogue above.</p>'
    h+='<section class="section-wrap" id="edition-guide">'+sectionhead('Reading this edition','The complete plan, made navigable.','The approved text remains intact. New planning is identifiable, and future artwork has named tasks.')
    h+='''<div class="status-meaning"><div>'''+badge('Approved text','confirmed')+'''<p>All 36 chapters and 28 tables from the approved text edition remain below, in full and in order.</p></div><div>'''+badge('New planning','new')+'''<p>The industrial progression, machine catalogue, Moon, Mars, and complete Tier 1 systems are expanded for review.</p></div><div>'''+badge('Confirmed reference','confirmed')+'''<p>Your 15 supplied sheets define the space hardware’s visual direction. Their labels are searchable as ordinary text.</p></div><div>'''+badge('Detailed artwork — backlog','backlog')+'''<p>Overview coverage is kept separate from individual regions, machine states, model views, and final asset production.</p></div></div>'''
    h+='<div class="intro-note"><h3>Current direction takes precedence where the new references clarify it.</h3><p>The <strong>Tier 1 Explorer Rocket</strong> is the first physical spacecraft and supports both orbital and lunar missions. The earlier Keel and Pilgrim hull proposals are retained in the approved text for context. Attainable Earth materials support the first complete round trip; advanced industry and deep Earth discoveries strengthen later exploration. The new expansion explains these changes explicitly.</p></div>'
    h+='<p class="overview-intro">Use the contents to open any chapter, or press <kbd>/</kbd> to search across the full writing, reference labels, and art backlog. Section links identify passages. Optional review notes stay in your browser and can be exported. The illustrations are creative references; exact connections and labels live in the diagrams and text.</p></section>'
    h+='<section class="section-wrap" id="world-atlas">'+sectionhead('01 / The whole horizon','The Solar System, and Afterlight.','All eight planets are represented. Campaign destinations, optional branches, orbital settings, and planning backlog are distinguished.')+diagram('solar-system')
    dest=[
      ('Moon','Pale Horizon','Learn to leave, descend, establish a refuge, and return. Silence, dust, fractured hollows, basalt passages, and impact glass.', 'Moon — complete destination planning expansion','OV-MOON'),
      ('Mars','Red Frontier','Read the old drainage, cross a weather-shaped surface, build a Frontier Yard, and investigate the geology beneath it.','Mars — complete destination planning expansion','OV-MARS'),
      ('Ceres','Optional industrial branch','Anchors, fractured routes, contained ice-pocket extraction, and cargo transfer. Holdfast makes a difficult route workable.','Ceres',None),
      ('Europa','Outer Worlds','Descend through the ice ceiling into Lantern Ocean and its fictional vent ecology. Pressure, current, sensing, and protected refuge define the expedition.','Europa',None),
      ('Io','Optional thermal branch','Survey and time an operation among mineral chimneys, cooling passages, and Furnace Wells. Build a bounded pocket of industrial order.','Io',None),
      ('Titan','Outer Worlds','Amber shorelines, waxlike passages, local underground weather, and slow folded life. Establish a harbor and learn a route through haze.','Titan',None),
      ('Pluto','Solar System conclusion','Long shadows, frozen folds, Nightglass optics, and the Quiet Lens. Complete the Far Observatory and choose what comes after homecoming.','Pluto',None),
      ('Cairn','Afterlight / Beyond Sol','Tilted plates, Inverted Terraces, Palimpsest Halls, and the Witness Engine. Read what remains and chart a local route to Morrow.','Cairn',None),
      ('Morrow','Afterlight / capstone','Sheltering growth, Rooted Vaults, Tide Orchards, and Hearthsea. Observation and a useful conservatory turn discovery into a place to tend.','Morrow',None)]
    h+='<div class="card-grid">'
    for title,tag,desc,target,img in dest:
        expanded=title in ('Moon','Mars')
        status=[('Documented','confirmed'),('Overview included','overview')]
        if not expanded:status.append(('Further design expansion — backlog','backlog'))
        h+=card(title,desc,anchor(target),tag,status,imageid=img)
    h+='</div><div class="small-world-grid">'
    planets=[('Mercury','#9b8a7c','Further design expansion — backlog','A named Solar System location in the atlas. Landing, strata, activities, and campaign role await design.'),('Venus','#d6b16b','Further design expansion — backlog','Atmosphere and surface identity need a deliberate gameplay brief before a new destination is promised.'),('Jupiter','#b69b82','Orbital setting','A monumental orbital view and access context for Europa and Io. No conventional solid landing surface is promised.'),('Saturn','#cebe8c','Orbital setting','Ringed orbital scenery and access context for Titan. Station and route details remain future work.'),('Uranus','#88bdc1','Further design expansion — backlog','Represented in the atlas. A future brief must choose orbital activity, supported moons, or another explicit experience.'),('Neptune','#5a7aa7','Further design expansion — backlog','Represented in the atlas. Future destination planning must define its role and scope before content expansion.')]
    for name,color,status,desc in planets:
        h+=f'<article class="card" id="world-{slug(name)}"><div class="destination-mini"><span class="orb {"ringed" if name=="Saturn" else ""}" style="--planet:{color}" aria-hidden="true"></span><div><h3>{name}</h3></div></div>'+badge(status,'backlog' if 'backlog' in status else 'overview')+'<p>'+esc(desc)+'</p></article>'
    h+='</div>'+plate('OV-OUTER')+plate('OV-FRONTIERS')+'</section>'
    h+='<section class="section-wrap" id="earth-overview">'+sectionhead('02 / The first world','Seven realms. Thirty-five experiences.','Depth is a tendency, with lateral geography and overlapping transitions. These are not seven equally thick floors.')+diagram('earth-layers')
    h+=plate('OV-EARTH')+'<div class="card-grid">'
    for name,ident,desc in [('Overworld','EAR01_H','Ordinary survival, expressive landscapes, a useful home, and the quarry’s invitation underground.'),('Deepwilds','EAR03_H','A living underground landscape of canopies, shores, gardens, cultivation, and established return routes.'),('Abyss','EAR05_H','Exposed crossings, pillars, dark water, careful navigation, and the emotional warmth of a dependable refuge.')]:
        h+=card(name,desc,anchor(name),status=[('Documented','confirmed'),('Concept included','overview')],imageid=ident)
    h+='</div><div class="intro-note"><strong>World-generation contract.</strong> The documented working envelope is 512 total block levels, Y −192 through 319. The Core is the fictional deep-crust Heart environment. Final realm allocations remain a generation decision. '+link('The vertical world specification','Read the full specification →')+'</div></section>'
    h+='<section class="section-wrap" id="moon-overview">'+sectionhead('03 / Pale Horizon','The Moon is a full destination.','Two surface experiences and three underground regions, with a first foothold, a growing workshop, a campaign, and reasons to return.')+plate('OV-MOON')+diagram('moon-layers')
    h+='<div class="comparison-grid">'+card('A Light Across the Basin','Connect observations, reflectors, and relay sites; learn the return route; expand from a small refuge to a useful lunar base.',anchor('MO-PLAN-07'),ident='MOON CAMPAIGN')+card('From regolith to a better expedition','Regolith Ceramic strengthens habitats and thermal panels. Lunar Glass Filament improves instruments and lighting. Earth-built equipment enables the first samples.',anchor('MO-PLAN-05'),ident='MATERIAL → PROCESS → USE')+'</div><div class="intro-note">'+link('Moon — complete destination planning expansion','Read the complete Moon planning expansion')+' — generation, all five regions, conditions, local industry, controller stages, mission beats, three expeditions, progression, acceptance criteria, and named future art.</div></section>'
    h+='<section class="section-wrap" id="mars-overview">'+sectionhead('04 / Red Frontier','Mars asks for field competence.','Weather, rover routes, repairable industry, buried drainage, and a more independent frontier home.')+plate('OV-MARS')+diagram('mars-layers')
    h+='<div class="comparison-grid">'+card('The River That Isn’t There','Connect the surface delta to buried channels and Rootstone observations. The mission rewards interpreting a connected place.',anchor('MA-PLAN-07'),ident='MARS CAMPAIGN')+card('A Frontier Yard that earns its place','A service pad and shelter grow into a rover bay, repair gantry, weather mast, greenhouse, and supply depot. Ferric Lattice and Brine Catalyst improve later operations.',anchor('MA-PLAN-06'),ident='OUTPOST → REGIONAL CAPABILITY')+'</div><div class="intro-note">'+link('Mars — complete destination planning expansion','Read the complete Mars planning expansion')+' — all five regions, weather and recovery, processing chains, outpost stages, mission evidence, three expeditions, revisits, and future artwork.</div></section>'
    return h

def render_progression():
    reg=json.loads((ROOT/'industry_progression_register.json').read_text())
    h='<section class="section-wrap" id="progression-overview">'+sectionhead('05 / Capability, work, and travel','Progression lives in what you can do.','A readable industrial world: machinery with physical purpose, broad processing capability, and meaningful operating choices.')+diagram('progression')
    h+='<div class="intro-note"><h3>A first launch is an achievable workshop project.</h3><p>After the first powered workshop, the player can prepare the Explorer and pursue a lunar foothold while regional and deep Earth industry continue. The industrial bands describe capabilities. They are not ten mandatory, sequential crafting gates, and they are separate from rocket Tiers 1–3.</p></div>'
    h+='<div class="table-wrap wide"><table><thead><tr><th>Band</th><th>Capability</th><th>First useful result</th><th>Relationship to the world</th></tr></thead><tbody>'
    for r in reg['industrial_bands']:
        h+='<tr><td>'+idlink(r['id'])+'<br>'+esc(r['title'])+'</td><td>'+esc(r['capability'])+'</td><td>'+esc(r['first_result'])+'</td><td>'+esc(r['exploration_relationship'])+'</td></tr>'
    h+='</tbody></table></div>'
    groups=[('Make and shape','Repair benches, timber work, ceramics, seals, textile production, forming, alloys, and recognizable vehicle parts.'),('Separate and refine','Crushing, washing, concentration, electrical refining, controlled chemical processing, gases, fluids, and useful recovery streams.'),('Power and distribute','Mechanical sources, generators, storage, distribution, shutoff, protected service allocations, and later specialist energy systems.'),('Support a journey','Charging, breathing supplies, propellant, habitat service, survey instruments, mobile repairs, and launch and recovery.'),('Move and automate','Cargo, conveyors, ports, bounded excavation, local order handling, established freight routes, and visible stop conditions.'),('Choose advanced capability','Optional specialist processes, larger energy projects, precision systems, powered equipment, and surveyed transit. Each keeps a defined role and cost.')]
    h+='<div class="card-grid">'+''.join(card(n,d,anchor('Machinery and industrial systems'),status=[('Documented in expansion','new'),('Detailed artwork — backlog','backlog')]) for n,d in groups)+'</div>'
    h+='<div class="intro-note"><strong>66 machine and function entries.</strong> Each sits beneath the existing TEC installation families. The detailed catalogue covers inputs, outputs, interaction, unlock context, cost, and tradeoffs; the surrounding chapters define service states, ports, networks, balancing, recovery, and regional pause. '+link('Machinery and industrial systems','Open the complete machine catalogue →')+'</div>'
    h+='<p class="overview-intro">The inspiration is Immersive Engineering’s visible industrial construction and Mekanism’s breadth of connected processing and configurable systems. SUBSTELLAR’s implementation and balance proposals are its own. '+link('Industry and progression','Read the progression and reference basis →')+'</p></section>'
    h+='<section class="section-wrap" id="tier1-overview">'+sectionhead('06 / A humble beginning','Tier 1 is the whole expedition.','The Explorer, its machines and items, the launch site, the lunar foothold, and the return are one connected feature.')+plate('REF15','Confirmed reference: the complete Tier 1 system family, from rocket parts and support machines to launch, Moon exploration, and return. The same sheet is preserved at full source resolution in the images folder.')
    h+='<div class="comparison-grid">'+card('Part Fabrication Table','Craft identified components from accessible Earth stock. Show input, working state, missing material, output, and safe interruption.',anchor('T1-M01'),ident='T1-M01 / REF10',imageid='REF10')+card('Rocket Assembly Station','Turn prepared components into one registered Explorer, commission it, and hand it to the launch site. “Rocket Assembly Table” remains a visible source alias.',anchor('T1-M02'),ident='T1-M02 / REF09 + REF15',imageid='REF09')+'</div>'
    h+='<div class="table-wrap"><table><thead><tr><th>System</th><th>Role in the first complete journey</th><th>Full planning</th></tr></thead><tbody>'
    rows=[('Fuel Refinery','Produce standard game propellant from defined, Earth-accessible inputs.','T1-M03'),('Fuel Pipe','Move propellant between compatible containers and a parked craft with explicit fill targets.','T1-M04'),('Oxygen Generator','Prepare breathing supplies for suit, cabin, and supported habitat services.','T1-M05'),('Launch Pad','Provide the registered staging, boarding, departure, and recovery location.','T1-M06'),('Control Console','Show route, readiness, cargo, services, reserves, and the next action.','T1-M07'),('Storage Crate','Keep workshop stock, mission cargo, samples, and reserved supplies distinct.','T1-M08'),('Rocket parts, engine, and tank','A small recognizable component family supports assembly, servicing, and refits.','T1-I01'),('Navigation Chip / Guidance Chip','Store and use supported route and mission information.','T1-I04'),('Launch Key','Make vehicle operation and ownership deliberate, with recovery if the key is lost.','T1-I05'),('Suit, helmet, and oxygen tank','Extend safe operation outside the cabin with legible reserves and serviceable equipment.','T1-E01'),('Explorer cabin and cargo','An inhabited first vehicle with compact seating, storage, access, and essential services.','T1-V01'),('Starter shelter and recovery','Earth-supplied survival, a lunar refuge, protected return allocations, and recoverable failure.','T1-S02')]
    for n,d,k in rows:h+='<tr><td>'+esc(n)+'</td><td>'+esc(d)+'</td><td>'+idlink(k)+'</td></tr>'
    h+='</tbody></table></div><div class="comparison-grid">'+card('The Explorer Rocket','An upright white-and-orange rocket with a stepped nose, dark cockpit window, visible hatch, fins, and dark engine cluster. One physical family covers first orbital and lunar capability.',anchor('T1-V01'),ident='CONFIRMED VEHICLE',imageid='REF11')+card('Player Space Suit','The matching practical suit, dark blue visor, reinforced gloves and boots, chest controls, and backpack establish the Tier 1 worn-equipment family.',anchor('T1-E01'),ident='CONFIRMED EQUIPMENT',imageid='REF12')+'</div>'
    h+='<div class="intro-note"><strong>Model decisions remain visible.</strong> The supplied sheets show rocket heights of approximately 6, 9, and 12 blocks. A production pass must reconcile exterior, interior, collision, and clearance together. Example recipe grids, the sample cargo-slot count, and illustrative station dimensions are retained as references without silently becoming final balance values. '+link('T1-OV01','Read the source reconciliation →')+'</div>'
    h+='<div class="comparison-grid">'+card('Tier 2 concept','The supplied Concept 2 / Earth Launch Site image establishes a later silhouette and launch-setting reference. Detailed systems and destination mapping remain for a later slice.',None,ident='REF06 / USER-SUPPLIED LATER TIER',status=[('Reference included','confirmed'),('Further design — backlog','backlog')],imageid='REF06')+card('Tier 3 concept','The supplied Concept 3 / Deep-Space Rocket shows a larger modular form. Its label is preserved; interstellar capability is not inferred from size alone.',None,ident='REF07 / USER-SUPPLIED LATER TIER',status=[('Reference included','confirmed'),('Further design — backlog','backlog')],imageid='REF07')+'</div>'
    h+='<div class="intro-note">'+link('Tier 1 space systems — complete first expedition','Read all 30 Tier 1 planning sections')+' — every named core object, commissioning, first flight, lunar campaign, servicing, return, refits, balance, recovery, data states, and the first implementation slice.</div></section>'
    h+='<section class="section-wrap" id="systems-overview">'+sectionhead('07 / A useful home','Settlements, materials, tools, and life.','The wider systems retain their complete writing and catalogues. These diagrams make the relationships easier to review.')+diagram('settlement-cycle')+diagram('resources-equipment')
    h+='<div class="card-grid">'+card('World generation','Regional shape, geology, water, light, ecology, fair variation, reachable landmarks, transitions, and return routes.',anchor('World generation and the Earth atlas'))+card('Settlements and authored life','Old Valley’s five chapters, construction controllers, residents, cooperative contribution, regional activity, and free building.',anchor('Settlements and authored adventures'))+card('Resources and products','19 Earth resources, 10 processed product families, 18 planetary materials, ordinary Minecraft resources, and source-to-use relationships.',anchor('Materials and the crafting economy'))+card('Tools and preparation','32 equipment families, loadouts, traversal, light, survey, excavation, protection, samples, repairs, and rescue.',anchor('Tools equipment and preparation'))+card('Living systems','Earth creatures and plants, food, cultivation, fishing, encounters, lore, audio, and fictional offworld ecology.',anchor('Life ecology and encounters'))+card('Stations and support vehicles','Orbit Anchorage, Transfer Yards, shared modules, rover, crawler, submersible, and atmospheric survey skiff.',anchor('Spacecraft and orbital places'))+'</div></section>'
    return h

def render_references():
    h='<section class="section-wrap" id="reference-gallery">'+sectionhead('08 / The confirmed visual record','All fifteen supplied references.','Every sheet is included unmodified in the folder. Names, purposes, scale notes, and variants are transcribed below each image.')
    h+='<p class="overview-intro">The recurring Tier 1 language is off-white and pale gray block forms, orange bands and service accents, dark machinery, blue glass or displays, clear hatches, and practical assembly. Source-sheet branding is preserved in the images; the project name remains SUBSTELLAR. Select an image to enlarge it.</p>'
    h+='<div class="reference-grid">'
    for r in REFS['records']:
        ident=r['id'];rid='ref-'+slug(ident)
        title=r['title'].replace(' / ',' · ')
        desc=' '.join(r['appearance_notes'])
        h+=f'<article class="reference-card" id="{rid}"><button type="button" data-image="{artpath(ident)}" data-original="{originalpath(ident)}" data-caption="{esc(ident+" · "+title)}" aria-label="Enlarge {esc(ident)}"><img src="{artpath(ident)}" alt="{esc(title)}" loading="lazy" decoding="async"></button><div class="reference-body">'+badge(ident,'overview')+badge('Confirmed reference','confirmed')+f'<h3>{esc(title)}</h3><p>{esc(desc)}</p><div class="tag-list">'+''.join('<span>'+esc(x)+'</span>' for x in r['object_labels'])+'</div>'
        if r['purposes']:
            h+='<details><summary>Read the supplied names and purposes</summary><dl class="source-callouts">'
            for p in r['purposes']:
                h+='<dt>'+esc(p['object'])+'</dt><dd>'+esc(p['source_text'])+(' <em>'+esc(p['note'])+'</em>' if p.get('note') else '')+'</dd>'
            h+='</dl></details>'
        h+='<details><summary>All readable labels and callouts</summary><ul>'+''.join('<li>'+esc(x)+'</li>' for x in r['visible_text_transcription'])+'</ul></details>'
        if r['duplicates_or_variants'] or r['legibility_uncertainties']:
            h+='<details><summary>Variants and reference notes</summary><ul>'+''.join('<li>'+esc(x)+'</li>' for x in r['duplicates_or_variants']+r['legibility_uncertainties'])+'</ul></details>'
        h+='<p style="margin-top:14px"><a href="'+originalpath(ident)+'" target="_blank" data-full-image>Open full image</a></p></div></article>'
        SEARCH.append({'id':rid,'chapter':'Confirmed reference gallery','title':ident+' · '+title,'text':desc+' '+' '.join(r['object_labels'])+' '+' '.join(r['visible_text_transcription'])+' '+' '.join(p['source_text'] for p in r['purposes']),'source':'references'})
    h+='</div><div class="intro-note"><h3>Confirmed appearance, explicit open decisions.</h3><p>The sheets do not agree on final rocket scale; the assembly form and chip name have variants. Their example recipes and sample interface values are retained as evidence. The new Tier 1 chapter gives the working interpretation and the production decisions still to resolve. '+link('T1-OV01','Read the reconciliation')+'</p></div></section>'
    return h

def render_backlog():
    path=ROOT/'artwork_backlog.json'
    if not path.exists():raise RuntimeError('Full artwork backlog must be present')
    obj=json.loads(path.read_text());rows=obj['records']
    current=json.loads((ROOT/'current_visual_coverage.json').read_text())
    coverage={r['original_record_id']:r for r in current['records'] if r['original_register']=='artwork_backlog.json'}
    families=sorted(set(r['family'] for r in rows))
    h='<section class="section-wrap" id="artwork-backlog">'+sectionhead('09 / Visual coverage and further detail','Included views and the illustration backlog.','The ten-part overview is complete. The register below retains individual view requests, with current delivery status shown where a specific earlier request has now been supplied.')
    h+='<div class="intro-note" id="current-visual-status"><strong>Current status is reconciled with the earlier record.</strong> All twelve requested interface layouts are supplied on the four editable interface sheets. The two earlier opening-scene briefs are supplied as the Old Valley and first workshop concept boards. Matching rows show their current status, with the previous status retained as history. Other records keep their existing status; a family board does not complete all of its individual views.<p class="current-coverage-links">'
    delivered=list(dict.fromkeys(r['delivered_figure_id'] for r in current['records']))
    h+=' · '.join('<a href="#illustrated-figure-'+ident.lower()+'">'+esc(ident)+'</a>' for ident in delivered)
    h+='</p><a class="button" href="sources/current_visual_coverage.json" download="current_visual_coverage.json">Download current coverage mappings</a></div>'
    h+='<p class="overview-intro">This register names the individual future views across realms, regional experiences, planets, materials, equipment, creatures, machines, interfaces, and construction. It records the writing status separately from the art status and links each task back to its planning context. Filter by a family, object, or stable ID.</p><div class="legend">'+badge('Concept/reference included','confirmed')+badge('Overview context included','overview')+badge('Detailed artwork — backlog','backlog')+'</div>'
    h+='<div class="backlog-controls"><input id="backlog-filter" type="search" placeholder="Find an object, region, or ID…" aria-label="Filter artwork tasks"><select id="backlog-family" aria-label="Artwork family"><option value="">All families</option>'+''.join('<option value="'+esc(f)+'">'+esc(f)+'</option>' for f in families)+'</select><button id="export-backlog" type="button">Export original register</button></div><p class="backlog-count" id="backlog-count"></p><div class="table-wrap backlog-table" tabindex="0" aria-label="Scrollable artwork task register"><table><thead><tr><th>Task ID</th><th>Subject and view</th><th>Writing</th><th>Artwork status</th><th>Available context and remaining work</th></tr></thead><tbody>'
    for r in rows:
        target=anchor(r.get('target_chapter',''))
        art=r['art_status'];st='confirmed' if ('included' in art.lower() and 'backlog' not in art.lower()) else 'backlog'
        ctx=r.get('overview_ref','') or ''
        if isinstance(ctx,list):ctx=', '.join(ctx)
        refs=r.get('source_refs',[]) or []
        detail=r.get('detail','')
        ctxparts=[]
        for code in re.findall(r'REF\d\d|OV-[A-Z]+|EAR\d\d[_-]H',ctx+' '+' '.join(refs)):
            if code in ART:
                lid='ref-'+slug(code) if code.startswith('REF') else 'art-'+slug(code)
                # Existing hero images are cards; link them to their full chapter context.
                if code in ('EAR01_H','EAR03_H','EAR05_H'):lid=anchor({'EAR01_H':'Overworld','EAR03_H':'Deepwilds','EAR05_H':'Abyss'}[code])
                ctxparts.append('<a href="#'+lid+'">'+esc(code)+'</a>')
        ranchor='task-'+slug(r['id'])
        now=coverage.get(r['id'])
        art_html=badge(art,st)
        current_detail=''
        if now:
            art_html=badge(now['current_status'],'confirmed')+'<details class="backlog-prior"><summary>Earlier status</summary>'+badge(art,st)+'</details>'
            current_detail='<p class="backlog-current-note"><a href="#illustrated-figure-'+now['delivered_figure_id'].lower()+'">Open '+esc(now['delivered_figure_id'])+'</a> · '+esc(now['coverage_note'])+'</p><details class="backlog-prior"><summary>Original view brief</summary>'+esc(detail)+'</details>'
            SEARCH.append({'id':ranchor,'chapter':'Current illustration coverage','title':r['id']+' · '+now['current_status'],'text':now['delivered_figure_id']+' '+now['coverage_note'],'source':'current-visual-coverage'})
        h+=f'<tr id="{ranchor}" data-backlog-row data-family="{esc(r["family"])}"><td>{esc(r["id"])}</td><td><a href="#{target}">{esc(r["subject"])}</a><br>{esc(r["view"])}</td><td>{esc(r["writing_status"])}</td><td>'+art_html+'</td><td>'+(' · '.join(dict.fromkeys(ctxparts))+'<br>' if ctxparts else '')+(current_detail or esc(detail))+'</td></tr>'
        SEARCH.append({'id':ranchor,'chapter':'Artwork backlog · '+r['family'],'title':r['id']+' · '+r['subject'],'text':r['view']+' '+r['writing_status']+' '+art+' '+detail+' '+ctx,'source':'backlog'})
    h+='</tbody></table></div><p class="endnote">The full register is a production reference, not an instruction to generate everything now. Tier 2 and Tier 3 spacecraft keep their supplied overview art and deferred detail. Additional planetary campaigns are not created merely because a body appears on the atlas.</p></section>'
    return h,obj

INSERTS={
 'The creative foundation': '<strong>Edition status.</strong> The 36 chapters below preserve the approved version 0.1 text verbatim, including its original review and image-production language. This overview edition records your subsequent text approval, includes the first illustration slice, and adds clearly identified new planning. The edition guide and new Tier 1 reconciliation express the current status.',
 'The complete player journey': '<strong>Progression clarification.</strong> The new industrial plan defines a practical early Tier 1 branch after a powered workshop. The long Earth adventure remains central and supports later capabilities; Core completion and Heartglass are not material gates for the first lunar round trip. See the industrial and Tier 1 expansions.',
 'Core': '<strong>Current Tier 1 relationship.</strong> This approved Core profile is preserved. The new confirmed Explorer direction uses attainable Earth materials. Core discoveries strengthen the wider Earth-to-stars journey and later capabilities, without becoming a mandatory material gate for the first lunar mission.',
 'Spacecraft and orbital places': '<strong>Confirmed Tier 1 reconciliation.</strong> The Explorer is the first physical spacecraft, combining Orbital and Lunar capability. The Keel and Pilgrim names and silhouettes below remain earlier proposed alternatives. The six capability classes still describe roles; they do not require six disposable vehicles. Detailed later-tier expansion and new art remain deferred.',
 'Visual direction and the illustrated edition': '<strong>Current image-production scope.</strong> This original comprehensive register is preserved. Only the current overview slice is included now. The separate artwork task register distinguishes existing concepts, your confirmed references, overview context, and dedicated views still in the backlog. It does not authorize generating the entire register at once.'
}

def render_chapters(chapters=None, source_band=True):
    chapters=chapters if chapters is not None else [c for c in CHAPTERS if c['source'] not in NEW_KEYS]
    h='<div class="reader-band" id="complete-text"><div class="eyebrow">Complete writing / preserved in full</div><h2>The approved creative and product direction</h2><p>All 36 chapters and 28 tables follow in their original order. The preserved planning expansions follow the approved text. Edition notes link each source to its current topic without removing the earlier wording.</p></div>' if source_band else ''
    last_source='approved' if source_band else chapters[0]['source'] if chapters else ''
    for ci,c in enumerate(chapters):
        if c['source']!=last_source:
            last_source=c['source']
            title={'industry':'Industrial progression and machinery','moon-mars':'Moon and Mars: complete destination planning','tier1':'Tier 1: the complete first space expedition','first-playable':'First Playable — Old Valley, the Quarry, and the First Workshop','tier1-models':'Tier 1 — Model and Implementation Specifications','illustrated':'Illustrated overview — ten reading guides',**{k:label for k,f,label in NEW_SOURCES}}[last_source]
            if last_source=='illustrated':
                h+='<div class="reader-band" id="expansion-illustrated"><div class="eyebrow">Illustrated overview 03 / Ten reading guides</div><h2>'+esc(title)+'</h2><p>Representative images, precise diagrams, and source-linked comparisons explain the complete direction. All earlier writing and artwork remain available through the chapter links.</p></div>'
            elif last_source not in NEW_KEYS:
                h+='<div class="reader-band" id="expansion-'+last_source+'"><div class="eyebrow">Preserved planning expansion</div><h2>'+esc(title)+'</h2><p>The detailed writing is retained in full. The supplied Tier 1 artwork is confirmed; proposed operational choices and numerical tuning remain identified for review.</p></div>'
        status=badge('Approved text','confirmed') if c['source']=='approved' else badge('Current synthesis · proposals labelled','overview') if c['source']=='current-direction' else badge('Edition 05 planning · working defaults','new') if c['source'] in NEW_KEYS else badge('Illustrated reading guide','overview') if c['source']=='illustrated' else badge('Expansion for review','new')
        number=('Chapter '+str(c['index']).zfill(2)) if c['source']=='approved' else c['label']+' '+str(c['index']).zfill(2)
        h+='<section class="chapter" id="'+c['id']+'" data-source-section="'+c['source']+'">'
        inbody=False;listkind=None;illustrated_slice=None;illustrated_inserted=False;current_heading=c['title'];current_inserted=False
        for entry in c['blocks']:
            b=entry['block'];kind=b[0];bid=entry['id'];aid=entry['anchor']
            attrs=' class="source-block" data-source="'+c['source']+'" data-block="'+bid+'"'
            if kind not in ('bullet','number') and listkind:
                h+='</'+listkind+'>';listkind=None
            if kind=='heading' and b[1]==1:
                # Chapter section owns the stable anchor; heading still has the exact source block ID.
                h+='<header class="chapter-header"><span class="eyebrow">'+esc(number)+'</span><h2'+attrs+' id="'+bid+'">'+inline(b[2])+'</h2><div class="chapter-actions">'+status+'<button type="button" data-copy-anchor="'+c['id']+'">Copy section link</button></div></header>'
                if c['title'] in INSERTS:
                    h+='<aside class="chapter-insert">'+INSERTS[c['title']]+' <a href="#edition-guide">Edition guide</a> · <a href="#tier1-overview">Tier 1 overview</a></aside>'
                if c['title'] in ('Moon','Mars'):
                    h+='<aside class="chapter-insert">This approved profile now has a complete destination planning expansion. '+link(c['title']+' — complete destination planning expansion','Open the full expansion →')+'</aside>'
                if COMPANION:h+=COMPANION.chapter_note(c['title'])
                if CURRENT and c['source'] not in NEW_KEYS:h+=CURRENT.original_chapter_note(c)
                h+='<div class="chapter-body">';inbody=True
            elif kind=='heading':
                current_heading=b[2];current_inserted=False
                lev=min(5,b[1]+1);h+='<h'+str(lev)+attrs+' id="'+aid+'">'+inline(b[2])+'<a class="anchor-mark" aria-hidden="true" tabindex="-1" href="#'+aid+'">#</a></h'+str(lev)+'>'
                if c['source']=='illustrated' and b[1]==2:
                    match=re.match(r'ILL-S(\d+)',b[2]);illustrated_slice=int(match[1]) if match else None;illustrated_inserted=False
            elif kind=='table':
                rows=b[1];cols=max(len(r) for r in rows)
                h+='<div class="table-wrap '+('wide' if cols>=4 else '')+'" tabindex="0" aria-label="Scrollable reference table"><table'+attrs+' id="'+aid+'"><thead><tr>'
                h+=''.join('<th scope="col">'+inline(v)+'</th>' for v in rows[0])+'</tr></thead><tbody>'
                for row in rows[1:]:h+='<tr>'+''.join('<td>'+inline(v)+'</td>' for v in row)+'</tr>'
                h+='</tbody></table></div>'
            elif kind in ('bullet','number'):
                needed='ul' if kind=='bullet' else 'ol'
                if listkind!=needed:
                    if listkind:h+='</'+listkind+'>'
                    h+='<'+needed+'>';listkind=needed
                h+='<li'+attrs+' id="'+aid+'">'+inline(b[1])+'</li>'
            else:
                h+='<p'+attrs+' id="'+aid+'">'+inline(b[1])+'</p>'
                if c['source']=='illustrated' and illustrated_slice and not illustrated_inserted and COMPANION:
                    h+=COMPANION.slice_insert(illustrated_slice);illustrated_inserted=True
                if CURRENT and c['source'] in NEW_KEYS and not current_inserted:
                    h+=CURRENT.after_paragraph(c['source'],current_heading);current_inserted=True
        if listkind:h+='</'+listkind+'>'
        if inbody:h+='</div>'
        h+='<details class="review-box" data-chapter="'+c['id']+'" data-title="'+esc(c['title'])+'"><summary>Your review notes <span class="review-state"></span></summary><div class="review-fields"><label>Direction for this chapter<select aria-label="Review direction for '+esc(c['title'])+'"><option value="">No review note</option><option value="Keep this direction">Keep this direction</option><option value="Question to resolve">Question to resolve</option><option value="Change requested">Change requested</option></select></label><label>Notes<textarea placeholder="What should stay, change, or be explored further?" aria-label="Review notes for '+esc(c['title'])+'"></textarea></label><span class="review-help">Notes stay in this browser. Export them to keep a portable copy.</span><button type="button" data-export-notes>Export all review notes</button></div></details>'
        prev=chapters[ci-1] if ci else None;nxt=chapters[ci+1] if ci+1<len(chapters) else None
        h+='<nav class="chapter-link-nav" aria-label="Adjacent chapters">'+('<a href="#'+prev['id']+'">← '+esc(prev['title'])+'</a>' if prev else '<span></span>')+('<a href="#'+nxt['id']+'">'+esc(nxt['title'])+' →</a>' if nxt else '<a href="#overview">Return to overview ↑</a>')+'</nav></section>'
    return h

def render_reader_navigation():
    return '''<nav class="reader-nav" id="reader-nav" aria-label="Reading navigation"><div class="reader-context"><span class="reader-kicker">Now reading</span><a id="reader-chapter" href="#overview" title="Edition overview">Edition overview</a></div><div class="reader-jump"><label class="sr-only" for="reader-section">Jump to a section in the current chapter</label><select id="reader-section"><option value="overview">Edition overview</option></select></div><div class="reader-actions"><a id="reader-prev" href="#overview" aria-label="Previous section" aria-disabled="true" tabindex="-1" title="Previous section"><span aria-hidden="true">←</span></a><span id="reader-position" class="reader-position" aria-label="Position within current chapter">1</span><a id="reader-next" href="#illustrated-overview" aria-label="Next section" title="Next section"><span aria-hidden="true">→</span></a><button id="reader-copy" type="button" title="Copy a link to the current section">Copy link</button></div><div id="reader-progress" class="reader-section-progress" role="progressbar" aria-label="Progress through current chapter or section" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><span id="reader-progress-fill"></span></div></nav>'''

def render_sidebar():
    prime=[('overview','Edition overview'),('illustrated-overview','Start here: illustrated guide'),('illustrated-coverage','Ten-slice coverage'),('chapter-directory','Chapter reading directory'),('visual-directory','Illustrations & diagrams'),('world-atlas','Solar System & destinations'),('earth-overview','Earth: seven realms'),('moon-overview','Moon: surface & layers'),('mars-overview','Mars: surface & layers'),('progression-overview','Progression & machinery'),('tier1-overview','Tier 1 systems'),('systems-overview','Settlements & living systems'),('reference-gallery','Confirmed reference gallery'),('artwork-backlog','Artwork backlog')]
    if CURRENT:prime=prime[:1]+CURRENT.nav()+prime[1:]
    h='<aside class="sidebar" id="sidebar" aria-label="Document contents"><div class="sidebar-tools"><p class="nav-eyebrow">Field guide / Contents</p><button class="search-trigger" type="button" data-open-search><span>Search the complete plan</span><kbd>/</kbd></button><div class="sidebar-follow-controls"><button id="reader-follow" type="button" aria-pressed="true" title="Keep the current reading section visible in the contents">Follow reading: On</button><button id="reader-locate" type="button" title="Show the current section in the contents">Locate</button></div></div><div class="sidebar-scroll" id="sidebar-scroll"><nav class="nav-prime">'+''.join('<a href="#'+a+'">'+esc(t)+'</a>' for a,t in prime)+'</nav>'
    h+='<details class="nav-group"><summary>Earlier planning edition 02</summary><div class="nav-sub">'+''.join('<a href="#'+a+'">'+esc(t)+'</a>' for a,t in [('planning-slice','Planning overview'),('planning-decisions','Recommended defaults and decisions'),('tier1-model-matrix','Tier 1 model specification index'),('planning-build-backlog','Retained implementation backlog'),('next-image-slice','Original two-board image briefs')])+'</div></details>'
    if COMPANION:
        h+='<details class="nav-group" open><summary>Ten illustrated reading guides</summary><div class="nav-sub">'+''.join('<a href="#'+COMPANION.slice_target(s['number'])+'">'+str(s['number']).zfill(2)+' · '+esc(s['title'])+'</a>' for s in COMPANION.slices)+'</div></details>'
    for key,filename,label in NEW_SOURCES+[s for s in SOURCES if s[0] not in NEW_KEYS]:
        cs=[c for c in CHAPTERS if c['source']==key]
        title='Approved text · 36 chapters' if key=='approved' else label
        h+='<details class="nav-group"><summary>'+esc(title)+'</summary>'
        for c in cs:
            h+='<div class="nav-chapter"><a href="#'+c['id']+'"><span class="nav-num">'+str(c['index']).zfill(2)+'</span>'+esc(c['title'])+'</a>'
            if c['subheads']:
                h+='<details><summary>Sections</summary><div class="nav-sub">'+''.join('<a href="#'+s['anchor']+'">'+esc(s['title'])+'</a>' for s in c['subheads'])+'</div></details>'
            h+='</div>'
        h+='</details>'
    return h+'<div class="side-foot">Current edition 05 · Reader update 04 retained<br>Complete sources + current direction + worked journeys<br><a href="#source-package">Sources &amp; edition record</a></div></div></aside>'

def render_sources():
    # Cover and navigation copy are retained as historical source front matter, in addition to every body block.
    historical=['SUBSTELLAR','Creative and Product Direction','Text edition for creative review','Version 0.1   6 October 2026','A complete reference for the intended worlds, experiences, settlements, progression, equipment, spacecraft, and illustrated edition.','The journey begins with a familiar home and continues through the Earth below it, the Solar System, and the proposed worlds of Afterlight.','Text confirmation is the next creative checkpoint. The full image register is included for the subsequent illustrated edition.','Contents','Each entry links to its chapter. Word navigation also exposes the detailed headings, named environments, materials, and image references.']
    h='<section class="section-wrap" id="source-package">'+sectionhead('Edition record','Text, references, and reusable sources.','The folder contains the full reading edition, every supplied image, the new overview boards, editable diagrams, and ordinary source files.')
    h+='<div class="intro-note"><strong>Source and decision order.</strong> Your latest directions and confirmed artwork govern Tier 1 appearance and the current slice. The complete approved text is retained. The new expansions connect that source to the requested broader machinery and destination planning. Generated boards are concept references; unresolved numerical design remains explicit.</div>'
    h+='<div class="table-wrap"><table><thead><tr><th>File</th><th>Purpose</th></tr></thead><tbody>'
    entries=[('complete-direction.txt','The approved text and all planning expansions in one plain-text file.'),('complete_text_for_typesetting.txt','Exact approved body source, preserved without edits.'),('industry_progression_expansion.txt','The complete industrial progression and machine expansion.'),('moon_mars_expansion.txt','The complete Moon and Mars expansion.'),('tier1_space_expansion.txt','All Tier 1 operational planning.'),('first_playable_expansion.txt','The complete opening experience, first workshop, generation, controller, and build plan.'),('tier1_model_specifications.txt','Proposed Tier 1 dimensions, components, ports, assembly, animation, states, and implementation criteria.'),('first_playable_register.json','Structured first-playable tasks, resource accounting, dependencies, and acceptance.'),('tier1_model_register.json','Structured Tier 1 assets, geometry, connections, decisions, and implementation tasks.'),('next_image_slice.json','Historical planning-edition-02 briefs; their completed concept boards appear in the current visual catalogue.'),('project_inspection.json','Available-workspace inspection and explicit unverified implementation baseline.'),('edition_02_change_record.json','Scope, preservation, and approval status for planning slice 02.'),('overview-edition-01-manifest.json','The previous edition record retained for comparison.'),('reference_catalogue.json','Every supplied reference with its labels, purposes, notes, and source checksum.'),('industry_progression_register.json','Machine/function entries and industrial capability bands.'),('artwork_backlog.json','The original commissioning register, preserved with its historical status.'),('current_visual_coverage.json','Current delivery mappings for twelve interface layouts and both opening concept briefs.'),('overview_prompts.json','The overview art prompts and bounded production scope.'),('edition-manifest.json','Content counts, source checksums, included art, and edition metadata.'),('SUBSTELLAR_Creative_and_Product_Direction_v0.1.docx','The original approved document, including its cover, contents, and page references.')]
    entries += [('illustrated_companion.txt','The ten illustrated reading guides as complete plain text.'),('illustrated_editorial.json','Chapter summaries, slice relationships, realm and destination comparisons.'),('illustrated_visual_catalogue.json','Every retained and new visual with its caption, kind and context.'),('illustrated_coverage.json','Actual overview coverage of all ten document slices and dedicated views remaining.'),('illustrated_03_scope_data.json','Exact destination regions, material pairs, projects, and eleven planetary ecology families.'),('illustrated_03_boards.json','The eight new concept briefs, outputs, captions and individual coverage notes.'),('illustrated_03_diagrams.json','The eight new editable diagrams and their source mappings.'),('illustrated_03_interfaces.json','All twelve interface references grouped into four precise sheets.'),('planning-edition-02-manifest.json','The complete prior edition record, retained unchanged.')]
    if CURRENT:
        entries += [(name,desc) for name,desc in CURRENT.data['source_descriptions'].items()]
    for name,desc in entries:h+='<tr><td><a href="sources/'+name+'" download="'+name+'">'+esc(name)+'</a></td><td>'+esc(desc)+'</td></tr>'
    h+='</tbody></table></div><details><summary>Original cover and navigation wording</summary><div class="intro-note">'+''.join('<p>'+esc(t)+'</p>' for t in historical)+'<p>The original table of contents and page numbers remain in the included DOCX. HTML navigation links to the same 36 chapters.</p></div></details>'
    h+='<p class="endnote">Documented does not mean implemented. The complete direction supports future roadmap decisions; feature implementation, numerical balance, final model dimensions, and detailed artwork proceed in explicit slices. The source record chapter retains the original project provenance. The industrial expansion links its external inspiration references directly.</p></section>'
    return h

def create_assets():
    OUT.mkdir(exist_ok=True)
    for d in ('images','images/originals','images/diagrams','sources'):(OUT/d).mkdir(exist_ok=True)
    asset_jobs=[]
    for ident,record in ART.items():
        path=Path(record['source']);path=path if path.is_absolute() else ROOT/path
        if not path.exists():raise RuntimeError('Missing image '+str(path))
        copy_file(path,OUT/originalpath(ident))
        asset_jobs.append({'source':str(path),'output':str(OUT/artpath(ident))})
    (ROOT/'web_image_jobs.json').write_text(json.dumps(asset_jobs))
    for rec in DIAGRAMS:copy_file(DIAGRAM_ROOT/rec['file'],OUT/'images/diagrams'/rec['file'])
    copy_file(DIAGRAM_META,OUT/'sources/overview-diagrams.json')
    for key,filename,label in SOURCES:copy_file(ROOT/filename,OUT/'sources'/filename)
    for filename in ('industry_progression_register.json','artwork_backlog.json','overview_prompts.json','overview_generated_assets.json','tier1_reference_findings.txt','SUBSTELLAR_Creative_and_Product_Direction_v0.1.docx','first_playable_register.json','tier1_model_register.json','next_image_slice.json','project_inspection.json','edition_02_change_record.json','overview-edition-01-manifest.json','current_visual_coverage.json'):
        copy_file(ROOT/filename,OUT/'sources'/filename)
    refs=json.loads(json.dumps(REFS))
    refs['inspection_method']='All 15 supplied PNGs were visually inspected. Original files are preserved; web display copies retain dimensions and transparency.'
    for r in refs['records']:r['file_path']='../'+originalpath(r['id'])
    (OUT/'sources/reference_catalogue.json').write_text(json.dumps(refs,ensure_ascii=False,indent=2))
    reg=json.loads((OUT/'sources/industry_progression_register.json').read_text());reg['source_document']='industry_progression_expansion.txt'
    (OUT/'sources/industry_progression_register.json').write_text(json.dumps(reg,ensure_ascii=False,indent=2))
    complete='SUBSTELLAR — Creative and Product Direction\nCurrent direction and worked journeys 05\n\nAll 90,735 earlier source words remain preserved in their seven original sources. Four new sources supply the current-direction synthesis, worked progression, occupied-place visual review, and complete first lunar expedition. The source map links the current topics to all 45 baseline chapters. Confirmed supplied Tier 1 artwork governs appearance. Numerical examples, tuning and final model dimensions remain proposals where stated.\n\n'
    for key,filename,label in SOURCES:complete+='\n\n'+('='*60)+'\n'+label.upper()+'\n'+('='*60)+'\n\n'+(ROOT/filename).read_text()
    (OUT/'sources/complete-direction.txt').write_text(complete)
    manifest={'title':'SUBSTELLAR Creative and Product Direction','edition':'Illustrated overview 03','date':'2026-10-06','date_timezone':'America/Edmonton','previous_edition':'Planning edition 02','prior_source_words_preserved':88193,'planning_slice_02':{'new_images_generated':0,'new_generation_calls':0,'new_art_briefs':2,'mod_development_authorized':False,'implementation_status':'Unverified: actual mod project not available in the inspected workspace.'},'original_text_approved':True,'new_planning_status':'Expansion for review','source_content':[],'references':15,'diagrams':7,'new_overview_boards':5,'new_generation_call_cap':6,'new_generation_calls_used':6,'overview_edition_01_generation_record':{'boards':5,'call_cap':6,'calls_used':6,'historical':True},'image_records':[],'site':'index.html','notes':['All original body paragraphs, headings, lists and table cells are retained in order.','Original cover, table of contents and page references remain in the included DOCX; cover wording is also visible in the HTML source record.','Confirmed artwork and original supplied PNG bytes are preserved.','Web display copies retain source dimensions and alpha.','Exact recipes, model dimensions and performance are not inferred from illustrative reference grids.','Legacy new_overview_boards and new_generation_call fields describe Overview edition 01; planning_slice_02 records zero new generation.']}
    for key,filename,label in SOURCES:
        b=SOURCE_BLOCKS[key];p=ROOT/filename
        manifest['source_content'].append({'key':key,'file':filename,'words':words(p.read_text()),'blocks':len(b),'chapters':sum(x[0]=='heading' and x[1]==1 for x in b),'tables':sum(x[0]=='table' for x in b),'sha256':hashlib.sha256(p.read_bytes()).hexdigest()})
    for ident,r in ART.items():
        p=OUT/originalpath(ident);manifest['image_records'].append({'id':ident,'title':r['name'],'status':r['kind'],'original':originalpath(ident),'web':artpath(ident),'sha256_original':hashlib.sha256(p.read_bytes()).hexdigest()})
    manifest['total_source_words']=sum(x['words'] for x in manifest['source_content'])
    manifest['total_chapters']=len(CHAPTERS)
    (OUT/'sources/edition-manifest.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2))
    (ROOT/'html_build_manifest.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2))
    return manifest

def build():
    global PLANNING_HTML,COMPANION,CURRENT
    COMPANION=IllustratedCompanion(ROOT,OUT,esc,inline,badge,anchor,SEARCH,ART,DIAGRAMS)
    CURRENT=CurrentDirectionEdition(ROOT,OUT,CHAPTERS,esc,inline,badge,anchor,SEARCH)
    manifest=create_assets()
    COMPANION.copy_extra_assets()
    visual_catalogue,illustrated_coverage=COMPANION.write_records()
    CURRENT.copy_assets_and_sources()
    visual_catalogue=json.loads(json.dumps(visual_catalogue))
    visual_catalogue['edition']='Current direction and worked journeys 05'
    visual_catalogue['records']+=CURRENT.catalogue_records()
    visual_catalogue['unique_raster_concepts_and_references']+=3
    visual_catalogue['editable_figures']+=6
    COMPANION._catalogue=visual_catalogue['records']
    (OUT/'sources/visual_catalogue_05.json').write_text(json.dumps(visual_catalogue,ensure_ascii=False,indent=2))
    manifest['illustrated_overview_03']={'document_slices':illustrated_coverage['slices'],'new_concept_board_cap':8,'new_concept_boards':len(COMPANION.boards),'new_editable_figures':len(COMPANION.diagrams)+len(COMPANION.interfaces),'interface_views':12,'prior_source_words_preserved':88193,'scope':'Balanced illustrated document website'}
    generation_record=json.loads((ROOT/'illustrated_03_boards.json').read_text())['summary']
    manifest['illustrated_overview_03']['generation_calls']=generation_record['generation_calls']
    manifest['illustrated_overview_03']['rerolls']=generation_record['revisions']
    manifest['new_overview_boards']=len(COMPANION.boards)
    manifest['new_generation_call_cap']=8
    manifest['new_generation_calls_used']=generation_record['generation_calls']
    manifest['notes'][-1]='Current new_overview_boards and new_generation_call fields describe Illustrated overview 03. The original generation counts are preserved in overview_edition_01_generation_record and both earlier edition manifests.'
    manifest['diagrams']=len(DIAGRAMS)+len(COMPANION.diagrams)+len(COMPANION.interfaces)
    manifest['raster_visuals']=len(ART)
    manifest['current_visual_coverage']={'file':'current_visual_coverage.json','interface_layouts_now_included':12,'opening_boards_now_included':2,'original_register_preserved':True}
    manifest['reader_navigation_revision']={'revision':'04','date':'2026-10-07','features':['Fixed current-reading bar','Chapter and subsection tracking','Contents follow toggle and current-location reveal','Section selector and previous/next navigation','Copy current section link','Within-chapter progress'],'new_image_generation':0,'source_text_unchanged':True}
    CURRENT.add_manifest(manifest)
    for target in (OUT/'sources/edition-manifest.json',ROOT/'html_build_manifest.json'):target.write_text(json.dumps(manifest,ensure_ascii=False,indent=2))
    PLANNING_HTML,planning_data=render_planning(ROOT,esc,inline,badge,sectionhead,anchor,SEARCH)
    overview=render_overview()+render_progression()+render_references()
    backlog,backlogdata=render_backlog()
    body=overview+backlog+render_chapters()+render_sources()
    CURRENT.verify_rendered()
    css=(ROOT/'substellar_style.css').read_text();js=(ROOT/'substellar_app.js').read_text()+'\n'+(ROOT/'substellar_reader_navigation.js').read_text()
    searchdata=json.dumps(SEARCH,ensure_ascii=False,separators=(',',':')).replace('</','<\\/')
    backlogjson=json.dumps(backlogdata,ensure_ascii=False,separators=(',',':')).replace('</','<\\/')
    planningjson=json.dumps(planning_data,ensure_ascii=False,separators=(',',':')).replace('</','<\\/')
    visualjson=json.dumps(visual_catalogue,ensure_ascii=False,separators=(',',':')).replace('</','<\\/')
    doc='''<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="The complete SUBSTELLAR creative and product direction, with worlds, progression, machinery, and confirmed Tier 1 space systems."><title>SUBSTELLAR — Creative &amp; Product Direction</title><style>'''+css+'''</style></head><body data-theme="dark"><a class="skip" href="#complete-text">Skip to the complete text</a><header class="topbar"><button class="menu-toggle" id="menu-toggle" type="button" aria-expanded="false" aria-controls="sidebar">Contents</button><a class="brand" href="#overview"><span class="brand-mark" aria-hidden="true"></span>SUBSTELLAR</a><span class="top-label">Creative &amp; product direction</span><div class="top-tools"><button type="button" data-open-search>Search</button><button type="button" id="export-notes" class="hide-mobile">Export notes</button><button type="button" id="theme-toggle">Light</button><button type="button" id="print-button" class="hide-mobile">Print</button></div></header><div class="progress-track" aria-hidden="true"><span id="read-progress"></span></div>'''+render_reader_navigation()+render_sidebar()+'''<main>'''+body+'''<footer class="footer"><span>SUBSTELLAR · Every world goes deeper.</span><span>Current edition 05 · Reader navigation update 04 retained</span><a href="#overview">Back to the beginning ↑</a></footer></main><div class="search-overlay" id="search-overlay" hidden><section class="search-dialog" role="dialog" aria-modal="true" aria-label="Search the complete direction"><div class="search-head"><label class="sr-only" for="search-input">Search the full document</label><input id="search-input" type="search" placeholder="Search a place, system, material, or ID…" autocomplete="off"><button type="button" id="close-search">Close · Esc</button></div><div class="search-info" id="search-info" aria-live="polite"></div><ul class="search-results" id="search-results"></ul></section></div><div class="image-overlay" id="image-overlay" hidden><section class="image-dialog" role="dialog" aria-modal="true" aria-label="Enlarged concept image"><div class="image-toolbar"><p id="image-title"></p><button id="close-image" type="button">Close · Esc</button></div><img id="modal-image" alt=""><a id="image-original" href="#" target="_blank">Open full image</a></section></div><div class="toast" id="toast" role="status" hidden></div><script type="application/json" id="search-data">'''+searchdata+'''</script><script type="application/json" id="backlog-data">'''+backlogjson+'''</script><script type="application/json" id="planning-data">'''+planningjson+'''</script><script type="application/json" id="visual-data">'''+visualjson+'''</script><script>'''+js+'''</script></body></html>'''
    (OUT/'index.html').write_text(doc)
    # Rebuild material is ordinary local source; no repository or hosted service is required.
    for f in ('build_substellar_html.py','parse_substellar_blocks.py','substellar_style.css','substellar_app.js','substellar_reader_navigation.js','test_reader_navigation.js','planning_hub.py','illustrated_companion.py','edition05_companion.py'):
        if (ROOT/f).exists():copy_file(ROOT/f,OUT/'sources'/f)
    (OUT/'README.txt').write_text((ROOT/'README_05.txt').read_text())
    copy_file(ROOT/'README_05.txt',OUT/'sources/README_05.txt')
    print(json.dumps({'html':str(OUT/'index.html'),'characters':len(doc),'search_passages':len(SEARCH),'chapters':len(CHAPTERS),'words':manifest['total_source_words'],'backlog_tasks':len(backlogdata['records']),'assets':manifest['raster_visuals'],'distinct_visuals':manifest['total_distinct_visuals']},indent=2))

if __name__=='__main__':build()
