@bendyline/squisq-react 0.1.2
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/index.d.ts +563 -0
- package/dist/index.js +3180 -0
- package/dist/index.js.map +1 -0
- package/dist/squisq-player.css +2 -0
- package/dist/squisq-player.css.map +1 -0
- package/dist/squisq-player.global.js +6 -0
- package/dist/squisq-player.global.js.map +1 -0
- package/dist/standalone-source.d.ts +2 -0
- package/dist/standalone-source.js +2 -0
- package/package.json +69 -0
- package/src/BlockRenderer.tsx +146 -0
- package/src/CaptionOverlay.tsx +86 -0
- package/src/DocControlsBottom.tsx +103 -0
- package/src/DocControlsOverlay.tsx +178 -0
- package/src/DocControlsSidebar.tsx +107 -0
- package/src/DocControlsSlideshow.tsx +132 -0
- package/src/DocPlayer.tsx +1005 -0
- package/src/DocPlayerWithSidebar.tsx +138 -0
- package/src/DocProgressBar.tsx +200 -0
- package/src/LinearDocView.tsx +313 -0
- package/src/MarkdownRenderer.tsx +360 -0
- package/src/__tests__/BlockRenderer.test.tsx +105 -0
- package/src/__tests__/DocControlsSlideshow.test.tsx +127 -0
- package/src/__tests__/LinearDocView.test.tsx +180 -0
- package/src/__tests__/MarkdownRenderer.test.tsx +234 -0
- package/src/__tests__/exports.test.ts +55 -0
- package/src/hooks/AudioProvider.ts +114 -0
- package/src/hooks/MediaContext.tsx +81 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useAudioSync.ts +390 -0
- package/src/hooks/useDocPlayback.ts +251 -0
- package/src/hooks/useViewportOrientation.ts +117 -0
- package/src/index.ts +46 -0
- package/src/layers/ImageLayer.tsx +182 -0
- package/src/layers/MapLayer.tsx +184 -0
- package/src/layers/ShapeLayer.tsx +107 -0
- package/src/layers/TextLayer.tsx +197 -0
- package/src/layers/VideoLayer.tsx +150 -0
- package/src/layers/index.ts +5 -0
- package/src/standalone-entry.tsx +228 -0
- package/src/styles/doc-animations.css +458 -0
- package/src/types.ts +152 -0
- package/src/utils/animationUtils.ts +13 -0
- package/src/utils/layerUtils.ts +42 -0
- package/src/utils/mapTileUtils.ts +375 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doc Animations
|
|
3
|
+
*
|
|
4
|
+
* CSS keyframe animations for the visual doc system.
|
|
5
|
+
* Designed to be ES2015/Chrome 49 compatible for Coherent GT (MSFS).
|
|
6
|
+
*
|
|
7
|
+
* Animation classes are applied dynamically based on Doc.
|
|
8
|
+
* Each animation has variants for common durations.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* ============================================
|
|
12
|
+
Slow Zoom Effect
|
|
13
|
+
Slow zoom with optional pan for cinematic feel
|
|
14
|
+
============================================ */
|
|
15
|
+
|
|
16
|
+
@keyframes slowZoomIn {
|
|
17
|
+
from {
|
|
18
|
+
transform: scale(1);
|
|
19
|
+
}
|
|
20
|
+
to {
|
|
21
|
+
transform: scale(1.15);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@keyframes slowZoomOut {
|
|
26
|
+
from {
|
|
27
|
+
transform: scale(1.15);
|
|
28
|
+
}
|
|
29
|
+
to {
|
|
30
|
+
transform: scale(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@keyframes slowZoomPanLeft {
|
|
35
|
+
from {
|
|
36
|
+
transform: translateX(0) scale(1.1);
|
|
37
|
+
}
|
|
38
|
+
to {
|
|
39
|
+
transform: translateX(-8%) scale(1.1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@keyframes slowZoomPanRight {
|
|
44
|
+
from {
|
|
45
|
+
transform: translateX(-8%) scale(1.1);
|
|
46
|
+
}
|
|
47
|
+
to {
|
|
48
|
+
transform: translateX(0) scale(1.1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@keyframes slowZoomPanUp {
|
|
53
|
+
from {
|
|
54
|
+
transform: translateY(0) scale(1.1);
|
|
55
|
+
}
|
|
56
|
+
to {
|
|
57
|
+
transform: translateY(-8%) scale(1.1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@keyframes slowZoomPanDown {
|
|
62
|
+
from {
|
|
63
|
+
transform: translateY(-8%) scale(1.1);
|
|
64
|
+
}
|
|
65
|
+
to {
|
|
66
|
+
transform: translateY(0) scale(1.1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Slow zoom combined zoom + pan */
|
|
71
|
+
@keyframes slowZoomCombinedPanLeft {
|
|
72
|
+
from {
|
|
73
|
+
transform: translateX(0) scale(1);
|
|
74
|
+
}
|
|
75
|
+
to {
|
|
76
|
+
transform: translateX(-5%) scale(1.12);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@keyframes slowZoomCombinedPanRight {
|
|
81
|
+
from {
|
|
82
|
+
transform: translateX(-5%) scale(1.12);
|
|
83
|
+
}
|
|
84
|
+
to {
|
|
85
|
+
transform: translateX(0) scale(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* ============================================
|
|
90
|
+
Fade Animations
|
|
91
|
+
============================================ */
|
|
92
|
+
|
|
93
|
+
@keyframes fadeIn {
|
|
94
|
+
from {
|
|
95
|
+
opacity: 0;
|
|
96
|
+
}
|
|
97
|
+
to {
|
|
98
|
+
opacity: 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@keyframes fadeOut {
|
|
103
|
+
from {
|
|
104
|
+
opacity: 1;
|
|
105
|
+
}
|
|
106
|
+
to {
|
|
107
|
+
opacity: 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@keyframes fadeInUp {
|
|
112
|
+
from {
|
|
113
|
+
opacity: 0;
|
|
114
|
+
transform: translateY(20px);
|
|
115
|
+
}
|
|
116
|
+
to {
|
|
117
|
+
opacity: 1;
|
|
118
|
+
transform: translateY(0);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@keyframes fadeInDown {
|
|
123
|
+
from {
|
|
124
|
+
opacity: 0;
|
|
125
|
+
transform: translateY(-20px);
|
|
126
|
+
}
|
|
127
|
+
to {
|
|
128
|
+
opacity: 1;
|
|
129
|
+
transform: translateY(0);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ============================================
|
|
134
|
+
Zoom Animations
|
|
135
|
+
============================================ */
|
|
136
|
+
|
|
137
|
+
@keyframes zoomIn {
|
|
138
|
+
from {
|
|
139
|
+
transform: scale(0.9);
|
|
140
|
+
opacity: 0;
|
|
141
|
+
}
|
|
142
|
+
to {
|
|
143
|
+
transform: scale(1);
|
|
144
|
+
opacity: 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@keyframes zoomOut {
|
|
149
|
+
from {
|
|
150
|
+
transform: scale(1);
|
|
151
|
+
opacity: 1;
|
|
152
|
+
}
|
|
153
|
+
to {
|
|
154
|
+
transform: scale(0.9);
|
|
155
|
+
opacity: 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* ============================================
|
|
160
|
+
Pan/Slide Animations
|
|
161
|
+
============================================ */
|
|
162
|
+
|
|
163
|
+
@keyframes panLeft {
|
|
164
|
+
from {
|
|
165
|
+
transform: translateX(0);
|
|
166
|
+
}
|
|
167
|
+
to {
|
|
168
|
+
transform: translateX(-10%);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@keyframes panRight {
|
|
173
|
+
from {
|
|
174
|
+
transform: translateX(-10%);
|
|
175
|
+
}
|
|
176
|
+
to {
|
|
177
|
+
transform: translateX(0);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@keyframes slideInLeft {
|
|
182
|
+
from {
|
|
183
|
+
transform: translateX(-100%);
|
|
184
|
+
}
|
|
185
|
+
to {
|
|
186
|
+
transform: translateX(0);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@keyframes slideInRight {
|
|
191
|
+
from {
|
|
192
|
+
transform: translateX(100%);
|
|
193
|
+
}
|
|
194
|
+
to {
|
|
195
|
+
transform: translateX(0);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@keyframes slideOutLeft {
|
|
200
|
+
from {
|
|
201
|
+
transform: translateX(0);
|
|
202
|
+
}
|
|
203
|
+
to {
|
|
204
|
+
transform: translateX(-100%);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@keyframes slideOutRight {
|
|
209
|
+
from {
|
|
210
|
+
transform: translateX(0);
|
|
211
|
+
}
|
|
212
|
+
to {
|
|
213
|
+
transform: translateX(100%);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ============================================
|
|
218
|
+
Text Animations
|
|
219
|
+
============================================ */
|
|
220
|
+
|
|
221
|
+
@keyframes typewriter {
|
|
222
|
+
from {
|
|
223
|
+
clip-path: inset(0 100% 0 0);
|
|
224
|
+
}
|
|
225
|
+
to {
|
|
226
|
+
clip-path: inset(0 0 0 0);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Fallback for browsers without clip-path */
|
|
231
|
+
@keyframes typewriterFallback {
|
|
232
|
+
from {
|
|
233
|
+
width: 0;
|
|
234
|
+
overflow: hidden;
|
|
235
|
+
}
|
|
236
|
+
to {
|
|
237
|
+
width: 100%;
|
|
238
|
+
overflow: hidden;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ============================================
|
|
243
|
+
Block Transitions
|
|
244
|
+
Applied to entire block containers
|
|
245
|
+
============================================ */
|
|
246
|
+
|
|
247
|
+
.transition-fade-enter {
|
|
248
|
+
animation: fadeIn var(--transition-duration, 1.5s) ease-out forwards;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.transition-fade-exit {
|
|
252
|
+
animation: fadeOut var(--transition-duration, 1.5s) ease-out forwards;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.transition-dissolve-enter {
|
|
256
|
+
animation: fadeIn var(--transition-duration, 2s) ease-in-out forwards;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.transition-dissolve-exit {
|
|
260
|
+
animation: fadeOut var(--transition-duration, 2s) ease-in-out forwards;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.transition-slideLeft-enter {
|
|
264
|
+
animation: slideInRight var(--transition-duration, 0.8s) ease-out forwards;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.transition-slideLeft-exit {
|
|
268
|
+
animation: slideOutLeft var(--transition-duration, 0.8s) ease-out forwards;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.transition-slideRight-enter {
|
|
272
|
+
animation: slideInLeft var(--transition-duration, 0.8s) ease-out forwards;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.transition-slideRight-exit {
|
|
276
|
+
animation: slideOutRight var(--transition-duration, 0.8s) ease-out forwards;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* ============================================
|
|
280
|
+
Animation Utility Classes
|
|
281
|
+
Applied dynamically based on layer.animation
|
|
282
|
+
============================================ */
|
|
283
|
+
|
|
284
|
+
.anim-none {
|
|
285
|
+
animation: none;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.anim-fadeIn {
|
|
289
|
+
animation: fadeIn var(--anim-duration, 1s) var(--anim-easing, ease-out) var(--anim-delay, 0s)
|
|
290
|
+
forwards;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.anim-fadeOut {
|
|
294
|
+
animation: fadeOut var(--anim-duration, 1s) var(--anim-easing, ease-out) var(--anim-delay, 0s)
|
|
295
|
+
forwards;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.anim-fadeInUp {
|
|
299
|
+
animation: fadeInUp var(--anim-duration, 0.6s) var(--anim-easing, ease-out) var(--anim-delay, 0s)
|
|
300
|
+
forwards;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.anim-slowZoom-in {
|
|
304
|
+
animation: slowZoomIn var(--anim-duration, 8s) var(--anim-easing, linear) var(--anim-delay, 0s)
|
|
305
|
+
forwards;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.anim-slowZoom-out {
|
|
309
|
+
animation: slowZoomOut var(--anim-duration, 8s) var(--anim-easing, linear) var(--anim-delay, 0s)
|
|
310
|
+
forwards;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.anim-slowZoom-panLeft {
|
|
314
|
+
animation: slowZoomCombinedPanLeft var(--anim-duration, 8s) var(--anim-easing, linear)
|
|
315
|
+
var(--anim-delay, 0s) forwards;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.anim-slowZoom-panRight {
|
|
319
|
+
animation: slowZoomCombinedPanRight var(--anim-duration, 8s) var(--anim-easing, linear)
|
|
320
|
+
var(--anim-delay, 0s) forwards;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.anim-zoomIn {
|
|
324
|
+
animation: zoomIn var(--anim-duration, 0.5s) var(--anim-easing, ease-out) var(--anim-delay, 0s)
|
|
325
|
+
forwards;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.anim-zoomOut {
|
|
329
|
+
animation: zoomOut var(--anim-duration, 0.5s) var(--anim-easing, ease-out) var(--anim-delay, 0s)
|
|
330
|
+
forwards;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.anim-panLeft {
|
|
334
|
+
animation: panLeft var(--anim-duration, 8s) var(--anim-easing, linear) var(--anim-delay, 0s)
|
|
335
|
+
forwards;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.anim-panRight {
|
|
339
|
+
animation: panRight var(--anim-duration, 8s) var(--anim-easing, linear) var(--anim-delay, 0s)
|
|
340
|
+
forwards;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.anim-typewriter {
|
|
344
|
+
animation: typewriter var(--anim-duration, 2s) var(--anim-easing, steps(40, end))
|
|
345
|
+
var(--anim-delay, 0s) forwards;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* ============================================
|
|
349
|
+
Doc Player Container Styles
|
|
350
|
+
============================================ */
|
|
351
|
+
|
|
352
|
+
.doc-player {
|
|
353
|
+
position: relative;
|
|
354
|
+
overflow: hidden;
|
|
355
|
+
/* clip-path provides a hard pixel clip that catches SVG foreignObject bleed
|
|
356
|
+
which can escape overflow:hidden in some browsers */
|
|
357
|
+
clip-path: inset(0);
|
|
358
|
+
background: #000;
|
|
359
|
+
/* Ensure max dimensions work with aspect-ratio */
|
|
360
|
+
max-width: 100%;
|
|
361
|
+
max-height: 100%;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.doc-player__viewport {
|
|
365
|
+
position: absolute;
|
|
366
|
+
top: 0;
|
|
367
|
+
left: 0;
|
|
368
|
+
width: 100%;
|
|
369
|
+
height: 100%;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.doc-player__block {
|
|
373
|
+
position: absolute;
|
|
374
|
+
top: 0;
|
|
375
|
+
left: 0;
|
|
376
|
+
width: 100%;
|
|
377
|
+
height: 100%;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.doc-player__block--active {
|
|
381
|
+
z-index: 2;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.doc-player__block--previous {
|
|
385
|
+
z-index: 1;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* Tap-to-toggle play/pause feedback */
|
|
389
|
+
.doc-player__tap-feedback {
|
|
390
|
+
position: absolute;
|
|
391
|
+
top: 50%;
|
|
392
|
+
left: 50%;
|
|
393
|
+
transform: translate(-50%, -50%);
|
|
394
|
+
z-index: 150;
|
|
395
|
+
pointer-events: none;
|
|
396
|
+
display: flex;
|
|
397
|
+
align-items: center;
|
|
398
|
+
justify-content: center;
|
|
399
|
+
width: 72px;
|
|
400
|
+
height: 72px;
|
|
401
|
+
border-radius: 50%;
|
|
402
|
+
background: rgba(0, 0, 0, 0.45);
|
|
403
|
+
animation: tap-feedback-fade 0.6s ease-out forwards;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@keyframes tap-feedback-fade {
|
|
407
|
+
0% {
|
|
408
|
+
opacity: 1;
|
|
409
|
+
transform: translate(-50%, -50%) scale(1);
|
|
410
|
+
}
|
|
411
|
+
100% {
|
|
412
|
+
opacity: 0;
|
|
413
|
+
transform: translate(-50%, -50%) scale(1.3);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/* SVG block container */
|
|
418
|
+
.block-svg {
|
|
419
|
+
width: 100%;
|
|
420
|
+
height: 100%;
|
|
421
|
+
display: block;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* Layer containers within SVG */
|
|
425
|
+
.block-layer {
|
|
426
|
+
overflow: hidden;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.block-layer--image {
|
|
430
|
+
/* Images need overflow visible for Ken Burns */
|
|
431
|
+
overflow: visible;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* ============================================
|
|
435
|
+
Text Styling Helpers
|
|
436
|
+
============================================ */
|
|
437
|
+
|
|
438
|
+
.text-shadow {
|
|
439
|
+
text-shadow:
|
|
440
|
+
0 2px 4px rgba(0, 0, 0, 0.8),
|
|
441
|
+
0 4px 8px rgba(0, 0, 0, 0.4);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.text-shadow-light {
|
|
445
|
+
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* Text background box */
|
|
449
|
+
.text-box {
|
|
450
|
+
background: rgba(0, 0, 0, 0.6);
|
|
451
|
+
padding: 16px 24px;
|
|
452
|
+
border-radius: 4px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.text-box--light {
|
|
456
|
+
background: rgba(255, 255, 255, 0.9);
|
|
457
|
+
color: #1a1a1a;
|
|
458
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doc Player Control Types
|
|
3
|
+
*
|
|
4
|
+
* Shared type definitions for doc player controls across different
|
|
5
|
+
* layout modes (overlay, sidebar, bottom). These interfaces allow
|
|
6
|
+
* control components to be decoupled from the core DocPlayer.
|
|
7
|
+
*
|
|
8
|
+
* Related Files:
|
|
9
|
+
* - DocPlayer.tsx — Core player component
|
|
10
|
+
* - DocProgressBar.tsx — Extracted progress bar
|
|
11
|
+
* - DocControlsOverlay.tsx — Overlay controls (default)
|
|
12
|
+
* - DocControlsSidebar.tsx — Vertical sidebar controls
|
|
13
|
+
* - DocControlsBottom.tsx — Horizontal bottom strip controls
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Block } from '@bendyline/squisq/schemas';
|
|
17
|
+
|
|
18
|
+
/** Layout mode for doc player controls */
|
|
19
|
+
export type ControlsLayout = 'overlay' | 'sidebar' | 'bottom';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Display mode for DocPlayer.
|
|
23
|
+
*
|
|
24
|
+
* - `'video'` — Traditional video-style playback with play/pause, scrub bar,
|
|
25
|
+
* and time-based auto-advance. Default mode.
|
|
26
|
+
* - `'slideshow'` — PowerPoint-style presentation with prev/next navigation.
|
|
27
|
+
* Blocks are treated as static slides that only change on user click.
|
|
28
|
+
* No auto-advance, no scrub bar.
|
|
29
|
+
* - `'linear'` — Long-scrolling document view. Renders the full markdown as
|
|
30
|
+
* readable HTML with template-annotated sections shown as inline SVG cards.
|
|
31
|
+
* No audio, no timeline, no controls — just a scrollable page.
|
|
32
|
+
*/
|
|
33
|
+
export type DisplayMode = 'video' | 'slideshow' | 'linear';
|
|
34
|
+
|
|
35
|
+
/** Slide navigation actions for slideshow display mode */
|
|
36
|
+
export interface SlideNavActions {
|
|
37
|
+
/** Navigate to the next slide */
|
|
38
|
+
nextSlide: () => void;
|
|
39
|
+
/** Navigate to the previous slide */
|
|
40
|
+
prevSlide: () => void;
|
|
41
|
+
/** Navigate to a specific slide by index (0-based) */
|
|
42
|
+
goToSlide: (index: number) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Playback state exposed to external control components */
|
|
46
|
+
export interface PlaybackState {
|
|
47
|
+
isPlaying: boolean;
|
|
48
|
+
currentTime: number;
|
|
49
|
+
totalDuration: number;
|
|
50
|
+
currentBlockIndex: number;
|
|
51
|
+
totalBlocks: number;
|
|
52
|
+
docProgress: number;
|
|
53
|
+
hasCaptions: boolean;
|
|
54
|
+
captionsEnabled: boolean;
|
|
55
|
+
isFullscreen?: boolean;
|
|
56
|
+
/** Current audio segment index (0-based) */
|
|
57
|
+
currentSegmentIndex: number;
|
|
58
|
+
/** Current audio segment name (e.g., 'intro', 'history') */
|
|
59
|
+
currentSegmentName: string | null;
|
|
60
|
+
/** Current block data (for extracting image info, etc.) */
|
|
61
|
+
currentBlock: Block | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Playback actions exposed to external control components */
|
|
65
|
+
export interface PlaybackActions {
|
|
66
|
+
toggle: () => void;
|
|
67
|
+
restart: () => void;
|
|
68
|
+
seekTo: (time: number) => void;
|
|
69
|
+
setCaptionsEnabled: (enabled: boolean) => void;
|
|
70
|
+
toggleFullscreen?: () => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Block marker data for the progress bar */
|
|
74
|
+
export interface BlockMarker {
|
|
75
|
+
block: Block;
|
|
76
|
+
index: number;
|
|
77
|
+
position: number;
|
|
78
|
+
title: string;
|
|
79
|
+
/** True if this block is the first in a new audio segment (section boundary) */
|
|
80
|
+
isSectionStart: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================
|
|
84
|
+
// Render-mode API (exposed on window for Playwright / debug)
|
|
85
|
+
// ============================================
|
|
86
|
+
|
|
87
|
+
/** Block metadata returned by SquisqRenderAPI.getBlocks() */
|
|
88
|
+
export interface RenderBlockInfo {
|
|
89
|
+
id: string;
|
|
90
|
+
template: string;
|
|
91
|
+
startTime: number;
|
|
92
|
+
duration: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Audio segment info returned by SquisqRenderAPI.getAudioSegments() */
|
|
96
|
+
export interface RenderAudioSegmentInfo {
|
|
97
|
+
src: string;
|
|
98
|
+
name: string;
|
|
99
|
+
duration: number;
|
|
100
|
+
startTime: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Caption phrase info returned by SquisqRenderAPI.getCaptions() */
|
|
104
|
+
export interface RenderCaptionInfo {
|
|
105
|
+
text: string;
|
|
106
|
+
startTime: number;
|
|
107
|
+
endTime: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Chapter marker returned by SquisqRenderAPI.getChapters() */
|
|
111
|
+
export interface RenderChapterInfo {
|
|
112
|
+
title: string;
|
|
113
|
+
startTime: number;
|
|
114
|
+
duration: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* API surface exposed on `window` in render mode and debug mode.
|
|
119
|
+
* Used by Playwright for video export and by ?debug=true for testing.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* // In Playwright:
|
|
124
|
+
* const w = window as unknown as SquisqWindow;
|
|
125
|
+
* await w.seekTo!(5.0);
|
|
126
|
+
* const blocks = w.getBlocks!();
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export interface SquisqRenderAPI {
|
|
130
|
+
seekTo: (time: number) => Promise<void>;
|
|
131
|
+
getDuration: () => number;
|
|
132
|
+
getBlocks: () => RenderBlockInfo[];
|
|
133
|
+
getAudioSegments: () => RenderAudioSegmentInfo[];
|
|
134
|
+
getCaptions: () => RenderCaptionInfo[];
|
|
135
|
+
getChapters: () => RenderChapterInfo[];
|
|
136
|
+
showCover: () => Promise<void>;
|
|
137
|
+
hideCover: () => Promise<void>;
|
|
138
|
+
hasCoverBlock: () => boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Window augmented with optional SquisqRenderAPI properties.
|
|
143
|
+
* Each property is optional because they're only present in render/debug mode.
|
|
144
|
+
*/
|
|
145
|
+
export type SquisqWindow = Window & typeof globalThis & Partial<SquisqRenderAPI>;
|
|
146
|
+
|
|
147
|
+
/** Format time in seconds to MM:SS string */
|
|
148
|
+
export function formatTime(seconds: number): string {
|
|
149
|
+
const mins = Math.floor(seconds / 60);
|
|
150
|
+
const secs = Math.floor(seconds % 60);
|
|
151
|
+
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
152
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation Utilities (React)
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the shared animation utilities for use in the React package.
|
|
5
|
+
* All animation logic is in shared/doc/utils.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
getAnimationStyle,
|
|
10
|
+
getDefaultAnimationDuration,
|
|
11
|
+
getTransitionClass,
|
|
12
|
+
getAnimationProgress,
|
|
13
|
+
} from '@bendyline/squisq/doc';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for layer components.
|
|
3
|
+
*
|
|
4
|
+
* Resolves position values and anchor offsets used by all layer types.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a position value (number or percentage string) to pixels.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveValue(value: number | string, dimension: number): number {
|
|
11
|
+
if (typeof value === 'number') {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
if (value.endsWith('%')) {
|
|
15
|
+
const percent = parseFloat(value);
|
|
16
|
+
return (percent / 100) * dimension;
|
|
17
|
+
}
|
|
18
|
+
return parseFloat(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get offset based on anchor point.
|
|
23
|
+
*/
|
|
24
|
+
export function getAnchorOffset(
|
|
25
|
+
anchor: string | undefined,
|
|
26
|
+
width: number,
|
|
27
|
+
height: number,
|
|
28
|
+
): { x: number; y: number } {
|
|
29
|
+
switch (anchor) {
|
|
30
|
+
case 'center':
|
|
31
|
+
return { x: -width / 2, y: -height / 2 };
|
|
32
|
+
case 'top-right':
|
|
33
|
+
return { x: -width, y: 0 };
|
|
34
|
+
case 'bottom-left':
|
|
35
|
+
return { x: 0, y: -height };
|
|
36
|
+
case 'bottom-right':
|
|
37
|
+
return { x: -width, y: -height };
|
|
38
|
+
case 'top-left':
|
|
39
|
+
default:
|
|
40
|
+
return { x: 0, y: 0 };
|
|
41
|
+
}
|
|
42
|
+
}
|