/* ===== Shared UI components ===== */ const { useState, useEffect, useRef, useCallback } = React; /* ---- Icon ---- */ function Icon({ name, size, className }) { const p = window.ICONS[name] || ''; return ( ); } /* ---- ScaledFrame: renders a section in an isolated iframe, scaled to fit ---- mode 'crop' -> fills a fixed box, top-aligned (for cards) mode 'fit' -> shows the whole section, height follows content (for detail) */ function ScaledFrame({ section, theme, logicalWidth, mode, maxScale }) { const wrapRef = useRef(null); const frameRef = useRef(null); const [scale, setScale] = useState(0.26); const [contentH, setContentH] = useState(section.height || 480); const lw = logicalWidth || 1280; const html = window.buildSectionHTML(section); // If the section has a user-uploaded thumbnail, show it in card/crop contexts // (grid cards, manage rows, category previews) for a consistent representation. const hasThumb = !!section.thumb; const measure = useCallback(() => { const el = wrapRef.current; if (!el) return; let s = el.clientWidth / lw; if (maxScale) s = Math.min(s, maxScale); setScale(s); }, [lw, maxScale]); useEffect(() => { measure(); const ro = new ResizeObserver(measure); if (wrapRef.current) ro.observe(wrapRef.current); return () => ro.disconnect(); }, [measure]); const onLoad = () => { try { const doc = frameRef.current.contentDocument; const h = Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight); if (h && Math.abs(h - contentH) > 2) setContentH(h); } catch (e) {} }; if (mode === 'fit') { return (