19-Apr-2026 — focal-point picker, save flow, screenshot fidelity #243
build 617 — commit 1a05985
Photo screenshot fidelity (originally #243):
The banner editor's saved/exported screenshot stretched the photo bitmap
because html2canvas (via ngx-capture) does not honour object-fit: cover
on <img> elements, while the live preview does (a global
img { object-fit: cover } rule applies). The captured PNG embedded in
booking confirmation emails therefore showed the photo distorted, even
though the editable preview looked correct.
Render the photo as a <div> with background-image and
background-size: cover instead. html2canvas handles CSS background
images correctly, so the captured screenshot now matches the live
preview pixel-for-pixel. The cropper transform (scale + translate) and
right/bottom anchoring are preserved.
Also moves the (load)="setPaperCutImageHeight()" trigger onto the
background image (the actual source of the height measurement) and
drops a stray empty <img/> left over from the original markup.
Banner editor:
- Photo focal-point picker driven by FocalPoint (x/y/zoom) on Image, with the picker's resizable handle controlling editableBanner.bannerHeight (200-900px). Replaces the fixed 42.1% padding-bottom with explicit height.px when set.
- Photo URL uses imageSource(..., true) to skip /admin/ prefixing.
- New Edit Saved Image button (with disabled "Not Saved Yet" tooltip when no fileNameData) that opens app-image-cropper-and-resizer preloaded with the saved banner PNG. On save, fileNameData.awsFileName is updated and the banner is re-saved.
- Banner select uses [compareWith] so it stays bound to the canonical record after a save swaps the response banner back into the list by id.
- URL sync via StoredValue.BANNER ("banner") query param using kebab-case slug; reads on init, writes on bannerSelected via router.navigate({...}, merge).
- Page title shows "Banners
- {ordinal} of {total}".
- Saved image summary ("Saved image: W x H px, NN kb.") on its own row beneath the action buttons, populated via a hidden img + fetch(blob.size) whether or not the saved preview is toggled. Resets on banner switch and on save.
- Save flow now sets notify busy=true so the alert shows a spinning faSpinner during the upload, replacing the static success icon until notify.hide().
- Cache-busting via savedImageVersion timestamp, and the saved view auto- toggles after a save.
- normaliseBanner strips any rootFolder/ prefix doubled into awsFileName so legacy persisted state still resolves; savedImagedSaved strips the prefix on the way back from the cropper to prevent the doubling at source. The prefix stripper is implemented as a small recursive helper to keep no-restricted-syntax (no imperative loops) happy.
- HostListener onResize signature trimmed to drop the unused $event param.
Image cropper:
- Default cropper position no longer forced from natural-pixel coordinates; when no saved cropperPosition is supplied the cropper component handles its own full-bounds default in display pixels (avoids the giant rectangle when natural >> display).
- 12px padding on the cropper host via ::ng-deep so the corner/edge resize handles render fully inside the host's overflow:hidden region instead of being clipped at the right and bottom edges.