@projektemacher/storymapjs 0.10.8 → 0.11.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.
Files changed (195) hide show
  1. package/CHANGELOG +81 -0
  2. package/README.md +129 -12
  3. package/dist/assets/{demo-DBpeF31K.js → demo-bWnOyfUw.js} +1 -1
  4. package/dist/assets/harness-C3JUv5Ii.js +1 -0
  5. package/dist/assets/{harness-multi-CYHgI0a9.js → harness-multi-XZNex3tb.js} +1 -1
  6. package/dist/assets/{harness-support-Do7sIp2j.js → harness-support-CjfZIVeb.js} +1 -1
  7. package/dist/assets/main-DQ32hWBM.js +17 -0
  8. package/dist/context.json +13 -2
  9. package/dist/css/storymap.css +1 -1
  10. package/dist/demo.html +3 -2
  11. package/dist/docs/api/assets/navigation.js +1 -1
  12. package/dist/docs/api/assets/search.js +1 -1
  13. package/dist/docs/api/classes/StoryMap.html +777 -269
  14. package/dist/docs/api/functions/MediaType.html +1 -1
  15. package/dist/docs/api/functions/isPresentation3Collection.html +1 -1
  16. package/dist/docs/api/functions/isPresentation3Manifest.html +1 -1
  17. package/dist/docs/api/functions/isSlideshowCollection.html +51 -0
  18. package/dist/docs/api/functions/loadCSS.html +1 -1
  19. package/dist/docs/api/functions/manifestToStorymapData.html +1 -1
  20. package/dist/docs/api/functions/setLanguage.html +1 -1
  21. package/dist/docs/api/functions/slideshowToStorymapData.html +51 -0
  22. package/dist/docs/api/functions/slideshowVersion.html +48 -0
  23. package/dist/docs/api/functions/storymapToManifest.html +1 -1
  24. package/dist/docs/api/functions/validateStorymap.html +1 -1
  25. package/dist/docs/api/functions/validateStorymapAndReport.html +1 -1
  26. package/dist/docs/api/index.html +115 -12
  27. package/dist/docs/api/interfaces/AnimationHandle.html +2 -2
  28. package/dist/docs/api/interfaces/ImagereadyPayload.html +4 -4
  29. package/dist/docs/api/interfaces/LanguageEntry.html +6 -6
  30. package/dist/docs/api/interfaces/LatLngLiteral.html +4 -4
  31. package/dist/docs/api/interfaces/LoadOptions.html +2 -2
  32. package/dist/docs/api/interfaces/MapEvents.html +116 -0
  33. package/dist/docs/api/interfaces/MapMarkerEvents.html +66 -0
  34. package/dist/docs/api/interfaces/MarkerEventPayload.html +57 -0
  35. package/dist/docs/api/interfaces/MediaEvents.html +78 -0
  36. package/dist/docs/api/interfaces/MenuBarEvents.html +114 -0
  37. package/dist/docs/api/interfaces/MessageEvents.html +72 -0
  38. package/dist/docs/api/interfaces/OlLayer.html +1 -1
  39. package/dist/docs/api/interfaces/OlMap.html +1 -1
  40. package/dist/docs/api/interfaces/OlSource.html +1 -1
  41. package/dist/docs/api/interfaces/OlTileLayer.html +1 -1
  42. package/dist/docs/api/interfaces/OlVectorLayer.html +1 -1
  43. package/dist/docs/api/interfaces/OlView.html +1 -1
  44. package/dist/docs/api/interfaces/SlideBackgroundState.html +66 -0
  45. package/dist/docs/api/interfaces/SlideEvents.html +78 -0
  46. package/dist/docs/api/interfaces/SlideNavEvents.html +72 -0
  47. package/dist/docs/api/interfaces/SlideshowPlayerSettings.html +182 -0
  48. package/dist/docs/api/interfaces/SlideshowToStorymapResult.html +60 -0
  49. package/dist/docs/api/interfaces/SlideshowTranslatorOptions.html +65 -0
  50. package/dist/docs/api/interfaces/SlideshowWarning.html +72 -0
  51. package/dist/docs/api/interfaces/StoryMapEvents.html +146 -0
  52. package/dist/docs/api/interfaces/StoryMapMarker.html +763 -0
  53. package/dist/docs/api/interfaces/StorySliderEvents.html +102 -0
  54. package/dist/docs/api/interfaces/StorymapBasemap.html +69 -0
  55. package/dist/docs/api/interfaces/StorymapData.html +12 -6
  56. package/dist/docs/api/interfaces/StorymapDataWrapper.html +2 -2
  57. package/dist/docs/api/interfaces/StorymapDocument.html +2 -2
  58. package/dist/docs/api/interfaces/StorymapError.html +3 -3
  59. package/dist/docs/api/interfaces/StorymapGeoreference.html +5 -5
  60. package/dist/docs/api/interfaces/StorymapGeoreferenceBody.html +4 -4
  61. package/dist/docs/api/interfaces/StorymapGroundControlPoint.html +4 -4
  62. package/dist/docs/api/interfaces/StorymapLanguageMap.html +2 -2
  63. package/dist/docs/api/interfaces/StorymapManifest.html +12 -12
  64. package/dist/docs/api/interfaces/StorymapManifestAnnotation.html +43 -11
  65. package/dist/docs/api/interfaces/StorymapManifestBackground.html +6 -6
  66. package/dist/docs/api/interfaces/StorymapManifestCanvas.html +65 -17
  67. package/dist/docs/api/interfaces/StorymapManifestContentResource.html +8 -8
  68. package/dist/docs/api/interfaces/StorymapManifestFeatureCollection.html +4 -4
  69. package/dist/docs/api/interfaces/StorymapManifestGeoreferencing.html +7 -7
  70. package/dist/docs/api/interfaces/StorymapManifestImageService.html +4 -4
  71. package/dist/docs/api/interfaces/StorymapManifestRange.html +5 -5
  72. package/dist/docs/api/interfaces/StorymapManifestService.html +4 -4
  73. package/dist/docs/api/interfaces/StorymapManifestSpecificResource.html +4 -4
  74. package/dist/docs/api/interfaces/StorymapManifestStatement.html +3 -3
  75. package/dist/docs/api/interfaces/StorymapMapOptions.html +5 -5
  76. package/dist/docs/api/interfaces/StorymapOptions.html +277 -75
  77. package/dist/docs/api/interfaces/StorymapOverlayLayer.html +42 -11
  78. package/dist/docs/api/interfaces/StorymapSlide.html +70 -16
  79. package/dist/docs/api/interfaces/StorymapSlideBackground.html +4 -4
  80. package/dist/docs/api/interfaces/StorymapSlideLocation.html +58 -14
  81. package/dist/docs/api/interfaces/StorymapSlideMarker.html +8 -9
  82. package/dist/docs/api/interfaces/StorymapSlideMedia.html +49 -10
  83. package/dist/docs/api/interfaces/StorymapSlideNarration.html +61 -9
  84. package/dist/docs/api/interfaces/StorymapSlideText.html +4 -4
  85. package/dist/docs/api/interfaces/StorymapTilejson.html +8 -8
  86. package/dist/docs/api/interfaces/SwipableEvents.html +102 -0
  87. package/dist/docs/api/interfaces/TextEvents.html +66 -0
  88. package/dist/docs/api/media/events.md +66 -0
  89. package/dist/docs/api/media/migration-from-knightlab.md +89 -49
  90. package/dist/docs/api/media/slideshow.md +109 -0
  91. package/dist/docs/api/media/storymap-as-iiif-manifest.md +1007 -0
  92. package/dist/docs/api/modules.html +6 -6
  93. package/dist/docs/api/types/ContentState.html +3 -3
  94. package/dist/docs/api/types/FiredEvent.html +43 -0
  95. package/dist/docs/api/types/ImagereadyKind.html +1 -1
  96. package/dist/docs/api/types/MediaTypeMatch.html +5 -5
  97. package/dist/docs/api/types/SwipeDirection.html +36 -0
  98. package/dist/docs/api/types/TileSourceFactory.html +1 -1
  99. package/dist/docs/authoring.html +20 -7
  100. package/dist/docs/events.html +263 -0
  101. package/dist/docs/iiif.html +136 -22
  102. package/dist/docs/migration.html +66 -19
  103. package/dist/docs/readme.html +113 -12
  104. package/dist/embed/index.html +7 -0
  105. package/dist/examples/font-css-false.json +33 -0
  106. package/dist/examples/issue-image-region.json +1 -1
  107. package/dist/examples/layers-control.json +60 -0
  108. package/dist/examples/map-template-provider.json +36 -0
  109. package/dist/examples/multi-media.json +71 -0
  110. package/dist/examples-iiif/annotated-image.json +16 -0
  111. package/dist/examples-iiif/font-css-false.json +114 -0
  112. package/dist/examples-iiif/issue-image-region.json +1 -1
  113. package/dist/examples-iiif/layers-control.json +226 -0
  114. package/dist/examples-iiif/map-template-provider.json +133 -0
  115. package/dist/examples-iiif/multi-media.json +237 -0
  116. package/dist/examples-slideshow/single.json +79 -0
  117. package/dist/examples-slideshow/static/pixel.png +0 -0
  118. package/dist/examples-slideshow/static.json +44 -0
  119. package/dist/harness-multi.html +3 -3
  120. package/dist/harness.html +4 -3
  121. package/dist/index.html +3 -2
  122. package/dist/js/storymap.d.ts +1326 -289
  123. package/dist/js/storymap.js +6690 -4784
  124. package/dist/js/storymap.js.map +1 -1
  125. package/dist/navplace-properties.json +14 -0
  126. package/dist/site.css +54 -36
  127. package/docs/migration-from-knightlab.md +89 -49
  128. package/docs/storymap-as-iiif-manifest.md +199 -126
  129. package/package.json +4 -2
  130. package/schema/storymap.schema.json +334 -34
  131. package/src/core/Util.ts +3 -1
  132. package/src/core/mixins.ts +42 -28
  133. package/src/language/Language.ts +7 -0
  134. package/src/language/locale/.expected-gaps.json +287 -27
  135. package/src/language/locale/de.json +11 -1
  136. package/src/language/locale/en.json +11 -1
  137. package/src/main.ts +51 -0
  138. package/src/map/Map.ts +44 -6
  139. package/src/map/MapMarker.ts +16 -3
  140. package/src/map/openlayers/Map.OpenLayers.ts +787 -62
  141. package/src/map/openlayers/MapMarker.OpenLayers.ts +113 -13
  142. package/src/map/slideView.ts +149 -0
  143. package/src/map/types.ts +2 -0
  144. package/src/media/EmbedUtil.ts +27 -2
  145. package/src/media/Media.ts +27 -4
  146. package/src/media/embedId.ts +6 -0
  147. package/src/media/types/Flickr.ts +14 -3
  148. package/src/media/types/HtmlMedia.ts +66 -0
  149. package/src/media/types/Image.ts +15 -3
  150. package/src/media/types/SoundCloud.ts +1 -1
  151. package/src/media/types/Text.ts +27 -8
  152. package/src/media/types/Twitter.ts +9 -7
  153. package/src/media/types/Vimeo.ts +1 -1
  154. package/src/media/types/YouTube.ts +2 -2
  155. package/src/scss/Typography.scss +15 -15
  156. package/src/scss/VCO.StoryMap.scss +173 -8
  157. package/src/scss/core/Reset.scss +5 -18
  158. package/src/scss/map/VCO.Map.scss +1 -1
  159. package/src/scss/map/VCO.MapMarker.scss +20 -9
  160. package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +7 -4
  161. package/src/scss/media/VCO.Media.scss +7 -7
  162. package/src/scss/media/types/VCO.Media.Blockquote.scss +1 -1
  163. package/src/scss/media/types/VCO.Media.Text.scss +5 -5
  164. package/src/scss/media/types/VCO.Media.Twitter.scss +4 -4
  165. package/src/scss/media/types/VCO.Media.Wikipedia.scss +2 -19
  166. package/src/scss/site/site.scss +55 -36
  167. package/src/scss/slider/VCO.Slide.scss +49 -11
  168. package/src/scss/slider/VCO.SlideNav.scss +23 -7
  169. package/src/scss/slider/VCO.StorySlider.scss +50 -0
  170. package/src/scss/snapmap/VCO.SnapMap.scss +3 -3
  171. package/src/scss/ui/VCO.Consent.scss +20 -14
  172. package/src/scss/ui/VCO.Layers.scss +119 -0
  173. package/src/scss/ui/VCO.MenuBar.Button.scss +140 -19
  174. package/src/scss/ui/VCO.MenuBar.scss +9 -2
  175. package/src/scss/ui/VCO.Message.scss +14 -0
  176. package/src/slider/Slide.ts +232 -74
  177. package/src/slider/SlideNav.ts +15 -4
  178. package/src/slider/StorySlider.ts +203 -22
  179. package/src/storymap/Consent.ts +22 -0
  180. package/src/storymap/StoryMap.ts +944 -131
  181. package/src/storymap/content-state.ts +3 -8
  182. package/src/storymap/font-css.ts +72 -0
  183. package/src/storymap/from-slideshow.ts +785 -0
  184. package/src/storymap/iiif-shared.ts +228 -0
  185. package/src/storymap/iiif.ts +289 -100
  186. package/src/storymap/to-iiif.ts +131 -44
  187. package/src/types.ts +382 -17
  188. package/src/ui/LayersControl.ts +302 -0
  189. package/src/ui/MenuBar.ts +232 -21
  190. package/src/ui/Message.ts +36 -3
  191. package/src/ui/Swipable.ts +25 -7
  192. package/dist/assets/harness-Cr0rYpvt.js +0 -1
  193. package/dist/assets/main-DXalctuR.js +0 -17
  194. package/src/scss/VCO.StoryMap.Dark.scss +0 -150
  195. package/src/scss/Variables.Dark.scss +0 -57
package/CHANGELOG CHANGED
@@ -1,3 +1,84 @@
1
+ 0.11.0 (2026-10-07)
2
+ ------------------------
3
+ Added
4
+ -----
5
+ - Inactive slides are hidden from assistive tech (`aria-hidden` + `inert`
6
+ in `Slide.setActive()`), so screen readers no longer read every
7
+ off-screen slide; preloading (including iframes) is unaffected
8
+ - Keyboard access: map pins are focusable buttons (Enter/Space activates,
9
+ focus returns on popup close), messages dismiss from the keyboard, and a
10
+ skip link jumps to the slide content. Autoplay gains a menubar
11
+ pause/resume toggle; slide text carries its `lang`; consent dialogs are
12
+ labelled dialogues with focus; media errors announce via `role=alert`
13
+ - Reduced motion is honored everywhere it matters: map glides, line draw,
14
+ background fades, nav intro, smooth scroll and the loader freeze, and an
15
+ OS toggle flipped mid-story stops autoplay. Touch targets are 44px,
16
+ muted text meets contrast, and focus rings are visible
17
+ - Programmatic popup cards: `storymap.openMarkerPopup(n)` navigates to the
18
+ stop and opens its card (false and no navigation when disabled, out of
19
+ range, disposed or mapless), plus `closeMarkerPopup(n?)` and
20
+ `isPopupOpen(n)`. The marker itself exposes `openPopup()` /
21
+ `closePopup()` / `togglePopup()` / `isPopupEnabled()` / `popupOpen`,
22
+ and the `StoryMapMarker` type is exported for hosts
23
+ - `popupopen` / `popupclose` (`{ marker_number, current_slide }`) and a
24
+ viewer-level `markerclick` re-fire, so hosts can tell marker clicks apart
25
+ from programmatic navigation
26
+ - `tweetAuthor()` next to `tweetId()`: `Twitter` extracts both through the
27
+ shared helpers instead of its own regex
28
+ - `font_css: false` (or `"none"`) injects no theme stylesheet for
29
+ bundler consumers that import it themselves; the consent dialog lists no
30
+ font service for it, and a failing theme load warns with its URL. The
31
+ IIIF `storymap:fontCss` term round-trips `false`
32
+ - Layer switcher: `show_layers_control: true` adds a menubar disclosure
33
+ button with basemap radios (`basemaps[]`, entry 0 initial unless
34
+ `map_type` says otherwise) and overlay checkboxes (rows track built
35
+ layers, so malformed entries never appear). Overlay `label` (string or
36
+ function), `locked` (checked and disabled) and `control: false` (hidden
37
+ row) annotate entries; cross-projection basemaps are refused with a
38
+ warning. Fires `basemapchange` / `overlaychange`, also on the viewer
39
+ - IIIF narration (V1 of #358): a `motivation: "supplementing"` Sound/Video
40
+ body plays as `slide.narration`, and the writer emits one back. Array
41
+ `motivation` values are honored everywhere — previously an array read as
42
+ "no motivation" and was accepted as the canvas painting, shadowing the
43
+ real media
44
+ - Second visible media slot (V2 of #358): `media_extra` renders further media
45
+ items after the primary one (`media_layout: "stack"`, the default, or
46
+ `"row"` for two side-by-side items); documents without it render
47
+ identically. Extra `painting` annotations read as `media_extra` and are
48
+ written back, so the slot round-trips — only the layout stays viewer-side,
49
+ IIIF has no term for it. Consent, the audio badge and the popup thumbnail
50
+ all see every item
51
+ - Dark theme without a second stylesheet: every widget colour is a
52
+ `--vco-*` custom property (Sass values kept as fallbacks, so the default
53
+ render is unchanged); `theme: "dark"` forces the dark palette, `"light"`
54
+ pins the light one, unset follows `prefers-color-scheme`. The unshippable
55
+ `VCO.StoryMap.Dark.scss` / `Variables.Dark.scss` pair is deleted, the
56
+ dead widget reset is live again, and dark text pairs clear AA. The
57
+ landing page follows the OS scheme too
58
+ - Typed events: every emitter has an `*Events` map (exported from the
59
+ library), so `storymap.on("chnage", …)` is a compile error and handlers
60
+ infer their payload. `fire()` can no longer be handed a `type`/`target`
61
+ in the payload, `off()` removes all matches, and teardowns detach
62
+ their listeners
63
+ - Full XYZ templates naming a known provider are credited to it
64
+ (`tile.openstreetmap.org` and subdomains, `stadiamaps.com`,
65
+ `openmaptiles.org`, `mapbox.com`, `arcgisonline.com`/`esri.com`,
66
+ `carto.com`/`basemaps.cartocdn.com` — subdomains match their parent), in
67
+ both the rendered line and the source attributions. Relative templates
68
+ still render "Map data"
69
+
70
+ Changed
71
+ -------
72
+ - The IIIF reader/writer share `src/storymap/iiif-shared.ts`
73
+ (`LOCATION_PROPERTIES`, JSON guards, bbox and URL checks) instead of
74
+ three copies; the writer's Node CLI import carries an explicit `.ts`
75
+ extension, which type-stripping resolution requires
76
+ - Map attribution comes from one provider-credit table (linked HTML and
77
+ plain text cannot drift apart); font-theme URL helpers live in
78
+ `src/storymap/font-css.ts` (re-exported from `StoryMap`)
79
+ - Error paths log with `console.warn`, prefixed `StoryMapJS:`, instead of
80
+ `console.log`
81
+
1
82
  0.10.8 (2026-09-29)
2
83
  ------------------------
3
84
  Code audit: one regression, a set of latent bugs, seven stored-XSS sinks, a
package/README.md CHANGED
@@ -39,8 +39,10 @@ Points are set to only display on mouseover in image mode, but you can set map_a
39
39
 
40
40
  ## Map Options
41
41
 
42
- To disable connecting lines on maps set `map_as_image: true` in the storymap
43
- options (the default `false` renders cartography).
42
+ To disable connecting lines on maps set `show_lines: false` in the storymap
43
+ options (the default `true` renders cartography with `show_history_line`).
44
+ `map_as_image: true` is something else: image-map mode, only meaningful with
45
+ `map_type: "iiif"` (the image itself becomes the map).
44
46
 
45
47
  `storymap.map` is the raw OpenLayers map, typed with the re-exported `ol`
46
48
  types, and `getBaseLayer()`, `getOverlayLayers()`, `getMinimap()`,
@@ -72,6 +74,85 @@ More config options available to do what you want with the line:
72
74
 
73
75
  To disable zoom calculation/edit zoom level set calculate_zoom to false in the config options.
74
76
 
77
+ ### More data options
78
+
79
+ Beyond the map options above, the storymap data takes `overlays[]`
80
+ (stacked raster layers over the basemap), `tilejson` (a TileJSON source for
81
+ the base layer), `overview_extent` (the overview slide's bounds),
82
+ `keyboard` (page-wide arrow-key navigation for multi-viewer pages),
83
+ per-slide `marker: { popup, audioBadge }` (the active marker's card, openable
84
+ via `storymap.openMarkerPopup(n)`, and the narration/audio dot) and
85
+ `narration: { url }` per slide. `autoplay_media` is global (wait for slide
86
+ media to end while autoplaying); the per-slide dwell is `slidetimeout`.
87
+ All are validated
88
+ against `schema/storymap.schema.json` — read it (or `src/types.ts`) for the
89
+ full list with defaults.
90
+
91
+ `show_layers_control: true` adds a menubar disclosure button listing the
92
+ `overlays[]` as checkboxes and `basemaps[]` as basemap radios, so the
93
+ visitor can switch layers without touching code.
94
+
95
+ `theme: "dark"` forces the dark palette, `"light"` pins the light one;
96
+ unset, the widget follows `prefers-color-scheme`.
97
+
98
+ ### Slideshow-style tours
99
+
100
+ Slides can carry per-slide presentation for image tours (a stop can restyle
101
+ the view while it is active; everything is optional and validated against
102
+ `schema/storymap.schema.json`):
103
+
104
+ location: {
105
+ region: [x, y, w, h], // image-pixel stop (IIIF xywh); negative origins clamp to the canvas
106
+ rotation: 45, // view rotation in degrees clockwise
107
+ filter: { sepia: 40 }, // brightness/contrast/saturate 0-200 (%), hueRotate -180-180 (°), sepia 0-100 (%), blur 0-20 (px)
108
+ mask: { x: 0.1, y: 0.1, w: 0.8, h: 0.8, opacity: 0.5 }, // spotlight mask (fractions) + color/invert
109
+ basemap: "osm:bright", // per-slide basemap (any map_type, IIIF info.json URL or image file)
110
+ },
111
+ narration: { url, loop, offset, play: "auto"|"click", stopOnExit, stopAllPrevious },
112
+ slidetimeout: 9000, // per-slide autoplay dwell in ms (0 holds); default is the global autoplay
113
+ imgoverlay: { url, size, extent, opacity }, // image overlay while the slide is active
114
+
115
+ Audio/video media take the same `offset`/`loop`/`play`/`stopOnExit` flags
116
+ on the slide's `media` object (`stopAllPrevious` is narration-only).
117
+
118
+ When a slide's IIIF `info.json` cannot be loaded, `iiif: { width, height,
119
+ fallbackUrl }` paints the static image instead of a blank basemap. Without
120
+ stated dimensions the viewer probes the fallback image's natural size first,
121
+ but only for slideshow tours (15s timeout); other documents keep the logged
122
+ error path. Per-slide static basemaps (`image_srv` pointing at a plain
123
+ image file, absolute or same-origin relative) probe the same way, and a
124
+ failed `info.json` fetch falls back to a probed static image in place.
125
+
126
+ The player chrome is opt-in storymap options (absent means the default
127
+ layout): `textmode: "left"|"right"|"bottom"` with `textsize` (10–80 %,
128
+ bottom dock only — the player drops side-dock sizes too),
129
+ `progressbar: "bar"|"dots"|"squares"|"block"|"thinblock"|"off"` (`true`
130
+ means `"bar"`, `false` means hidden),
131
+ `fxmode: "slide"|"fade"|"none"`, `mode: "static"` (stacked reading list with
132
+ scroll-spy map sync), `hudcolor`/`hudbgcolor`/`hudopacity`,
133
+ `shownav`/`show_headings`/`show_scrollbars`/`show_info` toggles and
134
+ `viewerheight: "400px"`.
135
+
136
+ **IIIF round-trip:** the per-slide fields do survive — `rotation`,
137
+ `filter`, `mask`, `basemap`, `slidetimeout`, `imgoverlay`, `provenance`
138
+ and the playback bags travel as `storymap:` terms (see
139
+ [docs/storymap-as-iiif-manifest.md](docs/storymap-as-iiif-manifest.md)).
140
+ The player chrome does **not**: it has no IIIF vocabulary and is
141
+ translator-only (a storymap→manifest→storymap cycle drops it).
142
+
143
+ These presentation fields render **only for slideshow tours** (documents
144
+ loaded from that format, which set the internal `slideshow_source` marker).
145
+ Hand-written storymap documents and IIIF manifests keep the long-standing
146
+ rendering even when they name the new keys — captions always show, the
147
+ autoplay interval stays global, the panel keeps its default dock. Hosts may
148
+ pass `slideshow_source: true` explicitly as an escape hatch (e.g. in tests).
149
+
150
+ Slideshow tours (W3C `AnnotationCollection` documents) load directly — by
151
+ object, file or URL, following `next` pages — or convert offline with
152
+ `npm run convert:slideshow -- [--settings settings.json] [--out out.json] <tour-url-or-file>`
153
+ (see [docs/slideshow.md](docs/slideshow.md) for the field mapping,
154
+ player-settings handling and loss table).
155
+
75
156
  Images can now be used in place of map pins.
76
157
  Use `image` inside the location object and include a url to use, together with
77
158
  `use_custom_marker: true` in the location object (or set `use_custom_markers:
@@ -103,7 +184,10 @@ an opaque slide panel instead of the gradient over the map), set
103
184
  Any value containing `{z}` renders as a raster XYZ layer; a path without `{z}`
104
185
  that looks like a URL/path renders as a vector style layer; anything else falls
105
186
  back to classic OSM raster — except `"none"`, which builds no map at all and
106
- runs the story as text and media only.
187
+ runs the story as text and media only. A full template naming a known provider
188
+ (`https://tile.openstreetmap.org/{z}/{x}/{y}.png`) is credited to that
189
+ provider; anything else gets the generic "Map data" line (add your own via
190
+ `attribution`).
107
191
 
108
192
  ### Switching the basemap at runtime
109
193
 
@@ -114,11 +198,18 @@ current slide is re-fitted after the swap.
114
198
 
115
199
  ### Icons
116
200
 
117
- Default pins use the bundled `vco-icons` font (`dist/css/icons/`, referenced via
118
- relative `./icons/...` URLs from `dist/css/storymap.css`), so pins render on
119
- subpath deploys, bundler consumers (Vite leaves absolute `/css/...` untouched)
120
- and `file://`/Electron hosts without extra configuration. Import the stylesheet
121
- once (`import "@projektemacher/storymapjs/css/storymap.css"`).
201
+ Default pins use the bundled `vco-icons` font (`dist/css/icons/`). The SCSS
202
+ source uses the absolute public path (`/css/icons/...`) so `vite dev` serves
203
+ the fonts directly; `vite build` rewrites them to relative `./icons/...` in
204
+ `dist/css/storymap.css`, so pins render on subpath deploys, for bundler
205
+ consumers and on `file://`/Electron hosts without extra configuration.
206
+ Import the stylesheet once
207
+ (`import "@projektemacher/storymapjs/css/storymap.css"`).
208
+
209
+ Font themes (`font_css: "stock:<name>"`) resolve via `import.meta.url`, so
210
+ they only work script-tagged, unbundled. A bundler consumer imports the theme
211
+ instead (`import "@projektemacher/storymapjs/css/fonts/font.default.css"`)
212
+ and passes `font_css: false` for no injected `<link>`.
122
213
 
123
214
  ## Custom HTML in slide content
124
215
 
@@ -172,9 +263,11 @@ Decisions are stored in `localStorage` under `storymapjs-consent` and have no
172
263
  expiry — clearing site data asks again.
173
264
 
174
265
  The consent labels are **not** fully translated. `src/language/locale/en.json`
175
- defines every string; 28 bundled locales fall back to English for the keys
176
- they lack — most are missing one, a handful are missing around ten (the
177
- per-service consent prompts and the fullscreen button labels). Run
266
+ defines every string (34 keys across 29 locale files: English plus 28
267
+ translations). German is missing 1 key, most locales are missing 9 (the audio
268
+ badge, autoplay, layer-switcher, iframe-title and narration-consent strings),
269
+ and five locales (`et`, `is`, `nn`, `ur`, `sr`) are missing 18–19 (additionally
270
+ the fullscreen, consent-dialog, error and overview strings). Run
178
271
  `npm run check:locales` to see the current state — it reports the gap per
179
272
  locale and is expected to be runnable without failing.
180
273
 
@@ -182,7 +275,31 @@ locale and is expected to be runnable without failing.
182
275
 
183
276
  If a storymap fails to render, open the browser console: the viewer logs
184
277
  fetch and validation errors (e.g. "could not load storymap data from ...").
185
- The `error` event also fires for programmatic consumers.
278
+ The `error` event also fires for programmatic consumers. The full event
279
+ reference (payloads included) is [docs/events.md](docs/events.md).
280
+
281
+ ## Accessibility
282
+
283
+ Supported, and covered by tests: text alternatives on slide images
284
+ (decorative `alt=""` included); real `<button>` menubar actions and slide
285
+ navigation; polite live-region slide announcements; reduced-motion handling
286
+ of the slide glide and autoplay; an embed page that does not disable zoom;
287
+ sanitized author tables that keep their semantics; slides hidden from
288
+ assistive tech while inactive (`aria-hidden` + `inert`), with a layer
289
+ switcher built from native checkboxes, radios and fieldsets; keyboard
290
+ operable map pins and message dismissal; a skip link to the slide content;
291
+ an autoplay pause/resume toggle; slide text pronounced in its own language
292
+ (`lang`); consent dialogs exposed with labels and focus; media errors
293
+ announced as alerts; and a motion kill-switch that also honors an
294
+ OS reduced-motion toggle flipped mid-story.
295
+
296
+ Known limitations (not fixed here): map markers are pointer-only; audio and
297
+ video have no viewer-side captions or transcripts; narration ships without a
298
+ transcript; focus moves only when the layer panel closes with Escape; labels
299
+ and announcements are hardcoded English; several muted text colors fall
300
+ below 4.5:1; third-party embeds are the vendor's conformance. No VPAT or
301
+ ACR is claimed — §508 conformance is decided per deliverable by an
302
+ external audit.
186
303
 
187
304
  ## Bundled credentials
188
305
 
@@ -1 +1 @@
1
- import"./modulepreload-polyfill-P2Xu9kJm.js";import{t as e}from"./main-DXalctuR.js";function t(e){document.title=`StoryMapJS: `+e.title}window.storymap=new e(`storymap-embed`,`./demo.json`,{},{title:t});
1
+ import"./modulepreload-polyfill-P2Xu9kJm.js";import{t as e}from"./main-DQ32hWBM.js";function t(e){document.title=`StoryMapJS: `+e.title}window.storymap=new e(`storymap-embed`,`./demo.json`,{},{title:t});
@@ -0,0 +1 @@
1
+ import"./modulepreload-polyfill-P2Xu9kJm.js";import{t as e}from"./main-DQ32hWBM.js";import{n as t,t as n}from"./harness-support-CjfZIVeb.js";window.__smErrors=[],window.__smReady=!1,window.addEventListener(`error`,e=>{window.__smErrors.push(String(e.message||e))});var r=new URLSearchParams(window.location.search),i=r.get(`manifest`),a=r.get(`slideshow`),o=r.get(`url`),s=i===null?a===null?r.get(`example`):a:i,c=i===null?a===null?`/examples/`:`/examples-slideshow/`:`/examples-iiif/`,l={};if(r.get(`options`))try{l=JSON.parse(r.get(`options`))}catch(e){window.__smErrors.push(`harness: bad options param: `+e)}var u=t(window);u&&(l.tile_source_factory=u);var d=o??c+s+`.json`;fetch(d).then(e=>e.json()).then(t=>{let i=new e(`storymap-embed`,a!==null&&o===null?d:t,l,{error:e=>{window.__smErrors.push(`storymap: `+(e.message||String(e)))}});window.__sm=i,n(i,r.get(`record`),window),window.__smReady=!0}).catch(e=>{window.__smErrors.push(`harness: `+(e&&e.message?e.message:String(e))),window.__smReady=!0});
@@ -1 +1 @@
1
- import"./modulepreload-polyfill-P2Xu9kJm.js";import{t as e}from"./main-DXalctuR.js";import{n as t,t as n}from"./harness-support-Do7sIp2j.js";window.__smErrors=[],window.__smReady=!1,window.addEventListener(`error`,e=>{window.__smErrors.push(String(e.message||e))});var r=new URLSearchParams(window.location.search),i={a:r.get(`a`)??`katrina`,b:r.get(`b`)??`seurat`},a={a:o(r.get(`aopts`)),b:o(r.get(`bopts`))};for(let[e,n]of Object.entries(a)){let r=t(window);r&&(n.tile_source_factory=r),a[e]=n}function o(e){if(!e)return{};try{return JSON.parse(e)}catch(e){return window.__smErrors.push(`harness-multi: bad options: `+e),{}}}async function s(e){let t=await fetch(`/examples/`+i[e]+`.json`);if(!t.ok)throw Error(`no example `+i[e]);return t.json()}var c=r.get(`shared`)===`1`?await s(`a`):null;try{let t=c??await s(`a`);window.__smA=new e(`sm-a`,t,a.a,{}),n(window.__smA,r.get(`record`),window,`A`);let i=c??await s(`b`);window.__smB=new e(`sm-b`,i,a.b,{}),n(window.__smB,r.get(`record`),window,`B`)}catch(e){window.__smErrors.push(`harness-multi: `+(e?.message??e))}window.__smReady=!0;
1
+ import"./modulepreload-polyfill-P2Xu9kJm.js";import{t as e}from"./main-DQ32hWBM.js";import{n as t,t as n}from"./harness-support-CjfZIVeb.js";window.__smErrors=[],window.__smReady=!1,window.addEventListener(`error`,e=>{window.__smErrors.push(String(e.message||e))});var r=new URLSearchParams(window.location.search),i={a:r.get(`a`)??`katrina`,b:r.get(`b`)??`seurat`},a={a:o(r.get(`aopts`)),b:o(r.get(`bopts`))};for(let[e,n]of Object.entries(a)){let r=t(window);r&&(n.tile_source_factory=r),a[e]=n}function o(e){if(!e)return{};try{return JSON.parse(e)}catch(e){return window.__smErrors.push(`harness-multi: bad options: `+e),{}}}async function s(e){let t=await fetch(`/examples/`+i[e]+`.json`);if(!t.ok)throw Error(`no example `+i[e]);return t.json()}var c=r.get(`shared`)===`1`?await s(`a`):null;try{let t=c??await s(`a`);window.__smA=new e(`sm-a`,t,a.a,{}),n(window.__smA,r.get(`record`),window,`A`);let i=c??await s(`b`);window.__smB=new e(`sm-b`,i,a.b,{}),n(window.__smB,r.get(`record`),window,`B`)}catch(e){window.__smErrors.push(`harness-multi: `+(e?.message??e))}window.__smReady=!0;
@@ -1 +1 @@
1
- function e(e,t,r,i=``){let a=(t??``).split(`,`).map(e=>e.trim()).filter(Boolean);if(a.length!==0){r.__events=r.__events??[];for(let t of a)e.on(t,e=>{r.__events.push({type:t,viewer:i,payload:n(e)})})}}function t(e){return typeof e.__tileSourceFactory==`function`?e.__tileSourceFactory:void 0}function n(e){if(typeof e!=`object`||!e)return e;let t={};for(let[a,o]of Object.entries(e))if(typeof o!=`object`||!o)t[a]=typeof o==`function`?`[fn]`:o;else if(a===`source`||a===`layer`)t[a]={type:o.constructor?.name??null,state:typeof o.getState==`function`?o.getState():void 0,url:o.url_??o.href_??r(o,`url`)};else if(a===`target`)t[a]=i(o);else if(Array.isArray(o))t[a]=o.map(e=>n(e));else{let e=Object.getPrototypeOf(o);t[a]=e===Object.prototype||e===null?n(o):{type:o.constructor?.name??null}}return t}function r(e,t){try{return e.get?.(t)}catch{return}}function i(e){let t=a(e)?e:a(e?._map)?e._map:null;if(!t)return{type:e?.constructor?.name??null};let n=t.getView(),r=t.getSize();return{type:e?.constructor?.name??null,size:r?[r[0],r[1]]:null,center:n?.getCenter?.()??null,resolution:n?.getResolution?.()??null,projection:n?.getProjection?.()?.getCode?.()??null}}function a(e){return!!e&&typeof e.getSize==`function`&&typeof e.getView==`function`}export{t as n,e as t};
1
+ function e(e,t,r,i=``){let a=(t??``).split(`,`).map(e=>e.trim()).filter(Boolean);if(a.length!==0){r.__events=r.__events??[];for(let t of a)e.on(t,e=>{r.__events.push({type:t,viewer:i,payload:n(e)})})}}function t(e){return typeof e.__tileSourceFactory==`function`?e.__tileSourceFactory:void 0}function n(e){if(typeof e!=`object`||!e)return e;let t={};for(let[a,o]of Object.entries(e))if(typeof o!=`object`||!o)t[a]=typeof o==`function`?`[fn]`:o;else if(a===`source`||a===`layer`)t[a]={type:o.constructor?.name??null,state:typeof o.getState==`function`?o.getState():void 0,url:o.url_??o.href_??r(o,`url`)};else if(a===`target`)t[a]=i(o);else if(Array.isArray(o))t[a]=o.map(e=>n(e));else{let e=Object.getPrototypeOf(o);t[a]=e===Object.prototype||e===null?n(o):{type:o.constructor?.name??null}}return t}function r(e,t){try{return e.get?.(t)}catch{return}}function i(e){let t=a(e)?e:a(e?.map)?e.map:a(e?._map)?e._map:null;if(!t)return{type:e?.constructor?.name??null};let n=t.getView(),r=t.getSize();return{type:e?.constructor?.name??null,size:r?[r[0],r[1]]:null,center:n?.getCenter?.()??null,resolution:n?.getResolution?.()??null,projection:n?.getProjection?.()?.getCode?.()??null}}function a(e){return!!e&&typeof e.getSize==`function`&&typeof e.getView==`function`}export{t as n,e as t};