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
2 changes: 2 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ path = [
"src/**.css",
# The design system's modules, vendored unchanged (src/vendor/vortex-design/README.md).
"src/vendor/**.js",
# The hashes that guard those copies (tests/unit/styles.test.ts).
"src/vendor/vortex-design/SHA256SUMS",
"scripts/**",
"tests/**",
".github/**",
Expand Down
24 changes: 24 additions & 0 deletions bench/web/app/vortex-skin.css
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,27 @@ body {
border-color: var(--border);
color: var(--fg);
}

/* The chrome in the system's label voice: the page title and the toolbar buttons read as mono
capitals, ranks are muted so Blue marks only ratios, and the count badges are square. */
.site-title,
.control-btn {
font-family: 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
font-weight: 500;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.site-title {
font-size: 12px;
color: var(--muted);
}
.control-btn {
font-size: 11px;
}
.score-rank {
color: var(--muted);
}
.filter-badge,
.group-count {
border-radius: 2px;
}
16 changes: 16 additions & 0 deletions bench/web/app/vortex-skin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const VISUAL = new Set([
'accent-color',
'font-family',
'font-weight',
// The label voice: size, tracking, and case change how text looks.
'font-size',
'letter-spacing',
'text-transform',
]);

const declsOf = (path: string) =>
Expand Down Expand Up @@ -131,6 +135,18 @@ describe('vortex-skin.css', () => {
}
});

it('sets the title and toolbar buttons as mono labels, mutes ranks, and squares the badges', () => {
for (const sel of ['.site-title', '.control-btn']) {
expect(valueOf(sel, 'font-family'), sel).toMatch(/Geist Mono/);
expect(valueOf(sel, 'text-transform'), sel).toBe('uppercase');
}
expect(valueOf('.score-rank', 'color')).toBe('var(--muted)');
expect(valueOf('.site-title', 'color')).toBe('var(--muted)');
for (const sel of ['.filter-badge', '.group-count']) {
expect(valueOf(sel, 'border-radius'), sel).toBe('2px');
}
});

it('loads after globals.css in the root layout', () => {
const layout = read('./layout.tsx');
const globals = layout.indexOf("import './globals.css';");
Expand Down
86 changes: 86 additions & 0 deletions scripts/mark-frame.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Prints a still of the homepage's ASCII mark as text, for src/components/blog/mark-frame.ts.
// It loads the page with reduced motion, records each character the mark's canvas draws,
// and lays them back on their grid, without the dot field behind the mark.
// Usage: bun scripts/mark-frame.mjs [site url]
import { chromium } from "@playwright/test";

const url = process.argv[2] ?? "http://localhost:3000/";
const browser = await chromium.launch();
const page = await browser.newPage({
viewport: { width: 1512, height: 860 },
reducedMotion: "reduce"
});
await page.addInitScript(() => {
window.__chars = [];
const fill = CanvasRenderingContext2D.prototype.fillText;
CanvasRenderingContext2D.prototype.fillText = function (text, x, y, ...rest) {
window.__chars.push([text, x, y, this.fillStyle]);
return fill.call(this, text, x, y, ...rest);
};
});
await page.goto(url, { waitUntil: "networkidle" });
// The still frame is the reduced-motion mark. Without WebGL the mark falls back
// to its static symbol (or errors), and there is no frame to capture.
const mode = await page
.waitForFunction(
() => document.querySelector("[data-mark-mode]")?.dataset.markMode
)
.then((handle) => handle.jsonValue());
if (mode !== "reduced") {
await browser.close();
throw new Error(
`the mark drew in "${mode}" mode, not "reduced": no frame to capture`
);
}
const frame = await page.evaluate(() => {
// The dot field behind the mark is drawn in its own colour, and every
// character in it is a ".". The mark's dimmest pixels are dots too, in an ink
// colour that also draws other characters, so only the field's colour goes.
const byColor = new Map();
for (const [text, , , color] of window.__chars) {
byColor.set(color, (byColor.get(color) ?? new Set()).add(text));
}
const field = [...byColor]
.filter(([, texts]) => texts.size === 1 && texts.has("."))
.map(([color]) => color);
const chars = window.__chars.filter((c) => !field.includes(c[3]));
const step = (values) => {
Comment thread
lwwmanning marked this conversation as resolved.
Resolved
const sorted = [...new Set(values)].sort((a, b) => a - b);
return Math.min(
...sorted
.slice(1)
.map((v, i) => v - sorted[i])
.filter((d) => d > 0.5)
);
};
const xs = chars.map((c) => c[1]);
const ys = chars.map((c) => c[2]);
const [x0, y0, dx, dy] = [
Math.min(...xs),
Math.min(...ys),
step(xs),
step(ys)
];
const grid = new Map(
chars.map(([t, x, y]) => [
`${Math.round((y - y0) / dy)},${Math.round((x - x0) / dx)}`,
t
])
);
const keys = [...grid.keys()].map((k) => k.split(",").map(Number));
const rows = Math.max(...keys.map(([r]) => r)) + 1;
const cols = Math.max(...keys.map(([, c]) => c)) + 1;
const lines = Array.from({ length: rows }, (_, r) =>
Array.from({ length: cols }, (_, c) => grid.get(`${r},${c}`) ?? " ")
.join("")
.trimEnd()
);
while (lines.length && !lines[0]) lines.shift();
while (lines.length && !lines.at(-1)) lines.pop();
const indent = Math.min(
...lines.filter(Boolean).map((l) => l.length - l.trimStart().length)
);
return lines.map((l) => l.slice(indent)).join("\n");
});
console.log(frame);
await browser.close();
12 changes: 10 additions & 2 deletions src/app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
import { MARK_FRAME } from "@/components/blog/mark-frame";
import { PostRows } from "@/components/blog/post-rows";
import { NewsletterForm } from "@/components/newsletter-form";
import { externalPosts } from "@/content/links";
Expand Down Expand Up @@ -30,7 +31,8 @@ const FEEDS = [
] as const;

/**
* The design system's BlogIndex card: the heading with the feeds, then one
* The design system's BlogIndex card: the heading with the feeds, over a dim
* still of the ASCII mark (`MARK_FRAME`), then one
* list, newest first, of the site's own posts and the posts on other sites
* (`src/content/links.ts`). The newsletter sign-up closes the list.
*/
Expand All @@ -39,7 +41,13 @@ export default function BlogPage() {

return (
<div className="blog-page">
<div className="vx-blog-head">
<div className="vx-blog-head has-mark">
{/* The frame is the ::before's content, so it is decoration, not text. */}
<span
className="vx-blog-mark"
aria-hidden="true"
data-frame={MARK_FRAME}
/>
<h1 className="vx-h2 vx-blog-title">Blog</h1>
<nav className="vx-label vx-blog-feeds" aria-label="Feeds">
{FEEDS.map((f) => (
Expand Down
23 changes: 23 additions & 0 deletions src/components/blog/mark-frame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// A still of the homepage's ASCII mark at rest (its reduced-motion frame), as
// text: the blog index sets it behind its heading. Regenerate it with
// `bun scripts/mark-frame.mjs <site url>` when the mark changes.
export const MARK_FRAME = ` =*=:
##+-
: ##*#*- =:
=##+##*: ##*#*+ :##*=.
:*##+*#*-=#*##*+ :*##==.
:*##+*###*##+-=##*#+
:====####++:==-##*######=-
:#+++##-:..:: ###**##+=:
:=*#######++ .-+##*=-.
=-:..-*#+=- *##==. :=:
=#***. ##############=
:######++ =+++*#*++=.....:
=#*##+##++=: *###########*+:
:##*=-#*####*=###-.........
=##+=:=#*##*###*###*:
:===- =#*##+=:###*###=.
=#*#*+- =##**###=.
=+#*+- :==::*##+-
=#*+- :--
*++=:`;
2 changes: 1 addition & 1 deletion src/lib/figures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export interface Bar {
name: string;
value: number;
/** Vortex's own bar, in `chart-1`. People and other systems are grey. */
/** Vortex's own bar, in `chart-vortex`. People and other systems are grey. */
ours?: boolean;
/** A ratio after the value, such as "0.62×". */
extra?: string;
Expand Down
4 changes: 3 additions & 1 deletion src/styles/vortex-base.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* Vortex design system base, copied by tools/site-styles.mjs.
Edit the system, then run tools/site-styles.mjs; do not edit here. */
Edit the system, then run tools/site-styles.mjs; do not edit here.
Made by: node tools/site-styles.mjs <site> --sections base,brand,site,docs,blog,focus --faces --vx-face --base
sha256 279b04a3a69a674b69363402659dcac31b4e7e64fbe1e91568c20c1382847064 */

/* Vortex base: a small reset for a site drawn with the system, such as vortex.dev after Tailwind
left. Load it with the tokens and bundle.css. The components never depend on it: they draw the
Expand Down
12 changes: 5 additions & 7 deletions src/styles/vortex-tokens.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* Vortex design system tokens, copied by tools/site-styles.mjs.
Edit the system, then run tools/site-styles.mjs; do not edit here. */
Edit the system, then run tools/site-styles.mjs; do not edit here.
Made by: node tools/site-styles.mjs <site> --sections base,brand,site,docs,blog,focus --faces --vx-face --base
sha256 28ff622d2c049d9c7b73ebab1f1c29fd4c61761c6b2de1cac042256d0f2dfd05 */
:root, [data-theme="dark"] {
--white: #ffffff;
--grey-50: #fcfcf9;
Expand Down Expand Up @@ -31,7 +33,6 @@
--ink: var(--white);
--ink-body: var(--grey-300);
--ink-muted: var(--grey-450);
--ink-faint: var(--grey-500);
--accent: var(--blue-bright);
--accent-ink: var(--blue-soft);
--accent-soft: rgba(47, 99, 255, 0.16);
Expand All @@ -45,7 +46,7 @@
--syntax-function: var(--orange);
--syntax-string: var(--white);
--syntax-comment: var(--grey-450);
--chart-1: var(--accent);
--chart-vortex: var(--accent);
--chart-muted: var(--grey-600);
--chart-parquet: #0d8a97;
--chart-lance: #976fa1;
Expand All @@ -60,7 +61,6 @@
--status-warn: var(--orange);
--status-error: var(--red);
--status-idle: var(--grey-500);
--shadow-float: 0 8px 24px rgba(0, 0, 0, 0.5);
color-scheme: dark;
}
[data-theme="light"] {
Expand All @@ -73,7 +73,6 @@
--ink: var(--black);
--ink-body: var(--grey-600);
--ink-muted: var(--grey-500);
--ink-faint: var(--grey-400);
--accent: var(--blue);
--accent-ink: var(--blue);
--accent-soft: rgba(29, 15, 237, 0.1);
Expand All @@ -87,7 +86,7 @@
--syntax-function: #a8460f;
--syntax-string: var(--black);
--syntax-comment: var(--grey-500);
--chart-1: var(--accent);
--chart-vortex: var(--accent);
--chart-muted: var(--grey-300);
--chart-track: var(--grey-100);
--chart-2: #a8460f;
Expand All @@ -99,7 +98,6 @@
--status-warn: var(--orange);
--status-error: var(--red);
--status-idle: var(--grey-400);
--shadow-float: 0 8px 16px rgba(0, 0, 0, 0.08);
color-scheme: light;
}
:root {
Expand Down
Loading
Loading