@oasisomniverse/js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/package.json +36 -0
  2. package/src/add-data.js +54 -0
  3. package/src/avatar-connect.js +126 -0
  4. package/src/avatar-wallet.js +60 -0
  5. package/src/coming-soon.js +35 -0
  6. package/src/confirmation.js +53 -0
  7. package/src/contact.js +84 -0
  8. package/src/cross-chain-management.js +36 -0
  9. package/src/edit-avatar.js +79 -0
  10. package/src/eggs.js +88 -0
  11. package/src/forgot-password.js +73 -0
  12. package/src/index.esm.js +15 -0
  13. package/src/index.js +15 -0
  14. package/src/karma-toast.js +50 -0
  15. package/src/load-data.js +56 -0
  16. package/src/login.js +154 -0
  17. package/src/manage-data.js +42 -0
  18. package/src/manage-eggs.js +41 -0
  19. package/src/manage-mission.js +42 -0
  20. package/src/manage-oasis-nft.js +41 -0
  21. package/src/message.js +57 -0
  22. package/src/navbar.js +80 -0
  23. package/src/oasis-modal.js +79 -0
  24. package/src/off-chain-management.js +36 -0
  25. package/src/provider-dropdown.js +87 -0
  26. package/src/purchase-oasis-nft.js +54 -0
  27. package/src/purchase-oasis-virtual-land-nft.js +54 -0
  28. package/src/reset-password.js +75 -0
  29. package/src/search-avatar.js +78 -0
  30. package/src/search-data.js +59 -0
  31. package/src/search-eggs.js +49 -0
  32. package/src/search-karma.js +48 -0
  33. package/src/search-mission.js +50 -0
  34. package/src/search-oasis-nft.js +49 -0
  35. package/src/search-profiles.js +50 -0
  36. package/src/seeds.js +83 -0
  37. package/src/signup.js +154 -0
  38. package/src/star-field.js +68 -0
  39. package/src/verify-email.js +77 -0
  40. package/src/view-achievements.js +42 -0
  41. package/src/view-avatar-karma.js +50 -0
  42. package/src/view-avatar.js +69 -0
  43. package/src/view-eggs.js +44 -0
  44. package/src/view-karma.js +41 -0
  45. package/src/view-leagues.js +41 -0
  46. package/src/view-mission.js +44 -0
  47. package/src/view-oasis-nft.js +46 -0
  48. package/src/view-tournaments.js +41 -0
  49. package/src/vote-karma.js +61 -0
package/src/signup.js ADDED
@@ -0,0 +1,154 @@
1
+ /**
2
+ * OasisSignup — new avatar registration popup widget
3
+ * Usage: const w = new OasisSignup({ onSuccess: (data) => {} }); w.open();
4
+ */
5
+ class OasisSignup {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onSuccess = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl;
8
+ this._onSuccess = onSuccess;
9
+ this._onClose = onClose;
10
+ this._overlay = null;
11
+ this._injectStyles();
12
+ if (container) this._render(container);
13
+ }
14
+
15
+ _injectStyles() {
16
+ if (document.getElementById('oasis-popup-styles')) return;
17
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
18
+ s.textContent = `
19
+ .oasis-popup-overlay{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s}
20
+ .oasis-popup-overlay.is-open{opacity:1}
21
+ .oasis-popup-modal{background:#0a1628;border:1px solid rgba(0,200,255,.2);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.6);width:90%;max-width:480px;max-height:90vh;overflow-y:auto;transform:translateY(16px);transition:transform .25s}
22
+ .oasis-popup-overlay.is-open .oasis-popup-modal{transform:none}
23
+ .oasis-popup-modal.lg{max-width:900px}
24
+ .oasis-popup-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(0,200,255,.1)}
25
+ .oasis-popup-title{font-family:'Orbitron',sans-serif;font-size:14px;font-weight:700;color:#00c8ff;letter-spacing:.08em}
26
+ .oasis-popup-close{background:none;border:none;color:#7a9bbf;font-size:22px;cursor:pointer;padding:0;line-height:1;transition:color .2s}
27
+ .oasis-popup-close:hover{color:#fff}
28
+ .oasis-popup-body{padding:24px}
29
+ .oasis-field{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}
30
+ .oasis-label{font-size:11px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase}
31
+ .oasis-input,.oasis-select,.oasis-textarea{background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.2);border-radius:8px;padding:10px 14px;color:#fff;font-size:14px;outline:none;transition:border-color .2s;width:100%;box-sizing:border-box}
32
+ .oasis-input:focus,.oasis-select:focus,.oasis-textarea:focus{border-color:rgba(0,200,255,.5)}
33
+ .oasis-textarea{resize:vertical;min-height:80px}
34
+ .oasis-select option{background:#0a1628}
35
+ .oasis-btn{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;letter-spacing:.08em;padding:11px 20px;cursor:pointer;transition:opacity .2s;width:100%}
36
+ .oasis-btn:hover{opacity:.85}
37
+ .oasis-btn:disabled{opacity:.5;cursor:not-allowed}
38
+ .oasis-btn-ghost{background:rgba(0,200,255,.1);border:1px solid rgba(0,200,255,.3);color:#00c8ff;border-radius:8px;font-size:12px;font-weight:600;padding:9px 16px;cursor:pointer;transition:background .2s}
39
+ .oasis-btn-ghost:hover{background:rgba(0,200,255,.2)}
40
+ .oasis-btn-row{display:flex;gap:10px;justify-content:flex-end;margin-top:20px}
41
+ .oasis-msg{border-radius:8px;padding:10px 14px;font-size:13px;margin-bottom:16px}
42
+ .oasis-msg-error{background:rgba(255,80,80,.12);border:1px solid rgba(255,80,80,.3);color:#ff6b6b}
43
+ .oasis-msg-success{background:rgba(0,200,100,.12);border:1px solid rgba(0,200,100,.3);color:#00c864}
44
+ .oasis-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
45
+ .oasis-info-block{background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:16px;margin-bottom:12px}
46
+ .oasis-info-label{font-size:10px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;margin-bottom:4px}
47
+ .oasis-info-value{color:#fff;font-size:14px;font-weight:500}
48
+ .oasis-cs{text-align:center;padding:32px 16px}
49
+ .oasis-cs-icon{font-size:48px;margin-bottom:12px}
50
+ .oasis-cs h3{font-family:'Orbitron',sans-serif;font-size:15px;color:#00c8ff;margin:0 0 8px}
51
+ .oasis-cs p{color:#7a9bbf;font-size:13px;margin:0}
52
+ .oasis-table{width:100%;border-collapse:collapse;font-size:13px}
53
+ .oasis-table th{text-align:left;padding:10px 12px;font-size:10px;font-weight:700;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;border-bottom:1px solid rgba(0,200,255,.1)}
54
+ .oasis-table td{padding:10px 12px;color:#fff;border-bottom:1px solid rgba(255,255,255,.05)}
55
+ .oasis-table tr:hover td{background:rgba(0,200,255,.04)}
56
+ .oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700}
57
+ .oasis-badge-blue{background:rgba(0,200,255,.15);color:#00c8ff}
58
+ .oasis-badge-green{background:rgba(0,200,100,.15);color:#00c864}
59
+ .oasis-badge-red{background:rgba(255,80,80,.15);color:#ff6b6b}
60
+ .oasis-badge-yellow{background:rgba(255,200,0,.15);color:#ffc800}
61
+ .oasis-search-row{display:flex;gap:10px;margin-bottom:16px}
62
+ .oasis-search-row .oasis-input{flex:1}
63
+ .oasis-stat-row{display:flex;gap:12px;margin-bottom:20px}
64
+ .oasis-stat{flex:1;background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:14px;text-align:center}
65
+ .oasis-stat-val{font-family:'Orbitron',sans-serif;font-size:20px;font-weight:700;color:#00c8ff}
66
+ .oasis-stat-lbl{font-size:10px;color:#7a9bbf;margin-top:4px;letter-spacing:.04em}
67
+ .oasis-divider{border:none;border-top:1px solid rgba(0,200,255,.1);margin:16px 0}
68
+ .oasis-section-title{font-size:11px;font-weight:700;color:#7a9bbf;letter-spacing:.08em;text-transform:uppercase;margin:0 0 12px}
69
+ .oasis-radio-group{display:flex;gap:16px;margin-bottom:16px}
70
+ .oasis-radio-label{display:flex;align-items:center;gap:8px;font-size:13px;color:#fff;cursor:pointer}
71
+ .oasis-radio-label input[type=radio]{accent-color:#00c8ff}
72
+ .oasis-link{background:none;border:none;color:#00c8ff;font-size:13px;cursor:pointer;padding:0;text-decoration:underline;text-underline-offset:2px}
73
+ .oasis-chat-area{background:rgba(0,0,0,.2);border:1px solid rgba(0,200,255,.1);border-radius:10px;height:220px;overflow-y:auto;padding:12px;margin-bottom:12px;display:flex;flex-direction:column;gap:8px}
74
+ .oasis-chat-bubble{background:rgba(0,200,255,.1);border-radius:10px;padding:8px 12px;font-size:13px;color:#fff;max-width:75%}
75
+ .oasis-chat-bubble.mine{background:rgba(0,128,255,.2);align-self:flex-end}
76
+ .oasis-chat-input-row{display:flex;gap:8px}
77
+ .oasis-chat-input-row .oasis-input{flex:1}
78
+ `;
79
+ document.head.appendChild(s);
80
+ }
81
+
82
+ open() {
83
+ if (this._overlay) return;
84
+ this._overlay = document.createElement('div');
85
+ this._overlay.className = 'oasis-popup-overlay';
86
+ this._overlay.innerHTML = `<div class="oasis-popup-modal">
87
+ <div class="oasis-popup-header"><span class="oasis-popup-title">Create Avatar</span><button class="oasis-popup-close">×</button></div>
88
+ <div class="oasis-popup-body">${this._buildBody()}</div>
89
+ </div>`;
90
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
91
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
92
+ document.body.appendChild(this._overlay);
93
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
94
+ this._attachEvents();
95
+ }
96
+
97
+ _buildBody() {
98
+ return `
99
+ <div id="oasis-signup-msg"></div>
100
+ <div class="oasis-grid-2">
101
+ <div class="oasis-field"><label class="oasis-label">First Name</label><input class="oasis-input" id="oasis-su-first" type="text" placeholder="Ada" /></div>
102
+ <div class="oasis-field"><label class="oasis-label">Last Name</label><input class="oasis-input" id="oasis-su-last" type="text" placeholder="Lovelace" /></div>
103
+ </div>
104
+ <div class="oasis-field"><label class="oasis-label">Username</label><input class="oasis-input" id="oasis-su-user" type="text" placeholder="ada_lovelace" autocomplete="username" /></div>
105
+ <div class="oasis-field"><label class="oasis-label">Email</label><input class="oasis-input" id="oasis-su-email" type="email" placeholder="ada@example.com" /></div>
106
+ <div class="oasis-field"><label class="oasis-label">Password</label><input class="oasis-input" id="oasis-su-pass" type="password" placeholder="••••••••" /></div>
107
+ <div class="oasis-field"><label class="oasis-label">Confirm Password</label><input class="oasis-input" id="oasis-su-pass2" type="password" placeholder="••••••••" /></div>
108
+ <button class="oasis-btn" id="oasis-su-submit" style="margin-top:4px">Create Avatar</button>
109
+ `;
110
+ }
111
+
112
+ _attachEvents() {
113
+ this._overlay.querySelector('#oasis-su-submit').addEventListener('click', () => this._doSignup());
114
+ }
115
+
116
+ async _doSignup() {
117
+ const f = (id) => this._overlay.querySelector(id).value.trim();
118
+ const msgEl = this._overlay.querySelector('#oasis-signup-msg');
119
+ const btn = this._overlay.querySelector('#oasis-su-submit');
120
+ if (!f('#oasis-su-user') || !f('#oasis-su-email') || !f('#oasis-su-pass')) {
121
+ msgEl.innerHTML = '<div class="oasis-msg oasis-msg-error">Please fill in all required fields.</div>'; return;
122
+ }
123
+ if (this._overlay.querySelector('#oasis-su-pass').value !== this._overlay.querySelector('#oasis-su-pass2').value) {
124
+ msgEl.innerHTML = '<div class="oasis-msg oasis-msg-error">Passwords do not match.</div>'; return;
125
+ }
126
+ btn.textContent = 'Creating…'; btn.disabled = true; msgEl.innerHTML = '';
127
+ try {
128
+ const res = await fetch(`${this._apiUrl}/api/avatar/register`, {
129
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
130
+ body: JSON.stringify({ firstName: f('#oasis-su-first'), lastName: f('#oasis-su-last'), username: f('#oasis-su-user'), email: f('#oasis-su-email'), password: this._overlay.querySelector('#oasis-su-pass').value })
131
+ });
132
+ const data = await res.json();
133
+ if (!res.ok || data.isError) throw new Error(data.message || 'Registration failed');
134
+ msgEl.innerHTML = '<div class="oasis-msg oasis-msg-success">Avatar created! Check your email to verify.</div>';
135
+ if (this._onSuccess) this._onSuccess(data);
136
+ } catch (e) {
137
+ msgEl.innerHTML = `<div class="oasis-msg oasis-msg-error">${e.message}</div>`;
138
+ btn.textContent = 'Create Avatar'; btn.disabled = false;
139
+ }
140
+ }
141
+
142
+ close() {
143
+ if (!this._overlay) return;
144
+ const el = this._overlay; this._overlay = null;
145
+ el.classList.remove('is-open');
146
+ el.addEventListener('transitionend', () => el.remove(), { once: true });
147
+ if (this._onClose) this._onClose();
148
+ }
149
+
150
+ _render(container) { container.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
151
+ destroy() { this.close(); }
152
+ }
153
+
154
+ window.OasisSignup = OasisSignup;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * OasisStarField — animated canvas starfield background
3
+ * Usage:
4
+ * const sf = new OasisStarField({ container: document.body, starCount: 160 })
5
+ * sf.destroy() // clean up
6
+ */
7
+ class OasisStarField {
8
+ constructor({ container = document.body, starCount = 160, speed = 0.3 } = {}) {
9
+ this._container = container;
10
+ this._starCount = starCount;
11
+ this._speed = speed;
12
+ this._stars = [];
13
+ this._raf = null;
14
+
15
+ this._canvas = document.createElement('canvas');
16
+ Object.assign(this._canvas.style, {
17
+ position: 'fixed', inset: '0', width: '100%', height: '100%',
18
+ pointerEvents: 'none', zIndex: '0'
19
+ });
20
+ this._ctx = this._canvas.getContext('2d');
21
+ container.prepend(this._canvas);
22
+
23
+ this._resize = this._resize.bind(this);
24
+ this._tick = this._tick.bind(this);
25
+ window.addEventListener('resize', this._resize);
26
+ this._resize();
27
+ this._init();
28
+ this._raf = requestAnimationFrame(this._tick);
29
+ }
30
+
31
+ _resize() {
32
+ this._canvas.width = window.innerWidth;
33
+ this._canvas.height = window.innerHeight;
34
+ }
35
+
36
+ _init() {
37
+ this._stars = Array.from({ length: this._starCount }, () => ({
38
+ x: Math.random() * window.innerWidth,
39
+ y: Math.random() * window.innerHeight,
40
+ r: Math.random() * 1.4 + 0.3,
41
+ o: Math.random() * 0.6 + 0.2,
42
+ dy: (Math.random() * 0.3 + 0.1) * this._speed
43
+ }));
44
+ }
45
+
46
+ _tick() {
47
+ const { width, height } = this._canvas;
48
+ const ctx = this._ctx;
49
+ ctx.clearRect(0, 0, width, height);
50
+ for (const s of this._stars) {
51
+ ctx.beginPath();
52
+ ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
53
+ ctx.fillStyle = `rgba(200,230,255,${s.o})`;
54
+ ctx.fill();
55
+ s.y -= s.dy;
56
+ if (s.y < -2) { s.y = height + 2; s.x = Math.random() * width; }
57
+ }
58
+ this._raf = requestAnimationFrame(this._tick);
59
+ }
60
+
61
+ destroy() {
62
+ cancelAnimationFrame(this._raf);
63
+ window.removeEventListener('resize', this._resize);
64
+ this._canvas.remove();
65
+ }
66
+ }
67
+
68
+ window.OasisStarField = OasisStarField;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * OasisVerifyEmail — email verification popup widget
3
+ * Usage: const w = new OasisVerifyEmail({ token: 'abc' }); w.open();
4
+ */
5
+ class OasisVerifyEmail {
6
+ constructor({ container, token = '', apiUrl = 'https://api.web4.oasisomniverse.one', onSuccess = null, onClose = null } = {}) {
7
+ this._token = token; this._apiUrl = apiUrl; this._onSuccess = onSuccess; this._onClose = onClose; this._overlay = null;
8
+ this._injectStyles();
9
+ if (container) this._render(container);
10
+ }
11
+
12
+ _injectStyles() {
13
+ if (document.getElementById('oasis-popup-styles')) return;
14
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
15
+ s.textContent = `.oasis-popup-overlay{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s}.oasis-popup-overlay.is-open{opacity:1}.oasis-popup-modal{background:#0a1628;border:1px solid rgba(0,200,255,.2);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.6);width:90%;max-width:480px;max-height:90vh;overflow-y:auto;transform:translateY(16px);transition:transform .25s}.oasis-popup-overlay.is-open .oasis-popup-modal{transform:none}.oasis-popup-modal.lg{max-width:900px}.oasis-popup-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-popup-title{font-family:'Orbitron',sans-serif;font-size:14px;font-weight:700;color:#00c8ff;letter-spacing:.08em}.oasis-popup-close{background:none;border:none;color:#7a9bbf;font-size:22px;cursor:pointer;padding:0;line-height:1}.oasis-popup-close:hover{color:#fff}.oasis-popup-body{padding:24px}.oasis-field{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}.oasis-label{font-size:11px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase}.oasis-input,.oasis-select,.oasis-textarea{background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.2);border-radius:8px;padding:10px 14px;color:#fff;font-size:14px;outline:none;transition:border-color .2s;width:100%;box-sizing:border-box}.oasis-input:focus{border-color:rgba(0,200,255,.5)}.oasis-btn{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;letter-spacing:.08em;padding:11px 20px;cursor:pointer;transition:opacity .2s;width:100%}.oasis-btn:hover{opacity:.85}.oasis-btn:disabled{opacity:.5;cursor:not-allowed}.oasis-msg{border-radius:8px;padding:10px 14px;font-size:13px;margin-bottom:16px}.oasis-msg-error{background:rgba(255,80,80,.12);border:1px solid rgba(255,80,80,.3);color:#ff6b6b}.oasis-msg-success{background:rgba(0,200,100,.12);border:1px solid rgba(0,200,100,.3);color:#00c864}.oasis-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}.oasis-info-block{background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:16px;margin-bottom:12px}.oasis-info-label{font-size:10px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;margin-bottom:4px}.oasis-info-value{color:#fff;font-size:14px;font-weight:500}.oasis-cs{text-align:center;padding:32px 16px}.oasis-cs-icon{font-size:48px;margin-bottom:12px}.oasis-cs h3{font-family:'Orbitron',sans-serif;font-size:15px;color:#00c8ff;margin:0 0 8px}.oasis-cs p{color:#7a9bbf;font-size:13px;margin:0}.oasis-table{width:100%;border-collapse:collapse;font-size:13px}.oasis-table th{text-align:left;padding:10px 12px;font-size:10px;font-weight:700;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-table td{padding:10px 12px;color:#fff;border-bottom:1px solid rgba(255,255,255,.05)}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700}.oasis-badge-blue{background:rgba(0,200,255,.15);color:#00c8ff}.oasis-badge-green{background:rgba(0,200,100,.15);color:#00c864}.oasis-badge-red{background:rgba(255,80,80,.15);color:#ff6b6b}.oasis-search-row{display:flex;gap:10px;margin-bottom:16px}.oasis-search-row .oasis-input{flex:1}.oasis-stat-row{display:flex;gap:12px;margin-bottom:20px}.oasis-stat{flex:1;background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:14px;text-align:center}.oasis-stat-val{font-family:'Orbitron',sans-serif;font-size:20px;font-weight:700;color:#00c8ff}.oasis-stat-lbl{font-size:10px;color:#7a9bbf;margin-top:4px}.oasis-divider{border:none;border-top:1px solid rgba(0,200,255,.1);margin:16px 0}.oasis-section-title{font-size:11px;font-weight:700;color:#7a9bbf;letter-spacing:.08em;text-transform:uppercase;margin:0 0 12px}.oasis-radio-group{display:flex;gap:16px;margin-bottom:16px}.oasis-radio-label{display:flex;align-items:center;gap:8px;font-size:13px;color:#fff;cursor:pointer}.oasis-radio-label input[type=radio]{accent-color:#00c8ff}.oasis-link{background:none;border:none;color:#00c8ff;font-size:13px;cursor:pointer;padding:0;text-decoration:underline}.oasis-chat-area{background:rgba(0,0,0,.2);border:1px solid rgba(0,200,255,.1);border-radius:10px;height:220px;overflow-y:auto;padding:12px;margin-bottom:12px;display:flex;flex-direction:column;gap:8px}.oasis-chat-bubble{background:rgba(0,200,255,.1);border-radius:10px;padding:8px 12px;font-size:13px;color:#fff;max-width:75%}.oasis-chat-bubble.mine{background:rgba(0,128,255,.2);align-self:flex-end}.oasis-chat-input-row{display:flex;gap:8px}.oasis-chat-input-row .oasis-input{flex:1}`;
16
+ document.head.appendChild(s);
17
+ }
18
+
19
+ open() {
20
+ if (this._overlay) return;
21
+ this._overlay = document.createElement('div');
22
+ this._overlay.className = 'oasis-popup-overlay';
23
+ this._overlay.innerHTML = `<div class="oasis-popup-modal">
24
+ <div class="oasis-popup-header"><span class="oasis-popup-title">Verify Email</span><button class="oasis-popup-close">×</button></div>
25
+ <div class="oasis-popup-body">${this._buildBody()}</div>
26
+ </div>`;
27
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
28
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
29
+ document.body.appendChild(this._overlay);
30
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
31
+ const btn = this._overlay.querySelector('#oasis-ve-submit');
32
+ if (btn) btn.addEventListener('click', () => this._doVerify());
33
+ if (this._token) this._doVerify();
34
+ }
35
+
36
+ _buildBody() {
37
+ return `
38
+ <div id="oasis-ve-msg"></div>
39
+ <div class="oasis-cs">
40
+ <div class="oasis-cs-icon">📧</div>
41
+ <h3>Verify Your Email</h3>
42
+ <p style="margin-bottom:20px">${this._token ? 'Verifying your email address…' : 'Enter the verification code sent to your email.'}</p>
43
+ </div>
44
+ ${this._token ? '' : `<div class="oasis-field"><label class="oasis-label">Verification Code</label><input class="oasis-input" id="oasis-ve-code" type="text" placeholder="Enter 6-digit code" /></div><button class="oasis-btn" id="oasis-ve-submit">Verify Email</button>`}
45
+ `;
46
+ }
47
+
48
+ async _doVerify() {
49
+ const code = this._token || (this._overlay.querySelector('#oasis-ve-code')?.value.trim());
50
+ const msgEl = this._overlay.querySelector('#oasis-ve-msg');
51
+ const btn = this._overlay.querySelector('#oasis-ve-submit');
52
+ if (btn) { btn.textContent = 'Verifying…'; btn.disabled = true; }
53
+ try {
54
+ const res = await fetch(`${this._apiUrl}/api/avatar/verify-email`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: code }) });
55
+ const data = await res.json();
56
+ if (!res.ok || data.isError) throw new Error(data.message || 'Verification failed');
57
+ msgEl.innerHTML = '<div class="oasis-msg oasis-msg-success">Email verified successfully!</div>';
58
+ if (this._onSuccess) this._onSuccess(data);
59
+ } catch (e) {
60
+ msgEl.innerHTML = `<div class="oasis-msg oasis-msg-error">${e.message}</div>`;
61
+ if (btn) { btn.textContent = 'Verify Email'; btn.disabled = false; }
62
+ }
63
+ }
64
+
65
+ close() {
66
+ if (!this._overlay) return;
67
+ const el = this._overlay; this._overlay = null;
68
+ el.classList.remove('is-open');
69
+ el.addEventListener('transitionend', () => el.remove(), { once: true });
70
+ if (this._onClose) this._onClose();
71
+ }
72
+
73
+ _render(container) { container.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
74
+ destroy() { this.close(); }
75
+ }
76
+
77
+ window.OasisVerifyEmail = OasisVerifyEmail;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * OasisViewAchievements — view avatar achievements popup widget
3
+ * Usage: const w = new OasisViewAchievements({ avatarId: '...' }); w.open();
4
+ */
5
+ class OasisViewAchievements {
6
+ constructor({ container, avatarId = '', apiUrl = 'https://api.web4.oasisomniverse.one', onClose = null } = {}) {
7
+ this._avatarId = avatarId; this._apiUrl = apiUrl; this._onClose = onClose; this._overlay = null;
8
+ this._injectStyles(); if (container) this._render(container);
9
+ }
10
+ _injectStyles() {
11
+ if (document.getElementById('oasis-popup-styles')) return;
12
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
13
+ s.textContent = `.oasis-popup-overlay{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s}.oasis-popup-overlay.is-open{opacity:1}.oasis-popup-modal{background:#0a1628;border:1px solid rgba(0,200,255,.2);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.6);width:90%;max-width:600px;max-height:90vh;overflow-y:auto;transform:translateY(16px);transition:transform .25s}.oasis-popup-overlay.is-open .oasis-popup-modal{transform:none}.oasis-popup-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-popup-title{font-family:'Orbitron',sans-serif;font-size:14px;font-weight:700;color:#00c8ff;letter-spacing:.08em}.oasis-popup-close{background:none;border:none;color:#7a9bbf;font-size:22px;cursor:pointer;padding:0;line-height:1}.oasis-popup-close:hover{color:#fff}.oasis-popup-body{padding:24px}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700}.oasis-badge-blue{background:rgba(0,200,255,.15);color:#00c8ff}.oasis-badge-green{background:rgba(0,200,100,.15);color:#00c864}.oasis-badge-yellow{background:rgba(255,200,0,.15);color:#ffc800}.oasis-stat-row{display:flex;gap:12px;margin-bottom:20px}.oasis-stat{flex:1;background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:14px;text-align:center}.oasis-stat-val{font-family:'Orbitron',sans-serif;font-size:20px;font-weight:700;color:#00c8ff}.oasis-stat-lbl{font-size:10px;color:#7a9bbf;margin-top:4px}.oasis-ach-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.oasis-ach-card{background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:16px;text-align:center}.oasis-ach-icon{font-size:32px;margin-bottom:8px}.oasis-ach-name{font-size:12px;font-weight:700;color:#fff;margin-bottom:4px}.oasis-ach-desc{font-size:10px;color:#7a9bbf}.oasis-cs{text-align:center;padding:32px 16px}.oasis-cs-icon{font-size:48px;margin-bottom:12px}.oasis-cs h3{font-family:'Orbitron',sans-serif;font-size:15px;color:#00c8ff;margin:0 0 8px}.oasis-cs p{color:#7a9bbf;font-size:13px;margin:0}.oasis-msg-error{background:rgba(255,80,80,.12);border:1px solid rgba(255,80,80,.3);color:#ff6b6b;border-radius:8px;padding:10px 14px;font-size:13px}`;
14
+ document.head.appendChild(s);
15
+ }
16
+ open() {
17
+ if (this._overlay) return;
18
+ this._overlay = document.createElement('div');
19
+ this._overlay.className = 'oasis-popup-overlay';
20
+ this._overlay.innerHTML = `<div class="oasis-popup-modal"><div class="oasis-popup-header"><span class="oasis-popup-title">Achievements</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body" id="oasis-vach-body"><div class="oasis-cs"><div class="oasis-cs-icon">⏳</div><p>Loading…</p></div></div></div>`;
21
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
22
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
23
+ document.body.appendChild(this._overlay);
24
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
25
+ this._load();
26
+ }
27
+ async _load() {
28
+ const body = this._overlay.querySelector('#oasis-vach-body');
29
+ try {
30
+ const r = await fetch(`${this._apiUrl}/api/game/achievements?avatarId=${this._avatarId}`);
31
+ const data = await r.json();
32
+ const achs = Array.isArray(data) ? data : (data.result || []);
33
+ const icons = ['🏆','⭐','🎯','🔥','💎','🌟','🎖️','🏅'];
34
+ if (!achs.length) { body.innerHTML = '<div class="oasis-cs"><div class="oasis-cs-icon">🏆</div><h3>No Achievements</h3><p>No achievements unlocked yet.</p></div>'; return; }
35
+ body.innerHTML = `<div class="oasis-stat-row"><div class="oasis-stat"><div class="oasis-stat-val">${achs.length}</div><div class="oasis-stat-lbl">Unlocked</div></div><div class="oasis-stat"><div class="oasis-stat-val">${achs.filter(a=>a.rarity==='rare'||a.rarity==='legendary').length}</div><div class="oasis-stat-lbl">Rare+</div></div></div><div class="oasis-ach-grid">${achs.map((a,i)=>`<div class="oasis-ach-card"><div class="oasis-ach-icon">${icons[i%icons.length]}</div><div class="oasis-ach-name">${a.name||'Achievement'}</div><div class="oasis-ach-desc">${a.description||''}</div></div>`).join('')}</div>`;
36
+ } catch { body.innerHTML = '<div class="oasis-msg-error">Failed to load achievements.</div>'; }
37
+ }
38
+ close() { if (!this._overlay) return; const el = this._overlay; this._overlay = null; el.classList.remove('is-open'); el.addEventListener('transitionend', () => el.remove(), { once: true }); if (this._onClose) this._onClose(); }
39
+ _render(c) { c.innerHTML = '<div class="oasis-popup-body"><p style="color:#7a9bbf">Loading…</p></div>'; }
40
+ destroy() { this.close(); }
41
+ }
42
+ window.OasisViewAchievements = OasisViewAchievements;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * OasisViewAvatarKarma — view a specific avatar's karma breakdown popup widget
3
+ * Usage: const w = new OasisViewAvatarKarma({ avatarId: '...' }); w.open();
4
+ */
5
+ class OasisViewAvatarKarma {
6
+ constructor({ container, avatarId = '', apiUrl = 'https://api.web4.oasisomniverse.one', onClose = null } = {}) {
7
+ this._avatarId = avatarId; this._apiUrl = apiUrl; this._onClose = onClose; this._overlay = null;
8
+ this._injectStyles(); if (container) this._render(container);
9
+ }
10
+ _injectStyles() {
11
+ if (document.getElementById('oasis-popup-styles')) return;
12
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
13
+ s.textContent = `.oasis-popup-overlay{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s}.oasis-popup-overlay.is-open{opacity:1}.oasis-popup-modal{background:#0a1628;border:1px solid rgba(0,200,255,.2);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.6);width:90%;max-width:560px;max-height:90vh;overflow-y:auto;transform:translateY(16px);transition:transform .25s}.oasis-popup-overlay.is-open .oasis-popup-modal{transform:none}.oasis-popup-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-popup-title{font-family:'Orbitron',sans-serif;font-size:14px;font-weight:700;color:#00c8ff;letter-spacing:.08em}.oasis-popup-close{background:none;border:none;color:#7a9bbf;font-size:22px;cursor:pointer;padding:0;line-height:1}.oasis-popup-close:hover{color:#fff}.oasis-popup-body{padding:24px}.oasis-info-block{background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:16px;margin-bottom:12px}.oasis-info-label{font-size:10px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;margin-bottom:4px}.oasis-info-value{color:#fff;font-size:14px;font-weight:500}.oasis-stat-row{display:flex;gap:12px;margin-bottom:20px}.oasis-stat{flex:1;background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:14px;text-align:center}.oasis-stat-val{font-family:'Orbitron',sans-serif;font-size:20px;font-weight:700;color:#00c8ff}.oasis-stat-lbl{font-size:10px;color:#7a9bbf;margin-top:4px}.oasis-cs{text-align:center;padding:32px 16px}.oasis-cs-icon{font-size:48px;margin-bottom:12px}.oasis-cs h3{font-family:'Orbitron',sans-serif;font-size:15px;color:#00c8ff;margin:0 0 8px}.oasis-cs p{color:#7a9bbf;font-size:13px;margin:0}.oasis-msg-error{background:rgba(255,80,80,.12);border:1px solid rgba(255,80,80,.3);color:#ff6b6b;border-radius:8px;padding:10px 14px;font-size:13px}.oasis-section-title{font-size:11px;font-weight:700;color:#7a9bbf;letter-spacing:.08em;text-transform:uppercase;margin:0 0 12px}`;
14
+ document.head.appendChild(s);
15
+ }
16
+ open() {
17
+ if (this._overlay) return;
18
+ this._overlay = document.createElement('div');
19
+ this._overlay.className = 'oasis-popup-overlay';
20
+ this._overlay.innerHTML = `<div class="oasis-popup-modal"><div class="oasis-popup-header"><span class="oasis-popup-title">Avatar Karma</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body" id="oasis-vak-body"><div class="oasis-cs"><div class="oasis-cs-icon">⏳</div><p>Loading karma…</p></div></div></div>`;
21
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
22
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
23
+ document.body.appendChild(this._overlay);
24
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
25
+ this._load();
26
+ }
27
+ async _load() {
28
+ const body = this._overlay.querySelector('#oasis-vak-body');
29
+ try {
30
+ const r = await fetch(`${this._apiUrl}/api/karma/avatar/${this._avatarId}`);
31
+ const data = await r.json();
32
+ const k = data.result || data;
33
+ body.innerHTML = `<div class="oasis-stat-row">
34
+ <div class="oasis-stat"><div class="oasis-stat-val">${k.total??0}</div><div class="oasis-stat-lbl">Total ⚡</div></div>
35
+ <div class="oasis-stat"><div class="oasis-stat-val">${k.thisWeek??0}</div><div class="oasis-stat-lbl">This Week</div></div>
36
+ <div class="oasis-stat"><div class="oasis-stat-val">${k.rank??'—'}</div><div class="oasis-stat-lbl">Rank</div></div>
37
+ </div>
38
+ <p class="oasis-section-title">Karma Breakdown</p>
39
+ <div class="oasis-info-block"><div class="oasis-info-label">Health</div><div class="oasis-info-value">${k.health??0}</div></div>
40
+ <div class="oasis-info-block"><div class="oasis-info-label">Food</div><div class="oasis-info-value">${k.food??0}</div></div>
41
+ <div class="oasis-info-block"><div class="oasis-info-label">Environment</div><div class="oasis-info-value">${k.environment??0}</div></div>
42
+ <div class="oasis-info-block"><div class="oasis-info-label">Community</div><div class="oasis-info-value">${k.community??0}</div></div>
43
+ <div class="oasis-info-block"><div class="oasis-info-label">Other</div><div class="oasis-info-value">${k.other??0}</div></div>`;
44
+ } catch { body.innerHTML = '<div class="oasis-msg-error">Failed to load karma data.</div>'; }
45
+ }
46
+ close() { if (!this._overlay) return; const el = this._overlay; this._overlay = null; el.classList.remove('is-open'); el.addEventListener('transitionend', () => el.remove(), { once: true }); if (this._onClose) this._onClose(); }
47
+ _render(c) { c.innerHTML = '<div class="oasis-popup-body"><p style="color:#7a9bbf">Loading…</p></div>'; }
48
+ destroy() { this.close(); }
49
+ }
50
+ window.OasisViewAvatarKarma = OasisViewAvatarKarma;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * OasisViewAvatar — view avatar profile popup widget
3
+ * Usage: const w = new OasisViewAvatar({ avatarId: '...' }); w.open();
4
+ */
5
+ class OasisViewAvatar {
6
+ constructor({ container, avatarId = '', apiUrl = 'https://api.web4.oasisomniverse.one', onClose = null } = {}) {
7
+ this._avatarId = avatarId; this._apiUrl = apiUrl; this._onClose = onClose; this._overlay = null;
8
+ this._injectStyles();
9
+ if (container) this._render(container);
10
+ }
11
+
12
+ _injectStyles() {
13
+ if (document.getElementById('oasis-popup-styles')) return;
14
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
15
+ s.textContent = `.oasis-popup-overlay{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s}.oasis-popup-overlay.is-open{opacity:1}.oasis-popup-modal{background:#0a1628;border:1px solid rgba(0,200,255,.2);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.6);width:90%;max-width:560px;max-height:90vh;overflow-y:auto;transform:translateY(16px);transition:transform .25s}.oasis-popup-overlay.is-open .oasis-popup-modal{transform:none}.oasis-popup-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-popup-title{font-family:'Orbitron',sans-serif;font-size:14px;font-weight:700;color:#00c8ff;letter-spacing:.08em}.oasis-popup-close{background:none;border:none;color:#7a9bbf;font-size:22px;cursor:pointer;padding:0;line-height:1}.oasis-popup-close:hover{color:#fff}.oasis-popup-body{padding:24px}.oasis-field{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}.oasis-label{font-size:11px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase}.oasis-input,.oasis-select,.oasis-textarea{background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.2);border-radius:8px;padding:10px 14px;color:#fff;font-size:14px;outline:none;transition:border-color .2s;width:100%;box-sizing:border-box}.oasis-btn{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;letter-spacing:.08em;padding:11px 20px;cursor:pointer;transition:opacity .2s}.oasis-btn:hover{opacity:.85}.oasis-btn-ghost{background:rgba(0,200,255,.1);border:1px solid rgba(0,200,255,.3);color:#00c8ff;border-radius:8px;font-size:12px;font-weight:600;padding:9px 16px;cursor:pointer}.oasis-btn-row{display:flex;gap:10px;justify-content:flex-end;margin-top:20px}.oasis-msg{border-radius:8px;padding:10px 14px;font-size:13px;margin-bottom:16px}.oasis-msg-error{background:rgba(255,80,80,.12);border:1px solid rgba(255,80,80,.3);color:#ff6b6b}.oasis-msg-success{background:rgba(0,200,100,.12);border:1px solid rgba(0,200,100,.3);color:#00c864}.oasis-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}.oasis-info-block{background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:16px;margin-bottom:12px}.oasis-info-label{font-size:10px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;margin-bottom:4px}.oasis-info-value{color:#fff;font-size:14px;font-weight:500}.oasis-cs{text-align:center;padding:32px 16px}.oasis-cs-icon{font-size:48px;margin-bottom:12px}.oasis-cs h3{font-family:'Orbitron',sans-serif;font-size:15px;color:#00c8ff;margin:0 0 8px}.oasis-cs p{color:#7a9bbf;font-size:13px;margin:0}.oasis-table{width:100%;border-collapse:collapse;font-size:13px}.oasis-table th{text-align:left;padding:10px 12px;font-size:10px;font-weight:700;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-table td{padding:10px 12px;color:#fff;border-bottom:1px solid rgba(255,255,255,.05)}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700}.oasis-badge-blue{background:rgba(0,200,255,.15);color:#00c8ff}.oasis-badge-green{background:rgba(0,200,100,.15);color:#00c864}.oasis-badge-red{background:rgba(255,80,80,.15);color:#ff6b6b}.oasis-search-row{display:flex;gap:10px;margin-bottom:16px}.oasis-search-row .oasis-input{flex:1}.oasis-stat-row{display:flex;gap:12px;margin-bottom:20px}.oasis-stat{flex:1;background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:14px;text-align:center}.oasis-stat-val{font-family:'Orbitron',sans-serif;font-size:20px;font-weight:700;color:#00c8ff}.oasis-stat-lbl{font-size:10px;color:#7a9bbf;margin-top:4px}.oasis-divider{border:none;border-top:1px solid rgba(0,200,255,.1);margin:16px 0}.oasis-section-title{font-size:11px;font-weight:700;color:#7a9bbf;letter-spacing:.08em;text-transform:uppercase;margin:0 0 12px}.oasis-avatar-hero{display:flex;align-items:center;gap:16px;margin-bottom:24px;padding-bottom:20px;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-avatar-circle{width:64px;height:64px;border-radius:50%;background:linear-gradient(135deg,#00c8ff,#0080ff);display:flex;align-items:center;justify-content:center;font-size:24px;flex-shrink:0}.oasis-avatar-name{font-size:18px;font-weight:700;color:#fff}.oasis-avatar-handle{color:#7a9bbf;font-size:13px;margin-top:2px}`;
16
+ document.head.appendChild(s);
17
+ }
18
+
19
+ open() {
20
+ if (this._overlay) return;
21
+ this._overlay = document.createElement('div');
22
+ this._overlay.className = 'oasis-popup-overlay';
23
+ this._overlay.innerHTML = `<div class="oasis-popup-modal">
24
+ <div class="oasis-popup-header"><span class="oasis-popup-title">Avatar Profile</span><button class="oasis-popup-close">×</button></div>
25
+ <div class="oasis-popup-body" id="oasis-va-body"><div class="oasis-cs"><div class="oasis-cs-icon">⏳</div><p>Loading…</p></div></div>
26
+ </div>`;
27
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
28
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
29
+ document.body.appendChild(this._overlay);
30
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
31
+ this._loadAvatar();
32
+ }
33
+
34
+ async _loadAvatar() {
35
+ const body = this._overlay.querySelector('#oasis-va-body');
36
+ try {
37
+ const r = await fetch(`${this._apiUrl}/api/avatar/${this._avatarId}`);
38
+ const data = await r.json();
39
+ const a = data.result || data;
40
+ body.innerHTML = `
41
+ <div class="oasis-avatar-hero">
42
+ <div class="oasis-avatar-circle">👤</div>
43
+ <div><div class="oasis-avatar-name">${a.firstName || ''} ${a.lastName || ''}</div><div class="oasis-avatar-handle">@${a.username || 'unknown'}</div></div>
44
+ </div>
45
+ <div class="oasis-stat-row">
46
+ <div class="oasis-stat"><div class="oasis-stat-val">${a.karma ?? 0}</div><div class="oasis-stat-lbl">Karma</div></div>
47
+ <div class="oasis-stat"><div class="oasis-stat-val">${a.level ?? 1}</div><div class="oasis-stat-lbl">Level</div></div>
48
+ <div class="oasis-stat"><div class="oasis-stat-val">${a.xp ?? 0}</div><div class="oasis-stat-lbl">XP</div></div>
49
+ </div>
50
+ <div class="oasis-info-block"><div class="oasis-info-label">Email</div><div class="oasis-info-value">${a.email || '—'}</div></div>
51
+ <div class="oasis-info-block"><div class="oasis-info-label">Bio</div><div class="oasis-info-value">${a.description || 'No bio set.'}</div></div>
52
+ <div class="oasis-info-block"><div class="oasis-info-label">Member Since</div><div class="oasis-info-value">${a.createdDate ? new Date(a.createdDate).toLocaleDateString() : '—'}</div></div>
53
+ `;
54
+ } catch { body.innerHTML = '<div class="oasis-msg oasis-msg-error">Failed to load avatar profile.</div>'; }
55
+ }
56
+
57
+ close() {
58
+ if (!this._overlay) return;
59
+ const el = this._overlay; this._overlay = null;
60
+ el.classList.remove('is-open');
61
+ el.addEventListener('transitionend', () => el.remove(), { once: true });
62
+ if (this._onClose) this._onClose();
63
+ }
64
+
65
+ _render(container) { container.innerHTML = '<div class="oasis-popup-body"><p style="color:#7a9bbf">Loading avatar…</p></div>'; }
66
+ destroy() { this.close(); }
67
+ }
68
+
69
+ window.OasisViewAvatar = OasisViewAvatar;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * OasisViewEggs — view an egg's details popup widget
3
+ * Usage: const w = new OasisViewEggs({ eggId: '...' }); w.open();
4
+ */
5
+ class OasisViewEggs {
6
+ constructor({ container, eggId = '', apiUrl = 'https://api.web4.oasisomniverse.one', onClose = null } = {}) {
7
+ this._eggId = eggId; this._apiUrl = apiUrl; this._onClose = onClose; this._overlay = null;
8
+ this._injectStyles(); if (container) this._render(container);
9
+ }
10
+ _injectStyles() {
11
+ if (document.getElementById('oasis-popup-styles')) return;
12
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
13
+ s.textContent = `.oasis-popup-overlay{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s}.oasis-popup-overlay.is-open{opacity:1}.oasis-popup-modal{background:#0a1628;border:1px solid rgba(0,200,255,.2);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.6);width:90%;max-width:480px;max-height:90vh;overflow-y:auto;transform:translateY(16px);transition:transform .25s}.oasis-popup-overlay.is-open .oasis-popup-modal{transform:none}.oasis-popup-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-popup-title{font-family:'Orbitron',sans-serif;font-size:14px;font-weight:700;color:#00c8ff;letter-spacing:.08em}.oasis-popup-close{background:none;border:none;color:#7a9bbf;font-size:22px;cursor:pointer;padding:0;line-height:1}.oasis-popup-close:hover{color:#fff}.oasis-popup-body{padding:24px}.oasis-info-block{background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:16px;margin-bottom:12px}.oasis-info-label{font-size:10px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;margin-bottom:4px}.oasis-info-value{color:#fff;font-size:14px;font-weight:500}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700}.oasis-badge-blue{background:rgba(0,200,255,.15);color:#00c8ff}.oasis-badge-green{background:rgba(0,200,100,.15);color:#00c864}.oasis-badge-yellow{background:rgba(255,200,0,.15);color:#ffc800}.oasis-cs{text-align:center;padding:32px 16px}.oasis-cs-icon{font-size:48px;margin-bottom:12px}.oasis-cs h3{font-family:'Orbitron',sans-serif;font-size:15px;color:#00c8ff;margin:0 0 8px}.oasis-cs p{color:#7a9bbf;font-size:13px;margin:0}.oasis-egg-hero{text-align:center;padding:24px 0 16px;border-bottom:1px solid rgba(0,200,255,.1);margin-bottom:20px}.oasis-egg-icon{font-size:64px}.oasis-egg-name{font-size:18px;font-weight:700;color:#fff;margin-top:8px}.oasis-msg-error{background:rgba(255,80,80,.12);border:1px solid rgba(255,80,80,.3);color:#ff6b6b;border-radius:8px;padding:10px 14px;font-size:13px}`;
14
+ document.head.appendChild(s);
15
+ }
16
+ open() {
17
+ if (this._overlay) return;
18
+ this._overlay = document.createElement('div');
19
+ this._overlay.className = 'oasis-popup-overlay';
20
+ this._overlay.innerHTML = `<div class="oasis-popup-modal"><div class="oasis-popup-header"><span class="oasis-popup-title">View Egg</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body" id="oasis-ve-body"><div class="oasis-cs"><div class="oasis-cs-icon">⏳</div><p>Loading…</p></div></div></div>`;
21
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
22
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
23
+ document.body.appendChild(this._overlay);
24
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
25
+ this._load();
26
+ }
27
+ async _load() {
28
+ const body = this._overlay.querySelector('#oasis-ve-body');
29
+ try {
30
+ const r = await fetch(`${this._apiUrl}/api/avatar/eggs/${this._eggId}`);
31
+ const data = await r.json();
32
+ const e = data.result || data;
33
+ body.innerHTML = `<div class="oasis-egg-hero"><div class="oasis-egg-icon">🥚</div><div class="oasis-egg-name">${e.name||'Unknown Egg'}</div></div>
34
+ <div class="oasis-info-block"><div class="oasis-info-label">Type</div><div class="oasis-info-value"><span class="oasis-badge oasis-badge-blue">${e.eggType||'Unknown'}</span></div></div>
35
+ <div class="oasis-info-block"><div class="oasis-info-label">Status</div><div class="oasis-info-value"><span class="oasis-badge ${e.isHatched?'oasis-badge-green':'oasis-badge-yellow'}">${e.isHatched?'Hatched':'Incubating'}</span></div></div>
36
+ <div class="oasis-info-block"><div class="oasis-info-label">Description</div><div class="oasis-info-value">${e.description||'No description.'}</div></div>
37
+ <div class="oasis-info-block"><div class="oasis-info-label">Hatched Date</div><div class="oasis-info-value">${e.hatchedDate?new Date(e.hatchedDate).toLocaleDateString():'Not yet hatched'}</div></div>`;
38
+ } catch { body.innerHTML = '<div class="oasis-msg-error">Failed to load egg.</div>'; }
39
+ }
40
+ close() { if (!this._overlay) return; const el = this._overlay; this._overlay = null; el.classList.remove('is-open'); el.addEventListener('transitionend', () => el.remove(), { once: true }); if (this._onClose) this._onClose(); }
41
+ _render(c) { c.innerHTML = '<div class="oasis-popup-body"><p style="color:#7a9bbf">Loading…</p></div>'; }
42
+ destroy() { this.close(); }
43
+ }
44
+ window.OasisViewEggs = OasisViewEggs;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * OasisViewKarma — view global karma leaderboard popup widget
3
+ * Usage: const w = new OasisViewKarma({}); w.open();
4
+ */
5
+ class OasisViewKarma {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onClose = onClose; this._overlay = null;
8
+ this._injectStyles(); if (container) this._render(container);
9
+ }
10
+ _injectStyles() {
11
+ if (document.getElementById('oasis-popup-styles')) return;
12
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
13
+ s.textContent = `.oasis-popup-overlay{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s}.oasis-popup-overlay.is-open{opacity:1}.oasis-popup-modal{background:#0a1628;border:1px solid rgba(0,200,255,.2);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.6);width:90%;max-width:600px;max-height:90vh;overflow-y:auto;transform:translateY(16px);transition:transform .25s}.oasis-popup-overlay.is-open .oasis-popup-modal{transform:none}.oasis-popup-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-popup-title{font-family:'Orbitron',sans-serif;font-size:14px;font-weight:700;color:#00c8ff;letter-spacing:.08em}.oasis-popup-close{background:none;border:none;color:#7a9bbf;font-size:22px;cursor:pointer;padding:0;line-height:1}.oasis-popup-close:hover{color:#fff}.oasis-popup-body{padding:24px}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700}.oasis-badge-blue{background:rgba(0,200,255,.15);color:#00c8ff}.oasis-badge-yellow{background:rgba(255,200,0,.15);color:#ffc800}.oasis-table{width:100%;border-collapse:collapse;font-size:13px}.oasis-table th{text-align:left;padding:10px 12px;font-size:10px;font-weight:700;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-table td{padding:10px 12px;color:#fff;border-bottom:1px solid rgba(255,255,255,.05)}.oasis-table tr:hover td{background:rgba(0,200,255,.04)}.oasis-cs{text-align:center;padding:32px 16px}.oasis-cs-icon{font-size:48px;margin-bottom:12px}.oasis-cs h3{font-family:'Orbitron',sans-serif;font-size:15px;color:#00c8ff;margin:0 0 8px}.oasis-cs p{color:#7a9bbf;font-size:13px;margin:0}.oasis-msg-error{background:rgba(255,80,80,.12);border:1px solid rgba(255,80,80,.3);color:#ff6b6b;border-radius:8px;padding:10px 14px;font-size:13px}`;
14
+ document.head.appendChild(s);
15
+ }
16
+ open() {
17
+ if (this._overlay) return;
18
+ this._overlay = document.createElement('div');
19
+ this._overlay.className = 'oasis-popup-overlay';
20
+ this._overlay.innerHTML = `<div class="oasis-popup-modal"><div class="oasis-popup-header"><span class="oasis-popup-title">Karma Leaderboard</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body" id="oasis-vk-body"><div class="oasis-cs"><div class="oasis-cs-icon">⏳</div><p>Loading…</p></div></div></div>`;
21
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
22
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
23
+ document.body.appendChild(this._overlay);
24
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
25
+ this._load();
26
+ }
27
+ async _load() {
28
+ const body = this._overlay.querySelector('#oasis-vk-body');
29
+ try {
30
+ const r = await fetch(`${this._apiUrl}/api/karma/leaderboard?top=20`);
31
+ const data = await r.json();
32
+ const avatars = Array.isArray(data) ? data : (data.result || []);
33
+ if (!avatars.length) { body.innerHTML = '<div class="oasis-cs"><div class="oasis-cs-icon">⚡</div><h3>No Data</h3><p>Karma leaderboard is empty.</p></div>'; return; }
34
+ body.innerHTML = `<table class="oasis-table"><thead><tr><th>#</th><th>Avatar</th><th>⚡ Total Karma</th><th>Level</th></tr></thead><tbody>${avatars.map((a,i)=>`<tr><td style="color:${i<3?'#ffc800':'#7a9bbf'};font-weight:700">${i+1}</td><td>${a.username||'—'}</td><td><span class="oasis-badge oasis-badge-blue">${a.karma??0}</span></td><td>${a.level??1}</td></tr>`).join('')}</tbody></table>`;
35
+ } catch { body.innerHTML = '<div class="oasis-msg-error">Failed to load karma leaderboard.</div>'; }
36
+ }
37
+ close() { if (!this._overlay) return; const el = this._overlay; this._overlay = null; el.classList.remove('is-open'); el.addEventListener('transitionend', () => el.remove(), { once: true }); if (this._onClose) this._onClose(); }
38
+ _render(c) { c.innerHTML = '<div class="oasis-popup-body"><p style="color:#7a9bbf">Loading…</p></div>'; }
39
+ destroy() { this.close(); }
40
+ }
41
+ window.OasisViewKarma = OasisViewKarma;