@jjlmoya/utils-tabletop 1.5.0 → 1.6.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 (46) hide show
  1. package/package.json +5 -3
  2. package/src/entries.ts +5 -1
  3. package/src/index.ts +2 -0
  4. package/src/tests/locale_completeness.test.ts +2 -1
  5. package/src/tests/tool_validation.test.ts +2 -1
  6. package/src/tool/investigation-board/bibliography.astro +16 -0
  7. package/src/tool/investigation-board/bibliography.ts +12 -0
  8. package/src/tool/investigation-board/boardContextMenu.ts +64 -0
  9. package/src/tool/investigation-board/client.ts +223 -0
  10. package/src/tool/investigation-board/component.astro +23 -0
  11. package/src/tool/investigation-board/components/BoardCanvas.astro +39 -0
  12. package/src/tool/investigation-board/components/CardDrawer.astro +73 -0
  13. package/src/tool/investigation-board/components/ConnectionModal.astro +49 -0
  14. package/src/tool/investigation-board/components/ContextMenu.astro +26 -0
  15. package/src/tool/investigation-board/components/ControlPanel.astro +57 -0
  16. package/src/tool/investigation-board/connectionModal.ts +60 -0
  17. package/src/tool/investigation-board/dom.ts +89 -0
  18. package/src/tool/investigation-board/drawer.ts +110 -0
  19. package/src/tool/investigation-board/entry.ts +59 -0
  20. package/src/tool/investigation-board/i18n/de.ts +126 -0
  21. package/src/tool/investigation-board/i18n/en.ts +127 -0
  22. package/src/tool/investigation-board/i18n/es.ts +126 -0
  23. package/src/tool/investigation-board/i18n/fr.ts +126 -0
  24. package/src/tool/investigation-board/i18n/id.ts +126 -0
  25. package/src/tool/investigation-board/i18n/it.ts +126 -0
  26. package/src/tool/investigation-board/i18n/ja.ts +126 -0
  27. package/src/tool/investigation-board/i18n/ko.ts +126 -0
  28. package/src/tool/investigation-board/i18n/nl.ts +126 -0
  29. package/src/tool/investigation-board/i18n/pl.ts +126 -0
  30. package/src/tool/investigation-board/i18n/pt.ts +126 -0
  31. package/src/tool/investigation-board/i18n/ru.ts +126 -0
  32. package/src/tool/investigation-board/i18n/sv.ts +126 -0
  33. package/src/tool/investigation-board/i18n/tr.ts +126 -0
  34. package/src/tool/investigation-board/i18n/zh.ts +126 -0
  35. package/src/tool/investigation-board/index.ts +11 -0
  36. package/src/tool/investigation-board/interactDrag.ts +58 -0
  37. package/src/tool/investigation-board/investigation-board.css +799 -0
  38. package/src/tool/investigation-board/layout.ts +16 -0
  39. package/src/tool/investigation-board/logic.test.ts +105 -0
  40. package/src/tool/investigation-board/logic.ts +106 -0
  41. package/src/tool/investigation-board/panzoom.ts +24 -0
  42. package/src/tool/investigation-board/renderer.ts +256 -0
  43. package/src/tool/investigation-board/seo.astro +16 -0
  44. package/src/tool/investigation-board/stateManager.ts +126 -0
  45. package/src/tool/investigation-board/types.ts +26 -0
  46. package/src/tools.ts +3 -0
@@ -0,0 +1,60 @@
1
+ import {
2
+ state,
3
+ connOverlay,
4
+ connSourceSelect,
5
+ connTargetSelect,
6
+ connTextInput,
7
+ connColorInput,
8
+ saveConnBtn,
9
+ cancelConnBtn,
10
+ closeConnBtn,
11
+ addConnectionBtn,
12
+ } from './dom';
13
+ import { createConnection } from './logic';
14
+ import { saveState } from './stateManager';
15
+ import { renderBoard } from './renderer';
16
+
17
+ export function populateSelects(sourceId?: string, targetId?: string): void {
18
+ connSourceSelect.innerHTML = '';
19
+ connTargetSelect.innerHTML = '';
20
+ state.nodes.forEach((n) => {
21
+ const opt1 = document.createElement('option');
22
+ opt1.value = n.id;
23
+ opt1.textContent = n.name;
24
+ opt1.selected = !!(sourceId && n.id === sourceId);
25
+ connSourceSelect.appendChild(opt1);
26
+
27
+ const opt2 = document.createElement('option');
28
+ opt2.value = n.id;
29
+ opt2.textContent = n.name;
30
+ opt2.selected = !!(targetId && n.id === targetId);
31
+ connTargetSelect.appendChild(opt2);
32
+ });
33
+ }
34
+
35
+ export function openConnModal(sourceId?: string, targetId?: string): void {
36
+ populateSelects(sourceId, targetId);
37
+ connTextInput.value = '';
38
+ connOverlay.classList.add('active');
39
+ }
40
+
41
+ export function closeConnModal(): void {
42
+ connOverlay.classList.remove('active');
43
+ }
44
+
45
+ saveConnBtn.addEventListener('click', () => {
46
+ const fromId = connSourceSelect.value;
47
+ const toId = connTargetSelect.value;
48
+ const label = connTextInput.value;
49
+ const color = connColorInput.value;
50
+ if (!fromId || !toId || fromId === toId) return;
51
+ const conn = createConnection(fromId, toId, label, color);
52
+ state.connections.push(conn);
53
+ saveState();
54
+ closeConnModal();
55
+ renderBoard();
56
+ });
57
+
58
+ cancelConnBtn.addEventListener('click', closeConnModal);
59
+ closeConnBtn.addEventListener('click', closeConnModal);
60
+ addConnectionBtn.addEventListener('click', () => openConnModal());
@@ -0,0 +1,89 @@
1
+ import type { BoardNode, BoardConnection } from './types';
2
+
3
+ export interface BoardMeta {
4
+ id: string;
5
+ name: string;
6
+ }
7
+
8
+ export const state = {
9
+ nodes: [] as BoardNode[],
10
+ connections: [] as BoardConnection[],
11
+ boardsList: [] as BoardMeta[],
12
+ currentBoardId: 'default',
13
+ currentCategoryFilter: 'all',
14
+ currentSearchQuery: '',
15
+ highlightedNodeId: null as string | null,
16
+ quickLinkSourceId: null as string | null,
17
+ zoomScale: 1.0,
18
+ longPressTimer: null as number | null,
19
+ startX: 0,
20
+ startY: 0,
21
+ longPressTargetNodeId: null as string | null,
22
+ longPressCanvasX: 0,
23
+ longPressCanvasY: 0,
24
+ };
25
+
26
+ export const mainCard = document.querySelector('.investigation-board-main') as HTMLElement;
27
+ export const viewportEl = document.getElementById('board-viewport') as HTMLElement;
28
+ export const areaEl = document.getElementById('board-area') as HTMLElement;
29
+ export const svgEl = document.getElementById('board-connections-svg') as unknown as SVGSVGElement;
30
+ export const nodesLayer = document.getElementById('board-nodes-layer') as HTMLElement;
31
+
32
+ export const searchInput = document.getElementById('board-search') as HTMLInputElement;
33
+ export const categoryBtns = document.querySelectorAll('.btn-filter');
34
+ export const addCardBtn = document.getElementById('add-card-btn') as HTMLButtonElement;
35
+ export const addConnectionBtn = document.getElementById('add-connection-btn') as HTMLButtonElement;
36
+ export const clearBoardBtn = document.getElementById('clear-board-btn') as HTMLButtonElement;
37
+
38
+ export const boardSelect = document.getElementById('board-select') as HTMLSelectElement;
39
+ export const saveBoardAsBtn = document.getElementById('save-board-as-btn') as HTMLButtonElement;
40
+ export const newBoardBtn = document.getElementById('new-board-btn') as HTMLButtonElement;
41
+
42
+ export const drawerOverlay = document.getElementById('card-drawer-overlay') as HTMLElement;
43
+ export const closeDrawerBtn = document.getElementById('close-drawer-btn') as HTMLButtonElement;
44
+ export const cancelDrawerBtn = document.getElementById('cancel-node-btn') as HTMLButtonElement;
45
+ export const saveNodeBtn = document.getElementById('save-node-btn') as HTMLButtonElement;
46
+ export const deleteNodeBtn = document.getElementById('delete-node-btn') as HTMLButtonElement;
47
+
48
+ export const editIdInput = document.getElementById('edit-node-id') as HTMLInputElement;
49
+ export const editNameInput = document.getElementById('edit-node-name') as HTMLInputElement;
50
+ export const editCategorySelect = document.getElementById('edit-node-category') as HTMLSelectElement;
51
+ export const editDescTextarea = document.getElementById('edit-node-description') as HTMLTextAreaElement;
52
+ export const editNotesList = document.getElementById('edit-notes-list') as HTMLElement;
53
+ export const addNoteRowBtn = document.getElementById('add-note-row-btn') as HTMLButtonElement;
54
+ export const editTagsInput = document.getElementById('edit-node-tags') as HTMLInputElement;
55
+ export const editColorInput = document.getElementById('edit-node-color') as HTMLInputElement;
56
+ export const colorPickerGroup = document.getElementById('color-picker-group') as HTMLElement;
57
+
58
+ export const connOverlay = document.getElementById('connection-modal-overlay') as HTMLElement;
59
+ export const closeConnBtn = document.getElementById('close-modal-btn') as HTMLButtonElement;
60
+ export const cancelConnBtn = document.getElementById('cancel-connection-btn') as HTMLButtonElement;
61
+ export const saveConnBtn = document.getElementById('save-connection-btn') as HTMLButtonElement;
62
+ export const connSourceSelect = document.getElementById('connection-source') as HTMLSelectElement;
63
+ export const connTargetSelect = document.getElementById('connection-target') as HTMLSelectElement;
64
+ export const connTextInput = document.getElementById('connection-text') as HTMLInputElement;
65
+ export const connColorInput = document.getElementById('connection-color-picker') as HTMLInputElement;
66
+ export const connBanner = document.getElementById('connection-banner') as HTMLElement;
67
+
68
+ export const zoomValueEl = document.getElementById('zoom-value') as HTMLElement;
69
+ export const zoomInBtn = document.getElementById('zoom-in-btn') as HTMLButtonElement;
70
+ export const zoomOutBtn = document.getElementById('zoom-out-btn') as HTMLButtonElement;
71
+ export const zoomResetBtn = document.getElementById('zoom-reset-btn') as HTMLButtonElement;
72
+ export const fullscreenToggleBtn = document.getElementById('fullscreen-toggle-btn') as HTMLButtonElement;
73
+
74
+ export const ctxMenu = document.getElementById('board-context-menu') as HTMLElement;
75
+ export const ctxAddCard = document.getElementById('ctx-add-card') as HTMLButtonElement;
76
+ export const ctxEditCard = document.getElementById('ctx-edit-card') as HTMLButtonElement;
77
+ export const ctxLinkCard = document.getElementById('ctx-link-card') as HTMLButtonElement;
78
+ export const ctxDeleteCard = document.getElementById('ctx-delete-card') as HTMLButtonElement;
79
+
80
+ export const CATEGORY_COLORS: Record<string, string> = {
81
+ character: '#3b82f6',
82
+ clue: '#ef4444',
83
+ location: '#10b981',
84
+ item: '#f59e0b',
85
+ };
86
+
87
+ export const STORAGE_KEY = 'tabletop_conspiracy_boards_list';
88
+ export const CURRENT_ID_KEY = 'tabletop_conspiracy_current_board_id';
89
+ export const DATA_KEY_PREFIX = 'tabletop_conspiracy_board_data_';
@@ -0,0 +1,110 @@
1
+ import type { BoardNode, NodeCategory } from './types';
2
+ import {
3
+ state,
4
+ editIdInput,
5
+ editNameInput,
6
+ editCategorySelect,
7
+ editDescTextarea,
8
+ editNotesList,
9
+ editTagsInput,
10
+ editColorInput,
11
+ colorPickerGroup,
12
+ drawerOverlay,
13
+ addNoteRowBtn,
14
+ saveNodeBtn,
15
+ deleteNodeBtn,
16
+ cancelDrawerBtn,
17
+ closeDrawerBtn,
18
+ CATEGORY_COLORS,
19
+ } from './dom';
20
+ import { updateNode, deleteNode } from './logic';
21
+ import { saveState } from './stateManager';
22
+ import { renderBoard } from './renderer';
23
+
24
+ export function createNoteRowElement(value: string = ''): HTMLElement {
25
+ const row = document.createElement('div');
26
+ row.className = 'note-input-row';
27
+ row.innerHTML = `
28
+ <input type="text" class="form-control note-item-input" style="flex: 1;" value="${value.replace(/"/g, '&quot;')}" />
29
+ <button type="button" class="btn btn-danger btn-sm btn-remove-note-row">&times;</button>
30
+ `;
31
+ row.querySelector('.btn-remove-note-row')?.addEventListener('click', () => {
32
+ row.remove();
33
+ });
34
+ return row;
35
+ }
36
+
37
+ export function toggleColorPicker(category: string): void {
38
+ const isCustom = category === 'custom';
39
+ colorPickerGroup.style.display = isCustom ? 'flex' : 'none';
40
+ }
41
+
42
+ export function openDrawer(node: BoardNode): void {
43
+ editIdInput.value = node.id;
44
+ editNameInput.value = node.name;
45
+ editCategorySelect.value = node.category;
46
+ editDescTextarea.value = node.description;
47
+ editNotesList.innerHTML = '';
48
+ const notesArray = node.notes.split('\n').map((n) => n.trim()).filter((n) => n.length > 0);
49
+ if (notesArray.length > 0) {
50
+ notesArray.forEach((noteText) => {
51
+ editNotesList.appendChild(createNoteRowElement(noteText));
52
+ });
53
+ } else {
54
+ editNotesList.appendChild(createNoteRowElement(''));
55
+ }
56
+ editTagsInput.value = node.tags.join(', ');
57
+ editColorInput.value = node.color;
58
+ toggleColorPicker(node.category);
59
+ drawerOverlay.classList.add('active');
60
+ }
61
+
62
+ export function closeDrawer(): void {
63
+ drawerOverlay.classList.remove('active');
64
+ }
65
+
66
+ addNoteRowBtn.addEventListener('click', () => {
67
+ editNotesList.appendChild(createNoteRowElement(''));
68
+ });
69
+
70
+ editCategorySelect.addEventListener('change', () => {
71
+ toggleColorPicker(editCategorySelect.value);
72
+ });
73
+
74
+ saveNodeBtn.addEventListener('click', () => {
75
+ const id = editIdInput.value;
76
+ const category = editCategorySelect.value as NodeCategory;
77
+ const inputElements = editNotesList.querySelectorAll('.note-item-input') as NodeListOf<HTMLInputElement>;
78
+ const notesString = Array.from(inputElements)
79
+ .map((inp) => inp.value.trim())
80
+ .filter((val) => val.length > 0)
81
+ .join('\n');
82
+
83
+ const color = category === 'custom' ? editColorInput.value : (CATEGORY_COLORS[category] || '#ef4444');
84
+
85
+ const updates = {
86
+ name: editNameInput.value,
87
+ category,
88
+ description: editDescTextarea.value,
89
+ notes: notesString,
90
+ tags: editTagsInput.value.split(',').map((s) => s.trim()),
91
+ color,
92
+ };
93
+ state.nodes = updateNode(state.nodes, id, updates);
94
+ saveState();
95
+ closeDrawer();
96
+ renderBoard();
97
+ });
98
+
99
+ deleteNodeBtn.addEventListener('click', () => {
100
+ const id = editIdInput.value;
101
+ const result = deleteNode(state.nodes, state.connections, id);
102
+ state.nodes = result.nodes;
103
+ state.connections = result.connections;
104
+ saveState();
105
+ closeDrawer();
106
+ renderBoard();
107
+ });
108
+
109
+ cancelDrawerBtn.addEventListener('click', closeDrawer);
110
+ closeDrawerBtn.addEventListener('click', closeDrawer);
@@ -0,0 +1,59 @@
1
+ import type { TabletopToolEntry, ToolLocaleContent } from '../../types';
2
+
3
+ export type InvestigationBoardUI = {
4
+ title: string;
5
+ addCard: string;
6
+ searchPlaceholder: string;
7
+ filterAll: string;
8
+ filterCharacter: string;
9
+ filterClue: string;
10
+ filterLocation: string;
11
+ filterItem: string;
12
+ cardName: string;
13
+ cardCategory: string;
14
+ cardDescription: string;
15
+ cardNotes: string;
16
+ cardTags: string;
17
+ cardColor: string;
18
+ save: string;
19
+ delete: string;
20
+ cancel: string;
21
+ clearBoard: string;
22
+ connectionsTitle: string;
23
+ addConnection: string;
24
+ connectionLabel: string;
25
+ connectionColor: string;
26
+ sourceCard: string;
27
+ targetCard: string;
28
+ close: string;
29
+ character: string;
30
+ clue: string;
31
+ location: string;
32
+ item: string;
33
+ custom: string;
34
+ immersive: string;
35
+ };
36
+
37
+ export type InvestigationBoardLocaleContent = ToolLocaleContent<InvestigationBoardUI>;
38
+
39
+ export const investigationBoard: TabletopToolEntry<InvestigationBoardUI> = {
40
+ id: 'investigation-board',
41
+ icons: { bg: 'mdi:graph-outline', fg: 'mdi:graph' },
42
+ i18n: {
43
+ de: () => import('./i18n/de').then((m) => m.content),
44
+ en: () => import('./i18n/en').then((m) => m.content),
45
+ es: () => import('./i18n/es').then((m) => m.content),
46
+ fr: () => import('./i18n/fr').then((m) => m.content),
47
+ id: () => import('./i18n/id').then((m) => m.content),
48
+ it: () => import('./i18n/it').then((m) => m.content),
49
+ ja: () => import('./i18n/ja').then((m) => m.content),
50
+ ko: () => import('./i18n/ko').then((m) => m.content),
51
+ nl: () => import('./i18n/nl').then((m) => m.content),
52
+ pl: () => import('./i18n/pl').then((m) => m.content),
53
+ pt: () => import('./i18n/pt').then((m) => m.content),
54
+ ru: () => import('./i18n/ru').then((m) => m.content),
55
+ sv: () => import('./i18n/sv').then((m) => m.content),
56
+ tr: () => import('./i18n/tr').then((m) => m.content),
57
+ zh: () => import('./i18n/zh').then((m) => m.content),
58
+ },
59
+ };
@@ -0,0 +1,126 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { InvestigationBoardLocaleContent } from '../entry';
3
+
4
+ export const content: InvestigationBoardLocaleContent = {
5
+ slug: "verschwoerungsbrett-ersteller",
6
+ title: "Verschwörungsbrett Ersteller",
7
+ description: "Entwerfen Sie interaktive Verschwörungsbretter und Ermittlungskarten. Verbinden Sie Charaktere, Hinweise, Orte und verfolgen Sie Beziehungen mit benutzerdefinierten Links.",
8
+ ui: {
9
+ "title": "Verschwörungsbrett Ersteller",
10
+ "addCard": "Karte hinzufügen",
11
+ "searchPlaceholder": "Karten nach Name oder Hinweisen durchsuchen",
12
+ "filterAll": "Alle Kategorien",
13
+ "filterCharacter": "Charaktere",
14
+ "filterClue": "Hinweise",
15
+ "filterLocation": "Orte",
16
+ "filterItem": "Gegenstände",
17
+ "cardName": "Kartenname",
18
+ "cardCategory": "Kategorie",
19
+ "cardDescription": "Beschreibung",
20
+ "cardNotes": "Private Notizen",
21
+ "cardTags": "Tags durch Kommas getrennt",
22
+ "cardColor": "Kartenhervorhebungsfarbe",
23
+ "save": "Änderungen speichern",
24
+ "delete": "Löschen",
25
+ "cancel": "Abbrechen",
26
+ "clearBoard": "Brett leeren",
27
+ "connectionsTitle": "Beziehungsnetzwerk",
28
+ "addConnection": "Verbindung hinzufügen",
29
+ "connectionLabel": "Beziehungsbezeichnung",
30
+ "connectionColor": "Linienfarbe",
31
+ "sourceCard": "Ausgangskarte",
32
+ "targetCard": "Zielkarte",
33
+ "close": "Schließen",
34
+ "character": "Charakter",
35
+ "clue": "Hinweis",
36
+ "location": "Ort",
37
+ "item": "Gegenstand",
38
+ "custom": "Benutzerdefiniert",
39
+ "immersive": "Vollbild"
40
+ },
41
+ seo: [
42
+ { type: 'title', text: "Verschwörungsbrett Ersteller für Tabletop und Detektiv RPG Spiele", level: 2 },
43
+ { type: 'paragraph', html: "Erstellen Sie immersive Ermittlungskarten und Verschwörungsbretter für Ihre Tabletop-Rollenspiele. Ziehen Sie Hinweise, Orte und Charaktere per Drag-and-Drop und verbinden Sie diese mit benutzerdefinierten Fäden, um komplexe Geheimnisse zu visualisieren." },
44
+ {
45
+ type: 'stats',
46
+ items: [
47
+ { value: "Unbegrenzt", label: "Knotenbrett" },
48
+ { value: "4", label: "Kategorien" },
49
+ { value: "Drag-and-Drop", label: "Schnittstelle" }
50
+ ],
51
+ columns: 3
52
+ },
53
+ { type: 'title', text: "Wie man eine Ermittlungskarte aufbaut", level: 2 },
54
+ { type: 'paragraph', html: "Fügen Sie benutzerdefinierte Karten hinzu, die Hinweise, Charaktere, Gegenstände und Orte darstellen. Ziehen Sie sie auf dem Raster in Position. Wählen Sie zwei Karten aus, um sie mit einem farbigen Beziehungsfaden zu verbinden. Doppelklicken Sie auf Knoten oder Fäden, um detaillierte Informationen anzuzeigen." }
55
+ ],
56
+ faq: [
57
+ {
58
+ question: "Wie füge ich Verbindungslinien hinzu?",
59
+ answer: "Klicken Sie im Bedienfeld auf Verbindung hinzufügen, wählen Sie die Ausgangs- und Zielkarten aus, weisen Sie eine Textbeschriftung und eine Farbe zu und speichern Sie sie."
60
+ },
61
+ {
62
+ question: "Kann ich Karten auf dem Handy ziehen?",
63
+ answer: "Ja, das Board unterstützt Touch-Events, um Karten auf Smartphones und Tablets reibungslos zu ziehen und zu bewegen."
64
+ }
65
+ ],
66
+ bibliography,
67
+ howTo: [
68
+ {
69
+ name: "Ermittlungskarten hinzufügen",
70
+ text: "Erstellen Sie Karten, die NPCs, Hinweise, Tatorte oder Gegenstände darstellen."
71
+ },
72
+ {
73
+ name: "Beziehungen verknüpfen",
74
+ text: "Erstellen Sie Verbindungslinien zwischen Karten, um zu zeigen, wie Hinweise zusammenhängen."
75
+ }
76
+ ],
77
+ schemas: [
78
+ {
79
+ '@context': 'https://schema.org',
80
+ '@type': 'SoftwareApplication',
81
+ 'name': "Verschwörungsbrett Ersteller",
82
+ 'operatingSystem': 'All',
83
+ 'applicationCategory': 'UtilitiesApplication',
84
+ 'browserRequirements': 'Requires HTML5 SVG support. Requires JavaScript.'
85
+ },
86
+ {
87
+ '@context': 'https://schema.org',
88
+ '@type': 'FAQPage',
89
+ 'mainEntity': [
90
+ {
91
+ '@type': 'Question',
92
+ 'name': "Wie füge ich Verbindungslinien hinzu?",
93
+ 'acceptedAnswer': {
94
+ '@type': 'Answer',
95
+ 'text': "Klicken Sie im Bedienfeld auf Verbindung hinzufügen, wählen Sie die Ausgangs- und Zielkarten aus, weisen Sie eine Textbeschriftung und eine Farbe zu und speichern Sie sie."
96
+ }
97
+ },
98
+ {
99
+ '@type': 'Question',
100
+ 'name': "Kann ich Karten auf dem Handy ziehen?",
101
+ 'acceptedAnswer': {
102
+ '@type': 'Answer',
103
+ 'text': "Ja, das Board unterstützt Touch-Events, um Karten auf Smartphones und Tablets reibungslos zu ziehen und zu bewegen."
104
+ }
105
+ }
106
+ ]
107
+ },
108
+ {
109
+ '@context': 'https://schema.org',
110
+ '@type': 'HowTo',
111
+ 'name': "Wie man den Verschwörungsbrett Ersteller benutzt",
112
+ 'step': [
113
+ {
114
+ '@type': 'HowToStep',
115
+ 'name': "Ermittlungskarten hinzufügen",
116
+ 'text': "Erstellen Sie Karten, die NPCs, Hinweise, Tatorte oder Gegenstände darstellen."
117
+ },
118
+ {
119
+ '@type': 'HowToStep',
120
+ 'name': "Beziehungen verknüpfen",
121
+ 'text': "Erstellen Sie Verbindungslinien zwischen Karten, um zu zeigen, wie Hinweise zusammenhängen."
122
+ }
123
+ ]
124
+ }
125
+ ]
126
+ };
@@ -0,0 +1,127 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { InvestigationBoardLocaleContent } from '../entry';
3
+
4
+ export const content: InvestigationBoardLocaleContent = {
5
+ slug: 'investigation-board',
6
+ title: 'Conspiracy Board Maker',
7
+ description: 'Design interactive conspiracy boards and investigation maps. Connect characters, clues, locations, and trace relationships with customized links.',
8
+ ui: {
9
+ title: 'Conspiracy Board Maker',
10
+ addCard: 'Add Card',
11
+ searchPlaceholder: 'Search cards by name or clues',
12
+ filterAll: 'All Categories',
13
+ filterCharacter: 'Characters',
14
+ filterClue: 'Clues',
15
+ filterLocation: 'Locations',
16
+ filterItem: 'Items',
17
+ cardName: 'Card Name',
18
+ cardCategory: 'Category',
19
+ cardDescription: 'Description',
20
+ cardNotes: 'Private Notes',
21
+ cardTags: 'Tags separated by commas',
22
+ cardColor: 'Card Highlight Color',
23
+ save: 'Save Changes',
24
+ delete: 'Delete',
25
+ cancel: 'Cancel',
26
+ clearBoard: 'Clear Board',
27
+ connectionsTitle: 'Relationships Map',
28
+ addConnection: 'Add Connection',
29
+ connectionLabel: 'Relationship Label',
30
+ connectionColor: 'Line Color',
31
+ sourceCard: 'From Card',
32
+ targetCard: 'To Card',
33
+ close: 'Close',
34
+ character: 'Character',
35
+ clue: 'Clue',
36
+ location: 'Location',
37
+ item: 'Item',
38
+ custom: 'Custom',
39
+ immersive: 'Fullscreen',
40
+ },
41
+ seo: [
42
+ { type: 'title', text: 'Conspiracy Board Maker for Tabletop and Detective RPG Games', level: 2 },
43
+ { type: 'paragraph', html: 'Create immersive investigation maps and conspiracy boards for your tabletop roleplaying games. Drag and drop clues, locations, and characters, then connect them with customized threads to visualize complex mysteries.' },
44
+ {
45
+ type: 'stats',
46
+ items: [
47
+ { value: 'Unlimited', label: 'Nodes Board' },
48
+ { value: '4', label: 'Categories' },
49
+ { value: 'Drag and Drop', label: 'Interface' },
50
+ ],
51
+ columns: 3,
52
+ },
53
+ { type: 'title', text: 'How to Build an Investigation Map', level: 2 },
54
+ { type: 'paragraph', html: 'Add custom cards representing clues, characters, items, and locations. Drag them into positions on the grid. Select two cards to link them with a colored relationship thread. Double click nodes or threads to view detailed information.' },
55
+ ],
56
+ faq: [
57
+ {
58
+ question: 'How do I add connection lines?',
59
+ answer: 'Click Add Connection in the control panel, choose the source and target cards, assign a text label and color, and save it.',
60
+ },
61
+ {
62
+ question: 'Can I drag cards on mobile?',
63
+ answer: 'Yes, the board supports touch events to drag and move cards around smoothly on smartphones and tablets.',
64
+ },
65
+ ],
66
+ bibliography,
67
+ howTo: [
68
+ {
69
+ name: 'Add Investigation Cards',
70
+ text: 'Create cards representing NPCs, clues, crime scenes, or items.',
71
+ },
72
+ {
73
+ name: 'Link Relationships',
74
+ text: 'Create connection lines between cards to show how clues relate.',
75
+ },
76
+ ],
77
+ schemas: [
78
+ {
79
+ '@context': 'https://schema.org',
80
+ '@type': 'SoftwareApplication',
81
+ 'name': 'Conspiracy Board Maker',
82
+ 'operatingSystem': 'All',
83
+ 'applicationCategory': 'UtilitiesApplication',
84
+ 'browserRequirements': 'Requires HTML5 SVG support. Requires JavaScript.',
85
+ },
86
+ {
87
+ '@context': 'https://schema.org',
88
+ '@type': 'FAQPage',
89
+ 'mainEntity': [
90
+ {
91
+ '@type': 'Question',
92
+ 'name': 'How do I add connection lines?',
93
+ 'acceptedAnswer': {
94
+ '@type': 'Answer',
95
+ 'text': 'Click Add Connection in the control panel, choose the source and target cards, assign a text label and color, and save it.',
96
+ },
97
+ },
98
+ {
99
+ '@type': 'Question',
100
+ 'name': 'Can I drag cards on mobile?',
101
+ 'acceptedAnswer': {
102
+ '@type': 'Answer',
103
+ 'text': 'Yes, the board supports touch events to drag and move cards around smoothly on smartphones and tablets.',
104
+ },
105
+ },
106
+ ],
107
+ },
108
+ {
109
+ '@context': 'https://schema.org',
110
+ '@type': 'HowTo',
111
+ 'name': 'How to Use the Conspiracy Board Maker',
112
+ 'step': [
113
+ {
114
+ '@type': 'HowToStep',
115
+ 'name': 'Add Investigation Cards',
116
+ 'text': 'Create cards representing NPCs, clues, crime scenes, or items.',
117
+ },
118
+ {
119
+ '@type': 'HowToStep',
120
+ 'name': 'Link Relationships',
121
+ 'text': 'Create connection lines between cards to show how clues relate.',
122
+ },
123
+ ],
124
+ },
125
+ ],
126
+
127
+ };