Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,16 @@ npm run dev
- `src/pages/index.astro`: the marketing homepage
- `src/pages/`: Product, Why, Self-hosted, Security, and the other hub routes
- `src/pages/404.astro`: branded not-found
- `src/layouts/MarketingPage.astro`: shared header/footer chrome for inner pages
- `src/lib/site.ts`: nav, footer groups, CTAs
- `src/components/`: Header, CheckRunPanel, RunFlow, Rivelle blocks
- `src/layouts/MarketingPage.astro`: header/footer chrome for inner pages; the
pages themselves compose full-bleed sections
- `src/lib/site.ts`: `siteSections` is the canonical site map — the header nav,
the footer, and the 404 all render it, so a route cannot exist without being
reachable from every page
- `src/components/marketing/`: the subpage system — `PageHero`, `Section`,
`Card`/`Grid`, `Steps`, `MarkList`, `CodePanel`, `CheckRunPanel`, `Callout`,
`Faq`, `Prose`, `CtaLink`/`Actions`
- `src/components/blocks/`, `src/components/ui/`: Rivelle blocks and primitives
used by the homepage template
- `src/layouts/Layout.astro`: head, meta, OG tags, header/footer chrome
- `src/styles/global.css`: product-green palette, JetBrains Mono, light/dark
via `prefers-color-scheme`
Expand Down
38 changes: 0 additions & 38 deletions src/components/CheckRunPanel.astro

This file was deleted.

31 changes: 0 additions & 31 deletions src/components/Header.astro

This file was deleted.

36 changes: 0 additions & 36 deletions src/components/RunFlow.astro

This file was deleted.

4 changes: 2 additions & 2 deletions src/components/blocks/footer-01.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function Footer01({
{...props}
>
<div className="mx-auto max-w-7xl">
<div className="grid gap-12 lg:grid-cols-[1.1fr_1.4fr]">
<div className="grid gap-12 lg:grid-cols-[.85fr_2fr]">
<div>
<a
className="inline-flex items-center gap-2.5 font-semibold tracking-tight"
Expand Down Expand Up @@ -89,7 +89,7 @@ function Footer01({
</div>
<nav
aria-label="Footer"
className="grid grid-cols-2 gap-8 sm:grid-cols-3"
className="grid grid-cols-2 gap-x-8 gap-y-10 sm:grid-cols-4"
>
{groups.map((group) => (
<div key={group.title}>
Expand Down
78 changes: 63 additions & 15 deletions src/components/blocks/site-header-01.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ import {
} from "@/components/ui/sheet";
import { cn } from "@/lib/utils";

type SiteHeaderLink = { label: string; href: string };
type SiteHeaderLink = { label: string; href: string; description?: string };

type SiteHeaderGroup = { title: string; links: readonly SiteHeaderLink[] };

type SiteHeader01Props = React.ComponentProps<"header"> & {
brand?: string;
groups?: readonly SiteHeaderGroup[];
links?: SiteHeaderLink[];
ctaLabel?: string;
ctaHref?: string;
Expand All @@ -60,6 +63,7 @@ function BrandMark() {

function SiteHeader01({
brand = "openpreflight",
groups = [],
links = defaultLinks,
ctaLabel = "Quickstart",
ctaHref = "https://docs.openpreflight.xyz/start/quickstart/",
Expand Down Expand Up @@ -93,10 +97,34 @@ function SiteHeader01({
<BrandMark />
{brand}
</a>
<nav
className="hidden items-center gap-1 lg:flex"
aria-label="Main"
>
<nav className="hidden items-center gap-1 lg:flex" aria-label="Main">
{groups.map((group) => (
<DropdownMenu key={group.title}>
<DropdownMenuTrigger asChild>
<button
className="group inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground outline-none transition-colors hover:bg-foreground/[.045] hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/55 data-[state=open]:bg-foreground/[.045] data-[state=open]:text-foreground"
type="button"
>
{group.title}
<ChevronDown className="size-3.5 transition-transform duration-200 group-data-[state=open]:rotate-180" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-80 p-1.5">
{group.links.map((link) => (
<DropdownMenuItem asChild key={link.label}>
<a className="flex-col items-start gap-0.5 py-2.5" href={link.href}>
<span className="text-sm font-semibold">{link.label}</span>
{link.description ? (
<span className="text-xs leading-snug text-muted-foreground">
{link.description}
</span>
) : null}
</a>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
))}
{links.map((link) => (
<a
className="rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-foreground/[.045] hover:text-foreground"
Expand Down Expand Up @@ -180,16 +208,36 @@ function SiteHeader01({
A small CI provider for private repos.
</SheetDescription>
</SheetHeader>
<nav className="mt-8 grid gap-1" aria-label="Mobile">
{links.map((link) => (
<a
className="rounded-xl px-3 py-3 text-sm font-semibold hover:bg-muted"
href={link.href}
key={link.label}
>
{link.label}
</a>
<nav className="mt-8 grid gap-6 overflow-y-auto pb-4" aria-label="Mobile">
{groups.map((group) => (
<div key={group.title}>
<p className="px-3 font-mono text-[0.68rem] font-semibold uppercase tracking-[0.06em] text-primary">
{group.title}
</p>
<div className="mt-1.5 grid gap-0.5">
{group.links.map((link) => (
<a
className="rounded-xl px-3 py-2.5 text-sm font-semibold hover:bg-muted"
href={link.href}
key={link.label}
>
{link.label}
</a>
))}
</div>
</div>
))}
<div className="grid gap-0.5 border-t border-foreground/10 pt-4">
{links.map((link) => (
<a
className="rounded-xl px-3 py-2.5 text-sm font-semibold hover:bg-muted"
href={link.href}
key={link.label}
>
{link.label}
</a>
))}
</div>
</nav>
<Button asChild className="mt-6 w-full" variant="signature">
<a href={ctaHref}>
Expand All @@ -205,4 +253,4 @@ function SiteHeader01({
}

export { SiteHeader01 };
export type { SiteHeader01Props, SiteHeaderLink };
export type { SiteHeader01Props, SiteHeaderGroup, SiteHeaderLink };
8 changes: 8 additions & 0 deletions src/components/marketing/Actions.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
interface Props {
class?: string;
}
const { class: className = '' } = Astro.props;
---

<div class={`mt-9 flex flex-wrap items-center gap-3 ${className}`.trim()}><slot /></div>
26 changes: 26 additions & 0 deletions src/components/marketing/Callout.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
/** Accent-rail note. Used for the "source of truth is the docs" reminders. */
interface Props {
title?: string;
tone?: 'accent' | 'muted';
class?: string;
}

const { title, tone = 'accent', class: className = '' } = Astro.props;

const toneClass =
tone === 'accent'
? 'border-primary/25 bg-primary/[.06]'
: 'border-foreground/10 bg-muted/40';
---

<aside class={`rounded-[1.25rem] border p-5 sm:p-6 ${toneClass} ${className}`.trim()}>
{
title && (
<p class="font-mono text-xs font-semibold uppercase tracking-[0.06em] text-primary">
{title}
</p>
)
}
<div class={`prose-block text-sm ${title ? 'mt-2.5' : ''}`}><slot /></div>
</aside>
60 changes: 60 additions & 0 deletions src/components/marketing/Card.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
/**
* Bordered card, matching the landing page's pillar cards. Becomes a link when
* `href` is set. `tone="dark"` for use inside a Section tone="dark".
*/
interface Props {
title?: string;
href?: string;
eyebrow?: string;
index?: number;
tone?: 'default' | 'dark';
class?: string;
}

const { title, href, eyebrow, index, tone = 'default', class: className = '' } = Astro.props;

const Tag = href ? 'a' : 'article';

const base =
'group flex flex-col rounded-[1.5rem] border p-6 transition-[border-color,background-color] duration-300 sm:p-7';
const toneClass =
tone === 'dark'
? 'border-background/12 bg-background/[.055]'
: 'border-foreground/10 bg-background';
const hoverClass = href
? tone === 'dark'
? 'hover:border-primary/50'
: 'hover:border-primary/45 hover:bg-primary/[.03]'
: '';

const eyebrowClass = tone === 'dark' ? 'text-primary' : 'text-muted-foreground';
const bodyClass = tone === 'dark' ? 'text-background/60' : 'text-muted-foreground';
---

<Tag href={href} class={`${base} ${toneClass} ${hoverClass} ${className}`.trim()}>
{
(eyebrow || index !== undefined) && (
<p class={`font-mono text-xs font-medium uppercase tracking-[0.06em] ${eyebrowClass}`}>
{index !== undefined ? String(index).padStart(2, '0') : eyebrow}
</p>
)
}
{
title && (
<h3
class={`text-lg font-semibold tracking-[-.03em] ${eyebrow || index !== undefined ? 'mt-4' : ''}`}
>
{title}
{href && (
<span aria-hidden="true" class="ml-1 inline-block text-primary opacity-0 transition-opacity group-hover:opacity-100">
</span>
)}
</h3>
)
}
<div class={`mt-3 text-sm leading-relaxed ${bodyClass} [&_code]:font-mono [&_code]:text-[0.85em]`}>
<slot />
</div>
</Tag>
Loading
Loading