Nº 271 of 520
Hold to log, streak and confetti
A reading-streak screen: press and hold to log today, the streak digits roll from 11 to 12, today's dot fills, and one short, restrained confetti burst fires.
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 "Hold to log, streak and confetti" (piece streak-hold-confetti) 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º 271 · "Hold to log, streak and confetti" · www.designlounge.live -->
# Hold to log, streak and confetti
> **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
Studied from 60fps.design, a gallery of mobile micro-interaction clips: this piece takes the streak, reward and "complete confetti" ideas from its games and habit shots and rebuilds them as one working success state. It is the daily screen of a fictional reading app called Paath. The user presses and holds a 64px pill for one second; a gold fill sweeps across it and the label turns dark exactly where the gold has reached. On completion the two-digit streak rolls from 11 to 12 (tens and ones on separate spring timings), a "Longest yet" badge pops, Sunday's dashed ring fills gold with a check, and 26 confetti pieces burst from the number and fall away in 1.6 seconds. The detail worth copying is the restraint: one burst, three colours from the palette, no loop, and the hold gesture means the reward is earned, not tapped by accident.
The language is dark and bookish: deep forest ground, cream type, one gold accent, a chunky display serif for the number and a geometric grotesk for everything else.
## Reference behaviour
1. First frame: "Paath" and "Sunday, 4 October" in the header; a gold flame; "11" at 132px; "DAY READING STREAK"; a week row Mon–Sat filled cream with dark checks and Sun as a dashed gold ring; a card "TODAY · 10 PAGES / Seto Bagh, chapter 4 / Pages 41 to 50 · about 14 minutes"; and a pill "Hold to log today" with a book icon.
2. The number does not roll on load; it appears at 11.
3. Pointer down on the pill: it scales to 0.97 and gains a 6px gold halo at 14% alpha. `navigator.vibrate(10)` fires where available. The pointer is captured.
4. While held, a gold gradient (`#a8802a` → `#f2c14e`) reveals left to right over exactly 1000ms, driven by `requestAnimationFrame`. A dark copy of the label sits inside the gold layer, so text is cream over green and dark over gold, split at the fill edge.
5. Release before 1000ms: the fill drains back to 0 over 450ms with `cubic-bezier(.2,.7,.2,1)`. Nothing is logged. Holding again starts from 0.
6. At 1000ms the action completes without waiting for release:
- the pill turns solid gold with the label "Logged · 09:46";
- the tens and ones columns roll to 1 and 2 (700ms spring, ones delayed 60ms);
- the flame plays a 700ms pop (scale 1.25, rotate -8°);
- Sunday's ring fills gold and scales to 1.12 (200ms delay), its check appears (320ms delay), then it settles back to 1 (700ms);
- "Longest yet" scales in from 0.4 (450ms delay);
- 26 confetti pieces burst upward from the number in a 140° fan and fall under gravity, fading out over the last 40% of 1.6s;
- a vibration pattern `[12, 40, 18]` fires;
- a live region says "Logged. 12 day streak, your longest yet."
7. "Undo, I have not read yet" appears under the pill. It restores 11, removes all success states, and returns focus to the pill.
8. Keyboard: holding Space or Enter on the focused pill behaves like holding the pointer; keyup cancels. Key repeat does not restart the timer.
9. Right-click / long-press context menus are suppressed on the pill so a long touch does not open the system menu.
## Structure
```
390 × 844, padding 54 / 20 / 34
┌──────────────────────────────────────┐
│ Paath Sunday, 4 Octob.│ 44px header
│ │ 34px
│ 🜂 │ flame 44px (SVG)
… 229 more lines. Copy or download for the full brief. Loading the source…Brief
269 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
Studied from 60fps.design, a gallery of mobile micro-interaction clips: this piece takes the streak, reward and “complete confetti” ideas from its games and habit shots and rebuilds them as one working success state. It is the daily screen of a fictional reading app called Paath. The user presses and holds a 64px pill for one second; a gold fill sweeps across it and the label turns dark exactly where the gold has reached. On completion the two-digit streak rolls from 11 to 12 (tens and ones on separate spring timings), a “Longest yet” badge pops, Sunday’s dashed ring fills gold with a check, and 26 confetti pieces burst from the number and fall away in 1.6 seconds. The detail worth copying is the restraint: one burst, three colours from the palette, no loop, and the hold gesture means the reward is earned, not tapped by accident.
The language is dark and bookish: deep forest ground, cream type, one gold accent, a chunky display serif for the number and a geometric grotesk for everything else.
Reference behaviour
- First frame: “Paath” and “Sunday, 4 October” in the header; a gold flame; “11” at 132px; “DAY READING STREAK”; a week row Mon–Sat filled cream with dark checks and Sun as a dashed gold ring; a card “TODAY · 10 PAGES / Seto Bagh, chapter 4 / Pages 41 to 50 · about 14 minutes”; and a pill “Hold to log today” with a book icon.
- The number does not roll on load; it appears at 11.
- Pointer down on the pill: it scales to 0.97 and gains a 6px gold halo at 14% alpha.
navigator.vibrate(10)fires where available. The pointer is captured. - While held, a gold gradient (
#a8802a→#f2c14e) reveals left to right over exactly 1000ms, driven byrequestAnimationFrame. A dark copy of the label sits inside the gold layer, so text is cream over green and dark over gold, split at the fill edge. - Release before 1000ms: the fill drains back to 0 over 450ms with
cubic-bezier(.2,.7,.2,1). Nothing is logged. Holding again starts from 0. - At 1000ms the action completes without waiting for release:
- the pill turns solid gold with the label “Logged · 09:46”;
- the tens and ones columns roll to 1 and 2 (700ms spring, ones delayed 60ms);
- the flame plays a 700ms pop (scale 1.25, rotate -8°);
- Sunday’s ring fills gold and scales to 1.12 (200ms delay), its check appears (320ms delay), then it settles back to 1 (700ms);
- “Longest yet” scales in from 0.4 (450ms delay);
- 26 confetti pieces burst upward from the number in a 140° fan and fall under gravity, fading out over the last 40% of 1.6s;
- a vibration pattern
[12, 40, 18]fires; - a live region says “Logged. 12 day streak, your longest yet.”
- “Undo, I have not read yet” appears under the pill. It restores 11, removes all success states, and returns focus to the pill.
- Keyboard: holding Space or Enter on the focused pill behaves like holding the pointer; keyup cancels. Key repeat does not restart the timer.
- Right-click / long-press context menus are suppressed on the pill so a long touch does not open the system menu.
Structure
390 × 844, padding 54 / 20 / 34
┌──────────────────────────────────────┐
│ Paath Sunday, 4 Octob.│ 44px header
│ │ 34px
│ 🜂 │ flame 44px (SVG)
│ 11 │ 132px serif, 2 rolling columns
│ DAY READING STREAK │ 13px caps
│ [Longest yet] │ 30px badge (after success)
│ │ 34px
│ ● ● ● ● ● ● ◌ │ 36px dots, 40px columns
│ Mon Tue Wed Thu Fri Sat Sun │ 12px
│ │ (flex gap)
│ ╭──────────────────────────────────╮ │ card r22, padding 18
│ │ TODAY · 10 PAGES │ │
│ │ Seto Bagh, chapter 4 │ │ serif 22
│ │ Pages 41 to 50 · about 14 min │ │
│ │ ╭──────────────────────────────╮ │ │
│ │ │ ▓▓▓▓▓▓▓ 📖 Hold to log today │ │ │ pill 64px
│ │ ╰──────────────────────────────╯ │ │
│ │ Undo, I have not read yet │ │ 40px link (after success)
│ ╰──────────────────────────────────╯ │
└──────────────────────────────────────┘
Canvas overlay covers the whole app (pointer-events none).
main.appis a flex column withoverflow: hidden; the confetticanvasis absolutely positioned over it witharia-hidden.- The hero is a
sectionlabelled by the “day reading streak” line, which contains a visually hidden “11 ” so the label reads “11 day reading streak”. The rolling digits arearia-hidden. - Each digit is a
div.colcontaining tenspans (0–9) stacked; the parent.numis 132px tall withoverflow: hidden. - The week is an
ollabelled “This week”; Sunday hasaria-current="date". - The goal card is a
sectionlabelled “Today’s reading” withsmall,h2,p, the holdbutton, a hidden help line, and the undobutton. - Inside the hold button: the cream label, then an
aria-hiddengold layer containing a dark copy of the label.
Tokens
:root {
--bg: #10261f; /* forest ground */
--surface: #173229; /* goal card */
--surface-2: #1f3d33; /* hold pill at rest */
--line: #2a4a3f; /* borders, empty dot ring */
--cream: #f6efdf; /* primary text, past dots */
--cream-2: #c9c2b0; /* secondary text */
--cream-3: #9fae9f; /* muted text */
--gold: #f2c14e; /* accent: flame, today, fill end, badge */
--gold-deep: #a8802a; /* fill start */
--sage: #8fb59a; /* third confetti colour */
--serif: "Young Serif", Georgia, serif;
--sans: "Space Grotesk", system-ui, sans-serif;
--num: 132px;
--dot: 36px;
--hold-h: 64px;
--r-card: 22px;
--r-pill: 999px;
--s-2: 8px; --s-3: 12px; --s-4: 16px; --s-5: 18px; --s-6: 20px; --s-8: 34px;
--spring: cubic-bezier(.34, 1.56, .64, 1);
--out: cubic-bezier(.16, 1, .3, 1);
--std: cubic-bezier(.2, .7, .2, 1);
--t-hold: 1000ms;
--t-drain: 450ms;
--t-roll: 700ms;
--t-confetti: 1600ms;
}
Typography
| Role | Family | Size | Weight | Line-height | Tracking | Case |
|---|---|---|---|---|---|---|
| Streak number | Young Serif | 132px | 400 | 1 (132px cells) | -0.04em | tabular |
| Brand | Young Serif | 15px | 400 | 1 | 0.01em | sentence |
| Book title (h2) | Young Serif | 22px | 400 | 1.2 | 0 | sentence |
| Streak unit | Space Grotesk | 13px | 500 | 1.45 | 0.16em | upper |
| Card label | Space Grotesk | 12px | 400 | 1.45 | 0.08em | upper |
| Card body | Space Grotesk | 13px | 400 | 1.45 | 0 | sentence |
| Hold label | Space Grotesk | 16px | 600 | 1 | 0 | sentence |
| Badge | Space Grotesk | 12px | 600 | 1 | 0.04em | sentence |
| Day labels | Space Grotesk | 12px | 400 | 1 | 0 | title |
Motion
| Thing | Trigger | Property | From → to | Duration | Easing | Delay | Reduced motion |
|---|---|---|---|---|---|---|---|
| Pill press | pointerdown / keydown | transform, box-shadow | 1 → 0.97, halo 0 → 6px | 300ms | --spring | 0 | no scale |
| Hold fill | while held | clip-path inset right | 100% → 0% | 1000ms | linear (rAF time) | 0 | same (it is the progress) |
| Drain | early release | clip-path | current → 100% | 450ms | --std | 0 | instant |
| Tens roll | complete | translateY | -1×cell → -1×cell (no change here) | 700ms | --spring | 0 | instant |
| Ones roll | complete | translateY | -1×cell → -2×cell | 700ms | --spring | 60ms | instant |
| Flame pop | complete | transform | 1 → 1.25 rotate(-8°) → 1 | 700ms | --spring | 0 | none |
| Today fill | complete | background, border, scale | dashed → gold, 1 → 1.12 | 300ms / 500ms | --std / --spring | 200ms | colour only |
| Today check | complete | opacity, scale | 0, 0.4 → 1, 1 | 400ms | --spring | 320ms | opacity only |
| Today settle | complete | transform | 1.12 → 1 | 500ms | --spring | 700ms | none |
| Badge | complete | transform, opacity | 0.4, 0 → 1, 1 | 500ms | --spring | 450ms | opacity only |
| Confetti | complete | canvas particles | burst, gravity 0.22/frame, drag 0.985 | 1600ms | physics | 0 | not drawn |
The number cells use the same height as the font size so translateY(-digit × cellHeight) lands exactly. Measure the cell height from the first span, not from the font size, so it survives font loading.
States
- Pill resting:
--surface-2with a 1px--lineborder, cream label. - Pill held: scale 0.97, gold halo; fill revealing.
- Pill partially filled, released: drains.
- Pill logged: solid gold, dark label “Logged · 09:46”,
aria-disabled="true", further holds ignored. - Focus-visible: 2px gold outline, 3px offset, on pill and undo.
- Today ring: dashed gold before, solid gold with dark check after.
- Badge: hidden (scale 0.4, opacity 0) before, shown after.
- Undo:
visibility: hiddenbefore, shown after (so it is not focusable before). - Streak broken (not in demo): number in
--cream-3, flame outlined not filled, and the pill reads “Start again today”.
Accessibility
- The hold button has
aria-describedbypointing to “Press and hold for one second. Holding Space or Enter also works.” - Keyboard hold is fully supported: keydown starts (ignoring
e.repeat), keyup cancels. - The streak number is announced through the hero label (“11 day reading streak”); the visual digits are
aria-hidden. - A polite live region announces the result and the undo.
aria-current="date"marks Sunday in the week list.- The confetti canvas is
aria-hiddenandpointer-events: none. - Contrast:
#f6efdfon#10261f≈ 14:1;#10261fon#f2c14e≈ 10:1;#9fae9fon#10261f≈ 6.5:1. - Hit targets: pill 64px tall full width; undo 40px tall.
- Long-press is a gesture some users cannot perform for a full second. In a product, add a setting to make it a single tap; keep the 1s default.
Responsive rules
- At 360 wide the week columns stay 40px and the gaps shrink (space-between). The pill stays full width.
- Below 720px tall the number drops to 100px (cells 100px) and the vertical gaps shrink so the card still fits above the home clearance.
- At tablet width, centre a 420px column; do not enlarge the number beyond 132px.
- The canvas re-sizes to the app box at each burst using
devicePixelRatiocapped at 2. - Do not draw a status bar.
Acceptance checklist
Always
- The action requires a press-and-hold of a fixed duration; early release drains the progress and logs nothing.
- Text inside the hold control stays readable at every fill level (two label layers split at the fill edge).
- Completion fires at the end of the hold, not on release.
- Each digit rolls in its own column; the ones digit lags the tens by about 60ms.
- The current day indicator fills and settles after a small overshoot.
- Confetti is one burst of 20–30 pieces in palette colours, lasting under 2s, never looping.
- Undo restores every state and returns focus to the control.
- Space/Enter hold works; key repeat is ignored.
- Reduced motion: no confetti, no roll, no pops; numbers and states still change.
- No horizontal scroll at 375px.
This demo
- Streak 11 → 12 at 132px Young Serif, “DAY READING STREAK” underneath.
- Week Mon–Sat done, Sun today.
- Card “Seto Bagh, chapter 4”, “Pages 41 to 50 · about 14 minutes”.
- Pill “Hold to log today” → “Logged · 09:46”; hold time 1000ms.
- Badge “Longest yet”; confetti colours
#f2c14e,#f6efdf,#8fb59a.
Implementation notes
1. The hold timer. Use requestAnimationFrame and elapsed time, not setInterval. Capture the pointer so moving a finger a few pixels does not cancel.
const HOLD = 1000; let holding = false, t0 = 0, raf = 0;
function frame(t) {
if (!holding) return;
const p = Math.min(1, (t - t0) / HOLD);
bar.style.clipPath = `inset(0 ${(1 - p) * 100}% 0 0)`;
if (p >= 1) { holding = false; complete(); return; }
raf = requestAnimationFrame(frame);
}
function start() { if (done || holding) return; holding = true; bar.style.transition = 'none';
t0 = performance.now(); raf = requestAnimationFrame(frame); }
function cancel() { if (!holding) return; holding = false; cancelAnimationFrame(raf);
bar.style.transition = 'clip-path .45s cubic-bezier(.2,.7,.2,1)';
bar.style.clipPath = 'inset(0 100% 0 0)'; }
hold.onpointerdown = e => { hold.setPointerCapture(e.pointerId); start(); };
['pointerup', 'pointercancel', 'lostpointercapture'].forEach(ev => hold.addEventListener(ev, cancel));
hold.onkeydown = e => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); if (!e.repeat) start(); } };
hold.onkeyup = e => { if (e.key === ' ' || e.key === 'Enter') cancel(); };
Common mistake: a scaleX fill under one label. The label is unreadable on half the pill. Use a clip-path layer that contains its own dark label.
2. Rolling digits. Ten stacked spans per column; move the column.
function showNum(n) {
const s = String(n).padStart(2, '0');
const h = tens.firstElementChild.offsetHeight;
tens.style.transform = `translateY(${-s[0] * h}px)`;
ones.style.transform = `translateY(${-s[1] * h}px)`;
}
.num { height: 132px; overflow: hidden; display: flex; }
.ready .col { transition: transform .7s var(--spring); }
.ready .col + .col { transition-delay: .06s; }
Add .ready two frames after load so the first paint is not a roll from 00.
3. Restrained confetti. One burst on a canvas, 26 particles, simple physics, alpha fade in the last 40%.
const ps = Array.from({ length: 26 }, (_, i) => {
const a = -Math.PI / 2 + (Math.random() - .5) * 2.4, v = 5 + Math.random() * 5;
return { x: ox, y: oy, vx: Math.cos(a) * v, vy: Math.sin(a) * v,
w: 5 + Math.random() * 5, h: 3 + Math.random() * 4,
r: Math.random() * 6, vr: (Math.random() - .5) * .4, c: colours[i % 4] };
});
const t1 = performance.now();
(function step(t) {
const k = (t - t1) / 1600; ctx.clearRect(0, 0, W, H); if (k >= 1) return;
for (const q of ps) { q.vy += .22; q.vx *= .985; q.vy *= .985; q.x += q.vx; q.y += q.vy; q.r += q.vr;
ctx.save(); ctx.globalAlpha = k < .6 ? 1 : 1 - (k - .6) / .4;
ctx.translate(q.x, q.y); ctx.rotate(q.r); ctx.fillStyle = q.c;
ctx.fillRect(-q.w / 2, -q.h / 2, q.w, q.h * Math.abs(Math.cos(q.r * 2)) + 1); ctx.restore(); }
requestAnimationFrame(step);
})(t1);
The cos(r*2) height fakes a paper flip. Every fifth piece is a dot for variety.
Common mistakes overall:
- Rainbow confetti or hundreds of pieces. Three palette colours, 26 pieces.
- Confetti that keeps raining. It ends at 1.6s and clears the canvas.
- Triggering the reward on tap. The hold is the point.
- Rolling the number on page load.
- Showing the badge before the number lands; it comes in at 450ms.
- Forgetting
touch-action: noneand the context-menu block, so a long touch selects text or opens a menu. - A green success colour. Gold is the only accent.
Where it sits: the daily check-in of any habit product (reading, language, workouts). Pair it with a calendar elsewhere; this screen shows only the current week.
Details
- Palette
- Type
- Young Serif · Space Grotesk
- Motion
- Rich motion
- Build
- One session
- Tags