@koehler8/cms-ext-crypto 1.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -0
  3. package/components/CommunityStrip.vue +655 -0
  4. package/components/CuriosityTeaser.vue +842 -0
  5. package/components/HeaderWalletAction.vue +398 -0
  6. package/components/HeroPresale.vue +778 -0
  7. package/components/PercentDoughnut.vue +249 -0
  8. package/components/Presale.vue +507 -0
  9. package/components/PresaleAdmin.vue +1259 -0
  10. package/components/PresaleFaq.vue +95 -0
  11. package/components/ProgressFomo.vue +766 -0
  12. package/components/SocialProofFeed.vue +585 -0
  13. package/components/Tokenomics.vue +617 -0
  14. package/components/Trust.vue +123 -0
  15. package/components/TrustBar.vue +619 -0
  16. package/components/presale/BonusIncentive.vue +476 -0
  17. package/components/presale/Buy.vue +5388 -0
  18. package/components/presale/CountdownTimer.vue +326 -0
  19. package/components/presale/FirstTimeOnboarding.vue +443 -0
  20. package/components/presale/FuseMeter.vue +276 -0
  21. package/components/presale/HoldersBenefits.vue +135 -0
  22. package/components/presale/MomentumCard.vue +163 -0
  23. package/components/presale/PresaleFaqContent.vue +393 -0
  24. package/components/presale/PriceTimeline.vue +143 -0
  25. package/components/presale/Stake.vue +1415 -0
  26. package/components/presale/Status.vue +1113 -0
  27. package/components/presale/StatusMetric.vue +336 -0
  28. package/components/presale/StatusProgressBar.vue +98 -0
  29. package/components/presale/TrustSignals.vue +595 -0
  30. package/components/presale/buyAnalytics.js +58 -0
  31. package/components/presale/buyAssets.js +75 -0
  32. package/components/presale/buyTextHelpers.js +582 -0
  33. package/components/presale/priceSnapshotCache.js +72 -0
  34. package/components/presale/useBuyContentConfig.js +643 -0
  35. package/components/presale/useBuyOnboarding.js +338 -0
  36. package/components/presale/useBuyTransaction.js +464 -0
  37. package/components/presale/useBuyWallet.js +509 -0
  38. package/components/presale/useFomoProgress.js +578 -0
  39. package/components/presale/walletBalanceHelper.js +47 -0
  40. package/composables/usePresaleContext.js +24 -0
  41. package/content.defaults.json +171 -0
  42. package/extension.config.json +116 -0
  43. package/index.js +8 -0
  44. package/package.json +47 -0
  45. package/plugins/appKit.js +261 -0
  46. package/setup.js +29 -0
  47. package/stores/presale.js +70 -0
  48. package/utils/presaleContracts.js +69 -0
  49. package/utils/scrollToPresale.js +21 -0
  50. package/utils/tokenFormat.js +21 -0
  51. package/utils/walletTracking.js +175 -0
@@ -0,0 +1,655 @@
1
+ <template>
2
+ <section
3
+ v-if="stripVisible"
4
+ class="community-strip"
5
+ role="complementary"
6
+ :aria-label="resolvedTitle"
7
+ >
8
+ <div class="community-strip__container">
9
+ <SbCard class="community-strip__card" :padded="false">
10
+ <header class="community-strip__header">
11
+ <h2 class="community-strip__title">{{ resolvedTitle }}</h2>
12
+ <p v-if="resolvedSubtitle" class="community-strip__subtitle">
13
+ {{ resolvedSubtitle }}
14
+ </p>
15
+ </header>
16
+
17
+ <div class="community-strip__content">
18
+ <div v-if="socialEntries.length" class="community-strip__social">
19
+ <article
20
+ v-for="(entry, index) in socialEntries"
21
+ :key="entry.id || index"
22
+ class="community-strip__social-card"
23
+ >
24
+ <span class="community-strip__social-icon" aria-hidden="true">
25
+ <component
26
+ v-if="entry.iconComponent"
27
+ :is="entry.iconComponent"
28
+ class="community-strip__icon-svg"
29
+ />
30
+ <span v-else-if="entry.icon" class="community-strip__icon-symbol">
31
+ {{ entry.icon }}
32
+ </span>
33
+ </span>
34
+ <div class="community-strip__social-meta">
35
+ <span class="community-strip__social-label">{{ entry.label }}</span>
36
+ <span class="community-strip__social-count">
37
+ {{ entry.count }}
38
+ <span v-if="entry.countApprox" class="community-strip__approx">(est.)</span>
39
+ </span>
40
+ </div>
41
+ <a
42
+ class="community-strip__social-link"
43
+ :href="entry.href"
44
+ target="_blank"
45
+ rel="noopener"
46
+ :aria-label="entry.ariaLabel"
47
+ @click="trackSocialClick(entry)"
48
+ >
49
+ {{ entry.cta || 'Open' }}
50
+ </a>
51
+ </article>
52
+ </div>
53
+
54
+ <div v-if="emailEnabled" class="community-strip__email">
55
+ <p v-if="emailDescription" class="community-strip__email-description">
56
+ {{ emailDescription }}
57
+ </p>
58
+ <form class="community-strip__email-form" @submit.prevent="handleEmailSubmit" novalidate>
59
+ <label class="visually-hidden" :for="emailFieldId">{{ emailLabel }}</label>
60
+ <input
61
+ :id="emailFieldId"
62
+ class="community-strip__email-input"
63
+ type="email"
64
+ :placeholder="emailPlaceholder"
65
+ autocomplete="email"
66
+ v-model.trim="emailValue"
67
+ :disabled="emailStatus === 'submitting'"
68
+ required
69
+ />
70
+ <button
71
+ class="community-strip__email-button"
72
+ type="submit"
73
+ :disabled="emailStatus === 'submitting'"
74
+ >
75
+ {{ emailStatus === 'submitting' ? 'Submitting…' : emailButtonText }}
76
+ </button>
77
+ </form>
78
+ <p
79
+ v-if="emailStatus === 'success'"
80
+ class="community-strip__feedback community-strip__feedback--success"
81
+ >
82
+ {{ emailSuccessMessage }}
83
+ </p>
84
+ <p
85
+ v-if="emailStatus === 'error'"
86
+ class="community-strip__feedback community-strip__feedback--error"
87
+ >
88
+ {{ emailErrorMessage }}
89
+ </p>
90
+ </div>
91
+ </div>
92
+ </SbCard>
93
+ </div>
94
+ </section>
95
+ </template>
96
+
97
+ <script setup>
98
+ import { computed, inject, reactive, ref, watch } from 'vue';
99
+ import { trackEvent, trackFunnelEvent } from '@koehler8/cms/utils/analytics';
100
+ import SbCard from '@koehler8/cms/components/ui/SbCard.vue';
101
+
102
+ const props = defineProps({
103
+ title: {
104
+ type: String,
105
+ default: 'Stay aligned with the crew',
106
+ },
107
+ subtitle: {
108
+ type: String,
109
+ default: '',
110
+ },
111
+ socials: {
112
+ type: Array,
113
+ default: () => [],
114
+ },
115
+ email: {
116
+ type: Object,
117
+ default: () => ({}),
118
+ },
119
+ });
120
+
121
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
122
+ const emailValue = ref('');
123
+ const emailStatus = ref('idle'); // idle | submitting | success | error
124
+ const emailErrorMessage = ref('');
125
+ const emailSuccessMessage = ref('');
126
+ const emailFieldId = `community-email-${Math.random().toString(36).slice(2, 9)}`;
127
+
128
+ const pageContent = inject('pageContent', ref({}));
129
+
130
+ const configFromContent = computed(() => pageContent.value?.communityStrip || {});
131
+
132
+ const resolvedTitle = computed(() => {
133
+ const param = [props.title, configFromContent.value?.title, 'Stay in the loop with the crew'];
134
+ return param.find((value) => typeof value === 'string' && value.trim())?.trim();
135
+ });
136
+
137
+ const resolvedSubtitle = computed(() => {
138
+ const param = [props.subtitle, configFromContent.value?.subtitle];
139
+ const value = param.find((entry) => typeof entry === 'string' && entry.trim());
140
+ return value ? value.trim() : '';
141
+ });
142
+
143
+ const resolvedSocials = computed(() => {
144
+ const socials =
145
+ Array.isArray(props.socials) && props.socials.length
146
+ ? props.socials
147
+ : Array.isArray(configFromContent.value?.socials)
148
+ ? configFromContent.value.socials
149
+ : [];
150
+ return socials;
151
+ });
152
+
153
+ function resolveSocialAriaLabel({ ariaLabel, label, cta, href }) {
154
+ if (ariaLabel) {
155
+ return ariaLabel;
156
+ }
157
+
158
+ const action = cta || 'Open';
159
+ const destination = label || 'community channel';
160
+ const base = `${action} ${destination} for this community`.trim();
161
+ const opensInNewTab = typeof href === 'string' && href.startsWith('http');
162
+
163
+ return opensInNewTab ? `${base} (opens in a new tab)` : base;
164
+ }
165
+
166
+ const sanitizedSocials = computed(() =>
167
+ resolvedSocials.value
168
+ .filter((item) => item && typeof item === 'object')
169
+ .map((item, index) => {
170
+ const href = typeof item.href === 'string' ? item.href.trim() : '';
171
+ const label = typeof item.label === 'string' ? item.label.trim() : '';
172
+ const count =
173
+ typeof item.count === 'string'
174
+ ? item.count.trim()
175
+ : Number.isFinite(item.count)
176
+ ? String(item.count)
177
+ : '';
178
+
179
+ return {
180
+ id: item.id || `social-${index}`,
181
+ href: href || '#',
182
+ label: label || 'Community',
183
+ count: count || '—',
184
+ countApprox: Boolean(item.countApprox),
185
+ icon: item.icon,
186
+ iconComponent: item.iconComponent,
187
+ iconLabel: item.iconLabel,
188
+ cta: typeof item.cta === 'string' && item.cta.trim() ? item.cta.trim() : 'Open',
189
+ ariaLabel: resolveSocialAriaLabel({
190
+ href,
191
+ label: label || 'Community channel',
192
+ cta: typeof item.cta === 'string' && item.cta.trim() ? item.cta.trim() : 'Open',
193
+ ariaLabel: typeof item.ariaLabel === 'string' ? item.ariaLabel.trim() : '',
194
+ }),
195
+ analytics: item.analytics || {},
196
+ };
197
+ })
198
+ );
199
+
200
+ const socialEntries = computed(() =>
201
+ sanitizedSocials.value.slice(0, 4)
202
+ );
203
+
204
+ const resolvedEmail = computed(() => {
205
+ const override = props.email || {};
206
+ const configEmail = configFromContent.value?.email || {};
207
+ return { ...configEmail, ...override };
208
+ });
209
+
210
+ const emailConfig = reactive({
211
+ enabled: false,
212
+ endpoint: '',
213
+ method: 'POST',
214
+ placeholder: 'you@example.com',
215
+ buttonText: 'Subscribe',
216
+ successMessage: 'Thanks! Check your inbox shortly.',
217
+ errorMessage: 'We could not submit your email. Try again.',
218
+ description: '',
219
+ label: 'Email address',
220
+ headers: null,
221
+ payloadFieldName: 'email',
222
+ extraFields: null,
223
+ });
224
+
225
+ watch(
226
+ resolvedEmail,
227
+ (value) => {
228
+ emailConfig.enabled = Boolean(value?.enabled) && Boolean(value?.endpoint);
229
+ emailConfig.endpoint =
230
+ typeof value?.endpoint === 'string' && value.endpoint.trim() ? value.endpoint.trim() : '';
231
+ emailConfig.method =
232
+ typeof value?.method === 'string' && value.method.trim()
233
+ ? value.method.trim().toUpperCase()
234
+ : 'POST';
235
+ emailConfig.placeholder =
236
+ typeof value?.placeholder === 'string' && value.placeholder.trim()
237
+ ? value.placeholder.trim()
238
+ : 'you@example.com';
239
+ emailConfig.buttonText =
240
+ typeof value?.button === 'string' && value.button.trim()
241
+ ? value.button.trim()
242
+ : 'Subscribe';
243
+ emailConfig.successMessage =
244
+ typeof value?.successMessage === 'string' && value.successMessage.trim()
245
+ ? value.successMessage.trim()
246
+ : 'Thanks! Check your inbox shortly.';
247
+ emailConfig.errorMessage =
248
+ typeof value?.errorMessage === 'string' && value.errorMessage.trim()
249
+ ? value.errorMessage.trim()
250
+ : 'We could not submit your email. Try again.';
251
+ emailConfig.description =
252
+ typeof value?.description === 'string' && value.description.trim()
253
+ ? value.description.trim()
254
+ : '';
255
+ emailConfig.label =
256
+ typeof value?.label === 'string' && value.label.trim() ? value.label.trim() : 'Email address';
257
+ emailConfig.headers =
258
+ value?.headers && typeof value.headers === 'object' ? { ...value.headers } : null;
259
+ emailConfig.payloadFieldName =
260
+ typeof value?.payloadFieldName === 'string' && value.payloadFieldName.trim()
261
+ ? value.payloadFieldName.trim()
262
+ : 'email';
263
+ emailConfig.extraFields =
264
+ value?.extraFields && typeof value.extraFields === 'object' ? { ...value.extraFields } : null;
265
+
266
+ if (!emailConfig.enabled) {
267
+ emailValue.value = '';
268
+ emailStatus.value = 'idle';
269
+ emailErrorMessage.value = '';
270
+ emailSuccessMessage.value = '';
271
+ }
272
+ },
273
+ { immediate: true }
274
+ );
275
+
276
+ const emailEnabled = computed(() => emailConfig.enabled && emailConfig.endpoint);
277
+ const emailPlaceholder = computed(() => emailConfig.placeholder);
278
+ const emailButtonText = computed(() => emailConfig.buttonText);
279
+ const emailDescription = computed(() => emailConfig.description);
280
+ const emailLabel = computed(() => emailConfig.label);
281
+ const stripVisible = computed(() => socialEntries.value.length > 0 || emailEnabled.value);
282
+
283
+ function trackSocialClick(entry) {
284
+ const href = entry?.href || '';
285
+ const label = entry?.label || '';
286
+ trackEvent('community_strip_social_click', {
287
+ channel: entry.analytics?.channel || label.toLowerCase() || 'social',
288
+ href,
289
+ });
290
+ const hrefLower = href.toLowerCase();
291
+ if (hrefLower.includes('t.me') || hrefLower.includes('telegram')) {
292
+ trackFunnelEvent('social_telegram_click', {
293
+ source: 'community_strip',
294
+ href,
295
+ label,
296
+ });
297
+ } else if (hrefLower.includes('twitter') || hrefLower.includes('x.com')) {
298
+ trackFunnelEvent('social_twitter_click', {
299
+ source: 'community_strip',
300
+ href,
301
+ label,
302
+ });
303
+ }
304
+ }
305
+
306
+ async function handleEmailSubmit() {
307
+ if (!emailEnabled.value) return;
308
+
309
+ const email = emailValue.value.trim();
310
+ if (!EMAIL_REGEX.test(email)) {
311
+ emailStatus.value = 'error';
312
+ emailErrorMessage.value = emailConfig.errorMessage || 'Please enter a valid email address.';
313
+ return;
314
+ }
315
+
316
+ emailStatus.value = 'submitting';
317
+ emailErrorMessage.value = '';
318
+ emailSuccessMessage.value = '';
319
+
320
+ const payload = {
321
+ [emailConfig.payloadFieldName || 'email']: email,
322
+ };
323
+
324
+ if (emailConfig.extraFields) {
325
+ Object.entries(emailConfig.extraFields).forEach(([key, value]) => {
326
+ if (key) payload[key] = value;
327
+ });
328
+ }
329
+
330
+ const headers = emailConfig.headers ? { ...emailConfig.headers } : {};
331
+ if (!headers['Content-Type'] && !headers['content-type']) {
332
+ headers['Content-Type'] = 'application/json';
333
+ }
334
+
335
+ const method = emailConfig.method || 'POST';
336
+ const contentType = headers['Content-Type'] || headers['content-type'] || '';
337
+ let body;
338
+ if (contentType.includes('application/json')) {
339
+ body = JSON.stringify(payload);
340
+ } else if (contentType.includes('application/x-www-form-urlencoded')) {
341
+ body = new URLSearchParams(payload).toString();
342
+ } else {
343
+ headers['Content-Type'] = 'application/json';
344
+ body = JSON.stringify(payload);
345
+ }
346
+
347
+ try {
348
+ const response = await fetch(emailConfig.endpoint, {
349
+ method,
350
+ headers,
351
+ body,
352
+ });
353
+
354
+ if (!response.ok) {
355
+ throw new Error(`Email submission failed with status ${response.status}`);
356
+ }
357
+
358
+ emailStatus.value = 'success';
359
+ emailSuccessMessage.value = emailConfig.successMessage;
360
+ emailValue.value = '';
361
+
362
+ trackEvent('community_strip_email_success', {
363
+ endpoint: emailConfig.endpoint || '',
364
+ });
365
+ } catch (error) {
366
+ console.error('Failed to submit community email signup', error);
367
+ emailStatus.value = 'error';
368
+ emailErrorMessage.value = emailConfig.errorMessage;
369
+
370
+ trackEvent('community_strip_email_error', {
371
+ endpoint: emailConfig.endpoint || '',
372
+ error_name: error?.name || 'unknown',
373
+ });
374
+ }
375
+ }
376
+ </script>
377
+
378
+ <style scoped>
379
+ .community-strip {
380
+ margin: 0;
381
+ padding: 60px 0;
382
+ background: var(--community-strip-bg, rgba(12, 15, 31, 0.88));
383
+ border-top: 1px solid var(--community-strip-border, rgba(39, 243, 255, 0.18));
384
+ border-bottom: 1px solid var(--community-strip-border, rgba(39, 243, 255, 0.12));
385
+ }
386
+
387
+ @media (min-width: 992px) {
388
+ .community-strip {
389
+ padding: 90px 0;
390
+ }
391
+ }
392
+
393
+ .community-strip__container {
394
+ max-width: 960px;
395
+ margin: 0 auto;
396
+ padding: 0 24px;
397
+ }
398
+
399
+ .community-strip__card {
400
+ padding: 32px 24px;
401
+ background: var(--community-strip-card-bg, rgba(10, 10, 13, 0.92));
402
+ }
403
+
404
+ @media (min-width: 992px) {
405
+ .community-strip__card {
406
+ padding: 40px 36px;
407
+ }
408
+ }
409
+
410
+ .community-strip__header {
411
+ text-align: center;
412
+ margin-bottom: 32px;
413
+ }
414
+
415
+ .community-strip__title {
416
+ margin: 0 0 12px;
417
+ font-family: 'Space Grotesk', 'Inter', sans-serif;
418
+ font-size: clamp(2.25rem, 4.5vw, 3rem);
419
+ font-weight: 700;
420
+ color: var(--community-strip-title-color, #f1f5f9);
421
+ }
422
+
423
+ .community-strip__subtitle {
424
+ margin: 0;
425
+ margin-top: 8px;
426
+ font-size: clamp(0.95rem, 2.3vw, 1.05rem);
427
+ font-family: 'Inter', 'Helvetica Neue', sans-serif;
428
+ color: var(--community-strip-subtitle-color, rgba(226, 232, 240, 0.7));
429
+ }
430
+
431
+ .community-strip__content {
432
+ display: flex;
433
+ flex-wrap: wrap;
434
+ gap: 24px;
435
+ justify-content: center;
436
+ align-items: stretch;
437
+ }
438
+
439
+ .community-strip__social {
440
+ display: grid;
441
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
442
+ gap: 16px;
443
+ flex: 2;
444
+ }
445
+
446
+ .community-strip__social-card {
447
+ display: grid;
448
+ grid-template-columns: auto 1fr auto;
449
+ align-items: center;
450
+ gap: 12px;
451
+ padding: 16px;
452
+ border-radius: 16px;
453
+ border: 1px solid var(--community-strip-card-border, rgba(39, 243, 255, 0.25));
454
+ background: var(
455
+ --community-strip-social-bg,
456
+ radial-gradient(circle at top left, rgba(10, 10, 13, 0.75), rgba(20, 18, 22, 0.85))
457
+ );
458
+ color: var(--community-strip-color, rgba(240, 234, 243, 0.92));
459
+ transition: transform 0.2s ease, border 0.2s ease;
460
+ }
461
+
462
+ .community-strip__social-card:hover,
463
+ .community-strip__social-card:focus-within {
464
+ border-color: var(--community-strip-card-border-hover, rgba(39, 243, 255, 0.45));
465
+ transform: translateY(-2px);
466
+ }
467
+
468
+ .community-strip__social-icon {
469
+ width: 36px;
470
+ height: 36px;
471
+ border-radius: 12px;
472
+ background: rgba(39, 243, 255, 0.12);
473
+ display: flex;
474
+ align-items: center;
475
+ justify-content: center;
476
+ font-size: 18px;
477
+ color: var(--brand-electric-blue, #27f3ff);
478
+ }
479
+
480
+ .community-strip__icon-symbol {
481
+ font-weight: 600;
482
+ }
483
+
484
+ .community-strip__icon-svg {
485
+ width: 20px;
486
+ height: 20px;
487
+ fill: currentColor;
488
+ }
489
+
490
+ .community-strip__social-meta {
491
+ display: flex;
492
+ flex-direction: column;
493
+ gap: 4px;
494
+ }
495
+
496
+ .community-strip__social-label {
497
+ font-size: clamp(0.75rem, 2vw, 0.85rem);
498
+ letter-spacing: 0.08em;
499
+ text-transform: uppercase;
500
+ font-family: 'Inter', 'Helvetica Neue', sans-serif;
501
+ color: rgba(226, 232, 240, 0.72);
502
+ }
503
+
504
+ .community-strip__social-count {
505
+ font-size: clamp(1.2rem, 3vw, 1.6rem);
506
+ font-family: 'Space Grotesk', 'Inter', sans-serif;
507
+ font-weight: 700;
508
+ }
509
+
510
+ .community-strip__approx {
511
+ font-size: clamp(0.68rem, 2vw, 0.75rem);
512
+ font-family: 'Inter', 'Helvetica Neue', sans-serif;
513
+ margin-left: 4px;
514
+ font-weight: 500;
515
+ color: rgba(226, 232, 240, 0.65);
516
+ }
517
+
518
+ .community-strip__social-link {
519
+ font-size: clamp(0.75rem, 2vw, 0.85rem);
520
+ font-family: 'Inter', 'Helvetica Neue', sans-serif;
521
+ letter-spacing: 0.08em;
522
+ text-transform: uppercase;
523
+ color: var(--community-strip-link-color, var(--brand-electric-blue, #27f3ff));
524
+ text-decoration: none;
525
+ border: 1px solid var(--community-strip-link-border, rgba(39, 243, 255, 0.3));
526
+ border-radius: var(--brand-button-radius, 14px);
527
+ padding: 8px 12px;
528
+ transition: background 0.2s ease, color 0.2s ease, border 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
529
+ }
530
+
531
+ .community-strip__social-link:hover,
532
+ .community-strip__social-link:focus {
533
+ background: var(--community-strip-link-hover-bg, rgba(39, 243, 255, 0.18));
534
+ border-color: var(--community-strip-link-hover-border, rgba(39, 243, 255, 0.45));
535
+ color: var(--community-strip-link-hover-color, var(--brand-neon-pink, #ff2d86));
536
+ transform: translateY(-1px);
537
+ box-shadow: var(--community-strip-link-hover-shadow, 0 12px 24px rgba(39, 243, 255, 0.18));
538
+ }
539
+
540
+ .community-strip__social-link:focus-visible {
541
+ outline: 2px solid rgba(39, 243, 255, 0.55);
542
+ outline-offset: 3px;
543
+ }
544
+
545
+ .community-strip__email {
546
+ flex: 1;
547
+ min-width: 260px;
548
+ max-width: 360px;
549
+ background: var(--community-strip-email-bg, rgba(15, 23, 42, 0.65));
550
+ border: 1px solid var(--community-strip-email-border, rgba(59, 130, 246, 0.18));
551
+ border-radius: var(--brand-card-radius, 24px);
552
+ padding: 20px;
553
+ display: flex;
554
+ flex-direction: column;
555
+ gap: 12px;
556
+ box-shadow: var(--brand-surface-card-shadow, 0 8px 24px rgba(217, 22, 75, 0.18));
557
+ }
558
+
559
+ .community-strip__email-description {
560
+ margin: 0;
561
+ font-size: clamp(0.9rem, 2.2vw, 1rem);
562
+ font-family: 'Inter', 'Helvetica Neue', sans-serif;
563
+ color: var(--community-strip-email-text, rgba(226, 232, 240, 0.78));
564
+ }
565
+
566
+ .community-strip__email-form {
567
+ display: flex;
568
+ gap: 10px;
569
+ }
570
+
571
+ .community-strip__email-input {
572
+ flex: 1;
573
+ min-width: 0;
574
+ border-radius: 12px;
575
+ border: 1px solid var(--community-strip-email-input-border, rgba(39, 243, 255, 0.28));
576
+ background: var(--community-strip-email-input-bg, rgba(10, 10, 13, 0.78));
577
+ color: var(--community-strip-email-input-color, rgba(240, 234, 243, 0.92));
578
+ padding: 10px 12px;
579
+ font-size: clamp(0.9rem, 2.2vw, 1rem);
580
+ font-family: 'Inter', 'Helvetica Neue', sans-serif;
581
+ }
582
+
583
+ .community-strip__email-input::placeholder {
584
+ color: var(--community-strip-email-input-placeholder, rgba(201, 191, 208, 0.6));
585
+ }
586
+
587
+ .community-strip__email-button {
588
+ border-radius: 12px;
589
+ border: 1px solid var(--community-strip-email-button-border, rgba(39, 243, 255, 0.35));
590
+ background: var(
591
+ --community-strip-email-button-bg,
592
+ linear-gradient(135deg, rgba(39, 243, 255, 0.22), rgba(255, 45, 134, 0.18))
593
+ );
594
+ color: var(--community-strip-email-button-color, var(--brand-fg-100, #f0eaf3));
595
+ font-size: clamp(0.75rem, 2vw, 0.85rem);
596
+ font-family: 'Inter', 'Helvetica Neue', sans-serif;
597
+ letter-spacing: 0.08em;
598
+ text-transform: uppercase;
599
+ padding: 10px 14px;
600
+ cursor: pointer;
601
+ transition: background 0.2s ease, border 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
602
+ }
603
+
604
+ .community-strip__email-button:hover,
605
+ .community-strip__email-button:focus {
606
+ background: var(
607
+ --community-strip-email-button-hover-bg,
608
+ linear-gradient(135deg, rgba(39, 243, 255, 0.32), rgba(255, 45, 134, 0.22))
609
+ );
610
+ border-color: var(--community-strip-email-button-hover-border, rgba(255, 45, 134, 0.45));
611
+ transform: translateY(-1px);
612
+ box-shadow: var(--community-strip-email-button-hover-shadow, 0 14px 28px rgba(39, 243, 255, 0.18));
613
+ }
614
+
615
+ .community-strip__email-button:disabled {
616
+ opacity: 0.6;
617
+ cursor: not-allowed;
618
+ }
619
+
620
+ .community-strip__feedback {
621
+ margin: 0;
622
+ font-size: clamp(0.8rem, 2vw, 0.95rem);
623
+ font-family: 'Inter', 'Helvetica Neue', sans-serif;
624
+ }
625
+
626
+ .community-strip__feedback--success {
627
+ color: var(--brand-status-success, #27f3ff);
628
+ }
629
+
630
+ .community-strip__feedback--error {
631
+ color: var(--brand-status-error, #d9164b);
632
+ }
633
+
634
+ @media (max-width: 768px) {
635
+ .community-strip__content {
636
+ flex-direction: column;
637
+ }
638
+
639
+ .community-strip__social {
640
+ width: 100%;
641
+ }
642
+
643
+ .community-strip__email {
644
+ width: 100%;
645
+ }
646
+
647
+ .community-strip__email-form {
648
+ flex-direction: column;
649
+ }
650
+
651
+ .community-strip__email-button {
652
+ width: 100%;
653
+ }
654
+ }
655
+ </style>