emergency: 911 demoted to very last resort — page now leads with event-help share card; emergency-services number moved to collapsed 'If none of the above is working' disclosure at the very bottom, no alert styling
Some checks failed
ci / check (push) Has been cancelled

This commit is contained in:
Lumen Stage1 2026-10-06 13:03:13 -05:00
commit 1c49b8a4a8
3 changed files with 85 additions and 17 deletions

View file

@ -661,16 +661,35 @@ html.dark .app-main__icon {
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 600; font-weight: 600;
} }
.emergency-fallback { .emergency-share-card {
background: var(--emergency); border: 1px solid var(--border);
border: 1px solid var(--emergency);
border-radius: var(--radius); border-radius: var(--radius);
color: var(--emergency-contrast);
padding: 1rem; padding: 1rem;
} }
.emergency-fallback h2 { .emergency-share-card h2 {
margin-top: 0; margin-top: 0;
} }
.emergency-last-resort {
border-top: 1px solid var(--border);
margin-top: 1.5rem;
padding-top: 0.75rem;
}
.emergency-last-resort summary {
color: var(--muted);
cursor: pointer;
font-size: 0.9rem;
}
.emergency-last-resort p {
color: var(--muted);
}
.emergency-call-last {
border: 1px solid var(--border);
border-radius: 999px;
color: var(--foreground);
display: inline-block;
padding: 0.4rem 1rem;
text-decoration: none;
}
.emergency-service p { .emergency-service p {
margin: 0; margin: 0;
} }

View file

@ -129,13 +129,18 @@ export function createEmergencyView(
const address = detail?.address ?? resolved.floor.address; const address = detail?.address ?? resolved.floor.address;
const procedures = detail?.procedures?.length ? detail.procedures : resolved.floor.procedures; const procedures = detail?.procedures?.length ? detail.procedures : resolved.floor.procedures;
const urgent = document.createElement("div"); // Share card at top: the primary self-help action. The emergency-services
urgent.className = "emergency-fallback"; // number is deliberately NOT here — per product rule it is the very last
urgent.append(heading("emergency-call-heading", "Can't reach event help?")); // resort and lives behind an explicit disclosure at the page bottom.
const shareCard = document.createElement("div");
// Share first: the practical action (send your location + numbers to your shareCard.className = "emergency-share-card";
// group) belongs above the emergency-services number, which the procedures shareCard.append(heading("emergency-share-heading", "Start with event help"));
// now frame as the fallback. shareCard.append(
text(
"p",
"Event staff, security, and first aid are your first responders here. Share your situation and location with them or your group.",
),
);
const shareBtn = document.createElement("button"); const shareBtn = document.createElement("button");
shareBtn.className = "emergency-share-btn"; shareBtn.className = "emergency-share-btn";
shareBtn.textContent = "Share emergency info"; shareBtn.textContent = "Share emergency info";
@ -153,11 +158,8 @@ export function createEmergencyView(
alert("Copied to clipboard: " + textToShare.slice(0, 80) + "..."); alert("Copied to clipboard: " + textToShare.slice(0, 80) + "...");
} }
}; };
urgent.append(shareBtn); shareCard.append(shareBtn);
section.append(shareCard);
addService(urgent, "Emergency services", services.emergencyNumber, true);
urgent.append(text("p", "Call only when event staff and first aid can't respond. Tap to call."));
section.append(urgent);
const contacts = document.createElement("section"); const contacts = document.createElement("section");
contacts.setAttribute("aria-labelledby", "emergency-services-heading"); contacts.setAttribute("aria-labelledby", "emergency-services-heading");
@ -241,5 +243,31 @@ export function createEmergencyView(
section.append(notices); section.append(notices);
} }
// Very last resort: the emergency-services number sits at the BOTTOM of
// the page behind an explicit disclosure, deliberately not styled as an
// alert. The dial link stays in the DOM for accessibility; it just is not
// presented until asked for.
const lastResort = document.createElement("details");
lastResort.className = "emergency-last-resort";
const summary = document.createElement("summary");
summary.textContent = "If none of the above is working";
lastResort.append(summary);
lastResort.append(
text(
"p",
`Only when event staff, security, and first aid are unreachable and someone is in immediate danger: call ${services.emergencyNumber}.`,
),
);
const dialLink = document.createElement("a");
dialLink.className = "emergency-call-last";
dialLink.href = `tel:${services.emergencyNumber}`;
dialLink.textContent = `Call ${services.emergencyNumber}`;
dialLink.setAttribute(
"aria-label",
`As a last resort, call emergency services at ${services.emergencyNumber}`,
);
lastResort.append(dialLink);
section.append(lastResort);
return section; return section;
} }

View file

@ -189,6 +189,27 @@ describe("emergency view", () => {
expect(view.textContent).toContain("isn't on this device yet"); expect(view.textContent).toContain("isn't on this device yet");
}); });
it("emergency number is last-resort: bottom of page, collapsed, after procedures", () => {
const view = createEmergencyView(undefined, NOW);
const last = view.querySelector(".emergency-last-resort");
expect(last).toBeTruthy();
// collapsed by default — the number is not presented until asked
expect(last?.hasAttribute("open")).toBe(false);
// it is the very last element of the view
expect(view.lastElementChild).toBe(last);
// ...it comes after the procedures section...
const idx = (selector: string): number => {
const el = view.querySelector(selector);
return el ? [...view.children].indexOf(el) : -1;
};
expect(idx(".emergency-last-resort")).toBeGreaterThan(
idx('[aria-labelledby="emergency-procedures-heading"]'),
);
// ...and the page leads with event help, not the dial number
expect(idx(".emergency-share-card")).toBeGreaterThanOrEqual(1);
expect(view.querySelector(".emergency-share-card")?.textContent).toContain("event help");
});
it("dataset tier renders locations with map handoffs and notices", () => { it("dataset tier renders locations with map handoffs and notices", () => {
const view = createEmergencyView(dataset(), NOW); const view = createEmergencyView(dataset(), NOW);
expect(view.textContent).toContain("FESTIVAL DATA v3"); expect(view.textContent).toContain("FESTIVAL DATA v3");