@jjlmoya/utils-tabletop 1.26.0 → 1.27.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 (40) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -0
  3. package/src/entries.ts +2 -0
  4. package/src/index.ts +1 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/tool_validation.test.ts +1 -1
  7. package/src/tool/token-stamp-studio/bibliography.astro +16 -0
  8. package/src/tool/token-stamp-studio/bibliography.ts +12 -0
  9. package/src/tool/token-stamp-studio/component.astro +91 -0
  10. package/src/tool/token-stamp-studio/controller.ts +179 -0
  11. package/src/tool/token-stamp-studio/dom-views.ts +266 -0
  12. package/src/tool/token-stamp-studio/entry.ts +27 -0
  13. package/src/tool/token-stamp-studio/evaluator.ts +6 -0
  14. package/src/tool/token-stamp-studio/i18n/de.ts +132 -0
  15. package/src/tool/token-stamp-studio/i18n/en.ts +132 -0
  16. package/src/tool/token-stamp-studio/i18n/es.ts +132 -0
  17. package/src/tool/token-stamp-studio/i18n/fr.ts +132 -0
  18. package/src/tool/token-stamp-studio/i18n/id.ts +132 -0
  19. package/src/tool/token-stamp-studio/i18n/it.ts +132 -0
  20. package/src/tool/token-stamp-studio/i18n/ja.ts +132 -0
  21. package/src/tool/token-stamp-studio/i18n/ko.ts +132 -0
  22. package/src/tool/token-stamp-studio/i18n/nl.ts +132 -0
  23. package/src/tool/token-stamp-studio/i18n/pl.ts +132 -0
  24. package/src/tool/token-stamp-studio/i18n/pt.ts +132 -0
  25. package/src/tool/token-stamp-studio/i18n/ru.ts +132 -0
  26. package/src/tool/token-stamp-studio/i18n/sv.ts +132 -0
  27. package/src/tool/token-stamp-studio/i18n/tr.ts +132 -0
  28. package/src/tool/token-stamp-studio/i18n/zh.ts +132 -0
  29. package/src/tool/token-stamp-studio/index.ts +11 -0
  30. package/src/tool/token-stamp-studio/logic.test.ts +36 -0
  31. package/src/tool/token-stamp-studio/logic.ts +127 -0
  32. package/src/tool/token-stamp-studio/seo.astro +16 -0
  33. package/src/tool/token-stamp-studio/storage.ts +69 -0
  34. package/src/tool/token-stamp-studio/store.ts +139 -0
  35. package/src/tool/token-stamp-studio/token-stamp-studio.css +646 -0
  36. package/src/tool/token-stamp-studio/types.ts +65 -0
  37. package/src/tool/token-stamp-studio/ui.ts +50 -0
  38. package/src/tool/token-stamp-studio/workspace-media.ts +36 -0
  39. package/src/tool/token-stamp-studio/workspace.ts +83 -0
  40. package/src/tools.ts +2 -0
@@ -0,0 +1,27 @@
1
+ import type { TabletopToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { TokenStampUI } from './ui';
3
+
4
+ export type { TokenStampUI } from './ui';
5
+ export type TokenStampLocaleContent = ToolLocaleContent<TokenStampUI>;
6
+
7
+ export const tokenStampStudio: TabletopToolEntry<TokenStampUI> = {
8
+ id: 'token-stamp-studio',
9
+ icons: { bg: 'mdi:account-circle-outline', fg: 'mdi:account-circle' },
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,6 @@
1
+ import { getStatus } from './logic';
2
+ import type { TokenStampState, TokenStampStatus } from './types';
3
+
4
+ export function evaluateTokenState(state: TokenStampState, ui: { statusReady: string; statusAddImage: string; statusAddText: string }): TokenStampStatus {
5
+ return getStatus(state, ui);
6
+ }
@@ -0,0 +1,132 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { TokenStampLocaleContent, TokenStampUI } from '../entry';
4
+
5
+ const ui: TokenStampUI = {
6
+ frameLegend: 'Wähle einen Rahmen',
7
+ frameHint: 'Wähle die Silhouette, die der Gruppe zeigt, wer dieser Charakter ist, bevor jemand den Namen liest.',
8
+ randomFrame: 'Zufälliger Rahmen',
9
+ surfaceLegend: 'Gestalte den Token',
10
+ backgroundLabel: 'Hintergrund',
11
+ borderLabel: 'Rahmen',
12
+ textLabel: 'Beschriftung',
13
+ overlayLabel: 'Tönung',
14
+ randomColors: 'Zufällige Farben',
15
+ borderWidthLabel: 'Rahmenstärke',
16
+ opacityLabel: 'Rahmentransparenz',
17
+ overlayOpacityLabel: 'Porträttönung',
18
+ stageLabel: 'Live Token Schmiede',
19
+ chooseImage: 'Porträt wählen',
20
+ clearImage: 'Porträt entfernen',
21
+ positionHint: 'Ziehe das Porträt oder Beschriftungen direkt auf den Token.',
22
+ markerName: 'Marker Name',
23
+ markerNamePlaceholder: 'Marker benennen',
24
+ textLegend: 'Füge lesbare Details hinzu',
25
+ textHint: 'Verwende kurze Angaben wie Name, Stufe, Rolle oder Zustand. Ziehe jede Beschriftung auf den Token.',
26
+ textPlaceholder: 'Charaktername oder Rolle',
27
+ addText: 'Beschriftung hinzufügen',
28
+ removeText: 'Beschriftung entfernen',
29
+ textSizeLabel: 'Schriftgröße',
30
+ alignmentLabel: 'Textausrichtung',
31
+ alignLeft: 'Links ausrichten',
32
+ alignCenter: 'Zentrieren',
33
+ alignRight: 'Rechts ausrichten',
34
+ imageLegend: 'Porträt anpassen',
35
+ imageZoomLabel: 'Porträt Zoom',
36
+ scaleLabel: 'Export Skalierung',
37
+ savedMarkers: 'Gespeicherte Marker',
38
+ noSavedMarkers: 'Gespeicherte Marker erscheinen hier, um sie erneut zu öffnen, zu bearbeiten und wiederzuverwenden.',
39
+ reuseMarker: 'Wähle einen Marker zum erneuten Öffnen.',
40
+ deleteMarker: 'Marker löschen',
41
+ newMarker: 'Neuer Marker',
42
+ download: 'PNG herunterladen',
43
+ copy: 'PNG kopieren',
44
+ downloadBatch: 'Stapel herunterladen',
45
+ batchLabel: 'Erweiterter Stapel Export',
46
+ batchHint: 'Optional: Exportiere mehrere Porträts mit den aktuellen Rahmen und Farbeinstellungen.',
47
+ chooseBatch: 'Porträts auswählen',
48
+ batchReady: '{count} Porträts bereit zum Schmieden.',
49
+ noImage: 'Kein Porträt geladen',
50
+ noText: 'Noch keine Beschriftungen. Füge Details hinzu, die deine Gruppe benötigt.',
51
+ tokenDetails: 'Charakter Token Steuerung',
52
+ exportHint: 'Das PNG ist außerhalb der gewählten Silhouette transparent, bereit für VTT oder Druckbögen.',
53
+ };
54
+
55
+ const faq = [
56
+ {
57
+ question: 'Was macht ein gutes Charakter Token Porträt aus?',
58
+ answer: 'Wähle ein Bild mit gut erkennbarem Gesicht oder starker Silhouette und ausreichend Kontrast zum Rahmen. Ein Kopf und Schultern Ausschnitt bleibt auch bei kleiner Darstellung auf dem VTT gut lesbar.',
59
+ },
60
+ {
61
+ question: 'Wie platziere ich das Porträt optimal im Rahmen?',
62
+ answer: 'Lade das Bild hoch und ziehe es direkt auf dem Token an die richtige Position. Nutze den Porträt Zoom, um das Gesicht zu vergrößern, ohne den Rahmen zu verändern.',
63
+ },
64
+ {
65
+ question: 'Kann ich Charaktername und Stufe hinzufügen?',
66
+ answer: 'Ja. Füge so viele kurze Beschriftungen hinzu wie nötig, wähle sie aus, passe Schriftgröße und Ausrichtung an und platziere sie gut lesbar auf dem Token.',
67
+ },
68
+ {
69
+ question: 'Bleibt das PNG außerhalb des Tokens transparent?',
70
+ answer: 'Ja. Das exportierte Bild behält den Bereich außerhalb der Silhouette transparent, sodass der Marker perfekt auf Spielplänen oder Druckbögen platziert werden kann.',
71
+ },
72
+ {
73
+ question: 'Kann ich eine ganze Abenteurergruppe auf einmal erstellen?',
74
+ answer: 'Ja. Lade mehrere Porträts im Stapel Export hoch, verwende dieselben Rahmen und Farbeinstellungen und lade für jedes Porträt ein transparentes PNG herunter.',
75
+ },
76
+ {
77
+ question: 'Wo werden meine gespeicherten Marker abgelegt?',
78
+ answer: 'Das Speichern auf diesem Gerät sichert den Marker im lokalen Browserspeicher. Es werden keine Bilder hochgeladen. Das Löschen von Browserdaten entfernt auch die lokale Kopie.',
79
+ },
80
+ ];
81
+
82
+ const howTo = [
83
+ { name: 'Wähle die Charakter Silhouette', text: 'Starte mit einem Rahmen, der zum Charakter passt: Kreis für klassische Helden, Sechseck für taktische Spiele oder Stern für Bosse und Champions.' },
84
+ { name: 'Porträt laden und ausrichten', text: 'Wähle ein Bild aus und ziehe es auf den Live Token. Positioniere das Gesicht mittig im Rahmen und passe den Zoom an.' },
85
+ { name: 'Wichtige Details hinzufügen', text: 'Ergänze kurze Beschriftungen für Name, Stufe oder Status. Halte den Text kurz, damit das Bild gut erkennbar bleibt.' },
86
+ { name: 'Speichern und als PNG exportieren', text: 'Der Marker wird automatisch gespeichert. Lade ein transparentes PNG für dein Virtual Tabletop oder den Druckbogen herunter.' },
87
+ ];
88
+
89
+ const faqSchema: WithContext<FAQPage> = {
90
+ '@context': 'https://schema.org',
91
+ '@type': 'FAQPage',
92
+ mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })),
93
+ };
94
+
95
+ const appSchema: WithContext<SoftwareApplication> = {
96
+ '@context': 'https://schema.org',
97
+ '@type': 'SoftwareApplication',
98
+ name: 'Token Stamp Studio',
99
+ operatingSystem: 'All',
100
+ applicationCategory: 'DesignApplication',
101
+ description: 'Erstelle gut lesbare Charakter Tokens für Pen and Paper Rollenspiele mit individuellen Rahmen, Beschriftungen, Transparenz und Stapel Export.',
102
+ };
103
+
104
+ const howToSchema: WithContext<HowTo> = {
105
+ '@context': 'https://schema.org',
106
+ '@type': 'HowTo',
107
+ name: 'So erstellst du ein Charakter Token für Rollenspiele',
108
+ step: howTo.map((item) => ({ '@type': 'HowToStep', name: item.name, text: item.text })),
109
+ };
110
+
111
+ export const content: TokenStampLocaleContent = {
112
+ slug: 'character-token-maker-rpg-rollenspiel',
113
+ title: 'Charakter Token Ersteller für Pen and Paper Rollenspiele',
114
+ description: 'Erstelle gut lesbare Charakter Tokens und Marker aus Porträts mit ausdrucksstarken Rahmen, verschiebbaren Ausschnitten, Beschriftungen und transparentem PNG Export.',
115
+ ui,
116
+ seo: [
117
+ { type: 'title', text: 'Gestalte deutliche Charakter Tokens für jede Kartengröße', level: 2 },
118
+ { type: 'paragraph', html: 'Ein Token ist ein visueller Signalgeber am Spieltisch. Seine Silhouette verrät der Gruppe sofort, ob es sich um einen Helden, ein Monster oder ein wichtiges Ziel handelt. Wähle den passenden Rahmen und halte Gesicht und Text auch bei kleiner Darstellung auf dem VTT gut lesbar.' },
119
+ { type: 'title', text: 'Der richtige Rahmen für jeden Charaktertyp', level: 2 },
120
+ { type: 'list', items: ['<strong>Kreis oder Ring:</strong> bewährt für Spielercharaktere und Verbündete.', '<strong>Sechseck oder Achteck:</strong> ideal zur Unterscheidung in taktischen Gefechten mit vielen Einheiten.', '<strong>Stern:</strong> perfekt für Bosse, Champions oder Schlüsselpersonen.', '<strong>Wolke:</strong> sanfte Form für Geister, Vertraute und Fabelwesen.'] },
121
+ { type: 'tip', title: 'Entwirf Tokens für die kleinste Spielansicht', html: 'Zoome auf der Karte heraus, bis der Token nur noch wenige Pixel klein ist. Wenn Gesicht, Rahmen und Text weiterhin gut trennbar sind, bleibt der Marker im Spielgeschehen optimal nutzbar.' },
122
+ { type: 'title', text: 'Kurze und nützliche Beschriftungen am Spieltisch', level: 2 },
123
+ { type: 'paragraph', html: 'Ein Token ersetzt keinen Charakterbogen. Ergänze nur die ein oder zwei Informationen, die am Tisch ständig gebraucht werden: Name, Stufe oder Status wie benommen. Lange Texte lenken nur vom Porträt ab.' },
124
+ { type: 'title', text: 'Erstelle ganze Abenteurergruppen mit einheitlichem Stil', level: 2 },
125
+ { type: 'paragraph', html: 'Der Stapel Export eignet sich hervorragend nach einer Session Zero. Nutze einen gemeinsamen Rahmenstil für die Gruppe, während jedes Gesicht individuell bleibt. Das sorgt für visuellen Zusammenhalt bei klarer Unterscheidbarkeit.' },
126
+ { type: 'tip', title: 'Automatische Speicherung auf deinem Gerät', html: 'Das lokale Speichern behält den Ausschnitt und die Beschriftung auf deinem Gerät. Exportierte PNGs nutzt du im Spiel; der gespeicherte Marker bleibt deine bearbeitbare Vorlage.' },
127
+ ],
128
+ faq,
129
+ bibliography,
130
+ howTo,
131
+ schemas: [faqSchema, appSchema, howToSchema] as unknown as Record<string, unknown>[],
132
+ };
@@ -0,0 +1,132 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { TokenStampLocaleContent, TokenStampUI } from '../entry';
4
+
5
+ const ui: TokenStampUI = {
6
+ frameLegend: 'Choose a frame',
7
+ frameHint: 'Pick the silhouette that tells the table who this character is before anyone reads the name.',
8
+ randomFrame: 'Random frame',
9
+ surfaceLegend: 'Paint the token',
10
+ backgroundLabel: 'Backdrop',
11
+ borderLabel: 'Frame',
12
+ textLabel: 'Lettering',
13
+ overlayLabel: 'Tint',
14
+ randomColors: 'Random colours',
15
+ borderWidthLabel: 'Frame weight',
16
+ opacityLabel: 'Frame opacity',
17
+ overlayOpacityLabel: 'Portrait tint',
18
+ stageLabel: 'Live token forge',
19
+ chooseImage: 'Choose portrait',
20
+ clearImage: 'Clear portrait',
21
+ positionHint: 'Drag the portrait or a label directly on the token.',
22
+ markerName: 'Marker name',
23
+ markerNamePlaceholder: 'Name this marker',
24
+ textLegend: 'Add table readable details',
25
+ textHint: 'Use short labels such as a name, level, role, or condition. Drag each one on the token.',
26
+ textPlaceholder: 'Character name or role',
27
+ addText: 'Add label',
28
+ removeText: 'Remove label',
29
+ textSizeLabel: 'Letter size',
30
+ alignmentLabel: 'Label alignment',
31
+ alignLeft: 'Align label left',
32
+ alignCenter: 'Align label center',
33
+ alignRight: 'Align label right',
34
+ imageLegend: 'Tune the portrait',
35
+ imageZoomLabel: 'Portrait zoom',
36
+ scaleLabel: 'Export scale',
37
+ savedMarkers: 'Saved markers',
38
+ noSavedMarkers: 'Saved markers will appear here so you can reopen, edit, and reuse them for another scene.',
39
+ reuseMarker: 'Select a marker to reopen it in the forge.',
40
+ deleteMarker: 'Delete marker',
41
+ newMarker: 'New marker',
42
+ download: 'Download PNG',
43
+ copy: 'Copy PNG',
44
+ downloadBatch: 'Download batch',
45
+ batchLabel: 'Advanced batch export',
46
+ batchHint: 'Optional: export several portraits with the current frame and paint settings.',
47
+ chooseBatch: 'Choose portraits',
48
+ batchReady: '{count} portraits ready to forge.',
49
+ noImage: 'No portrait loaded',
50
+ noText: 'No labels yet. Add the detail your group needs.',
51
+ tokenDetails: 'Character token controls',
52
+ exportHint: 'The PNG is transparent outside the chosen silhouette, ready for a virtual tabletop or print sheet.',
53
+ };
54
+
55
+ const faq = [
56
+ {
57
+ question: 'What makes a good character token portrait?',
58
+ answer: 'Choose art with a clear face or strong silhouette and enough contrast against the frame. A head and shoulders crop usually stays readable when the token is small on a virtual tabletop.',
59
+ },
60
+ {
61
+ question: 'How do I position a portrait inside the frame?',
62
+ answer: 'Drop in the image, then drag directly on the token to move the crop. Use Portrait zoom to bring a face forward without changing the frame or labels.',
63
+ },
64
+ {
65
+ question: 'Can I add a character name and level?',
66
+ answer: 'Yes. Add as many short labels as you need, select each label in the inspector, change its size and alignment, and drag it to a readable position on the token.',
67
+ },
68
+ {
69
+ question: 'Does the PNG keep transparency outside the token?',
70
+ answer: 'Yes. The exported image keeps the outside of the selected silhouette transparent, which makes it easy to place the marker over a map or print a sheet of tokens.',
71
+ },
72
+ {
73
+ question: 'Can I create a whole party at once?',
74
+ answer: 'Yes. Load several portraits in Batch forge, keep the current frame and paint settings, then download one PNG per portrait. Each file uses its source filename.',
75
+ },
76
+ {
77
+ question: 'Where is my saved marker stored?',
78
+ answer: 'Save on this device keeps the current marker in local browser storage. It never uploads the portrait, but clearing site data or using another device removes that local copy.',
79
+ },
80
+ ];
81
+
82
+ const howTo = [
83
+ { name: 'Choose the character silhouette', text: 'Start with the frame that matches the character mood: ring for a classic hero, hex for a tactical game, star for a champion, or a softer shape for a spirit or fey creature.' },
84
+ { name: 'Drop in and frame the portrait', text: 'Choose a portrait or drag it onto the live token. Drag the image until the face or defining silhouette sits inside the frame, then adjust Portrait zoom.' },
85
+ { name: 'Add only the information the table needs', text: 'Add a short name, role, level, or condition. Keep labels brief, choose a readable size, and drag them so the artwork remains visible.' },
86
+ { name: 'Reuse and export for play', text: 'Your active marker saves as you work. Reopen it from Saved markers, or download a transparent PNG for your virtual tabletop, print sheet, or campaign handout.' },
87
+ ];
88
+
89
+ const faqSchema: WithContext<FAQPage> = {
90
+ '@context': 'https://schema.org',
91
+ '@type': 'FAQPage',
92
+ mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })),
93
+ };
94
+
95
+ const appSchema: WithContext<SoftwareApplication> = {
96
+ '@context': 'https://schema.org',
97
+ '@type': 'SoftwareApplication',
98
+ name: 'Token Stamp Studio',
99
+ operatingSystem: 'All',
100
+ applicationCategory: 'DesignApplication',
101
+ description: 'Create readable character tokens for tabletop RPG maps with custom silhouettes, portraits, labels, transparency, and batch PNG export.',
102
+ };
103
+
104
+ const howToSchema: WithContext<HowTo> = {
105
+ '@context': 'https://schema.org',
106
+ '@type': 'HowTo',
107
+ name: 'How to make a tabletop character token',
108
+ step: howTo.map((item) => ({ '@type': 'HowToStep', name: item.name, text: item.text })),
109
+ };
110
+
111
+ export const content: TokenStampLocaleContent = {
112
+ slug: 'token-stamp-studio',
113
+ title: 'Character Token Maker for Tabletop RPGs',
114
+ description: 'Forge readable character markers from portraits with expressive frames, draggable crops, labels, transparency, local saving, and batch PNG export.',
115
+ ui,
116
+ seo: [
117
+ { type: 'title', text: 'Make a Character Token That Reads Across the Map', level: 2 },
118
+ { type: 'paragraph', html: 'A token is a piece of table language. Its silhouette tells the group whether they are looking at a hero, a monster, a spell effect, or a named location before anyone zooms in. Start with the frame that matches the character, then keep the portrait and label readable at the scale your virtual tabletop uses.' },
119
+ { type: 'title', text: 'Choose the Right Frame for the Character', level: 2 },
120
+ { type: 'list', items: ['<strong>Ring:</strong> dependable for player characters and recurring allies.', '<strong>Hex or octagon:</strong> easy to distinguish in tactical scenes with many units.', '<strong>Star:</strong> useful for a champion, boss, or character who needs instant attention.', '<strong>Cloud:</strong> a softer signal for spirits, familiars, and fey creatures.'] },
121
+ { type: 'tip', title: 'Design for the smallest size you will actually play at', html: 'Zoom the map out until a token is only a few dozen pixels wide. If the face, border, and label still separate from one another, the marker will remain useful during a busy encounter.' },
122
+ { type: 'title', text: 'Keep Labels Short and Useful During Play', level: 2 },
123
+ { type: 'paragraph', html: 'A character token is not a character sheet. Add the one or two facts the table repeatedly needs: a short name, a level, a role such as healer, or a condition such as stunned. Long sentences become visual noise and compete with the portrait.' },
124
+ { type: 'title', text: 'Prepare a Whole Party Without Losing Their Identity', level: 2 },
125
+ { type: 'paragraph', html: 'Batch export is useful after a session zero or when a campaign gains new allies. Choose one frame and paint language for the group, then let each portrait stay individual. Consistent framing makes the party feel like one cast while the faces remain instantly distinct.' },
126
+ { type: 'tip', title: 'Your marker saves as you work', html: 'Local saving keeps the editable portrait crop and labels on this device. Exported PNGs are the play copies; the saved marker is the version to reopen when the character changes.' },
127
+ ],
128
+ faq,
129
+ bibliography,
130
+ howTo,
131
+ schemas: [faqSchema, appSchema, howToSchema] as unknown as Record<string, unknown>[],
132
+ };
@@ -0,0 +1,132 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { TokenStampLocaleContent, TokenStampUI } from '../entry';
4
+
5
+ const ui: TokenStampUI = {
6
+ frameLegend: 'Elige un marco',
7
+ frameHint: 'Selecciona la silueta que indica a la mesa quién es este personaje antes de leer su nombre.',
8
+ randomFrame: 'Marco aleatorio',
9
+ surfaceLegend: 'Pinta la ficha',
10
+ backgroundLabel: 'Fondo',
11
+ borderLabel: 'Marco',
12
+ textLabel: 'Texto',
13
+ overlayLabel: 'Tinte',
14
+ randomColors: 'Colores aleatorios',
15
+ borderWidthLabel: 'Grosor de marco',
16
+ opacityLabel: 'Opacidad de marco',
17
+ overlayOpacityLabel: 'Tinte de retrato',
18
+ stageLabel: 'Forja de fichas en vivo',
19
+ chooseImage: 'Elegir retrato',
20
+ clearImage: 'Quitar retrato',
21
+ positionHint: 'Arrastra el retrato o las etiquetas directamente sobre la ficha.',
22
+ markerName: 'Nombre del marcador',
23
+ markerNamePlaceholder: 'Nombra este marcador',
24
+ textLegend: 'Añade detalles legibles para la mesa',
25
+ textHint: 'Usa etiquetas cortas como nombre, nivel, rol o estado. Arrastra cada una sobre la ficha.',
26
+ textPlaceholder: 'Nombre de personaje o rol',
27
+ addText: 'Añadir etiqueta',
28
+ removeText: 'Quitar etiqueta',
29
+ textSizeLabel: 'Tamaño de letra',
30
+ alignmentLabel: 'Alineación de etiqueta',
31
+ alignLeft: 'Alinear etiqueta a la izquierda',
32
+ alignCenter: 'Centrar etiqueta',
33
+ alignRight: 'Alinear etiqueta a la derecha',
34
+ imageLegend: 'Ajusta el retrato',
35
+ imageZoomLabel: 'Zoom de retrato',
36
+ scaleLabel: 'Escala de exportación',
37
+ savedMarkers: 'Marcadores guardados',
38
+ noSavedMarkers: 'Los marcadores guardados aparecerán aquí para reabrir, editar y reutilizar en otra escena.',
39
+ reuseMarker: 'Selecciona un marcador para reabrirlo en la forja.',
40
+ deleteMarker: 'Eliminar marcador',
41
+ newMarker: 'Nuevo marcador',
42
+ download: 'Descargar PNG',
43
+ copy: 'Copiar PNG',
44
+ downloadBatch: 'Descargar lote',
45
+ batchLabel: 'Exportación avanzada en lote',
46
+ batchHint: 'Opcional: exporta varios retratos con el marco y colores actuales.',
47
+ chooseBatch: 'Elegir retratos',
48
+ batchReady: '{count} retratos listos para forjar.',
49
+ noImage: 'Ningún retrato cargado',
50
+ noText: 'Sin etiquetas todavía. Añade la información que tu grupo necesite.',
51
+ tokenDetails: 'Controles de ficha de personaje',
52
+ exportHint: 'El PNG es transparente fuera de la silueta elegida, listo para tablero virtual o hoja impresa.',
53
+ };
54
+
55
+ const faq = [
56
+ {
57
+ question: '¿Qué hace que un retrato para ficha de personaje sea bueno?',
58
+ answer: 'Elige una ilustración con un rostro claro o una silueta fuerte y suficiente contraste respecto al marco. Un recorte de cabeza y hombros se mantiene legible cuando la ficha es pequeña en el tablero virtual.',
59
+ },
60
+ {
61
+ question: '¿Cómo me aseguro de posicionar bien el retrato dentro del marco?',
62
+ answer: 'Carga la imagen y arrastra directamente sobre la ficha para encuadrar la cara. Usa el zoom de retrato para acercar el rostro sin cambiar la forma del marco ni las etiquetas.',
63
+ },
64
+ {
65
+ question: '¿Puedo añadir el nombre y nivel del personaje?',
66
+ answer: 'Sí. Añade las etiquetas cortas que necesites, selecciona cada una en el inspector para cambiar su tamaño o alineación y arrástrala a una posición legible sobre la ficha.',
67
+ },
68
+ {
69
+ question: '¿El PNG conserva la transparencia fuera de la ficha?',
70
+ answer: 'Sí. La imagen exportada mantiene transparente todo el exterior de la silueta seleccionada, lo que facilita colocar el marcador sobre cualquier mapa o imprimirlo en una hoja.',
71
+ },
72
+ {
73
+ question: '¿Puedo crear las fichas de todo un grupo de golpe?',
74
+ answer: 'Sí. Carga varios retratos en la exportación en lote, mantén el marco y los colores elegidos y descarga un PNG transparente por cada retrato conservando su nombre original.',
75
+ },
76
+ {
77
+ question: '¿Dónde se almacenan mis marcadores guardados?',
78
+ answer: 'El guardado en este dispositivo conserva el marcador activo en el almacenamiento local del navegador. Nunca sube tus fotos a ningún servidor, pero borrar los datos de navegación o cambiar de dispositivo eliminará la copia local.',
79
+ },
80
+ ];
81
+
82
+ const howTo = [
83
+ { name: 'Elige la silueta del personaje', text: 'Comienza seleccionando el marco acorde a la naturaleza del personaje: circular para un héroe clásico, hexagonal para combate táctico, estrella para un campeón o nube para una criatura feérica o espiritual.' },
84
+ { name: 'Carga y encuadra el retrato', text: 'Selecciona una imagen o arrástrala sobre la ficha en vivo. Muévela hasta que el rostro quede centrado en la silueta y ajusta el zoom de retrato.' },
85
+ { name: 'Añade la información necesaria para la mesa', text: 'Incluye un nombre corto, rol, nivel o condición. Mantén las etiquetas breves, ajusta un tamaño legible y arrástralas sin tapar los detalles clave de la ilustración.' },
86
+ { name: 'Reutiliza y exporta para tu partida', text: 'Tu marcador se guarda automáticamente mientras trabajas. Reábrelo desde los marcadores guardados o descarga un PNG transparente para tu tablero virtual o lámina impresa.' },
87
+ ];
88
+
89
+ const faqSchema: WithContext<FAQPage> = {
90
+ '@context': 'https://schema.org',
91
+ '@type': 'FAQPage',
92
+ mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })),
93
+ };
94
+
95
+ const appSchema: WithContext<SoftwareApplication> = {
96
+ '@context': 'https://schema.org',
97
+ '@type': 'SoftwareApplication',
98
+ name: 'Token Stamp Studio',
99
+ operatingSystem: 'All',
100
+ applicationCategory: 'DesignApplication',
101
+ description: 'Crea fichas de personaje legibles para mapas de RPG de mesa con siluetas personalizadas, retratos, etiquetas, transparencia y exportación PNG en lote.',
102
+ };
103
+
104
+ const howToSchema: WithContext<HowTo> = {
105
+ '@context': 'https://schema.org',
106
+ '@type': 'HowTo',
107
+ name: 'Cómo hacer una ficha de personaje para RPG de mesa',
108
+ step: howTo.map((item) => ({ '@type': 'HowToStep', name: item.name, text: item.text })),
109
+ };
110
+
111
+ export const content: TokenStampLocaleContent = {
112
+ slug: 'creador-fichas-personaje-tokens-rpg',
113
+ title: 'Creador de Tokens y Fichas de Personaje para RPG de Mesa',
114
+ description: 'Crea marcadores y fichas de personaje legibles a partir de retratos con marcos expresivos, recortes arrastrabiles, etiquetas, transparencia y exportación PNG en lote.',
115
+ ui,
116
+ seo: [
117
+ { type: 'title', text: 'Crea fichas de personaje legibles a cualquier escala en el mapa', level: 2 },
118
+ { type: 'paragraph', html: 'Una ficha o token es un elemento de comunicación en la mesa. Su silueta comunica al grupo si se trata de un héroe, un monstruo, un efecto de hechizo o una localización destacada antes de acercar la cámara. Elige el marco adecuado para el personaje y mantén el retrato y las letras claros al tamaño real de tu tablero virtual.' },
119
+ { type: 'title', text: 'Elige el marco adecuado para la silueta del personaje', level: 2 },
120
+ { type: 'list', items: ['<strong>Círculo o anillo:</strong> ideal para personajes jugadores y aliados recurrentes.', '<strong>Hexágono u octógono:</strong> fácil de diferenciar en escenas tácticas con muchas unidades.', '<strong>Estrella:</strong> perfecto para un campeón, jefe de zona o personaje que requiere atención inmediata.', '<strong>Nube:</strong> una señal suave para espíritus, familiares y criaturas feéricas.'] },
121
+ { type: 'tip', title: 'Diseña pensando en el tamaño mínimo al que jugarás', html: 'Aleja el mapa hasta que la ficha tenga solo unas decenas de píxeles de ancho. Si el rostro, el borde y el texto se distinguen con claridad, el marcador funcionará perfectamente en combates concurridos.' },
122
+ { type: 'title', text: 'Mantén las etiquetas cortas y útiles durante la partida', level: 2 },
123
+ { type: 'paragraph', html: 'Una ficha de combate no es una hoja de personaje completa. Añade únicamente uno o dos datos que la mesa consulta constantemente: un nombre corto, el nivel, la función principal o una condición como aturdido. Las frases largas generan ruido visual y compiten con la ilustración.' },
124
+ { type: 'title', text: 'Prepara a todo un grupo de aventureros sin perder su identidad', level: 2 },
125
+ { type: 'paragraph', html: 'La exportación en lote es excelente tras una sesión cero o al incorporar aliados a la campaña. Selecciona una silueta y esquema de color común para el grupo y deja que cada retrato conserve su personalidad. El encuadre uniforme da cohesión al grupo mientras cada rostro se identifica al instante.' },
126
+ { type: 'tip', title: 'Tu marcador se guarda mientras trabajas', html: 'El guardado local conserva el encuadre del retrato y las etiquetas en este dispositivo. Los PNG exportados son para jugar; el marcador guardado es la versión que puedes editar cuando el personaje evolucione.' },
127
+ ],
128
+ faq,
129
+ bibliography,
130
+ howTo,
131
+ schemas: [faqSchema, appSchema, howToSchema] as unknown as Record<string, unknown>[],
132
+ };
@@ -0,0 +1,132 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { TokenStampLocaleContent, TokenStampUI } from '../entry';
4
+
5
+ const ui: TokenStampUI = {
6
+ frameLegend: 'Choisir un cadre',
7
+ frameHint: 'Sélectionnez la silhouette qui indique à la table qui est ce personnage avant même de lire son nom.',
8
+ randomFrame: 'Cadre aléatoire',
9
+ surfaceLegend: 'Peindre le jeton',
10
+ backgroundLabel: 'Arrière-plan',
11
+ borderLabel: 'Cadre',
12
+ textLabel: 'Texte',
13
+ overlayLabel: 'Teinte',
14
+ randomColors: 'Couleurs aléatoires',
15
+ borderWidthLabel: 'Épaisseur du cadre',
16
+ opacityLabel: 'Opacité du cadre',
17
+ overlayOpacityLabel: 'Teinte du portrait',
18
+ stageLabel: 'Forge de jetons en direct',
19
+ chooseImage: 'Choisir un portrait',
20
+ clearImage: 'Effacer le portrait',
21
+ positionHint: 'Faites glisser le portrait ou les étiquettes directement sur le jeton.',
22
+ markerName: 'Nom du marqueur',
23
+ markerNamePlaceholder: 'Nommer ce marqueur',
24
+ textLegend: 'Ajouter des détails lisibles',
25
+ textHint: 'Utilisez des étiquettes courtes comme le nom, le niveau ou le rôle. Glissez chacune sur le jeton.',
26
+ textPlaceholder: 'Nom ou rôle du personnage',
27
+ addText: 'Ajouter une étiquette',
28
+ removeText: 'Retirer l\'étiquette',
29
+ textSizeLabel: 'Taille du texte',
30
+ alignmentLabel: 'Alignement du texte',
31
+ alignLeft: 'Aligner à gauche',
32
+ alignCenter: 'Centrer',
33
+ alignRight: 'Aligner à droite',
34
+ imageLegend: 'Ajuster le portrait',
35
+ imageZoomLabel: 'Zoom du portrait',
36
+ scaleLabel: 'Échelle d\'exportation',
37
+ savedMarkers: 'Marqueurs enregistrés',
38
+ noSavedMarkers: 'Les marqueurs enregistrés apparaîtront ici pour être réouverts et réutilisés dans une autre scène.',
39
+ reuseMarker: 'Sélectionnez un marqueur pour le rouvrir dans la forge.',
40
+ deleteMarker: 'Supprimer le marqueur',
41
+ newMarker: 'Nouveau marqueur',
42
+ download: 'Télécharger PNG',
43
+ copy: 'Copier PNG',
44
+ downloadBatch: 'Télécharger le lot',
45
+ batchLabel: 'Exportation avancée en lot',
46
+ batchHint: 'Optionnel: exportez plusieurs portraits avec le cadre et les couleurs actuels.',
47
+ chooseBatch: 'Choisir les portraits',
48
+ batchReady: '{count} portraits prêts à forger.',
49
+ noImage: 'Aucun portrait chargé',
50
+ noText: 'Aucune étiquette pour l\'instant. Ajoutez les détails dont votre groupe a besoin.',
51
+ tokenDetails: 'Commandes du jeton de personnage',
52
+ exportHint: 'Le PNG est transparent autour de la silhouette choisie, prêt pour table virtuelle ou impression.',
53
+ };
54
+
55
+ const faq = [
56
+ {
57
+ question: 'Qu\'est-ce qui fait un bon portrait pour un jeton de personnage ?',
58
+ answer: 'Choisissez une illustration avec un visage net et un bon contraste par rapport au cadre. Un cadrage tête et épaules reste parfaitement lisible sur une table virtuelle.',
59
+ },
60
+ {
61
+ question: 'Comment positionner idéalement le portrait dans le cadre ?',
62
+ answer: 'Chargez l\'image puis faites-la glisser directement sur le jeton. Utilisez le zoom de portrait pour recentrer le visage sans modifier la forme du cadre.',
63
+ },
64
+ {
65
+ question: 'Puis-je ajouter un nom et un niveau de personnage ?',
66
+ answer: 'Oui. Ajoutez autant d\'étiquettes courtes que nécessaire, ajustez leur taille et leur alignement, puis placez-les sur une zone lisible du jeton.',
67
+ },
68
+ {
69
+ question: 'Le fichier PNG conserve-t-il la transparence autour du jeton ?',
70
+ answer: 'Oui. L\'image exportée garde le contour de la silhouette transparent, ce qui permet de placer facilement le jeton sur n\'importe quelle carte de jeu.',
71
+ },
72
+ {
73
+ question: 'Puis-je créer les jetons de toute mon équipe d\'un coup ?',
74
+ answer: 'Oui. Chargez plusieurs portraits dans l\'exportation en lot, conservez le style de cadre et téléchargez un fichier PNG transparent pour chaque portrait.',
75
+ },
76
+ {
77
+ question: 'Où sont stockés mes marqueurs enregistrés ?',
78
+ answer: 'La sauvegarde locale conserve vos marqueurs dans le navigateur de votre appareil. Aucune image n\'est envoyée vers un serveur distant.',
79
+ },
80
+ ];
81
+
82
+ const howTo = [
83
+ { name: 'Choisissez la silhouette du personnage', text: 'Sélectionnez le cadre adapté au rôle: rond pour un héros classique, hexagonal pour le combat tactique ou étoile pour un boss.' },
84
+ { name: 'Importez et cadrez le portrait', text: 'Choisissez un fichier ou glissez-le sur le jeton. Ajustez la position du visage et le niveau de zoom.' },
85
+ { name: 'Ajoutez les informations utiles pour la table', text: 'Indiquez un nom court, un niveau ou un état. Gardez les textes brefs pour ne pas masquer l\'illustration.' },
86
+ { name: 'Enregistrez et exportez en PNG', text: 'Votre marqueur s\'enregistre automatiquement. Téléchargez un PNG transparent prêt à l\'emploi pour votre table virtuelle.' },
87
+ ];
88
+
89
+ const faqSchema: WithContext<FAQPage> = {
90
+ '@context': 'https://schema.org',
91
+ '@type': 'FAQPage',
92
+ mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })),
93
+ };
94
+
95
+ const appSchema: WithContext<SoftwareApplication> = {
96
+ '@context': 'https://schema.org',
97
+ '@type': 'SoftwareApplication',
98
+ name: 'Token Stamp Studio',
99
+ operatingSystem: 'All',
100
+ applicationCategory: 'DesignApplication',
101
+ description: 'Créez des jetons de personnage lisibles pour jeux de rôle sur table avec cadres personnalisés, étiquettes, transparence et export PNG en lot.',
102
+ };
103
+
104
+ const howToSchema: WithContext<HowTo> = {
105
+ '@context': 'https://schema.org',
106
+ '@type': 'HowTo',
107
+ name: 'Comment créer un jeton de personnage pour JDR',
108
+ step: howTo.map((item) => ({ '@type': 'HowToStep', name: item.name, text: item.text })),
109
+ };
110
+
111
+ export const content: TokenStampLocaleContent = {
112
+ slug: 'createur-tokens-personnages-jdr-table',
113
+ title: 'Créateur de Tokens et Jetons de Personnage pour JDR',
114
+ description: 'Créez des jetons de personnage lisibles pour jeux de rôle sur table avec cadres personnalisés, étiquettes, transparence et export PNG en lot.',
115
+ ui,
116
+ seo: [
117
+ { type: 'title', text: 'Concevez des jetons de personnage lisibles sur vos cartes de jeu', level: 2 },
118
+ { type: 'paragraph', html: 'Un jeton est un outil de communication visuelle à la table de jeu. Sa silhouette indique immédiatement au groupe s\'il s\'agit d\'un héros, d\'un monstre ou d\'un PNJ. Choisissez le cadre approprié et gardez le visage et les textes parfaitement lisibles à petite échelle.' },
119
+ { type: 'title', text: 'Choisissez le bon cadre selon la nature du personnage', level: 2 },
120
+ { type: 'list', items: ['<strong>Cercle:</strong> classique et efficace pour les personnages joueurs et alliés.', '<strong>Hexagone ou octogone:</strong> idéal pour distinguer les unités dans les combats tactiques.', '<strong>Étoile:</strong> parfait pour les boss, champions ou cibles prioritaires.', '<strong>Nuage:</strong> adapté aux esprits, familiers et créatures féeriques.'] },
121
+ { type: 'tip', title: 'Concevez pour la taille minimale utilisée en jeu', html: 'Dézoomez la carte jusqu\'à ce que le jeton ne fasse plus que quelques dizaines de pixels. Si le visage et le cadre restent distincts, votre jeton sera parfait en cours de partie.' },
122
+ { type: 'title', text: 'Des étiquettes courtes et efficaces autour de la table', level: 2 },
123
+ { type: 'paragraph', html: 'Un jeton ne doit pas remplacer une feuille de personnage complète. Ajoutez seulement une ou deux informations essentielles: un nom court, un niveau ou un état. Les phrases longues créent du bruit visuel inutilisable.' },
124
+ { type: 'title', text: 'Préparez toute une équipe sans perdre leur identité', level: 2 },
125
+ { type: 'paragraph', html: 'L\'exportation en lot permet de traiter l\'ensemble du groupe après une session zéro. Appliquez un cadre commun pour l\'unité visuelle tout en laissant chaque illustration exprimer la personnalité du personnage.' },
126
+ { type: 'tip', title: 'Sauvegarde automatique dans votre navigateur', html: 'Le jeton actif est sauvegardé localement sur votre appareil. Vous pouvez y revenir à tout moment pour modifier un niveau ou un état au cours de la campagne.' },
127
+ ],
128
+ faq,
129
+ bibliography,
130
+ howTo,
131
+ schemas: [faqSchema, appSchema, howToSchema] as unknown as Record<string, unknown>[],
132
+ };