@jjlmoya/utils-books 1.6.0 → 1.8.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 (70) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +5 -1
  4. package/src/index.ts +1 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/tool_validation.test.ts +1 -1
  7. package/src/tool/book-interior-margin-and-gutter-planner/bibliography.astro +16 -0
  8. package/src/tool/book-interior-margin-and-gutter-planner/bibliography.ts +12 -0
  9. package/src/tool/book-interior-margin-and-gutter-planner/book-interior-margin-and-gutter-planner.css +462 -0
  10. package/src/tool/book-interior-margin-and-gutter-planner/component.astro +102 -0
  11. package/src/tool/book-interior-margin-and-gutter-planner/controller.ts +141 -0
  12. package/src/tool/book-interior-margin-and-gutter-planner/dom-views.ts +66 -0
  13. package/src/tool/book-interior-margin-and-gutter-planner/entry.ts +27 -0
  14. package/src/tool/book-interior-margin-and-gutter-planner/evaluator.ts +22 -0
  15. package/src/tool/book-interior-margin-and-gutter-planner/i18n/de.ts +59 -0
  16. package/src/tool/book-interior-margin-and-gutter-planner/i18n/en.ts +60 -0
  17. package/src/tool/book-interior-margin-and-gutter-planner/i18n/es.ts +59 -0
  18. package/src/tool/book-interior-margin-and-gutter-planner/i18n/fr.ts +59 -0
  19. package/src/tool/book-interior-margin-and-gutter-planner/i18n/id.ts +59 -0
  20. package/src/tool/book-interior-margin-and-gutter-planner/i18n/it.ts +59 -0
  21. package/src/tool/book-interior-margin-and-gutter-planner/i18n/ja.ts +58 -0
  22. package/src/tool/book-interior-margin-and-gutter-planner/i18n/ko.ts +58 -0
  23. package/src/tool/book-interior-margin-and-gutter-planner/i18n/nl.ts +59 -0
  24. package/src/tool/book-interior-margin-and-gutter-planner/i18n/pl.ts +59 -0
  25. package/src/tool/book-interior-margin-and-gutter-planner/i18n/pt.ts +59 -0
  26. package/src/tool/book-interior-margin-and-gutter-planner/i18n/ru.ts +58 -0
  27. package/src/tool/book-interior-margin-and-gutter-planner/i18n/sv.ts +59 -0
  28. package/src/tool/book-interior-margin-and-gutter-planner/i18n/tr.ts +58 -0
  29. package/src/tool/book-interior-margin-and-gutter-planner/i18n/zh.ts +58 -0
  30. package/src/tool/book-interior-margin-and-gutter-planner/index.ts +11 -0
  31. package/src/tool/book-interior-margin-and-gutter-planner/logic.test.ts +43 -0
  32. package/src/tool/book-interior-margin-and-gutter-planner/logic.ts +85 -0
  33. package/src/tool/book-interior-margin-and-gutter-planner/seo.astro +15 -0
  34. package/src/tool/book-interior-margin-and-gutter-planner/storage.ts +25 -0
  35. package/src/tool/book-interior-margin-and-gutter-planner/ui.ts +42 -0
  36. package/src/tool/book-reading-time-deadline-planner/bibliography.astro +16 -0
  37. package/src/tool/book-reading-time-deadline-planner/bibliography.ts +31 -0
  38. package/src/tool/book-reading-time-deadline-planner/book-reading-time-deadline-planner.css +449 -0
  39. package/src/tool/book-reading-time-deadline-planner/calendar.test.ts +18 -0
  40. package/src/tool/book-reading-time-deadline-planner/calendar.ts +56 -0
  41. package/src/tool/book-reading-time-deadline-planner/component.astro +91 -0
  42. package/src/tool/book-reading-time-deadline-planner/controller.ts +253 -0
  43. package/src/tool/book-reading-time-deadline-planner/dom-views.ts +60 -0
  44. package/src/tool/book-reading-time-deadline-planner/entry.ts +27 -0
  45. package/src/tool/book-reading-time-deadline-planner/evaluator.ts +17 -0
  46. package/src/tool/book-reading-time-deadline-planner/i18n/de.ts +45 -0
  47. package/src/tool/book-reading-time-deadline-planner/i18n/en.ts +51 -0
  48. package/src/tool/book-reading-time-deadline-planner/i18n/es.ts +45 -0
  49. package/src/tool/book-reading-time-deadline-planner/i18n/fr.ts +45 -0
  50. package/src/tool/book-reading-time-deadline-planner/i18n/id.ts +45 -0
  51. package/src/tool/book-reading-time-deadline-planner/i18n/it.ts +45 -0
  52. package/src/tool/book-reading-time-deadline-planner/i18n/ja.ts +45 -0
  53. package/src/tool/book-reading-time-deadline-planner/i18n/ko.ts +45 -0
  54. package/src/tool/book-reading-time-deadline-planner/i18n/nl.ts +45 -0
  55. package/src/tool/book-reading-time-deadline-planner/i18n/pl.ts +45 -0
  56. package/src/tool/book-reading-time-deadline-planner/i18n/pt.ts +45 -0
  57. package/src/tool/book-reading-time-deadline-planner/i18n/ru.ts +45 -0
  58. package/src/tool/book-reading-time-deadline-planner/i18n/sv.ts +45 -0
  59. package/src/tool/book-reading-time-deadline-planner/i18n/tr.ts +45 -0
  60. package/src/tool/book-reading-time-deadline-planner/i18n/zh.ts +45 -0
  61. package/src/tool/book-reading-time-deadline-planner/index.ts +11 -0
  62. package/src/tool/book-reading-time-deadline-planner/logic.test.ts +61 -0
  63. package/src/tool/book-reading-time-deadline-planner/logic.ts +129 -0
  64. package/src/tool/book-reading-time-deadline-planner/seo.astro +15 -0
  65. package/src/tool/book-reading-time-deadline-planner/speed-test.test.ts +15 -0
  66. package/src/tool/book-reading-time-deadline-planner/speed-test.ts +9 -0
  67. package/src/tool/book-reading-time-deadline-planner/storage.ts +24 -0
  68. package/src/tool/book-reading-time-deadline-planner/ui.ts +76 -0
  69. package/src/tool/book-reading-time-deadline-planner/validation.ts +19 -0
  70. package/src/tools.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-books",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,9 +1,10 @@
1
1
  import { bookPaginationAndSpineCalculator } from '../tool/book-pagination-and-spine-calculator/entry';
2
+ import { bookInteriorMarginAndGutterPlanner } from '../tool/book-interior-margin-and-gutter-planner/entry';
2
3
  import type { CategoryLocaleContent, KnownLocale } from '../types';
3
4
 
4
5
  export const booksCategory = {
5
6
  icon: 'mdi:book-open-page-variant-outline',
6
- tools: [bookPaginationAndSpineCalculator],
7
+ tools: [bookPaginationAndSpineCalculator, bookInteriorMarginAndGutterPlanner],
7
8
  i18n: {
8
9
  de: () => import('./i18n/de').then((module) => module.content),
9
10
  en: () => import('./i18n/en').then((module) => module.content),
package/src/entries.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  export { bookPaginationAndSpineCalculator } from './tool/book-pagination-and-spine-calculator/entry';
2
2
  export type { BookPaginationUI, BookPaginationLocaleContent } from './tool/book-pagination-and-spine-calculator/entry';
3
+ export { bookReadingTimeDeadlinePlanner } from './tool/book-reading-time-deadline-planner/entry';
4
+ export type { BookReadingUI, BookReadingLocaleContent } from './tool/book-reading-time-deadline-planner/entry';
3
5
 
4
6
  import { bookPaginationAndSpineCalculator } from './tool/book-pagination-and-spine-calculator/entry';
7
+ import { bookInteriorMarginAndGutterPlanner } from './tool/book-interior-margin-and-gutter-planner/entry';
8
+ import { bookReadingTimeDeadlinePlanner } from './tool/book-reading-time-deadline-planner/entry';
5
9
 
6
- export const ALL_ENTRIES = [bookPaginationAndSpineCalculator];
10
+ export const ALL_ENTRIES = [bookPaginationAndSpineCalculator, bookInteriorMarginAndGutterPlanner, bookReadingTimeDeadlinePlanner];
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export { booksCategory } from './category';
2
2
  export const booksCategorySEO = () => import('./category/BooksCategorySEO.astro').then((module) => module.default);
3
3
 
4
4
  export { bookPaginationAndSpineCalculator, BOOK_PAGINATION_AND_SPINE_CALCULATOR_TOOL } from './tool/book-pagination-and-spine-calculator';
5
+ export { bookInteriorMarginAndGutterPlanner, BOOK_INTERIOR_MARGIN_AND_GUTTER_PLANNER_TOOL } from './tool/book-interior-margin-and-gutter-planner';
5
6
 
6
7
  export type {
7
8
  KnownLocale,
@@ -18,6 +18,6 @@ describe('Locale Completeness Validation', () => {
18
18
  });
19
19
 
20
20
  it('all tools registered', () => {
21
- expect(ALL_TOOLS.length).toBe(1);
21
+ expect(ALL_TOOLS.length).toBe(3);
22
22
  });
23
23
  });
@@ -5,7 +5,7 @@ import { booksCategory } from '../data';
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
7
  it('should have tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(1);
8
+ expect(ALL_TOOLS.length).toBe(3);
9
9
  });
10
10
 
11
11
  it('booksCategory should be defined', () => {
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { bookInteriorMarginAndGutterPlanner } from './index';
4
+ import { bibliography } from './bibliography';
5
+ import type { KnownLocale } from '../../types';
6
+
7
+ interface Props {
8
+ locale?: KnownLocale;
9
+ }
10
+
11
+ const { locale = 'en' } = Astro.props as Props;
12
+ const loader = bookInteriorMarginAndGutterPlanner.i18n[locale] || bookInteriorMarginAndGutterPlanner.i18n.en;
13
+ const content = await loader?.();
14
+ ---
15
+
16
+ <SharedBibliography links={content?.bibliography || bibliography} />
@@ -0,0 +1,12 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Amazon KDP. Set Trim Size, Bleed, and Margins',
6
+ url: 'https://kdp.amazon.com/en_US/help/topic/GVBQ3CMEQW3W2VL6',
7
+ },
8
+ {
9
+ name: 'Lulu. Preguntas frecuentes sobre el formato interior',
10
+ url: 'https://help.lulu.com/es/support/solutions/articles/64000255590-formato-interior-conceptos-b%C3%A1sicos',
11
+ },
12
+ ];
@@ -0,0 +1,462 @@
1
+ .n-tool {
2
+ --n-ink: #25221e;
3
+ --n-muted: #756f66;
4
+ --n-paper: #fffdf8;
5
+ --n-paper-alt: #f5efe3;
6
+ --n-line: #d7cbbb;
7
+ --n-accent: #a34e2b;
8
+ --n-accent-soft: #f4dfd1;
9
+ --n-ok: #37684d;
10
+ --n-warn: #a15b2e;
11
+ --n-bad: #9c3831;
12
+ --n-shadow: rgba(57, 39, 25, 0.12);
13
+
14
+ display: grid;
15
+ grid-template-columns: minmax(280px, 0.82fr) minmax(420px, 1.18fr);
16
+ min-height: 530px;
17
+ overflow: visible;
18
+ color: var(--n-ink);
19
+ background: var(--n-paper);
20
+ border: 1px solid var(--n-line);
21
+ box-shadow: 0 18px 40px var(--n-shadow);
22
+ }
23
+
24
+ .n-specs,
25
+ .n-result {
26
+ padding: 30px;
27
+ }
28
+
29
+ .n-specs {
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: 26px;
33
+ background: var(--n-paper-alt);
34
+ border-right: 1px solid var(--n-line);
35
+ }
36
+
37
+ .n-specs-head,
38
+ .n-scene-label,
39
+ .n-output-row {
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: space-between;
43
+ gap: 16px;
44
+ }
45
+
46
+ .n-eyebrow,
47
+ .n-scene-label,
48
+ .n-page-band {
49
+ color: var(--n-muted);
50
+ font-size: 0.7rem;
51
+ font-weight: 700;
52
+ letter-spacing: 0.12em;
53
+ text-transform: uppercase;
54
+ }
55
+
56
+ .n-unit-toggle {
57
+ display: inline-flex;
58
+ padding: 3px;
59
+ border: 1px solid var(--n-line);
60
+ border-radius: 999px;
61
+ background: var(--n-paper);
62
+ }
63
+
64
+ .n-unit-toggle button,
65
+ .n-select-trigger,
66
+ .n-select-menu button,
67
+ .n-bleed-toggle {
68
+ color: var(--n-ink);
69
+ font: inherit;
70
+ }
71
+
72
+ .n-unit-toggle button {
73
+ padding: 7px 10px;
74
+ border: 0;
75
+ border-radius: 999px;
76
+ background: transparent;
77
+ color: var(--n-muted);
78
+ cursor: pointer;
79
+ font-size: 0.7rem;
80
+ font-weight: 700;
81
+ }
82
+
83
+ .n-unit-toggle button[aria-pressed="true"] {
84
+ background: var(--n-ink);
85
+ color: var(--n-paper);
86
+ }
87
+
88
+ .n-field-grid,
89
+ .n-select-grid {
90
+ display: grid;
91
+ grid-template-columns: repeat(2, minmax(0, 1fr));
92
+ gap: 16px;
93
+ }
94
+
95
+ .n-field-grid .n-field-pages {
96
+ grid-column: 1 / -1;
97
+ }
98
+
99
+ .n-field {
100
+ position: relative;
101
+ display: flex;
102
+ min-width: 0;
103
+ flex-direction: column;
104
+ gap: 8px;
105
+ color: var(--n-muted);
106
+ font-size: 0.76rem;
107
+ font-weight: 700;
108
+ }
109
+
110
+ .n-field input,
111
+ .n-select-trigger {
112
+ width: 100%;
113
+ min-height: 46px;
114
+ padding: 10px 12px;
115
+ border: 1px solid var(--n-line);
116
+ border-radius: 2px;
117
+ background: var(--n-paper);
118
+ color: var(--n-ink);
119
+ font-size: 1rem;
120
+ font-weight: 600;
121
+ }
122
+
123
+ .n-field input:focus-visible,
124
+ .n-select-trigger:focus-visible,
125
+ .n-unit-toggle button:focus-visible,
126
+ .n-bleed-toggle:focus-visible,
127
+ .n-select-menu button:focus-visible {
128
+ outline: 3px solid var(--n-accent-soft);
129
+ outline-offset: 2px;
130
+ }
131
+
132
+ .n-field small {
133
+ color: var(--n-accent);
134
+ font-size: 0.7rem;
135
+ }
136
+
137
+ .n-select {
138
+ z-index: 3;
139
+ }
140
+
141
+ .n-select-trigger {
142
+ position: relative;
143
+ padding-right: 32px;
144
+ cursor: pointer;
145
+ text-align: left;
146
+ }
147
+
148
+ .n-select-trigger::after {
149
+ position: absolute;
150
+ top: 50%;
151
+ right: 12px;
152
+ width: 7px;
153
+ height: 7px;
154
+ border-right: 1.5px solid currentcolor;
155
+ border-bottom: 1.5px solid currentcolor;
156
+ content: '';
157
+ transform: translateY(-70%) rotate(45deg);
158
+ }
159
+
160
+ .n-select[data-open="true"] .n-select-trigger::after {
161
+ transform: translateY(-20%) rotate(225deg);
162
+ }
163
+
164
+ .n-select-menu {
165
+ position: absolute;
166
+ z-index: 5;
167
+ top: calc(100% + 6px);
168
+ right: 0;
169
+ left: 0;
170
+ padding: 5px;
171
+ border: 1px solid var(--n-line);
172
+ background: var(--n-paper);
173
+ box-shadow: 0 12px 25px var(--n-shadow);
174
+ }
175
+
176
+ .n-select-menu[hidden] {
177
+ display: none;
178
+ }
179
+
180
+ .n-select-menu button {
181
+ display: block;
182
+ width: 100%;
183
+ padding: 10px;
184
+ border: 0;
185
+ background: transparent;
186
+ cursor: pointer;
187
+ text-align: left;
188
+ }
189
+
190
+ .n-select-menu button:hover {
191
+ background: var(--n-accent-soft);
192
+ }
193
+
194
+ .n-bleed-toggle {
195
+ display: grid;
196
+ grid-template-columns: 18px 1fr auto;
197
+ align-items: center;
198
+ gap: 10px;
199
+ margin-top: auto;
200
+ padding: 14px 0 0;
201
+ border: 0;
202
+ border-top: 1px solid var(--n-line);
203
+ background: transparent;
204
+ cursor: pointer;
205
+ text-align: left;
206
+ }
207
+
208
+ .n-bleed-mark {
209
+ width: 18px;
210
+ height: 18px;
211
+ border: 1px solid var(--n-line);
212
+ border-radius: 50%;
213
+ background: var(--n-paper);
214
+ }
215
+
216
+ .n-bleed-toggle[aria-checked="true"] .n-bleed-mark {
217
+ border-color: var(--n-accent);
218
+ background: var(--n-accent);
219
+ box-shadow: inset 0 0 0 4px var(--n-paper-alt);
220
+ }
221
+
222
+ .n-bleed-toggle strong {
223
+ color: var(--n-accent);
224
+ font-size: 0.72rem;
225
+ text-transform: uppercase;
226
+ }
227
+
228
+ .n-result {
229
+ display: grid;
230
+ grid-template-rows: auto 1fr;
231
+ gap: 24px;
232
+ }
233
+
234
+ .n-result-copy {
235
+ display: grid;
236
+ grid-template-columns: minmax(190px, 0.85fr) minmax(190px, 1.15fr);
237
+ align-items: start;
238
+ gap: 26px;
239
+ }
240
+
241
+ .n-status {
242
+ display: flex;
243
+ align-items: flex-start;
244
+ gap: 11px;
245
+ min-height: 64px;
246
+ }
247
+
248
+ .n-status-dot {
249
+ flex: 0 0 auto;
250
+ width: 12px;
251
+ height: 12px;
252
+ margin-top: 5px;
253
+ border-radius: 50%;
254
+ background: var(--n-ok);
255
+ box-shadow: 0 0 0 5px rgba(55, 104, 77, 0.12);
256
+ }
257
+
258
+ [data-status="low-pages"] .n-status-dot,
259
+ [data-status="high-pages"] .n-status-dot {
260
+ background: var(--n-warn);
261
+ box-shadow: 0 0 0 5px rgba(161, 91, 46, 0.12);
262
+ }
263
+
264
+ [data-status="invalid"] .n-status-dot {
265
+ background: var(--n-bad);
266
+ box-shadow: 0 0 0 5px rgba(156, 56, 49, 0.12);
267
+ }
268
+
269
+ .n-status strong {
270
+ display: block;
271
+ font-size: 1.05rem;
272
+ }
273
+
274
+ .n-status p {
275
+ max-width: 240px;
276
+ margin: 5px 0 0;
277
+ color: var(--n-muted);
278
+ font-size: 0.78rem;
279
+ line-height: 1.45;
280
+ }
281
+
282
+ .n-output-list {
283
+ display: grid;
284
+ gap: 7px;
285
+ }
286
+
287
+ .n-output-row {
288
+ padding-bottom: 7px;
289
+ border-bottom: 1px solid var(--n-line);
290
+ color: var(--n-muted);
291
+ font-size: 0.75rem;
292
+ }
293
+
294
+ .n-output-row strong {
295
+ color: var(--n-ink);
296
+ font-size: 0.82rem;
297
+ text-align: right;
298
+ }
299
+
300
+ .n-page-band {
301
+ margin: 20px 0 0;
302
+ }
303
+
304
+ .n-page-band span {
305
+ color: var(--n-accent);
306
+ }
307
+
308
+ .n-scene-wrap {
309
+ display: grid;
310
+ grid-template-rows: auto 1fr auto;
311
+ min-height: 300px;
312
+ padding: 17px;
313
+ border: 1px solid var(--n-line);
314
+ background: var(--n-paper-alt);
315
+ }
316
+
317
+ .n-scene-label {
318
+ font-size: 0.65rem;
319
+ }
320
+
321
+ .n-scene {
322
+ min-height: 220px;
323
+ }
324
+
325
+ .n-spread-svg {
326
+ display: block;
327
+ width: 100%;
328
+ height: 100%;
329
+ min-height: 230px;
330
+ }
331
+
332
+ .n-paper {
333
+ fill: var(--n-paper);
334
+ stroke: var(--n-line);
335
+ stroke-width: 1;
336
+ }
337
+
338
+ .n-safe-zone {
339
+ fill: var(--n-accent-soft);
340
+ opacity: 0.72;
341
+ }
342
+
343
+ .n-crease {
344
+ fill: none;
345
+ stroke: var(--n-accent);
346
+ stroke-width: 2;
347
+ }
348
+
349
+ .n-rule {
350
+ fill: none;
351
+ stroke: var(--n-ink);
352
+ stroke-linecap: round;
353
+ stroke-width: 1.5;
354
+ opacity: 0.58;
355
+ }
356
+
357
+ .n-rule-soft {
358
+ opacity: 0.23;
359
+ }
360
+
361
+ .n-measure {
362
+ fill: none;
363
+ stroke: var(--n-accent);
364
+ stroke-dasharray: 2 3;
365
+ stroke-width: 1;
366
+ }
367
+
368
+ .n-measure-outer {
369
+ stroke-dasharray: none;
370
+ }
371
+
372
+ .n-measure-text,
373
+ .n-scene-status {
374
+ fill: var(--n-accent);
375
+ font-size: 7px;
376
+ font-weight: 700;
377
+ }
378
+
379
+ .n-scene-status-ready {
380
+ fill: var(--n-ok);
381
+ }
382
+
383
+ .n-scene-status-low-pages,
384
+ .n-scene-status-high-pages {
385
+ fill: var(--n-warn);
386
+ }
387
+
388
+ .n-scene-status-invalid {
389
+ fill: var(--n-bad);
390
+ }
391
+
392
+ .n-scene-hint {
393
+ margin: 8px 0 0;
394
+ color: var(--n-muted);
395
+ font-size: 0.73rem;
396
+ line-height: 1.45;
397
+ }
398
+
399
+ .n-source {
400
+ margin: 0;
401
+ padding-top: 9px;
402
+ border-top: 1px solid var(--n-line);
403
+ color: var(--n-muted);
404
+ font-size: 0.72rem;
405
+ line-height: 1.45;
406
+ }
407
+
408
+ .n-tool[data-paper="white-uncoated"] .n-paper {
409
+ fill: var(--n-paper-white);
410
+ }
411
+
412
+ .n-tool[data-paper="coated"] .n-paper {
413
+ fill: var(--n-paper-coated);
414
+ }
415
+
416
+ html.theme-dark .n-tool {
417
+ --n-ink: #f6efe5;
418
+ --n-muted: #b9afa2;
419
+ --n-paper: #25231f;
420
+ --n-paper-white: #f8f5ee;
421
+ --n-paper-coated: #e9eef0;
422
+ --n-paper-alt: #171613;
423
+ --n-line: #51483e;
424
+ --n-accent: #e49a72;
425
+ --n-accent-soft: #493129;
426
+ --n-ok: #82ba93;
427
+ --n-warn: #e0a36f;
428
+ --n-bad: #ee8b7f;
429
+ --n-shadow: rgba(0, 0, 0, 0.28);
430
+ }
431
+
432
+ @media (max-width: 780px) {
433
+ .n-tool {
434
+ grid-template-columns: 1fr;
435
+ max-width: calc(100vw - 40px);
436
+ }
437
+
438
+ .n-specs {
439
+ border-right: 0;
440
+ border-bottom: 1px solid var(--n-line);
441
+ }
442
+
443
+ .n-result-copy {
444
+ grid-template-columns: 1fr;
445
+ }
446
+ }
447
+
448
+ @media (max-width: 480px) {
449
+ .n-specs,
450
+ .n-result {
451
+ padding: 20px;
452
+ }
453
+
454
+ .n-field-grid,
455
+ .n-select-grid {
456
+ grid-template-columns: 1fr;
457
+ }
458
+
459
+ .n-field-grid .n-field-pages {
460
+ grid-column: auto;
461
+ }
462
+ }
@@ -0,0 +1,102 @@
1
+ ---
2
+ import { calculateInterior, DEFAULT_INPUTS } from './logic';
3
+ import { createSpreadMarkup } from './dom-views';
4
+ import type { BookInteriorUI } from './ui';
5
+
6
+ interface Props {
7
+ ui: BookInteriorUI;
8
+ }
9
+
10
+ const { ui } = Astro.props as Props;
11
+ const initialCalculation = calculateInterior(DEFAULT_INPUTS);
12
+ ---
13
+
14
+ <div class="n-tool" data-tool="book-interior-margin-and-gutter-planner" data-unit="metric">
15
+ <script is:inline type="application/json" data-ui set:html={JSON.stringify(ui)}></script>
16
+ <div class="n-specs">
17
+ <div class="n-specs-head">
18
+ <span class="n-eyebrow">{ui.setupLabel}</span>
19
+ <div class="n-unit-toggle" role="group" aria-label={`${ui.metricLabel} ${ui.imperialLabel}`}>
20
+ <button type="button" data-unit="metric" aria-pressed="true">{ui.metricLabel}</button>
21
+ <button type="button" data-unit="imperial" aria-pressed="false">{ui.imperialLabel}</button>
22
+ </div>
23
+ </div>
24
+ <div class="n-field-grid">
25
+ <label class="n-field n-field-pages">
26
+ <span>{ui.pagesLabel}</span>
27
+ <input data-input="number" data-field="pages" type="number" inputmode="numeric" min="1" max="2000" value={DEFAULT_INPUTS.pages} />
28
+ </label>
29
+ <label class="n-field">
30
+ <span>{ui.widthLabel} <small data-unit-label>mm</small></span>
31
+ <input data-input="number" data-field="widthMm" type="number" inputmode="decimal" min="20" step="0.1" value={DEFAULT_INPUTS.widthMm} />
32
+ </label>
33
+ <label class="n-field">
34
+ <span>{ui.heightLabel} <small data-unit-label>mm</small></span>
35
+ <input data-input="number" data-field="heightMm" type="number" inputmode="decimal" min="20" step="0.1" value={DEFAULT_INPUTS.heightMm} />
36
+ </label>
37
+ </div>
38
+ <div class="n-select-grid">
39
+ <div class="n-field n-select" data-select="binding" data-open="false">
40
+ <span>{ui.bindingLabel}</span>
41
+ <button class="n-select-trigger" type="button" data-select-trigger aria-haspopup="listbox">{ui.bindingPerfectLabel}</button>
42
+ <div class="n-select-menu" data-select-menu role="listbox" hidden>
43
+ <button type="button" data-select-option data-value="perfect-bound" role="option">{ui.bindingPerfectLabel}</button>
44
+ <button type="button" data-select-option data-value="hardcover" role="option">{ui.bindingHardcoverLabel}</button>
45
+ <button type="button" data-select-option data-value="saddle-stitched" role="option">{ui.bindingSaddleLabel}</button>
46
+ </div>
47
+ </div>
48
+ <div class="n-field n-select" data-select="paperType" data-open="false">
49
+ <span>{ui.paperLabel}</span>
50
+ <button class="n-select-trigger" type="button" data-select-trigger aria-haspopup="listbox">{ui.paperCreamLabel}</button>
51
+ <div class="n-select-menu" data-select-menu role="listbox" hidden>
52
+ <button type="button" data-select-option data-value="white-uncoated" role="option">{ui.paperWhiteLabel}</button>
53
+ <button type="button" data-select-option data-value="cream-uncoated" role="option">{ui.paperCreamLabel}</button>
54
+ <button type="button" data-select-option data-value="coated" role="option">{ui.paperCoatedLabel}</button>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ <button class="n-bleed-toggle" type="button" data-bleed-toggle role="switch" aria-checked="false">
59
+ <span class="n-bleed-mark" aria-hidden="true"></span>
60
+ <span>{ui.bleedLabel}</span>
61
+ <strong data-bleed-state>{ui.bleedOff}</strong>
62
+ </button>
63
+ </div>
64
+
65
+ <div class="n-result" aria-label={ui.marginLabel}>
66
+ <div class="n-result-copy">
67
+ <div class="n-status" data-status="ready">
68
+ <span class="n-status-dot" aria-hidden="true"></span>
69
+ <div>
70
+ <strong data-status-title>{ui.statusReady}</strong>
71
+ <p data-status-detail>{ui.statusReadyDetail}</p>
72
+ </div>
73
+ </div>
74
+ <div class="n-output-list">
75
+ <div class="n-output-row"><span>{ui.gutterLabel}</span><strong data-output="gutter">12.7 mm</strong></div>
76
+ <div class="n-output-row"><span>{ui.outerLabel}</span><strong data-output="outer">6.4 mm</strong></div>
77
+ <div class="n-output-row"><span>{ui.safeWidthLabel}</span><strong data-output="safeWidth">132.9 mm</strong></div>
78
+ <div class="n-output-row"><span>{ui.safeHeightLabel}</span><strong data-output="safeHeight">216.2 mm</strong></div>
79
+ <div class="n-output-row"><span>{ui.normalizedPagesLabel}</span><strong data-output="pages">240</strong></div>
80
+ </div>
81
+ <p class="n-page-band">{ui.pageFormatLabel} <span data-output="band">151-300</span></p>
82
+ </div>
83
+ <div class="n-scene-wrap">
84
+ <div class="n-scene-label"><span>{ui.sceneLabel}</span><span>{ui.trimLabel}</span></div>
85
+ <div class="n-scene" data-scene set:html={createSpreadMarkup(initialCalculation, ui)}></div>
86
+ <p class="n-scene-hint">{ui.sceneHint}</p>
87
+ </div>
88
+ <p class="n-source">{ui.sourceLabel}</p>
89
+ </div>
90
+ </div>
91
+
92
+ <script>
93
+ import { initBookInteriorPlanner } from './controller';
94
+
95
+ const root = document.querySelector<HTMLElement>('[data-tool="book-interior-margin-and-gutter-planner"]');
96
+ const data = root?.querySelector<HTMLScriptElement>('[data-ui]');
97
+ if (root && data) initBookInteriorPlanner(root, JSON.parse(data.textContent || '{}'));
98
+ </script>
99
+
100
+ <style>
101
+ @import url('./book-interior-margin-and-gutter-planner.css');
102
+ </style>