@bendyline/squisq-react 2.0.1 → 2.2.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +20 -0
  3. package/README.md +6 -0
  4. package/dist/index.d.ts +101 -96
  5. package/dist/index.js +1483 -782
  6. package/dist/squisq-player.full.global.js +4165 -0
  7. package/dist/squisq-player.global.js +561 -168
  8. package/dist/standalone-source.d.ts +1 -0
  9. package/dist/standalone-source.js +1 -1
  10. package/dist/styles/index.css +64 -0
  11. package/package.json +8 -4
  12. package/dist/index.js.map +0 -1
  13. package/dist/squisq-player.css +0 -2
  14. package/dist/squisq-player.css.map +0 -1
  15. package/dist/squisq-player.global.js.map +0 -1
  16. package/src/BlockRenderer.tsx +0 -194
  17. package/src/CaptionOverlay.tsx +0 -111
  18. package/src/DocControlsBottom.tsx +0 -109
  19. package/src/DocControlsOverlay.tsx +0 -190
  20. package/src/DocControlsSidebar.tsx +0 -113
  21. package/src/DocControlsSlideshow.tsx +0 -360
  22. package/src/DocPlayer.tsx +0 -1528
  23. package/src/DocPlayerWithSidebar.tsx +0 -155
  24. package/src/DocProgressBar.tsx +0 -257
  25. package/src/InlineAudioPlayer.tsx +0 -46
  26. package/src/InlineVideoPlayer.tsx +0 -70
  27. package/src/LinearDocView.tsx +0 -569
  28. package/src/MarkdownRenderer.tsx +0 -624
  29. package/src/MediaClipLayer.tsx +0 -145
  30. package/src/SocialCaptionOverlay.tsx +0 -255
  31. package/src/__tests__/BlockRenderer.test.tsx +0 -235
  32. package/src/__tests__/DocControlsSlideshow.test.tsx +0 -221
  33. package/src/__tests__/DocPlayer.test.tsx +0 -635
  34. package/src/__tests__/DocPlayerStylesSentinel.test.tsx +0 -41
  35. package/src/__tests__/DocProgressBar.test.tsx +0 -102
  36. package/src/__tests__/JsonView.test.tsx +0 -111
  37. package/src/__tests__/LinearDocView.test.tsx +0 -426
  38. package/src/__tests__/MapLayer.test.tsx +0 -63
  39. package/src/__tests__/MarkdownRenderer.test.tsx +0 -357
  40. package/src/__tests__/MediaClipLayer.test.tsx +0 -70
  41. package/src/__tests__/MediaContext.test.tsx +0 -51
  42. package/src/__tests__/PathLayer.test.tsx +0 -84
  43. package/src/__tests__/TableLayer.test.tsx +0 -142
  44. package/src/__tests__/VideoLayer.test.tsx +0 -94
  45. package/src/__tests__/exports.test.ts +0 -55
  46. package/src/__tests__/fillStyle.test.tsx +0 -160
  47. package/src/__tests__/standaloneEntry.test.tsx +0 -103
  48. package/src/__tests__/transitionStyles.test.ts +0 -125
  49. package/src/__tests__/useAudioSync.test.ts +0 -49
  50. package/src/__tests__/useDocPlayback.transition.test.ts +0 -113
  51. package/src/__tests__/useJsonViewTokens.test.ts +0 -41
  52. package/src/__tests__/useSlideSwipe.test.ts +0 -81
  53. package/src/__tests__/useViewportOrientation.test.ts +0 -22
  54. package/src/hooks/AudioController.ts +0 -114
  55. package/src/hooks/MediaContext.tsx +0 -97
  56. package/src/hooks/index.ts +0 -11
  57. package/src/hooks/useAudioSync.ts +0 -456
  58. package/src/hooks/useAutoSurface.ts +0 -33
  59. package/src/hooks/useDocPlayback.ts +0 -265
  60. package/src/hooks/useMediaSchedule.ts +0 -39
  61. package/src/hooks/useSlideSwipe.ts +0 -265
  62. package/src/hooks/useViewportOrientation.ts +0 -115
  63. package/src/index.ts +0 -70
  64. package/src/jsonView/JsonView.tsx +0 -51
  65. package/src/jsonView/RenderNode.tsx +0 -51
  66. package/src/jsonView/index.ts +0 -2
  67. package/src/jsonView/json-view.css +0 -206
  68. package/src/jsonView/useJsonViewTokens.ts +0 -32
  69. package/src/jsonView/viewers.tsx +0 -343
  70. package/src/layers/ImageLayer.tsx +0 -297
  71. package/src/layers/MapLayer.tsx +0 -185
  72. package/src/layers/PathLayer.tsx +0 -155
  73. package/src/layers/ShapeLayer.tsx +0 -158
  74. package/src/layers/TableLayer.tsx +0 -129
  75. package/src/layers/TextLayer.tsx +0 -571
  76. package/src/layers/TreeLayer.tsx +0 -167
  77. package/src/layers/VideoLayer.tsx +0 -171
  78. package/src/layers/index.ts +0 -6
  79. package/src/standalone-entry.tsx +0 -337
  80. package/src/standalone-source.d.ts +0 -10
  81. package/src/styles/doc-animations.css +0 -2431
  82. package/src/styles/index.css +0 -7
  83. package/src/types.ts +0 -183
  84. package/src/utils/animationUtils.ts +0 -13
  85. package/src/utils/fillStyle.tsx +0 -148
  86. package/src/utils/layerUtils.ts +0 -42
  87. package/src/utils/mapTileUtils.ts +0 -375
@@ -1,569 +0,0 @@
1
- /**
2
- * LinearDocView Component
3
- *
4
- * Renders a Doc as a long-scrolling document view. Each block is displayed
5
- * as a readable section: non-annotated blocks render their markdown content
6
- * as HTML, while template-annotated blocks render as inline SVG visual cards
7
- * via BlockRenderer.
8
- *
9
- * This is the view used when `displayMode === 'linear'` in DocPlayer.
10
- *
11
- * Layout:
12
- * - Scrollable container with max-width for readability
13
- * - Headings from the block hierarchy rendered as HTML headings
14
- * - Body content rendered via MarkdownRenderer
15
- * - Template-annotated sections show an SVG card (BlockRenderer)
16
- * using `materializeBlockLayers()` for on-demand layer computation
17
- * - Blocks are rendered recursively to preserve the heading hierarchy
18
- */
19
-
20
- import { useEffect, useMemo, useRef } from 'react';
21
- import { useAutoSurface } from './hooks/useAutoSurface';
22
- import type { Doc, Block, DocBlock } from '@bendyline/squisq/schemas';
23
- import type { ViewportConfig } from '@bendyline/squisq/schemas';
24
- import {
25
- applySurface,
26
- resolveFontFamily,
27
- type SurfaceScheme,
28
- type Theme,
29
- } from '@bendyline/squisq/schemas';
30
- import { VIEWPORT_PRESETS } from '@bendyline/squisq/schemas';
31
- import {
32
- materializeBlockLayers,
33
- markdownToDoc,
34
- DEFAULT_THEME,
35
- deriveTemplateInputs,
36
- isTemplateBlock,
37
- } from '@bendyline/squisq/doc';
38
- import type { MaterializeBlockLayersOptions } from '@bendyline/squisq/doc';
39
- import { extractPlainText, parseMarkdown } from '@bendyline/squisq/markdown';
40
- import { BlockRenderer } from './BlockRenderer';
41
- import { MarkdownRenderer } from './MarkdownRenderer';
42
-
43
- // ── Props ──────────────────────────────────────────────────────────
44
-
45
- export interface LinearDocViewProps {
46
- /**
47
- * The Doc to render. Wins over `markdown` when both are provided.
48
- * When neither `doc` nor `markdown` is given, an empty container renders.
49
- */
50
- doc?: Doc;
51
- /**
52
- * Markdown source to render. When `doc` is absent, the markdown is parsed
53
- * and converted to a Doc via `markdownToDoc(parseMarkdown(markdown))`.
54
- * Ignored when `doc` is provided.
55
- */
56
- markdown?: string;
57
- /** Base path for resolving media URLs (images, etc.) */
58
- basePath?: string;
59
- /** Viewport config for SVG card rendering (default: landscape) */
60
- viewport?: ViewportConfig;
61
- /** Optional CSS class for the outer container */
62
- className?: string;
63
- /** Theme to use for rendering (default: DEFAULT_THEME from the theme library) */
64
- theme?: Theme;
65
- /** Whether inline visual cards render their layer animations (default: true). */
66
- animationsEnabled?: boolean;
67
- /**
68
- * Optional surface scheme (light / dark paper) overlaid on top of the
69
- * theme's colors. Orthogonal to `theme` — a theme picks editorial
70
- * identity, a surface picks the paper. Pass `'auto'` to follow the
71
- * user's OS `prefers-color-scheme`, a `SurfaceScheme` object to force a
72
- * specific surface, or omit to use the theme's built-in colors.
73
- */
74
- surface?: SurfaceScheme | 'auto';
75
- /**
76
- * Use tight padding + a wider content column. The default layout is
77
- * designed for a reading surface with breathing room (720px column,
78
- * 24×16px padding). Short conversational snippets like chat replies
79
- * benefit from a much tighter layout. Set to `true` to render with
80
- * minimal padding and no max-width cap so the content hugs its
81
- * container.
82
- */
83
- thinMargins?: boolean;
84
- /**
85
- * How images inside the doc should be sized. `'inline'` (default)
86
- * flows them at natural size up to the column width; `'thumbnail'`
87
- * constrains each image to a 100×100 box with aspect-preserving
88
- * containment — use for chat history and other dense surfaces where
89
- * full-size images would dominate the layout.
90
- */
91
- imageDisplayMode?: ImageDisplayMode;
92
- /**
93
- * Let unmodified Up/Down arrows scroll this view even when it does not
94
- * currently hold focus. Intended for a primary document preview.
95
- */
96
- globalKeyboardShortcuts?: boolean;
97
- }
98
-
99
- export type ImageDisplayMode = 'inline' | 'thumbnail';
100
-
101
- // ── Helpers ────────────────────────────────────────────────────────
102
-
103
- /**
104
- * Determine whether a block has a template annotation that should be
105
- * rendered as a visual SVG card. Unknown templates remain annotated so the
106
- * materializer can return a visible fallback and structured diagnostic.
107
- */
108
- function isAnnotatedBlock(block: Block): boolean {
109
- return (
110
- !!block.sourceHeading?.templateAnnotation?.template ||
111
- (!block.sourceHeading && isTemplateBlock(block as DocBlock))
112
- );
113
- }
114
-
115
- function visualTemplateName(block: Block): string | undefined {
116
- return block.sourceHeading?.templateAnnotation?.template ?? block.template;
117
- }
118
-
119
- /**
120
- * Count total blocks in a hierarchy for the materialization context.
121
- */
122
- function countAll(blocks: Block[]): number {
123
- let count = 0;
124
- for (const b of blocks) {
125
- count++;
126
- if (b.children) count += countAll(b.children);
127
- }
128
- return count;
129
- }
130
-
131
- // ── Block Section Renderer ─────────────────────────────────────────
132
-
133
- interface BlockSectionProps {
134
- block: Block;
135
- basePath: string;
136
- viewport: ViewportConfig;
137
- renderContext: MaterializeBlockLayersOptions;
138
- blockIndex: number;
139
- blockIndices: ReadonlyMap<Block, number>;
140
- animationsEnabled: boolean;
141
- }
142
-
143
- /**
144
- * Render a single block section: heading + body content or SVG card.
145
- * Recurses into children to render the full heading tree.
146
- */
147
- function BlockSection({
148
- block,
149
- basePath,
150
- viewport,
151
- renderContext,
152
- blockIndex,
153
- blockIndices,
154
- animationsEnabled,
155
- }: BlockSectionProps) {
156
- const isAnnotated = isAnnotatedBlock(block);
157
-
158
- // For annotated blocks, compute layers and build a Block with them
159
- const visualBlock = useMemo(() => {
160
- if (!isAnnotated) return null;
161
-
162
- const annotation = block.sourceHeading?.templateAnnotation;
163
- const templateName = visualTemplateName(block) ?? 'sectionHeader';
164
-
165
- // Authored Markdown blocks derive their typed template inputs from the
166
- // heading/body. Transform-generated blocks already ARE typed template
167
- // inputs, so materialize them directly instead of looking for authoring
168
- // nodes they intentionally do not carry.
169
- const templateBlock: Record<string, unknown> = annotation
170
- ? (() => {
171
- const headingText = extractPlainText(block.sourceHeading!);
172
- return {
173
- id: block.id,
174
- template: templateName,
175
- startTime: 0,
176
- duration: 1,
177
- audioSegment: 0,
178
- title: headingText,
179
- contents: block.contents,
180
- children: block.children,
181
- ...(deriveTemplateInputs(templateName, headingText, block.contents, {
182
- placeholders: true,
183
- }) ?? {}),
184
- ...annotation.params,
185
- ...block.templateOverrides,
186
- };
187
- })()
188
- : {
189
- ...block,
190
- startTime: block.startTime ?? 0,
191
- duration: block.duration ?? 1,
192
- audioSegment: block.audioSegment ?? 0,
193
- template: templateName,
194
- };
195
-
196
- const ctx: MaterializeBlockLayersOptions = {
197
- ...renderContext,
198
- blockIndex,
199
- };
200
- const { layers } = materializeBlockLayers(templateBlock as unknown as DocBlock, ctx);
201
-
202
- return {
203
- ...block,
204
- layers,
205
- template: templateName,
206
- } as Block;
207
- }, [block, isAnnotated, renderContext, blockIndex]);
208
-
209
- return (
210
- <div
211
- className="squisq-linear-section"
212
- data-block-id={block.id}
213
- data-block-index={blockIndex}
214
- data-template={isAnnotated ? visualTemplateName(block) : undefined}
215
- >
216
- {/* Render the heading (if present — preamble has no sourceHeading) */}
217
- {block.sourceHeading && !isAnnotated && <MarkdownRenderer nodes={[block.sourceHeading]} />}
218
-
219
- {/* Annotated block: render SVG card.
220
- The heading is intentionally *not* duplicated above the card —
221
- every template card renders its own title layer internally, so
222
- a separate `squisq-linear-card-label` only made the heading
223
- appear twice in the linear view. The card also drops its
224
- rounded border + drop shadow so it reads as a continuation of
225
- the surrounding page rather than a chrome'd preview. */}
226
- {isAnnotated && visualBlock && (
227
- <div className="squisq-linear-card">
228
- <div
229
- className="squisq-linear-card-svg"
230
- style={{
231
- width: '100%',
232
- aspectRatio: `${viewport.width} / ${viewport.height}`,
233
- overflow: 'hidden',
234
- marginBottom: '1em',
235
- }}
236
- >
237
- <BlockRenderer
238
- block={visualBlock}
239
- blockTime={0}
240
- basePath={basePath}
241
- viewport={viewport}
242
- animationsEnabled={animationsEnabled}
243
- />
244
- </div>
245
- </div>
246
- )}
247
-
248
- {/* Body content (always render for non-annotated blocks, skipped for annotated) */}
249
- {!isAnnotated && block.contents && block.contents.length > 0 && (
250
- <MarkdownRenderer nodes={block.contents} />
251
- )}
252
-
253
- {/* Recurse into children */}
254
- {block.children && block.children.length > 0 && (
255
- <div className="squisq-linear-children">
256
- {block.children.map((child, i) => (
257
- <BlockSection
258
- key={child.id}
259
- block={child}
260
- basePath={basePath}
261
- viewport={viewport}
262
- renderContext={renderContext}
263
- blockIndex={blockIndices.get(child) ?? blockIndex + i + 1}
264
- blockIndices={blockIndices}
265
- animationsEnabled={animationsEnabled}
266
- />
267
- ))}
268
- </div>
269
- )}
270
- </div>
271
- );
272
- }
273
-
274
- // ── Main Component ─────────────────────────────────────────────────
275
-
276
- /**
277
- * Renders a Doc as a long-scrolling, readable document.
278
- *
279
- * Non-annotated blocks are rendered as HTML text (headings, paragraphs,
280
- * lists, etc.) via MarkdownRenderer. Template-annotated blocks are
281
- * rendered as inline SVG visual cards via BlockRenderer.
282
- *
283
- * @example
284
- * ```tsx
285
- * <LinearDocView doc={doc} basePath="/media/" />
286
- * ```
287
- */
288
- export function LinearDocView({
289
- doc,
290
- markdown,
291
- basePath = '/',
292
- viewport,
293
- className,
294
- theme,
295
- surface,
296
- animationsEnabled = true,
297
- thinMargins = false,
298
- imageDisplayMode = 'inline',
299
- globalKeyboardShortcuts = false,
300
- }: LinearDocViewProps) {
301
- const scrollRef = useRef<HTMLDivElement>(null);
302
- const activeViewport = viewport ?? VIEWPORT_PRESETS.landscape;
303
-
304
- // Parse markdown into a Doc only when no explicit doc is supplied.
305
- const markdownDoc = useMemo(
306
- () => (!doc && markdown !== undefined ? markdownToDoc(parseMarkdown(markdown)) : undefined),
307
- [doc, markdown],
308
- );
309
- const resolvedDoc = doc ?? markdownDoc;
310
-
311
- const totalBlocks = useMemo(
312
- () => (resolvedDoc ? countAll(resolvedDoc.blocks) : 0),
313
- [resolvedDoc],
314
- );
315
- const blockIndices = useMemo(() => {
316
- const indices = new Map<Block, number>();
317
- let index = 0;
318
- const visit = (blocks: Block[]) => {
319
- for (const block of blocks) {
320
- indices.set(block, index++);
321
- if (block.children) visit(block.children);
322
- }
323
- };
324
- if (resolvedDoc) visit(resolvedDoc.blocks);
325
- return indices;
326
- }, [resolvedDoc]);
327
- const autoSurface = useAutoSurface(surface === 'auto');
328
- const resolvedSurface: SurfaceScheme | undefined = surface === 'auto' ? autoSurface : surface;
329
-
330
- const renderContext: MaterializeBlockLayersOptions = useMemo(() => {
331
- const baseTheme = theme ?? DEFAULT_THEME;
332
- const effectiveTheme = resolvedSurface ? applySurface(baseTheme, resolvedSurface) : baseTheme;
333
- return {
334
- theme: effectiveTheme,
335
- viewport: activeViewport,
336
- totalBlocks,
337
- // Theme atmosphere (vignette/grain/gradient persistent layers) shows
338
- // on the inline template cards so they match the player's look.
339
- persistentLayers: effectiveTheme.persistentLayers,
340
- customTemplates: resolvedDoc?.customTemplates,
341
- };
342
- }, [activeViewport, resolvedDoc?.customTemplates, totalBlocks, theme, resolvedSurface]);
343
-
344
- const activeTheme = renderContext.theme!;
345
-
346
- useEffect(() => {
347
- if (!globalKeyboardShortcuts) return;
348
- const handleKeyDown = (event: KeyboardEvent) => {
349
- if (
350
- event.defaultPrevented ||
351
- event.altKey ||
352
- event.ctrlKey ||
353
- event.metaKey ||
354
- event.shiftKey ||
355
- (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')
356
- ) {
357
- return;
358
- }
359
- const target = event.target instanceof Element ? event.target : null;
360
- if (
361
- target?.closest(
362
- 'input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role="textbox"], [role="combobox"], [role="listbox"], [role="menu"], [role="dialog"], [aria-modal="true"], .monaco-editor',
363
- )
364
- ) {
365
- return;
366
- }
367
- const scroller = scrollRef.current;
368
- if (!scroller) return;
369
- event.preventDefault();
370
- const distance = Math.max(64, Math.round(scroller.clientHeight * 0.12));
371
- scroller.scrollBy({
372
- top: event.key === 'ArrowDown' ? distance : -distance,
373
- behavior: 'smooth',
374
- });
375
- };
376
- document.addEventListener('keydown', handleKeyDown);
377
- return () => document.removeEventListener('keydown', handleKeyDown);
378
- }, [globalKeyboardShortcuts]);
379
-
380
- // Nothing to render — keep an empty (but classed) container so hosts can
381
- // still target/measure the view.
382
- if (!resolvedDoc) {
383
- return (
384
- <div ref={scrollRef} className={`squisq-linear squisq-linear--empty ${className || ''}`} />
385
- );
386
- }
387
-
388
- const bgColor = activeTheme.colors.background;
389
- const textColor = activeTheme.colors.text;
390
- const mutedColor = activeTheme.colors.textMuted;
391
- const primaryColor = activeTheme.colors.primary;
392
- const bodyFont = resolveFontFamily(activeTheme.typography.bodyFont, 'system-ui, sans-serif');
393
- const titleFont = resolveFontFamily(activeTheme.typography.titleFont, 'Georgia, serif');
394
- const lineHt = activeTheme.typography.lineHeight ?? 1.7;
395
-
396
- return (
397
- <div
398
- ref={scrollRef}
399
- className={`squisq-linear ${className || ''}`}
400
- style={{
401
- width: '100%',
402
- // Thin-margins mode is the "embedded in someone else's container"
403
- // signal (chat bubble, sidebar preview). Fit to content there so
404
- // the host's bubble doesn't render a tall empty box when the doc
405
- // is short. Standalone mode keeps height:100% for full-viewport
406
- // scrolling.
407
- height: thinMargins ? 'auto' : '100%',
408
- overflowY: thinMargins ? 'visible' : 'auto',
409
- overflowX: 'hidden',
410
- background: bgColor,
411
- }}
412
- >
413
- <div
414
- className={`squisq-linear-content squisq-md${thinMargins ? ' squisq-linear-content--thin' : ''}${imageDisplayMode === 'thumbnail' ? ' squisq-linear-content--thumbnail-images' : ''}`}
415
- style={
416
- {
417
- // Thin-margins mode drops the 720px reading column + generous
418
- // page padding (right for standalone docs) in favor of a tight
419
- // layout that hugs its container (right for chat bubbles and
420
- // sidebar previews).
421
- maxWidth: thinMargins ? 'none' : '720px',
422
- margin: thinMargins ? '0' : '0 auto',
423
- padding: thinMargins ? '0' : '24px 16px',
424
- lineHeight: lineHt,
425
- fontSize: '16px',
426
- fontFamily: bodyFont,
427
- color: textColor,
428
- // CSS custom properties for MarkdownRenderer / nested elements
429
- '--squisq-linear-title-font': titleFont,
430
- '--squisq-linear-body-font': bodyFont,
431
- '--squisq-linear-text': textColor,
432
- '--squisq-linear-muted': mutedColor,
433
- '--squisq-linear-primary': primaryColor,
434
- '--squisq-linear-bg': bgColor,
435
- } as React.CSSProperties
436
- }
437
- >
438
- {/* Theme-aware typography and layout for document mode */}
439
- <style>{`
440
- .squisq-linear-content h1,
441
- .squisq-linear-content h2,
442
- .squisq-linear-content h3,
443
- .squisq-linear-content h4,
444
- .squisq-linear-content h5,
445
- .squisq-linear-content h6 {
446
- font-family: var(--squisq-linear-title-font);
447
- color: var(--squisq-linear-text);
448
- margin-top: 1.2em;
449
- margin-bottom: 0.4em;
450
- }
451
- .squisq-linear-content h1 { font-size: 2em; }
452
- .squisq-linear-content h2 { font-size: 1.5em; }
453
- .squisq-linear-content h3 { font-size: 1.25em; }
454
- .squisq-linear-content p {
455
- margin-bottom: 0.75em;
456
- }
457
- .squisq-linear-content p + p {
458
- margin-top: 1.25em;
459
- }
460
- .squisq-linear-content ul,
461
- .squisq-linear-content ol {
462
- padding-left: 2em;
463
- margin-bottom: 0.75em;
464
- }
465
- .squisq-linear-content li {
466
- margin-bottom: 0.3em;
467
- }
468
- .squisq-linear-content a {
469
- /* Blend the theme's primary toward the body text color so
470
- links stay theme-flavored but always have enough contrast
471
- against the background. Some themes (e.g. Gezellig) pick a
472
- mid-tone primary that's nearly invisible on a dark page
473
- without this lift. */
474
- color: color-mix(in srgb, var(--squisq-linear-primary) 65%, var(--squisq-linear-text));
475
- text-decoration: underline;
476
- text-decoration-thickness: 1px;
477
- text-underline-offset: 2px;
478
- }
479
- .squisq-linear-content a:hover {
480
- color: var(--squisq-linear-primary);
481
- }
482
- .squisq-linear-content code {
483
- color: var(--squisq-linear-primary);
484
- font-size: 0.9em;
485
- padding: 0.15em 0.3em;
486
- border-radius: 3px;
487
- background: rgba(128, 128, 128, 0.15);
488
- }
489
- .squisq-linear-content pre {
490
- padding: 1em;
491
- border-radius: 6px;
492
- background: rgba(0, 0, 0, 0.2);
493
- overflow-x: auto;
494
- margin-bottom: 0.75em;
495
- }
496
- .squisq-linear-content pre code {
497
- padding: 0;
498
- background: none;
499
- }
500
- .squisq-linear-content blockquote {
501
- border-left: 3px solid var(--squisq-linear-muted);
502
- color: var(--squisq-linear-muted);
503
- padding-left: 1em;
504
- margin-left: 0;
505
- margin-bottom: 0.75em;
506
- }
507
- .squisq-linear-content hr {
508
- border: none;
509
- border-top: 1px solid var(--squisq-linear-muted);
510
- margin: 1.5em 0;
511
- }
512
- .squisq-linear-content img {
513
- max-width: 100%;
514
- height: auto;
515
- border-radius: 6px;
516
- margin: 0.5em 0;
517
- }
518
- .squisq-linear-content--thumbnail-images img {
519
- max-width: 100px;
520
- max-height: 100px;
521
- width: auto;
522
- height: auto;
523
- object-fit: contain;
524
- display: block;
525
- }
526
- .squisq-linear-content strong {
527
- font-weight: 700;
528
- }
529
- .squisq-linear-content em {
530
- font-style: italic;
531
- }
532
- .squisq-linear-content table {
533
- width: 100%;
534
- border-collapse: collapse;
535
- margin: 1em 0;
536
- font-size: 0.95em;
537
- }
538
- .squisq-linear-content thead th {
539
- background: var(--squisq-linear-primary);
540
- color: var(--squisq-linear-bg);
541
- font-family: var(--squisq-linear-title-font);
542
- font-weight: 600;
543
- padding: 10px 14px;
544
- text-align: left;
545
- }
546
- .squisq-linear-content tbody td {
547
- padding: 8px 14px;
548
- border-bottom: 1px solid color-mix(in srgb, var(--squisq-linear-muted) 30%, transparent);
549
- }
550
- .squisq-linear-content tbody tr:hover {
551
- background: color-mix(in srgb, var(--squisq-linear-primary) 8%, transparent);
552
- }
553
- `}</style>
554
- {resolvedDoc.blocks.map((block, i) => (
555
- <BlockSection
556
- key={block.id}
557
- block={block}
558
- basePath={basePath}
559
- viewport={activeViewport}
560
- renderContext={renderContext}
561
- blockIndex={blockIndices.get(block) ?? i}
562
- blockIndices={blockIndices}
563
- animationsEnabled={animationsEnabled}
564
- />
565
- ))}
566
- </div>
567
- </div>
568
- );
569
- }