Book a callGitHub10

Activity

Thursday, 23 Oct

Move
412 / 560 kcal
Exercise
24 / 30 min
Stand
9 / 12 h

148 kcal to close your Move ring. A 25-minute walk would do it.

It is live: hover, focus and its own controls respond. Actions that would change data (approve, send, delete) are for your app to wire. The width is a real 390 px, 768 px or 1280 px box, shrunk to fit your screen.

Source
<!-- Icons: Lucide (lucide.dev), ISC licence -->
<style>
.odl-activity-rings { box-sizing: border-box; inline-size: 100%; max-inline-size: 30rem; margin-inline: auto; container-type: inline-size; color: var(--ds-color-text-primary); font: var(--ds-text-body-md); text-wrap: pretty; }
.odl-activity-rings *, .odl-activity-rings *::before, .odl-activity-rings *::after { box-sizing: border-box; }
.odl-activity-rings .odl-card { --move: var(--ds-color-danger-light-9); --move-track: var(--ds-color-danger-light-4); --ex: var(--ds-color-success-light-9); --ex-track: var(--ds-color-success-light-4);
  --stand: var(--ds-color-info-light-9); --stand-track: var(--ds-color-info-light-4);
  display: grid; gap: var(--ds-space-stack-lg); padding: var(--ds-space-inset-xl); background: var(--ds-color-surface-raised);
  border: var(--ds-border-width-default) solid var(--ds-color-border-subtle); border-radius: var(--ds-radius-container); box-shadow: var(--ds-elevation-raised); }
.odl-activity-rings .odl-head { display: grid; gap: var(--ds-space-stack-xs); }
.odl-activity-rings h3 { margin: 0; font: var(--ds-text-title-lg); }
.odl-activity-rings .odl-date { margin: 0; color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); }
.odl-activity-rings .odl-main { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--ds-space-inset-xl); }
.odl-activity-rings .odl-rings { inline-size: calc(var(--ds-size-control-lg) * 3); aspect-ratio: 1; }
.odl-activity-rings svg { display: block; inline-size: 100%; block-size: 100%; overflow: visible; }
.odl-activity-rings circle { fill: none; stroke-linecap: round; }
.odl-activity-rings .odl-arc { transform: rotate(-90deg); transform-origin: 50% 50%; transition: stroke-dasharray var(--ds-motion-duration-long) var(--ds-motion-easing-standard); }
.odl-activity-rings .is-move { stroke: var(--move); } .odl-activity-rings .is-move.odl-track { stroke: var(--move-track); }
.odl-activity-rings .is-ex { stroke: var(--ex); } .odl-activity-rings .is-ex.odl-track { stroke: var(--ex-track); }
.odl-activity-rings .is-stand { stroke: var(--stand); } .odl-activity-rings .is-stand.odl-track { stroke: var(--stand-track); }
.odl-activity-rings dl { display: grid; gap: var(--ds-space-stack-md); margin: 0; }
.odl-activity-rings dl > div { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--ds-space-inline-md); row-gap: var(--ds-space-stack-xs); align-items: center; }
.odl-activity-rings dl > div > svg { grid-row: 1 / 3; inline-size: var(--ds-size-icon-md); block-size: var(--ds-size-icon-md); stroke-width: var(--ds-icon-stroke-lg); color: var(--t); }
.odl-activity-rings dt { color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); }
.odl-activity-rings dd { margin: 0; font: var(--ds-text-title-md); font-variant-numeric: tabular-nums; }
.odl-activity-rings dd small { color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); }
.odl-activity-rings .odl-m { --t: var(--ds-color-danger-light-11); } .odl-activity-rings .odl-e { --t: var(--ds-color-success-light-11); } .odl-activity-rings .odl-s { --t: var(--ds-color-info-light-11); }
.odl-activity-rings dd .odl-v { color: var(--t); }
.odl-activity-rings .odl-note { display: flex; align-items: flex-start; gap: var(--ds-space-inline-md); margin: 0; padding: var(--ds-space-inset-md); border-radius: var(--ds-radius-nested); background: var(--ds-color-bg-neutral-subtle); color: var(--ds-color-text-primary); font: var(--ds-text-body-md); }
.odl-activity-rings .odl-note svg { flex: none; inline-size: var(--ds-size-icon-md); block-size: var(--ds-size-icon-md); stroke-width: var(--ds-icon-stroke-md); color: var(--ds-color-icon-subtle); }
.odl-activity-rings .odl-week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--ds-space-inline-xs); margin: 0; padding: var(--ds-space-stack-lg) 0 0; list-style: none;
  border-block-start: var(--ds-border-width-default) solid var(--ds-color-border-separator); }
.odl-activity-rings .odl-week button { display: grid; justify-items: center; gap: var(--ds-space-stack-xs); inline-size: 100%; min-block-size: var(--ds-size-target-touch); padding: var(--ds-space-inset-xs) 0;
  border: 0; border-radius: var(--ds-radius-nested); background: transparent; color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); cursor: pointer;
  transition: background-color var(--ds-motion-duration-short) var(--ds-motion-easing-standard); }
.odl-activity-rings .odl-week button:hover { background: var(--ds-color-bg-neutral-subtle); }
.odl-activity-rings .odl-week button[aria-pressed="true"] { background: var(--ds-color-bg-neutral-subtle); color: var(--ds-color-text-primary); }
.odl-activity-rings .odl-week svg { inline-size: min(100%, calc(var(--ds-size-icon-lg) * 1.5)); block-size: auto; aspect-ratio: 1; }
/* The week shows the Move ring only: three rings at this size read as a blob. */
.odl-activity-rings .odl-week .is-ex, .odl-activity-rings .odl-week .is-stand { display: none; }
.odl-activity-rings .odl-week .odl-arc { transition: none; }
.odl-activity-rings button:focus-visible { outline: var(--ds-focus-ring-width) solid var(--ds-color-border-focus); outline-offset: var(--ds-focus-ring-offset); }
.odl-activity-rings .odl-sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@container (max-width: 26rem) {
  .odl-activity-rings .odl-card { padding: var(--ds-space-inset-lg); }
  .odl-activity-rings .odl-main { gap: var(--ds-space-inset-lg); }
  .odl-activity-rings .odl-rings { inline-size: calc(var(--ds-size-control-lg) * 2.4); }
  .odl-activity-rings .odl-week { gap: 0; }
}
@container (min-width: 28rem) { .odl-activity-rings .odl-rings { inline-size: calc(var(--ds-size-control-lg) * 3.8); } }
@media (prefers-reduced-motion: reduce) { .odl-activity-rings, .odl-activity-rings *, .odl-activity-rings *::before { transition: none !important; animation: none !important; } }
</style>
<section class="odl-activity-rings" aria-labelledby="odl-ar-title">
  <div class="odl-card">
    <div class="odl-head"><h3 id="odl-ar-title">Activity</h3><p class="odl-date" aria-live="polite">Thursday, 23 Oct</p></div>
    <div class="odl-main">
      <div class="odl-rings" role="img" aria-label="Move 412 of 560 calories, exercise 24 of 30 minutes, stand 9 of 12 hours">
        <svg viewBox="0 0 120 120" aria-hidden="true">
          <circle class="odl-track is-move" cx="60" cy="60" r="54" stroke-width="11"/>
          <circle class="odl-arc is-move" cx="60" cy="60" r="54" stroke-width="11" pathLength="100" stroke-dasharray="73.6 100"/>
          <circle class="odl-track is-ex" cx="60" cy="60" r="40" stroke-width="11"/>
          <circle class="odl-arc is-ex" cx="60" cy="60" r="40" stroke-width="11" pathLength="100" stroke-dasharray="80 100"/>
          <circle class="odl-track is-stand" cx="60" cy="60" r="26" stroke-width="11"/>
          <circle class="odl-arc is-stand" cx="60" cy="60" r="26" stroke-width="11" pathLength="100" stroke-dasharray="75 100"/>
        </svg>
      </div>
      <dl>
        <div class="odl-m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg><dt>Move</dt><dd><span class="odl-v">412</span><small> / <span class="odl-g">560</span> kcal</small></dd></div>
        <div class="odl-e"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 17 5-5-5-5"/><path d="m13 17 5-5-5-5"/></svg><dt>Exercise</dt><dd><span class="odl-v">24</span><small> / <span class="odl-g">30</span> min</small></dd></div>
        <div class="odl-s"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 7-7 7 7"/><path d="M12 19V5"/></svg><dt>Stand</dt><dd><span class="odl-v">9</span><small> / <span class="odl-g">12</span> h</small></dd></div>
      </dl>
    </div>
    <p class="odl-note"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z"/><path d="M20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0Z"/><path d="M16 17h4"/><path d="M4 13h4"/></svg><span class="odl-nt">148 kcal to close your Move ring. A 25-minute walk would do it.</span></p>
    <ul class="odl-week" aria-label="This week">
      <li><button type="button" data-day="0" aria-pressed="false"><span class="odl-sr">Friday 17 Oct</span><svg viewBox="0 0 120 120" aria-hidden="true"><circle class="odl-track is-move" cx="60" cy="60" r="50" stroke-width="15"/><circle class="odl-arc is-move" cx="60" cy="60" r="50" stroke-width="15" pathLength="100" stroke-dasharray="100 100"/><circle class="odl-track is-ex" cx="60" cy="60" r="32" stroke-width="15"/><circle class="odl-arc is-ex" cx="60" cy="60" r="32" stroke-width="15" pathLength="100" stroke-dasharray="100 100"/><circle class="odl-track is-stand" cx="60" cy="60" r="14" stroke-width="15"/><circle class="odl-arc is-stand" cx="60" cy="60" r="14" stroke-width="15" pathLength="100" stroke-dasharray="83 100"/></svg><span aria-hidden="true">F</span></button></li>
      <li><button type="button" data-day="1" aria-pressed="false"><span class="odl-sr">Saturday 18 Oct</span><svg viewBox="0 0 120 120" aria-hidden="true"><circle class="odl-track is-move" cx="60" cy="60" r="50" stroke-width="15"/><circle class="odl-arc is-move" cx="60" cy="60" r="50" stroke-width="15" pathLength="100" stroke-dasharray="100 100"/><circle class="odl-track is-ex" cx="60" cy="60" r="32" stroke-width="15"/><circle class="odl-arc is-ex" cx="60" cy="60" r="32" stroke-width="15" pathLength="100" stroke-dasharray="100 100"/><circle class="odl-track is-stand" cx="60" cy="60" r="14" stroke-width="15"/><circle class="odl-arc is-stand" cx="60" cy="60" r="14" stroke-width="15" pathLength="100" stroke-dasharray="100 100"/></svg><span aria-hidden="true">S</span></button></li>
      <li><button type="button" data-day="2" aria-pressed="false"><span class="odl-sr">Sunday 19 Oct</span><svg viewBox="0 0 120 120" aria-hidden="true"><circle class="odl-track is-move" cx="60" cy="60" r="50" stroke-width="15"/><circle class="odl-arc is-move" cx="60" cy="60" r="50" stroke-width="15" pathLength="100" stroke-dasharray="46 100"/><circle class="odl-track is-ex" cx="60" cy="60" r="32" stroke-width="15"/><circle class="odl-arc is-ex" cx="60" cy="60" r="32" stroke-width="15" pathLength="100" stroke-dasharray="20 100"/><circle class="odl-track is-stand" cx="60" cy="60" r="14" stroke-width="15"/><circle class="odl-arc is-stand" cx="60" cy="60" r="14" stroke-width="15" pathLength="100" stroke-dasharray="58 100"/></svg><span aria-hidden="true">S</span></button></li>
      <li><button type="button" data-day="3" aria-pressed="false"><span class="odl-sr">Monday 20 Oct</span><svg viewBox="0 0 120 120" aria-hidden="true"><circle class="odl-track is-move" cx="60" cy="60" r="50" stroke-width="15"/><circle class="odl-arc is-move" cx="60" cy="60" r="50" stroke-width="15" pathLength="100" stroke-dasharray="88 100"/><circle class="odl-track is-ex" cx="60" cy="60" r="32" stroke-width="15"/><circle class="odl-arc is-ex" cx="60" cy="60" r="32" stroke-width="15" pathLength="100" stroke-dasharray="100 100"/><circle class="odl-track is-stand" cx="60" cy="60" r="14" stroke-width="15"/><circle class="odl-arc is-stand" cx="60" cy="60" r="14" stroke-width="15" pathLength="100" stroke-dasharray="92 100"/></svg><span aria-hidden="true">M</span></button></li>
      <li><button type="button" data-day="4" aria-pressed="false"><span class="odl-sr">Tuesday 21 Oct</span><svg viewBox="0 0 120 120" aria-hidden="true"><circle class="odl-track is-move" cx="60" cy="60" r="50" stroke-width="15"/><circle class="odl-arc is-move" cx="60" cy="60" r="50" stroke-width="15" pathLength="100" stroke-dasharray="100 100"/><circle class="odl-track is-ex" cx="60" cy="60" r="32" stroke-width="15"/><circle class="odl-arc is-ex" cx="60" cy="60" r="32" stroke-width="15" pathLength="100" stroke-dasharray="100 100"/><circle class="odl-track is-stand" cx="60" cy="60" r="14" stroke-width="15"/><circle class="odl-arc is-stand" cx="60" cy="60" r="14" stroke-width="15" pathLength="100" stroke-dasharray="100 100"/></svg><span aria-hidden="true">T</span></button></li>
      <li><button type="button" data-day="5" aria-pressed="false"><span class="odl-sr">Wednesday 22 Oct</span><svg viewBox="0 0 120 120" aria-hidden="true"><circle class="odl-track is-move" cx="60" cy="60" r="50" stroke-width="15"/><circle class="odl-arc is-move" cx="60" cy="60" r="50" stroke-width="15" pathLength="100" stroke-dasharray="67 100"/><circle class="odl-track is-ex" cx="60" cy="60" r="32" stroke-width="15"/><circle class="odl-arc is-ex" cx="60" cy="60" r="32" stroke-width="15" pathLength="100" stroke-dasharray="50 100"/><circle class="odl-track is-stand" cx="60" cy="60" r="14" stroke-width="15"/><circle class="odl-arc is-stand" cx="60" cy="60" r="14" stroke-width="15" pathLength="100" stroke-dasharray="83 100"/></svg><span aria-hidden="true">W</span></button></li>
      <li><button type="button" data-day="6" aria-pressed="true"><span class="odl-sr">Today</span><svg viewBox="0 0 120 120" aria-hidden="true"><circle class="odl-track is-move" cx="60" cy="60" r="50" stroke-width="15"/><circle class="odl-arc is-move" cx="60" cy="60" r="50" stroke-width="15" pathLength="100" stroke-dasharray="73.6 100"/><circle class="odl-track is-ex" cx="60" cy="60" r="32" stroke-width="15"/><circle class="odl-arc is-ex" cx="60" cy="60" r="32" stroke-width="15" pathLength="100" stroke-dasharray="80 100"/><circle class="odl-track is-stand" cx="60" cy="60" r="14" stroke-width="15"/><circle class="odl-arc is-stand" cx="60" cy="60" r="14" stroke-width="15" pathLength="100" stroke-dasharray="75 100"/></svg><span aria-hidden="true">T</span></button></li>
    </ul>
  </div>
</section>
<script>
document.querySelectorAll(".odl-activity-rings").forEach((root) => {
  if (root.dataset.odlReady) return; // wire once, even if the file is pasted twice
  root.dataset.odlReady = "1";
  // [move, exercise, stand] for Friday to today; goals are 560 kcal, 30 min and 12 h
  const days = [[580, 34, 10, "Friday, 17 Oct"], [602, 41, 12, "Saturday, 18 Oct"], [258, 6, 7, "Sunday, 19 Oct"], [493, 30, 11, "Monday, 20 Oct"],
    [611, 45, 12, "Tuesday, 21 Oct"], [375, 15, 10, "Wednesday, 22 Oct"], [412, 24, 9, "Thursday, 23 Oct"]];
  const goals = [560, 30, 12], units = ["calories", "minutes", "hours"], names = ["Move", "Exercise", "Stand"];
  const arcs = root.querySelectorAll(".odl-rings .odl-arc"), values = root.querySelectorAll("dd .odl-v");
  const art = root.querySelector(".odl-rings"), date = root.querySelector(".odl-date"), note = root.querySelector(".odl-nt");
  const show = (i) => {
    const d = days[i];
    d.slice(0, 3).forEach((v, k) => { arcs[k].setAttribute("stroke-dasharray", `${Math.min(100, (v / goals[k]) * 100).toFixed(1)} 100`); values[k].textContent = String(v); });
    date.textContent = d[3];
    art.setAttribute("aria-label", d.slice(0, 3).map((v, k) => `${names[k]} ${v} of ${goals[k]} ${units[k]}`).join(", "));
    const left = goals[0] - d[0];
    note.textContent = left > 0 ? (i === 6 ? `${left} kcal to close your Move ring. A 25-minute walk would do it.` : `Move ring ${left} kcal short.`) : "All of your Move goal, and then some.";
    root.querySelectorAll(".odl-week button").forEach((b) => b.setAttribute("aria-pressed", String(Number(b.dataset.day) === i)));
  };
  root.querySelectorAll(".odl-week button").forEach((b) => b.addEventListener("click", () => show(Number(b.dataset.day))));
});
</script>

More like this

ConsumerFriendly · Personal finance

Savings goal

A savings pot with a cover photo: the goal, a ring of how much is saved, what is left and the date it is on track for, a round-ups switch, and quick amounts to add money that move the ring.

B2BQuiet · SaaS

Usage meters

Budget, seats and credit as three meters, with the forecast hatched past what is used and alert thresholds marked on the bar.

B2BQuiet · SaaS

Monthly spend chart

Six months of spend as bars, with the current month's forecast hatched on top, a dashed budget line and a tooltip on the bar you point at.

Your kit

Nothing here yet. Add parts with Add to kit, and a look if you want the whole system.

    Parts
    0
    Files to fetch
    0 KB
    Licence
    MIT
    Total
    Free, now and later

    How do you want it?

    Your prompt

    No sign-up. Nothing leaves your browser until you paste it.

    Free