@lemonppt/renderer 0.1.8 → 0.2.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 (78) hide show
  1. package/dist/client/client-render.js +136 -0
  2. package/dist/client/editor-script.js +3355 -0
  3. package/dist/client/theme-echarts.js +122 -0
  4. package/dist/client-render.d.ts +39 -0
  5. package/dist/client-render.d.ts.map +1 -0
  6. package/dist/client-render.js +65 -0
  7. package/dist/client-render.js.map +1 -0
  8. package/dist/editor-script.d.ts +1 -1
  9. package/dist/editor-script.d.ts.map +1 -1
  10. package/dist/editor-script.js +2791 -310
  11. package/dist/editor-script.js.map +1 -1
  12. package/dist/export-pdf.d.ts +5 -0
  13. package/dist/export-pdf.d.ts.map +1 -1
  14. package/dist/export-pdf.js +22 -2
  15. package/dist/export-pdf.js.map +1 -1
  16. package/dist/export-pptx.d.ts +1157 -4
  17. package/dist/export-pptx.d.ts.map +1 -1
  18. package/dist/export-pptx.js +2436 -1356
  19. package/dist/export-pptx.js.map +1 -1
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/render-editor-data.d.ts +21 -0
  25. package/dist/render-editor-data.d.ts.map +1 -0
  26. package/dist/render-editor-data.js +1068 -0
  27. package/dist/render-editor-data.js.map +1 -0
  28. package/dist/render.d.ts +0 -2
  29. package/dist/render.d.ts.map +1 -1
  30. package/dist/render.js +946 -160
  31. package/dist/render.js.map +1 -1
  32. package/dist/theme-echarts.d.ts +9 -0
  33. package/dist/theme-echarts.d.ts.map +1 -0
  34. package/dist/theme-echarts.js +14 -0
  35. package/dist/theme-echarts.js.map +1 -0
  36. package/dist/theme01-pptx.d.ts +12 -0
  37. package/dist/theme01-pptx.d.ts.map +1 -0
  38. package/dist/theme01-pptx.js +2954 -0
  39. package/dist/theme01-pptx.js.map +1 -0
  40. package/dist/theme02-pptx.d.ts +5 -0
  41. package/dist/theme02-pptx.d.ts.map +1 -0
  42. package/dist/theme02-pptx.js +1063 -0
  43. package/dist/theme02-pptx.js.map +1 -0
  44. package/dist/theme03-pptx.d.ts +5 -0
  45. package/dist/theme03-pptx.d.ts.map +1 -0
  46. package/dist/theme03-pptx.js +3279 -0
  47. package/dist/theme03-pptx.js.map +1 -0
  48. package/dist/theme04-pptx.d.ts +6 -0
  49. package/dist/theme04-pptx.d.ts.map +1 -0
  50. package/dist/theme04-pptx.js +5496 -0
  51. package/dist/theme04-pptx.js.map +1 -0
  52. package/dist/theme05-pptx.d.ts +5 -0
  53. package/dist/theme05-pptx.d.ts.map +1 -0
  54. package/dist/theme05-pptx.js +3152 -0
  55. package/dist/theme05-pptx.js.map +1 -0
  56. package/dist/theme06-pptx.d.ts +124 -0
  57. package/dist/theme06-pptx.d.ts.map +1 -0
  58. package/dist/theme06-pptx.js +8154 -0
  59. package/dist/theme06-pptx.js.map +1 -0
  60. package/dist/theme07-pptx.d.ts +49 -0
  61. package/dist/theme07-pptx.d.ts.map +1 -0
  62. package/dist/theme07-pptx.js +2579 -0
  63. package/dist/theme07-pptx.js.map +1 -0
  64. package/dist/theme08-pptx.d.ts +19 -0
  65. package/dist/theme08-pptx.d.ts.map +1 -0
  66. package/dist/theme08-pptx.js +1411 -0
  67. package/dist/theme08-pptx.js.map +1 -0
  68. package/dist/theme09-pptx.d.ts +18 -0
  69. package/dist/theme09-pptx.d.ts.map +1 -0
  70. package/dist/theme09-pptx.js +1513 -0
  71. package/dist/theme09-pptx.js.map +1 -0
  72. package/dist/theme10-pptx.d.ts +18 -0
  73. package/dist/theme10-pptx.d.ts.map +1 -0
  74. package/dist/theme10-pptx.js +2889 -0
  75. package/dist/theme10-pptx.js.map +1 -0
  76. package/package.json +8 -6
  77. package/templates/editor.html +580 -0
  78. package/templates/editor.js +222 -0
package/dist/render.js CHANGED
@@ -1,72 +1,56 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { normalizeDeckGoal } from '@lemonppt/core';
3
- import { renderSlide } from '@lemonppt/templates';
3
+ import { renderSlide, generateThemeCssVariablesWithDark, generateTheme02CssVariablesWithSchemes, generateTheme03CssVariablesWithSchemes, generateTheme04CssVariablesWithTonesAndAppearance, generateTheme05CssVariablesWithSchemesAndAppearance, generateTheme06CssVariablesWithSchemesAndAppearance, } from '@lemonppt/templates';
4
4
  import ReactDOMServer from 'react-dom/server';
5
- import { editorScript } from './editor-script.js';
6
5
  export function renderDeck(goal, options = {}) {
7
6
  goal = normalizeDeckGoal(goal);
8
- const { width = 1280, height = 720, editable = false } = options;
7
+ const { width = 1280, height = 720 } = options;
9
8
  const slideCount = goal.slides.length;
10
9
  const slideElements = goal.slides.map((slide, index) => {
11
- const element = renderSlide(slide, { slideIdx: index, editable, theme: goal.theme });
10
+ const element = renderSlide(slide, { slideIdx: index, editable: false, theme: goal.theme });
12
11
  const stateClass = index === 0 ? 'active' : '';
13
- return (_jsx("div", { className: `lp-slide-wrapper ${stateClass}`, "data-slide-index": index, "data-layout": slide.layout, style: {
12
+ return (_jsx("div", { className: `lp-slide-wrapper ${stateClass}`, "data-slide-index": index, "data-layout": slide.layout, "data-lp-transition": String(slide.props.transition || 'none'), style: {
14
13
  width: `${width}px`,
15
14
  height: `${height}px`,
16
15
  boxSizing: 'border-box',
17
16
  }, children: element ?? (_jsxs("div", { className: "lp-error", children: ["\u672A\u627E\u5230\u7248\u5F0F: ", slide.layout] })) }, index));
18
17
  });
19
- const slidesMarkup = ReactDOMServer.renderToStaticMarkup(_jsx("div", { className: "lp-deck", "data-theme": goal.theme, "data-editable": editable ? 'true' : undefined, style: {
18
+ const slidesMarkup = ReactDOMServer.renderToStaticMarkup(_jsx("div", { className: "lp-deck", "data-theme": goal.theme, "data-lp-transition": "none", style: {
20
19
  width: `${width}px`,
21
20
  height: `${height}px`,
22
21
  }, children: slideElements }));
23
- const slideHtmls = goal.slides.map((slide, index) => {
24
- const element = renderSlide(slide, { slideIdx: index, editable, theme: goal.theme });
25
- return ReactDOMServer.renderToStaticMarkup(_jsx("div", { style: {
26
- width: `${width}px`,
27
- height: `${height}px`,
28
- boxSizing: 'border-box',
29
- }, children: element ?? (_jsxs("div", { className: "lp-error", children: ["\u672A\u627E\u5230\u7248\u5F0F: ", slide.layout] })) }));
30
- });
31
22
  const navMarkup = buildNavMarkup(slideCount);
32
- const scriptMarkup = editable ? buildEditorScriptMarkup(goal) : buildScriptMarkup();
33
- const editorBarMarkup = editable ? buildEditorBarMarkup() : '';
34
- const leftPanelMarkup = editable ? buildLeftPanelMarkup(goal, slideHtmls, width, height) : '';
35
- const rightPanelMarkup = editable ? buildRightPanelMarkup() : '';
36
- const bodyClass = editable ? 'lp-editor-body' : '';
37
- const bodyMarkup = editable
38
- ? `<div class="lp-editor-root">
39
- ${editorBarMarkup}
40
- <div class="lp-editor-workspace">
41
- ${leftPanelMarkup}
42
- <main class="lp-editor-stage">
43
- <div class="lp-editor-stage-scaler">
44
- ${slidesMarkup}
45
- <div class="lp-editor-page-counter"><span id="lp-current">1</span> / ${slideCount}</div>
46
- </div>
47
- <div class="lp-editor-zoom-bar">
48
- <button id="lp-zoom-out" title="缩小">-</button>
49
- <input type="range" id="lp-zoom-slider" min="35" max="150" value="100">
50
- <button id="lp-zoom-in" title="放大">+</button>
51
- <button id="lp-zoom-fit" title="适应画布">适应</button>
52
- <span id="lp-zoom-value">100%</span>
53
- </div>
54
- </main>
55
- ${rightPanelMarkup}
56
- </div>
57
- ${scriptMarkup}
58
- </div>`
59
- : `${slidesMarkup}${navMarkup}${scriptMarkup}`;
23
+ const scriptMarkup = buildScriptMarkup();
24
+ const bodyMarkup = `${slidesMarkup}${navMarkup}${scriptMarkup}`;
25
+ const theme = goal.theme || 'theme01';
26
+ const colorScheme = goal.colorScheme || (theme === 'theme02' || theme === 'theme03' ? 'scheme-a' : theme === 'theme04' ? 'green' : theme === 'theme05' ? 'coral' : theme === 'theme06' ? 'volt' : 'light');
27
+ const appearance = goal.appearance || (theme === 'theme03' || theme === 'theme04' || theme === 'theme05' || theme === 'theme06' ? 'dark' : undefined);
28
+ const appearanceAttr = appearance ? ` data-appearance="${appearance}"` : '';
29
+ const themeCssVars = theme === 'theme01'
30
+ ? generateThemeCssVariablesWithDark()
31
+ : theme === 'theme02'
32
+ ? generateTheme02CssVariablesWithSchemes()
33
+ : theme === 'theme03'
34
+ ? generateTheme03CssVariablesWithSchemes()
35
+ : theme === 'theme04'
36
+ ? generateTheme04CssVariablesWithTonesAndAppearance()
37
+ : theme === 'theme05'
38
+ ? generateTheme05CssVariablesWithSchemesAndAppearance()
39
+ : theme === 'theme06'
40
+ ? generateTheme06CssVariablesWithSchemesAndAppearance()
41
+ : '';
60
42
  const html = `<!DOCTYPE html>
61
- <html lang="${goal.language ?? 'zh'}">
43
+ <html lang="${goal.language ?? 'zh'}" data-theme="${colorScheme}"${appearanceAttr}>
62
44
  <head>
63
45
  <meta charset="UTF-8">
64
46
  <meta name="viewport" content="width=${width}, initial-scale=1.0">
65
47
  <title>${escapeHtml(goal.title)}</title>
66
48
  <link rel="stylesheet" href="./assets/fonts/fonts.css">
67
- <link rel="stylesheet" href="./assets/${goal.theme || 'base'}.css">
49
+ <link rel="stylesheet" href="./assets/${theme}.css">
50
+ <script src="./assets/jquery.min.js"></script>
68
51
  <style>
69
52
  * { margin: 0; padding: 0; box-sizing: border-box; }
53
+ ${themeCssVars}
70
54
  html, body {
71
55
  width: 100%;
72
56
  height: 100%;
@@ -110,6 +94,8 @@ ${scriptMarkup}
110
94
  left: auto !important;
111
95
  transform: none !important;
112
96
  width: 100% !important;
97
+ height: 64px !important;
98
+ box-sizing: border-box !important;
113
99
  border-radius: 0 !important;
114
100
  border-bottom: 1px solid #333 !important;
115
101
  background: #1e1e1e !important;
@@ -118,20 +104,149 @@ ${scriptMarkup}
118
104
  display: flex !important;
119
105
  align-items: center !important;
120
106
  gap: 12px !important;
107
+ z-index: 100;
121
108
  }
122
109
  .lp-editor-root .lp-editor-title {
123
110
  margin-right: auto;
124
111
  }
112
+ .lp-editor-root .lp-appearance-switcher {
113
+ display: inline-flex;
114
+ align-items: center;
115
+ background: #2a2a2a;
116
+ border: 1px solid #444;
117
+ border-radius: 999px;
118
+ padding: 3px;
119
+ gap: 3px;
120
+ }
121
+ .lp-editor-root .lp-appearance-btn {
122
+ display: inline-flex;
123
+ align-items: center;
124
+ gap: 5px;
125
+ background: transparent;
126
+ color: #aaa;
127
+ border: none;
128
+ border-radius: 999px;
129
+ padding: 5px 10px;
130
+ font-size: 13px;
131
+ cursor: pointer;
132
+ transition: background 0.15s, color 0.15s;
133
+ }
134
+ .lp-editor-root .lp-appearance-btn:hover {
135
+ color: #e5e5e5;
136
+ }
137
+ .lp-editor-root .lp-appearance-active {
138
+ background: #3a3a3a;
139
+ color: #fff;
140
+ font-weight: 500;
141
+ }
125
142
  .lp-editor-root .lp-editor-btn {
143
+ display: inline-flex;
144
+ align-items: center;
145
+ gap: 6px;
126
146
  background: #2a2a2a;
127
147
  color: #e5e5e5;
128
148
  border: 1px solid #444;
149
+ border-radius: 8px;
150
+ padding: 7px 12px;
151
+ font-size: 13px;
152
+ line-height: 1.4;
153
+ cursor: pointer;
154
+ transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
129
155
  }
130
156
  .lp-editor-root .lp-editor-btn:hover:not(:disabled) {
131
157
  background: #333;
158
+ border-color: #555;
132
159
  }
133
160
  .lp-editor-root .lp-editor-btn:disabled {
134
161
  opacity: 0.5;
162
+ cursor: not-allowed;
163
+ }
164
+ .lp-editor-root .lp-editor-btn-primary {
165
+ background: #34d399 !important;
166
+ border-color: #34d399 !important;
167
+ color: #111 !important;
168
+ font-weight: 600;
169
+ }
170
+ .lp-editor-root .lp-editor-btn-primary:hover:not(:disabled) {
171
+ background: #10b981 !important;
172
+ border-color: #10b981 !important;
173
+ }
174
+ .lp-editor-root .lp-editor-select {
175
+ background: #2a2a2a;
176
+ color: #e5e5e5;
177
+ border: 1px solid #444;
178
+ border-radius: 6px;
179
+ padding: 6px 10px;
180
+ font-size: 13px;
181
+ outline: none;
182
+ max-width: 120px;
183
+ }
184
+ .lp-editor-root .lp-editor-select:focus {
185
+ border-color: #5b8cff;
186
+ }
187
+ .lp-editor-export {
188
+ position: relative;
189
+ }
190
+ .lp-editor-export-toggle {
191
+ display: inline-flex;
192
+ align-items: center;
193
+ gap: 6px;
194
+ }
195
+ .lp-editor-caret {
196
+ font-size: 10px;
197
+ line-height: 1;
198
+ transform: translateY(1px);
199
+ }
200
+ .lp-editor-export-menu {
201
+ position: absolute;
202
+ top: calc(100% + 6px);
203
+ right: 0;
204
+ min-width: 220px;
205
+ background: #2a2a2a;
206
+ border: 1px solid #444;
207
+ border-radius: 8px;
208
+ padding: 6px;
209
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
210
+ z-index: 1000;
211
+ display: flex;
212
+ flex-direction: column;
213
+ gap: 2px;
214
+ }
215
+ .lp-editor-export-menu[hidden] {
216
+ display: none !important;
217
+ }
218
+ .lp-editor-export-item {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 10px;
222
+ width: 100%;
223
+ padding: 8px 10px;
224
+ background: transparent;
225
+ border: none;
226
+ border-radius: 6px;
227
+ color: #e5e5e5;
228
+ font-size: 14px;
229
+ cursor: pointer;
230
+ text-align: left;
231
+ }
232
+ .lp-editor-export-item:hover {
233
+ background: #3a3a3a;
234
+ }
235
+ .lp-editor-export-icon {
236
+ width: 20px;
237
+ text-align: center;
238
+ color: #aaa;
239
+ font-size: 14px;
240
+ }
241
+ .lp-editor-export-label {
242
+ flex: 1;
243
+ }
244
+ .lp-editor-export-tag {
245
+ font-size: 11px;
246
+ color: #999;
247
+ background: #3a3a3a;
248
+ padding: 2px 6px;
249
+ border-radius: 4px;
135
250
  }
136
251
  .lp-editor-workspace {
137
252
  display: flex;
@@ -139,15 +254,16 @@ ${scriptMarkup}
139
254
  min-height: 0;
140
255
  }
141
256
  .lp-editor-left-panel {
142
- width: 240px;
257
+ width: 180px;
143
258
  flex: none;
144
259
  background: #1e1e1e;
145
260
  border-right: 1px solid #333;
146
261
  overflow-y: auto;
147
262
  padding: 12px;
263
+ position: relative;
148
264
  }
149
265
  .lp-editor-right-panel {
150
- width: 280px;
266
+ width: 400px;
151
267
  flex: none;
152
268
  background: #1e1e1e;
153
269
  border-left: 1px solid #333;
@@ -161,6 +277,10 @@ ${scriptMarkup}
161
277
  justify-content: center;
162
278
  overflow: hidden;
163
279
  position: relative;
280
+ background-color: #1a1a1a;
281
+ background-image: radial-gradient(circle, rgba(255,255,255,0.12) 1.5px, transparent 1.5px);
282
+ background-size: 28px 28px;
283
+ background-position: center center;
164
284
  }
165
285
  .lp-editor-stage-scaler {
166
286
  transform-origin: center center;
@@ -187,24 +307,37 @@ ${scriptMarkup}
187
307
  border: 2px solid transparent;
188
308
  border-radius: 6px;
189
309
  background: #2a2a2a;
190
- cursor: pointer;
310
+ cursor: grab;
191
311
  display: flex;
192
312
  flex-direction: column;
193
313
  justify-content: center;
194
314
  padding: 10px;
195
315
  text-align: left;
196
316
  color: #bbb;
197
- transition: border-color 0.15s, background 0.15s;
317
+ transition: border-color 0.15s, background 0.15s, opacity 0.15s;
198
318
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
319
+ position: relative;
320
+ user-select: none;
321
+ -webkit-user-select: none;
322
+ outline: none;
323
+ content-visibility: auto;
324
+ contain-intrinsic-size: auto 180px;
199
325
  }
200
326
  .lp-thumbnail:hover {
201
327
  background: #333;
202
328
  }
329
+ .lp-thumbnail:focus-visible {
330
+ border-color: #5b8cff;
331
+ }
203
332
  .lp-thumbnail.active {
204
333
  border-color: #3b82f6;
205
334
  background: #26354f;
206
335
  color: #fff;
207
336
  }
337
+ .lp-thumbnail.dragging,
338
+ .lp-thumbnail:active {
339
+ cursor: grabbing;
340
+ }
208
341
  .lp-thumbnail-index {
209
342
  font-size: 11px;
210
343
  color: #888;
@@ -225,6 +358,119 @@ ${scriptMarkup}
225
358
  color: #666;
226
359
  margin-top: 4px;
227
360
  }
361
+ .lp-thumbnail-drag-handle {
362
+ position: absolute;
363
+ top: 6px;
364
+ left: 6px;
365
+ width: 20px;
366
+ height: 24px;
367
+ display: flex;
368
+ flex-direction: column;
369
+ align-items: center;
370
+ justify-content: center;
371
+ gap: 2px;
372
+ opacity: 0.45;
373
+ z-index: 4;
374
+ color: #888;
375
+ transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;
376
+ line-height: 1;
377
+ font-size: 10px;
378
+ user-select: none;
379
+ -webkit-user-select: none;
380
+ pointer-events: none;
381
+ border-radius: 4px;
382
+ padding: 2px;
383
+ }
384
+ .lp-thumbnail:hover .lp-thumbnail-drag-handle,
385
+ .lp-thumbnail-drag-handle:hover {
386
+ opacity: 0.9;
387
+ color: #bbb;
388
+ background: rgba(255, 255, 255, 0.08);
389
+ }
390
+ .lp-thumbnail.dragging .lp-thumbnail-drag-handle {
391
+ opacity: 1;
392
+ color: #fff;
393
+ background: rgba(255, 255, 255, 0.15);
394
+ }
395
+ .lp-thumbnail.dragging {
396
+ opacity: 0.45;
397
+ border: 2px dashed #3b82f6;
398
+ background: #1f2937;
399
+ }
400
+ .lp-thumbnail.drag-over {
401
+ border-color: #10b981;
402
+ background: #1f3d32;
403
+ }
404
+ .lp-thumbnail-drop-indicator {
405
+ position: absolute;
406
+ left: 0;
407
+ right: 0;
408
+ height: 3px;
409
+ background: #3b82f6;
410
+ border-radius: 2px;
411
+ pointer-events: none;
412
+ z-index: 10;
413
+ opacity: 0;
414
+ transition: opacity 0.1s ease;
415
+ box-shadow: 0 0 8px rgba(59, 130, 246, 0.6);
416
+ }
417
+ .lp-thumbnail-drop-indicator.visible {
418
+ opacity: 1;
419
+ }
420
+ .lp-editor-thumbnails {
421
+ position: relative;
422
+ }
423
+ .lp-delete-confirm-toast {
424
+ position: fixed;
425
+ z-index: 1000;
426
+ width: 180px;
427
+ background: #262626;
428
+ border: 1px solid #444;
429
+ border-radius: 8px;
430
+ padding: 12px;
431
+ box-shadow: 0 8px 24px rgba(0,0,0,0.35);
432
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
433
+ transition: opacity 0.15s ease, transform 0.15s ease;
434
+ margin: 0;
435
+ }
436
+ .lp-delete-confirm-toast[hidden] {
437
+ display: none;
438
+ }
439
+ .lp-delete-confirm-text {
440
+ font-size: 13px;
441
+ color: #e5e5e5;
442
+ line-height: 1.4;
443
+ margin-bottom: 10px;
444
+ }
445
+ .lp-delete-confirm-actions {
446
+ display: flex;
447
+ gap: 8px;
448
+ justify-content: flex-end;
449
+ }
450
+ .lp-delete-confirm-cancel,
451
+ .lp-delete-confirm-confirm {
452
+ padding: 5px 12px;
453
+ border-radius: 6px;
454
+ font-size: 13px;
455
+ cursor: pointer;
456
+ border: 1px solid #555;
457
+ background: #333;
458
+ color: #e5e5e5;
459
+ transition: background 0.15s, border-color 0.15s;
460
+ }
461
+ .lp-delete-confirm-cancel:hover {
462
+ background: #3a3a3a;
463
+ border-color: #666;
464
+ }
465
+ .lp-delete-confirm-confirm {
466
+ background: #dc2626;
467
+ border-color: #dc2626;
468
+ color: #fff;
469
+ }
470
+ .lp-delete-confirm-confirm:hover {
471
+ background: #b91c1c;
472
+ border-color: #b91c1c;
473
+ }
228
474
  .lp-property-header {
229
475
  font-size: 14px;
230
476
  font-weight: 600;
@@ -266,8 +512,9 @@ ${scriptMarkup}
266
512
  resize: vertical;
267
513
  }
268
514
  .lp-selected {
269
- outline: 2px solid #3b82f6 !important;
515
+ outline: 2px solid var(--lp-accent, #3b82f6) !important;
270
516
  outline-offset: 2px !important;
517
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--lp-accent, #3b82f6) 20%, transparent);
271
518
  }
272
519
  .lp-editor-root .lp-nav,
273
520
  .lp-editor-root .lp-page-counter,
@@ -280,6 +527,8 @@ ${scriptMarkup}
280
527
  padding: 0;
281
528
  justify-content: flex-end;
282
529
  background: var(--lp-bg, #222);
530
+ content-visibility: auto;
531
+ contain-intrinsic-size: auto 120px;
283
532
  }
284
533
  .lp-thumbnail-render {
285
534
  position: absolute;
@@ -332,6 +581,33 @@ ${scriptMarkup}
332
581
  .lp-thumbnail.active .lp-thumbnail-scrim {
333
582
  background: linear-gradient(to bottom, rgba(59,130,246,0.15) 0%, rgba(59,130,246,0.45) 100%);
334
583
  }
584
+ .lp-thumbnail-delete {
585
+ position: absolute;
586
+ top: 4px;
587
+ right: 4px;
588
+ z-index: 3;
589
+ width: 20px;
590
+ height: 20px;
591
+ display: flex;
592
+ align-items: center;
593
+ justify-content: center;
594
+ border-radius: 4px;
595
+ background: rgba(239, 68, 68, 0.85);
596
+ color: #fff;
597
+ font-size: 14px;
598
+ line-height: 1;
599
+ cursor: pointer;
600
+ opacity: 0;
601
+ transition: opacity 0.15s, background 0.15s;
602
+ pointer-events: auto;
603
+ }
604
+ .lp-thumbnail:hover .lp-thumbnail-delete,
605
+ .lp-thumbnail:focus-within .lp-thumbnail-delete {
606
+ opacity: 1;
607
+ }
608
+ .lp-thumbnail-delete:hover {
609
+ background: rgba(220, 38, 38, 1);
610
+ }
335
611
  .lp-editor-zoom-bar {
336
612
  position: absolute;
337
613
  bottom: 16px;
@@ -378,6 +654,15 @@ ${scriptMarkup}
378
654
  text-transform: uppercase;
379
655
  letter-spacing: 0.05em;
380
656
  }
657
+ .lp-property-section-header {
658
+ display: flex;
659
+ align-items: center;
660
+ justify-content: space-between;
661
+ margin-bottom: 10px;
662
+ }
663
+ .lp-property-section-header .lp-property-section-title {
664
+ margin-bottom: 0;
665
+ }
381
666
  .lp-property-help {
382
667
  font-size: 12px;
383
668
  color: #888;
@@ -417,28 +702,30 @@ ${scriptMarkup}
417
702
  cursor: pointer;
418
703
  }
419
704
  .lp-property-toggle-track {
420
- width: 38px;
421
- height: 20px;
705
+ width: 46px;
706
+ height: 24px;
422
707
  background: #444;
423
- border-radius: 10px;
708
+ border-radius: 12px;
424
709
  position: relative;
425
- transition: background 0.2s;
710
+ transition: background 0.25s ease;
711
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.25);
426
712
  }
427
713
  .lp-property-toggle input:checked + .lp-property-toggle-track {
428
- background: #3b82f6;
714
+ background: #34d399;
429
715
  }
430
716
  .lp-property-toggle-thumb {
431
717
  position: absolute;
432
718
  top: 2px;
433
719
  left: 2px;
434
- width: 16px;
435
- height: 16px;
720
+ width: 20px;
721
+ height: 20px;
436
722
  background: #fff;
437
723
  border-radius: 50%;
438
- transition: transform 0.2s;
724
+ transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
725
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
439
726
  }
440
727
  .lp-property-toggle input:checked + .lp-property-toggle-track .lp-property-toggle-thumb {
441
- transform: translateX(18px);
728
+ transform: translateX(22px);
442
729
  }
443
730
  .lp-property-toggle input {
444
731
  position: absolute;
@@ -488,24 +775,193 @@ ${scriptMarkup}
488
775
  .lp-property-range {
489
776
  width: 100%;
490
777
  }
778
+ .lp-property-slider-wrap {
779
+ margin-bottom: 16px;
780
+ }
781
+ .lp-property-slider-label {
782
+ font-size: 12px;
783
+ color: #bbb;
784
+ }
785
+ .lp-property-slider-value {
786
+ font-size: 13px;
787
+ font-weight: 700;
788
+ color: #fff;
789
+ min-width: 26px;
790
+ text-align: center;
791
+ padding: 2px 8px;
792
+ background: linear-gradient(135deg, rgba(91,155,213,0.9), rgba(139,92,246,0.9));
793
+ border-radius: 999px;
794
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
795
+ }
796
+ .lp-property-slider-ruler {
797
+ position: relative;
798
+ padding: 18px 18px 10px;
799
+ background: rgba(255, 255, 255, 0.04);
800
+ border: 1px solid rgba(255, 255, 255, 0.08);
801
+ border-radius: 14px;
802
+ cursor: pointer;
803
+ transition: background 0.2s ease, border-color 0.2s ease;
804
+ }
805
+ .lp-property-slider-ruler:hover {
806
+ background: rgba(255, 255, 255, 0.07);
807
+ border-color: rgba(255, 255, 255, 0.14);
808
+ }
809
+ .lp-property-slider-ruler::before {
810
+ content: '';
811
+ position: absolute;
812
+ top: 22px;
813
+ left: 18px;
814
+ right: 18px;
815
+ height: 2px;
816
+ background: rgba(255, 255, 255, 0.12);
817
+ border-radius: 1px;
818
+ pointer-events: none;
819
+ }
820
+ .lp-property-slider {
821
+ width: 100%;
822
+ -webkit-appearance: none;
823
+ appearance: none;
824
+ height: 8px;
825
+ background: rgba(255, 255, 255, 0.14);
826
+ border-radius: 4px;
827
+ outline: none;
828
+ display: block;
829
+ margin: 0;
830
+ cursor: pointer;
831
+ transition: box-shadow 0.2s ease;
832
+ }
833
+ .lp-property-slider:hover {
834
+ box-shadow: 0 0 0 4px rgba(91, 155, 213, 0.12);
835
+ }
836
+ .lp-property-slider::-webkit-slider-thumb {
837
+ -webkit-appearance: none;
838
+ appearance: none;
839
+ width: 24px;
840
+ height: 24px;
841
+ background: #fff;
842
+ border-radius: 50%;
843
+ cursor: grab;
844
+ border: 4px solid #5b9bd5;
845
+ box-shadow: 0 0 0 4px rgba(91, 155, 213, 0.2), 0 4px 12px rgba(0, 0, 0, 0.35);
846
+ margin-top: -8px;
847
+ transition: transform 0.15s ease, box-shadow 0.2s ease;
848
+ }
849
+ .lp-property-slider::-webkit-slider-thumb:hover {
850
+ transform: scale(1.12);
851
+ box-shadow: 0 0 0 6px rgba(91, 155, 213, 0.28), 0 6px 16px rgba(0, 0, 0, 0.4);
852
+ }
853
+ .lp-property-slider:active::-webkit-slider-thumb {
854
+ cursor: grabbing;
855
+ transform: scale(1.05);
856
+ border-color: #8b5cf6;
857
+ }
858
+ .lp-property-slider::-webkit-slider-runnable-track {
859
+ height: 8px;
860
+ border-radius: 4px;
861
+ }
862
+ .lp-property-slider::-moz-range-thumb {
863
+ width: 24px;
864
+ height: 24px;
865
+ background: #fff;
866
+ border-radius: 50%;
867
+ cursor: grab;
868
+ border: 4px solid #5b9bd5;
869
+ box-shadow: 0 0 0 4px rgba(91, 155, 213, 0.2), 0 4px 12px rgba(0, 0, 0, 0.35);
870
+ transition: transform 0.15s ease, box-shadow 0.2s ease;
871
+ }
872
+ .lp-property-slider::-moz-range-thumb:hover {
873
+ transform: scale(1.12);
874
+ box-shadow: 0 0 0 6px rgba(91, 155, 213, 0.28), 0 6px 16px rgba(0, 0, 0, 0.4);
875
+ }
876
+ .lp-property-slider:active::-moz-range-thumb {
877
+ cursor: grabbing;
878
+ transform: scale(1.05);
879
+ border-color: #8b5cf6;
880
+ }
881
+ .lp-property-slider::-moz-range-track {
882
+ height: 8px;
883
+ border-radius: 4px;
884
+ }
885
+ .lp-property-slider-scale {
886
+ position: relative;
887
+ height: 34px;
888
+ margin-top: 10px;
889
+ margin-left: 0;
890
+ margin-right: 0;
891
+ }
892
+ .lp-property-slider-tick {
893
+ position: absolute;
894
+ top: 0;
895
+ transform: translateX(-50%);
896
+ display: flex;
897
+ flex-direction: column;
898
+ align-items: center;
899
+ gap: 5px;
900
+ cursor: pointer;
901
+ padding: 0 6px;
902
+ transition: transform 0.15s ease;
903
+ min-width: 20px;
904
+ }
905
+ .lp-property-slider-tick:hover {
906
+ transform: translateX(-50%) scale(1.15);
907
+ }
908
+ .lp-property-slider-tick::before {
909
+ content: '';
910
+ width: 2px;
911
+ height: 12px;
912
+ background: rgba(255, 255, 255, 0.65);
913
+ border-radius: 1px;
914
+ transition: background 0.2s ease, height 0.15s ease;
915
+ }
916
+ .lp-property-slider-tick:hover::before {
917
+ background: #fff;
918
+ height: 16px;
919
+ }
920
+ .lp-property-slider-tick-label {
921
+ font-size: 12px;
922
+ font-weight: 700;
923
+ color: rgba(255, 255, 255, 0.85);
924
+ line-height: 1;
925
+ transition: color 0.2s ease;
926
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
927
+ white-space: nowrap;
928
+ }
929
+ .lp-property-slider-tick:hover .lp-property-slider-tick-label {
930
+ color: #fff;
931
+ }
491
932
  .lp-property-array {
492
933
  display: flex;
493
934
  flex-direction: column;
494
- gap: 10px;
935
+ gap: 8px;
495
936
  }
496
937
  .lp-property-array-item {
497
938
  background: #252525;
498
- border: 1px solid #383838;
939
+ border-bottom: 1px solid #383838;
499
940
  border-radius: 8px;
500
- padding: 10px;
941
+ padding: 10px 10px 20px 10px;
501
942
  }
502
- .lp-property-array-header {
943
+ .lp-property-array-item-header {
503
944
  display: flex;
504
945
  align-items: center;
505
946
  justify-content: space-between;
947
+ font-family: var(--lp-font-mono, ui-monospace, monospace);
948
+ font-size: 11px;
949
+ font-weight: 700;
950
+ color: var(--lp-accent, #888);
506
951
  margin-bottom: 8px;
952
+ letter-spacing: 0.04em;
953
+ }
954
+ .lp-property-array-header {
955
+ display: flex;
956
+ align-items: center;
957
+ justify-content: space-between;
507
958
  font-size: 12px;
508
959
  color: #aaa;
960
+ line-height: 1;
961
+ }
962
+ .lp-property-array-header .lp-property-btn-sm {
963
+ width: auto;
964
+ margin-left: auto;
509
965
  }
510
966
  .lp-property-btn {
511
967
  width: 100%;
@@ -520,20 +976,34 @@ ${scriptMarkup}
520
976
  .lp-property-btn:hover {
521
977
  background: #333;
522
978
  }
979
+ .lp-property-btn:disabled {
980
+ opacity: 0.5;
981
+ cursor: not-allowed;
982
+ }
523
983
  .lp-property-btn-primary {
524
984
  background: #3b82f6;
525
985
  border-color: #3b82f6;
526
986
  color: #fff;
527
987
  }
528
- .lp-property-btn-primary:hover {
988
+ .lp-property-btn-primary:hover:not(:disabled) {
529
989
  background: #2563eb;
530
990
  }
531
991
  .lp-property-btn-danger {
532
- border-color: #7f1d1d;
533
- color: #fca5a5;
992
+ border-color: transparent;
993
+ background: transparent;
994
+ color: #ef4444;
995
+ width: 22px;
996
+ height: 22px;
997
+ padding: 0;
998
+ display: inline-flex;
999
+ align-items: center;
1000
+ justify-content: center;
1001
+ border-radius: 4px;
1002
+ font-size: 16px;
1003
+ line-height: 1;
534
1004
  }
535
- .lp-property-btn-danger:hover {
536
- background: #450a0a;
1005
+ .lp-property-btn-danger:hover:not(:disabled) {
1006
+ background: rgba(239, 68, 68, 0.12);
537
1007
  }
538
1008
  .lp-property-btn-sm {
539
1009
  padding: 4px 8px;
@@ -546,6 +1016,61 @@ ${scriptMarkup}
546
1016
  text-align: center;
547
1017
  padding: 20px 0;
548
1018
  }
1019
+ /* 可编辑图片占位区:固定区域、点击直接上传、深色主题可见 */
1020
+ .lp-editable-image-placeholder {
1021
+ width: 100%;
1022
+ height: 100%;
1023
+ min-height: 120px;
1024
+ display: flex;
1025
+ flex-direction: column;
1026
+ align-items: center;
1027
+ justify-content: center;
1028
+ gap: 8px;
1029
+ background: color-mix(in srgb, var(--lp-ink, #111) 6%, transparent);
1030
+ border: 2px dashed color-mix(in srgb, var(--lp-ink, #111) 22%, transparent);
1031
+ border-radius: 12px;
1032
+ color: var(--lp-ink3, #6b7280);
1033
+ cursor: pointer;
1034
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
1035
+ user-select: none;
1036
+ }
1037
+ .lp-editable-image-placeholder:hover {
1038
+ background: color-mix(in srgb, var(--lp-accent, #3b82f6) 10%, transparent);
1039
+ border-color: var(--lp-accent, #3b82f6);
1040
+ color: var(--lp-accent, #3b82f6);
1041
+ }
1042
+ .lp-editable-image-placeholder-icon {
1043
+ width: 36px;
1044
+ height: 36px;
1045
+ display: flex;
1046
+ align-items: center;
1047
+ justify-content: center;
1048
+ border-radius: 50%;
1049
+ background: color-mix(in srgb, var(--lp-ink, #111) 10%, transparent);
1050
+ font-size: 20px;
1051
+ line-height: 1;
1052
+ transition: background 0.15s, transform 0.15s;
1053
+ }
1054
+ .lp-editable-image-placeholder:hover .lp-editable-image-placeholder-icon {
1055
+ background: color-mix(in srgb, var(--lp-accent, #3b82f6) 16%, transparent);
1056
+ transform: scale(1.08);
1057
+ }
1058
+ .lp-editable-image-placeholder-text {
1059
+ font-size: 13px;
1060
+ font-weight: 500;
1061
+ }
1062
+ /* 画布空编辑元素占位提示:新增数组项或清空标题时仍可定位和编辑 */
1063
+ [data-lp-editable="true"]:empty::before {
1064
+ content: '点击编辑';
1065
+ opacity: 0.35;
1066
+ pointer-events: none;
1067
+ }
1068
+ [data-lp-editable="true"]:empty {
1069
+ min-width: 3em;
1070
+ outline: 1px dashed color-mix(in srgb, var(--lp-accent, #3b82f6) 40%, transparent);
1071
+ outline-offset: 2px;
1072
+ border-radius: 2px;
1073
+ }
549
1074
  @media print {
550
1075
  html, body {
551
1076
  display: block;
@@ -594,16 +1119,288 @@ ${scriptMarkup}
594
1119
  break-after: auto;
595
1120
  }
596
1121
  }
1122
+
1123
+ /* ---- 编辑器演示模式 ---- */
1124
+ .lp-editor-presentation-mode .lp-editor-bar,
1125
+ .lp-editor-presentation-mode .lp-editor-left-panel,
1126
+ .lp-editor-presentation-mode .lp-editor-right-panel,
1127
+ .lp-editor-presentation-mode .lp-editor-zoom-bar,
1128
+ .lp-editor-presentation-mode .lp-editor-page-counter {
1129
+ display: none !important;
1130
+ }
1131
+ .lp-editor-presentation-mode .lp-editor-workspace {
1132
+ display: block;
1133
+ }
1134
+ .lp-editor-presentation-mode .lp-editor-stage {
1135
+ position: fixed;
1136
+ inset: 0;
1137
+ z-index: 1000;
1138
+ background: #000;
1139
+ display: flex;
1140
+ align-items: center;
1141
+ justify-content: center;
1142
+ overflow: hidden;
1143
+ }
1144
+ .lp-editor-presentation-mode .lp-editor-stage-scaler {
1145
+ transform: scale(var(--lp-presentation-scale, 1)) !important;
1146
+ width: ${width}px !important;
1147
+ height: ${height}px !important;
1148
+ max-width: none;
1149
+ max-height: none;
1150
+ display: flex;
1151
+ align-items: center;
1152
+ justify-content: center;
1153
+ }
1154
+ .lp-editor-presentation-mode .lp-deck {
1155
+ width: ${width}px !important;
1156
+ height: ${height}px !important;
1157
+ border-radius: 0;
1158
+ }
1159
+ .lp-editor-root:fullscreen {
1160
+ background: #000;
1161
+ }
1162
+ .lp-editor-root:-webkit-full-screen {
1163
+ background: #000;
1164
+ }
1165
+ .lp-editor-presentation-exit {
1166
+ position: fixed;
1167
+ top: 16px;
1168
+ right: 16px;
1169
+ z-index: 1001;
1170
+ background: rgba(255, 255, 255, 0.12);
1171
+ color: #fff;
1172
+ border: 1px solid rgba(255, 255, 255, 0.2);
1173
+ border-radius: 6px;
1174
+ padding: 8px 12px;
1175
+ font-size: 13px;
1176
+ cursor: pointer;
1177
+ opacity: 0;
1178
+ transition: opacity 200ms ease;
1179
+ }
1180
+ .lp-editor-presentation-mode:hover .lp-editor-presentation-exit,
1181
+ .lp-editor-presentation-exit:focus {
1182
+ opacity: 1;
1183
+ }
1184
+
1185
+ /* 添加幻灯片弹窗 */
1186
+ .lp-add-slide-modal {
1187
+ position: fixed;
1188
+ inset: 0;
1189
+ z-index: 2000;
1190
+ display: flex;
1191
+ align-items: center;
1192
+ justify-content: center;
1193
+ }
1194
+ .lp-add-slide-modal[hidden] {
1195
+ display: none;
1196
+ }
1197
+ .lp-add-slide-modal-overlay {
1198
+ position: absolute;
1199
+ inset: 0;
1200
+ background: rgba(0, 0, 0, 0.72);
1201
+ backdrop-filter: blur(4px);
1202
+ }
1203
+ .lp-add-slide-modal-content {
1204
+ position: relative;
1205
+ width: min(1080px, calc(100vw - 48px));
1206
+ max-height: calc(100vh - 64px);
1207
+ background: #1e1e1e;
1208
+ border-radius: 16px;
1209
+ padding: 28px 32px 32px;
1210
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.48);
1211
+ display: flex;
1212
+ flex-direction: column;
1213
+ color: #fff;
1214
+ }
1215
+ .lp-add-slide-modal-close {
1216
+ position: absolute;
1217
+ top: 16px;
1218
+ right: 16px;
1219
+ width: 32px;
1220
+ height: 32px;
1221
+ border: none;
1222
+ background: transparent;
1223
+ color: rgba(255, 255, 255, 0.6);
1224
+ font-size: 26px;
1225
+ line-height: 1;
1226
+ cursor: pointer;
1227
+ border-radius: 6px;
1228
+ display: flex;
1229
+ align-items: center;
1230
+ justify-content: center;
1231
+ transition: background 150ms ease, color 150ms ease;
1232
+ }
1233
+ .lp-add-slide-modal-close:hover {
1234
+ background: rgba(255, 255, 255, 0.1);
1235
+ color: #fff;
1236
+ }
1237
+ .lp-add-slide-modal-title {
1238
+ font-size: 18px;
1239
+ font-weight: 500;
1240
+ margin: 0 0 24px;
1241
+ color: #fff;
1242
+ }
1243
+ .lp-add-slide-grid {
1244
+ display: grid;
1245
+ grid-template-columns: repeat(6, minmax(0, 1fr));
1246
+ gap: 14px;
1247
+ overflow-y: auto;
1248
+ overflow-x: hidden;
1249
+ padding-right: 4px;
1250
+ margin-bottom: 28px;
1251
+ }
1252
+ .lp-add-slide-option {
1253
+ display: flex;
1254
+ flex-direction: column;
1255
+ align-items: center;
1256
+ gap: 10px;
1257
+ padding: 16px 8px;
1258
+ background: #2a2a2a;
1259
+ border: 2px solid transparent;
1260
+ border-radius: 12px;
1261
+ cursor: pointer;
1262
+ transition: background 150ms ease, border-color 150ms ease, transform 120ms ease;
1263
+ }
1264
+ .lp-add-slide-option:hover {
1265
+ background: #333;
1266
+ }
1267
+ .lp-add-slide-option.selected {
1268
+ border-color: #34d399;
1269
+ background: #243a30;
1270
+ }
1271
+ .lp-add-slide-icon {
1272
+ width: 44px;
1273
+ height: 44px;
1274
+ border-radius: 8px;
1275
+ background: #3f3f3f;
1276
+ display: flex;
1277
+ align-items: center;
1278
+ justify-content: center;
1279
+ color: rgba(255, 255, 255, 0.75);
1280
+ transition: background 150ms ease, color 150ms ease;
1281
+ }
1282
+ .lp-add-slide-option:hover .lp-add-slide-icon {
1283
+ background: #4a4a4a;
1284
+ color: #fff;
1285
+ }
1286
+ .lp-add-slide-option.selected .lp-add-slide-icon {
1287
+ background: #34d399;
1288
+ color: #1e1e1e;
1289
+ }
1290
+ .lp-add-slide-icon svg {
1291
+ width: 22px;
1292
+ height: 22px;
1293
+ }
1294
+ .lp-add-slide-label {
1295
+ font-size: 13px;
1296
+ color: rgba(255, 255, 255, 0.85);
1297
+ text-align: center;
1298
+ line-height: 1.25;
1299
+ white-space: nowrap;
1300
+ overflow: hidden;
1301
+ text-overflow: ellipsis;
1302
+ max-width: 100%;
1303
+ }
1304
+ .lp-add-slide-option.selected .lp-add-slide-label {
1305
+ color: #34d399;
1306
+ }
1307
+ .lp-add-slide-modal-btn {
1308
+ align-self: center;
1309
+ padding: 12px 32px;
1310
+ border-radius: 999px;
1311
+ border: none;
1312
+ font-size: 15px;
1313
+ font-weight: 500;
1314
+ cursor: pointer;
1315
+ background: #34d399;
1316
+ color: #111;
1317
+ transition: background 150ms ease, opacity 150ms ease, transform 120ms ease;
1318
+ }
1319
+ .lp-add-slide-modal-btn:hover:not(:disabled) {
1320
+ background: #2cb886;
1321
+ }
1322
+ .lp-add-slide-modal-btn:disabled {
1323
+ background: #4a4a4a;
1324
+ color: rgba(255, 255, 255, 0.4);
1325
+ cursor: not-allowed;
1326
+ }
1327
+ @media (max-width: 860px) {
1328
+ .lp-add-slide-grid {
1329
+ grid-template-columns: repeat(4, 1fr);
1330
+ }
1331
+ }
1332
+ @media (max-width: 560px) {
1333
+ .lp-add-slide-grid {
1334
+ grid-template-columns: repeat(3, 1fr);
1335
+ }
1336
+ .lp-add-slide-modal-content {
1337
+ padding: 24px 20px 28px;
1338
+ }
1339
+ }
1340
+
1341
+ /* jQuery 自定义滚动条样式(基于 WebKit + Firefox 标准属性) */
1342
+ .lp-editor-left-panel.lp-custom-scrollbar::-webkit-scrollbar,
1343
+ .lp-editor-right-panel.lp-custom-scrollbar::-webkit-scrollbar,
1344
+ .lp-add-slide-grid.lp-custom-scrollbar::-webkit-scrollbar {
1345
+ width: 8px;
1346
+ height: 8px;
1347
+ }
1348
+ .lp-editor-left-panel.lp-custom-scrollbar::-webkit-scrollbar-track,
1349
+ .lp-editor-right-panel.lp-custom-scrollbar::-webkit-scrollbar-track,
1350
+ .lp-add-slide-grid.lp-custom-scrollbar::-webkit-scrollbar-track {
1351
+ background: rgba(255, 255, 255, 0.1);
1352
+ border-radius: 4px;
1353
+ }
1354
+ .lp-editor-left-panel.lp-custom-scrollbar::-webkit-scrollbar-thumb,
1355
+ .lp-editor-right-panel.lp-custom-scrollbar::-webkit-scrollbar-thumb,
1356
+ .lp-add-slide-grid.lp-custom-scrollbar::-webkit-scrollbar-thumb {
1357
+ background: rgba(255, 255, 255, 0.35);
1358
+ border-radius: 4px;
1359
+ }
1360
+ .lp-editor-left-panel.lp-custom-scrollbar::-webkit-scrollbar-thumb:hover,
1361
+ .lp-editor-right-panel.lp-custom-scrollbar::-webkit-scrollbar-thumb:hover,
1362
+ .lp-add-slide-grid.lp-custom-scrollbar::-webkit-scrollbar-thumb:hover {
1363
+ background: rgba(255, 255, 255, 0.55);
1364
+ }
1365
+ .lp-editor-left-panel.lp-custom-scrollbar,
1366
+ .lp-editor-right-panel.lp-custom-scrollbar,
1367
+ .lp-add-slide-grid.lp-custom-scrollbar {
1368
+ scrollbar-width: thin;
1369
+ scrollbar-color: rgba(255, 255, 255, 0.35) rgba(255, 255, 255, 0.1);
1370
+ }
1371
+
1372
+ .lp-filmstrip-v1-track.lp-custom-scrollbar::-webkit-scrollbar {
1373
+ width: 8px;
1374
+ height: 8px;
1375
+ }
1376
+ .lp-filmstrip-v1-track.lp-custom-scrollbar::-webkit-scrollbar-track {
1377
+ background: rgba(0, 0, 0, 0.08);
1378
+ border-radius: 4px;
1379
+ }
1380
+ .lp-filmstrip-v1-track.lp-custom-scrollbar::-webkit-scrollbar-thumb {
1381
+ background: rgba(0, 0, 0, 0.35);
1382
+ border-radius: 4px;
1383
+ }
1384
+ .lp-filmstrip-v1-track.lp-custom-scrollbar::-webkit-scrollbar-thumb:hover {
1385
+ background: rgba(0, 0, 0, 0.55);
1386
+ }
1387
+ .lp-filmstrip-v1-track.lp-custom-scrollbar {
1388
+ scrollbar-width: thin;
1389
+ scrollbar-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.08);
1390
+ }
597
1391
  </style>
598
1392
  </head>
599
- <body class="${bodyClass}">
1393
+ <body>
600
1394
  ${bodyMarkup}
601
1395
  </body>
602
1396
  </html>`;
603
- const theme = goal.theme || 'base';
604
1397
  return {
605
1398
  html,
606
- assets: ['./assets/fonts/fonts.css', `./assets/${theme}.css`],
1399
+ assets: [
1400
+ './assets/fonts/fonts.css',
1401
+ `./assets/${theme}.css`,
1402
+ './assets/jquery.min.js',
1403
+ ],
607
1404
  };
608
1405
  }
609
1406
  function buildNavMarkup(slideCount) {
@@ -618,80 +1415,6 @@ function buildNavMarkup(slideCount) {
618
1415
  <div class="lp-page-counter"><span id="lp-current">1</span> / ${slideCount}</div>
619
1416
  <div class="lp-hint">← → 翻页</div>`;
620
1417
  }
621
- function buildEditorBarMarkup() {
622
- return `<div class="lp-editor-bar">
623
- <span class="lp-editor-title">lemonPPT 编辑器</span>
624
- <label class="lp-theme-switcher-label">
625
- 主题
626
- <select id="lp-theme-switcher">
627
- <option value="base">极简白</option>
628
- <option value="dark-tech">深色科技</option>
629
- <option value="warm-business">暖色商务</option>
630
- </select>
631
- </label>
632
- <button class="lp-editor-btn" id="lp-play" title="播放演示">▶ 播放</button>
633
- <button class="lp-editor-btn" id="lp-undo" title="撤销 (Ctrl+Z)">↩ 撤销</button>
634
- <button class="lp-editor-btn" id="lp-redo" title="重做 (Ctrl+Y)">↪ 重做</button>
635
- <button class="lp-editor-btn" id="lp-download-goal" title="下载 goal.json">下载 goal.json</button>
636
- <button class="lp-editor-btn" id="lp-export-pptx" title="导出 PPTX">导出 PPTX</button>
637
- <button class="lp-editor-btn" id="lp-export-pdf" title="导出 PDF">导出 PDF</button>
638
- </div>`;
639
- }
640
- function buildLeftPanelMarkup(goal, slideHtmls, slideWidth, slideHeight) {
641
- const thumbInnerWidth = 216;
642
- const scale = thumbInnerWidth / slideWidth;
643
- const buttons = goal.slides.map((slide, index) => {
644
- const label = getSlideLabel(slide);
645
- const activeClass = index === 0 ? 'active' : '';
646
- const slideHtml = slideHtmls[index] || '';
647
- return `<button class="lp-thumbnail ${activeClass}" data-index="${index}" type="button">
648
- <div class="lp-thumbnail-render">
649
- <div class="lp-thumbnail-scaler" style="width:${slideWidth}px;height:${slideHeight}px;transform:scale(${scale});transform-origin:top left;">
650
- ${slideHtml}
651
- </div>
652
- </div>
653
- <div class="lp-thumbnail-scrim"></div>
654
- <div class="lp-thumbnail-content">
655
- <div class="lp-thumbnail-index">${index + 1} / ${goal.slides.length}</div>
656
- <div class="lp-thumbnail-title">${escapeHtml(label)}</div>
657
- <div class="lp-thumbnail-layout">${escapeHtml(slide.layout)}</div>
658
- </div>
659
- </button>`;
660
- }).join('');
661
- return `<aside class="lp-editor-left-panel" aria-label="幻灯片缩略图">
662
- <div class="lp-editor-thumbnails">
663
- ${buttons}
664
- </div>
665
- </aside>`;
666
- }
667
- function getSlideLabel(slide) {
668
- const props = slide.props;
669
- if (!props)
670
- return slide.layout;
671
- const candidates = [
672
- props.title,
673
- props.name,
674
- props.kicker,
675
- props.quote,
676
- props.heading,
677
- (props.items && Array.isArray(props.items) ? props.items[0] : undefined),
678
- (props.features && Array.isArray(props.features) ? props.features[0]?.title : undefined),
679
- (props.members && Array.isArray(props.members) ? props.members[0]?.name : undefined),
680
- ];
681
- for (const value of candidates) {
682
- if (value != null && String(value).trim())
683
- return String(value).trim();
684
- }
685
- return slide.layout;
686
- }
687
- function buildRightPanelMarkup() {
688
- return `<aside class="lp-editor-right-panel" aria-label="属性面板">
689
- <div class="lp-property-header">属性</div>
690
- <div id="lp-property-content">
691
- <div class="lp-property-empty">点击幻灯片中的文本或图片以编辑属性</div>
692
- </div>
693
- </aside>`;
694
- }
695
1418
  function buildScriptMarkup() {
696
1419
  return `<script>
697
1420
  (function () {
@@ -700,26 +1423,86 @@ function buildScriptMarkup() {
700
1423
  const prevBtn = document.getElementById('lp-prev');
701
1424
  const nextBtn = document.getElementById('lp-next');
702
1425
  const currentLabel = document.getElementById('lp-current');
1426
+ const deck = document.querySelector('.lp-deck');
703
1427
  let current = 0;
1428
+ let isTransitioning = false;
1429
+ let cleanupTimer = null;
1430
+
1431
+ function updateUI() {
1432
+ dots.forEach((dot, index) => {
1433
+ dot.classList.toggle('active', index === current);
1434
+ });
1435
+ if (currentLabel) currentLabel.textContent = String(current + 1);
1436
+ if (prevBtn) prevBtn.disabled = current === 0 || isTransitioning;
1437
+ if (nextBtn) nextBtn.disabled = current === slides.length - 1 || isTransitioning;
1438
+ }
1439
+
1440
+ function resetTransitionState() {
1441
+ slides.forEach((slide) => slide.classList.remove('enter', 'leave'));
1442
+ if (deck) {
1443
+ deck.setAttribute('data-lp-transition', 'none');
1444
+ deck.removeAttribute('data-lp-direction');
1445
+ }
1446
+ isTransitioning = false;
1447
+ updateClasses();
1448
+ }
704
1449
 
705
1450
  function updateClasses() {
706
1451
  slides.forEach((slide, index) => {
707
- slide.classList.remove('active', 'prev');
1452
+ slide.classList.remove('active', 'prev', 'enter', 'leave');
708
1453
  if (index === current) slide.classList.add('active');
709
1454
  else if (index < current) slide.classList.add('prev');
710
1455
  });
711
- dots.forEach((dot, index) => {
712
- dot.classList.toggle('active', index === current);
713
- });
714
- if (currentLabel) currentLabel.textContent = String(current + 1);
715
- if (prevBtn) prevBtn.disabled = current === 0;
716
- if (nextBtn) nextBtn.disabled = current === slides.length - 1;
1456
+ updateUI();
717
1457
  }
718
1458
 
719
1459
  function goTo(index) {
720
- if (index < 0 || index >= slides.length) return;
1460
+ if (index === current || index < 0 || index >= slides.length || isTransitioning) return;
1461
+ const leaving = slides[current];
1462
+ const entering = slides[index];
1463
+ const transition = entering.dataset.lpTransition || 'none';
1464
+ const direction = index > current ? 'next' : 'prev';
1465
+
1466
+ if (transition === 'none') {
1467
+ current = index;
1468
+ if (deck) {
1469
+ deck.setAttribute('data-lp-transition', 'none');
1470
+ deck.removeAttribute('data-lp-direction');
1471
+ }
1472
+ leaving.classList.remove('active');
1473
+ entering.classList.add('active');
1474
+ updateClasses();
1475
+ return;
1476
+ }
1477
+
1478
+ isTransitioning = true;
721
1479
  current = index;
722
- updateClasses();
1480
+ updateUI();
1481
+
1482
+ if (deck) {
1483
+ deck.setAttribute('data-lp-transition', transition);
1484
+ deck.setAttribute('data-lp-direction', direction);
1485
+ }
1486
+
1487
+ leaving.classList.add('leave');
1488
+ entering.classList.add('enter');
1489
+ // 先禁用 transition,让 leave/enter 的初始状态直接落位,
1490
+ // 避免从上一状态的 translateX 插值;随后恢复 transition 再切换 active。
1491
+ leaving.style.transition = 'none';
1492
+ entering.style.transition = 'none';
1493
+ void entering.offsetWidth;
1494
+ leaving.style.transition = '';
1495
+ entering.style.transition = '';
1496
+ leaving.classList.remove('active');
1497
+ entering.classList.add('active');
1498
+
1499
+ function finish() {
1500
+ entering.removeEventListener('transitionend', finish);
1501
+ clearTimeout(cleanupTimer);
1502
+ resetTransitionState();
1503
+ }
1504
+ entering.addEventListener('transitionend', finish);
1505
+ cleanupTimer = setTimeout(finish, 600);
723
1506
  }
724
1507
 
725
1508
  if (prevBtn) prevBtn.addEventListener('click', () => goTo(current - 1));
@@ -728,6 +1511,7 @@ function buildScriptMarkup() {
728
1511
  dot.addEventListener('click', () => goTo(Number(dot.dataset.index)));
729
1512
  });
730
1513
  document.addEventListener('keydown', (e) => {
1514
+ if (isTransitioning) return;
731
1515
  if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') goTo(current + 1);
732
1516
  if (e.key === 'ArrowLeft' || e.key === 'PageUp') goTo(current - 1);
733
1517
  if (e.key === 'Home') goTo(0);
@@ -736,13 +1520,15 @@ function buildScriptMarkup() {
736
1520
 
737
1521
  updateClasses();
738
1522
  })();
739
- </script>`;
740
- }
741
- function buildEditorScriptMarkup(goal) {
742
- const goalJson = JSON.stringify(goal, null, 2).replace(/</g, '\\u003c');
743
- return `<script>
744
- window.__lemonPPT_goal = ${goalJson};
745
- ${editorScript}
1523
+ </script>
1524
+ <script src="./assets/client-render.js"></script>
1525
+ <script src="./assets/theme-echarts.js"></script>
1526
+ <script>
1527
+ (function () {
1528
+ if (typeof window.__lemonPPT_initECharts === 'function') {
1529
+ window.__lemonPPT_initECharts();
1530
+ }
1531
+ })();
746
1532
  </script>`;
747
1533
  }
748
1534
  function escapeHtml(text) {