diff --git a/agentic/commands/audit/design-auditor.md b/agentic/commands/audit/design-auditor.md
index 4bd26e6907..b82d9b00b2 100644
--- a/agentic/commands/audit/design-auditor.md
+++ b/agentic/commands/audit/design-auditor.md
@@ -15,7 +15,7 @@ Almost every finding you emit is `DIMENSION: looks`. Tag the rare exception accu
- **Exemplary gap**: what would a best-in-class product site have here that anyplot lacks (a polished design system, motion/micro-interactions, a cohesive empty-state language)? Emit high-value gaps as `looks` findings.
**How to work:**
-1. Glob `app/src/theme/**`, `app/src/components/**`, `app/src/pages/**`, `app/src/styles/**`
+1. Glob `app/src/theme/**`, `app/src/components/**`, `app/src/sections/**`, `app/src/layouts/**`, `app/src/pages/**`, `app/src/styles/**`
2. Read the theme definition(s) and `useThemeMode` to learn the token system and how light/dark are derived
3. Grep for hardcoded visual values that bypass the theme: `#[0-9a-fA-F]{3,8}`, `rgb\(`, `style=\{\{`, `px`-literal sizing in `sx`, `color:\s*['"]`, `backgroundColor`
4. Read a representative set of pages/components to see how consistently theme tokens vs. literals are used
diff --git a/app/src/layouts/BareLayout.tsx b/app/src/layouts/BareLayout.tsx
deleted file mode 100644
index b1845f2f3e..0000000000
--- a/app/src/layouts/BareLayout.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Outlet } from 'react-router-dom';
-
-import Box from '@mui/material/Box';
-
-/**
- * Layout for routes that opt out of the global chrome — interactive plot
- * embeds, debug surfaces. Pages own their full viewport.
- */
-export function BareLayout() {
- return (
-
-
-
- );
-}
diff --git a/app/src/layouts/index.ts b/app/src/layouts/index.ts
index 79d77bb75d..991a275a6e 100644
--- a/app/src/layouts/index.ts
+++ b/app/src/layouts/index.ts
@@ -1,4 +1,3 @@
-export * from 'src/layouts/BareLayout';
export * from 'src/layouts/Footer';
export * from 'src/layouts/Layout';
export * from 'src/layouts/MastheadRule';
diff --git a/app/src/sections/landing/PaletteStrip.tsx b/app/src/sections/landing/PaletteStrip.tsx
deleted file mode 100644
index 9b42b6656a..0000000000
--- a/app/src/sections/landing/PaletteStrip.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import Box from '@mui/material/Box';
-
-// imprint palette — 8 categorical hues in hybrid-v3 sort order
-const DEFAULT_SWATCHES = [
- '#009E73',
- '#C475FD',
- '#4467A3',
- '#BD8233',
- '#AE3030',
- '#2ABCCD',
- '#954477',
- '#99B314',
-];
-
-interface PaletteStripProps {
- /** Maximum width in pixels (default 400). Set to `null` for no cap. */
- maxWidth?: number | null;
- /** Strip height in pixels (default 40). */
- height?: number;
- /** Top margin (MUI spacing units, default 5). */
- mt?: number;
- /** Override the default 8-hex imprint set (e.g. to render an alternate sort). */
- hexes?: string[];
-}
-
-export function PaletteStrip({
- maxWidth = 400,
- height = 40,
- mt = 5,
- hexes,
-}: PaletteStripProps = {}) {
- const SWATCHES = hexes ?? DEFAULT_SWATCHES;
- return (
-
- {SWATCHES.map((color, i) => (
-
- ))}
-
- );
-}
diff --git a/app/src/sections/landing/PlotOfTheDay.test.tsx b/app/src/sections/landing/PlotOfTheDay.test.tsx
deleted file mode 100644
index 277dbc4720..0000000000
--- a/app/src/sections/landing/PlotOfTheDay.test.tsx
+++ /dev/null
@@ -1,224 +0,0 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-
-import { render, screen, waitFor } from 'src/test-utils';
-
-const trackEvent = vi.fn();
-
-vi.mock('src/hooks', async () => {
- const actual = await vi.importActual('src/hooks');
- return {
- ...actual,
- useAnalytics: () => ({ trackEvent, trackPageview: vi.fn() }),
- };
-});
-
-import { PlotOfTheDay } from 'src/sections/landing/PlotOfTheDay';
-
-// Mock sessionStorage
-const sessionStorageMock: Record = {};
-const sessionStorageStub = {
- getItem: vi.fn((key: string) => sessionStorageMock[key] ?? null),
- setItem: vi.fn((key: string, value: string) => {
- sessionStorageMock[key] = value;
- }),
- removeItem: vi.fn((key: string) => {
- delete sessionStorageMock[key];
- }),
- clear: vi.fn(() => {
- Object.keys(sessionStorageMock).forEach(k => delete sessionStorageMock[k]);
- }),
- get length() {
- return Object.keys(sessionStorageMock).length;
- },
- key: vi.fn(() => null),
-};
-
-const mockData = {
- spec_id: 'scatter-basic',
- spec_title: 'Basic Scatter Plot',
- description: 'A scatter plot',
- library_id: 'matplotlib',
- library_name: 'Matplotlib',
- language: 'python',
- quality_score: 9,
- preview_url: 'https://cdn.example.com/plots/scatter-basic/matplotlib/plot.png',
- image_description: 'Shows data points with clear labels',
- library_version: '3.8.0',
- python_version: '3.12',
- language_version: '3.12',
- date: '2026-04-11',
-};
-
-describe('PlotOfTheDay', () => {
- let fetchMock: ReturnType;
-
- beforeEach(() => {
- fetchMock = vi.fn();
- vi.stubGlobal('fetch', fetchMock);
- // Reset sessionStorage mock state
- Object.keys(sessionStorageMock).forEach(k => delete sessionStorageMock[k]);
- vi.stubGlobal('sessionStorage', sessionStorageStub);
- sessionStorageStub.getItem.mockClear();
- sessionStorageStub.setItem.mockClear();
- trackEvent.mockClear();
- });
-
- afterEach(() => {
- vi.restoreAllMocks();
- });
-
- it('renders a placeholder while loading', () => {
- // Never resolve the fetch, so component stays in loading state
- fetchMock.mockReturnValue(new Promise(() => {}));
-
- const { container } = render();
-
- // The loading state renders a Box with minHeight for CLS prevention
- const placeholder = container.firstChild as HTMLElement;
- expect(placeholder).toBeInTheDocument();
- // Should not show any text content yet
- expect(screen.queryByText('plot of the day')).not.toBeInTheDocument();
- });
-
- it('shows the card after successful fetch', async () => {
- fetchMock.mockResolvedValueOnce({
- ok: true,
- json: () => Promise.resolve(mockData),
- });
-
- render();
-
- await waitFor(() => {
- expect(screen.getByText('plot of the day')).toBeInTheDocument();
- });
-
- expect(screen.getByText('Basic Scatter Plot')).toBeInTheDocument();
- });
-
- it('shows image description when present', async () => {
- fetchMock.mockResolvedValueOnce({
- ok: true,
- json: () => Promise.resolve(mockData),
- });
-
- render();
-
- await waitFor(() => {
- expect(screen.getByText(/Shows data points with clear labels/)).toBeInTheDocument();
- });
- });
-
- it('shows library version and python version in bottom bar', async () => {
- fetchMock.mockResolvedValueOnce({
- ok: true,
- json: () => Promise.resolve(mockData),
- });
-
- render();
-
- await waitFor(() => {
- expect(screen.getByText(/Matplotlib 3\.8\.0/)).toBeInTheDocument();
- expect(screen.getByText(/Python 3\.12/)).toBeInTheDocument();
- });
- });
-
- it('returns null immediately when dismissed via sessionStorage', () => {
- sessionStorageMock['potd_dismissed'] = 'true';
-
- const { container } = render();
-
- // Dismissed state returns null — no DOM output
- expect(container.firstChild).toBeNull();
- // Should not have fetched
- expect(fetchMock).not.toHaveBeenCalled();
- });
-
- it('returns null after API error', async () => {
- fetchMock.mockResolvedValueOnce({ ok: false });
-
- const { container } = render();
-
- await waitFor(() => {
- // After loading finishes with error, data is null so component returns null
- expect(container.firstChild).toBeNull();
- });
- });
-
- it('dismisses on close button click', async () => {
- fetchMock.mockResolvedValueOnce({
- ok: true,
- json: () => Promise.resolve(mockData),
- });
-
- const { userEvent } = await import('src/test-utils');
- const user = userEvent.setup();
-
- const { container } = render();
-
- await waitFor(() => {
- expect(screen.getByText('plot of the day')).toBeInTheDocument();
- });
-
- const dismissButton = screen.getByLabelText('Dismiss plot of the day');
- await user.click(dismissButton);
-
- // After dismiss, component should return null
- expect(container.firstChild).toBeNull();
- expect(sessionStorageStub.setItem).toHaveBeenCalledWith('potd_dismissed', 'true');
- expect(trackEvent).toHaveBeenCalledWith(
- 'potd_dismiss',
- expect.objectContaining({ spec: 'scatter-basic', library: 'matplotlib' })
- );
- });
-
- it('tracks nav_click when the image, title and source link are clicked', async () => {
- fetchMock.mockResolvedValueOnce({
- ok: true,
- json: () => Promise.resolve(mockData),
- });
-
- const { userEvent } = await import('src/test-utils');
- const user = userEvent.setup();
-
- render();
-
- await waitFor(() => {
- expect(screen.getByText('plot of the day')).toBeInTheDocument();
- });
-
- await user.click(screen.getByText('Basic Scatter Plot'));
- expect(trackEvent).toHaveBeenCalledWith(
- 'nav_click',
- expect.objectContaining({ source: 'potd_title' })
- );
-
- const sourceLink = screen.getByText(/python plots\/scatter-basic\/matplotlib\.py/);
- expect(sourceLink.getAttribute('href')).toMatch(
- /\/blob\/main\/plots\/scatter-basic\/implementations\/python\/matplotlib\.py$/
- );
- await user.click(sourceLink);
- expect(trackEvent).toHaveBeenCalledWith(
- 'nav_click',
- expect.objectContaining({ source: 'potd_source_link' })
- );
- });
-
- it('hides library version when it is "unknown"', async () => {
- const dataWithUnknownVersion = { ...mockData, library_version: 'unknown' };
- fetchMock.mockResolvedValueOnce({
- ok: true,
- json: () => Promise.resolve(dataWithUnknownVersion),
- });
-
- render();
-
- await waitFor(() => {
- expect(screen.getByText('plot of the day')).toBeInTheDocument();
- });
-
- // Should show "Matplotlib" without " unknown" appended
- // The bottom bar text node should contain just "Matplotlib" followed by python version
- const bottomText = screen.getByText(/Matplotlib/);
- expect(bottomText.textContent).not.toContain('unknown');
- });
-});
diff --git a/app/src/sections/landing/PlotOfTheDay.tsx b/app/src/sections/landing/PlotOfTheDay.tsx
deleted file mode 100644
index cb3e4f6444..0000000000
--- a/app/src/sections/landing/PlotOfTheDay.tsx
+++ /dev/null
@@ -1,388 +0,0 @@
-import { useCallback, useEffect, useState } from 'react';
-
-import { Link as RouterLink } from 'react-router-dom';
-
-import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
-import CloseIcon from '@mui/icons-material/Close';
-import Box from '@mui/material/Box';
-import IconButton from '@mui/material/IconButton';
-import Link from '@mui/material/Link';
-import Typography from '@mui/material/Typography';
-
-import { GITHUB_URL } from 'src/constants';
-import { useAnalytics } from 'src/hooks';
-import { useTheme } from 'src/hooks/useLayoutContext';
-import { apiGet, endpoints } from 'src/lib/api';
-import { specPath } from 'src/routes/paths';
-import { colors, fontSize, semanticColors, typography } from 'src/theme';
-import { buildSrcSet, getFallbackSrc } from 'src/utils/responsiveImage';
-import { selectPreviewUrl } from 'src/utils/themedPreview';
-
-interface PlotOfTheDayData {
- spec_id: string;
- spec_title: string;
- description: string | null;
- library_id: string;
- library_name: string;
- language: string;
- quality_score: number;
- preview_url_light?: string | null;
- preview_url_dark?: string | null;
- preview_url: string | null;
- image_description: string | null;
- library_version: string | null;
- python_version: string | null;
- language_version: string | null;
- date: string;
-}
-
-const mono = typography.fontFamily;
-
-export function PlotOfTheDay() {
- const [data, setData] = useState(null);
- const [loading, setLoading] = useState(true);
- const [dismissed, setDismissed] = useState(
- () => window.sessionStorage.getItem('potd_dismissed') === 'true'
- );
- const { isDark } = useTheme();
- const { trackEvent } = useAnalytics();
- const previewUrl = selectPreviewUrl(data, isDark);
-
- useEffect(() => {
- if (dismissed) return;
- apiGet(endpoints.plotOfTheDay)
- .then(setData)
- .catch(() => {})
- .finally(() => setLoading(false));
- }, [dismissed]);
-
- const handleDismiss = useCallback(
- (e: React.MouseEvent) => {
- e.stopPropagation();
- trackEvent('potd_dismiss', { spec: data?.spec_id, library: data?.library_id });
- setDismissed(true);
- window.sessionStorage.setItem('potd_dismissed', 'true');
- },
- [trackEvent, data]
- );
-
- // Already dismissed — no space needed (user saw page before)
- if (dismissed) return null;
-
- // Still loading — reserve space to prevent CLS
- if (loading) {
- return ;
- }
-
- // Fetch failed or no data — collapse (post-initial-paint, negligible CLS)
- if (!data) return null;
-
- return (
-
-
- {/* Top bar — full width terminal prompt */}
-
-
- $
-
- {(() => {
- // Per-language file extension + runner command. Anyplot ships Python
- // for nine libraries, R for ggplot2, Julia for makie, and JavaScript
- // for chartjs/d3/echarts/highcharts/muix; the chip mimics what a user
- // would actually type into a shell, so the runner label flips too.
- // muix is the React (.tsx) exception within JavaScript.
- const ext =
- data.library_id === 'muix'
- ? '.tsx'
- : data.language === 'r'
- ? '.R'
- : data.language === 'julia'
- ? '.jl'
- : data.language === 'javascript'
- ? '.js'
- : '.py';
- const runner =
- data.language === 'r'
- ? 'Rscript'
- : data.language === 'julia'
- ? 'julia --project=.'
- : data.language === 'javascript'
- ? 'node'
- : 'python';
- return (
- {
- e.stopPropagation();
- trackEvent('nav_click', {
- source: 'potd_source_link',
- target: 'github',
- spec: data.spec_id,
- library: data.library_id,
- });
- }}
- sx={{
- fontFamily: mono,
- fontSize: fontSize.xxs,
- color: semanticColors.mutedText,
- flex: 1,
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- textDecoration: 'none',
- '&:hover': { color: colors.primary },
- }}
- >
- {runner} plots/{data.spec_id}/{data.library_id}
- {ext}
-
- );
- })()}
-
-
-
-
-
- {/* Middle — image left, info right */}
-
- {/* Image */}
-
- trackEvent('nav_click', {
- source: 'potd_image',
- target: 'spec_detail',
- spec: data.spec_id,
- library: data.library_id,
- })
- }
- sx={{
- display: 'block',
- textDecoration: 'none',
- flexShrink: 0,
- width: { xs: '100%', sm: '50%' },
- '&:hover': { opacity: 0.95 },
- }}
- >
- {previewUrl && (
-
-
-
-
-
- )}
-
-
- {/* Info */}
-
- {/* Label */}
-
-
-
- plot of the day
-
-
-
- {/* Title */}
-
- trackEvent('nav_click', {
- source: 'potd_title',
- target: 'spec_detail',
- spec: data.spec_id,
- library: data.library_id,
- })
- }
- sx={{
- textDecoration: 'none',
- color: 'var(--ink)',
- '&:hover': { color: colors.primaryDark },
- }}
- >
-
- {data.spec_title}
-
-
-
- {/* Description */}
- {data.image_description && (
-
- “{data.image_description.trim()}”
-
- )}
-
-
-
- {/* Bottom bar — terminal output style */}
-
-
- >>>
-
-
- plot.png saved
-
-
-
- │
-
-
- {data.library_name}
- {data.library_version && data.library_version !== 'unknown'
- ? ` ${data.library_version}`
- : ''}{' '}
- ·{' '}
- {data.language === 'r'
- ? 'R'
- : data.language === 'julia'
- ? 'Julia'
- : data.language === 'javascript'
- ? 'JavaScript'
- : 'Python'}{' '}
- {data.language_version ||
- data.python_version ||
- (data.language === 'r'
- ? '4.4'
- : data.language === 'julia'
- ? '1.11'
- : data.language === 'javascript'
- ? '22'
- : '3.13')}
-
-
-
-
- );
-}
diff --git a/app/src/sections/landing/ScienceNote.tsx b/app/src/sections/landing/ScienceNote.tsx
deleted file mode 100644
index 515ba7cfdd..0000000000
--- a/app/src/sections/landing/ScienceNote.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { Link as RouterLink } from 'react-router-dom';
-
-import Box from '@mui/material/Box';
-
-import { paths } from 'src/routes/paths';
-import { PaletteStrip } from 'src/sections/landing/PaletteStrip';
-import { colors, typography } from 'src/theme';
-
-export function ScienceNote() {
- return (
-
-
-
- § 03 · On the palette
-
-
-
- Colors that everyone can see.
-
-
-
- A palette unambiguous to colourblind and non-colourblind viewers alike, warm-tinted to
- stay legible on screen and in print.
-
-
-
- — anyplot imprint, design rationale
-
-
-
-
-
- palette.explore()
-
-
-
- );
-}
diff --git a/app/src/sections/landing/index.ts b/app/src/sections/landing/index.ts
index 396b7c3aab..90e5fa6b7f 100644
--- a/app/src/sections/landing/index.ts
+++ b/app/src/sections/landing/index.ts
@@ -1,8 +1,5 @@
export * from 'src/sections/landing/HeroSection';
export * from 'src/sections/landing/LibrariesSection';
export * from 'src/sections/landing/NumbersStrip';
-export * from 'src/sections/landing/PaletteStrip';
-export * from 'src/sections/landing/PlotOfTheDay';
export * from 'src/sections/landing/PlotOfTheDayTerminal';
-export * from 'src/sections/landing/ScienceNote';
export * from 'src/sections/landing/TypewriterText';
diff --git a/app/src/sections/spec-detail/CodeShowcase.tsx b/app/src/sections/spec-detail/CodeShowcase.tsx
deleted file mode 100644
index 7e391b61f2..0000000000
--- a/app/src/sections/spec-detail/CodeShowcase.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-import Box from '@mui/material/Box';
-
-import { SectionHeader } from 'src/components/SectionHeader';
-import { typography } from 'src/theme';
-
-export function CodeShowcase() {
- return (
-
-
- One import.
- >
- }
- />
-
-
- {/* Left: description */}
-
-
- Same palette,
-
- every library.
-
-
- every example in the catalogue uses the same imprint palette. switch libraries without
- losing your color grammar — a gentoo penguin is
- always blue, whether you draw it in matplotlib or plotly.
-
-
- validated against deuteranopia, protanopia and tritanopia using the Machado et al.
- (2009) simulation model.
-
-
-
- {/* Right: code block — terminal showcase, intentionally dark in both themes
- so the macOS-style window dots and drop shadow stay coherent. */}
-
-
-
- {'# pick any library. the palette travels with you.\n'}
-
-
- import
-
- {' anyplot '}
-
- as
-
- {' ap\n\n'}
- {'data = ap.'}
-
- load
-
- {'('}
-
- "penguins"
-
- {')\n\n'}
-
- {'# matplotlib\n'}
-
- {'ap.'}
-
- mpl
-
- {'.'}
-
- scatter
-
- {'(data, x='}
-
- "bill"
-
- {', y='}
-
- "flipper"
-
- {',\n hue='}
-
- "species"
-
- {')\n\n'}
-
- {'# plotly — same colors, interactive\n'}
-
- {'ap.'}
-
- plotly
-
- {'.'}
-
- scatter
-
- {'(data, x='}
-
- "bill"
-
- {', y='}
-
- "flipper"
-
- {',\n hue='}
-
- "species"
-
- {')'}
-
-
-
-
- );
-}
diff --git a/app/src/sections/spec-detail/index.ts b/app/src/sections/spec-detail/index.ts
index 3b2f699ffa..0dbe83b1ca 100644
--- a/app/src/sections/spec-detail/index.ts
+++ b/app/src/sections/spec-detail/index.ts
@@ -1,4 +1,3 @@
-export * from 'src/sections/spec-detail/CodeShowcase';
export * from 'src/sections/spec-detail/LibraryPills';
export * from 'src/sections/spec-detail/RelatedSpecs';
export * from 'src/sections/spec-detail/SpecDetailView';
diff --git a/docs/reference/seo.md b/docs/reference/seo.md
index 6347fd72f1..1ac0acc665 100644
--- a/docs/reference/seo.md
+++ b/docs/reference/seo.md
@@ -344,7 +344,7 @@ filtering is served via a `?language=` query param on the hub, and the hub's
canonical tag omits the query — so the hub and its language-filtered variants
all consolidate on the same canonical URL. Legacy links to
`/{spec_id}/{language}` redirect to `/{spec_id}?language={language}` (SPA
-client-side redirect via `app/src/router.tsx`; bots get a 301 from
+client-side redirect via `app/src/routes/index.tsx`; bots get a 301 from
`/seo-proxy/{spec_id}/{language}` to `/seo-proxy/{spec_id}`).
The interactive view follows the same pattern: `?view=interactive` is a
diff --git a/docs/reference/style-guide.md b/docs/reference/style-guide.md
index 56f78542ca..16853476a1 100644
--- a/docs/reference/style-guide.md
+++ b/docs/reference/style-guide.md
@@ -446,7 +446,7 @@ It does **not** appear in:
- Static (non-cursor, non-status) decorative dots or glyphs
- **Default colour on in-prose links** (see below)
-**In-prose link treatment.** Links inside body text default to `--ink-soft` with a 1px `--rule` underline (via `text-decoration`). On hover the colour flips to `--imprint-green` and the underline thickens to `currentColor`. Do **not** set `color: colors.primary` as the default on inline links — brand green stays a signal colour that only appears on interaction. The reusable sx object is exported from `app/src/theme/index.ts` as `proseLinkStyle`; import it everywhere a contextual link lives in prose (About, Legal, MCP, Palette, Stats).
+**In-prose link treatment.** Links inside body text default to `--ink-soft` with a 1px `--rule` underline (via `text-decoration`). On hover the colour flips to `--imprint-green` and the underline thickens to `currentColor`. Do **not** set `color: colors.primary` as the default on inline links — brand green stays a signal colour that only appears on interaction. The reusable sx object is exported from `app/src/theme/tokens.ts` (re-exported via `src/theme`) as `proseLinkStyle`; import it everywhere a contextual link lives in prose (About, Legal, MCP, Palette, Stats).
```ts
import { proseLinkStyle } from '../theme';
@@ -1193,7 +1193,7 @@ For CSS:
The design system is implemented across:
- **HTML reference (full mockup)**: `mockups/landing.html` — single-file reference with all sections, SVG plots, and animations
-- **Theme tokens (frontend)**: `app/src/theme/index.ts` and `app/src/main.tsx` — MUI theme exports for colors, typography, spacing, headingStyle, subheadingStyle, textStyle, tableStyle, codeBlockStyle
+- **Theme tokens (frontend)**: `app/src/theme/tokens.ts` (design tokens: colors, typography, spacing, headingStyle, subheadingStyle, textStyle, tableStyle, codeBlockStyle) and `app/src/theme/create-theme.ts` (MUI theme composition); both re-exported via `src/theme`
- **Palette (Python library)**: `anyplot.palette` — see §9.5
**Reference CSS skeleton:**