feat(06-05): guidance/confirm/trip-end card builders + the token-only shell.css guidance block (GREEN)
- buildGuidanceCard: GUIDE register, verbatim headline, live telemetry row (MIN / {km} KM / ETA), 4px progressbar with full aria value set + instant inline width, no-reroute state line only when degrading, ONE btn-secondary END TRIP
- buildConfirmCard: GUIDE // CONFIRM + exact en/ms privacy copy, btn-primary YES ABANDON + btn-secondary CONTINUE, guide-confirm bottom-register hook — the ONE primary CTA in guidance chrome
- buildTripEndCard: GUIDE // END, exact deletion copy, no actions
- shell.css guidance block: banner chassis + 1px separator, CSS-only strip compaction under [data-guidance], list panel + row states + lamp, progress bar — token vars only, zero motion/depth/radius/hex (theme audit clean)
This commit is contained in:
@@ -276,3 +276,181 @@ function buildManeuverRow(row: ManeuverRowView): HTMLElement {
|
||||
el.append(glyphWrap, street, dist);
|
||||
return el;
|
||||
}
|
||||
|
||||
/* ————— the guidance card (D-06 — the route card evolved, bottom register) ————— */
|
||||
|
||||
export interface GuidanceCardView {
|
||||
/** Verbatim destination name — the continuity anchor. */
|
||||
destinationName: string;
|
||||
/** Deadband-smoothed minutes remaining (D-07 — the controller passes the
|
||||
* display values; this builder is a pure DOM composition). */
|
||||
minutesRemaining: number;
|
||||
/** Remaining kilometres (model-derived, D-07). */
|
||||
kmRemaining: number;
|
||||
/** The 24-hour HH:MM arrival clock — precomposed by the eta module. */
|
||||
etaClock: string;
|
||||
/** Snapped progress 0..100 — the bar STEPS per accepted fix, never glides. */
|
||||
progressPct: number;
|
||||
/** The no-reroute degradation is the ONLY state that adds a state line. */
|
||||
showNoReroute: boolean;
|
||||
}
|
||||
|
||||
export interface GuidanceCardActions {
|
||||
/** END TRIP → the abandon confirm card (the D-09 surface every exit reaches). */
|
||||
onEndTrip(): void;
|
||||
}
|
||||
|
||||
/** The route card's slots evolved in place: same card class, GUIDE register,
|
||||
* live telemetry row (minutes + MIN + {km} KM + ETA), the 4px progress bar,
|
||||
* the honest state line only in no-reroute, and ONE .btn-secondary —
|
||||
* NO .btn-primary here: the drive's forward action is driving. */
|
||||
export function buildGuidanceCard(
|
||||
locale: Locale,
|
||||
view: GuidanceCardView,
|
||||
actions: GuidanceCardActions,
|
||||
): HTMLElement {
|
||||
const loc = strings[locale];
|
||||
const card = document.createElement("div");
|
||||
card.className = "card";
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "card-header";
|
||||
const ref = document.createElement("span");
|
||||
ref.className = "card-ref";
|
||||
ref.textContent = loc.guideRegister;
|
||||
header.appendChild(ref);
|
||||
|
||||
const headline = document.createElement("h2");
|
||||
headline.className = "text-headline-md";
|
||||
headline.textContent = view.destinationName; // verbatim gazetteer name — textContent only
|
||||
|
||||
const telemetry = document.createElement("div");
|
||||
telemetry.className = "route-telemetry"; // the route card's row class, evolved
|
||||
const minutes = document.createElement("span");
|
||||
minutes.className = "text-telemetry-num";
|
||||
minutes.textContent = String(Math.round(view.minutesRemaining));
|
||||
const minUnit = document.createElement("span");
|
||||
minUnit.className = "text-label-sm";
|
||||
minUnit.textContent = "MIN"; // instrument constant, both locales
|
||||
const km = document.createElement("span");
|
||||
km.className = "text-label-sm";
|
||||
km.textContent = `${view.kmRemaining.toFixed(1)} KM`;
|
||||
const eta = document.createElement("span");
|
||||
eta.className = "text-label-sm";
|
||||
eta.textContent = `ETA ${view.etaClock}`;
|
||||
telemetry.append(minutes, minUnit, km, eta);
|
||||
|
||||
const progress = document.createElement("div");
|
||||
progress.className = "guide-progress";
|
||||
progress.setAttribute("role", "progressbar");
|
||||
progress.setAttribute("aria-valuemin", "0");
|
||||
progress.setAttribute("aria-valuemax", "100");
|
||||
progress.setAttribute("aria-valuenow", String(Math.round(view.progressPct)));
|
||||
progress.setAttribute("aria-label", loc.ariaTripProgress);
|
||||
const fill = document.createElement("div");
|
||||
fill.className = "guide-progress-fill";
|
||||
fill.style.width = `${view.progressPct}%`; // instant width set, never a transition
|
||||
progress.appendChild(fill);
|
||||
|
||||
card.append(header, headline, telemetry, progress);
|
||||
|
||||
if (view.showNoReroute) {
|
||||
const stateLine = document.createElement("p");
|
||||
stateLine.className = "text-body-sm guide-state"; // standard ink — no red, a deferral not an error
|
||||
stateLine.textContent = loc.guideNoRerouteBody;
|
||||
card.appendChild(stateLine);
|
||||
}
|
||||
|
||||
const actionsRow = document.createElement("div");
|
||||
actionsRow.className = "card-actions";
|
||||
actionsRow.appendChild(secondaryButton(locale, loc.ctaEndTrip, actions.onEndTrip));
|
||||
card.appendChild(actionsRow);
|
||||
return card;
|
||||
}
|
||||
|
||||
/* ————— the abandon-route confirm card (D-09/PX-03 — the ONE guard) ————— */
|
||||
|
||||
export interface ConfirmCardActions {
|
||||
/** YES, ABANDON → guidance teardown: chrome clears, envelope DELETE, watch stops. */
|
||||
onAbandon(): void;
|
||||
/** CONTINUE → the card dismisses, guidance continues (no dead ends). */
|
||||
onContinue(): void;
|
||||
}
|
||||
|
||||
/** One confirm surface for ALL guidance exits (END TRIP, BackButton, the
|
||||
* native-close net's return path). Bottom register (thumb-reach — the
|
||||
* mount reads the guide-confirm class). YES, ABANDON is .btn-primary —
|
||||
* the working forward path, deliberately NOT red (abandoning is a
|
||||
* deliberate act, not an error; orange = primary action semantics). */
|
||||
export function buildConfirmCard(locale: Locale, actions: ConfirmCardActions): HTMLElement {
|
||||
const loc = strings[locale];
|
||||
const card = document.createElement("div");
|
||||
card.className = "card guide-confirm"; // guide-confirm: the bottom-register placement hook
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "card-header";
|
||||
const ref = document.createElement("span");
|
||||
ref.className = "card-ref";
|
||||
ref.textContent = `${loc.guideRegister} // CONFIRM`; // instrument vocabulary, en both locales
|
||||
header.appendChild(ref);
|
||||
|
||||
const headline = document.createElement("h2");
|
||||
headline.className = "text-headline-md";
|
||||
headline.textContent = loc.guideConfirmHeadline;
|
||||
|
||||
const body = document.createElement("p");
|
||||
body.className = "text-body-sm";
|
||||
body.textContent = loc.guideConfirmBody;
|
||||
|
||||
const actionsRow = document.createElement("div");
|
||||
actionsRow.className = "card-actions";
|
||||
const abandon = document.createElement("button");
|
||||
abandon.type = "button";
|
||||
abandon.className = "btn-primary"; // the ONE primary CTA in all guidance chrome
|
||||
abandon.textContent = loc.ctaConfirmAbandon;
|
||||
abandon.addEventListener("click", actions.onAbandon);
|
||||
const keepGoing = secondaryButton(locale, loc.ctaConfirmContinue, actions.onContinue);
|
||||
actionsRow.append(abandon, keepGoing);
|
||||
|
||||
card.append(header, headline, body, actionsRow);
|
||||
return card;
|
||||
}
|
||||
|
||||
/* ————— the trip-ended card (D-14 — PRIV-01's in-app half, visible) ————— */
|
||||
|
||||
/** ONE card for arrival and abandon — both delete, one message. No actions:
|
||||
* the card persists until the next action (no silent auto-dismiss — the
|
||||
* honesty law); the BackButton walks to the search root. */
|
||||
export function buildTripEndCard(locale: Locale): HTMLElement {
|
||||
const loc = strings[locale];
|
||||
const card = document.createElement("div");
|
||||
card.className = "card";
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "card-header";
|
||||
const ref = document.createElement("span");
|
||||
ref.className = "card-ref";
|
||||
ref.textContent = `${loc.guideRegister} // END`; // instrument vocabulary, en both locales
|
||||
header.appendChild(ref);
|
||||
|
||||
const headline = document.createElement("h2");
|
||||
headline.className = "text-headline-md";
|
||||
headline.textContent = loc.guideEndHeadline;
|
||||
|
||||
const body = document.createElement("p");
|
||||
body.className = "text-body-sm";
|
||||
body.textContent = loc.guideEndBody;
|
||||
|
||||
card.append(header, headline, body);
|
||||
return card;
|
||||
}
|
||||
|
||||
/** The one secondary-button shape (btn-secondary — never primary here). */
|
||||
function secondaryButton(locale: Locale, label: string, onActivate: () => void): HTMLButtonElement {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "btn-secondary";
|
||||
button.textContent = label;
|
||||
button.addEventListener("click", onActivate);
|
||||
return button;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user