| Criterion | Level | Conformance | Notes | Evidence |
|---|---|---|---|---|
| 1.1.1 Non-text Content | A | Partially Supports | Map markers have aria-live announcements on click. Decorative images use aria-hidden. Map tile content is inherently non-textual; Section 508 data table provides equivalent text alternative. | npm run test:smoke runs axe on the main view, the storm panel, the preparedness and evacuation panels and the poster, which fails on a missing alternative. The map tiles themselves are covered by the manual audits of 2026-09-05 and 2026-09-08, which drove the table view as the text equivalent. |
| 1.2.1 Audio/Video | A | Not Applicable | No audio or video content. | No audio or video element exists in index.html. Nothing automated is needed to keep a criterion inapplicable, but a element added later would be caught by the axe sweeps. |
| 1.3.1 Info and Relationships | A | Supports | Proper heading hierarchy (h1-h4), ARIA landmarks, role attributes on interactive elements, labeled form controls. | npm run test:aria holds JSON accessibility-tree snapshots of the storm panel and the settings menu in all three locales, so a lost landmark, role or label fails as a named property rather than a diff. |
| 1.3.2 Meaningful Sequence | A | Supports | DOM order matches visual presentation. Panels read in logical order. | Manual: the audits of 2026-09-05 and 2026-09-08 drove tab order through the map, header, filters, storm panel, statistics, this-date, compare, state, glossary, prep, evacuation, spatial search and table view. No automated check reads visual order. |
| 1.3.3 Sensory Characteristics | A | Supports | Instructions do not rely solely on shape, size, or location. | Manual: the same two audits. Nothing automated can judge whether an instruction leans on shape or position. |
| 1.4.1 Use of Color | A | Supports | Category markers use dash patterns (solid/dashed/dotted/mixed) in addition to color. Colorblind palette available. Legend shows shape+color. | npm run check:track-ramps checks every colour stop stays separable under simulated protanopia, deuteranopia and tritanopia. The dash patterns and the legend are Manual: the visual baselines blank the map for determinism, so they hold no map geometry. |
| 1.4.2 Audio Control | A | Not Applicable | No audio content. | No audio element exists in index.html. |
| 1.4.3 Contrast (Minimum) | AA | Supports | Text meets 4.5:1 ratio. High-contrast mode provides 7:1+ ratios and seeds from the operating system's prefers-contrast setting when no local choice exists. | npm run test:smoke measures the theme contrast matrix across dark, light and both high-contrast modes, and runs axe on the default theme and on emulated forced colours. npm run test:visual holds the light and high-contrast baselines. |
| 1.4.4 Resize Text | AA | Supports | Text resizes to 200% without loss of content. Responsive breakpoints at 720px, 640px, and 390px. | Manual: the 2026-09-08 audit drove 1440x960 and 390x844. No automated check resizes text. |
| 1.4.5 Images of Text | AA | Supports | No images of text used. All text is rendered as HTML. | Manual: no image of text exists in the tree; the audits confirmed it. |
| 1.4.10 Reflow | AA | Supports | Content reflows at 320px CSS width. Mobile layout tested down to 390px. | npm run test:visual holds mobile baselines at 390px including the safe-area insets, and npm run test:smoke drives the 390px panel matrix. |
| 1.4.11 Non-text Contrast | AA | Supports | Focus indicators, form borders, and interactive controls meet 3:1 contrast ratio. Comparison track lines on the map are measured against the five large-area OpenStreetMap fills, with each theme’s own tile filter applied and the line’s 0.85 opacity composited. The worst case is 3.80:1 in the dark theme and 4.57:1 in both the light theme and high contrast, in every case a dark green line over the forest fill. High contrast renders the same basemap and the same four track colours as the light theme, and that’s deliberate. Taking those lines up to the 7:1 this product holds its text to would make the mode worse, not better. Draining the colour out of the basemap changes nothing, because a greyscale conversion preserves luminance and a contrast ratio is a luminance calculation. Darkening the four track colours until they clear 7:1 puts all of them within a few percent of black, where a reader can no longer tell one storm from another. WCAG asks 3:1 of graphical objects at every conformance level and has no enhanced criterion for non-text contrast, so map geometry is held to 3:1 in every mode. npm run check:track-contrast measures all three modes on every build and fails if any of them drops below it. | npm run check:track-contrast measures the comparison track colours against every basemap fill in every mode and fails below 3:1; npm run check:track-ramps does the same for the wind, pressure and month ramps. The note in this row records the reasoning behind holding map geometry to 3:1 rather than 7:1. |
| 1.4.12 Text Spacing | AA | Supports | Content accommodates increased text spacing without loss of functionality. | Manual: the audits of 2026-09-05 and 2026-09-08. No automated check applies the text-spacing bookmarklet. |
| 1.4.13 Content on Hover/Focus | AA | Supports | Tooltips are dismissible (Escape), hoverable, and persistent while hovered. | npm run test:aria drives the header tooltips: dismissible with Escape, hoverable, persistent while hovered, and Escape still reaching an open panel underneath. |
| Criterion | Level | Conformance | Notes | Evidence |
|---|---|---|---|---|
| 2.1.1 Keyboard | A | Partially Supports | All panels, dialogs, search, settings, and table view are keyboard-operable. Map markers are not individually focusable via keyboard (Leaflet limitation); the data table provides keyboard-navigable equivalent. | npm run test:smoke drives the keyboard and focus contracts for the panel launcher and the dialogs; npm run test:aria drives the search listbox. The 3D globe is a Cesium canvas and is covered only by the manual audits, which is why this row is partial. |
| 2.1.2 No Keyboard Trap | A | Supports | Escape closes all panels. Tab moves through controls without trapping. | npm run test:smoke drives the confirmation dialog, the storm panel, the preparedness and evacuation panels to Escape and asserts focus returns to the control that opened each. The poster, the 3D globe and the settings menu are Manual. |
| 2.2.1 Timing Adjustable | A | Not Applicable | No time limits on content. | No time limit exists. The advisory replay and the track playback are reader-driven and have no deadline. |
| 2.2.2 Pause, Stop, Hide | A | Supports | Track animation has pause/stop controls. Active storm badge pulse respects prefers-reduced-motion. | npm run test:smoke drives the playback controls and the reduced-motion setting. The visual baselines disable animation outright, so they say nothing about it. |
| 2.3.1 Three Flashes | A | Supports | No content flashes more than three times per second. | No content flashes. The animations are transitions and a track playback the reader starts. |
| 2.4.1 Bypass Blocks | A | Supports | A keyboard-visible skip link moves focus directly to the map, supplemented by header, map, filter, and panel landmarks. | npm run test:smoke drives the skip link and asserts it moves focus to the main region. |
| 2.4.2 Page Titled | A | Supports | "HurricaneMap — 174 Years of U.S. Hurricane Landfalls" | npm run test:storm-pages asserts every city page names its city in its title, and pins a sampled storm and season title; the application shell title is held by the axe sweeps in npm run test:smoke. |
| 2.4.3 Focus Order | A | Supports | Focus order follows visual layout: header → filters → map → panels. | npm run test:smoke asserts focus returns to its invoker for the preparedness, evacuation, table and spatial-results panels, at desktop and mobile widths. The remaining five managed panels are Manual. |
| 2.4.4 Link Purpose | A | Supports | All links have descriptive text or aria-labels. | npm run check:links checks every external link, and the axe sweeps in npm run test:smoke fail a link with no discernible name. |
| 2.4.6 Headings and Labels | AA | Supports | Sections use descriptive headings. Form controls have associated labels. | npm run test:aria snapshots hold every heading and label in ten surfaces, including the storm panel and the settings menu, in all three locales. The header, the filter drawer and the map controls are Manual. |
| 2.4.7 Focus Visible | AA | Supports | Universal focus-visible rings on all interactive elements (lavender 3px outline). | npm run test:smoke asserts a visible focus indicator survives in all four theme combinations. |
| 2.4.11 Focus Not Obscured | AA | Supports | Panels and controls do not obscure the focused element. | Manual: the audits of 2026-09-05 and 2026-09-08 drove focus through the sticky header and the panel chrome at both widths. |
| 2.5.1 Pointer Gestures | A | Supports | All multi-point gestures (pinch zoom) have single-pointer alternatives (zoom buttons). | No gesture is required. The map pan and zoom have keyboard and button equivalents; the audits confirmed it. |
| 2.5.2 Pointer Cancellation | A | Supports | Actions activate on click (up event), not on pointer down. | Manual: the audits. No automated check distinguishes down-event from up-event activation. |
| 2.5.3 Label in Name | A | Supports | Accessible names include visible text for all labeled controls. | npm run test:aria snapshots carry each control accessible name beside its visible label, in all three locales. |
| 2.5.4 Motion Actuation | A | Not Applicable | No motion-activated functions. | No motion-actuated feature exists. Nothing reads the accelerometer or the orientation sensor, and no automated check is needed to keep it that way. |
| Criterion | Level | Conformance | Notes | Evidence |
|---|---|---|---|---|
| 3.1.1 Language of Page | A | Supports | lang attribute set on <html> element (en/es/ht based on user preference). | npm run test:aria asserts the document language matches the selected locale for each of the three. |
| 3.1.2 Language of Parts | AA | Supports | Storm names and technical terms are in English regardless of locale (proper nouns). | npm run check:untranslated and the ARIA snapshots cover the catalogue; the generated English biography is wrapped in a lang="en" element, asserted by npm run test:smoke. |
| 3.2.1 On Focus | A | Supports | No context change on focus. | Manual: the audits. Nothing focuses a control and changes context. |
| 3.2.2 On Input | A | Supports | Filter changes update the map immediately (expected behavior). Settings persist without page reload (except language). | npm run test:smoke drives every filter control and asserts the map and the counts follow each change. That no change navigates is Manual: nothing listens for a navigation. |
| 3.2.3 Consistent Navigation | AA | Supports | Header actions remain in fixed position across all states. | npm run test:visual holds the shell baselines, so a moved navigation control fails as a pixel diff. |
| 3.2.4 Consistent Identification | AA | Supports | Same icons and labels used throughout for identical functions. | npm run test:aria snapshots hold the same names for the same functions across panels and locales. |
| 3.3.1 Error Identification | A | Supports | Invalid year ranges are clamped. Loading failures show actionable error cards. | npm run test:optional-feeds drives every one of the nineteen feeds through success, empty, error, offline, rate-limited and stale, and classifies timeout and malformed responses; npm run test:optional-feed-ui and npm run test:aria cover the words the status card puts on screen. |
| 3.3.2 Labels or Instructions | A | Supports | All form controls have visible labels or aria-labels. | npm run test:aria snapshots carry every form label; axe fails an unlabelled control. |
| Criterion | Level | Conformance | Notes | Evidence |
|---|---|---|---|---|
| 4.1.2 Name, Role, Value | A | Supports | Interactive elements use semantic HTML (button, input, select) or ARIA roles (dialog, listbox, combobox, radiogroup). Settings pills use role="radio" with aria-checked. | npm run test:aria JSON snapshots are exactly name, role and value for the storm panel and the settings menu, in all three locales. |
| 4.1.3 Status Messages | AA | Supports | Filter count uses role="status" aria-live="polite". Map marker clicks announce to a live region. Active storm badge uses aria-live="polite". | npm run test:aria drives the feed status regions and asserts aria-busy appears while a region is fetching and clears on settle or failure. |
This VPAT is based on the ITI VPAT 2.5 template. Last updated 2026-08-02.