Buckets:
| #!/usr/bin/env python3 | |
| """Wrap a rendered posterly poster into a self-contained `poster_embed.html`. | |
| The public posterly repo (github.com/Chenruishuo/posterly) ships no logbook-embed | |
| generator, so this wrapper is ours, not posterly's. What *is* posterly's is the | |
| poster itself: `poster.html` is scaffolded from `templates/landscape_4col_neutral.html` | |
| and passes `run_gates.py --strict-polish` with zero warnings. | |
| What this does: | |
| 1. opens `poster.html` in the same print-emulated Chromium viewport the | |
| `measure` gate uses, so hotspot geometry matches the rendered pixels; | |
| 2. reads the bounding box of every element carrying `data-logbook-target`; | |
| 3. validates each target slug against `.trackio/logbook/logbook.json` and | |
| REFUSES to emit an embed referencing a page that does not exist; | |
| 4. embeds the poster PNG as a data URI (the logbook Space is static and a | |
| strict CSP blocks external hosts, so nothing may be fetched at view time); | |
| 5. emits percentage-positioned hotspots, so the overlay tracks the image at | |
| any width. | |
| Usage: python make_embed.py [--png poster_preview.png] [--out poster_embed.html] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import base64 | |
| import json | |
| import pathlib | |
| import re | |
| import sys | |
| from playwright.sync_api import sync_playwright | |
| HERE = pathlib.Path(__file__).resolve().parent | |
| def _find_logbook_json() -> pathlib.Path: | |
| """Walk up for .trackio/logbook/logbook.json, like trackio itself does.""" | |
| for d in (HERE, *HERE.parents): | |
| cand = d / ".trackio" / "logbook" / "logbook.json" | |
| if cand.is_file(): | |
| return cand | |
| raise SystemExit("no .trackio/logbook/logbook.json found above " + str(HERE)) | |
| def label_for(slug: str) -> str: | |
| """Human label for a hotspot pill, derived from the page slug.""" | |
| if slug == "executive-summary": | |
| return "Executive summary" | |
| if slug == "conclusion": | |
| return "Conclusion" | |
| m = re.match(r"claim-(\d+)-(.*)", slug) | |
| if m: | |
| words = m.group(2).replace("-", " ").split() | |
| return f"Claim {m.group(1)} - {' '.join(words[:3])}" | |
| return slug.replace("-", " ")[:40] | |
| def known_slugs() -> set[str]: | |
| data = json.loads(_find_logbook_json().read_text(encoding="utf-8")) | |
| out: set[str] = set() | |
| def walk(node: dict) -> None: | |
| out.add(node["slug"]) | |
| for child in node.get("children", []): | |
| walk(child) | |
| walk(data["root"]) | |
| return out | |
| def main() -> int: | |
| ap = argparse.ArgumentParser(description=__doc__) | |
| ap.add_argument("--html", default="poster.html") | |
| ap.add_argument("--title", default="Reproduction poster", help="img alt text") | |
| ap.add_argument("--png", default="poster_preview.png") | |
| ap.add_argument("--out", default="poster_embed.html") | |
| args = ap.parse_args() | |
| slugs = known_slugs() | |
| print(f"logbook pages known: {len(slugs)}") | |
| with sync_playwright() as pw: | |
| browser = pw.chromium.launch() | |
| page = browser.new_page(viewport={"width": 5760, "height": 3456}) | |
| page.emulate_media(media="print") | |
| page.goto((HERE / args.html).as_uri()) | |
| page.wait_for_load_state("networkidle") | |
| poster = page.evaluate( | |
| """() => { | |
| const root = document.querySelector('[data-measure-role="poster"]'); | |
| const pr = root.getBoundingClientRect(); | |
| const targets = [...document.querySelectorAll('[data-logbook-target]')] | |
| .map(el => { | |
| const r = el.getBoundingClientRect(); | |
| return { | |
| slug: el.getAttribute('data-logbook-target'), | |
| left: (r.left - pr.left) / pr.width * 100, | |
| top: (r.top - pr.top) / pr.height * 100, | |
| width: r.width / pr.width * 100, | |
| height: r.height / pr.height * 100, | |
| }; | |
| }); | |
| return {w: pr.width, h: pr.height, targets}; | |
| }""" | |
| ) | |
| browser.close() | |
| targets = poster["targets"] | |
| if not targets: | |
| sys.exit("no [data-logbook-target] elements found in the poster") | |
| unknown = sorted({t["slug"] for t in targets} - slugs) | |
| if unknown: | |
| sys.exit( | |
| "refusing to emit: poster references unknown logbook page(s): " | |
| + ", ".join(unknown) | |
| ) | |
| png = (HERE / args.png).read_bytes() | |
| uri = "data:image/png;base64," + base64.b64encode(png).decode("ascii") | |
| print(f"poster {poster['w']:.0f}x{poster['h']:.0f} px, " | |
| f"{len(targets)} hotspots, PNG {len(png) / 1024:.0f} KB") | |
| spots = "\n".join( | |
| f' <a class="ps-hotspot" href="#/{t["slug"]}" data-slug="{t["slug"]}"\n' | |
| f' style="left:{t["left"]:.3f}%;top:{t["top"]:.3f}%;' | |
| f'width:{t["width"]:.3f}%;height:{t["height"]:.3f}%"\n' | |
| f' title="Open {label_for(t["slug"])}">' | |
| f'<span class="ps-pill">{label_for(t["slug"])} ↗</span></a>' | |
| for t in targets | |
| ) | |
| html = f"""<!-- poster_embed.html - generated by repro/poster/make_embed.py from | |
| posterly's rendered poster.html (landscape_4col_neutral, 60x36in). | |
| Self-contained: the poster is a data URI, no external requests. --> | |
| <div class="posterly-embed"> | |
| <figure class="ps-frame"> | |
| <img src="{uri}" alt="{args.title}"> | |
| {spots} | |
| </figure> | |
| <p class="ps-note">Poster built with <a href="https://github.com/Chenruishuo/posterly">Chenruishuo/posterly</a> | |
| (<code>landscape_4col_neutral</code>, 60×36in) — <code>run_gates.py --strict-polish</code>: | |
| preflight, style, measure and polish all PASS, 0 warnings. Shaded regions link to the claim page behind them.</p> | |
| </div> | |
| <style> | |
| .posterly-embed {{ max-width: 1400px; margin: 0 auto; }} | |
| .posterly-embed .ps-frame {{ position: relative; margin: 0; line-height: 0; }} | |
| .posterly-embed img {{ width: 100%; height: auto; display: block; | |
| border-radius: 6px; box-shadow: 0 2px 18px rgba(0,0,0,.18); }} | |
| .posterly-embed .ps-hotspot {{ position: absolute; display: block; | |
| border-radius: 6px; text-decoration: none; outline: none; | |
| border: 2px solid transparent; transition: background .15s, border-color .15s; }} | |
| .posterly-embed .ps-hotspot:hover, | |
| .posterly-embed .ps-hotspot:focus-visible {{ | |
| background: rgba(45,95,139,.14); border-color: #2D5F8B; }} | |
| .posterly-embed .ps-pill {{ position: absolute; right: 6px; bottom: 6px; | |
| font: 600 11px/1 ui-sans-serif, system-ui, sans-serif; white-space: nowrap; | |
| padding: 4px 9px; border-radius: 999px; background: #2D5F8B; color: #fff; | |
| opacity: .55; transition: opacity .15s; }} | |
| .posterly-embed .ps-hotspot:hover .ps-pill, | |
| .posterly-embed .ps-hotspot:focus-visible .ps-pill {{ opacity: 1; }} | |
| .posterly-embed .ps-note {{ margin: 10px 2px 0; line-height: 1.5; | |
| font: 12px/1.5 ui-sans-serif, system-ui, sans-serif; color: #666; }} | |
| .posterly-embed .ps-note a {{ color: #2D5F8B; }} | |
| @media (prefers-color-scheme: dark) {{ | |
| .posterly-embed .ps-note {{ color: #aaa; }} | |
| .posterly-embed .ps-note a {{ color: #8FB8DA; }} | |
| }} | |
| </style> | |
| <script> | |
| // The figure may be rendered inside an iframe; route navigation to the | |
| // logbook shell so a click lands on the page rather than inside the frame. | |
| document.querySelectorAll('.posterly-embed .ps-hotspot').forEach(function (a) {{ | |
| a.addEventListener('click', function (ev) {{ | |
| var target = window.top || window; | |
| try {{ target.location.hash = '#/' + a.dataset.slug; ev.preventDefault(); }} | |
| catch (e) {{ /* cross-origin: fall through to the plain href */ }} | |
| }}); | |
| }}); | |
| </script> | |
| """ | |
| out = HERE / args.out | |
| out.write_text(html, encoding="utf-8") | |
| print(f"wrote {out} ({len(html) / 1024:.0f} KB)") | |
| for t in targets: | |
| print(f" hotspot -> {t['slug'][:64]}") | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |
Xet Storage Details
- Size:
- 8.1 kB
- Xet hash:
- 20f058470e3e4d30e0a7acc0110fdb12d2a18b23ce6f17ece84c589aa7476caa
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.