cyclecad 2.1.0 → 3.1.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/BILLING-IMPLEMENTATION-SUMMARY.md +425 -0
- package/BILLING-INDEX.md +293 -0
- package/BILLING-INTEGRATION-GUIDE.md +414 -0
- package/COLLABORATION-INDEX.md +440 -0
- package/COLLABORATION-SYSTEM-SUMMARY.md +548 -0
- package/DELIVERABLES.txt +296 -445
- package/DOCKER-BUILD-MANIFEST.txt +483 -0
- package/DOCKER-FILES-REFERENCE.md +440 -0
- package/DOCKER-INFRASTRUCTURE.md +475 -0
- package/DOCKER-README.md +435 -0
- package/Dockerfile +33 -55
- package/ENHANCEMENT_COMPLETION_REPORT.md +383 -0
- package/ENHANCEMENT_SUMMARY.txt +308 -0
- package/FEATURE_INVENTORY.md +235 -0
- package/FUSION360_FEATURES_SUMMARY.md +452 -0
- package/FUSION360_PARITY_ENHANCEMENTS.md +461 -0
- package/FUSION360_PARITY_SUMMARY.md +520 -0
- package/FUSION360_QUICK_REFERENCE.md +351 -0
- package/MODULE_API_REFERENCE.md +712 -0
- package/MODULE_INVENTORY.txt +264 -0
- package/PWA-FILES-CREATED.txt +350 -0
- package/QUICK-START-TESTING.md +126 -0
- package/STEP-IMPORT-QUICKSTART.md +347 -0
- package/STEP-IMPORT-SYSTEM-SUMMARY.md +502 -0
- package/app/css/mobile.css +1074 -0
- package/app/icons/generate-icons.js +203 -0
- package/app/index.html +1342 -5031
- package/app/js/app.js +1312 -514
- package/app/js/billing-ui.js +990 -0
- package/app/js/brep-kernel.js +933 -981
- package/app/js/collab-client.js +750 -0
- package/app/js/mobile-nav.js +623 -0
- package/app/js/mobile-toolbar.js +476 -0
- package/app/js/modules/animation-module.js +497 -3
- package/app/js/modules/billing-module.js +724 -0
- package/app/js/modules/cam-module.js +507 -2
- package/app/js/modules/collaboration-module.js +513 -0
- package/app/js/modules/constraint-module.js +1266 -0
- package/app/js/modules/data-module.js +544 -1146
- package/app/js/modules/formats-module.js +438 -738
- package/app/js/modules/inspection-module.js +393 -0
- package/app/js/modules/mesh-module-enhanced.js +880 -0
- package/app/js/modules/plugin-module.js +597 -0
- package/app/js/modules/rendering-module.js +460 -0
- package/app/js/modules/scripting-module.js +593 -475
- package/app/js/modules/sketch-module.js +998 -2
- package/app/js/modules/step-module-enhanced.js +938 -0
- package/app/js/modules/surface-module.js +312 -0
- package/app/js/modules/version-module.js +420 -0
- package/app/js/offline-manager.js +705 -0
- package/app/js/responsive-init.js +360 -0
- package/app/js/touch-handler.js +429 -0
- package/app/manifest.json +211 -0
- package/app/offline.html +508 -0
- package/app/sw.js +571 -0
- package/app/tests/billing-tests.html +779 -0
- package/app/tests/brep-tests.html +980 -0
- package/app/tests/collab-tests.html +743 -0
- package/app/tests/mobile-tests.html +1299 -0
- package/app/tests/pwa-tests.html +1134 -0
- package/app/tests/step-tests.html +1042 -0
- package/app/tests/test-agent-v3.html +719 -0
- package/cycleCAD-Architecture-v2.pptx +0 -0
- package/docker-compose.yml +225 -0
- package/docs/BILLING-HELP.json +260 -0
- package/docs/BILLING-README.md +639 -0
- package/docs/BILLING-TUTORIAL.md +736 -0
- package/docs/BREP-HELP.json +326 -0
- package/docs/BREP-TUTORIAL.md +802 -0
- package/docs/COLLABORATION-HELP.json +228 -0
- package/docs/COLLABORATION-TUTORIAL.md +818 -0
- package/docs/DOCKER-HELP.json +224 -0
- package/docs/DOCKER-TUTORIAL.md +974 -0
- package/docs/MOBILE-HELP.json +243 -0
- package/docs/MOBILE-RESPONSIVE-README.md +378 -0
- package/docs/MOBILE-TUTORIAL.md +747 -0
- package/docs/PWA-HELP.json +228 -0
- package/docs/PWA-README.md +662 -0
- package/docs/PWA-TUTORIAL.md +757 -0
- package/docs/STEP-HELP.json +481 -0
- package/docs/STEP-IMPORT-TUTORIAL.md +824 -0
- package/docs/TESTING-GUIDE.md +528 -0
- package/docs/TESTING-HELP.json +182 -0
- package/fusion-vs-cyclecad.html +1771 -0
- package/nginx.conf +237 -0
- package/package.json +1 -1
- package/server/Dockerfile.converter +51 -0
- package/server/Dockerfile.signaling +28 -0
- package/server/billing-server.js +487 -0
- package/server/converter-enhanced.py +528 -0
- package/server/requirements-converter.txt +29 -0
- package/server/signaling-server.js +801 -0
- package/tests/docker-tests.sh +389 -0
- package/~$cycleCAD-Architecture-v2.pptx +0 -0
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing UI Module - User interface for Stripe billing
|
|
3
|
+
* Provides pricing page, upgrade modals, usage dashboard, and feature gates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BillingUI = {
|
|
7
|
+
/**
|
|
8
|
+
* Show pricing comparison page
|
|
9
|
+
* 3-column layout with Free, Pro, and Enterprise tiers
|
|
10
|
+
*/
|
|
11
|
+
showPricingPage() {
|
|
12
|
+
const html = `
|
|
13
|
+
<div class="billing-pricing-page">
|
|
14
|
+
<div class="pricing-header">
|
|
15
|
+
<h2>Simple, Transparent Pricing</h2>
|
|
16
|
+
<p>Choose the plan that's right for you</p>
|
|
17
|
+
|
|
18
|
+
<div class="billing-toggle">
|
|
19
|
+
<label>
|
|
20
|
+
<input type="radio" name="billing-cycle" value="monthly" checked onchange="BillingUI.updatePricing('monthly')">
|
|
21
|
+
Monthly
|
|
22
|
+
</label>
|
|
23
|
+
<label>
|
|
24
|
+
<input type="radio" name="billing-cycle" value="yearly" onchange="BillingUI.updatePricing('yearly')">
|
|
25
|
+
Yearly (Save 20%)
|
|
26
|
+
</label>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="pricing-grid">
|
|
31
|
+
${['free', 'pro', 'enterprise'].map(tierId => this.getPricingCard(tierId)).join('')}
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="pricing-faq">
|
|
35
|
+
<h3>Frequently Asked Questions</h3>
|
|
36
|
+
<div class="faq-item">
|
|
37
|
+
<h4>Can I try Pro before paying?</h4>
|
|
38
|
+
<p>Yes! Get 14 days free to try all Pro features. Cancel anytime before trial ends.</p>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="faq-item">
|
|
41
|
+
<h4>What's included in each tier?</h4>
|
|
42
|
+
<p>See the comparison table above. Free is perfect for learning. Pro for professionals. Enterprise for teams.</p>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="faq-item">
|
|
45
|
+
<h4>Can I change plans?</h4>
|
|
46
|
+
<p>Yes! Upgrade or downgrade anytime. Changes take effect at next billing cycle.</p>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="faq-item">
|
|
49
|
+
<h4>Do you offer discounts?</h4>
|
|
50
|
+
<p>Yes! Students (STUDENT20), nonprofits (NONPROFIT30), and volume discounts available.</p>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
id: 'pricing-page',
|
|
58
|
+
title: 'Pricing',
|
|
59
|
+
html,
|
|
60
|
+
styles: this.getPricingStyles()
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get pricing card HTML for a tier
|
|
66
|
+
*/
|
|
67
|
+
getPricingCard(tierId) {
|
|
68
|
+
const tier = window.BillingModule?.tiers?.[tierId];
|
|
69
|
+
if (!tier) return '';
|
|
70
|
+
|
|
71
|
+
const isCurrent = window.BillingModule?.state?.tier === tierId;
|
|
72
|
+
const isEnterprise = tierId === 'enterprise';
|
|
73
|
+
|
|
74
|
+
return `
|
|
75
|
+
<div class="pricing-card ${tierId} ${isCurrent ? 'current' : ''}">
|
|
76
|
+
${isCurrent ? '<div class="current-badge">Current Plan</div>' : ''}
|
|
77
|
+
<h3 class="tier-name">${tier.name}</h3>
|
|
78
|
+
<p class="tier-description">${tier.description}</p>
|
|
79
|
+
|
|
80
|
+
<div class="pricing-amount">
|
|
81
|
+
${tierId === 'free' ?
|
|
82
|
+
'<span class="price">€0</span><span class="period">/month</span>' :
|
|
83
|
+
'<span class="price">€' + (Math.round((tier.price || tier.priceYearly) / 100 * 10) / 10) + '</span><span class="period">/month</span>'
|
|
84
|
+
}
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<ul class="features-list">
|
|
88
|
+
${tier.features.map(f => `<li><span class="check">✓</span>${f}</li>`).join('')}
|
|
89
|
+
</ul>
|
|
90
|
+
|
|
91
|
+
<div class="pricing-actions">
|
|
92
|
+
${tierId === 'free' ?
|
|
93
|
+
'<button class="btn btn-secondary" disabled>Your Current Plan</button>' :
|
|
94
|
+
`<button class="btn btn-primary" onclick="window.BillingModule.startCheckout('${tierId}', 'monthly')">
|
|
95
|
+
${isCurrent ? 'Manage Plan' : 'Start Free Trial'}
|
|
96
|
+
</button>`
|
|
97
|
+
}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
`;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Show upgrade prompt modal when user hits limit
|
|
105
|
+
*/
|
|
106
|
+
showUpgradeModal(feature, context = '') {
|
|
107
|
+
const check = window.BillingModule?.checkLimit(feature);
|
|
108
|
+
|
|
109
|
+
const html = `
|
|
110
|
+
<div class="billing-modal-overlay" onclick="this.closest('.billing-modal-container')?.remove()">
|
|
111
|
+
<div class="billing-modal" onclick="event.stopPropagation()">
|
|
112
|
+
<button class="modal-close" onclick="this.closest('.billing-modal-container')?.remove()">✕</button>
|
|
113
|
+
|
|
114
|
+
<div class="modal-icon">📦</div>
|
|
115
|
+
<h3>You've Hit Your Limit</h3>
|
|
116
|
+
<p class="limit-message">${check?.message || 'Upgrade to continue using this feature'}</p>
|
|
117
|
+
${context ? `<p class="context-message">${context}</p>` : ''}
|
|
118
|
+
|
|
119
|
+
<div class="upgrade-options">
|
|
120
|
+
<button class="btn btn-option" onclick="window.BillingModule.startCheckout('pro', 'monthly')">
|
|
121
|
+
<div class="option-name">Pro</div>
|
|
122
|
+
<div class="option-price">€49/month</div>
|
|
123
|
+
<div class="option-features">Unlimited projects, 500 AI requests/day</div>
|
|
124
|
+
</button>
|
|
125
|
+
<button class="btn btn-option" onclick="window.BillingModule.startCheckout('enterprise', 'monthly')">
|
|
126
|
+
<div class="option-name">Enterprise</div>
|
|
127
|
+
<div class="option-price">€299/month</div>
|
|
128
|
+
<div class="option-features">Everything + custom branding, SSO, self-hosting</div>
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div class="modal-footer">
|
|
133
|
+
<button class="btn btn-text" onclick="this.closest('.billing-modal-container')?.remove()">Maybe later</button>
|
|
134
|
+
<a href="#pricing" class="btn btn-text">View all plans</a>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
`;
|
|
139
|
+
|
|
140
|
+
const container = document.createElement('div');
|
|
141
|
+
container.className = 'billing-modal-container';
|
|
142
|
+
container.innerHTML = html;
|
|
143
|
+
document.body.appendChild(container);
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Show usage dashboard with charts and warnings
|
|
148
|
+
*/
|
|
149
|
+
getUsageDashboard() {
|
|
150
|
+
const tier = window.BillingModule?.getCurrentTier();
|
|
151
|
+
const usage = window.BillingModule?.getUsage();
|
|
152
|
+
|
|
153
|
+
if (!tier || !usage) return '';
|
|
154
|
+
|
|
155
|
+
const usageItems = [
|
|
156
|
+
{ name: 'Projects', current: usage.projects, limit: tier.limits.projects, icon: '📁' },
|
|
157
|
+
{ name: 'Storage', current: usage.storageGB, limit: tier.limits.storageGB, icon: '💾', unit: 'GB' },
|
|
158
|
+
{ name: 'AI Requests/Day', current: usage.aiRequestsToday, limit: tier.limits.aiRequestsPerDay, icon: '🤖' },
|
|
159
|
+
{ name: 'Parts', current: usage.totalParts, limit: tier.limits.partsPerProject, icon: '⚙️' }
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
return `
|
|
163
|
+
<div class="usage-dashboard">
|
|
164
|
+
<h3>Current Usage</h3>
|
|
165
|
+
|
|
166
|
+
${usageItems.map(item => {
|
|
167
|
+
const percentage = item.limit === Infinity ? 0 : Math.round((item.current / item.limit) * 100);
|
|
168
|
+
const warning = percentage >= 80;
|
|
169
|
+
const exceeded = percentage > 100;
|
|
170
|
+
|
|
171
|
+
return `
|
|
172
|
+
<div class="usage-item ${warning ? 'warning' : ''} ${exceeded ? 'exceeded' : ''}">
|
|
173
|
+
<div class="usage-header">
|
|
174
|
+
<span class="icon">${item.icon}</span>
|
|
175
|
+
<span class="name">${item.name}</span>
|
|
176
|
+
<span class="values">${item.current} ${item.unit || ''} / ${item.limit === Infinity ? '∞' : item.limit}</span>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="usage-bar">
|
|
179
|
+
<div class="bar-fill" style="width: ${Math.min(percentage, 100)}%; background: ${
|
|
180
|
+
exceeded ? '#F56565' : warning ? '#F6AD55' : '#48BB78'
|
|
181
|
+
}"></div>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="usage-percent">${percentage}% used</div>
|
|
184
|
+
${warning && item.limit !== Infinity ?
|
|
185
|
+
`<div class="usage-warning">Approaching limit - consider upgrading</div>` :
|
|
186
|
+
''
|
|
187
|
+
}
|
|
188
|
+
</div>
|
|
189
|
+
`;
|
|
190
|
+
}).join('')}
|
|
191
|
+
|
|
192
|
+
<div class="usage-actions">
|
|
193
|
+
<button class="btn btn-secondary" onclick="window.BillingModule.exportUsageCSV()">
|
|
194
|
+
Export as CSV
|
|
195
|
+
</button>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
`;
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Show subscription status card
|
|
203
|
+
*/
|
|
204
|
+
getSubscriptionStatus() {
|
|
205
|
+
const state = window.BillingModule?.state;
|
|
206
|
+
const tier = window.BillingModule?.getCurrentTier();
|
|
207
|
+
|
|
208
|
+
if (!state || !tier) return '';
|
|
209
|
+
|
|
210
|
+
const daysUntilRenewal = Math.ceil((state.currentPeriodEnd - Date.now()) / (1000 * 60 * 60 * 24));
|
|
211
|
+
const daysUntilTrialExpires = state.trialEndsAt ?
|
|
212
|
+
Math.ceil((state.trialEndsAt - Date.now()) / (1000 * 60 * 60 * 24)) : null;
|
|
213
|
+
|
|
214
|
+
return `
|
|
215
|
+
<div class="subscription-status">
|
|
216
|
+
<div class="status-card">
|
|
217
|
+
<h3>${tier.name} Plan</h3>
|
|
218
|
+
|
|
219
|
+
<div class="status-badge ${state.status}">
|
|
220
|
+
${state.status.replace('_', ' ').toUpperCase()}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div class="status-details">
|
|
224
|
+
${daysUntilTrialExpires !== null && daysUntilTrialExpires > 0 ? `
|
|
225
|
+
<div class="detail-item">
|
|
226
|
+
<span class="label">Trial Ends In:</span>
|
|
227
|
+
<span class="value">${daysUntilTrialExpires} days</span>
|
|
228
|
+
</div>
|
|
229
|
+
` : ''}
|
|
230
|
+
|
|
231
|
+
${state.currentPeriodEnd ? `
|
|
232
|
+
<div class="detail-item">
|
|
233
|
+
<span class="label">Next Billing Date:</span>
|
|
234
|
+
<span class="value">${new Date(state.currentPeriodEnd).toLocaleDateString()}</span>
|
|
235
|
+
</div>
|
|
236
|
+
` : ''}
|
|
237
|
+
|
|
238
|
+
<div class="detail-item">
|
|
239
|
+
<span class="label">Billing Cycle:</span>
|
|
240
|
+
<span class="value">${state.billingCycle === 'yearly' ? 'Yearly (20% discount)' : 'Monthly'}</span>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
${state.cancelAtPeriodEnd ? `
|
|
245
|
+
<div class="cancel-notice">
|
|
246
|
+
⚠️ Your subscription will be canceled on ${new Date(state.currentPeriodEnd).toLocaleDateString()}
|
|
247
|
+
</div>
|
|
248
|
+
` : ''}
|
|
249
|
+
|
|
250
|
+
<div class="status-actions">
|
|
251
|
+
<button class="btn btn-secondary" onclick="window.BillingModule.openCustomerPortal()">
|
|
252
|
+
Manage Subscription
|
|
253
|
+
</button>
|
|
254
|
+
${state.tier !== 'enterprise' ? `
|
|
255
|
+
<button class="btn btn-primary" onclick="window.BillingModule.showUpgradePrompt('storage')">
|
|
256
|
+
Upgrade Plan
|
|
257
|
+
</button>
|
|
258
|
+
` : ''}
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
`;
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Show trial banner at top of app
|
|
267
|
+
*/
|
|
268
|
+
getTrialBanner() {
|
|
269
|
+
const state = window.BillingModule?.state;
|
|
270
|
+
|
|
271
|
+
if (state?.status !== 'trialing' || !state?.trialEndsAt) return '';
|
|
272
|
+
|
|
273
|
+
const daysLeft = Math.ceil((state.trialEndsAt - Date.now()) / (1000 * 60 * 60 * 24));
|
|
274
|
+
const percentage = Math.max(0, Math.round((daysLeft / 14) * 100));
|
|
275
|
+
|
|
276
|
+
return `
|
|
277
|
+
<div class="trial-banner">
|
|
278
|
+
<div class="banner-content">
|
|
279
|
+
<span class="banner-icon">⏰</span>
|
|
280
|
+
<div class="banner-text">
|
|
281
|
+
<strong>Free Trial Active</strong>
|
|
282
|
+
<p>${daysLeft} days remaining - Your trial expires on ${new Date(state.trialEndsAt).toLocaleDateString()}</p>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="banner-progress">
|
|
286
|
+
<div class="progress-bar" style="width: ${percentage}%"></div>
|
|
287
|
+
</div>
|
|
288
|
+
<button class="banner-action" onclick="window.BillingModule.openCustomerPortal()">
|
|
289
|
+
Add Payment Method
|
|
290
|
+
</button>
|
|
291
|
+
</div>
|
|
292
|
+
`;
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Feature gate overlay (lock icon + upgrade prompt)
|
|
297
|
+
*/
|
|
298
|
+
getFeatureGateOverlay(featureName) {
|
|
299
|
+
const hasFeature = window.BillingModule?.hasFeature(featureName);
|
|
300
|
+
|
|
301
|
+
if (hasFeature) return '';
|
|
302
|
+
|
|
303
|
+
return `
|
|
304
|
+
<div class="feature-gate-overlay" onclick="window.BillingModule.showUpgradePrompt('${featureName}')">
|
|
305
|
+
<div class="gate-content">
|
|
306
|
+
<span class="gate-icon">🔒</span>
|
|
307
|
+
<span class="gate-text">Pro Feature</span>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
`;
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Payment method form
|
|
315
|
+
*/
|
|
316
|
+
getPaymentForm() {
|
|
317
|
+
return `
|
|
318
|
+
<div class="payment-form">
|
|
319
|
+
<h3>Payment Method</h3>
|
|
320
|
+
|
|
321
|
+
<div class="form-group">
|
|
322
|
+
<label>Cardholder Name</label>
|
|
323
|
+
<input type="text" placeholder="John Doe" class="form-input">
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
<div class="form-group">
|
|
327
|
+
<label>Card Number</label>
|
|
328
|
+
<input type="text" placeholder="4242 4242 4242 4242" class="form-input card-input">
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<div class="form-row">
|
|
332
|
+
<div class="form-group">
|
|
333
|
+
<label>Expiration</label>
|
|
334
|
+
<input type="text" placeholder="MM/YY" class="form-input">
|
|
335
|
+
</div>
|
|
336
|
+
<div class="form-group">
|
|
337
|
+
<label>CVV</label>
|
|
338
|
+
<input type="text" placeholder="123" class="form-input">
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div class="form-group">
|
|
343
|
+
<label>
|
|
344
|
+
<input type="checkbox" checked>
|
|
345
|
+
Use this as my default payment method
|
|
346
|
+
</label>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<button class="btn btn-primary" style="width: 100%;">Save Payment Method</button>
|
|
350
|
+
</div>
|
|
351
|
+
`;
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Promo code input
|
|
356
|
+
*/
|
|
357
|
+
getPromoCodeInput() {
|
|
358
|
+
return `
|
|
359
|
+
<div class="promo-code-section">
|
|
360
|
+
<h3>Have a Promo Code?</h3>
|
|
361
|
+
<div class="promo-input-group">
|
|
362
|
+
<input
|
|
363
|
+
type="text"
|
|
364
|
+
id="promo-code-input"
|
|
365
|
+
placeholder="Enter promo code"
|
|
366
|
+
class="form-input"
|
|
367
|
+
onkeypress="if(event.key==='Enter') BillingUI.applyPromoCode()"
|
|
368
|
+
>
|
|
369
|
+
<button class="btn btn-secondary" onclick="BillingUI.applyPromoCode()">Apply</button>
|
|
370
|
+
</div>
|
|
371
|
+
<div id="promo-message" style="font-size: 12px; margin-top: 8px;"></div>
|
|
372
|
+
</div>
|
|
373
|
+
`;
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Apply promo code
|
|
378
|
+
*/
|
|
379
|
+
async applyPromoCode() {
|
|
380
|
+
const input = document.getElementById('promo-code-input');
|
|
381
|
+
const code = input?.value?.toUpperCase();
|
|
382
|
+
const message = document.getElementById('promo-message');
|
|
383
|
+
|
|
384
|
+
if (!code) {
|
|
385
|
+
message.innerHTML = '<span style="color: #F56565;">Please enter a promo code</span>';
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (window.BillingModule) {
|
|
390
|
+
const result = await window.BillingModule.applyPromoCode(code);
|
|
391
|
+
if (result.valid) {
|
|
392
|
+
message.innerHTML = `<span style="color: #48BB78;">✓ ${result.message}</span>`;
|
|
393
|
+
input.style.borderColor = '#48BB78';
|
|
394
|
+
} else {
|
|
395
|
+
message.innerHTML = `<span style="color: #F56565;">✗ ${result.message}</span>`;
|
|
396
|
+
input.style.borderColor = '#F56565';
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Update pricing display (monthly vs yearly)
|
|
403
|
+
*/
|
|
404
|
+
updatePricing(cycle) {
|
|
405
|
+
const cards = document.querySelectorAll('.pricing-card');
|
|
406
|
+
cards.forEach(card => {
|
|
407
|
+
const tierId = card.className.split(' ')[1];
|
|
408
|
+
const tier = window.BillingModule?.tiers?.[tierId];
|
|
409
|
+
|
|
410
|
+
if (tier && tierId !== 'free') {
|
|
411
|
+
const priceElement = card.querySelector('.price');
|
|
412
|
+
const price = cycle === 'yearly' ? tier.priceYearly / 100 : tier.price / 100;
|
|
413
|
+
if (priceElement) {
|
|
414
|
+
priceElement.textContent = '€' + (Math.round(price / 12 * 10) / 10);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Get CSS styles for billing UI
|
|
422
|
+
*/
|
|
423
|
+
getPricingStyles() {
|
|
424
|
+
return `
|
|
425
|
+
.billing-pricing-page {
|
|
426
|
+
max-width: 1200px;
|
|
427
|
+
margin: 0 auto;
|
|
428
|
+
padding: 40px 20px;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.pricing-header {
|
|
432
|
+
text-align: center;
|
|
433
|
+
margin-bottom: 60px;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.pricing-header h2 {
|
|
437
|
+
font-size: 32px;
|
|
438
|
+
margin-bottom: 12px;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.pricing-header p {
|
|
442
|
+
font-size: 16px;
|
|
443
|
+
color: #718096;
|
|
444
|
+
margin-bottom: 30px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.billing-toggle {
|
|
448
|
+
display: flex;
|
|
449
|
+
gap: 24px;
|
|
450
|
+
justify-content: center;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.billing-toggle label {
|
|
454
|
+
display: flex;
|
|
455
|
+
align-items: center;
|
|
456
|
+
gap: 8px;
|
|
457
|
+
cursor: pointer;
|
|
458
|
+
font-size: 14px;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.pricing-grid {
|
|
462
|
+
display: grid;
|
|
463
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
464
|
+
gap: 24px;
|
|
465
|
+
margin-bottom: 60px;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.pricing-card {
|
|
469
|
+
border: 2px solid #E2E8F0;
|
|
470
|
+
border-radius: 12px;
|
|
471
|
+
padding: 32px;
|
|
472
|
+
background: white;
|
|
473
|
+
transition: all 0.3s;
|
|
474
|
+
position: relative;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.pricing-card:hover {
|
|
478
|
+
border-color: #CBD5E0;
|
|
479
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
480
|
+
transform: translateY(-4px);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.pricing-card.free {
|
|
484
|
+
background: #F7FAFC;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.pricing-card.pro {
|
|
488
|
+
border-color: #667eea;
|
|
489
|
+
border-width: 3px;
|
|
490
|
+
transform: scale(1.05);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.pricing-card.enterprise {
|
|
494
|
+
border-color: #8B5CF6;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.pricing-card.current {
|
|
498
|
+
background: #EFF6FF;
|
|
499
|
+
border-color: #3B82F6;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.current-badge {
|
|
503
|
+
position: absolute;
|
|
504
|
+
top: 12px;
|
|
505
|
+
right: 12px;
|
|
506
|
+
background: #3B82F6;
|
|
507
|
+
color: white;
|
|
508
|
+
padding: 4px 12px;
|
|
509
|
+
border-radius: 20px;
|
|
510
|
+
font-size: 11px;
|
|
511
|
+
font-weight: 600;
|
|
512
|
+
text-transform: uppercase;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.tier-name {
|
|
516
|
+
font-size: 24px;
|
|
517
|
+
margin-bottom: 8px;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.tier-description {
|
|
521
|
+
color: #718096;
|
|
522
|
+
font-size: 14px;
|
|
523
|
+
margin-bottom: 20px;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.pricing-amount {
|
|
527
|
+
font-size: 36px;
|
|
528
|
+
font-weight: 700;
|
|
529
|
+
margin-bottom: 20px;
|
|
530
|
+
margin-top: 20px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.price {
|
|
534
|
+
color: #2D3748;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.period {
|
|
538
|
+
font-size: 14px;
|
|
539
|
+
color: #718096;
|
|
540
|
+
margin-left: 4px;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.features-list {
|
|
544
|
+
list-style: none;
|
|
545
|
+
margin: 20px 0;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.features-list li {
|
|
549
|
+
padding: 8px 0;
|
|
550
|
+
font-size: 14px;
|
|
551
|
+
color: #4A5568;
|
|
552
|
+
display: flex;
|
|
553
|
+
align-items: center;
|
|
554
|
+
gap: 8px;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.check {
|
|
558
|
+
color: #48BB78;
|
|
559
|
+
font-weight: bold;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.pricing-actions {
|
|
563
|
+
margin-top: 24px;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.btn {
|
|
567
|
+
padding: 12px 20px;
|
|
568
|
+
border: none;
|
|
569
|
+
border-radius: 8px;
|
|
570
|
+
font-size: 14px;
|
|
571
|
+
font-weight: 600;
|
|
572
|
+
cursor: pointer;
|
|
573
|
+
transition: all 0.2s;
|
|
574
|
+
width: 100%;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.btn-primary {
|
|
578
|
+
background: #667eea;
|
|
579
|
+
color: white;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.btn-primary:hover {
|
|
583
|
+
background: #5568d3;
|
|
584
|
+
transform: translateY(-2px);
|
|
585
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.btn-secondary {
|
|
589
|
+
background: #E2E8F0;
|
|
590
|
+
color: #2D3748;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.btn-secondary:hover {
|
|
594
|
+
background: #CBD5E0;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.btn-secondary:disabled {
|
|
598
|
+
opacity: 0.6;
|
|
599
|
+
cursor: not-allowed;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.pricing-faq {
|
|
603
|
+
background: #F7FAFC;
|
|
604
|
+
padding: 40px;
|
|
605
|
+
border-radius: 12px;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.pricing-faq h3 {
|
|
609
|
+
font-size: 24px;
|
|
610
|
+
margin-bottom: 24px;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.faq-item {
|
|
614
|
+
margin-bottom: 24px;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.faq-item h4 {
|
|
618
|
+
font-size: 14px;
|
|
619
|
+
font-weight: 600;
|
|
620
|
+
margin-bottom: 8px;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.faq-item p {
|
|
624
|
+
font-size: 13px;
|
|
625
|
+
color: #718096;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* Upgrade Modal */
|
|
629
|
+
.billing-modal-container {
|
|
630
|
+
position: fixed;
|
|
631
|
+
top: 0;
|
|
632
|
+
left: 0;
|
|
633
|
+
right: 0;
|
|
634
|
+
bottom: 0;
|
|
635
|
+
display: flex;
|
|
636
|
+
align-items: center;
|
|
637
|
+
justify-content: center;
|
|
638
|
+
z-index: 10000;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.billing-modal-overlay {
|
|
642
|
+
position: absolute;
|
|
643
|
+
top: 0;
|
|
644
|
+
left: 0;
|
|
645
|
+
right: 0;
|
|
646
|
+
bottom: 0;
|
|
647
|
+
background: rgba(0, 0, 0, 0.5);
|
|
648
|
+
cursor: pointer;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.billing-modal {
|
|
652
|
+
position: relative;
|
|
653
|
+
background: white;
|
|
654
|
+
border-radius: 12px;
|
|
655
|
+
padding: 32px;
|
|
656
|
+
max-width: 500px;
|
|
657
|
+
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
|
|
658
|
+
cursor: default;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.modal-close {
|
|
662
|
+
position: absolute;
|
|
663
|
+
top: 12px;
|
|
664
|
+
right: 12px;
|
|
665
|
+
background: none;
|
|
666
|
+
border: none;
|
|
667
|
+
font-size: 24px;
|
|
668
|
+
cursor: pointer;
|
|
669
|
+
color: #718096;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.modal-icon {
|
|
673
|
+
font-size: 48px;
|
|
674
|
+
text-align: center;
|
|
675
|
+
margin-bottom: 16px;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.billing-modal h3 {
|
|
679
|
+
font-size: 20px;
|
|
680
|
+
text-align: center;
|
|
681
|
+
margin-bottom: 8px;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.limit-message {
|
|
685
|
+
text-align: center;
|
|
686
|
+
color: #4A5568;
|
|
687
|
+
margin-bottom: 8px;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.context-message {
|
|
691
|
+
text-align: center;
|
|
692
|
+
font-size: 13px;
|
|
693
|
+
color: #718096;
|
|
694
|
+
margin-bottom: 24px;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.upgrade-options {
|
|
698
|
+
display: flex;
|
|
699
|
+
flex-direction: column;
|
|
700
|
+
gap: 12px;
|
|
701
|
+
margin-bottom: 24px;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.btn-option {
|
|
705
|
+
padding: 16px;
|
|
706
|
+
border: 2px solid #E2E8F0;
|
|
707
|
+
border-radius: 8px;
|
|
708
|
+
background: white;
|
|
709
|
+
cursor: pointer;
|
|
710
|
+
transition: all 0.2s;
|
|
711
|
+
text-align: left;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.btn-option:hover {
|
|
715
|
+
border-color: #667eea;
|
|
716
|
+
background: #EFF6FF;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.option-name {
|
|
720
|
+
font-weight: 600;
|
|
721
|
+
margin-bottom: 4px;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.option-price {
|
|
725
|
+
color: #667eea;
|
|
726
|
+
font-weight: 700;
|
|
727
|
+
margin-bottom: 4px;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
.option-features {
|
|
731
|
+
font-size: 12px;
|
|
732
|
+
color: #718096;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.modal-footer {
|
|
736
|
+
display: flex;
|
|
737
|
+
gap: 12px;
|
|
738
|
+
justify-content: space-between;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.btn-text {
|
|
742
|
+
background: none;
|
|
743
|
+
border: none;
|
|
744
|
+
color: #667eea;
|
|
745
|
+
cursor: pointer;
|
|
746
|
+
padding: 0;
|
|
747
|
+
font-size: 13px;
|
|
748
|
+
text-decoration: none;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.btn-text:hover {
|
|
752
|
+
text-decoration: underline;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/* Usage Dashboard */
|
|
756
|
+
.usage-dashboard {
|
|
757
|
+
padding: 16px;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.usage-dashboard h3 {
|
|
761
|
+
margin-bottom: 16px;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.usage-item {
|
|
765
|
+
margin-bottom: 20px;
|
|
766
|
+
padding: 12px;
|
|
767
|
+
border-radius: 8px;
|
|
768
|
+
background: #F7FAFC;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.usage-item.warning {
|
|
772
|
+
background: #FFFAF0;
|
|
773
|
+
border-left: 4px solid #F6AD55;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.usage-item.exceeded {
|
|
777
|
+
background: #FFF5F5;
|
|
778
|
+
border-left: 4px solid #F56565;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.usage-header {
|
|
782
|
+
display: flex;
|
|
783
|
+
align-items: center;
|
|
784
|
+
gap: 8px;
|
|
785
|
+
margin-bottom: 8px;
|
|
786
|
+
font-size: 13px;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.icon {
|
|
790
|
+
font-size: 18px;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.name {
|
|
794
|
+
flex: 1;
|
|
795
|
+
font-weight: 500;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.values {
|
|
799
|
+
color: #718096;
|
|
800
|
+
font-size: 12px;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.usage-bar {
|
|
804
|
+
height: 8px;
|
|
805
|
+
background: #E2E8F0;
|
|
806
|
+
border-radius: 4px;
|
|
807
|
+
overflow: hidden;
|
|
808
|
+
margin-bottom: 4px;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
.bar-fill {
|
|
812
|
+
height: 100%;
|
|
813
|
+
transition: width 0.3s;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.usage-percent {
|
|
817
|
+
font-size: 11px;
|
|
818
|
+
color: #718096;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.usage-warning {
|
|
822
|
+
font-size: 11px;
|
|
823
|
+
color: #F6AD55;
|
|
824
|
+
margin-top: 4px;
|
|
825
|
+
font-weight: 500;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/* Trial Banner */
|
|
829
|
+
.trial-banner {
|
|
830
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
831
|
+
color: white;
|
|
832
|
+
padding: 16px;
|
|
833
|
+
border-radius: 8px;
|
|
834
|
+
margin-bottom: 16px;
|
|
835
|
+
display: flex;
|
|
836
|
+
align-items: center;
|
|
837
|
+
gap: 16px;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.banner-content {
|
|
841
|
+
flex: 1;
|
|
842
|
+
display: flex;
|
|
843
|
+
gap: 12px;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.banner-icon {
|
|
847
|
+
font-size: 24px;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.banner-text strong {
|
|
851
|
+
display: block;
|
|
852
|
+
margin-bottom: 4px;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.banner-text p {
|
|
856
|
+
font-size: 12px;
|
|
857
|
+
opacity: 0.9;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.banner-progress {
|
|
861
|
+
width: 120px;
|
|
862
|
+
height: 4px;
|
|
863
|
+
background: rgba(255, 255, 255, 0.3);
|
|
864
|
+
border-radius: 2px;
|
|
865
|
+
overflow: hidden;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
.banner-progress .progress-bar {
|
|
869
|
+
height: 100%;
|
|
870
|
+
background: white;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.banner-action {
|
|
874
|
+
background: white;
|
|
875
|
+
color: #667eea;
|
|
876
|
+
border: none;
|
|
877
|
+
padding: 6px 12px;
|
|
878
|
+
border-radius: 4px;
|
|
879
|
+
font-size: 12px;
|
|
880
|
+
font-weight: 600;
|
|
881
|
+
cursor: pointer;
|
|
882
|
+
white-space: nowrap;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.banner-action:hover {
|
|
886
|
+
background: rgba(255, 255, 255, 0.9);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/* Feature Gate */
|
|
890
|
+
.feature-gate-overlay {
|
|
891
|
+
position: absolute;
|
|
892
|
+
top: 0;
|
|
893
|
+
left: 0;
|
|
894
|
+
right: 0;
|
|
895
|
+
bottom: 0;
|
|
896
|
+
background: rgba(0, 0, 0, 0.3);
|
|
897
|
+
border-radius: 8px;
|
|
898
|
+
display: flex;
|
|
899
|
+
align-items: center;
|
|
900
|
+
justify-content: center;
|
|
901
|
+
cursor: pointer;
|
|
902
|
+
transition: all 0.2s;
|
|
903
|
+
z-index: 100;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.feature-gate-overlay:hover {
|
|
907
|
+
background: rgba(0, 0, 0, 0.5);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.gate-content {
|
|
911
|
+
display: flex;
|
|
912
|
+
flex-direction: column;
|
|
913
|
+
align-items: center;
|
|
914
|
+
gap: 8px;
|
|
915
|
+
color: white;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.gate-icon {
|
|
919
|
+
font-size: 32px;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.gate-text {
|
|
923
|
+
font-size: 12px;
|
|
924
|
+
font-weight: 600;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/* Payment Form */
|
|
928
|
+
.payment-form {
|
|
929
|
+
padding: 16px;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.form-group {
|
|
933
|
+
margin-bottom: 16px;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
.form-group label {
|
|
937
|
+
display: block;
|
|
938
|
+
font-size: 12px;
|
|
939
|
+
font-weight: 600;
|
|
940
|
+
margin-bottom: 6px;
|
|
941
|
+
color: #2D3748;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
.form-group input[type="checkbox"] {
|
|
945
|
+
margin-right: 8px;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
.form-input {
|
|
949
|
+
width: 100%;
|
|
950
|
+
padding: 10px 12px;
|
|
951
|
+
border: 1px solid #E2E8F0;
|
|
952
|
+
border-radius: 6px;
|
|
953
|
+
font-size: 13px;
|
|
954
|
+
font-family: inherit;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.form-input:focus {
|
|
958
|
+
outline: none;
|
|
959
|
+
border-color: #667eea;
|
|
960
|
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
.form-row {
|
|
964
|
+
display: grid;
|
|
965
|
+
grid-template-columns: 1fr 1fr;
|
|
966
|
+
gap: 12px;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
.promo-code-section {
|
|
970
|
+
padding: 16px;
|
|
971
|
+
background: #F7FAFC;
|
|
972
|
+
border-radius: 8px;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.promo-input-group {
|
|
976
|
+
display: flex;
|
|
977
|
+
gap: 8px;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.promo-input-group .form-input {
|
|
981
|
+
flex: 1;
|
|
982
|
+
}
|
|
983
|
+
`;
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
// Export for use in cycleCAD
|
|
988
|
+
if (typeof window !== 'undefined') {
|
|
989
|
+
window.BillingUI = BillingUI;
|
|
990
|
+
}
|