slidev-theme-practicum 0.1.0 → 0.1.3

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/README.md +72 -10
  3. package/components/Slide.vue +81 -26
  4. package/components/Slot.vue +20 -85
  5. package/components/Text.vue +68 -2
  6. package/components/TextFitGroup.vue +71 -0
  7. package/components/Timeline.vue +40 -37
  8. package/composables/deck-slot-markup.cjs +249 -0
  9. package/composables/decor-catalog.mjs +169 -47
  10. package/composables/layout-authoring.ts +6 -2
  11. package/composables/layout-markdown.mjs +33 -0
  12. package/composables/layout-recipes.ts +51 -5
  13. package/composables/layout-shorthands.ts +251 -81
  14. package/composables/markdown-to-vnodes.cjs +1 -0
  15. package/composables/markdown-to-vnodes.mjs +237 -0
  16. package/composables/slot-placement.ts +1 -1
  17. package/composables/slot-spacing.ts +66 -0
  18. package/composables/text-fit-runtime.ts +458 -76
  19. package/composables/text-fit-scope.ts +13 -0
  20. package/composables/text-slot-markdown.mjs +193 -0
  21. package/composables/text-typography.mjs +2 -0
  22. package/composables/theme-assets.mjs +60 -0
  23. package/composables/timeline-grid.ts +31 -0
  24. package/composables/typescript-require.cjs +29 -0
  25. package/composables/validate-deck-layouts.cjs +277 -0
  26. package/decor-library.md +2 -0
  27. package/example.md +132 -10
  28. package/package.json +25 -11
  29. package/public/photos/photo-1.webp +0 -0
  30. package/public/photos/photo-10.webp +0 -0
  31. package/public/photos/photo-11.webp +0 -0
  32. package/public/photos/photo-12.webp +0 -0
  33. package/public/photos/photo-13.webp +0 -0
  34. package/public/photos/photo-14.webp +0 -0
  35. package/public/photos/photo-15.webp +0 -0
  36. package/public/photos/photo-16.webp +0 -0
  37. package/public/photos/photo-17.webp +0 -0
  38. package/public/photos/photo-18.webp +0 -0
  39. package/public/photos/photo-19.webp +0 -0
  40. package/public/photos/photo-2.webp +0 -0
  41. package/public/photos/photo-20.webp +0 -0
  42. package/public/photos/photo-21.webp +0 -0
  43. package/public/photos/photo-22.webp +0 -0
  44. package/public/photos/photo-23.webp +0 -0
  45. package/public/photos/photo-24.webp +0 -0
  46. package/public/photos/photo-25.webp +0 -0
  47. package/public/photos/photo-26.webp +0 -0
  48. package/public/photos/photo-27.webp +0 -0
  49. package/public/photos/photo-28.webp +0 -0
  50. package/public/photos/photo-29.webp +0 -0
  51. package/public/photos/photo-3.webp +0 -0
  52. package/public/photos/photo-30.webp +0 -0
  53. package/public/photos/photo-4.webp +0 -0
  54. package/public/photos/photo-5.webp +0 -0
  55. package/public/photos/photo-6.webp +0 -0
  56. package/public/photos/photo-7.webp +0 -0
  57. package/public/photos/photo-8.webp +0 -0
  58. package/public/photos/photo-9.webp +0 -0
  59. package/scripts/browser-smoke-runtime.mjs +428 -0
  60. package/scripts/browser-smoke.mjs +436 -0
  61. package/scripts/build-artifact-assets.mjs +55 -0
  62. package/scripts/check-build-artifact.mjs +243 -0
  63. package/scripts/check-package.mjs +132 -0
  64. package/scripts/check-test-architecture.mjs +176 -0
  65. package/scripts/npm-spawn.mjs +31 -0
  66. package/scripts/test-architecture-policy.mjs +245 -0
  67. package/scripts/theme-asset-inventory.mjs +83 -0
  68. package/scripts/validate-deck.cjs +18 -0
  69. package/setup/decor-save-middleware.ts +244 -0
  70. package/setup/vite-plugins.ts +14 -84
  71. package/skills/slidev-practicum/SKILL.md +25 -0
  72. package/skills/slidev-practicum/agents/openai.yaml +4 -0
  73. package/styles/index.css +10 -1
  74. package/composables/layout-registry.ts +0 -47
  75. package/env.d.ts +0 -6
  76. package/public/photos/photo-1.png +0 -0
  77. package/public/photos/photo-10.png +0 -0
  78. package/public/photos/photo-2.png +0 -0
  79. package/public/photos/photo-3.png +0 -0
  80. package/public/photos/photo-4.png +0 -0
  81. package/public/photos/photo-5.png +0 -0
  82. package/public/photos/photo-6.png +0 -0
  83. package/public/photos/photo-7.png +0 -0
  84. package/public/photos/photo-8.png +0 -0
  85. package/public/photos/photo-9.png +0 -0
  86. package/slidev-theme-practicum.png +0 -0
  87. package/slidev-theme-practicum.svg +0 -102
@@ -1,3 +1,5 @@
1
+ import { themeDecorPath, themePhotoPath } from './theme-assets.mjs'
2
+
1
3
  const PHOTO_DECOR_SIZES = Object.freeze([
2
4
  { cols: [4, 6], rows: [4, 6], ratio: [0.85, 1.2] },
3
5
  { cols: [5, 12], rows: [3, 8], ratio: [1.15, 4] },
@@ -12,7 +14,7 @@ const PHOTO_DECOR_SIZES = Object.freeze([
12
14
  function photoDecor(number, meanings, tones, image = {}) {
13
15
  return {
14
16
  id: `decor-photo-${String(number).padStart(2, '0')}`,
15
- src: `/photos/photo-${number}.png`,
17
+ src: themePhotoPath(number),
16
18
  sizes: PHOTO_DECOR_SIZES,
17
19
  meanings: ['photo', ...meanings],
18
20
  tones,
@@ -28,7 +30,7 @@ function photoDecor(number, meanings, tones, image = {}) {
28
30
  export const DEFAULT_DECOR_DATA = Object.freeze([
29
31
  {
30
32
  id: 'decor-cover-band-01',
31
- src: '/decor/decor-1.svg',
33
+ src: themeDecorPath('decor-1.svg'),
32
34
  sizes: [{ cols: [3, 8], rows: [2, 5], ratio: [1, 1.5] }],
33
35
  meanings: ['cover', 'hero', 'course'],
34
36
  tones: ['green', 'orange', 'blue'],
@@ -40,7 +42,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
40
42
  },
41
43
  {
42
44
  id: 'decor-support-raster-01',
43
- src: '/decor/decor-2.png',
45
+ src: themeDecorPath('decor-2.png'),
44
46
  sizes: [{ cols: [2, 6], rows: [2, 6], ratio: [0.75, 1.2] }],
45
47
  meanings: ['support', 'media'],
46
48
  tones: ['orange', 'light', 'warm'],
@@ -53,7 +55,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
53
55
  },
54
56
  {
55
57
  id: 'decor-square-orbit-01',
56
- src: '/decor/decor-3.svg',
58
+ src: themeDecorPath('decor-3.svg'),
57
59
  sizes: [{ cols: [3, 5], rows: [3, 5], ratio: [0.8, 1.25] }],
58
60
  meanings: ['qa', 'testing', 'practice'],
59
61
  tones: ['blue', 'orange', 'green'],
@@ -65,7 +67,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
65
67
  },
66
68
  {
67
69
  id: 'decor-support-wide-01',
68
- src: '/decor/decor-4.svg',
70
+ src: themeDecorPath('decor-4.svg'),
69
71
  sizes: [{ cols: [4, 8], rows: [2, 8], ratio: [1, 4] }],
70
72
  meanings: ['support', 'learning'],
71
73
  tones: ['green', 'blue', 'orange'],
@@ -77,7 +79,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
77
79
  },
78
80
  {
79
81
  id: 'decor-support-raster-02',
80
- src: '/decor/decor-5.png',
82
+ src: themeDecorPath('decor-5.png'),
81
83
  sizes: [
82
84
  { cols: 6, rows: 5 },
83
85
  { cols: 6, rows: 6 },
@@ -97,7 +99,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
97
99
  },
98
100
  {
99
101
  id: 'decor-support-raster-03',
100
- src: '/decor/decor-6.png',
102
+ src: themeDecorPath('decor-6.png'),
101
103
  sizes: [{ cols: [3, 5], rows: [3, 5], ratio: [0.8, 1.25] }],
102
104
  meanings: ['support', 'media'],
103
105
  tones: ['orange', 'warm'],
@@ -109,7 +111,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
109
111
  },
110
112
  {
111
113
  id: 'decor-media-square-01',
112
- src: '/decor/decor-7.png',
114
+ src: themeDecorPath('decor-7.png'),
113
115
  sizes: [{ cols: [3, 5], rows: [3, 5], ratio: [0.8, 1.25] }],
114
116
  meanings: ['media', 'practice', 'support'],
115
117
  tones: ['blue', 'orange', 'green'],
@@ -122,7 +124,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
122
124
  },
123
125
  {
124
126
  id: 'decor-cover-vertical-01',
125
- src: '/decor/decor-8.svg',
127
+ src: themeDecorPath('decor-8.svg'),
126
128
  sizes: [{ cols: [2, 4], rows: [3, 8], ratio: [0.25, 0.95] }],
127
129
  meanings: ['cover', 'hero', 'course'],
128
130
  tones: ['orange', 'green', 'blue'],
@@ -134,7 +136,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
134
136
  },
135
137
  {
136
138
  id: 'decor-cover-ribbon-01',
137
- src: '/decor/decor-9.svg',
139
+ src: themeDecorPath('decor-9.svg'),
138
140
  sizes: [{ cols: [4, 8], rows: [2, 4], ratio: [1.4, 4] }],
139
141
  meanings: ['cover', 'hero', 'support'],
140
142
  tones: ['orange', 'blue', 'green'],
@@ -146,7 +148,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
146
148
  },
147
149
  {
148
150
  id: 'decor-rules-wide-01',
149
- src: '/decor/decor-10.svg',
151
+ src: themeDecorPath('decor-10.svg'),
150
152
  sizes: [{ cols: [4, 12], rows: [3, 6], ratio: [1.4, 4] }],
151
153
  meanings: ['rules', 'webinar', 'qa'],
152
154
  tones: ['green', 'dark', 'blue', 'orange'],
@@ -163,7 +165,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
163
165
  },
164
166
  {
165
167
  id: 'decor-agenda-tall-01',
166
- src: '/decor/decor-11.svg',
168
+ src: themeDecorPath('decor-11.svg'),
167
169
  sizes: [{ cols: [3, 4], rows: [8, 12], ratio: [0.25, 0.55] }],
168
170
  meanings: ['agenda', 'toc'],
169
171
  tones: ['blue', 'orange', 'green', 'dark'],
@@ -176,7 +178,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
176
178
  },
177
179
  {
178
180
  id: 'decor-signal-tall-01',
179
- src: '/decor/decor-12.svg',
181
+ src: themeDecorPath('decor-12.svg'),
180
182
  sizes: [{ cols: [2, 4], rows: [4, 12], ratio: [0.2, 0.8] }],
181
183
  meanings: ['signal', 'cover', 'hero'],
182
184
  tones: ['blue', 'orange', 'green'],
@@ -188,7 +190,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
188
190
  },
189
191
  {
190
192
  id: 'decor-support-compact-01',
191
- src: '/decor/decor-13.svg',
193
+ src: themeDecorPath('decor-13.svg'),
192
194
  sizes: [{ cols: [2, 6], rows: [2, 6], ratio: [0.75, 1.35] }],
193
195
  meanings: ['support', 'practice'],
194
196
  tones: ['green', 'blue', 'orange'],
@@ -200,7 +202,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
200
202
  },
201
203
  {
202
204
  id: 'decor-panel-vertical-01',
203
- src: '/decor/decor-14.svg',
205
+ src: themeDecorPath('decor-14.svg'),
204
206
  sizes: [{ cols: [3, 5], rows: [4, 7], ratio: [0.45, 0.95] }],
205
207
  meanings: ['support', 'rules', 'qa'],
206
208
  tones: ['orange', 'blue', 'green'],
@@ -214,7 +216,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
214
216
  },
215
217
  {
216
218
  id: 'decor-support-wide-02',
217
- src: '/decor/decor-15.svg',
219
+ src: themeDecorPath('decor-15.svg'),
218
220
  sizes: [{ cols: [4, 8], rows: [2, 5], ratio: [1.2, 4] }],
219
221
  meanings: ['support', 'learning'],
220
222
  tones: ['orange', 'green', 'blue'],
@@ -226,7 +228,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
226
228
  },
227
229
  {
228
230
  id: 'decor-support-wide-03',
229
- src: '/decor/decor-16.svg',
231
+ src: themeDecorPath('decor-16.svg'),
230
232
  sizes: [{ cols: [5, 9], rows: [2, 4], ratio: [1.4, 4.5] }],
231
233
  meanings: ['support', 'learning'],
232
234
  tones: ['blue', 'green', 'orange'],
@@ -238,7 +240,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
238
240
  },
239
241
  {
240
242
  id: 'decor-media-square-02',
241
- src: '/decor/decor-17.png',
243
+ src: themeDecorPath('decor-17.png'),
242
244
  sizes: [{ cols: [3, 5], rows: [3, 5], ratio: [0.8, 1.25] }],
243
245
  meanings: ['media', 'support'],
244
246
  tones: ['green', 'orange', 'blue'],
@@ -251,7 +253,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
251
253
  },
252
254
  {
253
255
  id: 'decor-media-square-03',
254
- src: '/decor/decor-18.png',
256
+ src: themeDecorPath('decor-18.png'),
255
257
  sizes: [{ cols: [3, 6], rows: [3, 6], ratio: [0.75, 1.35] }],
256
258
  meanings: ['media', 'support'],
257
259
  tones: ['orange', 'blue', 'green'],
@@ -265,7 +267,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
265
267
  },
266
268
  {
267
269
  id: 'decor-support-tall-01',
268
- src: '/decor/decor-19.svg',
270
+ src: themeDecorPath('decor-19.svg'),
269
271
  sizes: [{ cols: [3, 5], rows: [3, 9], ratio: [0.35, 1.1] }],
270
272
  meanings: ['support', 'practice'],
271
273
  tones: ['blue', 'orange', 'green'],
@@ -277,62 +279,182 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
277
279
  photoDecor(
278
280
  1,
279
281
  ['portrait', 'workspace', 'home', 'learning'],
280
- ['blue', 'light', 'neutral', 'yellow'],
282
+ ['orange', 'yellow', 'light', 'neutral', 'warm'],
281
283
  { position: '50% 44%', zoom: 1.06 },
282
284
  ),
283
285
  photoDecor(
284
286
  2,
285
- ['portrait', 'workspace', 'learning'],
286
- ['blue', 'green', 'dark', 'cool'],
287
+ ['portrait', 'workspace', 'outdoor', 'learning'],
288
+ ['orange', 'blue', 'green', 'yellow', 'warm'],
287
289
  { position: '50% 42%', zoom: 1.06 },
288
290
  ),
289
291
  photoDecor(
290
292
  3,
291
- ['portrait', 'home', 'learning'],
292
- ['orange', 'yellow', 'green', 'dark', 'warm'],
293
- { position: '52% 45%', zoom: 1.04 },
293
+ ['portrait', 'workspace', 'office', 'learning'],
294
+ ['orange', 'yellow', 'dark', 'warm', 'neutral'],
295
+ { position: '50% 44%', zoom: 1.04 },
294
296
  ),
295
297
  photoDecor(
296
298
  4,
297
- ['portrait', 'workspace', 'learning'],
298
- ['blue', 'dark', 'neutral'],
299
- { position: '50% 44%', zoom: 1.06 },
299
+ ['portrait', 'transit', 'workspace', 'learning'],
300
+ ['blue', 'green', 'light', 'yellow', 'cool'],
301
+ { position: '42% 42%', zoom: 1.06 },
300
302
  ),
301
303
  photoDecor(
302
304
  5,
303
- ['portrait', 'outdoor', 'learning'],
304
- ['blue', 'green', 'light', 'yellow'],
305
- { position: '48% 44%', zoom: 1.08 },
305
+ ['portrait', 'workshop', 'outdoor', 'learning'],
306
+ ['blue', 'dark', 'cool', 'neutral'],
307
+ { position: '50% 40%', zoom: 1.08 },
306
308
  ),
307
309
  photoDecor(
308
310
  6,
309
- ['portrait', 'home', 'learning'],
310
- ['green', 'light', 'yellow', 'neutral'],
311
- { position: '50% 40%', zoom: 1.04 },
311
+ ['portrait', 'workspace', 'home', 'learning'],
312
+ ['blue', 'dark', 'cool', 'neutral'],
313
+ { position: '50% 44%', zoom: 1.04 },
312
314
  ),
313
315
  photoDecor(
314
316
  7,
315
- ['portrait', 'home', 'learning'],
316
- ['orange', 'blue', 'yellow', 'dark', 'warm'],
317
- { position: '50% 0%', zoom: 1.2 },
317
+ ['portrait', 'workspace', 'outdoor', 'learning'],
318
+ ['green', 'yellow', 'neutral', 'warm'],
319
+ { position: '50% 45%', zoom: 1.04 },
318
320
  ),
319
321
  photoDecor(
320
322
  8,
321
- ['portrait', 'campus', 'learning'],
322
- ['orange', 'yellow', 'green', 'neutral', 'warm'],
323
- { position: '50% 45%', zoom: 1.06 },
323
+ ['workspace', 'programming', 'home', 'learning'],
324
+ ['orange', 'yellow', 'light', 'neutral', 'warm'],
325
+ { position: '50% 44%', zoom: 1.02 },
324
326
  ),
325
327
  photoDecor(
326
328
  9,
327
- ['portrait', 'home', 'learning'],
328
- ['blue', 'light', 'neutral', 'green'],
329
- { position: '50% 45%', zoom: 1.05 },
329
+ ['portrait', 'workspace', 'learning'],
330
+ ['orange', 'yellow', 'light', 'neutral', 'warm'],
331
+ { position: '50% 44%', zoom: 1.05 },
330
332
  ),
331
333
  photoDecor(
332
334
  10,
333
- ['portrait', 'home', 'learning'],
334
- ['orange', 'yellow', 'dark', 'warm'],
335
- { position: '50% 46%', zoom: 1.08 },
335
+ ['portrait', 'outdoor', 'mobile', 'learning'],
336
+ ['orange', 'green', 'yellow', 'warm', 'neutral'],
337
+ { position: '50% 44%', zoom: 1.05 },
338
+ ),
339
+ photoDecor(
340
+ 11,
341
+ ['portrait', 'outdoor', 'mobile', 'learning'],
342
+ ['blue', 'dark', 'cool', 'neutral'],
343
+ { position: '50% 42%', zoom: 1.06 },
344
+ ),
345
+ photoDecor(
346
+ 12,
347
+ ['portrait', 'outdoor', 'mobile', 'learning'],
348
+ ['green', 'blue', 'yellow', 'cool', 'neutral'],
349
+ { position: '50% 44%', zoom: 1.06 },
350
+ ),
351
+ photoDecor(
352
+ 13,
353
+ ['portrait', 'outdoor', 'mobile', 'learning'],
354
+ ['blue', 'light', 'cool', 'neutral'],
355
+ { position: '50% 43%', zoom: 1.06 },
356
+ ),
357
+ photoDecor(
358
+ 14,
359
+ ['portrait', 'outdoor', 'hero'],
360
+ ['blue', 'green', 'light', 'cool', 'neutral'],
361
+ { position: '50% 42%', zoom: 1.05 },
362
+ ),
363
+ photoDecor(
364
+ 15,
365
+ ['portrait', 'outdoor', 'hero'],
366
+ ['orange', 'blue', 'warm', 'light'],
367
+ { position: '50% 47%', zoom: 1.04 },
368
+ ),
369
+ photoDecor(
370
+ 16,
371
+ ['portrait', 'outdoor', 'hero'],
372
+ ['blue', 'dark', 'cool'],
373
+ { position: '50% 45%', zoom: 1.04 },
374
+ ),
375
+ photoDecor(
376
+ 17,
377
+ ['portrait', 'outdoor', 'mobile', 'learning'],
378
+ ['blue', 'green', 'cool', 'neutral'],
379
+ { position: '50% 44%', zoom: 1.04 },
380
+ ),
381
+ photoDecor(
382
+ 18,
383
+ ['portrait', 'outdoor', 'mobile', 'community'],
384
+ ['blue', 'green', 'neutral', 'cool'],
385
+ { position: '50% 45%', zoom: 1.04 },
386
+ ),
387
+ photoDecor(
388
+ 19,
389
+ ['portrait', 'outdoor', 'learning'],
390
+ ['blue', 'light', 'cool', 'neutral'],
391
+ { position: '50% 45%', zoom: 1.06 },
392
+ ),
393
+ photoDecor(
394
+ 20,
395
+ ['group', 'community', 'collaboration'],
396
+ ['orange', 'warm', 'light', 'neutral'],
397
+ { position: '50% 45%', zoom: 1.05 },
398
+ ),
399
+ photoDecor(
400
+ 21,
401
+ ['group', 'workspace', 'collaboration', 'learning'],
402
+ ['orange', 'warm', 'light', 'neutral'],
403
+ { position: '50% 45%', zoom: 1.03 },
404
+ ),
405
+ photoDecor(
406
+ 22,
407
+ ['group', 'community', 'outdoor'],
408
+ ['blue', 'green', 'light', 'cool', 'neutral'],
409
+ { position: '50% 43%', zoom: 1.04 },
410
+ ),
411
+ photoDecor(
412
+ 23,
413
+ ['group', 'community', 'outdoor'],
414
+ ['blue', 'light', 'cool', 'neutral'],
415
+ { position: '50% 46%', zoom: 1.04 },
416
+ ),
417
+ photoDecor(
418
+ 24,
419
+ ['group', 'community', 'collaboration'],
420
+ ['green', 'orange', 'warm', 'light', 'neutral'],
421
+ { position: '50% 45%', zoom: 1.04 },
422
+ ),
423
+ photoDecor(
424
+ 25,
425
+ ['group', 'workshop', 'collaboration', 'learning'],
426
+ ['green', 'orange', 'warm', 'light'],
427
+ { position: '50% 44%', zoom: 1.03 },
428
+ ),
429
+ photoDecor(
430
+ 26,
431
+ ['group', 'community', 'office', 'collaboration'],
432
+ ['blue', 'green', 'light', 'neutral', 'cool'],
433
+ { position: '50% 45%', zoom: 1.04 },
434
+ ),
435
+ photoDecor(
436
+ 27,
437
+ ['group', 'community', 'mobile', 'outdoor'],
438
+ ['blue', 'light', 'cool', 'neutral'],
439
+ { position: '50% 46%', zoom: 1.04 },
440
+ ),
441
+ photoDecor(
442
+ 28,
443
+ ['portrait', 'workspace', 'office', 'learning'],
444
+ ['light', 'neutral', 'orange', 'warm'],
445
+ { position: '50% 45%', zoom: 1.05 },
446
+ ),
447
+ photoDecor(
448
+ 29,
449
+ ['portrait', 'outdoor', 'hero'],
450
+ ['orange', 'blue', 'green', 'warm', 'light'],
451
+ { position: '50% 45%', zoom: 1.06 },
452
+ ),
453
+ photoDecor(
454
+ 30,
455
+ ['portrait', 'outdoor', 'hero'],
456
+ ['blue', 'orange', 'cool', 'neutral'],
457
+ { position: '62% 42%', zoom: 1.08 },
336
458
  ),
337
459
  ])
338
460
 
@@ -10,12 +10,16 @@ import {
10
10
  import {
11
11
  resolveLayoutSpec,
12
12
  resolveLayoutVariant,
13
+ resolveLayoutRecipe,
14
+ resolveLayoutSlotProps,
15
+ THEME_LAYOUT_ROLES,
13
16
  type ThemeLayout,
14
17
  type ThemeLayoutHeader,
15
18
  type ThemeLayoutRole,
19
+ type ThemeLayoutRecipe,
20
+ type ThemeLayoutSpec,
16
21
  type ThemeLayoutSlotSpec,
17
- } from './layout-registry'
18
- import { resolveLayoutRecipe, resolveLayoutSlotProps, THEME_LAYOUT_ROLES, type ThemeLayoutRecipe, type ThemeLayoutSpec } from './layout-recipes'
22
+ } from './layout-recipes'
19
23
  import { isBlankTextVNode, isCommentVNode } from './layout-vnode'
20
24
  import { createSlotRules, type SlotRules } from './slot-rules'
21
25
  import {
@@ -1,3 +1,36 @@
1
+ const INLINE_WHITESPACE = /[ \t\r\n\f\v]+/g
2
+
3
+ /**
4
+ * @param {string} value
5
+ */
6
+ export function normalizeInlineText(value) {
7
+ return value.replace(INLINE_WHITESPACE, ' ').trim()
8
+ }
9
+
10
+ /**
11
+ * @param {string} source
12
+ */
13
+ export function dedentMarkdownSource(source) {
14
+ const lines = String(source ?? '').replace(/\r\n/g, '\n').split('\n')
15
+ const nonEmpty = lines.filter(line => line.trim())
16
+
17
+ if (!nonEmpty.length)
18
+ return ''
19
+
20
+ const minIndent = Math.min(...nonEmpty.map((line) => {
21
+ const match = line.match(/^(\s*)/)
22
+ return match ? match[1].length : 0
23
+ }))
24
+
25
+ if (!minIndent)
26
+ return source.trim()
27
+
28
+ return lines
29
+ .map(line => (line.trim() ? line.slice(minIndent) : line))
30
+ .join('\n')
31
+ .trim()
32
+ }
33
+
1
34
  /**
2
35
  * @param {unknown} input
3
36
  * @returns {{ level: number, text: string } | null}
@@ -167,8 +167,7 @@ export const LAYOUT_RECIPES: ThemeLayoutRecipe[] = [
167
167
  content: 'closing',
168
168
  aliases: [{ variant: 'closing' }],
169
169
  regions: {
170
- primary: { area: '4 / 2 / 10 / 12', centered: true },
171
- support: '12 / 1 / -1 / 4',
170
+ primary: { area: '1 / 2 / -1 / 12', centered: true },
172
171
  },
173
172
  },
174
173
  ]),
@@ -177,7 +176,7 @@ export const LAYOUT_RECIPES: ThemeLayoutRecipe[] = [
177
176
  content: 'title-supports',
178
177
  aliases: [{ variant: 'title-supports' }],
179
178
  regions: {
180
- primary: '2 / 1 / -1 / 7',
179
+ primary: { area: '2 / 1 / -1 / 7', marginRight: 4 },
181
180
  secondary: '2 / 7 / -1 / -1',
182
181
  },
183
182
  },
@@ -224,8 +223,8 @@ export const LAYOUT_RECIPES: ThemeLayoutRecipe[] = [
224
223
  content: 'agenda',
225
224
  aliases: [{ variant: 'agenda' }],
226
225
  regions: {
227
- primary: { area: '1 / 1 / -1 / 9', margin: 4 },
228
- media: '1 / 9 / -1 / -1',
226
+ primary: { area: '1 / 1 / -1 / 10', margin: 4 },
227
+ media: '1 / 10 / -1 / -1',
229
228
  },
230
229
  },
231
230
  {
@@ -312,6 +311,30 @@ export const LAYOUT_RECIPES: ThemeLayoutRecipe[] = [
312
311
  ],
313
312
  },
314
313
  },
314
+ {
315
+ content: 'facts',
316
+ aliases: [{ variant: 'facts-duo' }, { variant: 'facts', arrangement: 'duo' }],
317
+ regions: {
318
+ primary: { area: '2 / 1 / 6 / -1' },
319
+ support: [
320
+ { area: '6 / 1 / -1 / 7', surface: 'light', margin: 3 },
321
+ { area: '6 / 7 / -1 / -1', surface: 'light', margin: 3 },
322
+ ],
323
+ },
324
+ },
325
+ {
326
+ content: 'facts',
327
+ aliases: [{ variant: 'facts-quartet' }, { variant: 'facts', arrangement: 'quartet' }],
328
+ regions: {
329
+ primary: { area: '2 / 1 / 6 / -1' },
330
+ support: [
331
+ { area: '6 / 1 / -1 / 4', surface: 'light', margin: 3 },
332
+ { area: '6 / 4 / -1 / 7', surface: 'light', margin: 3 },
333
+ { area: '6 / 7 / -1 / 10', surface: 'light', margin: 3 },
334
+ { area: '6 / 10 / -1 / -1', surface: 'light', margin: 3 },
335
+ ],
336
+ },
337
+ },
315
338
  {
316
339
  content: 'facts',
317
340
  aliases: [{ variant: 'facts-featured' }, { variant: 'facts', arrangement: 'featured' }],
@@ -345,3 +368,26 @@ export function resolveLayoutRecipe(layout: ThemeLayout, variant: string, arrang
345
368
 
346
369
  return recipe
347
370
  }
371
+
372
+ export function resolveLayoutVariant(layout: ThemeLayout | '', variant: string, arrangement = '') {
373
+ if (!layout)
374
+ return variant.trim()
375
+
376
+ return resolveLayoutRecipe(layout, variant, arrangement).aliases[0].variant
377
+ }
378
+
379
+ export function resolveLayoutSpec(layout: ThemeLayout, variant: string) {
380
+ return normalizeRecipeRegions(resolveLayoutRecipe(layout, variant))
381
+ }
382
+
383
+ export function resolveLayoutSlotSpec(
384
+ layout: ThemeLayout,
385
+ variant: string,
386
+ role: ThemeLayoutRole,
387
+ index: number,
388
+ ) {
389
+ const resolved = resolveLayoutSpec(layout, variant).slots[role]?.[index]
390
+ if (!resolved)
391
+ fail('Slide', `Variant "${layout}:${variant}" не поддерживает ${role}[${index}].`)
392
+ return resolved
393
+ }