@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.
Files changed (102) hide show
  1. package/dist/Types-sh2VRxfo.d.ts +15 -0
  2. package/dist/chunk-7UDSRZKG.js +103 -0
  3. package/dist/chunk-7UDSRZKG.js.map +1 -0
  4. package/dist/chunk-O5HCIUAD.js +577 -0
  5. package/dist/chunk-O5HCIUAD.js.map +1 -0
  6. package/dist/chunk-PJ7AODIM.js +3040 -0
  7. package/dist/chunk-PJ7AODIM.js.map +1 -0
  8. package/dist/chunk-QWVRN6I4.js +81 -0
  9. package/dist/chunk-QWVRN6I4.js.map +1 -0
  10. package/dist/chunk-S4F2RY37.js +92 -0
  11. package/dist/chunk-S4F2RY37.js.map +1 -0
  12. package/dist/chunk-URU6QMLY.js +888 -0
  13. package/dist/chunk-URU6QMLY.js.map +1 -0
  14. package/dist/chunk-VJN7UB2Z.js +145 -0
  15. package/dist/chunk-VJN7UB2Z.js.map +1 -0
  16. package/dist/doc/index.d.ts +3 -0
  17. package/dist/doc/index.js +126 -0
  18. package/dist/doc/index.js.map +1 -0
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.js +217 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/markdown/index.d.ts +237 -0
  23. package/dist/markdown/index.js +33 -0
  24. package/dist/markdown/index.js.map +1 -0
  25. package/dist/schemas/index.d.ts +70 -0
  26. package/dist/schemas/index.js +61 -0
  27. package/dist/schemas/index.js.map +1 -0
  28. package/dist/spatial/index.d.ts +111 -0
  29. package/dist/spatial/index.js +25 -0
  30. package/dist/spatial/index.js.map +1 -0
  31. package/dist/storage/index.d.ts +91 -0
  32. package/dist/storage/index.js +11 -0
  33. package/dist/storage/index.js.map +1 -0
  34. package/dist/story/index.d.ts +691 -0
  35. package/dist/story/index.js +126 -0
  36. package/dist/story/index.js.map +1 -0
  37. package/dist/themeLibrary-DySHPcgj.d.ts +1352 -0
  38. package/dist/types-DHiv_Pnm.d.ts +496 -0
  39. package/package.json +91 -0
  40. package/src/__tests__/animationUtils.test.ts +77 -0
  41. package/src/__tests__/geohash.test.ts +109 -0
  42. package/src/__tests__/getLayers.test.ts +200 -0
  43. package/src/__tests__/haversine.test.ts +63 -0
  44. package/src/__tests__/localForageAdapter.test.ts +155 -0
  45. package/src/__tests__/markdown.test.ts +806 -0
  46. package/src/__tests__/markdownToDoc.test.ts +374 -0
  47. package/src/__tests__/storage.test.ts +60 -0
  48. package/src/__tests__/templates.test.ts +247 -0
  49. package/src/doc/docToMarkdown.ts +98 -0
  50. package/src/doc/getLayers.ts +152 -0
  51. package/src/doc/index.ts +8 -0
  52. package/src/doc/markdownToDoc.ts +277 -0
  53. package/src/doc/templates/accentImage.ts +338 -0
  54. package/src/doc/templates/captionUtils.ts +33 -0
  55. package/src/doc/templates/comparisonBar.ts +140 -0
  56. package/src/doc/templates/coverBlock.ts +156 -0
  57. package/src/doc/templates/dateEvent.ts +122 -0
  58. package/src/doc/templates/definitionCard.ts +129 -0
  59. package/src/doc/templates/factCard.ts +114 -0
  60. package/src/doc/templates/fullBleedQuote.ts +59 -0
  61. package/src/doc/templates/imageWithCaption.ts +167 -0
  62. package/src/doc/templates/index.ts +571 -0
  63. package/src/doc/templates/listBlock.ts +101 -0
  64. package/src/doc/templates/mapBlock.ts +132 -0
  65. package/src/doc/templates/persistentLayers.ts +522 -0
  66. package/src/doc/templates/photoGrid.ts +199 -0
  67. package/src/doc/templates/pullQuote.ts +119 -0
  68. package/src/doc/templates/quoteBlock.ts +112 -0
  69. package/src/doc/templates/sectionHeader.ts +105 -0
  70. package/src/doc/templates/statHighlight.ts +113 -0
  71. package/src/doc/templates/titleBlock.ts +100 -0
  72. package/src/doc/templates/twoColumn.ts +239 -0
  73. package/src/doc/templates/videoPullQuote.ts +128 -0
  74. package/src/doc/templates/videoWithCaption.ts +126 -0
  75. package/src/doc/utils/animationUtils.ts +135 -0
  76. package/src/doc/utils/themeUtils.ts +139 -0
  77. package/src/index.ts +5 -0
  78. package/src/markdown/convert.ts +897 -0
  79. package/src/markdown/htmlParse.ts +212 -0
  80. package/src/markdown/index.ts +110 -0
  81. package/src/markdown/parse.ts +103 -0
  82. package/src/markdown/stringify.ts +122 -0
  83. package/src/markdown/types.ts +605 -0
  84. package/src/markdown/utils.ts +165 -0
  85. package/src/ngeohash.d.ts +11 -0
  86. package/src/schemas/BlockTemplates.ts +706 -0
  87. package/src/schemas/Doc.ts +565 -0
  88. package/src/schemas/LayoutStrategy.ts +178 -0
  89. package/src/schemas/MediaProvider.ts +70 -0
  90. package/src/schemas/Theme.ts +235 -0
  91. package/src/schemas/Types.ts +15 -0
  92. package/src/schemas/Viewport.ts +91 -0
  93. package/src/schemas/index.ts +14 -0
  94. package/src/schemas/themeLibrary.ts +492 -0
  95. package/src/spatial/Geohash.ts +168 -0
  96. package/src/spatial/Haversine.ts +59 -0
  97. package/src/spatial/index.ts +2 -0
  98. package/src/storage/LocalForageAdapter.ts +103 -0
  99. package/src/storage/LocalStorageAdapter.ts +57 -0
  100. package/src/storage/MemoryStorageAdapter.ts +37 -0
  101. package/src/storage/Storage.ts +28 -0
  102. package/src/storage/index.ts +5 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Layout Strategy
3
+ *
4
+ * Provides orientation-specific layout hints for slide templates.
5
+ * Templates use these hints to position content appropriately for
6
+ * different aspect ratios (landscape, portrait, square).
7
+ *
8
+ * Key adaptations by orientation:
9
+ * - Portrait: Larger relative text, stacked layouts instead of side-by-side
10
+ * - Square: Balanced layout with moderate adjustments
11
+ * - Landscape: Default/reference layout
12
+ */
13
+
14
+ import type { ViewportOrientation, ViewportConfig } from './Viewport.js';
15
+ import { calculateFontScale } from './Viewport.js';
16
+
17
+ /**
18
+ * Layout hints for template positioning based on orientation.
19
+ */
20
+ export interface LayoutHints {
21
+ /** Primary content Y position (percentage string, e.g., "35%") */
22
+ primaryY: string;
23
+ /** Secondary content Y position (percentage string) */
24
+ secondaryY: string;
25
+ /** Tertiary/detail Y position (percentage string) */
26
+ tertiaryY: string;
27
+ /** Title font size multiplier (relative to base) */
28
+ titleScale: number;
29
+ /** Body text font size multiplier (relative to base) */
30
+ bodyScale: number;
31
+ /** Two-column: left X position (percentage string) */
32
+ columnLeftX: string;
33
+ /** Two-column: right X position (percentage string) */
34
+ columnRightX: string;
35
+ /** Two-column in portrait: top Y position for stacked layout */
36
+ columnTopY: string;
37
+ /** Two-column in portrait: bottom Y position for stacked layout */
38
+ columnBottomY: string;
39
+ /** Whether two-column should stack vertically */
40
+ stackColumns: boolean;
41
+ /** Max text width (percentage string) for wrapping */
42
+ maxTextWidth: string;
43
+ /** Horizontal padding from edges (percentage string) */
44
+ horizontalPadding: string;
45
+ /** Caption position Y for imageWithCaption (percentage string) */
46
+ captionY: string;
47
+ /** Caption font size multiplier */
48
+ captionScale: number;
49
+ }
50
+
51
+ /**
52
+ * Layout configurations by orientation.
53
+ */
54
+ const LAYOUT_CONFIGS: Record<ViewportOrientation, LayoutHints> = {
55
+ landscape: {
56
+ primaryY: '35%',
57
+ secondaryY: '55%',
58
+ tertiaryY: '75%',
59
+ titleScale: 1.0,
60
+ bodyScale: 1.0,
61
+ columnLeftX: '25%',
62
+ columnRightX: '75%',
63
+ columnTopY: '35%',
64
+ columnBottomY: '65%',
65
+ stackColumns: false,
66
+ maxTextWidth: '80%',
67
+ horizontalPadding: '10%',
68
+ captionY: '85%',
69
+ captionScale: 1.0,
70
+ },
71
+ portrait: {
72
+ // Content positioned higher for thumb-reach zone on mobile
73
+ primaryY: '25%',
74
+ secondaryY: '42%',
75
+ tertiaryY: '58%',
76
+ // Titles scale down for narrower width; body text scales up to fill vertical space
77
+ titleScale: 0.7,
78
+ bodyScale: 1.5,
79
+ // Stacked columns instead of side-by-side
80
+ columnLeftX: '50%',
81
+ columnRightX: '50%',
82
+ columnTopY: '30%',
83
+ columnBottomY: '55%',
84
+ stackColumns: true,
85
+ // Wider relative to viewport to use available space
86
+ maxTextWidth: '90%',
87
+ horizontalPadding: '5%',
88
+ captionY: '75%',
89
+ captionScale: 0.85,
90
+ },
91
+ square: {
92
+ primaryY: '30%',
93
+ secondaryY: '50%',
94
+ tertiaryY: '70%',
95
+ titleScale: 0.85,
96
+ bodyScale: 0.9,
97
+ columnLeftX: '25%',
98
+ columnRightX: '75%',
99
+ columnTopY: '35%',
100
+ columnBottomY: '65%',
101
+ stackColumns: false,
102
+ maxTextWidth: '85%',
103
+ horizontalPadding: '7.5%',
104
+ captionY: '82%',
105
+ captionScale: 0.9,
106
+ },
107
+ };
108
+
109
+ /**
110
+ * Get layout hints for a given orientation.
111
+ */
112
+ export function getLayoutHints(orientation: ViewportOrientation): LayoutHints {
113
+ return LAYOUT_CONFIGS[orientation];
114
+ }
115
+
116
+ /**
117
+ * Calculate a scaled font size based on viewport and layout hints.
118
+ *
119
+ * @param basePx - Base font size in pixels (designed for 1920x1080)
120
+ * @param viewport - Target viewport configuration
121
+ * @param orientation - Viewport orientation
122
+ * @param isTitle - Whether this is title text (uses titleScale) or body (uses bodyScale)
123
+ */
124
+ export function scaledFontSize(
125
+ basePx: number,
126
+ viewport: ViewportConfig,
127
+ orientation: ViewportOrientation,
128
+ isTitle: boolean = false,
129
+ ): number {
130
+ const fontScale = calculateFontScale(viewport);
131
+ const layout = getLayoutHints(orientation);
132
+ const typeScale = isTitle ? layout.titleScale : layout.bodyScale;
133
+ return Math.round(basePx * fontScale * typeScale);
134
+ }
135
+
136
+ /**
137
+ * Get position for two-column layout based on orientation.
138
+ * Returns { left: {x, y}, right: {x, y} } positions.
139
+ */
140
+ export function getTwoColumnPositions(orientation: ViewportOrientation): {
141
+ left: { x: string; y: string };
142
+ right: { x: string; y: string };
143
+ } {
144
+ const layout = getLayoutHints(orientation);
145
+
146
+ if (layout.stackColumns) {
147
+ // Portrait: stack vertically
148
+ return {
149
+ left: { x: '50%', y: layout.columnTopY },
150
+ right: { x: '50%', y: layout.columnBottomY },
151
+ };
152
+ }
153
+
154
+ // Landscape/square: side by side
155
+ return {
156
+ left: { x: layout.columnLeftX, y: '50%' },
157
+ right: { x: layout.columnRightX, y: '50%' },
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Get safe text bounds for a given orientation.
163
+ * Returns margins to keep text away from edges.
164
+ */
165
+ export function getSafeTextBounds(orientation: ViewportOrientation): {
166
+ left: string;
167
+ right: string;
168
+ top: string;
169
+ bottom: string;
170
+ } {
171
+ const layout = getLayoutHints(orientation);
172
+ return {
173
+ left: layout.horizontalPadding,
174
+ right: layout.horizontalPadding,
175
+ top: '10%',
176
+ bottom: '10%',
177
+ };
178
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * MediaProvider — abstract interface for resolving and managing media assets.
3
+ *
4
+ * Components (ImageLayer, VideoLayer, editor preview) use a MediaProvider to
5
+ * resolve relative media paths to displayable URLs and to manage media storage.
6
+ *
7
+ * This decouples the rendering layer from the storage backend:
8
+ * - In the dev site, a SlotStorage-backed provider serves blob URLs from IndexedDB
9
+ * - In production, a simple basePath provider constructs CDN URLs
10
+ * - In tests, a memory-backed provider returns data URIs
11
+ */
12
+
13
+ /**
14
+ * Metadata about a stored media asset.
15
+ */
16
+ export interface MediaEntry {
17
+ /** Filename (e.g., 'hero.jpg') */
18
+ name: string;
19
+ /** MIME type (e.g., 'image/jpeg') */
20
+ mimeType: string;
21
+ /** File size in bytes */
22
+ size: number;
23
+ }
24
+
25
+ /**
26
+ * Interface for resolving and managing media assets.
27
+ *
28
+ * All methods are async to support IndexedDB and other async storage backends.
29
+ * The `resolveUrl` method is the primary integration point for rendering
30
+ * components — it converts a relative media path into a displayable URL.
31
+ */
32
+ export interface MediaProvider {
33
+ /**
34
+ * Resolve a relative media path to a displayable URL.
35
+ *
36
+ * Returns a URL that can be used in <img src>, <video src>, etc.
37
+ * This may be a blob: URL, data: URL, or absolute HTTP URL depending
38
+ * on the provider implementation.
39
+ *
40
+ * @param relativePath - Relative path as stored in the document (e.g., 'hero.jpg')
41
+ * @returns Displayable URL, or the original path if resolution fails
42
+ */
43
+ resolveUrl(relativePath: string): Promise<string>;
44
+
45
+ /**
46
+ * List all media assets available in this provider's scope.
47
+ */
48
+ listMedia(): Promise<MediaEntry[]>;
49
+
50
+ /**
51
+ * Add a media asset. Returns the relative path to reference in documents.
52
+ *
53
+ * @param name - Filename for the asset (e.g., 'photo.jpg')
54
+ * @param data - Binary content (ArrayBuffer, Blob, or Uint8Array)
55
+ * @param mimeType - MIME type of the data
56
+ * @returns The relative path to use in document references
57
+ */
58
+ addMedia(name: string, data: ArrayBuffer | Blob | Uint8Array, mimeType: string): Promise<string>;
59
+
60
+ /**
61
+ * Remove a media asset by its relative path.
62
+ */
63
+ removeMedia(relativePath: string): Promise<void>;
64
+
65
+ /**
66
+ * Dispose of any held resources (e.g., revoke blob URLs).
67
+ * Call this when the provider is no longer needed.
68
+ */
69
+ dispose(): void;
70
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Theme System
3
+ *
4
+ * A Theme bundles color palette, typography, visual style, render-style
5
+ * algorithm, and per-block color schemes into one JSON-serializable object.
6
+ * Builders choose a theme from the built-in library or create a custom one
7
+ * via `createTheme(base, overrides)`.
8
+ *
9
+ * Design principles:
10
+ * - Fully JSON-serializable (no functions) — storable in config / APIs.
11
+ * - Doc carries an optional `themeId` pointer; resolution happens at render time.
12
+ * - `createTheme` deep-merges a base theme with partial overrides.
13
+ */
14
+
15
+ import type { LayoutHints } from './LayoutStrategy.js';
16
+ import type { AnimationType, TransitionType } from './Doc.js';
17
+ import type { PersistentLayerConfig } from './BlockTemplates.js';
18
+
19
+ // ============================================
20
+ // Color Palette
21
+ // ============================================
22
+
23
+ /**
24
+ * Core color palette for a theme. Every color is a CSS color string.
25
+ */
26
+ export interface ThemeColorPalette {
27
+ /** Primary accent color */
28
+ primary: string;
29
+ /** Secondary accent color */
30
+ secondary: string;
31
+ /** Background color (typically dark) */
32
+ background: string;
33
+ /** Lighter background for contrast panels */
34
+ backgroundLight: string;
35
+ /** Main text color */
36
+ text: string;
37
+ /** Muted/secondary text color */
38
+ textMuted: string;
39
+ /** Highlight/emphasis color */
40
+ highlight: string;
41
+ /** Warning/alert color */
42
+ warning: string;
43
+ }
44
+
45
+ /**
46
+ * A named color scheme used by templates for per-block color variation
47
+ * (e.g., statHighlight or sectionHeader).
48
+ */
49
+ export interface ThemeColorScheme {
50
+ /** Background fill */
51
+ bg: string;
52
+ /** Primary text tint */
53
+ text: string;
54
+ /** Accent / secondary tint */
55
+ accent: string;
56
+ }
57
+
58
+ // ============================================
59
+ // Typography
60
+ // ============================================
61
+
62
+ /**
63
+ * Typography settings for a theme.
64
+ */
65
+ export interface ThemeTypography {
66
+ /** Font family for body / description text */
67
+ bodyFontFamily: string;
68
+ /** Font family for titles and headings */
69
+ titleFontFamily: string;
70
+ /** Font family for code / monospaced text (optional) */
71
+ monoFontFamily?: string;
72
+ /** Multiplier applied to LayoutHints.titleScale (default 1.0) */
73
+ titleScale?: number;
74
+ /** Multiplier applied to LayoutHints.bodyScale (default 1.0) */
75
+ bodyScale?: number;
76
+ /** Default body line height (default 1.4) */
77
+ lineHeight?: number;
78
+ /** Default title line height */
79
+ titleLineHeight?: number;
80
+ /** Default title font weight */
81
+ titleWeight?: 'normal' | 'bold';
82
+ }
83
+
84
+ // ============================================
85
+ // Visual Style
86
+ // ============================================
87
+
88
+ /**
89
+ * Global visual-style knobs that templates consult.
90
+ */
91
+ export interface ThemeStyle {
92
+ /** Default border radius for cards / shapes (px) */
93
+ borderRadius?: number;
94
+ /** Whether templates should default to text shadows */
95
+ textShadow?: boolean;
96
+ /** Darkness of overlay on image-backed blocks (0–1) */
97
+ overlayOpacity?: number;
98
+ /** Multiplier on all animation durations (1.0 = normal, <1 faster, >1 slower) */
99
+ animationSpeed?: number;
100
+ /** Default horizontal padding for text (percentage string, e.g. "5%") */
101
+ blockPadding?: string;
102
+ }
103
+
104
+ // ============================================
105
+ // Render Style
106
+ // ============================================
107
+
108
+ /**
109
+ * Render-style algorithm preset. Controls layout tweaks, default animations,
110
+ * transitions, and per-template behavioral hints.
111
+ */
112
+ export interface RenderStyle {
113
+ /** Identifier for this algorithmic approach (e.g. "documentary", "magazine") */
114
+ name: string;
115
+ /** Partial overrides merged onto the orientation-based LayoutHints */
116
+ layoutOverrides?: Partial<LayoutHints>;
117
+ /** Default entrance animation for text layers */
118
+ defaultTextAnimation?: AnimationType;
119
+ /** Default animation for background / image layers */
120
+ defaultImageAnimation?: AnimationType;
121
+ /** Whether to apply Ken Burns ambient motion to images by default */
122
+ ambientMotion?: boolean;
123
+ /** Default block-to-block transition */
124
+ defaultTransition?: { type: TransitionType; duration?: number };
125
+ /**
126
+ * Per-template behavioral hints. Keys are template names, values are
127
+ * string/number/boolean maps that templates can read to vary their output.
128
+ *
129
+ * @example
130
+ * ```
131
+ * { statHighlight: { entrance: 'dramatic' }, titleBlock: { showAccentLine: false } }
132
+ * ```
133
+ */
134
+ templateHints?: Record<string, Record<string, string | number | boolean>>;
135
+ }
136
+
137
+ // ============================================
138
+ // Theme
139
+ // ============================================
140
+
141
+ /**
142
+ * A complete, JSON-serializable theme definition.
143
+ */
144
+ export interface Theme {
145
+ /** Unique identifier (e.g. "documentary") */
146
+ id: string;
147
+ /** Human-readable display name */
148
+ name: string;
149
+ /** Short description for theme pickers */
150
+ description?: string;
151
+ /** Color palette */
152
+ colors: ThemeColorPalette;
153
+ /** Typography settings */
154
+ typography: ThemeTypography;
155
+ /** Global visual-style knobs */
156
+ style: ThemeStyle;
157
+ /** Algorithmic render-style preset */
158
+ renderStyle: RenderStyle;
159
+ /**
160
+ * Named color schemes for per-block color variation.
161
+ * Templates reference these by name (e.g. "blue", "warm").
162
+ */
163
+ colorSchemes: Record<string, ThemeColorScheme>;
164
+ /** Optional persistent layers baked into the theme */
165
+ persistentLayers?: PersistentLayerConfig;
166
+ }
167
+
168
+ // ============================================
169
+ // Deep-Partial helper type
170
+ // ============================================
171
+
172
+ /**
173
+ * Recursively makes every property optional.
174
+ */
175
+ export type DeepPartial<T> = {
176
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
177
+ };
178
+
179
+ // ============================================
180
+ // Helpers
181
+ // ============================================
182
+
183
+ /**
184
+ * Deep-merge `source` into `target`, returning a new object.
185
+ * Arrays are replaced wholesale (not concatenated).
186
+ */
187
+ function deepMerge<T extends Record<string, unknown>>(target: T, source: DeepPartial<T>): T {
188
+ const result = { ...target } as Record<string, unknown>;
189
+ for (const key of Object.keys(source)) {
190
+ const srcVal = (source as Record<string, unknown>)[key];
191
+ const tgtVal = (target as Record<string, unknown>)[key];
192
+ if (
193
+ srcVal !== null &&
194
+ srcVal !== undefined &&
195
+ typeof srcVal === 'object' &&
196
+ !Array.isArray(srcVal) &&
197
+ tgtVal !== null &&
198
+ tgtVal !== undefined &&
199
+ typeof tgtVal === 'object' &&
200
+ !Array.isArray(tgtVal)
201
+ ) {
202
+ result[key] = deepMerge(
203
+ tgtVal as Record<string, unknown>,
204
+ srcVal as DeepPartial<Record<string, unknown>>,
205
+ );
206
+ } else if (srcVal !== undefined) {
207
+ result[key] = srcVal;
208
+ }
209
+ }
210
+ return result as T;
211
+ }
212
+
213
+ /**
214
+ * Create a new theme by deep-merging overrides onto a base theme.
215
+ * The returned theme gets its own `id` if one is provided in overrides,
216
+ * otherwise keeps the base theme's `id` suffixed with "-custom".
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * const myTheme = createTheme(THEMES.documentary, {
221
+ * colors: { primary: '#ff0000' },
222
+ * typography: { bodyFontFamily: '"Inter", sans-serif' },
223
+ * });
224
+ * ```
225
+ */
226
+ export function createTheme(base: Theme, overrides: DeepPartial<Theme>): Theme {
227
+ const merged = deepMerge(
228
+ base as unknown as Record<string, unknown>,
229
+ overrides as DeepPartial<Record<string, unknown>>,
230
+ ) as unknown as Theme;
231
+ if (!overrides.id && merged.id === base.id) {
232
+ merged.id = `${base.id}-custom`;
233
+ }
234
+ return merged;
235
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Core geographic types shared across squisq.
3
+ */
4
+
5
+ export interface Coordinates {
6
+ lat: number;
7
+ lng: number;
8
+ }
9
+
10
+ export interface BoundingBox {
11
+ north: number;
12
+ south: number;
13
+ east: number;
14
+ west: number;
15
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Viewport Configuration
3
+ *
4
+ * Defines viewport dimensions for doc rendering across different aspect ratios.
5
+ * Templates use this configuration to adapt their layouts appropriately.
6
+ *
7
+ * Supported aspect ratios:
8
+ * - landscape (16:9) - Default, standard video/presentation format
9
+ * - portrait (9:16) - Vertical video, mobile stories
10
+ * - square (1:1) - Social media posts
11
+ * - standard (4:3) - Legacy presentation format
12
+ */
13
+
14
+ /**
15
+ * Viewport configuration for doc rendering.
16
+ */
17
+ export interface ViewportConfig {
18
+ /** Canonical width in virtual pixels */
19
+ width: number;
20
+ /** Canonical height in virtual pixels */
21
+ height: number;
22
+ /** Human-readable name for debugging/display */
23
+ name: string;
24
+ }
25
+
26
+ /**
27
+ * Standard viewport presets for common aspect ratios.
28
+ */
29
+ export const VIEWPORT_PRESETS = {
30
+ /** 16:9 landscape (default, 1080p) */
31
+ landscape: { width: 1920, height: 1080, name: '16:9 Landscape' },
32
+ /** 9:16 portrait (vertical video, stories) */
33
+ portrait: { width: 1080, height: 1920, name: '9:16 Portrait' },
34
+ /** 1:1 square (social media) */
35
+ square: { width: 1080, height: 1080, name: '1:1 Square' },
36
+ /** 4:3 standard (legacy) */
37
+ standard: { width: 1440, height: 1080, name: '4:3 Standard' },
38
+ } as const;
39
+
40
+ /**
41
+ * Viewport preset name.
42
+ */
43
+ export type ViewportPreset = keyof typeof VIEWPORT_PRESETS;
44
+
45
+ /**
46
+ * Viewport orientation derived from aspect ratio.
47
+ */
48
+ export type ViewportOrientation = 'landscape' | 'portrait' | 'square';
49
+
50
+ /**
51
+ * Get a viewport configuration from a preset name or return the config if already a ViewportConfig.
52
+ */
53
+ export function getViewport(viewport: ViewportPreset | ViewportConfig): ViewportConfig {
54
+ if (typeof viewport === 'string') {
55
+ return VIEWPORT_PRESETS[viewport];
56
+ }
57
+ return viewport;
58
+ }
59
+
60
+ /**
61
+ * Calculate font scale factor for a viewport.
62
+ * Based on diagonal pixels relative to 1080p landscape reference.
63
+ * This ensures text remains readable across different viewport sizes.
64
+ */
65
+ const REFERENCE_DIAGONAL = Math.sqrt(1920 * 1920 + 1080 * 1080); // ~2203
66
+
67
+ export function calculateFontScale(viewport: ViewportConfig): number {
68
+ const currentDiagonal = Math.sqrt(
69
+ viewport.width * viewport.width + viewport.height * viewport.height,
70
+ );
71
+ return currentDiagonal / REFERENCE_DIAGONAL;
72
+ }
73
+
74
+ /**
75
+ * Determine if viewport is landscape, portrait, or square.
76
+ */
77
+ export function getViewportOrientation(viewport: ViewportConfig): ViewportOrientation {
78
+ const ratio = viewport.width / viewport.height;
79
+ // Allow small tolerance for "square" classification
80
+ if (Math.abs(ratio - 1) < 0.05) return 'square';
81
+ return ratio > 1 ? 'landscape' : 'portrait';
82
+ }
83
+
84
+ /**
85
+ * Get aspect ratio as a string (e.g., "16:9", "9:16", "1:1").
86
+ */
87
+ export function getAspectRatioString(viewport: ViewportConfig): string {
88
+ const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));
89
+ const divisor = gcd(viewport.width, viewport.height);
90
+ return `${viewport.width / divisor}:${viewport.height / divisor}`;
91
+ }
@@ -0,0 +1,14 @@
1
+ export * from './Types.js';
2
+ export * from './Doc.js';
3
+ export * from './BlockTemplates.js';
4
+ export * from './Theme.js';
5
+ export * from './themeLibrary.js';
6
+ export * from './Viewport.js';
7
+ export {
8
+ getLayoutHints,
9
+ getTwoColumnPositions,
10
+ getSafeTextBounds,
11
+ scaledFontSize as layoutScaledFontSize,
12
+ } from './LayoutStrategy.js';
13
+ export type { LayoutHints } from './LayoutStrategy.js';
14
+ export type { MediaProvider, MediaEntry } from './MediaProvider.js';