Art page round 2: virtual frames, real logo, hi-res zoom, spacing/affordance polish

- Virtual frames (Walnut/Gold/Silver/None), selectable + remembered in localStorage,
  built as a beveled moulding around a cream museum mat.
- Header uses the real /logo.svg wordmark; the "No ads" pill is replaced by an
  account icon (the pill doesn't need to follow every page).
- Lightbox now opens a full-resolution copy that fills the screen: art._download_image
  caches a hi-res {id}-full copy alongside the web-large display copy, served via
  /api/art/image/{id}?size=full (image_url_large in /api/art/today).
- Centered the placard bullet separators (explicit .sep spans, equal margins).
- Image no longer shifts on hover; a quiet "Click to expand" affordance sits on the art.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-21 16:25:31 -04:00
parent 9bfec573e2
commit 27788ba2a8
4 changed files with 198 additions and 58 deletions
+6 -2
View File
@@ -2276,11 +2276,15 @@ def create_app() -> FastAPI:
"is_public_domain": bool(a["is_public_domain"]),
"license": "Public Domain (CC0)" if a["is_public_domain"] else None,
"image_url": f"/api/art/image/{a['object_id']}",
"image_url_large": f"/api/art/image/{a['object_id']}?size=full",
}
@app.get("/api/art/image/{object_id}")
def art_image(object_id: int) -> FileResponse:
matches = sorted(art.cache_dir().glob(f"{object_id}.*"))
def art_image(object_id: int, size: str = Query("")) -> FileResponse:
cdir = art.cache_dir()
matches = sorted(cdir.glob(f"{object_id}-full.*")) if size == "full" else []
if not matches: # fall back to the web-large copy
matches = sorted(cdir.glob(f"{object_id}.*"))
if not matches:
raise HTTPException(status_code=404, detail="Not cached.")
# Cached museum image: immutable for a given object id.