Nº 429 of 520
Sticker activity rings
Three chunky concentric rings, Burn, Hustle and Rise, that spring-fill on load on a cream sticker card; tap a ring or pill for its detail and hourly bars.
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 "Sticker activity rings" (piece widget-activity-rings) 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º 429 · "Sticker activity rings" · designlounge.vercel.app -->
# Sticker activity rings
> **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 daily activity widget for a fictional app called Hoopla, drawn like a sticker: cream card, 2.5px ink border, hard 8px offset shadow, on a sunflower dotted ground. Three concentric rings track Burn (kcal), Hustle (hard minutes) and Rise (hours with movement). They spring-fill on load with a slight overshoot, staggered outside-in. Tapping a ring, or one of three pill buttons, focuses it: the other rings fade to 32%, the chosen ring thickens from 26 to 30, and the panel swaps to its number, a one-line nudge, and 12 hourly bars. A black step-count chip hangs off the card's bottom edge, tilted −3°. The detail worth copying is the overshoot fill plus the thickening on focus; together they make the rings feel like rubber bands.
## Reference behaviour
1. Initial state: rings empty, Burn selected (pill pressed), panel shows Burn with the number counting up.
2. Two frames after load, rings fill: Burn → 87% (520/600), Hustle → 60% (27/45), Rise → 83% (10/12). Delays 0 / 140 / 280ms, each 1000ms on `cubic-bezier(.34,1.56,.64,1)` (overshoots then settles).
3. The panel number counts 0 → value over 900ms (cubic ease out) with thousands separators.
4. Click a pill (Burn / Hustle / Rise) or click on a ring's stroke or track: that ring is selected and focused. Non-selected progress strokes go to opacity .32; the selected one goes to stroke-width 30.
5. The panel replays a 320ms pop: from opacity 0, translateY 10px, rotate −1° to rest, on the springy easing. Number counts up again from 0.
6. Hourly bars (7 am → 6 pm, 12 bars) animate to the new heights (320ms, springy). Hours with zero activity show an empty outlined stub (8px tall, card fill).
7. Selecting the already-focused ring again clears focus: all rings back to full opacity and 26 stroke.
8. Replay (top-right of the panel) empties the rings with no transition, then refills with the stagger, and clears focus.
9. Each ring has a white glyph at 12 o'clock inside the stroke: flame (Burn), bolt (Hustle), up arrow (Rise).
## Structure
```
1280 × 800, #ffd447 with 22px ink dot grid (12% alpha)
┌──────────────────── 760px card, 2.5px ink border, radius 32 ────────────────────┐
│ ┌──────────────┐ HOOPLA · SATURDAY ( ↻ Replay ) │
│ │ ◯ 126 r │ ( ● Burn ) ( ● Hustle ) ( ● Rise ) 40px pills │
│ │ ◯ 94 r │ │
│ │ ◯ 62 r │ 520 / 600 kcal 76px display numeral │
│ │ stroke 26 │ 80 kcal to close Burn. A brisk 15-minute walk does it. │
│ └──────────────┘ ▂▃▅▄▃▂▆█▄▃▂▂ 12 bars, 64px tall │
│ 300 × 300 7 am 12 pm 6 pm │
└──( ⋮⋮ 8,412 steps )─────────────────────────────────────────────────────────────┘
chip: ink pill, −3°, overlapping the bottom border by 22px
8px 8px 0 ink offset shadow
```
- `main.card` is a grid `300px 1fr`, gap 36px, padding 32/36/32/32.
… 145 more lines. Copy or download for the full brief. Loading the source…Brief
185 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 daily activity widget for a fictional app called Hoopla, drawn like a sticker: cream card, 2.5px ink border, hard 8px offset shadow, on a sunflower dotted ground. Three concentric rings track Burn (kcal), Hustle (hard minutes) and Rise (hours with movement). They spring-fill on load with a slight overshoot, staggered outside-in. Tapping a ring, or one of three pill buttons, focuses it: the other rings fade to 32%, the chosen ring thickens from 26 to 30, and the panel swaps to its number, a one-line nudge, and 12 hourly bars. A black step-count chip hangs off the card’s bottom edge, tilted −3°. The detail worth copying is the overshoot fill plus the thickening on focus; together they make the rings feel like rubber bands.
Reference behaviour
- Initial state: rings empty, Burn selected (pill pressed), panel shows Burn with the number counting up.
- Two frames after load, rings fill: Burn → 87% (520/600), Hustle → 60% (27/45), Rise → 83% (10/12). Delays 0 / 140 / 280ms, each 1000ms on
cubic-bezier(.34,1.56,.64,1)(overshoots then settles). - The panel number counts 0 → value over 900ms (cubic ease out) with thousands separators.
- Click a pill (Burn / Hustle / Rise) or click on a ring’s stroke or track: that ring is selected and focused. Non-selected progress strokes go to opacity .32; the selected one goes to stroke-width 30.
- The panel replays a 320ms pop: from opacity 0, translateY 10px, rotate −1° to rest, on the springy easing. Number counts up again from 0.
- Hourly bars (7 am → 6 pm, 12 bars) animate to the new heights (320ms, springy). Hours with zero activity show an empty outlined stub (8px tall, card fill).
- Selecting the already-focused ring again clears focus: all rings back to full opacity and 26 stroke.
- Replay (top-right of the panel) empties the rings with no transition, then refills with the stagger, and clears focus.
- Each ring has a white glyph at 12 o’clock inside the stroke: flame (Burn), bolt (Hustle), up arrow (Rise).
Structure
1280 × 800, #ffd447 with 22px ink dot grid (12% alpha)
┌──────────────────── 760px card, 2.5px ink border, radius 32 ────────────────────┐
│ ┌──────────────┐ HOOPLA · SATURDAY ( ↻ Replay ) │
│ │ ◯ 126 r │ ( ● Burn ) ( ● Hustle ) ( ● Rise ) 40px pills │
│ │ ◯ 94 r │ │
│ │ ◯ 62 r │ 520 / 600 kcal 76px display numeral │
│ │ stroke 26 │ 80 kcal to close Burn. A brisk 15-minute walk does it. │
│ └──────────────┘ ▂▃▅▄▃▂▆█▄▃▂▂ 12 bars, 64px tall │
│ 300 × 300 7 am 12 pm 6 pm │
└──( ⋮⋮ 8,412 steps )─────────────────────────────────────────────────────────────┘
chip: ink pill, −3°, overlapping the bottom border by 22px
8px 8px 0 ink offset shadow
main.cardis a grid300px 1fr, gap 36px, padding 32/36/32/32..ringsholds onesvg viewBox="0 0 300 300"(aria-hidden): three track circles, three progress circles, three glyph paths. Circles respond to clicks viapointer-events: stroke..side: top row (p.eyebrow,button.replay),div.picker(role="group", “Choose a ring”) with threebutton.pick[aria-pressed], anddiv.detail(aria-live="polite") containing.big,p.note,.hours,.hlabels.p.stepsis absolutely positioned at left 32px, bottom −22px.
Tokens
:root {
--bg: #ffd447; /* sunflower ground */
--card: #fff7e4; /* sticker */
--ink: #1b1a17; /* border, text, shadow */
--ink-2: #4a463d;
--ink-3: #6b6558;
--burn: #ff5b3a; --burn-t: #ffd9cd;
--hustle: #2f5bff; --hustle-t: #d5ddff;
--rise: #13b07a; --rise-t: #c6eedc;
--display: "Bowlby One", "Arial Black", sans-serif;
--sans: "Outfit", system-ui, sans-serif;
--r-card: 32px; --r-pill: 999px; --r-bar: 5px 5px 2px 2px;
--border: 2.5px solid var(--ink);
--shadow: 8px 8px 0 var(--ink);
--ring-stroke: 26px; --ring-stroke-on: 30px;
--ring-r: 126px 94px 62px;
--ease: cubic-bezier(.2, .7, .2, 1);
--ease-out: cubic-bezier(.16, 1, .3, 1);
--ease-pop: cubic-bezier(.34, 1.56, .64, 1);
--t-micro: 160ms; --t-swap: 320ms; --t-fill: 1000ms; --stagger: 140ms;
}
Spacing runs 4 / 8 / 12 / 16 / 20 / 32 / 36. Ring colours appear only in strokes, swatches, bars and the highlighter under the nudge, never as text colour.
Typography
| Role | Family | Size | Weight | Line-height | Tracking | Case |
|---|---|---|---|---|---|---|
| Panel number | Bowlby One | 76px | 400 | .95 | −0.01em | tabular nums |
| Goal ”/ 600 kcal” | Outfit | 20px | 600 | 1 | 0 | — |
| Eyebrow | Outfit | 12px | 700 | 1 | 0.16em | uppercase |
| Pill label | Outfit | 15px | 600 | 1 | 0 | — |
| Nudge | Outfit | 16px | 400, strong 700 | 1.45 | 0 | max 34ch |
| Hour labels | Outfit | 12px | 500 | 1 | 0 | — |
| Steps number | Bowlby One | 16px | 400 | 1 | 0.02em | — |
Motion
| Thing | Trigger | Property | From → to | Duration | Easing | Delay | Reduced motion |
|---|---|---|---|---|---|---|---|
| Ring fill | load, Replay | stroke-dashoffset | C → C × (1 − value/goal) | 1000ms | ease-pop | 0 / 140 / 280ms | instant, no delay |
| Ring focus | select | opacity, stroke-width | 1 → .32 (others); 26 → 30 (chosen) | 160ms / 320ms | standard / ease-pop | — | instant |
| Panel swap | select | opacity, translateY, rotate | 0, 10px, −1° → 1, 0, 0 | 320ms | ease-pop | — | none |
| Number | select, load | text | 0 → value | 900ms | 1 − (1 − t)^3 | — | final value |
| Hour bars | select | height | old → new | 320ms | ease-pop | — | instant |
| Pill press | aria-pressed | translateY, shadow | 0, 3px shadow → 3px, 0 | 160ms | standard | — | instant |
States
- Pill resting: card fill, 2px ink border,
0 3px 0 inkshadow. Hover: white fill. Pressed (aria-pressed="true"): ink fill, cream text, pushed down 3px, shadow gone, swatch border turns cream. - Ring focus:
.dimon the rings wrapper;.prog.onkeeps opacity 1 and grows to 30. - Replay: hover white; active nudges 1px down-right.
- Focus-visible on pills and Replay: 3px
--hustleoutline, 3px offset. - Zero-hour bar:
.zero, card fill, 8px tall. - Goal met (value ≥ goal): keep the ring closed, and change the nudge to “Burn closed. Anything more is bonus.” Not shown in this demo.
Accessibility
- The svg is
aria-hidden; the pills are the accessible controls (role="group", “Choose a ring”,aria-pressed). Ring clicks are a pointer shortcut for the same action. - The detail panel is
aria-live="polite", so selecting announces the new number, goal and nudge. - Tab order: Replay, Burn, Hustle, Rise.
- Contrast: ink on cream ≈ 16:1;
#4a463d≈ 9:1;#6b6558≈ 5.3:1. - Pills are 40px tall, Replay ~32px (desktop secondary control).
Responsive rules
- ≥1024: card 760px, rings 300px.
- 768: card shrinks to
100vw − 40px; still two columns down to 720px. - <720: single column, rings
min(260px, 100%)centred, panel full width, numeral 60px, steps chip centred on the bottom edge, body gets 32px top / 48px bottom padding so the chip and shadow don’t clip. - At 375 wide nothing overflows horizontally; the page may scroll vertically.
Acceptance checklist
Always
- Three concentric rings with round caps and tinted tracks, starting at 12 o’clock.
- Rings fill on load with an overshooting ease and an outside-in stagger.
- Each ring is selectable by tapping it and by an accessible toggle button.
- Selection dims the other rings and thickens the chosen one; selecting it again clears.
- Panel swaps number, goal, nudge and hourly bars for the chosen ring.
- A replay control refills the rings.
- Sticker treatment: 2.5px ink border, hard offset shadow, no blur shadows.
- Ring colours never used as text colour.
- Reduced motion removes fills, pops and count-ups.
This demo
- Brand “Hoopla · Saturday”.
- Burn 520 / 600 kcal, Hustle 27 / 45 min, Rise 10 / 12 hours.
- Ring radii 126 / 94 / 62 in a 300 viewBox, stroke 26 (30 when focused).
- Step chip “8,412 steps”, tilted −3°, overlapping the bottom border.
- Nudge for Burn: “80 kcal to close Burn. A brisk 15-minute walk does it.”
Implementation notes
Dash maths. Rotate each progress circle −90° around the centre so it starts at the top, set stroke-dasharray to its circumference, and animate stroke-dashoffset.
const circ = r => 2 * Math.PI * r;
progs.forEach((p, i) => {
const C = circ(RINGS[i].r);
p.style.strokeDasharray = C;
p.style.strokeDashoffset = C; // empty
});
function fill() {
progs.forEach((p, i) => {
const R = RINGS[i], C = circ(R.r);
p.style.transitionDelay = `${i * 140}ms`;
p.style.strokeDashoffset = C * (1 - R.val / R.goal);
});
}
requestAnimationFrame(() => requestAnimationFrame(fill)); // let the empty state paint
.prog { stroke-linecap: round; transform: rotate(-90deg); transform-origin: 150px 150px;
transition: stroke-dashoffset 1000ms cubic-bezier(.34,1.56,.64,1), opacity 160ms, stroke-width 320ms cubic-bezier(.34,1.56,.64,1); }
.dim .prog { opacity: .32; }
.dim .prog.on { opacity: 1; stroke-width: 30; }
Replay without a flash. To restart, set transition: none, reset offsets, force layout (svg.getBoundingClientRect()), clear the inline transition, then fill on the next frame.
Click targets on rings. pointer-events: stroke on both track and progress circles makes the 26px band clickable and the gaps between rings inert. Put data-i on each circle and read it in one delegated listener.
Common mistakes:
- Copying the red/green/blue of a famous watch. These are tomato, cobalt, mint on cream, and the names are Burn, Hustle, Rise.
- Using the overshoot easing on the pill press; that should be crisp.
- Soft blurred shadows; the sticker look needs a hard offset.
- Rendering ring colours as text (tomato on cream fails contrast).
- Forgetting that a ring above 100% needs a second lap; this demo stays under goal.
Details
- Palette
- Type
- Bowlby One · Outfit
- Motion
- Rich motion
- Build
- One session
- Tags
On the shelves