@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
@@ -1,60 +1,296 @@
1
- import Cookies from "js-cookie";
2
1
  import { Language } from "../language/Language";
2
+ import Dom from "../dom/Dom";
3
3
 
4
- /** Cookie holding the per-service consent state (JSON). */
5
- const CONSENT_COOKIE = "storymapjs-consent";
6
- /** Consent retention period in days. */
7
- const CONSENT_COOKIE_DAYS = 90;
4
+ /** localStorage key holding the per-service consent state (JSON). */
5
+ const CONSENT_STORAGE_KEY = "storymapjs-consent";
6
+
7
+ /**
8
+ * Read the shared consent record, remapping keys written by an earlier
9
+ * version onto their namespaced form.
10
+ *
11
+ * @returns The state, and whether any key was remapped (so a caller can
12
+ * write the modern form back once and retire the legacy keys).
13
+ */
14
+ function readStoredState(): { state: Record<string, boolean>; migrated: boolean } {
15
+ const state: Record<string, boolean> = {};
16
+ let migrated = false;
17
+ try {
18
+ const raw = window.localStorage.getItem(CONSENT_STORAGE_KEY);
19
+ if (!raw) {
20
+ return { state, migrated };
21
+ }
22
+ const parsed = JSON.parse(raw) as Record<string, boolean>;
23
+ for (const stored in parsed) {
24
+ if (!Object.hasOwn(parsed, stored)) continue;
25
+ const key = migrateLegacyKey(stored);
26
+ if (key !== stored) {
27
+ migrated = true;
28
+ }
29
+ state[key] = parsed[stored];
30
+ }
31
+ } catch {
32
+ // ignore malformed or unavailable storage
33
+ }
34
+ return { state, migrated };
35
+ }
36
+
37
+ /** Write the shared consent record. */
38
+ function writeStoredState(state: Record<string, boolean>): void {
39
+ try {
40
+ window.localStorage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(state));
41
+ } catch {
42
+ // storage unavailable (e.g. sandboxed contexts)
43
+ }
44
+ }
45
+
46
+ /**
47
+ * An external service the viewer asks permission to load.
48
+ *
49
+ * `key` is the *only* thing ever persisted, and it is stable, namespaced and
50
+ * never localized. `label` is display-only.
51
+ *
52
+ * These are two separate fields on purpose. The code used to pass a single
53
+ * string that served as both, which had two consequences:
54
+ *
55
+ * - the tile and font services used a *localized* string as the localStorage
56
+ * key, so translating `consent_service_tiles` would have silently
57
+ * invalidated every stored decision the moment the page language changed;
58
+ * - the per-slide panel used the media type *slug* as its display label, so
59
+ * it asked "Load content from youtube?" where the start-of-story dialog,
60
+ * 20 lines away, correctly said "YouTube".
61
+ *
62
+ * With the pair split, passing a label where a key is expected is no longer
63
+ * expressible.
64
+ */
65
+ export interface ConsentService {
66
+ /** Stable, namespaced, non-localized. The only value that is stored. */
67
+ key: string;
68
+ /** Display name. Localized for tiles/fonts; a brand name for media. */
69
+ label: string;
70
+ }
71
+
72
+ /** Namespaced storage keys. Prefixed so a future service can never collide
73
+ * with a bare media type slug. */
74
+ const TILE_KEY = "map:tiles";
75
+ const FONT_KEY = "fonts:web";
76
+ const MEDIA_KEY_PREFIX = "media:";
77
+
78
+ /** The map tile service: the basemap, overlays, minimap and overview. */
79
+ export function tileService(): ConsentService {
80
+ return {
81
+ key: TILE_KEY,
82
+ label: consentMessage("consent_service_tiles", "map tiles"),
83
+ };
84
+ }
85
+
86
+ /** An externally hosted web-font theme. */
87
+ export function fontService(): ConsentService {
88
+ return {
89
+ key: FONT_KEY,
90
+ label: consentMessage("consent_service_fonts", "web fonts"),
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Slide narration: the recording that plays at a tour stop. It is not a media
96
+ * embed, but it is the same kind of third-party fetch, so it is gated the same
97
+ * way — through the `Media` class it bypasses, narration would otherwise be a
98
+ * hole in the GDPR mode.
99
+ */
100
+ export function narrationService(): ConsentService {
101
+ return {
102
+ key: MEDIA_KEY_PREFIX + "narration",
103
+ label: consentMessage("consent_service_narration", "narration"),
104
+ };
105
+ }
106
+
107
+ /**
108
+ * A media embed from an external service.
109
+ *
110
+ * `type` is the MediaType slug (the stable identity); `name` is its display
111
+ * name (a brand name, e.g. "YouTube"). Both are needed: the slug is what gets
112
+ * stored and what the error-icon class is derived from, the name is what the
113
+ * visitor reads.
114
+ */
115
+ export function mediaService(type: string, name: string): ConsentService {
116
+ return { key: MEDIA_KEY_PREFIX + type, label: name || type };
117
+ }
118
+
119
+ /**
120
+ * Map a key written by an earlier version onto the current namespaced form.
121
+ *
122
+ * The pre-registry code stored bare slugs for media (`"youtube"`) and the
123
+ * *localized* strings for tiles and fonts (`"map tiles"`, `"web fonts"` — which
124
+ * is why every locale happened to fall back to the English spelling, and why
125
+ * translating them would have orphaned everyone's decisions).
126
+ *
127
+ * Every current key contains a `:` and no legacy key does, so that is the
128
+ * discriminator. Enumerating the current keys instead looked more explicit
129
+ * but silently re-prefixed `map:tiles` into `media:map:tiles` on every read,
130
+ * so a stored grant was never found again.
131
+ */
132
+ function migrateLegacyKey(key: string): string {
133
+ if (key.includes(":")) {
134
+ return key;
135
+ }
136
+ if (key === "map tiles") return TILE_KEY;
137
+ if (key === "web fonts") return FONT_KEY;
138
+ return MEDIA_KEY_PREFIX + key;
139
+ }
140
+
141
+ /** A button in a consent dialog. */
142
+ function consentButton(kind: "allow" | "deny", text: string): HTMLButtonElement {
143
+ const button = Dom.create("button", `vco-consent-${kind}`) as HTMLButtonElement;
144
+ button.setAttribute("type", "button");
145
+ // Text content provides the accessible name; native <button> is kept
146
+ // deliberately (no icon-only buttons, so no extra aria-label needed).
147
+ button.textContent = text;
148
+ return button;
149
+ }
150
+
151
+ /** Counter backing the unique title ids for aria-labelledby (multi-viewer pages). */
152
+ let consentDialogCounter = 0;
153
+
154
+ /** The title/message pair shared by both dialog shapes. */
155
+ function consentHeader(
156
+ titleText: string,
157
+ messageText: string,
158
+ ): { title: HTMLElement; message: HTMLElement } {
159
+ const title = Dom.create("p", "vco-consent-title");
160
+ title.textContent = titleText;
161
+ // Focus target on open: programmatically focusable, unique per dialog
162
+ // instance so aria-labelledby never collides across viewers.
163
+ title.setAttribute("tabindex", "-1");
164
+ consentDialogCounter += 1;
165
+ title.id = `vco-consent-title-${consentDialogCounter}`;
166
+ const message = Dom.create("p", "vco-consent-message");
167
+ message.textContent = messageText;
168
+ return { title: title, message: message };
169
+ }
8
170
 
9
171
  /**
10
172
  * Per-StoryMap GDPR consent manager. When the `consent_required` option is
11
173
  * set, every external service (media embeds, map tiles, external font CSS)
12
- * asks for permission before anything is loaded. Decisions are remembered
13
- * per service in a cookie for 90 days — clearing cookies asks again.
174
+ * asks for permission before anything is loaded.
175
+ *
176
+ * Decisions are remembered per service in localStorage with no expiry:
177
+ * clearing site data is what makes the viewer ask again.
14
178
  */
15
179
  export class ConsentManager {
16
180
  private granted = new Set<string>();
17
181
  private denied = new Set<string>();
18
182
  /** unanswered asks per service (preloaded slides stack several) */
19
183
  private pending = new Map<string, Array<{ el: HTMLElement; resolve: (v: boolean) => void }>>();
184
+ /** the start-of-story dialog's row per service, so a decision made
185
+ * anywhere (including a standalone per-service ask) clears it */
186
+ private startRows = new Map<string, HTMLElement>();
187
+ /** every start-of-story dialog root we put on screen, so dispose() can
188
+ * take the whole thing down rather than just its rows */
189
+ private startDialogs = new Set<HTMLElement>();
190
+ /** set by dispose(); late answers are ignored rather than persisted */
191
+ private disposed = false;
20
192
 
21
193
  constructor() {
22
194
  this.restore();
23
195
  }
24
196
 
25
- /** Seed the per-service state from the consent cookie, if present. */
197
+ /**
198
+ * Seed the per-service state from localStorage, if present.
199
+ *
200
+ * Keys written by an earlier version are remapped onto the namespaced
201
+ * form, so an existing visitor is not asked everything again after the
202
+ * upgrade. The remap is written back once, which also clears the legacy
203
+ * keys so they cannot drift out of sync later.
204
+ */
26
205
  private restore(): void {
27
- try {
28
- const raw = Cookies.get(CONSENT_COOKIE);
29
- if (!raw) return;
30
- const state = JSON.parse(raw) as Record<string, boolean>;
31
- for (const service in state) {
32
- if (Object.hasOwn(state, service)) {
33
- if (state[service]) {
34
- this.granted.add(service);
35
- } else {
36
- this.denied.add(service);
37
- }
38
- }
206
+ const { state, migrated } = readStoredState();
207
+ for (const [key, allowed] of Object.entries(state)) {
208
+ if (allowed) {
209
+ this.granted.add(key);
210
+ } else {
211
+ this.denied.add(key);
39
212
  }
40
- } catch {
41
- // ignore malformed cookies
213
+ }
214
+ if (migrated) {
215
+ this.persist();
42
216
  }
43
217
  }
44
218
 
45
- /** Persist the per-service state to the consent cookie. */
219
+ /**
220
+ * Persist the per-service state to localStorage.
221
+ *
222
+ * A page can hold more than one viewer, and they all share this one
223
+ * record — a visitor should not answer the same question twice for the
224
+ * same service. So this *merges* into whatever is already stored instead
225
+ * of replacing it: writing this viewer's full set back would silently
226
+ * erase a decision a sibling viewer made, while that sibling still
227
+ * believed otherwise. The merged result is then adopted, which is what
228
+ * makes every viewer converge on the same decisions.
229
+ */
46
230
  private persist(): void {
47
- const state: Record<string, boolean> = {};
48
- for (const service of this.granted) state[service] = true;
49
- for (const service of this.denied) state[service] = false;
50
- try {
51
- Cookies.set(CONSENT_COOKIE, JSON.stringify(state), {
52
- expires: CONSENT_COOKIE_DAYS,
53
- sameSite: "Lax",
54
- });
55
- } catch {
56
- // storage unavailable (e.g. sandboxed contexts)
231
+ const merged = readStoredState().state;
232
+ for (const service of this.granted) merged[service] = true;
233
+ for (const service of this.denied) merged[service] = false;
234
+ writeStoredState(merged);
235
+ this.adopt(merged);
236
+ }
237
+
238
+ /** Replace the in-memory decision set with `state`, in place. */
239
+ private adopt(state: Record<string, boolean>): void {
240
+ this.granted.clear();
241
+ this.denied.clear();
242
+ for (const [key, allowed] of Object.entries(state)) {
243
+ if (allowed) {
244
+ this.granted.add(key);
245
+ } else {
246
+ this.denied.add(key);
247
+ }
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Re-read the shared record, so a decision the visitor made in a sibling
253
+ * viewer is honoured here without having to reconstruct this one.
254
+ */
255
+ private sync(): void {
256
+ this.adopt(readStoredState().state);
257
+ }
258
+
259
+ /**
260
+ * Record a decision for one service and resolve every ask waiting on it.
261
+ *
262
+ * Both dialog shapes funnel through here, which is what keeps a service
263
+ * from ending up in the granted *and* the denied set: the opposite set is
264
+ * always cleared.
265
+ */
266
+ private decide(service: string, allowed: boolean): void {
267
+ if (this.disposed) {
268
+ return;
57
269
  }
270
+ if (allowed) {
271
+ this.granted.add(service);
272
+ this.denied.delete(service);
273
+ } else {
274
+ this.denied.add(service);
275
+ this.granted.delete(service);
276
+ }
277
+ // Store before resolving. A resolved promise starts loading the media,
278
+ // which asks again for the next service, and that ask re-reads the
279
+ // shared record — so the decision has to be durable first.
280
+ this.persist();
281
+
282
+ // answering resolves every pending ask of that service (preloaded
283
+ // slides ask in parallel)
284
+ for (const pending of this.pending.get(service) ?? []) {
285
+ pending.el.remove();
286
+ pending.resolve(allowed);
287
+ }
288
+ this.pending.delete(service);
289
+ // and the start-of-story dialog's row for it. The visitor can answer
290
+ // the same service from the standalone ask over the map, so the row
291
+ // cannot be cleaned up by its own click handler alone.
292
+ this.startRows.get(service)?.remove();
293
+ this.startRows.delete(service);
58
294
  }
59
295
 
60
296
  isGranted(service: string): boolean {
@@ -65,69 +301,180 @@ export class ConsentManager {
65
301
  return this.denied.has(service);
66
302
  }
67
303
 
304
+ /** True when at least one of the services has no stored decision yet. */
305
+ hasUnanswered(services: string[]): boolean {
306
+ this.sync();
307
+ return services.some((service) => !this.granted.has(service) && !this.denied.has(service));
308
+ }
309
+
68
310
  /**
69
- * Ask the visitor for permission to load `service` (a human-readable
70
- * name like "YouTube" or "map tiles"), optionally naming the `host`.
71
- * The ask is rendered into `container`; resolves `true`/`false` on the
72
- * visitor's decision, or immediately when the service was already
73
- * granted or denied.
311
+ * Start-of-story consent dialog: lists every known external service
312
+ * with per-service Allow/Deny, plus global "Allow all" / "Decline
313
+ * all" buttons. Individual decisions reuse the per-service state
314
+ * (pending slide asks resolve immediately); the dialog closes once
315
+ * every service has a decision.
74
316
  */
75
- request(service: string, host: string, container: HTMLElement): Promise<boolean> {
76
- if (this.granted.has(service)) return Promise.resolve(true);
77
- if (this.denied.has(service)) return Promise.resolve(false);
78
- return new Promise((resolve) => {
79
- const messages = (Language.messages ?? {}) as Record<string, string>;
80
- const el = document.createElement("div");
81
- el.className = "vco-consent";
317
+ requestAll(services: ConsentService[], container: HTMLElement): void {
318
+ if (!this.hasUnanswered(services.map((s) => s.key))) {
319
+ return;
320
+ }
321
+ // Only list the services still waiting on a decision. Rendering an
322
+ // already-answered row invited the visitor to re-answer a question
323
+ // they had settled on a previous visit — and, because the row shows
324
+ // "map tiles", it made the tile consent look unanswered even when
325
+ // localStorage said otherwise.
326
+ const unanswered = services.filter(
327
+ (s) => !this.granted.has(s.key) && !this.denied.has(s.key),
328
+ );
329
+ const messages = Language.messages;
330
+ const el = Dom.create("div", "vco-consent vco-consent-start");
331
+ const { title, message } = consentHeader(
332
+ messages.consent_start_title ?? "External content",
333
+ messages.consent_start_message ?? "",
334
+ );
335
+ // Non-modal library widget: exposed as a dialog without trapping
336
+ // focus or closing on Escape (no auto-decision for the visitor).
337
+ el.setAttribute("role", "dialog");
338
+ el.setAttribute("aria-modal", "false");
339
+ el.setAttribute("aria-labelledby", title.id);
82
340
 
83
- const title = document.createElement("p");
84
- title.className = "vco-consent-title";
85
- title.textContent = messages.consent_title ?? "External content";
341
+ const list = Dom.create("div", "vco-consent-services");
342
+ const toggles: Array<{ key: string; allow: HTMLButtonElement; deny: HTMLButtonElement }> =
343
+ [];
86
344
 
87
- const message = document.createElement("p");
88
- message.className = "vco-consent-message";
89
- const template = messages.consent_message ?? "Load content from {service}?";
90
- message.textContent =
91
- template.replace("{service}", service) + (host ? ` (${host})` : "");
345
+ const finish = () => {
346
+ if (this.hasUnanswered(services.map((s) => s.key))) return;
347
+ el.remove();
348
+ };
92
349
 
93
- const buttons = document.createElement("div");
94
- buttons.className = "vco-consent-buttons";
350
+ for (const service of unanswered) {
351
+ const row = Dom.create("div", "vco-consent-service");
352
+ const label = Dom.create("span", "vco-consent-service-label");
353
+ label.textContent = service.label;
354
+ const buttons = Dom.create("div", "vco-consent-buttons");
355
+ const allow = consentButton("allow", messages.consent_allow ?? "Allow");
356
+ const deny = consentButton("deny", messages.consent_deny ?? "Deny");
95
357
 
96
- const allow = document.createElement("button");
97
- allow.className = "vco-consent-allow";
98
- allow.textContent = messages.consent_allow ?? "Allow";
358
+ const answer = (allowed: boolean) => {
359
+ // decide() removes the row: a settled service must not keep
360
+ // showing in the dialog
361
+ this.decide(service.key, allowed);
362
+ allow.disabled = true;
363
+ deny.disabled = true;
364
+ finish();
365
+ };
366
+ allow.addEventListener("click", () => answer(true));
367
+ deny.addEventListener("click", () => answer(false));
99
368
 
100
- const deny = document.createElement("button");
101
- deny.className = "vco-consent-deny";
102
- deny.textContent = messages.consent_deny ?? "Deny";
369
+ buttons.append(allow, deny);
370
+ row.append(label, buttons);
371
+ list.append(row);
372
+ toggles.push({ key: service.key, allow, deny });
373
+ this.startRows.set(service.key, row);
374
+ }
103
375
 
104
- const list = this.pending.get(service) ?? [];
376
+ const actions = Dom.create("div", "vco-consent-buttons vco-consent-start-actions");
377
+ const allowAll = consentButton("allow", messages.consent_allow_all ?? "Allow all");
378
+ const declineAll = consentButton("deny", messages.consent_decline_all ?? "Decline all");
379
+
380
+ const answerAll = (allowed: boolean) => {
381
+ for (const toggle of toggles) {
382
+ this.decide(toggle.key, allowed);
383
+ toggle.allow.disabled = true;
384
+ toggle.deny.disabled = true;
385
+ }
386
+ el.remove();
387
+ };
388
+ allowAll.addEventListener("click", () => answerAll(true));
389
+ declineAll.addEventListener("click", () => answerAll(false));
390
+
391
+ actions.append(allowAll, declineAll);
392
+ el.append(title, message, list, actions);
393
+ container.append(el);
394
+ this.startDialogs.add(el);
395
+ title.focus();
396
+ }
397
+
398
+ /**
399
+ * Ask the visitor for permission to load `service`, optionally naming the
400
+ * `host` (shown in parentheses so they can see which domain is about to be
401
+ * contacted). The ask is rendered into `container`; resolves
402
+ * `true`/`false` on the visitor's decision, or immediately when the
403
+ * service was already granted or denied.
404
+ *
405
+ * The service is stored and looked up by `service.key`; `service.label`
406
+ * is only ever rendered.
407
+ */
408
+ request(service: ConsentService, host: string, container: HTMLElement): Promise<boolean> {
409
+ const key = service.key;
410
+ // the record is shared with any sibling viewer on the page, so re-read
411
+ // it: a decision the visitor just made over there answers this ask
412
+ // without showing a second dialog for the same service
413
+ this.sync();
414
+ if (this.granted.has(key)) return Promise.resolve(true);
415
+ if (this.denied.has(key)) return Promise.resolve(false);
416
+ return new Promise((resolve) => {
417
+ const messages = Language.messages;
418
+ const el = Dom.create("div", "vco-consent");
419
+ // Non-modal library widget: exposed as a dialog without trapping
420
+ // focus or closing on Escape (no auto-decision for the visitor).
421
+
422
+ const template = messages.consent_message ?? "Load content from {service}?";
423
+ const { title, message } = consentHeader(
424
+ // consent_title was byte-identical to consent_start_title and
425
+ // present in no other locale, so the per-service panel reuses
426
+ // the start-of-story heading
427
+ messages.consent_start_title ?? "External content",
428
+ template.replace("{service}", service.label) + (host ? ` (${host})` : ""),
429
+ );
430
+ el.setAttribute("role", "dialog");
431
+ el.setAttribute("aria-modal", "false");
432
+ el.setAttribute("aria-labelledby", title.id);
433
+
434
+ const buttons = Dom.create("div", "vco-consent-buttons");
435
+ const allow = consentButton("allow", messages.consent_allow ?? "Allow");
436
+ const deny = consentButton("deny", messages.consent_deny ?? "Deny");
437
+
438
+ const list = this.pending.get(key) ?? [];
105
439
  list.push({ el, resolve });
106
- this.pending.set(service, list);
107
-
108
- const decide = (allowed: boolean) => {
109
- if (allowed) {
110
- this.granted.add(service);
111
- } else {
112
- this.denied.add(service);
113
- }
114
- // answering one ask resolves every pending ask of the same
115
- // service (preloaded slides ask in parallel)
116
- for (const p of this.pending.get(service) ?? []) {
117
- p.el.remove();
118
- p.resolve(allowed);
119
- }
120
- this.pending.delete(service);
121
- this.persist();
122
- };
123
- allow.addEventListener("click", () => decide(true));
124
- deny.addEventListener("click", () => decide(false));
440
+ this.pending.set(key, list);
441
+
442
+ const answer = (allowed: boolean) => this.decide(key, allowed);
443
+ allow.addEventListener("click", () => answer(true));
444
+ deny.addEventListener("click", () => answer(false));
125
445
 
126
446
  buttons.append(allow, deny);
127
447
  el.append(title, message, buttons);
128
448
  container.append(el);
449
+ title.focus();
129
450
  });
130
451
  }
452
+
453
+ /**
454
+ * Release everything: drop the dialogs and settle every unanswered ask.
455
+ *
456
+ * `Media.loadMedia()` awaits `request()`. Without this, tearing the viewer
457
+ * down while a panel was on screen left that `await` pending forever, and
458
+ * the suspended frame kept the whole media subtree — DOM included — alive.
459
+ * Unanswered asks resolve `false`, which is the same outcome as declining.
460
+ */
461
+ dispose(): void {
462
+ this.disposed = true;
463
+ // the whole dialog, not just the rows: removing the rows leaves the
464
+ // heading and the allow-all/deny-all buttons behind
465
+ for (const el of this.startDialogs) {
466
+ el.remove();
467
+ }
468
+ this.startDialogs.clear();
469
+ this.startRows.clear();
470
+ for (const [, asks] of this.pending) {
471
+ for (const ask of asks) {
472
+ ask.el.remove();
473
+ ask.resolve(false);
474
+ }
475
+ }
476
+ this.pending.clear();
477
+ }
131
478
  }
132
479
 
133
480
  /**
@@ -142,5 +489,5 @@ export function consentManagerOf(options: unknown): ConsentManager | undefined {
142
489
  * A localized consent string, falling back to the English default.
143
490
  */
144
491
  export function consentMessage(key: string, fallback: string): string {
145
- return ((Language.messages ?? {}) as Record<string, string>)[key] ?? fallback;
492
+ return Language.messages[key] ?? fallback;
146
493
  }