diff --git a/src/components/Viewfinder.tsx b/src/components/Viewfinder.tsx
index 9d5c944..25f3fad 100644
--- a/src/components/Viewfinder.tsx
+++ b/src/components/Viewfinder.tsx
@@ -26,7 +26,7 @@ import { Recipe, GPSInfo, FrameId } from '../types';
import { getSkiaColorMatrix, applyExposureGain } from '../utils/colorUtils';
import { TONE_SKSL, getToneUniforms } from '../utils/toneShader';
import { CINEMA_SKSL, getCinemaUniforms, cinemaOffFlat } from '../utils/cinemaShader';
-import { polaroidLayout } from '../utils/frameUtils';
+import { polaroidLayout, wallframeLayout } from '../utils/frameUtils';
interface ViewfinderProps {
mode: 'camera' | 'library';
@@ -102,6 +102,9 @@ export default function Viewfinder({
const device = useCameraDevice('back');
const skiaImage = useImage(libraryImageUri || '');
+ // Wall-frame PNG artwork (project root). Loaded lazily so other frames pay
+ // no asset cost; source switches to '' when unselected (useImage → null).
+ const wallImage = useImage(selectedFrame === 'wallframe' ? require('../../wallframe.png') : '');
const customFont = useFont(
require('../../assets/Cousine-Regular.ttf'),
Math.round(vw * 0.032)
@@ -409,6 +412,13 @@ export default function Viewfinder({
: 3 / 4;
return polaroidLayout(vw, vh, aspect, 'contain');
}, [selectedFrame, mode, skiaImage, vw, vh]);
+ // Wall-frame PNG geometry (JS/overlay space). Fixed landscape 3:2 window —
+ // the PNG is contain-fitted like a physical frame; photo content behind it
+ // is cover-cropped by the mat opening (never letterboxed inside the window).
+ const wallGeo = useMemo(() => {
+ if (selectedFrame !== 'wallframe') return null;
+ return wallframeLayout(vw, vh, 'contain');
+ }, [selectedFrame, vw, vh]);
const onLibTouchStart = (e: any) => {
const ts = e.nativeEvent.touches;
if (ts.length >= 2) {
@@ -543,6 +553,25 @@ export default function Viewfinder({
);
}
+ if (selectedFrame === 'wallframe') {
+ const geo = wallGeo;
+ if (!geo || !wallImage) return null; // PNG still loading → bare feed
+ const { frame } = geo;
+ // Letterbox the live feed black outside the PNG, then draw the artwork:
+ // its transparent window lets the fullscreen feed show through (the mat
+ // opening = center crop of the feed, WYSIWYG with the export crop).
+ const black = '#000000';
+ return (
+
+
+
+
+
+
+
+ );
+ }
+
if (selectedFrame === 'classic-white') {
return (
@@ -582,7 +611,7 @@ export default function Viewfinder({
// photo corner. With a polaroid frame the photo lives inside the card
// window — anchor to that. Always clear the floating TopBar (recipe name +
// SAVE), which ends around insets.top + 56 dp.
- const win = polaroidGeo?.window ?? null;
+ const win = polaroidGeo?.window ?? wallGeo?.window ?? null;
const area = win
? { dx: win.x, dy: win.y, dw: win.w, dh: win.h }
: mode === 'library' && imageFitRect
@@ -612,18 +641,20 @@ export default function Viewfinder({
);
};
- const renderCameraOverlays = () => (
+ const renderCameraOverlays = () => {
+ const framedWin = polaroidGeo?.window ?? wallGeo?.window ?? null;
+ return (
<>