@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.
- package/dist/cache/CacheManager.d.ts.map +1 -0
- package/dist/cache/CacheManager.js +131 -0
- package/dist/cache/CacheManager.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/index.d.ts +11 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +9 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/normalize.d.ts +10 -0
- package/dist/ir/normalize.d.ts.map +1 -0
- package/dist/ir/normalize.js +207 -0
- package/dist/ir/normalize.js.map +1 -0
- package/dist/ir/persistence.d.ts +26 -0
- package/dist/ir/persistence.d.ts.map +1 -0
- package/dist/ir/persistence.js +21 -0
- package/dist/ir/persistence.js.map +1 -0
- package/dist/ir/sync.d.ts +12 -0
- package/dist/ir/sync.d.ts.map +1 -0
- package/dist/ir/sync.js +36 -0
- package/dist/ir/sync.js.map +1 -0
- package/dist/ir/types.d.ts +143 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/ir/types.js +13 -0
- package/dist/ir/types.js.map +1 -0
- package/dist/ir/views/dnd5e.d.ts +40 -0
- package/dist/ir/views/dnd5e.d.ts.map +1 -0
- package/dist/ir/views/dnd5e.js +50 -0
- package/dist/ir/views/dnd5e.js.map +1 -0
- package/dist/render/character.d.ts +19 -0
- package/dist/render/character.d.ts.map +1 -0
- package/dist/render/character.js +156 -0
- package/dist/render/character.js.map +1 -0
- package/dist/render/h.d.ts +27 -0
- package/dist/render/h.d.ts.map +1 -0
- package/dist/render/h.js +64 -0
- package/dist/render/h.js.map +1 -0
- package/dist/render/index.d.ts +11 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +8 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/mount.d.ts +31 -0
- package/dist/render/mount.d.ts.map +1 -0
- package/dist/render/mount.js +63 -0
- package/dist/render/mount.js.map +1 -0
- package/dist/supabase/fields.d.ts.map +1 -0
- package/dist/supabase/fields.js +120 -0
- package/dist/supabase/fields.js.map +1 -0
- package/dist/types/character.d.ts.map +1 -0
- package/dist/types/character.js +5 -0
- package/dist/types/character.js.map +1 -0
- package/package.json +73 -0
- package/src/browser.js +51 -0
- package/src/cache/CacheManager.ts +174 -0
- package/src/common/browser-polyfill.js +319 -0
- package/src/common/debug.js +123 -0
- package/src/common/html-utils.js +134 -0
- package/src/common/theme-manager.js +265 -0
- package/src/index.ts +25 -0
- package/src/ir/__fixtures__/dnd5e-character.json +75962 -0
- package/src/ir/__fixtures__/non-dnd-character.json +14218 -0
- package/src/ir/index.ts +10 -0
- package/src/ir/normalize.ts +245 -0
- package/src/ir/persistence.ts +37 -0
- package/src/ir/sync.ts +49 -0
- package/src/ir/types.ts +161 -0
- package/src/ir/views/dnd5e.ts +94 -0
- package/src/lib/indexeddb-cache.js +320 -0
- package/src/modules/action-announcements.js +102 -0
- package/src/modules/action-display.js +1557 -0
- package/src/modules/action-executor.js +860 -0
- package/src/modules/action-filters.js +167 -0
- package/src/modules/action-options.js +117 -0
- package/src/modules/card-creator.js +142 -0
- package/src/modules/character-portrait.js +169 -0
- package/src/modules/character-trait-popups.js +959 -0
- package/src/modules/character-traits.js +814 -0
- package/src/modules/class-feature-edge-cases.js +1320 -0
- package/src/modules/color-utils.js +69 -0
- package/src/modules/combat-maneuver-edge-cases.js +660 -0
- package/src/modules/companions-manager.js +178 -0
- package/src/modules/concentration-tracker.js +178 -0
- package/src/modules/data-manager.js +514 -0
- package/src/modules/dice-roller.js +719 -0
- package/src/modules/effects-manager.js +743 -0
- package/src/modules/feature-modals.js +1264 -0
- package/src/modules/formula-resolver.js +444 -0
- package/src/modules/gm-mode.js +184 -0
- package/src/modules/health-modals.js +399 -0
- package/src/modules/hp-management.js +752 -0
- package/src/modules/inventory-manager.js +242 -0
- package/src/modules/macro-system.js +825 -0
- package/src/modules/notification-system.js +92 -0
- package/src/modules/racial-feature-edge-cases.js +746 -0
- package/src/modules/resource-manager.js +775 -0
- package/src/modules/sheet-builder.js +654 -0
- package/src/modules/spell-action-modals.js +583 -0
- package/src/modules/spell-cards.js +602 -0
- package/src/modules/spell-casting.js +723 -0
- package/src/modules/spell-display.js +314 -0
- package/src/modules/spell-edge-cases.js +509 -0
- package/src/modules/spell-macros.js +201 -0
- package/src/modules/spell-modals.js +1221 -0
- package/src/modules/spell-slots.js +224 -0
- package/src/modules/status-bar-bridge.js +101 -0
- package/src/modules/ui-utilities.js +284 -0
- package/src/modules/warlock-invocations.js +219 -0
- package/src/modules/window-management.js +211 -0
- package/src/render/character.ts +234 -0
- package/src/render/h.ts +74 -0
- package/src/render/index.ts +10 -0
- package/src/render/mount.ts +94 -0
- package/src/supabase/client.js +1383 -0
- package/src/supabase/config.js +60 -0
- package/src/supabase/fields.ts +129 -0
- package/src/types/character.ts +85 -0
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character Trait Popups Module
|
|
3
|
+
*
|
|
4
|
+
* Handles all character trait popup UI (Halfling Luck, Lucky, Elven Accuracy, etc.).
|
|
5
|
+
* Loaded as a plain script (no ES6 modules) to export to globalThis.
|
|
6
|
+
*
|
|
7
|
+
* Functions exported to globalThis:
|
|
8
|
+
* - getPopupThemeColors()
|
|
9
|
+
* - showHalflingLuckPopup(rollData)
|
|
10
|
+
* - showLuckyPopup(rollData)
|
|
11
|
+
* - showTraitChoicePopup(rollData)
|
|
12
|
+
* - showWildMagicSurgePopup(d100Roll, effect)
|
|
13
|
+
* - showBardicInspirationPopup(rollData)
|
|
14
|
+
* - showElvenAccuracyPopup(rollData)
|
|
15
|
+
* - performHalflingReroll(originalRollData)
|
|
16
|
+
* - performLuckyReroll(originalRollData)
|
|
17
|
+
* - performBardicInspirationRoll(rollData)
|
|
18
|
+
* - performElvenAccuracyReroll(originalRollData)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
(function() {
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
// ===== HELPER FUNCTIONS =====
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get theme-aware colors for popups
|
|
28
|
+
* @returns {Object} Color scheme based on current theme
|
|
29
|
+
*/
|
|
30
|
+
function getPopupThemeColors() {
|
|
31
|
+
const isDarkMode = document.documentElement.classList.contains('theme-dark') ||
|
|
32
|
+
document.documentElement.getAttribute('data-theme') === 'dark';
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
background: isDarkMode ? '#2d2d2d' : '#ffffff',
|
|
36
|
+
text: isDarkMode ? '#e0e0e0' : '#333333',
|
|
37
|
+
heading: isDarkMode ? '#ffffff' : '#2D8B83',
|
|
38
|
+
border: isDarkMode ? '#444444' : '#f0f8ff',
|
|
39
|
+
borderAccent: isDarkMode ? '#2D8B83' : '#2D8B83',
|
|
40
|
+
infoBox: isDarkMode ? '#1a1a1a' : '#f0f8ff',
|
|
41
|
+
infoText: isDarkMode ? '#b0b0b0' : '#666666'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ===== HALFLING LUCK POPUP =====
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Show Halfling Luck popup when rolling a natural 1
|
|
49
|
+
* @param {Object} rollData - Roll information
|
|
50
|
+
*/
|
|
51
|
+
function showHalflingLuckPopup(rollData) {
|
|
52
|
+
debug.log('🍀 Halfling Luck popup called with:', rollData);
|
|
53
|
+
|
|
54
|
+
// Check if document.body exists
|
|
55
|
+
if (!document.body) {
|
|
56
|
+
debug.error('❌ document.body not available for Halfling Luck popup');
|
|
57
|
+
showNotification('🍀 Halfling Luck triggered! (Popup failed to display)', 'info');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
debug.log('🍀 Creating popup overlay...');
|
|
62
|
+
|
|
63
|
+
// Get theme-aware colors
|
|
64
|
+
const colors = getPopupThemeColors();
|
|
65
|
+
|
|
66
|
+
// Create popup overlay
|
|
67
|
+
const popupOverlay = document.createElement('div');
|
|
68
|
+
popupOverlay.style.cssText = `
|
|
69
|
+
position: fixed;
|
|
70
|
+
top: 0;
|
|
71
|
+
left: 0;
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: 100%;
|
|
74
|
+
background: rgba(0, 0, 0, 0.8);
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
z-index: 10000;
|
|
79
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
// Create popup content
|
|
83
|
+
const popupContent = document.createElement('div');
|
|
84
|
+
popupContent.style.cssText = `
|
|
85
|
+
background: ${colors.background};
|
|
86
|
+
border-radius: 12px;
|
|
87
|
+
padding: 24px;
|
|
88
|
+
max-width: 400px;
|
|
89
|
+
width: 90%;
|
|
90
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
91
|
+
text-align: center;
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
debug.log('🍀 Setting popup content HTML...');
|
|
95
|
+
|
|
96
|
+
popupContent.innerHTML = `
|
|
97
|
+
<div style="font-size: 24px; margin-bottom: 16px;">🍀</div>
|
|
98
|
+
<h2 style="margin: 0 0 8px 0; color: ${colors.heading};">Halfling Luck!</h2>
|
|
99
|
+
<p style="margin: 0 0 16px 0; color: ${colors.text};">
|
|
100
|
+
You rolled a natural 1! As a Halfling, you can reroll this d20.
|
|
101
|
+
</p>
|
|
102
|
+
<div style="margin: 0 0 16px 0; padding: 12px; background: ${colors.infoBox}; border-radius: 8px; border-left: 4px solid ${colors.borderAccent}; color: ${colors.text};">
|
|
103
|
+
<strong>Original Roll:</strong> ${rollData.rollName}<br>
|
|
104
|
+
<strong>Result:</strong> ${rollData.baseRoll} (natural 1)<br>
|
|
105
|
+
<strong>Total:</strong> ${rollData.rollResult}
|
|
106
|
+
</div>
|
|
107
|
+
<div style="display: flex; gap: 12px; justify-content: center;">
|
|
108
|
+
<button id="halflingRerollBtn" style="
|
|
109
|
+
background: #2D8B83;
|
|
110
|
+
color: white;
|
|
111
|
+
border: none;
|
|
112
|
+
padding: 12px 24px;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
font-weight: bold;
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
">🎲 Reroll</button>
|
|
118
|
+
<button id="halflingKeepBtn" style="
|
|
119
|
+
background: #e74c3c;
|
|
120
|
+
color: white;
|
|
121
|
+
border: none;
|
|
122
|
+
padding: 12px 24px;
|
|
123
|
+
border-radius: 8px;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
font-weight: bold;
|
|
126
|
+
font-size: 14px;
|
|
127
|
+
">Keep Roll</button>
|
|
128
|
+
</div>
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
debug.log('🍀 Appending popup to document.body...');
|
|
132
|
+
|
|
133
|
+
popupOverlay.appendChild(popupContent);
|
|
134
|
+
document.body.appendChild(popupOverlay);
|
|
135
|
+
|
|
136
|
+
// Add event listeners
|
|
137
|
+
document.getElementById('halflingRerollBtn').addEventListener('click', () => {
|
|
138
|
+
debug.log('🍀 User chose to reroll');
|
|
139
|
+
performHalflingReroll(rollData);
|
|
140
|
+
document.body.removeChild(popupOverlay);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
document.getElementById('halflingKeepBtn').addEventListener('click', () => {
|
|
144
|
+
debug.log('🍀 User chose to keep roll');
|
|
145
|
+
document.body.removeChild(popupOverlay);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Close on overlay click
|
|
149
|
+
popupOverlay.addEventListener('click', (e) => {
|
|
150
|
+
if (e.target === popupOverlay) {
|
|
151
|
+
debug.log('🍀 User closed popup');
|
|
152
|
+
document.body.removeChild(popupOverlay);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
debug.log('🍀 Halfling Luck popup displayed');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Perform Halfling Luck reroll
|
|
161
|
+
* @param {Object} originalRollData - Original roll data
|
|
162
|
+
*/
|
|
163
|
+
function performHalflingReroll(originalRollData) {
|
|
164
|
+
debug.log('🍀 Performing Halfling reroll for:', originalRollData);
|
|
165
|
+
|
|
166
|
+
// Extract the base formula (remove any modifiers)
|
|
167
|
+
const formula = originalRollData.rollType;
|
|
168
|
+
const baseFormula = formula.split('+')[0]; // Get just the d20 part
|
|
169
|
+
|
|
170
|
+
// Create a new roll with just the d20
|
|
171
|
+
const rerollData = {
|
|
172
|
+
name: `🍀 ${originalRollData.rollName} (Halfling Luck)`,
|
|
173
|
+
formula: baseFormula,
|
|
174
|
+
color: '#2D8B83',
|
|
175
|
+
characterName: characterData.name
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
debug.log('🍀 Reroll data:', rerollData);
|
|
179
|
+
|
|
180
|
+
// TODO: Add Owlbear Rodeo integration for Halfling Luck rerolls
|
|
181
|
+
showNotification('🍀 Halfling Luck reroll initiated!', 'success');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ===== LUCKY FEAT POPUP =====
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Show Lucky Feat popup
|
|
188
|
+
* @param {Object} rollData - Roll information
|
|
189
|
+
*/
|
|
190
|
+
function showLuckyPopup(rollData) {
|
|
191
|
+
debug.log('🎖️ Lucky popup called with:', rollData);
|
|
192
|
+
|
|
193
|
+
// Check if document.body exists
|
|
194
|
+
if (!document.body) {
|
|
195
|
+
debug.error('❌ document.body not available for Lucky popup');
|
|
196
|
+
showNotification('🎖️ Lucky triggered! (Popup failed to display)', 'info');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
debug.log('🎖️ Creating Lucky popup overlay...');
|
|
201
|
+
|
|
202
|
+
// Get theme-aware colors
|
|
203
|
+
const colors = getPopupThemeColors();
|
|
204
|
+
|
|
205
|
+
// Create popup overlay
|
|
206
|
+
const popupOverlay = document.createElement('div');
|
|
207
|
+
popupOverlay.style.cssText = `
|
|
208
|
+
position: fixed;
|
|
209
|
+
top: 0;
|
|
210
|
+
left: 0;
|
|
211
|
+
right: 0;
|
|
212
|
+
bottom: 0;
|
|
213
|
+
background: rgba(0, 0, 0, 0.8);
|
|
214
|
+
display: flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
justify-content: center;
|
|
217
|
+
z-index: 10000;
|
|
218
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
// Create popup content
|
|
222
|
+
const popupContent = document.createElement('div');
|
|
223
|
+
popupContent.style.cssText = `
|
|
224
|
+
background: ${colors.background};
|
|
225
|
+
border-radius: 12px;
|
|
226
|
+
padding: 24px;
|
|
227
|
+
max-width: 400px;
|
|
228
|
+
width: 90%;
|
|
229
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
230
|
+
text-align: center;
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
debug.log('🎖️ Setting Lucky popup content HTML...');
|
|
234
|
+
|
|
235
|
+
popupContent.innerHTML = `
|
|
236
|
+
<div style="font-size: 24px; margin-bottom: 16px;">🎖️</div>
|
|
237
|
+
<h2 style="margin: 0 0 8px 0; color: #f39c12;">Lucky Feat!</h2>
|
|
238
|
+
<p style="margin: 0 0 16px 0; color: ${colors.text};">
|
|
239
|
+
You rolled a ${rollData.baseRoll}! You have ${rollData.luckPointsRemaining} luck points remaining.
|
|
240
|
+
</p>
|
|
241
|
+
<div style="margin: 0 0 16px 0; padding: 12px; background: ${colors.infoBox}; border-radius: 8px; border-left: 4px solid #f39c12; color: ${colors.text};">
|
|
242
|
+
<strong>Original Roll:</strong> ${rollData.rollName}<br>
|
|
243
|
+
<strong>Result:</strong> ${rollData.baseRoll}<br>
|
|
244
|
+
<strong>Luck Points:</strong> ${rollData.luckPointsRemaining}/3
|
|
245
|
+
</div>
|
|
246
|
+
<div style="display: flex; gap: 12px; justify-content: center;">
|
|
247
|
+
<button id="luckyRerollBtn" style="
|
|
248
|
+
background: #f39c12;
|
|
249
|
+
color: white;
|
|
250
|
+
border: none;
|
|
251
|
+
padding: 12px 24px;
|
|
252
|
+
border-radius: 8px;
|
|
253
|
+
cursor: pointer;
|
|
254
|
+
font-size: 16px;
|
|
255
|
+
font-weight: bold;
|
|
256
|
+
transition: background 0.2s;
|
|
257
|
+
">
|
|
258
|
+
🎲 Reroll (Use Luck Point)
|
|
259
|
+
</button>
|
|
260
|
+
<button id="luckyKeepBtn" style="
|
|
261
|
+
background: #95a5a6;
|
|
262
|
+
color: white;
|
|
263
|
+
border: none;
|
|
264
|
+
padding: 12px 24px;
|
|
265
|
+
border-radius: 8px;
|
|
266
|
+
cursor: pointer;
|
|
267
|
+
font-size: 16px;
|
|
268
|
+
font-weight: bold;
|
|
269
|
+
transition: background 0.2s;
|
|
270
|
+
">
|
|
271
|
+
Keep Roll
|
|
272
|
+
</button>
|
|
273
|
+
</div>
|
|
274
|
+
`;
|
|
275
|
+
|
|
276
|
+
popupOverlay.appendChild(popupContent);
|
|
277
|
+
document.body.appendChild(popupOverlay);
|
|
278
|
+
|
|
279
|
+
debug.log('🎖️ Appending Lucky popup to document.body...');
|
|
280
|
+
|
|
281
|
+
// Add event listeners
|
|
282
|
+
const rerollBtn = document.getElementById('luckyRerollBtn');
|
|
283
|
+
const keepBtn = document.getElementById('luckyKeepBtn');
|
|
284
|
+
|
|
285
|
+
// Add hover effects via event listeners (CSP-compliant)
|
|
286
|
+
rerollBtn.addEventListener('mouseenter', () => rerollBtn.style.background = '#e67e22');
|
|
287
|
+
rerollBtn.addEventListener('mouseleave', () => rerollBtn.style.background = '#f39c12');
|
|
288
|
+
keepBtn.addEventListener('mouseenter', () => keepBtn.style.background = '#7f8c8d');
|
|
289
|
+
keepBtn.addEventListener('mouseleave', () => keepBtn.style.background = '#95a5a6');
|
|
290
|
+
|
|
291
|
+
rerollBtn.addEventListener('click', () => {
|
|
292
|
+
if (useLuckyPoint()) {
|
|
293
|
+
performLuckyReroll(rollData);
|
|
294
|
+
popupOverlay.remove();
|
|
295
|
+
} else {
|
|
296
|
+
alert('No luck points available!');
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
keepBtn.addEventListener('click', () => {
|
|
301
|
+
popupOverlay.remove();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Close on overlay click
|
|
305
|
+
popupOverlay.addEventListener('click', (e) => {
|
|
306
|
+
if (e.target === popupOverlay) {
|
|
307
|
+
popupOverlay.remove();
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
debug.log('🎖️ Lucky popup displayed');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Perform Lucky reroll
|
|
316
|
+
* @param {Object} originalRollData - Original roll data
|
|
317
|
+
*/
|
|
318
|
+
function performLuckyReroll(originalRollData) {
|
|
319
|
+
debug.log('🎖️ Performing Lucky reroll for:', originalRollData);
|
|
320
|
+
|
|
321
|
+
// Extract base formula (remove modifiers for the reroll)
|
|
322
|
+
const baseFormula = originalRollData.rollType.replace(/[+-]\d+$/i, '');
|
|
323
|
+
|
|
324
|
+
// Create a new roll with just the d20
|
|
325
|
+
const rerollData = {
|
|
326
|
+
name: `🎖️ ${originalRollData.rollName} (Lucky Reroll)`,
|
|
327
|
+
formula: baseFormula,
|
|
328
|
+
color: '#f39c12',
|
|
329
|
+
characterName: characterData.name
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// TODO: Add Owlbear Rodeo integration for Lucky rerolls
|
|
333
|
+
showNotification('🎖️ Lucky reroll initiated!', 'success');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ===== TRAIT CHOICE POPUP =====
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Unified Trait Choice Popup (when multiple traits apply)
|
|
340
|
+
* @param {Object} rollData - Roll information with multiple traits
|
|
341
|
+
*/
|
|
342
|
+
function showTraitChoicePopup(rollData) {
|
|
343
|
+
debug.log('🎯 Trait choice popup called with:', rollData);
|
|
344
|
+
|
|
345
|
+
// Check if document.body exists
|
|
346
|
+
if (!document.body) {
|
|
347
|
+
debug.error('❌ document.body not available for trait choice popup');
|
|
348
|
+
showNotification('🎯 Trait choice triggered! (Popup failed to display)', 'info');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
debug.log('🎯 Creating trait choice overlay...');
|
|
353
|
+
|
|
354
|
+
// Get theme-aware colors
|
|
355
|
+
const colors = getPopupThemeColors();
|
|
356
|
+
|
|
357
|
+
// Create popup overlay
|
|
358
|
+
const popupOverlay = document.createElement('div');
|
|
359
|
+
popupOverlay.style.cssText = `
|
|
360
|
+
position: fixed;
|
|
361
|
+
top: 0;
|
|
362
|
+
left: 0;
|
|
363
|
+
right: 0;
|
|
364
|
+
bottom: 0;
|
|
365
|
+
background: rgba(0, 0, 0, 0.8);
|
|
366
|
+
display: flex;
|
|
367
|
+
align-items: center;
|
|
368
|
+
justify-content: center;
|
|
369
|
+
z-index: 10000;
|
|
370
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
371
|
+
`;
|
|
372
|
+
|
|
373
|
+
// Create popup content
|
|
374
|
+
const popupContent = document.createElement('div');
|
|
375
|
+
popupContent.style.cssText = `
|
|
376
|
+
background: ${colors.background};
|
|
377
|
+
border-radius: 12px;
|
|
378
|
+
padding: 24px;
|
|
379
|
+
max-width: 450px;
|
|
380
|
+
width: 90%;
|
|
381
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
382
|
+
text-align: center;
|
|
383
|
+
`;
|
|
384
|
+
|
|
385
|
+
// Build trait options HTML
|
|
386
|
+
let traitOptionsHTML = '';
|
|
387
|
+
const allTraits = [...rollData.racialTraits, ...rollData.featTraits];
|
|
388
|
+
|
|
389
|
+
allTraits.forEach((trait, index) => {
|
|
390
|
+
let icon = '';
|
|
391
|
+
let color = '';
|
|
392
|
+
let description = '';
|
|
393
|
+
|
|
394
|
+
if (trait.name === 'Halfling Luck') {
|
|
395
|
+
icon = '🍀';
|
|
396
|
+
color = '#2D8B83';
|
|
397
|
+
description = 'Reroll natural 1s (must use new roll)';
|
|
398
|
+
} else if (trait.name === 'Lucky') {
|
|
399
|
+
icon = '🎖️';
|
|
400
|
+
color = '#f39c12';
|
|
401
|
+
const luckyResource = getLuckyResource();
|
|
402
|
+
description = `Reroll any roll (${luckyResource?.current || 0}/3 points left)`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
traitOptionsHTML += `
|
|
406
|
+
<button class="trait-option-btn" data-trait-index="${index}" data-trait-color="${color}" style="
|
|
407
|
+
background: ${color};
|
|
408
|
+
color: white;
|
|
409
|
+
border: none;
|
|
410
|
+
padding: 16px;
|
|
411
|
+
border-radius: 8px;
|
|
412
|
+
cursor: pointer;
|
|
413
|
+
font-size: 16px;
|
|
414
|
+
font-weight: bold;
|
|
415
|
+
margin: 8px 0;
|
|
416
|
+
transition: transform 0.2s, background 0.2s;
|
|
417
|
+
width: 100%;
|
|
418
|
+
display: flex;
|
|
419
|
+
align-items: center;
|
|
420
|
+
justify-content: center;
|
|
421
|
+
gap: 12px;
|
|
422
|
+
">
|
|
423
|
+
<span style="font-size: 20px;">${icon}</span>
|
|
424
|
+
<div style="text-align: left;">
|
|
425
|
+
<div style="font-weight: bold;">${trait.name}</div>
|
|
426
|
+
<div style="font-size: 12px; opacity: 0.9;">${description}</div>
|
|
427
|
+
</div>
|
|
428
|
+
</button>
|
|
429
|
+
`;
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
debug.log('🎯 Setting trait choice popup content HTML...');
|
|
433
|
+
|
|
434
|
+
popupContent.innerHTML = `
|
|
435
|
+
<div style="font-size: 24px; margin-bottom: 16px;">🎯</div>
|
|
436
|
+
<h2 style="margin: 0 0 8px 0; color: ${colors.heading};">Multiple Traits Available!</h2>
|
|
437
|
+
<p style="margin: 0 0 16px 0; color: ${colors.text};">
|
|
438
|
+
You rolled a ${rollData.baseRoll}! Choose which trait to use:
|
|
439
|
+
</p>
|
|
440
|
+
<div style="margin: 0 0 16px 0; padding: 12px; background: ${colors.infoBox}; border-radius: 8px; border-left: 4px solid #3498db; color: ${colors.text};">
|
|
441
|
+
<strong>Original Roll:</strong> ${rollData.rollName}<br>
|
|
442
|
+
<strong>Result:</strong> ${rollData.baseRoll}<br>
|
|
443
|
+
<strong>Total:</strong> ${rollData.rollResult}
|
|
444
|
+
</div>
|
|
445
|
+
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
446
|
+
${traitOptionsHTML}
|
|
447
|
+
</div>
|
|
448
|
+
<button id="cancelTraitBtn" style="
|
|
449
|
+
background: #95a5a6;
|
|
450
|
+
color: white;
|
|
451
|
+
border: none;
|
|
452
|
+
padding: 12px 24px;
|
|
453
|
+
border-radius: 8px;
|
|
454
|
+
cursor: pointer;
|
|
455
|
+
font-size: 14px;
|
|
456
|
+
font-weight: bold;
|
|
457
|
+
margin-top: 8px;
|
|
458
|
+
transition: background 0.2s;
|
|
459
|
+
">
|
|
460
|
+
Keep Original Roll
|
|
461
|
+
</button>
|
|
462
|
+
`;
|
|
463
|
+
|
|
464
|
+
popupOverlay.appendChild(popupContent);
|
|
465
|
+
document.body.appendChild(popupOverlay);
|
|
466
|
+
|
|
467
|
+
debug.log('🎯 Appending trait choice popup to document.body...');
|
|
468
|
+
|
|
469
|
+
// Add event listeners
|
|
470
|
+
const traitButtons = document.querySelectorAll('.trait-option-btn');
|
|
471
|
+
const cancelBtn = document.getElementById('cancelTraitBtn');
|
|
472
|
+
|
|
473
|
+
// Add hover effects for trait buttons (CSP-compliant)
|
|
474
|
+
traitButtons.forEach((btn, index) => {
|
|
475
|
+
const originalColor = btn.dataset.traitColor;
|
|
476
|
+
|
|
477
|
+
btn.addEventListener('mouseenter', () => {
|
|
478
|
+
btn.style.transform = 'translateY(-2px)';
|
|
479
|
+
btn.style.background = originalColor + 'dd';
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
btn.addEventListener('mouseleave', () => {
|
|
483
|
+
btn.style.transform = 'translateY(0)';
|
|
484
|
+
btn.style.background = originalColor;
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
btn.addEventListener('click', () => {
|
|
488
|
+
const trait = allTraits[index];
|
|
489
|
+
debug.log(`🎯 User chose trait: ${trait.name}`);
|
|
490
|
+
|
|
491
|
+
popupOverlay.remove();
|
|
492
|
+
|
|
493
|
+
// Execute the chosen trait's action
|
|
494
|
+
if (trait.name === 'Halfling Luck') {
|
|
495
|
+
showHalflingLuckPopup({
|
|
496
|
+
rollResult: rollData.baseRoll,
|
|
497
|
+
baseRoll: rollData.baseRoll,
|
|
498
|
+
rollType: rollData.rollType,
|
|
499
|
+
rollName: rollData.rollName
|
|
500
|
+
});
|
|
501
|
+
} else if (trait.name === 'Lucky') {
|
|
502
|
+
const luckyResource = getLuckyResource();
|
|
503
|
+
showLuckyPopup({
|
|
504
|
+
rollResult: rollData.baseRoll,
|
|
505
|
+
baseRoll: rollData.baseRoll,
|
|
506
|
+
rollType: rollData.rollType,
|
|
507
|
+
rollName: rollData.rollName,
|
|
508
|
+
luckPointsRemaining: luckyResource?.current || 0
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Add hover effects for cancel button (CSP-compliant)
|
|
515
|
+
cancelBtn.addEventListener('mouseenter', () => cancelBtn.style.background = '#7f8c8d');
|
|
516
|
+
cancelBtn.addEventListener('mouseleave', () => cancelBtn.style.background = '#95a5a6');
|
|
517
|
+
|
|
518
|
+
cancelBtn.addEventListener('click', () => {
|
|
519
|
+
popupOverlay.remove();
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// Close on overlay click
|
|
523
|
+
popupOverlay.addEventListener('click', (e) => {
|
|
524
|
+
if (e.target === popupOverlay) {
|
|
525
|
+
popupOverlay.remove();
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
debug.log('🎯 Trait choice popup displayed');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ===== WILD MAGIC SURGE POPUP =====
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Show Wild Magic Surge popup
|
|
536
|
+
* @param {number} d100Roll - d100 roll result
|
|
537
|
+
* @param {string} effect - Wild magic effect description
|
|
538
|
+
*/
|
|
539
|
+
function showWildMagicSurgePopup(d100Roll, effect) {
|
|
540
|
+
debug.log('🌀 Wild Magic Surge popup called with:', d100Roll, effect);
|
|
541
|
+
|
|
542
|
+
if (!document.body) {
|
|
543
|
+
debug.error('❌ document.body not available for Wild Magic Surge popup');
|
|
544
|
+
showNotification(`🌀 Wild Magic Surge! d100: ${d100Roll}`, 'warning');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const colors = getPopupThemeColors();
|
|
549
|
+
|
|
550
|
+
// Create popup overlay
|
|
551
|
+
const popupOverlay = document.createElement('div');
|
|
552
|
+
popupOverlay.style.cssText = `
|
|
553
|
+
position: fixed;
|
|
554
|
+
top: 0;
|
|
555
|
+
left: 0;
|
|
556
|
+
width: 100%;
|
|
557
|
+
height: 100%;
|
|
558
|
+
background: rgba(0, 0, 0, 0.8);
|
|
559
|
+
display: flex;
|
|
560
|
+
align-items: center;
|
|
561
|
+
justify-content: center;
|
|
562
|
+
z-index: 10000;
|
|
563
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
564
|
+
`;
|
|
565
|
+
|
|
566
|
+
// Create popup content
|
|
567
|
+
const popupContent = document.createElement('div');
|
|
568
|
+
popupContent.style.cssText = `
|
|
569
|
+
background: ${colors.background};
|
|
570
|
+
border-radius: 12px;
|
|
571
|
+
padding: 24px;
|
|
572
|
+
max-width: 500px;
|
|
573
|
+
width: 90%;
|
|
574
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
575
|
+
text-align: center;
|
|
576
|
+
`;
|
|
577
|
+
|
|
578
|
+
popupContent.innerHTML = `
|
|
579
|
+
<div style="font-size: 32px; margin-bottom: 16px;">🌀</div>
|
|
580
|
+
<h2 style="margin: 0 0 8px 0; color: #9b59b6;">Wild Magic Surge!</h2>
|
|
581
|
+
<p style="margin: 0 0 16px 0; color: ${colors.text};">
|
|
582
|
+
Your spell triggers a wild magic surge!
|
|
583
|
+
</p>
|
|
584
|
+
<div style="margin: 0 0 16px 0; padding: 16px; background: ${colors.infoBox}; border-radius: 8px; border-left: 4px solid #9b59b6; color: ${colors.text}; text-align: left;">
|
|
585
|
+
<div style="text-align: center; font-weight: bold; font-size: 18px; margin-bottom: 12px; color: #9b59b6;">
|
|
586
|
+
d100 Roll: ${d100Roll}
|
|
587
|
+
</div>
|
|
588
|
+
<div style="font-size: 14px; line-height: 1.6;">
|
|
589
|
+
${effect}
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
<button id="closeWildMagicBtn" style="
|
|
593
|
+
background: #9b59b6;
|
|
594
|
+
color: white;
|
|
595
|
+
border: none;
|
|
596
|
+
padding: 12px 32px;
|
|
597
|
+
border-radius: 8px;
|
|
598
|
+
cursor: pointer;
|
|
599
|
+
font-weight: bold;
|
|
600
|
+
font-size: 14px;
|
|
601
|
+
transition: background 0.2s;
|
|
602
|
+
">Got it!</button>
|
|
603
|
+
`;
|
|
604
|
+
|
|
605
|
+
popupOverlay.appendChild(popupContent);
|
|
606
|
+
document.body.appendChild(popupOverlay);
|
|
607
|
+
|
|
608
|
+
// Add event listeners
|
|
609
|
+
const closeBtn = document.getElementById('closeWildMagicBtn');
|
|
610
|
+
|
|
611
|
+
closeBtn.addEventListener('mouseenter', () => closeBtn.style.background = '#8e44ad');
|
|
612
|
+
closeBtn.addEventListener('mouseleave', () => closeBtn.style.background = '#9b59b6');
|
|
613
|
+
|
|
614
|
+
closeBtn.addEventListener('click', () => {
|
|
615
|
+
document.body.removeChild(popupOverlay);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// Close on overlay click
|
|
619
|
+
popupOverlay.addEventListener('click', (e) => {
|
|
620
|
+
if (e.target === popupOverlay) {
|
|
621
|
+
document.body.removeChild(popupOverlay);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// TODO: Add Owlbear Rodeo integration for Wild Magic Surge announcements
|
|
626
|
+
|
|
627
|
+
debug.log('🌀 Wild Magic Surge popup displayed');
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ===== BARDIC INSPIRATION POPUP =====
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Show Bardic Inspiration popup
|
|
634
|
+
* @param {Object} rollData - Roll information
|
|
635
|
+
*/
|
|
636
|
+
function showBardicInspirationPopup(rollData) {
|
|
637
|
+
debug.log('🎵 Bardic Inspiration popup called with:', rollData);
|
|
638
|
+
|
|
639
|
+
// Check if document.body exists
|
|
640
|
+
if (!document.body) {
|
|
641
|
+
debug.error('❌ document.body not available for Bardic Inspiration popup');
|
|
642
|
+
showNotification('🎵 Bardic Inspiration available! (Popup failed to display)', 'info');
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
debug.log('🎵 Creating Bardic Inspiration popup overlay...');
|
|
647
|
+
|
|
648
|
+
// Get theme-aware colors
|
|
649
|
+
const colors = getPopupThemeColors();
|
|
650
|
+
|
|
651
|
+
// Create popup overlay
|
|
652
|
+
const popupOverlay = document.createElement('div');
|
|
653
|
+
popupOverlay.style.cssText = `
|
|
654
|
+
position: fixed;
|
|
655
|
+
top: 0;
|
|
656
|
+
left: 0;
|
|
657
|
+
width: 100%;
|
|
658
|
+
height: 100%;
|
|
659
|
+
background: rgba(0, 0, 0, 0.8);
|
|
660
|
+
display: flex;
|
|
661
|
+
align-items: center;
|
|
662
|
+
justify-content: center;
|
|
663
|
+
z-index: 10000;
|
|
664
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
665
|
+
`;
|
|
666
|
+
|
|
667
|
+
// Create popup content
|
|
668
|
+
const popupContent = document.createElement('div');
|
|
669
|
+
popupContent.style.cssText = `
|
|
670
|
+
background: ${colors.background};
|
|
671
|
+
border-radius: 12px;
|
|
672
|
+
padding: 24px;
|
|
673
|
+
max-width: 450px;
|
|
674
|
+
width: 90%;
|
|
675
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
676
|
+
text-align: center;
|
|
677
|
+
`;
|
|
678
|
+
|
|
679
|
+
debug.log('🎵 Setting Bardic Inspiration popup content HTML...');
|
|
680
|
+
|
|
681
|
+
popupContent.innerHTML = `
|
|
682
|
+
<div style="font-size: 32px; margin-bottom: 16px;">🎵</div>
|
|
683
|
+
<h2 style="margin: 0 0 8px 0; color: ${colors.heading};">Bardic Inspiration!</h2>
|
|
684
|
+
<p style="margin: 0 0 16px 0; color: ${colors.text};">
|
|
685
|
+
Add a <strong>${rollData.inspirationDie}</strong> to this roll?
|
|
686
|
+
</p>
|
|
687
|
+
<div style="margin: 0 0 16px 0; padding: 12px; background: ${colors.infoBox}; border-radius: 8px; border-left: 4px solid #9b59b6; color: ${colors.text};">
|
|
688
|
+
<strong>Current Roll:</strong> ${rollData.rollName}<br>
|
|
689
|
+
<strong>Base Result:</strong> ${rollData.baseRoll}<br>
|
|
690
|
+
<strong>Inspiration Die:</strong> ${rollData.inspirationDie}<br>
|
|
691
|
+
<strong>Uses Left:</strong> ${rollData.usesRemaining}
|
|
692
|
+
</div>
|
|
693
|
+
<div style="margin-bottom: 16px; padding: 12px; background: ${colors.infoBox}; border-radius: 8px; color: ${colors.text}; font-size: 13px; text-align: left;">
|
|
694
|
+
<strong>💡 How it works:</strong><br>
|
|
695
|
+
• Roll the inspiration die and add it to your total<br>
|
|
696
|
+
• Can be used on ability checks, attack rolls, or saves<br>
|
|
697
|
+
• Only one inspiration die can be used per roll
|
|
698
|
+
</div>
|
|
699
|
+
<div style="display: flex; gap: 12px; justify-content: center;">
|
|
700
|
+
<button id="bardicUseBtn" style="
|
|
701
|
+
background: #9b59b6;
|
|
702
|
+
color: white;
|
|
703
|
+
border: none;
|
|
704
|
+
padding: 12px 24px;
|
|
705
|
+
border-radius: 8px;
|
|
706
|
+
cursor: pointer;
|
|
707
|
+
font-weight: bold;
|
|
708
|
+
font-size: 14px;
|
|
709
|
+
transition: background 0.2s;
|
|
710
|
+
">🎲 Use Inspiration</button>
|
|
711
|
+
<button id="bardicDeclineBtn" style="
|
|
712
|
+
background: #7f8c8d;
|
|
713
|
+
color: white;
|
|
714
|
+
border: none;
|
|
715
|
+
padding: 12px 24px;
|
|
716
|
+
border-radius: 8px;
|
|
717
|
+
cursor: pointer;
|
|
718
|
+
font-weight: bold;
|
|
719
|
+
font-size: 14px;
|
|
720
|
+
transition: background 0.2s;
|
|
721
|
+
">Decline</button>
|
|
722
|
+
</div>
|
|
723
|
+
`;
|
|
724
|
+
|
|
725
|
+
debug.log('🎵 Appending Bardic Inspiration popup to document.body...');
|
|
726
|
+
|
|
727
|
+
popupOverlay.appendChild(popupContent);
|
|
728
|
+
document.body.appendChild(popupOverlay);
|
|
729
|
+
|
|
730
|
+
// Add hover effects
|
|
731
|
+
const useBtn = document.getElementById('bardicUseBtn');
|
|
732
|
+
const declineBtn = document.getElementById('bardicDeclineBtn');
|
|
733
|
+
|
|
734
|
+
useBtn.addEventListener('mouseenter', () => {
|
|
735
|
+
useBtn.style.background = '#8e44ad';
|
|
736
|
+
});
|
|
737
|
+
useBtn.addEventListener('mouseleave', () => {
|
|
738
|
+
useBtn.style.background = '#9b59b6';
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
declineBtn.addEventListener('mouseenter', () => {
|
|
742
|
+
declineBtn.style.background = '#95a5a6';
|
|
743
|
+
});
|
|
744
|
+
declineBtn.addEventListener('mouseleave', () => {
|
|
745
|
+
declineBtn.style.background = '#7f8c8d';
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// Add event listeners
|
|
749
|
+
useBtn.addEventListener('click', () => {
|
|
750
|
+
debug.log('🎵 User chose to use Bardic Inspiration');
|
|
751
|
+
performBardicInspirationRoll(rollData);
|
|
752
|
+
document.body.removeChild(popupOverlay);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
declineBtn.addEventListener('click', () => {
|
|
756
|
+
debug.log('🎵 User declined Bardic Inspiration');
|
|
757
|
+
showNotification('Bardic Inspiration declined', 'info');
|
|
758
|
+
document.body.removeChild(popupOverlay);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
// Close on overlay click
|
|
762
|
+
popupOverlay.addEventListener('click', (e) => {
|
|
763
|
+
if (e.target === popupOverlay) {
|
|
764
|
+
debug.log('🎵 User closed Bardic Inspiration popup');
|
|
765
|
+
document.body.removeChild(popupOverlay);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
debug.log('🎵 Bardic Inspiration popup displayed');
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Perform Bardic Inspiration roll
|
|
774
|
+
* @param {Object} rollData - Roll information
|
|
775
|
+
*/
|
|
776
|
+
function performBardicInspirationRoll(rollData) {
|
|
777
|
+
debug.log('🎵 Performing Bardic Inspiration roll with data:', rollData);
|
|
778
|
+
|
|
779
|
+
// Use one Bardic Inspiration use
|
|
780
|
+
const success = useBardicInspiration();
|
|
781
|
+
if (!success) {
|
|
782
|
+
debug.error('❌ Failed to use Bardic Inspiration (no uses left?)');
|
|
783
|
+
showNotification('❌ Failed to use Bardic Inspiration', 'error');
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Roll the inspiration die
|
|
788
|
+
const dieSize = parseInt(rollData.inspirationDie.substring(1)); // "d6" -> 6
|
|
789
|
+
const inspirationRoll = Math.floor(Math.random() * dieSize) + 1;
|
|
790
|
+
|
|
791
|
+
debug.log(`🎵 Rolled ${rollData.inspirationDie}: ${inspirationRoll}`);
|
|
792
|
+
|
|
793
|
+
// Create the roll message
|
|
794
|
+
const inspirationMessage = `/roll ${rollData.inspirationDie}`;
|
|
795
|
+
const chatMessage = `🎵 Bardic Inspiration for ${rollData.rollName}: [[${inspirationRoll}]] (${rollData.inspirationDie})`;
|
|
796
|
+
|
|
797
|
+
// Show notification
|
|
798
|
+
showNotification(`🎵 Bardic Inspiration: +${inspirationRoll}!`, 'success');
|
|
799
|
+
|
|
800
|
+
// TODO: Add Owlbear Rodeo integration for Bardic Inspiration rolls
|
|
801
|
+
|
|
802
|
+
debug.log('🎵 Bardic Inspiration roll complete');
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// ===== ELVEN ACCURACY POPUP =====
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Show Elven Accuracy popup
|
|
809
|
+
* @param {Object} rollData - Roll information
|
|
810
|
+
*/
|
|
811
|
+
function showElvenAccuracyPopup(rollData) {
|
|
812
|
+
debug.log('🧝 Elven Accuracy popup called with:', rollData);
|
|
813
|
+
|
|
814
|
+
if (!document.body) {
|
|
815
|
+
debug.error('❌ document.body not available for Elven Accuracy popup');
|
|
816
|
+
showNotification('🧝 Elven Accuracy triggered!', 'info');
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const colors = getPopupThemeColors();
|
|
821
|
+
|
|
822
|
+
// Create popup overlay
|
|
823
|
+
const popupOverlay = document.createElement('div');
|
|
824
|
+
popupOverlay.style.cssText = `
|
|
825
|
+
position: fixed;
|
|
826
|
+
top: 0;
|
|
827
|
+
left: 0;
|
|
828
|
+
width: 100%;
|
|
829
|
+
height: 100%;
|
|
830
|
+
background: rgba(0, 0, 0, 0.8);
|
|
831
|
+
display: flex;
|
|
832
|
+
align-items: center;
|
|
833
|
+
justify-content: center;
|
|
834
|
+
z-index: 10000;
|
|
835
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
836
|
+
`;
|
|
837
|
+
|
|
838
|
+
// Create popup content
|
|
839
|
+
const popupContent = document.createElement('div');
|
|
840
|
+
popupContent.style.cssText = `
|
|
841
|
+
background: ${colors.background};
|
|
842
|
+
border-radius: 12px;
|
|
843
|
+
padding: 24px;
|
|
844
|
+
max-width: 400px;
|
|
845
|
+
width: 90%;
|
|
846
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
847
|
+
text-align: center;
|
|
848
|
+
`;
|
|
849
|
+
|
|
850
|
+
popupContent.innerHTML = `
|
|
851
|
+
<div style="font-size: 24px; margin-bottom: 16px;">🧝</div>
|
|
852
|
+
<h2 style="margin: 0 0 8px 0; color: #27ae60;">Elven Accuracy!</h2>
|
|
853
|
+
<p style="margin: 0 0 16px 0; color: ${colors.text};">
|
|
854
|
+
You have advantage! Would you like to reroll the lower die?
|
|
855
|
+
</p>
|
|
856
|
+
<div style="margin: 0 0 16px 0; padding: 12px; background: ${colors.infoBox}; border-radius: 8px; border-left: 4px solid #27ae60; color: ${colors.text};">
|
|
857
|
+
<strong>Roll:</strong> ${rollData.rollName}<br>
|
|
858
|
+
<strong>Type:</strong> Advantage attack roll
|
|
859
|
+
</div>
|
|
860
|
+
<div style="display: flex; gap: 12px; justify-content: center;">
|
|
861
|
+
<button id="elvenRerollBtn" style="
|
|
862
|
+
background: #27ae60;
|
|
863
|
+
color: white;
|
|
864
|
+
border: none;
|
|
865
|
+
padding: 12px 24px;
|
|
866
|
+
border-radius: 8px;
|
|
867
|
+
cursor: pointer;
|
|
868
|
+
font-weight: bold;
|
|
869
|
+
font-size: 14px;
|
|
870
|
+
">🎲 Reroll Lower Die</button>
|
|
871
|
+
<button id="elvenKeepBtn" style="
|
|
872
|
+
background: #95a5a6;
|
|
873
|
+
color: white;
|
|
874
|
+
border: none;
|
|
875
|
+
padding: 12px 24px;
|
|
876
|
+
border-radius: 8px;
|
|
877
|
+
cursor: pointer;
|
|
878
|
+
font-weight: bold;
|
|
879
|
+
font-size: 14px;
|
|
880
|
+
">Keep Rolls</button>
|
|
881
|
+
</div>
|
|
882
|
+
`;
|
|
883
|
+
|
|
884
|
+
popupOverlay.appendChild(popupContent);
|
|
885
|
+
document.body.appendChild(popupOverlay);
|
|
886
|
+
|
|
887
|
+
// Add event listeners
|
|
888
|
+
const rerollBtn = document.getElementById('elvenRerollBtn');
|
|
889
|
+
const keepBtn = document.getElementById('elvenKeepBtn');
|
|
890
|
+
|
|
891
|
+
rerollBtn.addEventListener('mouseenter', () => rerollBtn.style.background = '#229954');
|
|
892
|
+
rerollBtn.addEventListener('mouseleave', () => rerollBtn.style.background = '#27ae60');
|
|
893
|
+
keepBtn.addEventListener('mouseenter', () => keepBtn.style.background = '#7f8c8d');
|
|
894
|
+
keepBtn.addEventListener('mouseleave', () => keepBtn.style.background = '#95a5a6');
|
|
895
|
+
|
|
896
|
+
rerollBtn.addEventListener('click', () => {
|
|
897
|
+
debug.log('🧝 User chose to reroll with Elven Accuracy');
|
|
898
|
+
performElvenAccuracyReroll(rollData);
|
|
899
|
+
document.body.removeChild(popupOverlay);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
keepBtn.addEventListener('click', () => {
|
|
903
|
+
debug.log('🧝 User chose to keep original advantage rolls');
|
|
904
|
+
document.body.removeChild(popupOverlay);
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// Close on overlay click
|
|
908
|
+
popupOverlay.addEventListener('click', (e) => {
|
|
909
|
+
if (e.target === popupOverlay) {
|
|
910
|
+
document.body.removeChild(popupOverlay);
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
debug.log('🧝 Elven Accuracy popup displayed');
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Perform Elven Accuracy reroll
|
|
919
|
+
* @param {Object} originalRollData - Original roll data
|
|
920
|
+
*/
|
|
921
|
+
function performElvenAccuracyReroll(originalRollData) {
|
|
922
|
+
debug.log('🧝 Performing Elven Accuracy reroll for:', originalRollData);
|
|
923
|
+
|
|
924
|
+
// Roll a third d20
|
|
925
|
+
const thirdRoll = Math.floor(Math.random() * 20) + 1;
|
|
926
|
+
|
|
927
|
+
// Create reroll announcement
|
|
928
|
+
const rerollData = {
|
|
929
|
+
name: `🧝 ${originalRollData.rollName} (Elven Accuracy - 3rd die)`,
|
|
930
|
+
formula: '1d20',
|
|
931
|
+
color: '#27ae60',
|
|
932
|
+
characterName: characterData.name
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
debug.log('🧝 Third die roll:', thirdRoll);
|
|
936
|
+
|
|
937
|
+
// TODO: Add Owlbear Rodeo integration for Elven Accuracy rerolls
|
|
938
|
+
|
|
939
|
+
showNotification(`🧝 Elven Accuracy! Third die: ${thirdRoll}`, 'success');
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// ===== EXPORTS =====
|
|
943
|
+
|
|
944
|
+
// Export functions to globalThis
|
|
945
|
+
globalThis.getPopupThemeColors = getPopupThemeColors;
|
|
946
|
+
globalThis.showHalflingLuckPopup = showHalflingLuckPopup;
|
|
947
|
+
globalThis.showLuckyPopup = showLuckyPopup;
|
|
948
|
+
globalThis.showTraitChoicePopup = showTraitChoicePopup;
|
|
949
|
+
globalThis.showWildMagicSurgePopup = showWildMagicSurgePopup;
|
|
950
|
+
globalThis.showBardicInspirationPopup = showBardicInspirationPopup;
|
|
951
|
+
globalThis.showElvenAccuracyPopup = showElvenAccuracyPopup;
|
|
952
|
+
globalThis.performHalflingReroll = performHalflingReroll;
|
|
953
|
+
globalThis.performLuckyReroll = performLuckyReroll;
|
|
954
|
+
globalThis.performBardicInspirationRoll = performBardicInspirationRoll;
|
|
955
|
+
globalThis.performElvenAccuracyReroll = performElvenAccuracyReroll;
|
|
956
|
+
|
|
957
|
+
debug.log('✅ Character Trait Popups module loaded');
|
|
958
|
+
|
|
959
|
+
})();
|