diff --git a/src/bggpipe/export.py b/src/bggpipe/export.py index f8d4545..b5db5ce 100644 --- a/src/bggpipe/export.py +++ b/src/bggpipe/export.py @@ -186,7 +186,14 @@ a.game img { width:100%; aspect-ratio:1; object-fit:cover; display:block; a.game .noart { width:100%; aspect-ratio:1; display:flex; align-items:center; justify-content:center; font-size:3rem; font-weight:700; color:var(--navy); background:#e8ddc5; } -a.game .info { padding:.55rem .7rem .7rem; } +a.game .info, .colophon .info { padding:.55rem .7rem .7rem; } +.colophon { background:var(--board); border:1px solid var(--line); + border-radius:.8rem; overflow:hidden; display:flex; flex-direction:column; + box-shadow:0 2px 5px rgba(42,36,56,.15); } +.colophon > img { width:100%; aspect-ratio:1; object-fit:cover; display:block; + background:#cfe4f5; } +.colophon .meta { margin:.35rem 0 0; font-size:.72rem; line-height:1.45; } +.colophon a { color:var(--accent); } .gname { font-weight:700; } .meta { color:var(--ink-soft); font-size:.82rem; } main > .meta { color:var(--ink-soft-on-sky); } @@ -260,6 +267,35 @@ def _footer(title: str, rel: str, badge_present: bool) -> str: """ +_PIPER_NAME = "bggpipe-piper.jpg" +_PIPER_SRC = Path(__file__).parent / "static" / "logo-full.jpg" + + +def _colophon_card(title: str, badge_present: bool) -> str: + """The last card on the shelf: mascot, badge, and the fine print. A +
, not an — the search filter selects a.game, so the colophon + stays visible whatever is typed.""" + badge = ( + f'Powered by BGG' + if badge_present + else "Powered by BGG" + ) + return f"""
+ the bggpipe piper — a bagpiper whose bag is a board game box +
+
{html.escape(title)}
+

Cataloged from shelf photos by + bggpipe. + Game data and covers from + {badge}

+

BoardGameGeek and BGG are trademarks of BoardGameGeek, + LLC; this site is an independent project, not affiliated with or + endorsed by BoardGameGeek. Piper art by Juniper.

+
+
""" + + _FAVICON = ( "data:image/svg+xml,🎲" @@ -506,7 +542,7 @@ def run_export( f'

{len(games)} games, cataloged from shelf photos.

' '
' - f'
{cards}
' + f'
{cards}\n{_colophon_card(title, badge_present)}
' '' f"{_INDEX_JS}" ) @@ -524,8 +560,10 @@ def run_export( if stale_dir.is_dir(): shutil.rmtree(stale_dir) removed += 1 + if _PIPER_SRC.exists(): + atomic_write_bytes(out_dir / "art" / _PIPER_NAME, _PIPER_SRC.read_bytes()) wanted_art = {n for entry in art.values() for n in entry.values()} - wanted_art.add(BADGE_NAME) + wanted_art.update((BADGE_NAME, _PIPER_NAME)) for art_file in (out_dir / "art").iterdir(): if art_file.name not in wanted_art: art_file.unlink() diff --git a/tests/test_export.py b/tests/test_export.py index aa06c01..cb02473 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -103,7 +103,8 @@ def test_export_writes_pages_art_and_no_shelf_photos(tmp_path): assert "rpggeek.com/rpgitem/311654" in rpg # covers landed locally and pages never hotlink the CDN - assert len(list((out / "art").glob("*"))) == 4 # 2 full + 1 thumb + local art + # 2 full + 1 thumb + local art + the colophon's piper + assert len(list((out / "art").glob("*"))) == 5 all_html = index + detail + rpg assert "geekdo-images.com" not in all_html @@ -237,7 +238,7 @@ def test_garbage_200_is_a_failure_not_a_cached_cover(tmp_path): assert summary["downloaded"] == 0 # only the local-art copy survives; no CDN garbage was cached names = {f.name for f in (tmp_path / "site" / "art").iterdir()} - assert names == {"abc123.jpg"} + assert names == {"abc123.jpg", "bggpipe-piper.jpg"} def test_missing_local_art_is_a_counted_failure(tmp_path): @@ -330,3 +331,18 @@ def test_cli_export_wiring(tmp_path, monkeypatch): monkeypatch.setattr("bggpipe.export.run_export", failing_run_export) result = runner.invoke(app, ["export", "--out", str(tmp_path / "s")]) assert result.exit_code == 1 # scripted publishes must see failures + + +def test_colophon_card_closes_the_shelf(tmp_path): + """The last card: piper, badge, fine print — a div the search filter + (which selects a.game) can never hide.""" + cfg = _cfg(tmp_path) + out = tmp_path / "site" + run_export(cfg, out, title="Test Shelves", fetch=_cdn([]), sleep=lambda s: None) + index = (out / "index.html").read_text() + assert 'class="game colophon"' in index + assert "bggpipe-piper.jpg" in index and "Piper art by Juniper" in index + assert "trademarks of BoardGameGeek" in index + assert (out / "art" / "bggpipe-piper.jpg").exists() + # the filter only ever hides — the colophon is a div + assert '