@moneybar.online/moneybar 4.2.4 → 5.0.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/index.esm.js +1 -3201
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2 -3213
- package/dist/index.js.map +1 -1
- package/moneybar-config-template.js +2 -2
- package/package.json +1 -2
- package/src/index.ts +0 -3428
- package/src/types.ts +0 -180
package/dist/index.js
CHANGED
|
@@ -1,3213 +1,2 @@
|
|
|
1
|
-
import { createClient } from '@supabase/supabase-js';
|
|
2
|
-
|
|
3
|
-
* MoneyBar - The navbar of monetization
|
|
4
|
-
*
|
|
5
|
-
* Fix the 3 money-blocking stages that stop revenue:
|
|
6
|
-
* 1. Forced sign-ins before value → Value-first trials
|
|
7
|
-
* 2. Silent drop-offs → Exit feedback capture
|
|
8
|
-
* 3. Broken payment flows → Seamless Google Auth + payments
|
|
9
|
-
*
|
|
10
|
-
* Features:
|
|
11
|
-
* - Server-side usage tracking (incognito-proof)
|
|
12
|
-
* - Google OAuth authentication
|
|
13
|
-
* - Payment integration with DodoPayments
|
|
14
|
-
* - User feedback collection
|
|
15
|
-
* - Automatic UI management (paywall, auth, status)
|
|
16
|
-
* - User context for personalization
|
|
17
|
-
*
|
|
18
|
-
* @example Simple Usage (Recommended):
|
|
19
|
-
* ```javascript
|
|
20
|
-
* import { MoneyBar } from '@moneybar.online/moneybar';
|
|
21
|
-
*
|
|
22
|
-
* const moneyBar = new MoneyBar({
|
|
23
|
-
* appId: 'my-pdf-app',
|
|
24
|
-
* freeAttemptLimit: 3,
|
|
25
|
-
* supabase: { url: 'https://xyz.supabase.co', anonKey: 'anon_key' },
|
|
26
|
-
* payment: { productId: 'prod_xyz' }
|
|
27
|
-
* });
|
|
28
|
-
*
|
|
29
|
-
* // Single button - handles all monetization automatically
|
|
30
|
-
* moneyBar.attachToButton('#action-btn', (userContext) => {
|
|
31
|
-
* // Your action logic here - with user context for personalization
|
|
32
|
-
* if (userContext.isPremium) {
|
|
33
|
-
* generatePremiumPDF();
|
|
34
|
-
* } else {
|
|
35
|
-
* generateBasicPDF();
|
|
36
|
-
* }
|
|
37
|
-
* });
|
|
38
|
-
* ```
|
|
39
|
-
*
|
|
40
|
-
* @example Multiple Buttons:
|
|
41
|
-
* ```javascript
|
|
42
|
-
* moneyBar.attachToButtons({
|
|
43
|
-
* buttons: [
|
|
44
|
-
* {
|
|
45
|
-
* selector: '#free-btn',
|
|
46
|
-
* downloadCallback: (ctx) => downloadFreeTemplate(ctx),
|
|
47
|
-
* isPremium: false
|
|
48
|
-
* },
|
|
49
|
-
* {
|
|
50
|
-
* selector: '#premium-btn',
|
|
51
|
-
* downloadCallback: (ctx) => downloadPremiumTemplate(ctx),
|
|
52
|
-
* isPremium: true
|
|
53
|
-
* }
|
|
54
|
-
* ]
|
|
55
|
-
* });
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
export class MoneyBar {
|
|
59
|
-
mergeConfigs(config) {
|
|
60
|
-
// Try to get config from window.APP_CONFIG
|
|
61
|
-
const windowConfig = typeof window !== 'undefined' ? window.APP_CONFIG : null;
|
|
62
|
-
if (!config && !windowConfig) {
|
|
63
|
-
throw new Error('MoneyBar: No configuration provided. Either pass config to constructor or set window.APP_CONFIG');
|
|
64
|
-
}
|
|
65
|
-
// If no constructor config provided, use window config entirely
|
|
66
|
-
if (!config) {
|
|
67
|
-
return windowConfig;
|
|
68
|
-
}
|
|
69
|
-
// If no window config, use constructor config entirely
|
|
70
|
-
if (!windowConfig) {
|
|
71
|
-
return config;
|
|
72
|
-
}
|
|
73
|
-
// Merge configs: constructor config takes precedence over window config
|
|
74
|
-
return {
|
|
75
|
-
...windowConfig,
|
|
76
|
-
...config
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
constructor(config) {
|
|
80
|
-
this.currentUser = null;
|
|
81
|
-
this.userFingerprint = null;
|
|
82
|
-
this.eventListeners = new Map();
|
|
83
|
-
this.supabaseConnectionFailed = false;
|
|
84
|
-
this.paymentConnectionFailed = false;
|
|
85
|
-
this.securityKeyFailed = false; // Track security key validation failures
|
|
86
|
-
this.isLoadingStatus = false; // Track if we're currently loading status from backend
|
|
87
|
-
this.cachedStatus = null;
|
|
88
|
-
this.statusCacheTime = 0;
|
|
89
|
-
this.STATUS_CACHE_DURATION = 5000; // 5 seconds cache
|
|
90
|
-
this.initializationPromise = null;
|
|
91
|
-
this.cachedPremiumStatus = null;
|
|
92
|
-
this.premiumStatusCacheTime = 0;
|
|
93
|
-
// Auto-load from window.APP_CONFIG if no config provided, or merge configs
|
|
94
|
-
this.config = this.mergeConfigs(config);
|
|
95
|
-
if (this.config.options?.debug) {
|
|
96
|
-
// console.log('🔍 DEBUG: MoneyBar constructor - full config:', JSON.stringify(config, null, 2));
|
|
97
|
-
// console.log('🔍 DEBUG: Email config present:', !!config.email);
|
|
98
|
-
}
|
|
99
|
-
this.validateConfig();
|
|
100
|
-
this.initializeSupabase();
|
|
101
|
-
this.initializePayment();
|
|
102
|
-
this.initializeFingerprint();
|
|
103
|
-
this.setupAuthListener();
|
|
104
|
-
this.checkPaymentStatus();
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Main method to handle download attempts
|
|
108
|
-
* Call this from your download button's click handler
|
|
109
|
-
*/
|
|
110
|
-
async handleDownload() {
|
|
111
|
-
try {
|
|
112
|
-
// Security check: if security key validation failed
|
|
113
|
-
if (this.securityKeyFailed) {
|
|
114
|
-
this.showError('Invalid Security Key', 'Your security key is invalid, expired, or not found. Please check your MoneyBar configuration and ensure you have a valid security_key.');
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
// Security check: if Supabase connection failed, disable downloads to prevent localStorage bypass
|
|
118
|
-
if (this.supabaseConnectionFailed) {
|
|
119
|
-
this.config.callbacks?.onError?.(new Error('Server connection required for download verification. Please check your configuration.'));
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
// Security check: if payment configuration is invalid, disable downloads
|
|
123
|
-
if (this.config.payment && this.paymentConnectionFailed) {
|
|
124
|
-
this.config.callbacks?.onError?.(new Error('Payment configuration error. Please contact support.'));
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
// Check if user is premium first (if authenticated)
|
|
128
|
-
if (this.currentUser) {
|
|
129
|
-
const isPremium = await this.checkPremiumStatus(this.currentUser.email);
|
|
130
|
-
if (isPremium) {
|
|
131
|
-
this.config.callbacks?.onPremiumDownload();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// Check usage limits (works for both authenticated and anonymous users)
|
|
136
|
-
const status = await this.getDownloadStatus();
|
|
137
|
-
if (status.currentCount >= status.limit) {
|
|
138
|
-
// Limit reached - show paywall/limit message
|
|
139
|
-
this.config.callbacks?.onLimitReached(status.currentCount, status.limit);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
// Increment counter and proceed with action
|
|
143
|
-
await this.incrementDownloadCount();
|
|
144
|
-
this.config.callbacks?.onDownloadAllowed();
|
|
145
|
-
// Invalidate cache since count changed
|
|
146
|
-
this.cachedStatus = null;
|
|
147
|
-
// Check if limit reached after increment
|
|
148
|
-
const newStatus = await this.getDownloadStatus();
|
|
149
|
-
this.emit('countChanged', { count: newStatus.currentCount, limit: newStatus.limit });
|
|
150
|
-
if (newStatus.currentCount >= newStatus.limit) {
|
|
151
|
-
// Show paywall for next time
|
|
152
|
-
setTimeout(() => {
|
|
153
|
-
this.config.callbacks?.onLimitReached(newStatus.currentCount, newStatus.limit);
|
|
154
|
-
}, 100);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
this.handleError(error);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Get current download status for the user
|
|
163
|
-
*/
|
|
164
|
-
async getDownloadStatus(forceRefresh = false) {
|
|
165
|
-
// console.log(`🔍 [INIT] getDownloadStatus called (forceRefresh: ${forceRefresh}) at ${new Date().toISOString()}`);
|
|
166
|
-
// Check if we can use cached status
|
|
167
|
-
const cacheAge = this.cachedStatus ? Date.now() - this.statusCacheTime : -1;
|
|
168
|
-
const canUseCache = !forceRefresh && this.cachedStatus && cacheAge < this.STATUS_CACHE_DURATION;
|
|
169
|
-
if (canUseCache) {
|
|
170
|
-
// console.log(`🔍 [INIT] Using cached status (age: ${cacheAge}ms)`);
|
|
171
|
-
return this.cachedStatus;
|
|
172
|
-
}
|
|
173
|
-
// If there's already an initialization in progress and it's not forced refresh, wait for it
|
|
174
|
-
if (!forceRefresh && this.initializationPromise) {
|
|
175
|
-
// console.log(`🔍 [INIT] Waiting for existing initialization promise`);
|
|
176
|
-
return await this.initializationPromise;
|
|
177
|
-
}
|
|
178
|
-
// console.log(`🔍 [INIT] Starting new status fetch (cache age: ${cacheAge}ms)`);
|
|
179
|
-
// Create the initialization promise
|
|
180
|
-
this.initializationPromise = this.fetchFreshStatus(forceRefresh);
|
|
181
|
-
try {
|
|
182
|
-
const result = await this.initializationPromise;
|
|
183
|
-
// console.log(`🔍 [INIT] Status fetch completed successfully`);
|
|
184
|
-
return result;
|
|
185
|
-
}
|
|
186
|
-
finally {
|
|
187
|
-
// Clear the promise after completion
|
|
188
|
-
this.initializationPromise = null;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
async fetchFreshStatus(forceRefresh) {
|
|
192
|
-
try {
|
|
193
|
-
let count = 0;
|
|
194
|
-
let isPremium = false;
|
|
195
|
-
// Only call API if we really need fresh data
|
|
196
|
-
if (forceRefresh || !this.cachedStatus) {
|
|
197
|
-
// Get download count only if we don't have it cached
|
|
198
|
-
count = await this.getDownloadCount();
|
|
199
|
-
// CRITICAL: Check if security key validation failed
|
|
200
|
-
// Don't proceed with normal status if there's a security error
|
|
201
|
-
if (this.securityKeyFailed) {
|
|
202
|
-
// Return a minimal error status
|
|
203
|
-
return {
|
|
204
|
-
currentCount: 0,
|
|
205
|
-
limit: this.config.freeAttemptLimit,
|
|
206
|
-
isPremium: false,
|
|
207
|
-
isAuthenticated: false,
|
|
208
|
-
remaining: 0
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
// Use cached count if available to avoid unnecessary API call
|
|
214
|
-
count = this.cachedStatus.currentCount;
|
|
215
|
-
}
|
|
216
|
-
// Check premium status only for authenticated users, and only when needed
|
|
217
|
-
if (this.currentUser) {
|
|
218
|
-
const premiumCacheValid = this.cachedPremiumStatus !== null && (Date.now() - this.premiumStatusCacheTime) < this.STATUS_CACHE_DURATION;
|
|
219
|
-
if (forceRefresh || !premiumCacheValid) {
|
|
220
|
-
isPremium = await this.checkPremiumStatus(this.currentUser.email);
|
|
221
|
-
// Update cache
|
|
222
|
-
this.cachedPremiumStatus = isPremium;
|
|
223
|
-
this.premiumStatusCacheTime = Date.now();
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
// Use cached premium status to avoid unnecessary API call
|
|
227
|
-
isPremium = this.cachedPremiumStatus; // We know it's not null because premiumCacheValid is true
|
|
228
|
-
console.log(`🔍 [CACHE] Using cached premium status from getDownloadStatus: ${isPremium}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
// Unauthenticated users cannot be premium
|
|
233
|
-
isPremium = false;
|
|
234
|
-
}
|
|
235
|
-
const status = {
|
|
236
|
-
currentCount: count,
|
|
237
|
-
limit: this.config.freeAttemptLimit,
|
|
238
|
-
isPremium,
|
|
239
|
-
isAuthenticated: !!this.currentUser,
|
|
240
|
-
remaining: Math.max(0, this.config.freeAttemptLimit - count)
|
|
241
|
-
};
|
|
242
|
-
// Cache the status
|
|
243
|
-
this.cachedStatus = status;
|
|
244
|
-
this.statusCacheTime = Date.now();
|
|
245
|
-
return status;
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
this.handleError(error);
|
|
249
|
-
// Return safe defaults on error
|
|
250
|
-
const errorStatus = {
|
|
251
|
-
currentCount: 0,
|
|
252
|
-
limit: this.config.freeAttemptLimit,
|
|
253
|
-
isPremium: false,
|
|
254
|
-
isAuthenticated: false,
|
|
255
|
-
remaining: this.config.freeAttemptLimit
|
|
256
|
-
};
|
|
257
|
-
// Cache error status for short time to avoid repeated failures
|
|
258
|
-
this.cachedStatus = errorStatus;
|
|
259
|
-
this.statusCacheTime = Date.now();
|
|
260
|
-
return errorStatus;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Creates a UserContext object with current user information
|
|
265
|
-
*/
|
|
266
|
-
async getUserContext() {
|
|
267
|
-
const status = await this.getDownloadStatus();
|
|
268
|
-
return {
|
|
269
|
-
isPremium: status.isPremium,
|
|
270
|
-
isAuthenticated: status.isAuthenticated,
|
|
271
|
-
email: this.currentUser?.email,
|
|
272
|
-
name: this.currentUser?.user_metadata?.full_name || this.currentUser?.user_metadata?.name,
|
|
273
|
-
currentCount: status.currentCount,
|
|
274
|
-
remaining: status.remaining,
|
|
275
|
-
limit: status.limit,
|
|
276
|
-
user: this.currentUser
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Manually trigger Google sign-in
|
|
281
|
-
*/
|
|
282
|
-
async signIn() {
|
|
283
|
-
try {
|
|
284
|
-
const { error } = await this.supabase.auth.signInWithOAuth({
|
|
285
|
-
provider: 'google',
|
|
286
|
-
options: {
|
|
287
|
-
redirectTo: `${window.location.origin}${window.location.pathname}`
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
if (error)
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
catch (error) {
|
|
294
|
-
this.handleError(error);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Sign out current user
|
|
299
|
-
*/
|
|
300
|
-
async signOut() {
|
|
301
|
-
try {
|
|
302
|
-
await this.supabase.auth.signOut();
|
|
303
|
-
this.currentUser = null;
|
|
304
|
-
this.emit('authChanged', { user: null, isPremium: false });
|
|
305
|
-
}
|
|
306
|
-
catch (error) {
|
|
307
|
-
this.handleError(error);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Create payment for premium upgrade
|
|
312
|
-
*/
|
|
313
|
-
async createPayment() {
|
|
314
|
-
if (!this.currentUser) {
|
|
315
|
-
throw new Error('User must be signed in to create payment');
|
|
316
|
-
}
|
|
317
|
-
if (!this.config.payment) {
|
|
318
|
-
throw new Error('Payment configuration not provided');
|
|
319
|
-
}
|
|
320
|
-
const payment = this.config.payment?.find(p => p.provider === 'dodo');
|
|
321
|
-
if (!payment) {
|
|
322
|
-
throw new Error('No dodo payment provider configured');
|
|
323
|
-
}
|
|
324
|
-
const requestBody = {
|
|
325
|
-
email: this.currentUser.email,
|
|
326
|
-
product_id: payment.productId,
|
|
327
|
-
mode: payment.mode || 'test',
|
|
328
|
-
app_id: this.config.appId,
|
|
329
|
-
return_url: `${window.location.origin}${window.location.pathname}?payment=success`
|
|
330
|
-
};
|
|
331
|
-
console.log(`🔥 [PAYMENT API] createPayment called for provider: ${payment.provider}, email: ${this.currentUser.email} | Time: ${new Date().toISOString()}`);
|
|
332
|
-
try {
|
|
333
|
-
// Route to appropriate payment API based on provider
|
|
334
|
-
let response;
|
|
335
|
-
if (payment.provider === 'dodo') {
|
|
336
|
-
response = await fetch(`${this.config.supabase.url}/functions/v1/create-payment`, {
|
|
337
|
-
method: 'POST',
|
|
338
|
-
headers: {
|
|
339
|
-
'Content-Type': 'application/json',
|
|
340
|
-
'Authorization': `Bearer ${this.config.supabase.anonKey}`
|
|
341
|
-
},
|
|
342
|
-
body: JSON.stringify(requestBody)
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
throw new Error(`Payment provider '${payment.provider}' is not yet supported`);
|
|
347
|
-
}
|
|
348
|
-
console.log(`🔥 [PAYMENT API] createPayment response for ${payment.provider}: ${response.status}`);
|
|
349
|
-
if (this.config.options?.debug) {
|
|
350
|
-
//console.log('🔍 DEBUG: createPayment response status:', response.status);
|
|
351
|
-
}
|
|
352
|
-
if (!response.ok) {
|
|
353
|
-
const errorText = await response.text();
|
|
354
|
-
if (this.config.options?.debug) {
|
|
355
|
-
console.error('🔍 DEBUG: createPayment error response:', errorText);
|
|
356
|
-
}
|
|
357
|
-
let errorData;
|
|
358
|
-
try {
|
|
359
|
-
errorData = JSON.parse(errorText);
|
|
360
|
-
}
|
|
361
|
-
catch {
|
|
362
|
-
errorData = { error: errorText };
|
|
363
|
-
}
|
|
364
|
-
// Check if this is a product ID error (invalid config)
|
|
365
|
-
if (errorText.includes('404') || errorText.includes('could not be found')) {
|
|
366
|
-
this.paymentConnectionFailed = true;
|
|
367
|
-
if (this.config.options?.debug) {
|
|
368
|
-
console.warn('Invalid payment product ID detected, disabling downloads');
|
|
369
|
-
}
|
|
370
|
-
// Update UI to show disabled state
|
|
371
|
-
this.updateStatusDisplays();
|
|
372
|
-
}
|
|
373
|
-
throw new Error(errorData.error || 'Payment creation failed');
|
|
374
|
-
}
|
|
375
|
-
const result = await response.json();
|
|
376
|
-
if (this.config.options?.debug) {
|
|
377
|
-
//console.log('🔍 DEBUG: createPayment success response:', result);
|
|
378
|
-
}
|
|
379
|
-
return result;
|
|
380
|
-
}
|
|
381
|
-
catch (error) {
|
|
382
|
-
this.handleError(error);
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Simplified API: Attach download limiting to a single button
|
|
388
|
-
* Handles all UI logic automatically (paywall, auth, payment flow)
|
|
389
|
-
*/
|
|
390
|
-
attachToButton(selector, downloadCallback) {
|
|
391
|
-
//console.log('🎨 [THEME DEBUG] attachToButton called');
|
|
392
|
-
//console.log('🎨 [THEME DEBUG] Full config:', JSON.stringify(this.config, null, 2));
|
|
393
|
-
const button = document.querySelector(selector);
|
|
394
|
-
if (!button) {
|
|
395
|
-
throw new Error(`Button not found: ${selector}`);
|
|
396
|
-
}
|
|
397
|
-
// Store original button text (for potential future use)
|
|
398
|
-
// const originalText = button.textContent || 'Download';
|
|
399
|
-
// Create and inject UI elements
|
|
400
|
-
this.injectBaseStyles();
|
|
401
|
-
this.createTitleBar(); // Always create title bar first
|
|
402
|
-
this.createPaywallModal();
|
|
403
|
-
this.createSuccessModal();
|
|
404
|
-
this.createAuthUI();
|
|
405
|
-
this.createStatusDisplay();
|
|
406
|
-
// Set up button click handler
|
|
407
|
-
// Mark button as attached by LeadFast
|
|
408
|
-
button.setAttribute('data-leadfast-attached', 'true');
|
|
409
|
-
button.onclick = async (e) => {
|
|
410
|
-
e.preventDefault();
|
|
411
|
-
await this.handleButtonClick(downloadCallback);
|
|
412
|
-
};
|
|
413
|
-
// Update UI based on auth state
|
|
414
|
-
this.setupUIUpdates();
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Simplified API: Attach download limiting to multiple buttons
|
|
418
|
-
* Each button can have different download callbacks and premium status
|
|
419
|
-
*/
|
|
420
|
-
attachToButtons(config) {
|
|
421
|
-
this.injectBaseStyles();
|
|
422
|
-
this.createTitleBar(); // Always create title bar first
|
|
423
|
-
this.createPaywallModal();
|
|
424
|
-
this.createSuccessModal();
|
|
425
|
-
this.createAuthUI();
|
|
426
|
-
config.buttons.forEach(buttonConfig => {
|
|
427
|
-
const button = document.querySelector(buttonConfig.selector);
|
|
428
|
-
if (!button) {
|
|
429
|
-
console.warn(`Button not found: ${buttonConfig.selector}`);
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
// Store original text for potential future use
|
|
433
|
-
// const originalText = button.textContent || 'Download';
|
|
434
|
-
// Set up click handler - all buttons use the same logic now
|
|
435
|
-
// Mark button as attached by LeadFast
|
|
436
|
-
button.setAttribute('data-leadfast-attached', 'true');
|
|
437
|
-
button.onclick = async (e) => {
|
|
438
|
-
e.preventDefault();
|
|
439
|
-
await this.handleButtonClick(buttonConfig.downloadCallback);
|
|
440
|
-
};
|
|
441
|
-
this.setupUIUpdates();
|
|
442
|
-
});
|
|
443
|
-
// Add global status display
|
|
444
|
-
this.createGlobalStatusDisplay();
|
|
445
|
-
}
|
|
446
|
-
async handleButtonClick(downloadCallback) {
|
|
447
|
-
// Prevent rapid clicks/race conditions
|
|
448
|
-
const button = document.querySelector('[data-leadfast-attached="true"]');
|
|
449
|
-
if (button && button.disabled) {
|
|
450
|
-
console.log('⚠️ Button click ignored - operation already in progress');
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (button) {
|
|
454
|
-
button.disabled = true;
|
|
455
|
-
button.style.opacity = '0.6';
|
|
456
|
-
}
|
|
457
|
-
try {
|
|
458
|
-
await this.handleButtonClickInternal(downloadCallback);
|
|
459
|
-
}
|
|
460
|
-
finally {
|
|
461
|
-
// Re-enable button
|
|
462
|
-
if (button) {
|
|
463
|
-
button.disabled = false;
|
|
464
|
-
button.style.opacity = '1';
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
async handleButtonClickInternal(downloadCallback) {
|
|
469
|
-
// const clickTime = Date.now();
|
|
470
|
-
// console.log(`🕐 [TIMING] Button click started at: ${new Date(clickTime).toISOString()}`);
|
|
471
|
-
// Security check: if Supabase connection failed, disable downloads to prevent localStorage bypass
|
|
472
|
-
if (this.supabaseConnectionFailed) {
|
|
473
|
-
// console.log(`🕐 [TIMING] Connection failed check: ${Date.now() - clickTime}ms`);
|
|
474
|
-
this.showConnectionError();
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
// Security check: if payment configuration is invalid, disable downloads
|
|
478
|
-
if (this.config.payment && this.paymentConnectionFailed) {
|
|
479
|
-
// console.log(`🕐 [TIMING] Payment config failed check: ${Date.now() - clickTime}ms`);
|
|
480
|
-
this.showPaymentConfigError();
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
// Check cache status
|
|
484
|
-
const cacheValid = this.cachedStatus && (Date.now() - this.statusCacheTime) < this.STATUS_CACHE_DURATION;
|
|
485
|
-
// console.log(`🕐 [CACHE] Cache valid: ${cacheValid}, age: ${this.cachedStatus ? Date.now() - this.statusCacheTime : 'no cache'}ms`);
|
|
486
|
-
// console.log(`🕐 [CACHE] Cached status:`, this.cachedStatus);
|
|
487
|
-
// const statusStartTime = Date.now();
|
|
488
|
-
// Use cached status if available (since it's already displayed in title bar)
|
|
489
|
-
const status = cacheValid
|
|
490
|
-
? this.cachedStatus
|
|
491
|
-
: await this.getDownloadStatus();
|
|
492
|
-
// console.log(`🕐 [TIMING] Status check completed: ${Date.now() - statusStartTime}ms (total: ${Date.now() - clickTime}ms)`);
|
|
493
|
-
// console.log(`🕐 [STATUS] Final status:`, status);
|
|
494
|
-
// Check if user is premium first (quick check from cached status)
|
|
495
|
-
if (status.isPremium) {
|
|
496
|
-
// console.log(`🕐 [TIMING] Premium user - callback executed: ${Date.now() - clickTime}ms`);
|
|
497
|
-
// Get user context for premium users too
|
|
498
|
-
const userContext = await this.getUserContext();
|
|
499
|
-
// Check if callback expects user context parameter
|
|
500
|
-
if (downloadCallback.length > 0) {
|
|
501
|
-
// Callback expects user context parameter
|
|
502
|
-
downloadCallback(userContext);
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
// Legacy callback with no parameters
|
|
506
|
-
downloadCallback();
|
|
507
|
-
}
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
// Quick limit check from cached status - show paywall immediately if needed
|
|
511
|
-
if (status.currentCount >= status.limit) {
|
|
512
|
-
// console.log(`🕐 [TIMING] Limit reached (${status.currentCount}/${status.limit}) - showing paywall: ${Date.now() - clickTime}ms`);
|
|
513
|
-
// Limit reached - show paywall instantly (no additional API calls)
|
|
514
|
-
// const paywallStartTime = Date.now();
|
|
515
|
-
this.showPaywallInstant(status);
|
|
516
|
-
// console.log(`🕐 [TIMING] Paywall shown: ${Date.now() - paywallStartTime}ms (total: ${Date.now() - clickTime}ms)`);
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
// console.log(`🕐 [TIMING] Limit not reached (${status.currentCount}/${status.limit}) - proceeding with action`);
|
|
520
|
-
// Allow action and increment counter
|
|
521
|
-
// const incrementStartTime = Date.now();
|
|
522
|
-
// Execute user's function first - only increment count if it succeeds
|
|
523
|
-
try {
|
|
524
|
-
// Check if callback expects user context parameter
|
|
525
|
-
if (downloadCallback.length > 0) {
|
|
526
|
-
// Get user context BEFORE increment for callback
|
|
527
|
-
const userContext = await this.getUserContext();
|
|
528
|
-
// For non-premium users, simulate the increment so user context matches UI
|
|
529
|
-
if (!userContext.isPremium) {
|
|
530
|
-
userContext.currentCount += 1;
|
|
531
|
-
userContext.remaining = Math.max(0, userContext.limit - userContext.currentCount);
|
|
532
|
-
}
|
|
533
|
-
// Callback expects user context parameter
|
|
534
|
-
downloadCallback(userContext);
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
// Legacy callback with no parameters
|
|
538
|
-
downloadCallback();
|
|
539
|
-
}
|
|
540
|
-
// Double-check limit before increment (race condition protection)
|
|
541
|
-
const freshStatus = await this.getDownloadStatus(true); // Force refresh
|
|
542
|
-
if (freshStatus.currentCount >= freshStatus.limit) {
|
|
543
|
-
console.warn(`⚠️ Limit reached during execution (${freshStatus.currentCount}/${freshStatus.limit}) - canceling increment`);
|
|
544
|
-
// User reached limit while their function was executing
|
|
545
|
-
alert('⚠️ You reached the limit while this action was processing. No attempt was counted.');
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
// Only increment count after successful execution - with limit check
|
|
549
|
-
const newCount = await this.incrementDownloadCount();
|
|
550
|
-
// Double-check: if server returned count exceeding limit, something went wrong
|
|
551
|
-
if (newCount > this.config.freeAttemptLimit) {
|
|
552
|
-
console.error(`⚠️ Server returned count (${newCount}) exceeding limit (${this.config.freeAttemptLimit})`);
|
|
553
|
-
// Don't update UI with invalid state
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
// console.log(`🕐 [TIMING] Count incremented: ${Date.now() - incrementStartTime}ms`);
|
|
557
|
-
// Show success message only if explicitly enabled in config
|
|
558
|
-
if (this.config.successMessage?.enabled === true) {
|
|
559
|
-
const title = this.config.successMessage?.title || 'Action Completed!';
|
|
560
|
-
const message = this.config.successMessage?.message || 'Your action completed successfully!';
|
|
561
|
-
this.showSuccess(title, message);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
catch (error) {
|
|
565
|
-
console.error('User function failed:', error);
|
|
566
|
-
// Don't increment count on error - user shouldn't lose an attempt
|
|
567
|
-
alert('Action failed. Please check the console for details. No attempt was counted.');
|
|
568
|
-
return; // Exit early, don't update UI
|
|
569
|
-
}
|
|
570
|
-
// Update UI with fresh status (invalidate cache since count changed)
|
|
571
|
-
// console.log(`🕐 [CACHE] Invalidating cache due to count change (was: ${this.cachedStatus ? 'cached' : 'null'})`);
|
|
572
|
-
this.cachedStatus = null;
|
|
573
|
-
this.updateStatusDisplays();
|
|
574
|
-
// console.log(`🕐 [TIMING] Button click completed: ${Date.now() - clickTime}ms`);
|
|
575
|
-
}
|
|
576
|
-
injectBaseStyles() {
|
|
577
|
-
//console.log('🎨 [THEME DEBUG] injectBaseStyles called');
|
|
578
|
-
if (document.getElementById('lead-fast-styles')) {
|
|
579
|
-
//console.log('🎨 [THEME DEBUG] Styles already injected, skipping');
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
// Inject DaisyUI CDN if theme is specified
|
|
583
|
-
if (this.config.theme?.name) {
|
|
584
|
-
//console.log('🎨 [THEME DEBUG] Theme specified, calling injectDaisyUI');
|
|
585
|
-
this.injectDaisyUI();
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
//console.log('🎨 [THEME DEBUG] No theme specified in config');
|
|
589
|
-
}
|
|
590
|
-
const styles = document.createElement('style');
|
|
591
|
-
styles.id = 'lead-fast-styles';
|
|
592
|
-
const primaryColor = this.config.theme?.primaryColor || '#3182ce';
|
|
593
|
-
styles.textContent = `
|
|
594
|
-
/* User Profile Card - Top Right */
|
|
595
|
-
.lead-fast-profile {
|
|
596
|
-
position: fixed;
|
|
597
|
-
top: 20px;
|
|
598
|
-
left: 50%;
|
|
599
|
-
transform: translateX(-50%);
|
|
600
|
-
z-index: 1000;
|
|
601
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
602
|
-
width: 90%;
|
|
603
|
-
max-width: 1200px;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
.lead-fast-title-bar {
|
|
607
|
-
background: rgba(250, 247, 245, 0.9) !important;
|
|
608
|
-
border: 1px solid rgba(231, 226, 223, 0.6) !important;
|
|
609
|
-
color: #291334 !important;
|
|
610
|
-
border-radius: 16px;
|
|
611
|
-
padding: 12px 24px;
|
|
612
|
-
box-shadow: 0 8px 32px rgb(0 0 0 / 0.1), 0 2px 8px rgb(0 0 0 / 0.05);
|
|
613
|
-
backdrop-filter: blur(20px);
|
|
614
|
-
display: flex;
|
|
615
|
-
align-items: center;
|
|
616
|
-
justify-content: space-between;
|
|
617
|
-
min-height: 60px;
|
|
618
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/* Theme variations for floating title bar */
|
|
622
|
-
[data-theme="bumblebee"] .lead-fast-title-bar {
|
|
623
|
-
background: rgba(255, 248, 220, 0.9) !important;
|
|
624
|
-
border-color: rgba(254, 215, 170, 0.6) !important;
|
|
625
|
-
color: #1c1917 !important;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
[data-theme="garden"] .lead-fast-title-bar {
|
|
629
|
-
background: rgba(240, 253, 244, 0.9) !important;
|
|
630
|
-
border-color: rgba(134, 239, 172, 0.6) !important;
|
|
631
|
-
color: #14532d !important;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
[data-theme="emerald"] .lead-fast-title-bar {
|
|
635
|
-
background: rgba(236, 253, 245, 0.9) !important;
|
|
636
|
-
border-color: rgba(167, 243, 208, 0.6) !important;
|
|
637
|
-
color: #065f46 !important;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
[data-theme="corporate"] .lead-fast-title-bar {
|
|
641
|
-
background: rgba(248, 250, 252, 0.9) !important;
|
|
642
|
-
border-color: rgba(226, 232, 240, 0.6) !important;
|
|
643
|
-
color: #1e293b !important;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
[data-theme="dark"] .lead-fast-title-bar {
|
|
647
|
-
background: rgba(31, 41, 55, 0.9) !important;
|
|
648
|
-
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
649
|
-
color: #f9fafb !important;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
[data-theme="cyberpunk"] .lead-fast-title-bar {
|
|
653
|
-
background: rgba(0, 20, 36, 0.9) !important;
|
|
654
|
-
border-color: rgba(7, 89, 133, 0.6) !important;
|
|
655
|
-
color: #0ea5e9 !important;
|
|
656
|
-
box-shadow: 0 8px 32px rgba(14, 165, 233, 0.2), 0 2px 8px rgba(14, 165, 233, 0.1);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
[data-theme="valentine"] .lead-fast-title-bar {
|
|
660
|
-
background: rgba(233, 30, 122, 0.15) !important;
|
|
661
|
-
border-color: rgba(233, 30, 122, 0.6) !important;
|
|
662
|
-
color: #831843 !important;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
[data-theme="synthwave"] .lead-fast-title-bar {
|
|
666
|
-
background: rgba(32, 20, 64, 0.9) !important;
|
|
667
|
-
border-color: rgba(186, 85, 211, 0.6) !important;
|
|
668
|
-
color: #ff00ff !important;
|
|
669
|
-
box-shadow: 0 8px 32px rgba(255, 0, 255, 0.2), 0 2px 8px rgba(255, 0, 255, 0.1);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
[data-theme="dracula"] .lead-fast-title-bar {
|
|
673
|
-
background: rgba(40, 42, 54, 0.9) !important;
|
|
674
|
-
border-color: rgba(98, 114, 164, 0.6) !important;
|
|
675
|
-
color: #f8f8f2 !important;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
[data-theme="halloween"] .lead-fast-title-bar {
|
|
679
|
-
background: rgba(26, 26, 26, 0.9) !important;
|
|
680
|
-
border-color: rgba(255, 165, 0, 0.6) !important;
|
|
681
|
-
color: #ff6600 !important;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
[data-theme="forest"] .lead-fast-title-bar {
|
|
685
|
-
background: rgba(23, 46, 23, 0.9) !important;
|
|
686
|
-
border-color: rgba(34, 197, 94, 0.6) !important;
|
|
687
|
-
color: #22c55e !important;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
[data-theme="luxury"] .lead-fast-title-bar {
|
|
691
|
-
background: rgba(9, 9, 11, 0.9) !important;
|
|
692
|
-
border-color: rgba(212, 175, 55, 0.6) !important;
|
|
693
|
-
color: #d4af37 !important;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
[data-theme="night"] .lead-fast-title-bar {
|
|
697
|
-
background: rgba(15, 23, 42, 0.9) !important;
|
|
698
|
-
border-color: rgba(30, 58, 138, 0.6) !important;
|
|
699
|
-
color: #60a5fa !important;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
[data-theme="light"] .lead-fast-title-bar {
|
|
703
|
-
background: rgba(255, 255, 255, 0.9) !important;
|
|
704
|
-
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
705
|
-
color: #1f2937 !important;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
[data-theme="cupcake"] .lead-fast-title-bar {
|
|
709
|
-
background: rgba(250, 235, 215, 0.9) !important;
|
|
710
|
-
border-color: rgba(219, 185, 156, 0.6) !important;
|
|
711
|
-
color: #8b4513 !important;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
[data-theme="retro"] .lead-fast-title-bar {
|
|
715
|
-
background: rgba(212, 165, 116, 0.9) !important;
|
|
716
|
-
border-color: rgba(185, 144, 102, 0.6) !important;
|
|
717
|
-
color: #5d4037 !important;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
[data-theme="aqua"] .lead-fast-title-bar {
|
|
721
|
-
background: rgba(240, 253, 250, 0.9) !important;
|
|
722
|
-
border-color: rgba(125, 211, 252, 0.6) !important;
|
|
723
|
-
color: #155e75 !important;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
[data-theme="lofi"] .lead-fast-title-bar {
|
|
727
|
-
background: rgba(248, 248, 248, 0.9) !important;
|
|
728
|
-
border-color: rgba(68, 68, 68, 0.6) !important;
|
|
729
|
-
color: #1a1a1a !important;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
[data-theme="pastel"] .lead-fast-title-bar {
|
|
733
|
-
background: rgba(254, 251, 255, 0.9) !important;
|
|
734
|
-
border-color: rgba(209, 196, 233, 0.6) !important;
|
|
735
|
-
color: #7c3aed !important;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
[data-theme="fantasy"] .lead-fast-title-bar {
|
|
739
|
-
background: rgba(255, 247, 237, 0.9) !important;
|
|
740
|
-
border-color: rgba(249, 168, 212, 0.6) !important;
|
|
741
|
-
color: #be185d !important;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
[data-theme="wireframe"] .lead-fast-title-bar {
|
|
745
|
-
background: rgba(223, 223, 223, 0.9) !important;
|
|
746
|
-
border-color: rgba(0, 0, 0, 0.3) !important;
|
|
747
|
-
color: #000000 !important;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
[data-theme="black"] .lead-fast-title-bar {
|
|
751
|
-
background: rgba(0, 0, 0, 0.9) !important;
|
|
752
|
-
border-color: rgba(55, 55, 55, 0.6) !important;
|
|
753
|
-
color: #ffffff !important;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
[data-theme="cmyk"] .lead-fast-title-bar {
|
|
757
|
-
background: rgba(0, 255, 255, 0.9) !important;
|
|
758
|
-
border-color: rgba(255, 0, 255, 0.6) !important;
|
|
759
|
-
color: #000000 !important;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
[data-theme="autumn"] .lead-fast-title-bar {
|
|
763
|
-
background: rgba(139, 69, 19, 0.9) !important;
|
|
764
|
-
border-color: rgba(255, 140, 0, 0.6) !important;
|
|
765
|
-
color: #ff8c00 !important;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
[data-theme="business"] .lead-fast-title-bar {
|
|
769
|
-
background: rgba(29, 78, 216, 0.9) !important;
|
|
770
|
-
border-color: rgba(59, 130, 246, 0.6) !important;
|
|
771
|
-
color: #3b82f6 !important;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
[data-theme="acid"] .lead-fast-title-bar {
|
|
775
|
-
background: rgba(255, 255, 0, 0.9) !important;
|
|
776
|
-
border-color: rgba(255, 0, 255, 0.6) !important;
|
|
777
|
-
color: #000000 !important;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
[data-theme="lemonade"] .lead-fast-title-bar {
|
|
781
|
-
background: rgba(255, 255, 224, 0.9) !important;
|
|
782
|
-
border-color: rgba(34, 197, 94, 0.6) !important;
|
|
783
|
-
color: #15803d !important;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
[data-theme="coffee"] .lead-fast-title-bar {
|
|
787
|
-
background: rgba(101, 67, 33, 0.9) !important;
|
|
788
|
-
border-color: rgba(160, 82, 45, 0.6) !important;
|
|
789
|
-
color: #d2b48c !important;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
[data-theme="winter"] .lead-fast-title-bar {
|
|
793
|
-
background: rgba(248, 250, 252, 0.9) !important;
|
|
794
|
-
border-color: rgba(59, 130, 246, 0.6) !important;
|
|
795
|
-
color: #1e40af !important;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
.lead-fast-title-section {
|
|
799
|
-
display: flex;
|
|
800
|
-
align-items: center;
|
|
801
|
-
gap: 16px;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
.lead-fast-logo {
|
|
805
|
-
font-size: 20px;
|
|
806
|
-
font-weight: 700;
|
|
807
|
-
display: flex;
|
|
808
|
-
align-items: center;
|
|
809
|
-
gap: 8px;
|
|
810
|
-
color: inherit;
|
|
811
|
-
text-decoration: none;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
.lead-fast-logo img {
|
|
815
|
-
width: 32px;
|
|
816
|
-
height: 32px;
|
|
817
|
-
border-radius: 6px;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
.lead-fast-nav {
|
|
821
|
-
display: flex;
|
|
822
|
-
align-items: center;
|
|
823
|
-
gap: 32px;
|
|
824
|
-
margin-left: 24px;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
.lead-fast-nav-link {
|
|
828
|
-
color: inherit;
|
|
829
|
-
text-decoration: none;
|
|
830
|
-
font-weight: 500;
|
|
831
|
-
font-size: 14px;
|
|
832
|
-
opacity: 0.8;
|
|
833
|
-
transition: all 0.2s;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
.lead-fast-nav-link:hover {
|
|
837
|
-
opacity: 1;
|
|
838
|
-
text-decoration: none;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
.lead-fast-user-section {
|
|
842
|
-
display: flex;
|
|
843
|
-
align-items: center;
|
|
844
|
-
gap: 12px;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
.lead-fast-avatar {
|
|
848
|
-
width: 36px;
|
|
849
|
-
height: 36px;
|
|
850
|
-
border-radius: 50%;
|
|
851
|
-
background: linear-gradient(135deg, ${primaryColor}, #4f46e5);
|
|
852
|
-
display: flex;
|
|
853
|
-
align-items: center;
|
|
854
|
-
justify-content: center;
|
|
855
|
-
color: white;
|
|
856
|
-
font-weight: 600;
|
|
857
|
-
font-size: 14px;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
.lead-fast-user-info {
|
|
861
|
-
display: flex;
|
|
862
|
-
flex-direction: column;
|
|
863
|
-
align-items: flex-start;
|
|
864
|
-
gap: 2px;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
.lead-fast-user-email {
|
|
868
|
-
color: inherit !important;
|
|
869
|
-
font-size: 13px;
|
|
870
|
-
font-weight: 500;
|
|
871
|
-
line-height: 1;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
.lead-fast-user-status {
|
|
875
|
-
display: flex;
|
|
876
|
-
align-items: center;
|
|
877
|
-
gap: 6px;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
.lead-fast-badge {
|
|
881
|
-
padding: 2px 6px;
|
|
882
|
-
border-radius: 12px;
|
|
883
|
-
font-size: 10px;
|
|
884
|
-
font-weight: 600;
|
|
885
|
-
text-transform: uppercase;
|
|
886
|
-
letter-spacing: 0.025em;
|
|
887
|
-
line-height: 1;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
.lead-fast-badge.premium {
|
|
891
|
-
background: #10b981 !important;
|
|
892
|
-
color: #ffffff !important;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
.lead-fast-badge.free {
|
|
896
|
-
background: #65c3c8 !important;
|
|
897
|
-
color: #291334 !important;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
[data-theme="dark"] .lead-fast-badge.premium {
|
|
901
|
-
background: #059669 !important;
|
|
902
|
-
color: #ffffff !important;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
[data-theme="dark"] .lead-fast-badge.free {
|
|
906
|
-
background: #374151 !important;
|
|
907
|
-
color: #f9fafb !important;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
.lead-fast-count {
|
|
911
|
-
font-size: 10px;
|
|
912
|
-
color: inherit !important;
|
|
913
|
-
opacity: 0.7;
|
|
914
|
-
line-height: 1;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
.lead-fast-signout {
|
|
918
|
-
background: rgba(0, 0, 0, 0.1) !important;
|
|
919
|
-
border: none;
|
|
920
|
-
color: inherit !important;
|
|
921
|
-
cursor: pointer;
|
|
922
|
-
padding: 6px 12px;
|
|
923
|
-
border-radius: 8px;
|
|
924
|
-
transition: all 0.2s;
|
|
925
|
-
font-size: 11px;
|
|
926
|
-
font-weight: 500;
|
|
927
|
-
margin-left: 8px;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
[data-theme="dark"] .lead-fast-signout {
|
|
931
|
-
background: rgba(255, 255, 255, 0.1) !important;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
[data-theme="cyberpunk"] .lead-fast-signout {
|
|
935
|
-
background: rgba(14, 165, 233, 0.2) !important;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
.lead-fast-signout:hover {
|
|
939
|
-
background: rgba(0, 0, 0, 0.2) !important;
|
|
940
|
-
transform: translateY(-1px);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
[data-theme="dark"] .lead-fast-signout:hover {
|
|
944
|
-
background: rgba(255, 255, 255, 0.2) !important;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
[data-theme="cyberpunk"] .lead-fast-signout:hover {
|
|
948
|
-
background: rgba(14, 165, 233, 0.3) !important;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
.lead-fast-branding {
|
|
952
|
-
position: absolute;
|
|
953
|
-
bottom: -20px;
|
|
954
|
-
left: 24px;
|
|
955
|
-
font-size: 9px;
|
|
956
|
-
color: inherit !important;
|
|
957
|
-
opacity: 0.4;
|
|
958
|
-
font-weight: 500;
|
|
959
|
-
letter-spacing: 0.05em;
|
|
960
|
-
text-transform: lowercase;
|
|
961
|
-
background: rgba(255, 255, 255, 0.8);
|
|
962
|
-
padding: 2px 6px;
|
|
963
|
-
border-radius: 4px;
|
|
964
|
-
backdrop-filter: blur(8px);
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
[data-theme="dark"] .lead-fast-branding {
|
|
968
|
-
background: rgba(0, 0, 0, 0.6);
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
[data-theme="cyberpunk"] .lead-fast-branding {
|
|
972
|
-
background: rgba(0, 20, 36, 0.8);
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
/* Modal Theming */
|
|
976
|
-
.download-limiter-modal {
|
|
977
|
-
display: none;
|
|
978
|
-
position: fixed;
|
|
979
|
-
top: 0;
|
|
980
|
-
left: 0;
|
|
981
|
-
width: 100%;
|
|
982
|
-
height: 100%;
|
|
983
|
-
background: rgba(0,0,0,0.5);
|
|
984
|
-
z-index: 9999;
|
|
985
|
-
align-items: center;
|
|
986
|
-
justify-content: center;
|
|
987
|
-
}
|
|
988
|
-
.download-limiter-modal.show { display: flex; }
|
|
989
|
-
|
|
990
|
-
.download-limiter-content {
|
|
991
|
-
background: #faf7f5 !important;
|
|
992
|
-
color: #291334 !important;
|
|
993
|
-
padding: 2rem;
|
|
994
|
-
border-radius: 12px;
|
|
995
|
-
max-width: 400px;
|
|
996
|
-
text-align: center;
|
|
997
|
-
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
|
998
|
-
border: 1px solid #e7e2df !important;
|
|
999
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
[data-theme="dark"] .download-limiter-content {
|
|
1003
|
-
background: #1f2937 !important;
|
|
1004
|
-
color: #f9fafb !important;
|
|
1005
|
-
border-color: #374151 !important;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
[data-theme="cyberpunk"] .download-limiter-content {
|
|
1009
|
-
background: #001424 !important;
|
|
1010
|
-
color: #0ea5e9 !important;
|
|
1011
|
-
border-color: #075985 !important;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
[data-theme="valentine"] .download-limiter-content {
|
|
1015
|
-
background: rgba(233, 30, 122, 0.05) !important;
|
|
1016
|
-
color: #831843 !important;
|
|
1017
|
-
border-color: #e91e7a !important;
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
[data-theme="bumblebee"] .download-limiter-content {
|
|
1021
|
-
background: rgba(255, 248, 220, 0.95) !important;
|
|
1022
|
-
color: #1c1917 !important;
|
|
1023
|
-
border-color: rgba(254, 215, 170, 0.8) !important;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
[data-theme="garden"] .download-limiter-content {
|
|
1027
|
-
background: rgba(240, 253, 244, 0.95) !important;
|
|
1028
|
-
color: #14532d !important;
|
|
1029
|
-
border-color: rgba(134, 239, 172, 0.8) !important;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
[data-theme="emerald"] .download-limiter-content {
|
|
1033
|
-
background: rgba(236, 253, 245, 0.95) !important;
|
|
1034
|
-
color: #065f46 !important;
|
|
1035
|
-
border-color: rgba(167, 243, 208, 0.8) !important;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
[data-theme="corporate"] .download-limiter-content {
|
|
1039
|
-
background: rgba(248, 250, 252, 0.95) !important;
|
|
1040
|
-
color: #1e293b !important;
|
|
1041
|
-
border-color: rgba(226, 232, 240, 0.8) !important;
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
[data-theme="forest"] .download-limiter-content {
|
|
1045
|
-
background: rgba(23, 46, 23, 0.95) !important;
|
|
1046
|
-
color: #22c55e !important;
|
|
1047
|
-
border-color: rgba(34, 197, 94, 0.8) !important;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
[data-theme="halloween"] .download-limiter-content {
|
|
1051
|
-
background: rgba(26, 26, 26, 0.95) !important;
|
|
1052
|
-
color: #ff6500 !important;
|
|
1053
|
-
border-color: rgba(255, 165, 0, 0.3) !important;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
[data-theme="synthwave"] .download-limiter-content {
|
|
1057
|
-
background: rgba(32, 20, 64, 0.95) !important;
|
|
1058
|
-
color: #ba55d3 !important;
|
|
1059
|
-
border-color: rgba(186, 85, 211, 0.3) !important;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
[data-theme="dracula"] .download-limiter-content {
|
|
1063
|
-
background: rgba(40, 42, 54, 0.95) !important;
|
|
1064
|
-
color: #f8f8f2 !important;
|
|
1065
|
-
border-color: rgba(98, 114, 164, 0.3) !important;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
[data-theme="luxury"] .download-limiter-content {
|
|
1069
|
-
background: rgba(9, 9, 11, 0.95) !important;
|
|
1070
|
-
color: #d4af37 !important;
|
|
1071
|
-
border-color: rgba(212, 175, 55, 0.3) !important;
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
[data-theme="night"] .download-limiter-content {
|
|
1075
|
-
background: rgba(15, 23, 42, 0.95) !important;
|
|
1076
|
-
color: #1e40af !important;
|
|
1077
|
-
border-color: rgba(30, 58, 138, 0.3) !important;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
[data-theme="light"] .download-limiter-content {
|
|
1081
|
-
background: rgba(255, 255, 255, 0.95) !important;
|
|
1082
|
-
color: #1f2937 !important;
|
|
1083
|
-
border-color: rgba(229, 231, 235, 0.8) !important;
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
[data-theme="cupcake"] .download-limiter-content {
|
|
1087
|
-
background: rgba(250, 235, 215, 0.95) !important;
|
|
1088
|
-
color: #8b4513 !important;
|
|
1089
|
-
border-color: rgba(219, 185, 156, 0.8) !important;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
[data-theme="retro"] .download-limiter-content {
|
|
1093
|
-
background: rgba(212, 165, 116, 0.95) !important;
|
|
1094
|
-
color: #5d4037 !important;
|
|
1095
|
-
border-color: rgba(185, 144, 102, 0.8) !important;
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
[data-theme="aqua"] .download-limiter-content {
|
|
1099
|
-
background: rgba(240, 253, 250, 0.95) !important;
|
|
1100
|
-
color: #155e75 !important;
|
|
1101
|
-
border-color: rgba(125, 211, 252, 0.8) !important;
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
[data-theme="lofi"] .download-limiter-content {
|
|
1105
|
-
background: rgba(248, 248, 248, 0.95) !important;
|
|
1106
|
-
color: #1a1a1a !important;
|
|
1107
|
-
border-color: rgba(68, 68, 68, 0.8) !important;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
[data-theme="pastel"] .download-limiter-content {
|
|
1111
|
-
background: rgba(254, 251, 255, 0.95) !important;
|
|
1112
|
-
color: #7c3aed !important;
|
|
1113
|
-
border-color: rgba(209, 196, 233, 0.8) !important;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
[data-theme="fantasy"] .download-limiter-content {
|
|
1117
|
-
background: rgba(255, 247, 237, 0.95) !important;
|
|
1118
|
-
color: #be185d !important;
|
|
1119
|
-
border-color: rgba(249, 168, 212, 0.8) !important;
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
[data-theme="wireframe"] .download-limiter-content {
|
|
1123
|
-
background: rgba(255, 255, 255, 0.95) !important;
|
|
1124
|
-
color: #000000 !important;
|
|
1125
|
-
border-color: rgba(0, 0, 0, 0.3) !important;
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
[data-theme="black"] .download-limiter-content {
|
|
1129
|
-
background: rgba(0, 0, 0, 0.95) !important;
|
|
1130
|
-
color: #ffffff !important;
|
|
1131
|
-
border-color: rgba(55, 55, 55, 0.8) !important;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
[data-theme="cmyk"] .download-limiter-content {
|
|
1135
|
-
background: rgba(0, 255, 255, 0.95) !important;
|
|
1136
|
-
color: #000000 !important;
|
|
1137
|
-
border-color: rgba(255, 0, 255, 0.8) !important;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
[data-theme="autumn"] .download-limiter-content {
|
|
1141
|
-
background: rgba(139, 69, 19, 0.95) !important;
|
|
1142
|
-
color: #ff8c00 !important;
|
|
1143
|
-
border-color: rgba(255, 140, 0, 0.8) !important;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
[data-theme="business"] .download-limiter-content {
|
|
1147
|
-
background: rgba(29, 78, 216, 0.95) !important;
|
|
1148
|
-
color: #3b82f6 !important;
|
|
1149
|
-
border-color: rgba(59, 130, 246, 0.8) !important;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
[data-theme="acid"] .download-limiter-content {
|
|
1153
|
-
background: rgba(255, 255, 0, 0.95) !important;
|
|
1154
|
-
color: #000000 !important;
|
|
1155
|
-
border-color: rgba(255, 0, 255, 0.8) !important;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
[data-theme="lemonade"] .download-limiter-content {
|
|
1159
|
-
background: rgba(255, 255, 224, 0.95) !important;
|
|
1160
|
-
color: #15803d !important;
|
|
1161
|
-
border-color: rgba(34, 197, 94, 0.8) !important;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
[data-theme="coffee"] .download-limiter-content {
|
|
1165
|
-
background: rgba(101, 67, 33, 0.95) !important;
|
|
1166
|
-
color: #d2b48c !important;
|
|
1167
|
-
border-color: rgba(160, 82, 45, 0.8) !important;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
[data-theme="winter"] .download-limiter-content {
|
|
1171
|
-
background: rgba(248, 250, 252, 0.95) !important;
|
|
1172
|
-
color: #1e40af !important;
|
|
1173
|
-
border-color: rgba(59, 130, 246, 0.8) !important;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
.download-limiter-content h2 {
|
|
1177
|
-
color: inherit !important;
|
|
1178
|
-
margin: 0 0 1rem 0;
|
|
1179
|
-
font-size: 1.5rem;
|
|
1180
|
-
font-weight: 600;
|
|
1181
|
-
}
|
|
1182
|
-
.download-limiter-content p {
|
|
1183
|
-
color: inherit !important;
|
|
1184
|
-
margin: 0 0 1.5rem 0;
|
|
1185
|
-
line-height: 1.6;
|
|
1186
|
-
}
|
|
1187
|
-
.download-limiter-signin-btn {
|
|
1188
|
-
display: inline-flex;
|
|
1189
|
-
align-items: center;
|
|
1190
|
-
gap: 8px;
|
|
1191
|
-
background: #65c3c8 !important;
|
|
1192
|
-
color: #291334 !important;
|
|
1193
|
-
border: none;
|
|
1194
|
-
padding: 12px 20px;
|
|
1195
|
-
border-radius: 8px;
|
|
1196
|
-
font-size: 14px;
|
|
1197
|
-
font-weight: 500;
|
|
1198
|
-
cursor: pointer;
|
|
1199
|
-
text-decoration: none;
|
|
1200
|
-
transition: all 0.2s;
|
|
1201
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
[data-theme="dark"] .download-limiter-signin-btn {
|
|
1205
|
-
background: #3b82f6 !important;
|
|
1206
|
-
color: #ffffff !important;
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
[data-theme="cyberpunk"] .download-limiter-signin-btn {
|
|
1210
|
-
background: #f59e0b !important;
|
|
1211
|
-
color: #000000 !important;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
[data-theme="valentine"] .download-limiter-signin-btn {
|
|
1215
|
-
background: #e91e7a !important;
|
|
1216
|
-
color: #ffffff !important;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
[data-theme="bumblebee"] .download-limiter-signin-btn {
|
|
1220
|
-
background: #fbbf24 !important;
|
|
1221
|
-
color: #1c1917 !important;
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
[data-theme="garden"] .download-limiter-signin-btn {
|
|
1225
|
-
background: #22c55e !important;
|
|
1226
|
-
color: #ffffff !important;
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
[data-theme="emerald"] .download-limiter-signin-btn {
|
|
1230
|
-
background: #10b981 !important;
|
|
1231
|
-
color: #ffffff !important;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
[data-theme="corporate"] .download-limiter-signin-btn {
|
|
1235
|
-
background: #3b82f6 !important;
|
|
1236
|
-
color: #ffffff !important;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
[data-theme="forest"] .download-limiter-signin-btn {
|
|
1240
|
-
background: #22c55e !important;
|
|
1241
|
-
color: #1a1a1a !important;
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
[data-theme="halloween"] .download-limiter-signin-btn {
|
|
1245
|
-
background: #ff6500 !important;
|
|
1246
|
-
color: #1a1a1a !important;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
[data-theme="synthwave"] .download-limiter-signin-btn {
|
|
1250
|
-
background: #ba55d3 !important;
|
|
1251
|
-
color: #201040 !important;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
[data-theme="dracula"] .download-limiter-signin-btn {
|
|
1255
|
-
background: #8be9fd !important;
|
|
1256
|
-
color: #282a36 !important;
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
[data-theme="luxury"] .download-limiter-signin-btn {
|
|
1260
|
-
background: #d4af37 !important;
|
|
1261
|
-
color: #09090b !important;
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
[data-theme="night"] .download-limiter-signin-btn {
|
|
1265
|
-
background: #1e40af !important;
|
|
1266
|
-
color: #ffffff !important;
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
[data-theme="light"] .download-limiter-signin-btn {
|
|
1270
|
-
background: #3b82f6 !important;
|
|
1271
|
-
color: #ffffff !important;
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
[data-theme="cupcake"] .download-limiter-signin-btn {
|
|
1275
|
-
background: #8b4513 !important;
|
|
1276
|
-
color: #ffffff !important;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
[data-theme="retro"] .download-limiter-signin-btn {
|
|
1280
|
-
background: #d4a574 !important;
|
|
1281
|
-
color: #ffffff !important;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
[data-theme="aqua"] .download-limiter-signin-btn {
|
|
1285
|
-
background: #0891b2 !important;
|
|
1286
|
-
color: #ffffff !important;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
[data-theme="lofi"] .download-limiter-signin-btn {
|
|
1290
|
-
background: #1a1a1a !important;
|
|
1291
|
-
color: #ffffff !important;
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
[data-theme="pastel"] .download-limiter-signin-btn {
|
|
1295
|
-
background: #7c3aed !important;
|
|
1296
|
-
color: #ffffff !important;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
[data-theme="fantasy"] .download-limiter-signin-btn {
|
|
1300
|
-
background: #be185d !important;
|
|
1301
|
-
color: #ffffff !important;
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
[data-theme="wireframe"] .download-limiter-signin-btn {
|
|
1305
|
-
background: #dfdfdf !important;
|
|
1306
|
-
color: #000000 !important;
|
|
1307
|
-
border: 1px solid #000000 !important;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
[data-theme="black"] .download-limiter-signin-btn {
|
|
1311
|
-
background: #ffffff !important;
|
|
1312
|
-
color: #000000 !important;
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
[data-theme="cmyk"] .download-limiter-signin-btn {
|
|
1316
|
-
background: #ff00ff !important;
|
|
1317
|
-
color: #000000 !important;
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
[data-theme="autumn"] .download-limiter-signin-btn {
|
|
1321
|
-
background: #ff8c00 !important;
|
|
1322
|
-
color: #ffffff !important;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
[data-theme="business"] .download-limiter-signin-btn {
|
|
1326
|
-
background: #3b82f6 !important;
|
|
1327
|
-
color: #ffffff !important;
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
[data-theme="acid"] .download-limiter-signin-btn {
|
|
1331
|
-
background: #ff00ff !important;
|
|
1332
|
-
color: #000000 !important;
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
[data-theme="lemonade"] .download-limiter-signin-btn {
|
|
1336
|
-
background: #22c55e !important;
|
|
1337
|
-
color: #ffffff !important;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
[data-theme="coffee"] .download-limiter-signin-btn {
|
|
1341
|
-
background: #a0522d !important;
|
|
1342
|
-
color: #ffffff !important;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
[data-theme="winter"] .download-limiter-signin-btn {
|
|
1346
|
-
background: #3b82f6 !important;
|
|
1347
|
-
color: #ffffff !important;
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
.download-limiter-signin-btn:hover {
|
|
1351
|
-
opacity: 0.9;
|
|
1352
|
-
transform: translateY(-1px);
|
|
1353
|
-
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
/* Upgrade Button Styles for All Themes */
|
|
1357
|
-
[data-theme="dark"] .upgrade-btn {
|
|
1358
|
-
background: #3b82f6 !important;
|
|
1359
|
-
color: #ffffff !important;
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
[data-theme="cyberpunk"] .upgrade-btn {
|
|
1363
|
-
background: #ff073a !important;
|
|
1364
|
-
color: #ffffff !important;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
[data-theme="valentine"] .upgrade-btn {
|
|
1368
|
-
background: #e91e7a !important;
|
|
1369
|
-
color: #ffffff !important;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
[data-theme="bumblebee"] .upgrade-btn {
|
|
1373
|
-
background: #f59e0b !important;
|
|
1374
|
-
color: #ffffff !important;
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
[data-theme="garden"] .upgrade-btn {
|
|
1378
|
-
background: #10b981 !important;
|
|
1379
|
-
color: #ffffff !important;
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
[data-theme="emerald"] .upgrade-btn {
|
|
1383
|
-
background: #10b981 !important;
|
|
1384
|
-
color: #ffffff !important;
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
[data-theme="corporate"] .upgrade-btn {
|
|
1388
|
-
background: #3b82f6 !important;
|
|
1389
|
-
color: #ffffff !important;
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
[data-theme="forest"] .upgrade-btn {
|
|
1393
|
-
background: #10b981 !important;
|
|
1394
|
-
color: #ffffff !important;
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
[data-theme="halloween"] .upgrade-btn {
|
|
1398
|
-
background: #ff7b00 !important;
|
|
1399
|
-
color: #000000 !important;
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
[data-theme="synthwave"] .upgrade-btn {
|
|
1403
|
-
background: #e879f9 !important;
|
|
1404
|
-
color: #ffffff !important;
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
[data-theme="dracula"] .upgrade-btn {
|
|
1408
|
-
background: #bd93f9 !important;
|
|
1409
|
-
color: #ffffff !important;
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
[data-theme="luxury"] .upgrade-btn {
|
|
1413
|
-
background: #d4af37 !important;
|
|
1414
|
-
color: #000000 !important;
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
[data-theme="night"] .upgrade-btn {
|
|
1418
|
-
background: #38bdf8 !important;
|
|
1419
|
-
color: #ffffff !important;
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
[data-theme="light"] .upgrade-btn {
|
|
1423
|
-
background: #3b82f6 !important;
|
|
1424
|
-
color: #ffffff !important;
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
[data-theme="cupcake"] .upgrade-btn {
|
|
1428
|
-
background: #f472b6 !important;
|
|
1429
|
-
color: #ffffff !important;
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
[data-theme="retro"] .upgrade-btn {
|
|
1433
|
-
background: #d4a574 !important;
|
|
1434
|
-
color: #ffffff !important;
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
[data-theme="aqua"] .upgrade-btn {
|
|
1438
|
-
background: #06b6d4 !important;
|
|
1439
|
-
color: #ffffff !important;
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
[data-theme="lofi"] .upgrade-btn {
|
|
1443
|
-
background: #a3a3a3 !important;
|
|
1444
|
-
color: #000000 !important;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
[data-theme="pastel"] .upgrade-btn {
|
|
1448
|
-
background: #a78bfa !important;
|
|
1449
|
-
color: #ffffff !important;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
[data-theme="fantasy"] .upgrade-btn {
|
|
1453
|
-
background: #f472b6 !important;
|
|
1454
|
-
color: #ffffff !important;
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
[data-theme="wireframe"] .upgrade-btn {
|
|
1458
|
-
background: #dfdfdf !important;
|
|
1459
|
-
color: #000000 !important;
|
|
1460
|
-
border: 1px solid #000000 !important;
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
[data-theme="black"] .upgrade-btn {
|
|
1464
|
-
background: #ffffff !important;
|
|
1465
|
-
color: #000000 !important;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
[data-theme="cmyk"] .upgrade-btn {
|
|
1469
|
-
background: #0891b2 !important;
|
|
1470
|
-
color: #ffffff !important;
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
[data-theme="autumn"] .upgrade-btn {
|
|
1474
|
-
background: #d97706 !important;
|
|
1475
|
-
color: #ffffff !important;
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
[data-theme="business"] .upgrade-btn {
|
|
1479
|
-
background: #3b82f6 !important;
|
|
1480
|
-
color: #ffffff !important;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
[data-theme="acid"] .upgrade-btn {
|
|
1484
|
-
background: #84cc16 !important;
|
|
1485
|
-
color: #000000 !important;
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
[data-theme="lemonade"] .upgrade-btn {
|
|
1489
|
-
background: #22c55e !important;
|
|
1490
|
-
color: #ffffff !important;
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
[data-theme="coffee"] .upgrade-btn {
|
|
1494
|
-
background: #a0522d !important;
|
|
1495
|
-
color: #ffffff !important;
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
[data-theme="winter"] .upgrade-btn {
|
|
1499
|
-
background: #3b82f6 !important;
|
|
1500
|
-
color: #ffffff !important;
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
/* Cancel Button Styles for All Themes */
|
|
1504
|
-
[data-theme="dark"] .cancel-btn {
|
|
1505
|
-
background: #4b5563 !important;
|
|
1506
|
-
color: #ffffff !important;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
[data-theme="cyberpunk"] .cancel-btn {
|
|
1510
|
-
background: #7e22ce !important;
|
|
1511
|
-
color: #ffffff !important;
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
[data-theme="valentine"] .cancel-btn {
|
|
1515
|
-
background: #c11560 !important;
|
|
1516
|
-
color: #ffffff !important;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
[data-theme="bumblebee"] .cancel-btn {
|
|
1520
|
-
background: #d97706 !important;
|
|
1521
|
-
color: #ffffff !important;
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
[data-theme="garden"] .cancel-btn {
|
|
1525
|
-
background: #059669 !important;
|
|
1526
|
-
color: #ffffff !important;
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
[data-theme="emerald"] .cancel-btn {
|
|
1530
|
-
background: #059669 !important;
|
|
1531
|
-
color: #ffffff !important;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
[data-theme="corporate"] .cancel-btn {
|
|
1535
|
-
background: #6b7280 !important;
|
|
1536
|
-
color: #ffffff !important;
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
[data-theme="forest"] .cancel-btn {
|
|
1540
|
-
background: #059669 !important;
|
|
1541
|
-
color: #ffffff !important;
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
[data-theme="halloween"] .cancel-btn {
|
|
1545
|
-
background: #7c3aed !important;
|
|
1546
|
-
color: #ffffff !important;
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
[data-theme="synthwave"] .cancel-btn {
|
|
1550
|
-
background: #7e22ce !important;
|
|
1551
|
-
color: #ffffff !important;
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
[data-theme="dracula"] .cancel-btn {
|
|
1555
|
-
background: #6272a4 !important;
|
|
1556
|
-
color: #ffffff !important;
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
[data-theme="luxury"] .cancel-btn {
|
|
1560
|
-
background: #8b5cf6 !important;
|
|
1561
|
-
color: #ffffff !important;
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
[data-theme="night"] .cancel-btn {
|
|
1565
|
-
background: #0284c7 !important;
|
|
1566
|
-
color: #ffffff !important;
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
[data-theme="light"] .cancel-btn {
|
|
1570
|
-
background: #6b7280 !important;
|
|
1571
|
-
color: #ffffff !important;
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
[data-theme="cupcake"] .cancel-btn {
|
|
1575
|
-
background: #ec4899 !important;
|
|
1576
|
-
color: #ffffff !important;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
[data-theme="retro"] .cancel-btn {
|
|
1580
|
-
background: #b8925c !important;
|
|
1581
|
-
color: #ffffff !important;
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
[data-theme="aqua"] .cancel-btn {
|
|
1585
|
-
background: #0891b2 !important;
|
|
1586
|
-
color: #ffffff !important;
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
[data-theme="lofi"] .cancel-btn {
|
|
1590
|
-
background: #6b7280 !important;
|
|
1591
|
-
color: #ffffff !important;
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
[data-theme="pastel"] .cancel-btn {
|
|
1595
|
-
background: #8b5cf6 !important;
|
|
1596
|
-
color: #ffffff !important;
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
[data-theme="fantasy"] .cancel-btn {
|
|
1600
|
-
background: #ec4899 !important;
|
|
1601
|
-
color: #ffffff !important;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
[data-theme="wireframe"] .cancel-btn {
|
|
1605
|
-
background: #f5f5f5 !important;
|
|
1606
|
-
color: #000000 !important;
|
|
1607
|
-
border: 1px solid #000000 !important;
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
[data-theme="black"] .cancel-btn {
|
|
1611
|
-
background: #6b7280 !important;
|
|
1612
|
-
color: #ffffff !important;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
[data-theme="cmyk"] .cancel-btn {
|
|
1616
|
-
background: #0369a1 !important;
|
|
1617
|
-
color: #ffffff !important;
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
[data-theme="autumn"] .cancel-btn {
|
|
1621
|
-
background: #c2410c !important;
|
|
1622
|
-
color: #ffffff !important;
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
[data-theme="business"] .cancel-btn {
|
|
1626
|
-
background: #6b7280 !important;
|
|
1627
|
-
color: #ffffff !important;
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
[data-theme="acid"] .cancel-btn {
|
|
1631
|
-
background: #65a30d !important;
|
|
1632
|
-
color: #ffffff !important;
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
[data-theme="lemonade"] .cancel-btn {
|
|
1636
|
-
background: #16a34a !important;
|
|
1637
|
-
color: #ffffff !important;
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
[data-theme="coffee"] .cancel-btn {
|
|
1641
|
-
background: #8b4513 !important;
|
|
1642
|
-
color: #ffffff !important;
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
[data-theme="winter"] .cancel-btn {
|
|
1646
|
-
background: #1e40af !important;
|
|
1647
|
-
color: #ffffff !important;
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
/* Success Button Styles for All Themes */
|
|
1651
|
-
[data-theme="dark"] .success-btn {
|
|
1652
|
-
background: #10b981 !important;
|
|
1653
|
-
color: #ffffff !important;
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
[data-theme="cyberpunk"] .success-btn {
|
|
1657
|
-
background: #00ff41 !important;
|
|
1658
|
-
color: #000000 !important;
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
[data-theme="valentine"] .success-btn {
|
|
1662
|
-
background: #e91e7a !important;
|
|
1663
|
-
color: #ffffff !important;
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
[data-theme="bumblebee"] .success-btn {
|
|
1667
|
-
background: #f59e0b !important;
|
|
1668
|
-
color: #ffffff !important;
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
[data-theme="garden"] .success-btn {
|
|
1672
|
-
background: #10b981 !important;
|
|
1673
|
-
color: #ffffff !important;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
[data-theme="emerald"] .success-btn {
|
|
1677
|
-
background: #10b981 !important;
|
|
1678
|
-
color: #ffffff !important;
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
[data-theme="corporate"] .success-btn {
|
|
1682
|
-
background: #10b981 !important;
|
|
1683
|
-
color: #ffffff !important;
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
[data-theme="forest"] .success-btn {
|
|
1687
|
-
background: #10b981 !important;
|
|
1688
|
-
color: #ffffff !important;
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
[data-theme="halloween"] .success-btn {
|
|
1692
|
-
background: #ff7b00 !important;
|
|
1693
|
-
color: #000000 !important;
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
[data-theme="synthwave"] .success-btn {
|
|
1697
|
-
background: #00ff41 !important;
|
|
1698
|
-
color: #000000 !important;
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
[data-theme="dracula"] .success-btn {
|
|
1702
|
-
background: #50fa7b !important;
|
|
1703
|
-
color: #000000 !important;
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1706
|
-
[data-theme="luxury"] .success-btn {
|
|
1707
|
-
background: #d4af37 !important;
|
|
1708
|
-
color: #000000 !important;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
[data-theme="night"] .success-btn {
|
|
1712
|
-
background: #22d3ee !important;
|
|
1713
|
-
color: #ffffff !important;
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
[data-theme="light"] .success-btn {
|
|
1717
|
-
background: #10b981 !important;
|
|
1718
|
-
color: #ffffff !important;
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
[data-theme="cupcake"] .success-btn {
|
|
1722
|
-
background: #f472b6 !important;
|
|
1723
|
-
color: #ffffff !important;
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
[data-theme="retro"] .success-btn {
|
|
1727
|
-
background: #d4a574 !important;
|
|
1728
|
-
color: #ffffff !important;
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
[data-theme="aqua"] .success-btn {
|
|
1732
|
-
background: #06b6d4 !important;
|
|
1733
|
-
color: #ffffff !important;
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
[data-theme="lofi"] .success-btn {
|
|
1737
|
-
background: #10b981 !important;
|
|
1738
|
-
color: #ffffff !important;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
[data-theme="pastel"] .success-btn {
|
|
1742
|
-
background: #34d399 !important;
|
|
1743
|
-
color: #ffffff !important;
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
[data-theme="fantasy"] .success-btn {
|
|
1747
|
-
background: #f472b6 !important;
|
|
1748
|
-
color: #ffffff !important;
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
[data-theme="wireframe"] .success-btn {
|
|
1752
|
-
background: #dfdfdf !important;
|
|
1753
|
-
color: #000000 !important;
|
|
1754
|
-
border: 1px solid #000000 !important;
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
[data-theme="black"] .success-btn {
|
|
1758
|
-
background: #10b981 !important;
|
|
1759
|
-
color: #ffffff !important;
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
[data-theme="cmyk"] .success-btn {
|
|
1763
|
-
background: #06b6d4 !important;
|
|
1764
|
-
color: #ffffff !important;
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
[data-theme="autumn"] .success-btn {
|
|
1768
|
-
background: #d97706 !important;
|
|
1769
|
-
color: #ffffff !important;
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
[data-theme="business"] .success-btn {
|
|
1773
|
-
background: #10b981 !important;
|
|
1774
|
-
color: #ffffff !important;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
[data-theme="acid"] .success-btn {
|
|
1778
|
-
background: #84cc16 !important;
|
|
1779
|
-
color: #000000 !important;
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
[data-theme="lemonade"] .success-btn {
|
|
1783
|
-
background: #22c55e !important;
|
|
1784
|
-
color: #ffffff !important;
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
[data-theme="coffee"] .success-btn {
|
|
1788
|
-
background: #a0522d !important;
|
|
1789
|
-
color: #ffffff !important;
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
[data-theme="winter"] .success-btn {
|
|
1793
|
-
background: #3b82f6 !important;
|
|
1794
|
-
color: #ffffff !important;
|
|
1795
|
-
}
|
|
1796
|
-
.download-limiter-signout-btn {
|
|
1797
|
-
background: #dc3545;
|
|
1798
|
-
color: white;
|
|
1799
|
-
border: none;
|
|
1800
|
-
padding: 6px 12px;
|
|
1801
|
-
border-radius: 4px;
|
|
1802
|
-
font-size: 12px;
|
|
1803
|
-
cursor: pointer;
|
|
1804
|
-
margin-left: 8px;
|
|
1805
|
-
}
|
|
1806
|
-
.download-limiter-premium-badge {
|
|
1807
|
-
background: linear-gradient(45deg, #f39c12, #e74c3c);
|
|
1808
|
-
color: white;
|
|
1809
|
-
padding: 2px 6px;
|
|
1810
|
-
border-radius: 3px;
|
|
1811
|
-
font-size: 10px;
|
|
1812
|
-
margin-left: 8px;
|
|
1813
|
-
font-weight: bold;
|
|
1814
|
-
}
|
|
1815
|
-
.download-limiter-btn-premium {
|
|
1816
|
-
opacity: 0.7;
|
|
1817
|
-
}
|
|
1818
|
-
.download-limiter-success-modal {
|
|
1819
|
-
display: none;
|
|
1820
|
-
position: fixed;
|
|
1821
|
-
top: 0;
|
|
1822
|
-
left: 0;
|
|
1823
|
-
width: 100%;
|
|
1824
|
-
height: 100%;
|
|
1825
|
-
background: rgba(0,0,0,0.5);
|
|
1826
|
-
z-index: 10000;
|
|
1827
|
-
align-items: center;
|
|
1828
|
-
justify-content: center;
|
|
1829
|
-
}
|
|
1830
|
-
.download-limiter-success-modal.show { display: flex; }
|
|
1831
|
-
.download-limiter-success-content {
|
|
1832
|
-
background: white;
|
|
1833
|
-
padding: 2rem;
|
|
1834
|
-
border-radius: 8px;
|
|
1835
|
-
max-width: 400px;
|
|
1836
|
-
text-align: center;
|
|
1837
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
|
1838
|
-
border-left: 4px solid #38a169;
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
/* Single upgrade flow styles */
|
|
1842
|
-
.single-upgrade-container {
|
|
1843
|
-
margin: 20px 0;
|
|
1844
|
-
text-align: center;
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
.single-upgrade-content .upgrade-btn {
|
|
1848
|
-
background: linear-gradient(135deg, #059669, #0d9488) !important;
|
|
1849
|
-
color: white !important;
|
|
1850
|
-
border: none !important;
|
|
1851
|
-
padding: 16px 32px !important;
|
|
1852
|
-
border-radius: 12px !important;
|
|
1853
|
-
font-size: 18px !important;
|
|
1854
|
-
font-weight: 600 !important;
|
|
1855
|
-
cursor: pointer !important;
|
|
1856
|
-
transition: all 0.3s ease !important;
|
|
1857
|
-
box-shadow: 0 4px 12px rgba(5, 150, 105, 0.3) !important;
|
|
1858
|
-
margin-bottom: 16px !important;
|
|
1859
|
-
min-width: 200px !important;
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
.single-upgrade-content .upgrade-btn:hover {
|
|
1863
|
-
background: linear-gradient(135deg, #047857, #0f766e) !important;
|
|
1864
|
-
transform: translateY(-2px) !important;
|
|
1865
|
-
box-shadow: 0 6px 20px rgba(5, 150, 105, 0.4) !important;
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
.single-upgrade-content .upgrade-description {
|
|
1869
|
-
font-size: 14px !important;
|
|
1870
|
-
color: #6b7280 !important;
|
|
1871
|
-
margin: 12px 0 0 0 !important;
|
|
1872
|
-
line-height: 1.5 !important;
|
|
1873
|
-
max-width: 300px !important;
|
|
1874
|
-
margin-left: auto !important;
|
|
1875
|
-
margin-right: auto !important;
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
/* Anonymous user usage count styles */
|
|
1879
|
-
.lead-fast-anonymous-status {
|
|
1880
|
-
display: flex;
|
|
1881
|
-
align-items: center;
|
|
1882
|
-
gap: 12px;
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
.lead-fast-anonymous-status .lead-fast-count {
|
|
1886
|
-
background: rgba(59, 130, 246, 0.1);
|
|
1887
|
-
color: #1d4ed8;
|
|
1888
|
-
padding: 6px 12px;
|
|
1889
|
-
border-radius: 8px;
|
|
1890
|
-
font-size: 14px;
|
|
1891
|
-
font-weight: 500;
|
|
1892
|
-
border: 1px solid rgba(59, 130, 246, 0.2);
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
/* Loading state styles */
|
|
1896
|
-
.lead-fast-loading-status {
|
|
1897
|
-
display: flex;
|
|
1898
|
-
align-items: center;
|
|
1899
|
-
gap: 10px;
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
.lead-fast-spinner {
|
|
1903
|
-
width: 20px;
|
|
1904
|
-
height: 20px;
|
|
1905
|
-
color: #3b82f6;
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
.lead-fast-loading-text {
|
|
1909
|
-
font-size: 14px;
|
|
1910
|
-
color: #6b7280;
|
|
1911
|
-
font-weight: 500;
|
|
1912
|
-
}
|
|
1913
|
-
|
|
1914
|
-
/* Success button theming */
|
|
1915
|
-
.download-limiter-btn.success-btn {
|
|
1916
|
-
background: #059669 !important;
|
|
1917
|
-
color: white !important;
|
|
1918
|
-
border: none !important;
|
|
1919
|
-
padding: 12px 24px !important;
|
|
1920
|
-
border-radius: 8px !important;
|
|
1921
|
-
cursor: pointer !important;
|
|
1922
|
-
margin-top: 20px !important;
|
|
1923
|
-
font-size: 16px !important;
|
|
1924
|
-
font-weight: 500 !important;
|
|
1925
|
-
transition: all 0.2s ease !important;
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
.download-limiter-btn.success-btn:hover {
|
|
1929
|
-
background: #047857 !important;
|
|
1930
|
-
transform: translateY(-1px) !important;
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
/* Theme-specific success button colors */
|
|
1934
|
-
[data-theme="cyberpunk"] .download-limiter-btn.success-btn {
|
|
1935
|
-
background: #0ea5e9 !important;
|
|
1936
|
-
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3) !important;
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
[data-theme="cyberpunk"] .download-limiter-btn.success-btn:hover {
|
|
1940
|
-
background: #0284c7 !important;
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
[data-theme="valentine"] .download-limiter-btn.success-btn {
|
|
1944
|
-
background: #be185d !important;
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
[data-theme="valentine"] .download-limiter-btn.success-btn:hover {
|
|
1948
|
-
background: #9d174d !important;
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
[data-theme="bumblebee"] .download-limiter-btn.success-btn {
|
|
1952
|
-
background: #f59e0b !important;
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
[data-theme="bumblebee"] .download-limiter-btn.success-btn:hover {
|
|
1956
|
-
background: #d97706 !important;
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
[data-theme="garden"] .download-limiter-btn.success-btn {
|
|
1960
|
-
background: #16a34a !important;
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
[data-theme="garden"] .download-limiter-btn.success-btn:hover {
|
|
1964
|
-
background: #15803d !important;
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
/* Google Sign-in button theming */
|
|
1968
|
-
.download-limiter-btn.google-signin-btn {
|
|
1969
|
-
background: #4285f4 !important;
|
|
1970
|
-
color: white !important;
|
|
1971
|
-
border: none !important;
|
|
1972
|
-
padding: 12px 24px !important;
|
|
1973
|
-
border-radius: 8px !important;
|
|
1974
|
-
cursor: pointer !important;
|
|
1975
|
-
margin-right: 10px !important;
|
|
1976
|
-
display: inline-flex !important;
|
|
1977
|
-
align-items: center !important;
|
|
1978
|
-
gap: 8px !important;
|
|
1979
|
-
font-size: 14px !important;
|
|
1980
|
-
font-weight: 500 !important;
|
|
1981
|
-
transition: all 0.2s ease !important;
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
.download-limiter-btn.google-signin-btn:hover {
|
|
1985
|
-
background: #3367d6 !important;
|
|
1986
|
-
transform: translateY(-1px) !important;
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
/* Upgrade button theming */
|
|
1990
|
-
.download-limiter-btn.upgrade-btn {
|
|
1991
|
-
background: #059669 !important;
|
|
1992
|
-
color: white !important;
|
|
1993
|
-
border: none !important;
|
|
1994
|
-
padding: 12px 24px !important;
|
|
1995
|
-
border-radius: 8px !important;
|
|
1996
|
-
cursor: pointer !important;
|
|
1997
|
-
margin-right: 10px !important;
|
|
1998
|
-
font-size: 16px !important;
|
|
1999
|
-
font-weight: 500 !important;
|
|
2000
|
-
transition: all 0.2s ease !important;
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
.download-limiter-btn.upgrade-btn:hover {
|
|
2004
|
-
background: #047857 !important;
|
|
2005
|
-
transform: translateY(-1px) !important;
|
|
2006
|
-
}
|
|
2007
|
-
|
|
2008
|
-
/* Theme-specific button colors */
|
|
2009
|
-
[data-theme="cyberpunk"] .download-limiter-btn.upgrade-btn {
|
|
2010
|
-
background: #0ea5e9 !important;
|
|
2011
|
-
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3) !important;
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
[data-theme="valentine"] .download-limiter-btn.upgrade-btn {
|
|
2015
|
-
background: #be185d !important;
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
[data-theme="bumblebee"] .download-limiter-btn.upgrade-btn {
|
|
2019
|
-
background: #f59e0b !important;
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
[data-theme="garden"] .download-limiter-btn.upgrade-btn {
|
|
2023
|
-
background: #16a34a !important;
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
/* Cancel button container and styling - positioned at very bottom */
|
|
2027
|
-
.download-limiter-cancel-container {
|
|
2028
|
-
margin-top: 30px !important;
|
|
2029
|
-
padding-top: 20px !important;
|
|
2030
|
-
border-top: 1px solid rgba(0, 0, 0, 0.1) !important;
|
|
2031
|
-
text-align: center !important;
|
|
2032
|
-
position: relative !important;
|
|
2033
|
-
bottom: 0 !important;
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
.download-limiter-btn.cancel-btn {
|
|
2037
|
-
background: transparent !important;
|
|
2038
|
-
color: #6b7280 !important;
|
|
2039
|
-
border: none !important;
|
|
2040
|
-
padding: 8px 16px !important;
|
|
2041
|
-
border-radius: 6px !important;
|
|
2042
|
-
cursor: pointer !important;
|
|
2043
|
-
font-size: 13px !important;
|
|
2044
|
-
font-weight: 400 !important;
|
|
2045
|
-
transition: all 0.2s ease !important;
|
|
2046
|
-
text-decoration: underline !important;
|
|
2047
|
-
margin: 0 auto !important;
|
|
2048
|
-
display: block !important;
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
.download-limiter-btn.cancel-btn:hover {
|
|
2052
|
-
color: #374151 !important;
|
|
2053
|
-
background: rgba(107, 114, 128, 0.1) !important;
|
|
2054
|
-
text-decoration: none !important;
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
/* Theme-specific cancel button border */
|
|
2058
|
-
[data-theme="dark"] .download-limiter-cancel-container {
|
|
2059
|
-
border-top-color: rgba(255, 255, 255, 0.1) !important;
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
[data-theme="dark"] .download-limiter-btn.cancel-btn {
|
|
2063
|
-
color: #9ca3af !important;
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
[data-theme="dark"] .download-limiter-btn.cancel-btn:hover {
|
|
2067
|
-
color: #d1d5db !important;
|
|
2068
|
-
background: rgba(156, 163, 175, 0.1) !important;
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
[data-theme="cyberpunk"] .download-limiter-cancel-container {
|
|
2072
|
-
border-top-color: rgba(14, 165, 233, 0.2) !important;
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
[data-theme="cyberpunk"] .download-limiter-btn.cancel-btn {
|
|
2076
|
-
color: #0ea5e9 !important;
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
[data-theme="cyberpunk"] .download-limiter-btn.cancel-btn:hover {
|
|
2080
|
-
color: #38bdf8 !important;
|
|
2081
|
-
background: rgba(14, 165, 233, 0.1) !important;
|
|
2082
|
-
}
|
|
2083
|
-
`;
|
|
2084
|
-
document.head.appendChild(styles);
|
|
2085
|
-
}
|
|
2086
|
-
injectDaisyUI() {
|
|
2087
|
-
//console.log('🎨 [THEME DEBUG] injectDaisyUI called');
|
|
2088
|
-
//console.log('🎨 [THEME DEBUG] Theme config:', this.config.theme);
|
|
2089
|
-
// Check if DaisyUI is already loaded
|
|
2090
|
-
const existingLink = document.querySelector('link[href*="daisyui"]');
|
|
2091
|
-
if (existingLink) {
|
|
2092
|
-
//console.log('🎨 [THEME DEBUG] DaisyUI already exists:', existingLink);
|
|
2093
|
-
// Apply theme if DaisyUI already exists
|
|
2094
|
-
if (this.config.theme?.name) {
|
|
2095
|
-
const themeName = this.config.theme.name.toLowerCase();
|
|
2096
|
-
//console.log('🎨 [THEME DEBUG] Setting existing theme:', themeName);
|
|
2097
|
-
document.documentElement.setAttribute('data-theme', themeName);
|
|
2098
|
-
//console.log('🎨 [THEME DEBUG] Current data-theme attribute:', document.documentElement.getAttribute('data-theme'));
|
|
2099
|
-
}
|
|
2100
|
-
return;
|
|
2101
|
-
}
|
|
2102
|
-
//console.log('🎨 [THEME DEBUG] No existing DaisyUI found, injecting...');
|
|
2103
|
-
// Note: Tailwind CSS is not needed as we use custom CSS for all styling
|
|
2104
|
-
// Apply theme directly using our custom CSS - no external dependencies needed
|
|
2105
|
-
if (this.config.theme?.name) {
|
|
2106
|
-
const themeName = this.config.theme.name.toLowerCase();
|
|
2107
|
-
document.documentElement.setAttribute('data-theme', themeName);
|
|
2108
|
-
// Update status displays immediately since no CSS loading is needed
|
|
2109
|
-
this.updateStatusDisplays();
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
createPaywallModal() {
|
|
2113
|
-
if (document.getElementById('download-limiter-paywall'))
|
|
2114
|
-
return;
|
|
2115
|
-
const modal = document.createElement('div');
|
|
2116
|
-
modal.id = 'download-limiter-paywall';
|
|
2117
|
-
modal.className = 'download-limiter-modal';
|
|
2118
|
-
modal.innerHTML = `
|
|
2119
|
-
<div class="download-limiter-content">
|
|
2120
|
-
<h3 id="download-limiter-paywall-title">Usage Limit Reached</h3>
|
|
2121
|
-
<p id="download-limiter-paywall-subtitle">You've used all your free attempts. Upgrade to premium for unlimited usage!</p>
|
|
2122
|
-
|
|
2123
|
-
<!-- Auth Section (shows when not signed in) -->
|
|
2124
|
-
<div id="download-limiter-auth-section" style="margin: 20px 0;">
|
|
2125
|
-
<p>Sign in to continue or upgrade to premium</p>
|
|
2126
|
-
<button id="download-limiter-signin-btn" class="download-limiter-btn google-signin-btn">
|
|
2127
|
-
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
|
2128
|
-
<path d="M16.51 8H8.98v3h4.3c-.18 1-.74 1.48-1.6 2.04v2.01h2.6a8.8 8.8 0 0 0 2.38-5.88c0-.57-.05-.66-.15-1.18z" fill="white"/>
|
|
2129
|
-
<path d="M8.98 17c2.16 0 3.97-.72 5.3-1.94l-2.6-2.04a4.8 4.8 0 0 1-2.7.75 4.8 4.8 0 0 1-4.52-3.4H1.83v2.07A8 8 0 0 0 8.98 17z" fill="white"/>
|
|
2130
|
-
<path d="M4.46 10.37a4.8 4.8 0 0 1-.25-1.37c0-.48.09-.94.25-1.37V5.56H1.83a8 8 0 0 0 0 6.88l2.63-2.07z" fill="white"/>
|
|
2131
|
-
<path d="M8.98 3.77c1.32 0 2.5.45 3.44 1.35l2.58-2.58C13.94.64 11.66 0 8.98 0A8 8 0 0 0 1.83 5.56l2.63 2.07c.61-1.8 2.26-3.86 4.52-3.86z" fill="white"/>
|
|
2132
|
-
</svg>
|
|
2133
|
-
Sign in with Google
|
|
2134
|
-
</button>
|
|
2135
|
-
</div>
|
|
2136
|
-
|
|
2137
|
-
<!-- Payment Section (shows when signed in) -->
|
|
2138
|
-
<div id="download-limiter-payment-section" style="margin: 20px 0; display: none;">
|
|
2139
|
-
<button id="download-limiter-upgrade-btn" class="download-limiter-btn upgrade-btn">
|
|
2140
|
-
Upgrade to Premium
|
|
2141
|
-
</button>
|
|
2142
|
-
</div>
|
|
2143
|
-
|
|
2144
|
-
<div class="download-limiter-cancel-container">
|
|
2145
|
-
<button id="download-limiter-close-btn" class="download-limiter-btn cancel-btn">
|
|
2146
|
-
Cancel
|
|
2147
|
-
</button>
|
|
2148
|
-
</div>
|
|
2149
|
-
</div>
|
|
2150
|
-
`;
|
|
2151
|
-
document.body.appendChild(modal);
|
|
2152
|
-
// Set up event handlers
|
|
2153
|
-
modal.querySelector('#download-limiter-signin-btn').addEventListener('click', async () => {
|
|
2154
|
-
await this.signIn();
|
|
2155
|
-
});
|
|
2156
|
-
modal.querySelector('#download-limiter-upgrade-btn').addEventListener('click', async () => {
|
|
2157
|
-
const payment = await this.createPayment();
|
|
2158
|
-
if (payment?.checkout_url) {
|
|
2159
|
-
window.location.href = payment.checkout_url;
|
|
2160
|
-
}
|
|
2161
|
-
});
|
|
2162
|
-
modal.querySelector('#download-limiter-close-btn').addEventListener('click', () => {
|
|
2163
|
-
if (this.config.options?.debug) {
|
|
2164
|
-
console.log('🔍 DEBUG: Cancel button clicked');
|
|
2165
|
-
console.log('🔍 DEBUG: this.config.feedback:', this.config.feedback);
|
|
2166
|
-
console.log('🔍 DEBUG: this.config.email:', this.config.email);
|
|
2167
|
-
console.log('🔍 DEBUG: Feedback config exists:', !!(this.config.feedback || this.config.email));
|
|
2168
|
-
}
|
|
2169
|
-
// Show feedback form if feedback config exists, otherwise just close
|
|
2170
|
-
if (this.config.feedback || this.config.email) {
|
|
2171
|
-
if (this.config.options?.debug) {
|
|
2172
|
-
console.log('🔍 DEBUG: Showing feedback form');
|
|
2173
|
-
}
|
|
2174
|
-
this.showFeedbackForm();
|
|
2175
|
-
}
|
|
2176
|
-
else {
|
|
2177
|
-
if (this.config.options?.debug) {
|
|
2178
|
-
console.log('🔍 DEBUG: No email config, just closing modal');
|
|
2179
|
-
}
|
|
2180
|
-
modal.classList.remove('show');
|
|
2181
|
-
}
|
|
2182
|
-
});
|
|
2183
|
-
// Removed click-outside-to-close behavior to encourage users to use the Cancel button
|
|
2184
|
-
// which triggers the feedback form. This improves feedback collection.
|
|
2185
|
-
}
|
|
2186
|
-
createSuccessModal() {
|
|
2187
|
-
if (document.getElementById('download-limiter-success'))
|
|
2188
|
-
return;
|
|
2189
|
-
const modal = document.createElement('div');
|
|
2190
|
-
modal.id = 'download-limiter-success';
|
|
2191
|
-
modal.className = 'download-limiter-modal'; // Use same modal class as paywall
|
|
2192
|
-
modal.innerHTML = `
|
|
2193
|
-
<div class="download-limiter-content"> <!-- Use same content class as paywall for theming -->
|
|
2194
|
-
<h3 id="download-limiter-success-title">Action Started!</h3>
|
|
2195
|
-
<p id="download-limiter-success-message">Your action completed successfully!</p>
|
|
2196
|
-
<button id="download-limiter-success-close" class="download-limiter-btn success-btn">
|
|
2197
|
-
OK
|
|
2198
|
-
</button>
|
|
2199
|
-
</div>
|
|
2200
|
-
`;
|
|
2201
|
-
document.body.appendChild(modal);
|
|
2202
|
-
// Set up event handlers
|
|
2203
|
-
modal.querySelector('#download-limiter-success-close').addEventListener('click', () => {
|
|
2204
|
-
modal.classList.remove('show');
|
|
2205
|
-
});
|
|
2206
|
-
modal.addEventListener('click', (e) => {
|
|
2207
|
-
if (e.target === modal) {
|
|
2208
|
-
modal.classList.remove('show');
|
|
2209
|
-
}
|
|
2210
|
-
});
|
|
2211
|
-
}
|
|
2212
|
-
showFeedbackForm() {
|
|
2213
|
-
// const feedbackStartTime = Date.now();
|
|
2214
|
-
// console.log(`🕐 [FEEDBACK] showFeedbackForm called: ${new Date(feedbackStartTime).toISOString()}`);
|
|
2215
|
-
// Hide paywall first
|
|
2216
|
-
// const paywallHideStart = Date.now();
|
|
2217
|
-
const paywall = document.getElementById('download-limiter-paywall');
|
|
2218
|
-
if (paywall) {
|
|
2219
|
-
paywall.classList.remove('show');
|
|
2220
|
-
// console.log(`🕐 [FEEDBACK] Paywall hidden: ${Date.now() - paywallHideStart}ms`);
|
|
2221
|
-
}
|
|
2222
|
-
// Create or show feedback modal
|
|
2223
|
-
// const createFeedbackStart = Date.now();
|
|
2224
|
-
this.createFeedbackModal();
|
|
2225
|
-
// console.log(`🕐 [FEEDBACK] createFeedbackModal completed: ${Date.now() - createFeedbackStart}ms`);
|
|
2226
|
-
const modal = document.getElementById('download-limiter-feedback');
|
|
2227
|
-
if (modal) {
|
|
2228
|
-
modal.classList.add('show');
|
|
2229
|
-
// console.log(`🕐 [FEEDBACK] Feedback modal shown: ${Date.now() - feedbackStartTime}ms total`);
|
|
2230
|
-
}
|
|
2231
|
-
else {
|
|
2232
|
-
// console.log(`🕐 [FEEDBACK] ERROR - Feedback modal not found after ${Date.now() - feedbackStartTime}ms`);
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
createFeedbackModal() {
|
|
2236
|
-
if (document.getElementById('download-limiter-feedback')) {
|
|
2237
|
-
if (this.config.options?.debug) {
|
|
2238
|
-
console.log('🔍 DEBUG: Feedback modal already exists');
|
|
2239
|
-
}
|
|
2240
|
-
return;
|
|
2241
|
-
}
|
|
2242
|
-
// Get feedback configuration or fallback to legacy email config
|
|
2243
|
-
const feedbackConfig = this.config.feedback || (this.config.email ? {
|
|
2244
|
-
form: {
|
|
2245
|
-
title: 'Quick Feedback',
|
|
2246
|
-
description: 'Why didn\'t you upgrade?',
|
|
2247
|
-
option1: 'Not useful for my needs',
|
|
2248
|
-
option2: 'Didn\'t find what I was looking for',
|
|
2249
|
-
option3: 'Just trying it out'
|
|
2250
|
-
},
|
|
2251
|
-
email: this.config.email
|
|
2252
|
-
} : null);
|
|
2253
|
-
if (this.config.options?.debug) {
|
|
2254
|
-
console.log('🔍 DEBUG: Feedback config:', feedbackConfig);
|
|
2255
|
-
console.log('🔍 DEBUG: this.config.feedback:', this.config.feedback);
|
|
2256
|
-
console.log('🔍 DEBUG: this.config.email:', this.config.email);
|
|
2257
|
-
}
|
|
2258
|
-
if (!feedbackConfig) {
|
|
2259
|
-
if (this.config.options?.debug) {
|
|
2260
|
-
console.log('🔍 DEBUG: No feedback config found, not creating modal');
|
|
2261
|
-
}
|
|
2262
|
-
return;
|
|
2263
|
-
}
|
|
2264
|
-
const modal = document.createElement('div');
|
|
2265
|
-
modal.id = 'download-limiter-feedback';
|
|
2266
|
-
modal.className = 'download-limiter-modal';
|
|
2267
|
-
modal.innerHTML = `
|
|
2268
|
-
<div class="download-limiter-content">
|
|
2269
|
-
<h3>${feedbackConfig.form.title}</h3>
|
|
2270
|
-
<p style="margin-bottom: 20px;">${feedbackConfig.form.description}</p>
|
|
2271
|
-
|
|
2272
|
-
<form id="feedback-form" style="text-align: left;">
|
|
2273
|
-
<div style="margin-bottom: 15px;">
|
|
2274
|
-
<label style="display: block; margin-bottom: 8px;">
|
|
2275
|
-
<input type="radio" name="reason" value="option1" style="margin-right: 8px;">
|
|
2276
|
-
${feedbackConfig.form.option1}
|
|
2277
|
-
</label>
|
|
2278
|
-
<label style="display: block; margin-bottom: 8px;">
|
|
2279
|
-
<input type="radio" name="reason" value="option2" style="margin-right: 8px;">
|
|
2280
|
-
${feedbackConfig.form.option2}
|
|
2281
|
-
</label>
|
|
2282
|
-
<label style="display: block; margin-bottom: 8px;">
|
|
2283
|
-
<input type="radio" name="reason" value="option3" style="margin-right: 8px;">
|
|
2284
|
-
${feedbackConfig.form.option3}
|
|
2285
|
-
</label>
|
|
2286
|
-
</div>
|
|
2287
|
-
|
|
2288
|
-
<div style="margin-bottom: 15px;">
|
|
2289
|
-
<label for="feedback-details" style="display: block; margin-bottom: 5px;">Tell us more:</label>
|
|
2290
|
-
<textarea
|
|
2291
|
-
id="feedback-details"
|
|
2292
|
-
name="details"
|
|
2293
|
-
rows="3"
|
|
2294
|
-
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"
|
|
2295
|
-
placeholder="Your feedback helps us improve..." maxlength="200"></textarea>
|
|
2296
|
-
</div>
|
|
2297
|
-
|
|
2298
|
-
<div style="margin-bottom: 20px;">
|
|
2299
|
-
<label for="feedback-email" style="display: block; margin-bottom: 5px;">
|
|
2300
|
-
Email (optional):
|
|
2301
|
-
</label>
|
|
2302
|
-
<input
|
|
2303
|
-
type="email"
|
|
2304
|
-
id="feedback-email"
|
|
2305
|
-
name="email"
|
|
2306
|
-
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"
|
|
2307
|
-
placeholder="your@email.com">
|
|
2308
|
-
<small style="color: #666; display: block; margin-top: 4px;">
|
|
2309
|
-
Only if you want us to reach out about what you mentioned above. We don't follow up unless you want us to
|
|
2310
|
-
</small>
|
|
2311
|
-
</div>
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
<div style="text-align: center;">
|
|
2315
|
-
<button type="submit" class="download-limiter-btn success-btn" style="margin-right: 10px;">
|
|
2316
|
-
Submit Feedback
|
|
2317
|
-
</button>
|
|
2318
|
-
<button type="button" id="feedback-skip-btn" class="download-limiter-btn cancel-btn">
|
|
2319
|
-
Skip
|
|
2320
|
-
</button>
|
|
2321
|
-
</div>
|
|
2322
|
-
</form>
|
|
2323
|
-
</div>
|
|
2324
|
-
`;
|
|
2325
|
-
document.body.appendChild(modal);
|
|
2326
|
-
if (this.config.options?.debug) {
|
|
2327
|
-
console.log('🔍 DEBUG: Feedback modal created and added to DOM');
|
|
2328
|
-
}
|
|
2329
|
-
// Set up event listeners
|
|
2330
|
-
const form = modal.querySelector('#feedback-form');
|
|
2331
|
-
form.addEventListener('submit', (e) => {
|
|
2332
|
-
e.preventDefault();
|
|
2333
|
-
this.submitFeedback(form);
|
|
2334
|
-
});
|
|
2335
|
-
modal.querySelector('#feedback-skip-btn').addEventListener('click', () => {
|
|
2336
|
-
modal.classList.remove('show');
|
|
2337
|
-
});
|
|
2338
|
-
modal.addEventListener('click', (e) => {
|
|
2339
|
-
if (e.target === modal) {
|
|
2340
|
-
modal.classList.remove('show');
|
|
2341
|
-
}
|
|
2342
|
-
});
|
|
2343
|
-
}
|
|
2344
|
-
async submitFeedback(form) {
|
|
2345
|
-
const formData = new FormData(form);
|
|
2346
|
-
const reason = formData.get('reason');
|
|
2347
|
-
const details = formData.get('details');
|
|
2348
|
-
const email = formData.get('email');
|
|
2349
|
-
if (!reason) {
|
|
2350
|
-
alert('Please select a reason');
|
|
2351
|
-
return;
|
|
2352
|
-
}
|
|
2353
|
-
const feedbackData = {
|
|
2354
|
-
reason,
|
|
2355
|
-
details,
|
|
2356
|
-
email: email || undefined,
|
|
2357
|
-
timestamp: new Date().toISOString(),
|
|
2358
|
-
userAgent: navigator.userAgent,
|
|
2359
|
-
appId: this.config.appId
|
|
2360
|
-
};
|
|
2361
|
-
try {
|
|
2362
|
-
await this.sendFeedbackEmail(feedbackData);
|
|
2363
|
-
// Close feedback modal
|
|
2364
|
-
const modal = document.getElementById('download-limiter-feedback');
|
|
2365
|
-
if (modal) {
|
|
2366
|
-
modal.classList.remove('show');
|
|
2367
|
-
}
|
|
2368
|
-
// Show thank you message
|
|
2369
|
-
alert('Thank you for your feedback!');
|
|
2370
|
-
}
|
|
2371
|
-
catch (error) {
|
|
2372
|
-
console.error('Failed to send feedback:', error);
|
|
2373
|
-
alert('Failed to send feedback. Please try again.');
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
async sendFeedbackEmail(feedbackData) {
|
|
2377
|
-
// Get email config - handle both new array format and legacy format
|
|
2378
|
-
let emailConfig;
|
|
2379
|
-
if (this.config.feedback?.email) {
|
|
2380
|
-
// New array format - use first provider (typically resend)
|
|
2381
|
-
if (Array.isArray(this.config.feedback.email)) {
|
|
2382
|
-
emailConfig = this.config.feedback.email[0];
|
|
2383
|
-
}
|
|
2384
|
-
else {
|
|
2385
|
-
// Legacy format fallback
|
|
2386
|
-
emailConfig = this.config.feedback.email;
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
else if (this.config.email) {
|
|
2390
|
-
// Deprecated legacy email config
|
|
2391
|
-
emailConfig = {
|
|
2392
|
-
provider: 'resend',
|
|
2393
|
-
apiKey: this.config.email.resendApiKey,
|
|
2394
|
-
fromEmail: this.config.email.fromEmail
|
|
2395
|
-
};
|
|
2396
|
-
}
|
|
2397
|
-
if (!emailConfig) {
|
|
2398
|
-
throw new Error('Email configuration not available');
|
|
2399
|
-
}
|
|
2400
|
-
const emailBody = `
|
|
2401
|
-
New Feedback from ${this.config.appId}
|
|
2402
|
-
|
|
2403
|
-
Reason: ${feedbackData.reason}
|
|
2404
|
-
Details: ${feedbackData.details}
|
|
2405
|
-
User Email: ${feedbackData.email || 'Not provided'}
|
|
2406
|
-
Timestamp: ${feedbackData.timestamp}
|
|
2407
|
-
User Agent: ${feedbackData.userAgent}
|
|
2408
|
-
`.trim();
|
|
2409
|
-
// Handle different email providers
|
|
2410
|
-
let response;
|
|
2411
|
-
if (emailConfig.provider === 'resend' || !emailConfig.provider) {
|
|
2412
|
-
response = await fetch('https://api.resend.com/emails', {
|
|
2413
|
-
method: 'POST',
|
|
2414
|
-
headers: {
|
|
2415
|
-
'Content-Type': 'application/json',
|
|
2416
|
-
'Authorization': `Bearer ${emailConfig.apiKey || emailConfig.resendApiKey}`
|
|
2417
|
-
},
|
|
2418
|
-
body: JSON.stringify({
|
|
2419
|
-
from: emailConfig.fromEmail,
|
|
2420
|
-
to: [emailConfig.fromEmail], // Send feedback to yourself
|
|
2421
|
-
subject: `Feedback from ${this.config.appId}`,
|
|
2422
|
-
text: emailBody
|
|
2423
|
-
})
|
|
2424
|
-
});
|
|
2425
|
-
}
|
|
2426
|
-
else {
|
|
2427
|
-
throw new Error(`Unsupported email provider: ${emailConfig.provider}`);
|
|
2428
|
-
}
|
|
2429
|
-
if (!response.ok) {
|
|
2430
|
-
throw new Error(`Email API error: ${response.status}`);
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
createAuthUI() {
|
|
2434
|
-
if (document.getElementById('lead-fast-profile'))
|
|
2435
|
-
return;
|
|
2436
|
-
const profileContainer = document.createElement('div');
|
|
2437
|
-
profileContainer.id = 'lead-fast-profile';
|
|
2438
|
-
profileContainer.className = 'lead-fast-profile';
|
|
2439
|
-
document.body.appendChild(profileContainer);
|
|
2440
|
-
}
|
|
2441
|
-
createStatusDisplay() {
|
|
2442
|
-
if (document.getElementById('download-limiter-status'))
|
|
2443
|
-
return;
|
|
2444
|
-
const statusDiv = document.createElement('div');
|
|
2445
|
-
statusDiv.id = 'download-limiter-status';
|
|
2446
|
-
statusDiv.className = 'download-limiter-status';
|
|
2447
|
-
document.body.appendChild(statusDiv);
|
|
2448
|
-
}
|
|
2449
|
-
createGlobalStatusDisplay() {
|
|
2450
|
-
this.createStatusDisplay();
|
|
2451
|
-
}
|
|
2452
|
-
showPaywallInstant(status) {
|
|
2453
|
-
// const showStartTime = Date.now();
|
|
2454
|
-
// console.log(`🕐 [MODAL] showPaywallInstant started: ${new Date(showStartTime).toISOString()}`);
|
|
2455
|
-
// Ensure modal exists before trying to show it
|
|
2456
|
-
// const createStartTime = Date.now();
|
|
2457
|
-
this.createPaywallModal();
|
|
2458
|
-
// console.log(`🕐 [MODAL] createPaywallModal completed: ${Date.now() - createStartTime}ms`);
|
|
2459
|
-
const modal = document.getElementById('download-limiter-paywall');
|
|
2460
|
-
if (modal) {
|
|
2461
|
-
// console.log(`🕐 [MODAL] Modal found in DOM: ${Date.now() - showStartTime}ms`);
|
|
2462
|
-
// Update paywall title with exact count (no API call needed)
|
|
2463
|
-
const titleEl = modal.querySelector('#download-limiter-paywall-title');
|
|
2464
|
-
if (titleEl) {
|
|
2465
|
-
titleEl.textContent = `You've used ${status.limit} free attempts`;
|
|
2466
|
-
// console.log(`🕐 [MODAL] Title updated: ${Date.now() - showStartTime}ms`);
|
|
2467
|
-
}
|
|
2468
|
-
// Show appropriate section based on auth state
|
|
2469
|
-
const authSection = modal.querySelector('#download-limiter-auth-section');
|
|
2470
|
-
const paymentSection = modal.querySelector('#download-limiter-payment-section');
|
|
2471
|
-
if (!this.currentUser) {
|
|
2472
|
-
// console.log(`🕐 [MODAL] Setting up anonymous user flow: ${Date.now() - showStartTime}ms`);
|
|
2473
|
-
// Anonymous user - show single upgrade flow
|
|
2474
|
-
const subtitleEl = modal.querySelector('#download-limiter-paywall-subtitle');
|
|
2475
|
-
if (subtitleEl) {
|
|
2476
|
-
subtitleEl.textContent = 'Sign in to continue. If you\'re already premium, you\'ll get unlimited access. New users can upgrade after signing in.';
|
|
2477
|
-
}
|
|
2478
|
-
// Hide separate auth/payment sections and show single upgrade button
|
|
2479
|
-
authSection.style.display = 'none';
|
|
2480
|
-
paymentSection.style.display = 'none';
|
|
2481
|
-
// Show single upgrade button
|
|
2482
|
-
// const upgradeStartTime = Date.now();
|
|
2483
|
-
this.showSingleUpgradeFlow(modal);
|
|
2484
|
-
// console.log(`🕐 [MODAL] Single upgrade flow setup: ${Date.now() - upgradeStartTime}ms`);
|
|
2485
|
-
}
|
|
2486
|
-
else {
|
|
2487
|
-
// console.log(`🕐 [MODAL] Setting up signed-in user flow: ${Date.now() - showStartTime}ms`);
|
|
2488
|
-
// Existing flow for signed-in users - direct to payment
|
|
2489
|
-
authSection.style.display = 'none';
|
|
2490
|
-
paymentSection.style.display = 'block';
|
|
2491
|
-
}
|
|
2492
|
-
modal.classList.add('show');
|
|
2493
|
-
// console.log(`🕐 [MODAL] Modal shown (classList.add('show')): ${Date.now() - showStartTime}ms`);
|
|
2494
|
-
}
|
|
2495
|
-
else {
|
|
2496
|
-
// console.log(`🕐 [MODAL] ERROR: Modal not found in DOM after creation: ${Date.now() - showStartTime}ms`);
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
async showPaywall() {
|
|
2500
|
-
// Get current status if not cached
|
|
2501
|
-
const status = await this.getDownloadStatus();
|
|
2502
|
-
this.showPaywallInstant(status);
|
|
2503
|
-
}
|
|
2504
|
-
showSingleUpgradeFlow(modal) {
|
|
2505
|
-
// Create single upgrade button that handles both login and payment
|
|
2506
|
-
let upgradeContainer = modal.querySelector('.single-upgrade-container');
|
|
2507
|
-
if (!upgradeContainer) {
|
|
2508
|
-
upgradeContainer = document.createElement('div');
|
|
2509
|
-
upgradeContainer.className = 'single-upgrade-container';
|
|
2510
|
-
upgradeContainer.innerHTML = `
|
|
2511
|
-
<div class="single-upgrade-content">
|
|
2512
|
-
<button class="download-limiter-btn upgrade-btn" id="single-upgrade-btn">
|
|
2513
|
-
Continue with Google
|
|
2514
|
-
</button>
|
|
2515
|
-
<!--<p class="upgrade-description">
|
|
2516
|
-
Sign in to restore access. Premium members get unlimited usage immediately.
|
|
2517
|
-
</p>-->
|
|
2518
|
-
</div>
|
|
2519
|
-
`;
|
|
2520
|
-
// Insert before the cancel container to keep cancel at the bottom
|
|
2521
|
-
const cancelContainer = modal.querySelector('.download-limiter-cancel-container');
|
|
2522
|
-
if (cancelContainer) {
|
|
2523
|
-
modal.querySelector('.download-limiter-content')?.insertBefore(upgradeContainer, cancelContainer);
|
|
2524
|
-
}
|
|
2525
|
-
else {
|
|
2526
|
-
modal.querySelector('.download-limiter-content')?.appendChild(upgradeContainer);
|
|
2527
|
-
}
|
|
2528
|
-
}
|
|
2529
|
-
upgradeContainer.style.display = 'block';
|
|
2530
|
-
// Add click handler for single upgrade flow
|
|
2531
|
-
const upgradeBtn = modal.querySelector('#single-upgrade-btn');
|
|
2532
|
-
if (upgradeBtn) {
|
|
2533
|
-
upgradeBtn.replaceWith(upgradeBtn.cloneNode(true)); // Remove existing listeners
|
|
2534
|
-
const newUpgradeBtn = modal.querySelector('#single-upgrade-btn');
|
|
2535
|
-
newUpgradeBtn?.addEventListener('click', async () => {
|
|
2536
|
-
try {
|
|
2537
|
-
// Step 1: Sign in with Google
|
|
2538
|
-
const { error } = await this.supabase.auth.signInWithOAuth({
|
|
2539
|
-
provider: 'google',
|
|
2540
|
-
options: {
|
|
2541
|
-
redirectTo: `${window.location.origin}${window.location.pathname}?upgrade=true`
|
|
2542
|
-
}
|
|
2543
|
-
});
|
|
2544
|
-
if (error) {
|
|
2545
|
-
console.error('Sign in error:', error);
|
|
2546
|
-
this.showError('Sign In Failed', 'Failed to sign in with Google. Please try again.');
|
|
2547
|
-
}
|
|
2548
|
-
// OAuth will redirect, so no further action needed here
|
|
2549
|
-
}
|
|
2550
|
-
catch (error) {
|
|
2551
|
-
console.error('Upgrade flow error:', error);
|
|
2552
|
-
this.showError('Upgrade Failed', 'Failed to start upgrade process. Please try again.');
|
|
2553
|
-
}
|
|
2554
|
-
});
|
|
2555
|
-
}
|
|
2556
|
-
}
|
|
2557
|
-
showSuccess(title, message) {
|
|
2558
|
-
const modal = document.getElementById('download-limiter-success');
|
|
2559
|
-
if (modal) {
|
|
2560
|
-
const titleEl = modal.querySelector('#download-limiter-success-title');
|
|
2561
|
-
const messageEl = modal.querySelector('#download-limiter-success-message');
|
|
2562
|
-
if (titleEl)
|
|
2563
|
-
titleEl.textContent = title;
|
|
2564
|
-
if (messageEl)
|
|
2565
|
-
messageEl.textContent = message;
|
|
2566
|
-
modal.classList.add('show');
|
|
2567
|
-
// No auto-hide - let user dismiss manually
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
showError(title, message) {
|
|
2571
|
-
// Create a clean error toast instead of alert
|
|
2572
|
-
const toast = document.createElement('div');
|
|
2573
|
-
toast.style.cssText = `
|
|
2574
|
-
position: fixed;
|
|
2575
|
-
top: 20px;
|
|
2576
|
-
right: 20px;
|
|
2577
|
-
background: #fee2e2;
|
|
2578
|
-
border: 1px solid #fecaca;
|
|
2579
|
-
color: #dc2626;
|
|
2580
|
-
padding: 12px 16px;
|
|
2581
|
-
border-radius: 8px;
|
|
2582
|
-
font-size: 14px;
|
|
2583
|
-
font-weight: 500;
|
|
2584
|
-
line-height: 1.4;
|
|
2585
|
-
max-width: 300px;
|
|
2586
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
2587
|
-
z-index: 10000;
|
|
2588
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2589
|
-
`;
|
|
2590
|
-
toast.innerHTML = `
|
|
2591
|
-
<div style="font-weight: 600; margin-bottom: 4px;">${title}</div>
|
|
2592
|
-
<div style="font-size: 13px; opacity: 0.9;">${message}</div>
|
|
2593
|
-
`;
|
|
2594
|
-
document.body.appendChild(toast);
|
|
2595
|
-
// Auto-remove after 5 seconds
|
|
2596
|
-
setTimeout(() => {
|
|
2597
|
-
if (toast.parentNode) {
|
|
2598
|
-
toast.parentNode.removeChild(toast);
|
|
2599
|
-
}
|
|
2600
|
-
}, 5000);
|
|
2601
|
-
// Click to dismiss
|
|
2602
|
-
toast.addEventListener('click', () => {
|
|
2603
|
-
if (toast.parentNode) {
|
|
2604
|
-
toast.parentNode.removeChild(toast);
|
|
2605
|
-
}
|
|
2606
|
-
});
|
|
2607
|
-
}
|
|
2608
|
-
showConnectionError() {
|
|
2609
|
-
this.showError('Connection Error', 'Server connection required for download verification. Please check your configuration and try again.');
|
|
2610
|
-
}
|
|
2611
|
-
showPaymentConfigError() {
|
|
2612
|
-
this.showError('Payment Configuration Error', 'Payment system is not properly configured. Please contact support.');
|
|
2613
|
-
}
|
|
2614
|
-
disableDownloadButtons(errorType) {
|
|
2615
|
-
// Find all buttons that were attached by the library
|
|
2616
|
-
const buttons = document.querySelectorAll('[data-leadfast-attached]');
|
|
2617
|
-
const errorMessages = {
|
|
2618
|
-
connection: 'Download disabled: Server connection required',
|
|
2619
|
-
payment: 'Download disabled: Payment configuration error',
|
|
2620
|
-
security_key: 'Download disabled: Invalid or missing security key'
|
|
2621
|
-
};
|
|
2622
|
-
const statusMessages = {
|
|
2623
|
-
connection: '⚠️ Connection Error - Downloads Disabled',
|
|
2624
|
-
payment: '⚠️ Payment Config Error - Downloads Disabled',
|
|
2625
|
-
security_key: '🔐 Invalid Security Key - Please Update Configuration'
|
|
2626
|
-
};
|
|
2627
|
-
buttons.forEach(button => {
|
|
2628
|
-
const btnElement = button;
|
|
2629
|
-
btnElement.disabled = true;
|
|
2630
|
-
btnElement.style.opacity = '0.5';
|
|
2631
|
-
btnElement.style.cursor = 'not-allowed';
|
|
2632
|
-
btnElement.title = errorMessages[errorType];
|
|
2633
|
-
});
|
|
2634
|
-
// Update user section to show error
|
|
2635
|
-
const userSection = document.querySelector('.lead-fast-user-section');
|
|
2636
|
-
if (userSection) {
|
|
2637
|
-
userSection.innerHTML = `
|
|
2638
|
-
<div style="
|
|
2639
|
-
color: #ef4444;
|
|
2640
|
-
padding: 6px 8px;
|
|
2641
|
-
font-size: 12px;
|
|
2642
|
-
font-weight: 500;
|
|
2643
|
-
line-height: 1.2;
|
|
2644
|
-
border-radius: 4px;
|
|
2645
|
-
background: rgba(239, 68, 68, 0.05);
|
|
2646
|
-
border: 1px solid rgba(239, 68, 68, 0.2);
|
|
2647
|
-
max-width: 200px;
|
|
2648
|
-
text-align: center;
|
|
2649
|
-
">
|
|
2650
|
-
${statusMessages[errorType]}
|
|
2651
|
-
</div>
|
|
2652
|
-
`;
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
async updateStatusDisplays() {
|
|
2656
|
-
//console.log('🎨 [THEME DEBUG] updateStatusDisplays called');
|
|
2657
|
-
// Check for security key error first (most specific)
|
|
2658
|
-
if (this.securityKeyFailed) {
|
|
2659
|
-
this.disableDownloadButtons('security_key');
|
|
2660
|
-
return;
|
|
2661
|
-
}
|
|
2662
|
-
// If connection failed, disable all download buttons
|
|
2663
|
-
if (this.supabaseConnectionFailed) {
|
|
2664
|
-
this.disableDownloadButtons('connection');
|
|
2665
|
-
return;
|
|
2666
|
-
}
|
|
2667
|
-
// If payment configuration is invalid, disable all download buttons
|
|
2668
|
-
if (this.config.payment && this.paymentConnectionFailed) {
|
|
2669
|
-
this.disableDownloadButtons('payment');
|
|
2670
|
-
return;
|
|
2671
|
-
}
|
|
2672
|
-
// Show loading state
|
|
2673
|
-
this.isLoadingStatus = true;
|
|
2674
|
-
this.updateUserSection(null); // Show loading UI
|
|
2675
|
-
const status = await this.getDownloadStatus();
|
|
2676
|
-
// Clear loading state
|
|
2677
|
-
this.isLoadingStatus = false;
|
|
2678
|
-
// CRITICAL: Check again AFTER API call - flag may have been set during getDownloadStatus
|
|
2679
|
-
if (this.securityKeyFailed) {
|
|
2680
|
-
this.disableDownloadButtons('security_key');
|
|
2681
|
-
return;
|
|
2682
|
-
}
|
|
2683
|
-
if (this.supabaseConnectionFailed) {
|
|
2684
|
-
this.disableDownloadButtons('connection');
|
|
2685
|
-
return;
|
|
2686
|
-
}
|
|
2687
|
-
this.updateUserSection(status);
|
|
2688
|
-
}
|
|
2689
|
-
createTitleBar() {
|
|
2690
|
-
//console.log('🎨 [THEME DEBUG] Creating title bar');
|
|
2691
|
-
// Create the profile div if it doesn't exist
|
|
2692
|
-
let profileDiv = document.getElementById('lead-fast-profile');
|
|
2693
|
-
if (!profileDiv) {
|
|
2694
|
-
profileDiv = document.createElement('div');
|
|
2695
|
-
profileDiv.id = 'lead-fast-profile';
|
|
2696
|
-
profileDiv.className = 'lead-fast-profile';
|
|
2697
|
-
document.body.appendChild(profileDiv);
|
|
2698
|
-
}
|
|
2699
|
-
// Get title bar configuration
|
|
2700
|
-
const titleConfig = this.config.titleBar || {};
|
|
2701
|
-
const title = titleConfig.title || 'My App';
|
|
2702
|
-
const titleImage = titleConfig.titleImage;
|
|
2703
|
-
const links = titleConfig.links || [];
|
|
2704
|
-
// Always create title bar with logo and navigation
|
|
2705
|
-
profileDiv.innerHTML = `
|
|
2706
|
-
<div class="lead-fast-title-bar">
|
|
2707
|
-
<div class="lead-fast-title-section">
|
|
2708
|
-
<a href="#" class="lead-fast-logo">
|
|
2709
|
-
${titleImage ? `<img src="${titleImage}" alt="${title}">` : ''}
|
|
2710
|
-
<span>${title}</span>
|
|
2711
|
-
</a>
|
|
2712
|
-
<nav class="lead-fast-nav">
|
|
2713
|
-
${links.map(link => `
|
|
2714
|
-
<a href="${link.url}" class="lead-fast-nav-link" ${link.target ? `target="${link.target}"` : ''}>
|
|
2715
|
-
${link.text}
|
|
2716
|
-
</a>
|
|
2717
|
-
`).join('')}
|
|
2718
|
-
</nav>
|
|
2719
|
-
</div>
|
|
2720
|
-
|
|
2721
|
-
<div class="lead-fast-user-section" id="lead-fast-user-section">
|
|
2722
|
-
<!-- User section will be populated when logged in -->
|
|
2723
|
-
</div>
|
|
2724
|
-
|
|
2725
|
-
<!-- <a href="https://www.moneyfast.bar" target="_blank" class="lead-fast-branding" style="text-decoration: none; color: inherit;">moneyfast.bar</a>-->
|
|
2726
|
-
</div>
|
|
2727
|
-
`;
|
|
2728
|
-
}
|
|
2729
|
-
updateUserSection(status) {
|
|
2730
|
-
//console.log('🎨 [THEME DEBUG] updateUserSection called');
|
|
2731
|
-
//console.log('🎨 [THEME DEBUG] Current data-theme:', document.documentElement.getAttribute('data-theme'));
|
|
2732
|
-
const userSection = document.getElementById('lead-fast-user-section');
|
|
2733
|
-
if (!userSection) {
|
|
2734
|
-
//console.log('🎨 [THEME DEBUG] User section not found!');
|
|
2735
|
-
return;
|
|
2736
|
-
}
|
|
2737
|
-
// Show loading state if status is null
|
|
2738
|
-
if (status === null || this.isLoadingStatus) {
|
|
2739
|
-
userSection.innerHTML = `
|
|
2740
|
-
<div class="lead-fast-loading-status">
|
|
2741
|
-
<svg class="lead-fast-spinner" viewBox="0 0 24 24">
|
|
2742
|
-
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" fill="none" stroke-dasharray="31.4 31.4" stroke-linecap="round">
|
|
2743
|
-
<animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/>
|
|
2744
|
-
</circle>
|
|
2745
|
-
</svg>
|
|
2746
|
-
<span class="lead-fast-loading-text">Loading...</span>
|
|
2747
|
-
</div>
|
|
2748
|
-
`;
|
|
2749
|
-
return;
|
|
2750
|
-
}
|
|
2751
|
-
if (this.currentUser) {
|
|
2752
|
-
// User is signed in - show user info
|
|
2753
|
-
const userInitial = this.currentUser.email.charAt(0).toUpperCase();
|
|
2754
|
-
const userEmail = this.currentUser.email;
|
|
2755
|
-
const badgeClass = status.isPremium ? 'premium' : 'free';
|
|
2756
|
-
const badgeText = status.isPremium ? 'Premium' : 'Free';
|
|
2757
|
-
const countText = status.isPremium ? 'Unlimited' : `${status.currentCount}/${status.limit}`;
|
|
2758
|
-
//console.log('🎨 [THEME DEBUG] Populating user section');
|
|
2759
|
-
userSection.innerHTML = `
|
|
2760
|
-
<div class="lead-fast-avatar">${userInitial}</div>
|
|
2761
|
-
<div class="lead-fast-user-info">
|
|
2762
|
-
<div class="lead-fast-user-email">${userEmail}</div>
|
|
2763
|
-
<div class="lead-fast-user-status">
|
|
2764
|
-
<span class="lead-fast-badge ${badgeClass}">${badgeText}</span>
|
|
2765
|
-
<span class="lead-fast-count">${countText}</span>
|
|
2766
|
-
</div>
|
|
2767
|
-
</div>
|
|
2768
|
-
<button class="lead-fast-signout">signout</button>
|
|
2769
|
-
`;
|
|
2770
|
-
// Debug CSS variables after updating user section
|
|
2771
|
-
const titleBar = document.querySelector('.lead-fast-title-bar');
|
|
2772
|
-
if (titleBar) {
|
|
2773
|
-
const computedStyle = getComputedStyle(titleBar);
|
|
2774
|
-
//console.log('🎨 [THEME DEBUG] Title bar background color:', computedStyle.backgroundColor);
|
|
2775
|
-
//console.log('🎨 [THEME DEBUG] Title bar border color:', computedStyle.borderColor);
|
|
2776
|
-
}
|
|
2777
|
-
// Add sign out functionality
|
|
2778
|
-
const signoutBtn = userSection.querySelector('.lead-fast-signout');
|
|
2779
|
-
if (signoutBtn) {
|
|
2780
|
-
signoutBtn.addEventListener('click', () => {
|
|
2781
|
-
this.signOut();
|
|
2782
|
-
});
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
else {
|
|
2786
|
-
// Anonymous user - show usage count
|
|
2787
|
-
//console.log('🎨 [THEME DEBUG] Showing anonymous user usage count');
|
|
2788
|
-
const countText = `Attempts: ${status.currentCount}/${status.limit}`;
|
|
2789
|
-
userSection.innerHTML = `
|
|
2790
|
-
<div class="lead-fast-anonymous-status">
|
|
2791
|
-
<span class="lead-fast-count">${countText}</span>
|
|
2792
|
-
</div>
|
|
2793
|
-
`;
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
setupUIUpdates() {
|
|
2797
|
-
// Update UI on auth changes
|
|
2798
|
-
this.on('authChanged', async () => {
|
|
2799
|
-
// Invalidate cache when auth state changes
|
|
2800
|
-
// console.log(`🕐 [CACHE] Invalidating cache due to auth change (was: ${this.cachedStatus ? 'cached' : 'null'})`);
|
|
2801
|
-
this.cachedStatus = null;
|
|
2802
|
-
this.updateStatusDisplays();
|
|
2803
|
-
// Update paywall display if it's currently shown
|
|
2804
|
-
const modal = document.getElementById('download-limiter-paywall');
|
|
2805
|
-
if (modal && modal.classList.contains('show')) {
|
|
2806
|
-
this.updatePaywallDisplay();
|
|
2807
|
-
// Auto-redirect to payment after sign-in from paywall (if not already premium)
|
|
2808
|
-
if (this.currentUser && this.config.payment) {
|
|
2809
|
-
setTimeout(async () => {
|
|
2810
|
-
const isPremium = await this.checkPremiumStatus(this.currentUser.email);
|
|
2811
|
-
if (!isPremium) {
|
|
2812
|
-
// User is not premium, redirect to payment
|
|
2813
|
-
const payment = await this.createPayment();
|
|
2814
|
-
if (payment?.checkout_url) {
|
|
2815
|
-
window.location.href = payment.checkout_url;
|
|
2816
|
-
}
|
|
2817
|
-
}
|
|
2818
|
-
else {
|
|
2819
|
-
// User is already premium, just close paywall and stay on page
|
|
2820
|
-
modal.classList.remove('show');
|
|
2821
|
-
}
|
|
2822
|
-
}, 1000); // Small delay to let UI update
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2825
|
-
});
|
|
2826
|
-
// Update UI on count changes
|
|
2827
|
-
this.on('countChanged', () => {
|
|
2828
|
-
this.updateStatusDisplays();
|
|
2829
|
-
});
|
|
2830
|
-
// Initial update
|
|
2831
|
-
this.updateStatusDisplays();
|
|
2832
|
-
}
|
|
2833
|
-
updatePaywallDisplay() {
|
|
2834
|
-
const modal = document.getElementById('download-limiter-paywall');
|
|
2835
|
-
if (!modal)
|
|
2836
|
-
return;
|
|
2837
|
-
const authSection = modal.querySelector('#download-limiter-auth-section');
|
|
2838
|
-
const paymentSection = modal.querySelector('#download-limiter-payment-section');
|
|
2839
|
-
if (!this.currentUser) {
|
|
2840
|
-
// Not signed in - show auth section
|
|
2841
|
-
authSection.style.display = 'block';
|
|
2842
|
-
paymentSection.style.display = 'none';
|
|
2843
|
-
}
|
|
2844
|
-
else {
|
|
2845
|
-
// Signed in - show payment section
|
|
2846
|
-
authSection.style.display = 'none';
|
|
2847
|
-
paymentSection.style.display = 'block';
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2850
|
-
/**
|
|
2851
|
-
* Event listener management
|
|
2852
|
-
*/
|
|
2853
|
-
on(event, listener) {
|
|
2854
|
-
if (!this.eventListeners.has(event)) {
|
|
2855
|
-
this.eventListeners.set(event, []);
|
|
2856
|
-
}
|
|
2857
|
-
this.eventListeners.get(event).push(listener);
|
|
2858
|
-
}
|
|
2859
|
-
off(event, listener) {
|
|
2860
|
-
const listeners = this.eventListeners.get(event);
|
|
2861
|
-
if (listeners) {
|
|
2862
|
-
const index = listeners.indexOf(listener);
|
|
2863
|
-
if (index > -1) {
|
|
2864
|
-
listeners.splice(index, 1);
|
|
2865
|
-
}
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
emit(event, data) {
|
|
2869
|
-
const listeners = this.eventListeners.get(event);
|
|
2870
|
-
if (listeners) {
|
|
2871
|
-
listeners.forEach(listener => {
|
|
2872
|
-
try {
|
|
2873
|
-
listener(data);
|
|
2874
|
-
}
|
|
2875
|
-
catch (error) {
|
|
2876
|
-
console.error(`Error in ${event} listener:`, error);
|
|
2877
|
-
}
|
|
2878
|
-
});
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
2881
|
-
validateConfig() {
|
|
2882
|
-
if (!this.config.appId)
|
|
2883
|
-
throw new Error('appId is required');
|
|
2884
|
-
if (!this.config.supabase?.url)
|
|
2885
|
-
throw new Error('supabase.url is required');
|
|
2886
|
-
if (!this.config.supabase?.anonKey)
|
|
2887
|
-
throw new Error('supabase.anonKey is required');
|
|
2888
|
-
if (typeof this.config.freeAttemptLimit !== 'number' || this.config.freeAttemptLimit < 0) {
|
|
2889
|
-
throw new Error('freeAttemptLimit must be a non-negative number');
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
async initializeSupabase() {
|
|
2893
|
-
try {
|
|
2894
|
-
this.supabase = createClient(this.config.supabase.url, this.config.supabase.anonKey);
|
|
2895
|
-
// Skip connection test - we'll detect connection issues during actual API calls
|
|
2896
|
-
this.supabaseConnectionFailed = false;
|
|
2897
|
-
}
|
|
2898
|
-
catch (error) {
|
|
2899
|
-
console.error('Failed to initialize Supabase:', error);
|
|
2900
|
-
this.supabaseConnectionFailed = true;
|
|
2901
|
-
}
|
|
2902
|
-
}
|
|
2903
|
-
async initializePayment() {
|
|
2904
|
-
// Skip if no payment configuration
|
|
2905
|
-
if (!this.config.payment) {
|
|
2906
|
-
return;
|
|
2907
|
-
}
|
|
2908
|
-
// Validate product ID format
|
|
2909
|
-
const payment = this.config.payment?.find(p => p.provider === 'dodo');
|
|
2910
|
-
if (!payment) {
|
|
2911
|
-
this.paymentConnectionFailed = true;
|
|
2912
|
-
if (this.config.options?.debug) {
|
|
2913
|
-
console.warn('No dodo payment provider configured');
|
|
2914
|
-
}
|
|
2915
|
-
return;
|
|
2916
|
-
}
|
|
2917
|
-
const productId = payment.productId;
|
|
2918
|
-
if (!productId || !productId.startsWith('pdt_') || productId.length < 10) {
|
|
2919
|
-
this.paymentConnectionFailed = true;
|
|
2920
|
-
if (this.config.options?.debug) {
|
|
2921
|
-
console.warn('Invalid payment product ID format:', productId);
|
|
2922
|
-
}
|
|
2923
|
-
return;
|
|
2924
|
-
}
|
|
2925
|
-
// For now, skip payment gateway connectivity test
|
|
2926
|
-
// Only validate product ID format (which we already did above)
|
|
2927
|
-
// In production, you can add actual DodoPayments API test here
|
|
2928
|
-
this.paymentConnectionFailed = false;
|
|
2929
|
-
}
|
|
2930
|
-
async initializeFingerprint() {
|
|
2931
|
-
try {
|
|
2932
|
-
this.userFingerprint = await this.generateFingerprint();
|
|
2933
|
-
}
|
|
2934
|
-
catch (error) {
|
|
2935
|
-
this.userFingerprint = crypto.randomUUID ? crypto.randomUUID() : String(Date.now());
|
|
2936
|
-
if (this.config.options?.debug) {
|
|
2937
|
-
console.warn('Fingerprint generation failed, using fallback:', error);
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
}
|
|
2941
|
-
async generateFingerprint() {
|
|
2942
|
-
const ua = navigator.userAgent || '';
|
|
2943
|
-
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
|
|
2944
|
-
const screenSize = `${screen.width}x${screen.height}`;
|
|
2945
|
-
const lang = navigator.language || '';
|
|
2946
|
-
const raw = `${ua}||${tz}||${screenSize}||${lang}`;
|
|
2947
|
-
const buf = new TextEncoder().encode(raw);
|
|
2948
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', buf);
|
|
2949
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
2950
|
-
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
2951
|
-
}
|
|
2952
|
-
async setupAuthListener() {
|
|
2953
|
-
this.supabase.auth.onAuthStateChange(async (event, session) => {
|
|
2954
|
-
const previousUser = this.currentUser;
|
|
2955
|
-
this.currentUser = session?.user || null;
|
|
2956
|
-
let isPremium = false;
|
|
2957
|
-
if (this.currentUser) {
|
|
2958
|
-
// Only check premium status if user changed or cache is expired
|
|
2959
|
-
const userChanged = !previousUser || previousUser.email !== this.currentUser.email;
|
|
2960
|
-
const cacheExpired = !this.cachedPremiumStatus || (Date.now() - this.premiumStatusCacheTime) > this.STATUS_CACHE_DURATION;
|
|
2961
|
-
if (userChanged || cacheExpired) {
|
|
2962
|
-
isPremium = await this.checkPremiumStatus(this.currentUser.email);
|
|
2963
|
-
// Cache the result
|
|
2964
|
-
this.cachedPremiumStatus = isPremium;
|
|
2965
|
-
this.premiumStatusCacheTime = Date.now();
|
|
2966
|
-
}
|
|
2967
|
-
else {
|
|
2968
|
-
// Use cached result
|
|
2969
|
-
isPremium = this.cachedPremiumStatus; // We know it's not null because cacheExpired is false
|
|
2970
|
-
console.log(`🔍 [CACHE] Using cached premium status from auth listener: ${isPremium}`);
|
|
2971
|
-
}
|
|
2972
|
-
}
|
|
2973
|
-
else {
|
|
2974
|
-
// Clear cache when user logs out
|
|
2975
|
-
this.cachedPremiumStatus = null;
|
|
2976
|
-
this.premiumStatusCacheTime = 0;
|
|
2977
|
-
}
|
|
2978
|
-
// Check if user just signed in for upgrade flow
|
|
2979
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
2980
|
-
const isUpgradeFlow = urlParams.get('upgrade') === 'true';
|
|
2981
|
-
if (event === 'SIGNED_IN' && !previousUser && this.currentUser && this.config.payment) {
|
|
2982
|
-
// Check if this is an upgrade flow (from anonymous trial)
|
|
2983
|
-
if (isUpgradeFlow) {
|
|
2984
|
-
if (!isPremium) {
|
|
2985
|
-
// User signed in for upgrade - redirect to payment immediately
|
|
2986
|
-
const payment = await this.createPayment();
|
|
2987
|
-
if (payment?.checkout_url) {
|
|
2988
|
-
// Clean URL before redirect
|
|
2989
|
-
window.history.replaceState({}, document.title, window.location.pathname);
|
|
2990
|
-
window.location.href = payment.checkout_url;
|
|
2991
|
-
}
|
|
2992
|
-
}
|
|
2993
|
-
else {
|
|
2994
|
-
// User is already premium, just clean URL and close modal
|
|
2995
|
-
window.history.replaceState({}, document.title, window.location.pathname);
|
|
2996
|
-
const modal = document.getElementById('download-limiter-paywall');
|
|
2997
|
-
if (modal) {
|
|
2998
|
-
modal.classList.remove('show');
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
this.emit('authChanged', { user: this.currentUser, isPremium });
|
|
3004
|
-
});
|
|
3005
|
-
// Get initial auth state
|
|
3006
|
-
const { data: { user } } = await this.supabase.auth.getUser();
|
|
3007
|
-
this.currentUser = user;
|
|
3008
|
-
}
|
|
3009
|
-
async getDownloadCount() {
|
|
3010
|
-
if (!this.userFingerprint)
|
|
3011
|
-
return 0;
|
|
3012
|
-
try {
|
|
3013
|
-
const response = await fetch(`${this.config.supabase.url}/functions/v1/check-download-limit`, {
|
|
3014
|
-
method: 'POST',
|
|
3015
|
-
headers: {
|
|
3016
|
-
'Content-Type': 'application/json',
|
|
3017
|
-
'Authorization': `Bearer ${this.config.supabase.anonKey}`
|
|
3018
|
-
},
|
|
3019
|
-
body: JSON.stringify({
|
|
3020
|
-
fingerprint: this.userFingerprint,
|
|
3021
|
-
security_key: this.config.security_key,
|
|
3022
|
-
action: 'check'
|
|
3023
|
-
})
|
|
3024
|
-
});
|
|
3025
|
-
if (response.ok) {
|
|
3026
|
-
const data = await response.json();
|
|
3027
|
-
this.supabaseConnectionFailed = false; // Connection successful
|
|
3028
|
-
return data.current_count || 0;
|
|
3029
|
-
}
|
|
3030
|
-
else {
|
|
3031
|
-
// Handle auth errors (401/403) as connection failures
|
|
3032
|
-
if (response.status === 401 || response.status === 403) {
|
|
3033
|
-
// Try to get the actual error message from response
|
|
3034
|
-
try {
|
|
3035
|
-
const errorData = await response.json();
|
|
3036
|
-
const errorMsg = errorData.error || errorData.message || 'Authentication failed';
|
|
3037
|
-
// Check if it's a security key validation error
|
|
3038
|
-
if (errorMsg.toLowerCase().includes('security key')) {
|
|
3039
|
-
console.error(`❌ Security Key Validation Failed [check-download-limit]: ${errorMsg}`);
|
|
3040
|
-
console.error(`💡 Backend Function: check-download-limit`);
|
|
3041
|
-
console.error(`💡 Please check your security_key in the MoneyBar configuration`);
|
|
3042
|
-
this.securityKeyFailed = true; // Set security key error flag
|
|
3043
|
-
this.supabaseConnectionFailed = true; // Also mark connection as failed to disable downloads
|
|
3044
|
-
}
|
|
3045
|
-
else {
|
|
3046
|
-
console.warn(`Supabase authentication failed [check-download-limit]: ${response.status} - ${errorMsg}`);
|
|
3047
|
-
this.supabaseConnectionFailed = true;
|
|
3048
|
-
}
|
|
3049
|
-
}
|
|
3050
|
-
catch (e) {
|
|
3051
|
-
console.warn(`Supabase authentication failed: ${response.status} - Unable to parse error`);
|
|
3052
|
-
this.supabaseConnectionFailed = true;
|
|
3053
|
-
}
|
|
3054
|
-
}
|
|
3055
|
-
}
|
|
3056
|
-
}
|
|
3057
|
-
catch (error) {
|
|
3058
|
-
this.supabaseConnectionFailed = true; // Mark connection as failed
|
|
3059
|
-
if (this.config.options?.debug) {
|
|
3060
|
-
console.warn('Failed to get server usage count, connection failed:', error);
|
|
3061
|
-
}
|
|
3062
|
-
}
|
|
3063
|
-
// Security: Do not fallback to localStorage when server connection fails
|
|
3064
|
-
// This prevents users from bypassing limits by manipulating localStorage
|
|
3065
|
-
return 0;
|
|
3066
|
-
}
|
|
3067
|
-
async incrementDownloadCount() {
|
|
3068
|
-
if (!this.userFingerprint) {
|
|
3069
|
-
// if (this.config.options?.debug) {
|
|
3070
|
-
// console.warn('🔍 DEBUG: No userFingerprint available for incrementDownloadCount');
|
|
3071
|
-
// }
|
|
3072
|
-
return 0;
|
|
3073
|
-
}
|
|
3074
|
-
const requestBody = {
|
|
3075
|
-
fingerprint: this.userFingerprint,
|
|
3076
|
-
security_key: this.config.security_key,
|
|
3077
|
-
action: 'increment'
|
|
3078
|
-
};
|
|
3079
|
-
try {
|
|
3080
|
-
const response = await fetch(`${this.config.supabase.url}/functions/v1/check-download-limit`, {
|
|
3081
|
-
method: 'POST',
|
|
3082
|
-
headers: {
|
|
3083
|
-
'Content-Type': 'application/json',
|
|
3084
|
-
'Authorization': `Bearer ${this.config.supabase.anonKey}`
|
|
3085
|
-
},
|
|
3086
|
-
body: JSON.stringify(requestBody)
|
|
3087
|
-
});
|
|
3088
|
-
if (this.config.options?.debug) {
|
|
3089
|
-
//console.log('🔍 DEBUG: Response status:', response.status);
|
|
3090
|
-
if (!response.ok) {
|
|
3091
|
-
const errorText = await response.text();
|
|
3092
|
-
console.error('🔍 DEBUG: Error response:', errorText);
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
if (response.ok) {
|
|
3096
|
-
const data = await response.json();
|
|
3097
|
-
this.supabaseConnectionFailed = false; // Connection successful
|
|
3098
|
-
return data.new_count || 0;
|
|
3099
|
-
}
|
|
3100
|
-
else {
|
|
3101
|
-
// Handle auth errors (401/403) as connection failures
|
|
3102
|
-
if (response.status === 401 || response.status === 403) {
|
|
3103
|
-
// Try to get the actual error message from response
|
|
3104
|
-
try {
|
|
3105
|
-
const errorData = await response.json();
|
|
3106
|
-
const errorMsg = errorData.error || errorData.message || 'Authentication failed';
|
|
3107
|
-
// Check if it's a security key validation error
|
|
3108
|
-
if (errorMsg.toLowerCase().includes('security key')) {
|
|
3109
|
-
console.error(`❌ Security Key Validation Failed [check-download-limit]: ${errorMsg}`);
|
|
3110
|
-
console.error(`💡 Backend Function: check-download-limit`);
|
|
3111
|
-
console.error(`💡 Please check your security_key in the MoneyBar configuration`);
|
|
3112
|
-
this.securityKeyFailed = true; // Set security key error flag
|
|
3113
|
-
this.supabaseConnectionFailed = true; // Also mark connection as failed to disable downloads
|
|
3114
|
-
}
|
|
3115
|
-
else {
|
|
3116
|
-
console.warn(`Supabase authentication failed [check-download-limit]: ${response.status} - ${errorMsg}`);
|
|
3117
|
-
this.supabaseConnectionFailed = true;
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
catch (e) {
|
|
3121
|
-
console.warn(`Supabase authentication failed: ${response.status} - Unable to parse error`);
|
|
3122
|
-
this.supabaseConnectionFailed = true;
|
|
3123
|
-
}
|
|
3124
|
-
}
|
|
3125
|
-
}
|
|
3126
|
-
}
|
|
3127
|
-
catch (error) {
|
|
3128
|
-
this.supabaseConnectionFailed = true; // Mark connection as failed
|
|
3129
|
-
if (this.config.options?.debug) {
|
|
3130
|
-
console.warn('Failed to increment server count, connection failed:', error);
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
// Security: Do not fallback to localStorage when server connection fails
|
|
3134
|
-
// This prevents users from bypassing limits by manipulating localStorage
|
|
3135
|
-
return 0;
|
|
3136
|
-
}
|
|
3137
|
-
async checkPremiumStatus(email) {
|
|
3138
|
-
try {
|
|
3139
|
-
const response = await fetch(`${this.config.supabase.url}/functions/v1/check-payment-status`, {
|
|
3140
|
-
method: 'POST',
|
|
3141
|
-
headers: {
|
|
3142
|
-
'Content-Type': 'application/json',
|
|
3143
|
-
'Authorization': `Bearer ${this.config.supabase.anonKey}`
|
|
3144
|
-
},
|
|
3145
|
-
body: JSON.stringify({
|
|
3146
|
-
email,
|
|
3147
|
-
app_id: this.config.appId
|
|
3148
|
-
})
|
|
3149
|
-
});
|
|
3150
|
-
if (response.ok) {
|
|
3151
|
-
const data = await response.json();
|
|
3152
|
-
return data.status === 'completed';
|
|
3153
|
-
}
|
|
3154
|
-
}
|
|
3155
|
-
catch (error) {
|
|
3156
|
-
if (this.config.options?.debug) {
|
|
3157
|
-
console.warn('Failed to check premium status:', error);
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
return false;
|
|
3161
|
-
}
|
|
3162
|
-
getLocalDownloadCount() {
|
|
3163
|
-
const key = `download_count_${this.config.appId}`;
|
|
3164
|
-
const val = parseInt(localStorage.getItem(key) || '0', 10);
|
|
3165
|
-
return isNaN(val) ? 0 : val;
|
|
3166
|
-
}
|
|
3167
|
-
incrementLocalDownloadCount() {
|
|
3168
|
-
const key = `download_count_${this.config.appId}`;
|
|
3169
|
-
const next = this.getLocalDownloadCount() + 1;
|
|
3170
|
-
localStorage.setItem(key, String(next));
|
|
3171
|
-
return next;
|
|
3172
|
-
}
|
|
3173
|
-
handleError(error) {
|
|
3174
|
-
if (this.config.options?.debug) {
|
|
3175
|
-
console.error('Lead Fast Error:', error);
|
|
3176
|
-
}
|
|
3177
|
-
if (this.config.callbacks?.onError) {
|
|
3178
|
-
this.config.callbacks.onError(error);
|
|
3179
|
-
}
|
|
3180
|
-
else {
|
|
3181
|
-
// Default error handling
|
|
3182
|
-
console.error('Lead Fast:', error.message);
|
|
3183
|
-
}
|
|
3184
|
-
this.emit('error', error);
|
|
3185
|
-
}
|
|
3186
|
-
async checkPaymentStatus() {
|
|
3187
|
-
// Check if user returned from payment
|
|
3188
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
3189
|
-
if (urlParams.get('payment') === 'success') {
|
|
3190
|
-
// Wait for auth to be ready
|
|
3191
|
-
setTimeout(async () => {
|
|
3192
|
-
if (this.currentUser) {
|
|
3193
|
-
const isPremium = await this.checkPremiumStatus(this.currentUser.email);
|
|
3194
|
-
if (isPremium) {
|
|
3195
|
-
this.showSuccess('Payment Successful!', 'Welcome to premium! You now have unlimited usage.');
|
|
3196
|
-
}
|
|
3197
|
-
else {
|
|
3198
|
-
this.showSuccess('Processing Payment', 'Payment is being processed. Please refresh in a few moments.');
|
|
3199
|
-
}
|
|
3200
|
-
}
|
|
3201
|
-
else {
|
|
3202
|
-
this.showSuccess('Processing Payment', 'Please wait while we verify your payment...');
|
|
3203
|
-
}
|
|
3204
|
-
// Clean up URL
|
|
3205
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
3206
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
3207
|
-
}, 2000);
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3210
|
-
}
|
|
3211
|
-
// Export types for consumer use
|
|
3212
|
-
export * from './types.js';
|
|
3213
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var n=require("@supabase/supabase-js");exports.MoneyBar=class{mergeConfigs(n){const t="undefined"!=typeof window?window.APP_CONFIG:null;if(!n&&!t)throw new Error("MoneyBar: No configuration provided. Either pass config to constructor or set window.APP_CONFIG");return n?t?{...t,...n}:n:t}constructor(n){this.currentUser=null,this.userFingerprint=null,this.eventListeners=new Map,this.supabaseConnectionFailed=!1,this.paymentConnectionFailed=!1,this.securityKeyFailed=!1,this.isLoadingStatus=!1,this.cachedStatus=null,this.statusCacheTime=0,this.STATUS_CACHE_DURATION=5e3,this.initializationPromise=null,this.cachedPremiumStatus=null,this.premiumStatusCacheTime=0,this.config=this.mergeConfigs(n),this.config.options,this.validateConfig(),this.initializeSupabase(),this.initializePayment(),this.initializeFingerprint(),this.setupAuthListener(),this.checkPaymentStatus()}async handleDownload(){try{if(this.securityKeyFailed)return void this.showError("Invalid Security Key","Your security key is invalid, expired, or not found. Please check your MoneyBar configuration and ensure you have a valid security_key.");if(this.supabaseConnectionFailed)return void this.config.callbacks?.onError?.(new Error("Server connection required for download verification. Please check your configuration."));if(this.config.payment&&this.paymentConnectionFailed)return void this.config.callbacks?.onError?.(new Error("Payment configuration error. Please contact support."));if(this.currentUser){if(await this.checkPremiumStatus(this.currentUser.email))return void this.config.callbacks?.onPremiumDownload()}const n=await this.getDownloadStatus();if(n.currentCount>=n.limit)return void this.config.callbacks?.onLimitReached(n.currentCount,n.limit);await this.incrementDownloadCount(),this.config.callbacks?.onDownloadAllowed(),this.cachedStatus=null;const t=await this.getDownloadStatus();this.emit("countChanged",{count:t.currentCount,limit:t.limit}),t.currentCount>=t.limit&&setTimeout(()=>{this.config.callbacks?.onLimitReached(t.currentCount,t.limit)},100)}catch(n){this.handleError(n)}}async getDownloadStatus(n=!1){const t=this.cachedStatus?Date.now()-this.statusCacheTime:-1;if(!n&&this.cachedStatus&&t<this.STATUS_CACHE_DURATION)return this.cachedStatus;if(!n&&this.initializationPromise)return await this.initializationPromise;this.initializationPromise=this.fetchFreshStatus(n);try{return await this.initializationPromise}finally{this.initializationPromise=null}}async fetchFreshStatus(n){try{let t=0,e=!1;if(n||!this.cachedStatus){if(t=await this.getDownloadCount(),this.securityKeyFailed)return{currentCount:0,limit:this.config.freeAttemptLimit,isPremium:!1,isAuthenticated:!1,remaining:0}}else t=this.cachedStatus.currentCount;if(this.currentUser){const t=null!==this.cachedPremiumStatus&&Date.now()-this.premiumStatusCacheTime<this.STATUS_CACHE_DURATION;n||!t?(e=await this.checkPremiumStatus(this.currentUser.email),this.cachedPremiumStatus=e,this.premiumStatusCacheTime=Date.now()):(e=this.cachedPremiumStatus,console.log(`🔍 [CACHE] Using cached premium status from getDownloadStatus: ${e}`))}else e=!1;const a={currentCount:t,limit:this.config.freeAttemptLimit,isPremium:e,isAuthenticated:!!this.currentUser,remaining:Math.max(0,this.config.freeAttemptLimit-t)};return this.cachedStatus=a,this.statusCacheTime=Date.now(),a}catch(n){this.handleError(n);const t={currentCount:0,limit:this.config.freeAttemptLimit,isPremium:!1,isAuthenticated:!1,remaining:this.config.freeAttemptLimit};return this.cachedStatus=t,this.statusCacheTime=Date.now(),t}}async getUserContext(){const n=await this.getDownloadStatus();return{isPremium:n.isPremium,isAuthenticated:n.isAuthenticated,email:this.currentUser?.email,name:this.currentUser?.user_metadata?.full_name||this.currentUser?.user_metadata?.name,currentCount:n.currentCount,remaining:n.remaining,limit:n.limit,user:this.currentUser}}async signIn(){try{const{error:n}=await this.supabase.auth.signInWithOAuth({provider:"google",options:{redirectTo:`${window.location.origin}${window.location.pathname}`}});if(n)throw n}catch(n){this.handleError(n)}}async signOut(){try{await this.supabase.auth.signOut(),this.currentUser=null,this.emit("authChanged",{user:null,isPremium:!1})}catch(n){this.handleError(n)}}async createPayment(){if(!this.currentUser)throw new Error("User must be signed in to create payment");if(!this.config.payment)throw new Error("Payment configuration not provided");const n=this.config.payment?.find(n=>"dodo"===n.provider);if(!n)throw new Error("No dodo payment provider configured");const t={email:this.currentUser.email,product_id:n.productId,mode:n.mode||"test",app_id:this.config.appId,return_url:`${window.location.origin}${window.location.pathname}?payment=success`};console.log(`🔥 [PAYMENT API] createPayment called for provider: ${n.provider}, email: ${this.currentUser.email} | Time: ${(new Date).toISOString()}`);try{let e;if("dodo"!==n.provider)throw new Error(`Payment provider '${n.provider}' is not yet supported`);if(e=await fetch(`${this.config.supabase.url}/functions/v1/create-payment`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.supabase.anonKey}`},body:JSON.stringify(t)}),console.log(`🔥 [PAYMENT API] createPayment response for ${n.provider}: ${e.status}`),this.config.options,!e.ok){const n=await e.text();let t;this.config.options?.debug&&console.error("🔍 DEBUG: createPayment error response:",n);try{t=JSON.parse(n)}catch{t={error:n}}throw(n.includes("404")||n.includes("could not be found"))&&(this.paymentConnectionFailed=!0,this.config.options?.debug&&console.warn("Invalid payment product ID detected, disabling downloads"),this.updateStatusDisplays()),new Error(t.error||"Payment creation failed")}const a=await e.json();return this.config.options,a}catch(n){return this.handleError(n),null}}attachToButton(n,t){const e=document.querySelector(n);if(!e)throw new Error(`Button not found: ${n}`);this.injectBaseStyles(),this.createTitleBar(),this.createPaywallModal(),this.createSuccessModal(),this.createAuthUI(),this.createStatusDisplay(),e.setAttribute("data-leadfast-attached","true"),e.onclick=async n=>{n.preventDefault(),await this.handleButtonClick(t)},this.setupUIUpdates()}attachToButtons(n){this.injectBaseStyles(),this.createTitleBar(),this.createPaywallModal(),this.createSuccessModal(),this.createAuthUI(),n.buttons.forEach(n=>{const t=document.querySelector(n.selector);t?(t.setAttribute("data-leadfast-attached","true"),t.onclick=async t=>{t.preventDefault(),await this.handleButtonClick(n.downloadCallback)},this.setupUIUpdates()):console.warn(`Button not found: ${n.selector}`)}),this.createGlobalStatusDisplay()}async handleButtonClick(n){const t=document.querySelector('[data-leadfast-attached="true"]');if(t&&t.disabled)console.log("⚠️ Button click ignored - operation already in progress");else{t&&(t.disabled=!0,t.style.opacity="0.6");try{await this.handleButtonClickInternal(n)}finally{t&&(t.disabled=!1,t.style.opacity="1")}}}async handleButtonClickInternal(n){if(this.supabaseConnectionFailed)return void this.showConnectionError();if(this.config.payment&&this.paymentConnectionFailed)return void this.showPaymentConfigError();const t=this.cachedStatus&&Date.now()-this.statusCacheTime<this.STATUS_CACHE_DURATION?this.cachedStatus:await this.getDownloadStatus();if(t.isPremium){const t=await this.getUserContext();return void(n.length>0?n(t):n())}if(t.currentCount>=t.limit)this.showPaywallInstant(t);else{try{if(n.length>0){const t=await this.getUserContext();t.isPremium||(t.currentCount+=1,t.remaining=Math.max(0,t.limit-t.currentCount)),n(t)}else n();const t=await this.getDownloadStatus(!0);if(t.currentCount>=t.limit)return console.warn(`⚠️ Limit reached during execution (${t.currentCount}/${t.limit}) - canceling increment`),void alert("⚠️ You reached the limit while this action was processing. No attempt was counted.");const e=await this.incrementDownloadCount();if(e>this.config.freeAttemptLimit)return void console.error(`⚠️ Server returned count (${e}) exceeding limit (${this.config.freeAttemptLimit})`);if(!0===this.config.successMessage?.enabled){const n=this.config.successMessage?.title||"Action Completed!",t=this.config.successMessage?.message||"Your action completed successfully!";this.showSuccess(n,t)}}catch(n){return console.error("User function failed:",n),void alert("Action failed. Please check the console for details. No attempt was counted.")}this.cachedStatus=null,this.updateStatusDisplays()}}injectBaseStyles(){if(document.getElementById("lead-fast-styles"))return;this.config.theme?.name&&this.injectDaisyUI();const n=document.createElement("style");n.id="lead-fast-styles";const t=this.config.theme?.primaryColor||"#3182ce";n.textContent=`\n /* User Profile Card - Top Right */\n .lead-fast-profile {\n position: fixed;\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 1000;\n font-family: system-ui, -apple-system, sans-serif;\n width: 90%;\n max-width: 1200px;\n }\n\n .lead-fast-title-bar {\n background: rgba(250, 247, 245, 0.9) !important;\n border: 1px solid rgba(231, 226, 223, 0.6) !important;\n color: #291334 !important;\n border-radius: 16px;\n padding: 12px 24px;\n box-shadow: 0 8px 32px rgb(0 0 0 / 0.1), 0 2px 8px rgb(0 0 0 / 0.05);\n backdrop-filter: blur(20px);\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: 60px;\n font-family: system-ui, -apple-system, sans-serif;\n }\n\n /* Theme variations for floating title bar */\n [data-theme="bumblebee"] .lead-fast-title-bar {\n background: rgba(255, 248, 220, 0.9) !important;\n border-color: rgba(254, 215, 170, 0.6) !important;\n color: #1c1917 !important;\n }\n\n [data-theme="garden"] .lead-fast-title-bar {\n background: rgba(240, 253, 244, 0.9) !important;\n border-color: rgba(134, 239, 172, 0.6) !important;\n color: #14532d !important;\n }\n\n [data-theme="emerald"] .lead-fast-title-bar {\n background: rgba(236, 253, 245, 0.9) !important;\n border-color: rgba(167, 243, 208, 0.6) !important;\n color: #065f46 !important;\n }\n\n [data-theme="corporate"] .lead-fast-title-bar {\n background: rgba(248, 250, 252, 0.9) !important;\n border-color: rgba(226, 232, 240, 0.6) !important;\n color: #1e293b !important;\n }\n\n [data-theme="dark"] .lead-fast-title-bar {\n background: rgba(31, 41, 55, 0.9) !important;\n border-color: rgba(55, 65, 81, 0.6) !important;\n color: #f9fafb !important;\n }\n\n [data-theme="cyberpunk"] .lead-fast-title-bar {\n background: rgba(0, 20, 36, 0.9) !important;\n border-color: rgba(7, 89, 133, 0.6) !important;\n color: #0ea5e9 !important;\n box-shadow: 0 8px 32px rgba(14, 165, 233, 0.2), 0 2px 8px rgba(14, 165, 233, 0.1);\n }\n\n [data-theme="valentine"] .lead-fast-title-bar {\n background: rgba(233, 30, 122, 0.15) !important;\n border-color: rgba(233, 30, 122, 0.6) !important;\n color: #831843 !important;\n }\n\n [data-theme="synthwave"] .lead-fast-title-bar {\n background: rgba(32, 20, 64, 0.9) !important;\n border-color: rgba(186, 85, 211, 0.6) !important;\n color: #ff00ff !important;\n box-shadow: 0 8px 32px rgba(255, 0, 255, 0.2), 0 2px 8px rgba(255, 0, 255, 0.1);\n }\n\n [data-theme="dracula"] .lead-fast-title-bar {\n background: rgba(40, 42, 54, 0.9) !important;\n border-color: rgba(98, 114, 164, 0.6) !important;\n color: #f8f8f2 !important;\n }\n\n [data-theme="halloween"] .lead-fast-title-bar {\n background: rgba(26, 26, 26, 0.9) !important;\n border-color: rgba(255, 165, 0, 0.6) !important;\n color: #ff6600 !important;\n }\n\n [data-theme="forest"] .lead-fast-title-bar {\n background: rgba(23, 46, 23, 0.9) !important;\n border-color: rgba(34, 197, 94, 0.6) !important;\n color: #22c55e !important;\n }\n\n [data-theme="luxury"] .lead-fast-title-bar {\n background: rgba(9, 9, 11, 0.9) !important;\n border-color: rgba(212, 175, 55, 0.6) !important;\n color: #d4af37 !important;\n }\n\n [data-theme="night"] .lead-fast-title-bar {\n background: rgba(15, 23, 42, 0.9) !important;\n border-color: rgba(30, 58, 138, 0.6) !important;\n color: #60a5fa !important;\n }\n\n [data-theme="light"] .lead-fast-title-bar {\n background: rgba(255, 255, 255, 0.9) !important;\n border-color: rgba(229, 231, 235, 0.6) !important;\n color: #1f2937 !important;\n }\n\n [data-theme="cupcake"] .lead-fast-title-bar {\n background: rgba(250, 235, 215, 0.9) !important;\n border-color: rgba(219, 185, 156, 0.6) !important;\n color: #8b4513 !important;\n }\n\n [data-theme="retro"] .lead-fast-title-bar {\n background: rgba(212, 165, 116, 0.9) !important;\n border-color: rgba(185, 144, 102, 0.6) !important;\n color: #5d4037 !important;\n }\n\n [data-theme="aqua"] .lead-fast-title-bar {\n background: rgba(240, 253, 250, 0.9) !important;\n border-color: rgba(125, 211, 252, 0.6) !important;\n color: #155e75 !important;\n }\n\n [data-theme="lofi"] .lead-fast-title-bar {\n background: rgba(248, 248, 248, 0.9) !important;\n border-color: rgba(68, 68, 68, 0.6) !important;\n color: #1a1a1a !important;\n }\n\n [data-theme="pastel"] .lead-fast-title-bar {\n background: rgba(254, 251, 255, 0.9) !important;\n border-color: rgba(209, 196, 233, 0.6) !important;\n color: #7c3aed !important;\n }\n\n [data-theme="fantasy"] .lead-fast-title-bar {\n background: rgba(255, 247, 237, 0.9) !important;\n border-color: rgba(249, 168, 212, 0.6) !important;\n color: #be185d !important;\n }\n\n [data-theme="wireframe"] .lead-fast-title-bar {\n background: rgba(223, 223, 223, 0.9) !important;\n border-color: rgba(0, 0, 0, 0.3) !important;\n color: #000000 !important;\n }\n\n [data-theme="black"] .lead-fast-title-bar {\n background: rgba(0, 0, 0, 0.9) !important;\n border-color: rgba(55, 55, 55, 0.6) !important;\n color: #ffffff !important;\n }\n\n [data-theme="cmyk"] .lead-fast-title-bar {\n background: rgba(0, 255, 255, 0.9) !important;\n border-color: rgba(255, 0, 255, 0.6) !important;\n color: #000000 !important;\n }\n\n [data-theme="autumn"] .lead-fast-title-bar {\n background: rgba(139, 69, 19, 0.9) !important;\n border-color: rgba(255, 140, 0, 0.6) !important;\n color: #ff8c00 !important;\n }\n\n [data-theme="business"] .lead-fast-title-bar {\n background: rgba(29, 78, 216, 0.9) !important;\n border-color: rgba(59, 130, 246, 0.6) !important;\n color: #3b82f6 !important;\n }\n\n [data-theme="acid"] .lead-fast-title-bar {\n background: rgba(255, 255, 0, 0.9) !important;\n border-color: rgba(255, 0, 255, 0.6) !important;\n color: #000000 !important;\n }\n\n [data-theme="lemonade"] .lead-fast-title-bar {\n background: rgba(255, 255, 224, 0.9) !important;\n border-color: rgba(34, 197, 94, 0.6) !important;\n color: #15803d !important;\n }\n\n [data-theme="coffee"] .lead-fast-title-bar {\n background: rgba(101, 67, 33, 0.9) !important;\n border-color: rgba(160, 82, 45, 0.6) !important;\n color: #d2b48c !important;\n }\n\n [data-theme="winter"] .lead-fast-title-bar {\n background: rgba(248, 250, 252, 0.9) !important;\n border-color: rgba(59, 130, 246, 0.6) !important;\n color: #1e40af !important;\n }\n\n .lead-fast-title-section {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n\n .lead-fast-logo {\n font-size: 20px;\n font-weight: 700;\n display: flex;\n align-items: center;\n gap: 8px;\n color: inherit;\n text-decoration: none;\n }\n\n .lead-fast-logo img {\n width: 32px;\n height: 32px;\n border-radius: 6px;\n }\n\n .lead-fast-nav {\n display: flex;\n align-items: center;\n gap: 32px;\n margin-left: 24px;\n }\n\n .lead-fast-nav-link {\n color: inherit;\n text-decoration: none;\n font-weight: 500;\n font-size: 14px;\n opacity: 0.8;\n transition: all 0.2s;\n }\n\n .lead-fast-nav-link:hover {\n opacity: 1;\n text-decoration: none;\n }\n\n .lead-fast-user-section {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .lead-fast-avatar {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: linear-gradient(135deg, ${t}, #4f46e5);\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-weight: 600;\n font-size: 14px;\n }\n\n .lead-fast-user-info {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n }\n\n .lead-fast-user-email {\n color: inherit !important;\n font-size: 13px;\n font-weight: 500;\n line-height: 1;\n }\n\n .lead-fast-user-status {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .lead-fast-badge {\n padding: 2px 6px;\n border-radius: 12px;\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.025em;\n line-height: 1;\n }\n\n .lead-fast-badge.premium {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n .lead-fast-badge.free {\n background: #65c3c8 !important;\n color: #291334 !important;\n }\n\n [data-theme="dark"] .lead-fast-badge.premium {\n background: #059669 !important;\n color: #ffffff !important;\n }\n\n [data-theme="dark"] .lead-fast-badge.free {\n background: #374151 !important;\n color: #f9fafb !important;\n }\n\n .lead-fast-count {\n font-size: 10px;\n color: inherit !important;\n opacity: 0.7;\n line-height: 1;\n }\n\n .lead-fast-signout {\n background: rgba(0, 0, 0, 0.1) !important;\n border: none;\n color: inherit !important;\n cursor: pointer;\n padding: 6px 12px;\n border-radius: 8px;\n transition: all 0.2s;\n font-size: 11px;\n font-weight: 500;\n margin-left: 8px;\n }\n\n [data-theme="dark"] .lead-fast-signout {\n background: rgba(255, 255, 255, 0.1) !important;\n }\n\n [data-theme="cyberpunk"] .lead-fast-signout {\n background: rgba(14, 165, 233, 0.2) !important;\n }\n\n .lead-fast-signout:hover {\n background: rgba(0, 0, 0, 0.2) !important;\n transform: translateY(-1px);\n }\n\n [data-theme="dark"] .lead-fast-signout:hover {\n background: rgba(255, 255, 255, 0.2) !important;\n }\n\n [data-theme="cyberpunk"] .lead-fast-signout:hover {\n background: rgba(14, 165, 233, 0.3) !important;\n }\n\n .lead-fast-branding {\n position: absolute;\n bottom: -20px;\n left: 24px;\n font-size: 9px;\n color: inherit !important;\n opacity: 0.4;\n font-weight: 500;\n letter-spacing: 0.05em;\n text-transform: lowercase;\n background: rgba(255, 255, 255, 0.8);\n padding: 2px 6px;\n border-radius: 4px;\n backdrop-filter: blur(8px);\n }\n\n [data-theme="dark"] .lead-fast-branding {\n background: rgba(0, 0, 0, 0.6);\n }\n\n [data-theme="cyberpunk"] .lead-fast-branding {\n background: rgba(0, 20, 36, 0.8);\n }\n\n /* Modal Theming */\n .download-limiter-modal {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0,0,0,0.5);\n z-index: 9999;\n align-items: center;\n justify-content: center;\n }\n .download-limiter-modal.show { display: flex; }\n\n .download-limiter-content {\n background: #faf7f5 !important;\n color: #291334 !important;\n padding: 2rem;\n border-radius: 12px;\n max-width: 400px;\n text-align: center;\n box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);\n border: 1px solid #e7e2df !important;\n font-family: system-ui, -apple-system, sans-serif;\n }\n\n [data-theme="dark"] .download-limiter-content {\n background: #1f2937 !important;\n color: #f9fafb !important;\n border-color: #374151 !important;\n }\n\n [data-theme="cyberpunk"] .download-limiter-content {\n background: #001424 !important;\n color: #0ea5e9 !important;\n border-color: #075985 !important;\n }\n\n [data-theme="valentine"] .download-limiter-content {\n background: rgba(233, 30, 122, 0.05) !important;\n color: #831843 !important;\n border-color: #e91e7a !important;\n }\n\n [data-theme="bumblebee"] .download-limiter-content {\n background: rgba(255, 248, 220, 0.95) !important;\n color: #1c1917 !important;\n border-color: rgba(254, 215, 170, 0.8) !important;\n }\n\n [data-theme="garden"] .download-limiter-content {\n background: rgba(240, 253, 244, 0.95) !important;\n color: #14532d !important;\n border-color: rgba(134, 239, 172, 0.8) !important;\n }\n\n [data-theme="emerald"] .download-limiter-content {\n background: rgba(236, 253, 245, 0.95) !important;\n color: #065f46 !important;\n border-color: rgba(167, 243, 208, 0.8) !important;\n }\n\n [data-theme="corporate"] .download-limiter-content {\n background: rgba(248, 250, 252, 0.95) !important;\n color: #1e293b !important;\n border-color: rgba(226, 232, 240, 0.8) !important;\n }\n\n [data-theme="forest"] .download-limiter-content {\n background: rgba(23, 46, 23, 0.95) !important;\n color: #22c55e !important;\n border-color: rgba(34, 197, 94, 0.8) !important;\n }\n\n [data-theme="halloween"] .download-limiter-content {\n background: rgba(26, 26, 26, 0.95) !important;\n color: #ff6500 !important;\n border-color: rgba(255, 165, 0, 0.3) !important;\n }\n\n [data-theme="synthwave"] .download-limiter-content {\n background: rgba(32, 20, 64, 0.95) !important;\n color: #ba55d3 !important;\n border-color: rgba(186, 85, 211, 0.3) !important;\n }\n\n [data-theme="dracula"] .download-limiter-content {\n background: rgba(40, 42, 54, 0.95) !important;\n color: #f8f8f2 !important;\n border-color: rgba(98, 114, 164, 0.3) !important;\n }\n\n [data-theme="luxury"] .download-limiter-content {\n background: rgba(9, 9, 11, 0.95) !important;\n color: #d4af37 !important;\n border-color: rgba(212, 175, 55, 0.3) !important;\n }\n\n [data-theme="night"] .download-limiter-content {\n background: rgba(15, 23, 42, 0.95) !important;\n color: #1e40af !important;\n border-color: rgba(30, 58, 138, 0.3) !important;\n }\n\n [data-theme="light"] .download-limiter-content {\n background: rgba(255, 255, 255, 0.95) !important;\n color: #1f2937 !important;\n border-color: rgba(229, 231, 235, 0.8) !important;\n }\n\n [data-theme="cupcake"] .download-limiter-content {\n background: rgba(250, 235, 215, 0.95) !important;\n color: #8b4513 !important;\n border-color: rgba(219, 185, 156, 0.8) !important;\n }\n\n [data-theme="retro"] .download-limiter-content {\n background: rgba(212, 165, 116, 0.95) !important;\n color: #5d4037 !important;\n border-color: rgba(185, 144, 102, 0.8) !important;\n }\n\n [data-theme="aqua"] .download-limiter-content {\n background: rgba(240, 253, 250, 0.95) !important;\n color: #155e75 !important;\n border-color: rgba(125, 211, 252, 0.8) !important;\n }\n\n [data-theme="lofi"] .download-limiter-content {\n background: rgba(248, 248, 248, 0.95) !important;\n color: #1a1a1a !important;\n border-color: rgba(68, 68, 68, 0.8) !important;\n }\n\n [data-theme="pastel"] .download-limiter-content {\n background: rgba(254, 251, 255, 0.95) !important;\n color: #7c3aed !important;\n border-color: rgba(209, 196, 233, 0.8) !important;\n }\n\n [data-theme="fantasy"] .download-limiter-content {\n background: rgba(255, 247, 237, 0.95) !important;\n color: #be185d !important;\n border-color: rgba(249, 168, 212, 0.8) !important;\n }\n\n [data-theme="wireframe"] .download-limiter-content {\n background: rgba(255, 255, 255, 0.95) !important;\n color: #000000 !important;\n border-color: rgba(0, 0, 0, 0.3) !important;\n }\n\n [data-theme="black"] .download-limiter-content {\n background: rgba(0, 0, 0, 0.95) !important;\n color: #ffffff !important;\n border-color: rgba(55, 55, 55, 0.8) !important;\n }\n\n [data-theme="cmyk"] .download-limiter-content {\n background: rgba(0, 255, 255, 0.95) !important;\n color: #000000 !important;\n border-color: rgba(255, 0, 255, 0.8) !important;\n }\n\n [data-theme="autumn"] .download-limiter-content {\n background: rgba(139, 69, 19, 0.95) !important;\n color: #ff8c00 !important;\n border-color: rgba(255, 140, 0, 0.8) !important;\n }\n\n [data-theme="business"] .download-limiter-content {\n background: rgba(29, 78, 216, 0.95) !important;\n color: #3b82f6 !important;\n border-color: rgba(59, 130, 246, 0.8) !important;\n }\n\n [data-theme="acid"] .download-limiter-content {\n background: rgba(255, 255, 0, 0.95) !important;\n color: #000000 !important;\n border-color: rgba(255, 0, 255, 0.8) !important;\n }\n\n [data-theme="lemonade"] .download-limiter-content {\n background: rgba(255, 255, 224, 0.95) !important;\n color: #15803d !important;\n border-color: rgba(34, 197, 94, 0.8) !important;\n }\n\n [data-theme="coffee"] .download-limiter-content {\n background: rgba(101, 67, 33, 0.95) !important;\n color: #d2b48c !important;\n border-color: rgba(160, 82, 45, 0.8) !important;\n }\n\n [data-theme="winter"] .download-limiter-content {\n background: rgba(248, 250, 252, 0.95) !important;\n color: #1e40af !important;\n border-color: rgba(59, 130, 246, 0.8) !important;\n }\n\n .download-limiter-content h2 {\n color: inherit !important;\n margin: 0 0 1rem 0;\n font-size: 1.5rem;\n font-weight: 600;\n }\n .download-limiter-content p {\n color: inherit !important;\n margin: 0 0 1.5rem 0;\n line-height: 1.6;\n }\n .download-limiter-signin-btn {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n background: #65c3c8 !important;\n color: #291334 !important;\n border: none;\n padding: 12px 20px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n text-decoration: none;\n transition: all 0.2s;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n\n [data-theme="dark"] .download-limiter-signin-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cyberpunk"] .download-limiter-signin-btn {\n background: #f59e0b !important;\n color: #000000 !important;\n }\n\n [data-theme="valentine"] .download-limiter-signin-btn {\n background: #e91e7a !important;\n color: #ffffff !important;\n }\n\n [data-theme="bumblebee"] .download-limiter-signin-btn {\n background: #fbbf24 !important;\n color: #1c1917 !important;\n }\n\n [data-theme="garden"] .download-limiter-signin-btn {\n background: #22c55e !important;\n color: #ffffff !important;\n }\n\n [data-theme="emerald"] .download-limiter-signin-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="corporate"] .download-limiter-signin-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="forest"] .download-limiter-signin-btn {\n background: #22c55e !important;\n color: #1a1a1a !important;\n }\n\n [data-theme="halloween"] .download-limiter-signin-btn {\n background: #ff6500 !important;\n color: #1a1a1a !important;\n }\n\n [data-theme="synthwave"] .download-limiter-signin-btn {\n background: #ba55d3 !important;\n color: #201040 !important;\n }\n\n [data-theme="dracula"] .download-limiter-signin-btn {\n background: #8be9fd !important;\n color: #282a36 !important;\n }\n\n [data-theme="luxury"] .download-limiter-signin-btn {\n background: #d4af37 !important;\n color: #09090b !important;\n }\n\n [data-theme="night"] .download-limiter-signin-btn {\n background: #1e40af !important;\n color: #ffffff !important;\n }\n\n [data-theme="light"] .download-limiter-signin-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cupcake"] .download-limiter-signin-btn {\n background: #8b4513 !important;\n color: #ffffff !important;\n }\n\n [data-theme="retro"] .download-limiter-signin-btn {\n background: #d4a574 !important;\n color: #ffffff !important;\n }\n\n [data-theme="aqua"] .download-limiter-signin-btn {\n background: #0891b2 !important;\n color: #ffffff !important;\n }\n\n [data-theme="lofi"] .download-limiter-signin-btn {\n background: #1a1a1a !important;\n color: #ffffff !important;\n }\n\n [data-theme="pastel"] .download-limiter-signin-btn {\n background: #7c3aed !important;\n color: #ffffff !important;\n }\n\n [data-theme="fantasy"] .download-limiter-signin-btn {\n background: #be185d !important;\n color: #ffffff !important;\n }\n\n [data-theme="wireframe"] .download-limiter-signin-btn {\n background: #dfdfdf !important;\n color: #000000 !important;\n border: 1px solid #000000 !important;\n }\n\n [data-theme="black"] .download-limiter-signin-btn {\n background: #ffffff !important;\n color: #000000 !important;\n }\n\n [data-theme="cmyk"] .download-limiter-signin-btn {\n background: #ff00ff !important;\n color: #000000 !important;\n }\n\n [data-theme="autumn"] .download-limiter-signin-btn {\n background: #ff8c00 !important;\n color: #ffffff !important;\n }\n\n [data-theme="business"] .download-limiter-signin-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="acid"] .download-limiter-signin-btn {\n background: #ff00ff !important;\n color: #000000 !important;\n }\n\n [data-theme="lemonade"] .download-limiter-signin-btn {\n background: #22c55e !important;\n color: #ffffff !important;\n }\n\n [data-theme="coffee"] .download-limiter-signin-btn {\n background: #a0522d !important;\n color: #ffffff !important;\n }\n\n [data-theme="winter"] .download-limiter-signin-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n .download-limiter-signin-btn:hover {\n opacity: 0.9;\n transform: translateY(-1px);\n box-shadow: 0 4px 8px rgba(0,0,0,0.15);\n }\n\n /* Upgrade Button Styles for All Themes */\n [data-theme="dark"] .upgrade-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cyberpunk"] .upgrade-btn {\n background: #ff073a !important;\n color: #ffffff !important;\n }\n\n [data-theme="valentine"] .upgrade-btn {\n background: #e91e7a !important;\n color: #ffffff !important;\n }\n\n [data-theme="bumblebee"] .upgrade-btn {\n background: #f59e0b !important;\n color: #ffffff !important;\n }\n\n [data-theme="garden"] .upgrade-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="emerald"] .upgrade-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="corporate"] .upgrade-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="forest"] .upgrade-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="halloween"] .upgrade-btn {\n background: #ff7b00 !important;\n color: #000000 !important;\n }\n\n [data-theme="synthwave"] .upgrade-btn {\n background: #e879f9 !important;\n color: #ffffff !important;\n }\n\n [data-theme="dracula"] .upgrade-btn {\n background: #bd93f9 !important;\n color: #ffffff !important;\n }\n\n [data-theme="luxury"] .upgrade-btn {\n background: #d4af37 !important;\n color: #000000 !important;\n }\n\n [data-theme="night"] .upgrade-btn {\n background: #38bdf8 !important;\n color: #ffffff !important;\n }\n\n [data-theme="light"] .upgrade-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cupcake"] .upgrade-btn {\n background: #f472b6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="retro"] .upgrade-btn {\n background: #d4a574 !important;\n color: #ffffff !important;\n }\n\n [data-theme="aqua"] .upgrade-btn {\n background: #06b6d4 !important;\n color: #ffffff !important;\n }\n\n [data-theme="lofi"] .upgrade-btn {\n background: #a3a3a3 !important;\n color: #000000 !important;\n }\n\n [data-theme="pastel"] .upgrade-btn {\n background: #a78bfa !important;\n color: #ffffff !important;\n }\n\n [data-theme="fantasy"] .upgrade-btn {\n background: #f472b6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="wireframe"] .upgrade-btn {\n background: #dfdfdf !important;\n color: #000000 !important;\n border: 1px solid #000000 !important;\n }\n\n [data-theme="black"] .upgrade-btn {\n background: #ffffff !important;\n color: #000000 !important;\n }\n\n [data-theme="cmyk"] .upgrade-btn {\n background: #0891b2 !important;\n color: #ffffff !important;\n }\n\n [data-theme="autumn"] .upgrade-btn {\n background: #d97706 !important;\n color: #ffffff !important;\n }\n\n [data-theme="business"] .upgrade-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="acid"] .upgrade-btn {\n background: #84cc16 !important;\n color: #000000 !important;\n }\n\n [data-theme="lemonade"] .upgrade-btn {\n background: #22c55e !important;\n color: #ffffff !important;\n }\n\n [data-theme="coffee"] .upgrade-btn {\n background: #a0522d !important;\n color: #ffffff !important;\n }\n\n [data-theme="winter"] .upgrade-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n\n /* Cancel Button Styles for All Themes */\n [data-theme="dark"] .cancel-btn {\n background: #4b5563 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cyberpunk"] .cancel-btn {\n background: #7e22ce !important;\n color: #ffffff !important;\n }\n\n [data-theme="valentine"] .cancel-btn {\n background: #c11560 !important;\n color: #ffffff !important;\n }\n\n [data-theme="bumblebee"] .cancel-btn {\n background: #d97706 !important;\n color: #ffffff !important;\n }\n\n [data-theme="garden"] .cancel-btn {\n background: #059669 !important;\n color: #ffffff !important;\n }\n\n [data-theme="emerald"] .cancel-btn {\n background: #059669 !important;\n color: #ffffff !important;\n }\n\n [data-theme="corporate"] .cancel-btn {\n background: #6b7280 !important;\n color: #ffffff !important;\n }\n\n [data-theme="forest"] .cancel-btn {\n background: #059669 !important;\n color: #ffffff !important;\n }\n\n [data-theme="halloween"] .cancel-btn {\n background: #7c3aed !important;\n color: #ffffff !important;\n }\n\n [data-theme="synthwave"] .cancel-btn {\n background: #7e22ce !important;\n color: #ffffff !important;\n }\n\n [data-theme="dracula"] .cancel-btn {\n background: #6272a4 !important;\n color: #ffffff !important;\n }\n\n [data-theme="luxury"] .cancel-btn {\n background: #8b5cf6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="night"] .cancel-btn {\n background: #0284c7 !important;\n color: #ffffff !important;\n }\n\n [data-theme="light"] .cancel-btn {\n background: #6b7280 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cupcake"] .cancel-btn {\n background: #ec4899 !important;\n color: #ffffff !important;\n }\n\n [data-theme="retro"] .cancel-btn {\n background: #b8925c !important;\n color: #ffffff !important;\n }\n\n [data-theme="aqua"] .cancel-btn {\n background: #0891b2 !important;\n color: #ffffff !important;\n }\n\n [data-theme="lofi"] .cancel-btn {\n background: #6b7280 !important;\n color: #ffffff !important;\n }\n\n [data-theme="pastel"] .cancel-btn {\n background: #8b5cf6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="fantasy"] .cancel-btn {\n background: #ec4899 !important;\n color: #ffffff !important;\n }\n\n [data-theme="wireframe"] .cancel-btn {\n background: #f5f5f5 !important;\n color: #000000 !important;\n border: 1px solid #000000 !important;\n }\n\n [data-theme="black"] .cancel-btn {\n background: #6b7280 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cmyk"] .cancel-btn {\n background: #0369a1 !important;\n color: #ffffff !important;\n }\n\n [data-theme="autumn"] .cancel-btn {\n background: #c2410c !important;\n color: #ffffff !important;\n }\n\n [data-theme="business"] .cancel-btn {\n background: #6b7280 !important;\n color: #ffffff !important;\n }\n\n [data-theme="acid"] .cancel-btn {\n background: #65a30d !important;\n color: #ffffff !important;\n }\n\n [data-theme="lemonade"] .cancel-btn {\n background: #16a34a !important;\n color: #ffffff !important;\n }\n\n [data-theme="coffee"] .cancel-btn {\n background: #8b4513 !important;\n color: #ffffff !important;\n }\n\n [data-theme="winter"] .cancel-btn {\n background: #1e40af !important;\n color: #ffffff !important;\n }\n\n /* Success Button Styles for All Themes */\n [data-theme="dark"] .success-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cyberpunk"] .success-btn {\n background: #00ff41 !important;\n color: #000000 !important;\n }\n\n [data-theme="valentine"] .success-btn {\n background: #e91e7a !important;\n color: #ffffff !important;\n }\n\n [data-theme="bumblebee"] .success-btn {\n background: #f59e0b !important;\n color: #ffffff !important;\n }\n\n [data-theme="garden"] .success-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="emerald"] .success-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="corporate"] .success-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="forest"] .success-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="halloween"] .success-btn {\n background: #ff7b00 !important;\n color: #000000 !important;\n }\n\n [data-theme="synthwave"] .success-btn {\n background: #00ff41 !important;\n color: #000000 !important;\n }\n\n [data-theme="dracula"] .success-btn {\n background: #50fa7b !important;\n color: #000000 !important;\n }\n\n [data-theme="luxury"] .success-btn {\n background: #d4af37 !important;\n color: #000000 !important;\n }\n\n [data-theme="night"] .success-btn {\n background: #22d3ee !important;\n color: #ffffff !important;\n }\n\n [data-theme="light"] .success-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cupcake"] .success-btn {\n background: #f472b6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="retro"] .success-btn {\n background: #d4a574 !important;\n color: #ffffff !important;\n }\n\n [data-theme="aqua"] .success-btn {\n background: #06b6d4 !important;\n color: #ffffff !important;\n }\n\n [data-theme="lofi"] .success-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="pastel"] .success-btn {\n background: #34d399 !important;\n color: #ffffff !important;\n }\n\n [data-theme="fantasy"] .success-btn {\n background: #f472b6 !important;\n color: #ffffff !important;\n }\n\n [data-theme="wireframe"] .success-btn {\n background: #dfdfdf !important;\n color: #000000 !important;\n border: 1px solid #000000 !important;\n }\n\n [data-theme="black"] .success-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="cmyk"] .success-btn {\n background: #06b6d4 !important;\n color: #ffffff !important;\n }\n\n [data-theme="autumn"] .success-btn {\n background: #d97706 !important;\n color: #ffffff !important;\n }\n\n [data-theme="business"] .success-btn {\n background: #10b981 !important;\n color: #ffffff !important;\n }\n\n [data-theme="acid"] .success-btn {\n background: #84cc16 !important;\n color: #000000 !important;\n }\n\n [data-theme="lemonade"] .success-btn {\n background: #22c55e !important;\n color: #ffffff !important;\n }\n\n [data-theme="coffee"] .success-btn {\n background: #a0522d !important;\n color: #ffffff !important;\n }\n\n [data-theme="winter"] .success-btn {\n background: #3b82f6 !important;\n color: #ffffff !important;\n }\n .download-limiter-signout-btn {\n background: #dc3545;\n color: white;\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 12px;\n cursor: pointer;\n margin-left: 8px;\n }\n .download-limiter-premium-badge {\n background: linear-gradient(45deg, #f39c12, #e74c3c);\n color: white;\n padding: 2px 6px;\n border-radius: 3px;\n font-size: 10px;\n margin-left: 8px;\n font-weight: bold;\n }\n .download-limiter-btn-premium {\n opacity: 0.7;\n }\n .download-limiter-success-modal {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0,0,0,0.5);\n z-index: 10000;\n align-items: center;\n justify-content: center;\n }\n .download-limiter-success-modal.show { display: flex; }\n .download-limiter-success-content {\n background: white;\n padding: 2rem;\n border-radius: 8px;\n max-width: 400px;\n text-align: center;\n box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n border-left: 4px solid #38a169;\n }\n\n /* Single upgrade flow styles */\n .single-upgrade-container {\n margin: 20px 0;\n text-align: center;\n }\n\n .single-upgrade-content .upgrade-btn {\n background: linear-gradient(135deg, #059669, #0d9488) !important;\n color: white !important;\n border: none !important;\n padding: 16px 32px !important;\n border-radius: 12px !important;\n font-size: 18px !important;\n font-weight: 600 !important;\n cursor: pointer !important;\n transition: all 0.3s ease !important;\n box-shadow: 0 4px 12px rgba(5, 150, 105, 0.3) !important;\n margin-bottom: 16px !important;\n min-width: 200px !important;\n }\n\n .single-upgrade-content .upgrade-btn:hover {\n background: linear-gradient(135deg, #047857, #0f766e) !important;\n transform: translateY(-2px) !important;\n box-shadow: 0 6px 20px rgba(5, 150, 105, 0.4) !important;\n }\n\n .single-upgrade-content .upgrade-description {\n font-size: 14px !important;\n color: #6b7280 !important;\n margin: 12px 0 0 0 !important;\n line-height: 1.5 !important;\n max-width: 300px !important;\n margin-left: auto !important;\n margin-right: auto !important;\n }\n\n /* Anonymous user usage count styles */\n .lead-fast-anonymous-status {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .lead-fast-anonymous-status .lead-fast-count {\n background: rgba(59, 130, 246, 0.1);\n color: #1d4ed8;\n padding: 6px 12px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n border: 1px solid rgba(59, 130, 246, 0.2);\n }\n\n /* Loading state styles */\n .lead-fast-loading-status {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .lead-fast-spinner {\n width: 20px;\n height: 20px;\n color: #3b82f6;\n }\n\n .lead-fast-loading-text {\n font-size: 14px;\n color: #6b7280;\n font-weight: 500;\n }\n\n /* Success button theming */\n .download-limiter-btn.success-btn {\n background: #059669 !important;\n color: white !important;\n border: none !important;\n padding: 12px 24px !important;\n border-radius: 8px !important;\n cursor: pointer !important;\n margin-top: 20px !important;\n font-size: 16px !important;\n font-weight: 500 !important;\n transition: all 0.2s ease !important;\n }\n\n .download-limiter-btn.success-btn:hover {\n background: #047857 !important;\n transform: translateY(-1px) !important;\n }\n\n /* Theme-specific success button colors */\n [data-theme="cyberpunk"] .download-limiter-btn.success-btn {\n background: #0ea5e9 !important;\n box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3) !important;\n }\n\n [data-theme="cyberpunk"] .download-limiter-btn.success-btn:hover {\n background: #0284c7 !important;\n }\n\n [data-theme="valentine"] .download-limiter-btn.success-btn {\n background: #be185d !important;\n }\n\n [data-theme="valentine"] .download-limiter-btn.success-btn:hover {\n background: #9d174d !important;\n }\n\n [data-theme="bumblebee"] .download-limiter-btn.success-btn {\n background: #f59e0b !important;\n }\n\n [data-theme="bumblebee"] .download-limiter-btn.success-btn:hover {\n background: #d97706 !important;\n }\n\n [data-theme="garden"] .download-limiter-btn.success-btn {\n background: #16a34a !important;\n }\n\n [data-theme="garden"] .download-limiter-btn.success-btn:hover {\n background: #15803d !important;\n }\n\n /* Google Sign-in button theming */\n .download-limiter-btn.google-signin-btn {\n background: #4285f4 !important;\n color: white !important;\n border: none !important;\n padding: 12px 24px !important;\n border-radius: 8px !important;\n cursor: pointer !important;\n margin-right: 10px !important;\n display: inline-flex !important;\n align-items: center !important;\n gap: 8px !important;\n font-size: 14px !important;\n font-weight: 500 !important;\n transition: all 0.2s ease !important;\n }\n\n .download-limiter-btn.google-signin-btn:hover {\n background: #3367d6 !important;\n transform: translateY(-1px) !important;\n }\n\n /* Upgrade button theming */\n .download-limiter-btn.upgrade-btn {\n background: #059669 !important;\n color: white !important;\n border: none !important;\n padding: 12px 24px !important;\n border-radius: 8px !important;\n cursor: pointer !important;\n margin-right: 10px !important;\n font-size: 16px !important;\n font-weight: 500 !important;\n transition: all 0.2s ease !important;\n }\n\n .download-limiter-btn.upgrade-btn:hover {\n background: #047857 !important;\n transform: translateY(-1px) !important;\n }\n\n /* Theme-specific button colors */\n [data-theme="cyberpunk"] .download-limiter-btn.upgrade-btn {\n background: #0ea5e9 !important;\n box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3) !important;\n }\n\n [data-theme="valentine"] .download-limiter-btn.upgrade-btn {\n background: #be185d !important;\n }\n\n [data-theme="bumblebee"] .download-limiter-btn.upgrade-btn {\n background: #f59e0b !important;\n }\n\n [data-theme="garden"] .download-limiter-btn.upgrade-btn {\n background: #16a34a !important;\n }\n\n /* Cancel button container and styling - positioned at very bottom */\n .download-limiter-cancel-container {\n margin-top: 30px !important;\n padding-top: 20px !important;\n border-top: 1px solid rgba(0, 0, 0, 0.1) !important;\n text-align: center !important;\n position: relative !important;\n bottom: 0 !important;\n }\n\n .download-limiter-btn.cancel-btn {\n background: transparent !important;\n color: #6b7280 !important;\n border: none !important;\n padding: 8px 16px !important;\n border-radius: 6px !important;\n cursor: pointer !important;\n font-size: 13px !important;\n font-weight: 400 !important;\n transition: all 0.2s ease !important;\n text-decoration: underline !important;\n margin: 0 auto !important;\n display: block !important;\n }\n\n .download-limiter-btn.cancel-btn:hover {\n color: #374151 !important;\n background: rgba(107, 114, 128, 0.1) !important;\n text-decoration: none !important;\n }\n\n /* Theme-specific cancel button border */\n [data-theme="dark"] .download-limiter-cancel-container {\n border-top-color: rgba(255, 255, 255, 0.1) !important;\n }\n\n [data-theme="dark"] .download-limiter-btn.cancel-btn {\n color: #9ca3af !important;\n }\n\n [data-theme="dark"] .download-limiter-btn.cancel-btn:hover {\n color: #d1d5db !important;\n background: rgba(156, 163, 175, 0.1) !important;\n }\n\n [data-theme="cyberpunk"] .download-limiter-cancel-container {\n border-top-color: rgba(14, 165, 233, 0.2) !important;\n }\n\n [data-theme="cyberpunk"] .download-limiter-btn.cancel-btn {\n color: #0ea5e9 !important;\n }\n\n [data-theme="cyberpunk"] .download-limiter-btn.cancel-btn:hover {\n color: #38bdf8 !important;\n background: rgba(14, 165, 233, 0.1) !important;\n }\n `,document.head.appendChild(n)}injectDaisyUI(){if(document.querySelector('link[href*="daisyui"]')){if(this.config.theme?.name){const n=this.config.theme.name.toLowerCase();document.documentElement.setAttribute("data-theme",n)}}else if(this.config.theme?.name){const n=this.config.theme.name.toLowerCase();document.documentElement.setAttribute("data-theme",n),this.updateStatusDisplays()}}createPaywallModal(){if(document.getElementById("download-limiter-paywall"))return;const n=document.createElement("div");n.id="download-limiter-paywall",n.className="download-limiter-modal",n.innerHTML='\n <div class="download-limiter-content">\n <h3 id="download-limiter-paywall-title">Usage Limit Reached</h3>\n <p id="download-limiter-paywall-subtitle">You\'ve used all your free attempts. Upgrade to premium for unlimited usage!</p>\n\n \x3c!-- Auth Section (shows when not signed in) --\x3e\n <div id="download-limiter-auth-section" style="margin: 20px 0;">\n <p>Sign in to continue or upgrade to premium</p>\n <button id="download-limiter-signin-btn" class="download-limiter-btn google-signin-btn">\n <svg width="18" height="18" viewBox="0 0 18 18" fill="none">\n <path d="M16.51 8H8.98v3h4.3c-.18 1-.74 1.48-1.6 2.04v2.01h2.6a8.8 8.8 0 0 0 2.38-5.88c0-.57-.05-.66-.15-1.18z" fill="white"/>\n <path d="M8.98 17c2.16 0 3.97-.72 5.3-1.94l-2.6-2.04a4.8 4.8 0 0 1-2.7.75 4.8 4.8 0 0 1-4.52-3.4H1.83v2.07A8 8 0 0 0 8.98 17z" fill="white"/>\n <path d="M4.46 10.37a4.8 4.8 0 0 1-.25-1.37c0-.48.09-.94.25-1.37V5.56H1.83a8 8 0 0 0 0 6.88l2.63-2.07z" fill="white"/>\n <path d="M8.98 3.77c1.32 0 2.5.45 3.44 1.35l2.58-2.58C13.94.64 11.66 0 8.98 0A8 8 0 0 0 1.83 5.56l2.63 2.07c.61-1.8 2.26-3.86 4.52-3.86z" fill="white"/>\n </svg>\n Sign in with Google\n </button>\n </div>\n\n \x3c!-- Payment Section (shows when signed in) --\x3e\n <div id="download-limiter-payment-section" style="margin: 20px 0; display: none;">\n <button id="download-limiter-upgrade-btn" class="download-limiter-btn upgrade-btn">\n Upgrade to Premium\n </button>\n </div>\n\n <div class="download-limiter-cancel-container">\n <button id="download-limiter-close-btn" class="download-limiter-btn cancel-btn">\n Cancel\n </button>\n </div>\n </div>\n ',document.body.appendChild(n),n.querySelector("#download-limiter-signin-btn").addEventListener("click",async()=>{await this.signIn()}),n.querySelector("#download-limiter-upgrade-btn").addEventListener("click",async()=>{const n=await this.createPayment();n?.checkout_url&&(window.location.href=n.checkout_url)}),n.querySelector("#download-limiter-close-btn").addEventListener("click",()=>{this.config.options?.debug&&(console.log("🔍 DEBUG: Cancel button clicked"),console.log("🔍 DEBUG: this.config.feedback:",this.config.feedback),console.log("🔍 DEBUG: this.config.email:",this.config.email),console.log("🔍 DEBUG: Feedback config exists:",!(!this.config.feedback&&!this.config.email))),this.config.feedback||this.config.email?(this.config.options?.debug&&console.log("🔍 DEBUG: Showing feedback form"),this.showFeedbackForm()):(this.config.options?.debug&&console.log("🔍 DEBUG: No email config, just closing modal"),n.classList.remove("show"))})}createSuccessModal(){if(document.getElementById("download-limiter-success"))return;const n=document.createElement("div");n.id="download-limiter-success",n.className="download-limiter-modal",n.innerHTML='\n <div class="download-limiter-content"> \x3c!-- Use same content class as paywall for theming --\x3e\n <h3 id="download-limiter-success-title">Action Started!</h3>\n <p id="download-limiter-success-message">Your action completed successfully!</p>\n <button id="download-limiter-success-close" class="download-limiter-btn success-btn">\n OK\n </button>\n </div>\n ',document.body.appendChild(n),n.querySelector("#download-limiter-success-close").addEventListener("click",()=>{n.classList.remove("show")}),n.addEventListener("click",t=>{t.target===n&&n.classList.remove("show")})}showFeedbackForm(){const n=document.getElementById("download-limiter-paywall");n&&n.classList.remove("show"),this.createFeedbackModal();const t=document.getElementById("download-limiter-feedback");t&&t.classList.add("show")}createFeedbackModal(){if(document.getElementById("download-limiter-feedback"))return void(this.config.options?.debug&&console.log("🔍 DEBUG: Feedback modal already exists"));const n=this.config.feedback||(this.config.email?{form:{title:"Quick Feedback",description:"Why didn't you upgrade?",option1:"Not useful for my needs",option2:"Didn't find what I was looking for",option3:"Just trying it out"},email:this.config.email}:null);if(this.config.options?.debug&&(console.log("🔍 DEBUG: Feedback config:",n),console.log("🔍 DEBUG: this.config.feedback:",this.config.feedback),console.log("🔍 DEBUG: this.config.email:",this.config.email)),!n)return void(this.config.options?.debug&&console.log("🔍 DEBUG: No feedback config found, not creating modal"));const t=document.createElement("div");t.id="download-limiter-feedback",t.className="download-limiter-modal",t.innerHTML=`\n <div class="download-limiter-content">\n <h3>${n.form.title}</h3>\n <p style="margin-bottom: 20px;">${n.form.description}</p>\n\n <form id="feedback-form" style="text-align: left;">\n <div style="margin-bottom: 15px;">\n <label style="display: block; margin-bottom: 8px;">\n <input type="radio" name="reason" value="option1" style="margin-right: 8px;">\n ${n.form.option1}\n </label>\n <label style="display: block; margin-bottom: 8px;">\n <input type="radio" name="reason" value="option2" style="margin-right: 8px;">\n ${n.form.option2}\n </label>\n <label style="display: block; margin-bottom: 8px;">\n <input type="radio" name="reason" value="option3" style="margin-right: 8px;">\n ${n.form.option3}\n </label>\n </div>\n\n <div style="margin-bottom: 15px;">\n <label for="feedback-details" style="display: block; margin-bottom: 5px;">Tell us more:</label>\n <textarea\n id="feedback-details"\n name="details"\n rows="3"\n style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"\n placeholder="Your feedback helps us improve..." maxlength="200"></textarea>\n </div>\n\n <div style="margin-bottom: 20px;">\n <label for="feedback-email" style="display: block; margin-bottom: 5px;">\n Email (optional):\n </label>\n <input\n type="email"\n id="feedback-email"\n name="email"\n style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"\n placeholder="your@email.com">\n <small style="color: #666; display: block; margin-top: 4px;">\n Only if you want us to reach out about what you mentioned above. We don't follow up unless you want us to\n </small>\n </div>\n\n \n <div style="text-align: center;">\n <button type="submit" class="download-limiter-btn success-btn" style="margin-right: 10px;">\n Submit Feedback\n </button>\n <button type="button" id="feedback-skip-btn" class="download-limiter-btn cancel-btn">\n Skip\n </button>\n </div>\n </form>\n </div>\n `,document.body.appendChild(t),this.config.options?.debug&&console.log("🔍 DEBUG: Feedback modal created and added to DOM");const e=t.querySelector("#feedback-form");e.addEventListener("submit",n=>{n.preventDefault(),this.submitFeedback(e)}),t.querySelector("#feedback-skip-btn").addEventListener("click",()=>{t.classList.remove("show")}),t.addEventListener("click",n=>{n.target===t&&t.classList.remove("show")})}async submitFeedback(n){const t=new FormData(n),e=t.get("reason"),a=t.get("details"),o=t.get("email");if(!e)return void alert("Please select a reason");const r={reason:e,details:a,email:o||void 0,timestamp:(new Date).toISOString(),userAgent:navigator.userAgent,appId:this.config.appId};try{await this.sendFeedbackEmail(r);const n=document.getElementById("download-limiter-feedback");n&&n.classList.remove("show"),alert("Thank you for your feedback!")}catch(n){console.error("Failed to send feedback:",n),alert("Failed to send feedback. Please try again.")}}async sendFeedbackEmail(n){let t;if(this.config.feedback?.email?t=Array.isArray(this.config.feedback.email)?this.config.feedback.email[0]:this.config.feedback.email:this.config.email&&(t={provider:"resend",apiKey:this.config.email.resendApiKey,fromEmail:this.config.email.fromEmail}),!t)throw new Error("Email configuration not available");const e=`\nNew Feedback from ${this.config.appId}\n\nReason: ${n.reason}\nDetails: ${n.details}\nUser Email: ${n.email||"Not provided"}\nTimestamp: ${n.timestamp}\nUser Agent: ${n.userAgent}\n `.trim();let a;if("resend"!==t.provider&&t.provider)throw new Error(`Unsupported email provider: ${t.provider}`);if(a=await fetch("https://api.resend.com/emails",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.apiKey||t.resendApiKey}`},body:JSON.stringify({from:t.fromEmail,to:[t.fromEmail],subject:`Feedback from ${this.config.appId}`,text:e})}),!a.ok)throw new Error(`Email API error: ${a.status}`)}createAuthUI(){if(document.getElementById("lead-fast-profile"))return;const n=document.createElement("div");n.id="lead-fast-profile",n.className="lead-fast-profile",document.body.appendChild(n)}createStatusDisplay(){if(document.getElementById("download-limiter-status"))return;const n=document.createElement("div");n.id="download-limiter-status",n.className="download-limiter-status",document.body.appendChild(n)}createGlobalStatusDisplay(){this.createStatusDisplay()}showPaywallInstant(n){this.createPaywallModal();const t=document.getElementById("download-limiter-paywall");if(t){const e=t.querySelector("#download-limiter-paywall-title");e&&(e.textContent=`You've used ${n.limit} free attempts`);const a=t.querySelector("#download-limiter-auth-section"),o=t.querySelector("#download-limiter-payment-section");if(this.currentUser)a.style.display="none",o.style.display="block";else{const n=t.querySelector("#download-limiter-paywall-subtitle");n&&(n.textContent="Sign in to continue. If you're already premium, you'll get unlimited access. New users can upgrade after signing in."),a.style.display="none",o.style.display="none",this.showSingleUpgradeFlow(t)}t.classList.add("show")}}async showPaywall(){const n=await this.getDownloadStatus();this.showPaywallInstant(n)}showSingleUpgradeFlow(n){let t=n.querySelector(".single-upgrade-container");if(!t){t=document.createElement("div"),t.className="single-upgrade-container",t.innerHTML='\n <div class="single-upgrade-content">\n <button class="download-limiter-btn upgrade-btn" id="single-upgrade-btn">\n Continue with Google\n </button>\n \x3c!--<p class="upgrade-description">\n Sign in to restore access. Premium members get unlimited usage immediately.\n </p>--\x3e\n </div>\n ';const e=n.querySelector(".download-limiter-cancel-container");e?n.querySelector(".download-limiter-content")?.insertBefore(t,e):n.querySelector(".download-limiter-content")?.appendChild(t)}t.style.display="block";const e=n.querySelector("#single-upgrade-btn");if(e){e.replaceWith(e.cloneNode(!0));const t=n.querySelector("#single-upgrade-btn");t?.addEventListener("click",async()=>{try{const{error:n}=await this.supabase.auth.signInWithOAuth({provider:"google",options:{redirectTo:`${window.location.origin}${window.location.pathname}?upgrade=true`}});n&&(console.error("Sign in error:",n),this.showError("Sign In Failed","Failed to sign in with Google. Please try again."))}catch(n){console.error("Upgrade flow error:",n),this.showError("Upgrade Failed","Failed to start upgrade process. Please try again.")}})}}showSuccess(n,t){const e=document.getElementById("download-limiter-success");if(e){const a=e.querySelector("#download-limiter-success-title"),o=e.querySelector("#download-limiter-success-message");a&&(a.textContent=n),o&&(o.textContent=t),e.classList.add("show")}}showError(n,t){const e=document.createElement("div");e.style.cssText="\n position: fixed;\n top: 20px;\n right: 20px;\n background: #fee2e2;\n border: 1px solid #fecaca;\n color: #dc2626;\n padding: 12px 16px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n line-height: 1.4;\n max-width: 300px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n ",e.innerHTML=`\n <div style="font-weight: 600; margin-bottom: 4px;">${n}</div>\n <div style="font-size: 13px; opacity: 0.9;">${t}</div>\n `,document.body.appendChild(e),setTimeout(()=>{e.parentNode&&e.parentNode.removeChild(e)},5e3),e.addEventListener("click",()=>{e.parentNode&&e.parentNode.removeChild(e)})}showConnectionError(){this.showError("Connection Error","Server connection required for download verification. Please check your configuration and try again.")}showPaymentConfigError(){this.showError("Payment Configuration Error","Payment system is not properly configured. Please contact support.")}disableDownloadButtons(n){const t=document.querySelectorAll("[data-leadfast-attached]"),e={connection:"Download disabled: Server connection required",payment:"Download disabled: Payment configuration error",security_key:"Download disabled: Invalid or missing security key"},a={connection:"⚠️ Connection Error - Downloads Disabled",payment:"⚠️ Payment Config Error - Downloads Disabled",security_key:"🔐 Invalid Security Key - Please Update Configuration"};t.forEach(t=>{const a=t;a.disabled=!0,a.style.opacity="0.5",a.style.cursor="not-allowed",a.title=e[n]});const o=document.querySelector(".lead-fast-user-section");o&&(o.innerHTML=`\n <div style="\n color: #ef4444;\n padding: 6px 8px;\n font-size: 12px;\n font-weight: 500;\n line-height: 1.2;\n border-radius: 4px;\n background: rgba(239, 68, 68, 0.05);\n border: 1px solid rgba(239, 68, 68, 0.2);\n max-width: 200px;\n text-align: center;\n ">\n ${a[n]}\n </div>\n `)}async updateStatusDisplays(){if(this.securityKeyFailed)return void this.disableDownloadButtons("security_key");if(this.supabaseConnectionFailed)return void this.disableDownloadButtons("connection");if(this.config.payment&&this.paymentConnectionFailed)return void this.disableDownloadButtons("payment");this.isLoadingStatus=!0,this.updateUserSection(null);const n=await this.getDownloadStatus();this.isLoadingStatus=!1,this.securityKeyFailed?this.disableDownloadButtons("security_key"):this.supabaseConnectionFailed?this.disableDownloadButtons("connection"):this.updateUserSection(n)}createTitleBar(){let n=document.getElementById("lead-fast-profile");n||(n=document.createElement("div"),n.id="lead-fast-profile",n.className="lead-fast-profile",document.body.appendChild(n));const t=this.config.titleBar||{},e=t.title||"My App",a=t.titleImage,o=t.links||[];n.innerHTML=`\n <div class="lead-fast-title-bar">\n <div class="lead-fast-title-section">\n <a href="#" class="lead-fast-logo">\n ${a?`<img src="${a}" alt="${e}">`:""}\n <span>${e}</span>\n </a>\n <nav class="lead-fast-nav">\n ${o.map(n=>`\n <a href="${n.url}" class="lead-fast-nav-link" ${n.target?`target="${n.target}"`:""}>\n ${n.text}\n </a>\n `).join("")}\n </nav>\n </div>\n\n <div class="lead-fast-user-section" id="lead-fast-user-section">\n \x3c!-- User section will be populated when logged in --\x3e\n </div>\n\n \x3c!-- <a href="https://www.moneyfast.bar" target="_blank" class="lead-fast-branding" style="text-decoration: none; color: inherit;">moneyfast.bar</a>--\x3e\n </div>\n `}updateUserSection(n){const t=document.getElementById("lead-fast-user-section");if(t)if(null===n||this.isLoadingStatus)t.innerHTML='\n <div class="lead-fast-loading-status">\n <svg class="lead-fast-spinner" viewBox="0 0 24 24">\n <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" fill="none" stroke-dasharray="31.4 31.4" stroke-linecap="round">\n <animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/>\n </circle>\n </svg>\n <span class="lead-fast-loading-text">Loading...</span>\n </div>\n ';else if(this.currentUser){const e=this.currentUser.email.charAt(0).toUpperCase(),a=this.currentUser.email,o=n.isPremium?"premium":"free",r=n.isPremium?"Premium":"Free",i=n.isPremium?"Unlimited":`${n.currentCount}/${n.limit}`;t.innerHTML=`\n <div class="lead-fast-avatar">${e}</div>\n <div class="lead-fast-user-info">\n <div class="lead-fast-user-email">${a}</div>\n <div class="lead-fast-user-status">\n <span class="lead-fast-badge ${o}">${r}</span>\n <span class="lead-fast-count">${i}</span>\n </div>\n </div>\n <button class="lead-fast-signout">signout</button>\n `;const s=document.querySelector(".lead-fast-title-bar");s&&getComputedStyle(s);const d=t.querySelector(".lead-fast-signout");d&&d.addEventListener("click",()=>{this.signOut()})}else{const e=`Attempts: ${n.currentCount}/${n.limit}`;t.innerHTML=`\n <div class="lead-fast-anonymous-status">\n <span class="lead-fast-count">${e}</span>\n </div>\n `}}setupUIUpdates(){this.on("authChanged",async()=>{this.cachedStatus=null,this.updateStatusDisplays();const n=document.getElementById("download-limiter-paywall");n&&n.classList.contains("show")&&(this.updatePaywallDisplay(),this.currentUser&&this.config.payment&&setTimeout(async()=>{if(await this.checkPremiumStatus(this.currentUser.email))n.classList.remove("show");else{const n=await this.createPayment();n?.checkout_url&&(window.location.href=n.checkout_url)}},1e3))}),this.on("countChanged",()=>{this.updateStatusDisplays()}),this.updateStatusDisplays()}updatePaywallDisplay(){const n=document.getElementById("download-limiter-paywall");if(!n)return;const t=n.querySelector("#download-limiter-auth-section"),e=n.querySelector("#download-limiter-payment-section");this.currentUser?(t.style.display="none",e.style.display="block"):(t.style.display="block",e.style.display="none")}on(n,t){this.eventListeners.has(n)||this.eventListeners.set(n,[]),this.eventListeners.get(n).push(t)}off(n,t){const e=this.eventListeners.get(n);if(e){const n=e.indexOf(t);n>-1&&e.splice(n,1)}}emit(n,t){const e=this.eventListeners.get(n);e&&e.forEach(e=>{try{e(t)}catch(t){console.error(`Error in ${n} listener:`,t)}})}validateConfig(){if(!this.config.appId)throw new Error("appId is required");if(!this.config.supabase?.url)throw new Error("supabase.url is required");if(!this.config.supabase?.anonKey)throw new Error("supabase.anonKey is required");if("number"!=typeof this.config.freeAttemptLimit||this.config.freeAttemptLimit<0)throw new Error("freeAttemptLimit must be a non-negative number")}async initializeSupabase(){try{this.supabase=n.createClient(this.config.supabase.url,this.config.supabase.anonKey),this.supabaseConnectionFailed=!1}catch(n){console.error("Failed to initialize Supabase:",n),this.supabaseConnectionFailed=!0}}async initializePayment(){if(!this.config.payment)return;const n=this.config.payment?.find(n=>"dodo"===n.provider);if(!n)return this.paymentConnectionFailed=!0,void(this.config.options?.debug&&console.warn("No dodo payment provider configured"));const t=n.productId;if(!t||!t.startsWith("pdt_")||t.length<10)return this.paymentConnectionFailed=!0,void(this.config.options?.debug&&console.warn("Invalid payment product ID format:",t));this.paymentConnectionFailed=!1}async initializeFingerprint(){try{this.userFingerprint=await this.generateFingerprint()}catch(n){this.userFingerprint=crypto.randomUUID?crypto.randomUUID():String(Date.now()),this.config.options?.debug&&console.warn("Fingerprint generation failed, using fallback:",n)}}async generateFingerprint(){const n=`${navigator.userAgent||""}||${Intl.DateTimeFormat().resolvedOptions().timeZone||""}||${`${screen.width}x${screen.height}`}||${navigator.language||""}`,t=(new TextEncoder).encode(n),e=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(e)).map(n=>n.toString(16).padStart(2,"0")).join("")}async setupAuthListener(){this.supabase.auth.onAuthStateChange(async(n,t)=>{const e=this.currentUser;this.currentUser=t?.user||null;let a=!1;if(this.currentUser){const n=!e||e.email!==this.currentUser.email,t=!this.cachedPremiumStatus||Date.now()-this.premiumStatusCacheTime>this.STATUS_CACHE_DURATION;n||t?(a=await this.checkPremiumStatus(this.currentUser.email),this.cachedPremiumStatus=a,this.premiumStatusCacheTime=Date.now()):(a=this.cachedPremiumStatus,console.log(`🔍 [CACHE] Using cached premium status from auth listener: ${a}`))}else this.cachedPremiumStatus=null,this.premiumStatusCacheTime=0;const o="true"===new URLSearchParams(window.location.search).get("upgrade");if("SIGNED_IN"===n&&!e&&this.currentUser&&this.config.payment&&o)if(a){window.history.replaceState({},document.title,window.location.pathname);const n=document.getElementById("download-limiter-paywall");n&&n.classList.remove("show")}else{const n=await this.createPayment();n?.checkout_url&&(window.history.replaceState({},document.title,window.location.pathname),window.location.href=n.checkout_url)}this.emit("authChanged",{user:this.currentUser,isPremium:a})});const{data:{user:n}}=await this.supabase.auth.getUser();this.currentUser=n}async getDownloadCount(){if(!this.userFingerprint)return 0;try{const n=await fetch(`${this.config.supabase.url}/functions/v1/check-download-limit`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.supabase.anonKey}`},body:JSON.stringify({fingerprint:this.userFingerprint,security_key:this.config.security_key,action:"check"})});if(n.ok){const t=await n.json();return this.supabaseConnectionFailed=!1,t.current_count||0}if(401===n.status||403===n.status)try{const t=await n.json(),e=t.error||t.message||"Authentication failed";e.toLowerCase().includes("security key")?(console.error(`❌ Security Key Validation Failed [check-download-limit]: ${e}`),console.error("💡 Backend Function: check-download-limit"),console.error("💡 Please check your security_key in the MoneyBar configuration"),this.securityKeyFailed=!0,this.supabaseConnectionFailed=!0):(console.warn(`Supabase authentication failed [check-download-limit]: ${n.status} - ${e}`),this.supabaseConnectionFailed=!0)}catch(t){console.warn(`Supabase authentication failed: ${n.status} - Unable to parse error`),this.supabaseConnectionFailed=!0}}catch(n){this.supabaseConnectionFailed=!0,this.config.options?.debug&&console.warn("Failed to get server usage count, connection failed:",n)}return 0}async incrementDownloadCount(){if(!this.userFingerprint)return 0;const n={fingerprint:this.userFingerprint,security_key:this.config.security_key,action:"increment"};try{const t=await fetch(`${this.config.supabase.url}/functions/v1/check-download-limit`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.supabase.anonKey}`},body:JSON.stringify(n)});if(this.config.options?.debug&&!t.ok){const n=await t.text();console.error("🔍 DEBUG: Error response:",n)}if(t.ok){const n=await t.json();return this.supabaseConnectionFailed=!1,n.new_count||0}if(401===t.status||403===t.status)try{const n=await t.json(),e=n.error||n.message||"Authentication failed";e.toLowerCase().includes("security key")?(console.error(`❌ Security Key Validation Failed [check-download-limit]: ${e}`),console.error("💡 Backend Function: check-download-limit"),console.error("💡 Please check your security_key in the MoneyBar configuration"),this.securityKeyFailed=!0,this.supabaseConnectionFailed=!0):(console.warn(`Supabase authentication failed [check-download-limit]: ${t.status} - ${e}`),this.supabaseConnectionFailed=!0)}catch(n){console.warn(`Supabase authentication failed: ${t.status} - Unable to parse error`),this.supabaseConnectionFailed=!0}}catch(n){this.supabaseConnectionFailed=!0,this.config.options?.debug&&console.warn("Failed to increment server count, connection failed:",n)}return 0}async checkPremiumStatus(n){try{const t=await fetch(`${this.config.supabase.url}/functions/v1/check-payment-status`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.supabase.anonKey}`},body:JSON.stringify({email:n,app_id:this.config.appId})});if(t.ok){return"completed"===(await t.json()).status}}catch(n){this.config.options?.debug&&console.warn("Failed to check premium status:",n)}return!1}getLocalDownloadCount(){const n=`download_count_${this.config.appId}`,t=parseInt(localStorage.getItem(n)||"0",10);return isNaN(t)?0:t}incrementLocalDownloadCount(){const n=`download_count_${this.config.appId}`,t=this.getLocalDownloadCount()+1;return localStorage.setItem(n,String(t)),t}handleError(n){this.config.options?.debug&&console.error("Lead Fast Error:",n),this.config.callbacks?.onError?this.config.callbacks.onError(n):console.error("Lead Fast:",n.message),this.emit("error",n)}async checkPaymentStatus(){"success"===new URLSearchParams(window.location.search).get("payment")&&setTimeout(async()=>{if(this.currentUser){await this.checkPremiumStatus(this.currentUser.email)?this.showSuccess("Payment Successful!","Welcome to premium! You now have unlimited usage."):this.showSuccess("Processing Payment","Payment is being processed. Please refresh in a few moments.")}else this.showSuccess("Processing Payment","Please wait while we verify your payment...");const n=window.location.href.split("?")[0];window.history.replaceState({},document.title,n)},2e3)}};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|