featurely-site-manager 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,873 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ SiteManager: () => SiteManager,
24
+ default: () => index_default
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var SiteManager = class {
28
+ constructor(config) {
29
+ this.siteConfig = null;
30
+ this.pollIntervalId = null;
31
+ this.messageContainers = /* @__PURE__ */ new Map();
32
+ this.dismissedMessages = /* @__PURE__ */ new Set();
33
+ this.featureFlagBuckets = /* @__PURE__ */ new Map();
34
+ // Consistent bucketing for percentage rollouts
35
+ this.analyticsQueue = [];
36
+ this.analyticsFlushIntervalId = null;
37
+ this.sessionId = this.generateSessionId();
38
+ var _a, _b, _c;
39
+ if (!config.apiKey) {
40
+ throw new Error("Featurely Site Manager: apiKey is required");
41
+ }
42
+ if (!config.projectId) {
43
+ throw new Error("Featurely Site Manager: projectId is required");
44
+ }
45
+ this.config = {
46
+ apiKey: config.apiKey,
47
+ projectId: config.projectId,
48
+ apiUrl: config.apiUrl || "https://featurely.com",
49
+ pollInterval: (_a = config.pollInterval) != null ? _a : 6e4,
50
+ userEmail: config.userEmail,
51
+ userId: config.userId,
52
+ bypassCheck: config.bypassCheck,
53
+ onMaintenanceEnabled: config.onMaintenanceEnabled,
54
+ onMaintenanceDisabled: config.onMaintenanceDisabled,
55
+ onMessageReceived: config.onMessageReceived,
56
+ onMessageDismissed: config.onMessageDismissed,
57
+ onFeatureFlagsUpdated: config.onFeatureFlagsUpdated,
58
+ onError: config.onError,
59
+ enableAnalytics: (_b = config.enableAnalytics) != null ? _b : true,
60
+ analyticsFlushInterval: (_c = config.analyticsFlushInterval) != null ? _c : 6e4
61
+ };
62
+ this.loadDismissedMessages();
63
+ }
64
+ /**
65
+ * Initialize and start the site manager
66
+ */
67
+ async init() {
68
+ if (typeof window === "undefined" || typeof document === "undefined") {
69
+ console.warn(
70
+ "Featurely Site Manager: Can only be initialized in a browser environment"
71
+ );
72
+ return;
73
+ }
74
+ await this.fetchConfig();
75
+ this.startPolling();
76
+ if (this.config.enableAnalytics) {
77
+ this.startAnalyticsFlushing();
78
+ }
79
+ this.injectStyles();
80
+ }
81
+ /**
82
+ * Stop the site manager and clean up
83
+ */
84
+ destroy() {
85
+ this.stopPolling();
86
+ this.stopAnalyticsFlushing();
87
+ this.flushAnalytics();
88
+ this.clearMessages();
89
+ }
90
+ /**
91
+ * Update user email for whitelist checks
92
+ */
93
+ setUser(email, userId) {
94
+ var _a;
95
+ this.config.userEmail = email;
96
+ if (userId) {
97
+ this.config.userId = userId;
98
+ }
99
+ if ((_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled) {
100
+ this.checkMaintenanceMode();
101
+ }
102
+ }
103
+ /**
104
+ * Check if a feature flag is enabled for the current user
105
+ */
106
+ isFeatureEnabled(flagKey) {
107
+ var _a;
108
+ if (!((_a = this.siteConfig) == null ? void 0 : _a.featureFlags)) {
109
+ return false;
110
+ }
111
+ const flag = this.siteConfig.featureFlags.find((f) => f.key === flagKey);
112
+ if (!flag || !flag.enabled) {
113
+ return false;
114
+ }
115
+ const isEnabled = this.evaluateFeatureFlag(flag);
116
+ const trackKey = `feature_flag_tracked_${flagKey}`;
117
+ if (typeof sessionStorage !== "undefined" && !sessionStorage.getItem(trackKey)) {
118
+ this.trackEvent("feature_flag_evaluated", {
119
+ flagKey,
120
+ enabled: isEnabled
121
+ });
122
+ sessionStorage.setItem(trackKey, "1");
123
+ }
124
+ return isEnabled;
125
+ }
126
+ /**
127
+ * Get the variant for a feature flag (for A/B testing)
128
+ */
129
+ getFeatureVariant(flagKey) {
130
+ var _a;
131
+ if (!((_a = this.siteConfig) == null ? void 0 : _a.featureFlags)) {
132
+ return null;
133
+ }
134
+ const flag = this.siteConfig.featureFlags.find((f) => f.key === flagKey);
135
+ if (!flag || !flag.enabled || !flag.variants || flag.variants.length === 0) {
136
+ return null;
137
+ }
138
+ if (!this.evaluateFeatureFlag(flag)) {
139
+ return null;
140
+ }
141
+ const cacheKey = `variant_${flagKey}`;
142
+ const cached = localStorage.getItem(cacheKey);
143
+ if (cached) {
144
+ return cached;
145
+ }
146
+ const bucket = this.getUserBucket(flagKey);
147
+ let cumulative = 0;
148
+ for (const variant of flag.variants) {
149
+ cumulative += variant.weight;
150
+ if (bucket < cumulative) {
151
+ localStorage.setItem(cacheKey, variant.key);
152
+ return variant.key;
153
+ }
154
+ }
155
+ const defaultVariant = flag.defaultVariant || flag.variants[0].key;
156
+ localStorage.setItem(cacheKey, defaultVariant);
157
+ return defaultVariant;
158
+ }
159
+ /**
160
+ * Get all feature flags
161
+ */
162
+ getAllFeatureFlags() {
163
+ var _a;
164
+ return ((_a = this.siteConfig) == null ? void 0 : _a.featureFlags) || [];
165
+ }
166
+ /**
167
+ * Get all enabled feature flags for the current user
168
+ */
169
+ getEnabledFeatures() {
170
+ var _a;
171
+ if (!((_a = this.siteConfig) == null ? void 0 : _a.featureFlags)) {
172
+ return [];
173
+ }
174
+ return this.siteConfig.featureFlags.filter((flag) => flag.enabled && this.evaluateFeatureFlag(flag)).map((flag) => flag.key);
175
+ }
176
+ /**
177
+ * Manually refresh configuration
178
+ */
179
+ async refresh() {
180
+ await this.fetchConfig();
181
+ }
182
+ /**
183
+ * Track a custom analytics event
184
+ * @param eventName - Name of the event (e.g., 'button_clicked', 'feature_used')
185
+ * @param properties - Optional event properties (e.g., { button: 'signup', page: '/home' })
186
+ */
187
+ trackEvent(eventName, properties) {
188
+ if (!this.config.enableAnalytics) {
189
+ return;
190
+ }
191
+ this.analyticsQueue.push({
192
+ eventName,
193
+ properties,
194
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
195
+ });
196
+ if (this.analyticsQueue.length >= 10) {
197
+ this.flushAnalytics();
198
+ }
199
+ }
200
+ // ============================================================================
201
+ // Configuration Fetching
202
+ // ============================================================================
203
+ async fetchConfig() {
204
+ var _a, _b;
205
+ try {
206
+ const response = await fetch(
207
+ `${this.config.apiUrl}/api/public/v1/site-config?projectId=${this.config.projectId}`,
208
+ {
209
+ headers: {
210
+ "X-API-Key": this.config.apiKey
211
+ }
212
+ }
213
+ );
214
+ if (!response.ok) {
215
+ throw new Error(`Failed to fetch site configuration: ${response.statusText}`);
216
+ }
217
+ const newConfig = await response.json();
218
+ const configChanged = JSON.stringify(newConfig) !== JSON.stringify(this.siteConfig);
219
+ if (configChanged) {
220
+ const wasMaintenanceEnabled = (_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled;
221
+ const oldFeatureFlags = ((_b = this.siteConfig) == null ? void 0 : _b.featureFlags) || [];
222
+ this.siteConfig = newConfig;
223
+ if (newConfig.maintenance.enabled && !wasMaintenanceEnabled) {
224
+ this.enableMaintenanceMode();
225
+ } else if (!newConfig.maintenance.enabled && wasMaintenanceEnabled) {
226
+ this.disableMaintenanceMode();
227
+ } else if (newConfig.maintenance.enabled) {
228
+ this.checkMaintenanceMode();
229
+ }
230
+ if (JSON.stringify(newConfig.featureFlags) !== JSON.stringify(oldFeatureFlags)) {
231
+ if (this.config.onFeatureFlagsUpdated) {
232
+ this.config.onFeatureFlagsUpdated(newConfig.featureFlags);
233
+ }
234
+ }
235
+ this.updateMessages();
236
+ }
237
+ } catch (error) {
238
+ console.error("Featurely Site Manager: Failed to fetch configuration", error);
239
+ if (this.config.onError && error instanceof Error) {
240
+ this.config.onError(error);
241
+ }
242
+ }
243
+ }
244
+ startPolling() {
245
+ if (this.pollIntervalId) return;
246
+ this.pollIntervalId = setInterval(() => {
247
+ this.fetchConfig();
248
+ }, this.config.pollInterval);
249
+ }
250
+ stopPolling() {
251
+ if (this.pollIntervalId) {
252
+ clearInterval(this.pollIntervalId);
253
+ this.pollIntervalId = null;
254
+ }
255
+ }
256
+ // ============================================================================
257
+ // Analytics
258
+ // ============================================================================
259
+ startAnalyticsFlushing() {
260
+ if (this.analyticsFlushIntervalId) return;
261
+ this.analyticsFlushIntervalId = setInterval(() => {
262
+ this.flushAnalytics();
263
+ }, this.config.analyticsFlushInterval);
264
+ }
265
+ stopAnalyticsFlushing() {
266
+ if (this.analyticsFlushIntervalId) {
267
+ clearInterval(this.analyticsFlushIntervalId);
268
+ this.analyticsFlushIntervalId = null;
269
+ }
270
+ }
271
+ async flushAnalytics() {
272
+ if (this.analyticsQueue.length === 0) {
273
+ return;
274
+ }
275
+ const eventsToSend = [...this.analyticsQueue];
276
+ this.analyticsQueue = [];
277
+ for (const event of eventsToSend) {
278
+ try {
279
+ await fetch(
280
+ `${this.config.apiUrl}/api/projects/${this.config.projectId}/analytics/events`,
281
+ {
282
+ method: "POST",
283
+ headers: {
284
+ "Content-Type": "application/json"
285
+ },
286
+ body: JSON.stringify({
287
+ eventName: event.eventName,
288
+ properties: event.properties,
289
+ userId: this.config.userId,
290
+ sessionId: this.sessionId,
291
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
292
+ platform: typeof navigator !== "undefined" ? navigator.platform : void 0
293
+ })
294
+ }
295
+ );
296
+ } catch (error) {
297
+ console.error("Failed to send analytics event:", error);
298
+ }
299
+ }
300
+ }
301
+ generateSessionId() {
302
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
303
+ }
304
+ // ============================================================================
305
+ // Feature Flags
306
+ // ============================================================================
307
+ /**
308
+ * Evaluate if a feature flag should be enabled for the current user
309
+ */
310
+ evaluateFeatureFlag(flag) {
311
+ if (!flag.enabled) {
312
+ return false;
313
+ }
314
+ const userEmail = this.config.userEmail;
315
+ if (flag.excludeEmails && userEmail && flag.excludeEmails.includes(userEmail)) {
316
+ return false;
317
+ }
318
+ if (flag.targetEmails && flag.targetEmails.length > 0) {
319
+ if (!userEmail || !flag.targetEmails.includes(userEmail)) {
320
+ return false;
321
+ }
322
+ return true;
323
+ }
324
+ if (flag.rolloutPercentage !== void 0 && flag.rolloutPercentage < 100) {
325
+ const bucket = this.getUserBucket(flag.key);
326
+ return bucket < flag.rolloutPercentage;
327
+ }
328
+ return true;
329
+ }
330
+ /**
331
+ * Get consistent bucket (0-99) for a user+flag combination
332
+ * This ensures the same user always gets the same result for percentage rollouts
333
+ */
334
+ getUserBucket(flagKey) {
335
+ if (this.featureFlagBuckets.has(flagKey)) {
336
+ return this.featureFlagBuckets.get(flagKey);
337
+ }
338
+ const identifier = this.config.userId || this.config.userEmail || this.getAnonymousId();
339
+ const hash = this.simpleHash(`${identifier}:${flagKey}`);
340
+ const bucket = hash % 100;
341
+ this.featureFlagBuckets.set(flagKey, bucket);
342
+ return bucket;
343
+ }
344
+ /**
345
+ * Simple hash function for consistent bucketing
346
+ */
347
+ simpleHash(str) {
348
+ let hash = 0;
349
+ for (let i = 0; i < str.length; i++) {
350
+ const char = str.charCodeAt(i);
351
+ hash = (hash << 5) - hash + char;
352
+ hash = hash & hash;
353
+ }
354
+ return Math.abs(hash);
355
+ }
356
+ /**
357
+ * Get or create anonymous user ID for bucketing
358
+ */
359
+ getAnonymousId() {
360
+ const storageKey = "featurely_anonymous_id";
361
+ let id = localStorage.getItem(storageKey);
362
+ if (!id) {
363
+ id = `anon_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
364
+ localStorage.setItem(storageKey, id);
365
+ }
366
+ return id;
367
+ }
368
+ // ============================================================================
369
+ // Maintenance Mode
370
+ // ============================================================================
371
+ checkMaintenanceMode() {
372
+ var _a;
373
+ if (!((_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled)) return;
374
+ if (this.shouldBypassMaintenance()) {
375
+ return;
376
+ }
377
+ this.showMaintenancePage();
378
+ }
379
+ enableMaintenanceMode() {
380
+ if (!this.siteConfig) return;
381
+ if (this.shouldBypassMaintenance()) {
382
+ return;
383
+ }
384
+ this.showMaintenancePage();
385
+ this.trackEvent("maintenance_enabled", {
386
+ maintenanceType: this.siteConfig.maintenance.type
387
+ });
388
+ if (this.config.onMaintenanceEnabled) {
389
+ this.config.onMaintenanceEnabled(this.siteConfig.maintenance);
390
+ }
391
+ }
392
+ disableMaintenanceMode() {
393
+ this.hideMaintenancePage();
394
+ this.trackEvent("maintenance_disabled");
395
+ if (this.config.onMaintenanceDisabled) {
396
+ this.config.onMaintenanceDisabled();
397
+ }
398
+ }
399
+ shouldBypassMaintenance() {
400
+ if (!this.siteConfig) return false;
401
+ const whitelist = this.siteConfig.maintenance.whitelist;
402
+ if (this.config.bypassCheck && this.config.bypassCheck()) {
403
+ return true;
404
+ }
405
+ if (whitelist.localStorageKeys && whitelist.localStorageKeys.length > 0) {
406
+ const hasKey = whitelist.localStorageKeys.some((key) => {
407
+ try {
408
+ return localStorage.getItem(key) !== null;
409
+ } catch {
410
+ return false;
411
+ }
412
+ });
413
+ if (hasKey) return true;
414
+ }
415
+ if (whitelist.emails && this.config.userEmail) {
416
+ if (whitelist.emails.includes(this.config.userEmail)) {
417
+ return true;
418
+ }
419
+ }
420
+ return false;
421
+ }
422
+ showMaintenancePage() {
423
+ if (!this.siteConfig) return;
424
+ const config = this.siteConfig.maintenance;
425
+ const overlay = document.createElement("div");
426
+ overlay.id = "featurely-maintenance-overlay";
427
+ overlay.className = "featurely-maintenance-overlay";
428
+ if (config.type === "custom" && config.customHtml) {
429
+ overlay.innerHTML = config.customHtml;
430
+ } else {
431
+ overlay.innerHTML = this.getDefaultMaintenanceHtml(config);
432
+ }
433
+ document.body.appendChild(overlay);
434
+ }
435
+ hideMaintenancePage() {
436
+ const overlay = document.getElementById("featurely-maintenance-overlay");
437
+ if (overlay) {
438
+ overlay.remove();
439
+ }
440
+ }
441
+ getDefaultMaintenanceHtml(config) {
442
+ const restoration = config.expectedRestoration ? new Date(config.expectedRestoration).toLocaleString() : "soon";
443
+ const statusLink = config.showStatusLink && config.statusPageUrl ? `<p><a href="${config.statusPageUrl}" target="_blank" rel="noopener noreferrer" class="featurely-status-link">View Status Page \u2192</a></p>` : "";
444
+ return `
445
+ <div class="featurely-maintenance-content">
446
+ <div class="featurely-maintenance-icon">\u{1F527}</div>
447
+ <h1>We'll be back soon!</h1>
448
+ <p>We're performing scheduled maintenance.</p>
449
+ <p class="featurely-maintenance-time">Expected restoration: <strong>${restoration}</strong></p>
450
+ ${statusLink}
451
+ <p class="featurely-maintenance-footer">Thank you for your patience.</p>
452
+ </div>
453
+ `;
454
+ }
455
+ // ============================================================================
456
+ // Status Messages
457
+ // ============================================================================
458
+ updateMessages() {
459
+ if (!this.siteConfig) return;
460
+ const currentTime = /* @__PURE__ */ new Date();
461
+ const currentPath = window.location.pathname;
462
+ const activeMessages = this.siteConfig.messages.filter((message) => {
463
+ if (this.dismissedMessages.has(message.id)) {
464
+ return false;
465
+ }
466
+ if (message.startsAt && new Date(message.startsAt) > currentTime) {
467
+ return false;
468
+ }
469
+ if (message.expiresAt && new Date(message.expiresAt) < currentTime) {
470
+ return false;
471
+ }
472
+ if (message.targetPages && message.targetPages.length > 0) {
473
+ const matches = message.targetPages.some((pattern) => {
474
+ const regex = new RegExp(pattern);
475
+ return regex.test(currentPath);
476
+ });
477
+ if (!matches) return false;
478
+ }
479
+ return true;
480
+ });
481
+ this.messageContainers.forEach((container, id) => {
482
+ if (!activeMessages.find((m) => m.id === id)) {
483
+ container.remove();
484
+ this.messageContainers.delete(id);
485
+ }
486
+ });
487
+ activeMessages.forEach((message) => {
488
+ if (!this.messageContainers.has(message.id)) {
489
+ this.showMessage(message);
490
+ if (this.config.onMessageReceived) {
491
+ this.config.onMessageReceived(message);
492
+ }
493
+ }
494
+ });
495
+ }
496
+ showMessage(message) {
497
+ var _a;
498
+ const messageEl = document.createElement("div");
499
+ messageEl.className = `featurely-message featurely-message-${message.type} featurely-message-${message.position} featurely-message-${message.style}`;
500
+ messageEl.dataset.messageId = message.id;
501
+ const icon = message.icon || this.getDefaultIcon(message.type);
502
+ const ctaHtml = message.cta ? `<a href="${message.cta.url}" class="featurely-message-cta">${message.cta.text}</a>` : "";
503
+ const closeButton = message.dismissible ? `<button class="featurely-message-close" aria-label="Close message">&times;</button>` : "";
504
+ messageEl.innerHTML = `
505
+ <div class="featurely-message-content">
506
+ <div class="featurely-message-icon">${icon}</div>
507
+ <div class="featurely-message-text">
508
+ <strong class="featurely-message-title">${message.title}</strong>
509
+ <span class="featurely-message-body">${message.message}</span>
510
+ </div>
511
+ ${ctaHtml}
512
+ ${closeButton}
513
+ </div>
514
+ `;
515
+ if (message.dismissible) {
516
+ const closeBtn = messageEl.querySelector(".featurely-message-close");
517
+ closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => this.dismissMessage(message.id));
518
+ }
519
+ if ((_a = message.cta) == null ? void 0 : _a.action) {
520
+ const ctaEl = messageEl.querySelector(".featurely-message-cta");
521
+ ctaEl == null ? void 0 : ctaEl.addEventListener("click", (e) => {
522
+ var _a2;
523
+ e.preventDefault();
524
+ if ((_a2 = message.cta) == null ? void 0 : _a2.action) {
525
+ this.handleMessageAction(message.cta.action, message);
526
+ }
527
+ });
528
+ }
529
+ document.body.appendChild(messageEl);
530
+ setTimeout(() => messageEl.classList.add("featurely-message-show"), 10);
531
+ this.messageContainers.set(message.id, messageEl);
532
+ this.trackEvent("message_shown", {
533
+ messageId: message.id,
534
+ messageType: message.type,
535
+ messagePosition: message.position,
536
+ messageStyle: message.style
537
+ });
538
+ if (message.style === "toast" && message.dismissible) {
539
+ setTimeout(() => this.dismissMessage(message.id), 5e3);
540
+ }
541
+ }
542
+ dismissMessage(messageId) {
543
+ var _a, _b;
544
+ const messageEl = this.messageContainers.get(messageId);
545
+ if (!messageEl) return;
546
+ messageEl.classList.remove("featurely-message-show");
547
+ setTimeout(() => {
548
+ messageEl.remove();
549
+ this.messageContainers.delete(messageId);
550
+ }, 300);
551
+ this.dismissedMessages.add(messageId);
552
+ this.saveDismissedMessages();
553
+ this.trackEvent("message_dismissed", {
554
+ messageId,
555
+ messageType: ((_b = (_a = this.siteConfig) == null ? void 0 : _a.messages.find((m) => m.id === messageId)) == null ? void 0 : _b.type) || "unknown"
556
+ });
557
+ if (this.config.onMessageDismissed) {
558
+ this.config.onMessageDismissed(messageId);
559
+ }
560
+ }
561
+ clearMessages() {
562
+ this.messageContainers.forEach((el) => el.remove());
563
+ this.messageContainers.clear();
564
+ }
565
+ handleMessageAction(action, message) {
566
+ var _a;
567
+ console.log("Message action triggered:", action, message);
568
+ if ((_a = message.cta) == null ? void 0 : _a.url) {
569
+ window.location.href = message.cta.url;
570
+ }
571
+ }
572
+ getDefaultIcon(type) {
573
+ const icons = {
574
+ info: "\u2139\uFE0F",
575
+ warning: "\u26A0\uFE0F",
576
+ error: "\u274C",
577
+ success: "\u2705"
578
+ };
579
+ return icons[type];
580
+ }
581
+ // ============================================================================
582
+ // Persistence
583
+ // ============================================================================
584
+ loadDismissedMessages() {
585
+ try {
586
+ const stored = localStorage.getItem("featurely_dismissed_messages");
587
+ if (stored) {
588
+ this.dismissedMessages = new Set(JSON.parse(stored));
589
+ }
590
+ } catch {
591
+ }
592
+ }
593
+ saveDismissedMessages() {
594
+ try {
595
+ localStorage.setItem(
596
+ "featurely_dismissed_messages",
597
+ JSON.stringify(Array.from(this.dismissedMessages))
598
+ );
599
+ } catch {
600
+ }
601
+ }
602
+ // ============================================================================
603
+ // Styles
604
+ // ============================================================================
605
+ injectStyles() {
606
+ if (document.getElementById("featurely-site-manager-styles")) {
607
+ return;
608
+ }
609
+ const style = document.createElement("style");
610
+ style.id = "featurely-site-manager-styles";
611
+ style.textContent = `
612
+ /* Maintenance Overlay */
613
+ .featurely-maintenance-overlay {
614
+ position: fixed;
615
+ top: 0;
616
+ left: 0;
617
+ width: 100%;
618
+ height: 100%;
619
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
620
+ z-index: 999999;
621
+ display: flex;
622
+ align-items: center;
623
+ justify-content: center;
624
+ color: white;
625
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
626
+ }
627
+
628
+ .featurely-maintenance-content {
629
+ text-align: center;
630
+ max-width: 600px;
631
+ padding: 40px;
632
+ background: rgba(255, 255, 255, 0.1);
633
+ border-radius: 16px;
634
+ backdrop-filter: blur(10px);
635
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
636
+ }
637
+
638
+ .featurely-maintenance-icon {
639
+ font-size: 64px;
640
+ margin-bottom: 20px;
641
+ }
642
+
643
+ .featurely-maintenance-content h1 {
644
+ font-size: 36px;
645
+ margin: 0 0 16px 0;
646
+ font-weight: 700;
647
+ }
648
+
649
+ .featurely-maintenance-content p {
650
+ font-size: 18px;
651
+ margin: 12px 0;
652
+ opacity: 0.9;
653
+ }
654
+
655
+ .featurely-maintenance-time {
656
+ margin: 24px 0;
657
+ padding: 16px;
658
+ background: rgba(255, 255, 255, 0.1);
659
+ border-radius: 8px;
660
+ }
661
+
662
+ .featurely-status-link {
663
+ display: inline-block;
664
+ margin: 20px 0;
665
+ padding: 12px 24px;
666
+ background: rgba(255, 255, 255, 0.2);
667
+ color: white;
668
+ text-decoration: none;
669
+ border-radius: 8px;
670
+ font-weight: 600;
671
+ transition: all 0.2s;
672
+ }
673
+
674
+ .featurely-status-link:hover {
675
+ background: rgba(255, 255, 255, 0.3);
676
+ transform: translateY(-2px);
677
+ }
678
+
679
+ .featurely-maintenance-footer {
680
+ margin-top: 32px;
681
+ font-size: 14px;
682
+ opacity: 0.7;
683
+ }
684
+
685
+ /* Status Messages */
686
+ .featurely-message {
687
+ position: fixed;
688
+ left: 0;
689
+ right: 0;
690
+ z-index: 999998;
691
+ padding: 16px 20px;
692
+ background: white;
693
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
694
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
695
+ opacity: 0;
696
+ transform: translateY(-20px);
697
+ transition: all 0.3s ease;
698
+ }
699
+
700
+ .featurely-message-show {
701
+ opacity: 1;
702
+ transform: translateY(0);
703
+ }
704
+
705
+ .featurely-message-top {
706
+ top: 0;
707
+ }
708
+
709
+ .featurely-message-bottom {
710
+ bottom: 0;
711
+ }
712
+
713
+ .featurely-message-toast {
714
+ left: auto;
715
+ right: 20px;
716
+ max-width: 400px;
717
+ border-radius: 8px;
718
+ }
719
+
720
+ .featurely-message-toast.featurely-message-top {
721
+ top: 20px;
722
+ }
723
+
724
+ .featurely-message-toast.featurely-message-bottom {
725
+ bottom: 20px;
726
+ }
727
+
728
+ .featurely-message-content {
729
+ display: flex;
730
+ align-items: center;
731
+ gap: 12px;
732
+ }
733
+
734
+ .featurely-message-icon {
735
+ font-size: 24px;
736
+ flex-shrink: 0;
737
+ }
738
+
739
+ .featurely-message-text {
740
+ flex: 1;
741
+ display: flex;
742
+ flex-direction: column;
743
+ gap: 4px;
744
+ }
745
+
746
+ .featurely-message-title {
747
+ font-size: 14px;
748
+ font-weight: 600;
749
+ display: block;
750
+ }
751
+
752
+ .featurely-message-body {
753
+ font-size: 14px;
754
+ opacity: 0.9;
755
+ }
756
+
757
+ .featurely-message-cta {
758
+ padding: 8px 16px;
759
+ border-radius: 6px;
760
+ text-decoration: none;
761
+ font-weight: 600;
762
+ font-size: 14px;
763
+ white-space: nowrap;
764
+ transition: all 0.2s;
765
+ }
766
+
767
+ .featurely-message-close {
768
+ background: none;
769
+ border: none;
770
+ font-size: 24px;
771
+ line-height: 1;
772
+ cursor: pointer;
773
+ padding: 0;
774
+ width: 24px;
775
+ height: 24px;
776
+ display: flex;
777
+ align-items: center;
778
+ justify-content: center;
779
+ opacity: 0.5;
780
+ transition: opacity 0.2s;
781
+ flex-shrink: 0;
782
+ }
783
+
784
+ .featurely-message-close:hover {
785
+ opacity: 1;
786
+ }
787
+
788
+ /* Message Types */
789
+ .featurely-message-info {
790
+ background: #e3f2fd;
791
+ color: #1976d2;
792
+ }
793
+
794
+ .featurely-message-info .featurely-message-cta {
795
+ background: #1976d2;
796
+ color: white;
797
+ }
798
+
799
+ .featurely-message-info .featurely-message-cta:hover {
800
+ background: #1565c0;
801
+ }
802
+
803
+ .featurely-message-warning {
804
+ background: #fff3e0;
805
+ color: #f57c00;
806
+ }
807
+
808
+ .featurely-message-warning .featurely-message-cta {
809
+ background: #f57c00;
810
+ color: white;
811
+ }
812
+
813
+ .featurely-message-warning .featurely-message-cta:hover {
814
+ background: #ef6c00;
815
+ }
816
+
817
+ .featurely-message-error {
818
+ background: #ffebee;
819
+ color: #c62828;
820
+ }
821
+
822
+ .featurely-message-error .featurely-message-cta {
823
+ background: #c62828;
824
+ color: white;
825
+ }
826
+
827
+ .featurely-message-error .featurely-message-cta:hover {
828
+ background: #b71c1c;
829
+ }
830
+
831
+ .featurely-message-success {
832
+ background: #e8f5e9;
833
+ color: #2e7d32;
834
+ }
835
+
836
+ .featurely-message-success .featurely-message-cta {
837
+ background: #2e7d32;
838
+ color: white;
839
+ }
840
+
841
+ .featurely-message-success .featurely-message-cta:hover {
842
+ background: #1b5e20;
843
+ }
844
+
845
+ /* Mobile Responsive */
846
+ @media (max-width: 640px) {
847
+ .featurely-message-toast {
848
+ left: 10px;
849
+ right: 10px;
850
+ max-width: none;
851
+ }
852
+
853
+ .featurely-maintenance-content {
854
+ padding: 20px;
855
+ }
856
+
857
+ .featurely-maintenance-content h1 {
858
+ font-size: 28px;
859
+ }
860
+
861
+ .featurely-maintenance-content p {
862
+ font-size: 16px;
863
+ }
864
+ }
865
+ `;
866
+ document.head.appendChild(style);
867
+ }
868
+ };
869
+ var index_default = SiteManager;
870
+ // Annotate the CommonJS export names for ESM import in node:
871
+ 0 && (module.exports = {
872
+ SiteManager
873
+ });