Skip to content
Closed
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
21 changes: 7 additions & 14 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,6 @@
.pen-note{position:absolute;right:16px;bottom:10px;font-family:var(--hand);font-size:20px;line-height:1.1;color:var(--red);opacity:0;transition:opacity .28s ease .3s;pointer-events:none;white-space:nowrap}
.pen-note--a{transform:rotate(-1.6deg)}
.pen-note--b{transform:rotate(-2.9deg)}
.pen-note--c{transform:rotate(-3.4deg)}
.pen-note--d{transform:rotate(-1.9deg)}
.pen-note--e{transform:rotate(-0.5deg)}
.lab:hover .pen-note,.lab:focus-within .pen-note{opacity:.92}
Expand Down Expand Up @@ -227,6 +226,7 @@
.lab__links{display:flex;flex-wrap:wrap;gap:4px 28px;margin-top:auto;padding-top:14px}
.lab__links .lab__go{margin-top:0;padding-top:0}
.lab__go--alt{position:relative;z-index:1}
.lab--private{grid-column:1/-1}
.lab--private .lab__go{color:var(--soft)}
.also{margin-top:28px;font-size:16px}
.also a{display:inline-flex;align-items:center;min-height:44px}
Expand Down Expand Up @@ -465,15 +465,8 @@ <h3><a href="/beijing-loop/">Beijing — Endless <span class="ul" data-ink="1">S
<span class="pen-note pen-note--b" aria-hidden="true">one take</span>
</li>
<li class="lab" data-ruled>
<p class="lab__n"><span class="mono">Exp. 03 · Cloudflare Workers</span><span class="mono">live demo</span></p>
<h3><a href="https://open-ledger.brickerp.workers.dev"><span class="ul" data-ink="2">open-ledger</span></a></h3>
<p class="lab__fact">Every fill hash-chained into an append-only ledger · verify it in the browser</p>
<span class="lab__go">Open the ledger ↗</span>
<span class="pen-note pen-note--c" aria-hidden="true">verify it yourself</span>
</li>
<li class="lab" data-ruled>
<p class="lab__n"><span class="mono">Exp. 04 · public skill · MIT</span><span class="mono">Cookiy AI</span></p>
<h3><a href="https://github.com/cookiy-ai/user-research-skill"><span class="ul" data-ink="3">user-research-skill</span></a></h3>
<p class="lab__n"><span class="mono">Exp. 03 · public skill · MIT</span><span class="mono">Cookiy AI</span></p>
<h3><a href="https://github.com/cookiy-ai/user-research-skill"><span class="ul" data-ink="2">user-research-skill</span></a></h3>
<p class="lab__fact">1.5k+ GitHub stars · the open-source skill that gives Claude, Codex, Cursor and OpenClaw end-to-end user research</p>
<div class="lab__links">
<span class="lab__go">user-research-skill on GitHub ↗</span>
Expand All @@ -482,14 +475,14 @@ <h3><a href="https://github.com/cookiy-ai/user-research-skill"><span class="ul"
<span class="pen-note pen-note--d" aria-hidden="true">read the source</span>
</li>
<li class="lab lab--private" data-ruled>
<p class="lab__n"><span class="mono">Exp. 05 · private · Baidu MeDo</span><span class="mono">not public</span></p>
<h3>Agent platform &amp; <span class="ul" data-ink="4">revenue backend</span></h3>
<p class="lab__n"><span class="mono">Exp. 04 · private · Baidu MeDo</span><span class="mono">not public</span></p>
<h3>Agent platform &amp; <span class="ul" data-ink="3">revenue backend</span></h3>
<p class="lab__fact"><span class="redact" role="img" aria-label="6,750,000">█,███,███</span> ledger entries reconciled in the overseas billing migration</p>
<a class="lab__go" href="/resume.pdf">Details in the resume →</a>
<span class="pen-note pen-note--e" aria-hidden="true">ask me about this</span>
</li>
</ol>
<p class="also">Also: <a href="https://ledger-mcp.brickerp.workers.dev">ledger-mcp ↗</a>&nbsp;· <a href="/about/">full programme →</a>&nbsp;· <a href="/resume.pdf">resume (PDF)</a></p>
<p class="also">Also: <a href="/about/">full programme →</a>&nbsp;· <a href="/resume.pdf">resume (PDF)</a></p>
</div>
</section>

Expand Down Expand Up @@ -562,7 +555,7 @@ <h3>Agent platform &amp; <span class="ul" data-ink="4">revenue backend</span></h

<section id="contact" class="sec contact" aria-labelledby="contact-h">
<div class="wrap">
<header class="sec-h"><span class="mono">§3 · Contact</span><h2 id="contact-h">Exp. 06 — your project</h2></header>
<header class="sec-h"><span class="mono">§3 · Contact</span><h2 id="contact-h">Exp. 05 — your project</h2></header>
<p class="contact__sub">I work on agent tool use, streaming and real-time backends, billing systems, and the tests and operational checks that keep them reliable.</p>
<aside class="status-card" data-ruled aria-label="Status and terms">
<p class="mono">Status · 2026</p>
Expand Down
15 changes: 8 additions & 7 deletions scripts/verify-static.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -510,16 +510,16 @@ assert.match(rootHtml, /id=["']pendulum-burst["']/, 'index.html: the burst layer
assert.match(rootHtml, /id=["']notebook-stamp["']/, 'index.html: the live stamp must exist');
assert.match(rootHtml, /id=["']notebook-page["']/, 'index.html: the visit counter must exist');
assert.match(pendulumSvg, /id=["']pendulum-burst["']/, 'index.html: the burst layer stays inside Fig. 1');
// The reader's pen: one underlined key phrase per lab entry, five hands that
// The reader's pen: one underlined key phrase per lab entry, four hands that
// must not repeat, drawn in real pixels (never a stretched viewBox), and
// the lab entry's whole-card link must stay whole.
const pensSource = await text('src/pens.ts');
assert.match(pendulumSource, /from\s+['"]\.\/pens['"]/, 'src/pendulum.ts: must boot the reader\'s pen');
const inkSpans = [...rootHtml.matchAll(/class=["']ul["'][^>]*data-ink=["'](\d+)["']/g)].map((match) => match[1]);
assert.deepEqual(inkSpans, ['0', '1', '2', '3', '4'], 'index.html: every lab entry needs its underlined phrase, in order');
assert.deepEqual(inkSpans, ['0', '1', '2', '3'], 'index.html: every lab entry needs its underlined phrase, in order');
const inkSeeds = [...pensSource.matchAll(/seed:\s*(\d+)/g)].map((match) => match[1]);
assert.equal(inkSeeds.length, 10, 'src/pens.ts: five hands, two strokes each');
assert.equal(new Set(inkSeeds).size, 10, 'src/pens.ts: no two strokes may share a seed');
assert.equal(inkSeeds.length, 8, 'src/pens.ts: four hands, two strokes each');
assert.equal(new Set(inkSeeds).size, 8, 'src/pens.ts: no two strokes may share a seed');
assert.doesNotMatch(pensSource, /vector-effect|non-scaling-stroke/, 'src/pens.ts: pen strokes must not rely on non-scaling strokes');
assert.match(
rootHtml,
Expand Down Expand Up @@ -566,7 +566,7 @@ assert.match(plateSource, /IntersectionObserver/, 'src/plate.ts: the plate plays
const rulingSource = await text('src/ruling.ts');
assert.match(pendulumSource, /from\s+['"]\.\/ruling['"]/, 'src/pendulum.ts: must boot the hand-drawn rulings');
assert.ok(
(rootHtml.match(/\bdata-ruled\b/g) ?? []).length >= 8,
(rootHtml.match(/\bdata-ruled\b/g) ?? []).length >= 7,
'index.html: the plate, every lab entry, the margin note and the status card must carry data-ruled',
);
assert.match(rootHtml, /\.ruling__frame\{[^}]*animation:ruling-boil/, 'index.html: rulings must boil in stepped frames');
Expand All @@ -579,10 +579,10 @@ assert.doesNotMatch(
'src/paper.ts: the ink mark and the K splash are retired',
);

// Contact: the status card replaced the red checks, and Exp. 06 keeps the story.
// Contact: the status card replaced the red checks, and Exp. 05 keeps the story.
assert.match(rootHtml, /class=["']status-card["']/, 'index.html: #contact must print the status card');
assert.doesNotMatch(rootHtml, /class=["']contact__list["']/, 'index.html: the red checklist must be gone');
assert.match(rootHtml, /Exp\. 06 — your project/, 'index.html: the contact title must keep the Exp. 06 story');
assert.match(rootHtml, /Exp\. 05 — your project/, 'index.html: the contact title must keep the Exp. 05 story');

// The page's spine: the three floors stated once at the top, and the two
// exhibits that carry their own entry context.
Expand Down Expand Up @@ -627,6 +627,7 @@ for (const [, href, label] of rootHtml.matchAll(/<a\b[^>]*href=["']([^"']+)["'][
assert.match(rootHtml, /<h3><a href="https:\/\/github\.com\/cookiy-ai\/user-research-skill">/, 'index.html: the Cookiy entry must open user-research-skill, its most-starred public repository');
assert.doesNotMatch(rootHtml, /npmjs\.com\/package\/cookiy/, 'index.html: the Cookiy entry must not stop at a package page');
assert.doesNotMatch(rootHtml, /href=["']\/hire\/["']/, 'index.html: /hire/ only redirects back to #contact and must not be linked from the homepage');
assert.doesNotMatch(rootHtml, /open-ledger|ledger-mcp/, 'index.html: the retired ledger Workers must not be linked from the homepage');

// Fig. 2: a seeded random process with a stop rule, measured over 999
// futures — canvases for the drawing, the numbers printed from the sim,
Expand Down
22 changes: 4 additions & 18 deletions src/pens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*
* Each entry gets one key phrase underlined by two strokes, the way a
* reader marks a textbook: a pressured centre line with tapered ends and
* ragged edges, then a thinner second pass a beat later. The five hands
* are deliberately unlike one another — confident, slipped, looped, lazy,
* ragged edges, then a thinner second pass a beat later. The four hands
* are deliberately unlike one another — confident, slipped, lazy,
* hesitant. Strokes are filled shapes revealed in stepped frames (a few
* chunks of ink at a time, stop-motion style) on hover or focus.
* Decorative: aria-hidden, pointer-transparent, drawn in real pixels so
Expand Down Expand Up @@ -39,8 +39,6 @@ interface Hand {
hook?: number;
/** A flick as the pen lifts. */
flick?: number;
/** A small curl as the pen leaves. */
loop?: boolean;
/** Draw only the trailing share of the phrase. */
frac?: number;
}
Expand All @@ -57,17 +55,12 @@ const HANDS: ReadonlyArray<readonly [Hand, Hand]> = [
{ seed: 23, from: -2, to: 6, y: 5.6, tilt: 1.6, amp: 0.9, tremble: 0.2, w: 3.2, floor: 0.22, flick: 1.2 },
{ seed: 24, from: 10, to: -3, y: 12.6, tilt: -1.4, amp: 0.8, tremble: 0.3, w: 2.2, floor: 0.2 },
],
// 2 · open-ledger — wavy, and the second pass leaves with a curl
[
{ seed: 35, from: -5, to: 4, y: 5.4, tilt: 0.4, amp: 1.5, tremble: 0.5, w: 2.8, floor: 0.3, hook: 0.8 },
{ seed: 36, from: 6, to: -8, y: 11.4, tilt: -0.6, amp: 1.1, tremble: 0.4, w: 2.0, floor: 0.28, loop: true },
],
// 3 · cookiy-mcp — one long stroke, then a lazy half of a second
// 2 · user-research-skill — one long stroke, then a lazy half of a second
[
{ seed: 47, from: -3, to: 14, y: 5.0, tilt: -0.4, amp: 0.6, tremble: 0.2, w: 3.3, floor: 0.18 },
{ seed: 48, from: 0, to: -2, y: 11.0, tilt: 0.9, amp: 0.5, tremble: 0.35, w: 2.0, floor: 0.3, frac: 0.55 },
],
// 4 · revenue backend — private work, the least certain hand
// 3 · revenue backend — private work, the least certain hand
[
{ seed: 59, from: -2, to: 2, y: 5.6, tilt: 0.8, amp: 1.7, tremble: 1.1, w: 2.4, floor: 0.45, hook: 1.2 },
{ seed: 60, from: 8, to: -4, y: 11.8, tilt: -0.8, amp: 1.4, tremble: 1.2, w: 1.8, floor: 0.4, frac: 0.4 },
Expand Down Expand Up @@ -95,13 +88,6 @@ function stroke(width: number, hand: Hand, start: number): string {
hand.w * (hand.floor + (1 - hand.floor) * pressure) * (0.9 + 0.2 * random()),
]);
}
if (hand.loop) {
const [ex, ey] = pts[pts.length - 1]!;
for (let k = 1; k <= 14; k += 1) {
const t = (k / 14) * Math.PI * 2 * 0.92;
pts.push([ex + Math.sin(t) * 4.2 + k * 0.25, ey - (1 - Math.cos(t)) * 3.1, hand.w * 0.46]);
}
}
const top: Array<[number, number]> = [];
const bottom: Array<[number, number]> = [];
for (let i = 0; i < pts.length; i += 1) {
Expand Down
Loading