@kahwee/sf-map-svg 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,16 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ User-visible changes are recorded here. Unreleased entries describe changes on `main` that are not part of a tagged package release.
4
+
5
+ ## Unreleased
6
+
7
+ - Make the measures atlas a full-screen map with floating controls, expandable mobile results, touch pan/zoom, overlay tables and sources, and a distraction-free focus view.
8
+
9
+ - Redesign the measures explorer around visible district maps, side-by-side comparisons, Yes/No and district-color modes, keyboard zoom/pan, district fitting, stable labels, and SVG/CSV exports.
10
+
11
+ - Add a Pages ballot-measures explorer for certified June 2026 local results, with district comparisons, shareable selections, and official data downloads.
12
+
13
+ - Build public Pages demos and SVG downloads from the published npm version, with visible release metadata, installation copying, release links, and social previews.
14
+ - Refresh Pages after successful publishing and improve mobile layout and release documentation.
15
+
16
+ ## 1.2.0
17
+
18
+ ### Added
19
+
20
+ - Add `@kahwee/sf-map-svg/custom-map`, a data-injected renderer entry point that lets consumers bundle only the geographic datasets they provide.
21
+ - Add public styled geographic overlays, explorer theme tokens, configurable labels, and optional controls.
22
+
23
+ ## 1.1.0
24
+
25
+ ### Added
26
+
27
+ - Public [GitHub Pages explorer](https://kahwee.github.io/sf-map-svg/) with neighborhood search, map modes, usage tips, SVG examples, and GeoJSON downloads.
28
+ - Automatic Pages builds and deployment from `main`, plus `pnpm build:pages` for local previews.
29
+ - Optional `@kahwee/sf-map-svg/transit` browser component and an [embeddable transit demo](https://kahwee.github.io/sf-map-svg/transit.html). The schematic BART journey connects official station locations with straight segments; it does not represent actual tracks or live service.
30
+ - Play/pause controls, a keyboard-accessible journey slider, pause-on-hidden behavior, and a Storybook transit example. Animation starts paused.
31
+
32
+ ### Changed
33
+
34
+ - Reorganize the README around installation, choosing an API, static and interactive options, geographic data, development, and Pages deployment.
35
+
36
+ ### Fixed
37
+
38
+ - Release map listeners, observers, animation frames, and download URLs when explorer initialization fails. Add browser regression coverage for failed initialization.
39
+
3
40
  ## 1.0.0
4
41
 
5
42
  - Publish the stable 1.0 API with public npm access and a public source repository.
6
43
  - Add the reusable interactive map entrypoint, controlled viewport and selection APIs, keyboard/touch navigation, and marker selection.
7
44
  - Add interactive examples, Storybook stories, and viewport/navigation regression checks.
8
-
9
45
  - Add district/neighborhood explorer modes and a labels toggle, with fixed screen-size labels while zooming.
10
46
  - Add a master visible-label switch for standalone SVGs.
11
-
12
47
  - Add nine optional key-road landmarks from DataSF centerlines, public JSON, and zoom-aware explorer labels.
13
-
14
48
  - Add a transit-inspired map theme with pale water, quiet land, green parks, and blue station symbols.
15
49
  - Refine the neighborhood explorer presentation and map legend.
16
50
  - Add a transit example and Storybook preset.
@@ -29,11 +63,8 @@
29
63
  ## 0.3.0
30
64
 
31
65
  - Enable public npm distribution with explicit public registry access.
32
-
33
66
  - Remove realtor boundary sliver overlaps while preserving all 92 neighborhoods and their combined footprint; enforce disjoint interiors in regression tests.
34
-
35
67
  - Use the 92 SFAR realtor neighborhoods by default for map outlines, data lookup, and Storybook. Other source collections remain available.
36
-
37
68
  - Move all geographic data to exported canonical GeoJSON files, preserving district display extras.
38
69
  - Expose complete SF Find (117), analysis (41), and realtor (92) neighborhood collections with source-specific canonical names and documented aliases.
39
70
  - Add immutable data helpers, exact-name lookup, source-aware search, and a typed geometry export.
package/README.md CHANGED
@@ -1,143 +1,90 @@
1
1
  # San Francisco SVG maps
2
2
 
3
- ![District fills, optional neighborhood boundaries, and plain outlines](docs/map-preview.png)
3
+ [![npm version](https://img.shields.io/npm/v/@kahwee/sf-map-svg)](https://www.npmjs.com/package/@kahwee/sf-map-svg)
4
4
 
5
- An MIT-licensed package extracted from KahWee’s **San Francisco District Map** Site. Draws a self-contained SVG with bundled geometry and no runtime dependencies, tiles, WebGL, or network requests.
5
+ [Explore the live map](https://kahwee.github.io/sf-map-svg/) · [Data guide](data/README.md) · [Geographic sources](SOURCES.md) · [Contributing](CONTRIBUTING.md)
6
6
 
7
- ```js
8
- import { renderSFMap, createSFMap } from '@kahwee/sf-map-svg';
9
-
10
- const svg = renderSFMap({
11
- year: 2022,
12
- districtLines: true,
13
- landmarks: true, // parks with labels
14
- bartStations: true, // all eight SF stations
15
- highways: true,
16
- neighborhoodLines: true, // optional; off by default
17
- markers: [{ id: 'dolores', lng: -122.4269, lat: 37.7596, label: 'Dolores Park' }],
18
- });
19
- ```
20
-
21
- Write `svg` to a `.svg` file or embed it in your page. For Astro, render it with `<div set:html={svg} />`. Text and attribute values are XML escaped.
22
-
23
- ## Layers and options
24
-
25
- | Option | Default | Purpose |
26
- | ------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
27
- | `year` | `2022` | District boundaries: `2002`, `2012`, or `2022` |
28
- | `districtLines` | `true` | Supervisorial district outlines |
29
- | `neighborhoodLines` | `false` | Dashed SFAR realtor neighborhood outlines |
30
- | `theme` | `'districts'` | Use `'transit'` for pale blue water, ivory land, green parks, and blue BART symbols; custom `colors` still take precedence |
31
- | `districtFills` | `true` | Original Site’s eleven muted district colors |
32
- | `labels` | `true` | Master switch for visible map text; symbols and accessible titles remain |
33
- | `districtLabels` | `true` | District number badges |
34
- | `highways` | `false` | Original Site’s highway geometry |
35
- | `landmarks` | `false` | Golden Gate Park, Presidio, Lincoln Park, Twin Peaks, Dolores Park, and McLaren Park |
36
- | `bartStations` | `false` | Eight San Francisco BART stations with blue rings and names |
37
- | `width`, `height` | `800`, `800` | SVG viewBox and intrinsic size |
38
- | `padding` | `28` | Space around the coast |
39
- | `markers` | `[]` | Points with `id`, `lng`, `lat`, optional `label`, `color`, `selected` |
40
- | `keyRoads` | `false` | Nine selected road corridors and names for orientation |
41
- | `colors` | Built-in palette | Override `water`, `land`, `district`, `neighborhood`, `highway`, `road`, `park`, `landmark`, `bart`, `label`, `marker`, `selected` |
42
- | `title` | `San Francisco map` | Accessible SVG title |
43
- | `idPrefix` | Unique per process | Set explicitly for deterministic output or independent server renders |
44
-
45
- For a plain outline map, set `districtFills: false`. Neighborhood areas are **August 2010 SFAR realtor areas**, not legal boundaries or a historical layer matched to the district year. See [SOURCES.md](SOURCES.md).
7
+ Self-contained SVG maps of San Francisco, with precise coastlines, soft district colors, parks, roads, BART stations, and searchable neighborhoods. Render static SVGs in Node or add an interactive map to a browser. All geometry is bundled; there are no runtime dependencies, map tiles, API keys, or external data requests.
46
8
 
47
- `createSFMap(options)` returns `{ svg, project, viewBox }`. `project([longitude, latitude])` gives matching SVG coordinates for custom overlays. Named exports also include `districtYears`, `districtColors`, and `neighborhoodNames`.
9
+ ![San Francisco district maps with optional neighborhood boundaries](docs/map-preview.png)
48
10
 
49
- ## Installation
11
+ ## Install
50
12
 
51
13
  ```sh
52
14
  pnpm add @kahwee/sf-map-svg
53
15
  ```
54
16
 
55
- The package is published publicly on npm. Geographic JSON files are included in the package. See `LICENSE` and `SOURCES.md` for software and source-data rights.
56
-
57
- ## Development
58
-
59
- ```sh
60
- pnpm install --frozen-lockfile
61
- pnpm test
62
- pnpm demo
63
- ```
64
-
65
- Open `examples/generated/index.html` to compare district and neighborhood maps. Generated SVG files are there too. See [CONTRIBUTING.md](CONTRIBUTING.md) for source structure, checks and release steps. GitHub Actions runs validation; npm releases use public access.
66
-
67
- The package uses a small Mercator SVG renderer while retaining the Site’s boundary geometry, coastline, palette, district labels, and highway data.
68
-
69
- The `theme: 'transit'` preset borrows the clear visual hierarchy of [BART’s system map](https://www.bart.gov/system-map), retaining geographic positions. It uses a quiet, single-color land fill instead of district colors. The neighborhood explorer uses this preset.
17
+ Use Node 22.12+ for server-side rendering. Browser components need a DOM and a bundler that supports the package’s JSON imports.
70
18
 
71
- Enable `landmarks`, `bartStations`, and `highways` together for the featured example. Park fills use `colors.park`, park labels use `colors.landmark`, and station rings and labels use `colors.bart`. These current geographic overlays are independent of the district year; BART stations are city-only (Daly City is outside the map). Station positions are geographic points, not a route diagram.
72
-
73
- ## Examples
19
+ | Start with | Entry point | What you get |
20
+ | --- | --- | --- |
21
+ | Static SVG | `@kahwee/sf-map-svg` | SVG markup, projection helpers, optional layers |
22
+ | Data-injected SVG | `@kahwee/sf-map-svg/custom-map` | Tree-shakeable renderer core with only the geographic data you provide |
23
+ | Neighborhood explorer | `@kahwee/sf-map-svg/explorer` | Search, source selection, map controls, GeoJSON downloads |
24
+ | Interactive map | `@kahwee/sf-map-svg/interactive` | Embeddable map and controls without the explorer sidebar |
25
+ | Animated transit demo | `@kahwee/sf-map-svg/transit` | Optional, schematic BART journey with playback controls |
26
+ | Geographic data | `@kahwee/sf-map-svg/data` | Source-aware lookup and canonical GeoJSON |
74
27
 
75
- ### Landmarks, BART stations, and highways
28
+ ## Render a static map
76
29
 
77
30
  ```js
78
- import { renderSFMap } from '@kahwee/sf-map-svg';
79
31
  import { writeFile } from 'node:fs/promises';
32
+ import { renderSFMap } from '@kahwee/sf-map-svg';
80
33
 
81
- await writeFile(
82
- 'san-francisco.svg',
83
- renderSFMap({
84
- landmarks: true,
85
- bartStations: true,
86
- highways: true,
87
- }),
88
- );
89
- ```
90
-
91
- ### Plain map with a selected place
92
-
93
- ```js
94
- const svg = renderSFMap({
95
- districtFills: false,
96
- districtLabels: false,
97
- landmarks: true,
98
- markers: [{ id: 'dolores', lng: -122.4269, lat: 37.7596, label: 'Dolores Park', selected: true }],
99
- });
100
- ```
101
-
102
- ### Match a site's colors
103
-
104
- ```js
105
34
  const svg = renderSFMap({
106
35
  landmarks: true,
107
36
  bartStations: true,
108
- colors: { park: '#c4d4b1', landmark: '#3e6346', bart: '#795285' },
37
+ highways: true,
38
+ neighborhoodLines: true,
39
+ markers: [{ id: 'dolores', lng: -122.4269, lat: 37.7596, label: 'Dolores Park' }],
109
40
  });
41
+
42
+ await writeFile('san-francisco.svg', svg);
110
43
  ```
111
44
 
112
- ## Storybook
45
+ For smaller browser bundles, import `createSFMapWithData` from
46
+ `@kahwee/sf-map-svg/custom-map` and pass only the geographic assets your map uses.
47
+ That entry point does not import the package's built-in JSON collections. Its `SFMapData`
48
+ requires the coast and accepts selected district vintages plus optional neighborhood,
49
+ road, park, and station arrays. Use the canonical JSON subpaths documented in
50
+ [`data/README.md`](data/README.md) as source; map each feature collection to the
51
+ corresponding `SFMapData` records. The root entry remains convenient and includes the
52
+ built-in datasets for backward compatibility.
113
53
 
114
- ```sh
115
- pnpm storybook # http://127.0.0.1:6006
116
- pnpm build-storybook # static output in storybook-static/
117
- ```
54
+ Embed the returned SVG markup directly in a page; in Astro, use `<div set:html={svg} />`. User-supplied text and attributes are XML escaped. Use a distinct `idPrefix` for each map when combining independently rendered SVGs.
118
55
 
119
- Map stories cover the default map, combined and independent landmark/BART layers, neighborhoods, outlines, historical district years, custom markers, a custom palette, and a narrow map. The Data / Neighborhood explorer adds examples for comparing Mission, Outer Mission, SoMa, and NoPa across source definitions. Controls edit map options live; the Docs tab shows usage examples. Storybook and Vite are development dependencies only and are excluded from the package archive. Development requires Node 22.12+ and pnpm 12. Only esbuild's dependency build script is enabled in `pnpm-workspace.yaml`.
56
+ ## Static options
120
57
 
121
- Dependabot checks npm dependencies and GitHub Actions weekly, grouping Storybook updates. CI validates formatting, SVG tests, generated examples, Storybook builds, and package creation on Node 22 and 26. Dependency PRs require review; updates are not merged automatically.
58
+ | Option | Default | Purpose |
59
+ | --- | --- | --- |
60
+ | `year` | `2022` | District boundaries: `2002`, `2012`, or `2022` |
61
+ | `districtLines` | `true` | Supervisorial district outlines |
62
+ | `neighborhoodLines` | `false` | Dashed SFAR realtor neighborhood outlines |
63
+ | `theme` | `'districts'` | Use `'transit'` for pale blue water, ivory land, green parks, and blue BART symbols; custom `colors` still take precedence |
64
+ | `districtFills` | `true` | Original Site’s eleven muted district colors |
65
+ | `labels` | `true` | Master switch for visible map text; symbols and accessible titles remain |
66
+ | `districtLabels` | `true` | District number badges |
67
+ | `highways` | `false` | Original Site’s highway geometry |
68
+ | `landmarks` | `false` | Golden Gate Park, Presidio, Lincoln Park, Twin Peaks, Dolores Park, and McLaren Park |
69
+ | `bartStations` | `false` | Eight San Francisco BART stations with blue rings and names |
70
+ | `width`, `height` | `800`, `800` | SVG viewBox and intrinsic size |
71
+ | `padding` | `28` | Space around the coast |
72
+ | `markers` | `[]` | Points with `id`, `lng`, `lat`, optional `label`, `color`, `selected` |
73
+ | `keyRoads` | `false` | Nine selected road corridors and names for orientation |
74
+ | `colors` | Built-in palette | Override `water`, `land`, `district`, `neighborhood`, `highway`, `road`, `park`, `landmark`, `bart`, `label`, `marker`, `selected` |
75
+ | `title` | `San Francisco map` | Accessible SVG title |
76
+ | `idPrefix` | Unique per process | Set explicitly for deterministic output or independent server renders |
122
77
 
123
- ## Accessible JSON data and neighborhood lookup
78
+ For a plain outline map, set `districtFills: false`. Neighborhood areas are **August 2010 SFAR realtor areas**, not legal boundaries or a historical layer matched to the district year. See [SOURCES.md](SOURCES.md).
124
79
 
125
- All map geometry is available through stable JSON package exports. There are three district files (2002, 2012, 2022), the full 117 SF Find neighborhoods, 41 analysis neighborhoods, 92 realtor-defined areas, and separate coastline, highway, landmark, and BART files. Neighborhood records include a canonical display name, exact source name, stable ID, aliases where documented, source definition, and full polygon geometry.
80
+ `createSFMap(options)` returns `{ svg, project, viewBox }`. `project([longitude, latitude])` gives matching SVG coordinates for custom overlays. Named exports also include `districtYears`, `districtColors`, and `neighborhoodNames`.
126
81
 
127
- ```js
128
- import neighborhoods from '@kahwee/sf-map-svg/data/neighborhoods-realtor.json' with { type: 'json' };
129
- import districts2022 from '@kahwee/sf-map-svg/data/districts-2022.json' with { type: 'json' };
130
- import { getNeighborhood, searchNeighborhoods } from '@kahwee/sf-map-svg/data';
82
+ Use `theme: 'transit'` for pale water, ivory land, green parks, and blue BART symbols. Custom `colors` override the preset. Park and station overlays represent current source geography, independently of the district year; stations outside San Francisco, including Daly City, are excluded.
131
83
 
132
- const mission = getNeighborhood('Inner Mission');
133
- const outerMission = getNeighborhood('Outer Mission');
134
- const nopa = getNeighborhood('NoPa', { source: 'realtor' });
135
- const matchingDefinitions = searchNeighborhoods('mission');
136
- ```
84
+ `keyRoads: true` adds Market, Mission, Geary, Van Ness, 19th Avenue, Sunset, The Embarcadero, Columbus, and Divisadero using DataSF centerlines. These are orientation features, not routing guidance.
137
85
 
138
- These are 250 **source-specific definitions**, not 250 distinct neighborhoods. Canonical names are package display names, and boundaries reflect each documented source rather than a claimed universal consensus. Mission and Outer Mission remain distinct. JSON files are the source of truth used by the renderer; the default map and lookup use the 92 SFAR realtor neighborhoods. See [the data API guide](data/README.md) for all filenames, schema, lookup rules, source comparisons, and custom SVG overlays. Storybook provides downloadable JSON files beside its neighborhood examples.
139
86
 
140
- ## Interactive neighborhood explorer
87
+ ## Neighborhood explorer
141
88
 
142
89
  The browser explorer includes canonical-name and alias search, source selection, neighborhood outlines, zoom controls, and GeoJSON downloads. SFAR realtor definitions are selected by default; SF Find and analysis neighborhoods remain separate choices.
143
90
 
@@ -158,19 +105,6 @@ Selected downloads are one-feature GeoJSON FeatureCollections retaining source a
158
105
 
159
106
  At city scale, labels stay sparse. Zooming reveals neighborhood and BART names, with label sizing and collision checks based on the visible viewport. Station points remain visible. The static `renderSFMap` API keeps its existing labels and defaults.
160
107
 
161
- Run `pnpm demo`, serve the repository root over HTTP, and open `examples/generated/explorer.html`. Storybook includes city, selected neighborhood, alternative-source, and mobile examples.
162
-
163
- ## License
164
-
165
- Software is licensed under MIT. Geographic datasets retain their source terms and attribution requirements; see [SOURCES.md](SOURCES.md).
166
-
167
-
168
- ## TypeScript development
169
-
170
- The library is authored in strict TypeScript 7. Run `pnpm build` to compile JavaScript and declarations into `dist/`. JavaScript consumers require no TypeScript runtime. `pnpm format` applies Biome formatting and safe lint fixes; `pnpm check` checks Biome, data, source and consumer types, and tests.
171
-
172
- Enable `keyRoads: true` for Market, Mission, Geary, Van Ness, 19th Avenue, Sunset, The Embarcadero, Columbus, and Divisadero. These use active DataSF centerlines, not invented routes. The explorer reveals road names as you zoom. Import `keyRoads` from the data entry point or `data/key-roads.json` for geometry, source segment IDs, and label anchors.
173
-
174
108
  ### Map modes and labels
175
109
 
176
110
  The interactive explorer includes a map-mode selector and a Labels toggle. `mode: 'districts'` shows numbered supervisorial districts; `mode: 'neighborhoods'` shows names from the selected neighborhood source (SFAR realtor by default). Labels are collision-filtered and remain about 12 screen pixels through map zoom and resize; more names fit as you zoom in. Road labels use 11 pixels.
@@ -186,7 +120,7 @@ explorer.setLabels(true);
186
120
 
187
121
  ## Reusable interactive map
188
122
 
189
- The new `@kahwee/sf-map-svg/interactive` entry point provides a map, accessible controls, and
123
+ The `@kahwee/sf-map-svg/interactive` entry point provides a map, accessible controls, and
190
124
  attribution without the explorer's search sidebar or detail panel. It does not change URLs,
191
125
  load articles, apply editorial filters, or navigate. Importing the static entry point does not
192
126
  import this interactive runtime. Both paths retain zero runtime dependencies.
@@ -235,6 +169,10 @@ aliases. Import types including `InteractiveSFMapOptions`, `InteractiveSFMapElem
235
169
  | `markerRadius` / `markerHitSize` | `6` / `44` | Screen-pixel visible radius and tap-target diameter, independent of zoom; selected radius grows by 2px |
236
170
  | `markerColor` / `selectedMarkerColor` | `#245b61` / `#f04f32` | Default marker colors; individual `marker.color` overrides the unselected color |
237
171
  | `onMarkerActivate` | Unset | Called when a non-null marker selection changes, including programmatic changes |
172
+ | `overlays` | `[]` | GeoJSON line or polygon overlays with stable IDs and optional SVG styles |
173
+ | `style` | Built-in tokens | Explorer CSS tokens: `ink`, `surface`, `accent`, `border`, `focus`, `controlGap`, `font` |
174
+ | `strings` | English defaults | Replace visible map labels and gesture help for localization |
175
+ | `controls` | All enabled | Independently hide `zoom`, `pan`, `reset`, `labels`, `touch`, or `legend`; source attribution remains visible |
238
176
 
239
177
  Explicit layer options override mode defaults even after `setMode()`. District fills, outlines,
240
178
  and badges can therefore be composed with neighborhood names without requiring district
@@ -280,6 +218,11 @@ unknown identities. `getSelection()` and `getSelectedMarker()` read current sele
280
218
  when needed. `setMode(mode)` resets the viewport. Repeating the same viewport or selection
281
219
  emits no change event, avoiding state feedback loops. All events bubble. Call `destroy()`
282
220
  before removing the element to release listeners, observers, frames, and download URLs.
221
+ `setOverlays(overlays)` replaces all consumer overlays; each overlay has a unique `id`,
222
+ WGS84 `LineString`, `MultiLineString`, `Polygon`, or `MultiPolygon` geometry, optional
223
+ `stroke`, `strokeWidth`, `fill`, `fillOpacity`, `visible`, and accessible `label`. Overlays
224
+ track every pan, zoom, resize, and source change and render above geography but below markers.
225
+ They are decorative and do not participate in label collision layout.
283
226
 
284
227
  ### Dense markers
285
228
 
@@ -319,7 +262,39 @@ chooser to reach obscured markers. Automated clustering is not included in this
319
262
  - Pointer cancellation, loss of capture, window blur, and resizing cancel active gestures.
320
263
  There is no animated camera or inertia. Button transitions are disabled with reduced motion.
321
264
 
322
- ### Examples and verification
265
+ ## Geographic data and lookup
266
+
267
+ All map geometry is available through stable JSON package exports. There are three district files (2002, 2012, 2022), the full 117 SF Find neighborhoods, 41 analysis neighborhoods, 92 realtor-defined areas, and separate coastline, highway, landmark, and BART files. Neighborhood records include a canonical display name, exact source name, stable ID, aliases where documented, source definition, and full polygon geometry.
268
+
269
+ ```js
270
+ import neighborhoods from '@kahwee/sf-map-svg/data/neighborhoods-realtor.json' with { type: 'json' };
271
+ import districts2022 from '@kahwee/sf-map-svg/data/districts-2022.json' with { type: 'json' };
272
+ import { getNeighborhood, searchNeighborhoods } from '@kahwee/sf-map-svg/data';
273
+
274
+ const mission = getNeighborhood('Inner Mission');
275
+ const outerMission = getNeighborhood('Outer Mission');
276
+ const nopa = getNeighborhood('NoPa', { source: 'realtor' });
277
+ const matchingDefinitions = searchNeighborhoods('mission');
278
+ ```
279
+
280
+ These are 250 **source-specific definitions**, not 250 distinct neighborhoods. Canonical names are package display names, and boundaries reflect each documented source rather than a claimed universal consensus. Mission and Outer Mission remain distinct. JSON files are the source of truth used by the renderer; the default map and lookup use the 92 SFAR realtor neighborhoods. See [the data API guide](data/README.md) for all filenames, schema, lookup rules, source comparisons, and custom SVG overlays. Storybook provides downloadable JSON files beside its neighborhood examples.
281
+
282
+ ## Development
283
+
284
+ Requires Node 22.12+ and pnpm 12. The library uses strict TypeScript; the build emits JavaScript and declarations to `dist/`.
285
+
286
+ ```sh
287
+ pnpm install --frozen-lockfile
288
+ pnpm check # formatting, data catalog, types, and tests
289
+ pnpm demo # generated SVGs and example pages
290
+ pnpm build-storybook # static component documentation
291
+ pnpm test:package # install and check the packed package
292
+ ```
293
+
294
+ Use `pnpm format` to apply Biome formatting and safe lint fixes. Run `pnpm storybook` for interactive component examples at http://127.0.0.1:6006. CI runs checks on Node 22, 24, and 26. See [CONTRIBUTING.md](CONTRIBUTING.md) for source structure and release instructions.
295
+
296
+ ### Browser verification
297
+
323
298
 
324
299
  Run `pnpm demo` and serve the repository root. `examples/generated/index.html` covers static
325
300
  maps, `explorer.html` covers the full explorer, and `interactive.html` covers independently
@@ -342,3 +317,39 @@ teardown. Physical iOS Safari and Android Chrome verification remains required b
342
317
  check page scroll and browser pinch in default mode; map pan and pinch in engaged mode; lift one
343
318
  finger; interrupt/cancel; rotate; use Done; verify scrolling resumes. Desktop automation and
344
319
  synthetic pointer tests do not establish physical-device compatibility.
320
+
321
+ ## GitHub Pages
322
+
323
+ The [live explorer](https://kahwee.github.io/sf-map-svg/) provides neighborhood search, 2022 district views, SVG examples, and GeoJSON downloads. Its source is in `website/` and uses the public explorer API.
324
+
325
+ ```sh
326
+ pnpm build:pages # local preview
327
+ pnpm build:pages --released # use the current npm release
328
+ python3 -m http.server 8765 --directory pages-dist
329
+ # Open http://localhost:8765
330
+ ```
331
+
332
+ The build bundles assets into `pages-dist/` with relative URLs for GitHub’s project path. Local builds use the working package; deployed builds use npm’s latest stable package for both browser components and SVG downloads. The page shows that version and links to its release notes. `pages-dist/release.json` records the build’s version and source.
333
+
334
+ `.github/workflows/pages.yml` validates and deploys on pushes to `main` and after successful npm publishing. Release-triggered builds wait for registry processing before using the newly published version. Pages deployment does not publish npm packages or releases.
335
+
336
+ ## License and attribution
337
+
338
+ MIT-licensed software, originally extracted from KahWee’s San Francisco District Map. Geographic data retains its source terms and attribution requirements; see [LICENSE](LICENSE) and [SOURCES.md](SOURCES.md). Neighborhood definitions vary by source and are not legal boundaries or a claim of universal consensus.
339
+
340
+ ### Schematic transit animation
341
+
342
+ ```js
343
+ import { createTransitAnimation } from '@kahwee/sf-map-svg/transit';
344
+ const animation = createTransitAnimation();
345
+ document.querySelector('#transit').append(animation);
346
+ // On removal: animation.destroy();
347
+ ```
348
+
349
+ This optional browser component starts paused, with Play/Pause and a keyboard-accessible journey slider. A loop lasts 28 seconds; timing is illustrative. It connects the bundled official BART station centroids with straight segments, not actual tracks or live service. It pauses when the page is hidden. No autoplay means reduced-motion users can inspect the static map or scrub manually. Existing map defaults are unchanged.
350
+
351
+ Embed the Pages demo with `<iframe src="https://kahwee.github.io/sf-map-svg/transit.html" title="Schematic BART journey" loading="lazy" style="width:100%;height:clamp(650px, calc(100vw + 240px), 930px);border:0"></iframe>`.
352
+
353
+ ## Ballot measures explorer
354
+
355
+ [Explore June 2026 ballot measures](https://kahwee.github.io/sf-map-svg/measures.html): certified local Measures A–D, citywide outcomes, full-screen district maps with floating controls, expandable mobile results, touch pan/zoom, a focus view, side-by-side measure comparisons, Yes/No or original district colors, zoom and district fitting, a sortable comparison table, shareable views, and SVG/CSV/JSON downloads. The page uses the released map renderer and separately bundled official election results. It is an archive, not a live results service or voting guide. Methodology and source links are available on the page and in [SOURCES.md](SOURCES.md).
package/SOURCES.md CHANGED
@@ -79,3 +79,16 @@ The cleanup retains all 92 identities, names, and source codes. It uses no round
79
79
  ## Key road landmarks
80
80
 
81
81
  Downloaded September 26, 2026 (UTC; September 25 in San Francisco) from [DataSF Streets – Active and Retired](https://data.sf.gov/resource/3psu-pn9h.geojson), filtering `active = true` and exact source street names. `data/key-roads.json` groups 724 source segments into nine named corridors, preserving every source coordinate and CNN segment ID. Geary St and Geary Blvd are grouped under the Geary Blvd display label. These are geographic orientation features, not a complete network or vehicle-access guidance. Label anchors select existing source vertices near editorial targets. Regenerate with `node scripts/import-key-roads.mjs` then `pnpm data:catalog`. The full query and retrieval date are embedded in the JSON. DataSF terms apply.
82
+
83
+ ## June 2026 ballot measures explorer
84
+
85
+ Downloaded September 26, 2026 from the San Francisco Department of Elections:
86
+
87
+ - Final district workbook: https://sfelections.org/results/20260602/data/20260625/dsov.xlsx
88
+ - Citywide summary, official measure titles, ballot questions, and thresholds: https://sfelections.org/results/20260602/index.html
89
+ - Certification dated June 25, 2026: https://sfelections.org/results/20260602/data/20260625/CertificationLetterJun22026.pdf
90
+ - Final report index: https://sfelections.org/results/20260602w/detail.html
91
+
92
+ `data/elections/2026-06-02.json` contains Measures A–D, their citywide counts, and the 11 `SUP DIST n - Total` rows from workbook sheets 20–23. Each row retains its worksheet row number; metadata retains the workbook SHA-256. `scripts/import-election-results.py` extracts these with openpyxl (ingestion only), checks all district sums including under/overvotes, and cross-checks Yes/No citywide counts against the official HTML summary. Rerun with the downloaded workbook and summary paths. No original Site ballot overlays are reused.
93
+
94
+ The Pages-only explorer calculates Yes / (Yes + No), excluding under/overvotes. Measure A uses the two-thirds threshold; B–D use a strict majority, as stated in the official summary. These are citywide outcomes, not district-level passage decisions. District counts are reported directly by Elections, not spatially assigned to neighborhoods. The existing 2022 district display map is reused unchanged. Election JSON is a separate website dataset, not a new npm package API; no live service or forthcoming-election coverage is claimed.
@@ -33,7 +33,7 @@ const mission = neighborhoods.features.find((f) => f.id === 'inner-mission');
33
33
  console.log(mission.properties.canonicalName, mission.geometry);
34
34
  ```
35
35
 
36
- Node 22.12+ supports this syntax. Bundlers may also support JSON imports without the import attribute. Files are included in the package archive; non-JavaScript consumers can parse the same JSON files directly. The repository remains private; these are public package entry points, not a publicly hosted API. Consumers who need only one dataset should import its JSON subpath rather than the convenience module, which loads all collections.
36
+ Node 22.12+ supports this syntax. Bundlers may also support JSON imports without the import attribute. Files are included in the package archive; non-JavaScript consumers can parse the same JSON files directly. These are package entry points, not a hosted API. The public repository and GitHub Pages explorer also provide access to the source data. Consumers who need only one dataset should import its JSON subpath rather than the convenience module, which loads all collections.
37
37
 
38
38
  ## Names and definitions
39
39
 
@@ -99,3 +99,7 @@ Edit the canonical JSON only. Keep coordinate precision and source labels; recor
99
99
  The 92 realtor neighborhoods have disjoint interiors. Shared borders and corner points are allowed. The source contained tiny overlapping boundary slivers; the normalized JSON assigns each such area once using stable-ID order, preserving the combined footprint without rounding or buffering. `topology` records this processing. This guarantee applies within the realtor collection; alternative neighborhood sources and district/park layers describe different concepts and must not be treated as additional mutually exclusive neighborhoods.
100
100
 
101
101
  Run `pnpm data:normalize-realtor` when updating realtor geometry, then `pnpm data:catalog` and `pnpm check`. Cleanup aborts if it would erase a neighborhood, leave overlapping interiors, or change the combined footprint. Tests reject any nonempty polygon intersection; they do not excuse small slivers with an area threshold.
102
+
103
+ ## Election result snapshots (Pages only)
104
+
105
+ `elections/2026-06-02.json` holds certified local-measure results for the June 2026 Pages explorer. It contains four measures, citywide counts, and eleven supervisorial district totals per measure, plus source URLs, workbook checksum, and source row references. It is not a GeoJSON collection or a public npm export. It does not change the default SFAR neighborhood dataset. See `SOURCES.md` and `scripts/import-election-results.py` for extraction and validation.
@@ -0,0 +1,3 @@
1
+ export type { BartStationData, DistrictRowData, KeyRoadData, LandmarkData, SFMapData, } from './map-core.js';
2
+ export { createSFMapWithData, districtColors, districtYears, } from './map-core.js';
3
+ export type { DistrictYear, MapMarker, MapOverlay, SFMapOptions } from './types.js';
@@ -0,0 +1 @@
1
+ export { createSFMapWithData, districtColors, districtYears, } from './map-core.js';
@@ -1,4 +1,4 @@
1
1
  import type { NeighborhoodExplorerElement, NeighborhoodExplorerOptions } from './types.js';
2
2
  export type { ExplorerMode, NeighborhoodExplorerElement, NeighborhoodExplorerOptions, } from './types.js';
3
3
  /** Create an offline, browser-only neighborhood explorer. Call destroy() before disposal. */
4
- export declare function createNeighborhoodExplorer({ source, mode, labels, neighborhood, year, theme, interface: chrome, layers, selectableNeighborhoods, labelSize, fitPadding, markers: initialMarkers, markerRadius, markerHitSize, markerColor, selectedMarkerColor, onMarkerActivate, }?: NeighborhoodExplorerOptions): NeighborhoodExplorerElement;
4
+ export declare function createNeighborhoodExplorer({ source, mode, labels, neighborhood, year, theme, interface: chrome, layers, selectableNeighborhoods, labelSize, fitPadding, markers: initialMarkers, markerRadius, markerHitSize, markerColor, selectedMarkerColor, onMarkerActivate, overlays: initialOverlays, onOverlayActivate, style: styleOptions, strings, controls, }?: NeighborhoodExplorerOptions): NeighborhoodExplorerElement;