@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
@@ -0,0 +1,78 @@
1
+ /**
2
+ * OasisSearchAvatar — search avatars popup widget
3
+ * Usage: const w = new OasisSearchAvatar({ onSelect: (avatar) => {} }); w.open();
4
+ */
5
+ class OasisSearchAvatar {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onSelect = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onSelect = onSelect; 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-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,.oasis-select:focus,.oasis-textarea:focus{border-color:rgba(0,200,255,.5)}.oasis-textarea{resize:vertical;min-height:80px}.oasis-select option{background:#0a1628}.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:disabled{opacity:.5;cursor:not-allowed}.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-table tr:hover td{background:rgba(0,200,255,.04)}.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-badge-yellow{background:rgba(255,200,0,.15);color:#ffc800}.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">Search Avatars</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-sa-btn');
32
+ const inp = this._overlay.querySelector('#oasis-sa-q');
33
+ btn.addEventListener('click', () => this._doSearch());
34
+ inp.addEventListener('keydown', e => { if (e.key === 'Enter') this._doSearch(); });
35
+ }
36
+
37
+ _buildBody() {
38
+ return `
39
+ <div class="oasis-search-row">
40
+ <input class="oasis-input" id="oasis-sa-q" type="text" placeholder="Search by username or name…" />
41
+ <button class="oasis-btn" id="oasis-sa-btn" style="white-space:nowrap">Search</button>
42
+ </div>
43
+ <div id="oasis-sa-results"><p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">Enter a name or username to search avatars.</p></div>
44
+ `;
45
+ }
46
+
47
+ async _doSearch() {
48
+ const q = this._overlay.querySelector('#oasis-sa-q').value.trim();
49
+ const res = this._overlay.querySelector('#oasis-sa-results');
50
+ const btn = this._overlay.querySelector('#oasis-sa-btn');
51
+ if (!q) return;
52
+ btn.textContent = '…'; btn.disabled = true;
53
+ try {
54
+ const r = await fetch(`${this._apiUrl}/api/avatar/search?username=${encodeURIComponent(q)}`);
55
+ const data = await r.json();
56
+ const avatars = Array.isArray(data) ? data : (data.result || []);
57
+ if (!avatars.length) { res.innerHTML = '<p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">No avatars found.</p>'; }
58
+ else {
59
+ res.innerHTML = `<table class="oasis-table"><thead><tr><th>Username</th><th>Name</th><th>Karma</th><th></th></tr></thead><tbody>${avatars.map(a => `<tr><td>${a.username || '—'}</td><td>${a.firstName || ''} ${a.lastName || ''}</td><td>${a.karma ?? 0}</td><td><button class="oasis-badge oasis-badge-blue" data-id="${a.id}">Select</button></td></tr>`).join('')}</tbody></table>`;
60
+ res.querySelectorAll('[data-id]').forEach(b => b.addEventListener('click', () => { if (this._onSelect) this._onSelect(avatars.find(a => a.id === b.dataset.id)); this.close(); }));
61
+ }
62
+ } catch { res.innerHTML = '<p style="color:#ff6b6b;font-size:13px;text-align:center;margin:24px 0">Search failed. Please try again.</p>'; }
63
+ btn.textContent = 'Search'; btn.disabled = false;
64
+ }
65
+
66
+ close() {
67
+ if (!this._overlay) return;
68
+ const el = this._overlay; this._overlay = null;
69
+ el.classList.remove('is-open');
70
+ el.addEventListener('transitionend', () => el.remove(), { once: true });
71
+ if (this._onClose) this._onClose();
72
+ }
73
+
74
+ _render(container) { container.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
75
+ destroy() { this.close(); }
76
+ }
77
+
78
+ window.OasisSearchAvatar = OasisSearchAvatar;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * OasisSearchData — search OASIS data store popup widget
3
+ * Usage: const w = new OasisSearchData({ onSelect: (item) => {} }); w.open();
4
+ */
5
+ class OasisSearchData {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onSelect = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onSelect = onSelect; 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:700px;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-input:focus,.oasis-select:focus{border-color:rgba(0,200,255,.5)}.oasis-select option{background:#0a1628}.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: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-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-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-search-row{display:flex;gap:10px;margin-bottom:16px}.oasis-search-row .oasis-input{flex:1}.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}`;
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">Search Data</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body">${this._buildBody()}</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
+ const btn = this._overlay.querySelector('#oasis-sd-btn');
26
+ btn.addEventListener('click', () => this._doSearch());
27
+ this._overlay.querySelector('#oasis-sd-q').addEventListener('keydown', e => { if (e.key === 'Enter') this._doSearch(); });
28
+ }
29
+ _buildBody() {
30
+ return `<div class="oasis-search-row">
31
+ <input class="oasis-input" id="oasis-sd-q" type="text" placeholder="Search by key or value…" />
32
+ <select class="oasis-select" id="oasis-sd-prov" style="width:auto;flex-shrink:0"><option value="">All Providers</option><option value="holochain">Holochain</option><option value="ethereum">Ethereum</option><option value="ipfs">IPFS</option><option value="mongodb">MongoDB</option></select>
33
+ <button class="oasis-btn" id="oasis-sd-btn" style="white-space:nowrap">Search</button>
34
+ </div>
35
+ <div id="oasis-sd-results"><p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">Enter a query to search the OASIS data store.</p></div>`;
36
+ }
37
+ async _doSearch() {
38
+ const q = this._overlay.querySelector('#oasis-sd-q').value.trim();
39
+ const prov = this._overlay.querySelector('#oasis-sd-prov').value;
40
+ const res = this._overlay.querySelector('#oasis-sd-results');
41
+ const btn = this._overlay.querySelector('#oasis-sd-btn');
42
+ if (!q) return;
43
+ btn.textContent = '…'; btn.disabled = true;
44
+ try {
45
+ const url = `${this._apiUrl}/api/data/search?query=${encodeURIComponent(q)}${prov ? '&provider=' + prov : ''}`;
46
+ const r = await fetch(url);
47
+ const data = await r.json();
48
+ const items = Array.isArray(data) ? data : (data.result || []);
49
+ if (!items.length) res.innerHTML = '<p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">No results found.</p>';
50
+ else res.innerHTML = `<table class="oasis-table"><thead><tr><th>Key</th><th>Provider</th><th>Modified</th><th></th></tr></thead><tbody>${items.map(i => `<tr><td>${i.key || '—'}</td><td><span class="oasis-badge oasis-badge-blue">${i.provider || '—'}</span></td><td>${i.modifiedDate ? new Date(i.modifiedDate).toLocaleDateString() : '—'}</td><td><button class="oasis-badge oasis-badge-green" data-idx="${items.indexOf(i)}">Select</button></td></tr>`).join('')}</tbody></table>`;
51
+ res.querySelectorAll('[data-idx]').forEach(b => b.addEventListener('click', () => { if (this._onSelect) this._onSelect(items[+b.dataset.idx]); this.close(); }));
52
+ } catch { res.innerHTML = '<div class="oasis-msg oasis-msg-error">Search failed.</div>'; }
53
+ btn.textContent = 'Search'; btn.disabled = false;
54
+ }
55
+ 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(); }
56
+ _render(c) { c.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
57
+ destroy() { this.close(); }
58
+ }
59
+ window.OasisSearchData = OasisSearchData;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * OasisSearchEggs — search OASIS Eggs popup widget
3
+ * Usage: const w = new OasisSearchEggs({ onSelect: (egg) => {} }); w.open();
4
+ */
5
+ class OasisSearchEggs {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onSelect = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onSelect = onSelect; 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-input,.oasis-select{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,.oasis-select:focus{border-color:rgba(0,200,255,.5)}.oasis-select option{background:#0a1628}.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:disabled{opacity:.5;cursor:not-allowed}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700;cursor:pointer}.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-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-search-row{display:flex;gap:10px;margin-bottom:16px}.oasis-search-row .oasis-input{flex:1}.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}`;
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">Search Eggs</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body">${this._buildBody()}</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._overlay.querySelector('#oasis-se-btn').addEventListener('click', () => this._doSearch());
26
+ }
27
+ _buildBody() {
28
+ return `<div class="oasis-search-row"><input class="oasis-input" id="oasis-se-q" type="text" placeholder="Search eggs by name or type…" /><button class="oasis-btn" id="oasis-se-btn">Search</button></div><div id="oasis-se-results"><p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">Search the OASIS Egg registry.</p></div>`;
29
+ }
30
+ async _doSearch() {
31
+ const q = this._overlay.querySelector('#oasis-se-q').value.trim();
32
+ const res = this._overlay.querySelector('#oasis-se-results');
33
+ const btn = this._overlay.querySelector('#oasis-se-btn');
34
+ btn.textContent = '…'; btn.disabled = true;
35
+ try {
36
+ const r = await fetch(`${this._apiUrl}/api/avatar/eggs/search?query=${encodeURIComponent(q)}`);
37
+ const data = await r.json();
38
+ const eggs = Array.isArray(data) ? data : (data.result || []);
39
+ if (!eggs.length) res.innerHTML = '<p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">No eggs found.</p>';
40
+ else res.innerHTML = `<table class="oasis-table"><thead><tr><th>Name</th><th>Type</th><th>Status</th><th></th></tr></thead><tbody>${eggs.map((e,i)=>`<tr><td>${e.name||'Egg'}</td><td><span class="oasis-badge oasis-badge-blue">${e.eggType||'Unknown'}</span></td><td><span class="oasis-badge ${e.isHatched?'oasis-badge-green':'oasis-badge-yellow'}">${e.isHatched?'Hatched':'Incubating'}</span></td><td><button class="oasis-badge oasis-badge-blue" data-i="${i}">Select</button></td></tr>`).join('')}</tbody></table>`;
41
+ res.querySelectorAll('[data-i]').forEach(b=>b.addEventListener('click',()=>{if(this._onSelect)this._onSelect(eggs[+b.dataset.i]);this.close();}));
42
+ } catch { res.innerHTML = '<p style="color:#ff6b6b;font-size:13px;text-align:center;margin:24px 0">Search failed.</p>'; }
43
+ btn.textContent = 'Search'; btn.disabled = false;
44
+ }
45
+ 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(); }
46
+ _render(c) { c.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
47
+ destroy() { this.close(); }
48
+ }
49
+ window.OasisSearchEggs = OasisSearchEggs;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * OasisSearchKarma — search karma transactions popup widget
3
+ * Usage: const w = new OasisSearchKarma({ onSelect: (item) => {} }); w.open();
4
+ */
5
+ class OasisSearchKarma {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onSelect = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onSelect = onSelect; 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-input,.oasis-select{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,.oasis-select:focus{border-color:rgba(0,200,255,.5)}.oasis-select option{background:#0a1628}.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:disabled{opacity:.5;cursor:not-allowed}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700;cursor:pointer}.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-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-search-row{display:flex;gap:10px;margin-bottom:16px}.oasis-search-row .oasis-input{flex:1}.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}`;
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">Search Karma</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body">${this._buildBody()}</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._overlay.querySelector('#oasis-sk-btn').addEventListener('click', () => this._doSearch());
26
+ }
27
+ _buildBody() {
28
+ return `<div class="oasis-search-row"><input class="oasis-input" id="oasis-sk-q" type="text" placeholder="Search by avatar username…" /><button class="oasis-btn" id="oasis-sk-btn">Search</button></div><div id="oasis-sk-results"><p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">Search karma by avatar or action.</p></div>`;
29
+ }
30
+ async _doSearch() {
31
+ const q = this._overlay.querySelector('#oasis-sk-q').value.trim();
32
+ const res = this._overlay.querySelector('#oasis-sk-results');
33
+ const btn = this._overlay.querySelector('#oasis-sk-btn');
34
+ btn.textContent = '…'; btn.disabled = true;
35
+ try {
36
+ const r = await fetch(`${this._apiUrl}/api/karma/search?query=${encodeURIComponent(q)}`);
37
+ const data = await r.json();
38
+ const items = Array.isArray(data) ? data : (data.result || []);
39
+ if (!items.length) res.innerHTML = '<p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">No karma records found.</p>';
40
+ else res.innerHTML = `<table class="oasis-table"><thead><tr><th>Avatar</th><th>Action</th><th>⚡ Karma</th><th>Date</th></tr></thead><tbody>${items.map(i=>`<tr><td>${i.avatarUsername||'—'}</td><td>${i.karmaType||'—'}</td><td><span class="oasis-badge oasis-badge-blue">+${i.karmaAmount??0}</span></td><td>${i.date?new Date(i.date).toLocaleDateString():'—'}</td></tr>`).join('')}</tbody></table>`;
41
+ } catch { res.innerHTML = '<p style="color:#ff6b6b;font-size:13px;text-align:center;margin:24px 0">Search failed.</p>'; }
42
+ btn.textContent = 'Search'; btn.disabled = false;
43
+ }
44
+ 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(); }
45
+ _render(c) { c.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
46
+ destroy() { this.close(); }
47
+ }
48
+ window.OasisSearchKarma = OasisSearchKarma;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * OasisSearchMission — search OASIS missions popup widget
3
+ * Usage: const w = new OasisSearchMission({ onSelect: (mission) => {} }); w.open();
4
+ */
5
+ class OasisSearchMission {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onSelect = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onSelect = onSelect; 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-input{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}.oasis-btn:hover{opacity:.85}.oasis-btn:disabled{opacity:.5;cursor:not-allowed}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700;cursor:pointer}.oasis-badge-blue{background:rgba(0,200,255,.15);color:#00c8ff}.oasis-badge-green{background:rgba(0,200,100,.15);color:#00c864}.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-search-row{display:flex;gap:10px;margin-bottom:16px}.oasis-search-row .oasis-input{flex:1}.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}`;
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">Search Missions</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body">${this._buildBody()}</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._overlay.querySelector('#oasis-sm-btn').addEventListener('click', () => this._doSearch());
26
+ this._overlay.querySelector('#oasis-sm-q').addEventListener('keydown', e => { if (e.key === 'Enter') this._doSearch(); });
27
+ }
28
+ _buildBody() {
29
+ return `<div class="oasis-search-row"><input class="oasis-input" id="oasis-sm-q" type="text" placeholder="Search missions…" /><button class="oasis-btn" id="oasis-sm-btn">Search</button></div><div id="oasis-sm-results"><p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">Search for OASIS missions.</p></div>`;
30
+ }
31
+ async _doSearch() {
32
+ const q = this._overlay.querySelector('#oasis-sm-q').value.trim();
33
+ const res = this._overlay.querySelector('#oasis-sm-results');
34
+ const btn = this._overlay.querySelector('#oasis-sm-btn');
35
+ btn.textContent = '…'; btn.disabled = true;
36
+ try {
37
+ const r = await fetch(`${this._apiUrl}/api/mission/search?query=${encodeURIComponent(q)}`);
38
+ const data = await r.json();
39
+ const ms = Array.isArray(data) ? data : (data.result || []);
40
+ if (!ms.length) res.innerHTML = '<p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">No missions found.</p>';
41
+ else res.innerHTML = `<table class="oasis-table"><thead><tr><th>Mission</th><th>Type</th><th>Karma</th><th></th></tr></thead><tbody>${ms.map((m,i)=>`<tr><td>${m.name||'—'}</td><td><span class="oasis-badge oasis-badge-blue">${m.missionType||'—'}</span></td><td>+${m.karmaReward??0} ⚡</td><td><button class="oasis-badge oasis-badge-green" data-i="${i}">Select</button></td></tr>`).join('')}</tbody></table>`;
42
+ res.querySelectorAll('[data-i]').forEach(b=>b.addEventListener('click',()=>{if(this._onSelect)this._onSelect(ms[+b.dataset.i]);this.close();}));
43
+ } catch { res.innerHTML = '<p style="color:#ff6b6b;font-size:13px;text-align:center;margin:24px 0">Search failed.</p>'; }
44
+ btn.textContent = 'Search'; btn.disabled = false;
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">${this._buildBody()}</div>`; }
48
+ destroy() { this.close(); }
49
+ }
50
+ window.OasisSearchMission = OasisSearchMission;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * OasisSearchOasisNft — search OASIS NFTs popup widget
3
+ * Usage: const w = new OasisSearchOasisNft({ onSelect: (nft) => {} }); w.open();
4
+ */
5
+ class OasisSearchOasisNft {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onSelect = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onSelect = onSelect; 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:640px;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-input,.oasis-select{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,.oasis-select:focus{border-color:rgba(0,200,255,.5)}.oasis-select option{background:#0a1628}.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:disabled{opacity:.5;cursor:not-allowed}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700;cursor:pointer}.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-nft-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px;margin-top:16px}.oasis-nft-card{background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:16px;text-align:center;cursor:pointer;transition:border-color .2s}.oasis-nft-card:hover{border-color:rgba(0,200,255,.4)}.oasis-nft-thumb{font-size:36px;margin-bottom:8px}.oasis-nft-card-name{font-size:12px;font-weight:700;color:#fff;margin-bottom:4px}.oasis-nft-price{font-size:11px;color:#00c8ff}.oasis-search-row{display:flex;gap:10px;margin-bottom:16px}.oasis-search-row .oasis-input{flex:1}.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}`;
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">Search NFTs</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body">${this._buildBody()}</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._overlay.querySelector('#oasis-snft-btn').addEventListener('click', () => this._doSearch());
26
+ this._overlay.querySelector('#oasis-snft-q').addEventListener('keydown', e => { if (e.key === 'Enter') this._doSearch(); });
27
+ }
28
+ _buildBody() {
29
+ return `<div class="oasis-search-row"><input class="oasis-input" id="oasis-snft-q" type="text" placeholder="Search by name, collection, or creator…" /><button class="oasis-btn" id="oasis-snft-btn">Search</button></div><div id="oasis-snft-results"><p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">Search the OASIS NFT marketplace.</p></div>`;
30
+ }
31
+ async _doSearch() {
32
+ const q = this._overlay.querySelector('#oasis-snft-q').value.trim();
33
+ const res = this._overlay.querySelector('#oasis-snft-results');
34
+ const btn = this._overlay.querySelector('#oasis-snft-btn');
35
+ btn.textContent = '…'; btn.disabled = true;
36
+ try {
37
+ const r = await fetch(`${this._apiUrl}/api/nft/search?query=${encodeURIComponent(q)}`);
38
+ const data = await r.json();
39
+ const nfts = Array.isArray(data) ? data : (data.result || []);
40
+ if (!nfts.length) res.innerHTML = '<div class="oasis-cs"><div class="oasis-cs-icon">🖼️</div><p>No NFTs found.</p></div>';
41
+ else { res.innerHTML = `<div class="oasis-nft-grid">${nfts.map((n,i)=>`<div class="oasis-nft-card" data-i="${i}"><div class="oasis-nft-thumb">🖼️</div><div class="oasis-nft-card-name">${n.name||'NFT'}</div><div class="oasis-nft-price">${n.price||'—'} ${n.currency||'ETH'}</div></div>`).join('')}</div>`; res.querySelectorAll('[data-i]').forEach(b=>b.addEventListener('click',()=>{if(this._onSelect)this._onSelect(nfts[+b.dataset.i]);this.close();})); }
42
+ } catch { res.innerHTML = '<p style="color:#ff6b6b;font-size:13px;text-align:center;margin:24px 0">Search failed.</p>'; }
43
+ btn.textContent = 'Search'; btn.disabled = false;
44
+ }
45
+ 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(); }
46
+ _render(c) { c.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
47
+ destroy() { this.close(); }
48
+ }
49
+ window.OasisSearchOasisNft = OasisSearchOasisNft;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * OasisSearchProfiles — search game profiles popup widget
3
+ * Usage: const w = new OasisSearchProfiles({ onSelect: (profile) => {} }); w.open();
4
+ */
5
+ class OasisSearchProfiles {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onSelect = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onSelect = onSelect; 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-input,.oasis-select{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}.oasis-btn:hover{opacity:.85}.oasis-btn:disabled{opacity:.5;cursor:not-allowed}.oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700;cursor:pointer}.oasis-badge-blue{background:rgba(0,200,255,.15);color:#00c8ff}.oasis-badge-green{background:rgba(0,200,100,.15);color:#00c864}.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-search-row{display:flex;gap:10px;margin-bottom:16px}.oasis-search-row .oasis-input{flex:1}.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}`;
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">Search Profiles</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body">${this._buildBody()}</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._overlay.querySelector('#oasis-sp-btn').addEventListener('click', () => this._doSearch());
26
+ this._overlay.querySelector('#oasis-sp-q').addEventListener('keydown', e => { if (e.key === 'Enter') this._doSearch(); });
27
+ }
28
+ _buildBody() {
29
+ return `<div class="oasis-search-row"><input class="oasis-input" id="oasis-sp-q" type="text" placeholder="Search game profiles…" /><button class="oasis-btn" id="oasis-sp-btn">Search</button></div><div id="oasis-sp-results"><p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">Search OASIS game profiles.</p></div>`;
30
+ }
31
+ async _doSearch() {
32
+ const q = this._overlay.querySelector('#oasis-sp-q').value.trim();
33
+ const res = this._overlay.querySelector('#oasis-sp-results');
34
+ const btn = this._overlay.querySelector('#oasis-sp-btn');
35
+ btn.textContent = '…'; btn.disabled = true;
36
+ try {
37
+ const r = await fetch(`${this._apiUrl}/api/game/profiles/search?query=${encodeURIComponent(q)}`);
38
+ const data = await r.json();
39
+ const profiles = Array.isArray(data) ? data : (data.result || []);
40
+ if (!profiles.length) res.innerHTML = '<p style="color:#7a9bbf;font-size:13px;text-align:center;margin:24px 0">No profiles found.</p>';
41
+ else res.innerHTML = `<table class="oasis-table"><thead><tr><th>Username</th><th>Level</th><th>XP</th><th></th></tr></thead><tbody>${profiles.map((p,i)=>`<tr><td>${p.username||'—'}</td><td>${p.level??1}</td><td>${p.xp??0}</td><td><button class="oasis-badge oasis-badge-blue" data-i="${i}">View</button></td></tr>`).join('')}</tbody></table>`;
42
+ res.querySelectorAll('[data-i]').forEach(b=>b.addEventListener('click',()=>{if(this._onSelect)this._onSelect(profiles[+b.dataset.i]);this.close();}));
43
+ } catch { res.innerHTML = '<p style="color:#ff6b6b;font-size:13px;text-align:center;margin:24px 0">Search failed.</p>'; }
44
+ btn.textContent = 'Search'; btn.disabled = false;
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">${this._buildBody()}</div>`; }
48
+ destroy() { this.close(); }
49
+ }
50
+ window.OasisSearchProfiles = OasisSearchProfiles;
package/src/seeds.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * OasisSeeds — standalone Seeds widget
3
+ * Usage: new OasisSeeds({ container: document.getElementById('seeds') })
4
+ */
5
+ (function () {
6
+ const SEEDS = [
7
+ { id: '1', name: 'Wisdom Seed', type: 'Spiritual', rarity: 'rare', karma: 50, planted: false },
8
+ { id: '2', name: 'Healing Seed', type: 'Health', rarity: 'common', karma: 20, planted: false },
9
+ { id: '3', name: 'Unity Seed', type: 'Community', rarity: 'epic', karma: 150, planted: false },
10
+ { id: '4', name: 'Truth Seed', type: 'Knowledge', rarity: 'legendary', karma: 500, planted: false },
11
+ { id: '5', name: 'Love Seed', type: 'Spiritual', rarity: 'common', karma: 30, planted: false },
12
+ { id: '6', name: 'Peace Seed', type: 'Community', rarity: 'rare', karma: 75, planted: false },
13
+ ];
14
+ const ICONS = { Spiritual: '✨', Health: '💚', Community: '🤝', Knowledge: '📖' };
15
+ const RARITY_COLORS = { common: '#7a9bbf', rare: '#5ba8ff', epic: '#b87fff', legendary: '#ffb43c' };
16
+
17
+ const CSS = `
18
+ .oasis-seeds-shell{font-family:'Rajdhani',sans-serif;display:flex;flex-direction:column;gap:20px}
19
+ .oasis-seeds-header{text-align:center}
20
+ .oasis-seeds-title{font-family:'Orbitron',sans-serif;font-size:20px;color:#fff;margin:0 0 6px}
21
+ .oasis-seeds-sub{font-size:13px;color:#7a9bbf;margin:0}
22
+ .oasis-seeds-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:16px}
23
+ .oasis-seed-card{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.15);border-radius:12px;padding:18px 14px;display:flex;flex-direction:column;align-items:center;gap:10px;transition:border-color .2s;cursor:default}
24
+ .oasis-seed-card:hover{border-color:rgba(0,200,255,.35)}
25
+ .oasis-seed-card.planted{border-color:rgba(72,220,130,.3);background:rgba(72,220,130,.04)}
26
+ .oasis-seed-rarity{font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;border-radius:999px;padding:3px 10px;border:1px solid currentColor}
27
+ .oasis-seed-icon{font-size:36px}
28
+ .oasis-seed-name{font-family:'Orbitron',sans-serif;font-size:13px;color:#fff;text-align:center}
29
+ .oasis-seed-type{font-size:11px;color:#7a9bbf}
30
+ .oasis-seed-karma{font-size:12px;color:#48dc82;font-weight:600}
31
+ .oasis-seed-btn{width:100%;background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:7px;color:#fff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;letter-spacing:.08em;padding:8px;cursor:pointer;transition:opacity .2s}
32
+ .oasis-seed-btn:hover{opacity:.85}
33
+ .oasis-seed-planted{font-size:12px;color:#48dc82;font-weight:600}
34
+ `;
35
+
36
+ class OasisSeeds {
37
+ constructor({ container } = {}) {
38
+ this._container = typeof container === 'string' ? document.querySelector(container) : container;
39
+ if (!this._container) throw new Error('OasisSeeds: container not found');
40
+ if (!document.getElementById('oasis-seeds-styles')) {
41
+ const s = document.createElement('style'); s.id = 'oasis-seeds-styles'; s.textContent = CSS;
42
+ document.head.appendChild(s);
43
+ }
44
+ this._seeds = SEEDS.map(s => ({ ...s }));
45
+ this._render();
46
+ }
47
+
48
+ _render() {
49
+ this._container.innerHTML = `
50
+ <div class="oasis-seeds-shell">
51
+ <div class="oasis-seeds-header">
52
+ <h2 class="oasis-seeds-title">🌱 Seeds</h2>
53
+ <p class="oasis-seeds-sub">Plant seeds to grow your OASIS avatar and earn karma.</p>
54
+ </div>
55
+ <div class="oasis-seeds-grid">
56
+ ${this._seeds.map(s => `
57
+ <div class="oasis-seed-card${s.planted ? ' planted' : ''}" data-id="${s.id}">
58
+ <div class="oasis-seed-rarity" style="color:${RARITY_COLORS[s.rarity]};border-color:${RARITY_COLORS[s.rarity]}44">${s.rarity}</div>
59
+ <div class="oasis-seed-icon">${ICONS[s.type] || '🌱'}</div>
60
+ <div class="oasis-seed-name">${s.name}</div>
61
+ <div class="oasis-seed-type">${s.type}</div>
62
+ <div class="oasis-seed-karma">+${s.karma} Karma</div>
63
+ ${s.planted
64
+ ? '<div class="oasis-seed-planted">🌿 Growing…</div>'
65
+ : `<button class="oasis-seed-btn" data-plant="${s.id}">Plant Seed</button>`}
66
+ </div>`).join('')}
67
+ </div>
68
+ </div>`;
69
+ this._container.querySelectorAll('[data-plant]').forEach(btn => {
70
+ btn.addEventListener('click', () => { this._plant(btn.dataset.plant); });
71
+ });
72
+ }
73
+
74
+ _plant(id) {
75
+ const s = this._seeds.find(x => x.id === id);
76
+ if (s) { s.planted = true; this._render(); }
77
+ }
78
+
79
+ destroy() { this._container.innerHTML = ''; }
80
+ }
81
+
82
+ window.OasisSeeds = OasisSeeds;
83
+ })();