Manual: full participation guide (media/accounts/decisions/safety) + Enter-to-door + provisioner env passthrough

This commit is contained in:
glass beadian 2026-10-12 15:49:25 +00:00
parent 07d2df8c53
commit 6161ac4740
5 changed files with 251 additions and 42 deletions

View file

@ -7,6 +7,7 @@ import {
MANUAL_INTRO,
MANUAL_PATHS_HEADING,
MANUAL_TITLE,
MANUAL_TOC_HEADING,
PARTICIPATION_MANUAL,
PARTICIPATION_PATHS,
isManualLanguage,
@ -83,6 +84,17 @@ function ManualLanding(): React.ReactElement {
useEffect(() => setLang(readStoredLanguage()), []);
// Enter jumps to the door only when body has focus; interactive elements keep their own Enter.
useEffect(() => {
const onKey = (e: KeyboardEvent): void => {
if (e.key === "Enter" && e.target === document.body && !e.shiftKey) {
router.push(status === "authenticated" ? "/streams" : "/onboarding");
}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [status, router]);
function switchLanguage(next: ManualLanguage): void {
setLang(next);
try {
@ -101,7 +113,7 @@ function ManualLanding(): React.ReactElement {
return (
<div className="min-h-dvh bg-bg text-text">
<header className="border-b border-border-subtle bg-surface">
<header className="sticky top-0 z-10 border-b border-border-subtle bg-surface">
<div className="mx-auto flex max-w-2xl items-center gap-3 px-4 py-4">
<Logo size={26} />
<span className="flex-1 truncate text-xs font-bold tracking-tight text-accent">Tactical Media Broadcast Hub</span>
@ -163,10 +175,31 @@ function ManualLanding(): React.ReactElement {
</ul>
</section>
{/* Compact table of contents, one line per section, anchor links. */}
<nav aria-labelledby="toc-heading" className="mb-10 rounded-lg border border-border-subtle bg-surface p-4">
<h2 id="toc-heading" className="mb-2 text-xs font-bold uppercase tracking-wider text-text-faint">
{t(MANUAL_TOC_HEADING)}
</h2>
<ol className="grid gap-1.5">
{PARTICIPATION_MANUAL.map((section, index) => (
<li key={section.id} className="flex items-baseline gap-2.5 text-sm">
<span className="font-mono text-xs font-bold text-text-faint">{String(index + 1).padStart(2, "0")}</span>
<a
href={`#section-${section.id}`}
className="text-accent underline-offset-2 hover:underline"
onClick={jumpToSection}
>
{t(section.heading)}
</a>
</li>
))}
</ol>
</nav>
{/* The manual itself. */}
<div className="flex flex-col gap-8">
{PARTICIPATION_MANUAL.map((section, index) => (
<section key={section.id} aria-labelledby={`section-${section.id}`}>
<section key={section.id} aria-labelledby={`section-${section.id}`} className="scroll-mt-20">
<h2 id={`section-${section.id}`} className="mb-2 flex items-baseline gap-2.5 text-base font-bold">
<span className="font-mono text-xs font-bold text-accent">{String(index + 1).padStart(2, "0")}</span>
{t(section.heading)}
@ -188,3 +221,19 @@ function ManualLanding(): React.ReactElement {
</div>
);
}
/**
* Smooth-scroll to the target section. The anchor itself works without
* JavaScript; this only makes the jump gentle and keeps the sticky header from
* covering the heading (paired with scroll-mt-20 above).
*/
function jumpToSection(event: React.MouseEvent<HTMLAnchorElement>): void {
const href = event.currentTarget.getAttribute("href");
if (href === null || !href.startsWith("#")) return;
const target = document.getElementById(href.slice(1));
if (target === null) return;
event.preventDefault();
target.scrollIntoView({ behavior: "smooth", block: "start" });
// Reflect the jump in the URL so the link is shareable.
window.history.replaceState(null, "", href);
}