Skip to content

Commit 198d19f

Browse files
DylanMerigaudclaude
andcommitted
Give the Run button more presence: play icon + soft breath
The idle Run CTA now carries a play icon and a slow breathing halo so it reads as the thing to click; the halo stops once a run is underway. Running shows a spinner. Honours prefers-reduced-motion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5cf9398 commit 198d19f

2 files changed

Lines changed: 72 additions & 6 deletions

File tree

app/globals.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,28 @@ body {
107107
.animate-scan {
108108
animation: scan 1.6s ease-in-out infinite;
109109
}
110+
111+
/* Soft "breath" glow on the idle Run CTA — a slow pulsing halo that says the
112+
button is the thing to click, without the jitter of a hard pulse. */
113+
@keyframes breath {
114+
0%,
115+
100% {
116+
box-shadow: 0 0 0 0 rgba(79, 70, 229, 0.35);
117+
}
118+
50% {
119+
box-shadow: 0 0 0 6px rgba(79, 70, 229, 0);
120+
}
121+
}
122+
123+
.animate-breath {
124+
animation: breath 2.4s ease-in-out infinite;
125+
}
126+
127+
/* Respect users who prefer no motion. */
128+
@media (prefers-reduced-motion: reduce) {
129+
.animate-breath,
130+
.animate-scan,
131+
.animate-pulse-ring {
132+
animation: none;
133+
}
134+
}

components/dashboard.tsx

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,39 @@ function readIntake(
5858
};
5959
}
6060

61+
/** Solid play triangle for the Run button. */
62+
function PlayIcon() {
63+
return (
64+
<svg aria-hidden viewBox="0 0 12 12" className="h-3 w-3 fill-current">
65+
<path d="M3 1.8v8.4a.6.6 0 0 0 .92.5l6.4-4.2a.6.6 0 0 0 0-1L3.92 1.3A.6.6 0 0 0 3 1.8Z" />
66+
</svg>
67+
);
68+
}
69+
70+
/** Small spinner shown while a run is in flight. */
71+
function Spinner() {
72+
return (
73+
<svg aria-hidden viewBox="0 0 16 16" className="h-3.5 w-3.5 animate-spin">
74+
<circle
75+
cx="8"
76+
cy="8"
77+
r="6"
78+
fill="none"
79+
stroke="currentColor"
80+
strokeOpacity="0.3"
81+
strokeWidth="2"
82+
/>
83+
<path
84+
d="M8 2a6 6 0 0 1 6 6"
85+
fill="none"
86+
stroke="currentColor"
87+
strokeWidth="2"
88+
strokeLinecap="round"
89+
/>
90+
</svg>
91+
);
92+
}
93+
6194
export function Dashboard({ queue }: { queue: QueueItem[] }) {
6295
const [selectedId, setSelectedId] = useState<string | null>(
6396
queue[0]?.id ?? null,
@@ -313,13 +346,21 @@ export function Dashboard({ queue }: { queue: QueueItem[] }) {
313346
data-testid="run-btn"
314347
disabled={state.status === "running"}
315348
onClick={() => run(selected.id)}
316-
className="shrink-0 rounded-lg bg-accent px-3.5 py-2 text-[13px] font-medium text-accent-fg shadow-sm transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40"
349+
className={`flex shrink-0 items-center gap-1.5 rounded-lg bg-accent px-3.5 py-2 text-[13px] font-medium text-accent-fg shadow-sm transition-all hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40 ${
350+
state.status === "idle" ? "animate-breath" : ""
351+
}`}
317352
>
318-
{state.status === "running"
319-
? "Running…"
320-
: state.status === "idle"
321-
? "Run pipeline"
322-
: "Run again"}
353+
{state.status === "running" ? (
354+
<>
355+
<Spinner />
356+
Running…
357+
</>
358+
) : (
359+
<>
360+
<PlayIcon />
361+
{state.status === "idle" ? "Run pipeline" : "Run again"}
362+
</>
363+
)}
323364
</button>
324365
)
325366
)}

0 commit comments

Comments
 (0)