feat(03-03): status strip, state cards, recenter control, boot choreography — map-first, D-09 staged states
- strip.ts: six-state lamp+word left cluster, ±M / FIX Ns AGO telemetry (U+00B1 glyph), elapsed-seconds for acquiring - cards.ts: boot/denied/lost builders from theme classes, machine-blink gantry (500ms JS duty cycle), GEO/SYS registers from strings.ts, D-06 CTA inversion (chat-fallback=btn-primary, retry=btn-secondary) - recenter.ts: 40x40 plate w/ >=44 hit slop, orthogonal inline-SVG crosshair, three data-attribute notch states, no inline handlers - main.ts: boot order map->session->chain (bootMap before acquireFix), null-map guards on every map consumer, ARAHARAH-LIVE emitted outside the guard, data-map=absent fallback, localeFromInitData, ASK VIA CHAT w/ D-03 revalidation, 1s tick re-running the chain at 45s staleness - map.ts: attribution control moved bottom-left (UI-SPEC flush chip, compact) - shell.css: attrib chip #map-scoped override (wins cascade over maplibre css), strip left cluster, recenter z-index - vite.config.ts: define __ARAHARAH_HARNESS__ keyed on --mode harness (bare ambient global would ReferenceError in plain dev; Rule 3) - session.test.mjs: SESS1-4 memory-token gates
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* recenter.ts — the follow/recenter control (UI-SPEC §Follow / Recenter Control).
|
||||
* ---------------------------------------------------------------------------------
|
||||
* One 40x40 floating square plate, bottom-right, machined microswitch:
|
||||
* inline SVG crosshair (2px uniform stroke, square linecaps, orthogonal
|
||||
* geometry — self-hosted inline vector, no Material Symbols CDN per D-14)
|
||||
* plus the 8px center notch. Three instant notch states via data attributes
|
||||
* (colors come from shell.css; swaps are instantaneous, zero easing):
|
||||
* following + fresh → solid signal-orange notch (locked-on register)
|
||||
* free → notch removed (user panned away)
|
||||
* following + lost → soft-beige notch (unlocked, NOT red — red is
|
||||
* reserved for the strip/degraded registers)
|
||||
* Press feedback is the CSS :active invert — instant. The click handler is
|
||||
* an addEventListener wire, never an inline onclick attribute (D-01).
|
||||
*/
|
||||
import { strings, type Locale } from "../i18n/strings.ts";
|
||||
|
||||
export function createRecenterControl(
|
||||
host: HTMLElement,
|
||||
locale: Locale,
|
||||
onRecenter: () => void,
|
||||
): {
|
||||
setMode(mode: "following" | "free"): void;
|
||||
setFixState(state: "fresh" | "lost"): void;
|
||||
setLocale(locale: Locale): void;
|
||||
} {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "recenter";
|
||||
button.dataset.follow = "following";
|
||||
button.dataset.fix = "fresh";
|
||||
button.setAttribute("aria-label", strings[locale].ariaRecenter);
|
||||
myLocationGlyph(button);
|
||||
button.addEventListener("click", () => onRecenter());
|
||||
host.appendChild(button);
|
||||
return {
|
||||
setMode(mode: "following" | "free"): void {
|
||||
button.dataset.follow = mode;
|
||||
},
|
||||
setFixState(state: "fresh" | "lost"): void {
|
||||
button.dataset.fix = state;
|
||||
},
|
||||
setLocale(next: Locale): void {
|
||||
button.setAttribute("aria-label", strings[next].ariaRecenter);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** my_location silhouette redrawn as an orthogonal glyph: outer square
|
||||
* frame + four orthogonal ticks + a filled 4x4 center dot. stroke-linecap
|
||||
* square, weight 2 — uniform with the marker geometry. currentColor keeps
|
||||
* the plate/glyph pairing theme-driven via CSS alone. */
|
||||
function myLocationGlyph(button: HTMLButtonElement): void {
|
||||
const svgNs = "http://www.w3.org/2000/svg";
|
||||
const svg = document.createElementNS(svgNs, "svg");
|
||||
svg.setAttribute("width", "20");
|
||||
svg.setAttribute("height", "20");
|
||||
svg.setAttribute("viewBox", "-12 -12 24 24");
|
||||
svg.setAttribute("aria-hidden", "true");
|
||||
const attributes = (el: SVGElement, map: Record<string, string>): void => {
|
||||
for (const [k, v] of Object.entries(map)) el.setAttribute(k, v);
|
||||
};
|
||||
const frame = document.createElementNS(svgNs, "rect");
|
||||
attributes(frame, { x: "-8", y: "-8", width: "16", height: "16", fill: "none", stroke: "currentColor", "stroke-width": "2" });
|
||||
const tick = (x1: number, y1: number, x2: number, y2: number): SVGElement => {
|
||||
const line = document.createElementNS(svgNs, "line");
|
||||
attributes(line, { x1: String(x1), y1: String(y1), x2: String(x2), y2: String(y2), stroke: "currentColor", "stroke-width": "2" });
|
||||
return line;
|
||||
};
|
||||
const dot = document.createElementNS(svgNs, "rect");
|
||||
attributes(dot, { x: "-2", y: "-2", width: "4", height: "4", fill: "currentColor" });
|
||||
svg.append(frame, tick(0, -12, 0, -8), tick(0, 8, 0, 12), tick(-12, 0, -8, 0), tick(8, 0, 12, 0), dot);
|
||||
const notch = document.createElement("span");
|
||||
notch.className = "notch";
|
||||
button.append(svg, notch);
|
||||
}
|
||||
Reference in New Issue
Block a user