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
Using Claude Code? Give it the agent prompt and skip these steps.
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.