import { Link } from 'react-router-dom'
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)' }
// fontSize may be a number (px, from the editor) or a CSS string (e.g. a clamp()
// used by the pre-populated default so the hero stays responsive until edited).
function sizeToCss(v) {
return typeof v === 'number' ? `${v}px` : v
}
function lineStyle(line) {
return {
display: 'block', // each line stacks (so a span line behaves like the others)
margin: line.marginTop != null ? `${line.marginTop}px 0 0` : '0',
fontFamily: FONT[line.font] || undefined,
fontSize: sizeToCss(line.fontSize),
color: line.color || 'inherit',
fontWeight: line.weight || undefined,
fontStyle: line.italic ? 'italic' : undefined,
letterSpacing: line.letterSpacing || undefined,
textTransform: line.transform || undefined,
lineHeight: line.lineHeight || undefined,
maxWidth: line.maxWidth ? `${line.maxWidth}px` : undefined,
marginLeft: line.maxWidth ? 'auto' : undefined,
marginRight: line.maxWidth ? 'auto' : undefined,
}
}
function TextBlock({ props }) {
const align = props.align || 'center'
return (
)
}
function Badge({ props }) {
return (
{props.text}
)
}
function HeroImage({ props }) {
if (!props.src) {
// Editor placeholder until an image is chosen (a srcless image never ships live).
return (
Upload an image
)
}
return (
)
}
function content(element) {
switch (element.type) {
case 'text_block':
return
case 'buttons':
return
case 'moon': {
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 (
)
}
case 'badge':
return
case 'image':
return
default:
return null
}
}
// Absolute-positioned wrapper + type-specific content. In `editor` mode the inner
// content is made non-interactive (so clicks select/drag the wrapper) and the
// wrapper takes selection styling + an onPointerDown handler.
export default function HeroElement({
element,
wrapperStyle,
editor = false,
selected = false,
onPointerDown,
children,
}) {
const anchor = element.anchor || 'center'
const transform =
anchor === 'center'
? 'translate(-50%, -50%)'
: anchor === 'top-right'
? 'translateX(-100%)'
: undefined
// text_block/buttons may set a box width (px); kept within the containing block
// (the hero section live, or the editor canvas) with small side gutters.
const boxWidth =
(element.type === 'text_block' || element.type === 'buttons') && element.props?.width
? `min(${element.props.width}px, calc(100% - 36px))`
: undefined
const cls = [editor ? 'hero-el-editable' : '', selected ? 'is-selected' : ''].filter(Boolean).join(' ')
return (