# To-do List

A Cursor-style to-do list the agent maintains: a collapsible header with done, in-progress and pending item states.

- Category: Structured Outputs
- Source: AICSS (https://www.aicss.dev/components/task-list)
- Author: @kvnkld (https://x.com/kvnkld)
- Styling: Self-contained CSS with CSS custom properties (design tokens). Theme-aware via [data-theme] (light/dark).

## Instructions

Add this component to the project. Keep the styling self-contained. Map the design tokens (CSS custom properties) to the project's theme if they are not already defined.

## Code

### React - `TodoList.tsx`

```tsx
"use client";

import styles from "./TodoList.module.css";
import { useEffect, useRef, useState } from "react";

const LABELS = [
  "Scaffold the project structure",
  "Build the component registry",
  "Implement entitlement gating",
  "Wire up Stripe checkout",
  "Polish the landing page",
];

const START_DELAY = 700;
const STEP_MS = 2250; // how long each task stays "working"

const cls = (base: string, on?: boolean) => base + (on ? " " + styles.on : "");
const CheckIcon = ({ on }: { on?: boolean }) => (
  <svg className={cls(styles.todoIcon, on)} viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
    <path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
  </svg>
);
const ArrowIcon = ({ on }: { on?: boolean }) => (
  <svg className={cls(styles.todoIcon + " " + styles.strong, on)} viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
    <path d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
  </svg>
);
const DashedIcon = ({ on }: { on?: boolean }) => (
  <svg className={cls(styles.todoIcon, on)} viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
    <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeWidth="1.8" strokeDasharray="1.8 3.6" strokeLinecap="round" />
  </svg>
);

// one character slot that rolls the old glyph up and the new one in on change
const RollDigit = ({ char }: { char: string }) => {
  const prev = useRef(char);
  const [roll, setRoll] = useState<{ from: string; to: string } | null>(null);
  const [up, setUp] = useState(false);
  useEffect(() => {
    if (char === prev.current) return;
    const from = prev.current;
    prev.current = char;
    setRoll({ from, to: char });
    setUp(false);
    const raf = requestAnimationFrame(() => requestAnimationFrame(() => setUp(true)));
    const done = setTimeout(() => setRoll(null), 380);
    return () => { cancelAnimationFrame(raf); clearTimeout(done); };
  }, [char]);
  if (!roll) return <span className={styles.rollDigit}>{char}</span>;
  return (
    <span className={styles.rollDigit}>
      <span className={cls(styles.rollInner, up)}>
        <span>{roll.from}</span>
        <span>{roll.to}</span>
      </span>
    </span>
  );
};
const RollingCount = ({ value }: { value: string }) => (
  <span className={styles.rollCount} aria-label={value}>
    {value.split("").map((c, i) => <RollDigit key={i} char={c} />)}
  </span>
);
const FilledCheckIcon = () => (
  <svg className={styles.todoHeadCheck} viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
    <path fillRule="evenodd" clipRule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" fill="currentColor" />
  </svg>
);

export function TodoList() {
  const [collapsed, setCollapsed] = useState(false);
  // -1 = not started (plan shown), 0..n-1 = working on that task, n = all done
  const [current, setCurrent] = useState(-1);
  const n = LABELS.length;

  useEffect(() => {
    if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
      setCurrent(n);
      return;
    }
    const timers = [setTimeout(() => setCurrent(0), START_DELAY)];
    for (let i = 0; i < n; i++) {
      timers.push(setTimeout(() => setCurrent(i + 1), START_DELAY + (i + 1) * STEP_MS));
    }
    return () => timers.forEach(clearTimeout);
  }, [n]);

  const started = current >= 0;
  const allDone = current >= n;
  const running = started && !allDone;
  const pct = Math.round((Math.min(Math.max(current, 0), n) / n) * 100);

  return (
    <div className={styles.todo}>
      <button
        type="button"
        className={styles.todoHead}
        aria-expanded={!collapsed}
        aria-label="Toggle to-dos"
        onClick={() => setCollapsed((c) => !c)}
      >
        <span className={styles.todoHeadIcon}>
          {allDone ? (
            <FilledCheckIcon />
          ) : running ? (
            <span className={styles.todoHeadPie} style={{ ["--todo-pie" as string]: pct + "%" }} aria-hidden="true">
              <svg className={styles.todoHeadPieRing} viewBox="0 0 24 24">
                <circle cx="12" cy="12" r="10.5" fill="none" stroke="currentColor" strokeWidth="2.2" strokeDasharray="2.2 4.4" strokeLinecap="round" />
              </svg>
            </span>
          ) : (
            <svg className={styles.todoListIcon} viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <path d="M13 5h8" />
              <path d="M13 12h8" />
              <path d="M13 19h8" />
              <path d="m3 17 2 2 4-4" />
              <path d="m3 7 2 2 4-4" />
            </svg>
          )}
          <svg className={styles.todoChevron} viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
            <path d="m19.5 8.25-7.5 7.5-7.5-7.5" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </span>
        <span className={styles.todoTitle}>To-dos</span>
        <span className={styles.todoCount}>
          <RollingCount value={Math.min(Math.max(current, 0), n) + "/" + n} />
        </span>
      </button>

      <div className={styles.todoCollapsible + (collapsed ? " " + styles.isCollapsed : "")}>
        <div className={styles.todoInner}>
          <ul className={styles.todoList}>
            {LABELS.map((label, i) => {
              const done = started && i < current;
              const active = started && i === current && !allDone;
              return (
                <li
                  key={i}
                  className={styles.todoItem + (done ? " " + styles.done : active ? " " + styles.active : "")}
                  style={{ ["--i" as string]: i }}
                >
                  <span className={styles.todoIconWrap}>
                    <DashedIcon on={!done && !active} />
                    <ArrowIcon on={active} />
                    <CheckIcon on={done} />
                  </span>
                  <span className={styles.todoLabel} data-label={label}>{label}</span>
                </li>
              );
            })}
          </ul>
        </div>
      </div>
    </div>
  );
}

```

### React - `TodoList.module.css`

```css
/* Theme follows the nearest [data-theme] ancestor (preview switch),
   then .dark, then the OS when no data-theme is set. */
:global(:root),
:global([data-theme="light"]) {
  --todo-fg: #1a1a1a;
  --todo-bg: #fff;
  --todo-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05),
    0 2px 4px rgba(0, 0, 0, 0.02);
  --todo-muted: #a1a1a1;
  --todo-check: #15a06a;
  --todo-strong: #1a1a1a;
  --todo-pie-fg: #1a1a1a;
  --todo-shine: linear-gradient(90deg, #1a1a1a 0%, #1a1a1a 30%, rgba(26, 26, 26, 0.45) 45%, rgba(26, 26, 26, 0.45) 55%, #1a1a1a 70%, #1a1a1a 100%);
}
:global([data-theme="dark"]),
:global(.dark) {
  --todo-fg: #f5f5f5;
  --todo-bg: #1a1a1a;
  --todo-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12),
    0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --todo-muted: #737373;
  --todo-check: #34d399;
  --todo-strong: #f5f5f5;
  --todo-pie-fg: #f5f5f5;
  --todo-shine: linear-gradient(90deg, #f5f5f5 0%, #f5f5f5 30%, rgba(245, 245, 245, 0.45) 45%, rgba(245, 245, 245, 0.45) 55%, #f5f5f5 70%, #f5f5f5 100%);
}
@media (prefers-color-scheme: dark) {
  :global(:root:not([data-theme])) {
    --todo-fg: #f5f5f5;
    --todo-bg: #1a1a1a;
    --todo-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12),
      0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
    --todo-muted: #737373;
    --todo-check: #34d399;
    --todo-strong: #f5f5f5;
    --todo-pie-fg: #f5f5f5;
    --todo-shine: linear-gradient(90deg, #f5f5f5 0%, #f5f5f5 30%, rgba(245, 245, 245, 0.45) 45%, rgba(245, 245, 245, 0.45) 55%, #f5f5f5 70%, #f5f5f5 100%);
  }
}

.todo { width: 100%; font-family: "Inter", system-ui, sans-serif; font-size: 13px; color: var(--todo-fg, #1a1a1a); background: var(--todo-bg, #fff); border-radius: 12px; padding: 6px 12px 12px; box-shadow: var(--todo-shadow); }
.todoHead {
  display: flex; width: 100%; align-items: center; gap: 8px;
  padding: 0; border: 0; background: transparent; cursor: pointer;
  color: var(--todo-fg, #1a1a1a); font-size: 13px; min-height: 22px;
}
.todoHeadIcon { position: relative; width: 16px; height: 16px; flex: none; color: var(--todo-muted, #a1a1a1); }
.todoListIcon, .todoChevron, .todoHeadCheck { position: absolute; inset: 0; margin: auto; transition: opacity 140ms ease; }
.todoListIcon, .todoChevron { width: 13px; height: 13px; }
/* the solid check reads smaller than an outlined glyph, so render it full-size */
.todoHeadCheck { width: 16px; height: 16px; color: var(--todo-check, #15a06a); }
.todoChevron { opacity: 0; transition: opacity 140ms ease, transform 220ms ease; }
.todoHead[aria-expanded="false"] .todoChevron { transform: rotate(-90deg); }
.todoHead:hover .todoListIcon, .todoHead:hover .todoHeadPie, .todoHead:hover .todoHeadCheck { opacity: 0; }
.todoHead:hover .todoChevron { opacity: 1; }
.todoTitle { font-weight: 500; }
.todoCount { margin-left: auto; color: var(--todo-muted, #a1a1a1); font-variant-numeric: tabular-nums; }
.rollCount { display: inline-flex; align-items: baseline; }
.rollDigit { display: inline-block; overflow: hidden; height: 1em; line-height: 1em; }
.rollInner { display: flex; flex-direction: column; transition: transform 350ms cubic-bezier(0.4, 0, 0.2, 1); }
.rollInner span { height: 1em; line-height: 1em; }
.rollInner.on { transform: translateY(-1em); }
.rollStatic { display: inline-block; height: 1em; line-height: 1em; }
.todoCollapsible {
  display: grid; grid-template-rows: 1fr; opacity: 1;
  transition: grid-template-rows 280ms ease, opacity 200ms ease;
}
.todoCollapsible.isCollapsed { grid-template-rows: 0fr; opacity: 0; pointer-events: none; }
.todoInner { min-height: 0; overflow: hidden; }
.todoList { list-style: none; display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 10px 0 0; }
.todoItem {
  display: flex; align-items: flex-start; gap: 9px; line-height: 18px; color: var(--todo-muted, #a1a1a1);
  animation: todo-item-in 360ms ease backwards;
  animation-delay: calc(var(--i, 0) * 50ms);
}
@keyframes todo-item-in {
  from { opacity: 0; transform: translateY(-7px); }
  to { opacity: 1; transform: translateY(0); }
}
.todoIconWrap { position: relative; width: 16px; height: 16px; flex: none; margin-top: 1px; }
.todoIcon {
  position: absolute; inset: 0; width: 16px; height: 16px; color: var(--todo-muted, #a1a1a1);
  opacity: 0; transition: opacity 320ms ease;
}
.todoIcon.on { opacity: 1; }
.todoIcon.strong { color: var(--todo-strong, #1a1a1a); }
.todoLabel {
  position: relative; font-weight: 400; color: var(--todo-muted, #a1a1a1);
  transition: color 360ms ease;
}
/* crossfade the gray label into the dark shimmering active state */
.todoLabel::before {
  content: attr(data-label);
  position: absolute; inset: 0;
  background: var(--todo-shine, linear-gradient(90deg, #1a1a1a 0%, #1a1a1a 30%, rgba(26, 26, 26, 0.45) 45%, rgba(26, 26, 26, 0.45) 55%, #1a1a1a 70%, #1a1a1a 100%));
  background-size: 300% 100%;
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  opacity: 0; transition: opacity 360ms ease; pointer-events: none;
}
.todoItem.active .todoLabel { color: transparent; }
.todoItem.active .todoLabel::before {
  opacity: 1;
  animation: todo-shine 2.25s cubic-bezier(0.25, 0.1, 0.25, 1) infinite;
}
.todoItem.done .todoLabel { color: var(--todo-muted, #a1a1a1); text-decoration: line-through; }
@keyframes todo-shine {
  0%, 18% { background-position: 100% 0; }
  82%, 100% { background-position: 0% 0; }
}

/* running progress pie in the header - determinate fill = completed / total */
@property --todo-pie {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}
.todoHeadPie {
  position: absolute; inset: 0; margin: auto; width: 13px; height: 13px; border-radius: 50%;
  color: var(--todo-pie-fg, #1a1a1a);
  transition: opacity 140ms ease, --todo-pie 400ms ease;
}
/* dotted outline matching the pending item circles */
.todoHeadPieRing { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; color: var(--todo-muted, #a1a1a1); }
.todoHeadPie::after {
  content: ""; position: absolute; inset: 2.6px; border-radius: 50%;
  background: conic-gradient(currentColor var(--todo-pie, 0%), transparent 0);
}
@media (prefers-reduced-motion: reduce) {
  .todoItem { animation: none; }
  .todoIcon, .todoLabel, .todoLabel::before { transition: none; }
  .todoItem.active .todoLabel::before { animation: none; }
}

```

### Vue - `TodoList.vue`

```vue
<template>
  <div class="todo">
    <button
      type="button"
      class="todo-head"
      :aria-expanded="!collapsed"
      aria-label="Toggle to-dos"
      @click="collapsed = !collapsed"
    >
      <span class="todo-head-icon">
        <svg v-if="allDone" class="todo-head-check" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" fill="currentColor" />
        </svg>
        <span v-else-if="running" class="todo-head-pie" :style="{ '--todo-pie': pct + '%' }" aria-hidden="true">
          <svg class="todo-head-pie-ring" viewBox="0 0 24 24">
            <circle cx="12" cy="12" r="10.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-dasharray="2.2 4.4" stroke-linecap="round" />
          </svg>
        </span>
        <svg v-else class="todo-list-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <path d="M13 5h8" />
          <path d="M13 12h8" />
          <path d="M13 19h8" />
          <path d="m3 17 2 2 4-4" />
          <path d="m3 7 2 2 4-4" />
        </svg>
        <svg class="todo-chevron" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
          <path d="m19.5 8.25-7.5 7.5-7.5-7.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
        </svg>
      </span>
      <span class="todo-title">To-dos</span>
      <span class="todo-count">
        <span class="roll-count" :aria-label="done + '/' + n">
          <span class="roll-digit">
            <span v-if="roll" class="roll-inner" :class="{ on: rollUp }">
              <span>{{ roll.from }}</span>
              <span>{{ roll.to }}</span>
            </span>
            <template v-else>{{ done }}</template>
          </span>
          <span class="roll-static">/{{ n }}</span>
        </span>
      </span>
    </button>

    <div class="todo-collapsible" :class="{ 'is-collapsed': collapsed }">
      <div class="todo-inner">
        <ul class="todo-list">
          <li v-for="(label, i) in LABELS" :key="i" :class="['todo-item', itemClass(i)]" :style="{ '--i': i }">
            <span class="todo-icon-wrap">
              <svg class="todo-icon" :class="{ on: !isDone(i) && !isActive(i) }" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
                <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="1.8 3.6" stroke-linecap="round" />
              </svg>
              <svg class="todo-icon strong" :class="{ on: isActive(i) }" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
                <path d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
              </svg>
              <svg class="todo-icon" :class="{ on: isDone(i) }" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
                <path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
              </svg>
            </span>
            <span class="todo-label" :data-label="label">{{ label }}</span>
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, onMounted, onUnmounted, ref, watch } from "vue";

const LABELS = [
  "Scaffold the project structure",
  "Build the component registry",
  "Implement entitlement gating",
  "Wire up Stripe checkout",
  "Polish the landing page",
];
const START_DELAY = 700;
const STEP_MS = 2250; // how long each task stays "working"

const collapsed = ref(false);
const current = ref(-1); // -1 = not started, 0..n-1 = working, n = all done
const n = LABELS.length;

const started = computed(() => current.value >= 0);
const allDone = computed(() => current.value >= n);
const running = computed(() => started.value && !allDone.value);
const pct = computed(() => Math.round((Math.min(Math.max(current.value, 0), n) / n) * 100));
const done = computed(() => Math.min(Math.max(current.value, 0), n));

// roll the numerator up whenever the completed count changes
const roll = ref(null);
const rollUp = ref(false);
let rollTimer;
watch(done, (val, old) => {
  roll.value = { from: String(old), to: String(val) };
  rollUp.value = false;
  requestAnimationFrame(() => requestAnimationFrame(() => (rollUp.value = true)));
  clearTimeout(rollTimer);
  rollTimer = setTimeout(() => (roll.value = null), 380);
});
const isDone = (i) => started.value && i < current.value;
const isActive = (i) => started.value && i === current.value && !allDone.value;
const itemClass = (i) => (isDone(i) ? "done" : isActive(i) ? "active" : "");

let timers = [];
onMounted(() => {
  if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
    current.value = n;
    return;
  }
  timers.push(setTimeout(() => (current.value = 0), START_DELAY));
  for (let i = 0; i < n; i++) {
    timers.push(setTimeout(() => (current.value = i + 1), START_DELAY + (i + 1) * STEP_MS));
  }
});
onUnmounted(() => timers.forEach(clearTimeout));
</script>

<style scoped>
/* Theme follows the nearest [data-theme] ancestor, then .dark, then the OS. */
:global(:root),
:global([data-theme="light"]) {
  --todo-fg: #1a1a1a;
  --todo-bg: #fff;
  --todo-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.02);
  --todo-muted: #a1a1a1;
  --todo-check: #15a06a;
  --todo-strong: #1a1a1a;
  --todo-pie-fg: #1a1a1a;
  --todo-shine: linear-gradient(90deg, #1a1a1a 0%, #1a1a1a 30%, rgba(26, 26, 26, 0.45) 45%, rgba(26, 26, 26, 0.45) 55%, #1a1a1a 70%, #1a1a1a 100%);
}
:global([data-theme="dark"]),
:global(.dark) {
  --todo-fg: #f5f5f5;
  --todo-bg: #1a1a1a;
  --todo-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --todo-muted: #737373;
  --todo-check: #34d399;
  --todo-strong: #f5f5f5;
  --todo-pie-fg: #f5f5f5;
  --todo-shine: linear-gradient(90deg, #f5f5f5 0%, #f5f5f5 30%, rgba(245, 245, 245, 0.45) 45%, rgba(245, 245, 245, 0.45) 55%, #f5f5f5 70%, #f5f5f5 100%);
}
@media (prefers-color-scheme: dark) {
  :global(:root:not([data-theme])) {
  --todo-fg: #f5f5f5;
  --todo-bg: #1a1a1a;
  --todo-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --todo-muted: #737373;
  --todo-check: #34d399;
  --todo-strong: #f5f5f5;
  --todo-pie-fg: #f5f5f5;
  --todo-shine: linear-gradient(90deg, #f5f5f5 0%, #f5f5f5 30%, rgba(245, 245, 245, 0.45) 45%, rgba(245, 245, 245, 0.45) 55%, #f5f5f5 70%, #f5f5f5 100%);
  }
}
.todo { width: 100%; font-size: 13px; color: var(--todo-fg, #1a1a1a); background: var(--todo-bg, #fff); border-radius: 12px; padding: 6px 12px 12px; box-shadow: var(--todo-shadow, 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.02)); }
.todo-head {
  display: flex; width: 100%; align-items: center; gap: 8px;
  padding: 0; border: 0; background: transparent; cursor: pointer;
  color: var(--todo-fg, #1a1a1a); font-size: 13px; min-height: 22px;
}
.todo-head-icon { position: relative; width: 16px; height: 16px; flex: none; color: var(--todo-muted, #a1a1a1); }
.todo-list-icon, .todo-chevron, .todo-head-check { position: absolute; inset: 0; margin: auto; transition: opacity 140ms ease; }
.todo-list-icon, .todo-chevron { width: 13px; height: 13px; }
/* the solid check reads smaller than an outlined glyph, so render it full-size */
.todo-head-check { width: 16px; height: 16px; color: var(--todo-check, #15a06a); }
.todo-chevron { opacity: 0; transition: opacity 140ms ease, transform 220ms ease; }
.todo-head[aria-expanded="false"] .todo-chevron { transform: rotate(-90deg); }
.todo-head:hover .todo-list-icon, .todo-head:hover .todo-head-pie, .todo-head:hover .todo-head-check { opacity: 0; }
.todo-head:hover .todo-chevron { opacity: 1; }
.todo-title { font-weight: 500; }
.todo-count { margin-left: auto; color: var(--todo-muted, #a1a1a1); font-variant-numeric: tabular-nums; }
.roll-count { display: inline-flex; align-items: baseline; }
.roll-digit { display: inline-block; overflow: hidden; height: 1em; line-height: 1em; }
.roll-inner { display: flex; flex-direction: column; transition: transform 350ms cubic-bezier(0.4, 0, 0.2, 1); }
.roll-inner span { height: 1em; line-height: 1em; }
.roll-inner.on { transform: translateY(-1em); }
.roll-static { display: inline-block; height: 1em; line-height: 1em; }
.todo-collapsible {
  display: grid; grid-template-rows: 1fr; opacity: 1;
  transition: grid-template-rows 280ms ease, opacity 200ms ease;
}
.todo-collapsible.is-collapsed { grid-template-rows: 0fr; opacity: 0; pointer-events: none; }
.todo-inner { min-height: 0; overflow: hidden; }
.todo-list { list-style: none; display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 10px 0 0; }
.todo-item {
  display: flex; align-items: flex-start; gap: 9px; line-height: 18px; color: var(--todo-muted, #a1a1a1);
  animation: todo-item-in 360ms ease backwards;
  animation-delay: calc(var(--i, 0) * 50ms);
}
@keyframes todo-item-in {
  from { opacity: 0; transform: translateY(-7px); }
  to { opacity: 1; transform: translateY(0); }
}
.todo-icon-wrap { position: relative; width: 16px; height: 16px; flex: none; margin-top: 1px; }
.todo-icon {
  position: absolute; inset: 0; width: 16px; height: 16px; color: var(--todo-muted, #a1a1a1);
  opacity: 0; transition: opacity 320ms ease;
}
.todo-icon.on { opacity: 1; }
.todo-icon.strong { color: var(--todo-strong, #1a1a1a); }
.todo-label {
  position: relative; font-weight: 400; color: var(--todo-muted, #a1a1a1);
  transition: color 360ms ease;
}
.todo-label::before {
  content: attr(data-label);
  position: absolute; inset: 0;
  background: var(--todo-shine, linear-gradient(90deg, #1a1a1a 0%, #1a1a1a 30%, rgba(26, 26, 26, 0.45) 45%, rgba(26, 26, 26, 0.45) 55%, #1a1a1a 70%, #1a1a1a 100%));
  background-size: 300% 100%;
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  opacity: 0; transition: opacity 360ms ease; pointer-events: none;
}
.todo-item.active .todo-label { color: transparent; }
.todo-item.active .todo-label::before {
  opacity: 1;
  animation: todo-shine 2.25s cubic-bezier(0.25, 0.1, 0.25, 1) infinite;
}
.todo-item.done .todo-label { color: var(--todo-muted, #a1a1a1); text-decoration: line-through; }
@keyframes todo-shine {
  0%, 18% { background-position: 100% 0; }
  82%, 100% { background-position: 0% 0; }
}
@property --todo-pie {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}
.todo-head-pie {
  position: absolute; inset: 0; margin: auto; width: 13px; height: 13px; border-radius: 50%;
  color: var(--todo-pie-fg, #1a1a1a);
  transition: opacity 140ms ease, --todo-pie 400ms ease;
}
/* dotted outline matching the pending item circles */
.todo-head-pie-ring { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; color: var(--todo-muted, #a1a1a1); }
.todo-head-pie::after {
  content: ""; position: absolute; inset: 2.6px; border-radius: 50%;
  background: conic-gradient(currentColor var(--todo-pie, 0%), transparent 0);
}
@media (prefers-reduced-motion: reduce) {
  .todo-item { animation: none; }
  .todo-icon, .todo-label, .todo-label::before { transition: none; }
  .todo-item.active .todo-label::before { animation: none; }
}
</style>
```

### Svelte - `TodoList.svelte`

```svelte
<script>
  import { onMount, onDestroy } from "svelte";

  const LABELS = [
    "Scaffold the project structure",
    "Build the component registry",
    "Implement entitlement gating",
    "Wire up Stripe checkout",
    "Polish the landing page",
  ];
  const START_DELAY = 700;
  const STEP_MS = 2250; // how long each task stays "working"

  let collapsed = false;
  let current = -1; // -1 = not started, 0..n-1 = working, n = all done
  const n = LABELS.length;

  $: started = current >= 0;
  $: allDone = current >= n;
  $: running = started && !allDone;
  $: pct = Math.round((Math.min(Math.max(current, 0), n) / n) * 100);
  $: done = Math.min(Math.max(current, 0), n);

  // roll the numerator up whenever the completed count changes
  let roll = null;
  let rollUp = false;
  let rollTimer;
  let prevDone = 0;
  $: if (done !== prevDone) {
    const from = prevDone;
    prevDone = done;
    roll = { from: String(from), to: String(done) };
    rollUp = false;
    requestAnimationFrame(() => requestAnimationFrame(() => (rollUp = true)));
    clearTimeout(rollTimer);
    rollTimer = setTimeout(() => (roll = null), 380);
  }
  $: statuses = LABELS.map((_, i) =>
    started && i < current
      ? "done"
      : started && i === current && !allDone
        ? "active"
        : "pending",
  );

  let timers = [];
  onMount(() => {
    if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
      current = n;
      return;
    }
    timers.push(setTimeout(() => (current = 0), START_DELAY));
    for (let i = 0; i < n; i++) {
      timers.push(setTimeout(() => (current = i + 1), START_DELAY + (i + 1) * STEP_MS));
    }
  });
  onDestroy(() => timers.forEach(clearTimeout));
</script>

<div class="todo">
  <button
    type="button"
    class="todo-head"
    aria-expanded={!collapsed}
    aria-label="Toggle to-dos"
    on:click={() => (collapsed = !collapsed)}
  >
    <span class="todo-head-icon">
      {#if allDone}
        <svg class="todo-head-check" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" fill="currentColor" />
        </svg>
      {:else if running}
        <span class="todo-head-pie" style="--todo-pie: {pct}%" aria-hidden="true">
          <svg class="todo-head-pie-ring" viewBox="0 0 24 24">
            <circle cx="12" cy="12" r="10.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-dasharray="2.2 4.4" stroke-linecap="round" />
          </svg>
        </span>
      {:else}
        <svg class="todo-list-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <path d="M13 5h8" />
          <path d="M13 12h8" />
          <path d="M13 19h8" />
          <path d="m3 17 2 2 4-4" />
          <path d="m3 7 2 2 4-4" />
        </svg>
      {/if}
      <svg class="todo-chevron" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
        <path d="m19.5 8.25-7.5 7.5-7.5-7.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
      </svg>
    </span>
    <span class="todo-title">To-dos</span>
    <span class="todo-count">
      <span class="roll-count" aria-label={done + "/" + n}>
        <span class="roll-digit">
          {#if roll}
            <span class="roll-inner" class:on={rollUp}>
              <span>{roll.from}</span>
              <span>{roll.to}</span>
            </span>
          {:else}
            {done}
          {/if}
        </span><span class="roll-static">/{n}</span>
      </span>
    </span>
  </button>

  <div class="todo-collapsible" class:is-collapsed={collapsed}>
    <div class="todo-inner">
      <ul class="todo-list">
        {#each LABELS as label, i (i)}
          <li
            class="todo-item {statuses[i] === 'done' ? 'done' : statuses[i] === 'active' ? 'active' : ''}"
            style="--i: {i}"
          >
            <span class="todo-icon-wrap">
              <svg class="todo-icon {statuses[i] === 'pending' ? 'on' : ''}" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
                <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="1.8 3.6" stroke-linecap="round" />
              </svg>
              <svg class="todo-icon strong {statuses[i] === 'active' ? 'on' : ''}" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
                <path d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
              </svg>
              <svg class="todo-icon {statuses[i] === 'done' ? 'on' : ''}" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
                <path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
              </svg>
            </span>
            <span class="todo-label" data-label={label}>{label}</span>
          </li>
        {/each}
      </ul>
    </div>
  </div>
</div>

<style>
/* Theme follows the nearest [data-theme] ancestor, then .dark, then the OS. */
:global(:root),
:global([data-theme="light"]) {
  --todo-fg: #1a1a1a;
  --todo-bg: #fff;
  --todo-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.02);
  --todo-muted: #a1a1a1;
  --todo-check: #15a06a;
  --todo-strong: #1a1a1a;
  --todo-pie-fg: #1a1a1a;
  --todo-shine: linear-gradient(90deg, #1a1a1a 0%, #1a1a1a 30%, rgba(26, 26, 26, 0.45) 45%, rgba(26, 26, 26, 0.45) 55%, #1a1a1a 70%, #1a1a1a 100%);
}
:global([data-theme="dark"]),
:global(.dark) {
  --todo-fg: #f5f5f5;
  --todo-bg: #1a1a1a;
  --todo-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --todo-muted: #737373;
  --todo-check: #34d399;
  --todo-strong: #f5f5f5;
  --todo-pie-fg: #f5f5f5;
  --todo-shine: linear-gradient(90deg, #f5f5f5 0%, #f5f5f5 30%, rgba(245, 245, 245, 0.45) 45%, rgba(245, 245, 245, 0.45) 55%, #f5f5f5 70%, #f5f5f5 100%);
}
@media (prefers-color-scheme: dark) {
  :global(:root:not([data-theme])) {
  --todo-fg: #f5f5f5;
  --todo-bg: #1a1a1a;
  --todo-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --todo-muted: #737373;
  --todo-check: #34d399;
  --todo-strong: #f5f5f5;
  --todo-pie-fg: #f5f5f5;
  --todo-shine: linear-gradient(90deg, #f5f5f5 0%, #f5f5f5 30%, rgba(245, 245, 245, 0.45) 45%, rgba(245, 245, 245, 0.45) 55%, #f5f5f5 70%, #f5f5f5 100%);
  }
}
  .todo { width: 100%; font-size: 13px; color: var(--todo-fg, #1a1a1a); background: var(--todo-bg, #fff); border-radius: 12px; padding: 6px 12px 12px; box-shadow: var(--todo-shadow, 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.02)); }
  .todo-head {
    display: flex; width: 100%; align-items: center; gap: 8px;
    padding: 0; border: 0; background: transparent; cursor: pointer;
    color: var(--todo-fg, #1a1a1a); font-size: 13px; min-height: 22px;
  }
  .todo-head-icon { position: relative; width: 16px; height: 16px; flex: none; color: var(--todo-muted, #a1a1a1); }
  .todo-list-icon, .todo-chevron, .todo-head-check { position: absolute; inset: 0; margin: auto; transition: opacity 140ms ease; }
  .todo-list-icon, .todo-chevron { width: 13px; height: 13px; }
  /* the solid check reads smaller than an outlined glyph, so render it full-size */
  .todo-head-check { width: 16px; height: 16px; color: var(--todo-check, #15a06a); }
  .todo-chevron { opacity: 0; transition: opacity 140ms ease, transform 220ms ease; }
  .todo-head[aria-expanded="false"] .todo-chevron { transform: rotate(-90deg); }
  .todo-head:hover .todo-list-icon, .todo-head:hover .todo-head-pie, .todo-head:hover .todo-head-check { opacity: 0; }
  .todo-head:hover .todo-chevron { opacity: 1; }
  .todo-title { font-weight: 500; }
  .todo-count { margin-left: auto; color: var(--todo-muted, #a1a1a1); font-variant-numeric: tabular-nums; }
.roll-count { display: inline-flex; align-items: baseline; }
.roll-digit { display: inline-block; overflow: hidden; height: 1em; line-height: 1em; }
.roll-inner { display: flex; flex-direction: column; transition: transform 350ms cubic-bezier(0.4, 0, 0.2, 1); }
.roll-inner span { height: 1em; line-height: 1em; }
.roll-inner.on { transform: translateY(-1em); }
.roll-static { display: inline-block; height: 1em; line-height: 1em; }
  .todo-collapsible {
    display: grid; grid-template-rows: 1fr; opacity: 1;
    transition: grid-template-rows 280ms ease, opacity 200ms ease;
  }
  .todo-collapsible.is-collapsed { grid-template-rows: 0fr; opacity: 0; pointer-events: none; }
  .todo-inner { min-height: 0; overflow: hidden; }
  .todo-list { list-style: none; display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 10px 0 0; }
  .todo-item {
    display: flex; align-items: flex-start; gap: 9px; line-height: 18px; color: var(--todo-muted, #a1a1a1);
    animation: todo-item-in 360ms ease backwards;
    animation-delay: calc(var(--i, 0) * 50ms);
  }
  @keyframes todo-item-in {
    from { opacity: 0; transform: translateY(-7px); }
    to { opacity: 1; transform: translateY(0); }
  }
  .todo-icon-wrap { position: relative; width: 16px; height: 16px; flex: none; margin-top: 1px; }
  .todo-icon {
    position: absolute; inset: 0; width: 16px; height: 16px; color: var(--todo-muted, #a1a1a1);
    opacity: 0; transition: opacity 320ms ease;
  }
  .todo-icon.on { opacity: 1; }
  .todo-icon.strong { color: var(--todo-strong, #1a1a1a); }
  .todo-label {
    position: relative; font-weight: 400; color: var(--todo-muted, #a1a1a1);
    transition: color 360ms ease;
  }
  .todo-label::before {
    content: attr(data-label);
    position: absolute; inset: 0;
    background: var(--todo-shine, linear-gradient(90deg, #1a1a1a 0%, #1a1a1a 30%, rgba(26, 26, 26, 0.45) 45%, rgba(26, 26, 26, 0.45) 55%, #1a1a1a 70%, #1a1a1a 100%));
    background-size: 300% 100%;
    -webkit-background-clip: text; background-clip: text;
    color: transparent; -webkit-text-fill-color: transparent;
    opacity: 0; transition: opacity 360ms ease; pointer-events: none;
  }
  .todo-item.active .todo-label { color: transparent; }
  .todo-item.active .todo-label::before {
    opacity: 1;
    animation: todo-shine 2.25s cubic-bezier(0.25, 0.1, 0.25, 1) infinite;
  }
  .todo-item.done .todo-label { color: var(--todo-muted, #a1a1a1); text-decoration: line-through; }
  @keyframes todo-shine {
    0%, 18% { background-position: 100% 0; }
    82%, 100% { background-position: 0% 0; }
  }
  @property --todo-pie {
    syntax: "<percentage>";
    inherits: true;
    initial-value: 0%;
  }
  .todo-head-pie {
    position: absolute; inset: 0; margin: auto; width: 13px; height: 13px; border-radius: 50%;
    color: var(--todo-pie-fg, #1a1a1a);
    transition: opacity 140ms ease, --todo-pie 400ms ease;
  }
  /* dotted outline matching the pending item circles */
  .todo-head-pie-ring { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; color: var(--todo-muted, #a1a1a1); }
  .todo-head-pie::after {
    content: ""; position: absolute; inset: 2.6px; border-radius: 50%;
    background: conic-gradient(currentColor var(--todo-pie, 0%), transparent 0);
  }
  @media (prefers-reduced-motion: reduce) {
    .todo-item { animation: none; }
    .todo-icon, .todo-label, .todo-label::before { transition: none; }
    .todo-item.active .todo-label::before { animation: none; }
  }
</style>
```
