Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
7.2 KiB
Python
89 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
||
"""Build an offline comparison index and catalog from the current export manifest."""
|
||
from html import escape
|
||
import json
|
||
from pathlib import Path
|
||
|
||
PROJECT = Path(__file__).resolve().parents[1]
|
||
OUT = PROJECT / 'docs/screenshots/phase4'
|
||
FACTIONS = {'glutpakt': 'Glutpakt', 'nietenkonvent': 'Nietenkonvent',
|
||
'nebelchor': 'Nebelchor', 'wurzelbund': 'Wurzelbund', 'neutral': 'Söldner & Arbeiter'}
|
||
ROLES = {'melee': 'Nahkampf', 'tank': 'Tank', 'ranged': 'Fernkampf',
|
||
'artillery': 'Artillerie', 'support': 'Unterstützung', 'mercenary': 'Söldner',
|
||
'worker': 'Arbeiter', 'lane': 'Lane-Segment', 'enemy': 'Wellengegner', 'core': 'Saumkern'}
|
||
|
||
|
||
def main():
|
||
assets = json.loads((PROJECT / 'assets/phase4_manifest.json').read_text())['assets']
|
||
cards = []
|
||
for faction, label in FACTIONS.items():
|
||
selected = [a for a in assets if a['faction'] == faction]
|
||
for start in range(0, len(selected), 2):
|
||
pair = selected[start:start + 2]
|
||
filename = 'reference-' + pair[0]['id'] + '.png'
|
||
assert (OUT / filename).is_file(), filename
|
||
title = ' / '.join(a['id'].upper() + ' · ' + a['name'] for a in pair)
|
||
downloads = ' · '.join(
|
||
f'{a["id"].upper()}: <a href="../../../assets/models/{a["file"]}" download>GLB</a> / '
|
||
f'<a href="../../../assets-source/phase4/blend/{a["id"]}.blend" download>Blender</a>' for a in pair)
|
||
cards.append(f'<article data-faction="{faction}"><header><span>{escape(label)}</span>'
|
||
f'<h2>{escape(title)}</h2></header><a href="{filename}" aria-label="{escape(title)} – Bild in voller Größe">'
|
||
f'<img src="{filename}" width="1600" height="960" loading="lazy" '
|
||
f'alt="{escape(title)}: jeweils Originalicon links und aktuelles 3D-Modell rechts"></a>'
|
||
f'<footer>{downloads}</footer></article>')
|
||
buttons = '<button type="button" data-filter="all" aria-pressed="true">Alle 46 Figuren</button>'
|
||
buttons += ''.join(f'<button type="button" data-filter="{key}" aria-pressed="false">{escape(value)}</button>'
|
||
for key, value in FACTIONS.items())
|
||
html = '''<!doctype html><html lang="de"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Saumwacht · Bild und Modell</title><style>
|
||
:root{color-scheme:dark;font-family:system-ui,sans-serif;background:#10141b;color:#eee6d8}
|
||
*{box-sizing:border-box}body{margin:0}main{max-width:1440px;margin:auto;padding:48px 24px}
|
||
.eyebrow,article header span{font-size:.8rem;letter-spacing:.12em;color:#d7c392;text-transform:uppercase}
|
||
h1{font-size:clamp(2rem,5vw,3.7rem);line-height:1.05;margin:18px 0}p{color:#b6c0cf;max-width:850px;line-height:1.65}
|
||
nav{display:flex;gap:8px;flex-wrap:wrap;margin:28px 0 16px}button{font:inherit;cursor:pointer;border:1px solid #455063;
|
||
background:#1b222d;color:#dbe1eb;border-radius:6px;padding:11px 15px}button[aria-pressed="true"]{background:#ddc898;color:#171b23;border-color:#ddc898}
|
||
button:hover{border-color:#ddc898}button:focus-visible,a:focus-visible{outline:3px solid #82dbce;outline-offset:4px}
|
||
#count{font-size:.85rem;color:#9eacc0;margin-bottom:30px}article{margin:0 0 36px;border:1px solid #303a49;border-radius:10px;overflow:hidden;background:#151c26}
|
||
article header{padding:20px 24px}h2{font-size:1.1rem;font-weight:550;margin:7px 0 0}img{display:block;width:100%;height:auto}
|
||
article footer{padding:15px 24px;font-size:.85rem;color:#acb9cd}a{color:#ddc898;text-underline-offset:3px}
|
||
[hidden]{display:none!important}.foot{border-top:1px solid #303a49;padding-top:22px}
|
||
@media(max-width:600px){main{padding:28px 12px}article header,article footer{padding:14px}button{padding:10px;font-size:.85rem}}
|
||
</style><main><div class="eyebrow">Saumwacht / Grafiküberarbeitung / 16. September 2026</div>
|
||
<h1>Bild und Modell.<br>Direkt nebeneinander.</h1>
|
||
<p>Die zwanzig Glutpakt- und Wurzelbund-Figuren wurden mit hochdetaillierter Geometrie neu aufgebaut; Nietenkonvent und Nebelchor behalten ihren bisherigen Detailgrad. Jeder Vergleich zeigt die unveränderte Illustration neben dem aktuellen Modell aus Godot. Klicke auf ein Bild, um es in voller Größe zu öffnen.</p>
|
||
<p>Die neuen Glutpakt-Modelle besitzen modellierte Gelenke, Panzerplatten, Metallkanten, Finger und Feuerkammern. Die Illustrationen dienen als Stilvorlage; die Figuren sind eigenständige 3D-Umsetzungen. Der Wurzelbund ergänzt geflochtene Wurzeln, Borkenplatten, Blattadern, Pilzkelche und Heilbecken.</p>
|
||
<nav aria-label="Fraktion auswählen">''' + buttons + '''</nav><div id="count" role="status">23 Vergleichsbilder · 46 Figuren</div>
|
||
''' + '\n'.join(cards) + '''
|
||
<p class="foot">Die Vorschauen passen die Figuren auf eine gemeinsame Bühne ein; sie vergleichen keine Weltmaßstäbe. Lane, Teamkerne und die zehn Wellengegner wurden ebenfalls überarbeitet.
|
||
<a href="models-rift.png">Wellengegner ansehen</a> · <a href="models-environment.png">Umgebung ansehen</a> · <a href="models-workshop.png">3D-Werkstatt ansehen</a></p></main>
|
||
<script>
|
||
const cards=[...document.querySelectorAll('article')];
|
||
document.querySelectorAll('button[data-filter]').forEach(button=>button.addEventListener('click',()=>{
|
||
document.querySelectorAll('button[data-filter]').forEach(b=>b.setAttribute('aria-pressed',String(b===button)));
|
||
cards.forEach(card=>card.hidden=button.dataset.filter!=='all'&&card.dataset.faction!==button.dataset.filter);
|
||
const count=cards.filter(card=>!card.hidden).length;
|
||
document.getElementById('count').textContent=`${count} Vergleichsbilder · ${count*2} Figuren`;
|
||
}));
|
||
</script></html>'''
|
||
(OUT / 'reference-gallery.html').write_text(html, encoding='utf-8')
|
||
|
||
catalog = ['# Saumwacht · Modellkatalog', '',
|
||
'Stand: 17. September 2026. Zwanzig Glutpakt- und Wurzelbund-Figuren, zehn Wellengegner, Arbeiter und Kristalle wurden neu aufgebaut; Söldner und Lane erhielten zusätzliche Details. Die 59 Einträge stammen aus `assets/phase4_manifest.json`.', '',
|
||
'Maße in Metern: **B = X, H = Y, T = Z** in Godot, auf zwei Nachkommastellen gerundet. '
|
||
'GLBs sind über die IDs verlinkt; native Quellen liegen in `blend/<ID>.blend`. '
|
||
'[Anleitung](README.md) · [Bildvergleiche](../../docs/screenshots/phase4/reference-gallery.html)', '',
|
||
'| ID / GLB | Modell | Fraktion | Rolle | Upgrade | Dreiecke | B (m) | H (m) | T (m) |',
|
||
'| --- | --- | --- | --- | --- | ---: | ---: | ---: | ---: |']
|
||
for a in assets:
|
||
b = a['bounds_godot']
|
||
dims = [hi-lo for hi, lo in zip(b['max'], b['min'])]
|
||
faction = FACTIONS.get(a['faction'], 'Risskreaturen' if a['faction']=='rift' else 'Umgebung')
|
||
upgrade = 'Ja' if a.get('upgraded', a.get('upgrade', False)) else 'Nein'
|
||
catalog.append(f'| [{a["id"]}](../../assets/models/{a["file"]}) | {a["name"]} | {faction} | '
|
||
f'{ROLES[a["role"]]} | {upgrade} | {a["triangles"]} | ' + ' | '.join(f'{d:.2f}' for d in dims) + ' |')
|
||
(PROJECT / 'assets-source/phase4/catalog.md').write_text('\n'.join(catalog)+'\n', encoding='utf-8')
|
||
print('REFERENCE_GALLERY_PASS: 23 comparison sheets, 46 characters; catalog 59 models')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|