@jjlmoya/utils-books 1.5.0 → 1.7.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 (37) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +2 -1
  4. package/src/index.ts +1 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/seo_translation_completeness.test.ts +1 -1
  7. package/src/tests/tool_validation.test.ts +1 -1
  8. package/src/tool/book-interior-margin-and-gutter-planner/bibliography.astro +16 -0
  9. package/src/tool/book-interior-margin-and-gutter-planner/bibliography.ts +12 -0
  10. package/src/tool/book-interior-margin-and-gutter-planner/book-interior-margin-and-gutter-planner.css +462 -0
  11. package/src/tool/book-interior-margin-and-gutter-planner/component.astro +102 -0
  12. package/src/tool/book-interior-margin-and-gutter-planner/controller.ts +141 -0
  13. package/src/tool/book-interior-margin-and-gutter-planner/dom-views.ts +66 -0
  14. package/src/tool/book-interior-margin-and-gutter-planner/entry.ts +27 -0
  15. package/src/tool/book-interior-margin-and-gutter-planner/evaluator.ts +22 -0
  16. package/src/tool/book-interior-margin-and-gutter-planner/i18n/de.ts +59 -0
  17. package/src/tool/book-interior-margin-and-gutter-planner/i18n/en.ts +60 -0
  18. package/src/tool/book-interior-margin-and-gutter-planner/i18n/es.ts +59 -0
  19. package/src/tool/book-interior-margin-and-gutter-planner/i18n/fr.ts +59 -0
  20. package/src/tool/book-interior-margin-and-gutter-planner/i18n/id.ts +59 -0
  21. package/src/tool/book-interior-margin-and-gutter-planner/i18n/it.ts +59 -0
  22. package/src/tool/book-interior-margin-and-gutter-planner/i18n/ja.ts +58 -0
  23. package/src/tool/book-interior-margin-and-gutter-planner/i18n/ko.ts +58 -0
  24. package/src/tool/book-interior-margin-and-gutter-planner/i18n/nl.ts +59 -0
  25. package/src/tool/book-interior-margin-and-gutter-planner/i18n/pl.ts +59 -0
  26. package/src/tool/book-interior-margin-and-gutter-planner/i18n/pt.ts +59 -0
  27. package/src/tool/book-interior-margin-and-gutter-planner/i18n/ru.ts +58 -0
  28. package/src/tool/book-interior-margin-and-gutter-planner/i18n/sv.ts +59 -0
  29. package/src/tool/book-interior-margin-and-gutter-planner/i18n/tr.ts +58 -0
  30. package/src/tool/book-interior-margin-and-gutter-planner/i18n/zh.ts +58 -0
  31. package/src/tool/book-interior-margin-and-gutter-planner/index.ts +11 -0
  32. package/src/tool/book-interior-margin-and-gutter-planner/logic.test.ts +43 -0
  33. package/src/tool/book-interior-margin-and-gutter-planner/logic.ts +85 -0
  34. package/src/tool/book-interior-margin-and-gutter-planner/seo.astro +15 -0
  35. package/src/tool/book-interior-margin-and-gutter-planner/storage.ts +25 -0
  36. package/src/tool/book-interior-margin-and-gutter-planner/ui.ts +42 -0
  37. package/src/tools.ts +2 -1
@@ -0,0 +1,141 @@
1
+ import { calculateInterior, DEFAULT_INPUTS, type BookInteriorInputs } from './logic';
2
+ import { loadBookInteriorState, saveBookInteriorState } from './storage';
3
+ import { renderCalculation } from './dom-views';
4
+ import type { BookInteriorUI } from './ui';
5
+
6
+ const MM_PER_INCH = 25.4;
7
+
8
+ export function initBookInteriorPlanner(root: HTMLElement, ui: BookInteriorUI): void {
9
+ const fallback = { inputs: DEFAULT_INPUTS, unit: 'metric' as const };
10
+ const stored = loadBookInteriorState(fallback);
11
+ const state = { inputs: { ...DEFAULT_INPUTS, ...stored.inputs }, unit: stored.unit };
12
+ bindSelects(root, state, ui);
13
+ bindInputs(root, state, ui);
14
+ bindUnitToggle(root, state, ui);
15
+ bindBleed(root, state, ui);
16
+ updateView(root, state, ui);
17
+ }
18
+
19
+ function bindInputs(root: HTMLElement, state: { inputs: BookInteriorInputs; unit: 'metric' | 'imperial' }, ui: BookInteriorUI): void {
20
+ root.querySelectorAll<HTMLInputElement>('[data-input="number"]').forEach((input) => {
21
+ input.addEventListener('input', () => {
22
+ const key = input.dataset.field as 'pages' | 'widthMm' | 'heightMm';
23
+ const value = Number(input.value);
24
+ state.inputs[key] = key === 'pages' ? value : toMillimetres(value, state.unit);
25
+ updateView(root, state, ui);
26
+ });
27
+ });
28
+ }
29
+
30
+ function bindSelects(root: HTMLElement, state: { inputs: BookInteriorInputs; unit: 'metric' | 'imperial' }, ui: BookInteriorUI): void {
31
+ root.querySelectorAll<HTMLElement>('[data-select]').forEach((select) => {
32
+ const trigger = select.querySelector<HTMLButtonElement>('[data-select-trigger]');
33
+ const menu = select.querySelector<HTMLElement>('[data-select-menu]');
34
+ if (!trigger || !menu) return;
35
+ trigger.addEventListener('click', () => {
36
+ const isOpen = select.dataset.open === 'true';
37
+ closeSelects(root);
38
+ if (!isOpen) {
39
+ select.dataset.open = 'true';
40
+ menu.hidden = false;
41
+ }
42
+ });
43
+ select.querySelectorAll<HTMLButtonElement>('[data-select-option]').forEach((option) => {
44
+ option.addEventListener('click', () => {
45
+ const field = select.dataset.select as 'binding' | 'paperType';
46
+ state.inputs[field] = option.dataset.value as never;
47
+ closeSelects(root);
48
+ updateView(root, state, ui);
49
+ });
50
+ });
51
+ });
52
+ document.addEventListener('click', (event) => {
53
+ if (!(event.target instanceof Node) || !root.contains(event.target)) closeSelects(root);
54
+ });
55
+ }
56
+
57
+ function bindUnitToggle(root: HTMLElement, state: { inputs: BookInteriorInputs; unit: 'metric' | 'imperial' }, ui: BookInteriorUI): void {
58
+ root.querySelectorAll<HTMLButtonElement>('[data-unit]').forEach((button) => {
59
+ button.addEventListener('click', () => {
60
+ state.unit = button.dataset.unit as 'metric' | 'imperial';
61
+ updateView(root, state, ui);
62
+ });
63
+ });
64
+ }
65
+
66
+ function bindBleed(root: HTMLElement, state: { inputs: BookInteriorInputs; unit: 'metric' | 'imperial' }, ui: BookInteriorUI): void {
67
+ const toggle = root.querySelector<HTMLButtonElement>('[data-bleed-toggle]');
68
+ toggle?.addEventListener('click', () => {
69
+ state.inputs.bleed = !state.inputs.bleed;
70
+ updateView(root, state, ui);
71
+ });
72
+ }
73
+
74
+ function updateView(root: HTMLElement, state: { inputs: BookInteriorInputs; unit: 'metric' | 'imperial' }, ui: BookInteriorUI): void {
75
+ const calculation = calculateInterior(state.inputs);
76
+ updateNumberInputs(root, state);
77
+ updateSelectLabels(root, state, ui);
78
+ updateUnitState(root, state);
79
+ updateBleedState(root, state, ui);
80
+ root.dataset.paper = state.inputs.paperType;
81
+ renderCalculation(root, calculation, ui, state.unit);
82
+ saveBookInteriorState(state);
83
+ }
84
+
85
+ function updateNumberInputs(root: HTMLElement, state: { inputs: BookInteriorInputs; unit: 'metric' | 'imperial' }): void {
86
+ root.querySelectorAll<HTMLInputElement>('[data-input="number"]').forEach((input) => {
87
+ const field = input.dataset.field as 'pages' | 'widthMm' | 'heightMm';
88
+ const value = field === 'pages' ? state.inputs.pages : fromMillimetres(state.inputs[field], state.unit);
89
+ input.value = value.toFixed(field === 'pages' ? 0 : 2);
90
+ });
91
+ }
92
+
93
+ function updateSelectLabels(root: HTMLElement, state: { inputs: BookInteriorInputs; unit: 'metric' | 'imperial' }, ui: BookInteriorUI): void {
94
+ const labels: Record<string, string> = {
95
+ 'perfect-bound': ui.bindingPerfectLabel,
96
+ hardcover: ui.bindingHardcoverLabel,
97
+ 'saddle-stitched': ui.bindingSaddleLabel,
98
+ 'white-uncoated': ui.paperWhiteLabel,
99
+ 'cream-uncoated': ui.paperCreamLabel,
100
+ coated: ui.paperCoatedLabel,
101
+ };
102
+ root.querySelectorAll<HTMLElement>('[data-select]').forEach((select) => {
103
+ const field = select.dataset.select as 'binding' | 'paperType';
104
+ const trigger = select.querySelector<HTMLElement>('[data-select-trigger]');
105
+ const selected = labels[state.inputs[field]];
106
+ if (trigger && selected) trigger.textContent = selected;
107
+ });
108
+ }
109
+
110
+ function updateUnitState(root: HTMLElement, state: { inputs: BookInteriorInputs; unit: 'metric' | 'imperial' }): void {
111
+ root.dataset.unit = state.unit;
112
+ root.querySelectorAll<HTMLButtonElement>('[data-unit]').forEach((button) => {
113
+ button.setAttribute('aria-pressed', String(button.dataset.unit === state.unit));
114
+ });
115
+ root.querySelectorAll<HTMLElement>('[data-unit-label]').forEach((label) => {
116
+ label.textContent = state.unit === 'metric' ? 'mm' : 'in';
117
+ });
118
+ }
119
+
120
+ function updateBleedState(root: HTMLElement, state: { inputs: BookInteriorInputs; unit: 'metric' | 'imperial' }, ui: BookInteriorUI): void {
121
+ const toggle = root.querySelector<HTMLButtonElement>('[data-bleed-toggle]');
122
+ if (!toggle) return;
123
+ toggle.setAttribute('aria-checked', String(state.inputs.bleed));
124
+ toggle.querySelector('[data-bleed-state]')!.textContent = state.inputs.bleed ? ui.bleedOn : ui.bleedOff;
125
+ }
126
+
127
+ function closeSelects(root: HTMLElement): void {
128
+ root.querySelectorAll<HTMLElement>('[data-select]').forEach((select) => {
129
+ select.dataset.open = 'false';
130
+ const menu = select.querySelector<HTMLElement>('[data-select-menu]');
131
+ if (menu) menu.hidden = true;
132
+ });
133
+ }
134
+
135
+ function toMillimetres(value: number, unit: 'metric' | 'imperial'): number {
136
+ return unit === 'metric' ? value : value * MM_PER_INCH;
137
+ }
138
+
139
+ function fromMillimetres(value: number, unit: 'metric' | 'imperial'): number {
140
+ return unit === 'metric' ? value : value / MM_PER_INCH;
141
+ }
@@ -0,0 +1,66 @@
1
+ import type { BookInteriorCalculation } from './logic';
2
+ import type { BookInteriorUI } from './ui';
3
+ import { evaluateInterior } from './evaluator';
4
+
5
+ type DisplayUnit = 'metric' | 'imperial';
6
+
7
+ export function createSpreadMarkup(calculation: BookInteriorCalculation, ui: BookInteriorUI, unit: DisplayUnit = 'metric'): string {
8
+ const result = evaluateInterior(calculation);
9
+ const gutter = Math.min(24, Math.max(8, calculation.gutterMm * 0.8));
10
+ const outer = Math.min(22, Math.max(10, calculation.outerMarginMm * 0.8));
11
+ const pageWidth = 132;
12
+ const pageHeight = 174;
13
+ const leftX = 24;
14
+ const rightX = 164;
15
+ const pageY = 28;
16
+ const safeLeft = leftX + outer; const safeRight = rightX + outer;
17
+ const safeY = pageY + outer;
18
+ const safeWidth = pageWidth - outer * 2 - gutter / 2;
19
+ const safeHeight = pageHeight - outer * 2;
20
+ const gutterLabel = formatValue(calculation.gutterMm, unit);
21
+ const outerLabel = formatValue(calculation.outerMarginMm, unit);
22
+ return `<svg class="n-spread-svg" viewBox="0 0 320 228" role="img" aria-label="${ui.sceneLabel}. ${ui.safeZoneLabel}. ${ui.spineLabel}. ${ui.pageLabel}">
23
+ <rect class="n-paper n-paper-left" x="${leftX}" y="${pageY}" width="${pageWidth}" height="${pageHeight}" rx="2" />
24
+ <rect class="n-paper n-paper-right" x="${rightX}" y="${pageY}" width="${pageWidth}" height="${pageHeight}" rx="2" />
25
+ <rect class="n-safe-zone" x="${safeLeft}" y="${safeY}" width="${safeWidth}" height="${safeHeight}" rx="1" />
26
+ <rect class="n-safe-zone" x="${safeRight + gutter / 2}" y="${safeY}" width="${safeWidth}" height="${safeHeight}" rx="1" />
27
+ <path class="n-crease" d="M160 25 C156 76 156 178 160 204" />
28
+ <path class="n-rule" d="M38 56 H142 M38 66 H142 M38 76 H142 M38 86 H126 M184 56 H288 M184 66 H288 M184 76 H288 M184 86 H272" />
29
+ <path class="n-rule n-rule-soft" d="M38 107 H128 M38 117 H136 M38 127 H118 M184 107 H276 M184 117 H264 M184 127 H284" />
30
+ <path class="n-measure n-measure-gutter" d="M151 28 V202 M169 28 V202" />
31
+ <path class="n-measure n-measure-outer" d="M24 213 H46" />
32
+ <text class="n-measure-text" x="160" y="218" text-anchor="middle">${gutterLabel}</text>
33
+ <text class="n-measure-text" x="35" y="224" text-anchor="middle">${outerLabel}</text>
34
+ <text class="n-scene-status n-scene-status-${result.status}" x="160" y="16" text-anchor="middle">${ui[result.messageKey]}</text>
35
+ </svg>`;
36
+ }
37
+
38
+ export function renderCalculation(root: HTMLElement, calculation: BookInteriorCalculation, ui: BookInteriorUI, unit: DisplayUnit): void {
39
+ const result = evaluateInterior(calculation);
40
+ root.dataset.status = result.status;
41
+ root.dataset.pages = String(calculation.pages);
42
+ const values: Record<string, string> = {
43
+ gutter: formatValue(calculation.gutterMm, unit),
44
+ outer: formatValue(calculation.outerMarginMm, unit),
45
+ safeWidth: formatValue(calculation.safeWidthMm, unit),
46
+ safeHeight: formatValue(calculation.safeHeightMm, unit),
47
+ pages: String(calculation.pages),
48
+ band: calculation.referenceBand,
49
+ };
50
+ Object.entries(values).forEach(([key, value]) => {
51
+ const target = root.querySelector(`[data-output="${key}"]`);
52
+ if (target) target.textContent = value;
53
+ });
54
+ const status = root.querySelector('[data-status-title]');
55
+ const detail = root.querySelector('[data-status-detail]');
56
+ if (status) status.textContent = ui[result.messageKey];
57
+ if (detail) detail.textContent = ui[result.detailKey];
58
+ const scene = root.querySelector('[data-scene]');
59
+ if (scene) scene.innerHTML = createSpreadMarkup(calculation, ui, unit);
60
+ }
61
+
62
+ function formatValue(valueMm: number, unit: DisplayUnit): string {
63
+ const value = unit === 'metric' ? valueMm : valueMm / 25.4;
64
+ const suffix = unit === 'metric' ? 'mm' : 'in';
65
+ return `${Math.max(0, value).toFixed(2)} ${suffix}`;
66
+ }
@@ -0,0 +1,27 @@
1
+ import type { BooksToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { BookInteriorUI } from './ui';
3
+
4
+ export type { BookInteriorUI } from './ui';
5
+ export type BookInteriorLocaleContent = ToolLocaleContent<BookInteriorUI>;
6
+
7
+ export const bookInteriorMarginAndGutterPlanner: BooksToolEntry<BookInteriorUI> = {
8
+ id: 'book-interior-margin-and-gutter-planner',
9
+ icons: { bg: 'mdi:book-open-page-variant-outline', fg: 'mdi:ruler-square' },
10
+ i18n: {
11
+ de: () => import('./i18n/de').then((module) => module.content),
12
+ en: () => import('./i18n/en').then((module) => module.content),
13
+ es: () => import('./i18n/es').then((module) => module.content),
14
+ fr: () => import('./i18n/fr').then((module) => module.content),
15
+ id: () => import('./i18n/id').then((module) => module.content),
16
+ it: () => import('./i18n/it').then((module) => module.content),
17
+ ja: () => import('./i18n/ja').then((module) => module.content),
18
+ ko: () => import('./i18n/ko').then((module) => module.content),
19
+ nl: () => import('./i18n/nl').then((module) => module.content),
20
+ pl: () => import('./i18n/pl').then((module) => module.content),
21
+ pt: () => import('./i18n/pt').then((module) => module.content),
22
+ ru: () => import('./i18n/ru').then((module) => module.content),
23
+ sv: () => import('./i18n/sv').then((module) => module.content),
24
+ tr: () => import('./i18n/tr').then((module) => module.content),
25
+ zh: () => import('./i18n/zh').then((module) => module.content),
26
+ },
27
+ };
@@ -0,0 +1,22 @@
1
+ import type { BookInteriorCalculation } from './logic';
2
+
3
+ export type InteriorStatus = 'ready' | 'low-pages' | 'high-pages' | 'invalid';
4
+
5
+ export interface InteriorEvaluation {
6
+ status: InteriorStatus;
7
+ messageKey: 'statusReady' | 'statusLowPages' | 'statusHighPages' | 'statusInvalid';
8
+ detailKey: 'statusReadyDetail' | 'statusLowPagesDetail' | 'statusHighPagesDetail' | 'statusInvalidDetail';
9
+ }
10
+
11
+ export function evaluateInterior(calculation: BookInteriorCalculation): InteriorEvaluation {
12
+ if (calculation.invalidGeometry) {
13
+ return { status: 'invalid', messageKey: 'statusInvalid', detailKey: 'statusInvalidDetail' };
14
+ }
15
+ if (calculation.aboveReferenceCeiling) {
16
+ return { status: 'high-pages', messageKey: 'statusHighPages', detailKey: 'statusHighPagesDetail' };
17
+ }
18
+ if (calculation.belowReferenceFloor) {
19
+ return { status: 'low-pages', messageKey: 'statusLowPages', detailKey: 'statusLowPagesDetail' };
20
+ }
21
+ return { status: 'ready', messageKey: 'statusReady', detailKey: 'statusReadyDetail' };
22
+ }
@@ -0,0 +1,59 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import type { BookInteriorUI } from '../ui';
3
+ import { bibliography } from '../bibliography';
4
+
5
+ const ui: BookInteriorUI = {
6
+ setupLabel: 'Druckeinrichtung', pagesLabel: 'Innenseiten', widthLabel: 'Beschnittbreite', heightLabel: 'Beschnitthöhe',
7
+ bindingLabel: 'Bindung', bindingPerfectLabel: 'Taschenbuch mit Klebebindung', bindingHardcoverLabel: 'Hardcover mit Deckenbindung', bindingSaddleLabel: 'Rückstichheftung',
8
+ paperLabel: 'Papier', paperWhiteLabel: 'Weiß ungestrichen', paperCreamLabel: 'Creme ungestrichen', paperCoatedLabel: 'Gestrichenes Papier',
9
+ bleedLabel: 'Randabfallende Gestaltung', bleedOn: 'Enthalten', bleedOff: 'Nicht enthalten', metricLabel: 'Metrisch mm', imperialLabel: 'Imperial in',
10
+ marginLabel: 'Randplanung', gutterLabel: 'Bundsteg', outerLabel: 'Sicherheitsrand außen', safeWidthLabel: 'Sichere Textbreite', safeHeightLabel: 'Sichere Texthöhe',
11
+ normalizedPagesLabel: 'Seitenzahl für das Layout', pageFormatLabel: 'Referenzbereich', sceneLabel: 'Doppelseitenplan',
12
+ sceneHint: 'Der warme Bereich ist die nutzbare Textzone. Die mittlere Falz ist die Bindekante. Wichtiger Text bleibt außerhalb.',
13
+ statusReady: 'Referenzbereich abgedeckt', statusLowPages: 'Kurze Auflage prüfen', statusHighPages: 'Außerhalb des Referenzbereichs', statusInvalid: 'Beschnittformat zu klein',
14
+ statusReadyDetail: 'Nutze diese Maße als Layoutanfang und bestätige sie mit der Druckvorlage.',
15
+ statusLowPagesDetail: 'Die veröffentlichte Randtabelle beginnt bei 24 Seiten. Bitte die Heftungsspezifikation der Druckerei prüfen.',
16
+ statusHighPagesDetail: 'Die veröffentlichte Tabelle endet bei 828 Seiten. Bitte eine individuelle Prüfung von Rand und Bindung anfordern.',
17
+ statusInvalidDetail: 'Das gewählte Format lässt nach den erforderlichen Rändern keine positive sichere Textfläche übrig.',
18
+ pageLabel: 'Seite', spineLabel: 'Bindekante', safeZoneLabel: 'Sichere Textzone', trimLabel: 'Endformat',
19
+ sourceLabel: 'Planungswerte sind kein Druckabzug. Bestätige Vorlage, Papier, Bindung und Produktionstoleranzen mit der Druckerei.',
20
+ };
21
+
22
+ const faq = [
23
+ { question: 'Was ist der Bundsteg?', answer: 'Der Bundsteg ist der zusätzliche Rand an der Bindekante. Er hält wichtigen Text und Seitendetails von dem Bereich fern, der nach der Bindung schwer lesbar werden kann.' },
24
+ { question: 'Warum wächst der Bundsteg mit der Seitenzahl?', answer: 'Ein dickeres Buch führt mehr Papier in die Bindung. Deshalb steigen die empfohlenen Mindestwerte für den inneren Rand mit der Länge des Buchblocks.' },
25
+ { question: 'Was ändert ein randabfallendes Motiv?', answer: 'Randabfallende Bilder benötigen zusätzliche Bildfläche über die Schnittkante hinaus. Der Planer vergrößert außerdem den äußeren Sicherheitsrand für Text und wichtige Details.' },
26
+ { question: 'Kann ich diese Werte direkt an die Druckerei schicken?', answer: 'Nein. Verwende sie als Planungsbrief. Die Druckvorlage für Format, Papier, Bindung und Produktionsverfahren ist maßgeblich.' },
27
+ ];
28
+
29
+ const howTo = [
30
+ { name: 'Innenformat eingeben', text: 'Lege Seitenzahl sowie beschnittene Breite und Höhe des Buches fest.' },
31
+ { name: 'Produktionskontext wählen', text: 'Wähle Bindung, Papier und ob die Innenseiten randabfallende Gestaltung enthalten.' },
32
+ { name: 'Doppelseite lesen', text: 'Nutze Bundsteg, Außenrand und sichere Textmaße als Layoutgrenze.' },
33
+ { name: 'Druckdatei bestätigen', text: 'Vergleiche den Plan mit der exakten Vorlage und den Produktionstoleranzen der Druckerei.' },
34
+ ];
35
+
36
+ const seo: ToolLocaleContent<BookInteriorUI>['seo'] = [
37
+ { type: 'title', text: 'Buchinnenränder und Bundsteg vor dem Layout planen', level: 2 },
38
+ { type: 'paragraph', html: 'Lege einen praktischen Startpunkt für druckfertige Buchinnenseiten fest. Seitenzahl, Endformat, Bindung, Papier und Anschnitt werden in Bundsteg, äußeren Sicherheitsrand und nutzbare Textfläche einer Doppelseite übersetzt.' },
39
+ { type: 'title', text: 'So funktioniert die Randplanung', level: 2 },
40
+ { type: 'list', items: ['Gib Seitenzahl und Endformat ein und wähle die passende Bindung.', 'Aktiviere den Anschnitt, wenn Bilder oder Farbflächen über die Schnittkante hinausreichen.', 'Behandle den Bundsteg als Mindestwert und halte wichtige Texte, Seitenzahlen und Tabellen von der Falz fern.', 'Verwende die sichere Breite und Höhe als Layoutgrenze und übertrage danach die genaue Druckvorlage.'] },
41
+ { type: 'title', text: 'Warum die Seitenzahl den Bundsteg verändert', level: 2 },
42
+ { type: 'paragraph', html: 'Ein dickerer Buchblock führt mehr Papier an die Bindekante. Die Referenzbereiche reichen von 9,6 mm für 24 bis 150 Seiten bis 22,3 mm für 701 bis 828 Seiten. Gebundene Bücher werden auf eine gerade Seitenzahl und Rückstichheftungen auf ein Vielfaches von vier gerundet.' },
43
+ { type: 'table', headers: ['Innenseiten', 'Bundsteg', 'Außenrand ohne Anschnitt'], rows: [['24 bis 150', '9,6 mm', 'mindestens 6,4 mm'], ['151 bis 300', '12,7 mm', 'mindestens 6,4 mm'], ['301 bis 500', '15,9 mm', 'mindestens 6,4 mm'], ['501 bis 700', '19,1 mm', 'mindestens 6,4 mm'], ['701 bis 828', '22,3 mm', 'mindestens 6,4 mm']] },
44
+ { type: 'tip', title: 'Nicht bis an den Mindestwert gestalten', html: 'Mindestwerte sind eine Produktionsgrenze und keine Garantie für angenehmes Lesen. Plane zusätzliche Reserve für breite Buchstaben, Seitenzahlen, Abbildungen und das Öffnungsverhalten der Bindung ein.' },
45
+ { type: 'title', text: 'Anschnitt und Sicherheitszone haben verschiedene Aufgaben', level: 2 },
46
+ { type: 'paragraph', html: 'Anschnitt gibt Bildern und Hintergründen zusätzliche Fläche für den Schnitt. Er macht Text nicht sicher an Schnitt- oder Bindekante. Wichtige Inhalte bleiben in der Sicherheitszone, während nur wirklich randfüllende Gestaltung über den Rand hinausläuft.' },
47
+ { type: 'tip', title: 'Das Ergebnis als Layoutbrief verwenden', html: 'Notiere Endformat, Seitenzahl, Bindung, Papier, Bundsteg und Anschnitt neben deiner Designdatei. Wenn Papier oder Bindung wechseln, berechne den Brief neu.' },
48
+ ];
49
+
50
+ export const content: ToolLocaleContent<BookInteriorUI> = {
51
+ slug: 'buchinnenraender-und-bundsteg-planer', title: 'Planer für Buchinnenränder und Bundsteg',
52
+ description: 'Plane Bundsteg, äußeren Sicherheitsrand und nutzbare Textfläche für Buchinnenseiten vor dem Layout.',
53
+ ui, seo, faq, bibliography, howTo,
54
+ schemas: [
55
+ { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Planer für Buchinnenränder und Bundsteg', applicationCategory: 'DesignApplication', operatingSystem: 'Any', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' } },
56
+ { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) },
57
+ { '@context': 'https://schema.org', '@type': 'HowTo', name: 'Buchinnenränder und Bundsteg planen', step: howTo.map((item) => ({ '@type': 'HowToStep', name: item.name, text: item.text })) },
58
+ ],
59
+ };
@@ -0,0 +1,60 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import type { BookInteriorUI } from '../ui';
3
+ import { bibliography } from '../bibliography';
4
+
5
+ const ui: BookInteriorUI = {
6
+ setupLabel: 'Print setup', pagesLabel: 'Interior pages', widthLabel: 'Trim width', heightLabel: 'Trim height',
7
+ bindingLabel: 'Binding', bindingPerfectLabel: 'Paperback perfect bound', bindingHardcoverLabel: 'Hardcover case bound', bindingSaddleLabel: 'Saddle stitched booklet',
8
+ paperLabel: 'Paper stock', paperWhiteLabel: 'White uncoated', paperCreamLabel: 'Cream uncoated', paperCoatedLabel: 'Coated paper',
9
+ bleedLabel: 'Full bleed artwork', bleedOn: 'Included', bleedOff: 'Not included', metricLabel: 'Metric mm', imperialLabel: 'Imperial in',
10
+ marginLabel: 'Margin plan', gutterLabel: 'Inside gutter', outerLabel: 'Outside safe margin', safeWidthLabel: 'Safe text width', safeHeightLabel: 'Safe text height',
11
+ normalizedPagesLabel: 'Layout page count', pageFormatLabel: 'Reference band', sceneLabel: 'Facing page plan',
12
+ sceneHint: 'The warm area is the usable text zone. The central crease is the binding edge; keep essential text outside it.',
13
+ statusReady: 'Reference band covered', statusLowPages: 'Short run needs a check', statusHighPages: 'Beyond the reference band', statusInvalid: 'Trim size is too small',
14
+ statusReadyDetail: 'Use these dimensions as a starting layout and confirm them with the printer template.',
15
+ statusLowPagesDetail: 'The published margin table starts at 24 pages. Ask the printer for the booklet specification.',
16
+ statusHighPagesDetail: 'The published table ends at 828 pages. Request a custom margin and binding review.',
17
+ statusInvalidDetail: 'The chosen trim size leaves no positive safe text area after the required margins.',
18
+ pageLabel: 'Page', spineLabel: 'Binding edge', safeZoneLabel: 'Safe text zone', trimLabel: 'Trim size',
19
+ sourceLabel: 'Planning values are not a printer proof. Confirm the final template, stock, binding, and production tolerances with the printer.',
20
+ };
21
+
22
+ const faq = [
23
+ { question: 'What is the inside gutter?', answer: 'The inside gutter is the extra margin beside the binding edge. It keeps essential text and page details away from the part of the spread that becomes difficult to read after binding.' },
24
+ { question: 'Why does the gutter grow with page count?', answer: 'A thicker book places more paper into the binding. The reference margin bands therefore increase the minimum inside gutter as the interior becomes longer.' },
25
+ { question: 'What does full bleed change?', answer: 'Full bleed artwork needs extra image area beyond the trim edge. This planner also increases the outside safe margin so text and important details stay farther from the cut.' },
26
+ { question: 'Can I send these values directly to a printer?', answer: 'No. Use them as a planning brief. The printer template for the selected trim size, paper, binding, and production method is the final authority.' },
27
+ ];
28
+
29
+ const howTo = [
30
+ { name: 'Enter the interior format', text: 'Set the final page count and trimmed width and height of the book.' },
31
+ { name: 'Choose the production context', text: 'Select the binding, paper stock, and whether the interior includes full bleed artwork.' },
32
+ { name: 'Read the spread plan', text: 'Use the gutter, outside margin, and safe text dimensions as the starting envelope for layout.' },
33
+ { name: 'Confirm the printer file', text: 'Compare the plan with the exact template and production tolerances supplied by the printer.' },
34
+ ];
35
+
36
+ const seo: ToolLocaleContent<BookInteriorUI>['seo'] = [
37
+ { type: 'title', text: 'Plan Book Interior Margins and Gutter Before Layout', level: 2 },
38
+ { type: 'paragraph', html: 'Set a practical starting point for a print ready book interior by combining page count, trim size, binding, paper context, and bleed. The planner turns those choices into an inside gutter, an outside safe margin, and a usable text area for a facing page spread.' },
39
+ { type: 'title', text: 'How the Margin Plan Works', level: 2 },
40
+ { type: 'list', items: ['Enter the final interior page count and trim size, then choose the binding that matches the job.', 'Turn full bleed on when images or color continue past the trim edge. The outside safe margin becomes more generous.', 'Use the gutter as the minimum inside margin and keep important text, folios, and table details outside the central crease.', 'Treat the safe text width and height as a layout envelope, then apply the exact template supplied by the printer.'] },
41
+ { type: 'title', text: 'Why Page Count Changes the Gutter', level: 2 },
42
+ { type: 'paragraph', html: 'A thicker book pushes more paper into the binding edge. The reference bands used here step the inside margin from 9.6 mm for 24 to 150 pages up to 22.3 mm for 701 to 828 pages. The calculator rounds bound books to an even page count and saddle stitched booklets to a multiple of four for a realistic planning spread.' },
43
+ { type: 'table', headers: ['Interior pages', 'Inside gutter', 'Outside margin without bleed'], rows: [['24 to 150', '9.6 mm', '6.4 mm minimum'], ['151 to 300', '12.7 mm', '6.4 mm minimum'], ['301 to 500', '15.9 mm', '6.4 mm minimum'], ['501 to 700', '19.1 mm', '6.4 mm minimum'], ['701 to 828', '22.3 mm', '6.4 mm minimum']] },
44
+ { type: 'tip', title: 'Do not design to the edge of the number', html: 'Minimum margins are a production floor, not a promise of comfortable reading. Leave additional room for wide letterforms, folios, illustrations, captions, and the way a particular binding opens. A printer supplied template wins over a generic estimate.' },
45
+ { type: 'title', text: 'Bleed and Safe Zones Are Different Jobs', level: 2 },
46
+ { type: 'paragraph', html: 'Bleed gives an image or background extra area to survive trimming. It does not make text safe near the cut or binding edge. Keep essential content inside the safe zone, extend only the artwork that truly reaches the edge, and inspect the first and last pages of every spread before exporting.' },
47
+ { type: 'tip', title: 'Use the result as a brief for your layout', html: 'Record the trim size, page count, binding, paper stock, gutter, and bleed choice beside your design file. When the printer changes paper or binding, recalculate the brief instead of quietly reusing old margins.' },
48
+ ];
49
+
50
+ export const content: ToolLocaleContent<BookInteriorUI> = {
51
+ slug: 'book-interior-margin-and-gutter-planner',
52
+ title: 'Book Interior Margin and Gutter Planner',
53
+ description: 'Plan the inside gutter, outside safe margin, and usable text area for a book interior before you start layout.',
54
+ ui, seo, faq, bibliography, howTo,
55
+ schemas: [
56
+ { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Book Interior Margin and Gutter Planner', applicationCategory: 'DesignApplication', operatingSystem: 'Any', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } },
57
+ { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) },
58
+ { '@context': 'https://schema.org', '@type': 'HowTo', name: 'Plan book interior margins and gutter', step: howTo.map((item) => ({ '@type': 'HowToStep', name: item.name, text: item.text })) },
59
+ ],
60
+ };
@@ -0,0 +1,59 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import type { BookInteriorUI } from '../ui';
3
+ import { bibliography } from '../bibliography';
4
+
5
+ const ui: BookInteriorUI = {
6
+ setupLabel: 'Configuración de impresión', pagesLabel: 'Páginas interiores', widthLabel: 'Ancho de corte', heightLabel: 'Alto de corte',
7
+ bindingLabel: 'Encuadernación', bindingPerfectLabel: 'Rústica fresada', bindingHardcoverLabel: 'Tapa dura', bindingSaddleLabel: 'Grapado a caballete',
8
+ paperLabel: 'Papel', paperWhiteLabel: 'Blanco no estucado', paperCreamLabel: 'Crema no estucado', paperCoatedLabel: 'Papel estucado',
9
+ bleedLabel: 'Diseño a sangre', bleedOn: 'Incluido', bleedOff: 'No incluido', metricLabel: 'Métrico mm', imperialLabel: 'Imperial in',
10
+ marginLabel: 'Plan de márgenes', gutterLabel: 'Medianil interior', outerLabel: 'Margen exterior seguro', safeWidthLabel: 'Ancho de texto seguro', safeHeightLabel: 'Alto de texto seguro',
11
+ normalizedPagesLabel: 'Páginas para maquetar', pageFormatLabel: 'Rango de referencia', sceneLabel: 'Plan de doble página',
12
+ sceneHint: 'La zona cálida es el área de texto utilizable. El pliegue central es el borde de encuadernación; mantén fuera el texto esencial.',
13
+ statusReady: 'Rango de referencia cubierto', statusLowPages: 'Revisa la tirada corta', statusHighPages: 'Fuera del rango de referencia', statusInvalid: 'Formato de corte demasiado pequeño',
14
+ statusReadyDetail: 'Usa estas medidas como punto de partida y confírmalas con la plantilla de la imprenta.',
15
+ statusLowPagesDetail: 'La tabla publicada empieza en 24 páginas. Consulta la especificación del folleto con la imprenta.',
16
+ statusHighPagesDetail: 'La tabla publicada termina en 828 páginas. Solicita una revisión personalizada de margen y encuadernación.',
17
+ statusInvalidDetail: 'El formato elegido no deja una zona de texto segura positiva después de aplicar los márgenes requeridos.',
18
+ pageLabel: 'Página', spineLabel: 'Borde de encuadernación', safeZoneLabel: 'Zona de texto segura', trimLabel: 'Tamaño final',
19
+ sourceLabel: 'Estos valores son de planificación, no una prueba de imprenta. Confirma plantilla, papel, encuadernación y tolerancias con la imprenta.',
20
+ };
21
+
22
+ const faq = [
23
+ { question: '¿Qué es el medianil interior?', answer: 'El medianil interior es el margen adicional junto al borde de encuadernación. Aleja el texto y los detalles importantes de la zona que puede resultar difícil de leer al abrir el libro.' },
24
+ { question: '¿Por qué aumenta el medianil con el número de páginas?', answer: 'Un libro más grueso introduce más papel en la encuadernación. Por eso los rangos de referencia elevan el margen interior mínimo a medida que crece el bloque.' },
25
+ { question: '¿Qué cambia al activar el diseño a sangre?', answer: 'Las imágenes a sangre necesitan área adicional más allá del borde de corte. El planificador también amplía el margen exterior seguro para alejar texto y detalles importantes del corte.' },
26
+ { question: '¿Puedo enviar estos valores directamente a la imprenta?', answer: 'No. Úsalos como un briefing de maquetación. La plantilla de la imprenta para ese formato, papel, encuadernación y método de producción es la referencia final.' },
27
+ ];
28
+
29
+ const howTo = [
30
+ { name: 'Introduce el formato interior', text: 'Indica el número final de páginas y el ancho y alto de corte del libro.' },
31
+ { name: 'Elige el contexto de producción', text: 'Selecciona encuadernación, papel y si el interior lleva elementos a sangre.' },
32
+ { name: 'Lee el plan de doble página', text: 'Usa medianil, margen exterior y dimensiones seguras como límite inicial de maquetación.' },
33
+ { name: 'Confirma el archivo de imprenta', text: 'Compara el plan con la plantilla exacta y las tolerancias de producción de la imprenta.' },
34
+ ];
35
+
36
+ const seo: ToolLocaleContent<BookInteriorUI>['seo'] = [
37
+ { type: 'title', text: 'Planifica los márgenes interiores y el medianil de un libro', level: 2 },
38
+ { type: 'paragraph', html: 'Establece un punto de partida práctico para un interior de libro listo para imprimir combinando páginas, tamaño final, encuadernación, papel y sangre. El planificador convierte esas decisiones en medianil, margen exterior seguro y área de texto útil para una doble página.' },
39
+ { type: 'title', text: 'Cómo funciona el plan de márgenes', level: 2 },
40
+ { type: 'list', items: ['Introduce las páginas y el tamaño final, y elige la encuadernación del trabajo.', 'Activa la sangre cuando las imágenes o los fondos continúen más allá del borde de corte.', 'Usa el medianil como mínimo interior y mantén fuera del pliegue el texto, los folios y los datos de tablas.', 'Trata el ancho y el alto seguros como un límite de maquetación y aplica después la plantilla de la imprenta.'] },
41
+ { type: 'title', text: 'Por qué las páginas cambian el medianil', level: 2 },
42
+ { type: 'paragraph', html: 'Un bloque más grueso introduce más papel en el borde de encuadernación. Los rangos de referencia van de 9,6 mm para 24 a 150 páginas hasta 22,3 mm para 701 a 828 páginas. Los libros encuadernados se redondean a páginas pares y los folletos grapados a múltiplos de cuatro.' },
43
+ { type: 'table', headers: ['Páginas interiores', 'Medianil', 'Margen exterior sin sangre'], rows: [['24 a 150', '9,6 mm', 'mínimo 6,4 mm'], ['151 a 300', '12,7 mm', 'mínimo 6,4 mm'], ['301 a 500', '15,9 mm', 'mínimo 6,4 mm'], ['501 a 700', '19,1 mm', 'mínimo 6,4 mm'], ['701 a 828', '22,3 mm', 'mínimo 6,4 mm']] },
44
+ { type: 'tip', title: 'No maquetes al límite del número', html: 'Los mínimos son un suelo de producción, no una promesa de lectura cómoda. Deja margen adicional para letras anchas, folios, ilustraciones y la apertura real de la encuadernación.' },
45
+ { type: 'title', text: 'La sangre y la zona segura cumplen funciones distintas', level: 2 },
46
+ { type: 'paragraph', html: 'La sangre da a imágenes y fondos superficie adicional para sobrevivir al corte. No hace seguro el texto cerca del corte o de la encuadernación. Mantén los elementos esenciales dentro de la zona segura y extiende solo el arte que deba llegar al borde.' },
47
+ { type: 'tip', title: 'Usa el resultado como briefing de maquetación', html: 'Anota tamaño final, páginas, encuadernación, papel, medianil y sangre junto al archivo de diseño. Si cambia el papel o la encuadernación, recalcula el briefing.' },
48
+ ];
49
+
50
+ export const content: ToolLocaleContent<BookInteriorUI> = {
51
+ slug: 'planificador-de-margenes-y-medianil-interior-de-libro', title: 'Planificador de márgenes y medianil interior de libro',
52
+ description: 'Planifica el medianil, el margen exterior seguro y el área de texto útil de un interior de libro antes de maquetar.',
53
+ ui, seo, faq, bibliography, howTo,
54
+ schemas: [
55
+ { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Planificador de márgenes y medianil interior de libro', applicationCategory: 'DesignApplication', operatingSystem: 'Any', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' } },
56
+ { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) },
57
+ { '@context': 'https://schema.org', '@type': 'HowTo', name: 'Planificar márgenes interiores y medianil', step: howTo.map((item) => ({ '@type': 'HowToStep', name: item.name, text: item.text })) },
58
+ ],
59
+ };
@@ -0,0 +1,59 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import type { BookInteriorUI } from '../ui';
3
+ import { bibliography } from '../bibliography';
4
+
5
+ const ui: BookInteriorUI = {
6
+ setupLabel: "Paramètres d'impression", pagesLabel: 'Pages intérieures', widthLabel: 'Largeur de coupe', heightLabel: 'Hauteur de coupe',
7
+ bindingLabel: 'Reliure', bindingPerfectLabel: 'Broché dos carré collé', bindingHardcoverLabel: 'Relié pleine toile', bindingSaddleLabel: 'Dos agrafé',
8
+ paperLabel: 'Papier', paperWhiteLabel: 'Blanc non couché', paperCreamLabel: 'Crème non couché', paperCoatedLabel: 'Papier couché',
9
+ bleedLabel: 'Illustration à fond perdu', bleedOn: 'Activée', bleedOff: 'Désactivée', metricLabel: 'Métrique mm', imperialLabel: 'Impérial in',
10
+ marginLabel: 'Plan de marges', gutterLabel: 'Marge de reliure', outerLabel: 'Marge extérieure sûre', safeWidthLabel: 'Largeur de texte sûre', safeHeightLabel: 'Hauteur de texte sûre',
11
+ normalizedPagesLabel: 'Pages pour la mise en page', pageFormatLabel: 'Plage de référence', sceneLabel: 'Plan de double page',
12
+ sceneHint: 'La zone chaude est la zone de texte utilisable. Le pli central est le bord de reliure. Gardez-y le texte essentiel à distance.',
13
+ statusReady: 'Plage de référence couverte', statusLowPages: 'Vérifier le petit tirage', statusHighPages: 'Hors plage de référence', statusInvalid: 'Format de coupe trop petit',
14
+ statusReadyDetail: "Utilisez ces dimensions comme point de départ et confirmez-les avec le gabarit de l'imprimeur.",
15
+ statusLowPagesDetail: "Le tableau publié commence à 24 pages. Demandez la spécification du livret à l'imprimeur.",
16
+ statusHighPagesDetail: "Le tableau publié s'arrête à 828 pages. Demandez une vérification personnalisée des marges et de la reliure.",
17
+ statusInvalidDetail: 'Le format choisi ne laisse aucune zone de texte sûre positive après les marges requises.',
18
+ pageLabel: 'Page', spineLabel: 'Bord de reliure', safeZoneLabel: 'Zone de texte sûre', trimLabel: 'Format fini',
19
+ sourceLabel: "Ces valeurs servent à planifier, pas à produire une épreuve. Confirmez le gabarit, le papier, la reliure et les tolérances auprès de l'imprimeur.",
20
+ };
21
+
22
+ const faq = [
23
+ { question: "Qu'est-ce que la marge de reliure ?", answer: "La marge de reliure est l'espace supplémentaire placé près du bord relié. Elle éloigne le texte et les détails importants de la zone qui devient difficile à lire une fois le livre relié." },
24
+ { question: 'Pourquoi la marge de reliure augmente-t-elle avec le nombre de pages ?', answer: "Un livre plus épais amène davantage de papier dans la reliure. Les plages de référence augmentent donc la marge intérieure minimale lorsque le bloc s'allonge." },
25
+ { question: 'Que change une illustration à fond perdu ?', answer: 'Une image à fond perdu a besoin de surface supplémentaire au-delà de la ligne de coupe. Le planificateur éloigne aussi le texte et les détails importants de la coupe.' },
26
+ { question: "Puis-je envoyer ces valeurs directement à l'imprimeur ?", answer: "Non. Utilisez-les comme brief de mise en page. Le gabarit de l'imprimeur adapté au format, au papier, à la reliure et au procédé reste la référence finale." },
27
+ ];
28
+
29
+ const howTo = [
30
+ { name: 'Saisir le format intérieur', text: 'Indiquez le nombre final de pages et les largeur et hauteur de coupe du livre.' },
31
+ { name: 'Choisir le contexte de production', text: "Sélectionnez la reliure, le papier et la présence d'éléments à fond perdu." },
32
+ { name: 'Lire le plan de double page', text: 'Utilisez la marge de reliure, la marge extérieure et les dimensions sûres comme enveloppe de départ.' },
33
+ { name: 'Confirmer le fichier imprimeur', text: "Comparez le plan avec le gabarit exact et les tolérances de production de l'imprimeur." },
34
+ ];
35
+
36
+ const seo: ToolLocaleContent<BookInteriorUI>['seo'] = [
37
+ { type: 'title', text: "Planifier les marges intérieures et la reliure d'un livre", level: 2 },
38
+ { type: 'paragraph', html: 'Définissez un point de départ pratique pour un intérieur de livre prêt à imprimer. Le nombre de pages, le format fini, la reliure, le papier et le fond perdu deviennent une marge de reliure, une marge extérieure sûre et une zone de texte utile pour une double page.' },
39
+ { type: 'title', text: 'Fonctionnement du plan de marges', level: 2 },
40
+ { type: 'list', items: ['Saisissez les pages et le format fini, puis choisissez la reliure adaptée.', 'Activez le fond perdu lorsque les images ou les aplats dépassent la coupe.', 'Considérez la marge de reliure comme un minimum et éloignez le texte, les folios et les tableaux du pli central.', "Utilisez la largeur et la hauteur sûres comme limite de mise en page avant d'appliquer le gabarit exact."] },
41
+ { type: 'title', text: 'Pourquoi le nombre de pages modifie la marge de reliure', level: 2 },
42
+ { type: 'paragraph', html: 'Un bloc plus épais pousse davantage de papier vers le bord relié. Les plages de référence vont de 9,6 mm pour 24 à 150 pages à 22,3 mm pour 701 à 828 pages. Les livres reliés sont arrondis à un nombre pair et les livrets agrafés à un multiple de quatre.' },
43
+ { type: 'table', headers: ['Pages intérieures', 'Marge de reliure', 'Marge extérieure sans fond perdu'], rows: [['24 à 150', '9,6 mm', '6,4 mm minimum'], ['151 à 300', '12,7 mm', '6,4 mm minimum'], ['301 à 500', '15,9 mm', '6,4 mm minimum'], ['501 à 700', '19,1 mm', '6,4 mm minimum'], ['701 à 828', '22,3 mm', '6,4 mm minimum']] },
44
+ { type: 'tip', title: 'Ne pas composer au bord du minimum', html: "Les minima sont un seuil de production, pas une garantie de confort de lecture. Ajoutez une réserve pour les lettres larges, les folios, les illustrations et l'ouverture réelle de la reliure." },
45
+ { type: 'title', text: 'Fond perdu et zone sûre ont des rôles différents', level: 2 },
46
+ { type: 'paragraph', html: 'Le fond perdu donne aux images et aux arrière-plans une surface supplémentaire pour supporter la coupe. Il ne sécurise pas le texte près de la coupe ou de la reliure. Gardez les éléments essentiels dans la zone sûre et prolongez seulement les images qui doivent atteindre le bord.' },
47
+ { type: 'tip', title: 'Utiliser le résultat comme brief de mise en page', html: 'Notez le format fini, les pages, la reliure, le papier, la marge de reliure et le fond perdu à côté du fichier. Si le papier ou la reliure changent, recalculez le brief.' },
48
+ ];
49
+
50
+ export const content: ToolLocaleContent<BookInteriorUI> = {
51
+ slug: 'planificateur-marges-interieures-et-gouttiere-livre', title: 'Planificateur de marges intérieures et de reliure du livre',
52
+ description: "Planifiez la marge de reliure, la marge extérieure sûre et la zone de texte d'un intérieur de livre avant la mise en page.",
53
+ ui, seo, faq, bibliography, howTo,
54
+ schemas: [
55
+ { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Planificateur de marges intérieures et de reliure du livre', applicationCategory: 'DesignApplication', operatingSystem: 'Any', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' } },
56
+ { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) },
57
+ { '@context': 'https://schema.org', '@type': 'HowTo', name: "Planifier les marges intérieures d'un livre", step: howTo.map((item) => ({ '@type': 'HowToStep', name: item.name, text: item.text })) },
58
+ ],
59
+ };
@@ -0,0 +1,59 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import type { BookInteriorUI } from '../ui';
3
+ import { bibliography } from '../bibliography';
4
+
5
+ const ui: BookInteriorUI = {
6
+ setupLabel: 'Pengaturan cetak', pagesLabel: 'Halaman isi', widthLabel: 'Lebar potong', heightLabel: 'Tinggi potong',
7
+ bindingLabel: 'Penjilidan', bindingPerfectLabel: 'Buku sampul lunak lem', bindingHardcoverLabel: 'Buku sampul keras', bindingSaddleLabel: 'Jilid kawat tengah',
8
+ paperLabel: 'Kertas', paperWhiteLabel: 'Putih tanpa lapisan', paperCreamLabel: 'Krem tanpa lapisan', paperCoatedLabel: 'Kertas berlapis',
9
+ bleedLabel: 'Karya sampai tepi', bleedOn: 'Aktif', bleedOff: 'Tidak aktif', metricLabel: 'Metrik mm', imperialLabel: 'Imperial in',
10
+ marginLabel: 'Rencana margin', gutterLabel: 'Margin dalam', outerLabel: 'Margin luar aman', safeWidthLabel: 'Lebar teks aman', safeHeightLabel: 'Tinggi teks aman',
11
+ normalizedPagesLabel: 'Jumlah halaman tata letak', pageFormatLabel: 'Rentang referensi', sceneLabel: 'Rencana dua halaman',
12
+ sceneHint: 'Area berwarna hangat adalah zona teks yang dapat digunakan. Lipatan tengah adalah tepi jilid; jauhkan teks penting darinya.',
13
+ statusReady: 'Rentang referensi tercakup', statusLowPages: 'Periksa cetak pendek', statusHighPages: 'Di luar rentang referensi', statusInvalid: 'Ukuran potong terlalu kecil',
14
+ statusReadyDetail: 'Gunakan ukuran ini sebagai awal tata letak dan konfirmasikan dengan templat percetakan.',
15
+ statusLowPagesDetail: 'Tabel margin yang diterbitkan dimulai dari 24 halaman. Tanyakan spesifikasi buklet kepada percetakan.',
16
+ statusHighPagesDetail: 'Tabel yang diterbitkan berakhir pada 828 halaman. Minta pemeriksaan margin dan jilid khusus.',
17
+ statusInvalidDetail: 'Ukuran yang dipilih tidak menyisakan area teks aman yang positif setelah margin diterapkan.',
18
+ pageLabel: 'Halaman', spineLabel: 'Tepi jilid', safeZoneLabel: 'Zona teks aman', trimLabel: 'Ukuran jadi',
19
+ sourceLabel: 'Nilai ini untuk perencanaan, bukan bukti cetak. Konfirmasikan templat, kertas, jilid, dan toleransi produksi kepada percetakan.',
20
+ };
21
+
22
+ const faq = [
23
+ { question: 'Apa yang dimaksud margin dalam?', answer: 'Margin dalam adalah ruang tambahan di dekat tepi jilid. Ruang ini menjauhkan teks dan detail penting dari bagian halaman ganda yang sulit dibaca setelah dijilid.' },
24
+ { question: 'Mengapa margin dalam bertambah sesuai jumlah halaman?', answer: 'Buku yang lebih tebal memasukkan lebih banyak kertas ke dalam jilid. Karena itu, rentang referensi menaikkan margin dalam minimum saat blok buku bertambah.' },
25
+ { question: 'Apa yang berubah saat karya sampai tepi diaktifkan?', answer: 'Gambar sampai tepi membutuhkan area tambahan di luar garis potong. Perencana ini juga memperbesar margin luar aman agar teks dan detail penting menjauh dari potongan.' },
26
+ { question: 'Bolehkah nilai ini langsung dikirim ke percetakan?', answer: 'Tidak. Gunakan sebagai ringkasan tata letak. Templat percetakan untuk ukuran, kertas, jilid, dan metode produksi yang dipilih tetap menjadi acuan akhir.' },
27
+ ];
28
+
29
+ const howTo = [
30
+ { name: 'Masukkan format isi', text: 'Atur jumlah halaman akhir serta lebar dan tinggi potong buku.' },
31
+ { name: 'Pilih konteks produksi', text: 'Pilih jilid, jenis kertas, dan apakah isi memakai karya sampai tepi.' },
32
+ { name: 'Baca rencana halaman', text: 'Gunakan margin dalam, margin luar, dan ukuran teks aman sebagai batas awal.' },
33
+ { name: 'Konfirmasikan berkas cetak', text: 'Bandingkan rencana dengan templat dan toleransi produksi dari percetakan.' },
34
+ ];
35
+
36
+ const seo: ToolLocaleContent<BookInteriorUI>['seo'] = [
37
+ { type: 'title', text: 'Merencanakan margin dalam dan jilid untuk isi buku', level: 2 },
38
+ { type: 'paragraph', html: 'Tentukan titik awal praktis untuk isi buku siap cetak. Jumlah halaman, ukuran jadi, jilid, kertas, dan area sampai tepi diterjemahkan menjadi margin dalam, margin luar aman, serta area teks yang dapat digunakan pada dua halaman.' },
39
+ { type: 'title', text: 'Cara kerja rencana margin', level: 2 },
40
+ { type: 'list', items: ['Masukkan halaman dan ukuran jadi, lalu pilih jilid yang sesuai.', 'Aktifkan karya sampai tepi saat gambar atau latar berlanjut melewati garis potong.', 'Anggap margin dalam sebagai minimum dan jauhkan teks, nomor halaman, serta tabel dari lipatan tengah.', 'Gunakan lebar dan tinggi aman sebagai batas tata letak awal sebelum menerapkan templat percetakan.'] },
41
+ { type: 'title', text: 'Mengapa jumlah halaman mengubah margin dalam', level: 2 },
42
+ { type: 'paragraph', html: 'Blok buku yang lebih tebal mendorong lebih banyak kertas ke tepi jilid. Rentang referensi naik dari 9,6 mm untuk 24 sampai 150 halaman menjadi 22,3 mm untuk 701 sampai 828 halaman. Buku berjilid dibulatkan ke jumlah halaman genap dan buklet kawat tengah ke kelipatan empat.' },
43
+ { type: 'table', headers: ['Halaman isi', 'Margin dalam', 'Margin luar tanpa area sampai tepi'], rows: [['24 sampai 150', '9,6 mm', 'minimum 6,4 mm'], ['151 sampai 300', '12,7 mm', 'minimum 6,4 mm'], ['301 sampai 500', '15,9 mm', 'minimum 6,4 mm'], ['501 sampai 700', '19,1 mm', 'minimum 6,4 mm'], ['701 sampai 828', '22,3 mm', 'minimum 6,4 mm']] },
44
+ { type: 'tip', title: 'Jangan menata tepat di batas minimum', html: 'Nilai minimum adalah batas produksi, bukan jaminan kenyamanan membaca. Sisakan ruang untuk huruf lebar, nomor halaman, ilustrasi, dan cara jilid membuka buku.' },
45
+ { type: 'title', text: 'Area sampai tepi dan zona aman memiliki fungsi berbeda', level: 2 },
46
+ { type: 'paragraph', html: 'Area sampai tepi memberi gambar dan latar ruang ekstra untuk bertahan saat dipotong. Area ini tidak membuat teks aman di dekat potongan atau jilid. Simpan elemen penting di dalam zona aman dan panjangkan hanya karya yang memang harus mencapai tepi.' },
47
+ { type: 'tip', title: 'Gunakan hasil sebagai ringkasan tata letak', html: 'Catat ukuran jadi, halaman, jilid, kertas, margin dalam, dan area sampai tepi di samping berkas desain. Jika kertas atau jilid berubah, hitung ulang ringkasannya.' },
48
+ ];
49
+
50
+ export const content: ToolLocaleContent<BookInteriorUI> = {
51
+ slug: 'perencana-margin-dalam-dan-gutter-buku', title: 'Perencana margin dalam dan jilid isi buku',
52
+ description: 'Rencanakan margin dalam, margin luar aman, dan area teks isi buku sebelum mulai menata halaman.',
53
+ ui, seo, faq, bibliography, howTo,
54
+ schemas: [
55
+ { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Perencana margin dalam dan jilid isi buku', applicationCategory: 'DesignApplication', operatingSystem: 'Any', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } },
56
+ { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) },
57
+ { '@context': 'https://schema.org', '@type': 'HowTo', name: 'Merencanakan margin dalam buku', step: howTo.map((item) => ({ '@type': 'HowToStep', name: item.name, text: item.text })) },
58
+ ],
59
+ };