@projektemacher/storymapjs 0.10.7 → 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 (587) hide show
  1. package/CHANGELOG +431 -19
  2. package/README.md +168 -17
  3. package/dist/assets/demo-bWnOyfUw.js +1 -0
  4. package/dist/assets/harness-C3JUv5Ii.js +1 -0
  5. package/dist/assets/harness-multi-XZNex3tb.js +1 -0
  6. package/dist/assets/harness-support-CjfZIVeb.js +1 -0
  7. package/dist/assets/index-wHPqGrBS.js +1 -0
  8. package/dist/assets/main-DQ32hWBM.js +17 -0
  9. package/dist/context.json +23 -9
  10. package/dist/css/fonts/files/amatic-sc-cyrillic-400-normal.woff2 +0 -0
  11. package/dist/css/fonts/files/amatic-sc-cyrillic-700-normal.woff2 +0 -0
  12. package/dist/css/fonts/files/amatic-sc-hebrew-400-normal.woff2 +0 -0
  13. package/dist/css/fonts/files/amatic-sc-hebrew-700-normal.woff2 +0 -0
  14. package/dist/css/fonts/files/amatic-sc-vietnamese-400-normal.woff2 +0 -0
  15. package/dist/css/fonts/files/amatic-sc-vietnamese-700-normal.woff2 +0 -0
  16. package/dist/css/fonts/files/bitter-cyrillic-100-italic.woff2 +0 -0
  17. package/dist/css/fonts/files/bitter-cyrillic-100-normal.woff2 +0 -0
  18. package/dist/css/fonts/files/bitter-cyrillic-200-italic.woff2 +0 -0
  19. package/dist/css/fonts/files/bitter-cyrillic-200-normal.woff2 +0 -0
  20. package/dist/css/fonts/files/bitter-cyrillic-300-italic.woff2 +0 -0
  21. package/dist/css/fonts/files/bitter-cyrillic-300-normal.woff2 +0 -0
  22. package/dist/css/fonts/files/bitter-cyrillic-400-italic.woff2 +0 -0
  23. package/dist/css/fonts/files/bitter-cyrillic-400-normal.woff2 +0 -0
  24. package/dist/css/fonts/files/bitter-cyrillic-500-italic.woff2 +0 -0
  25. package/dist/css/fonts/files/bitter-cyrillic-500-normal.woff2 +0 -0
  26. package/dist/css/fonts/files/bitter-cyrillic-600-italic.woff2 +0 -0
  27. package/dist/css/fonts/files/bitter-cyrillic-600-normal.woff2 +0 -0
  28. package/dist/css/fonts/files/bitter-cyrillic-700-italic.woff2 +0 -0
  29. package/dist/css/fonts/files/bitter-cyrillic-700-normal.woff2 +0 -0
  30. package/dist/css/fonts/files/bitter-cyrillic-800-italic.woff2 +0 -0
  31. package/dist/css/fonts/files/bitter-cyrillic-800-normal.woff2 +0 -0
  32. package/dist/css/fonts/files/bitter-cyrillic-900-italic.woff2 +0 -0
  33. package/dist/css/fonts/files/bitter-cyrillic-900-normal.woff2 +0 -0
  34. package/dist/css/fonts/files/bitter-cyrillic-ext-100-italic.woff2 +0 -0
  35. package/dist/css/fonts/files/bitter-cyrillic-ext-100-normal.woff2 +0 -0
  36. package/dist/css/fonts/files/bitter-cyrillic-ext-200-italic.woff2 +0 -0
  37. package/dist/css/fonts/files/bitter-cyrillic-ext-200-normal.woff2 +0 -0
  38. package/dist/css/fonts/files/bitter-cyrillic-ext-300-italic.woff2 +0 -0
  39. package/dist/css/fonts/files/bitter-cyrillic-ext-300-normal.woff2 +0 -0
  40. package/dist/css/fonts/files/bitter-cyrillic-ext-400-italic.woff2 +0 -0
  41. package/dist/css/fonts/files/bitter-cyrillic-ext-400-normal.woff2 +0 -0
  42. package/dist/css/fonts/files/bitter-cyrillic-ext-500-italic.woff2 +0 -0
  43. package/dist/css/fonts/files/bitter-cyrillic-ext-500-normal.woff2 +0 -0
  44. package/dist/css/fonts/files/bitter-cyrillic-ext-600-italic.woff2 +0 -0
  45. package/dist/css/fonts/files/bitter-cyrillic-ext-600-normal.woff2 +0 -0
  46. package/dist/css/fonts/files/bitter-cyrillic-ext-700-italic.woff2 +0 -0
  47. package/dist/css/fonts/files/bitter-cyrillic-ext-700-normal.woff2 +0 -0
  48. package/dist/css/fonts/files/bitter-cyrillic-ext-800-italic.woff2 +0 -0
  49. package/dist/css/fonts/files/bitter-cyrillic-ext-800-normal.woff2 +0 -0
  50. package/dist/css/fonts/files/bitter-cyrillic-ext-900-italic.woff2 +0 -0
  51. package/dist/css/fonts/files/bitter-cyrillic-ext-900-normal.woff2 +0 -0
  52. package/dist/css/fonts/files/bitter-cyrillic-ext-wght-italic.woff2 +0 -0
  53. package/dist/css/fonts/files/bitter-cyrillic-ext-wght-normal.woff2 +0 -0
  54. package/dist/css/fonts/files/bitter-cyrillic-wght-italic.woff2 +0 -0
  55. package/dist/css/fonts/files/bitter-cyrillic-wght-normal.woff2 +0 -0
  56. package/dist/css/fonts/files/bitter-vietnamese-100-italic.woff2 +0 -0
  57. package/dist/css/fonts/files/bitter-vietnamese-100-normal.woff2 +0 -0
  58. package/dist/css/fonts/files/bitter-vietnamese-200-italic.woff2 +0 -0
  59. package/dist/css/fonts/files/bitter-vietnamese-200-normal.woff2 +0 -0
  60. package/dist/css/fonts/files/bitter-vietnamese-300-italic.woff2 +0 -0
  61. package/dist/css/fonts/files/bitter-vietnamese-300-normal.woff2 +0 -0
  62. package/dist/css/fonts/files/bitter-vietnamese-400-italic.woff2 +0 -0
  63. package/dist/css/fonts/files/bitter-vietnamese-400-normal.woff2 +0 -0
  64. package/dist/css/fonts/files/bitter-vietnamese-500-italic.woff2 +0 -0
  65. package/dist/css/fonts/files/bitter-vietnamese-500-normal.woff2 +0 -0
  66. package/dist/css/fonts/files/bitter-vietnamese-600-italic.woff2 +0 -0
  67. package/dist/css/fonts/files/bitter-vietnamese-600-normal.woff2 +0 -0
  68. package/dist/css/fonts/files/bitter-vietnamese-700-italic.woff2 +0 -0
  69. package/dist/css/fonts/files/bitter-vietnamese-700-normal.woff2 +0 -0
  70. package/dist/css/fonts/files/bitter-vietnamese-800-italic.woff2 +0 -0
  71. package/dist/css/fonts/files/bitter-vietnamese-800-normal.woff2 +0 -0
  72. package/dist/css/fonts/files/bitter-vietnamese-900-italic.woff2 +0 -0
  73. package/dist/css/fonts/files/bitter-vietnamese-900-normal.woff2 +0 -0
  74. package/dist/css/fonts/files/bitter-vietnamese-wght-italic.woff2 +0 -0
  75. package/dist/css/fonts/files/bitter-vietnamese-wght-normal.woff2 +0 -0
  76. package/dist/css/fonts/files/cairo-arabic-200-normal.woff2 +0 -0
  77. package/dist/css/fonts/files/cairo-arabic-300-normal.woff2 +0 -0
  78. package/dist/css/fonts/files/cairo-arabic-400-normal.woff2 +0 -0
  79. package/dist/css/fonts/files/cairo-arabic-500-normal.woff2 +0 -0
  80. package/dist/css/fonts/files/cairo-arabic-600-normal.woff2 +0 -0
  81. package/dist/css/fonts/files/cairo-arabic-700-normal.woff2 +0 -0
  82. package/dist/css/fonts/files/cairo-arabic-800-normal.woff2 +0 -0
  83. package/dist/css/fonts/files/cairo-arabic-900-normal.woff2 +0 -0
  84. package/dist/css/fonts/files/cairo-latin-200-normal.woff2 +0 -0
  85. package/dist/css/fonts/files/cairo-latin-300-normal.woff2 +0 -0
  86. package/dist/css/fonts/files/cairo-latin-400-normal.woff2 +0 -0
  87. package/dist/css/fonts/files/cairo-latin-500-normal.woff2 +0 -0
  88. package/dist/css/fonts/files/cairo-latin-600-normal.woff2 +0 -0
  89. package/dist/css/fonts/files/cairo-latin-700-normal.woff2 +0 -0
  90. package/dist/css/fonts/files/cairo-latin-800-normal.woff2 +0 -0
  91. package/dist/css/fonts/files/cairo-latin-900-normal.woff2 +0 -0
  92. package/dist/css/fonts/files/cairo-latin-ext-200-normal.woff2 +0 -0
  93. package/dist/css/fonts/files/cairo-latin-ext-300-normal.woff2 +0 -0
  94. package/dist/css/fonts/files/cairo-latin-ext-400-normal.woff2 +0 -0
  95. package/dist/css/fonts/files/cairo-latin-ext-500-normal.woff2 +0 -0
  96. package/dist/css/fonts/files/cairo-latin-ext-600-normal.woff2 +0 -0
  97. package/dist/css/fonts/files/cairo-latin-ext-700-normal.woff2 +0 -0
  98. package/dist/css/fonts/files/cairo-latin-ext-800-normal.woff2 +0 -0
  99. package/dist/css/fonts/files/cairo-latin-ext-900-normal.woff2 +0 -0
  100. package/dist/css/fonts/files/dancing-script-vietnamese-wght-normal.woff2 +0 -0
  101. package/dist/css/fonts/files/eb-garamond-cyrillic-ext-wght-italic.woff2 +0 -0
  102. package/dist/css/fonts/files/eb-garamond-cyrillic-ext-wght-normal.woff2 +0 -0
  103. package/dist/css/fonts/files/eb-garamond-cyrillic-wght-italic.woff2 +0 -0
  104. package/dist/css/fonts/files/eb-garamond-cyrillic-wght-normal.woff2 +0 -0
  105. package/dist/css/fonts/files/eb-garamond-greek-ext-wght-italic.woff2 +0 -0
  106. package/dist/css/fonts/files/eb-garamond-greek-ext-wght-normal.woff2 +0 -0
  107. package/dist/css/fonts/files/eb-garamond-greek-wght-italic.woff2 +0 -0
  108. package/dist/css/fonts/files/eb-garamond-greek-wght-normal.woff2 +0 -0
  109. package/dist/css/fonts/files/eb-garamond-vietnamese-wght-italic.woff2 +0 -0
  110. package/dist/css/fonts/files/eb-garamond-vietnamese-wght-normal.woff2 +0 -0
  111. package/dist/css/fonts/files/gentium-book-plus-cyrillic-400-italic.woff2 +0 -0
  112. package/dist/css/fonts/files/gentium-book-plus-cyrillic-400-normal.woff2 +0 -0
  113. package/dist/css/fonts/files/gentium-book-plus-cyrillic-700-italic.woff2 +0 -0
  114. package/dist/css/fonts/files/gentium-book-plus-cyrillic-700-normal.woff2 +0 -0
  115. package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-400-italic.woff2 +0 -0
  116. package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-400-normal.woff2 +0 -0
  117. package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-700-italic.woff2 +0 -0
  118. package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-700-normal.woff2 +0 -0
  119. package/dist/css/fonts/files/gentium-book-plus-greek-400-italic.woff2 +0 -0
  120. package/dist/css/fonts/files/gentium-book-plus-greek-400-normal.woff2 +0 -0
  121. package/dist/css/fonts/files/gentium-book-plus-greek-700-italic.woff2 +0 -0
  122. package/dist/css/fonts/files/gentium-book-plus-greek-700-normal.woff2 +0 -0
  123. package/dist/css/fonts/files/gentium-book-plus-greek-ext-400-italic.woff2 +0 -0
  124. package/dist/css/fonts/files/gentium-book-plus-greek-ext-400-normal.woff2 +0 -0
  125. package/dist/css/fonts/files/gentium-book-plus-greek-ext-700-italic.woff2 +0 -0
  126. package/dist/css/fonts/files/gentium-book-plus-greek-ext-700-normal.woff2 +0 -0
  127. package/dist/css/fonts/files/gentium-book-plus-vietnamese-400-italic.woff2 +0 -0
  128. package/dist/css/fonts/files/gentium-book-plus-vietnamese-400-normal.woff2 +0 -0
  129. package/dist/css/fonts/files/gentium-book-plus-vietnamese-700-italic.woff2 +0 -0
  130. package/dist/css/fonts/files/gentium-book-plus-vietnamese-700-normal.woff2 +0 -0
  131. package/dist/css/fonts/files/old-standard-tt-cyrillic-400-italic.woff2 +0 -0
  132. package/dist/css/fonts/files/old-standard-tt-cyrillic-400-normal.woff2 +0 -0
  133. package/dist/css/fonts/files/old-standard-tt-cyrillic-700-normal.woff2 +0 -0
  134. package/dist/css/fonts/files/old-standard-tt-cyrillic-ext-400-italic.woff2 +0 -0
  135. package/dist/css/fonts/files/old-standard-tt-cyrillic-ext-400-normal.woff2 +0 -0
  136. package/dist/css/fonts/files/old-standard-tt-cyrillic-ext-700-normal.woff2 +0 -0
  137. package/dist/css/fonts/files/old-standard-tt-vietnamese-400-italic.woff2 +0 -0
  138. package/dist/css/fonts/files/old-standard-tt-vietnamese-400-normal.woff2 +0 -0
  139. package/dist/css/fonts/files/old-standard-tt-vietnamese-700-normal.woff2 +0 -0
  140. package/dist/css/fonts/files/open-sans-cyrillic-ext-wght-italic.woff2 +0 -0
  141. package/dist/css/fonts/files/open-sans-cyrillic-ext-wght-normal.woff2 +0 -0
  142. package/dist/css/fonts/files/open-sans-cyrillic-wght-italic.woff2 +0 -0
  143. package/dist/css/fonts/files/open-sans-cyrillic-wght-normal.woff2 +0 -0
  144. package/dist/css/fonts/files/open-sans-greek-ext-wght-italic.woff2 +0 -0
  145. package/dist/css/fonts/files/open-sans-greek-ext-wght-normal.woff2 +0 -0
  146. package/dist/css/fonts/files/open-sans-greek-wght-italic.woff2 +0 -0
  147. package/dist/css/fonts/files/open-sans-greek-wght-normal.woff2 +0 -0
  148. package/dist/css/fonts/files/open-sans-hebrew-wght-italic.woff2 +0 -0
  149. package/dist/css/fonts/files/open-sans-hebrew-wght-normal.woff2 +0 -0
  150. package/dist/css/fonts/files/open-sans-vietnamese-wght-italic.woff2 +0 -0
  151. package/dist/css/fonts/files/open-sans-vietnamese-wght-normal.woff2 +0 -0
  152. package/dist/css/fonts/files/playfair-display-cyrillic-wght-italic.woff2 +0 -0
  153. package/dist/css/fonts/files/playfair-display-cyrillic-wght-normal.woff2 +0 -0
  154. package/dist/css/fonts/files/playfair-display-sc-cyrillic-400-italic.woff2 +0 -0
  155. package/dist/css/fonts/files/playfair-display-sc-cyrillic-400-normal.woff2 +0 -0
  156. package/dist/css/fonts/files/playfair-display-sc-cyrillic-700-italic.woff2 +0 -0
  157. package/dist/css/fonts/files/playfair-display-sc-cyrillic-700-normal.woff2 +0 -0
  158. package/dist/css/fonts/files/playfair-display-sc-cyrillic-900-italic.woff2 +0 -0
  159. package/dist/css/fonts/files/playfair-display-sc-cyrillic-900-normal.woff2 +0 -0
  160. package/dist/css/fonts/files/playfair-display-sc-vietnamese-400-italic.woff2 +0 -0
  161. package/dist/css/fonts/files/playfair-display-sc-vietnamese-400-normal.woff2 +0 -0
  162. package/dist/css/fonts/files/playfair-display-sc-vietnamese-700-italic.woff2 +0 -0
  163. package/dist/css/fonts/files/playfair-display-sc-vietnamese-700-normal.woff2 +0 -0
  164. package/dist/css/fonts/files/playfair-display-sc-vietnamese-900-italic.woff2 +0 -0
  165. package/dist/css/fonts/files/playfair-display-sc-vietnamese-900-normal.woff2 +0 -0
  166. package/dist/css/fonts/files/playfair-display-vietnamese-wght-italic.woff2 +0 -0
  167. package/dist/css/fonts/files/playfair-display-vietnamese-wght-normal.woff2 +0 -0
  168. package/dist/css/fonts/files/pt-sans-cyrillic-400-italic.woff2 +0 -0
  169. package/dist/css/fonts/files/pt-sans-cyrillic-400-normal.woff2 +0 -0
  170. package/dist/css/fonts/files/pt-sans-cyrillic-700-italic.woff2 +0 -0
  171. package/dist/css/fonts/files/pt-sans-cyrillic-700-normal.woff2 +0 -0
  172. package/dist/css/fonts/files/pt-sans-cyrillic-ext-400-italic.woff2 +0 -0
  173. package/dist/css/fonts/files/pt-sans-cyrillic-ext-400-normal.woff2 +0 -0
  174. package/dist/css/fonts/files/pt-sans-cyrillic-ext-700-italic.woff2 +0 -0
  175. package/dist/css/fonts/files/pt-sans-cyrillic-ext-700-normal.woff2 +0 -0
  176. package/dist/css/fonts/files/pt-sans-narrow-cyrillic-400-normal.woff2 +0 -0
  177. package/dist/css/fonts/files/pt-sans-narrow-cyrillic-700-normal.woff2 +0 -0
  178. package/dist/css/fonts/files/pt-sans-narrow-cyrillic-ext-400-normal.woff2 +0 -0
  179. package/dist/css/fonts/files/pt-sans-narrow-cyrillic-ext-700-normal.woff2 +0 -0
  180. package/dist/css/fonts/files/pt-serif-cyrillic-400-italic.woff2 +0 -0
  181. package/dist/css/fonts/files/pt-serif-cyrillic-400-normal.woff2 +0 -0
  182. package/dist/css/fonts/files/pt-serif-cyrillic-700-italic.woff2 +0 -0
  183. package/dist/css/fonts/files/pt-serif-cyrillic-700-normal.woff2 +0 -0
  184. package/dist/css/fonts/files/pt-serif-cyrillic-ext-400-italic.woff2 +0 -0
  185. package/dist/css/fonts/files/pt-serif-cyrillic-ext-400-normal.woff2 +0 -0
  186. package/dist/css/fonts/files/pt-serif-cyrillic-ext-700-italic.woff2 +0 -0
  187. package/dist/css/fonts/files/pt-serif-cyrillic-ext-700-normal.woff2 +0 -0
  188. package/dist/css/fonts/files/raleway-cyrillic-ext-wght-italic.woff2 +0 -0
  189. package/dist/css/fonts/files/raleway-cyrillic-ext-wght-normal.woff2 +0 -0
  190. package/dist/css/fonts/files/raleway-cyrillic-wght-italic.woff2 +0 -0
  191. package/dist/css/fonts/files/raleway-cyrillic-wght-normal.woff2 +0 -0
  192. package/dist/css/fonts/files/raleway-vietnamese-wght-italic.woff2 +0 -0
  193. package/dist/css/fonts/files/raleway-vietnamese-wght-normal.woff2 +0 -0
  194. package/dist/css/fonts/files/roboto-slab-cyrillic-ext-wght-normal.woff2 +0 -0
  195. package/dist/css/fonts/files/roboto-slab-cyrillic-wght-normal.woff2 +0 -0
  196. package/dist/css/fonts/files/roboto-slab-greek-ext-wght-normal.woff2 +0 -0
  197. package/dist/css/fonts/files/roboto-slab-greek-wght-normal.woff2 +0 -0
  198. package/dist/css/fonts/files/roboto-slab-vietnamese-wght-normal.woff2 +0 -0
  199. package/dist/css/fonts/files/ubuntu-cyrillic-300-italic.woff2 +0 -0
  200. package/dist/css/fonts/files/ubuntu-cyrillic-300-normal.woff2 +0 -0
  201. package/dist/css/fonts/files/ubuntu-cyrillic-400-italic.woff2 +0 -0
  202. package/dist/css/fonts/files/ubuntu-cyrillic-400-normal.woff2 +0 -0
  203. package/dist/css/fonts/files/ubuntu-cyrillic-500-italic.woff2 +0 -0
  204. package/dist/css/fonts/files/ubuntu-cyrillic-500-normal.woff2 +0 -0
  205. package/dist/css/fonts/files/ubuntu-cyrillic-700-italic.woff2 +0 -0
  206. package/dist/css/fonts/files/ubuntu-cyrillic-700-normal.woff2 +0 -0
  207. package/dist/css/fonts/files/ubuntu-cyrillic-ext-300-italic.woff2 +0 -0
  208. package/dist/css/fonts/files/ubuntu-cyrillic-ext-300-normal.woff2 +0 -0
  209. package/dist/css/fonts/files/ubuntu-cyrillic-ext-400-italic.woff2 +0 -0
  210. package/dist/css/fonts/files/ubuntu-cyrillic-ext-400-normal.woff2 +0 -0
  211. package/dist/css/fonts/files/ubuntu-cyrillic-ext-500-italic.woff2 +0 -0
  212. package/dist/css/fonts/files/ubuntu-cyrillic-ext-500-normal.woff2 +0 -0
  213. package/dist/css/fonts/files/ubuntu-cyrillic-ext-700-italic.woff2 +0 -0
  214. package/dist/css/fonts/files/ubuntu-cyrillic-ext-700-normal.woff2 +0 -0
  215. package/dist/css/fonts/files/ubuntu-greek-300-italic.woff2 +0 -0
  216. package/dist/css/fonts/files/ubuntu-greek-300-normal.woff2 +0 -0
  217. package/dist/css/fonts/files/ubuntu-greek-400-italic.woff2 +0 -0
  218. package/dist/css/fonts/files/ubuntu-greek-400-normal.woff2 +0 -0
  219. package/dist/css/fonts/files/ubuntu-greek-500-italic.woff2 +0 -0
  220. package/dist/css/fonts/files/ubuntu-greek-500-normal.woff2 +0 -0
  221. package/dist/css/fonts/files/ubuntu-greek-700-italic.woff2 +0 -0
  222. package/dist/css/fonts/files/ubuntu-greek-700-normal.woff2 +0 -0
  223. package/dist/css/fonts/files/ubuntu-greek-ext-300-italic.woff2 +0 -0
  224. package/dist/css/fonts/files/ubuntu-greek-ext-300-normal.woff2 +0 -0
  225. package/dist/css/fonts/files/ubuntu-greek-ext-400-italic.woff2 +0 -0
  226. package/dist/css/fonts/files/ubuntu-greek-ext-400-normal.woff2 +0 -0
  227. package/dist/css/fonts/files/ubuntu-greek-ext-500-italic.woff2 +0 -0
  228. package/dist/css/fonts/files/ubuntu-greek-ext-500-normal.woff2 +0 -0
  229. package/dist/css/fonts/files/ubuntu-greek-ext-700-italic.woff2 +0 -0
  230. package/dist/css/fonts/files/ubuntu-greek-ext-700-normal.woff2 +0 -0
  231. package/dist/css/fonts/files/unica-one-vietnamese-400-normal.woff2 +0 -0
  232. package/dist/css/fonts/files/vollkorn-cyrillic-ext-wght-italic.woff2 +0 -0
  233. package/dist/css/fonts/files/vollkorn-cyrillic-ext-wght-normal.woff2 +0 -0
  234. package/dist/css/fonts/files/vollkorn-cyrillic-wght-italic.woff2 +0 -0
  235. package/dist/css/fonts/files/vollkorn-cyrillic-wght-normal.woff2 +0 -0
  236. package/dist/css/fonts/files/vollkorn-greek-wght-italic.woff2 +0 -0
  237. package/dist/css/fonts/files/vollkorn-greek-wght-normal.woff2 +0 -0
  238. package/dist/css/fonts/files/vollkorn-vietnamese-wght-italic.woff2 +0 -0
  239. package/dist/css/fonts/files/vollkorn-vietnamese-wght-normal.woff2 +0 -0
  240. package/dist/css/fonts/font.abril-droidsans.css +3 -0
  241. package/dist/css/fonts/font.amatic-andika.css +57 -0
  242. package/dist/css/fonts/font.bitter-raleway.css +111 -0
  243. package/dist/css/fonts/font.cairo.css +312 -0
  244. package/dist/css/fonts/font.clicker-garamond.css +93 -0
  245. package/dist/css/fonts/font.dancing-ledger.css +12 -0
  246. package/dist/css/fonts/font.default.css +183 -0
  247. package/dist/css/fonts/font.fjalla-average.css +3 -0
  248. package/dist/css/fonts/font.georgia-helvetica.css +3 -0
  249. package/dist/css/fonts/font.knightlab.css +642 -0
  250. package/dist/css/fonts/font.lustria-lato.css +3 -0
  251. package/dist/css/fonts/font.medula-lato.css +3 -0
  252. package/dist/css/fonts/font.oldstandard.css +84 -0
  253. package/dist/css/fonts/font.opensans-gentiumbook.css +183 -0
  254. package/dist/css/fonts/font.playfair-faunaone.css +39 -0
  255. package/dist/css/fonts/font.playfair.css +147 -0
  256. package/dist/css/fonts/font.pt.css +183 -0
  257. package/dist/css/fonts/font.roboto-megrim.css +3 -0
  258. package/dist/css/fonts/font.rufina-sintony.css +3 -0
  259. package/dist/css/fonts/font.ubuntu.css +291 -0
  260. package/dist/css/fonts/font.unicaone-vollkorn.css +84 -0
  261. package/dist/css/storymap.css +1 -1
  262. package/dist/demo.html +3 -2
  263. package/dist/docs/api/.nojekyll +1 -0
  264. package/dist/docs/api/assets/custom.css +47 -0
  265. package/dist/docs/api/assets/hierarchy.js +1 -0
  266. package/dist/docs/api/assets/highlight.css +57 -0
  267. package/dist/docs/api/assets/icons.js +18 -0
  268. package/dist/docs/api/assets/icons.svg +1 -0
  269. package/dist/docs/api/assets/main.js +60 -0
  270. package/dist/docs/api/assets/navigation.js +1 -0
  271. package/dist/docs/api/assets/search.js +1 -0
  272. package/dist/docs/api/assets/style.css +1648 -0
  273. package/dist/docs/api/classes/StoryMap.html +2112 -0
  274. package/dist/docs/api/functions/MediaType.html +51 -0
  275. package/dist/docs/api/functions/isPresentation3Collection.html +47 -0
  276. package/dist/docs/api/functions/isPresentation3Manifest.html +53 -0
  277. package/dist/docs/api/functions/isSlideshowCollection.html +51 -0
  278. package/dist/docs/api/functions/loadCSS.html +63 -0
  279. package/dist/docs/api/functions/manifestToStorymapData.html +49 -0
  280. package/dist/docs/api/functions/setLanguage.html +54 -0
  281. package/dist/docs/api/functions/slideshowToStorymapData.html +51 -0
  282. package/dist/docs/api/functions/slideshowVersion.html +48 -0
  283. package/dist/docs/api/functions/storymapToManifest.html +57 -0
  284. package/dist/docs/api/functions/validateStorymap.html +53 -0
  285. package/dist/docs/api/functions/validateStorymapAndReport.html +54 -0
  286. package/dist/docs/api/hierarchy.html +28 -0
  287. package/dist/docs/api/index.html +300 -0
  288. package/dist/docs/api/interfaces/AnimationHandle.html +60 -0
  289. package/dist/docs/api/interfaces/ImagereadyPayload.html +71 -0
  290. package/dist/docs/api/interfaces/LanguageEntry.html +102 -0
  291. package/dist/docs/api/interfaces/LatLngLiteral.html +73 -0
  292. package/dist/docs/api/interfaces/LoadOptions.html +59 -0
  293. package/dist/docs/api/interfaces/MapEvents.html +116 -0
  294. package/dist/docs/api/interfaces/MapMarkerEvents.html +66 -0
  295. package/dist/docs/api/interfaces/MarkerEventPayload.html +57 -0
  296. package/dist/docs/api/interfaces/MediaEvents.html +78 -0
  297. package/dist/docs/api/interfaces/MenuBarEvents.html +114 -0
  298. package/dist/docs/api/interfaces/MessageEvents.html +72 -0
  299. package/dist/docs/api/interfaces/OlLayer.html +1400 -0
  300. package/dist/docs/api/interfaces/OlMap.html +1656 -0
  301. package/dist/docs/api/interfaces/OlProjection.html +332 -0
  302. package/dist/docs/api/interfaces/OlSource.html +784 -0
  303. package/dist/docs/api/interfaces/OlTileLayer.html +1497 -0
  304. package/dist/docs/api/interfaces/OlVectorLayer.html +1559 -0
  305. package/dist/docs/api/interfaces/OlView.html +1958 -0
  306. package/dist/docs/api/interfaces/SlideBackgroundState.html +66 -0
  307. package/dist/docs/api/interfaces/SlideEvents.html +78 -0
  308. package/dist/docs/api/interfaces/SlideNavEvents.html +72 -0
  309. package/dist/docs/api/interfaces/SlideshowPlayerSettings.html +182 -0
  310. package/dist/docs/api/interfaces/SlideshowToStorymapResult.html +60 -0
  311. package/dist/docs/api/interfaces/SlideshowTranslatorOptions.html +65 -0
  312. package/dist/docs/api/interfaces/SlideshowWarning.html +72 -0
  313. package/dist/docs/api/interfaces/StoryMapEvents.html +146 -0
  314. package/dist/docs/api/interfaces/StoryMapMarker.html +763 -0
  315. package/dist/docs/api/interfaces/StorySliderEvents.html +102 -0
  316. package/dist/docs/api/interfaces/StorymapBasemap.html +69 -0
  317. package/dist/docs/api/interfaces/StorymapData.html +77 -0
  318. package/dist/docs/api/interfaces/StorymapDataWrapper.html +60 -0
  319. package/dist/docs/api/interfaces/StorymapDocument.html +68 -0
  320. package/dist/docs/api/interfaces/StorymapError.html +63 -0
  321. package/dist/docs/api/interfaces/StorymapGeoreference.html +93 -0
  322. package/dist/docs/api/interfaces/StorymapGeoreferenceBody.html +74 -0
  323. package/dist/docs/api/interfaces/StorymapGroundControlPoint.html +69 -0
  324. package/dist/docs/api/interfaces/StorymapLanguageMap.html +57 -0
  325. package/dist/docs/api/interfaces/StorymapManifest.html +118 -0
  326. package/dist/docs/api/interfaces/StorymapManifestAnnotation.html +137 -0
  327. package/dist/docs/api/interfaces/StorymapManifestBackground.html +81 -0
  328. package/dist/docs/api/interfaces/StorymapManifestCanvas.html +188 -0
  329. package/dist/docs/api/interfaces/StorymapManifestContentResource.html +97 -0
  330. package/dist/docs/api/interfaces/StorymapManifestFeatureCollection.html +82 -0
  331. package/dist/docs/api/interfaces/StorymapManifestGeoreferencing.html +87 -0
  332. package/dist/docs/api/interfaces/StorymapManifestImageService.html +69 -0
  333. package/dist/docs/api/interfaces/StorymapManifestRange.html +75 -0
  334. package/dist/docs/api/interfaces/StorymapManifestService.html +77 -0
  335. package/dist/docs/api/interfaces/StorymapManifestSpecificResource.html +69 -0
  336. package/dist/docs/api/interfaces/StorymapManifestStatement.html +63 -0
  337. package/dist/docs/api/interfaces/StorymapMapOptions.html +92 -0
  338. package/dist/docs/api/interfaces/StorymapOptions.html +734 -0
  339. package/dist/docs/api/interfaces/StorymapOverlayLayer.html +157 -0
  340. package/dist/docs/api/interfaces/StorymapSlide.html +186 -0
  341. package/dist/docs/api/interfaces/StorymapSlideBackground.html +72 -0
  342. package/dist/docs/api/interfaces/StorymapSlideLocation.html +161 -0
  343. package/dist/docs/api/interfaces/StorymapSlideMarker.html +103 -0
  344. package/dist/docs/api/interfaces/StorymapSlideMedia.html +149 -0
  345. package/dist/docs/api/interfaces/StorymapSlideNarration.html +117 -0
  346. package/dist/docs/api/interfaces/StorymapSlideText.html +72 -0
  347. package/dist/docs/api/interfaces/StorymapTilejson.html +105 -0
  348. package/dist/docs/api/interfaces/SwipableEvents.html +102 -0
  349. package/dist/docs/api/interfaces/TextEvents.html +66 -0
  350. package/dist/docs/api/media/es.json +31 -0
  351. package/dist/docs/api/media/events.md +66 -0
  352. package/dist/docs/api/media/migration-from-knightlab.md +399 -0
  353. package/dist/docs/api/media/slideshow.md +109 -0
  354. package/dist/docs/api/media/storymap-as-iiif-manifest.md +1007 -0
  355. package/dist/docs/api/modules.html +50 -0
  356. package/dist/docs/api/types/ContentState.html +70 -0
  357. package/dist/docs/api/types/FiredEvent.html +43 -0
  358. package/dist/docs/api/types/ImagereadyKind.html +34 -0
  359. package/dist/docs/api/types/MediaTypeMatch.html +78 -0
  360. package/dist/docs/api/types/SwipeDirection.html +36 -0
  361. package/dist/docs/api/types/TileSourceFactory.html +53 -0
  362. package/dist/docs/authoring.html +306 -0
  363. package/dist/docs/events.html +263 -0
  364. package/dist/docs/iiif.html +738 -69
  365. package/dist/docs/migration.html +360 -11
  366. package/dist/docs/readme.html +149 -17
  367. package/dist/embed/index.html +8 -1
  368. package/dist/examples/font-css-false.json +33 -0
  369. package/dist/examples/issue-242-nonlatin.json +55 -0
  370. package/dist/examples/issue-305-start-at-slide-data.json +96 -0
  371. package/dist/examples/issue-358-iframe.json +57 -0
  372. package/dist/examples/issue-iiif-geo.json +32 -0
  373. package/dist/examples/issue-image-alt.json +23 -0
  374. package/dist/examples/issue-image-region.json +42 -0
  375. package/dist/examples/issue-image-srcset.json +24 -0
  376. package/dist/examples/layers-control.json +60 -0
  377. package/dist/examples/map-area-overlays.json +68 -0
  378. package/dist/examples/map-template-provider.json +36 -0
  379. package/dist/examples/multi-media.json +71 -0
  380. package/dist/examples/no-map.json +44 -0
  381. package/dist/examples-iiif/annotated-image.json +204 -0
  382. package/dist/examples-iiif/bob-boilen.json +11 -5
  383. package/dist/examples-iiif/courbet.json +9 -6
  384. package/dist/examples-iiif/empty.json +8 -4
  385. package/dist/examples-iiif/flickr-drewvigal.json +248 -24
  386. package/dist/examples-iiif/flickr-zach.wise.json +8 -4
  387. package/dist/examples-iiif/font-css-false.json +114 -0
  388. package/dist/examples-iiif/georeferenced-layer-unsupported.json +153 -0
  389. package/dist/examples-iiif/georeferenced-layer.json +318 -0
  390. package/dist/examples-iiif/iiif-wellcome.json +9 -6
  391. package/dist/examples-iiif/instagram-chenpamela.json +381 -36
  392. package/dist/examples-iiif/instagram_brentfoster.json +429 -40
  393. package/dist/examples-iiif/instagram_couch.json +261 -26
  394. package/dist/examples-iiif/instagram_joegermuska.json +441 -41
  395. package/dist/examples-iiif/instagram_matteich.json +261 -26
  396. package/dist/examples-iiif/instagram_mirandamulligan.json +261 -26
  397. package/dist/examples-iiif/instagram_raymondmccrea.json +261 -26
  398. package/dist/examples-iiif/instagram_test.json +261 -26
  399. package/dist/examples-iiif/issue-134-no-lines.json +9 -5
  400. package/dist/examples-iiif/issue-177-color-vars.json +9 -5
  401. package/dist/examples-iiif/issue-211-rtl.json +9 -5
  402. package/dist/examples-iiif/issue-242-nonlatin.json +239 -0
  403. package/dist/examples-iiif/issue-272-facebook.json +9 -5
  404. package/dist/examples-iiif/issue-305-start-at-slide-data.json +305 -0
  405. package/dist/examples-iiif/issue-305-start-at-slide.json +9 -5
  406. package/dist/examples-iiif/issue-341-distance.json +9 -5
  407. package/dist/examples-iiif/issue-358-iframe.json +190 -0
  408. package/dist/examples-iiif/issue-360-juxtapose.json +22 -7
  409. package/dist/examples-iiif/issue-368-null-fields.json +9 -5
  410. package/dist/examples-iiif/issue-375-code-lists.json +9 -5
  411. package/dist/examples-iiif/issue-381-dateline.json +9 -5
  412. package/dist/examples-iiif/issue-405-custom-icon.json +203 -0
  413. package/dist/examples-iiif/issue-425-many-slides.json +9 -5
  414. package/dist/examples-iiif/issue-428-background.json +20 -8
  415. package/dist/examples-iiif/issue-435-googlemaps.json +9 -5
  416. package/dist/examples-iiif/issue-437-documentcloud.json +22 -7
  417. package/dist/examples-iiif/issue-451-webp.json +9 -5
  418. package/dist/examples-iiif/issue-455-audio.json +9 -5
  419. package/dist/examples-iiif/issue-480-shorts.json +9 -5
  420. package/dist/examples-iiif/issue-506-marker-sync.json +9 -5
  421. package/dist/examples-iiif/issue-iiif-geo.json +200 -0
  422. package/dist/examples-iiif/issue-image-alt.json +90 -0
  423. package/dist/examples-iiif/issue-image-region.json +203 -0
  424. package/dist/examples-iiif/issue-image-srcset.json +87 -0
  425. package/dist/examples-iiif/jansteen.json +9 -6
  426. package/dist/examples-iiif/katrina.json +197 -41
  427. package/dist/examples-iiif/layers-control.json +226 -0
  428. package/dist/examples-iiif/map-area-overlays.json +289 -0
  429. package/dist/examples-iiif/map-template-provider.json +133 -0
  430. package/dist/examples-iiif/marktwain.json +255 -58
  431. package/dist/examples-iiif/multi-media.json +237 -0
  432. package/dist/examples-iiif/no-map.json +148 -0
  433. package/dist/examples-iiif/population.json +440 -76
  434. package/dist/examples-iiif/president.json +456 -83
  435. package/dist/examples-iiif/scroll-hint.json +190 -0
  436. package/dist/examples-iiif/seurat.json +113 -22
  437. package/dist/examples-iiif/zoomify-clamp.json +153 -0
  438. package/dist/examples-slideshow/single.json +79 -0
  439. package/dist/examples-slideshow/static/pixel.png +0 -0
  440. package/dist/examples-slideshow/static.json +44 -0
  441. package/dist/harness-multi.html +17 -0
  442. package/dist/harness.html +4 -2
  443. package/dist/index.html +19 -12
  444. package/dist/js/storymap.d.ts +2669 -307
  445. package/dist/js/storymap.js +10820 -6196
  446. package/dist/js/storymap.js.map +1 -1
  447. package/dist/navplace-properties.json +54 -0
  448. package/dist/site.css +54 -36
  449. package/docs/migration-from-knightlab.md +279 -19
  450. package/docs/storymap-as-iiif-manifest.md +633 -136
  451. package/package.json +12 -7
  452. package/schema/storymap.schema.json +733 -80
  453. package/src/animation/easings.ts +8 -1
  454. package/src/animation/tween.ts +186 -0
  455. package/src/core/Browser.ts +6 -5
  456. package/src/core/Load.ts +54 -2
  457. package/src/core/Util.ts +257 -193
  458. package/src/core/mixins.ts +66 -35
  459. package/src/core/viewers.ts +94 -0
  460. package/src/dom/DomEvent.ts +27 -8
  461. package/src/globals.d.ts +8 -0
  462. package/src/language/Language.ts +206 -25
  463. package/src/language/locale/.expected-gaps.json +346 -0
  464. package/src/language/locale/be.json +15 -2
  465. package/src/language/locale/bg.json +15 -2
  466. package/src/language/locale/cs.json +15 -2
  467. package/src/language/locale/de.json +23 -2
  468. package/src/language/locale/el.json +15 -2
  469. package/src/language/locale/en.json +17 -3
  470. package/src/language/locale/es.json +15 -2
  471. package/src/language/locale/et.json +5 -1
  472. package/src/language/locale/fr.json +15 -2
  473. package/src/language/locale/he.json +16 -3
  474. package/src/language/locale/hu.json +15 -2
  475. package/src/language/locale/is.json +5 -1
  476. package/src/language/locale/it.json +15 -2
  477. package/src/language/locale/jp.json +15 -2
  478. package/src/language/locale/ko.json +15 -2
  479. package/src/language/locale/nl.json +15 -2
  480. package/src/language/locale/nn.json +5 -1
  481. package/src/language/locale/no.json +15 -2
  482. package/src/language/locale/pl.json +15 -2
  483. package/src/language/locale/pt.json +15 -2
  484. package/src/language/locale/ru.json +15 -2
  485. package/src/language/locale/sk.json +15 -2
  486. package/src/language/locale/sr.json +5 -1
  487. package/src/language/locale/sv.json +15 -2
  488. package/src/language/locale/tr.json +15 -2
  489. package/src/language/locale/uk.json +15 -2
  490. package/src/language/locale/ur.json +5 -1
  491. package/src/language/locale/zh-cn.json +15 -2
  492. package/src/language/locale/zh-tw.json +15 -2
  493. package/src/main.ts +143 -0
  494. package/src/map/Map.ts +227 -112
  495. package/src/map/MapMarker.ts +44 -8
  496. package/src/map/georeference.ts +325 -0
  497. package/src/map/openlayers/Map.OpenLayers.ts +1601 -263
  498. package/src/map/openlayers/MapMarker.OpenLayers.ts +348 -24
  499. package/src/map/openlayers/zoomifyTiles.ts +1 -4
  500. package/src/map/slideView.ts +149 -0
  501. package/src/map/types.ts +2 -0
  502. package/src/media/EmbedUtil.ts +333 -24
  503. package/src/media/Media.ts +270 -59
  504. package/src/media/embedId.ts +110 -0
  505. package/src/media/types/Audio.ts +9 -82
  506. package/src/media/types/Blockquote.ts +2 -3
  507. package/src/media/types/DailyMotion.ts +5 -10
  508. package/src/media/types/DocumentCloud.ts +6 -35
  509. package/src/media/types/Facebook.ts +3 -4
  510. package/src/media/types/Flickr.ts +23 -12
  511. package/src/media/types/GoogleDoc.ts +3 -4
  512. package/src/media/types/HtmlMedia.ts +233 -0
  513. package/src/media/types/IFrame.ts +3 -4
  514. package/src/media/types/Image.ts +87 -9
  515. package/src/media/types/Juxtapose.ts +14 -5
  516. package/src/media/types/SoundCloud.ts +28 -6
  517. package/src/media/types/Text.ts +46 -21
  518. package/src/media/types/Twitter.ts +35 -20
  519. package/src/media/types/Video.ts +9 -79
  520. package/src/media/types/Vimeo.ts +11 -11
  521. package/src/media/types/Website.ts +23 -13
  522. package/src/media/types/Wikipedia.ts +18 -8
  523. package/src/media/types/YouTube.ts +72 -40
  524. package/src/scss/Typography.scss +16 -18
  525. package/src/scss/VCO.StoryMap.scss +208 -12
  526. package/src/scss/Variables.scss +3 -2
  527. package/src/scss/core/Mixins.scss +5 -63
  528. package/src/scss/core/Reset.scss +5 -18
  529. package/src/scss/fonts/font.amatic-andika.scss +3 -0
  530. package/src/scss/fonts/font.bitter-raleway.scss +6 -0
  531. package/src/scss/fonts/font.cairo.scss +79 -0
  532. package/src/scss/fonts/font.clicker-garamond.scss +5 -0
  533. package/src/scss/fonts/font.dancing-ledger.scss +1 -0
  534. package/src/scss/fonts/font.knightlab.scss +14 -0
  535. package/src/scss/fonts/font.oldstandard.scss +6 -0
  536. package/src/scss/fonts/font.opensans-gentiumbook.scss +5 -0
  537. package/src/scss/fonts/font.playfair-faunaone.scss +2 -0
  538. package/src/scss/fonts/font.playfair.scss +4 -0
  539. package/src/scss/fonts/font.pt.scss +6 -0
  540. package/src/scss/fonts/font.ubuntu.scss +4 -0
  541. package/src/scss/fonts/font.unicaone-vollkorn.scss +5 -0
  542. package/src/scss/map/VCO.Map.scss +1 -1
  543. package/src/scss/map/VCO.MapMarker.scss +114 -8
  544. package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +7 -4
  545. package/src/scss/media/VCO.Media.scss +14 -17
  546. package/src/scss/media/types/VCO.Media.Blockquote.scss +1 -1
  547. package/src/scss/media/types/VCO.Media.Text.scss +13 -5
  548. package/src/scss/media/types/VCO.Media.Twitter.scss +4 -4
  549. package/src/scss/media/types/VCO.Media.Wikipedia.scss +3 -19
  550. package/src/scss/site/site.scss +55 -36
  551. package/src/scss/slider/VCO.Slide.scss +61 -13
  552. package/src/scss/slider/VCO.SlideNav.scss +34 -24
  553. package/src/scss/slider/VCO.StorySlider.scss +53 -1
  554. package/src/scss/snapmap/VCO.SnapMap.scss +3 -3
  555. package/src/scss/ui/VCO.Consent.scss +63 -13
  556. package/src/scss/ui/VCO.Layers.scss +119 -0
  557. package/src/scss/ui/VCO.MenuBar.Button.scss +143 -14
  558. package/src/scss/ui/VCO.MenuBar.scss +9 -2
  559. package/src/scss/ui/VCO.Message.scss +16 -5
  560. package/src/site/site.ts +2 -1
  561. package/src/slider/Slide.ts +326 -74
  562. package/src/slider/SlideNav.ts +54 -9
  563. package/src/slider/StorySlider.ts +352 -60
  564. package/src/storymap/Consent.ts +429 -82
  565. package/src/storymap/StoryMap.ts +2163 -340
  566. package/src/storymap/content-state.ts +180 -0
  567. package/src/storymap/font-css.ts +72 -0
  568. package/src/storymap/from-slideshow.ts +785 -0
  569. package/src/storymap/iiif-shared.ts +228 -0
  570. package/src/storymap/iiif.ts +1742 -95
  571. package/src/storymap/to-iiif.ts +1008 -0
  572. package/src/storymap/validate.ts +80 -7
  573. package/src/types.ts +591 -29
  574. package/src/ui/LayersControl.ts +302 -0
  575. package/src/ui/MenuBar.ts +325 -61
  576. package/src/ui/Message.ts +44 -2
  577. package/src/ui/Swipable.ts +170 -85
  578. package/dist/assets/demo-D6cTmzn6.js +0 -1
  579. package/dist/assets/harness-BPFlzutp.js +0 -1
  580. package/dist/assets/index-DDsPBPZA.js +0 -1
  581. package/dist/assets/main-Y-MYUSJ4.js +0 -17
  582. package/src/animation/morpheus.d.ts +0 -41
  583. package/src/scss/VCO.StoryMap.Dark.scss +0 -138
  584. package/src/scss/Variables.Dark.scss +0 -56
  585. package/src/scss/media/types/VCO.Media.Instagram.scss +0 -7
  586. package/src/scss/media/types/VCO.Media.Profile.scss +0 -5
  587. package/src/ui/Draggable.ts +0 -374
@@ -2,6 +2,9 @@ import Overlay from "ol/Overlay";
2
2
  import { fromLonLat } from "ol/proj";
3
3
  import type { Map as OlMap } from "ol";
4
4
  import MapMarker from "../MapMarker";
5
+ import { clearTimer } from "../../core/Util";
6
+ import { Language } from "../../language/Language";
7
+ import { sanitizeSlideText } from "../../media/EmbedUtil";
5
8
  import type { LatLngLiteral, MapMarkerData, StorymapOptions } from "../../types";
6
9
 
7
10
  /* MapMarker.OpenLayers
@@ -11,30 +14,131 @@ import type { LatLngLiteral, MapMarkerData, StorymapOptions } from "../../types"
11
14
  existing .vco-mapmarker styles apply unchanged.
12
15
  ================================================= */
13
16
 
17
+ /** The resolved marker presentation: `marker.*` over `location.*`. */
18
+ interface MarkerPresentation {
19
+ icon?: string;
20
+ iconSize?: number[];
21
+ image?: string;
22
+ label?: string;
23
+ popup: boolean;
24
+ audioBadge: boolean;
25
+ useCustomMarker?: boolean;
26
+ }
27
+
14
28
  export default class OpenLayersMapMarker extends MapMarker {
15
29
  declare "_overlay": Overlay;
30
+ declare "_onMarkerClickBound": ((e: Event) => void) | null;
31
+ /** Enter/Space activation for keyboard visitors, detached in dispose(). */
32
+ declare "_onMarkerKeyBound": ((e: KeyboardEvent) => void) | null;
33
+ /** Base accessible name (slide headline or label), without audio suffix. */
34
+ declare "_marker_label": string;
35
+ /** marker.popup: the active marker opens its card when clicked. */
36
+ declare "_popup_enabled": boolean;
37
+ /** marker.audioBadge: flag a slide that has narration or audio media. */
38
+ declare "_audio_badge": boolean;
39
+ /**
40
+ * The popup card while it is open. `declare` fields are undefined until
41
+ * assigned, so every read below is a truthiness check, never `=== null`,
42
+ * and there is no constructor to initialise them: `MapMarker`'s own
43
+ * constructor already runs `_initLayout()` → `_createMarker()`, which
44
+ * assigns the flags, and initialising afterwards would wipe them.
45
+ */
46
+ declare "_popup_el": HTMLElement | null;
47
+ /** Escape-to-close, detached in dispose(). */
48
+ declare "_onPopupKeyBound": ((e: KeyboardEvent) => void) | null;
49
+ /** Mirrors `active()`, as a flag rather than a method reference. */
50
+ declare "_is_active": boolean;
16
51
 
17
52
  /* Create Marker
18
53
  ================================================== */
54
+ /**
55
+ * The merged marker presentation: `marker.*` wins, `location.*` is the
56
+ * legacy spelling and still works.
57
+ * `location` also stays the carrier of geography, so lat/lon are read
58
+ * from it alone — presentation never moves a marker.
59
+ */
60
+ _presentation(d?: MapMarkerData): MarkerPresentation {
61
+ const location = (d?.location ?? {}) as Record<string, unknown>;
62
+ const marker = (d?.marker ?? {}) as Record<string, unknown>;
63
+ return {
64
+ icon: (marker.icon as string | undefined) ?? (location.icon as string | undefined),
65
+ iconSize:
66
+ (marker.iconSize as number[] | undefined) ??
67
+ (location.iconSize as number[] | undefined),
68
+ image: (marker.image as string | undefined) ?? (location.image as string | undefined),
69
+ // in a manifest the label arrives as the navPlace property `name`
70
+ label: (marker.label as string | undefined) ?? (location.name as string | undefined),
71
+ popup: Boolean(marker.popup ?? location.popup),
72
+ audioBadge: Boolean(marker.audioBadge ?? location.audioBadge),
73
+ useCustomMarker: location.use_custom_marker as boolean | undefined,
74
+ };
75
+ }
76
+
19
77
  _createMarker(d?: MapMarkerData, o?: StorymapOptions): void {
20
- if (d.location && typeof d.location.lat == "number" && typeof d.location.lon == "number") {
78
+ const location = d?.location;
79
+ if (location && typeof location.lat == "number" && typeof location.lon == "number") {
21
80
  this.data.real_marker = true;
22
- const use_custom_marker = o.use_custom_markers || d.location.use_custom_marker;
23
- if (use_custom_marker && d.location.icon) {
81
+ const presentation = this._presentation(d);
82
+ this._popup_enabled = presentation.popup;
83
+ this._audio_badge = presentation.audioBadge;
84
+ const use_custom_marker = o?.use_custom_markers || presentation.useCustomMarker;
85
+ if (use_custom_marker && presentation.icon) {
24
86
  this._custom_icon = {
25
- url: d.location.icon,
26
- size: d.location.iconSize || [48, 48],
27
- anchor: this._customIconAnchor(d.location.iconSize),
87
+ url: presentation.icon,
88
+ size: presentation.iconSize || [48, 48],
89
+ anchor: this._customIconAnchor(presentation.iconSize),
28
90
  };
29
- } else if (use_custom_marker && d.location.image) {
30
- this._custom_image_icon = d.location.image;
91
+ } else if (use_custom_marker && presentation.image) {
92
+ this._custom_image_icon = presentation.image;
31
93
  }
32
94
 
33
- this._marker = this._createMarkerElement(d, o);
34
- this._marker.addEventListener("click", (e) => {
95
+ this._marker = this._createMarkerElement(d as MapMarkerData, o);
96
+ // keyboard route to the same behaviour as a click: focusable,
97
+ // announced as a button, Enter/Space activate
98
+ const text = (d as MapMarkerData)?.text;
99
+ const headline =
100
+ typeof text?.headline === "string" && text.headline !== "" ? text.headline : null;
101
+ this._marker_label = presentation.label ?? headline ?? "Marker";
102
+ this._marker.setAttribute("tabindex", "0");
103
+ this._marker.setAttribute("role", "button");
104
+ this._marker.setAttribute("aria-label", this._marker_label);
105
+ // kept as a field so dispose() can detach it; the inline arrow
106
+ // had no other handle
107
+ this._onMarkerClickBound = (e: Event) => {
35
108
  e.stopPropagation();
109
+ // the active marker's card toggles instead of navigating:
110
+ // the slide is already showing, and a popup that also moved
111
+ // the story would be impossible to dismiss
112
+ if (this._popup_enabled && this._is_active) {
113
+ this._togglePopup();
114
+ return;
115
+ }
36
116
  this._onMarkerClick(e);
37
- });
117
+ };
118
+ this._marker.addEventListener("click", this._onMarkerClickBound);
119
+ // Keyboard visitors get the click behaviour: Enter/Space
120
+ // activates, and an opened card takes focus so its close button
121
+ // is reachable without a pointer.
122
+ this._onMarkerKeyBound = (e: KeyboardEvent) => {
123
+ if (e.key !== "Enter" && e.key !== " " && e.key !== "Spacebar") return;
124
+ e.preventDefault();
125
+ const wasOpen = this.popupOpen;
126
+ this._onMarkerClickBound?.(e);
127
+ if (!wasOpen && this.popupOpen) {
128
+ (
129
+ this._popup_el?.querySelector(
130
+ ".vco-marker-popup-close",
131
+ ) as HTMLElement | null
132
+ )?.focus();
133
+ }
134
+ };
135
+ this._marker.addEventListener("keydown", this._onMarkerKeyBound);
136
+ // Escape closes the card; the listener is on the document and is
137
+ // detached in dispose(), so a torn-down map leaves nothing behind
138
+ this._onPopupKeyBound = (e: KeyboardEvent) => {
139
+ if (e.key === "Escape") this._closePopup();
140
+ };
141
+ document.addEventListener("keydown", this._onPopupKeyBound);
38
142
  }
39
143
  }
40
144
 
@@ -49,6 +153,8 @@ export default class OpenLayersMapMarker extends MapMarker {
49
153
  el.className = "vco-mapmarker-custom";
50
154
  const img = document.createElement("img");
51
155
  img.src = this._custom_icon.url;
156
+ // decorative: the parent div carries the marker's title
157
+ img.alt = "";
52
158
  img.style.width = this._custom_icon.size[0] + "px";
53
159
  img.style.height = "auto";
54
160
  el.appendChild(img);
@@ -58,6 +164,7 @@ export default class OpenLayersMapMarker extends MapMarker {
58
164
  el.className = "vco-mapmarker-image-icon";
59
165
  const img = document.createElement("img");
60
166
  img.src = this._custom_image_icon;
167
+ img.alt = "";
61
168
  img.style.width = "48px";
62
169
  img.style.height = "auto";
63
170
  el.appendChild(img);
@@ -69,14 +176,31 @@ export default class OpenLayersMapMarker extends MapMarker {
69
176
  return el;
70
177
  }
71
178
 
179
+ /**
180
+ * The marker's {lat, lon}, or null when it is a non-georeferenced marker
181
+ * (an overview slide, or a slide with only an image region). `_createMarker`
182
+ * already established this for a real marker, but returning it from one
183
+ * place means the type checker can see it too.
184
+ */
185
+ latLon(): LatLngLiteral | null {
186
+ const location = this.data.location;
187
+ if (!this.data.real_marker || !location) {
188
+ return null;
189
+ }
190
+ if (typeof location.lat !== "number" || typeof location.lon !== "number") {
191
+ return null;
192
+ }
193
+ return { lat: location.lat, lon: location.lon };
194
+ }
195
+
72
196
  _addTo(m: OlMap): void {
73
- if (this.data.real_marker) {
74
- const d = this.data;
197
+ const latlon = this.latLon();
198
+ if (latlon && this._marker) {
75
199
  // Image-space maps (IIIF) use EPSG:4326 with raw image pixel coordinates
76
200
  const is_image_space = m.getView().getProjection().getCode() === "EPSG:4326";
77
201
  const position = is_image_space
78
- ? [d.location.lon, d.location.lat]
79
- : fromLonLat([d.location.lon, d.location.lat]);
202
+ ? [latlon.lon, latlon.lat]
203
+ : fromLonLat([latlon.lon, latlon.lat]);
80
204
 
81
205
  // Default pins anchor on their tip: OL's inline styles override
82
206
  // any CSS top/left, so the alignment is done here — bottom-center
@@ -101,7 +225,211 @@ export default class OpenLayersMapMarker extends MapMarker {
101
225
  }
102
226
  }
103
227
 
228
+ /**
229
+ * Terminal teardown: detach the click listener and drop the overlay and
230
+ * element. Called by the map's dispose(); the marker must not be used
231
+ * afterwards.
232
+ */
233
+ /* Marker popup and audio badge
234
+ ================================================== */
235
+
236
+ /**
237
+ * Toggle the popup card for the *active* marker. Inactive markers keep
238
+ * their original behaviour — a click navigates — so a popup never
239
+ * swallows navigation on a map full of pins.
240
+ *
241
+ * The card is a DOM element parented to the marker element and positioned
242
+ * with CSS, which is how it survives a move to the vector renderer
243
+ * (docs/plans/issue-159-vector-markers.md): only the anchor is the
244
+ * marker's, and that is `latLon()`.
245
+ */
246
+ _togglePopup(): void {
247
+ this.togglePopup();
248
+ }
249
+
250
+ _closePopup(): void {
251
+ this.closePopup();
252
+ }
253
+
254
+ /** True when this marker is configured to show a popup card. */
255
+ isPopupEnabled(): boolean {
256
+ return this._popup_enabled === true;
257
+ }
258
+
259
+ /**
260
+ * Open the popup card for this marker.
261
+ *
262
+ * Returns false (and opens nothing) when popups are disabled for the
263
+ * marker or it is not a real map marker — e.g. an overview slide. A
264
+ * click on an inactive marker navigates instead of opening (see the
265
+ * click handler in `_createMarker`); programmatic callers that want the
266
+ * card on another stop should navigate first (`StoryMap.openMarkerPopup`
267
+ * does exactly that). Fires `popupopen` on success.
268
+ */
269
+ openPopup(): boolean {
270
+ if (!this._popup_enabled) return false;
271
+ if (!this.data.real_marker) return false;
272
+ if (this._popup_el) return true;
273
+ const el = this._createPopupElement();
274
+ if (el === null) return false;
275
+ this._marker.appendChild(el);
276
+ this._popup_el = el;
277
+ this._marker.classList.add("vco-mapmarker-popup-open");
278
+ this.fire("popupopen", { marker_number: this.marker_number });
279
+ return true;
280
+ }
281
+
282
+ /**
283
+ * Close the popup card if it is open. Fires `popupclose`. Also runs on
284
+ * Escape, the card's close button, deactivation and `dispose()`.
285
+ */
286
+ closePopup(): void {
287
+ if (!this._popup_el) return;
288
+ // keyboard visitors may be inside the card (its close button is
289
+ // focusable): hand focus back to the marker instead of dropping it
290
+ // on the page body. Mouse visitors never had focus here, so theirs
291
+ // is untouched.
292
+ const hadFocus = this._popup_el.contains(document.activeElement);
293
+ this._popup_el.parentNode?.removeChild(this._popup_el);
294
+ this._popup_el = null;
295
+ this._marker?.classList?.remove("vco-mapmarker-popup-open");
296
+ if (hadFocus) this._marker?.focus();
297
+ this.fire("popupclose", { marker_number: this.marker_number });
298
+ }
299
+
300
+ /**
301
+ * Open the card when closed, close it when open. Returns whether the
302
+ * card is open afterwards (false when popups are disabled).
303
+ */
304
+ togglePopup(): boolean {
305
+ if (this._popup_el) {
306
+ this.closePopup();
307
+ return false;
308
+ }
309
+ return this.openPopup();
310
+ }
311
+
312
+ get popupOpen(): boolean {
313
+ return Boolean(this._popup_el);
314
+ }
315
+
316
+ /**
317
+ * Headline, a sanitized excerpt, the media thumb and an audio control.
318
+ * Every piece of text goes through `sanitizeSlideText` — a slide's text
319
+ * is untrusted input and a marker card is no different (the stored-XSS
320
+ * audit made that lesson once; a label is attacker-controlled text like
321
+ * any other).
322
+ */
323
+ _createPopupElement(): HTMLElement | null {
324
+ const data = this.data as unknown as {
325
+ text?: { headline?: string; text?: string };
326
+ media?: { thumb?: string | null; url?: string | null; caption?: string | null };
327
+ media_extra?: { thumb?: string | null; url?: string | null; caption?: string | null }[];
328
+ };
329
+ const card = document.createElement("div");
330
+ card.className = "vco-marker-popup";
331
+ // a dialog-ish card, not a modal: no focus trap, Escape closes
332
+ card.setAttribute("role", "group");
333
+ card.setAttribute("aria-label", data.text?.headline ?? "Slide details");
334
+
335
+ const close = document.createElement("button");
336
+ close.type = "button";
337
+ close.className = "vco-marker-popup-close";
338
+ close.setAttribute("aria-label", "Close");
339
+ close.textContent = "\u00d7";
340
+ close.addEventListener("click", (e) => {
341
+ e.stopPropagation();
342
+ this._closePopup();
343
+ });
344
+ card.appendChild(close);
345
+
346
+ // first item with a thumb across the primary and the extras (#358)
347
+ const thumb_item = [data.media, ...(data.media_extra ?? [])].find((item) => !!item?.thumb);
348
+ if (thumb_item?.thumb) {
349
+ const img = document.createElement("img");
350
+ img.className = "vco-marker-popup-thumb";
351
+ img.src = thumb_item.thumb;
352
+ img.alt = thumb_item.caption ?? "";
353
+ img.loading = "lazy";
354
+ card.appendChild(img);
355
+ }
356
+
357
+ const body = document.createElement("div");
358
+ body.className = "vco-marker-popup-body";
359
+ if (data.text?.headline) {
360
+ const h = document.createElement("h3");
361
+ h.className = "vco-marker-popup-headline";
362
+ h.appendChild(sanitizeSlideText(data.text.headline));
363
+ body.appendChild(h);
364
+ }
365
+ if (data.text?.text) {
366
+ const p = document.createElement("p");
367
+ p.className = "vco-marker-popup-excerpt";
368
+ p.appendChild(sanitizeSlideText(data.text.text));
369
+ body.appendChild(p);
370
+ }
371
+ card.appendChild(body);
372
+ return card;
373
+ }
374
+
375
+ /**
376
+ * A small indicator on markers whose slide has narration or audio media
377
+ * (the Micrio affordance). Recomputed on activation, because a slide's
378
+ * media may only be known once it is resolved.
379
+ */
380
+ _updateAudioBadge(): void {
381
+ if (!this._audio_badge) return;
382
+ const media = (this.data as unknown as { media?: { mediatype?: { type?: string } } }).media;
383
+ // any audible item badges the marker, not just item 0: an
384
+ // image-first/audio-second slide is still an audible stop (#358)
385
+ const extra =
386
+ (this.data as unknown as { media_extra?: { mediatype?: { type?: string } }[] })
387
+ .media_extra ?? [];
388
+ const audible =
389
+ [media, ...extra].some((item) => {
390
+ const kind = item?.mediatype?.type;
391
+ return kind === "audio" || kind === "video";
392
+ }) || !!this.data.narration;
393
+ this._marker.classList.toggle("vco-mapmarker-has-audio", audible);
394
+ const suffix =
395
+ audible && typeof Language.buttons.audio_badge === "string"
396
+ ? `, ${Language.buttons.audio_badge}`
397
+ : "";
398
+ this._marker.setAttribute("aria-label", `${this._marker_label}${suffix}`);
399
+ }
400
+
401
+ dispose(): void {
402
+ const marker = this._marker as unknown as HTMLElement | null;
403
+ this._closePopup();
404
+ if (this._onPopupKeyBound) {
405
+ document.removeEventListener("keydown", this._onPopupKeyBound);
406
+ this._onPopupKeyBound = null;
407
+ }
408
+ if (this._onMarkerClickBound) {
409
+ marker?.removeEventListener?.("click", this._onMarkerClickBound);
410
+ this._onMarkerClickBound = null;
411
+ }
412
+ if (this._onMarkerKeyBound) {
413
+ marker?.removeEventListener?.("keydown", this._onMarkerKeyBound);
414
+ this._onMarkerKeyBound = null;
415
+ }
416
+ if (this._overlay) {
417
+ this._overlay.setElement(undefined);
418
+ this._overlay = null as unknown as Overlay;
419
+ }
420
+ // via the parent so a non-DOM stand-in (a test double) is tolerated
421
+ marker?.parentNode?.removeChild(marker);
422
+ this._marker = null as unknown as HTMLDivElement;
423
+ }
424
+
104
425
  _active(a: boolean): void {
426
+ this._is_active = a;
427
+ if (!a) {
428
+ // navigating away closes the card: a popup belongs to its stop
429
+ this._closePopup();
430
+ } else {
431
+ this._updateAudioBadge();
432
+ }
105
433
  if (this.data.media && this.data.media.mediatype) {
106
434
  this.media_icon_class = "vco-mapmarker-icon vco-icon-" + this.data.media.mediatype.type;
107
435
  } else {
@@ -112,7 +440,7 @@ export default class OpenLayersMapMarker extends MapMarker {
112
440
  // custom icons look the same active or not (as in the
113
441
  // original Leaflet version): only the stacking changes
114
442
  if (!a) {
115
- clearTimeout(this.timer);
443
+ clearTimer(this.timer);
116
444
  }
117
445
  this._marker.style.zIndex = a ? "1000" : "";
118
446
  } else if (this._custom_image_icon) {
@@ -121,7 +449,7 @@ export default class OpenLayersMapMarker extends MapMarker {
121
449
  this._marker.classList.add("vco-mapmarker-image-icon-active");
122
450
  this._marker.style.zIndex = "1000";
123
451
  } else {
124
- clearTimeout(this.timer);
452
+ clearTimer(this.timer);
125
453
  this._marker.classList.remove("vco-mapmarker-image-icon-active");
126
454
  this._marker.classList.add("vco-mapmarker-image-icon");
127
455
  this._marker.style.zIndex = "";
@@ -131,7 +459,7 @@ export default class OpenLayersMapMarker extends MapMarker {
131
459
  this._marker.classList.add("vco-mapmarker-active");
132
460
  this._marker.style.zIndex = "1000";
133
461
  } else {
134
- clearTimeout(this.timer);
462
+ clearTimer(this.timer);
135
463
  this._marker.classList.remove("vco-mapmarker-active");
136
464
  this._marker.classList.add("vco-mapmarker");
137
465
  this._marker.style.zIndex = "";
@@ -166,11 +494,7 @@ export default class OpenLayersMapMarker extends MapMarker {
166
494
  }
167
495
  }
168
496
 
169
- _location(): LatLngLiteral {
170
- if (this.data.real_marker) {
171
- return { lat: this.data.location.lat, lon: this.data.location.lon };
172
- } else {
173
- return {} as LatLngLiteral;
174
- }
497
+ _location(): LatLngLiteral | null {
498
+ return this.latLon();
175
499
  }
176
500
  }
@@ -15,10 +15,7 @@ export const ZOOMIFY_TILE_SIZE = 256;
15
15
  * Full-size tiles are left untouched for OpenLayers' own load handling.
16
16
  */
17
17
  export function padCroppedZoomifyTile(tile: ImageTile, image: HTMLImageElement): void {
18
- if (
19
- image.naturalWidth !== ZOOMIFY_TILE_SIZE ||
20
- image.naturalHeight !== ZOOMIFY_TILE_SIZE
21
- ) {
18
+ if (image.naturalWidth !== ZOOMIFY_TILE_SIZE || image.naturalHeight !== ZOOMIFY_TILE_SIZE) {
22
19
  const canvas = document.createElement("canvas");
23
20
  canvas.width = ZOOMIFY_TILE_SIZE;
24
21
  canvas.height = ZOOMIFY_TILE_SIZE;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Pure view helpers for slideshow-driven slide presentation: rotation,
3
+ * CSS filter grading, spotlight masks and per-slide basemaps.
4
+ *
5
+ * DOM- and OpenLayers-free so the CLI and unit tests can use them; the map
6
+ * engine (`Map.OpenLayers`) applies their output. Every helper treats
7
+ * absent/mistyped input as "feature off", so stories that never mention the
8
+ * new fields render exactly as before.
9
+ */
10
+
11
+ import type { StorymapSlideFilter, StorymapSlideLocation, StorymapSlideMask } from "../types";
12
+ import { asClampedNumber } from "../storymap/iiif-shared.ts";
13
+
14
+ /**
15
+ * Valid ranges for filter grading, shared by the translator (which reads
16
+ * them) and the renderer (which enforces them again — hand-written JSON
17
+ * reaches the renderer directly, so read-time clamping alone is not enough).
18
+ */
19
+ export const FILTER_RANGES = {
20
+ brightness: { min: 0, max: 200, def: 100 },
21
+ contrast: { min: 0, max: 200, def: 100 },
22
+ saturate: { min: 0, max: 200, def: 100 },
23
+ hueRotate: { min: -180, max: 180, def: 0 },
24
+ sepia: { min: 0, max: 100, def: 0 },
25
+ blur: { min: 0, max: 20, def: 0 },
26
+ } as const;
27
+
28
+ /** View rotation in radians, or null when the slide states none. */
29
+ export function slideRotationRad(
30
+ location: StorymapSlideLocation | null | undefined,
31
+ ): number | null {
32
+ const rotation = location?.rotation;
33
+ if (typeof rotation !== "number" || !Number.isFinite(rotation) || rotation === 0) {
34
+ return null;
35
+ }
36
+ return (rotation * Math.PI) / 180;
37
+ }
38
+
39
+ /**
40
+ * A `filter()` CSS value for the map viewport from a slide `filter` bag
41
+ * (slideshow `filters`): brightness/contrast/saturate in percent,
42
+ * hue-rotate in degrees, sepia in percent, blur in pixels. Unknown keys
43
+ * never reach CSS. Returns "" when nothing is graded.
44
+ */
45
+ export function buildSlideFilter(filter: StorymapSlideFilter | null | undefined): string {
46
+ if (!filter || typeof filter !== "object") return "";
47
+ const parts: string[] = [];
48
+ const percent = (value: unknown, name: "brightness" | "contrast" | "saturate" | "sepia") => {
49
+ if (typeof value !== "number" || !Number.isFinite(value)) return;
50
+ const { min, max, def } = FILTER_RANGES[name];
51
+ if (value === def) return;
52
+ parts.push(`${name}(${asClampedNumber(value, min, max)}%)`);
53
+ };
54
+ percent(filter.brightness, "brightness");
55
+ percent(filter.contrast, "contrast");
56
+ percent(filter.saturate, "saturate");
57
+ const hue = asClampedNumber(filter.hueRotate, ...filterRange("hueRotate"));
58
+ if (hue !== null && hue !== FILTER_RANGES.hueRotate.def) {
59
+ parts.push(`hue-rotate(${hue}deg)`);
60
+ }
61
+ percent(filter.sepia, "sepia");
62
+ const blur = asClampedNumber(filter.blur, ...filterRange("blur"));
63
+ if (blur !== null && blur !== FILTER_RANGES.blur.def) {
64
+ parts.push(`blur(${blur}px)`);
65
+ }
66
+ return parts.join(" ");
67
+ }
68
+
69
+ /** The `[min, max]` pair for a filter range (spread into `asClampedNumber`). */
70
+ function filterRange(key: keyof typeof FILTER_RANGES): [number, number] {
71
+ return [FILTER_RANGES[key].min, FILTER_RANGES[key].max];
72
+ }
73
+
74
+ const MASK_COLOR = /^(#[0-9a-f]{3}([0-9a-f]{3}([0-9a-f]{2})?)?|rgba?\([^)]*\))$/i;
75
+
76
+ export interface NormalizedMask {
77
+ x: number;
78
+ y: number;
79
+ w: number;
80
+ h: number;
81
+ color: string;
82
+ invert: boolean;
83
+ opacity: number;
84
+ }
85
+
86
+ /**
87
+ * A validated spotlight mask (slideshow `passepartout`): normalized
88
+ * viewport fractions plus presentation. Returns null for anything that is
89
+ * not a usable `{x, y, w, h}` shape; out-of-range fractions are clamped,
90
+ * an unparsable color falls back to translucent black.
91
+ */
92
+ export function normalizeMask(mask: StorymapSlideMask | null | undefined): NormalizedMask | null {
93
+ if (!mask || typeof mask !== "object") return null;
94
+ const { x, y, w, h } = mask;
95
+ if (
96
+ typeof x !== "number" ||
97
+ typeof y !== "number" ||
98
+ typeof w !== "number" ||
99
+ typeof h !== "number" ||
100
+ ![x, y, w, h].every(Number.isFinite)
101
+ ) {
102
+ return null;
103
+ }
104
+ const clamp01 = (n: number) => Math.min(1, Math.max(0, n));
105
+ const nx = clamp01(x);
106
+ const ny = clamp01(y);
107
+ // shrink the far edge with a clamped origin (like clampRegion): a mask
108
+ // fully outside the viewport resolves to nothing, not a full mask
109
+ const nw = Math.min(1 - nx, Math.max(0, w + Math.min(0, x)));
110
+ const nh = Math.min(1 - ny, Math.max(0, h + Math.min(0, y)));
111
+ if (!(nw > 0) || !(nh > 0)) return null;
112
+ const rawColor = typeof mask.color === "string" ? mask.color.trim() : "";
113
+ const color = rawColor !== "" && MASK_COLOR.test(rawColor) ? rawColor : "rgba(0,0,0,0.5)";
114
+ const opacity =
115
+ typeof mask.opacity === "number" && Number.isFinite(mask.opacity)
116
+ ? Math.min(1, Math.max(0, mask.opacity))
117
+ : 1;
118
+ return { x: nx, y: ny, w: nw, h: nh, color, invert: mask.invert === true, opacity };
119
+ }
120
+
121
+ /** The per-slide basemap key, or null when the slide keeps the story basemap. */
122
+ export function slideBasemapKey(location: StorymapSlideLocation | null | undefined): string | null {
123
+ const basemap = location?.basemap;
124
+ return typeof basemap === "string" && basemap !== "" ? basemap : null;
125
+ }
126
+
127
+ /**
128
+ * True for plain image-file URLs (no IIIF service, `{z}` template or
129
+ * keyword). Only unambiguous raster extensions: `.jpx`/`.jp2` can be IIIF
130
+ * bases too (Leipzig serves `info.json` off `.jpx`), so those go through
131
+ * the service attempt with a static fallback instead. Relative URLs
132
+ * (same-origin static files) count; templates never match (no extension).
133
+ */
134
+ export function isStaticImageUrl(url: string): boolean {
135
+ if (url.includes("{z}")) return false;
136
+ if (!/\.(jpe?g|png|gif|webp|tif?f|bmp|avif)(\?.*)?$/i.test(url)) return false;
137
+ if (/^https?:\/\//i.test(url)) return true;
138
+ // same-origin relative file without any other scheme (mapbox:, osm:, …)
139
+ return !/^[a-z][a-z0-9+.-]*:/i.test(url);
140
+ }
141
+
142
+ /**
143
+ * An image service base as its `info.json` URL (idempotent for URLs that
144
+ * already name it). Shared by the slideshow translator (story default) and
145
+ * the per-slide basemap builder so the suffix rule cannot drift.
146
+ */
147
+ export function serviceToInfoJson(service: string): string {
148
+ return /info\.json$/i.test(service) ? service : `${service.replace(/\/$/, "")}/info.json`;
149
+ }
package/src/map/types.ts CHANGED
@@ -7,6 +7,8 @@ export interface ViewToOptions {
7
7
  calculate_zoom?: boolean;
8
8
  duration?: number;
9
9
  zoom?: number;
10
+ /** View rotation in degrees clockwise; absent leaves rotation unchanged. */
11
+ rotation?: number;
10
12
  }
11
13
 
12
14
  /** A point of the connection line: either a slide or a raw lat/lon pair. */