Nº 117 of 520
Ink blob cursor
A gooey deep-green ink blob lags the pointer across six editorial cards; overlapping cards invert to ink and lift 8px. Replay recenters the blob.
Preview
Use it
Free to use · credit appreciated1 Once, in your project
npx skills add SusanAcharya/Design-Lounge 2 Then ask
Using the Design Lounge skill, build "Ink blob cursor" (piece cursor-ink-blob) in my stack. Match its brief; restyle it onto my product's theme.
No skill? Paste the brief instead. It carries the numbers, tokens and behaviour.
<!-- Design Lounge Nº 117 · "Ink blob cursor" · designlounge.vercel.app -->
# Ink blob cursor
> **Build brief for a coding agent.** Rebuild this piece in the reader's stack. If they haven't said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don't "improve" them.
## What it is
An issue index for Field Notes, a fictional garden journal. Six editorial cards sit on warm paper. A 168px gooey ink blot — three dark-green SVG circles blurred together with an SVG `feColorMatrix` "goo" filter — follows the pointer with a 0.14 lerp lag. When the blot's centre sits inside a card (8px inset), that card fills with ink `#0f2e22`, type flips to paper, and the card lifts 8px. The system cursor is hidden. Replay recenters the blot and clears hits. The one idea is ink moving over paper, not a generic spotlight.
## Reference behaviour
1. Initial state: paper `#efe8d8`, 64px nav with italic 26px "Field Notes", links Essays (current) / Garden / Archive, Replay on the right. Title "Letters from the plot" plus "Issue 09 · Autumn 2026". Six cards in two rows of three.
2. The blob starts at 62% × 48% of the viewport. `body { cursor: none }`. The blob is `pointer-events: none` and does not steal clicks.
3. `pointermove` writes target coordinates. A `requestAnimationFrame` loop lerps current x/y toward the target by **0.14** per frame (~14% of the remaining distance). The blob's `transform: translate(x px, y px)` uses a −84px margin so the 168px square is centred on that point.
4. Each frame, for every `[data-card]`, if the blob centre is inside the card rect inset by 8px, add class `hit`. Otherwise remove it. Multiple cards can theoretically hit if they overlap; they do not overlap here, so one at a time.
5. `.hit`: background `--ink`, colour `--paper`, border `--ink`, `translateY(-8px)` over 280ms expo-out. Index, paragraph and time go paper at 78% opacity.
6. The SVG circles keep a slow `animate` on r / cx / cy (5s, 6s, 4.5s) so the blot breathes while idle.
7. Replay sets current and target to the start position, applies the transform, and removes `.hit` from every card.
8. Reduced motion: lerp factor becomes **1** (blob sticks to the pointer), circle SMIL animation is off, card transition is 1ms. Hit-testing still runs.
9. Nav links `href="#"` preventDefault. Focus-visible restores a default cursor on the focused control so keyboard users can see it.
## Structure
```
1280 × 800
┌────────────────────────────────────────────────────────────┐
│ Field Notes Essays Garden Archive [Replay] │ 64
├────────────────────────────────────────────────────────────┤
│ Letters from the plot ISSUE 09 · AUTUMN │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 01 · Soil│ │ 02 · Seed│ │ 03 · Light│ cards, 16 gap │
│ │ Clay │ │ Saving │ │ The short │ │
│ │ after… │ │ the beans│ │ afternoon │ │
│ │ 12 Sep │ │ 19 Sep │ │ 26 Sep │ │
│ ├──────────┤ ├──────────┤ ├──────────┤ │
│ │ 04 · Tool│ │ 05 · Kit.│ │ 06 · Note │ │
│ │ A hoe… │ │ First │ │ Leave the │ │
│ │ 1 Oct │ │ quince… │ │ seed heads│ │
└────────────────────────────────────────────────────────────┘
… 135 more lines. Copy or download for the full brief. Loading the source…Brief
175 linesBuild brief for a coding agent. Rebuild this piece in the reader’s stack. If they haven’t said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don’t “improve” them.
What it is
An issue index for Field Notes, a fictional garden journal. Six editorial cards sit on warm paper. A 168px gooey ink blot — three dark-green SVG circles blurred together with an SVG feColorMatrix “goo” filter — follows the pointer with a 0.14 lerp lag. When the blot’s centre sits inside a card (8px inset), that card fills with ink #0f2e22, type flips to paper, and the card lifts 8px. The system cursor is hidden. Replay recenters the blot and clears hits. The one idea is ink moving over paper, not a generic spotlight.
Reference behaviour
- Initial state: paper
#efe8d8, 64px nav with italic 26px “Field Notes”, links Essays (current) / Garden / Archive, Replay on the right. Title “Letters from the plot” plus “Issue 09 · Autumn 2026”. Six cards in two rows of three. - The blob starts at 62% × 48% of the viewport.
body { cursor: none }. The blob ispointer-events: noneand does not steal clicks. pointermovewrites target coordinates. ArequestAnimationFrameloop lerps current x/y toward the target by 0.14 per frame (~14% of the remaining distance). The blob’stransform: translate(x px, y px)uses a −84px margin so the 168px square is centred on that point.- Each frame, for every
[data-card], if the blob centre is inside the card rect inset by 8px, add classhit. Otherwise remove it. Multiple cards can theoretically hit if they overlap; they do not overlap here, so one at a time. .hit: background--ink, colour--paper, border--ink,translateY(-8px)over 280ms expo-out. Index, paragraph and time go paper at 78% opacity.- The SVG circles keep a slow
animateon r / cx / cy (5s, 6s, 4.5s) so the blot breathes while idle. - Replay sets current and target to the start position, applies the transform, and removes
.hitfrom every card. - Reduced motion: lerp factor becomes 1 (blob sticks to the pointer), circle SMIL animation is off, card transition is 1ms. Hit-testing still runs.
- Nav links
href="#"preventDefault. Focus-visible restores a default cursor on the focused control so keyboard users can see it.
Structure
1280 × 800
┌────────────────────────────────────────────────────────────┐
│ Field Notes Essays Garden Archive [Replay] │ 64
├────────────────────────────────────────────────────────────┤
│ Letters from the plot ISSUE 09 · AUTUMN │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 01 · Soil│ │ 02 · Seed│ │ 03 · Light│ cards, 16 gap │
│ │ Clay │ │ Saving │ │ The short │ │
│ │ after… │ │ the beans│ │ afternoon │ │
│ │ 12 Sep │ │ 19 Sep │ │ 26 Sep │ │
│ ├──────────┤ ├──────────┤ ├──────────┤ │
│ │ 04 · Tool│ │ 05 · Kit.│ │ 06 · Note │ │
│ │ A hoe… │ │ First │ │ Leave the │ │
│ │ 1 Oct │ │ quince… │ │ seed heads│ │
└────────────────────────────────────────────────────────────┘
blob 168×168, centred on pointer, z-index 5, not in the grid
- Hidden SVG in the document with
<filter id="goo">:feGaussianBlurstdDeviation 8,feColorMatrixalpha goo (20 / −8),feBlend. <nav>+<main>padding 28/48/32..gridis 3 columns, rowsauto 1fr 1fr, gap 16, height 100% of main (calc(100% − 64px)).- Each card:
.idx,<h2>,<p>,<time>. #blob.blobisposition: fixed; width/height: 168px; margin: -84px 0 0 -84px. Inner SVG viewBox 0 0 120 120, three circles filled#143d2c/#0f2e22.
Card copy
| Idx | Title | Body (short) | Date |
|---|---|---|---|
| 01 · Soil | Clay after rain | The bed by the wall holds water a day longer than the rest. We stopped fighting it and planted what likes wet feet. | 12 Sep |
| 02 · Seed | Saving the beans | Hung in pairs from the rafter until they rattle. Next spring they will remember this roof. | 19 Sep |
| 03 · Light | The short afternoon | Shade reaches the rosemary by four. We moved the bench, not the plant. | 26 Sep |
| 04 · Tool | A hoe with a name | Ash handle, worn where Marta’s thumb sits. Oil it on Sundays; it will outlast the fence. | 1 Oct |
| 05 · Kitchen | First quince jelly | Four fruits, too hard to bite, cooked down to a rose slab that cuts like cheese. | 2 Oct |
| 06 · Note | Leave the seed heads | Goldfinches found the fennel. The plot is not only ours, and the calendar can wait. | 2 Oct |
Tokens
:root {
--paper: #efe8d8; /* page */
--paper-2: #e4dcc8; /* card rest */
--ink: #0f2e22; /* type, hit fill */
--ink-2: #3d5a45; /* card body */
--ink-3: #7a8f7e; /* index, dates, replay */
--line: rgba(15, 46, 34, .16);
--blob: #143d2c; /* circle fill */
--serif: "Instrument Serif", Georgia, serif;
--sans: "Inter", system-ui, sans-serif;
--gutter: 48px;
--t-micro: 160ms;
--t-card: 280ms;
--ease: cubic-bezier(.2, .7, .2, 1);
--expo: cubic-bezier(.16, 1, .3, 1);
}
Typography
| Role | Family | Size | Weight | Line-height | Tracking | Case |
|---|---|---|---|---|---|---|
| Page title | Instrument Serif | 36px | italic 400 | 1.1 | −0.02em | sentence |
| Card title | Instrument Serif | 26px | italic 400 | 1.15 | −0.02em | sentence |
| Brand | Instrument Serif | 26px | italic 400 | 1 | 0 | Title |
| Card body | Inter | 13.5px | 400 | 1.5 | 0 | sentence |
| Nav | Inter | 12px | 500 | 1 | +0.04em | Title |
| Index/date | Inter | 11px | 500 | 1 | +0.06–.14em | UPPERCASE |
| Replay | Inter | 11px | 500 | 1 | +0.12em | UPPERCASE |
Motion
| Element | Trigger | Property | From → To | Duration | Easing | Notes |
|---|---|---|---|---|---|---|
| Blob position | pointermove | transform translate | lerp 0.14 / frame | rAF | — | factor 1 if reduced |
| Blob shape | always | r / cx / cy | ±6px breathe | 4.5–6s | SMIL | off if reduced |
| Card hit | overlap | bg, color, Y | paper → ink, 0 → −8px | 280ms | --expo |
Do not use mix-blend-mode difference (it turns the paper a strange complementary colour). Invert by swapping tokens on .hit.
States
- Card rest:
--paper-2fill, 1px--line, ink type. - Card hit:
--inkfill, paper type, −8px Y, ink border. - Nav current / hover:
--ink. - Replay hover: paper type on ink fill.
- Focus-visible: 2px ink outline, 3px offset,
cursor: defaultso the ring is findable.
Accessibility
cursor: noneis visual only. Keyboard users never need the blob. Cards are not buttons; they do not need a keyboard equivalent of the overlap invert.- Replay is a
<button>and is in the tab order after the nav links. - Goo filter SVG is
aria-hidden. The blob isaria-hidden. - Contrast on rest cards: ink on
--paper-2exceeds 8:1. On hit: paper on ink exceeds 10:1.--ink-3is 11px meta. - Hit target for Replay: padding 8/12, border; pad to 40px tall if the host is touch.
Responsive rules
- ≥ 1280: 3×2 cards, blob 168px, lerp 0.14.
- 1024–1279: same grid, slightly smaller type (card title 22px). Blob 144px.
- 768–1023: 2 columns, 3 rows. Blob 128px.
- < 640: 1 column. Show the system cursor (
cursor: auto) — a hidden cursor on a phone is unusable. Still invert a card on pointerdown if a stylus appears.
Acceptance checklist
- Blob is 168px, goo-filtered, filled
#143d2c/#0f2e22, and lags the pointer with lerp 0.14. - System cursor is hidden on the page; the blob has
pointer-events: none. - A card inverts to ink and lifts 8px when the blob centre is inside it (8px inset).
- Leaving the card restores paper-2 and Y=0 over 280ms.
- Replay recenters the blob at 62%/48% and clears
.hit. - Reduced motion: blob tracks with no lag, no SMIL, 1ms card transitions; invert still happens.
- Six cards with the copy in the table, Instrument Serif titles, Inter body.
- No emoji, no purple, no glassmorphism.
- Only Instrument Serif and Inter load.
Implementation notes
Goo from three circles, not a morphing path, keeps the SVG tiny:
<filter id="goo">
<feGaussianBlur in="SourceGraphic" stdDeviation="8" result="b"/>
<feColorMatrix in="b" mode="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -8" result="g"/>
<feBlend in="SourceGraphic" in2="g"/>
</filter>
Lag and hit-test in one rAF so they cannot drift:
function tick() {
const k = reduce ? 1 : 0.14;
x += (tx - x) * k;
y += (ty - y) * k;
blob.style.transform = `translate(${x}px,${y}px)`;
cards.forEach((c) => {
const r = c.getBoundingClientRect();
c.classList.toggle('hit',
x > r.left + 8 && x < r.right - 8 && y > r.top + 8 && y < r.bottom - 8);
});
requestAnimationFrame(tick);
}
Centre with negative margin, not translate(-50%,-50%) stacked on the lerp translate — two translates on the same property overwrite each other.
Common mistakes: mix-blend-mode: difference on cream paper (the blot goes magenta). Putting pointer-events on the blob so cards never receive hover. Lerping at 0.4, which feels glued, or 0.04, which never catches up.
Details
- Palette
- Type
- Instrument Serif · Inter
- Motion
- Rich motion
- Build
- One session
- Tags
On the shelves