@objectifthunes/three-book 0.5.4 → 0.5.6

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 (3) hide show
  1. package/README.md +5 -0
  2. package/demo.css +445 -0
  3. package/package.json +5 -3
package/README.md CHANGED
@@ -149,6 +149,11 @@ Final color = `texture * paperColor`. Keep color white (`new THREE.Color(1, 1, 1
149
149
  | Core | `Book`, `BookContent`, `BookDirection`, `Paper`, `PaperSetup`, `PaperUVMargin` |
150
150
  | Binding | `BookBinding`, `BookBound`, `StapleBookBinding`, `StapleBookBound`, `StapleSetup` |
151
151
  | Content | `IPageContent`, `PageContent`, `SpritePageContent2` |
152
+ | Text | `TextBlock`, `TextBlockOptions` — styled text rendered to 2D canvas. Methods: `draw(ctx)`, `wrapLines(ctx)`, `measureHeight(ctx)`, `hitTest(ctx, px, py)` |
153
+ | Text overlay | `TextOverlayContent`, `TextOverlayContentOptions` — `IPageContent` that composites text blocks on top of a source canvas. Methods: `addText(opts)`, `removeText(text)`, `updateText(index, opts)`, `markDirty()`, `update(root?)`, `syncMaterials(root)`, `dispose()` |
154
+ | Spreads | `SpreadContent`, `SpreadContentOptions` — double-page spread with text. Same methods as `TextOverlayContent` plus `left`/`right` accessors. `getSpreadPairs(pageCount)` returns valid 0-indexed spread start positions |
155
+ | Texture utils | `createPageCanvas(color, label, image, fitMode, fullBleed, pageWidth?, pageHeight?, imageRect?)`, `createPageTexture(…)`, `computeDefaultImageRect(image, canvasWidth, canvasHeight, fitMode, fullBleed)`, `loadImage(blob)` |
156
+ | Texture types | `PX_PER_UNIT` (256) — pixels per world unit. `ImageFitMode` (`'contain' \| 'cover' \| 'fill'`), `ImageRect` (`{ x, y, width, height }`), `LoadedImage` (`{ image, objectUrl }`) |
152
157
  | Auto turn | `AutoTurnDirection`, `AutoTurnMode`, `AutoTurnSettings`, `AutoTurnSetting` |
153
158
  | Low-level | `PaperMeshData`, `PaperPattern`, `PaperNode`, `PaperMeshUtility`, `PaperMaterialData`, `RendererFactory`, `BookRenderer`, `MeshFactory`, `PaperMeshDataPool` |
154
159
 
package/demo.css ADDED
@@ -0,0 +1,445 @@
1
+ /*
2
+ * @objectifthunes/three-book — shared demo stylesheet.
3
+ *
4
+ * Import from any demo:
5
+ * import '@objectifthunes/three-book/demo.css';
6
+ *
7
+ * One canonical set of class names used by all four book-repo demos.
8
+ */
9
+
10
+ /* ── Reset ─────────────────────────────────────────────────────────────────── */
11
+
12
+ *,
13
+ *::before,
14
+ *::after {
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ margin: 0;
20
+ background: #0a0c14;
21
+ color: #ecf2ff;
22
+ font-family: 'Avenir Next', 'Trebuchet MS', 'Segoe UI', sans-serif;
23
+ font-size: 13px;
24
+ -webkit-font-smoothing: antialiased;
25
+ }
26
+
27
+ /* ── Design tokens ─────────────────────────────────────────────────────────── */
28
+
29
+ :root {
30
+ --demo-bg: rgba(8, 10, 18, 0.7);
31
+ --demo-border: rgba(214, 225, 255, 0.2);
32
+ --demo-border-light: rgba(236, 242, 255, 0.15);
33
+ --demo-border-input: rgba(236, 242, 255, 0.22);
34
+ --demo-border-card: rgba(236, 242, 255, 0.12);
35
+ --demo-border-section: rgba(236, 242, 255, 0.18);
36
+
37
+ --demo-text: #ecf2ff;
38
+ --demo-text-label: rgba(236, 242, 255, 0.92);
39
+ --demo-text-section: rgba(236, 242, 255, 0.68);
40
+ --demo-text-muted: rgba(236, 242, 255, 0.55);
41
+ --demo-text-dim: rgba(236, 242, 255, 0.5);
42
+ --demo-text-info: #9aa3bf;
43
+
44
+ --demo-accent: #89d8b0;
45
+ --demo-accent-bg: rgba(137, 216, 176, 0.18);
46
+ --demo-accent-border: rgba(137, 216, 176, 0.35);
47
+ --demo-status: #8cf0bf;
48
+ --demo-danger: #f08080;
49
+
50
+ --demo-surface: rgba(255, 255, 255, 0.04);
51
+ --demo-surface-input: rgba(255, 255, 255, 0.06);
52
+ --demo-surface-btn: rgba(255, 255, 255, 0.08);
53
+
54
+ --demo-shadow: 0 18px 42px rgba(0, 0, 0, 0.32);
55
+ --demo-shadow-sm: 0 8px 24px rgba(0, 0, 0, 0.3);
56
+ --demo-blur: blur(8px);
57
+
58
+ --demo-radius-panel: 12px;
59
+ --demo-radius-card: 8px;
60
+ --demo-radius-btn: 6px;
61
+ --demo-radius-toggle: 10px;
62
+
63
+ --demo-font: 'Avenir Next', 'Trebuchet MS', 'Segoe UI', sans-serif;
64
+ }
65
+
66
+ /* ── Panel ─────────────────────────────────────────────────────────────────── */
67
+
68
+ .demo-panel {
69
+ position: fixed;
70
+ top: 10px;
71
+ bottom: 10px;
72
+ overflow-y: auto;
73
+ user-select: none;
74
+ padding: 14px;
75
+ border-radius: var(--demo-radius-panel);
76
+ color: var(--demo-text);
77
+ font-family: var(--demo-font);
78
+ font-size: 13px;
79
+ background: var(--demo-bg);
80
+ border: 1px solid var(--demo-border);
81
+ box-shadow: var(--demo-shadow);
82
+ backdrop-filter: var(--demo-blur);
83
+ z-index: 10;
84
+ }
85
+
86
+ .demo-panel-header {
87
+ display: flex;
88
+ justify-content: space-between;
89
+ align-items: flex-start;
90
+ margin-bottom: 4px;
91
+ }
92
+
93
+ .demo-panel-title {
94
+ margin: 0;
95
+ font-size: 15px;
96
+ font-weight: 700;
97
+ }
98
+
99
+ .demo-panel-subtitle {
100
+ margin: 3px 0 0;
101
+ color: var(--demo-text-muted);
102
+ font-size: 11px;
103
+ }
104
+
105
+ .demo-status {
106
+ margin-bottom: 8px;
107
+ color: var(--demo-status);
108
+ font-weight: 700;
109
+ font-size: 12px;
110
+ }
111
+
112
+ /* ── Toggle / Close ────────────────────────────────────────────────────────── */
113
+
114
+ .demo-toggle-btn {
115
+ position: fixed;
116
+ top: 14px;
117
+ left: 14px;
118
+ z-index: 10;
119
+ padding: 8px 14px;
120
+ border-radius: var(--demo-radius-toggle);
121
+ border: 1px solid var(--demo-border);
122
+ background: var(--demo-bg);
123
+ backdrop-filter: var(--demo-blur);
124
+ color: var(--demo-text);
125
+ font-family: var(--demo-font);
126
+ font-size: 13px;
127
+ font-weight: 600;
128
+ cursor: pointer;
129
+ box-shadow: var(--demo-shadow-sm);
130
+ }
131
+
132
+ .demo-close-btn {
133
+ padding: 4px 8px;
134
+ border: 1px solid var(--demo-border-light);
135
+ border-radius: var(--demo-radius-btn);
136
+ background: var(--demo-surface);
137
+ color: var(--demo-text-dim);
138
+ font-family: var(--demo-font);
139
+ font-size: 14px;
140
+ cursor: pointer;
141
+ line-height: 1;
142
+ }
143
+
144
+ /* ── Tabs ──────────────────────────────────────────────────────────────────── */
145
+
146
+ .demo-tab-bar {
147
+ display: flex;
148
+ gap: 4px;
149
+ margin-bottom: 10px;
150
+ }
151
+
152
+ .demo-tab {
153
+ flex: 1;
154
+ padding: 6px 0;
155
+ border: 1px solid var(--demo-border-light);
156
+ border-radius: var(--demo-radius-card);
157
+ background: var(--demo-surface);
158
+ color: var(--demo-text-muted);
159
+ font-family: var(--demo-font);
160
+ font-size: 12px;
161
+ font-weight: 600;
162
+ cursor: pointer;
163
+ text-align: center;
164
+ }
165
+
166
+ .demo-tab--active {
167
+ background: var(--demo-accent-bg);
168
+ color: var(--demo-accent);
169
+ border-color: var(--demo-accent-border);
170
+ }
171
+
172
+ /* ── Section title ─────────────────────────────────────────────────────────── */
173
+
174
+ .demo-section-title {
175
+ margin: 10px 0 8px;
176
+ padding-top: 8px;
177
+ border-top: 1px solid var(--demo-border-section);
178
+ font-size: 11px;
179
+ letter-spacing: 0.06em;
180
+ text-transform: uppercase;
181
+ color: var(--demo-text-section);
182
+ font-weight: 700;
183
+ }
184
+
185
+ /* ── Buttons ───────────────────────────────────────────────────────────────── */
186
+
187
+ .demo-btn {
188
+ padding: 3px 8px;
189
+ border-radius: var(--demo-radius-btn);
190
+ border: 1px solid var(--demo-border-input);
191
+ background: var(--demo-surface-btn);
192
+ color: #eef4ff;
193
+ font-family: var(--demo-font);
194
+ font-size: 11px;
195
+ cursor: pointer;
196
+ }
197
+
198
+ .demo-btn--danger {
199
+ color: var(--demo-danger);
200
+ }
201
+
202
+ .demo-btn--active {
203
+ background: var(--demo-accent-bg);
204
+ border-color: var(--demo-accent);
205
+ }
206
+
207
+ .demo-btn--block {
208
+ width: 100%;
209
+ margin-top: 4px;
210
+ }
211
+
212
+ .demo-btn--rebuild {
213
+ width: 100%;
214
+ margin-top: 6px;
215
+ padding: 8px;
216
+ background: linear-gradient(135deg, #28895e 0%, #216e4c 100%);
217
+ color: #f0fffa;
218
+ border: none;
219
+ border-radius: var(--demo-radius-toggle);
220
+ font-size: 13px;
221
+ font-weight: 700;
222
+ cursor: pointer;
223
+ }
224
+
225
+ /* ── Form controls ─────────────────────────────────────────────────────────── */
226
+
227
+ .demo-field {
228
+ display: block;
229
+ margin: 0 0 8px;
230
+ }
231
+
232
+ .demo-field-row {
233
+ display: flex;
234
+ justify-content: space-between;
235
+ margin-bottom: 3px;
236
+ color: var(--demo-text-label);
237
+ }
238
+
239
+ .demo-slider {
240
+ width: 100%;
241
+ accent-color: var(--demo-accent);
242
+ }
243
+
244
+ .demo-color-input {
245
+ width: 100%;
246
+ }
247
+
248
+ .demo-checkbox-field {
249
+ display: flex;
250
+ justify-content: space-between;
251
+ align-items: center;
252
+ margin: 0 0 7px;
253
+ color: var(--demo-text-label);
254
+ cursor: pointer;
255
+ }
256
+
257
+ .demo-checkbox {
258
+ width: 16px;
259
+ height: 16px;
260
+ }
261
+
262
+ .demo-checkbox--sm {
263
+ width: 13px;
264
+ height: 13px;
265
+ }
266
+
267
+ .demo-select {
268
+ width: 100%;
269
+ padding: 6px 8px;
270
+ border-radius: var(--demo-radius-card);
271
+ border: 1px solid var(--demo-border-input);
272
+ background: var(--demo-surface-input);
273
+ color: #eef4ff;
274
+ font-family: var(--demo-font);
275
+ font-size: 13px;
276
+ }
277
+
278
+ .demo-select--mini {
279
+ width: auto;
280
+ padding: 3px 6px;
281
+ border-radius: var(--demo-radius-btn);
282
+ font-size: 11px;
283
+ }
284
+
285
+ .demo-input--page {
286
+ width: 42px;
287
+ padding: 3px 6px;
288
+ border-radius: var(--demo-radius-btn);
289
+ border: 1px solid var(--demo-border-input);
290
+ background: var(--demo-surface-input);
291
+ color: #eef4ff;
292
+ font-size: 11px;
293
+ font-family: var(--demo-font);
294
+ text-align: center;
295
+ }
296
+
297
+ .demo-file-input {
298
+ width: 100%;
299
+ font-size: 11px;
300
+ color: rgba(236, 242, 255, 0.76);
301
+ margin-top: 5px;
302
+ }
303
+
304
+ /* ── Card ──────────────────────────────────────────────────────────────────── */
305
+
306
+ .demo-card {
307
+ margin: 0 0 8px;
308
+ padding: 10px;
309
+ border-radius: var(--demo-radius-card);
310
+ background: var(--demo-surface);
311
+ border: 1px solid var(--demo-border-card);
312
+ }
313
+
314
+ .demo-card-row {
315
+ display: flex;
316
+ gap: 10px;
317
+ align-items: flex-start;
318
+ }
319
+
320
+ .demo-card-body {
321
+ flex: 1;
322
+ min-width: 0;
323
+ }
324
+
325
+ .demo-card-label {
326
+ font-weight: 600;
327
+ font-size: 12px;
328
+ margin-bottom: 6px;
329
+ color: var(--demo-text-label);
330
+ }
331
+
332
+ .demo-card-controls {
333
+ display: flex;
334
+ gap: 4px;
335
+ flex-wrap: wrap;
336
+ align-items: center;
337
+ }
338
+
339
+ /* ── Thumbnail ─────────────────────────────────────────────────────────────── */
340
+
341
+ .demo-thumb {
342
+ width: 64px;
343
+ height: 64px;
344
+ border-radius: var(--demo-radius-btn);
345
+ object-fit: cover;
346
+ background: var(--demo-surface-input);
347
+ border: 1px solid var(--demo-border-light);
348
+ flex-shrink: 0;
349
+ }
350
+
351
+ /* ── Inline label (for bleed checkbox, spread toggle, etc.) ────────────────── */
352
+
353
+ .demo-inline-label {
354
+ display: flex;
355
+ align-items: center;
356
+ gap: 3px;
357
+ font-size: 11px;
358
+ color: rgba(236, 242, 255, 0.78);
359
+ cursor: pointer;
360
+ }
361
+
362
+ /* ── Page navigation ───────────────────────────────────────────────────────── */
363
+
364
+ .demo-page-nav {
365
+ display: flex;
366
+ align-items: center;
367
+ gap: 6px;
368
+ margin-bottom: 8px;
369
+ }
370
+
371
+ .demo-page-count {
372
+ font-size: 12px;
373
+ color: rgba(236, 242, 255, 0.7);
374
+ }
375
+
376
+ .demo-spread-label {
377
+ font-size: 11px;
378
+ color: rgba(137, 216, 176, 0.85);
379
+ }
380
+
381
+ .demo-spread-toggle {
382
+ display: flex;
383
+ align-items: center;
384
+ gap: 6px;
385
+ margin: 0 0 8px;
386
+ font-size: 12px;
387
+ color: var(--demo-text-label);
388
+ cursor: pointer;
389
+ }
390
+
391
+ .demo-spread-checkbox {
392
+ width: 14px;
393
+ height: 14px;
394
+ }
395
+
396
+ /* ── Placement toggle ──────────────────────────────────────────────────────── */
397
+
398
+ .demo-placement-row {
399
+ display: flex;
400
+ gap: 4px;
401
+ margin-bottom: 8px;
402
+ }
403
+
404
+ /* ── Element header (title + remove button) ────────────────────────────────── */
405
+
406
+ .demo-element-header {
407
+ display: flex;
408
+ align-items: center;
409
+ justify-content: space-between;
410
+ margin-bottom: 6px;
411
+ }
412
+
413
+ .demo-element-title {
414
+ font-weight: 600;
415
+ font-size: 12px;
416
+ color: var(--demo-text-label);
417
+ }
418
+
419
+ /* ── Editor canvas ─────────────────────────────────────────────────────────── */
420
+
421
+ .demo-editor-canvas {
422
+ display: block;
423
+ border-radius: var(--demo-radius-card);
424
+ border: 1px solid var(--demo-border-card);
425
+ cursor: default;
426
+ }
427
+
428
+ /* ── Info bar ──────────────────────────────────────────────────────────────── */
429
+
430
+ .demo-info {
431
+ color: var(--demo-text-info);
432
+ font-family: monospace;
433
+ font-size: 12px;
434
+ pointer-events: none;
435
+ }
436
+
437
+ /* ── Tab content visibility helper ─────────────────────────────────────────── */
438
+
439
+ .demo-tab-content {
440
+ display: none;
441
+ }
442
+
443
+ .demo-tab-content--active {
444
+ display: block;
445
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectifthunes/three-book",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -8,10 +8,12 @@
8
8
  ".": {
9
9
  "import": "./dist/index.js",
10
10
  "types": "./dist/index.d.ts"
11
- }
11
+ },
12
+ "./demo.css": "./demo.css"
12
13
  },
13
14
  "files": [
14
- "dist"
15
+ "dist",
16
+ "demo.css"
15
17
  ],
16
18
  "scripts": {
17
19
  "build": "vite build && tsc --emitDeclarationOnly",