Skip to content

Apply the new Vortex design system - #120

Open
lwwmanning wants to merge 98 commits into
mainfrom
wm/bench-skin-2
Open

lwwmanning wants to merge 98 commits into
mainfrom
wm/bench-skin-2

Conversation

@lwwmanning

@lwwmanning lwwmanning commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

This change applies the Vortex design system to all pages in this repository: the homepage, the blog, the 404 and error pages, and bench.

  • The homepage shows what Vortex is, the install commands, a benchmark card, and the adopters' logos. Each logo links to its owner's page about Vortex.
  • The blog is one list, by year, of our posts and posts on other sites about Vortex. Only the blog has a Light theme.
  • The site and bench work without JavaScript.
  • The site uses the system's generated CSS and copies of its modules in src/vendor/vortex-design/. Tailwind is removed.
  • Bench gets a skin only. Its layout, behavior, and chart colors do not change.

🤖 Generated with Claude Code

- Load vortex-skin.css after globals.css: the Blue accent, warm greys,
  6px cards and 4px controls. It sets only visual properties, and
  app/vortex-skin.test.ts holds it to that.
- Load Geist and Geist Mono with Funnel Display in one font request.
- Replace the Lucide icons with Material Symbols Sharp, in one shared
  icons module. The GitHub mark stays.
- Write the Compression Size headings as words, without emoji.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
@vercel

vercel Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
vortex Ignored Ignored Preview Oct 30, 2026 11:51am UTC

Request Review

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Bench preview for 25e862491ad5230e248c58b8126167d44aca39fb: https://benchmarks-website-kh03a5rg8-vortex-data.vercel.app

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T11:53:45.824315Z 25e8624 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

- Bench paints its one --accent as words and as fills. Fills now take
  --accent-fill with white text: blue-bright (#6e5cff) in Dark, and the
  same Blue as words in Light, so Light does not change.
- app/vortex-skin.test.ts reads globals.css and fails if an accent fill
  there has no override in the skin.
- Dark muted text is grey-450 (#9c9c99), one step below body text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
- The group title's description hint was a Unicode text glyph beside
  Material Symbols Sharp icons. It now renders the Sharp `info` icon from
  the same pinned source as the others.
- components/icons.test.tsx fails if a component uses a text glyph as an
  icon again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
lwwmanning and others added 2 commits October 28, 2026 22:55
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
…x.dev

- src/styles holds copies of the system's tokens and the base, brand,
  site, docs, blog and focus components. Its type styles read
  var(--font-*), which point at next/font's faces and fallbacks.
- next/font loads Funnel Display 500 and 600, Geist and Geist Mono.
- The ground is the system's Dark ground (#0f0f0e), mirrored in
  src/lib/theme.ts for the verify parity check.
- The blog follows the reader's stored theme (vx-theme); every other page
  is Dark. An inline script sets it before first paint, and RouteTheme
  sets it again on client-side navigation.
- bun test runs tests/unit in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

🔦 Lighthouse audit

URL Perf A11y Best practices SEO Report
/ 🟢 100 🟢 100 🟢 96 🟢 100 report
/blog 🟢 100 🟢 100 🟢 96 🟢 100 report
/blog/btrblocks-compressor 🟢 100 🟢 100 🟢 96 🟢 100 report

Run 41051094493 · 2026-10-30 11:54 UTC

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a4c372aab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/theme.ts Outdated
Playwright's default pattern matched the node:test files in tests/unit
and failed to load them. testMatch now takes *.spec.ts only; bun test
runs tests/unit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
lwwmanning and others added 2 commits October 28, 2026 23:21
- Header follows the Header card's site variant: the Vortex wordmarks
  (Dark and Light), nav Docs, Bench, Explorer, Blog, Slack, and the
  GitHub button with the star count. Below 800px the nav takes a
  second row.
- src/lib/github.ts reads the star count at build time with a one-day
  revalidate and a timeout. The count hides when the fetch fails.
- Footer follows the Footer card: the LF mark, the LF Projects line,
  and Charter, Code of conduct, Security and Calendar.
- The root layout puts header, main and footer in one --page-max frame.
- Remove the old logos and the donor credit's logo, with their REUSE
  entries. Annotate the new wordmarks as CC-BY-4.0.
- Replace the scramble smoke test with header and footer link checks.

Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fixes

- The GitHub button's count is the Material Symbols Sharp `star` beside
  the number, not a ★ glyph, and the link names the repository and its
  stars in full.
- A post marks the Blog link as the current section
  (aria-current="true"); the index stays the current page.
- The header's wrap at 800px, the right-hand end group on two rows and
  the wrapping footer nav are now in the design system; the site's
  stopgap overrides are removed after syncing its styles.
- tests/unit/icons.test.ts fails if site code uses a text glyph as an
  icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8171a90d3c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/route-theme/index.tsx Outdated
Comment thread src/app/globals.css
Comment thread src/components/layout/header.tsx Outdated
The header now links /slack, a redirect to Slack's invite page, which
answers CI's fetch with 403. The internal-link check no longer follows
redirects off the site; a redirect means the route exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
- The page follows the Home card: the hero (Eyebrow, the vx-display
  headline, InstallTabs and the AsciiMark), then two empty bands for the
  BenchmarkCard and the LogoBanner. At 1440 x 900 the page fills one
  screen. Below 1100px the hero stacks, and on phones the headline is
  48px.
- InstallTabs: Rust, Python, DuckDB, Spark and CLI, one tab stop with
  the arrow keys, Home and End, and a copy button that leaves out the
  prompts. All panels share one cell, so the block keeps its height.
- src/lib/maven.ts reads the Spark version from Maven Central's
  metadata at build time with a one-day revalidate. The command shows
  <latest> when the read fails.
- AsciiMark runs the system's renderer, vendored unchanged in
  src/vendor/vortex-design, with three 0.170.0 from npm. Both load only
  after the page is idle. Without WebGL it shows the Vortex symbol.
- Add the content_copy and check icons.
- Remove the old OGL hero and its overlay.
- Replace the hero smoke tests with headline, tab, copy and mark checks.

Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/content/links.ts Fixed
lwwmanning and others added 18 commits October 29, 2026 22:08
The smoke test built a RegExp from a source host such as
"iceberg.apache.org", so its dots matched any character. CodeQL
flagged it. A plain string matches a substring of the link's name, which
is what the test meant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
Random access sums several charts into one dataset's bucket. A window
that missed one of them still counted the bucket, so the card could show
a time summed from part of a dataset. Each bucket now takes the group's
full chart list from /api/groups, a chart with no times joins its bucket
empty, and a time panel uses its times only when every bucket is
measured for Vortex and Parquet. Otherwise it shows bench's scores.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
main moves CI, the types, and both Vercel projects to Node 26 (#143).
package.json and renovate.json take main's Node 26 lines. CLAUDE.md
takes main's note on @types/node and keeps this branch's record that
postcss-selector-parser went with @tailwindcss/typography, so main's
override rule for it is not needed here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
It mentions Vortex in one sentence. The list keeps posts where Vortex is
a significant part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
bench drew Vortex in red, Parquet in cyan, and Arrow in orange. Now
each format takes the design system's series palette:

- Vortex is Blue: blue-bright (#6e5cff) on datafusion and for encode,
  a lighter Blue (#8b84f3) on duckdb and for decode. vortex-compact
  takes an azure Blue.
- Parquet, DuckDB, Lance, and Arrow take muted hues (sand, sage, warm
  grey, and dusty rose), each at its own lightness.
- A pair (two engines, or encode and decode) keeps its hue and changes
  its lightness. Before, encode and decode had one color.
- Each color holds 3:1 on the Light and the Dark card, so the chart
  still does not recolor when the theme changes.

Only the colors change. Widths, order, and the cold dash stay.

Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing above the fold said that Vortex is a file format. A standfirst
under "Fast, small, open: pick three." now says it: "Vortex is an
open-source columnar file format for fast scans, random access, and
compression."

It is one vx-lede line in ink-body, 12px under the headline. It fits in
the text column's free height, so the hero keeps its height, and the
ASCII mark keeps its size: 472 × 383 at 1512 × 860, 472 × 423 at
1440 × 900, and 472 × 472 at 1512 × 982. New smoke tests check the
standfirst and the mark's size.

Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landing page wrapped its groups in a Suspense boundary, so a browser
got them in a hidden segment that only React's inline script reveals.
With JavaScript off, a reader saw the loading card and nothing else. The
page now awaits its data and sends the groups as visible HTML. On a Data
Cache miss the browser shows nothing until the groups have been read;
performance.md has the numbers.

Visible groups can paint before the last one arrives on a slow
connection, so the open-groups bootstrap now runs before the first group
and opens each listed group from a MutationObserver as the parser
inserts it. A finish step after the last group opens groups listed by
slug and leaves the marker. The loading card is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
The root <html> renders with a no-js class, and the pre-paint bootstrap
swaps it for js as its first statement. Controls that do nothing
without JavaScript carry data-requires-js, which .no-js hides: the theme
toggle, the mobile nav toggle, Expand and Collapse All, the filter
dropdown, the group jump menu, and a chart card's toolbar, canvas and
range strip. On a phone the nav shows in the header row instead, with
its GitHub link. The group copy-link control is now a real link to
/#<anchor>; with JavaScript a click, Enter or Space still copies it.

A collapsed group's body now hides through CSS, since it sits after the
<details>. A <noscript> list of each group's chart links was measured
and left out: it adds 7.3 KB gzipped (151 KB raw) to the landing page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
The canvas is now role="img" with an aria-label that names the chart.
Once the chart has data, the label adds one sentence with each visible
series' value at the latest commit with data, dated, in the chart's
display unit and formatted as the tooltip formats it. On the production
data the newest commit in every chart's window has no results yet, so
the literal latest commit would always read as empty. The controller
restates the label after construction, a payload swap, a filter change
and a legend click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
Without JavaScript a group's summary still toggles its <details>, but
opening it shows nothing more. Under the no-js class the row now reads
as a plain heading: no chevron, pointer, hover or open state. The note
for readers without JavaScript opens <main> and says the summaries are
complete and the charts need JavaScript.

The copy link lets a modified or middle click through, so open in a new
tab works. It renders as "Link to <group>" and takes the copy wording
once mounted, so hydration matches. Header swaps a no-js class back in
a layout effect, before paint. The open-groups bootstrap defines its
finish step before it makes the observer, and makes none when the
fragment lists nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
Each request to / starts reading its data and waits up to 150 ms. A
Data Cache hit answers in time, so the page renders the groups as plain
HTML, as before. A miss streams the loading view at once inside a
Suspense boundary, and the groups follow when the read finishes; a
client navigation to / shows it the same way. Without JavaScript the
loading view says to reload in a few minutes, since the CDN keeps / for
five. HomeLoading and its styles are back.

Measured on the local dump, a miss now gets its first byte at 180 ms
and the loading view at 328 ms instead of a blank page until 720 ms; a
hit is unchanged. performance.md has the table, and notes that a
database failure inside the wait returns a real 500.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
The AsciiMark's host was empty until the page went idle, and empty for
good without scripts. The server now renders the static symbol in it,
sized as the design system's fallback; the live or reduced-motion mark
replaces it when it draws, and it stays without WebGL or on an error.

The root renders with a no-js class, which the first-paint script swaps
for js as its first statement; RouteTheme swaps it back if React ever
re-renders the root. Controls that do nothing without scripts carry
data-requires-js and hide: the install tabs and copy button (every
install panel then shows, stacked under its tab's name), the logo
banner's pause (the logos then sit still, as under reduced motion), the
blog's theme switch, code copy buttons and newsletter form.

With scripts and WebGL on, the homepage, the 404 page and /blog are pixel
identical to #120.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
The symbol the server renders for readers without scripts showed from
first paint until the live mark drew, then jumped to the mark at a
different size; #120 showed an empty box. With scripts the symbol now
stays hidden, and shows only once the host's data-mark-mode says the
mark cannot draw (static or error). Readers without scripts keep it.
The mark's host is pixel identical to #120 at DOMContentLoaded, at load
and once drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
Browsers send a middle click as auxclick, never click, so the button
check in the click handler could not match. The modifier keys still
pass through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
performance.md's note on misses now says an ingest causes none, since
it marks the Data Cache stale rather than dropping it. The 150 ms
loading-view threshold was measured against a local Data Cache and
needs checking against Vercel's function logs on a preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
The caption after the headline's dot read as a spec: "times in ms, the
geomean over queries or datasets, the rest against Parquet = 1, best
first". Now it is "times in ms, ratios to Parquet, best first".

The method moves to the caption's title, for hover, and to
screen-reader text after the caption: "A time is the geomean over the
queries or datasets that bench's score covers. A ratio is against
Parquet = 1." The design system's benchCaption and benchMethod build
both from the panels' units, so the score fallback still gets its own
words, and no unit that no panel shows is named. The vendored
benchmark-card module is a byte-for-byte copy of the system's.

Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Take origin/wm/bench-skin-2 at a1a27c2: the homepage and bench work
without scripts. The fixes from the page review stay on top.

Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without scripts the static symbol stands in for the ASCII mark, and the
install commands stack. The hero still stretched to fill the window, so
a tall window put a wide empty band around the small symbol.

Now, from 1100px wide and only under html.no-js, the hero takes its
content's height with the scripts-on page's room at 1512 × 860: 72px
from the header to the eyebrow, and 64px from the install block to the
benchmark card. The symbol is 56% of the host (264px), not 40%, so it
balances the stacked commands. AsciiMark reads that width from
--mark-still-width, with 40% as the default.

With scripts the page does not change: the mark's box stays 472 × 383 at
1512 × 860, 472 × 423 at 1440 × 900, and 472 × 472 at 1512 × 982. A
smoke test checks the hero without scripts at 1512 × 860 and
1512 × 1400.

Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1a27c28ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bench/web/components/GroupPermalink.tsx Outdated
lwwmanning and others added 7 commits October 29, 2026 22:35
On /blog at 1512px, "datalakehousehub.com" ran out of the 150px meta
column into the title, and iceberg.apache.org and parquetreader.com
touched its edge.

The BlogIndex card now:

- sets the meta column to 180px, which holds the longest listed host on
  one line,
- puts the host in its own span, which ends in an ellipsis if a longer
  host comes, and
- gives the source the full host as its title.

A smoke test checks every external host in links.ts at 1512, 1440, and
375px. The CSS is the design system's, copied with site-styles.mjs.

Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The index was one list of 40 rows with no breaks. Now each year has a
quiet heading in Geist Mono ("2026", "2025") over that year's own list,
from the BlogIndex card:

- h2.vx-label.vx-posts-year, in ink-muted, on the column's edge, with an
  id such as posts-2026.
- One ul.vx-posts per year, so the rows keep their list semantics and
  stay in date order.
- Each row's title is now an h3 under its year's h2.

byYear in src/lib/posts.ts splits the rows by the UTC year of each date,
as the date line reads. Unit and smoke tests check the groups, their
order, and the headings.

Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The copy link's href was a bare /#anchor, so a modified or middle click
opened the group without the page's filters, such as ?engine=duckdb.
Once mounted, the href is now the same permalink a plain click copies,
and it is refreshed on pointer down and focus so it carries the group's
series too. Without scripts the link stays /#anchor, because filters do
nothing there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main runs Lighthouse on every PR as a required check (#147) and resolves
four code-scanning alerts (#148). The merge is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
Will found the themed series palette hard to read. Bench's charts go
back to their original colours, the same as on main. Legibility of the
data comes before the brand palette there.

This reverts commit b1cd3b8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
The Dark accent was #6e5cff, a periwinkle 6° of hue from Lance's mark
(about #625eff). It is now cobalt: blue-bright #2f63ff for marks and
fills, and blue-soft #8fa9ff for Blue text and thin lines, both near
225°. Contrast holds: #2f63ff is 4.0:1 on the page with white text at
4.8:1 on it, and #8fa9ff is about 8:1. Light keeps its Blue.

The design system's tokens, the bench skin, the Open Graph images, and
the ASCII mark's brush all take the new values.

Signed-off-by: Will Manning <will@willmanning.io>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

'duckdb:vortex-file-compressed': { color: '#22c55e', width: HERO_WIDTH }, // neon green
'datafusion:vortex-compact': { color: '#a855f7', width: COMPACT_WIDTH }, // bright purple
'duckdb:vortex-compact': { color: '#7e22ce', width: COMPACT_WIDTH }, // deep purple
'datafusion:parquet': { color: '#38bdf8', width: SECONDARY_WIDTH }, // light blue
'duckdb:parquet': { color: '#2563eb', width: SECONDARY_WIDTH }, // dark(er) blue

P2 Badge Restore chart-series contrast in both themes

When bench uses its Light theme with the white card background, duckdb:vortex-file-compressed has only 2.28:1 contrast and datafusion:parquet only 2.14:1; on the Dark #171716 card, duckdb:vortex-compact reaches only 2.57:1. Because this mode-independent palette uses solid lines, these important graphical objects fall below the 3:1 non-text contrast threshold and become difficult for low-vision readers to distinguish; choose colors that clear 3:1 against both card backgrounds or theme the series colors.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Each format now has a colour that identifies it: Vortex is cobalt
#2f63ff, vortex-compact is red, Parquet is teal, Lance is purple,
DuckDB is yellow, and Arrow is orange. The duckdb run of a format is
a shade of that colour. Widths, order, and dashes do not change.

One set of colours cannot be legible on both cards. A colour with 3:1
contrast on the white card and on the #171716 card must have a very
narrow range of lightness, and ten lines do not fit in it. Thus there
is one set for each theme, and the chart controller recolours a drawn
chart when the theme changes. lib/theme.ts holds the theme helper that
the header and the charts use.

New tests make these rules for each card:

- Each colour has a minimum of 3:1 contrast against the card.
- Each two formats have a minimum ΔE2000 of 25, and 12 with simulated
  protanopia and deuteranopia (Machado 2009).
- All other pairs of lines in a query chart have a minimum ΔE2000 of
  15, and 5 with the simulations.
- Lance is a minimum of 25° of hue from Vortex, and Parquet 20°. No
  format other than Vortex is in its blue family.

The old palette failed all of these rules.

Signed-off-by: Will Manning <will@willmanning.io>
A format now has one colour on every engine. The engine shows as the
line style: a duckdb run is dashed, and a datafusion run is solid. A
cold random-access run stays dashed. The two never share a chart.

DuckDB is green: a soft green on the Dark card and a deep forest
green on the Light card. Parquet's Dark teal moves from #0fb0aa to
#09aeae to stay apart from the green. The duckdb shades and their
tests are removed. seriesStyle returns the dash, so datasets.ts no
longer sets it.

Signed-off-by: Will Manning <will@willmanning.io>

This branch was successfully deployed

2 active (1 outdated) deployments
bench-preview — 25e86249 Deployed Oct 30, 2026 by lwwmanning via Bench: deploy #146
Preview — b9f222a4 Deployed Oct 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants