App panel 01
A serif heading beside a short line, two round buttons, then an app mockup with a sidebar, prompt box and recent projects on a soft primary panel, over a row of logos.
Last updated
---
/**
* App panel 01: A serif heading beside a short line, two round buttons, then an app mockup with a sidebar, prompt box and recent projects on a soft primary panel, over a row of logos.
*
* @block hero
* @order 52
*/
import { ArrowRight, Atom, BarChart3, Box, FileText, Folder, Hexagon, Home, Layers, Mountain, Plus, Search, Sparkles, Sprout, ArrowUp, Waves, Wind, Megaphone, Palette, Plug, BookOpen } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Button from '@/components/apogee/Button/Button.astro'
import Badge from '@/components/apogee/Badge/Badge.astro'
import Divider from '@/components/apogee/Divider/Divider.astro'
import { plain, type HeroContent } from '@/components/apogee/sections/_shared/spine.ts'
/** @composable */
interface Props extends HeroContent {
/** Names in the logo row; each gets the next placeholder mark. @category content */
logos?: string[] | null
}
const {
heading = 'Marketing that pays its way',
description = 'An assistant that finds your buyers, makes the ads and runs the campaigns around the clock. You set the rules; every spend is measured on revenue.',
primaryAction = { label: 'Book a demo', href: '#' },
secondaryAction = { label: 'See it in action', href: '#' },
logos = ['Lumen', 'Quanta', 'Fieldwork', 'Northpeak', 'Arcwave', 'Kitestring'],
} = Astro.props
const nav = [
{ label: 'Home', icon: Home, active: true },
{ label: 'Insights', icon: Sparkles },
{ label: 'Deliverables', icon: FileText },
{ label: 'Analytics', icon: BarChart3 },
{ label: 'Automations', icon: Layers },
]
const records = [
{ label: 'Campaigns', icon: Megaphone },
{ label: 'Creatives', icon: Palette },
{ label: 'Product', icon: Box },
]
const projects = [
{ name: 'Spring launch', meta: 'In progress', badge: 'Working' },
{ name: 'Ad refresh for new buyers', meta: 'Updated 2 hours ago' },
{ name: 'Weekly spend review', meta: 'Updated 3 hours ago' },
]
// ponytail: placeholder marks cycle through the logo names; swap for real logo images when a site has them.
const logoIcons = [Hexagon, Atom, Sprout, Mountain, Waves, Wind]
---
<Section spacing="large">
<div class="grid items-end gap-6 lg:grid-cols-2 lg:gap-16">
<Heading as="h1" size="display" class="text-balance">{plain(heading)}</Heading>
{description && <p class="max-w-xl text-lg md:text-xl text-neutral-faded lg:justify-self-end">{description}</p>}
</div>
<Divider class="my-8" />
{(primaryAction || secondaryAction) && (
<div class="flex flex-col gap-3 sm:flex-row">
{primaryAction && <Button href={primaryAction.href} size="large" rounded><ArrowRight slot="start" class="rtl:-scale-x-100" aria-hidden="true" />{primaryAction.label}</Button>}
{secondaryAction && <Button href={secondaryAction.href} size="large" rounded variant="outline" color="neutral">{secondaryAction.label}</Button>}
</div>
)}
<!-- Soft panel: the primary hue mixed into the page in a few washes, so it follows the theme. -->
<div class="mt-10 rounded-2xl p-3 sm:p-6 md:p-12" style="background: radial-gradient(ellipse at 15% 10%, color-mix(in oklab, var(--background-primary) 8%, var(--background-page)), transparent 60%), radial-gradient(ellipse at 90% 70%, color-mix(in oklab, var(--background-primary) 40%, var(--background-page)), transparent 70%), color-mix(in oklab, var(--background-primary) 22%, var(--background-page))" aria-hidden="true">
<div class="flex overflow-hidden rounded-xl border border-neutral-faded bg-page text-sm shadow-lg">
<div class="hidden w-56 shrink-0 flex-col gap-4 border-e border-neutral-faded bg-neutral-faded p-3 md:flex">
<div class="flex items-center justify-between px-2 pt-1"><span class="flex items-center gap-2 font-medium"><span class="grid size-5 place-items-center rounded bg-primary text-xs text-on-primary">b</span>Brightfield</span><Search class="size-4 text-neutral-faded" /></div>
<ul class="flex flex-col gap-0.5">
{nav.map((n) => <li class:list={['flex items-center gap-2 rounded-md px-2 py-1.5', n.active && 'bg-page shadow-sm']}><n.icon class="size-4 text-neutral-faded" />{n.label}</li>)}
</ul>
<div class="flex flex-col gap-0.5">
<p class="px-2 text-xs uppercase tracking-wide text-neutral-faded">Records</p>
<ul class="flex flex-col gap-0.5">{records.map((n) => <li class="flex items-center gap-2 rounded-md px-2 py-1.5"><n.icon class="size-4 text-neutral-faded" />{n.label}</li>)}</ul>
</div>
<ul class="mt-auto flex flex-col gap-0.5">
<li class="flex items-center gap-2 px-2 py-1.5"><BookOpen class="size-4 text-neutral-faded" />Knowledge</li>
<li class="flex items-center gap-2 px-2 py-1.5"><Plug class="size-4 text-neutral-faded" />Integrations</li>
</ul>
</div>
<div class="flex min-w-0 flex-1 flex-col">
<div class="flex items-center gap-1 border-b border-neutral-faded px-3 py-2">
<Plus class="size-4 text-neutral-faded" />
<span class="flex items-center gap-1.5 rounded-md bg-neutral-faded px-2 py-1"><Home class="size-4" />Home</span>
<span class="flex items-center gap-1.5 px-2 py-1 text-neutral-faded"><Megaphone class="size-4" />Campaigns</span>
<span class="hidden items-center gap-1.5 px-2 py-1 text-neutral-faded sm:flex"><Palette class="size-4" />Creatives</span>
</div>
<div class="flex flex-col items-center gap-6 px-4 py-10 md:py-16">
<p class="text-base font-medium">What should we work on next?</p>
<div class="flex w-full max-w-2xl flex-col gap-6 rounded-xl border border-neutral-faded p-3 shadow-sm">
<span class="text-neutral-faded">Ask anything, or type @ to add context</span>
<span class="flex justify-between"><span class="grid size-6 place-items-center rounded-md border border-neutral-faded"><Plus class="size-3.5" /></span><span class="grid size-6 place-items-center rounded-full bg-neutral-faded"><ArrowUp class="size-3.5" /></span></span>
</div>
<div class="flex w-full max-w-lg flex-col">
<p class="pb-2 text-neutral-faded">Recent projects</p>
{projects.map((p) => (
<div class="flex items-center gap-3 border-t border-neutral-faded py-3">
<span class="grid size-8 place-items-center rounded-md bg-neutral-faded"><Folder class="size-4 text-neutral-faded" /></span>
<span class="flex min-w-0 flex-1 flex-col"><span class="truncate">{p.name}</span><span class="text-xs text-neutral-faded">{p.meta}</span></span>
{p.badge && <Badge variant="faded" color="primary">{p.badge}</Badge>}
</div>
))}
</div>
</div>
</div>
</div>
</div>
{logos && (
<ul class="mt-10 flex flex-wrap items-center justify-center gap-x-12 gap-y-4">
{logos.map((name, i) => { const Mark = logoIcons[i % logoIcons.length]!; return <li class="flex items-center gap-2 text-lg font-semibold text-neutral-faded"><Mark class="size-6" aria-hidden="true" />{name}</li> })}
</ul>
)}
</Section>
Show codeHide code
---
/**
* App panel 01: A serif heading beside a short line, two round buttons, then an app mockup with a sidebar, prompt box and recent projects on a soft primary panel, over a row of logos.
*
* @block hero
* @order 52
*/
import { ArrowRight, Atom, BarChart3, Box, FileText, Folder, Hexagon, Home, Layers, Mountain, Plus, Search, Sparkles, Sprout, ArrowUp, Waves, Wind, Megaphone, Palette, Plug, BookOpen } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Button from '@/components/apogee/Button/Button.astro'
import Badge from '@/components/apogee/Badge/Badge.astro'
import Divider from '@/components/apogee/Divider/Divider.astro'
import { plain, type HeroContent } from '@/components/apogee/sections/_shared/spine.ts'
/** @composable */
interface Props extends HeroContent {
/** Names in the logo row; each gets the next placeholder mark. @category content */
logos?: string[] | null
}
const {
heading = 'Marketing that pays its way',
description = 'An assistant that finds your buyers, makes the ads and runs the campaigns around the clock. You set the rules; every spend is measured on revenue.',
primaryAction = { label: 'Book a demo', href: '#' },
secondaryAction = { label: 'See it in action', href: '#' },
logos = ['Lumen', 'Quanta', 'Fieldwork', 'Northpeak', 'Arcwave', 'Kitestring'],
} = Astro.props
const nav = [
{ label: 'Home', icon: Home, active: true },
{ label: 'Insights', icon: Sparkles },
{ label: 'Deliverables', icon: FileText },
{ label: 'Analytics', icon: BarChart3 },
{ label: 'Automations', icon: Layers },
]
const records = [
{ label: 'Campaigns', icon: Megaphone },
{ label: 'Creatives', icon: Palette },
{ label: 'Product', icon: Box },
]
const projects = [
{ name: 'Spring launch', meta: 'In progress', badge: 'Working' },
{ name: 'Ad refresh for new buyers', meta: 'Updated 2 hours ago' },
{ name: 'Weekly spend review', meta: 'Updated 3 hours ago' },
]
// ponytail: placeholder marks cycle through the logo names; swap for real logo images when a site has them.
const logoIcons = [Hexagon, Atom, Sprout, Mountain, Waves, Wind]
---
<Section spacing="large">
<div class="grid items-end gap-6 lg:grid-cols-2 lg:gap-16">
<Heading as="h1" size="display" class="text-balance">{plain(heading)}</Heading>
{description && <p class="max-w-xl text-lg md:text-xl text-neutral-faded lg:justify-self-end">{description}</p>}
</div>
<Divider class="my-8" />
{(primaryAction || secondaryAction) && (
<div class="flex flex-col gap-3 sm:flex-row">
{primaryAction && <Button href={primaryAction.href} size="large" rounded><ArrowRight slot="start" class="rtl:-scale-x-100" aria-hidden="true" />{primaryAction.label}</Button>}
{secondaryAction && <Button href={secondaryAction.href} size="large" rounded variant="outline" color="neutral">{secondaryAction.label}</Button>}
</div>
)}
<!-- Soft panel: the primary hue mixed into the page in a few washes, so it follows the theme. -->
<div class="mt-10 rounded-2xl p-3 sm:p-6 md:p-12" style="background: radial-gradient(ellipse at 15% 10%, color-mix(in oklab, var(--background-primary) 8%, var(--background-page)), transparent 60%), radial-gradient(ellipse at 90% 70%, color-mix(in oklab, var(--background-primary) 40%, var(--background-page)), transparent 70%), color-mix(in oklab, var(--background-primary) 22%, var(--background-page))" aria-hidden="true">
<div class="flex overflow-hidden rounded-xl border border-neutral-faded bg-page text-sm shadow-lg">
<div class="hidden w-56 shrink-0 flex-col gap-4 border-e border-neutral-faded bg-neutral-faded p-3 md:flex">
<div class="flex items-center justify-between px-2 pt-1"><span class="flex items-center gap-2 font-medium"><span class="grid size-5 place-items-center rounded bg-primary text-xs text-on-primary">b</span>Brightfield</span><Search class="size-4 text-neutral-faded" /></div>
<ul class="flex flex-col gap-0.5">
{nav.map((n) => <li class:list={['flex items-center gap-2 rounded-md px-2 py-1.5', n.active && 'bg-page shadow-sm']}><n.icon class="size-4 text-neutral-faded" />{n.label}</li>)}
</ul>
<div class="flex flex-col gap-0.5">
<p class="px-2 text-xs uppercase tracking-wide text-neutral-faded">Records</p>
<ul class="flex flex-col gap-0.5">{records.map((n) => <li class="flex items-center gap-2 rounded-md px-2 py-1.5"><n.icon class="size-4 text-neutral-faded" />{n.label}</li>)}</ul>
</div>
<ul class="mt-auto flex flex-col gap-0.5">
<li class="flex items-center gap-2 px-2 py-1.5"><BookOpen class="size-4 text-neutral-faded" />Knowledge</li>
<li class="flex items-center gap-2 px-2 py-1.5"><Plug class="size-4 text-neutral-faded" />Integrations</li>
</ul>
</div>
<div class="flex min-w-0 flex-1 flex-col">
<div class="flex items-center gap-1 border-b border-neutral-faded px-3 py-2">
<Plus class="size-4 text-neutral-faded" />
<span class="flex items-center gap-1.5 rounded-md bg-neutral-faded px-2 py-1"><Home class="size-4" />Home</span>
<span class="flex items-center gap-1.5 px-2 py-1 text-neutral-faded"><Megaphone class="size-4" />Campaigns</span>
<span class="hidden items-center gap-1.5 px-2 py-1 text-neutral-faded sm:flex"><Palette class="size-4" />Creatives</span>
</div>
<div class="flex flex-col items-center gap-6 px-4 py-10 md:py-16">
<p class="text-base font-medium">What should we work on next?</p>
<div class="flex w-full max-w-2xl flex-col gap-6 rounded-xl border border-neutral-faded p-3 shadow-sm">
<span class="text-neutral-faded">Ask anything, or type @ to add context</span>
<span class="flex justify-between"><span class="grid size-6 place-items-center rounded-md border border-neutral-faded"><Plus class="size-3.5" /></span><span class="grid size-6 place-items-center rounded-full bg-neutral-faded"><ArrowUp class="size-3.5" /></span></span>
</div>
<div class="flex w-full max-w-lg flex-col">
<p class="pb-2 text-neutral-faded">Recent projects</p>
{projects.map((p) => (
<div class="flex items-center gap-3 border-t border-neutral-faded py-3">
<span class="grid size-8 place-items-center rounded-md bg-neutral-faded"><Folder class="size-4 text-neutral-faded" /></span>
<span class="flex min-w-0 flex-1 flex-col"><span class="truncate">{p.name}</span><span class="text-xs text-neutral-faded">{p.meta}</span></span>
{p.badge && <Badge variant="faded" color="primary">{p.badge}</Badge>}
</div>
))}
</div>
</div>
</div>
</div>
</div>
{logos && (
<ul class="mt-10 flex flex-wrap items-center justify-center gap-x-12 gap-y-4">
{logos.map((name, i) => { const Mark = logoIcons[i % logoIcons.length]!; return <li class="flex items-center gap-2 text-lg font-semibold text-neutral-faded"><Mark class="size-6" aria-hidden="true" />{name}</li> })}
</ul>
)}
</Section>Add
npx @ava-ui/apogee add hero/app-panel-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.