Flight Manifest
A launch table on warm black: filter it, and an orange wipe runs across each row you point at; open a flight for its window, pad and mass.
DOM + CSS0 deps1 file · 16 KB TSXreduced-motion aware
Props
"use client";
import { useEffect, useId, useRef, useState, useSyncExternalStore } from "react";
import type { CSSProperties, RefObject } from "react";
/** Safety orange as a field colour. Black on it reads at 6.3:1. */
const HV_ORANGE = "#FF5B14";
/** The hot highlight, for orange marks on black. */
const HV_HOT = "#FF7A1A";
const HV_BLACK = "#0B0A09";
const HV_BONE = "#EFEBE4";
/**
* The display face: Archivo's width axis does the shouting (wdth 62..125).
* Load it as a variable font with the width axis and expose it as --font-hv,
* e.g. in Next: Archivo({ subsets: ["latin"], axes: ["wdth"], variable: "--font-hv" }).
*/
const HV_DISPLAY = 'var(--font-hv, "Archivo"), "Archivo", "Arial Narrow", ui-sans-serif, system-ui, sans-serif';
/** Labels and readouts: Geist Mono as --font-hv-mono (any monospace works). */
const HV_MONO = 'var(--font-hv-mono, "Geist Mono"), "Geist Mono", ui-monospace, "SFMono-Regular", Menlo, monospace';
const hvClamp = (v: number, a = 0, b = 1) => (v < a ? a : v > b ? b : v);
/** Where p sits between a and b, clamped to 0..1. */
const hvRange = (p: number, a: number, b: number) => hvClamp((p - a) / (b - a));
/** Ease out (cubic): fast, then settles. */
const hvOut = (t: number) => 1 - Math.pow(1 - hvClamp(t), 3);
function hvReducedSub(fn: () => void) {
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {};
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
mq.addEventListener("change", fn);
return () => mq.removeEventListener("change", fn);
}
const hvReducedSnap = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/** True when the reader asked for reduced motion. Live: follows the system setting as it changes. */
function useHvReduced(): boolean {
return useSyncExternalStore(hvReducedSub, hvReducedSnap, () => false);
}
/**
* The scrolling box an element lives in: the nearest ancestor that actually
* scrolls, or null for the window. Resolved on every frame, because a box only
* becomes a scroller once its content is taller than it.
*/
function hvScrollParent(el: Element | null): HTMLElement | null {
let n = el?.parentElement ?? null;
while (n && n !== document.body && n !== document.documentElement) {
const oy = getComputedStyle(n).overflowY;
if ((oy === "auto" || oy === "scroll" || oy === "overlay") && n.scrollHeight > n.clientHeight + 1) return n;
n = n.parentElement;
}
return null;
}
interface HvFrame {
/** The element's box, in viewport pixels. */
rect: DOMRect;
/** The visible part of whatever scrolls it: the scroller's box, or the window. */
view: { top: number; height: number; left: number; width: number };
}
/**
* Run `apply` with the element's position whenever anything scrolls or
* resizes, at most once per frame. One capture-phase scroll listener hears
* every scroller, so the same section works on a page and inside a scrolling
* preview. `apply` writes to the DOM; nothing here re-renders React.
*/
function useHvScrollFrame<T extends HTMLElement>(ref: RefObject<T | null>, apply: (f: HvFrame) => void) {
const applyRef = useRef(apply);
useEffect(() => {
applyRef.current = apply;
});
useEffect(() => {
let raf = 0;
const run = () => {
raf = 0;
const el = ref.current;
if (!el) return;
const sp = hvScrollParent(el);
const r = sp ? sp.getBoundingClientRect() : null;
const view = sp && r ? { top: r.top, height: sp.clientHeight, left: r.left, width: sp.clientWidth } : { top: 0, height: window.innerHeight, left: 0, width: window.innerWidth };
applyRef.current({ rect: el.getBoundingClientRect(), view });
};
const kick = () => {
if (!raf) raf = requestAnimationFrame(run);
};
document.addEventListener("scroll", kick, { capture: true, passive: true });
window.addEventListener("resize", kick);
const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(kick) : null;
if (ref.current && ro) ro.observe(ref.current);
run();
return () => {
document.removeEventListener("scroll", kick, { capture: true });
window.removeEventListener("resize", kick);
ro?.disconnect();
if (raf) cancelAnimationFrame(raf);
};
}, [ref]);
}
/**
* The kit's scoped CSS: one focus ring (2px, offset 3px, in the ink of the
* ground it sits on), tabular figures, the press, and transitions off for
* anything a frame loop writes.
*/
const HV_CSS =
".hv-scope{--hv-ring:" + HV_HOT + "}" +
".hv-scope[data-hv-ground=orange],.hv-scope [data-hv-ground=orange]{--hv-ring:" + HV_BLACK + "}" +
".hv-scope[data-hv-ground=bone],.hv-scope [data-hv-ground=bone]{--hv-ring:" + HV_BLACK + "}" +
".hv-scope :focus-visible{outline:2px solid var(--hv-ring)!important;outline-offset:3px!important}" +
".hv-scope .hv-num{font-variant-numeric:tabular-nums}" +
".hv-scope [data-hv-live]{transition:none!important}" +
".hv-scope .hv-press{transition:transform 160ms cubic-bezier(.2,.8,.2,1),background-color 160ms,color 160ms}" +
".hv-scope .hv-press:active{transform:scale(.97)}" +
".hv-scope .hv-arr{transition:transform 220ms cubic-bezier(.2,.8,.2,1)}.hv-scope a:hover .hv-arr,.hv-scope button:hover .hv-arr{transform:translateX(3px)}" +
"@media (prefers-reduced-motion:reduce){.hv-scope .hv-press,.hv-scope .hv-arr{transition:none}}";
export interface ManifestFlight {
date: string;
mission: string;
payload: string;
orbit: string;
status: "go" | "hold" | "flown";
/** Shown when the row is opened. */
details: [string, string][];
}
const MF_STATUS: Record<ManifestFlight["status"], string> = { go: "Go", hold: "Hold", flown: "Flown" };
const MF_CSS =
".hv-mf-row{position:relative;isolation:isolate}" +
".hv-mf-row::before{content:'';position:absolute;inset:0;z-index:-1;background:" + HV_ORANGE + ";transform:scaleX(0);transform-origin:left;transition:transform 380ms cubic-bezier(.2,.8,.2,1)}" +
".hv-mf-row:hover::before,.hv-mf-row:focus-visible::before,.hv-mf-row[aria-expanded=true]::before{transform:scaleX(1)}" +
".hv-mf-row:hover,.hv-mf-row:focus-visible,.hv-mf-row[aria-expanded=true]{color:" + HV_BLACK + "!important}" +
".hv-mf-row{transition:color 200ms}" +
".hv-mf-row:hover .hv-mf-pill,.hv-mf-row:focus-visible .hv-mf-pill,.hv-mf-row[aria-expanded=true] .hv-mf-pill{box-shadow:inset 0 0 0 1.5px " + HV_BLACK + "!important;background:transparent!important;color:" + HV_BLACK + "!important}" +
".hv-mf-row .hv-mf-x{transition:transform 260ms cubic-bezier(.2,.8,.2,1)}.hv-mf-row[aria-expanded=true] .hv-mf-x{transform:rotate(45deg)}" +
".hv-mf-grid{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 16px;align-items:baseline}" +
"@container (min-width:768px){.hv-mf-grid{grid-template-columns:150px minmax(0,1.1fr) minmax(0,1.3fr) minmax(0,1fr) 92px 24px;gap:24px;align-items:center}}" +
".hv-mf-more{display:grid;grid-template-rows:0fr;transition:grid-template-rows 360ms cubic-bezier(.2,.8,.2,1)}.hv-mf-more[data-open]{grid-template-rows:1fr}" +
"@keyframes hv-mf-in{from{opacity:0;transform:translateY(8px)}}.hv-mf-li{animation:hv-mf-in 360ms cubic-bezier(.2,.8,.2,1) both;animation-delay:calc(var(--i) * 40ms)}" +
"@media (prefers-reduced-motion:reduce){.hv-mf-row::before,.hv-mf-row,.hv-mf-row .hv-mf-x,.hv-mf-more{transition:none}.hv-mf-li{animation:none}}";
export interface FlightManifestProps {
eyebrow?: string;
headline?: string;
flights?: ManifestFlight[];
className?: string;
style?: CSSProperties;
}
/**
* FlightManifest — the launch table: filter it, open a flight for its details.
*/
export function FlightManifest({
eyebrow = "Flight manifest",
headline = "Next on the stand.",
flights = [
{ date: "14 Nov 2026", mission: "AR-31", payload: "Rideshare · 14 satellites", orbit: "SSO 540 km", status: "go", details: [["Payload mass", "412 kg"], ["Window", "02:14–02:44 UTC"], ["Pad", "LC-2"], ["Engines", "9 × R-9, two relights"]] },
{ date: "02 Dec 2026", mission: "AR-32", payload: "Dedicated · weather radar", orbit: "SSO 610 km", status: "go", details: [["Payload mass", "486 kg"], ["Window", "21:05–21:20 UTC"], ["Pad", "LC-1"], ["Engines", "9 × R-9, one relight"]] },
{ date: "19 Dec 2026", mission: "HF-118", payload: "Hot fire · upper stage", orbit: "Test stand B", status: "hold", details: [["Duration", "240 s at 100%"], ["Starts", "3"], ["Stand", "B, horizontal"], ["Status", "Waiting on propellant"]] },
{ date: "28 Sep 2026", mission: "AR-30", payload: "Rideshare · 9 satellites", orbit: "LEO 500 km", status: "flown", details: [["Payload mass", "338 kg"], ["Lift-off", "03:41 UTC"], ["Pad", "LC-2"], ["Result", "All payloads deployed"]] },
{ date: "06 Aug 2026", mission: "AR-29", payload: "Dedicated · imaging", orbit: "SSO 525 km", status: "flown", details: [["Payload mass", "455 kg"], ["Lift-off", "22:17 UTC"], ["Pad", "LC-1"], ["Result", "Nominal, booster recovered"]] },
],
className = "",
style,
}: FlightManifestProps) {
const [filter, setFilter] = useState<"all" | "upcoming" | "flown">("all");
const [open, setOpen] = useState<string | null>(null);
const ref = useRef<HTMLElement>(null);
const reduced = useHvReduced();
const base = useId();
const shown = flights.filter((f) => (filter === "all" ? true : filter === "flown" ? f.status === "flown" : f.status !== "flown"));
const count = (k: "all" | "upcoming" | "flown") => flights.filter((f) => (k === "all" ? true : k === "flown" ? f.status === "flown" : f.status !== "flown")).length;
// Rows draw in from the left as they cross the lower part of the view.
useHvScrollFrame(ref, (f) => {
const root = ref.current;
if (!root) return;
root.querySelectorAll<HTMLElement>("[data-mf-rule]").forEach((el) => {
const r = el.getBoundingClientRect();
const t = reduced ? 1 : hvOut(hvRange((f.view.top + f.view.height - r.top) / f.view.height, 0.04, 0.3));
el.style.transform = "scaleX(" + t.toFixed(4) + ")";
});
});
return (
<section ref={ref} data-hv-ground="black" className={"hv-scope @container relative w-full " + className} style={{ background: HV_BLACK, color: HV_BONE, fontFamily: HV_DISPLAY, ...style }}>
<style>{HV_CSS + MF_CSS}</style>
<div className="mx-auto w-full max-w-[1320px] px-5 py-20 @3xl:px-[4cqw] @3xl:py-28">
<div className="mb-10 flex flex-col gap-6 @3xl:mb-14 @3xl:flex-row @3xl:items-end @3xl:justify-between">
<div className="flex flex-col gap-3">
<p className="m-0 flex items-center gap-2.5 text-[11.5px] font-medium uppercase tracking-[0.14em] @3xl:text-[12.5px]" style={{ fontFamily: HV_MONO, opacity: 0.8 }}>
<span aria-hidden className="inline-block h-2 w-2" style={{ background: HV_HOT }} />
{eyebrow}
</p>
<h2 className="m-0 text-[clamp(44px,12cqw,64px)] uppercase leading-[0.86] @3xl:text-[clamp(56px,7cqw,112px)]" style={{ fontWeight: 900, fontVariationSettings: '"wdth" 70' }}>
{headline}
</h2>
</div>
<div role="group" aria-label="Show flights" className="flex self-start @3xl:self-auto" style={{ boxShadow: "inset 0 0 0 1.5px " + HV_BONE }}>
{(["all", "upcoming", "flown"] as const).map((k) => (
<button
key={k}
type="button"
aria-pressed={filter === k}
onClick={() => {
setFilter(k);
setOpen(null);
}}
className="hv-press h-10 border-0 px-4 text-[12px] font-medium uppercase tracking-[0.12em]"
style={{ fontFamily: HV_MONO, background: filter === k ? HV_BONE : "transparent", color: filter === k ? HV_BLACK : HV_BONE, cursor: "pointer" }}
>
{k} <span className="hv-num" style={{ opacity: 0.6 }}>{count(k)}</span>
</button>
))}
</div>
</div>
<div aria-hidden className="hidden @3xl:block">
<div className="hv-mf-grid px-3 pb-3 text-[11px] uppercase tracking-[0.14em]" style={{ fontFamily: HV_MONO, opacity: 0.55 }}>
<span>Date</span>
<span>Mission</span>
<span>Payload</span>
<span>Orbit</span>
<span>Status</span>
<span />
</div>
</div>
<ul key={filter} className="m-0 list-none p-0">
{shown.map((fl, i) => {
const isOpen = open === fl.mission;
const panel = base + fl.mission;
return (
<li key={fl.mission} className="hv-mf-li relative" style={{ "--i": i } as CSSProperties}>
<span aria-hidden data-mf-rule="" data-hv-live="" className="absolute inset-x-0 top-0 h-px origin-left" style={{ background: "rgba(239,235,228,.28)" }} />
<button type="button" aria-expanded={isOpen} aria-controls={panel} onClick={() => setOpen(isOpen ? null : fl.mission)} className="hv-mf-row hv-mf-grid w-full border-0 bg-transparent px-3 py-5 text-left @3xl:py-6" style={{ color: "inherit", font: "inherit", cursor: "pointer" }}>
<span className="hv-num text-[12px] uppercase tracking-[0.1em] @3xl:text-[13px]" style={{ fontFamily: HV_MONO }}>
{fl.date}
</span>
<span className="order-first col-span-2 text-[34px] uppercase leading-none @3xl:order-none @3xl:col-span-1 @3xl:text-[40px]" style={{ fontWeight: 900, fontVariationSettings: '"wdth" 76' }}>
{fl.mission}
</span>
<span className="col-span-2 text-[15px] @3xl:col-span-1" style={{ fontWeight: 500 }}>
{fl.payload}
</span>
<span className="text-[12px] uppercase tracking-[0.1em] @3xl:text-[13px]" style={{ fontFamily: HV_MONO, opacity: 0.8 }}>
{fl.orbit}
</span>
<span className="justify-self-end @3xl:justify-self-start">
<span className="hv-mf-pill inline-flex h-7 min-w-[56px] items-center justify-center gap-1.5 px-2.5 text-[11.5px] font-bold uppercase tracking-[0.12em]" style={{ fontFamily: HV_MONO, background: fl.status === "go" ? HV_ORANGE : "transparent", color: fl.status === "go" ? HV_BLACK : HV_BONE, boxShadow: fl.status === "go" ? "none" : "inset 0 0 0 1.5px rgba(239,235,228,.5)" }}>
{fl.status === "hold" ? <span aria-hidden className="h-1.5 w-1.5" style={{ background: "currentColor" }} /> : null}
{MF_STATUS[fl.status]}
</span>
</span>
<svg className="hv-mf-x hidden justify-self-end @3xl:block" width="16" height="16" viewBox="0 0 16 16" aria-hidden>
<path d="M8 2v12M2 8h12" stroke="currentColor" strokeWidth="2" />
</svg>
</button>
<div id={panel} className="hv-mf-more" data-open={isOpen ? "" : undefined} style={{ visibility: isOpen ? "visible" : "hidden", transition: isOpen ? "grid-template-rows 360ms cubic-bezier(.2,.8,.2,1), visibility 0s" : "grid-template-rows 360ms cubic-bezier(.2,.8,.2,1), visibility 0s linear 360ms" }}>
<div className="overflow-hidden">
<dl className="m-0 grid grid-cols-2 gap-x-6 gap-y-4 px-3 pb-6 pt-1 @3xl:grid-cols-4 @3xl:pl-[174px]">
{fl.details.map(([k, v]) => (
<div key={k}>
<dt className="text-[11px] uppercase tracking-[0.14em]" style={{ fontFamily: HV_MONO, opacity: 0.55 }}>
{k}
</dt>
<dd className="m-0 mt-1 text-[17px]" style={{ fontWeight: 600 }}>
{v}
</dd>
</div>
))}
</dl>
</div>
</div>
</li>
);
})}
</ul>
<span aria-hidden className="block h-px w-full" style={{ background: "rgba(239,235,228,.28)" }} />
</div>
</section>
);
}
Free. Pro unlocks everything, from $27/month.
Details
About this section
Schedules and changelogs are usually the dullest table on a page. This one is a launch manifest with some weight to it: big condensed mission names, mono dates and orbits, a filled orange pill for flights that are go. Point at a row and safety orange wipes across it from the left with the type turning black; click it and it opens to show the payload mass, window, pad and engines. Filter between all, upcoming and flown, and the rows deal back in. The rules between rows draw out as the scroll reaches them. Under 768px each row becomes a stacked card.
Curator’s note
Free. Each row is a real button (aria-expanded) that opens a details grid with a 0fr to 1fr row transition; the filter is a pressed-button group with live counts; the row rules draw in with the scroll. Every Hi-Vis entry is cut byte for byte from one site (Ardent, a made-up engine maker) and shares its fixed palette: safety orange #FF5B14 as a field, warm black, bone, Archivo's width axis for the shouting.
