/* eslint-disable @next/next/no-img-element -- frame image comes from the MinIO CDN; next/image is out of scope for the designer canvas. */ import { useRef } from 'react'; import { CARD_W, CARD_H, DEFAULT_LAYOUT } from './CardFrame'; const ZONE_COLORS = { art: { fill: 'rgba(110, 168, 220, 0.18)', border: '#6ea8dc' }, text: { fill: 'rgba(143, 188, 111, 0.18)', border: '#8fbc6f' }, }; const HANDLE = 14; /** * Interactive zone editor for image-based frames. Renders the uploaded * frame artwork at natural card size with draggable/resizable overlays * for the art and text windows. Zones are fractions of the card. * * Drag inside a zone to move it; drag the bottom-right handle to resize. */ export default function ZoneEditor({ frameImageUrl, layout, onChange }) { const dragState = useRef(null); const clampZone = (zone) => { const w = Math.min(Math.max(zone.w, 0.05), 1); const h = Math.min(Math.max(zone.h, 0.05), 1); return { w, h, x: Math.min(Math.max(zone.x, 0), 1 - w), y: Math.min(Math.max(zone.y, 0), 1 - h), }; }; const startDrag = (zoneKey, mode) => (event) => { event.preventDefault(); event.target.setPointerCapture?.(event.pointerId); dragState.current = { zoneKey, mode, startX: event.clientX, startY: event.clientY, origin: layout[zoneKey], }; }; const onPointerMove = (event) => { const state = dragState.current; if (!state) return; const dx = (event.clientX - state.startX) / CARD_W; const dy = (event.clientY - state.startY) / CARD_H; const o = state.origin; let next; if (state.mode === 'resize') { next = clampZone({ x: o.x, y: o.y, w: o.w + dx, h: o.h + dy }); } else { next = clampZone({ x: o.x + dx, y: o.y + dy, w: o.w, h: o.h }); } onChange({ ...layout, [state.zoneKey]: next }); }; const endDrag = () => { dragState.current = null; }; const renderZone = (zoneKey) => { const zone = layout[zoneKey] || DEFAULT_LAYOUT[zoneKey]; const colors = ZONE_COLORS[zoneKey]; return (
{zoneKey === 'art' ? 'ARTWORK' : 'TEXT'} {/* Resize handle */}
); }; return (
{!frameImageUrl && (
Upload a frame image to position its windows
)} {renderZone('art')} {renderZone('text')}
); }