Prompt cubes 01
A mono eyebrow, centred heading and line over a prompt box with actions, flanked by two gridded cubes, above an app window on a gridded panel.
Last updated
---
/**
* Prompt cubes 01: A mono eyebrow, centred heading and line over a prompt box with actions, flanked by two gridded cubes, above an app window on a gridded panel.
*
* @block hero
* @order 80
*/
import { Brush, Library, LayoutTemplate, Map, Paperclip, Play, Globe, Share2, Upload } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Eyebrow from '@/components/apogee/Eyebrow/Eyebrow.astro'
import Button from '@/components/apogee/Button/Button.astro'
import Link from '@/components/apogee/Link/Link.astro'
import { plain, type HeroContent } from '@/components/apogee/sections/_shared/spine.ts'
const steps = [['Brief', Map], ['Sitemap', Map], ['Wireframe', LayoutTemplate], ['Design', Brush]] as const
// A cube face: a grid drawn with two repeating lines over a primary shade.
const face = (mix: number) => `background: linear-gradient(var(--foreground-neutral) 1px, transparent 1px) 0 0 / 10% 10%, linear-gradient(90deg, var(--foreground-neutral) 1px, transparent 1px) 0 0 / 10% 10%, color-mix(in oklab, var(--background-primary) ${mix}%, var(--background-page))`
/** @composable */
interface Props extends HeroContent {
/** The prompt form's submit button. @category content */
submitLabel?: string
}
const {
eyebrow = 'Trusted by 1M+ freelancers and agencies',
heading = 'Build a marketing site you’d actually publish',
description = 'Start with a brief. We draft the sitemap, the wireframes and a site you’d be happy to ship. The AI does the work; you make the calls.',
secondaryAction = { label: 'Try an example', href: '#' },
submitLabel = 'Generate site',
} = Astro.props
---
<Section spacing="large" container={false} class="overflow-hidden">
<!-- Cubes: three faces each, turned in 3D. -->
{[{ side: '-start-16 rotate-12', }, { side: '-end-16 -rotate-12' }].map((c) => (
<div class:list={['cube pointer-events-none absolute top-[32%] hidden lg:block', c.side]} aria-hidden="true">
<span style={face(85)}></span><span style={face(55)}></span><span style={face(70)}></span>
</div>
))}
<div class="relative mx-auto flex max-w-2xl flex-col items-center gap-6 px-gutter text-center">
{eyebrow && <Eyebrow class="font-mono">{eyebrow}</Eyebrow>}
<Heading as="h1" size="display" class="font-normal text-balance">{plain(heading)}</Heading>
{description && <p class="text-lg text-neutral-faded">{description}</p>}
<form class="flex w-full flex-col gap-4 rounded-xl border border-neutral-faded bg-page p-4 text-start shadow-lg" onsubmit="return false">
<label for="prompt-cubes-brief" class="sr-only">Your brief</label>
<textarea id="prompt-cubes-brief" rows="3" placeholder="Drop in your notes, a brief or a URL…" class="w-full resize-none bg-transparent outline-none placeholder:text-neutral-faded"></textarea>
<div class="flex flex-wrap items-center justify-between gap-2">
<button type="button" class="text-neutral-faded" aria-label="Attach a file"><Paperclip class="size-4" /></button>
<span class="flex flex-wrap gap-2">
<Button size="small" variant="outline" color="neutral"><Globe slot="start" aria-hidden="true" />Publish</Button>
<Button size="small" variant="outline" color="neutral"><Upload slot="start" aria-hidden="true" />Export</Button>
<Button size="small" type="submit">{submitLabel}</Button>
</span>
</div>
</form>
{secondaryAction && <Link href={secondaryAction.href} color="neutral" class="text-sm decoration-dotted">{secondaryAction.label}</Link>}
</div>
<div class="relative mx-auto mt-16 max-w-5xl px-gutter" aria-hidden="true">
<div class="rounded-t-2xl border border-b-0 border-neutral-faded px-3 pt-12 md:px-12" style="background: linear-gradient(color-mix(in oklab, var(--background-primary) 20%, transparent) 1px, transparent 1px) 0 0 / 3rem 3rem, linear-gradient(90deg, color-mix(in oklab, var(--background-primary) 20%, transparent) 1px, transparent 1px) 0 0 / 3rem 3rem, var(--background-primary-faded)">
<div class="overflow-hidden rounded-t-xl border border-b-0 border-neutral-faded bg-page text-sm shadow-xl">
<div class="flex items-center gap-3 border-b border-neutral-faded px-4 py-2">
<span class="font-medium">Maisons</span>
<span class="mx-auto hidden gap-1 rounded-lg bg-neutral-faded p-1 md:flex">{steps.map(([s, Icon], i) => <span class:list={['flex items-center gap-1.5 rounded-md px-2.5 py-1', i === 3 && 'bg-page shadow-sm']}><Icon class="size-3.5" />{s}</span>)}</span>
<span class="ms-auto flex items-center gap-2 md:ms-0"><Share2 class="size-4 text-neutral-faded" /><Play class="size-4 text-neutral-faded" /><span class="rounded-md bg-primary px-2.5 py-1 text-on-primary">Publish site</span></span>
</div>
<div class="flex">
<div class="flex flex-col gap-4 border-e border-neutral-faded p-3 text-xs text-neutral-faded"><Globe class="size-4" /><Library class="size-4" /><Brush class="size-4" /></div>
<div class="flex flex-1 flex-col gap-3 p-4">
<span class="rounded-md border border-neutral-faded px-3 py-1.5 text-neutral-faded">Home</span>
<div data-theme="dark" class="flex items-center gap-6 rounded-t-md bg-page px-4 py-3 text-xs text-neutral"><span class="font-semibold tracking-[0.2em]">MAISONS</span><span class="hidden gap-4 text-neutral-faded sm:flex"><span>Living</span><span>Places</span><span>Lifestyle</span></span><span class="ms-auto rounded border border-neutral-faded px-2 py-1">Register</span></div>
</div>
</div>
</div>
</div>
</div>
</Section>
<style>
/* A cube from three faces: top, front and side, each a gridded square. */
.cube { --s: 13rem; inline-size: var(--s); block-size: var(--s); transform-style: preserve-3d; transform: rotateX(-25deg) rotateY(40deg); }
.cube span { position: absolute; inset: 0; border: 1px solid var(--foreground-neutral); }
.cube span:nth-child(1) { transform: rotateX(90deg) translateZ(calc(var(--s) / 2)); }
.cube span:nth-child(2) { transform: translateZ(calc(var(--s) / 2)); }
.cube span:nth-child(3) { transform: rotateY(90deg) translateZ(calc(var(--s) / 2)); }
</style>
Show codeHide code
---
/**
* Prompt cubes 01: A mono eyebrow, centred heading and line over a prompt box with actions, flanked by two gridded cubes, above an app window on a gridded panel.
*
* @block hero
* @order 80
*/
import { Brush, Library, LayoutTemplate, Map, Paperclip, Play, Globe, Share2, Upload } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Eyebrow from '@/components/apogee/Eyebrow/Eyebrow.astro'
import Button from '@/components/apogee/Button/Button.astro'
import Link from '@/components/apogee/Link/Link.astro'
import { plain, type HeroContent } from '@/components/apogee/sections/_shared/spine.ts'
const steps = [['Brief', Map], ['Sitemap', Map], ['Wireframe', LayoutTemplate], ['Design', Brush]] as const
// A cube face: a grid drawn with two repeating lines over a primary shade.
const face = (mix: number) => `background: linear-gradient(var(--foreground-neutral) 1px, transparent 1px) 0 0 / 10% 10%, linear-gradient(90deg, var(--foreground-neutral) 1px, transparent 1px) 0 0 / 10% 10%, color-mix(in oklab, var(--background-primary) ${mix}%, var(--background-page))`
/** @composable */
interface Props extends HeroContent {
/** The prompt form's submit button. @category content */
submitLabel?: string
}
const {
eyebrow = 'Trusted by 1M+ freelancers and agencies',
heading = 'Build a marketing site you’d actually publish',
description = 'Start with a brief. We draft the sitemap, the wireframes and a site you’d be happy to ship. The AI does the work; you make the calls.',
secondaryAction = { label: 'Try an example', href: '#' },
submitLabel = 'Generate site',
} = Astro.props
---
<Section spacing="large" container={false} class="overflow-hidden">
<!-- Cubes: three faces each, turned in 3D. -->
{[{ side: '-start-16 rotate-12', }, { side: '-end-16 -rotate-12' }].map((c) => (
<div class:list={['cube pointer-events-none absolute top-[32%] hidden lg:block', c.side]} aria-hidden="true">
<span style={face(85)}></span><span style={face(55)}></span><span style={face(70)}></span>
</div>
))}
<div class="relative mx-auto flex max-w-2xl flex-col items-center gap-6 px-gutter text-center">
{eyebrow && <Eyebrow class="font-mono">{eyebrow}</Eyebrow>}
<Heading as="h1" size="display" class="font-normal text-balance">{plain(heading)}</Heading>
{description && <p class="text-lg text-neutral-faded">{description}</p>}
<form class="flex w-full flex-col gap-4 rounded-xl border border-neutral-faded bg-page p-4 text-start shadow-lg" onsubmit="return false">
<label for="prompt-cubes-brief" class="sr-only">Your brief</label>
<textarea id="prompt-cubes-brief" rows="3" placeholder="Drop in your notes, a brief or a URL…" class="w-full resize-none bg-transparent outline-none placeholder:text-neutral-faded"></textarea>
<div class="flex flex-wrap items-center justify-between gap-2">
<button type="button" class="text-neutral-faded" aria-label="Attach a file"><Paperclip class="size-4" /></button>
<span class="flex flex-wrap gap-2">
<Button size="small" variant="outline" color="neutral"><Globe slot="start" aria-hidden="true" />Publish</Button>
<Button size="small" variant="outline" color="neutral"><Upload slot="start" aria-hidden="true" />Export</Button>
<Button size="small" type="submit">{submitLabel}</Button>
</span>
</div>
</form>
{secondaryAction && <Link href={secondaryAction.href} color="neutral" class="text-sm decoration-dotted">{secondaryAction.label}</Link>}
</div>
<div class="relative mx-auto mt-16 max-w-5xl px-gutter" aria-hidden="true">
<div class="rounded-t-2xl border border-b-0 border-neutral-faded px-3 pt-12 md:px-12" style="background: linear-gradient(color-mix(in oklab, var(--background-primary) 20%, transparent) 1px, transparent 1px) 0 0 / 3rem 3rem, linear-gradient(90deg, color-mix(in oklab, var(--background-primary) 20%, transparent) 1px, transparent 1px) 0 0 / 3rem 3rem, var(--background-primary-faded)">
<div class="overflow-hidden rounded-t-xl border border-b-0 border-neutral-faded bg-page text-sm shadow-xl">
<div class="flex items-center gap-3 border-b border-neutral-faded px-4 py-2">
<span class="font-medium">Maisons</span>
<span class="mx-auto hidden gap-1 rounded-lg bg-neutral-faded p-1 md:flex">{steps.map(([s, Icon], i) => <span class:list={['flex items-center gap-1.5 rounded-md px-2.5 py-1', i === 3 && 'bg-page shadow-sm']}><Icon class="size-3.5" />{s}</span>)}</span>
<span class="ms-auto flex items-center gap-2 md:ms-0"><Share2 class="size-4 text-neutral-faded" /><Play class="size-4 text-neutral-faded" /><span class="rounded-md bg-primary px-2.5 py-1 text-on-primary">Publish site</span></span>
</div>
<div class="flex">
<div class="flex flex-col gap-4 border-e border-neutral-faded p-3 text-xs text-neutral-faded"><Globe class="size-4" /><Library class="size-4" /><Brush class="size-4" /></div>
<div class="flex flex-1 flex-col gap-3 p-4">
<span class="rounded-md border border-neutral-faded px-3 py-1.5 text-neutral-faded">Home</span>
<div data-theme="dark" class="flex items-center gap-6 rounded-t-md bg-page px-4 py-3 text-xs text-neutral"><span class="font-semibold tracking-[0.2em]">MAISONS</span><span class="hidden gap-4 text-neutral-faded sm:flex"><span>Living</span><span>Places</span><span>Lifestyle</span></span><span class="ms-auto rounded border border-neutral-faded px-2 py-1">Register</span></div>
</div>
</div>
</div>
</div>
</div>
</Section>
<style>
/* A cube from three faces: top, front and side, each a gridded square. */
.cube { --s: 13rem; inline-size: var(--s); block-size: var(--s); transform-style: preserve-3d; transform: rotateX(-25deg) rotateY(40deg); }
.cube span { position: absolute; inset: 0; border: 1px solid var(--foreground-neutral); }
.cube span:nth-child(1) { transform: rotateX(90deg) translateZ(calc(var(--s) / 2)); }
.cube span:nth-child(2) { transform: translateZ(calc(var(--s) / 2)); }
.cube span:nth-child(3) { transform: rotateY(90deg) translateZ(calc(var(--s) / 2)); }
</style>Add
npx @ava-ui/apogee add hero/prompt-cubes-01
Every piece of copy is a prop, with the sample copy as its default. Pass yours, or null to hide a part; see copy is props.