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
61 changes: 17 additions & 44 deletions apps/web/src/app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { siteConfig } from '@/config/site';
import { Section } from '@/components/ui/section';
import { HeroSection } from '@/components/marketing/hero-section';
import { ProblemSection } from '@/components/marketing/problem-section';
import { HowRecallWorksSection } from '@/components/marketing/how-recall-works-section';
import { CommandBlock } from '@/components/marketing/command-block';
import { FeatureCard } from '@/components/marketing/feature-card';

const problemRows = [
{
Expand Down Expand Up @@ -32,24 +33,6 @@ const problemRows = [
},
];

const steps = [
{
title: 'Scan',
description:
'Recall walks the repository deterministically and analyzes structure, workspaces, entry points, and conventions — no network access, no AI provider required.',
},
{
title: 'Rank',
description:
'For a given task, files are ranked with explainable, local signals: filename and symbol matches, import-graph proximity, and workspace locality.',
},
{
title: 'Generate',
description:
'Recall writes evidence-backed Markdown and JSON context into `.recall/`, ready to paste into or pipe to a coding agent.',
},
];

const features = [
{
title: 'Deterministic, local-first',
Expand Down Expand Up @@ -93,6 +76,10 @@ export default function MarketingPage() {
<>
<HeroSection />

<ProblemSection />

<HowRecallWorksSection />

<Section aria-labelledby="problem-heading">
<h2 id="problem-heading" className="text-2xl font-semibold tracking-tight text-foreground">
CLAUDE.md and AGENTS.md vs. Recall
Expand Down Expand Up @@ -131,37 +118,23 @@ export default function MarketingPage() {
</div>
</Section>

<Section aria-labelledby="how-it-works-heading">
<h2
id="how-it-works-heading"
className="text-2xl font-semibold tracking-tight text-foreground"
>
How it works
</h2>
<div className="mt-8 grid gap-8 sm:grid-cols-3">
{steps.map((step, index) => (
<div key={step.title}>
<span className="text-sm font-mono text-muted-foreground">
{String(index + 1).padStart(2, '0')}
</span>
<h3 className="mt-2 text-sm font-semibold text-foreground">{step.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{step.description}
</p>
</div>
))}
</div>
</Section>

<Section aria-labelledby="features-heading">
<h2 id="features-heading" className="text-2xl font-semibold tracking-tight text-foreground">
What Recall generates
</h2>
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<dl className="mt-8 divide-y divide-border">
{features.map((feature) => (
<FeatureCard key={feature.title} {...feature} />
<div
key={feature.title}
className="grid gap-x-8 gap-y-2 py-6 sm:grid-cols-[220px_1fr] sm:py-8"
>
<dt className="text-sm font-semibold text-foreground">{feature.title}</dt>
<dd className="text-sm leading-relaxed text-muted-foreground">
{feature.description}
</dd>
</div>
))}
</div>
</dl>
</Section>

<Section aria-labelledby="terminal-heading">
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@
outline-offset: 2px;
}

/* Accent at low opacity, not a browser default (design-system.md §3). */
::selection {
background-color: hsl(var(--accent) / 0.25);
color: hsl(var(--foreground));
}

@media (prefers-reduced-motion: reduce) {
*,
*::before,
Expand Down
17 changes: 0 additions & 17 deletions apps/web/src/components/marketing/feature-card.tsx

This file was deleted.

136 changes: 136 additions & 0 deletions apps/web/src/components/marketing/how-recall-works-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Fragment } from 'react';
import { Section } from '@/components/ui/section';

interface Step {
number: string;
title: string;
description: string;
}

const steps: Step[] = [
{
number: '01',
title: 'Scan Repository',
description: 'Analyze project structure, frameworks, dependencies and conventions.',
},
{
number: '02',
title: 'Build Memory',
description: 'Generate architecture, decisions, conventions, risks and technical debt.',
},
{
number: '03',
title: 'Understand Task',
description: 'Rank the most relevant files for the current request.',
},
{
number: '04',
title: 'Provide Context',
description: 'Produce deterministic context that any coding agent can immediately use.',
},
];

// Reused verbatim from HeroTerminal and ProblemSection's "With Recall" card
// — not new content, just referenced again at the point where it's actually
// produced.
const memoryCategories = ['Architecture', 'Conventions', 'Decisions', 'Risks', 'Technical debt'];

/**
* One equally-sized step in the flow. Real headings (h3) carry the
* accessible structure — connectors between cards are purely decorative
* (design-system.md §7 card rules: border, bg-card, no shadow, no hover,
* since these aren't clickable).
*/
function StepCard({ step }: { step: Step }) {
return (
<div className="flex-1 rounded-lg border border-border bg-card p-6">
<span className="font-mono text-sm text-muted-foreground">{step.number}</span>
<h3 className="mt-3 text-sm font-semibold text-foreground">{step.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">{step.description}</p>
</div>
);
}

/**
* A single hairline connecting adjacent steps — vertical when stacked
* (mobile), horizontal in the row (desktop). Decorative only: the real
* sequence is already conveyed by the step numbers and heading order.
*
* On desktop the line is pinned to a fixed offset (card padding + half the
* step numeral's line-height: 24px + 10px) rather than centered against
* whichever card happens to be tallest — so it runs precisely through the
* numerals themselves regardless of how much a description wraps, instead
* of landing at an approximate row-center.
*/
function StepConnector() {
return (
<div
aria-hidden
className="h-6 w-px self-center bg-border lg:mt-[34px] lg:h-px lg:w-10 lg:flex-none lg:self-start"
/>
);
}

/**
* A borderless data-flow line, not another terminal: the step cards above
* already show the four *stages* of the process, so this deliberately
* avoids repeating that same "boxes + connectors" grammar, and avoids
* repeating the Hero's own `$ recall context` terminal a second time in the
* same visit. Instead it states the *transformation* as plain, precise
* typography — repository in, structured memory in the middle, coding
* agent out — closer to a type signature or a pipeline notation than a UI
* pattern. Every label is reused verbatim from elsewhere on the page; no
* new copy.
*/
function MemoryFlow() {
return (
<div
role="img"
aria-label="Repository transforms into structured memory — architecture, conventions, decisions, risks, and technical debt — which becomes available to the coding agent. Context ready."
>
<div
aria-hidden
className="flex flex-col gap-x-3 gap-y-2 font-mono text-sm sm:flex-row sm:flex-wrap sm:items-baseline"
>
<span className="text-foreground">Repository</span>
<span className="text-muted-foreground">→</span>
<span className="text-muted-foreground">{memoryCategories.join(' · ')}</span>
<span className="text-muted-foreground">→</span>
<span className="text-foreground">Coding agent</span>
</div>
<p aria-hidden className="mt-3 text-sm text-muted-foreground">
Context ready.
</p>
</div>
);
}

export function HowRecallWorksSection() {
return (
<Section aria-labelledby="how-recall-works-heading">
<h2
id="how-recall-works-heading"
className="text-2xl font-semibold tracking-tight text-foreground"
>
How Recall works
</h2>
<p className="mt-3 max-w-2xl text-muted-foreground">
Recall analyzes your repository once, builds a structured project memory, and gives every
future AI session immediate access to the parts of the codebase that matter.
</p>

<div className="mt-10 flex flex-col lg:flex-row">
{steps.map((step, index) => (
<Fragment key={step.number}>
<StepCard step={step} />
{index < steps.length - 1 ? <StepConnector /> : null}
</Fragment>
))}
</div>

<div className="mt-10">
<MemoryFlow />
</div>
</Section>
);
}
109 changes: 109 additions & 0 deletions apps/web/src/components/marketing/problem-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type { ReactNode } from 'react';
import { Check } from 'lucide-react';
import { Section } from '@/components/ui/section';
import { cn } from '@/lib/utils';

const disappearingContext = [
'Architecture decisions.',
'Naming conventions.',
'Important dependencies.',
'Project history.',
'Previous trade-offs.',
];

const transcript = [
{ speaker: 'Developer', message: 'How does authentication work?' },
{ speaker: 'Agent', message: 'I do not know this repository yet.' },
{
speaker: 'Agent',
message: 'Can you explain the architecture, conventions, and relevant files?',
},
];

const availableMemory = ['Architecture', 'Conventions', 'Decisions', 'Risks', 'Technical debt'];

interface ComparisonCardProps {
title: string;
children: ReactNode;
/**
* "Without Recall" renders its title in muted-foreground rather than
* foreground — a deliberate, tokens-only hierarchy choice (§4 rule 3:
* "Color, not size, is the secondary hierarchy signal") that reads the
* left column as the gap being described and the right column as the
* resolution, without introducing any new color or icon.
*/
muted?: boolean;
}

/** Shared card shell for the two comparison columns — same border/padding/radius as every other card on the site (design-system.md §7), never clickable, so no hover state. */
function ComparisonCard({ title, children, muted }: ComparisonCardProps) {
return (
<div className="rounded-lg border border-border bg-card p-6">
<h3
className={cn('text-sm font-semibold', muted ? 'text-muted-foreground' : 'text-foreground')}
>
{title}
</h3>
{children}
</div>
);
}

export function ProblemSection() {
return (
<Section aria-labelledby="context-loss-heading">
<div className="max-w-2xl">
<h2
id="context-loss-heading"
className="text-2xl font-semibold tracking-tight text-foreground"
>
Every new session starts here.
</h2>

<p className="mt-3 text-muted-foreground">
Most coding agents only know what you paste into the current conversation.
</p>

<ul className="mt-4 flex flex-col gap-1 text-muted-foreground">
{disappearingContext.map((item) => (
<li key={item}>{item}</li>
))}
</ul>

<p className="mt-6 text-muted-foreground">They disappear.</p>
<p className="mt-1 text-muted-foreground">So every new session starts from zero.</p>
</div>

<div className="mt-10 grid gap-6 lg:grid-cols-2">
<ComparisonCard title="Without Recall" muted>
<div className="mt-4 flex flex-col gap-4">
{transcript.map((turn, index) => (
<div key={index}>
<p className="text-sm text-muted-foreground">{turn.speaker}:</p>
<p className="mt-1 text-sm text-foreground">{turn.message}</p>
</div>
))}
</div>
</ComparisonCard>

<ComparisonCard title="With Recall">
<ul className="mt-4 flex flex-col gap-2">
{availableMemory.map((item) => (
<li key={item} className="flex items-center gap-2 text-sm text-foreground">
<Check aria-hidden className="h-3.5 w-3.5 shrink-0 text-success" />
{item}
</li>
))}
</ul>
<p className="mt-4 text-sm text-muted-foreground">Context ready.</p>
</ComparisonCard>
</div>

<p className="mx-auto mt-10 max-w-xl text-center text-muted-foreground">
Recall does not replace the coding agent.
<br />
It gives the agent the repository context it did not have.
</p>
</Section>
);
}
2 changes: 1 addition & 1 deletion apps/web/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cva } from 'class-variance-authority';

export const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
'inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
Expand Down
Loading
Loading