Nº 495 of 520
Live cursors on a board
Three named cursors on a rehearsal floor plan. Yours follows the pointer. Click a set piece to claim it.
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 "Live cursors on a board" (piece live-cursors-board) 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º 495 · "Live cursors on a board" · www.designlounge.live -->
# Live cursors on a board
> **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
A shared floor plan for House Lamp, a fictional theatre company, on Thursday's call. The sheet is warm paper on a slightly darker ground. Four set pieces sit on the plan: Riser A, Centre table, Window bay, Prompt desk. Three people are on the board. Mira is you. Jo Hale and Ellis Voss drift. Your cursor is a rust arrow with your name. Theirs are pine and navy. Click a set piece and it takes a rust inset ring and the word Mira. The one detail worth copying is that every cursor carries a name, and only the board hides the system pointer.
## Reference behaviour
1. First frame: header 64px, "House Lamp" in serif, "Floor 2 · Thursday call" in small caps. Three presence chips at the right: Mira (you), Jo Hale, Ellis Voss. Centre table is already claimed (`aria-pressed="true"`), filled `#f3d2c6` with a 2px inset rust ring, and the italic word Mira under the title. Mira's cursor sits on that table. Jo is by the riser. Ellis is on the window bay.
2. Moving the pointer inside the room moves Mira's cursor to that point, clamped 8px inside the room edges. The system cursor is `none` on the board only.
3. Jo's cursor loops a three-point path over 16s, alternate, easing `cubic-bezier(.2,.7,.2,1)`. Ellis loops a different path over 18s. Both are CSS animations. They do not follow the pointer.
4. Click a set piece: it becomes the only pressed piece. The others lose the ring, the wash, and the name. The clicked piece shows "Mira".
5. Cursors are `pointer-events: none`. They never block the click.
6. Tab reaches the four pieces. Focus-visible is a 2px rust outline, 3px offset.
7. Reduced motion: Jo and Ellis stay at their first positions. Mira still follows the pointer. Claiming still works.
8. Leaving the room leaves Mira where the pointer last was.
## Structure
```
1280 × 800
┌ header 64: House Lamp FLOOR 2 · THURSDAY CALL (Mira you) (Jo) (Ellis) ┐
│ room, inset 48px from the sides and 28px from top and bottom of the board │
│ ┌──────────┐ ┌────────────┐ │
│ │ 01 Riser │ │ 03 Window │ │
│ └──────────┘ ┌─ claimed ─────────┐ └────────────┘ │
│ │ 02 Centre table │ │
│ └───────────────────┘ ┌──────────────┐ │
│ │ 04 Prompt │ │
│ Click a piece to claim it. └──────────────┘ plate │
└──────────────────────────────────────────────────────────────────────────────┘
```
- `header.head`: brand in Newsreader 22/600, meta in Public Sans 12/500 uppercase, tracking +0.08em.
- `.who`: three `.chip` pills, height 32px, sheet fill, 1px `--line` border, radius 999px. An 8px dot, then the name.
- `.board` fills the rest. `.room` is the sheet, 1px ink border, an inner rule at `inset: 10px`, and four 14px corner ticks.
… 124 more lines. Copy or download for the full brief. Loading the source…Brief
164 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
A shared floor plan for House Lamp, a fictional theatre company, on Thursday’s call. The sheet is warm paper on a slightly darker ground. Four set pieces sit on the plan: Riser A, Centre table, Window bay, Prompt desk. Three people are on the board. Mira is you. Jo Hale and Ellis Voss drift. Your cursor is a rust arrow with your name. Theirs are pine and navy. Click a set piece and it takes a rust inset ring and the word Mira. The one detail worth copying is that every cursor carries a name, and only the board hides the system pointer.
Reference behaviour
- First frame: header 64px, “House Lamp” in serif, “Floor 2 · Thursday call” in small caps. Three presence chips at the right: Mira (you), Jo Hale, Ellis Voss. Centre table is already claimed (
aria-pressed="true"), filled#f3d2c6with a 2px inset rust ring, and the italic word Mira under the title. Mira’s cursor sits on that table. Jo is by the riser. Ellis is on the window bay. - Moving the pointer inside the room moves Mira’s cursor to that point, clamped 8px inside the room edges. The system cursor is
noneon the board only. - Jo’s cursor loops a three-point path over 16s, alternate, easing
cubic-bezier(.2,.7,.2,1). Ellis loops a different path over 18s. Both are CSS animations. They do not follow the pointer. - Click a set piece: it becomes the only pressed piece. The others lose the ring, the wash, and the name. The clicked piece shows “Mira”.
- Cursors are
pointer-events: none. They never block the click. - Tab reaches the four pieces. Focus-visible is a 2px rust outline, 3px offset.
- Reduced motion: Jo and Ellis stay at their first positions. Mira still follows the pointer. Claiming still works.
- Leaving the room leaves Mira where the pointer last was.
Structure
1280 × 800
┌ header 64: House Lamp FLOOR 2 · THURSDAY CALL (Mira you) (Jo) (Ellis) ┐
│ room, inset 48px from the sides and 28px from top and bottom of the board │
│ ┌──────────┐ ┌────────────┐ │
│ │ 01 Riser │ │ 03 Window │ │
│ └──────────┘ ┌─ claimed ─────────┐ └────────────┘ │
│ │ 02 Centre table │ │
│ └───────────────────┘ ┌──────────────┐ │
│ │ 04 Prompt │ │
│ Click a piece to claim it. └──────────────┘ plate │
└──────────────────────────────────────────────────────────────────────────────┘
header.head: brand in Newsreader 22/600, meta in Public Sans 12/500 uppercase, tracking +0.08em..who: three.chippills, height 32px, sheet fill, 1px--lineborder, radius 999px. An 8px dot, then the name..boardfills the rest..roomis the sheet, 1px ink border, an inner rule atinset: 10px, and four 14px corner ticks.- Each set piece is a
<button class="block">: small uppercase index, a 20px serif title, and an italic “Mira” that isdisplay: noneunlessaria-pressed="true". - Positions inside the room: Riser
36,48240×280. Centre table320,210340×160. Window bay760,48280×190. Prompt desk700,400340×150. .platesitsright: 28px; bottom: 24px, 200px wide, a 1px ink rule on top, three uppercase lines.- Each
.curis a zero-size point. A 22px filled arrow SVG and a 22px name chip (left: 16px; top: 16px, radius 4px, paper text on the person’s colour).
Tokens
:root {
--bg: #e4ddd0; /* page ground */
--sheet: #f4efe6; /* plan and chips */
--ink: #1c1914; /* titles, borders, plate name */
--ink-2: #5c564c; /* indexes, meta, hint */
--line: #c9c0b2; /* chip border */
--mira: #9c3418; /* you: cursor, claim, focus */
--jo: #1b5e45;
--ellis: #243e68;
--you-soft: #f3d2c6; /* claimed piece wash */
--font: "Public Sans", system-ui, sans-serif;
--serif: "Newsreader", Georgia, serif;
--ease: cubic-bezier(.2, .7, .2, 1);
}
Typography
| Role | Family | Size | Weight | Line-height | Tracking | Case |
|---|---|---|---|---|---|---|
| Wordmark | Newsreader | 22px | 600 | 1 | −0.02em | sentence |
| Meta, indexes, plate | Public Sans | 11–12px | 500 | 1.45 | +0.06em to +0.12em | uppercase |
| Chip name | Public Sans | 13px | 600 | 1 | 0 | sentence |
| Piece title | Newsreader | 20px | 600 | 1.1 | 0 | sentence |
| Claim name | Newsreader | 13px | 400 italic | 1.3 | 0 | sentence |
| Cursor name | Public Sans | 12px | 600 | 22px | 0 | sentence |
| Hint | Public Sans | 12px | 500 | 1 | 0 | sentence |
Motion
| Element | Trigger | Property | From → To | Duration | Easing | Notes |
|---|---|---|---|---|---|---|
| Mira cursor | pointermove in the room | transform | previous point → clamped pointer | 0 | — | set directly, no loop |
| Jo cursor | load | transform | (150,150) → (250,250) → (170,310) | 16s | --ease | infinite alternate |
| Ellis cursor | load | transform | (880,110) → (960,180) → (820,160) | 18s | --ease | infinite alternate |
| Claimed piece | click | background, box-shadow | sheet → --you-soft plus 2px inset rust | 0 | — | instant |
Reduced motion: animation: none on Jo and Ellis, so their inline first transforms remain. Mira still tracks the pointer.
States
- Idle piece: sheet fill, 1px ink border, claim line hidden.
- Hover piece: background
#efe8dc. The system cursor stays hidden. - Pressed piece: background
--you-soft,box-shadow: inset 0 0 0 2px var(--mira), italic “Mira” visible. Only one piece is pressed. - Focus-visible:
outline: 2px solid var(--mira); outline-offset: 3px. - Your cursor: rust fill
#9c3418, chip the same, paper label “Mira”. - Jo / Ellis: pine
#1b5e45and navy#243e68. Chips use paper text#f7f3ec.
Accessibility
- The four set pieces are real buttons.
aria-pressedis true on the claimed one only. - Presence chips are not buttons. They are labelled by the group
aria-label="People on this board". - Cursor arrows are
aria-hidden. The names are visible text, not the only name of a control. - Tab order is the four pieces. Cursors are not in the tab order.
- Contrast: ink
#1c1914on sheet#f4efe6is above 12:1. Ink-2#5c564con sheet is about 6:1. Paper on rust, pine, and navy is above 4.5:1. - Hit targets: each piece is at least 150×140.
Responsive rules
- ≥ 1280: as drawn.
- 1024–1279: room padding drops to 24px. Pieces scale down by about 15% but keep the same corners. Cursors stay in percentages of the room if you convert the paths.
- 768–1023: pieces stack in a 2×2 grid inside the room. Header chips wrap under the wordmark. Cursor paths stay inside the room.
- < 768: one column of pieces. The board does not hide the system cursor, because a finger has no pointer to follow. Show the three names as chips only, and keep the claim interaction.
Acceptance checklist
Always
- Exactly three cursors, each with a visible name chip.
- The local cursor follows the pointer only inside the plan, and is clamped 8px inside the edges.
- The system cursor is hidden on the plan and visible everywhere else.
- Cursors do not receive pointer events.
- Clicking a piece claims it and clears the previous claim.
- The two remote cursors move on a slow loop. Reduced motion freezes them at the start of that loop.
- Focus-visible outline is 2px in the local person’s colour.
This demo
- The people are Mira (you,
#9c3418), Jo Hale (#1b5e45), and Ellis Voss (#243e68). - Centre table starts claimed.
- The four pieces are Riser A, Centre table, Window bay, and Prompt desk.
- Header reads “House Lamp” and “Floor 2 · Thursday call”.
Implementation notes
Remote cursors are CSS so the page does not run a frame loop. Give them a resting transform, then let the animation override it. When reduced motion sets animation: none, the resting transform shows again.
.jo { transform: translate(150px, 150px); animation: jo 16s var(--ease) infinite alternate; }
@keyframes jo {
0% { transform: translate(150px, 150px); }
50% { transform: translate(250px, 250px); }
100% { transform: translate(170px, 310px); }
}
@media (prefers-reduced-motion: reduce) { .jo, .ellis { animation: none; } }
The local cursor is a zero-size element. The arrow and the name hang off it. Clamp to the room, not the page:
room.addEventListener('pointermove', function (e) {
var r = room.getBoundingClientRect();
var x = Math.max(8, Math.min(r.width - 28, e.clientX - r.left));
var y = Math.max(8, Math.min(r.height - 28, e.clientY - r.top));
you.style.transform = 'translate(' + x + 'px,' + y + 'px)';
});
Claim is exclusive. Set aria-pressed on the clicked button and clear the others. Do not also move that person’s cursor to the piece. The cursor and the claim are separate.
Common mistakes: hiding the cursor on body, so the header cannot be used; running requestAnimationFrame forever for two drifting dots; making the name chip the only accessible name of the button.
Details
- Palette
- Type
- Newsreader · Public Sans
- Motion
- Rich motion
- Build
- One session
- Tags