@projektemacher/storymapjs 0.10.6 → 0.10.7

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 (148) hide show
  1. package/CHANGELOG +11 -0
  2. package/dist/assets/{demo-CVQ7kFek.js → demo-D6cTmzn6.js} +1 -1
  3. package/dist/assets/{harness-C-VZV-ou.js → harness-BPFlzutp.js} +1 -1
  4. package/dist/assets/main-Y-MYUSJ4.js +17 -0
  5. package/dist/css/fonts/font.oldstandard.css +18 -22
  6. package/dist/demo.html +2 -2
  7. package/dist/docs/migration.html +1 -1
  8. package/dist/embed/index.html +129 -4
  9. package/dist/harness.html +2 -2
  10. package/dist/index.html +17 -0
  11. package/dist/js/storymap.d.ts +126 -7
  12. package/dist/js/storymap.js +4518 -3613
  13. package/dist/js/storymap.js.map +1 -1
  14. package/docs/migration-from-knightlab.md +139 -0
  15. package/docs/storymap-as-iiif-manifest.md +510 -0
  16. package/package.json +6 -3
  17. package/src/animation/easings.ts +18 -0
  18. package/src/animation/morpheus.d.ts +41 -0
  19. package/src/core/Browser.ts +19 -0
  20. package/src/core/Load.ts +180 -0
  21. package/src/core/Util.ts +354 -0
  22. package/src/core/mixins.ts +171 -0
  23. package/src/dom/Dom.ts +31 -0
  24. package/src/dom/DomEvent.ts +61 -0
  25. package/src/globals.d.ts +16 -0
  26. package/src/language/Language.ts +107 -0
  27. package/src/language/locale/be.json +18 -0
  28. package/src/language/locale/bg.json +18 -0
  29. package/src/language/locale/cs.json +18 -0
  30. package/src/language/locale/de.json +20 -0
  31. package/src/language/locale/el.json +18 -0
  32. package/src/language/locale/en.json +28 -0
  33. package/src/language/locale/es.json +18 -0
  34. package/src/language/locale/et.json +18 -0
  35. package/src/language/locale/fr.json +18 -0
  36. package/src/language/locale/he.json +18 -0
  37. package/src/language/locale/hu.json +18 -0
  38. package/src/language/locale/is.json +18 -0
  39. package/src/language/locale/it.json +18 -0
  40. package/src/language/locale/jp.json +18 -0
  41. package/src/language/locale/ko.json +18 -0
  42. package/src/language/locale/nl.json +18 -0
  43. package/src/language/locale/nn.json +18 -0
  44. package/src/language/locale/no.json +18 -0
  45. package/src/language/locale/pl.json +18 -0
  46. package/src/language/locale/pt.json +18 -0
  47. package/src/language/locale/ru.json +18 -0
  48. package/src/language/locale/sk.json +18 -0
  49. package/src/language/locale/sr.json +17 -0
  50. package/src/language/locale/sv.json +18 -0
  51. package/src/language/locale/tr.json +18 -0
  52. package/src/language/locale/uk.json +18 -0
  53. package/src/language/locale/ur.json +18 -0
  54. package/src/language/locale/zh-cn.json +18 -0
  55. package/src/language/locale/zh-tw.json +18 -0
  56. package/src/main.ts +51 -0
  57. package/src/map/Map.ts +717 -0
  58. package/src/map/MapMarker.ts +149 -0
  59. package/src/map/openlayers/Map.OpenLayers.ts +1879 -0
  60. package/src/map/openlayers/MapMarker.OpenLayers.ts +176 -0
  61. package/src/map/openlayers/zoomifyTiles.ts +28 -0
  62. package/src/map/types.ts +17 -0
  63. package/src/media/EmbedUtil.ts +168 -0
  64. package/src/media/Media.ts +409 -0
  65. package/src/media/MediaType.ts +163 -0
  66. package/src/media/types/Audio.ts +87 -0
  67. package/src/media/types/Blockquote.ts +40 -0
  68. package/src/media/types/DailyMotion.ts +58 -0
  69. package/src/media/types/DocumentCloud.ts +41 -0
  70. package/src/media/types/Facebook.ts +41 -0
  71. package/src/media/types/Flickr.ts +115 -0
  72. package/src/media/types/GoogleDoc.ts +59 -0
  73. package/src/media/types/IFrame.ts +51 -0
  74. package/src/media/types/Image.ts +49 -0
  75. package/src/media/types/Juxtapose.ts +32 -0
  76. package/src/media/types/SoundCloud.ts +77 -0
  77. package/src/media/types/Text.ts +172 -0
  78. package/src/media/types/Twitter.ts +96 -0
  79. package/src/media/types/Video.ts +84 -0
  80. package/src/media/types/Vimeo.ts +65 -0
  81. package/src/media/types/Website.ts +36 -0
  82. package/src/media/types/Wikipedia.ts +124 -0
  83. package/src/media/types/YouTube.ts +192 -0
  84. package/src/scss/Typography.scss +237 -0
  85. package/src/scss/VCO.StoryMap.Dark.scss +138 -0
  86. package/src/scss/VCO.StoryMap.scss +164 -0
  87. package/src/scss/Variables.Dark.scss +56 -0
  88. package/src/scss/Variables.scss +47 -0
  89. package/src/scss/core/Mixins.scss +112 -0
  90. package/src/scss/core/Reset.scss +206 -0
  91. package/src/scss/fonts/_font.base.scss +113 -0
  92. package/src/scss/fonts/font.abril-droidsans.scss +75 -0
  93. package/src/scss/fonts/font.amatic-andika.scss +73 -0
  94. package/src/scss/fonts/font.bitter-raleway.scss +86 -0
  95. package/src/scss/fonts/font.clicker-garamond.scss +92 -0
  96. package/src/scss/fonts/font.dancing-ledger.scss +74 -0
  97. package/src/scss/fonts/font.default.scss +9 -0
  98. package/src/scss/fonts/font.fjalla-average.scss +80 -0
  99. package/src/scss/fonts/font.georgia-helvetica.scss +58 -0
  100. package/src/scss/fonts/font.knightlab.scss +97 -0
  101. package/src/scss/fonts/font.lustria-lato.scss +80 -0
  102. package/src/scss/fonts/font.medula-lato.scss +73 -0
  103. package/src/scss/fonts/font.oldstandard.scss +88 -0
  104. package/src/scss/fonts/font.opensans-gentiumbook.scss +80 -0
  105. package/src/scss/fonts/font.playfair-faunaone.scss +86 -0
  106. package/src/scss/fonts/font.playfair.scss +85 -0
  107. package/src/scss/fonts/font.pt.scss +90 -0
  108. package/src/scss/fonts/font.roboto-megrim.scss +73 -0
  109. package/src/scss/fonts/font.rufina-sintony.scss +73 -0
  110. package/src/scss/fonts/font.ubuntu.scss +78 -0
  111. package/src/scss/fonts/font.unicaone-vollkorn.scss +85 -0
  112. package/src/scss/icons/Icons.scss +239 -0
  113. package/src/scss/map/VCO.ImageMarker.scss +23 -0
  114. package/src/scss/map/VCO.Map.scss +21 -0
  115. package/src/scss/map/VCO.MapMarker.scss +147 -0
  116. package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +126 -0
  117. package/src/scss/media/VCO.Media.scss +222 -0
  118. package/src/scss/media/types/VCO.Media.Blockquote.scss +76 -0
  119. package/src/scss/media/types/VCO.Media.IFrame.scss +11 -0
  120. package/src/scss/media/types/VCO.Media.Image.scss +13 -0
  121. package/src/scss/media/types/VCO.Media.Instagram.scss +7 -0
  122. package/src/scss/media/types/VCO.Media.Profile.scss +5 -0
  123. package/src/scss/media/types/VCO.Media.Text.scss +90 -0
  124. package/src/scss/media/types/VCO.Media.Twitter.scss +87 -0
  125. package/src/scss/media/types/VCO.Media.Wikipedia.scss +64 -0
  126. package/src/scss/site/site.scss +748 -0
  127. package/src/scss/slider/VCO.Slide.scss +447 -0
  128. package/src/scss/slider/VCO.SlideNav.scss +338 -0
  129. package/src/scss/slider/VCO.StorySlider.scss +64 -0
  130. package/src/scss/snapmap/VCO.SnapMap.scss +103 -0
  131. package/src/scss/ui/VCO.Consent.scss +77 -0
  132. package/src/scss/ui/VCO.MenuBar.Button.scss +99 -0
  133. package/src/scss/ui/VCO.MenuBar.scss +20 -0
  134. package/src/scss/ui/VCO.Message.scss +104 -0
  135. package/src/site/site.ts +87 -0
  136. package/src/slider/Slide.ts +460 -0
  137. package/src/slider/SlideNav.ts +212 -0
  138. package/src/slider/StorySlider.ts +722 -0
  139. package/src/storymap/Consent.ts +146 -0
  140. package/src/storymap/StoryMap.ts +1108 -0
  141. package/src/storymap/iiif.ts +386 -0
  142. package/src/storymap/validate.ts +184 -0
  143. package/src/types.ts +297 -0
  144. package/src/ui/Draggable.ts +374 -0
  145. package/src/ui/MenuBar.ts +323 -0
  146. package/src/ui/Message.ts +118 -0
  147. package/src/ui/Swipable.ts +388 -0
  148. package/dist/assets/main-DwbXrrYn.js +0 -17
@@ -0,0 +1,104 @@
1
+ @use "../core/Mixins" as *;
2
+
3
+ /* MESSAGE
4
+ ================================================== */
5
+ .vco-message,
6
+ .vco-message-full {
7
+ width: 100%;
8
+
9
+ // max-width: 200px;
10
+ height: 100%;
11
+ position: absolute;
12
+
13
+ // position: relative;
14
+
15
+ display: table;
16
+ overflow: hidden;
17
+ top: 0;
18
+ left: 0;
19
+ z-index: 99;
20
+ margin: auto;
21
+ text-align: center;
22
+
23
+ .vco-message-container {
24
+ padding: 20px;
25
+ margin: 20px;
26
+ text-align: center;
27
+ vertical-align: middle;
28
+ display: table-cell;
29
+
30
+ .vco-message-content {
31
+ color: #666;
32
+ text-align: center;
33
+ font-size: 11px;
34
+ line-height: 13px;
35
+ text-transform: uppercase;
36
+ margin-top: 7.5px;
37
+ margin-bottom: 7.5px;
38
+ text-shadow: 1px 1px 1px #fff;
39
+ }
40
+
41
+ .vco-loading-icon {
42
+ width: 30px;
43
+ height: 30px;
44
+ background-color: #666;
45
+ vertical-align: middle;
46
+
47
+ @include box-shadow(inset 0 1px 2px rgba(255, 255, 255, 0.1));
48
+
49
+ margin-left: auto;
50
+ margin-right: auto;
51
+
52
+ // margin: 100px auto;
53
+ animation: rotateplane 1.2s infinite ease-in-out;
54
+ }
55
+
56
+ @keyframes rotateplane {
57
+ 0% {
58
+ transform: perspective(120px) rotateX(0deg) rotateY(0deg);
59
+ }
60
+
61
+ 50% {
62
+ transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
63
+ }
64
+
65
+ 100% {
66
+ transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ .vco-message-full {
73
+ @include translucent-background(#333, 0.8);
74
+
75
+ [class^="vco-icon-"],
76
+ [class*=" vco-icon-"] {
77
+ // width: 48px;
78
+ // height: 48px;
79
+ color: #fff;
80
+ font-size: 72px;
81
+ }
82
+
83
+ .vco-message-container {
84
+ .vco-message-content {
85
+ font-size: 22px;
86
+ line-height: 22px;
87
+ text-shadow: none;
88
+ color: #fff;
89
+ text-transform: none;
90
+ font-weight: bold;
91
+
92
+ .vco-button {
93
+ display: inline-block;
94
+ cursor: pointer;
95
+ background-color: #fff;
96
+ color: #333;
97
+ padding: 10px;
98
+ margin-top: 10px;
99
+
100
+ @include border-radius(7px);
101
+ }
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,87 @@
1
+ /* Example cards for the landing page. Curated, non-`issue-*` fixtures from
2
+ * public/examples plus remote showcase examples; each card opens the storymap
3
+ * in the built embed player. The page chrome is site.css (compiled from
4
+ * src/scss/site/site.scss by the sitegen vite plugin) — this script only adds
5
+ * the cards and the demo height adjustment. */
6
+
7
+ interface ExampleEntry {
8
+ /** Local fixture name (public/examples/<id>.json), or a remote storymap JSON url */
9
+ id?: string;
10
+ url?: string;
11
+ /** Thumbnail name under thumbs/ (defaults to the fixture id) */
12
+ thumb?: string;
13
+ title: string;
14
+ kind: string;
15
+ }
16
+
17
+ const EXAMPLES: ExampleEntry[] = [
18
+ { id: "katrina", title: "Hurricane Katrina", kind: "Map" },
19
+ { id: "population", title: "US Population Shifts", kind: "Map" },
20
+ { id: "marktwain", title: "Mark Twain's Travels", kind: "Map" },
21
+ { id: "seurat", title: "A Sunday on La Grande Jatte", kind: "Gigapixel" },
22
+ { id: "iiif-wellcome", title: "Wellcome Collection (IIIF)", kind: "IIIF" },
23
+ { id: "president", title: "A Month in the Life of President Obama", kind: "Map" },
24
+ // remote showcase examples, rendered by the embed player
25
+ {
26
+ url: "https://s3.amazonaws.com/uploads.knightlab.com/storymapjs/a1a349b51799ee49e96bed10cc235e7f/garden-of-earthly-delights/published.json",
27
+ thumb: "bosch-garden",
28
+ title: "The Garden of Earthly Delights – Hieronymus Bosch",
29
+ kind: "Knight Lab example",
30
+ },
31
+ {
32
+ url: "https://uploads.knightlab.com/storymapjs/3df56e350378e51781746bfb2a2ea428/test/published.json",
33
+ thumb: "southern-literary-trail",
34
+ title: "Southern Literary Trail",
35
+ kind: "Georgia Humanities",
36
+ },
37
+ ];
38
+
39
+ function card(ex: ExampleEntry): HTMLAnchorElement {
40
+ const target = ex.url ?? "examples/" + ex.id + ".json";
41
+ const thumbId = ex.thumb ?? ex.id ?? "";
42
+
43
+ const a = document.createElement("a");
44
+ a.className = "card";
45
+ a.href = `./embed/index.html?url=${encodeURIComponent(target)}`;
46
+
47
+ const header = document.createElement("span");
48
+ header.className = "header-image";
49
+ const media = document.createElement("span");
50
+ media.className = "header-image-background bw";
51
+ // build-time screenshots (tasks/build-thumbnails.mjs); fall back to a
52
+ // deterministic two-tone gradient when a thumb is missing
53
+ media.style.backgroundImage = `url('./thumbs/${thumbId}.jpg'), linear-gradient(135deg, #b8b8b8, #646464)`;
54
+ media.setAttribute("role", "img");
55
+ media.setAttribute("aria-label", ex.title);
56
+ header.append(media);
57
+
58
+ const body = document.createElement("article");
59
+ body.className = "card-content";
60
+ const title = document.createElement("h3");
61
+ title.textContent = ex.title;
62
+ body.append(title);
63
+
64
+ const footer = document.createElement("footer");
65
+ footer.textContent = ex.kind;
66
+
67
+ a.append(header, body, footer);
68
+ return a;
69
+ }
70
+
71
+ const target = document.getElementById("examples-cards");
72
+ if (target) {
73
+ target.classList.add("cards-link");
74
+ for (const ex of EXAMPLES) {
75
+ target.append(card(ex));
76
+ }
77
+ }
78
+
79
+ // the original sizes the homepage demo to the window height minus 20px
80
+ const frame = document.getElementById("demo-frame") as HTMLIFrameElement | null;
81
+ if (frame) {
82
+ const size = () => {
83
+ frame.style.height = window.innerHeight - 20 + "px";
84
+ };
85
+ size();
86
+ window.addEventListener("resize", size);
87
+ }
@@ -0,0 +1,460 @@
1
+ import { mergeData } from "../core/Util";
2
+ import { DomMixed, Evented, type EventedInstance } from "../core/mixins";
3
+ import { DomEvent } from "../dom/DomEvent";
4
+ import Dom from "../dom/Dom";
5
+ import { easeInSpline } from "../animation/easings";
6
+ import MediaType from "../media/MediaType";
7
+ import Text from "../media/types/Text";
8
+ import { Browser } from "../core/Browser";
9
+ import { MediaTypeMatch, StorymapSlide, StorymapSlideBackground } from "../types";
10
+ /* VCO.Slide
11
+ Creates a slide. Takes a data object and
12
+ populates the slide with content.
13
+ ================================================== */
14
+
15
+ /* Slide options: the fields Slide itself sets or reads; everything
16
+ else merged in from the StorySlider options is absorbed by the
17
+ index signature. */
18
+ interface SlideOptions {
19
+ duration: number;
20
+ slide_padding_lr: number;
21
+ ease: unknown;
22
+ width: number;
23
+ height: number;
24
+ skinny_size: number;
25
+ media_name?: string;
26
+ media_type?: string;
27
+ [key: string]: unknown;
28
+ }
29
+
30
+ /* Media instance surface as used by Slide; instances are created
31
+ dynamically via the matched MediaTypeMatch.cls constructor. Media is
32
+ Evented — "media_loaded" signals that content heights may have changed. */
33
+ interface MediaInstance {
34
+ addTo: (container: HTMLElement) => void;
35
+ loadMedia: () => void;
36
+ stopMedia: () => void;
37
+ updateDisplay: (w?: number, h?: number, l?: string) => void;
38
+ on?: EventedInstance["on"];
39
+ _state?: { loaded?: boolean };
40
+ }
41
+
42
+ interface SlideHas {
43
+ headline: boolean;
44
+ text: boolean;
45
+ media: boolean;
46
+ title: boolean;
47
+ background: {
48
+ image: boolean;
49
+ color: boolean;
50
+ color_value: string;
51
+ };
52
+ }
53
+
54
+ class SlideBase {
55
+ declare "_el": Record<string, HTMLElement>;
56
+ declare "_media": MediaInstance | null;
57
+ declare "_mediaclass": unknown;
58
+ declare "_text": Text;
59
+ declare "_state": { loaded: boolean };
60
+ declare "_scroll_hint": HTMLElement | null;
61
+ declare "_scroll_hint_dismissed": boolean;
62
+ declare "has": SlideHas;
63
+ declare "title": string;
64
+ declare "data": StorymapSlide;
65
+ declare "options": SlideOptions;
66
+ declare "active": boolean;
67
+ declare "animator": unknown;
68
+ declare "fire": EventedInstance["fire"];
69
+ declare "onLoaded": () => void;
70
+
71
+ //_el: {},
72
+
73
+ /* Constructor
74
+ ================================================== */
75
+ constructor(data: StorymapSlide, options: Record<string, unknown>, title_slide?: boolean) {
76
+ // DOM Elements
77
+ this._el = {
78
+ container: {} as HTMLElement,
79
+ scroll_container: {} as HTMLElement,
80
+ background: {} as HTMLElement,
81
+ content_container: {} as HTMLElement,
82
+ content: {} as HTMLElement,
83
+ call_to_action: null,
84
+ };
85
+
86
+ // Components
87
+ this._media = null;
88
+ this._mediaclass = {};
89
+ this._text = {} as Text;
90
+
91
+ // State
92
+ this._state = {
93
+ loaded: false,
94
+ };
95
+ this._scroll_hint = null;
96
+ this._scroll_hint_dismissed = false;
97
+
98
+ this.has = {
99
+ headline: false,
100
+ text: false,
101
+ media: false,
102
+ title: false,
103
+ background: {
104
+ image: false,
105
+ color: false,
106
+ color_value: "",
107
+ },
108
+ };
109
+
110
+ this.has.title = title_slide;
111
+
112
+ this.title = "";
113
+
114
+ // Data
115
+ this.data = {
116
+ uniqueid: null,
117
+ background: null,
118
+ date: null,
119
+ location: null,
120
+ text: null,
121
+ media: null,
122
+ };
123
+
124
+ // Options
125
+ this.options = {
126
+ // animation
127
+ duration: 1000,
128
+ slide_padding_lr: 40,
129
+ ease: easeInSpline,
130
+ width: 600,
131
+ height: 600,
132
+ skinny_size: 650,
133
+ media_name: "",
134
+ };
135
+
136
+ // Actively Displaying
137
+ this.active = false;
138
+
139
+ // Animation Object
140
+ this.animator = {};
141
+
142
+ // Merge Data and Options
143
+ mergeData(this.options, options);
144
+ mergeData(this.data, data);
145
+
146
+ this._initLayout();
147
+ }
148
+
149
+ /* Adding, Hiding, Showing etc
150
+ ================================================== */
151
+ show() {
152
+ // Positioning is handled by StorySlider._updateDisplay
153
+ }
154
+
155
+ hide() {}
156
+
157
+ setActive(is_active: boolean) {
158
+ this.active = is_active;
159
+
160
+ if (this.active) {
161
+ if (this.data.background) {
162
+ this.fire("background_change", this.has.background);
163
+ }
164
+ // the hint may re-appear on a revisit if the content still
165
+ // overflows (the dismissed flag resets per activation)
166
+ this._scroll_hint_dismissed = false;
167
+ this.loadMedia();
168
+ this._updateScrollHint();
169
+ } else {
170
+ this.stopMedia();
171
+ this._hideScrollHint();
172
+ }
173
+ }
174
+
175
+ updateDisplay(w?: number, h?: number, l?: string) {
176
+ this._updateDisplay(w, h, l);
177
+ }
178
+
179
+ loadMedia() {
180
+ if (this._media && !this._state.loaded) {
181
+ this._media.loadMedia();
182
+ this._state.loaded = true;
183
+ }
184
+ }
185
+
186
+ stopMedia() {
187
+ if (this._media && this._state.loaded) {
188
+ try {
189
+ this._media.stopMedia();
190
+ } catch (e: unknown) {
191
+ // Some sort of race condition or other ordering condition can cause
192
+ // an error when the preview tab is selected in the editor due to
193
+ // the stopped media not being properly formed.
194
+ if (
195
+ (e as Error).message === "this._el.content_item.querySelector is not a function"
196
+ ) {
197
+ console.log("Ignoring error in editor context: " + (e as Error).message);
198
+ } else {
199
+ throw e;
200
+ }
201
+ }
202
+ // If the media load never started (its pending timer was
203
+ // cancelled on a quick pass-through), allow a revisit to retry
204
+ if (!this._media._state?.loaded) {
205
+ this._state.loaded = false;
206
+ }
207
+ }
208
+ }
209
+
210
+ getBackground() {
211
+ return this.has.background;
212
+ }
213
+
214
+ scrollToTop() {
215
+ this._el.container.scrollTop = 0;
216
+ }
217
+
218
+ /* Scroll hint
219
+ ================================================== */
220
+ /**
221
+ * Show a bouncing downward arrow when the slide content overflows
222
+ * (scrollable) — the hint of "more below" for scrollbars that are not
223
+ * visible until touched. Hidden after the first scroll of any kind;
224
+ * tappable to scroll down one step.
225
+ */
226
+ _updateScrollHint() {
227
+ if (!this.active || this._scroll_hint_dismissed) {
228
+ this._hideScrollHint();
229
+ return;
230
+ }
231
+ const el = this._el.container;
232
+ const overflows = el.scrollHeight > el.clientHeight + 1;
233
+ if (!overflows) {
234
+ this._hideScrollHint();
235
+ return;
236
+ }
237
+ if (!this._scroll_hint) {
238
+ this._scroll_hint = Dom.create(
239
+ "div",
240
+ "vco-slide-scroll-hint",
241
+ el,
242
+ );
243
+ this._scroll_hint.innerHTML = "<span class='vco-icon-arrow-down'></span>";
244
+ DomEvent.addListener(this._scroll_hint, "click", this._onScrollHintClick, this);
245
+ }
246
+ this._scroll_hint.style.display = "flex";
247
+ }
248
+
249
+ _hideScrollHint() {
250
+ if (this._scroll_hint) {
251
+ this._scroll_hint.style.display = "none";
252
+ }
253
+ }
254
+
255
+ _onScrollHintClick() {
256
+ // scroll down one step, then hide (the scroll event dismisses too)
257
+ const el = this._el.container;
258
+ el.scrollBy({ top: el.clientHeight * 0.8, behavior: "smooth" });
259
+ this._scroll_hint_dismissed = true;
260
+ this._hideScrollHint();
261
+ }
262
+
263
+ _onSlideScroll() {
264
+ // programmatic scrollToTop on preloaded (inactive) slides must not
265
+ // dismiss the hint
266
+ if (!this.active || this._scroll_hint_dismissed) {
267
+ return;
268
+ }
269
+ this._scroll_hint_dismissed = true;
270
+ this._hideScrollHint();
271
+ }
272
+
273
+ addCallToAction(str: string) {
274
+ this._el.call_to_action = Dom.create(
275
+ "div",
276
+ "vco-slide-calltoaction",
277
+ this._el.content_container,
278
+ );
279
+ this._el.call_to_action.innerHTML =
280
+ "<span class='vco-slide-calltoaction-button-text'>" + str + "</span>";
281
+ DomEvent.addListener(this._el.call_to_action, "click", this._onCallToAction, this);
282
+ }
283
+
284
+ /* Events
285
+ ================================================== */
286
+ _onCallToAction(e: Event) {
287
+ this.fire("call_to_action", e);
288
+ }
289
+
290
+ /* Private Methods
291
+ ================================================== */
292
+ _initLayout() {
293
+ // Create Layout
294
+ this._el.container = Dom.create("div", "vco-slide");
295
+ if (this.data.uniqueid) {
296
+ this._el.container.id = this.data.uniqueid;
297
+ }
298
+ this._el.scroll_container = Dom.create(
299
+ "div",
300
+ "vco-slide-scrollable-container",
301
+ this._el.container,
302
+ );
303
+ this._el.content_container = Dom.create(
304
+ "div",
305
+ "vco-slide-content-container",
306
+ this._el.scroll_container,
307
+ );
308
+ this._el.content = Dom.create("div", "vco-slide-content", this._el.content_container);
309
+ this._el.background = Dom.create("div", "vco-slide-background", this._el.container);
310
+ // first scroll of any kind hides the scroll hint (passive: the
311
+ // listener never prevents default)
312
+ this._el.container.addEventListener("scroll", this._onSlideScroll.bind(this), {
313
+ passive: true,
314
+ });
315
+ // Style Slide Background
316
+ if (this.data.background) {
317
+ const background = this.data.background as StorymapSlideBackground & {
318
+ text_background?: unknown;
319
+ };
320
+ if (background.url) {
321
+ this.has.background.image = true;
322
+ this._el.container.className += " vco-full-image-background";
323
+ this.has.background.color_value = "#000";
324
+ this._el.background.style.backgroundImage = "url('" + background.url + "')";
325
+ this._el.background.style.display = "block";
326
+ }
327
+ if (background.color) {
328
+ this.has.background.color = true;
329
+ this._el.container.className += " vco-full-color-background";
330
+ this.has.background.color_value = background.color;
331
+ }
332
+ if (background.text_background) {
333
+ this._el.container.className += " vco-text-background";
334
+ }
335
+ }
336
+
337
+ // Determine Assets for layout and loading
338
+ if (this.data.media && this.data.media.url && this.data.media.url !== "") {
339
+ this.has.media = true;
340
+ }
341
+ if (this.data.text && this.data.text.text) {
342
+ this.has.text = true;
343
+ }
344
+ if (this.data.text && this.data.text.headline) {
345
+ this.has.headline = true;
346
+ this.title = this.data.text.headline;
347
+ }
348
+
349
+ // Create Media
350
+ if (this.has.media) {
351
+ // Determine the media type
352
+ this.data.media.mediatype = MediaType(this.data.media) as MediaTypeMatch;
353
+ this.options.media_name = this.data.media.mediatype.name;
354
+ this.options.media_type = this.data.media.mediatype.type;
355
+
356
+ // Create a media object using the matched class name
357
+ this._media = new this.data.media.mediatype.cls(
358
+ this.data.media,
359
+ this.options,
360
+ ) as MediaInstance;
361
+ // loaded media changes the content height — the scroll hint
362
+ // may appear or disappear
363
+ this._media.on?.("media_loaded", () => this._updateScrollHint());
364
+ }
365
+
366
+ // Create Text
367
+ if (this.has.text || this.has.headline) {
368
+ this._text = new Text(this.data.text, {
369
+ title: this.has.title,
370
+ text_align: this.options.text_align as string | undefined,
371
+ });
372
+ }
373
+
374
+ // Add to DOM
375
+ if (!this.has.text && !this.has.headline && this.has.media) {
376
+ this._el.container.className += " vco-slide-media-only";
377
+ this._media.addTo(this._el.content);
378
+ } else if (this.has.headline && this.has.media && !this.has.text) {
379
+ this._el.container.className += " vco-slide-media-only";
380
+ this._text.addTo(this._el.content);
381
+ this._media.addTo(this._el.content);
382
+ } else if (this.has.text && this.has.media) {
383
+ this._media.addTo(this._el.content);
384
+ this._text.addTo(this._el.content);
385
+ } else if (this.has.text || this.has.headline) {
386
+ this._el.container.className += " vco-slide-text-only";
387
+ this._text.addTo(this._el.content);
388
+ }
389
+
390
+ // Fire event that the slide is loaded
391
+ this.onLoaded();
392
+ }
393
+
394
+ // Update Display
395
+ _updateDisplay(width?: number, height?: number, layout?: string) {
396
+ let pad_left, pad_right, new_width;
397
+
398
+ if (width) {
399
+ this.options.width = width;
400
+ } else {
401
+ this.options.width = this._el.container.offsetWidth;
402
+ }
403
+
404
+ if (Browser.mobile && this.options.width <= this.options.skinny_size) {
405
+ pad_left = 0 + "px";
406
+ pad_right = 0 + "px";
407
+ new_width = this.options.width - 0 + "px";
408
+ } else if (layout === "landscape") {
409
+ pad_left = 40 + "px";
410
+ pad_right = 75 + "px";
411
+ new_width = this.options.width - (75 + 40) + "px";
412
+ } else if (this.options.width <= this.options.skinny_size) {
413
+ pad_left = this.options.slide_padding_lr + "px";
414
+ pad_right = this.options.slide_padding_lr + "px";
415
+ new_width = this.options.width - this.options.slide_padding_lr * 2 + "px";
416
+ } else {
417
+ pad_left = this.options.slide_padding_lr + "px";
418
+ pad_right = this.options.slide_padding_lr + "px";
419
+ new_width = this.options.width - this.options.slide_padding_lr * 2 + "px";
420
+ }
421
+
422
+ this._el.content.style.paddingLeft = pad_left;
423
+ this._el.content.style.paddingRight = pad_right;
424
+ this._el.content.style.width = new_width;
425
+
426
+ if (this._el.call_to_action) {
427
+ this._el.call_to_action.style.paddingLeft = pad_left;
428
+ this._el.call_to_action.style.paddingRight = pad_right;
429
+ this._el.call_to_action.style.width = new_width;
430
+ }
431
+
432
+ if (height) {
433
+ this.options.height = height;
434
+ //this._el.scroll_container.style.height = this.options.height + "px";
435
+ } else {
436
+ this.options.height = this._el.container.offsetHeight;
437
+ }
438
+
439
+ if (this._media) {
440
+ if (!this.has.text && this.has.headline) {
441
+ this._media.updateDisplay(
442
+ this.options.width,
443
+ this.options.height - this._text.headlineHeight(),
444
+ layout,
445
+ );
446
+ } else {
447
+ this._media.updateDisplay(this.options.width, this.options.height, layout);
448
+ }
449
+ }
450
+
451
+ // layout/resize changes move the overflow boundary
452
+ this._updateScrollHint();
453
+ }
454
+ }
455
+
456
+ export default class Slide extends DomMixed(Evented(SlideBase)) {
457
+ constructor(...args: ConstructorParameters<typeof SlideBase>) {
458
+ super(...args);
459
+ }
460
+ }