Hero editor: larger faithful canvas + real moon from the hero art

Addresses feedback that the editor was too small/cramped (elements overlapping)
and that the generated CSS moon looked bad.

- AdminLayout: the /admin/hero view now uses the full content width (no 1000px cap)
- HeroEditor canvas is a scaled 1280x720 "stage" (transform: scale to fit the
  column, capped at ~66vh). Because viewport-unit fonts and % positions scale
  together, the canvas is now a faithful miniature of the live hero — the default
  text block and CTA buttons no longer overlap. Drag snaps to the 8px stage grid;
  resize math is scale-aware.
- Moon element now renders the actual moon cropped from the hero artwork
  (client/public/assets/img/hero-moon.png, circular alpha mask) instead of the CSS
  dot; MoonPanel exposes size + glow.

Verified: at 1440px the canvas is ~785x442 with the panel beside it, default
elements don't overlap, and the moon loads the real image. Build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 09:13:22 -05:00
parent f69c86f737
commit 73eac2a138
4 changed files with 75 additions and 38 deletions

View File

@@ -1,5 +1,6 @@
import { Link } from 'react-router-dom'
import MoonDot from './MoonDot.jsx'
const MOON_IMAGE = '/assets/img/hero-moon.png'
// Font family tokens a line may opt into; default is the page serif.
const FONT = { display: 'var(--display)', sans: 'var(--sans)' }
@@ -105,8 +106,23 @@ function content(element) {
return <TextBlock props={element.props || {}} />
case 'buttons':
return <Buttons props={element.props || {}} />
case 'moon':
return <MoonDot size={element.props?.size || 48} glow={element.props?.glow ?? 0.45} color={element.props?.color} />
case 'moon': {
const size = element.props?.size || 96
const glow = element.props?.glow ?? 0.45
return (
<img
src={MOON_IMAGE}
alt=""
draggable={false}
style={{
width: size,
height: 'auto',
display: 'block',
filter: glow ? `drop-shadow(0 0 ${size * 0.45}px rgba(216,226,239,${glow}))` : undefined,
}}
/>
)
}
case 'badge':
return <Badge props={element.props || {}} />
case 'image':