Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0b4f417
chore(prompts): bump grid-line opacity token from 0.10 to 0.15
MarkusNeusinger May 23, 2026
ef29a01
feat(palette): variants v1 — D-baseline + 4 candidates with CAM02-UCS…
MarkusNeusinger May 23, 2026
4158b01
Merge branch 'main' into feat/palette-variants-v1
MarkusNeusinger May 24, 2026
ef6a658
feat(palette): add D1-8 variant — D1 tight-chroma expanded to 8 hues
MarkusNeusinger May 24, 2026
5b47e6e
feat(palette): v2 head-to-head viewer — vivid-8 vs muted-8 across 4 s…
MarkusNeusinger May 24, 2026
51016cd
feat(palette): add expert review synthesis for muted-8
MarkusNeusinger May 24, 2026
2435f8e
feat(palette): v3 muted-8 with hybrid-v3 ordering and decision rationale
MarkusNeusinger May 26, 2026
aec4c01
feat(palette): add semantic anchors outside the categorical pool (amb…
MarkusNeusinger May 26, 2026
5ac270c
feat(palette): add WCAG contrast audit + outline-fix demo
MarkusNeusinger May 26, 2026
50abbf2
feat(palette): use 1px ink ring instead of 2px in contrast demo
MarkusNeusinger May 26, 2026
9ee7630
docs(palette): backfill 3 decision sections in rationale
MarkusNeusinger May 26, 2026
1e25248
feat(palette): adopt imprint as the anyplot palette
MarkusNeusinger May 26, 2026
e68304e
feat(palette): prompts for imprint palette + n>4 guidance + outline p…
MarkusNeusinger May 26, 2026
6cc8e85
feat(palette): rebuild /palette page for imprint + update theme tokens
MarkusNeusinger May 26, 2026
39d9f58
feat(palette): /palette page iteration 2 — wheel + sober copy + compare
MarkusNeusinger May 26, 2026
d92e2a6
feat(palette): compact slot grid + sort toggle + cmap section
MarkusNeusinger May 26, 2026
805131d
feat(palette): hover-to-see-hex, click-to-copy, +ColorBrewer Set2, dr…
MarkusNeusinger May 26, 2026
1028d75
feat(palette): fold "best variant for CVD" into the sort-toggle expan…
MarkusNeusinger May 26, 2026
1d2d229
fix(palette): drop the "click to copy hex" tooltip — visual feedback …
MarkusNeusinger May 26, 2026
3304642
feat(palette): phase 5b + phase 3 — migrate remaining consumers to im…
MarkusNeusinger May 26, 2026
e4c1209
chore(palette): final audit sweep — close the long tail of old-palett…
MarkusNeusinger May 26, 2026
c5c7266
fix(palette): satisfy ruff — move palette imports to top, sort, format
MarkusNeusinger May 26, 2026
206f338
Merge remote-tracking branch 'origin/main' into feat/palette-variants-v1
MarkusNeusinger May 26, 2026
9099e4d
fix(palette): address Copilot review feedback on the imprint PR
MarkusNeusinger May 26, 2026
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
39 changes: 22 additions & 17 deletions .serena/memories/style_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,29 @@ Frontend tokens: `app/src/theme/index.ts` + `app/src/main.tsx` (MUI theme).
- **Exception**: ErrorBoundary uses raw `'monospace'` (crash-safe fallback).
- `fontSize.micro` (0.5rem) and `fontSize.xxs` (0.625rem) restricted to data-dense pages (StatsPage, DebugPage). Public pages: `fontSize.xs` (0.75rem) minimum.

## Color — Okabe-Ito palette
Colorblind-safe categorical palette; first 7 fixed across themes, 8th is adaptive neutral.
## Color — imprint palette
Colourblind-safe categorical palette (8 hues, hybrid-v3 sort) plus 3 semantic anchors outside the pool. All 8 categorical slots stay fixed across themes; the neutral and muted anchors flip per theme. Full rationale: `docs/reference/palette-variants-v3/decision-rationale.md`.

```
1 #009E73 bluish green ★ brand
2 #D55E00 vermillion (errors, destructive)
3 #0072B2 blue (info links, footnotes)
4 #CC79A7 reddish purple PLOT-ONLY
5 #E69F00 orange ("new" badges, secondary hover)
6 #56B4E9 sky blue PLOT-ONLY
7 #F0E442 yellow PLOT-ONLY
8 adaptive neutral: #1A1A1A light / #E8E8E0 dark
slot 0 #009E73 brand green ★ first series — logo, primary CTAs
slot 1 #C475FD lavender (creative / artistic) PLOT-ONLY
slot 2 #4467A3 blue (info links, footnotes)
slot 3 #BD8233 ochre (hover accent, earth / commodity)
slot 4 #AE3030 matte red (errors, destructive — deferred semantic anchor)
slot 5 #2ABCCD cyan (sky / tech-cool) PLOT-ONLY
slot 6 #954477 rose (wellness / health) PLOT-ONLY
slot 7 #99B314 lime (growth / nature) PLOT-ONLY

semantic anchors (outside the categorical pool):
amber #DDCC77 warning / caution (fixed)
neutral #1A1A17 / #F0EFE8 totals / baseline / outline (theme-adaptive)
muted #6B6A63 / #A8A79F other / rest / disabled (theme-adaptive)
```

**Brand green `#009E73` appears in UI ONLY in 8 contexts:** logo dot, italic accent words in headlines, hero terminal cursor, active nav item (dot prefix + underline), code-action button hover, code-block syntax (strings), palette strip, small status indicators.
**Brand green `#009E73` appears in UI ONLY in 8 contexts:** logo dot, italic accent words in headlines, hero terminal cursor, active nav item (dot prefix + underline), code-action button hover, code-block syntax (comments), palette strip, small status indicators.
**Never**: backgrounds, regular card borders, body text emphasis, non-logo icons, static decorative dots.

**Plot-only colors** (purple/sky/yellow) must never appear in UI chrome.
**Plot-only colours** (lavender, cyan, rose, lime) must never appear in UI chrome — they are reserved for data marks so categorical plots retain visual impact.

## Surfaces — warm, not clinical
Pure `#FFFFFF` is out — makes saturated palette colors look harsh.
Expand Down Expand Up @@ -70,15 +75,15 @@ Highlight treatments: `colors.highlight.bg`/`colors.highlight.text`, `colors.too

## Section-header pattern (shell-prompt prefixes)
- `❯` navigation/categorical · `$` action/list · `~/path/` hierarchical/about
- Title in italic + ss02 (script), `--ok-green`, underlined with 1px `--rule`.
- Title in italic + ss02 (script), `--imprint-green`, underlined with 1px `--rule`.

## Buttons — method-call doctrine
- **Action** (primary affordance): `.copy()`, `.open()`, `.download()` — `::before { content: "." }`, hover flips to `--ok-green`.
- **Action** (primary affordance): `.copy()`, `.open()`, `.download()` — `::before { content: "." }`, hover flips to `--imprint-green`.
- **Hero CTA** (filled, landing only): dark pill, 4px radius, hover → green.
- **Ghost** (rare): transparent, `--rule` border.

## Logo `any.plot()`
MonoLisa Bold, letters `--ink`, `.` `--ok-green` scaled 1.3× (circle), `()` `--ink` weight 400 at 45% opacity. Favicon reduces to `a.p`. Clear space `1em`.
MonoLisa Bold, letters `--ink`, `.` `--imprint-green` scaled 1.3× (circle), `()` `--ink` weight 400 at 45% opacity. Favicon reduces to `a.p`. Clear space `1em`.

## Voice
Precise · understated · curious · respectful · slightly playful · code-native · AI-honest.
Expand All @@ -92,6 +97,6 @@ Gradients (esp. purple-blue SaaS), glass/backdrop-blur, isometric illustrations,
## Plot defaults
- First series **always** `#009E73`.
- Neutral (pos 8) reserved for aggregates/residuals/reference lines.
- Yellow `#F0E442` poor on white — position 7+ only, never thin lines/small markers.
- Non-categorical: sequential → `viridis`/`cividis`; diverging → `BrBG`; heatmaps → `viridis` or single-polarity `Reds`/`Blues`.
- 5 lighter members (lavender, ochre, cyan, lime, amber) fall under WCAG 3:1 on cream bg. Add a 1px ink stroke on affected series when the chart is small or accessibility-strict — see outline pattern in `docs/reference/palette-variants-v3/decision-rationale.md`.
- Non-categorical: sequential → `imprint_seq` (green→blue); diverging → `imprint_div` (red↔theme-adaptive-midpoint↔blue). Never substitute viridis/cividis/BrBG/jet/hsv/rainbow — palette identity is part of the brand.
- Plot-internal typography (ticks/labels/legends): MonoLisa, 10–13px.
6 changes: 3 additions & 3 deletions app/src/components/CodeHighlighter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ const PRISM_LANGUAGE: Record<string, string> = {
julia: 'julia',
};

// Theme-aware Okabe-Ito syntax theme. All colors come from CSS variables in
// Theme-aware imprint syntax theme. All colors come from CSS variables in
// tokens.css so the block adapts to light (paper) and dark modes. Comments
// use the brand green as a deliberate editorial accent.
const okabeItoTheme: Record<string, React.CSSProperties> = {
const imprintTheme: Record<string, React.CSSProperties> = {
'pre[class*="language-"]': {
color: 'var(--code-text)',
background: 'var(--code-bg)',
Expand Down Expand Up @@ -65,7 +65,7 @@ export default function CodeHighlighter({ code, language = 'python' }: CodeHighl
return (
<SyntaxHighlighter
language={prismLanguage}
style={okabeItoTheme}
style={imprintTheme}
customStyle={{
margin: 0,
padding: '24px 28px',
Expand Down
32 changes: 16 additions & 16 deletions app/src/components/CodeShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function CodeShowcase() {
mb: 3,
m: 0,
color: 'var(--ink)',
'& em': { fontStyle: 'italic', color: 'var(--ok-green)' },
'& em': { fontStyle: 'italic', color: 'var(--imprint-green)' },
}}>
Same palette,<br /><em>every library</em>.
</Box>
Expand All @@ -39,7 +39,7 @@ export function CodeShowcase() {
color: 'var(--ink-soft)',
mb: 2.5,
}}>
every example in the catalogue uses the same Okabe-Ito palette. switch libraries
every example in the catalogue uses the same imprint palette. switch libraries
without losing your color grammar — a <em style={{ fontStyle: 'italic' }}>gentoo penguin</em> is always blue,
whether you draw it in matplotlib or plotly.
</Box>
Expand Down Expand Up @@ -79,40 +79,40 @@ export function CodeShowcase() {
<Box component="span" sx={{ color: '#666', fontStyle: 'italic' }}>
{'# pick any library. the palette travels with you.\n'}
</Box>
<Box component="span" sx={{ color: '#56B4E9' }}>import</Box>{' anyplot '}
<Box component="span" sx={{ color: '#56B4E9' }}>as</Box>{' ap\n\n'}
<Box component="span" sx={{ color: '#2ABCCD' }}>import</Box>{' anyplot '}
<Box component="span" sx={{ color: '#2ABCCD' }}>as</Box>{' ap\n\n'}
{'data = ap.'}
<Box component="span" sx={{ color: '#E69F00' }}>load</Box>
<Box component="span" sx={{ color: '#99B314' }}>load</Box>
{'('}
<Box component="span" sx={{ color: '#009E73' }}>"penguins"</Box>
<Box component="span" sx={{ color: '#DDCC77' }}>"penguins"</Box>
{')\n\n'}
<Box component="span" sx={{ color: '#666', fontStyle: 'italic' }}>
{'# matplotlib\n'}
</Box>
{'ap.'}
<Box component="span" sx={{ color: '#E69F00' }}>mpl</Box>
<Box component="span" sx={{ color: '#99B314' }}>mpl</Box>
{'.'}
<Box component="span" sx={{ color: '#E69F00' }}>scatter</Box>
<Box component="span" sx={{ color: '#99B314' }}>scatter</Box>
{'(data, x='}
<Box component="span" sx={{ color: '#009E73' }}>"bill"</Box>
<Box component="span" sx={{ color: '#DDCC77' }}>"bill"</Box>
{', y='}
<Box component="span" sx={{ color: '#009E73' }}>"flipper"</Box>
<Box component="span" sx={{ color: '#DDCC77' }}>"flipper"</Box>
{',\n hue='}
<Box component="span" sx={{ color: '#009E73' }}>"species"</Box>
<Box component="span" sx={{ color: '#DDCC77' }}>"species"</Box>
{')\n\n'}
<Box component="span" sx={{ color: '#666', fontStyle: 'italic' }}>
{'# plotly — same colors, interactive\n'}
</Box>
{'ap.'}
<Box component="span" sx={{ color: '#E69F00' }}>plotly</Box>
<Box component="span" sx={{ color: '#99B314' }}>plotly</Box>
{'.'}
<Box component="span" sx={{ color: '#E69F00' }}>scatter</Box>
<Box component="span" sx={{ color: '#99B314' }}>scatter</Box>
{'(data, x='}
<Box component="span" sx={{ color: '#009E73' }}>"bill"</Box>
<Box component="span" sx={{ color: '#DDCC77' }}>"bill"</Box>
{', y='}
<Box component="span" sx={{ color: '#009E73' }}>"flipper"</Box>
<Box component="span" sx={{ color: '#DDCC77' }}>"flipper"</Box>
{',\n hue='}
<Box component="span" sx={{ color: '#009E73' }}>"species"</Box>
<Box component="span" sx={{ color: '#DDCC77' }}>"species"</Box>
{')'}
</Box>
</Box>
Expand Down
12 changes: 8 additions & 4 deletions app/src/components/PaletteStrip.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import Box from '@mui/material/Box';

const SWATCHES = [
'#009E73', '#D55E00', '#0072B2', '#CC79A7',
'#E69F00', '#56B4E9', '#F0E442', 'var(--ink)',
// imprint palette — 8 categorical hues in hybrid-v3 sort order
const DEFAULT_SWATCHES = [
'#009E73', '#C475FD', '#4467A3', '#BD8233',
'#AE3030', '#2ABCCD', '#954477', '#99B314',
];

interface PaletteStripProps {
Expand All @@ -12,9 +13,12 @@ interface PaletteStripProps {
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 }: PaletteStripProps = {}) {
export function PaletteStrip({ maxWidth = 400, height = 40, mt = 5, hexes }: PaletteStripProps = {}) {
const SWATCHES = hexes ?? DEFAULT_SWATCHES;
return (
<Box sx={{
display: 'flex',
Expand Down
6 changes: 3 additions & 3 deletions app/src/components/ScienceNote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export function ScienceNote() {
px: 2.5,
m: 0,
}}>
A palette proposed as unambiguous to both colorblind and non-colorblind
viewers, with vivid colors that stay recognizable on screen and in print.
A palette unambiguous to colourblind and non-colourblind viewers alike,
warm-tinted to stay legible on screen and in print.
</Box>

<Box component="cite" sx={{
Expand All @@ -72,7 +72,7 @@ export function ScienceNote() {
textTransform: 'uppercase',
letterSpacing: '0.15em',
}}>
Okabe & Ito, Color Universal Design (2008)
anyplot imprint, design rationale
</Box>

<PaletteStrip />
Expand Down
10 changes: 6 additions & 4 deletions app/src/pages/AboutPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,12 @@ export function AboutPage() {
<SectionHeader prompt="❯" title={<em>palette</em>} />
<Box sx={proseColumnSx}>
<Box sx={textStyle}>
every plot uses the Okabe-Ito palette, designed to stay distinguishable under the main
forms of color vision deficiency. Masataka Okabe and Kei Ito published it on the Color
Universal Design page in 2002 (revised 2008). about 8% of men have some form of color
vision deficiency — most plotting libraries ignore this entirely. we make it the default.
every plot uses <strong>imprint</strong>, our own colourblind-safe categorical palette —
8 hues plus 3 semantic anchors, tuned for warm-paper rendering and validated against the
three main forms of colour vision deficiency. it sits in the same neighbourhood as
Okabe-Ito, Paul Tol&apos;s &ldquo;muted&rdquo;, and ColorBrewer Set2 — about 8% of men
have some form of CVD, and most plotting libraries ignore this entirely. we make it
the default.
</Box>
<Box sx={{ ...textStyle, mt: 1 }}>
see the{' '}
Expand Down
5 changes: 1 addition & 4 deletions app/src/pages/LandingPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,12 @@ describe('LandingPage', () => {
expect(trackEvent).toHaveBeenCalledWith('nav_click', { source: 'specs_more_link', target: '/specs' });
});

it('tracks the suggest_spec link and the okabe-ito reference', async () => {
it('tracks the suggest_spec link', async () => {
const user = userEvent.setup();
render(<LandingPage />);

await user.click(screen.getByText(/suggest/));
expect(trackEvent).toHaveBeenCalledWith('nav_click', expect.objectContaining({ source: 'suggest_spec_link' }));

await user.click(screen.getByText(/Okabe/));
expect(trackEvent).toHaveBeenCalledWith('nav_click', expect.objectContaining({ source: 'palette_okabe_ito' }));
});

it('tracks the map teaser visual click', async () => {
Expand Down
Loading