Skip to content

Commit db013c7

Browse files
feat(landing): specifications section with featured examples
- reorder landing: hero → specifications → libraries (was: hero → libraries → specs) - rewrite the specs intro to actually define what a spec is, how implementations derive from it, and how anyone can propose one via github. lowercase throughout per style-guide doctrine - drop the decorative "spec pipeline" code-block from the landing — the pipeline story lives on /about now - new useFeaturedSpecs hook pulls top 4 implementations from the cached /insights/dashboard endpoint - 2x2 featured grid below the intro shows those implementations as mini terminal-window cards (top-bar $ prompt, image body, bottom-bar >>> prompt, library label) so the shape rhymes with PlotOfTheDay - skeleton thumbs preserve grid height while data loads to avoid CLS Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d439ebe commit db013c7

2 files changed

Lines changed: 217 additions & 115 deletions

File tree

app/src/hooks/useFeaturedSpecs.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useEffect, useState } from 'react';
2+
import { API_URL } from '../constants';
3+
4+
export interface FeaturedImpl {
5+
spec_id: string;
6+
spec_title: string;
7+
library_id: string;
8+
quality_score: number;
9+
preview_url: string | null;
10+
}
11+
12+
/**
13+
* Fetches a handful of high-quality implementations to feature on the landing
14+
* page as spec examples. Backed by `/insights/dashboard` (server-cached), we
15+
* just read its `top_implementations` slice.
16+
*/
17+
export function useFeaturedSpecs(count: number = 4): FeaturedImpl[] | null {
18+
const [featured, setFeatured] = useState<FeaturedImpl[] | null>(null);
19+
20+
useEffect(() => {
21+
let cancelled = false;
22+
fetch(`${API_URL}/insights/dashboard`)
23+
.then(r => (r.ok ? r.json() : null))
24+
.then((data: { top_implementations?: FeaturedImpl[] } | null) => {
25+
if (cancelled || !data?.top_implementations) return;
26+
setFeatured(data.top_implementations.slice(0, count));
27+
})
28+
.catch(() => {});
29+
return () => {
30+
cancelled = true;
31+
};
32+
}, [count]);
33+
34+
return featured;
35+
}

app/src/pages/LandingPage.tsx

Lines changed: 182 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,23 @@
11
import { Helmet } from 'react-helmet-async';
22
import Box from '@mui/material/Box';
3-
import Container from '@mui/material/Container';
43
import { Link as RouterLink } from 'react-router-dom';
54

6-
import { MastheadRule } from '../components/MastheadRule';
7-
import { NavBar } from '../components/NavBar';
85
import { HeroSection } from '../components/HeroSection';
96
import { NumbersStrip } from '../components/NumbersStrip';
107
import { LibrariesSection } from '../components/LibrariesSection';
118
import { SectionHeader } from '../components/SectionHeader';
12-
import { Footer } from '../components/Footer';
13-
import { useAppData, useAnalytics } from '../hooks';
9+
import { useAppData } from '../hooks';
1410
import { usePlotOfTheDay } from '../hooks/usePlotOfTheDay';
11+
import { useFeaturedSpecs, type FeaturedImpl } from '../hooks/useFeaturedSpecs';
1512
import { GITHUB_URL } from '../constants';
16-
import { colors, typography } from '../theme';
13+
import { specPath } from '../utils/paths';
14+
import { buildSrcSet, getFallbackSrc } from '../utils/responsiveImage';
15+
import { colors, fontSize, semanticColors, typography } from '../theme';
1716

1817
export function LandingPage() {
1918
const { librariesData, stats } = useAppData();
20-
const { trackEvent } = useAnalytics();
2119
const potd = usePlotOfTheDay();
22-
23-
// Every section on the landing page lives on the catalog tier so the grid
24-
// stays consistent from hero to footer on ultrawide displays.
25-
const catalogContainerSx = {
26-
px: { xs: 2, sm: 4, md: 8, lg: 12 },
27-
maxWidth: 'var(--max-catalog)',
28-
mx: 'auto',
29-
} as const;
20+
const featured = useFeaturedSpecs(4);
3021

3122
return (
3223
<>
@@ -39,58 +30,41 @@ export function LandingPage() {
3930
<link rel="canonical" href="https://anyplot.ai/" />
4031
</Helmet>
4132

42-
<Box sx={{ minHeight: '100vh', bgcolor: 'var(--bg-page)' }}>
43-
<Container maxWidth={false} sx={catalogContainerSx}>
44-
<MastheadRule />
45-
<NavBar />
46-
</Container>
47-
48-
{/* Hero — fills the viewport so the fold shows only the hero */}
49-
<Box
50-
component="section"
51-
sx={{
52-
display: 'flex',
53-
flexDirection: 'column',
54-
justifyContent: 'center',
55-
minHeight: { xs: 'auto', md: 'calc(88svh - 88px)' },
56-
}}
57-
>
58-
<Container maxWidth={false} sx={catalogContainerSx}>
59-
<HeroSection potd={potd} />
60-
<NumbersStrip stats={stats} />
61-
</Container>
62-
</Box>
63-
64-
<Container maxWidth={false} sx={catalogContainerSx}>
65-
<LibrariesSection
66-
libraries={librariesData}
67-
onLibraryClick={() => {}}
68-
widthTier="catalog"
69-
headerStyle="prompt"
70-
/>
71-
</Container>
33+
{/* Hero — fills the viewport so the fold shows only the hero */}
34+
<Box
35+
component="section"
36+
sx={{
37+
display: 'flex',
38+
flexDirection: 'column',
39+
justifyContent: 'center',
40+
minHeight: { xs: 'auto', md: 'calc(88svh - 88px)' },
41+
}}
42+
>
43+
<HeroSection potd={potd} />
44+
<NumbersStrip stats={stats} />
45+
</Box>
7246

73-
<Container maxWidth={false} sx={catalogContainerSx}>
74-
<SpecsSection specCount={stats?.specs} />
75-
</Container>
47+
<SpecsSection specCount={stats?.specs} featured={featured} />
7648

77-
<Container maxWidth={false} sx={catalogContainerSx}>
78-
<Footer onTrackEvent={trackEvent} />
79-
</Container>
80-
</Box>
49+
<LibrariesSection
50+
libraries={librariesData}
51+
onLibraryClick={() => {}}
52+
widthTier="catalog"
53+
headerStyle="prompt"
54+
/>
8155
</>
8256
);
8357
}
8458

8559
/**
86-
* Specs section — catalog-tier layout. On narrow screens it stacks; on wide
87-
* screens the prompt/title sits left and the narrative + action chips sit
88-
* right, so the section doesn't leave a rectangle of whitespace on 2200px.
60+
* Specs section — catalog-tier layout. Description sits on the left, a 2×2
61+
* preview grid of featured implementations on the right so visitors see what
62+
* specs become.
8963
*/
90-
function SpecsSection({ specCount }: { specCount?: number }) {
64+
function SpecsSection({ specCount, featured }: { specCount?: number; featured: FeaturedImpl[] | null }) {
9165
return (
9266
<Box sx={{ py: { xs: 6, md: 10 } }}>
93-
<SectionHeader prompt="$" title={<em>specs</em>} linkText="view all" linkTo="/specs" />
67+
<SectionHeader prompt="$" title={<em>specifications</em>} linkText="view all" linkTo="/specs" />
9468

9569
<Box
9670
sx={{
@@ -100,72 +74,58 @@ function SpecsSection({ specCount }: { specCount?: number }) {
10074
alignItems: 'start',
10175
}}
10276
>
103-
<Box
104-
sx={{
105-
fontFamily: typography.serif,
106-
fontSize: { xs: '1rem', md: '1.25rem' },
107-
lineHeight: 1.55,
108-
color: 'var(--ink-soft)',
109-
fontWeight: 300,
110-
maxWidth: '52ch',
111-
}}
112-
>
113-
Every plot lives as a library-agnostic markdown spec.{' '}
114-
<Box component="span" sx={{ color: 'var(--ink)' }}>
115-
One source, nine libraries.
116-
</Box>{' '}
117-
Drafted by AI from a human idea, approved before any code is
118-
generated — so the intent stays with the humans.
119-
</Box>
120-
121-
<Box
122-
sx={{
123-
display: 'flex',
124-
flexDirection: 'column',
125-
gap: 2,
126-
fontFamily: typography.mono,
127-
}}
128-
>
129-
<Box sx={{ fontSize: '12px', color: 'var(--ink-muted)', mb: 1 }}>
130-
// spec pipeline
77+
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
78+
<Box
79+
sx={{
80+
fontFamily: typography.serif,
81+
fontSize: { xs: '1rem', md: '1.25rem' },
82+
lineHeight: 1.55,
83+
color: 'var(--ink-soft)',
84+
fontWeight: 300,
85+
maxWidth: '52ch',
86+
}}
87+
>
88+
a spec is a short, library-agnostic markdown document —{' '}
89+
<Box component="span" sx={{ color: 'var(--ink)' }}>
90+
what the plot shows, what data it needs, and when to use it.
91+
</Box>{' '}
92+
from that single source, implementations are generated for every
93+
supported library. new specs come from github issues; anyone can
94+
propose one.
13195
</Box>
132-
{[
133-
['idea ', 'human-submitted'],
134-
['spec ', 'ai-drafted, human-approved'],
135-
['code ', 'ai-generated'],
136-
['review ', 'ai-evaluated'],
137-
].map(([k, v]) => (
138-
<Box
139-
key={k}
140-
sx={{
141-
display: 'flex',
142-
gap: 1.5,
143-
fontSize: '13px',
144-
lineHeight: 1.6,
145-
}}
146-
>
147-
<Box component="span" sx={{ color: 'var(--ink)', whiteSpace: 'pre' }}>
148-
{k}
149-
</Box>
150-
<Box component="span" sx={{ color: 'var(--ink-muted)' }}>
151-
152-
</Box>
153-
<Box component="span" sx={{ color: 'var(--ink-soft)' }}>
154-
{v}
155-
</Box>
156-
</Box>
157-
))}
158-
159-
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', mt: 2 }}>
96+
97+
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
16098
<ActionChip to="/specs" label={`browse ${specCount ?? ''} specs`.trim()} />
161-
<ActionChip href={GITHUB_URL} label="github" external />
99+
<ActionChip
100+
href={`${GITHUB_URL}/issues/new?template=request-new-plot.yml`}
101+
label="suggest spec"
102+
external
103+
/>
162104
</Box>
163105
</Box>
106+
107+
<FeaturedGrid featured={featured} />
164108
</Box>
165109
</Box>
166110
);
167111
}
168112

113+
function FeaturedGrid({ featured }: { featured: FeaturedImpl[] | null }) {
114+
return (
115+
<Box
116+
sx={{
117+
display: 'grid',
118+
gridTemplateColumns: { xs: 'repeat(2, 1fr)', sm: 'repeat(2, 1fr)' },
119+
gap: 2,
120+
}}
121+
>
122+
{(featured ?? Array.from({ length: 4 }, () => null)).map((item, i) => (
123+
<FeaturedThumb key={item?.spec_id ? `${item.spec_id}-${item.library_id}` : `skel-${i}`} item={item} />
124+
))}
125+
</Box>
126+
);
127+
}
128+
169129
interface ActionChipProps {
170130
to?: string;
171131
href?: string;
@@ -188,8 +148,8 @@ function ActionChip({ to, href, label, external }: ActionChipProps) {
188148
display: 'inline-flex',
189149
alignItems: 'baseline',
190150
transition: 'color 0.2s, background 0.2s',
191-
'&:hover': { color: colors.primary, bgcolor: 'var(--bg-elevated)' },
192151
'&::before': { content: '"."', color: 'inherit' },
152+
'&:hover': { color: colors.primary, bgcolor: 'var(--bg-elevated)' },
193153
} as const;
194154

195155
if (href) {
@@ -211,3 +171,110 @@ function ActionChip({ to, href, label, external }: ActionChipProps) {
211171
</Box>
212172
);
213173
}
174+
175+
/**
176+
* Compact terminal-window card — mirrors PlotOfTheDay's shape (top bar with
177+
* `$` prompt, square-ish image body, bottom bar with `>>>` output) so the
178+
* featured grid feels part of the same language as the hero POTD.
179+
*/
180+
function FeaturedThumb({ item }: { item: FeaturedImpl | null }) {
181+
const cardSx = {
182+
display: 'flex',
183+
flexDirection: 'column' as const,
184+
borderRadius: 2,
185+
overflow: 'hidden',
186+
border: `1px solid ${colors.gray[200]}`,
187+
boxShadow: '0 2px 12px rgba(0,0,0,0.04)',
188+
transition: 'box-shadow 0.25s, transform 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
189+
textDecoration: 'none',
190+
color: 'inherit',
191+
bgcolor: 'var(--bg-surface)',
192+
'&:hover': {
193+
transform: 'translateY(-2px)',
194+
boxShadow: '0 6px 24px rgba(0,0,0,0.08)',
195+
},
196+
} as const;
197+
198+
const barSx = {
199+
display: 'flex',
200+
alignItems: 'center',
201+
px: 1.25,
202+
py: 0.5,
203+
bgcolor: colors.gray[100],
204+
gap: 0.75,
205+
fontFamily: typography.mono,
206+
fontSize: fontSize.xxs,
207+
} as const;
208+
209+
if (!item || !item.preview_url) {
210+
return (
211+
<Box sx={cardSx}>
212+
<Box sx={{ ...barSx, borderBottom: `1px solid ${colors.gray[200]}` }}>
213+
<Box component="span" sx={{ color: colors.gray[300] }}>$</Box>
214+
<Box component="span" sx={{ color: colors.gray[300] }}>&nbsp;</Box>
215+
</Box>
216+
<Box sx={{ aspectRatio: '16 / 10', bgcolor: 'var(--bg-elevated)' }} />
217+
<Box sx={{ ...barSx, borderTop: `1px solid ${colors.gray[200]}` }}>
218+
<Box component="span" sx={{ color: colors.gray[300] }}>&gt;&gt;&gt;</Box>
219+
</Box>
220+
</Box>
221+
);
222+
}
223+
224+
return (
225+
<Box component={RouterLink} to={specPath(item.spec_id, item.library_id)} sx={cardSx}>
226+
{/* Top bar — mimics POTD's `$ python …` prompt */}
227+
<Box sx={{ ...barSx, borderBottom: `1px solid ${colors.gray[200]}` }}>
228+
<Box component="span" sx={{ color: colors.primary, fontWeight: 600 }}>$</Box>
229+
<Box
230+
component="span"
231+
sx={{
232+
color: semanticColors.mutedText,
233+
flex: 1,
234+
whiteSpace: 'nowrap',
235+
overflow: 'hidden',
236+
textOverflow: 'ellipsis',
237+
}}
238+
>
239+
{item.spec_id}.py
240+
</Box>
241+
</Box>
242+
243+
{/* Image body */}
244+
<Box sx={{ aspectRatio: '16 / 10', overflow: 'hidden' }}>
245+
<Box component="picture" sx={{ display: 'block', width: '100%', height: '100%' }}>
246+
<source type="image/webp" srcSet={buildSrcSet(item.preview_url, 'webp')} sizes="(max-width: 599px) 50vw, 25vw" />
247+
<source type="image/png" srcSet={buildSrcSet(item.preview_url, 'png')} sizes="(max-width: 599px) 50vw, 25vw" />
248+
<Box
249+
component="img"
250+
src={getFallbackSrc(item.preview_url)}
251+
alt={item.spec_title}
252+
loading="lazy"
253+
sx={{ display: 'block', width: '100%', height: '100%', objectFit: 'cover' }}
254+
/>
255+
</Box>
256+
</Box>
257+
258+
{/* Bottom bar — mimics POTD's `>>> plot.png saved | library` */}
259+
<Box sx={{ ...barSx, borderTop: `1px solid ${colors.gray[200]}` }}>
260+
<Box component="span" sx={{ color: colors.primary }}>&gt;&gt;&gt;</Box>
261+
<Box
262+
component="span"
263+
sx={{
264+
color: semanticColors.mutedText,
265+
flex: 1,
266+
whiteSpace: 'nowrap',
267+
overflow: 'hidden',
268+
textOverflow: 'ellipsis',
269+
}}
270+
>
271+
plot.png saved
272+
</Box>
273+
<Box component="span" sx={{ color: colors.gray[300] }}></Box>
274+
<Box component="span" sx={{ color: semanticColors.mutedText, flexShrink: 0 }}>
275+
{item.library_id}
276+
</Box>
277+
</Box>
278+
</Box>
279+
);
280+
}

0 commit comments

Comments
 (0)