@bendyline/squisq 0.1.1
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.
- package/dist/Types-sh2VRxfo.d.ts +15 -0
- package/dist/chunk-7UDSRZKG.js +103 -0
- package/dist/chunk-7UDSRZKG.js.map +1 -0
- package/dist/chunk-O5HCIUAD.js +577 -0
- package/dist/chunk-O5HCIUAD.js.map +1 -0
- package/dist/chunk-PJ7AODIM.js +3040 -0
- package/dist/chunk-PJ7AODIM.js.map +1 -0
- package/dist/chunk-QWVRN6I4.js +81 -0
- package/dist/chunk-QWVRN6I4.js.map +1 -0
- package/dist/chunk-S4F2RY37.js +92 -0
- package/dist/chunk-S4F2RY37.js.map +1 -0
- package/dist/chunk-URU6QMLY.js +888 -0
- package/dist/chunk-URU6QMLY.js.map +1 -0
- package/dist/chunk-VJN7UB2Z.js +145 -0
- package/dist/chunk-VJN7UB2Z.js.map +1 -0
- package/dist/doc/index.d.ts +3 -0
- package/dist/doc/index.js +126 -0
- package/dist/doc/index.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/index.d.ts +237 -0
- package/dist/markdown/index.js +33 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/schemas/index.d.ts +70 -0
- package/dist/schemas/index.js +61 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/spatial/index.d.ts +111 -0
- package/dist/spatial/index.js +25 -0
- package/dist/spatial/index.js.map +1 -0
- package/dist/storage/index.d.ts +91 -0
- package/dist/storage/index.js +11 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/story/index.d.ts +691 -0
- package/dist/story/index.js +126 -0
- package/dist/story/index.js.map +1 -0
- package/dist/themeLibrary-DySHPcgj.d.ts +1352 -0
- package/dist/types-DHiv_Pnm.d.ts +496 -0
- package/package.json +91 -0
- package/src/__tests__/animationUtils.test.ts +77 -0
- package/src/__tests__/geohash.test.ts +109 -0
- package/src/__tests__/getLayers.test.ts +200 -0
- package/src/__tests__/haversine.test.ts +63 -0
- package/src/__tests__/localForageAdapter.test.ts +155 -0
- package/src/__tests__/markdown.test.ts +806 -0
- package/src/__tests__/markdownToDoc.test.ts +374 -0
- package/src/__tests__/storage.test.ts +60 -0
- package/src/__tests__/templates.test.ts +247 -0
- package/src/doc/docToMarkdown.ts +98 -0
- package/src/doc/getLayers.ts +152 -0
- package/src/doc/index.ts +8 -0
- package/src/doc/markdownToDoc.ts +277 -0
- package/src/doc/templates/accentImage.ts +338 -0
- package/src/doc/templates/captionUtils.ts +33 -0
- package/src/doc/templates/comparisonBar.ts +140 -0
- package/src/doc/templates/coverBlock.ts +156 -0
- package/src/doc/templates/dateEvent.ts +122 -0
- package/src/doc/templates/definitionCard.ts +129 -0
- package/src/doc/templates/factCard.ts +114 -0
- package/src/doc/templates/fullBleedQuote.ts +59 -0
- package/src/doc/templates/imageWithCaption.ts +167 -0
- package/src/doc/templates/index.ts +571 -0
- package/src/doc/templates/listBlock.ts +101 -0
- package/src/doc/templates/mapBlock.ts +132 -0
- package/src/doc/templates/persistentLayers.ts +522 -0
- package/src/doc/templates/photoGrid.ts +199 -0
- package/src/doc/templates/pullQuote.ts +119 -0
- package/src/doc/templates/quoteBlock.ts +112 -0
- package/src/doc/templates/sectionHeader.ts +105 -0
- package/src/doc/templates/statHighlight.ts +113 -0
- package/src/doc/templates/titleBlock.ts +100 -0
- package/src/doc/templates/twoColumn.ts +239 -0
- package/src/doc/templates/videoPullQuote.ts +128 -0
- package/src/doc/templates/videoWithCaption.ts +126 -0
- package/src/doc/utils/animationUtils.ts +135 -0
- package/src/doc/utils/themeUtils.ts +139 -0
- package/src/index.ts +5 -0
- package/src/markdown/convert.ts +897 -0
- package/src/markdown/htmlParse.ts +212 -0
- package/src/markdown/index.ts +110 -0
- package/src/markdown/parse.ts +103 -0
- package/src/markdown/stringify.ts +122 -0
- package/src/markdown/types.ts +605 -0
- package/src/markdown/utils.ts +165 -0
- package/src/ngeohash.d.ts +11 -0
- package/src/schemas/BlockTemplates.ts +706 -0
- package/src/schemas/Doc.ts +565 -0
- package/src/schemas/LayoutStrategy.ts +178 -0
- package/src/schemas/MediaProvider.ts +70 -0
- package/src/schemas/Theme.ts +235 -0
- package/src/schemas/Types.ts +15 -0
- package/src/schemas/Viewport.ts +91 -0
- package/src/schemas/index.ts +14 -0
- package/src/schemas/themeLibrary.ts +492 -0
- package/src/spatial/Geohash.ts +168 -0
- package/src/spatial/Haversine.ts +59 -0
- package/src/spatial/index.ts +2 -0
- package/src/storage/LocalForageAdapter.ts +103 -0
- package/src/storage/LocalStorageAdapter.ts +57 -0
- package/src/storage/MemoryStorageAdapter.ts +37 -0
- package/src/storage/Storage.ts +28 -0
- package/src/storage/index.ts +5 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Theme Library
|
|
3
|
+
*
|
|
4
|
+
* Ships 8 curated themes covering documentary, editorial, minimal, and
|
|
5
|
+
* cinematic styles. Each theme is fully JSON-serializable.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { THEMES, resolveTheme } from '@bendyline/squisq/schemas';
|
|
10
|
+
*
|
|
11
|
+
* const theme = resolveTheme('minimalist'); // look up by id
|
|
12
|
+
* const theme = THEMES.documentary; // direct access
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Theme, ThemeColorScheme } from './Theme.js';
|
|
17
|
+
|
|
18
|
+
// ============================================
|
|
19
|
+
// Shared constants
|
|
20
|
+
// ============================================
|
|
21
|
+
|
|
22
|
+
const SERIF = '"PT Serif", Georgia, "Times New Roman", serif';
|
|
23
|
+
const SANS = '"Inter", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
|
|
24
|
+
const MONO = '"JetBrains Mono", "Fira Code", "Consolas", monospace';
|
|
25
|
+
const MERRIWEATHER = '"Merriweather", Georgia, "Times New Roman", serif';
|
|
26
|
+
const GEORGIA = 'Georgia, "Times New Roman", serif';
|
|
27
|
+
|
|
28
|
+
/** Standard 6-scheme set used by the documentary theme (migrated from old COLOR_SCHEMES). */
|
|
29
|
+
const CLASSIC_COLOR_SCHEMES: Record<string, ThemeColorScheme> = {
|
|
30
|
+
blue: { bg: '#1a365d', text: '#63b3ed', accent: '#90cdf4' },
|
|
31
|
+
green: { bg: '#22543d', text: '#9ae6b4', accent: '#68d391' },
|
|
32
|
+
purple: { bg: '#44337a', text: '#d6bcfa', accent: '#b794f4' },
|
|
33
|
+
red: { bg: '#742a2a', text: '#fc8181', accent: '#feb2b2' },
|
|
34
|
+
orange: { bg: '#744210', text: '#fbd38d', accent: '#f6ad55' },
|
|
35
|
+
teal: { bg: '#234e52', text: '#81e6d9', accent: '#4fd1c5' },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// ============================================
|
|
39
|
+
// 1. Documentary (default — migrated from old DEFAULT_THEME)
|
|
40
|
+
// ============================================
|
|
41
|
+
|
|
42
|
+
const documentary: Theme = {
|
|
43
|
+
id: 'documentary',
|
|
44
|
+
name: 'Documentary',
|
|
45
|
+
description: 'Classic dark cinematic look with serif typography. The original Squisq style.',
|
|
46
|
+
colors: {
|
|
47
|
+
primary: '#3d5a80',
|
|
48
|
+
secondary: '#63b3ed',
|
|
49
|
+
background: '#1a202c',
|
|
50
|
+
backgroundLight: '#2d3748',
|
|
51
|
+
text: '#ffffff',
|
|
52
|
+
textMuted: '#a0aec0',
|
|
53
|
+
highlight: '#63b3ed',
|
|
54
|
+
warning: '#fc8181',
|
|
55
|
+
},
|
|
56
|
+
typography: {
|
|
57
|
+
bodyFontFamily: SERIF,
|
|
58
|
+
titleFontFamily: SERIF,
|
|
59
|
+
titleWeight: 'bold',
|
|
60
|
+
},
|
|
61
|
+
style: {
|
|
62
|
+
textShadow: true,
|
|
63
|
+
overlayOpacity: 0.5,
|
|
64
|
+
animationSpeed: 1.0,
|
|
65
|
+
},
|
|
66
|
+
renderStyle: {
|
|
67
|
+
name: 'documentary',
|
|
68
|
+
defaultTextAnimation: 'fadeIn',
|
|
69
|
+
defaultImageAnimation: 'slowZoom',
|
|
70
|
+
ambientMotion: true,
|
|
71
|
+
defaultTransition: { type: 'fade', duration: 0.8 },
|
|
72
|
+
},
|
|
73
|
+
colorSchemes: CLASSIC_COLOR_SCHEMES,
|
|
74
|
+
persistentLayers: {
|
|
75
|
+
bottomLayers: [
|
|
76
|
+
{
|
|
77
|
+
template: 'gradientBackground',
|
|
78
|
+
config: { type: 'gradientBackground', preset: 'dark-vignette' },
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ============================================
|
|
85
|
+
// 2. Minimalist
|
|
86
|
+
// ============================================
|
|
87
|
+
|
|
88
|
+
const minimalist: Theme = {
|
|
89
|
+
id: 'minimalist',
|
|
90
|
+
name: 'Minimalist',
|
|
91
|
+
description: 'Clean near-white background with sans-serif type and minimal animation.',
|
|
92
|
+
colors: {
|
|
93
|
+
primary: '#2d3748',
|
|
94
|
+
secondary: '#4a5568',
|
|
95
|
+
background: '#f7fafc',
|
|
96
|
+
backgroundLight: '#edf2f7',
|
|
97
|
+
text: '#1a202c',
|
|
98
|
+
textMuted: '#718096',
|
|
99
|
+
highlight: '#3182ce',
|
|
100
|
+
warning: '#e53e3e',
|
|
101
|
+
},
|
|
102
|
+
typography: {
|
|
103
|
+
bodyFontFamily: SANS,
|
|
104
|
+
titleFontFamily: SANS,
|
|
105
|
+
titleWeight: 'bold',
|
|
106
|
+
lineHeight: 1.6,
|
|
107
|
+
},
|
|
108
|
+
style: {
|
|
109
|
+
textShadow: false,
|
|
110
|
+
overlayOpacity: 0.3,
|
|
111
|
+
animationSpeed: 1.2,
|
|
112
|
+
borderRadius: 8,
|
|
113
|
+
},
|
|
114
|
+
renderStyle: {
|
|
115
|
+
name: 'minimalist',
|
|
116
|
+
defaultTextAnimation: 'fadeIn',
|
|
117
|
+
ambientMotion: false,
|
|
118
|
+
defaultTransition: { type: 'fade', duration: 0.6 },
|
|
119
|
+
templateHints: {
|
|
120
|
+
titleBlock: { showAccentLine: false },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
colorSchemes: {
|
|
124
|
+
blue: { bg: '#ebf8ff', text: '#2b6cb0', accent: '#3182ce' },
|
|
125
|
+
green: { bg: '#f0fff4', text: '#276749', accent: '#38a169' },
|
|
126
|
+
purple: { bg: '#faf5ff', text: '#553c9a', accent: '#805ad5' },
|
|
127
|
+
red: { bg: '#fff5f5', text: '#c53030', accent: '#e53e3e' },
|
|
128
|
+
orange: { bg: '#fffaf0', text: '#c05621', accent: '#dd6b20' },
|
|
129
|
+
teal: { bg: '#e6fffa', text: '#285e61', accent: '#319795' },
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ============================================
|
|
134
|
+
// 3. Bold
|
|
135
|
+
// ============================================
|
|
136
|
+
|
|
137
|
+
const bold: Theme = {
|
|
138
|
+
id: 'bold',
|
|
139
|
+
name: 'Bold',
|
|
140
|
+
description: 'High-contrast black with vivid accents and dramatic animations.',
|
|
141
|
+
colors: {
|
|
142
|
+
primary: '#e53e3e',
|
|
143
|
+
secondary: '#ed8936',
|
|
144
|
+
background: '#000000',
|
|
145
|
+
backgroundLight: '#1a1a1a',
|
|
146
|
+
text: '#ffffff',
|
|
147
|
+
textMuted: '#cbd5e0',
|
|
148
|
+
highlight: '#f6e05e',
|
|
149
|
+
warning: '#fc8181',
|
|
150
|
+
},
|
|
151
|
+
typography: {
|
|
152
|
+
bodyFontFamily: SANS,
|
|
153
|
+
titleFontFamily: SANS,
|
|
154
|
+
titleWeight: 'bold',
|
|
155
|
+
titleScale: 1.15,
|
|
156
|
+
bodyScale: 1.0,
|
|
157
|
+
titleLineHeight: 1.1,
|
|
158
|
+
},
|
|
159
|
+
style: {
|
|
160
|
+
textShadow: true,
|
|
161
|
+
overlayOpacity: 0.6,
|
|
162
|
+
animationSpeed: 0.8,
|
|
163
|
+
},
|
|
164
|
+
renderStyle: {
|
|
165
|
+
name: 'bold',
|
|
166
|
+
defaultTextAnimation: 'zoomIn',
|
|
167
|
+
defaultImageAnimation: 'slowZoom',
|
|
168
|
+
ambientMotion: true,
|
|
169
|
+
defaultTransition: { type: 'dissolve', duration: 0.5 },
|
|
170
|
+
templateHints: {
|
|
171
|
+
statHighlight: { entrance: 'dramatic' },
|
|
172
|
+
fullBleedQuote: { entrance: 'dramatic' },
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
colorSchemes: {
|
|
176
|
+
blue: { bg: '#1a202c', text: '#63b3ed', accent: '#90cdf4' },
|
|
177
|
+
green: { bg: '#1a202c', text: '#48bb78', accent: '#68d391' },
|
|
178
|
+
purple: { bg: '#1a202c', text: '#b794f4', accent: '#d6bcfa' },
|
|
179
|
+
red: { bg: '#1a202c', text: '#fc8181', accent: '#feb2b2' },
|
|
180
|
+
orange: { bg: '#1a202c', text: '#f6ad55', accent: '#fbd38d' },
|
|
181
|
+
teal: { bg: '#1a202c', text: '#4fd1c5', accent: '#81e6d9' },
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// ============================================
|
|
186
|
+
// 4. Morning Light
|
|
187
|
+
// ============================================
|
|
188
|
+
|
|
189
|
+
const morningLight: Theme = {
|
|
190
|
+
id: 'morning-light',
|
|
191
|
+
name: 'Morning Light',
|
|
192
|
+
description: 'Warm light background with dark text and gentle serif typography.',
|
|
193
|
+
colors: {
|
|
194
|
+
primary: '#744210',
|
|
195
|
+
secondary: '#975a16',
|
|
196
|
+
background: '#faf8f5',
|
|
197
|
+
backgroundLight: '#f5f0eb',
|
|
198
|
+
text: '#2d3748',
|
|
199
|
+
textMuted: '#718096',
|
|
200
|
+
highlight: '#dd6b20',
|
|
201
|
+
warning: '#c53030',
|
|
202
|
+
},
|
|
203
|
+
typography: {
|
|
204
|
+
bodyFontFamily: MERRIWEATHER,
|
|
205
|
+
titleFontFamily: MERRIWEATHER,
|
|
206
|
+
titleWeight: 'bold',
|
|
207
|
+
lineHeight: 1.7,
|
|
208
|
+
},
|
|
209
|
+
style: {
|
|
210
|
+
textShadow: false,
|
|
211
|
+
overlayOpacity: 0.35,
|
|
212
|
+
animationSpeed: 1.1,
|
|
213
|
+
borderRadius: 4,
|
|
214
|
+
},
|
|
215
|
+
renderStyle: {
|
|
216
|
+
name: 'morning-light',
|
|
217
|
+
defaultTextAnimation: 'fadeIn',
|
|
218
|
+
ambientMotion: false,
|
|
219
|
+
defaultTransition: { type: 'fade', duration: 1.0 },
|
|
220
|
+
},
|
|
221
|
+
colorSchemes: {
|
|
222
|
+
blue: { bg: '#ebf8ff', text: '#2c5282', accent: '#4299e1' },
|
|
223
|
+
green: { bg: '#f0fff4', text: '#22543d', accent: '#48bb78' },
|
|
224
|
+
purple: { bg: '#faf5ff', text: '#44337a', accent: '#9f7aea' },
|
|
225
|
+
red: { bg: '#fff5f5', text: '#9b2c2c', accent: '#fc8181' },
|
|
226
|
+
orange: { bg: '#fffaf0', text: '#744210', accent: '#ed8936' },
|
|
227
|
+
teal: { bg: '#e6fffa', text: '#234e52', accent: '#38b2ac' },
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// ============================================
|
|
232
|
+
// 5. Tech Dark
|
|
233
|
+
// ============================================
|
|
234
|
+
|
|
235
|
+
const techDark: Theme = {
|
|
236
|
+
id: 'tech-dark',
|
|
237
|
+
name: 'Tech Dark',
|
|
238
|
+
description: 'True black with neon cyan/green accents and monospace titles.',
|
|
239
|
+
colors: {
|
|
240
|
+
primary: '#00e5ff',
|
|
241
|
+
secondary: '#00e676',
|
|
242
|
+
background: '#0a0a0a',
|
|
243
|
+
backgroundLight: '#1a1a2e',
|
|
244
|
+
text: '#e0e0e0',
|
|
245
|
+
textMuted: '#9e9e9e',
|
|
246
|
+
highlight: '#00e5ff',
|
|
247
|
+
warning: '#ff5252',
|
|
248
|
+
},
|
|
249
|
+
typography: {
|
|
250
|
+
bodyFontFamily: SANS,
|
|
251
|
+
titleFontFamily: MONO,
|
|
252
|
+
monoFontFamily: MONO,
|
|
253
|
+
titleWeight: 'bold',
|
|
254
|
+
titleScale: 0.9,
|
|
255
|
+
lineHeight: 1.5,
|
|
256
|
+
},
|
|
257
|
+
style: {
|
|
258
|
+
textShadow: true,
|
|
259
|
+
overlayOpacity: 0.55,
|
|
260
|
+
animationSpeed: 0.9,
|
|
261
|
+
borderRadius: 2,
|
|
262
|
+
},
|
|
263
|
+
renderStyle: {
|
|
264
|
+
name: 'tech-dark',
|
|
265
|
+
defaultTextAnimation: 'typewriter',
|
|
266
|
+
defaultImageAnimation: 'slowZoom',
|
|
267
|
+
ambientMotion: true,
|
|
268
|
+
defaultTransition: { type: 'dissolve', duration: 0.4 },
|
|
269
|
+
templateHints: {
|
|
270
|
+
statHighlight: { entrance: 'dramatic' },
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
colorSchemes: {
|
|
274
|
+
blue: { bg: '#0d1b2a', text: '#00b0ff', accent: '#40c4ff' },
|
|
275
|
+
green: { bg: '#0d1b0d', text: '#00e676', accent: '#69f0ae' },
|
|
276
|
+
purple: { bg: '#1a0d2e', text: '#d500f9', accent: '#ea80fc' },
|
|
277
|
+
red: { bg: '#2e0d0d', text: '#ff5252', accent: '#ff8a80' },
|
|
278
|
+
orange: { bg: '#2e1a0d', text: '#ff9100', accent: '#ffab40' },
|
|
279
|
+
teal: { bg: '#0d2e2e', text: '#1de9b6', accent: '#64ffda' },
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// ============================================
|
|
284
|
+
// 6. Magazine
|
|
285
|
+
// ============================================
|
|
286
|
+
|
|
287
|
+
const magazine: Theme = {
|
|
288
|
+
id: 'magazine',
|
|
289
|
+
name: 'Magazine',
|
|
290
|
+
description: 'Rich editorial palette with mixed serif/sans typography and slide transitions.',
|
|
291
|
+
colors: {
|
|
292
|
+
primary: '#c53030',
|
|
293
|
+
secondary: '#2b6cb0',
|
|
294
|
+
background: '#1a202c',
|
|
295
|
+
backgroundLight: '#2d3748',
|
|
296
|
+
text: '#f7fafc',
|
|
297
|
+
textMuted: '#cbd5e0',
|
|
298
|
+
highlight: '#ed8936',
|
|
299
|
+
warning: '#fc8181',
|
|
300
|
+
},
|
|
301
|
+
typography: {
|
|
302
|
+
bodyFontFamily: SANS,
|
|
303
|
+
titleFontFamily: GEORGIA,
|
|
304
|
+
titleWeight: 'bold',
|
|
305
|
+
titleScale: 1.05,
|
|
306
|
+
lineHeight: 1.5,
|
|
307
|
+
},
|
|
308
|
+
style: {
|
|
309
|
+
textShadow: true,
|
|
310
|
+
overlayOpacity: 0.45,
|
|
311
|
+
animationSpeed: 1.0,
|
|
312
|
+
borderRadius: 6,
|
|
313
|
+
},
|
|
314
|
+
renderStyle: {
|
|
315
|
+
name: 'magazine',
|
|
316
|
+
defaultTextAnimation: 'fadeIn',
|
|
317
|
+
defaultImageAnimation: 'slowZoom',
|
|
318
|
+
ambientMotion: true,
|
|
319
|
+
defaultTransition: { type: 'slideLeft', duration: 0.7 },
|
|
320
|
+
},
|
|
321
|
+
colorSchemes: {
|
|
322
|
+
blue: { bg: '#1a365d', text: '#90cdf4', accent: '#63b3ed' },
|
|
323
|
+
green: { bg: '#1c4532', text: '#9ae6b4', accent: '#68d391' },
|
|
324
|
+
purple: { bg: '#322659', text: '#d6bcfa', accent: '#b794f4' },
|
|
325
|
+
red: { bg: '#63171b', text: '#feb2b2', accent: '#fc8181' },
|
|
326
|
+
orange: { bg: '#652b19', text: '#fbd38d', accent: '#f6ad55' },
|
|
327
|
+
teal: { bg: '#1d4044', text: '#81e6d9', accent: '#4fd1c5' },
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// ============================================
|
|
332
|
+
// 7. Cinematic
|
|
333
|
+
// ============================================
|
|
334
|
+
|
|
335
|
+
const cinematic: Theme = {
|
|
336
|
+
id: 'cinematic',
|
|
337
|
+
name: 'Cinematic',
|
|
338
|
+
description: 'Ultra-dark with moody gradients, gold accents, and slow dissolve transitions.',
|
|
339
|
+
colors: {
|
|
340
|
+
primary: '#d69e2e',
|
|
341
|
+
secondary: '#b7791f',
|
|
342
|
+
background: '#0a0f1a',
|
|
343
|
+
backgroundLight: '#1a202c',
|
|
344
|
+
text: '#f7fafc',
|
|
345
|
+
textMuted: '#a0aec0',
|
|
346
|
+
highlight: '#ecc94b',
|
|
347
|
+
warning: '#fc8181',
|
|
348
|
+
},
|
|
349
|
+
typography: {
|
|
350
|
+
bodyFontFamily: SERIF,
|
|
351
|
+
titleFontFamily: SERIF,
|
|
352
|
+
titleWeight: 'bold',
|
|
353
|
+
lineHeight: 1.5,
|
|
354
|
+
titleLineHeight: 1.2,
|
|
355
|
+
},
|
|
356
|
+
style: {
|
|
357
|
+
textShadow: true,
|
|
358
|
+
overlayOpacity: 0.55,
|
|
359
|
+
animationSpeed: 1.2,
|
|
360
|
+
},
|
|
361
|
+
renderStyle: {
|
|
362
|
+
name: 'cinematic',
|
|
363
|
+
defaultTextAnimation: 'fadeIn',
|
|
364
|
+
defaultImageAnimation: 'slowZoom',
|
|
365
|
+
ambientMotion: true,
|
|
366
|
+
defaultTransition: { type: 'dissolve', duration: 1.2 },
|
|
367
|
+
templateHints: {
|
|
368
|
+
titleBlock: { showAccentLine: true },
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
colorSchemes: {
|
|
372
|
+
blue: { bg: '#0d1b2a', text: '#90cdf4', accent: '#63b3ed' },
|
|
373
|
+
green: { bg: '#0d1b14', text: '#9ae6b4', accent: '#68d391' },
|
|
374
|
+
purple: { bg: '#1a0d2e', text: '#d6bcfa', accent: '#b794f4' },
|
|
375
|
+
red: { bg: '#2a0d0d', text: '#fc8181', accent: '#feb2b2' },
|
|
376
|
+
orange: { bg: '#2a1a0d', text: '#fbd38d', accent: '#f6ad55' },
|
|
377
|
+
teal: { bg: '#0d2a2a', text: '#81e6d9', accent: '#4fd1c5' },
|
|
378
|
+
},
|
|
379
|
+
persistentLayers: {
|
|
380
|
+
bottomLayers: [
|
|
381
|
+
{
|
|
382
|
+
template: 'gradientBackground',
|
|
383
|
+
config: { type: 'gradientBackground', preset: 'radial-dark' },
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// ============================================
|
|
390
|
+
// 8. Warm Earth
|
|
391
|
+
// ============================================
|
|
392
|
+
|
|
393
|
+
const warmEarth: Theme = {
|
|
394
|
+
id: 'warm-earth',
|
|
395
|
+
name: 'Warm Earth',
|
|
396
|
+
description: 'Natural warm tones with earthy browns and terracotta accents.',
|
|
397
|
+
colors: {
|
|
398
|
+
primary: '#9c4221',
|
|
399
|
+
secondary: '#c05621',
|
|
400
|
+
background: '#1c1410',
|
|
401
|
+
backgroundLight: '#2d241e',
|
|
402
|
+
text: '#faf5f0',
|
|
403
|
+
textMuted: '#c4a882',
|
|
404
|
+
highlight: '#dd6b20',
|
|
405
|
+
warning: '#e53e3e',
|
|
406
|
+
},
|
|
407
|
+
typography: {
|
|
408
|
+
bodyFontFamily: GEORGIA,
|
|
409
|
+
titleFontFamily: GEORGIA,
|
|
410
|
+
titleWeight: 'bold',
|
|
411
|
+
lineHeight: 1.6,
|
|
412
|
+
},
|
|
413
|
+
style: {
|
|
414
|
+
textShadow: true,
|
|
415
|
+
overlayOpacity: 0.5,
|
|
416
|
+
animationSpeed: 1.1,
|
|
417
|
+
},
|
|
418
|
+
renderStyle: {
|
|
419
|
+
name: 'warm-earth',
|
|
420
|
+
defaultTextAnimation: 'fadeIn',
|
|
421
|
+
defaultImageAnimation: 'panLeft',
|
|
422
|
+
ambientMotion: true,
|
|
423
|
+
defaultTransition: { type: 'fade', duration: 0.9 },
|
|
424
|
+
},
|
|
425
|
+
colorSchemes: {
|
|
426
|
+
blue: { bg: '#2a1f14', text: '#90cdf4', accent: '#63b3ed' },
|
|
427
|
+
green: { bg: '#1e2a14', text: '#9ae6b4', accent: '#68d391' },
|
|
428
|
+
purple: { bg: '#2a142a', text: '#d6bcfa', accent: '#b794f4' },
|
|
429
|
+
red: { bg: '#2a1414', text: '#fc8181', accent: '#feb2b2' },
|
|
430
|
+
orange: { bg: '#2a1e0a', text: '#fbd38d', accent: '#f6ad55' },
|
|
431
|
+
teal: { bg: '#142a28', text: '#81e6d9', accent: '#4fd1c5' },
|
|
432
|
+
},
|
|
433
|
+
persistentLayers: {
|
|
434
|
+
bottomLayers: [
|
|
435
|
+
{
|
|
436
|
+
template: 'gradientBackground',
|
|
437
|
+
config: { type: 'gradientBackground', preset: 'earth-tones' },
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// ============================================
|
|
444
|
+
// Theme Library
|
|
445
|
+
// ============================================
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* All built-in themes, keyed by id.
|
|
449
|
+
*/
|
|
450
|
+
export const THEMES: Record<string, Theme> = {
|
|
451
|
+
documentary,
|
|
452
|
+
minimalist,
|
|
453
|
+
bold,
|
|
454
|
+
'morning-light': morningLight,
|
|
455
|
+
'tech-dark': techDark,
|
|
456
|
+
magazine,
|
|
457
|
+
cinematic,
|
|
458
|
+
'warm-earth': warmEarth,
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/** The default theme id. */
|
|
462
|
+
export const DEFAULT_THEME_ID = 'documentary';
|
|
463
|
+
|
|
464
|
+
/** The default theme (documentary — migrated from old DEFAULT_THEME). */
|
|
465
|
+
export const DEFAULT_THEME: Theme = documentary;
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Resolve a theme by id. Returns the default theme when `id` is
|
|
469
|
+
* undefined or not found in the library.
|
|
470
|
+
*/
|
|
471
|
+
export function resolveTheme(id: string | undefined): Theme {
|
|
472
|
+
if (id && id in THEMES) return THEMES[id];
|
|
473
|
+
return DEFAULT_THEME;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Get all available theme ids.
|
|
478
|
+
*/
|
|
479
|
+
export function getAvailableThemes(): string[] {
|
|
480
|
+
return Object.keys(THEMES);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Get a summary of all themes (id + name + description) for theme pickers.
|
|
485
|
+
*/
|
|
486
|
+
export function getThemeSummaries(): Array<{ id: string; name: string; description?: string }> {
|
|
487
|
+
return Object.values(THEMES).map((t) => ({
|
|
488
|
+
id: t.id,
|
|
489
|
+
name: t.name,
|
|
490
|
+
description: t.description,
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geohash Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides geohash encoding/decoding and spatial utilities using the standard
|
|
5
|
+
* ngeohash library for core operations, plus custom utilities for overlap checking
|
|
6
|
+
* and distance calculation.
|
|
7
|
+
*
|
|
8
|
+
* Geohashes are a hierarchical spatial indexing system that divides the world
|
|
9
|
+
* into a grid of cells. Each additional character narrows the area by ~1/32.
|
|
10
|
+
*
|
|
11
|
+
* Precision guide:
|
|
12
|
+
* - 4 chars: ~39km x 19km (used for tile indexing)
|
|
13
|
+
* - 5 chars: ~5km x 5km
|
|
14
|
+
* - 6 chars: ~1.2km x 0.6km
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import ngeohash from 'ngeohash';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Encode latitude/longitude to a geohash string.
|
|
21
|
+
* Uses the standard ngeohash library.
|
|
22
|
+
*/
|
|
23
|
+
export function encodeGeohash(lat: number, lng: number, precision = 9): string {
|
|
24
|
+
return ngeohash.encode(lat, lng, precision);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Decode a geohash to latitude/longitude center with error bounds.
|
|
29
|
+
* Uses the standard ngeohash library.
|
|
30
|
+
*/
|
|
31
|
+
export function decodeGeohash(hash: string): {
|
|
32
|
+
lat: number;
|
|
33
|
+
lng: number;
|
|
34
|
+
latErr: number;
|
|
35
|
+
lngErr: number;
|
|
36
|
+
} {
|
|
37
|
+
const result = ngeohash.decode(hash);
|
|
38
|
+
return {
|
|
39
|
+
lat: result.latitude,
|
|
40
|
+
lng: result.longitude,
|
|
41
|
+
latErr: result.error.latitude,
|
|
42
|
+
lngErr: result.error.longitude,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the 8 neighboring geohash cells.
|
|
48
|
+
* Returns up to 8 neighbors (fewer at poles or antimeridian).
|
|
49
|
+
* Uses the standard ngeohash library.
|
|
50
|
+
*/
|
|
51
|
+
export function getNeighbors(hash: string): string[] {
|
|
52
|
+
return ngeohash.neighbors(hash);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get a 3x3 grid of geohash4 cells (center + 8 neighbors).
|
|
57
|
+
* Used for landing bonus expansions in Fly mode.
|
|
58
|
+
*
|
|
59
|
+
* @param geohash4 - Center geohash4 cell
|
|
60
|
+
* @returns Array of 9 geohash4 cells (center first, then neighbors)
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* getGeohash4Neighbors('c23n')
|
|
64
|
+
* // => ['c23n', 'c23p', 'c23q', 'c23j', 'c23m', 'c23k', 'c23h', 'c23e', 'c23s']
|
|
65
|
+
*/
|
|
66
|
+
export function getGeohash4Neighbors(geohash4: string): string[] {
|
|
67
|
+
if (geohash4.length !== 4) {
|
|
68
|
+
throw new Error(`getGeohash4Neighbors requires 4-char geohash, got: ${geohash4}`);
|
|
69
|
+
}
|
|
70
|
+
const neighbors = getNeighbors(geohash4);
|
|
71
|
+
return [geohash4, ...neighbors];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the prefix of a geohash at a given precision.
|
|
76
|
+
* Simple helper for extracting geohash prefixes for directory organization.
|
|
77
|
+
*/
|
|
78
|
+
export function getGeohashPrefix(geohash: string, precision: number): string {
|
|
79
|
+
return geohash.slice(0, precision);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Convert a geohash prefix to a hierarchical path.
|
|
84
|
+
*
|
|
85
|
+
* Examples:
|
|
86
|
+
* "9mud" -> "9/m/u/d"
|
|
87
|
+
* "9q8y" -> "9/q/8/y"
|
|
88
|
+
* "c24k" -> "c/2/4/k"
|
|
89
|
+
*
|
|
90
|
+
* This is used for organizing files in hierarchical directories to avoid
|
|
91
|
+
* thousands of folders in a single directory, which causes filesystem
|
|
92
|
+
* performance issues.
|
|
93
|
+
*/
|
|
94
|
+
export function geohashToHierarchicalPath(geohash4: string): string {
|
|
95
|
+
if (geohash4.length !== 4) {
|
|
96
|
+
throw new Error(`geohashToHierarchicalPath requires 4-char geohash, got: ${geohash4}`);
|
|
97
|
+
}
|
|
98
|
+
const [c1, c2, c3, c4] = geohash4.split('');
|
|
99
|
+
return `${c1}/${c2}/${c3}/${c4}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Compute all geohash cells along the path between two geohash cells.
|
|
104
|
+
* Interpolates lat/lng points between the centers of `from` and `to`,
|
|
105
|
+
* encoding each to a geohash at the given precision, and returns the
|
|
106
|
+
* deduplicated list of intermediate cells (excluding `from` and `to`).
|
|
107
|
+
*/
|
|
108
|
+
export function getGeohashPath(from: string, to: string, precision = 4): string[] {
|
|
109
|
+
if (from === to) return [];
|
|
110
|
+
|
|
111
|
+
const fromCenter = decodeGeohash(from);
|
|
112
|
+
const toCenter = decodeGeohash(to);
|
|
113
|
+
|
|
114
|
+
// Compute approximate distance to determine step count
|
|
115
|
+
const dLat = Math.abs(toCenter.lat - fromCenter.lat);
|
|
116
|
+
const dLng = Math.abs(toCenter.lng - fromCenter.lng);
|
|
117
|
+
// Rough km: 1 degree lat ≈ 111km, 1 degree lng ≈ 111km * cos(midLat)
|
|
118
|
+
const midLatRad = ((fromCenter.lat + toCenter.lat) / 2) * (Math.PI / 180);
|
|
119
|
+
const approxKm = Math.sqrt((dLat * 111) ** 2 + (dLng * 111 * Math.cos(midLatRad)) ** 2);
|
|
120
|
+
|
|
121
|
+
// Step every ~15km to avoid skipping any geohash4 cell (~39x19km)
|
|
122
|
+
const steps = Math.max(2, Math.ceil(approxKm / 15));
|
|
123
|
+
|
|
124
|
+
const seen = new Set<string>();
|
|
125
|
+
seen.add(from);
|
|
126
|
+
seen.add(to);
|
|
127
|
+
|
|
128
|
+
const path: string[] = [];
|
|
129
|
+
for (let i = 1; i < steps; i++) {
|
|
130
|
+
const t = i / steps;
|
|
131
|
+
const lat = fromCenter.lat + t * (toCenter.lat - fromCenter.lat);
|
|
132
|
+
const lng = fromCenter.lng + t * (toCenter.lng - fromCenter.lng);
|
|
133
|
+
const hash = encodeGeohash(lat, lng, precision);
|
|
134
|
+
if (!seen.has(hash)) {
|
|
135
|
+
seen.add(hash);
|
|
136
|
+
path.push(hash);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return path;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if a geohash cell overlaps with a bounding box.
|
|
145
|
+
* Custom implementation (not available in ngeohash).
|
|
146
|
+
*/
|
|
147
|
+
export function geohashOverlapsBounds(
|
|
148
|
+
hash: string,
|
|
149
|
+
bounds: { north: number; south: number; east: number; west: number },
|
|
150
|
+
): boolean {
|
|
151
|
+
const { lat, lng, latErr, lngErr } = decodeGeohash(hash);
|
|
152
|
+
|
|
153
|
+
const hashNorth = lat + latErr;
|
|
154
|
+
const hashSouth = lat - latErr;
|
|
155
|
+
const hashEast = lng + lngErr;
|
|
156
|
+
const hashWest = lng - lngErr;
|
|
157
|
+
|
|
158
|
+
return !(
|
|
159
|
+
hashNorth < bounds.south ||
|
|
160
|
+
hashSouth > bounds.north ||
|
|
161
|
+
hashEast < bounds.west ||
|
|
162
|
+
hashWest > bounds.east
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// haversineDistance is now in shared/spatial/Haversine.ts (canonical location).
|
|
167
|
+
// Re-export for backwards compatibility with existing imports.
|
|
168
|
+
export { haversineDistance } from './Haversine.js';
|