@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,507 @@
1
+ <template>
2
+ <section id="presale" class="ui-section" data-analytics-section="presale">
3
+ <div class="ui-section__container">
4
+ <div class="ui-section__grid">
5
+ <div class="ui-section__panel ui-section__panel--status">
6
+ <!-- Status column renders countdown + live price ahead of metrics for clear screen-reader order -->
7
+ <h3 class="section-title section-title--muted presale-status-title">
8
+ {{ panelTitles.status }}
9
+ </h3>
10
+ <div class="ui-section__card ui-section__card--status brand-card ui-card">
11
+ <Status />
12
+ </div>
13
+ </div>
14
+ <div class="ui-section__panel ui-section__panel--actions">
15
+ <div class="presale-tabs" role="tablist" aria-label="Presale actions">
16
+ <button
17
+ v-for="tab in tabConfigs"
18
+ :key="tab.id"
19
+ type="button"
20
+ class="presale-tab"
21
+ :class="{ 'presale-tab--active': activeTab === tab.id }"
22
+ role="tab"
23
+ :id="tabButtonId(tab.id)"
24
+ :aria-selected="activeTab === tab.id"
25
+ :aria-controls="tabPanelId(tab.id)"
26
+ :tabindex="activeTab === tab.id ? 0 : -1"
27
+ @click="setActiveTab(tab.id)"
28
+ @keydown="handleTabKeydown($event, tab.id)"
29
+ >
30
+ <span class="presale-tab__label">{{ tab.label }}</span>
31
+ </button>
32
+ </div>
33
+ <div
34
+ v-for="tab in tabConfigs"
35
+ :key="`${tab.id}-panel`"
36
+ :id="tabPanelId(tab.id)"
37
+ class="presale-tab-panel"
38
+ role="tabpanel"
39
+ :aria-labelledby="tabButtonId(tab.id)"
40
+ :aria-hidden="activeTab !== tab.id"
41
+ v-show="activeTab === tab.id"
42
+ >
43
+ <div
44
+ :class="[
45
+ 'ui-section__card',
46
+ tab.id === 'buy' ? 'ui-section__card--buy' : 'ui-section__card--stake',
47
+ 'brand-card',
48
+ 'ui-card',
49
+ ]"
50
+ >
51
+ <component :is="tab.component" />
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </section>
58
+ </template>
59
+
60
+ <script setup>
61
+ import { computed, inject, onBeforeUnmount, onMounted, provide, ref } from 'vue';
62
+ import Buy from './presale/Buy.vue';
63
+ import Stake from './presale/Stake.vue';
64
+ import Status from './presale/Status.vue';
65
+ import { usePresaleContext } from '../composables/usePresaleContext';
66
+ import { getTokenSymbol } from '../utils/tokenFormat';
67
+
68
+ defineProps({
69
+ content: {
70
+ type: Object,
71
+ default: null,
72
+ },
73
+ configKey: {
74
+ type: String,
75
+ default: null,
76
+ },
77
+ });
78
+
79
+ const pageContent = inject('pageContent', ref({}));
80
+
81
+ const { presalePulse, personalPresaleSummary } = usePresaleContext();
82
+
83
+ provide('presalePulse', presalePulse);
84
+ provide('personalPresaleSummary', personalPresaleSummary);
85
+
86
+ const tokenSymbol = getTokenSymbol();
87
+ const defaultBuyLabel = tokenSymbol ? `Buy ${tokenSymbol}` : 'Buy';
88
+
89
+ function resolvePanelTitle(value, fallback) {
90
+ if (typeof value === 'string') {
91
+ const trimmed = value.trim();
92
+ if (trimmed) return trimmed;
93
+ }
94
+ return fallback;
95
+ }
96
+
97
+ const panelTitles = computed(() => {
98
+ const titles = pageContent.value?.presale?.panelTitles || {};
99
+ return {
100
+ status: resolvePanelTitle(titles.status, 'Status'),
101
+ buy: resolvePanelTitle(titles.buy, defaultBuyLabel),
102
+ stake: resolvePanelTitle(titles.stake, 'Stake & Rewards'),
103
+ };
104
+ });
105
+
106
+ const activeTab = ref('buy');
107
+
108
+ const tabConfigs = computed(() => [
109
+ { id: 'buy', step: '2', label: panelTitles.value.buy, component: Buy },
110
+ { id: 'stake', step: '3', label: panelTitles.value.stake, component: Stake },
111
+ ]);
112
+
113
+ function tabButtonId(tabId) {
114
+ return `presale-tab-${tabId}`;
115
+ }
116
+
117
+ function tabPanelId(tabId) {
118
+ return `presale-panel-${tabId}`;
119
+ }
120
+
121
+ function setActiveTab(tabId) {
122
+ const availableIds = tabConfigs.value.map((tab) => tab.id);
123
+ if (!availableIds.includes(tabId)) return;
124
+ activeTab.value = tabId;
125
+ }
126
+ provide('setActivePresaleTab', setActiveTab);
127
+
128
+ function focusTabButton(tabId) {
129
+ if (typeof document === 'undefined') return;
130
+ const target = document.getElementById(tabButtonId(tabId));
131
+ if (target) target.focus();
132
+ }
133
+
134
+ function handleTabKeydown(event, currentId) {
135
+ if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') return;
136
+ event.preventDefault();
137
+ const ids = tabConfigs.value.map((tab) => tab.id);
138
+ const currentIndex = ids.indexOf(currentId);
139
+ if (currentIndex === -1) return;
140
+ const delta = event.key === 'ArrowRight' ? 1 : -1;
141
+ const nextIndex = (currentIndex + delta + ids.length) % ids.length;
142
+ const nextId = ids[nextIndex];
143
+ setActiveTab(nextId);
144
+ if (typeof window !== 'undefined') {
145
+ window.requestAnimationFrame(() => focusTabButton(nextId));
146
+ }
147
+ }
148
+
149
+ function normalizeHash(hash) {
150
+ return typeof hash === 'string' ? hash.trim().toLowerCase() : '';
151
+ }
152
+
153
+ function syncStateWithHash(hash) {
154
+ const normalized = normalizeHash(hash);
155
+ if (normalized === '#rightpresale' || normalized === '#stake') {
156
+ setActiveTab('stake');
157
+ } else if (normalized === '#centerpresale' || normalized === '#buy') {
158
+ setActiveTab('buy');
159
+ }
160
+ }
161
+
162
+ function handleHashChange() {
163
+ if (typeof window === 'undefined') return;
164
+ syncStateWithHash(window.location.hash);
165
+ }
166
+
167
+ onMounted(() => {
168
+ if (typeof window === 'undefined') return;
169
+ syncStateWithHash(window.location.hash);
170
+ window.addEventListener('hashchange', handleHashChange);
171
+ });
172
+
173
+ onBeforeUnmount(() => {
174
+ if (typeof window === 'undefined') return;
175
+ window.removeEventListener('hashchange', handleHashChange);
176
+ });
177
+
178
+ </script>
179
+
180
+ <style>
181
+ #presale {
182
+ --ui-card-padding: 24px;
183
+ --ui-card-gap: 24px;
184
+ --ui-gap-lg: 24px;
185
+ --ui-gap-md: 16px;
186
+ --ui-gap-tight: 8px;
187
+ --ui-radius-card: var(--brand-card-radius, 24px);
188
+ --ui-shadow-card: var(--brand-surface-card-shadow, 0 8px 24px rgba(15, 23, 42, 0.12));
189
+ --ui-type-title: 1.75rem;
190
+ --ui-type-subheadline: 1.25rem;
191
+ --ui-type-value: 1.5rem;
192
+ --ui-type-body: 1rem;
193
+ --ui-type-caption: 0.8125rem;
194
+ --ui-font-heading: 'Space Grotesk', 'Inter', sans-serif;
195
+ --ui-font-body: 'Inter', 'Space Grotesk', sans-serif;
196
+ --ui-title-spacing: 20px;
197
+ }
198
+ </style>
199
+
200
+ <style scoped>
201
+
202
+ #presale {
203
+ --presale-grid-gap: clamp(24px, 3vw, 32px);
204
+ }
205
+
206
+ .ui-section__container {
207
+ width: min(var(--layout-container-max-width, min(1180px, 92vw)), 100%);
208
+ margin-left: auto;
209
+ margin-right: auto;
210
+ padding-left: var(--layout-container-padding, clamp(16px, 4vw, 48px));
211
+ padding-right: var(--layout-container-padding, clamp(16px, 4vw, 48px));
212
+ box-sizing: border-box;
213
+ }
214
+
215
+ .ui-section__grid {
216
+ display: grid;
217
+ grid-template-columns: minmax(0, 1fr);
218
+ gap: var(--presale-grid-gap);
219
+ margin-bottom: clamp(32px, 6vw, 64px);
220
+ }
221
+
222
+ .ui-section__panel {
223
+ min-width: 0;
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: var(--ui-gap-md, 16px);
227
+ height: 100%;
228
+ }
229
+
230
+ .presale-tabs {
231
+ display: inline-flex;
232
+ flex-wrap: wrap;
233
+ align-items: center;
234
+ gap: 12px;
235
+ padding: 8px;
236
+ border-radius: 999px;
237
+ border: 1px solid var(--tabs-border, color-mix(in srgb, var(--brand-border-highlight, rgba(255, 255, 255, 0.08)) 90%, transparent));
238
+ background: color-mix(in srgb, var(--brand-card-soft, rgba(0, 0, 0, 0.4)) 92%, transparent);
239
+ box-shadow: var(--tabs-shadow, 0 16px 36px color-mix(in srgb, var(--brand-shadow-color, rgba(0, 0, 0, 0.35)) 65%, transparent));
240
+ }
241
+
242
+ .presale-tab {
243
+ display: inline-flex;
244
+ align-items: center;
245
+ gap: 12px;
246
+ padding: 10px 22px;
247
+ border: none;
248
+ border-radius: 999px;
249
+ background: transparent;
250
+ color: var(--tab-color, var(--brand-fg-300, #a798b0));
251
+ font-family: var(--ui-font-heading);
252
+ font-size: 0.95rem;
253
+ font-weight: 600;
254
+ letter-spacing: 0.12em;
255
+ text-transform: uppercase;
256
+ cursor: pointer;
257
+ transition: color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
258
+ }
259
+
260
+ .presale-tab:hover {
261
+ color: var(--brand-fg-100, #f5f5f5);
262
+ transform: translateY(-1px);
263
+ }
264
+
265
+ .presale-tab:focus-visible {
266
+ outline: 2px solid var(--brand-accent-electric, #27f3ff);
267
+ outline-offset: 3px;
268
+ }
269
+
270
+ .presale-tab__step {
271
+ display: inline-flex;
272
+ align-items: center;
273
+ justify-content: center;
274
+ width: 28px;
275
+ height: 28px;
276
+ border-radius: 999px;
277
+ border: 1px solid var(--tab-step-border, rgba(255, 255, 255, 0.18));
278
+ background: var(--tab-step-bg, rgba(255, 255, 255, 0.06));
279
+ font-size: 0.75rem;
280
+ font-weight: 600;
281
+ letter-spacing: 0.12em;
282
+ text-transform: uppercase;
283
+ transition: border 0.2s ease, background 0.2s ease, color 0.2s ease;
284
+ color: var(--tab-step-color, rgba(255, 255, 255, 0.72));
285
+ }
286
+
287
+ .presale-tab__label {
288
+ white-space: nowrap;
289
+ }
290
+
291
+ .presale-tab--active {
292
+ background: linear-gradient(
293
+ 120deg,
294
+ color-mix(in srgb, var(--brand-accent-secondary, #ff6edc) 32%, transparent),
295
+ color-mix(in srgb, var(--brand-accent-primary, #27f3ff) 22%, transparent)
296
+ );
297
+ color: var(--tab-active-color, var(--brand-fg-100, #f5f5f5));
298
+ box-shadow: var(--tab-active-shadow, 0 18px 42px color-mix(in srgb, var(--brand-accent-primary, #27f3ff) 18%, transparent));
299
+ }
300
+
301
+ .presale-tab--active .presale-tab__step {
302
+ background: color-mix(in srgb, var(--brand-accent-primary, #27f3ff) 18%, transparent);
303
+ border-color: color-mix(in srgb, var(--brand-accent-primary, #27f3ff) 42%, transparent);
304
+ color: var(--tab-active-step-color, var(--brand-accent-primary, #27f3ff));
305
+ }
306
+
307
+ .presale-tab-panel {
308
+ display: flex;
309
+ flex-direction: column;
310
+ gap: var(--ui-gap-tight, 4px);
311
+ margin-top: -20px;
312
+ }
313
+
314
+ .section-title {
315
+ margin: var(--ui-title-spacing) 0 0;
316
+ font-family: var(--ui-font-heading);
317
+ font-weight: 700;
318
+ font-size: var(--ui-type-title);
319
+ line-height: 1.2;
320
+ letter-spacing: 0.015em;
321
+ text-transform: uppercase;
322
+ color: var(--ui-text-primary, var(--brand-fg-100, #f5f5f5));
323
+ }
324
+
325
+ .section-title--muted {
326
+ color: var(--ui-text-muted, var(--brand-fg-300, #a798b0));
327
+ font-weight: 600;
328
+ }
329
+
330
+ .presale-status-title {
331
+ display: block;
332
+ color: var(
333
+ --ui-status-heading-color,
334
+ var(--ui-text-muted, var(--brand-fg-300, #a798b0))
335
+ );
336
+ }
337
+
338
+ .ui-section__card--buy,
339
+ .ui-section__card--stake {
340
+ position: relative;
341
+ overflow: hidden;
342
+ }
343
+
344
+ .ui-section__card {
345
+ flex: 1 1 auto;
346
+ }
347
+
348
+ .presale-scroll-highlight {
349
+ position: relative;
350
+ }
351
+
352
+ .presale-scroll-highlight::after {
353
+ content: '';
354
+ position: absolute;
355
+ inset: -10px;
356
+ border-radius: calc(var(--ui-radius-card, 24px) + 10px);
357
+ border: 1px solid color-mix(in srgb, var(--brand-accent-electric, #27f3ff) 85%, transparent);
358
+ box-shadow:
359
+ 0 0 32px color-mix(in srgb, var(--brand-accent-electric, #27f3ff) 35%, transparent),
360
+ 0 0 72px color-mix(in srgb, var(--brand-neon-pink, #ff2d86) 25%, transparent);
361
+ opacity: 0;
362
+ animation: presaleCardPulse 1.25s ease-out forwards;
363
+ pointer-events: none;
364
+ z-index: 0;
365
+ }
366
+ @keyframes presaleCardPulse {
367
+ 0% {
368
+ opacity: 0;
369
+ box-shadow: 0 0 0 0 rgba(39, 243, 255, 0);
370
+ }
371
+ 35% {
372
+ opacity: 1;
373
+ }
374
+ 100% {
375
+ opacity: 0;
376
+ box-shadow: 0 0 0 0 rgba(39, 243, 255, 0);
377
+ }
378
+ }
379
+
380
+ @media (prefers-reduced-motion: reduce) {
381
+ .presale-scroll-highlight::after {
382
+ animation-duration: 0.01s;
383
+ }
384
+ }
385
+
386
+ @media (min-width: 768px) {
387
+ .ui-section__grid {
388
+ grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
389
+ }
390
+
391
+ .ui-section__panel--status {
392
+ position: sticky;
393
+ top: 80px;
394
+ }
395
+ }
396
+
397
+ @media (min-width: 1280px) {
398
+ .ui-section__panel--actions {
399
+ align-self: flex-start;
400
+ }
401
+
402
+ .ui-section__card--buy {
403
+ position: sticky;
404
+ top: 108px;
405
+ z-index: var(--layer-sticky-card, 0);
406
+ }
407
+ }
408
+
409
+ @media (max-width: 768px) {
410
+ .presale-tabs {
411
+ width: 100%;
412
+ justify-content: center;
413
+ gap: 8px;
414
+ padding: 6px;
415
+ }
416
+
417
+ .presale-tab {
418
+ flex: 1 1 calc(50% - 8px);
419
+ justify-content: center;
420
+ padding: 10px 16px;
421
+ font-size: 0.85rem;
422
+ letter-spacing: 0.1em;
423
+ }
424
+
425
+ .presale-tab__label {
426
+ white-space: normal;
427
+ text-align: center;
428
+ }
429
+
430
+ .presale-tab__step {
431
+ width: 24px;
432
+ height: 24px;
433
+ font-size: 0.6875rem;
434
+ }
435
+ }
436
+
437
+ @media (max-width: 640px) {
438
+ .ui-section {
439
+ padding: 28px 0 36px;
440
+ --ui-title-spacing: 16px;
441
+ scroll-margin-top: 76px;
442
+ }
443
+
444
+ .ui-section__grid {
445
+ gap: var(--ui-gap-tight, 8px);
446
+ }
447
+
448
+ .presale-tab-panel {
449
+ margin-top: -22px;
450
+ }
451
+
452
+ .ui-section__inner {
453
+ padding-inline: 16px;
454
+ }
455
+
456
+ .section-title {
457
+ font-size: 1.5rem;
458
+ letter-spacing: 0.01em;
459
+ }
460
+
461
+ .presale-status-title {
462
+ opacity: 0;
463
+ }
464
+ }
465
+
466
+ @media (max-width: 480px) {
467
+ .presale-tab {
468
+ flex: 1 1 100%;
469
+ font-size: 0.78rem;
470
+ letter-spacing: 0.08em;
471
+ }
472
+
473
+ .section-title {
474
+ font-size: 1.4rem;
475
+ }
476
+ }
477
+
478
+ @media (max-width: 960px) and (min-width: 641px) {
479
+ .ui-section {
480
+ padding-top: 78px;
481
+ scroll-margin-top: clamp(124px, 19vh, 180px);
482
+ }
483
+ }
484
+
485
+ :global(.brand-card) {
486
+ background: var(--brand-surface-card-bg, #ffffff);
487
+ border: 1px solid var(--brand-surface-card-border, rgba(79, 108, 240, 0.16));
488
+ border-radius: var(--ui-radius-card, 24px);
489
+ padding: var(--ui-card-padding, 24px);
490
+ box-shadow: var(--ui-shadow-card, var(--brand-surface-card-shadow, 0 18px 40px rgba(15, 23, 42, 0.12)));
491
+ font-family: var(--ui-font-body, 'Inter', 'Space Grotesk', sans-serif);
492
+ color: var(--brand-fg-200, #334261);
493
+ line-height: 1.6;
494
+ }
495
+
496
+ @media (max-width: 1024px) {
497
+ :global(.brand-card) {
498
+ padding: 20px;
499
+ }
500
+ }
501
+
502
+ @media (max-width: 600px) {
503
+ :global(.brand-card) {
504
+ padding: 16px;
505
+ }
506
+ }
507
+ </style>