Driver decisions (cards BUILD-OUTPUT-IS-ONE-FOLDER, BUILD-PLUGIN-STUDIO-IS-THE-OUTPUT-FRONT-PAGE).
- public/ holds only <tree>/ and a generated README.md. The build no longer writes public/index.html,
public/assets/ or public/__gallery/; `rm -rf public && bun run build` recreates exactly these two.
- The Plugin Studio is <tree>/index.html (tree-base "."), replacing the staging-forge chooser; nothing
is written above the tree. check:staging-lang, which tested that chooser, is retired with it.
- Assets are gathered in a temp directory and the fetched allowlist staged into <tree>/assets/ as
before. fonts/, images/, brand/ and legacy/ existed ONLY in public/assets: they are sources and move
to data/ (git mv). Poster previews keep their tracked sources in plugin-designs/*/assets/previews
(all 378 verified present).
- __gallery/ is archived out of public/ (a copy kept in the umbrella's _qa-cycle/archive) and out of
the build; generate-gallery renders only on request, into a directory you name, never public/.
- Native shells load the output folder: capacitor webDir public/<tree> (cap copy needs an index.html
there), Tauri frontendDist + window url relative to it. internal/sync-app-shells.mjs keeps both in
step with the tree's name on every build, so the coming rename stays one config line; the Capacitor
start page resolves its leaf tree-relative.
- dropin.include now lists only extras (README.md): the tree is always shipped and never named twice;
excludes are tree-relative ({tree}/...). clean.mjs removes the old root entries from existing
checkouts; the build lock and public/.gitignore's rules move out of public/.
- check:wp-embed read the BUILT manifest from gates:pre, before the build, so a from-scratch build
could never pass; it runs in gates:post and reads the tree name from config.
- Stale bundles refreshed by the clean build (key-cities, template-bundle, wall-of-people-groups);
the CLI doc's quoted scripts re-synced; capability exemplars regenerated; the '#' note leaves
check:stale-sweep-claim's command (it was already in //scripts).
Proof: rm -rf public && bun run build -> public/ = README.md + doxa-maps/; check:sri, check:drop,
check:parse, check:public-hygiene, check:quoted-scripts, check:capability-exemplars PASS; all 57 pages
(the front page + 56 leaves) mount under /doxa-maps/ on this tree. check:facet-cascade is red as on main.
66 lines
3 KiB
Python
66 lines
3 KiB
Python
#!/usr/bin/env python3
|
|
"""make-previews.py — turn the rendered 3000x3000 posters into the TWO WebP sets
|
|
the app ships.
|
|
|
|
python3 make-previews.py
|
|
-> plugin-designs/my-upg-100-list/assets/previews/<zone>.webp 560px card thumbnails
|
|
-> data/posters/<zone>.webp 1500px the download
|
|
|
|
WHY TWO SIZES
|
|
The wall card shows a small thumbnail; tapping "Poster Map" must hand the user
|
|
THE SAME POSTER at a usable size. The button used to re-render a live Mapbox
|
|
canvas through useMapPoster.js — a completely different artefact from the preview
|
|
above it, carrying none of the template. Serving the pre-rendered image makes
|
|
preview and download identical BY CONSTRUCTION, and it works on Android, where a
|
|
Node generator cannot run at all.
|
|
|
|
SIZE BUDGET (measured): 1500px q80 WebP is ~217 KB per poster, 5.9 MB for all 28 —
|
|
small enough to ship in the APK, and 5in at 300dpi / 10in at 150dpi, a real print.
|
|
The 3000px PNG masters (51 MB) stay OUT of the app; export-public.mjs sends those
|
|
to a sibling folder for desktop/CDN use.
|
|
|
|
Uses PIL rather than sharp: sharp is not installed in this repo, PIL is present.
|
|
"""
|
|
import os, sys, glob
|
|
from PIL import Image
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
SRC = os.path.join(HERE, 'out', 'posters')
|
|
REPO = os.path.abspath(os.path.join(HERE, '..', '..'))
|
|
# WHICH APP the previews belong to. The country-posters app renders from the SAME
|
|
# generator into the SAME out/posters, so the only thing that differs is where the
|
|
# WebPs land — parameterize rather than fork this script.
|
|
# PROFILE=my-upg-100-list (default)
|
|
# PROFILE=country-posters python3 make-previews.py
|
|
# ONLY filters by filename prefix so a country run does not re-emit the 28 zone
|
|
# posters sitting in the same folder (and vice versa).
|
|
PROFILE = os.environ.get('PROFILE', 'my-upg-100-list')
|
|
ONLY = os.environ.get('ONLY_PREFIX', '')
|
|
THUMB = os.path.join(REPO, 'plugin-designs', PROFILE, 'assets', 'previews')
|
|
FULL = os.path.join(REPO, 'data', 'posters') # a SOURCE: the build stages it into <tree>/assets/posters on request
|
|
|
|
os.makedirs(THUMB, exist_ok=True)
|
|
os.makedirs(FULL, exist_ok=True)
|
|
|
|
pngs = sorted(glob.glob(os.path.join(SRC, (ONLY + '*.png') if ONLY else '*.png')))
|
|
if not pngs:
|
|
sys.exit(f'[make-previews] no PNGs in {SRC} — run `bun run posters` then `bun run posters:shoot` first.')
|
|
|
|
tkb = fkb = 0
|
|
for p in pngs:
|
|
zone = os.path.basename(p)[:-4]
|
|
im = Image.open(p).convert('RGB')
|
|
|
|
t = im.copy(); t.thumbnail((560, 560), Image.LANCZOS)
|
|
tp = os.path.join(THUMB, zone + '.webp')
|
|
t.save(tp, 'WEBP', quality=82, method=6)
|
|
tkb += os.path.getsize(tp) / 1024
|
|
|
|
f = im.copy(); f.thumbnail((1500, 1500), Image.LANCZOS)
|
|
fp = os.path.join(FULL, zone + '.webp')
|
|
f.save(fp, 'WEBP', quality=80, method=6)
|
|
fkb += os.path.getsize(fp) / 1024
|
|
|
|
print(f'[make-previews] {len(pngs)} posters')
|
|
print(f' thumbnails -> plugin-designs/.../assets/previews {tkb/1024:5.1f} MB (inlined in the bundle)')
|
|
print(f' full size -> data/posters {fkb/1024:5.1f} MB (served + shipped in the APK)')
|