Skip to content
Merged
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
197 changes: 83 additions & 114 deletions components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,131 +96,100 @@ export function Hero() {
Jump in with a ready-made stack template, explore the full wizard, or drop a GitHub repo for an automatic scan.
</motion.p>

<motion.ul
className="mx-auto flex max-w-3xl flex-col items-start gap-2 text-left text-sm text-muted-foreground md:flex-row md:flex-wrap md:justify-center"
variants={itemVariants}
>
{featureHighlights.map((feature) => (
<motion.li
key={feature}
className="inline-flex items-center gap-2 rounded-full border border-border/60 bg-background/70 px-4 py-2 text-sm text-foreground shadow-sm backdrop-blur md:text-base"
variants={itemVariants}
>
<CheckCircle2 className="size-4 text-primary" />
{feature}
</motion.li>
))}
</motion.ul>


<motion.div
className="mx-auto w-full max-w-4xl space-y-8 text-left"
className="mx-auto w-full max-w-4xl text-left"
variants={itemVariants}
>
<div className="flex flex-col gap-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex flex-col gap-6 lg:flex-row lg:items-start">
<div className="flex-1 space-y-4">
<h2 className="text-lg font-semibold uppercase tracking-wide text-muted-foreground">
Start fast with popular stacks
</h2>
<Button variant="outline" size="sm" onClick={handleMoreStacks}>
More stacks
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</div>
<div className="grid gap-3 md:grid-cols-3">
{popularStacks.map((stack) => {
const descriptor = getIconDescriptor(stack.icon ?? stack.value)
const iconHex = descriptor ? iconColorOverrides[descriptor.slug] ?? descriptor.hex : undefined
const iconColor = iconHex ? getAccessibleIconColor(iconHex) : undefined
const iconBackground = iconColor ? hexToRgba(iconColor, 0.18) ?? undefined : undefined
const iconRing = iconColor ? hexToRgba(iconColor, 0.32) ?? undefined : undefined
const initials = getFallbackInitials(stack.label)

return (
<button
key={stack.value}
type="button"
onClick={() => handleStackClick(stack.value)}
className="group flex h-full flex-col gap-4 rounded-3xl border border-border/70 bg-background/90 px-5 py-6 text-left shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
>
<span
className="flex h-10 w-10 items-center justify-center rounded-xl ring-1 ring-border/40"
style={{
color: iconColor,
backgroundColor: iconBackground,
boxShadow: iconRing ? `inset 0 0 0 1px ${iconRing}` : undefined,
}}
aria-hidden
<div className="flex flex-wrap gap-3">
{popularStacks.map((stack) => {
const descriptor = getIconDescriptor(stack.icon ?? stack.value)
const iconHex = descriptor ? iconColorOverrides[descriptor.slug] ?? descriptor.hex : undefined
const iconColor = iconHex ? getAccessibleIconColor(iconHex) : undefined
const iconBackground = iconColor ? hexToRgba(iconColor, 0.18) ?? undefined : undefined
const iconRing = iconColor ? hexToRgba(iconColor, 0.32) ?? undefined : undefined
const initials = getFallbackInitials(stack.label)

return (
<button
key={stack.value}
type="button"
onClick={() => handleStackClick(stack.value)}
className="group inline-flex items-center gap-3 rounded-full border border-border/70 bg-background/90 px-4 py-2 text-sm font-medium text-foreground shadow-sm transition hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
>
{descriptor ? (
<span
className="inline-flex h-6 w-6 items-center justify-center [&>svg]:h-full [&>svg]:w-full"
style={{ color: iconColor ?? "inherit" }}
dangerouslySetInnerHTML={{ __html: descriptor.markup }}
/>
) : (
<span className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
{initials}
</span>
)}
</span>
<div className="space-y-2">
<div className="flex items-center justify-between gap-3">
<p className="text-base font-semibold text-foreground">{stack.label}</p>
<ArrowRight className="h-4 w-4 text-muted-foreground transition group-hover:text-primary" />
</div>
{stack.tags && stack.tags.length > 0 ? (
<div className="flex flex-wrap gap-1 text-[10px] uppercase tracking-wide text-muted-foreground/70">
{stack.tags.slice(0, 3).map((tag) => (
<span key={tag} className="rounded-full bg-muted/70 px-2 py-0.5">
{tag}
</span>
))}
</div>
) : null}
<p className="text-sm text-muted-foreground">
Auto-fill recommended defaults and jump straight to the summary.
</p>
</div>
</button>
)
})}
<span
className="flex h-8 w-8 items-center justify-center rounded-full ring-1 ring-border/40"
style={{
color: iconColor,
backgroundColor: iconBackground,
boxShadow: iconRing ? `inset 0 0 0 1px ${iconRing}` : undefined,
}}
aria-hidden
>
{descriptor ? (
<span
className="inline-flex h-5 w-5 items-center justify-center [&>svg]:h-full [&>svg]:w-full"
style={{ color: iconColor ?? "inherit" }}
dangerouslySetInnerHTML={{ __html: descriptor.markup }}
/>
) : (
<span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
{initials}
</span>
)}
</span>
<span className="flex items-center gap-2">
{stack.label}
<ArrowRight className="h-3.5 w-3.5 text-muted-foreground transition group-hover:text-primary" />
</span>
</button>
)
})}

<button
type="button"
onClick={handleMoreStacks}
className="inline-flex items-center gap-2 rounded-full border border-border/70 bg-background/80 px-4 py-2 text-sm font-semibold text-foreground shadow-sm transition hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
>
More stacks
<ArrowRight className="h-3.5 w-3.5" />
</button>
</div>
</div>
</div>

<div className="flex items-center gap-4">
<div className="h-px flex-1 bg-border/60" />
<span className="text-xs font-semibold uppercase tracking-[0.3em] text-muted-foreground">
or
</span>
<div className="h-px flex-1 bg-border/60" />
<form
onSubmit={handleGithubSubmit}
className="flex flex-1 flex-col gap-3 rounded-3xl border border-border/70 bg-background/95 p-6 shadow-sm"
>
<div className="space-y-2 text-left">
<p className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
Scan a GitHub repository
</p>
<p className="text-sm text-muted-foreground">
Paste an owner/repo or URL and we&apos;ll prefill the wizard with detected tech and tooling.
</p>
</div>
<div className="flex w-full flex-col gap-2 sm:flex-row">
<input
type="text"
value={githubRepo}
onChange={(event) => setGithubRepo(event.target.value)}
placeholder="github.com/owner/repo"
className="w-full rounded-xl border border-border/70 bg-background px-4 py-2 text-sm text-foreground shadow-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60 sm:min-w-[260px]"
/>
<Button type="submit" size="sm" className="gap-2">
Scan repo
<Github className="h-4 w-4" />
</Button>
</div>
</form>
</div>

<form
onSubmit={handleGithubSubmit}
className="flex flex-col gap-3 rounded-3xl border border-border/70 bg-background/95 p-6 shadow-sm sm:flex-row sm:items-center"
>
<div className="flex-1 space-y-2 text-left">
<p className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
Scan a GitHub repository
</p>
<p className="text-sm text-muted-foreground">
Paste an owner/repo or URL and we&apos;ll prefill the wizard with detected tech and tooling.
</p>
</div>
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row">
<input
type="text"
value={githubRepo}
onChange={(event) => setGithubRepo(event.target.value)}
placeholder="github.com/owner/repo"
className="w-full rounded-xl border border-border/70 bg-background px-4 py-2 text-sm text-foreground shadow-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60 sm:min-w-[260px]"
/>
<Button type="submit" size="sm" className="gap-2">
Scan repo
<Github className="h-4 w-4" />
</Button>
</div>
</form>
</motion.div>
</motion.div>
</motion.section>
Expand Down