@jjlmoya/utils-tabletop 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 (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 +209 -0
  21. package/src/tool/investigation-board/i18n/en.ts +209 -0
  22. package/src/tool/investigation-board/i18n/es.ts +209 -0
  23. package/src/tool/investigation-board/i18n/fr.ts +209 -0
  24. package/src/tool/investigation-board/i18n/id.ts +209 -0
  25. package/src/tool/investigation-board/i18n/it.ts +209 -0
  26. package/src/tool/investigation-board/i18n/ja.ts +209 -0
  27. package/src/tool/investigation-board/i18n/ko.ts +209 -0
  28. package/src/tool/investigation-board/i18n/nl.ts +209 -0
  29. package/src/tool/investigation-board/i18n/pl.ts +209 -0
  30. package/src/tool/investigation-board/i18n/pt.ts +209 -0
  31. package/src/tool/investigation-board/i18n/ru.ts +209 -0
  32. package/src/tool/investigation-board/i18n/sv.ts +209 -0
  33. package/src/tool/investigation-board/i18n/tr.ts +209 -0
  34. package/src/tool/investigation-board/i18n/zh.ts +209 -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,209 @@
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: Online Detektiv RPG Ermittlungskarten",
7
+ description: "Erstellen Sie interaktive Verschwörungsbretter und RPG-Ermittlungskarten. Verbinden Sie Verdächtige, Hinweise und Tatorte mit farbigen Fäden.",
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: "Online Verschwörungsbrett Ersteller: Organisieren Sie Detektiv-RPG-Hinweise", level: 2 },
43
+ { type: 'paragraph', html: "Das Entwirren eines komplexen Geflechts aus Lügen, das Verfolgen von Alibis von Verdächtigen und das Verknüpfen von Beweisen am Tatort kann jede Detektiv-RPG-Gruppe überfordern. Egal, ob Sie eine Call of Cthulhu-Kampagne, ein Cyberpunk-Krimi-Spiel, ein Detektiv-Abenteuer in D&D leiten oder einen Thriller schreiben - unser Online-Verschwörungsbrett-Ersteller ist das ultimative Werkzeug. Ziehen, kategorisieren und verknüpfen Sie Hinweise, NPCs und physische Beweise auf einer unbegrenzten digitalen Korktafel. Nutzen Sie farbcodierte Beziehungsfäden, um sofort zu sehen, wie Verdächtige mit Tatorten, Alibis und geheimen Motiven zusammenhängen. So gehören unübersichtliche Papierzettel der Vergangenheit an und Ihre Spieler bleiben vollständig in die Ermittlung eingetaucht." },
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: "Tipps zur Strukturierung Ihrer Detektiv-RPG-Ermittlungskarte", level: 2 },
54
+ { type: 'tip', title: "Die Drei Hinweise Regel", html: "Fügen Sie für jede Schlussfolgerung oder Deduktion, die die Spieler ziehen sollen, mindestens drei verschiedene Hinweise auf dem Ermittlungsbrett hinzu. Verbinden Sie diese mit benutzerdefinierten farbigen Fäden, um alternative Argumentationslinien aufzuzeigen. Verwenden Sie Charakterkarten für Verdächtige und Zeugen, Ortskarten für Tatorte und Gegenstandskarten für physische Beweise oder Dokumente. Halten Sie die Notizen stets mit den Entdeckungen der Spieler auf dem neuesten Stand." },
55
+ { type: 'title', text: "Digitale Verschwörungsbretter im Vergleich zu physischen Korktafeln mit rotem Faden", level: 2 },
56
+ {
57
+ type: 'proscons',
58
+ title: "Digitale Verschwörungsbretter im Vergleich zu physischen Korktafeln mit rotem Faden",
59
+ items: [
60
+ { pro: "Unbegrenzte Brettgröße, um so viele Hinweise, Verdächtige und Charakterbeziehungen wie nötig ohne Platzmangel hinzuzufügen.", con: "Erfordert einen Bildschirm, ein Tablet oder ein digitales Gerät während der Spielrunde." },
61
+ { pro: "Sofortige Suche und Kategoriefilter, um bestimmte Zeugen oder Hinweise während der Runden sofort zu finden.", con: "Es fehlt das haptische Gefühl von echtem roten Faden und Pins an einer echten Wand." },
62
+ { pro: "Speichern, Laden und Teilen Sie Karten digital, ohne physischen Platz zwischen den Spieleabenden einzunehmen.", con: "Erfordert Internetzugang oder lokalen Speicher des Browsers, um den Zustand des Bretts zu sichern." }
63
+ ]
64
+ },
65
+ { type: 'title', text: "Auswahl der Kartenkategorien für Ihre Ermittlungskarte", level: 3 },
66
+ {
67
+ type: 'comparative',
68
+ items: [
69
+ {
70
+ title: "Charakterknoten",
71
+ description: "Stellt NPCs, Verdächtige, Zeugen oder Organisationen dar. Heben Sie Beziehungen mit farbigen Linien hervor.",
72
+ icon: 'mdi:account-group',
73
+ highlight: true,
74
+ points: [
75
+ "Verdächtigen-Alibis und Motive verfolgen",
76
+ "Familien- und Fraktionsverbindungen verknüpfen",
77
+ "Zeugenaussagen im Detail festhalten"
78
+ ]
79
+ },
80
+ {
81
+ title: "Hinweisknoten",
82
+ description: "Stellt physische Beweise, Berichte, Alibis oder Gerüchte dar, die von den Spielern entdeckt wurden.",
83
+ icon: 'mdi:magnify',
84
+ highlight: false,
85
+ points: [
86
+ "Obduktionsberichte und forensische Analysen",
87
+ "Physische Beweismittel vom Tatort",
88
+ "Aufgeschnappte Gerüchte und Geheimnisse"
89
+ ]
90
+ },
91
+ {
92
+ title: "Ortsknoten",
93
+ description: "Tatorte, Häuser von Verdächtigen, geheime Verstecke oder Städte.",
94
+ icon: 'mdi:map-marker',
95
+ highlight: false,
96
+ points: [
97
+ "Tatortfotos und Umgebungskarten",
98
+ "Wohnorte wichtiger NPCs",
99
+ "Geheime Unterschlüpfe und Portale"
100
+ ]
101
+ }
102
+ ],
103
+ columns: 3
104
+ },
105
+ { type: 'title', text: "Glossar zur Verschwörungskartierung", level: 3 },
106
+ {
107
+ type: 'glossary',
108
+ items: [
109
+ { term: "Ermittlungsknoten", definition: "Jede Karte auf dem Brett, die eine Person, einen Ort, einen Hinweis oder einen Gegenstand darstellt." },
110
+ { term: "Beziehungsfaden", definition: "Eine farbige Linie, die zwei Karten verbindet und zeigt, wie sie zusammenhängen (z. B. Verdächtig, Alibi, Eigentümer)." },
111
+ { term: "Vollbildmodus", definition: "Ein Layout, das die Steuerungselemente ausblendet, um den Arbeitsbereich auf Mobilgeräten oder Tablets zu maximieren." },
112
+ { term: "Hervorhebungspfad", definition: "Ein visueller Pfad, der nur die verbundenen Knoten einer ausgewählten Karte anzeigt und den Rest dimmt." }
113
+ ]
114
+ },
115
+ {
116
+ type: 'diagnostic',
117
+ variant: 'info',
118
+ title: "Leistungstipp für Mobilgeräte",
119
+ icon: 'mdi:information-outline',
120
+ badge: 'GRID TIP',
121
+ html: "Wenn das Ziehen von Knoten auf älteren Tablets langsam ist, aktivieren Sie den Vollbildmodus. Stellen Sie sicher, dass das Einrasten am Raster aktiviert ist (Karten rasten alle 15px ein), um das Brett mit minimalem Aufwand ordentlich zu halten."
122
+ }
123
+ ],
124
+ faq: [
125
+ { question: "Wie füge ich Verbindungslinien hinzu?", answer: "Klicken Sie auf Verbindung hinzufügen oder wählen Sie eine Karte aus, klicken Sie auf das Link-Symbol und wählen Sie die Zielkarte." },
126
+ { question: "Kann ich Karten auf dem Handy ziehen?", answer: "Ja, das Board unterstützt Touch-Events, um Karten auf Smartphones und Tablets reibungslos zu ziehen und zu bewegen." },
127
+ { question: "Kann ich benutzerdefinierte Kartenfarben verwenden?", answer: "Ja. Doppelklicken Sie auf eine Karte, wählen Sie Custom in der Kategorie und wählen Sie eine Highlight-Farbe." },
128
+ { question: "Speichert das Board meine Arbeit?", answer: "Ja. Ihr Fortschritt wird automatisch im lokalen Speicher Ihres Browsers unter dem aktuellen Brettnamen gespeichert." },
129
+ { question: "Wie kann ich zoomen und mich auf dem Brett bewegen?", answer: "Verwenden Sie Pinch-to-Zoom-Gesten oder das Mausrad zum Zoomen. Ziehen Sie den Hintergrund, um das Brett zu bewegen." }
130
+ ],
131
+ bibliography,
132
+ howTo: [
133
+ { name: "Ermittlungskarten hinzufügen", text: "Erstellen Sie Karten, die NPCs, Hinweise, Tatorte oder Gegenstände darstellen." },
134
+ { name: "Beziehungen verknüpfen", text: "Erstellen Sie Verbindungslinien zwischen Karten, um zu zeigen, wie Hinweise zusammenhängen." }
135
+ ],
136
+ schemas: [
137
+ {
138
+ '@context': 'https://schema.org',
139
+ '@type': 'SoftwareApplication',
140
+ 'name': "Verschwörungsbrett Ersteller: Online Detektiv RPG Ermittlungskarten",
141
+ 'operatingSystem': 'All',
142
+ 'applicationCategory': 'UtilitiesApplication',
143
+ 'browserRequirements': 'Requires HTML5 SVG support. Requires JavaScript.'
144
+ },
145
+ {
146
+ '@context': 'https://schema.org',
147
+ '@type': 'FAQPage',
148
+ 'mainEntity': [
149
+ {
150
+ '@type': 'Question',
151
+ 'name': "Wie füge ich Verbindungslinien hinzu?",
152
+ 'acceptedAnswer': {
153
+ '@type': 'Answer',
154
+ 'text': "Klicken Sie auf Verbindung hinzufügen oder wählen Sie eine Karte aus, klicken Sie auf das Link-Symbol und wählen Sie die Zielkarte."
155
+ }
156
+ },
157
+ {
158
+ '@type': 'Question',
159
+ 'name': "Kann ich Karten auf dem Handy ziehen?",
160
+ 'acceptedAnswer': {
161
+ '@type': 'Answer',
162
+ 'text': "Ja, das Board unterstützt Touch-Events, um Karten auf Smartphones und Tablets reibungslos zu ziehen und zu bewegen."
163
+ }
164
+ },
165
+ {
166
+ '@type': 'Question',
167
+ 'name': "Kann ich benutzerdefinierte Kartenfarben verwenden?",
168
+ 'acceptedAnswer': {
169
+ '@type': 'Answer',
170
+ 'text': "Ja. Doppelklicken Sie auf eine Karte, wählen Sie Custom in der Kategorie und wählen Sie eine Highlight-Farbe."
171
+ }
172
+ },
173
+ {
174
+ '@type': 'Question',
175
+ 'name': "Speichert das Board meine Arbeit?",
176
+ 'acceptedAnswer': {
177
+ '@type': 'Answer',
178
+ 'text': "Ja. Ihr Fortschritt wird automatisch im lokalen Speicher Ihres Browsers unter dem aktuellen Brettnamen gespeichert."
179
+ }
180
+ },
181
+ {
182
+ '@type': 'Question',
183
+ 'name': "Wie kann ich zoomen und mich auf dem Brett bewegen?",
184
+ 'acceptedAnswer': {
185
+ '@type': 'Answer',
186
+ 'text': "Verwenden Sie Pinch-to-Zoom-Gesten oder das Mausrad zum Zoomen. Ziehen Sie den Hintergrund, um das Brett zu bewegen."
187
+ }
188
+ }
189
+ ]
190
+ },
191
+ {
192
+ '@context': 'https://schema.org',
193
+ '@type': 'HowTo',
194
+ 'name': "Wie man den Verschwörungsbrett Ersteller benutzt",
195
+ 'step': [
196
+ {
197
+ '@type': 'HowToStep',
198
+ 'name': "Ermittlungskarten hinzufügen",
199
+ 'text': "Erstellen Sie Karten, die NPCs, Hinweise, Tatorte oder Gegenstände darstellen."
200
+ },
201
+ {
202
+ '@type': 'HowToStep',
203
+ 'name': "Beziehungen verknüpfen",
204
+ 'text': "Erstellen Sie Verbindungslinien zwischen Karten, um zu zeigen, wie Hinweise zusammenhängen."
205
+ }
206
+ ]
207
+ }
208
+ ]
209
+ };