457 lines
12 KiB
HTML
457 lines
12 KiB
HTML
<!-- Copyright 2026 Anthropic PBC · SPDX-License-Identifier: Apache-2.0 -->
|
|
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Acme — Task completed micro-interaction</title>
|
|
<style>
|
|
:root {
|
|
--ivory: #FAF9F5;
|
|
--slate: #141413;
|
|
--clay: #D97757;
|
|
--oat: #E3DACC;
|
|
--olive: #788C5D;
|
|
--gray-50: #F0EEE6;
|
|
--gray-200: #D1CFC5;
|
|
--gray-500: #87867F;
|
|
--gray-800: #3D3D3A;
|
|
--white: #ffffff;
|
|
--serif: ui-serif, Georgia, "Times New Roman", serif;
|
|
--sans: system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
--mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
/* swapped at runtime by the easing panel */
|
|
--ease: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
* { box-sizing: border-box; }
|
|
html, body {
|
|
margin: 0;
|
|
background: var(--ivory);
|
|
color: var(--slate);
|
|
font-family: var(--sans);
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
}
|
|
.wrap {
|
|
max-width: 1080px;
|
|
margin: 0 auto;
|
|
padding: 48px 32px 64px;
|
|
}
|
|
header { margin-bottom: 28px; }
|
|
.eyebrow {
|
|
font-family: var(--mono);
|
|
font-size: 11px;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--gray-500);
|
|
}
|
|
h1 {
|
|
font-family: var(--serif);
|
|
font-weight: 500;
|
|
font-size: 30px;
|
|
letter-spacing: -0.01em;
|
|
margin: 6px 0 4px;
|
|
}
|
|
.sub {
|
|
color: var(--gray-500);
|
|
max-width: 560px;
|
|
}
|
|
|
|
/* ---------- layout ---------- */
|
|
.bench {
|
|
display: grid;
|
|
grid-template-columns: 1fr 240px;
|
|
gap: 24px;
|
|
align-items: start;
|
|
}
|
|
.stage {
|
|
background: var(--white);
|
|
border: 1.5px solid var(--gray-200);
|
|
border-radius: 12px;
|
|
height: 360px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.hint {
|
|
position: absolute;
|
|
bottom: 14px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
font-family: var(--mono);
|
|
font-size: 11px;
|
|
color: var(--gray-500);
|
|
}
|
|
|
|
/* ---------- the task row ---------- */
|
|
.task {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
padding: 14px 18px;
|
|
border: 1.5px solid var(--gray-200);
|
|
border-radius: 8px;
|
|
background: var(--white);
|
|
width: 360px;
|
|
max-height: 64px;
|
|
position: relative;
|
|
transition: max-height 280ms var(--ease) 600ms,
|
|
opacity 280ms var(--ease) 600ms,
|
|
padding 280ms var(--ease) 600ms,
|
|
border-color 200ms var(--ease);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
.task:hover { border-color: var(--gray-500); }
|
|
|
|
.check {
|
|
flex: none;
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
border: 1.5px solid var(--gray-500);
|
|
background: var(--white);
|
|
position: relative;
|
|
transition: background 180ms var(--ease),
|
|
border-color 180ms var(--ease),
|
|
transform 260ms var(--ease);
|
|
}
|
|
.check svg {
|
|
position: absolute;
|
|
inset: 0;
|
|
margin: auto;
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
.check path {
|
|
stroke: var(--white);
|
|
stroke-width: 2.5;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
fill: none;
|
|
stroke-dasharray: 20;
|
|
stroke-dashoffset: 20;
|
|
transition: stroke-dashoffset 220ms var(--ease) 80ms;
|
|
}
|
|
|
|
.label {
|
|
position: relative;
|
|
font-size: 15px;
|
|
color: var(--slate);
|
|
transition: color 200ms var(--ease) 120ms;
|
|
}
|
|
.label::after {
|
|
content: "";
|
|
position: absolute;
|
|
left: 0;
|
|
top: 52%;
|
|
height: 1.5px;
|
|
width: 0;
|
|
background: var(--gray-500);
|
|
transition: width 240ms var(--ease) 120ms;
|
|
}
|
|
|
|
.due {
|
|
margin-left: auto;
|
|
font-family: var(--mono);
|
|
font-size: 11px;
|
|
color: var(--gray-500);
|
|
background: var(--gray-50);
|
|
border: 1.5px solid var(--gray-200);
|
|
padding: 3px 8px;
|
|
border-radius: 8px;
|
|
transition: opacity 200ms var(--ease) 120ms;
|
|
}
|
|
|
|
/* confetti — six particles, css-only, driven by .done on the row */
|
|
.confetti {
|
|
position: absolute;
|
|
left: 22px;
|
|
top: 50%;
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 2px;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
.confetti.c1 { background: var(--clay); }
|
|
.confetti.c2 { background: var(--olive); }
|
|
.confetti.c3 { background: var(--oat); }
|
|
.confetti.c4 { background: var(--clay); }
|
|
.confetti.c5 { background: var(--olive); }
|
|
.confetti.c6 { background: var(--gray-800); }
|
|
|
|
/* ---------- done state ---------- */
|
|
.task.done .check {
|
|
background: var(--olive);
|
|
border-color: var(--olive);
|
|
transform: scale(1.12);
|
|
animation: settle 380ms var(--ease) forwards;
|
|
}
|
|
@keyframes settle {
|
|
0% { transform: scale(0.8); background: var(--clay); border-color: var(--clay); }
|
|
55% { transform: scale(1.18); }
|
|
100% { transform: scale(1); background: var(--olive); border-color: var(--olive); }
|
|
}
|
|
.task.done .check path { stroke-dashoffset: 0; }
|
|
.task.done .label { color: var(--gray-500); }
|
|
.task.done .label::after { width: 100%; }
|
|
.task.done .due { opacity: 0.35; }
|
|
.task.done {
|
|
max-height: 44px;
|
|
padding-top: 8px;
|
|
padding-bottom: 8px;
|
|
opacity: 0.6;
|
|
}
|
|
.task.done .confetti { animation: pop 520ms var(--ease) 200ms forwards; }
|
|
.task.done .confetti.c1 { --dx: -28px; --dy: -22px; --rot: 40deg; }
|
|
.task.done .confetti.c2 { --dx: -6px; --dy: -34px; --rot: -60deg; }
|
|
.task.done .confetti.c3 { --dx: 18px; --dy: -26px; --rot: 90deg; }
|
|
.task.done .confetti.c4 { --dx: 30px; --dy: -4px; --rot: -30deg; }
|
|
.task.done .confetti.c5 { --dx: -24px; --dy: 14px; --rot: 20deg; }
|
|
.task.done .confetti.c6 { --dx: 12px; --dy: 22px; --rot: -80deg; }
|
|
@keyframes pop {
|
|
0% { opacity: 0; transform: translate(0, 0) rotate(0) scale(0.6); }
|
|
15% { opacity: 1; }
|
|
100% { opacity: 0; transform: translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(1); }
|
|
}
|
|
|
|
/* ---------- easing panel ---------- */
|
|
.panel {
|
|
background: var(--white);
|
|
border: 1.5px solid var(--gray-200);
|
|
border-radius: 12px;
|
|
padding: 18px;
|
|
}
|
|
.panel h2 {
|
|
font-family: var(--serif);
|
|
font-weight: 500;
|
|
font-size: 16px;
|
|
margin: 0 0 4px;
|
|
}
|
|
.panel p {
|
|
margin: 0 0 14px;
|
|
font-size: 12px;
|
|
color: var(--gray-500);
|
|
}
|
|
.ease-btn {
|
|
display: block;
|
|
width: 100%;
|
|
text-align: left;
|
|
background: var(--gray-50);
|
|
border: 1.5px solid var(--gray-200);
|
|
border-radius: 8px;
|
|
padding: 10px 12px;
|
|
margin-bottom: 8px;
|
|
font-family: var(--sans);
|
|
font-size: 13px;
|
|
color: var(--slate);
|
|
cursor: pointer;
|
|
}
|
|
.ease-btn:hover { border-color: var(--gray-500); }
|
|
.ease-btn.active {
|
|
border-color: var(--clay);
|
|
background: #FBF1EC;
|
|
}
|
|
.ease-btn code {
|
|
display: block;
|
|
font-family: var(--mono);
|
|
font-size: 10px;
|
|
color: var(--gray-500);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* ---------- timeline ---------- */
|
|
.timeline {
|
|
margin-top: 28px;
|
|
background: var(--white);
|
|
border: 1.5px solid var(--gray-200);
|
|
border-radius: 12px;
|
|
padding: 22px 22px 30px;
|
|
}
|
|
.timeline h2 {
|
|
font-family: var(--serif);
|
|
font-weight: 500;
|
|
font-size: 16px;
|
|
margin: 0 0 18px;
|
|
}
|
|
.track {
|
|
position: relative;
|
|
height: 2px;
|
|
background: var(--gray-200);
|
|
margin: 20px 8px 0;
|
|
}
|
|
.key {
|
|
position: absolute;
|
|
top: -5px;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
background: var(--clay);
|
|
border: 1.5px solid var(--white);
|
|
box-shadow: 0 0 0 1.5px var(--clay);
|
|
transform: translateX(-50%);
|
|
}
|
|
.key span {
|
|
position: absolute;
|
|
top: 16px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
white-space: nowrap;
|
|
font-family: var(--mono);
|
|
font-size: 10px;
|
|
color: var(--gray-800);
|
|
}
|
|
.key em {
|
|
position: absolute;
|
|
bottom: 16px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
white-space: nowrap;
|
|
font-style: normal;
|
|
font-size: 11px;
|
|
color: var(--gray-500);
|
|
}
|
|
.k1 { left: 0%; }
|
|
.k2 { left: 13%; }
|
|
.k3 { left: 20%; }
|
|
.k4 { left: 33%; }
|
|
.k5 { left: 100%; background: var(--olive); box-shadow: 0 0 0 1.5px var(--olive); }
|
|
|
|
/* ---------- snippet ---------- */
|
|
.snippet {
|
|
margin-top: 28px;
|
|
}
|
|
.snippet h2 {
|
|
font-family: var(--serif);
|
|
font-weight: 500;
|
|
font-size: 16px;
|
|
margin: 0 0 10px;
|
|
}
|
|
pre {
|
|
background: var(--gray-800);
|
|
color: var(--gray-50);
|
|
border-radius: 12px;
|
|
padding: 18px 20px;
|
|
font-family: var(--mono);
|
|
font-size: 12px;
|
|
line-height: 1.7;
|
|
overflow-x: auto;
|
|
margin: 0;
|
|
}
|
|
pre .c { color: var(--gray-500); }
|
|
pre .k { color: var(--clay); }
|
|
pre .v { color: #B8C99D; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrap">
|
|
|
|
<header>
|
|
<div class="eyebrow">Acme / prototype / micro-interaction</div>
|
|
<h1>Task completed</h1>
|
|
<p class="sub">
|
|
A single click should feel like a tiny win. Circle fills, check draws,
|
|
label strikes, a small burst, then the row quietly steps back. Click
|
|
the row to play; click again to reset.
|
|
</p>
|
|
</header>
|
|
|
|
<div class="bench">
|
|
|
|
<div class="stage">
|
|
<div class="task" id="task">
|
|
<div class="check">
|
|
<svg viewBox="0 0 16 16"><path d="M3 8.5 L6.5 12 L13 4.5"/></svg>
|
|
</div>
|
|
<span class="label">Send weekly digest</span>
|
|
<span class="due">Fri</span>
|
|
<div class="confetti c1"></div>
|
|
<div class="confetti c2"></div>
|
|
<div class="confetti c3"></div>
|
|
<div class="confetti c4"></div>
|
|
<div class="confetti c5"></div>
|
|
<div class="confetti c6"></div>
|
|
</div>
|
|
<div class="hint">click to toggle</div>
|
|
</div>
|
|
|
|
<aside class="panel">
|
|
<h2>Easing</h2>
|
|
<p>Swaps <code>--ease</code> across every transition so you can feel the difference.</p>
|
|
<button class="ease-btn" data-ease="linear">
|
|
Linear
|
|
<code>linear</code>
|
|
</button>
|
|
<button class="ease-btn" data-ease="cubic-bezier(0.16, 1, 0.3, 1)">
|
|
Ease-out
|
|
<code>cubic-bezier(.16, 1, .3, 1)</code>
|
|
</button>
|
|
<button class="ease-btn active" data-ease="cubic-bezier(0.34, 1.56, 0.64, 1)">
|
|
Spring
|
|
<code>cubic-bezier(.34, 1.56, .64, 1)</code>
|
|
</button>
|
|
</aside>
|
|
|
|
</div>
|
|
|
|
<section class="timeline">
|
|
<h2>Keyframes</h2>
|
|
<div class="track">
|
|
<div class="key k1"><em>fill</em><span>0ms</span></div>
|
|
<div class="key k2"><em>check</em><span>80ms</span></div>
|
|
<div class="key k3"><em>strike</em><span>120ms</span></div>
|
|
<div class="key k4"><em>confetti</em><span>200ms</span></div>
|
|
<div class="key k5"><em>collapse</em><span>600ms</span></div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="snippet">
|
|
<h2>Copy-paste CSS</h2>
|
|
<pre><span class="c">/* circle: clay flash, settle to olive with spring overshoot */</span>
|
|
<span class="k">.task.done .check</span> {
|
|
animation: settle 380ms <span class="v">cubic-bezier(.34,1.56,.64,1)</span> forwards;
|
|
}
|
|
<span class="k">@keyframes settle</span> {
|
|
0% { transform: scale(.8); background: <span class="v">#D97757</span>; }
|
|
55% { transform: scale(1.18); }
|
|
100% { transform: scale(1); background: <span class="v">#788C5D</span>; }
|
|
}
|
|
|
|
<span class="c">/* checkmark draws via stroke-dashoffset, 80ms delay */</span>
|
|
<span class="k">.check path</span> { stroke-dasharray: 20; stroke-dashoffset: 20;
|
|
transition: stroke-dashoffset 220ms var(--ease) 80ms; }
|
|
<span class="k">.task.done path</span> { stroke-dashoffset: 0; }
|
|
|
|
<span class="c">/* strikethrough grows left → right */</span>
|
|
<span class="k">.label::after</span> { width: 0; transition: width 240ms var(--ease) 120ms; }
|
|
<span class="k">.task.done .label::after</span> { width: 100%; }
|
|
|
|
<span class="c">/* row steps back after the celebration */</span>
|
|
<span class="k">.task.done</span> { max-height: 44px; opacity: .6;
|
|
transition-delay: 600ms; }</pre>
|
|
</section>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
var task = document.getElementById('task');
|
|
task.addEventListener('click', function () {
|
|
task.classList.toggle('done');
|
|
});
|
|
|
|
var root = document.documentElement;
|
|
var buttons = document.querySelectorAll('.ease-btn');
|
|
buttons.forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
buttons.forEach(function (b) { b.classList.remove('active'); });
|
|
btn.classList.add('active');
|
|
root.style.setProperty('--ease', btn.dataset.ease);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|