Design Lounge
Install

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

Open full size
1280 × 800 · 11.8 KB · 186 lines

Use it

Free to use · credit appreciated

1 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.

Brief

185 lines

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.
  • .rings holds one svg viewBox="0 0 300 300" (aria-hidden): three track circles, three progress circles, three glyph paths. Circles respond to clicks via pointer-events: stroke.
  • .side: top row (p.eyebrow, button.replay), div.picker (role="group", “Choose a ring”) with three button.pick[aria-pressed], and div.detail (aria-live="polite") containing .big, p.note, .hours, .hlabels.
  • p.steps is 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

RoleFamilySizeWeightLine-heightTrackingCase
Panel numberBowlby One76px400.95−0.01emtabular nums
Goal ”/ 600 kcal”Outfit20px60010—
EyebrowOutfit12px70010.16emuppercase
Pill labelOutfit15px60010—
NudgeOutfit16px400, strong 7001.450max 34ch
Hour labelsOutfit12px50010—
Steps numberBowlby One16px40010.02em—

Motion

ThingTriggerPropertyFrom → toDurationEasingDelayReduced motion
Ring fillload, Replaystroke-dashoffsetC → C × (1 − value/goal)1000msease-pop0 / 140 / 280msinstant, no delay
Ring focusselectopacity, stroke-width1 → .32 (others); 26 → 30 (chosen)160ms / 320msstandard / ease-pop—instant
Panel swapselectopacity, translateY, rotate0, 10px, −1° → 1, 0, 0320msease-pop—none
Numberselect, loadtext0 → value900ms1 − (1 − t)^3—final value
Hour barsselectheightold → new320msease-pop—instant
Pill pressaria-pressedtranslateY, shadow0, 3px shadow → 3px, 0160msstandard—instant

States

  • Pill resting: card fill, 2px ink border, 0 3px 0 ink shadow. Hover: white fill. Pressed (aria-pressed="true"): ink fill, cream text, pushed down 3px, shadow gone, swatch border turns cream.
  • Ring focus: .dim on the rings wrapper; .prog.on keeps opacity 1 and grows to 30.
  • Replay: hover white; active nudges 1px down-right.
  • Focus-visible on pills and Replay: 3px --hustle outline, 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
Designed bySusan AcharyaKathmandu

Studied

UseArticle

A control. It studies this site for quiet, not for a show.

All sources

On the shelves