Add completion animation and ding sound on Done button
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Intercepts the Done form submit, plays a two-note ding via Web Audio
API (no audio file), animates the card with a white glow + scale, then
submits after 680ms. Only fires for completion, not abandon or delete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 23:59:09 +03:00
parent b289e7c7fe
commit b723214c86

View File

@@ -55,6 +55,17 @@
.btn-danger:hover { background: #f87171; color: #0f172a; opacity: 1; }
.btn-done { background: transparent; color: #34d399; border: 1px solid #34d399; padding: 0.3rem 0.65rem; font-size: 0.78rem; }
.btn-done:hover { background: #34d399; color: #0f172a; opacity: 1; }
@keyframes complete-glow {
0% { box-shadow: 0 0 0px 0px rgba(255,255,255,0); transform: scale(1); }
30% { box-shadow: 0 0 18px 6px rgba(255,255,255,0.55); transform: scale(1.03); }
70% { box-shadow: 0 0 24px 8px rgba(255,255,255,0.35); transform: scale(1.03); }
100% { box-shadow: 0 0 0px 0px rgba(255,255,255,0); transform: scale(1); opacity: 0; }
}
.card.completing {
animation: complete-glow 0.7s ease-out forwards;
pointer-events: none;
}
.btn-abandon { background: transparent; color: #fb923c; border: 1px solid #fb923c; padding: 0.3rem 0.65rem; font-size: 0.78rem; }
.btn-abandon:hover { background: #fb923c; color: #0f172a; opacity: 1; }
@@ -242,7 +253,7 @@
<div class="card-actions">
<a href="{% url 'backlogger:edit' item.pk %}" class="btn btn-sm btn-outline">Edit</a>
{% if shelf == 'active' %}
<form method="post" action="{% url 'backlogger:set_status' item.pk %}">
<form method="post" action="{% url 'backlogger:set_status' item.pk %}" data-complete>
{% csrf_token %}
<input type="hidden" name="status" value="completed">
<input type="hidden" name="next" value="{{ request.get_full_path }}">
@@ -285,5 +296,37 @@
{% endif %}
</div>
<script>
function playDing() {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
// Two-note ding: base note then a fifth above
[[880, 0, 0.15], [1320, 0.12, 0.28]].forEach(([freq, start, end]) => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'sine';
osc.frequency.value = freq;
gain.gain.setValueAtTime(0, ctx.currentTime + start);
gain.gain.linearRampToValueAtTime(0.25, ctx.currentTime + start + 0.02);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + end);
osc.start(ctx.currentTime + start);
osc.stop(ctx.currentTime + end);
});
} catch (e) {}
}
document.querySelectorAll('form[data-complete]').forEach(form => {
form.addEventListener('submit', function(e) {
e.preventDefault();
const card = form.closest('.card');
playDing();
card.classList.add('completing');
setTimeout(() => form.submit(), 680);
});
});
</script>
</body>
</html>