v3.36.0: a deliberate "insert the next card" cue for batch runs

The waiting phase is the only moment the operator must act, so it now:
- shows a card-into-reader SVG animation in the status modal (inline
  SVG, CSS keyframes in src/style.css; reduced-motion users get the
  static card-above-the-slot picture);
- plays a short Web Audio chime once per card, edge-triggered in the
  2 s poll so it never repeats (the AudioContext is unlocked by the
  Start click);
- sets the tab title to "Insert next card" while the window is hidden;
- offers a speaker toggle next to the status (persisted as
  simple_ram_batch_sound, on by default).

The prebuilt style.css was rebuilt: it had gone stale and lacked
utilities the UI already used (bg-blue-50 on the batch notice,
inline-flex, mx-2, max-h-48, w-44, min-w-24).

1022 Python + 1062 frontend green; simple-v470.
This commit is contained in:
2026-10-23 21:14:32 +03:00
parent 194b88e7d1
commit fb65644328
10 changed files with 365 additions and 21 deletions
+96 -10
View File
@@ -616,6 +616,10 @@ video {
pointer-events: none;
}
.\!visible {
visibility: visible !important;
}
.visible {
visibility: visible;
}
@@ -684,6 +688,11 @@ video {
grid-column: span 4 / span 4;
}
.mx-2 {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.mx-3 {
margin-left: 0.75rem;
margin-right: 0.75rem;
@@ -807,6 +816,10 @@ video {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.table {
display: table;
}
@@ -843,6 +856,10 @@ video {
max-height: 8rem;
}
.max-h-48 {
max-height: 12rem;
}
.max-h-56 {
max-height: 14rem;
}
@@ -935,6 +952,10 @@ video {
width: 10rem;
}
.w-44 {
width: 11rem;
}
.w-48 {
width: 12rem;
}
@@ -980,6 +1001,10 @@ video {
min-width: 0px;
}
.min-w-24 {
min-width: 6rem;
}
.min-w-\[12rem\] {
min-width: 12rem;
}
@@ -1395,6 +1420,11 @@ video {
background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
}
.bg-blue-50 {
--tw-bg-opacity: 1;
background-color: rgb(239 246 255 / var(--tw-bg-opacity, 1));
}
.bg-blue-50\/50 {
background-color: rgb(239 246 255 / 0.5);
}
@@ -1796,6 +1826,11 @@ video {
color: rgb(31 41 55 / var(--tw-text-opacity, 1));
}
.text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
}
.text-green-600 {
--tw-text-opacity: 1;
color: rgb(22 163 74 / var(--tw-text-opacity, 1));
@@ -1893,10 +1928,60 @@ video {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.dark .text-heading {
color: #f1f5f9;
}
/* RAM batch: the "insert the next card" cue (v3.36.0).
The card slides into the reader slot and back; the slot glows while the
run waits. The static transform is the reduced-motion fallback: the card
rests above the slot. */
.ram-insert-card {
transform: translateY(-14px);
animation: ram-insert 2.8s ease-in-out infinite;
}
@keyframes ram-insert {
0%, 12% {
transform: translateY(-14px);
}
45%, 55% {
transform: translateY(10px);
}
88%, 100% {
transform: translateY(-14px);
}
}
.ram-insert-glow {
animation: ram-insert-glow 1.4s ease-in-out infinite;
}
@keyframes ram-insert-glow {
0%, 100% {
opacity: 0.35;
}
50% {
opacity: 0.95;
}
}
@media (prefers-reduced-motion: reduce) {
.ram-insert-card, .ram-insert-glow {
animation: none;
}
}
.hover\:bg-amber-100:hover {
--tw-bg-opacity: 1;
background-color: rgb(254 243 199 / var(--tw-bg-opacity, 1));
@@ -1956,11 +2041,6 @@ video {
background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
}
.hover\:bg-red-100:hover {
--tw-bg-opacity: 1;
background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
}
.hover\:bg-red-200:hover {
--tw-bg-opacity: 1;
background-color: rgb(254 202 202 / var(--tw-bg-opacity, 1));
@@ -2093,6 +2173,10 @@ video {
background-color: rgb(30 58 138 / 0.1);
}
.dark\:bg-blue-900\/20:is(.dark *) {
background-color: rgb(30 58 138 / 0.2);
}
.dark\:bg-blue-900\/40:is(.dark *) {
background-color: rgb(30 58 138 / 0.4);
}
@@ -2258,10 +2342,6 @@ video {
background-color: rgb(6 78 59 / 0.4);
}
.dark\:hover\:bg-red-900\/40:hover:is(.dark *) {
background-color: rgb(127 29 29 / 0.4);
}
.dark\:hover\:bg-red-900\/50:hover:is(.dark *) {
background-color: rgb(127 29 29 / 0.5);
}
@@ -2289,6 +2369,11 @@ video {
color: rgb(209 213 219 / var(--tw-text-opacity, 1));
}
.dark\:hover\:text-slate-200:hover:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(226 232 240 / var(--tw-text-opacity, 1));
}
.dark\:hover\:text-slate-300:hover:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(203 213 225 / var(--tw-text-opacity, 1));
@@ -2328,7 +2413,8 @@ video {
.lg\:flex-row {
flex-direction: row;
}
}/* ===== Theme contrast adjustments ===== */
}
/* ===== Theme contrast adjustments ===== */
/* Light theme: darken softer (muted) text one step for higher contrast */
.text-gray-400 {