New

Goal & Streak

A gamified progress tracker combining a weekly goal progress bar with streak counters. Composes Progress Bar, Avatar, and Badge. Reference: JobFlo's MacGoalStreakCard.

Demo

Weekly Goal

5 / 7 applications

🔥
12
day streak
🏆
21
best streak

Goal-met state

Weekly Goal

7 / 7 applications

Goal met!
🔥
13
day streak
🏆
21
best streak

Anatomy

ColumnContents
Progress (2fr)Label + count / goal + animated progress bar + goal-met checkmark
Current streak (1fr)🔥 or 💤 emoji + large rounded-digit number + “day streak” unit
Best streak (1fr)🏆 emoji + number + “best streak” unit

States

StateBehaviour
In progressBar fills with --accent; streak shows 🔥 if > 0, else 💤
Goal metBar transitions to --status-success; card surface gets --accent / 0.06 tint (sfBrandLitSurface); checkmark appears
Zero progressBar empty; streak shows 💤; best streak preserved

Animation

The progress bar animates on mount with a spring curve: spring(response: 0.5, dampingFraction: 0.75). This gives the bar a subtle overshoot that communicates momentum without being distracting.

Implementation

GoalStreakCard.tsx
tsx
"use client";

import { useState, useEffect } from "react";

interface GoalStreakProps {
  label: string;
  current: number;
  goal: number;
  currentStreak: number;
  bestStreak: number;
  unit?: string;
}

export function GoalStreakCard({
  label,
  current,
  goal,
  currentStreak,
  bestStreak,
  unit = "applications",
}: GoalStreakProps) {
  const pct = Math.min(current / goal, 1);
  const isGoalMet = current >= goal;

  // Spring-animate the bar width on mount
  const [width, setWidth] = useState(0);
  useEffect(() => { requestAnimationFrame(() => setWidth(pct)); }, [pct]);

  const fillColor = isGoalMet
    ? "rgb(var(--status-success))"
    : "rgb(var(--accent))";

  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "2fr 1fr 1fr",
        borderRadius: 14,
        overflow: "hidden",
        background: "rgb(var(--surface))",
        border: "1px solid rgb(var(--border))",
        boxShadow: "var(--shadow-card)",
        // sfBrandLitSurface when goal met
        ...(isGoalMet && {
          background: "rgb(var(--accent) / 0.06)",
          border: "1px solid rgb(var(--accent) / 0.25)",
        }),
      }}
    >
      {/* Progress column */}
      <div style={{ padding: "16px 20px", borderRight: "1px solid rgb(var(--border))" }}>
        <p style={{ fontSize: 11, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "rgb(var(--text-tertiary))", marginBottom: 8 }}>
          {label}
        </p>
        <p style={{ fontSize: 13, color: "rgb(var(--text-secondary))", marginBottom: 10 }}>
          <span style={{ fontWeight: 700, fontSize: 20, color: "rgb(var(--text-primary))", fontVariantNumeric: "tabular-nums" }}>{current}</span>
          {" / "}{goal} {unit}
        </p>

        {/* Progress bar */}
        <div style={{ height: 8, borderRadius: 99, background: "rgb(var(--progress-track))", overflow: "hidden" }}>
          <div style={{
            height: "100%",
            width: `${width * 100}%`,
            borderRadius: 99,
            background: fillColor,
            transition: "width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)",
          }} />
        </div>

        {isGoalMet && (
          <div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 10, color: "rgb(var(--status-success))", fontSize: 12, fontWeight: 600 }}>
            <span style={{ width: 18, height: 18, borderRadius: "50%", background: "rgb(var(--status-success))", display: "flex", alignItems: "center", justifyContent: "center" }}>
              <svg width="10" height="8" viewBox="0 0 10 8" fill="none"><path d="M1 4l3 3 5-6" stroke="#fff" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>
            </span>
            Goal met!
          </div>
        )}
      </div>

      {/* Current streak */}
      <div style={{ padding: 16, textAlign: "center", borderRight: "1px solid rgb(var(--border))" }}>
        <div style={{ fontSize: 22, marginBottom: 4 }}>{currentStreak > 0 ? "🔥" : "💤"}</div>
        <div style={{ fontSize: 24, fontWeight: 800, fontVariantNumeric: "tabular-nums", letterSpacing: "-0.02em" }}>{currentStreak}</div>
        <div style={{ fontSize: 11, color: "rgb(var(--text-tertiary))", marginTop: 2 }}>day streak</div>
      </div>

      {/* Best streak */}
      <div style={{ padding: 16, textAlign: "center" }}>
        <div style={{ fontSize: 22, marginBottom: 4 }}>🏆</div>
        <div style={{ fontSize: 24, fontWeight: 800, fontVariantNumeric: "tabular-nums", letterSpacing: "-0.02em" }}>{bestStreak}</div>
        <div style={{ fontSize: 11, color: "rgb(var(--text-tertiary))", marginTop: 2 }}>best streak</div>
      </div>
    </div>
  );
}