@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,476 @@
1
+ <template>
2
+ <SbCard
3
+ tag="section"
4
+ class="bonus-incentive"
5
+ :class="{ 'bonus-incentive--depleted': isDepleted }"
6
+ role="status"
7
+ :aria-live="ariaLive"
8
+ >
9
+ <header class="bonus-incentive__header">
10
+ <p v-if="badgeText" class="bonus-incentive__badge">{{ badgeText }}</p>
11
+ <span v-if="bonusValueText" class="bonus-incentive__pill">{{ bonusValueText }}</span>
12
+ </header>
13
+ <h3 class="bonus-incentive__headline">
14
+ {{ headlineText }}
15
+ </h3>
16
+ <p v-if="descriptionText" class="bonus-incentive__description">
17
+ {{ descriptionText }}
18
+ </p>
19
+ <p v-if="bonusLabelText" class="bonus-incentive__tagline">
20
+ {{ bonusLabelText }}
21
+ </p>
22
+ <div class="bonus-incentive__metrics">
23
+ <div class="bonus-incentive__counter">
24
+ <span class="bonus-incentive__count-main">{{ remainingDisplay }}</span>
25
+ <span class="bonus-incentive__count-sub">{{ countMeta }}</span>
26
+ </div>
27
+ <div v-if="countdownDisplay" class="bonus-incentive__countdown">
28
+ <span class="bonus-incentive__countdown-label">{{ countdownLabelText }}</span>
29
+ <span class="bonus-incentive__countdown-value">{{ countdownDisplay }}</span>
30
+ </div>
31
+ </div>
32
+ <div
33
+ v-if="progressPercent !== null"
34
+ class="bonus-incentive__progress"
35
+ role="progressbar"
36
+ :aria-valuemin="0"
37
+ :aria-valuemax="100"
38
+ :aria-valuenow="progressPercent"
39
+ :aria-label="progressAriaLabel"
40
+ >
41
+ <span class="bonus-incentive__progress-bar" :style="{ width: progressWidth }"></span>
42
+ </div>
43
+ <p v-if="footnoteText" class="bonus-incentive__footnote">
44
+ {{ footnoteText }}
45
+ </p>
46
+ <a
47
+ v-if="ctaText && ctaHref"
48
+ class="bonus-incentive__cta"
49
+ :href="ctaHref"
50
+ :target="ctaTarget"
51
+ :rel="ctaRel"
52
+ @click="emit('cta-click')"
53
+ >
54
+ {{ ctaText }}
55
+ </a>
56
+ </SbCard>
57
+ </template>
58
+
59
+ <script setup>
60
+ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
61
+ import SbCard from '@koehler8/cms/components/ui/SbCard.vue';
62
+
63
+ const props = defineProps({
64
+ badge: {
65
+ type: String,
66
+ default: '',
67
+ },
68
+ headline: {
69
+ type: String,
70
+ default: 'Early buyer bonus live',
71
+ },
72
+ description: {
73
+ type: String,
74
+ default: '',
75
+ },
76
+ bonusLabel: {
77
+ type: String,
78
+ default: '',
79
+ },
80
+ bonusValue: {
81
+ type: String,
82
+ default: '',
83
+ },
84
+ total: {
85
+ type: Number,
86
+ default: null,
87
+ },
88
+ remaining: {
89
+ type: Number,
90
+ default: null,
91
+ },
92
+ footnote: {
93
+ type: String,
94
+ default: '',
95
+ },
96
+ cta: {
97
+ type: Object,
98
+ default: () => ({
99
+ text: '',
100
+ href: '',
101
+ external: false,
102
+ }),
103
+ },
104
+ countdownLabel: {
105
+ type: String,
106
+ default: '',
107
+ },
108
+ expiresAt: {
109
+ type: Number,
110
+ default: null,
111
+ },
112
+ });
113
+
114
+ const emit = defineEmits(['cta-click']);
115
+
116
+ const totalCount = computed(() => {
117
+ const value = Number.isFinite(props.total) ? Math.floor(props.total) : null;
118
+ if (value === null || value < 0) return null;
119
+ return value;
120
+ });
121
+
122
+ const remainingCount = computed(() => {
123
+ const value = Number.isFinite(props.remaining) ? Math.floor(props.remaining) : null;
124
+ if (value === null) return null;
125
+ return value < 0 ? 0 : value;
126
+ });
127
+
128
+ const claimedCount = computed(() => {
129
+ if (totalCount.value === null || remainingCount.value === null) return null;
130
+ const claimed = totalCount.value - remainingCount.value;
131
+ return claimed < 0 ? 0 : claimed;
132
+ });
133
+
134
+ const progressPercent = computed(() => {
135
+ if (totalCount.value === null || claimedCount.value === null || totalCount.value === 0) {
136
+ return null;
137
+ }
138
+ const ratio = (claimedCount.value / totalCount.value) * 100;
139
+ return Math.min(Math.max(Math.round(ratio), 0), 100);
140
+ });
141
+
142
+ const progressWidth = computed(() => (progressPercent.value === null ? '0%' : `${progressPercent.value}%`));
143
+
144
+ const progressAriaLabel = computed(() => {
145
+ if (claimedCount.value === null || totalCount.value === null) return 'Bonus claim progress';
146
+ return `${claimedCount.value.toLocaleString()} of ${totalCount.value.toLocaleString()} bonuses claimed`;
147
+ });
148
+
149
+ const isDepleted = computed(() => remainingCount.value !== null && remainingCount.value <= 0);
150
+
151
+ const remainingDisplay = computed(() => {
152
+ if (remainingCount.value === null) return 'Bonus available';
153
+ if (remainingCount.value === 0) return 'Bonus depleted';
154
+ return `${remainingCount.value.toLocaleString()} left`;
155
+ });
156
+
157
+ const countMeta = computed(() => {
158
+ if (totalCount.value !== null) {
159
+ return `of ${totalCount.value.toLocaleString()} bonus slots`;
160
+ }
161
+ if (props.bonusLabel) {
162
+ return props.bonusLabel;
163
+ }
164
+ return 'Limited quantity';
165
+ });
166
+
167
+ const badgeText = computed(() => props.badge);
168
+ const headlineText = computed(() => props.headline);
169
+ const descriptionText = computed(() => props.description);
170
+ const bonusLabelText = computed(() => props.bonusLabel);
171
+ const bonusValueText = computed(() => props.bonusValue);
172
+ const footnoteText = computed(() => props.footnote);
173
+
174
+ const ctaText = computed(() => (typeof props.cta?.text === 'string' ? props.cta.text : ''));
175
+ const ctaHref = computed(() => (typeof props.cta?.href === 'string' ? props.cta.href : ''));
176
+ const ctaTarget = computed(() => (props.cta?.external ? '_blank' : '_self'));
177
+ const ctaRel = computed(() => (props.cta?.external ? 'noopener noreferrer' : undefined));
178
+
179
+ const countdownLabelText = computed(() => props.countdownLabel);
180
+ const countdownDisplay = ref('');
181
+ let countdownTimer = null;
182
+
183
+ function pad(value) {
184
+ return value.toString().padStart(2, '0');
185
+ }
186
+
187
+ function updateCountdown() {
188
+ if (!props.expiresAt) {
189
+ countdownDisplay.value = '';
190
+ return;
191
+ }
192
+ const nowSeconds = Math.floor(Date.now() / 1000);
193
+ let diff = Math.floor(props.expiresAt - nowSeconds);
194
+ if (diff <= 0) {
195
+ countdownDisplay.value = 'Ending soon';
196
+ stopCountdown();
197
+ return;
198
+ }
199
+
200
+ const days = Math.floor(diff / 86400);
201
+ diff -= days * 86400;
202
+ const hours = Math.floor(diff / 3600);
203
+ diff -= hours * 3600;
204
+ const minutes = Math.floor(diff / 60);
205
+ diff -= minutes * 60;
206
+ const seconds = diff;
207
+
208
+ const parts = [];
209
+ if (days > 0) {
210
+ parts.push(`${days}d`);
211
+ }
212
+ parts.push(`${pad(hours)}h`, `${pad(minutes)}m`, `${pad(seconds)}s`);
213
+ countdownDisplay.value = parts.join(' ');
214
+ }
215
+
216
+ function startCountdown() {
217
+ if (!props.expiresAt) return;
218
+ updateCountdown();
219
+ if (typeof window === 'undefined') {
220
+ return;
221
+ }
222
+ if (countdownTimer) {
223
+ window.clearInterval(countdownTimer);
224
+ }
225
+ countdownTimer = window.setInterval(updateCountdown, 1000);
226
+ }
227
+
228
+ function stopCountdown() {
229
+ if (typeof window === 'undefined') {
230
+ countdownTimer = null;
231
+ return;
232
+ }
233
+ if (countdownTimer) {
234
+ window.clearInterval(countdownTimer);
235
+ countdownTimer = null;
236
+ }
237
+ }
238
+
239
+ onMounted(() => {
240
+ if (props.expiresAt) {
241
+ startCountdown();
242
+ }
243
+ });
244
+
245
+ onBeforeUnmount(() => {
246
+ stopCountdown();
247
+ });
248
+
249
+ watch(
250
+ () => props.expiresAt,
251
+ (next) => {
252
+ if (!next) {
253
+ stopCountdown();
254
+ countdownDisplay.value = '';
255
+ return;
256
+ }
257
+ startCountdown();
258
+ },
259
+ { immediate: true }
260
+ );
261
+
262
+ const ariaLive = computed(() => {
263
+ if (remainingCount.value === null) return 'polite';
264
+ return remainingCount.value <= 5 ? 'assertive' : 'polite';
265
+ });
266
+ </script>
267
+
268
+ <style scoped>
269
+
270
+ .bonus-incentive {
271
+ position: relative;
272
+ display: flex;
273
+ flex-direction: column;
274
+ gap: 12px;
275
+ margin: 0 0 18px;
276
+ padding: 24px 24px;
277
+ color: #f8f9ff;
278
+ }
279
+
280
+ .bonus-incentive::before {
281
+ content: '';
282
+ position: absolute;
283
+ inset: 0;
284
+ background: radial-gradient(circle at top right, rgba(255, 120, 216, 0.14), transparent 55%);
285
+ pointer-events: none;
286
+ border-radius: inherit;
287
+ }
288
+
289
+ .bonus-incentive > * {
290
+ position: relative;
291
+ z-index: 1;
292
+ }
293
+
294
+ .bonus-incentive--depleted {
295
+ border: 1px solid rgba(255, 255, 255, 0.12);
296
+ }
297
+
298
+ .bonus-incentive__header {
299
+ display: flex;
300
+ align-items: center;
301
+ justify-content: space-between;
302
+ gap: 12px;
303
+ }
304
+
305
+ .bonus-incentive__badge {
306
+ margin: 0;
307
+ font-size: 0.68rem;
308
+ letter-spacing: 0.18em;
309
+ text-transform: uppercase;
310
+ font-family: var(--ui-font-body, 'Inter', 'Space Grotesk', sans-serif);
311
+ color: rgba(200, 213, 255, 0.8);
312
+ }
313
+
314
+ .bonus-incentive__pill {
315
+ display: inline-flex;
316
+ align-items: center;
317
+ justify-content: center;
318
+ padding: 0.35rem 0.75rem;
319
+ border-radius: 999px;
320
+ background: rgba(255, 153, 255, 0.2);
321
+ font-size: 0.75rem;
322
+ font-weight: 600;
323
+ font-family: var(--ui-font-body, 'Inter', 'Space Grotesk', sans-serif);
324
+ color: #fee9ff;
325
+ }
326
+
327
+ .bonus-incentive__headline {
328
+ margin: 0;
329
+ font-family: var(--ui-font-heading, 'Space Grotesk', 'Inter', sans-serif);
330
+ font-size: clamp(1.75rem, 4vw, 2.25rem);
331
+ line-height: 1.25;
332
+ font-weight: 700;
333
+ }
334
+
335
+ .bonus-incentive__description {
336
+ margin: 0;
337
+ font-size: clamp(1rem, 2.4vw, 1.1rem);
338
+ font-family: var(--ui-font-body, 'Inter', 'Space Grotesk', sans-serif);
339
+ line-height: 1.5;
340
+ color: rgba(227, 233, 255, 0.85);
341
+ }
342
+
343
+ .bonus-incentive__tagline {
344
+ margin: 0;
345
+ font-size: 0.78rem;
346
+ letter-spacing: 0.08em;
347
+ text-transform: uppercase;
348
+ font-family: var(--ui-font-body, 'Inter', 'Space Grotesk', sans-serif);
349
+ color: rgba(208, 215, 255, 0.7);
350
+ }
351
+
352
+ .bonus-incentive__metrics {
353
+ display: flex;
354
+ align-items: center;
355
+ justify-content: space-between;
356
+ gap: 16px;
357
+ flex-wrap: wrap;
358
+ }
359
+
360
+ .bonus-incentive__counter {
361
+ display: flex;
362
+ flex-direction: column;
363
+ gap: 4px;
364
+ }
365
+
366
+ .bonus-incentive__count-main {
367
+ font-size: clamp(1.4rem, 3.2vw, 1.8rem);
368
+ font-family: var(--ui-font-heading, 'Space Grotesk', 'Inter', sans-serif);
369
+ font-weight: 700;
370
+ letter-spacing: 0.06em;
371
+ }
372
+
373
+ .bonus-incentive__count-sub {
374
+ font-size: clamp(0.82rem, 2vw, 0.95rem);
375
+ font-family: var(--ui-font-body, 'Inter', 'Space Grotesk', sans-serif);
376
+ color: rgba(215, 225, 255, 0.75);
377
+ }
378
+
379
+ .bonus-incentive__countdown {
380
+ display: flex;
381
+ flex-direction: column;
382
+ gap: 2px;
383
+ text-align: right;
384
+ font-family: var(--ui-font-body, 'Inter', 'Space Grotesk', sans-serif);
385
+ }
386
+
387
+ .bonus-incentive__countdown-label {
388
+ font-size: 0.75rem;
389
+ text-transform: uppercase;
390
+ letter-spacing: 0.12em;
391
+ color: rgba(208, 215, 255, 0.65);
392
+ }
393
+
394
+ .bonus-incentive__countdown-value {
395
+ font-size: clamp(1rem, 2.6vw, 1.2rem);
396
+ font-weight: 600;
397
+ font-family: var(--ui-font-heading, 'Space Grotesk', 'Inter', sans-serif);
398
+ }
399
+
400
+ .bonus-incentive__progress {
401
+ position: relative;
402
+ width: 100%;
403
+ height: 6px;
404
+ border-radius: 999px;
405
+ background: rgba(255, 255, 255, 0.12);
406
+ overflow: hidden;
407
+ }
408
+
409
+ .bonus-incentive__progress-bar {
410
+ position: absolute;
411
+ top: 0;
412
+ left: 0;
413
+ bottom: 0;
414
+ border-radius: 999px;
415
+ background: linear-gradient(90deg, #ff4c94, #ffb648);
416
+ transition: width 0.4s ease;
417
+ }
418
+
419
+ .bonus-incentive__footnote {
420
+ margin: 0;
421
+ font-size: clamp(0.82rem, 2vw, 0.95rem);
422
+ font-family: var(--ui-font-body, 'Inter', 'Space Grotesk', sans-serif);
423
+ color: rgba(208, 215, 255, 0.7);
424
+ }
425
+
426
+ .bonus-incentive__cta {
427
+ align-self: flex-start;
428
+ display: inline-flex;
429
+ align-items: center;
430
+ gap: 6px;
431
+ padding: 0.55rem 1rem;
432
+ border-radius: 999px;
433
+ background: rgba(255, 255, 255, 0.12);
434
+ color: #f8f9ff;
435
+ font-size: 0.82rem;
436
+ font-family: var(--ui-font-body, 'Inter', 'Space Grotesk', sans-serif);
437
+ font-weight: 600;
438
+ text-decoration: none;
439
+ transition: background 0.2s ease, transform 0.2s ease;
440
+ }
441
+
442
+ .bonus-incentive__cta:hover,
443
+ .bonus-incentive__cta:focus {
444
+ background: rgba(255, 255, 255, 0.2);
445
+ transform: translateY(-1px);
446
+ }
447
+
448
+ .bonus-incentive__cta:focus {
449
+ outline: 2px solid rgba(255, 255, 255, 0.7);
450
+ outline-offset: 2px;
451
+ }
452
+
453
+ @media (max-width: 520px) {
454
+ .bonus-incentive {
455
+ padding: 20px;
456
+ }
457
+
458
+ .bonus-incentive__metrics {
459
+ flex-direction: column;
460
+ align-items: flex-start;
461
+ gap: 10px;
462
+ }
463
+
464
+ .bonus-incentive__countdown {
465
+ text-align: left;
466
+ align-items: flex-start;
467
+ }
468
+ }
469
+
470
+ @media (prefers-reduced-motion: reduce) {
471
+ .bonus-incentive__progress-bar,
472
+ .bonus-incentive__cta {
473
+ transition: none;
474
+ }
475
+ }
476
+ </style>