diff --git a/client/src/components/HeroElement.jsx b/client/src/components/HeroElement.jsx index 7d12d8d..fd3efb0 100644 --- a/client/src/components/HeroElement.jsx +++ b/client/src/components/HeroElement.jsx @@ -107,12 +107,15 @@ function content(element) { case 'buttons': return case 'moon': { - const size = element.props?.size || 96 - const glow = element.props?.glow ?? 0.45 + const props = element.props || {} + const size = props.size || 96 + const glow = props.glow ?? 0.45 + // Image source is configurable; old layouts with no src fall back to the + // default hero moon so they render exactly as before. Size/glow unchanged. return ( Math.round(v * 100) / 100 const genId = () => (crypto.randomUUID ? crypto.randomUUID() : `el-${Date.now()}-${Math.random()}`) const hexOf = (v) => (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(v || '') ? v : '#ffffff') +// Soft page-weight warning shown before uploading a large hero image. This is +// only a nudge (hero images render on the public landing page); the server hard- +// limits uploads at 8 MB. Returns true when the caller should abort the upload. +const WARN_UPLOAD_MB = 5 +function tooLargeToUpload(size) { + return ( + size > WARN_UPLOAD_MB * 1024 * 1024 && + !confirm(`This image is over ${WARN_UPLOAD_MB} MB and may slow the page. Upload anyway?`) + ) +} + function newElement(type, z) { const base = { id: genId(), type, x: 50, y: 50, z, anchor: 'center' } if (type === 'text_block') { @@ -206,7 +217,7 @@ export default function HeroEditor() { const file = e.target.files?.[0] e.target.value = '' if (!file) return - if (file.size > 1024 * 1024 && !confirm('This image is over 1 MB and may slow the page. Upload anyway?')) return + if (tooLargeToUpload(file.size)) return setUploading(true) try { const { url } = await api.admin.upload(file) @@ -499,9 +510,45 @@ const swatch = { width: '100%', height: 38, padding: 2, border: '1px solid var(- function MoonPanel({ element, onProps }) { const p = element.props || {} + const [up, setUp] = useState(false) + // Reuses the shared admin upload endpoint (same as the image/background panels); + // a successful upload just points props.src at the returned URL. + async function onFile(e) { + const f = e.target.files?.[0] + e.target.value = '' + if (!f) return + if (tooLargeToUpload(f.size)) return + setUp(true) + try { + const { url } = await api.admin.upload(f) + onProps({ src: url }) + } catch { + /* ignore */ + } finally { + setUp(false) + } + } return (
-

Uses the moon from the hero artwork.

+
+ Moon image + {p.src ? ( + + ) : ( +

Using the default moon from the hero artwork.

+ )} + + {p.src && ( + onProps({ src: '' })}>Use default + )} +
+