@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
@@ -33,6 +33,9 @@ export function iiifSizedUrl(url: string | null, width: number): string | null {
33
33
  ================================================== */
34
34
 
35
35
  export default class Image extends Media {
36
+ /** The `load` handler on the image, stored so dispose() can detach it. */
37
+ declare "_onLoad": (() => void) | null;
38
+
36
39
  /* Load the media
37
40
  ================================================== */
38
41
  _loadMedia() {
@@ -61,10 +64,12 @@ export default class Image extends Media {
61
64
  );
62
65
  }
63
66
 
64
- // Media Loaded Event
65
- this._el.content_item.addEventListener("load", (e) => {
67
+ // Media Loaded Event. The reference is kept so dispose() can detach
68
+ // it: an inline arrow function has no other handle.
69
+ this._onLoad = () => {
66
70
  this.onMediaLoaded();
67
- });
71
+ };
72
+ this._el.content_item.addEventListener("load", this._onLoad);
68
73
 
69
74
  const img = this._el.content_item as HTMLImageElement;
70
75
  // offscreen/preloaded slides stay lazy (the active slide is
@@ -112,4 +117,11 @@ export default class Image extends Media {
112
117
  _updateMediaDisplay(layout?: string) {
113
118
  // modern browsers size media correctly without engine-specific fixes
114
119
  }
120
+
121
+ _disposeMedia(): void {
122
+ if (this._onLoad) {
123
+ this._el.content_item?.removeEventListener("load", this._onLoad);
124
+ this._onLoad = null;
125
+ }
126
+ }
115
127
  }
@@ -91,7 +91,7 @@ export default class SoundCloud extends Media {
91
91
  try {
92
92
  this.widget?.unbind?.();
93
93
  } catch (err) {
94
- console.log(err);
94
+ console.warn("StoryMapJS: error unbinding SoundCloud widget.", err);
95
95
  }
96
96
  this.widget = null;
97
97
  this.soundCloudCreated = false;
@@ -14,13 +14,21 @@ interface TextData {
14
14
  interface TextOptions {
15
15
  title?: boolean;
16
16
  text_align?: string;
17
+ /** BCP 47 tag for the slide text (from `StorymapSlide.language`). */
18
+ language?: string;
19
+ }
20
+
21
+ export interface TextEvents {
22
+ loaded: TextData;
23
+ added: TextData;
24
+ removed: TextData;
17
25
  }
18
26
 
19
27
  class TextBase {
20
28
  declare "_el": Record<string, HTMLElement>;
21
29
  declare "data": TextData;
22
30
  declare "options": TextOptions;
23
- declare "fire": EventedInstance["fire"];
31
+ declare "fire": EventedInstance<TextEvents>["fire"];
24
32
 
25
33
  /* Constructor
26
34
  ================================================== */
@@ -57,6 +65,15 @@ class TextBase {
57
65
 
58
66
  this._el.container = Dom.create("div", "vco-text");
59
67
  this._el.container.id = this.data.uniqueid ?? "";
68
+ // Pronounce mixed-language slides correctly: the reader reports
69
+ // which language map it picked, and the DOM should say so too.
70
+ if (
71
+ typeof this.options.language === "string" &&
72
+ this.options.language !== "" &&
73
+ this.options.language !== "none"
74
+ ) {
75
+ this._el.container.setAttribute("lang", this.options.language);
76
+ }
60
77
 
61
78
  this._initLayout();
62
79
 
@@ -124,12 +141,6 @@ class TextBase {
124
141
  this._el.content_container.classList.add("vco-text-align-" + align);
125
142
  }
126
143
 
127
- // Date (only rendered when the slide has one; issue #286)
128
- if (this.data.date && this.data.date.created_time && this.data.date.created_time !== "") {
129
- this._el.date = Dom.create("h3", "vco-headline-date", this._el.content_container);
130
- this.addDateText(convertUnixTime(this.data.date.created_time));
131
- }
132
-
133
144
  // Headline (sanitized; issue #358 keeps formatting but drops scripts)
134
145
  const headline = this.data.headline ?? "";
135
146
  if (headline !== "") {
@@ -141,6 +152,14 @@ class TextBase {
141
152
  this._el.headline.appendChild(sanitizeSlideText(headline));
142
153
  }
143
154
 
155
+ // Date (only rendered when the slide has one; issue #286). Emitted
156
+ // after the headline and as a <p> (issue #385 wave): an <h3> before
157
+ // the <h2> was a heading-order skip
158
+ if (this.data.date && this.data.date.created_time && this.data.date.created_time !== "") {
159
+ this._el.date = Dom.create("p", "vco-headline-date", this._el.content_container);
160
+ this.addDateText(convertUnixTime(this.data.date.created_time));
161
+ }
162
+
144
163
  // Text (sanitized; issue #358 allows extra iframe media in the text field)
145
164
  const text = this.data.text ?? "";
146
165
  if (text !== "") {
@@ -171,7 +190,7 @@ class TextBase {
171
190
  }
172
191
  }
173
192
 
174
- export default class Text extends Evented(TextBase) {
193
+ export default class Text extends Evented<TextEvents, typeof TextBase>(TextBase) {
175
194
  constructor(...args: ConstructorParameters<typeof TextBase>) {
176
195
  super(...args);
177
196
  }
@@ -1,6 +1,7 @@
1
1
  import { Media } from "../Media";
2
2
  import Dom from "../../dom/Dom";
3
3
  import { sanitizeSlideText } from "../EmbedUtil";
4
+ import { tweetAuthor, tweetId } from "../embedId";
4
5
  import { loadJSONP, uniqueGlobalName } from "../../core/Load";
5
6
 
6
7
  /* Media.Twitter
@@ -20,13 +21,14 @@ export default class Twitter extends Media {
20
21
  // Create Dom element
21
22
  this._el.content_item = Dom.create("div", "vco-media-twitter", this._el.content);
22
23
 
23
- // Get Media ID
24
-
25
- const r = /(?:twitter\.com|x\.com)\/(.+?)\/status\/(\d+)/;
26
- const match = r.exec(this._url());
27
- if (match) {
28
- this.user_id = match[1];
29
- this.media_id = match[2];
24
+ // Get Media ID — routed here by MediaType, extracted by the shared
25
+ // helpers so the accepted shapes cannot diverge again. The id stays
26
+ // numeric-only, as before: anything else is not a status URL.
27
+ const media_id = tweetId(this._url());
28
+ const user_id = tweetAuthor(this._url());
29
+ if (media_id && user_id && /^\d+$/.test(media_id)) {
30
+ this.user_id = user_id;
31
+ this.media_id = media_id;
30
32
  } else {
31
33
  throw new Error("Invalid Twitter URL");
32
34
  }
@@ -59,7 +59,7 @@ export default class Vimeo extends Media {
59
59
  "https://player.vimeo.com",
60
60
  );
61
61
  } catch (err) {
62
- console.log(err);
62
+ console.warn("StoryMapJS: error pausing Vimeo player.", err);
63
63
  }
64
64
  }
65
65
  }
@@ -94,7 +94,7 @@ export default class YouTube extends Media {
94
94
  this.player.pauseVideo();
95
95
  }
96
96
  } catch (err) {
97
- console.log(err);
97
+ console.warn("StoryMapJS: error pausing YouTube player.", err);
98
98
  }
99
99
  }
100
100
  }
@@ -186,7 +186,7 @@ export default class YouTube extends Media {
186
186
  try {
187
187
  this.player?.destroy?.();
188
188
  } catch (err) {
189
- console.log(err);
189
+ console.warn("StoryMapJS: error destroying YouTube player.", err);
190
190
  }
191
191
  }
192
192
  this.player = null;
@@ -5,11 +5,6 @@
5
5
  .vco-storymap {
6
6
  font-family: $font-main;
7
7
 
8
- // runtime-themable text colors (issue #177); set inline via the
9
- // text_color/text_background_color options
10
- --vco-color-text: #{$color-text};
11
- --vco-color-text-muted: #{color.adjust($color-dark, $lightness: 40%)};
12
-
13
8
  h1,
14
9
  h2,
15
10
  h3 {
@@ -56,15 +51,20 @@
56
51
  font-size: $base-font-size;
57
52
 
58
53
  // font-family: $font-secondary;
59
- color: var(--vco-color-text-muted);
54
+ color: var(--vco-color-text-muted, #{color.adjust($color-dark, $lightness: 40%)});
60
55
 
61
56
  a {
62
- color: var(--vco-color-text-muted);
57
+ color: var(--vco-color-text-muted, #{color.adjust($color-dark, $lightness: 40%)});
63
58
  text-decoration: none;
64
59
  background-image: linear-gradient(
65
60
  to bottom,
66
- rgba(color.adjust($color-dark, $lightness: 40%), 0%) 50%,
67
- rgba(color.adjust($color-dark, $lightness: 40%), 60%) 50%
61
+ rgba(var(--vco-color-text-muted, #{color.adjust($color-dark, $lightness: 40%)}), 0%)
62
+ 50%,
63
+ rgba(
64
+ var(--vco-color-text-muted, #{color.adjust($color-dark, $lightness: 40%)}),
65
+ 60%
66
+ )
67
+ 50%
68
68
  );
69
69
  background-repeat: repeat-x;
70
70
  background-size: 2px 2px;
@@ -77,7 +77,7 @@
77
77
 
78
78
  &:hover,
79
79
  &:focus {
80
- color: $color-theme;
80
+ color: var(--vco-color-theme, #{$color-theme});
81
81
  text-decoration: none;
82
82
  }
83
83
  }
@@ -90,7 +90,7 @@
90
90
 
91
91
  &:hover,
92
92
  &:focus {
93
- color: $color-theme;
93
+ color: var(--vco-color-theme, #{$color-theme});
94
94
  text-decoration: underline;
95
95
  }
96
96
  }
@@ -111,12 +111,12 @@
111
111
 
112
112
  a {
113
113
  text-decoration: none;
114
- color: $color-theme;
114
+ color: var(--vco-color-theme, #{$color-theme});
115
115
  }
116
116
 
117
117
  a:hover {
118
118
  text-decoration: underline;
119
- color: color.adjust($color-theme, $lightness: -20%);
119
+ color: var(--vco-color-theme-deep, #{color.adjust($color-theme, $lightness: -20%)});
120
120
  }
121
121
 
122
122
  .vco-caption,
@@ -128,14 +128,14 @@
128
128
  line-height: $base-font-size-small;
129
129
 
130
130
  a {
131
- color: $color-dark;
131
+ color: var(--vco-color-dark, #{$color-dark});
132
132
  }
133
133
  }
134
134
 
135
135
  blockquote,
136
136
  blockquote p {
137
137
  font-family: $font-serif;
138
- color: color.adjust($color-dark, $lightness: 60%);
138
+ color: var(--vco-color-text, #{$color-text});
139
139
  text-align: left;
140
140
  background: transparent;
141
141
  border: 0;
@@ -11,6 +11,7 @@
11
11
 
12
12
  /* Includes
13
13
  ================================================== */
14
+ @use "sass:color";
14
15
  @use "Variables" as *; // Variables (easy way to make style changes)
15
16
  @use "core/Mixins" as *; // Less shortcuts and functions
16
17
  @use "core/Reset" as *; // CSS Reset
@@ -22,6 +23,7 @@
22
23
  // Components
23
24
  @use "ui/VCO.MenuBar" as *;
24
25
  @use "ui/VCO.MenuBar.Button" as *;
26
+ @use "ui/VCO.Layers" as *;
25
27
  @use "ui/VCO.Message" as *;
26
28
  @use "ui/VCO.Consent" as *;
27
29
  @use "slider/VCO.Slide" as *;
@@ -51,11 +53,60 @@
51
53
  position: relative;
52
54
  user-select: none;
53
55
 
56
+ // Theme palette (dark theme plan): every Variables.scss colour as a
57
+ // custom property, so a dark theme redeclares values instead of
58
+ // shipping a second stylesheet. Unused declarations cost nothing, and
59
+ // use sites keep their Sass value as the fallback, so each partial
60
+ // migrates independently. Includes the two runtime-themable text
61
+ // colors (issue #177), set inline via text_color/text_background_color.
62
+ --vco-color-background: #{$color-background};
63
+ --vco-color-foreground: #{$color-foreground};
64
+ --vco-color-dark: #{$color-dark};
65
+ --vco-color-theme: #{$color-theme};
66
+ --vco-color-theme-dark: #{$color-theme-dark};
67
+ --vco-color-text: #{$color-text};
68
+ --vco-color-text-muted: #{color.adjust($color-dark, $lightness: 40%)};
69
+ --vco-color-text-background: #{$color-background};
70
+ --vco-color-header-text: #{$color-header-text};
71
+ --vco-color-text-inverted: #{$color-text-inverted};
72
+ --vco-color-header-text-inverted: #{$color-header-text-inverted};
73
+ --vco-color-ui-background: #{$ui-background-color};
74
+
75
+ // Derived tones. Each is computed from the light palette here so the
76
+ // default render is unchanged; the dark block redefines them from its
77
+ // own palette. One property per distinct derivation, shared by every
78
+ // use site with the same arguments.
79
+ --vco-color-border: #{color.adjust($color-background, $lightness: -10%)};
80
+ --vco-color-hover: #{color.adjust($color-background, $lightness: -5%)};
81
+ --vco-color-nav-border: #{color.adjust($color-background, $lightness: -30%)};
82
+ --vco-color-theme-faint: #{color.adjust($color-theme, $lightness: 90%)};
83
+ --vco-color-theme-deep: #{color.adjust($color-theme, $lightness: -20%)};
84
+ --vco-color-theme-bright: #{color.adjust($color-theme, $lightness: 15%)};
85
+ --vco-color-link: #{color.adjust($color-dark, $lightness: 20%)};
86
+ --vco-color-ui-fade: #{fadeout($ui-background-color, 10%)};
87
+ --vco-color-ui-faint: #{fadeout($ui-background-color, 33%)};
88
+
89
+ /* Skip link: first tab stop inside the widget, off-screen until
90
+ focused, so keyboard visitors can jump past the chrome. */
91
+ .vco-skip-link {
92
+ position: absolute;
93
+ top: -999px;
94
+ left: 8px;
95
+ z-index: 100;
96
+ padding: 8px 12px;
97
+ background-color: var(--vco-color-background, #{$color-background});
98
+ color: var(--vco-color-text, #{$color-text});
99
+
100
+ &:focus-visible {
101
+ top: 8px;
102
+ }
103
+ }
104
+
54
105
  /* fullscreen: fill the screen and sit above everything else */
55
106
  &:fullscreen {
56
107
  width: 100%;
57
108
  height: 100%;
58
- background-color: $color-background;
109
+ background-color: var(--vco-color-background, #{$color-background});
59
110
  }
60
111
 
61
112
  *,
@@ -65,6 +116,52 @@
65
116
  }
66
117
  }
67
118
 
119
+ /* Dark theme (dark theme plan): the same property names redeclared from
120
+ the dark palette. Values below were carried over from
121
+ `src/scss/Variables.Dark.scss` (now deleted), except `$color-theme`
122
+ (`#c34528` is 3.18:1 on `#222`) and `$color-text` (`#808080` is 4.03:1),
123
+ which are lightened to `#dd735a` (5.06:1) and `#949494` (5.24:1) so
124
+ every text pair clears AA. Hover tints go brighter in dark mode rather
125
+ than darker, so they stay visible on dark surfaces.
126
+ Selected with `data-vco-theme="dark"`, or (when unset) the OS
127
+ `prefers-color-scheme`; explicit `"light"` pins the light palette. */
128
+ @mixin vco-dark-palette {
129
+ --vco-color-background: #222;
130
+ --vco-color-foreground: #ccc;
131
+ --vco-color-dark: #fff;
132
+ --vco-color-theme: #dd735a;
133
+ --vco-color-theme-dark: #c24628;
134
+ --vco-color-text: #949494;
135
+ --vco-color-text-muted: #999;
136
+ --vco-color-text-background: #222;
137
+ --vco-color-header-text: #fff;
138
+ --vco-color-text-inverted: #bfbfbf;
139
+ --vco-color-header-text-inverted: #fff;
140
+ --vco-color-ui-background: #4d4d4d;
141
+ --vco-color-border: #080808;
142
+ --vco-color-hover: #151515;
143
+ --vco-color-nav-border: #000;
144
+ --vco-color-theme-faint: #fff;
145
+
146
+ // the three link colours share #eaa999 on purpose: in dark mode links,
147
+ // hovers and deep accents are one tone (5.06:1 on #222)
148
+ --vco-color-theme-deep: #eaa999;
149
+ --vco-color-theme-bright: #eaa999;
150
+ --vco-color-link: #eaa999;
151
+ --vco-color-ui-fade: #{fadeout(#4d4d4d, 10%)};
152
+ --vco-color-ui-faint: #{fadeout(#4d4d4d, 33%)};
153
+ }
154
+
155
+ .vco-storymap[data-vco-theme="dark"] {
156
+ @include vco-dark-palette;
157
+ }
158
+
159
+ @media (prefers-color-scheme: dark) {
160
+ .vco-storymap:not([data-vco-theme="light"]) {
161
+ @include vco-dark-palette;
162
+ }
163
+ }
164
+
68
165
  /* Portrait
69
166
  ================================================== */
70
167
  .vco-layout-portrait {
@@ -89,12 +186,14 @@
89
186
  width: 120%;
90
187
  overflow: hidden;
91
188
 
92
- @include gradient-horizontal(
93
- rgba(255, 255, 255, 0.0001),
94
- rgba(255, 255, 255, 0.85),
95
- 0%,
96
- 15%
189
+ // map-to-panel fade in the panel color so it stays seamless in
190
+ // both themes (the old white gradient washed dark slides)
191
+ background-image: linear-gradient(
192
+ to right,
193
+ transparent 0%,
194
+ var(--vco-color-background, #fff) 15%
97
195
  );
196
+ background-repeat: repeat-x;
98
197
  }
99
198
 
100
199
  .vco-slider-container-mask {
@@ -116,7 +215,7 @@
116
215
  top: 0;
117
216
  left: 0;
118
217
  width: 100%;
119
- background-color: $color-background;
218
+ background-color: var(--vco-color-background, #{$color-background});
120
219
 
121
220
  .vco-slider-background {
122
221
  display: none;
@@ -130,7 +229,7 @@
130
229
  ================================================== */
131
230
  .vco-map-area-left {
132
231
  .vco-storyslider {
133
- background-color: $color-background;
232
+ background-color: var(--vco-color-background, #{$color-background});
134
233
  }
135
234
 
136
235
  .vco-slider-background {
@@ -193,3 +292,69 @@
193
292
  white-space: nowrap;
194
293
  border: 0;
195
294
  }
295
+
296
+ /* Slide panel dock + size (textmode/textsize): right is the long-standing
297
+ landscape layout; left mirrors it; bottom docks the panel below the map
298
+ (the component split is computed in StoryMap._updateDisplay). Unset means
299
+ right at 50/50, so existing stories render unchanged.
300
+ ================================================== */
301
+ .vco-layout-landscape {
302
+ .vco-storyslider {
303
+ width: var(--vco-panel-size, 50%);
304
+ left: calc(100% - var(--vco-panel-size, 50%));
305
+ }
306
+
307
+ &.vco-textmode-left {
308
+ .vco-storyslider {
309
+ left: 0;
310
+
311
+ .vco-slider-background {
312
+ margin-left: 0;
313
+ width: 120%;
314
+ background-image: linear-gradient(
315
+ to left,
316
+ transparent 0%,
317
+ var(--vco-color-background, #fff) 15%
318
+ );
319
+ background-repeat: repeat-x;
320
+ }
321
+ }
322
+ }
323
+
324
+ &.vco-textmode-bottom {
325
+ .vco-storyslider {
326
+ left: 0;
327
+ width: 100%;
328
+
329
+ .vco-slider-background {
330
+ margin-left: 0;
331
+ width: 100%;
332
+ background-image: linear-gradient(
333
+ to top,
334
+ transparent 0%,
335
+ var(--vco-color-background, #fff) 15%
336
+ );
337
+ background-repeat: repeat-x;
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ /* Headline visibility (show_headings: false hides slide headlines) */
344
+ .vco-no-headings {
345
+ .vco-headline {
346
+ display: none;
347
+ }
348
+ }
349
+
350
+ /* Scrollbar visibility (show_scrollbars: false hides the panel scrollbars;
351
+ content still scrolls) */
352
+ .vco-no-scrollbars {
353
+ .vco-slide {
354
+ scrollbar-width: none;
355
+
356
+ &::-webkit-scrollbar {
357
+ display: none;
358
+ }
359
+ }
360
+ }
@@ -1,16 +1,13 @@
1
1
  /* Reset
2
2
  ------------------------------------------------------------------------------------------- */
3
+ @use "../Variables" as *;
3
4
 
4
- .vco-storyjs {
5
+ .vco-storymap {
5
6
  /* Reset tags and common classes
6
7
  Display in IE6-9 and FF3
7
8
  Based on: http://github.com/necolas/normalize.css
8
9
  ------------------------------------------------------------------------------------------- */
9
10
 
10
- div * {
11
- box-sizing: content-box;
12
- }
13
-
14
11
  h1,
15
12
  h2,
16
13
  h3,
@@ -91,7 +88,6 @@
91
88
  font-weight: normal;
92
89
  font-style: normal;
93
90
  font-size: 100%;
94
- line-height: 1;
95
91
  font-family: inherit;
96
92
  width: auto;
97
93
  float: none;
@@ -111,11 +107,6 @@
111
107
  border-spacing: 0;
112
108
  }
113
109
 
114
- ol,
115
- ul {
116
- list-style: none;
117
- }
118
-
119
110
  q::before,
120
111
  q::after,
121
112
  blockquote::before,
@@ -123,13 +114,9 @@
123
114
  content: "";
124
115
  }
125
116
 
126
- a:focus {
127
- outline: thin dotted;
128
- }
129
-
130
- a:hover,
131
- a:active {
132
- outline: 0;
117
+ a:focus-visible {
118
+ outline: 2px solid var(--vco-color-dark, #{$color-dark});
119
+ outline-offset: 2px;
133
120
  }
134
121
 
135
122
  article,
@@ -6,7 +6,7 @@
6
6
  height: 100%;
7
7
  overflow: hidden;
8
8
  top: 0;
9
- background-color: $color-foreground;
9
+ background-color: var(--vco-color-foreground, #{$color-foreground});
10
10
 
11
11
  .vco-map-mask {
12
12
  width: 100%;
@@ -46,7 +46,18 @@ $map-icon-width: 38px;
46
46
  }
47
47
 
48
48
  .vco-mapmarker-active {
49
- color: $color-theme;
49
+ color: var(--vco-color-theme, #{$color-theme});
50
+ }
51
+
52
+ // pins are keyboard-focusable (tabindex + role=button): every marker
53
+ // shape gets the same visible ring
54
+ .vco-mapmarker:focus-visible,
55
+ .vco-mapmarker-active:focus-visible,
56
+ .vco-mapmarker-custom:focus-visible,
57
+ .vco-mapmarker-image-icon:focus-visible,
58
+ .vco-mapmarker-image-icon-active:focus-visible {
59
+ outline: 2px solid var(--vco-color-dark, #{$color-dark});
60
+ outline-offset: 2px;
50
61
  }
51
62
 
52
63
  // Custom icon markers render only their image (no vco-mapmarker class,
@@ -72,7 +83,7 @@ $map-icon-width: 38px;
72
83
  }
73
84
 
74
85
  .vco-mapmarker-image-icon-active {
75
- border: 4px solid $color-theme;
86
+ border: 4px solid var(--vco-color-theme, #{$color-theme});
76
87
  filter: grayscale(0);
77
88
  }
78
89
 
@@ -89,9 +100,9 @@ $map-icon-width: 38px;
89
100
  font-family: $font-sanserif;
90
101
  font-size: 12px;
91
102
  font-weight: bold;
92
- color: $color-text;
93
- background-color: $color-background;
94
- border: 1px solid $color-theme;
103
+ color: var(--vco-color-text, #{$color-text});
104
+ background-color: var(--vco-color-background, #{$color-background});
105
+ border: 1px solid var(--vco-color-theme, #{$color-theme});
95
106
  border-radius: 4px;
96
107
  box-shadow: 2px 2px 2px rgb(0 0 0 / 30%);
97
108
  pointer-events: none;
@@ -145,7 +156,7 @@ $map-icon-width: 38px;
145
156
  }
146
157
  }
147
158
 
148
- /* Marker popup and audio badge (docs/plans/iiif-media-tours.md §2)
159
+ /* Marker popup and audio badge
149
160
  ================================================== */
150
161
  // A card anchored to the marker element: it is a DOM child, so it travels
151
162
  // with the pin and needs no positioning maths. 44px tap targets and a
@@ -163,7 +174,7 @@ $map-icon-width: 38px;
163
174
  gap: 8px;
164
175
  align-items: flex-start;
165
176
  width: max-content;
166
- max-width: 260px;
177
+ max-width: min(260px, calc(100vw - 32px));
167
178
  padding: 10px 12px;
168
179
  border: 1px solid rgb(0 0 0 / 20%);
169
180
  border-radius: 4px;
@@ -178,8 +189,8 @@ $map-icon-width: 38px;
178
189
 
179
190
  .vco-marker-popup-close {
180
191
  flex: 0 0 auto;
181
- min-width: 28px;
182
- min-height: 28px;
192
+ min-width: 44px;
193
+ min-height: 44px;
183
194
  margin: -4px -4px 0 0;
184
195
  padding: 0;
185
196
  border: 0;
@@ -33,13 +33,13 @@
33
33
  position: absolute;
34
34
  bottom: 0;
35
35
  right: 0;
36
- background-color: rgb(255 255 255 / 75%);
36
+ background-color: rgb(255 255 255 / 90%);
37
37
  font-size: 10px;
38
38
  padding: 1px 4px;
39
39
  z-index: 1000;
40
40
 
41
41
  a {
42
- color: #666;
42
+ color: #333;
43
43
  text-decoration: none;
44
44
  }
45
45
 
@@ -64,14 +64,17 @@
64
64
  }
65
65
 
66
66
  span {
67
- color: $color-theme;
67
+ color: var(--vco-color-theme, #{$color-theme});
68
68
  display: inline-block;
69
69
  transform: rotate(45deg);
70
70
  }
71
71
 
72
72
  &:hover {
73
73
  text-decoration: none;
74
- color: color.adjust($color-theme, $lightness: 15%);
74
+ color: var(
75
+ --vco-color-theme-bright,
76
+ #{color.adjust($color-theme, $lightness: 15%)}
77
+ );
75
78
  }
76
79
  }
77
80
  }