Draw the braille spinner as one masked CSS animation

This commit is contained in:
Ian Gulliver
2026-10-14 23:22:44 -07:00
parent fbfc3d1923
commit c6e57a4740
5 changed files with 17 additions and 74 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ export function mountTranscript(root) {
};
let ctx = newCtx();
let context = new Map();
const spinner = h("div", { class: "spinner", hidden: true }, ...[1, 2, 3, 4, 5, 6].map((n) => h("span", { class: `dot${n}` })));
const spinner = h("div", { class: "spinner", hidden: true });
const setRunning = (running) => {
transcript.classList.toggle("running", running);
if (running === !spinner.hidden) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -82,7 +82,7 @@ export function mountTranscript(root: HTMLElement): () => void {
let ctx = newCtx();
let context = new Map<string, { used: number; window: number; model: string }>();
const spinner = h("div", { class: "spinner", hidden: true }, ...[1, 2, 3, 4, 5, 6].map((n) => h("span", { class: `dot${n}` })));
const spinner = h("div", { class: "spinner", hidden: true });
const setRunning = (running: boolean) => {
transcript.classList.toggle("running", running);
if (running === !spinner.hidden) {
+13 -70
View File
@@ -484,83 +484,26 @@ textarea {
}
.spinner {
--frames: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='50' height='10'%3E%3Cg%3E%3Ccircle cx='1' cy='1' r='1'/%3E%3Ccircle cx='1' cy='5' r='1'/%3E%3Ccircle cx='4' cy='1' r='1'/%3E%3Ccircle cx='6' cy='1' r='1'/%3E%3Ccircle cx='9' cy='1' r='1'/%3E%3Ccircle cx='9' cy='5' r='1'/%3E%3Ccircle cx='11' cy='1' r='1'/%3E%3Ccircle cx='14' cy='1' r='1'/%3E%3Ccircle cx='14' cy='5' r='1'/%3E%3Ccircle cx='14' cy='9' r='1'/%3E%3Ccircle cx='19' cy='1' r='1'/%3E%3Ccircle cx='19' cy='5' r='1'/%3E%3Ccircle cx='19' cy='9' r='1'/%3E%3Ccircle cx='21' cy='9' r='1'/%3E%3Ccircle cx='24' cy='1' r='1'/%3E%3Ccircle cx='24' cy='5' r='1'/%3E%3Ccircle cx='24' cy='9' r='1'/%3E%3Ccircle cx='26' cy='9' r='1'/%3E%3Ccircle cx='29' cy='5' r='1'/%3E%3Ccircle cx='29' cy='9' r='1'/%3E%3Ccircle cx='31' cy='5' r='1'/%3E%3Ccircle cx='31' cy='9' r='1'/%3E%3Ccircle cx='34' cy='9' r='1'/%3E%3Ccircle cx='36' cy='1' r='1'/%3E%3Ccircle cx='36' cy='5' r='1'/%3E%3Ccircle cx='36' cy='9' r='1'/%3E%3Ccircle cx='39' cy='9' r='1'/%3E%3Ccircle cx='41' cy='1' r='1'/%3E%3Ccircle cx='41' cy='5' r='1'/%3E%3Ccircle cx='41' cy='9' r='1'/%3E%3Ccircle cx='46' cy='1' r='1'/%3E%3Ccircle cx='46' cy='5' r='1'/%3E%3Ccircle cx='46' cy='9' r='1'/%3E%3Ccircle cx='49' cy='1' r='1'/%3E%3C/g%3E%3C/svg%3E");
flex-shrink: 0;
display: grid;
grid-auto-flow: column;
grid-template-rows: repeat(3, 2px);
grid-template-columns: repeat(2, 2px);
gap: 2px 1px;
width: max-content;
padding: 5px 1px;
width: 5px;
height: 10px;
margin: 5px 1px;
background: var(--accent);
-webkit-mask: var(--frames) 0 0 / 50px 10px no-repeat;
mask: var(--frames) 0 0 / 50px 10px no-repeat;
animation: braille 0.8s steps(10) infinite;
}
.spinner[hidden] {
display: none;
}
.spinner span {
border-radius: 50%;
background: var(--accent);
animation: 0.8s step-end infinite;
}
.spinner .dot1 {
animation-name: braille-1;
}
.spinner .dot2 {
animation-name: braille-2;
}
.spinner .dot3 {
animation-name: braille-3;
}
.spinner .dot4 {
animation-name: braille-4;
}
.spinner .dot5 {
animation-name: braille-5;
}
.spinner .dot6 {
animation-name: braille-6;
}
@keyframes braille-1 {
0% { opacity: 1; }
30% { opacity: 0; }
70%, 100% { opacity: 1; }
}
@keyframes braille-2 {
0% { opacity: 1; }
10% { opacity: 0; }
60%, 100% { opacity: 1; }
}
@keyframes braille-3 {
0% { opacity: 0; }
40%, 100% { opacity: 1; }
}
@keyframes braille-4 {
0% { opacity: 1; }
50% { opacity: 0; }
90%, 100% { opacity: 1; }
}
@keyframes braille-5 {
0% { opacity: 0; }
10% { opacity: 1; }
60%, 100% { opacity: 0; }
}
@keyframes braille-6 {
0% { opacity: 0; }
20% { opacity: 1; }
80%, 100% { opacity: 0; }
@keyframes braille {
to {
-webkit-mask-position: -50px 0;
mask-position: -50px 0;
}
}
.empty {