diff --git a/src/pages/support.astro b/src/pages/support.astro index c8c428b..a5c0590 100644 --- a/src/pages/support.astro +++ b/src/pages/support.astro @@ -4,7 +4,7 @@ import Callout from "../components/Callout.astro"; import SearchPromo from "../components/SearchPromo.astro"; import ServiceIcon from "../components/ServiceIcon.astro"; import Chip from "../components/Chip.astro"; -import { Search, ShieldCheck } from "@lucide/astro"; +import { Search, ShieldCheck, X } from "@lucide/astro"; import { getServiceSupport } from "../lib/generated-content"; const support = await getServiceSupport(); @@ -32,6 +32,12 @@ const services = support.services const completeCount = services.filter((service) => service.gap === 0).length; const totalGap = services.reduce((sum, service) => sum + service.gap, 0); + +const showOptions: Array<{ value: "all" | "gaps" | "complete"; label: string; count: number }> = [ + { value: "all", label: "All", count: services.length }, + { value: "gaps", label: "Has gaps", count: services.length - completeCount }, + { value: "complete", label: "Complete", count: completeCount }, +]; --- @@ -77,43 +83,76 @@ const totalGap = services.reduce((sum, service) => sum + service.gap, 0); -
+
{/* A placeholder is not a label: it disappears the moment anything is typed and is not what a screen reader reads for the field. The visible design has no room for a caption, so the name is carried off-screen. */} - + -
- + + +
+
+ {/* Toggle buttons rather than radios: each is one press, and the counts on them + follow the text filter, so they say what pressing one would leave. */} +
+ {showOptions.map((option) => ( + + ))} +
+ +
{/* The count labels the table, so it keeps a `group` gap to it and takes the full `block` gap from the filters above — the three used to stack at 12px - each and read as one undifferentiated pile. */} -

- Showing all {services.length} services -

+ each and read as one undifferentiated pile. The reset sits beside it, outside + the live region, so it is not read out on every keystroke. */} +
+

+ Showing all {services.length} services +

+ +
{/* The column strip is a visual key for sighted readers only: nothing associates @@ -133,44 +172,66 @@ const totalGap = services.reduce((sum, service) => sum + service.gap, 0); {/* One line per service at every width. The meter is the only thing that drops on a phone: keeping it stacked each row onto two lines, which made a 50-row table half as long again as the one it replaced. The counts beside it carry - the same fact exactly. */} - {services.map((service) => ( - ))}
- +
@@ -185,14 +246,96 @@ const totalGap = services.reduce((sum, service) => sum + service.gap, 0); diff --git a/src/pages/support/operations.json.ts b/src/pages/support/operations.json.ts new file mode 100644 index 0000000..d119e8b --- /dev/null +++ b/src/pages/support/operations.json.ts @@ -0,0 +1,20 @@ +import type { APIRoute } from "astro"; +import { getServiceSupport } from "../../lib/generated-content"; + +// https://overcast.sh/support/operations.json — every listed operation and its status, +// keyed by service id. The support matrix filter fetches it the first time someone goes +// to use the filter, so the ~1,500 names are not part of every visit to the page. +type Operation = { operation: string; status: string }; + +export const GET: APIRoute = async () => { + const support = await getServiceSupport(); + const index = Object.fromEntries( + support.services.map((service) => [ + service.service, + (service.operations as Operation[]).map((op) => [op.operation, op.status]), + ]), + ); + return new Response(JSON.stringify(index), { + headers: { "Content-Type": "application/json; charset=utf-8" }, + }); +};