@carmaclouds/core 2.3.1

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 (118) hide show
  1. package/dist/cache/CacheManager.d.ts.map +1 -0
  2. package/dist/cache/CacheManager.js +131 -0
  3. package/dist/cache/CacheManager.js.map +1 -0
  4. package/dist/index.d.ts +18 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +22 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/ir/index.d.ts +11 -0
  9. package/dist/ir/index.d.ts.map +1 -0
  10. package/dist/ir/index.js +9 -0
  11. package/dist/ir/index.js.map +1 -0
  12. package/dist/ir/normalize.d.ts +10 -0
  13. package/dist/ir/normalize.d.ts.map +1 -0
  14. package/dist/ir/normalize.js +207 -0
  15. package/dist/ir/normalize.js.map +1 -0
  16. package/dist/ir/persistence.d.ts +26 -0
  17. package/dist/ir/persistence.d.ts.map +1 -0
  18. package/dist/ir/persistence.js +21 -0
  19. package/dist/ir/persistence.js.map +1 -0
  20. package/dist/ir/sync.d.ts +12 -0
  21. package/dist/ir/sync.d.ts.map +1 -0
  22. package/dist/ir/sync.js +36 -0
  23. package/dist/ir/sync.js.map +1 -0
  24. package/dist/ir/types.d.ts +143 -0
  25. package/dist/ir/types.d.ts.map +1 -0
  26. package/dist/ir/types.js +13 -0
  27. package/dist/ir/types.js.map +1 -0
  28. package/dist/ir/views/dnd5e.d.ts +40 -0
  29. package/dist/ir/views/dnd5e.d.ts.map +1 -0
  30. package/dist/ir/views/dnd5e.js +50 -0
  31. package/dist/ir/views/dnd5e.js.map +1 -0
  32. package/dist/render/character.d.ts +19 -0
  33. package/dist/render/character.d.ts.map +1 -0
  34. package/dist/render/character.js +156 -0
  35. package/dist/render/character.js.map +1 -0
  36. package/dist/render/h.d.ts +27 -0
  37. package/dist/render/h.d.ts.map +1 -0
  38. package/dist/render/h.js +64 -0
  39. package/dist/render/h.js.map +1 -0
  40. package/dist/render/index.d.ts +11 -0
  41. package/dist/render/index.d.ts.map +1 -0
  42. package/dist/render/index.js +8 -0
  43. package/dist/render/index.js.map +1 -0
  44. package/dist/render/mount.d.ts +31 -0
  45. package/dist/render/mount.d.ts.map +1 -0
  46. package/dist/render/mount.js +63 -0
  47. package/dist/render/mount.js.map +1 -0
  48. package/dist/supabase/fields.d.ts.map +1 -0
  49. package/dist/supabase/fields.js +120 -0
  50. package/dist/supabase/fields.js.map +1 -0
  51. package/dist/types/character.d.ts.map +1 -0
  52. package/dist/types/character.js +5 -0
  53. package/dist/types/character.js.map +1 -0
  54. package/package.json +73 -0
  55. package/src/browser.js +51 -0
  56. package/src/cache/CacheManager.ts +174 -0
  57. package/src/common/browser-polyfill.js +319 -0
  58. package/src/common/debug.js +123 -0
  59. package/src/common/html-utils.js +134 -0
  60. package/src/common/theme-manager.js +265 -0
  61. package/src/index.ts +25 -0
  62. package/src/ir/__fixtures__/dnd5e-character.json +75962 -0
  63. package/src/ir/__fixtures__/non-dnd-character.json +14218 -0
  64. package/src/ir/index.ts +10 -0
  65. package/src/ir/normalize.ts +245 -0
  66. package/src/ir/persistence.ts +37 -0
  67. package/src/ir/sync.ts +49 -0
  68. package/src/ir/types.ts +161 -0
  69. package/src/ir/views/dnd5e.ts +94 -0
  70. package/src/lib/indexeddb-cache.js +320 -0
  71. package/src/modules/action-announcements.js +102 -0
  72. package/src/modules/action-display.js +1557 -0
  73. package/src/modules/action-executor.js +860 -0
  74. package/src/modules/action-filters.js +167 -0
  75. package/src/modules/action-options.js +117 -0
  76. package/src/modules/card-creator.js +142 -0
  77. package/src/modules/character-portrait.js +169 -0
  78. package/src/modules/character-trait-popups.js +959 -0
  79. package/src/modules/character-traits.js +814 -0
  80. package/src/modules/class-feature-edge-cases.js +1320 -0
  81. package/src/modules/color-utils.js +69 -0
  82. package/src/modules/combat-maneuver-edge-cases.js +660 -0
  83. package/src/modules/companions-manager.js +178 -0
  84. package/src/modules/concentration-tracker.js +178 -0
  85. package/src/modules/data-manager.js +514 -0
  86. package/src/modules/dice-roller.js +719 -0
  87. package/src/modules/effects-manager.js +743 -0
  88. package/src/modules/feature-modals.js +1264 -0
  89. package/src/modules/formula-resolver.js +444 -0
  90. package/src/modules/gm-mode.js +184 -0
  91. package/src/modules/health-modals.js +399 -0
  92. package/src/modules/hp-management.js +752 -0
  93. package/src/modules/inventory-manager.js +242 -0
  94. package/src/modules/macro-system.js +825 -0
  95. package/src/modules/notification-system.js +92 -0
  96. package/src/modules/racial-feature-edge-cases.js +746 -0
  97. package/src/modules/resource-manager.js +775 -0
  98. package/src/modules/sheet-builder.js +654 -0
  99. package/src/modules/spell-action-modals.js +583 -0
  100. package/src/modules/spell-cards.js +602 -0
  101. package/src/modules/spell-casting.js +723 -0
  102. package/src/modules/spell-display.js +314 -0
  103. package/src/modules/spell-edge-cases.js +509 -0
  104. package/src/modules/spell-macros.js +201 -0
  105. package/src/modules/spell-modals.js +1221 -0
  106. package/src/modules/spell-slots.js +224 -0
  107. package/src/modules/status-bar-bridge.js +101 -0
  108. package/src/modules/ui-utilities.js +284 -0
  109. package/src/modules/warlock-invocations.js +219 -0
  110. package/src/modules/window-management.js +211 -0
  111. package/src/render/character.ts +234 -0
  112. package/src/render/h.ts +74 -0
  113. package/src/render/index.ts +10 -0
  114. package/src/render/mount.ts +94 -0
  115. package/src/supabase/client.js +1383 -0
  116. package/src/supabase/config.js +60 -0
  117. package/src/supabase/fields.ts +129 -0
  118. package/src/types/character.ts +85 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Companions Manager Module
3
+ *
4
+ * Handles companion/familiar display and interaction.
5
+ * - Displays companion stats, abilities, and actions
6
+ * - Provides attack and damage roll buttons for companion actions
7
+ *
8
+ * Loaded as a plain script (no ES6 modules) to export to window.
9
+ */
10
+
11
+ (function() {
12
+ 'use strict';
13
+
14
+ /**
15
+ * Build and display companions list
16
+ * @param {Array} companions - Array of companion objects
17
+ */
18
+ function buildCompanionsDisplay(companions) {
19
+ const container = document.getElementById('companions-container');
20
+ const section = document.getElementById('companions-section');
21
+
22
+ // Show the companions section
23
+ section.style.display = 'block';
24
+
25
+ container.innerHTML = '';
26
+
27
+ companions.forEach(companion => {
28
+ debug.log('🔍 DEBUG: Companion object in popup:', companion);
29
+ debug.log('🔍 DEBUG: Companion abilities:', companion.abilities);
30
+ debug.log('🔍 DEBUG: Companion abilities keys:', Object.keys(companion.abilities));
31
+
32
+ const companionCard = document.createElement('div');
33
+ companionCard.className = 'action-card';
34
+ companionCard.style.background = 'var(--bg-card)';
35
+ companionCard.style.borderColor = 'var(--border-card)';
36
+
37
+ // Header with name and basic info
38
+ const header = document.createElement('div');
39
+ header.className = 'action-header';
40
+ header.style.cursor = 'pointer';
41
+
42
+ const nameDiv = document.createElement('div');
43
+ nameDiv.innerHTML = `
44
+ <div class="action-name">🐾 ${companion.name}</div>
45
+ <div style="font-size: 0.85em; color: var(--text-secondary); font-style: italic;">
46
+ ${companion.size} ${companion.type}${companion.alignment ? ', ' + companion.alignment : ''}
47
+ </div>
48
+ `;
49
+
50
+ header.appendChild(nameDiv);
51
+ companionCard.appendChild(header);
52
+
53
+ // Stats block
54
+ const statsDiv = document.createElement('div');
55
+ statsDiv.className = 'action-description expanded';
56
+ statsDiv.style.display = 'block';
57
+ statsDiv.style.background = 'var(--bg-secondary)';
58
+ statsDiv.style.padding = '12px';
59
+ statsDiv.style.borderRadius = '4px';
60
+ statsDiv.style.marginTop = '10px';
61
+
62
+ let statsHTML = '<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 10px;">';
63
+
64
+ // AC, HP, Speed
65
+ if (companion.ac) statsHTML += `<div><strong>AC:</strong> ${companion.ac}</div>`;
66
+ if (companion.hp) statsHTML += `<div><strong>HP:</strong> ${companion.hp}</div>`;
67
+ if (companion.speed) statsHTML += `<div style="grid-column: span 3;"><strong>Speed:</strong> ${companion.speed}</div>`;
68
+
69
+ statsHTML += '</div>';
70
+
71
+ // Abilities
72
+ if (Object.keys(companion.abilities).length > 0) {
73
+ statsHTML += '<div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; text-align: center; margin: 10px 0; padding: 8px; background: var(--bg-tertiary); border-radius: 4px;">';
74
+ ['str', 'dex', 'con', 'int', 'wis', 'cha'].forEach(ability => {
75
+ if (companion.abilities[ability]) {
76
+ const abil = companion.abilities[ability];
77
+ statsHTML += `
78
+ <div>
79
+ <div style="font-weight: bold; font-size: 0.75em; color: var(--text-secondary);">${ability.toUpperCase()}</div>
80
+ <div style="font-size: 1.1em; color: var(--text-primary);">${abil.score}</div>
81
+ <div style="font-size: 0.9em; color: var(--accent-success);">(${abil.modifier >= 0 ? '+' : ''}${abil.modifier})</div>
82
+ </div>
83
+ `;
84
+ }
85
+ });
86
+ statsHTML += '</div>';
87
+ }
88
+
89
+ // Senses, Languages, PB
90
+ if (companion.senses) statsHTML += `<div style="margin: 5px 0; color: var(--text-primary);"><strong>Senses:</strong> ${companion.senses}</div>`;
91
+ if (companion.languages) statsHTML += `<div style="margin: 5px 0; color: var(--text-primary);"><strong>Languages:</strong> ${companion.languages}</div>`;
92
+ if (companion.proficiencyBonus) statsHTML += `<div style="margin: 5px 0; color: var(--text-primary);"><strong>Proficiency Bonus:</strong> +${companion.proficiencyBonus}</div>`;
93
+
94
+ // Features
95
+ if (companion.features && companion.features.length > 0) {
96
+ statsHTML += '<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-color);">';
97
+ companion.features.forEach(feature => {
98
+ statsHTML += `<div style="margin: 8px 0; color: var(--text-primary);"><strong>${feature.name}.</strong> ${feature.description}</div>`;
99
+ });
100
+ statsHTML += '</div>';
101
+ }
102
+
103
+ // Actions with attack buttons
104
+ if (companion.actions && companion.actions.length > 0) {
105
+ statsHTML += '<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-color); color: var(--text-primary);"><strong>Actions</strong></div>';
106
+ companion.actions.forEach(action => {
107
+ statsHTML += `
108
+ <div style="margin: 10px 0; padding: 8px; background: var(--bg-action); border: 1px solid var(--accent-danger); border-radius: 4px;">
109
+ <div style="display: flex; justify-content: space-between; align-items: center;">
110
+ <div style="color: var(--text-primary);">
111
+ <strong>${action.name}.</strong> Melee Weapon Attack: +${action.attackBonus} to hit, ${action.reach}. <em>Hit:</em> ${action.damage}
112
+ </div>
113
+ <div style="display: flex; gap: 8px;">
114
+ <button class="attack-btn companion-attack-btn" data-name="${companion.name} - ${action.name}" data-bonus="${action.attackBonus}">⚔️ Attack</button>
115
+ <button class="damage-btn companion-damage-btn" data-name="${companion.name} - ${action.name}" data-damage="${action.damage}">💥 Damage</button>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ `;
120
+ });
121
+ }
122
+
123
+ statsDiv.innerHTML = statsHTML;
124
+ companionCard.appendChild(statsDiv);
125
+
126
+ // Add event listeners for attack/damage buttons
127
+ companionCard.querySelectorAll('.companion-attack-btn').forEach(btn => {
128
+ btn.addEventListener('click', (e) => {
129
+ e.stopPropagation();
130
+ const name = btn.dataset.name;
131
+ const bonus = parseInt(btn.dataset.bonus);
132
+
133
+ // Announce companion attack
134
+ const announcement = `&{template:default} {{name=${getColoredBanner(characterData)}${characterData.name}'s ${name} attacks!}} {{Type=Companion Attack}}`;
135
+ const messageData = {
136
+ action: 'announceSpell',
137
+ message: announcement,
138
+ color: characterData.notificationColor
139
+ };
140
+
141
+ sendToRoll20(messageData);
142
+
143
+ roll(`${name} - Attack`, `1d20+${bonus}`);
144
+ });
145
+ });
146
+
147
+ companionCard.querySelectorAll('.companion-damage-btn').forEach(btn => {
148
+ btn.addEventListener('click', (e) => {
149
+ e.stopPropagation();
150
+ const name = btn.dataset.name;
151
+ const damage = btn.dataset.damage;
152
+
153
+ // Announce companion damage
154
+ const announcement = `&{template:default} {{name=${getColoredBanner(characterData)}${characterData.name}'s ${name} deals damage!}} {{Type=Companion Damage}}`;
155
+ const messageData = {
156
+ action: 'announceSpell',
157
+ message: announcement,
158
+ color: characterData.notificationColor
159
+ };
160
+
161
+ sendToRoll20(messageData);
162
+
163
+ roll(`${name} - Damage`, damage);
164
+ });
165
+ });
166
+
167
+ container.appendChild(companionCard);
168
+ });
169
+ }
170
+
171
+ // Export function to globalThis
172
+ Object.assign(globalThis, {
173
+ buildCompanionsDisplay
174
+ });
175
+
176
+ console.log('✅ Companions Manager module loaded');
177
+
178
+ })();
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Concentration Tracker Module
3
+ *
4
+ * Manages spell concentration mechanics:
5
+ * - Tracks which spell the character is concentrating on
6
+ * - Displays concentration indicator UI
7
+ * - Handles dropping concentration
8
+ * - Auto-hides for non-spellcasters
9
+ *
10
+ * Concentration is a core D&D 5e mechanic where certain spells require
11
+ * ongoing focus to maintain their effects. Only one concentration spell
12
+ * can be active at a time.
13
+ *
14
+ * Loaded as a plain script (no ES6 modules) to export to globalThis.
15
+ *
16
+ * Functions exported to globalThis:
17
+ * - updateConcentrationDisplay()
18
+ * - initConcentrationTracker()
19
+ * - setConcentration(spellName)
20
+ * - dropConcentration()
21
+ *
22
+ * State exported to globalThis:
23
+ * - concentratingSpell (via getter/setter)
24
+ */
25
+
26
+ (function() {
27
+ 'use strict';
28
+
29
+ // Track concentration state
30
+ let concentratingSpell = null;
31
+
32
+ /**
33
+ * Update the concentration indicator UI
34
+ * Shows/hides the concentration banner based on current state
35
+ * Auto-hides for characters without spell slots (e.g., rogues, fighters)
36
+ */
37
+ function updateConcentrationDisplay() {
38
+ const concentrationIndicator = document.getElementById('concentration-indicator');
39
+ const concentrationSpell = document.getElementById('concentration-spell');
40
+
41
+ if (!concentrationIndicator || !concentrationSpell) return;
42
+
43
+ // Hide concentration row if character has no spell slots (e.g., rogues)
44
+ if (characterData && characterData.spellSlots) {
45
+ const spellSlots = characterData.spellSlots;
46
+ let hasSpellSlots = false;
47
+
48
+ // Check for regular spell slots (levels 1-9)
49
+ for (let level = 1; level <= 9; level++) {
50
+ if ((spellSlots[`level${level}SpellSlotsMax`] || 0) > 0) {
51
+ hasSpellSlots = true;
52
+ break;
53
+ }
54
+ }
55
+
56
+ // Also check for pact magic (warlocks)
57
+ if ((spellSlots.pactMagicSlotsMax || 0) > 0) {
58
+ hasSpellSlots = true;
59
+ }
60
+
61
+ // If no spell slots, hide the concentration tracker entirely
62
+ if (!hasSpellSlots) {
63
+ concentrationIndicator.style.display = 'none';
64
+ return;
65
+ }
66
+ }
67
+
68
+ // Show/hide based on concentration state
69
+ if (concentratingSpell) {
70
+ concentrationIndicator.style.display = 'flex';
71
+ if (concentrationSpell) {
72
+ concentrationSpell.textContent = concentratingSpell;
73
+ }
74
+ } else {
75
+ concentrationIndicator.style.display = 'none';
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Initialize concentration tracker button
81
+ * Sets up click handler for the "Drop Concentration" button
82
+ */
83
+ function initConcentrationTracker() {
84
+ const dropConcentrationBtn = document.getElementById('drop-concentration-btn');
85
+
86
+ if (dropConcentrationBtn) {
87
+ dropConcentrationBtn.addEventListener('click', () => {
88
+ dropConcentration();
89
+ });
90
+ }
91
+
92
+ debug.log('✅ Concentration tracker initialized');
93
+ }
94
+
95
+ /**
96
+ * Set concentration on a spell
97
+ * Automatically drops concentration on any previous spell
98
+ * @param {string} spellName - Name of the spell to concentrate on
99
+ */
100
+ function setConcentration(spellName) {
101
+ debug.log(`🧠 setConcentration called with: "${spellName}"`);
102
+ debug.log(`🧠 Current concentration: "${concentratingSpell}"`);
103
+
104
+ // Track if this is a replacement
105
+ const isReplacement = concentratingSpell && concentratingSpell !== spellName;
106
+
107
+ // If already concentrating on a different spell, notify that it's being dropped
108
+ if (isReplacement) {
109
+ const previousSpell = concentratingSpell;
110
+ showNotification(`⚠️ Dropped concentration on ${previousSpell} to concentrate on ${spellName}`);
111
+ debug.log(`🔄 Replacing concentration: ${previousSpell} → ${spellName}`);
112
+ }
113
+
114
+ concentratingSpell = spellName;
115
+ debug.log(`🧠 Updated concentratingSpell variable to: "${concentratingSpell}"`);
116
+
117
+ if (characterData) {
118
+ characterData.concentrationSpell = spellName;
119
+ debug.log(`🧠 Updated characterData.concentrationSpell to: "${spellName}"`);
120
+ saveCharacterData();
121
+ }
122
+
123
+ updateConcentrationDisplay();
124
+ debug.log(`🧠 Called updateConcentrationDisplay()`);
125
+
126
+ // Update status bar if available
127
+ if (typeof sendStatusUpdate === 'function') {
128
+ sendStatusUpdate();
129
+ }
130
+
131
+ // Only show "Concentrating on" notification if this is a new concentration (not replacement)
132
+ if (!isReplacement) {
133
+ showNotification(`🧠 Concentrating on: ${spellName}`);
134
+ }
135
+ debug.log(`🧠 Concentration set complete: ${spellName}`);
136
+ }
137
+
138
+ /**
139
+ * Drop concentration on the current spell
140
+ */
141
+ function dropConcentration() {
142
+ if (!concentratingSpell) return;
143
+
144
+ const spellName = concentratingSpell;
145
+ concentratingSpell = null;
146
+ if (characterData) {
147
+ characterData.concentrationSpell = null;
148
+ saveCharacterData();
149
+ }
150
+ updateConcentrationDisplay();
151
+
152
+ // Update status bar if available
153
+ if (typeof sendStatusUpdate === 'function') {
154
+ sendStatusUpdate();
155
+ }
156
+
157
+ showNotification(`✅ Dropped concentration on ${spellName}`);
158
+ debug.log(`🗑️ Concentration dropped: ${spellName}`);
159
+ }
160
+
161
+ // ===== EXPORTS =====
162
+
163
+ // Export concentratingSpell state variable to window (for access from content script context)
164
+ Object.defineProperty(window, 'concentratingSpell', {
165
+ get: () => concentratingSpell,
166
+ set: (value) => { concentratingSpell = value; },
167
+ configurable: true
168
+ });
169
+
170
+ // Export functions to window
171
+ window.updateConcentrationDisplay = updateConcentrationDisplay;
172
+ window.initConcentrationTracker = initConcentrationTracker;
173
+ window.setConcentration = setConcentration;
174
+ window.dropConcentration = dropConcentration;
175
+
176
+ debug.log('✅ Concentration Tracker module loaded');
177
+
178
+ })();