Drafting report 214
Pulling GA4 data
Syncing citation runs
The weekly report is ready for review. Checking brand voice
Pixel-grid loader with a shimmering label and elapsed timer.
FeedbackComponentNo dependencies Beautiful UI, MIT
The weekly report is ready for review. Checking brand voice
foundation.css holds the tokens every component reads.
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
--- ---
import LoadingState from "../components/viewership/loading-state/LoadingState.astro";
---
<LoadingState /> | Prop | Type | Default | Description |
|---|---|---|---|
variant | "drive" | "dots" | "orbit" | "drive" | Grid pattern. |
label | string | "Working" | Status text, shown and announced. |
elapsed | boolean | true | Show the elapsed timer (needs JS). |
startedAt | number | · | Epoch ms the work started. Defaults to page load. |
inline | boolean | false | Render as an inline <span> inside text or a button. |
shimmer | boolean | true | Shimmer the label. |
autoplay | boolean | true | Animate the grid. Pauses offscreen. |
announce | boolean | true | Expose as role="status". Turn off inside a control that already reports busy. |
---
/**
* LoadingState: pixel-grid loader for long-running work.
*
* A 3×3 grid pulses in a wavefront beside a shimmering label and a live
* elapsed timer.
*
* drive square cells, chevron wavefront moving right
* dots the same wavefront on circular cells
* orbit a comet lapping the grid perimeter
*
* `inline` renders a <span> that inherits the surrounding type.
*
* Ported from Beautiful UI `components/primitives/LoadingState.tsx`
* (MIT, © 2026 Shane Levine).
*
* @example
* <LoadingState label="Drafting report 214" />
* <LoadingState variant="orbit" label="Syncing orders" startedAt={job.startedAt} />
* <button aria-busy="true"><LoadingState inline announce={false} label="Saving" elapsed={false} /></button>
*/
import type { HTMLAttributes } from "astro/types";
type Variant = "drive" | "dots" | "orbit";
type Props = HTMLAttributes<"div"> & {
variant?: Variant;
/** Visible status text; also what screen readers announce. */
label?: string;
/** Show the live elapsed timer. */
elapsed?: boolean;
/** Epoch ms the work started; the timer counts from here. Defaults to page load. */
startedAt?: number;
/** Render inline (a <span>) so it can sit inside text or a button. */
inline?: boolean;
/** Shimmer the label. */
shimmer?: boolean;
/** Animate the grid. When false the grid rests in its dim state. */
autoplay?: boolean;
/** Expose as a polite live region (role="status"). Turn off inside an already-busy control. */
announce?: boolean;
};
const {
variant = "drive",
label = "Working",
elapsed = true,
startedAt,
inline = false,
shimmer = true,
autoplay = true,
announce = true,
class: className,
...rest
} = Astro.props;
/* chevron wavefront: column plus distance from the middle row, 90ms per step */
const chevron: (number | null)[] = Array.from({ length: 9 }, (_, i) => {
const r = Math.floor(i / 3);
const c = i % 3;
return (c + Math.abs(r - 1)) * 90;
});
/* comet: clockwise around the perimeter, the centre cell stays off */
const ORBIT_ORDER = [0, 1, 2, 5, 8, 7, 6, 3];
const orbit: (number | null)[] = Array.from({ length: 9 }, (_, i) => {
const k = ORBIT_ORDER.indexOf(i);
return k === -1 ? null : k * 110;
});
const PATTERNS: Record<Variant, { delays: (number | null)[]; dur: number; round: boolean }> = {
drive: { delays: chevron, dur: 650, round: false },
dots: { delays: chevron, dur: 650, round: true },
orbit: { delays: orbit, dur: 950, round: false },
};
const { delays, dur, round } = PATTERNS[variant] ?? PATTERNS.drive;
const Tag = inline ? "span" : "div";
---
<Tag
class:list={["vs-loading-state", inline && "vs-loading-state--inline", round && "is-round", className]}
role={announce ? "status" : undefined}
data-autoplay={autoplay ? "" : undefined}
data-started-at={startedAt}
style={`--vs-ls-dur: ${dur}ms`}
{...rest}
>
<span class="ls-grid" aria-hidden="true">
{delays.map((delay) =>
delay === null ? <span class="cell is-off" /> : <span class="cell" style={`--vs-ls-delay: ${delay}ms`} />,
)}
</span>
<span class:list={["ls-label", shimmer && "is-shimmer"]}>{label}</span>
{elapsed && (
<span class="elapsed" aria-hidden="true" data-elapsed>0.0s</span>
)}
</Tag>
<style>
.vs-loading-state {
display: flex;
width: fit-content;
align-items: center;
gap: 10px;
}
.vs-loading-state--inline {
display: inline-flex;
gap: 0.5em;
vertical-align: middle;
}
.ls-grid {
display: grid;
flex-shrink: 0;
grid-template-columns: repeat(3, 4px);
gap: 1.5px;
}
.cell {
width: 4px;
height: 4px;
border-radius: 1px;
background: var(--vs-ink);
opacity: 0.15;
}
.is-round .cell {
border-radius: 50%;
}
.cell.is-off {
opacity: 0.07;
}
[data-autoplay] .cell:not(.is-off) {
animation: vs-pixel-on var(--vs-ls-dur) ease-in-out var(--vs-ls-delay) infinite;
}
.ls-label {
font-size: 13px;
font-weight: 500;
color: var(--vs-ink-2);
white-space: nowrap;
}
.vs-loading-state--inline .ls-label {
font-size: inherit;
font-weight: inherit;
}
.ls-label.is-shimmer {
color: transparent;
background-image: linear-gradient(90deg, var(--vs-ink-3) 35%, var(--vs-ink) 50%, var(--vs-ink-3) 65%);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
animation: vs-shimmer-text 1.4s linear infinite;
}
.elapsed {
font-family: var(--vs-font-sans);
font-size: 12px;
color: var(--vs-ink-3);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.vs-loading-state--inline .elapsed {
font-size: 0.92em;
}
/* offscreen: freeze the grid and shimmer where they are */
[data-paused] .cell,
[data-paused] .ls-label {
animation-play-state: paused;
}
/* the timer needs script; without it, show only the loader and label */
@media (scripting: none) {
.elapsed {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
[data-autoplay] .cell:not(.is-off) {
animation: none;
}
.ls-label.is-shimmer {
animation: none;
background: none;
color: var(--vs-ink-2);
}
}
@media (forced-colors: active) {
.cell {
background: CanvasText;
}
.ls-label.is-shimmer {
background: none;
color: CanvasText;
}
}
</style>
<script>
const format = (ms: number) => {
const total = Math.max(0, Math.floor(ms / 100)) / 10;
if (total < 60) return `${total.toFixed(1)}s`;
return `${Math.floor(total / 60)}m ${(total % 60).toFixed(1)}s`;
};
function init(root: HTMLElement) {
const out = root.querySelector<HTMLElement>("[data-elapsed]");
const started = Number(root.dataset.startedAt) || Date.now();
let interval: number | undefined;
const render = () => {
if (!root.isConnected) return teardown();
if (out) out.textContent = format(Date.now() - started);
};
const start = () => {
root.removeAttribute("data-paused");
render();
if (out && interval === undefined) interval = window.setInterval(render, 100);
};
const stop = () => {
root.setAttribute("data-paused", "");
window.clearInterval(interval);
interval = undefined;
};
/* the timer measures wall-clock time, so pausing offscreen only skips repaints */
const io = new IntersectionObserver(([entry]) => (entry?.isIntersecting ? start() : stop()));
io.observe(root);
render();
function teardown() {
stop();
io.disconnect();
}
document.addEventListener("astro:before-swap", teardown, { once: true });
}
const boot = () =>
document.querySelectorAll<HTMLElement>(".vs-loading-state:not([data-vs-ready])").forEach((root) => {
root.dataset.vsReady = "";
init(root);
});
boot();
document.addEventListener("astro:page-load", boot);
</script> Ported from Beautiful UI, MIT. Notices.