@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,41 @@
1
+ /**
2
+ * OasisManageOasisNft — manage OASIS NFTs popup widget
3
+ * Usage: const w = new OasisManageOasisNft({}); w.open();
4
+ */
5
+ class OasisManageOasisNft {
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: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-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-badge-red{background:rgba(255,80,80,.15);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-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-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}`;
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">Manage NFTs</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body" id="oasis-mnft-body"><div class="oasis-cs"><div class="oasis-cs-icon">⏳</div><p>Loading NFTs…</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-mnft-body');
29
+ try {
30
+ const r = await fetch(`${this._apiUrl}/api/nft`);
31
+ const data = await r.json();
32
+ const nfts = Array.isArray(data) ? data : (data.result || []);
33
+ if (!nfts.length) { body.innerHTML = '<div class="oasis-cs"><div class="oasis-cs-icon">🖼️</div><h3>No NFTs</h3><p>No NFTs in your collection.</p></div>'; return; }
34
+ body.innerHTML = `<div class="oasis-stat-row"><div class="oasis-stat"><div class="oasis-stat-val">${nfts.length}</div><div class="oasis-stat-lbl">Total NFTs</div></div><div class="oasis-stat"><div class="oasis-stat-val">${nfts.filter(n=>n.isListed).length}</div><div class="oasis-stat-lbl">Listed</div></div></div><table class="oasis-table"><thead><tr><th>Name</th><th>Collection</th><th>Chain</th><th>Status</th></tr></thead><tbody>${nfts.map(n=>`<tr><td>${n.name||'—'}</td><td>${n.collection||'—'}</td><td><span class="oasis-badge oasis-badge-blue">${n.chain||'ETH'}</span></td><td><span class="oasis-badge ${n.isListed?'oasis-badge-green':'oasis-badge-yellow'}">${n.isListed?'Listed':'Held'}</span></td></tr>`).join('')}</tbody></table>`;
35
+ } catch { body.innerHTML = '<div class="oasis-msg-error">Failed to load NFTs.</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.OasisManageOasisNft = OasisManageOasisNft;
package/src/message.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * OasisMessage — messaging UI popup widget
3
+ * Usage: const w = new OasisMessage({ recipientId: '...', recipientName: 'Alice' }); w.open();
4
+ */
5
+ class OasisMessage {
6
+ constructor({ container, recipientId = '', recipientName = 'Avatar', apiUrl = 'https://api.web4.oasisomniverse.one', onClose = null } = {}) {
7
+ this._recipientId = recipientId; this._recipientName = recipientName; 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-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-chat-area{background:rgba(0,0,0,.2);border:1px solid rgba(0,200,255,.1);border-radius:10px;height:240px;overflow-y:auto;padding:12px;margin-bottom:12px;display:flex;flex-direction:column;gap:8px}.oasis-chat-bubble{border-radius:10px;padding:8px 12px;font-size:13px;color:#fff;max-width:75%;background:rgba(0,200,255,.1)}.oasis-chat-bubble.mine{background:rgba(0,128,255,.2);align-self:flex-end;margin-left:auto}.oasis-chat-meta{font-size:10px;color:#7a9bbf;margin-top:3px}.oasis-chat-input-row{display:flex;gap:8px}.oasis-chat-input-row .oasis-input{flex:1}.oasis-msg{border-radius:8px;padding:10px 14px;font-size:13px;margin-bottom:12px}.oasis-msg-error{background:rgba(255,80,80,.12);border:1px solid rgba(255,80,80,.3);color:#ff6b6b}`;
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">Message: ${this._recipientName}</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._attachEvents();
26
+ }
27
+ _buildBody() {
28
+ return `<div id="oasis-msg-err"></div>
29
+ <div class="oasis-chat-area" id="oasis-chat-area">
30
+ <div style="color:#7a9bbf;font-size:12px;text-align:center;padding:8px">Start of your conversation with ${this._recipientName}</div>
31
+ </div>
32
+ <div class="oasis-chat-input-row">
33
+ <input class="oasis-input" id="oasis-msg-input" type="text" placeholder="Type a message…" />
34
+ <button class="oasis-btn" id="oasis-msg-send" style="width:auto;padding:10px 16px">Send</button>
35
+ </div>`;
36
+ }
37
+ _attachEvents() {
38
+ const send = () => {
39
+ const input = this._overlay.querySelector('#oasis-msg-input');
40
+ const area = this._overlay.querySelector('#oasis-chat-area');
41
+ const text = input.value.trim();
42
+ if (!text) return;
43
+ const bubble = document.createElement('div');
44
+ bubble.innerHTML = `<div class="oasis-chat-bubble mine">${text}<div class="oasis-chat-meta">${new Date().toLocaleTimeString()}</div></div>`;
45
+ area.appendChild(bubble);
46
+ area.scrollTop = area.scrollHeight;
47
+ input.value = '';
48
+ fetch(`${this._apiUrl}/api/message`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ recipientId: this._recipientId, message: text }) }).catch(() => {});
49
+ };
50
+ this._overlay.querySelector('#oasis-msg-send').addEventListener('click', send);
51
+ this._overlay.querySelector('#oasis-msg-input').addEventListener('keydown', e => { if (e.key === 'Enter') send(); });
52
+ }
53
+ 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(); }
54
+ _render(c) { c.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; this._overlay = { querySelector: (s) => c.querySelector(s) }; this._attachEvents(); }
55
+ destroy() { this.close(); }
56
+ }
57
+ window.OasisMessage = OasisMessage;
package/src/navbar.js ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * OasisNavBar — standalone responsive navigation bar widget
3
+ * Usage: new OasisNavBar({ container, brand, links, onLogin })
4
+ */
5
+ (function () {
6
+ const CSS = `
7
+ .oasis-nav{position:relative;z-index:100;padding:16px 32px;display:flex;align-items:center;justify-content:space-between;background:rgba(3,7,20,.92);backdrop-filter:blur(12px);border-bottom:1px solid rgba(0,200,255,.1);font-family:'Rajdhani',sans-serif;box-sizing:border-box}
8
+ .oasis-nav-brand{font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;letter-spacing:.16em;color:#fff;text-decoration:none;display:flex;align-items:center;gap:10px;white-space:nowrap}
9
+ .oasis-nav-brand-icon{color:#00c8ff;font-size:16px}
10
+ .oasis-nav-links{display:flex;gap:16px;list-style:none;margin:0;padding:0}
11
+ .oasis-nav-links a{font-family:'Share Tech Mono',monospace;font-size:11px;letter-spacing:.08em;color:#a8bfd8;text-decoration:none;transition:color .2s;text-transform:uppercase}
12
+ .oasis-nav-links a:hover{color:#00c8ff}
13
+ .oasis-nav-right{display:flex;align-items:center;gap:10px;flex-shrink:0}
14
+ .oasis-nav-login{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:7px;color:#fff;font-family:'Orbitron',sans-serif;font-size:10px;font-weight:700;letter-spacing:.08em;padding:8px 16px;cursor:pointer;white-space:nowrap;transition:opacity .2s}
15
+ .oasis-nav-login:hover{opacity:.85}
16
+ .oasis-nav-toggle{display:none;flex-direction:column;justify-content:center;gap:5px;width:32px;height:32px;background:none;border:none;cursor:pointer;padding:0}
17
+ .oasis-nav-toggle span{display:block;width:100%;height:2px;background:#a8bfd8;transition:transform .25s,opacity .25s;border-radius:1px}
18
+ .oasis-nav-toggle.open span:nth-child(1){transform:translateY(7px) rotate(45deg)}
19
+ .oasis-nav-toggle.open span:nth-child(2){opacity:0}
20
+ .oasis-nav-toggle.open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
21
+ @media(max-width:900px){
22
+ .oasis-nav-toggle{display:flex}
23
+ .oasis-nav-links{position:fixed;top:0;right:0;height:100vh;width:min(78vw,300px);flex-direction:column;justify-content:flex-start;align-items:flex-start;gap:14px;padding:72px 28px 24px;overflow-y:auto;background:rgba(3,7,20,.98);border-left:1px solid rgba(0,200,255,.12);transform:translateX(100%);transition:transform .3s ease;z-index:200}
24
+ .oasis-nav-links.open{transform:translateX(0)}
25
+ }
26
+ `;
27
+
28
+ class OasisNavBar {
29
+ constructor({
30
+ container,
31
+ brand = 'OASIS',
32
+ brandIcon = '✦',
33
+ links = [],
34
+ loginLabel = 'Login',
35
+ onLogin = null,
36
+ } = {}) {
37
+ this._container = typeof container === 'string' ? document.querySelector(container) : container;
38
+ if (!this._container) throw new Error('OasisNavBar: container not found');
39
+ if (!document.getElementById('oasis-nav-styles')) {
40
+ const s = document.createElement('style'); s.id = 'oasis-nav-styles'; s.textContent = CSS;
41
+ document.head.appendChild(s);
42
+ }
43
+ this._brand = brand;
44
+ this._brandIcon = brandIcon;
45
+ this._links = links;
46
+ this._loginLabel = loginLabel;
47
+ this._onLogin = onLogin;
48
+ this._open = false;
49
+ this._render();
50
+ }
51
+
52
+ _render() {
53
+ this._container.innerHTML = `
54
+ <nav class="oasis-nav">
55
+ <a class="oasis-nav-brand" href="#">
56
+ <span class="oasis-nav-brand-icon">${this._brandIcon}</span>
57
+ <span>${this._brand}</span>
58
+ </a>
59
+ <button class="oasis-nav-toggle${this._open ? ' open' : ''}" id="oasis-nav-toggle" aria-label="Menu">
60
+ <span></span><span></span><span></span>
61
+ </button>
62
+ <ul class="oasis-nav-links${this._open ? ' open' : ''}">
63
+ ${this._links.map(l => `<li><a href="${l.href || '#'}">${l.label}</a></li>`).join('')}
64
+ </ul>
65
+ ${this._onLogin ? `<div class="oasis-nav-right"><button class="oasis-nav-login" id="oasis-nav-login">${this._loginLabel}</button></div>` : ''}
66
+ </nav>`;
67
+ document.getElementById('oasis-nav-toggle')?.addEventListener('click', () => {
68
+ this._open = !this._open; this._render();
69
+ });
70
+ document.getElementById('oasis-nav-login')?.addEventListener('click', this._onLogin);
71
+ this._container.querySelectorAll('.oasis-nav-links a').forEach(a => {
72
+ a.addEventListener('click', () => { this._open = false; this._render(); });
73
+ });
74
+ }
75
+
76
+ destroy() { this._container.innerHTML = ''; }
77
+ }
78
+
79
+ window.OasisNavBar = OasisNavBar;
80
+ })();
@@ -0,0 +1,79 @@
1
+ /**
2
+ * OasisModal — reusable modal wrapper
3
+ * Usage:
4
+ * const modal = new OasisModal({ title: 'My Modal', content: '<p>Hello</p>' })
5
+ * modal.open() / modal.close() / modal.destroy()
6
+ */
7
+ class OasisModal {
8
+ constructor({ title = '', content = '', accentColor = '#00c8ff', onClose = null } = {}) {
9
+ this._onClose = onClose;
10
+ this._el = null;
11
+ this._title = title;
12
+ this._content = content;
13
+ this._accent = accentColor;
14
+ this._build();
15
+ }
16
+
17
+ _build() {
18
+ const el = document.createElement('div');
19
+ el.className = 'oasis-modal-backdrop';
20
+ el.innerHTML = `
21
+ <div class="oasis-modal-box" role="dialog" aria-modal="true">
22
+ <div class="oasis-modal-header" style="border-bottom-color:${this._accent}33">
23
+ <span class="oasis-modal-title" style="color:${this._accent}">${this._title}</span>
24
+ <button class="oasis-modal-close" aria-label="Close">&#x2715;</button>
25
+ </div>
26
+ <div class="oasis-modal-body">${this._content}</div>
27
+ </div>
28
+ `;
29
+ el.querySelector('.oasis-modal-close').addEventListener('click', () => this.close());
30
+ el.addEventListener('click', e => { if (e.target === el) this.close(); });
31
+ document.addEventListener('keydown', this._onKey = e => { if (e.key === 'Escape') this.close(); });
32
+ this._el = el;
33
+ this._injectStyles();
34
+ }
35
+
36
+ _injectStyles() {
37
+ if (document.getElementById('oasis-modal-styles')) return;
38
+ const s = document.createElement('style');
39
+ s.id = 'oasis-modal-styles';
40
+ s.textContent = `
41
+ .oasis-modal-backdrop{position:fixed;inset:0;background:rgba(3,7,20,.75);backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;z-index:9999;opacity:0;transition:opacity .25s}
42
+ .oasis-modal-backdrop.is-open{opacity:1}
43
+ .oasis-modal-box{background:#0d1829;border:1px solid rgba(0,200,255,.18);border-radius:16px;padding:28px;width:min(520px,92vw);max-height:88vh;overflow-y:auto;transform:translateY(16px);transition:transform .25s}
44
+ .oasis-modal-backdrop.is-open .oasis-modal-box{transform:none}
45
+ .oasis-modal-header{display:flex;align-items:center;justify-content:space-between;padding-bottom:14px;border-bottom:1px solid;margin-bottom:20px}
46
+ .oasis-modal-title{font-family:'Orbitron',sans-serif;font-size:15px;font-weight:700;letter-spacing:.06em}
47
+ .oasis-modal-close{background:none;border:none;color:#7a9bbf;font-size:18px;cursor:pointer;padding:0;line-height:1}
48
+ .oasis-modal-close:hover{color:#fff}
49
+ .oasis-modal-body{color:#a8bfd8;font-size:14px;line-height:1.6}
50
+ `;
51
+ document.head.appendChild(s);
52
+ }
53
+
54
+ setContent(html) {
55
+ this._el.querySelector('.oasis-modal-body').innerHTML = html;
56
+ }
57
+
58
+ open() {
59
+ document.body.appendChild(this._el);
60
+ requestAnimationFrame(() => this._el.classList.add('is-open'));
61
+ document.body.style.overflow = 'hidden';
62
+ }
63
+
64
+ close() {
65
+ this._el.classList.remove('is-open');
66
+ this._el.addEventListener('transitionend', () => {
67
+ this._el.remove();
68
+ document.body.style.overflow = '';
69
+ }, { once: true });
70
+ if (this._onClose) this._onClose();
71
+ }
72
+
73
+ destroy() {
74
+ document.removeEventListener('keydown', this._onKey);
75
+ this._el.remove();
76
+ }
77
+ }
78
+
79
+ window.OasisModal = OasisModal;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * OasisOffChainManagement — manage off-chain data providers popup widget
3
+ * Usage: const w = new OasisOffChainManagement({}); w.open();
4
+ */
5
+ class OasisOffChainManagement {
6
+ constructor({ container, onClose = null } = {}) {
7
+ 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:680px;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-red{background:rgba(255,80,80,.15);color:#ff6b6b}.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-section-title{font-size:11px;font-weight:700;color:#7a9bbf;letter-spacing:.08em;text-transform:uppercase;margin:0 0 12px}.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">Off-Chain Management</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
+ }
26
+ _buildBody() {
27
+ const providers = [{ name: 'MongoDB', type: 'Document DB', status: 'connected', records: 1243 }, { name: 'Neo4j', type: 'Graph DB', status: 'connected', records: 892 }, { name: 'SQLite', type: 'Relational DB', status: 'disconnected', records: 0 }, { name: 'IPFS', type: 'Distributed FS', status: 'connected', records: 56 }];
28
+ return `<p class="oasis-section-title">Off-Chain Providers</p>
29
+ ${providers.map(p => `<div class="oasis-info-block" style="display:flex;align-items:center;justify-content:space-between"><div><div class="oasis-info-label">${p.name} — ${p.type}</div><div class="oasis-info-value">${p.records.toLocaleString()} records</div></div><span class="oasis-badge ${p.status === 'connected' ? 'oasis-badge-green' : 'oasis-badge-red'}">${p.status}</span></div>`).join('')}
30
+ <div style="text-align:right;margin-top:16px"><button class="oasis-btn" onclick="this.textContent='Refreshing…';setTimeout(()=>this.textContent='Refresh All',1500)">Refresh All</button></div>`;
31
+ }
32
+ 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(); }
33
+ _render(c) { c.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
34
+ destroy() { this.close(); }
35
+ }
36
+ window.OasisOffChainManagement = OasisOffChainManagement;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * OasisProviderDropdown — blockchain provider selector widget
3
+ * Usage: new OasisProviderDropdown({ container, onChange })
4
+ */
5
+ (function () {
6
+ const PROVIDERS = [
7
+ { id: 'ethereum', name: 'Ethereum', icon: '⟠', color: '#627eea' },
8
+ { id: 'solana', name: 'Solana', icon: '◎', color: '#9945ff' },
9
+ { id: 'polygon', name: 'Polygon', icon: '⬡', color: '#8247e5' },
10
+ { id: 'arbitrum', name: 'Arbitrum', icon: '🔵', color: '#2d9cdb' },
11
+ { id: 'eosio', name: 'EOSIO', icon: '🔮', color: '#443f54' },
12
+ { id: 'holochain', name: 'Holochain', icon: '⬡', color: '#00e5be' },
13
+ { id: 'thegraph', name: 'The Graph', icon: '📊', color: '#6f4cff' },
14
+ ];
15
+
16
+ const CSS = `
17
+ .oasis-pd-shell{position:relative;font-family:'Rajdhani',sans-serif}
18
+ .oasis-pd-trigger{display:flex;align-items:center;gap:10px;background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.25);border-radius:10px;padding:10px 14px;cursor:pointer;transition:border-color .2s;color:#fff;width:100%;box-sizing:border-box}
19
+ .oasis-pd-trigger:hover,.oasis-pd-trigger.open{border-color:rgba(0,200,255,.5)}
20
+ .oasis-pd-icon{font-size:20px;width:28px;text-align:center}
21
+ .oasis-pd-label{flex:1;text-align:left}
22
+ .oasis-pd-name{font-family:'Orbitron',sans-serif;font-size:13px;font-weight:700}
23
+ .oasis-pd-sub{font-size:11px;color:#7a9bbf;margin-top:2px}
24
+ .oasis-pd-arrow{font-size:10px;color:#7a9bbf;transition:transform .2s}
25
+ .oasis-pd-arrow.open{transform:rotate(180deg)}
26
+ .oasis-pd-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;background:#0d1b2e;border:1px solid rgba(0,200,255,.2);border-radius:10px;overflow:hidden;z-index:999;box-shadow:0 8px 32px rgba(0,0,0,.5)}
27
+ .oasis-pd-option{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;transition:background .15s;color:#e0f0ff}
28
+ .oasis-pd-option:hover{background:rgba(0,200,255,.1)}
29
+ .oasis-pd-option.selected{background:rgba(0,200,255,.12);border-left:3px solid #00c8ff}
30
+ .oasis-pd-opt-icon{font-size:18px;width:24px;text-align:center}
31
+ .oasis-pd-opt-name{font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700}
32
+ `;
33
+
34
+ class OasisProviderDropdown {
35
+ constructor({ container, onChange, defaultProvider = 'ethereum' } = {}) {
36
+ this._container = typeof container === 'string' ? document.querySelector(container) : container;
37
+ if (!this._container) throw new Error('OasisProviderDropdown: container not found');
38
+ if (!document.getElementById('oasis-pd-styles')) {
39
+ const s = document.createElement('style'); s.id = 'oasis-pd-styles'; s.textContent = CSS;
40
+ document.head.appendChild(s);
41
+ }
42
+ this._selected = PROVIDERS.find(p => p.id === defaultProvider) || PROVIDERS[0];
43
+ this._open = false;
44
+ this._onChange = onChange || (() => {});
45
+ this._render();
46
+ document.addEventListener('click', this._onDocClick.bind(this));
47
+ }
48
+
49
+ _render() {
50
+ const p = this._selected;
51
+ this._container.innerHTML = `
52
+ <div class="oasis-pd-shell">
53
+ <div class="oasis-pd-trigger${this._open ? ' open' : ''}" id="oasis-pd-trigger">
54
+ <div class="oasis-pd-icon" style="color:${p.color}">${p.icon}</div>
55
+ <div class="oasis-pd-label">
56
+ <div class="oasis-pd-name">${p.name}</div>
57
+ <div class="oasis-pd-sub">Active Provider</div>
58
+ </div>
59
+ <div class="oasis-pd-arrow${this._open ? ' open' : ''}">▼</div>
60
+ </div>
61
+ ${this._open ? `<div class="oasis-pd-menu" id="oasis-pd-menu">
62
+ ${PROVIDERS.map(pr => `
63
+ <div class="oasis-pd-option${pr.id === p.id ? ' selected' : ''}" data-id="${pr.id}">
64
+ <div class="oasis-pd-opt-icon" style="color:${pr.color}">${pr.icon}</div>
65
+ <div class="oasis-pd-opt-name">${pr.name}</div>
66
+ </div>`).join('')}
67
+ </div>` : ''}
68
+ </div>`;
69
+ document.getElementById('oasis-pd-trigger')?.addEventListener('click', e => { e.stopPropagation(); this._toggle(); });
70
+ this._container.querySelectorAll('[data-id]').forEach(el => {
71
+ el.addEventListener('click', e => { e.stopPropagation(); this._select(el.dataset.id); });
72
+ });
73
+ }
74
+
75
+ _toggle() { this._open = !this._open; this._render(); }
76
+ _select(id) {
77
+ this._selected = PROVIDERS.find(p => p.id === id) || this._selected;
78
+ this._open = false; this._render(); this._onChange(this._selected);
79
+ }
80
+ _onDocClick() { if (this._open) { this._open = false; this._render(); } }
81
+
82
+ get value() { return this._selected; }
83
+ destroy() { document.removeEventListener('click', this._onDocClick.bind(this)); this._container.innerHTML = ''; }
84
+ }
85
+
86
+ window.OasisProviderDropdown = OasisProviderDropdown;
87
+ })();
@@ -0,0 +1,54 @@
1
+ /**
2
+ * OasisPurchaseOasisNft — purchase an OASIS NFT popup widget
3
+ * Usage: const w = new OasisPurchaseOasisNft({ nftId: '...', onSuccess: (tx) => {} }); w.open();
4
+ */
5
+ class OasisPurchaseOasisNft {
6
+ constructor({ container, nftId = '', nftName = 'OASIS NFT', price = '0.00', currency = 'ETH', apiUrl = 'https://api.web4.oasisomniverse.one', onSuccess = null, onClose = null } = {}) {
7
+ this._nftId = nftId; this._nftName = nftName; this._price = price; this._currency = currency;
8
+ this._apiUrl = apiUrl; this._onSuccess = onSuccess; this._onClose = onClose; this._overlay = null;
9
+ this._injectStyles(); if (container) this._render(container);
10
+ }
11
+ _injectStyles() {
12
+ if (document.getElementById('oasis-popup-styles')) return;
13
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
14
+ 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-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-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-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;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-info-block{background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:16px;margin-bottom:16px}.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-price-big{font-family:'Orbitron',sans-serif;font-size:28px;font-weight:700;color:#00c8ff;margin:4px 0}.oasis-nft-preview{text-align:center;padding:20px;background:rgba(0,200,255,.04);border-radius:10px;margin-bottom:20px;border:1px solid rgba(0,200,255,.1)}.oasis-nft-icon{font-size:56px;margin-bottom:8px}.oasis-nft-preview-name{font-size:15px;font-weight:700;color:#fff}.oasis-divider{border:none;border-top:1px solid rgba(0,200,255,.1);margin:16px 0}`;
15
+ document.head.appendChild(s);
16
+ }
17
+ open() {
18
+ if (this._overlay) return;
19
+ this._overlay = document.createElement('div');
20
+ this._overlay.className = 'oasis-popup-overlay';
21
+ this._overlay.innerHTML = `<div class="oasis-popup-modal"><div class="oasis-popup-header"><span class="oasis-popup-title">Purchase NFT</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body">${this._buildBody()}</div></div>`;
22
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
23
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
24
+ document.body.appendChild(this._overlay);
25
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
26
+ this._overlay.querySelector('#oasis-pnft-confirm').addEventListener('click', () => this._doPurchase());
27
+ }
28
+ _buildBody() {
29
+ return `<div class="oasis-nft-preview"><div class="oasis-nft-icon">🖼️</div><div class="oasis-nft-preview-name">${this._nftName}</div></div>
30
+ <div id="oasis-pnft-msg"></div>
31
+ <div class="oasis-info-block"><div class="oasis-info-label">Price</div><div class="oasis-price-big">${this._price} ${this._currency}</div></div>
32
+ <div class="oasis-field"><label class="oasis-label">Pay with</label><select class="oasis-select" id="oasis-pnft-wallet"><option value="metamask">MetaMask</option><option value="walletconnect">WalletConnect</option><option value="seeds">SEEDS Token</option></select></div>
33
+ <hr class="oasis-divider" />
34
+ <p style="color:#7a9bbf;font-size:12px;margin:0 0 16px">By confirming, you agree to the OASIS NFT Terms of Service. Transaction fees may apply.</p>
35
+ <button class="oasis-btn" id="oasis-pnft-confirm">Confirm Purchase</button>`;
36
+ }
37
+ async _doPurchase() {
38
+ const msgEl = this._overlay.querySelector('#oasis-pnft-msg');
39
+ const btn = this._overlay.querySelector('#oasis-pnft-confirm');
40
+ btn.textContent = 'Processing…'; btn.disabled = true;
41
+ try {
42
+ const res = await fetch(`${this._apiUrl}/api/nft/purchase`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nftId: this._nftId, wallet: this._overlay.querySelector('#oasis-pnft-wallet').value }) });
43
+ const data = await res.json();
44
+ if (!res.ok || data.isError) throw new Error(data.message || 'Purchase failed');
45
+ msgEl.innerHTML = '<div class="oasis-msg oasis-msg-success">Purchase successful! NFT transferred to your wallet.</div>';
46
+ if (this._onSuccess) this._onSuccess(data);
47
+ btn.textContent = 'Purchased!';
48
+ } catch (e) { msgEl.innerHTML = `<div class="oasis-msg oasis-msg-error">${e.message}</div>`; btn.textContent = 'Confirm Purchase'; btn.disabled = false; }
49
+ }
50
+ 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(); }
51
+ _render(c) { c.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
52
+ destroy() { this.close(); }
53
+ }
54
+ window.OasisPurchaseOasisNft = OasisPurchaseOasisNft;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * OasisPurchaseOasisVirtualLandNft — purchase OASIS virtual land NFT popup widget
3
+ * Usage: const w = new OasisPurchaseOasisVirtualLandNft({ landId: '...' }); w.open();
4
+ */
5
+ class OasisPurchaseOasisVirtualLandNft {
6
+ constructor({ container, landId = '', landName = 'Virtual Land', price = '0.00', currency = 'ETH', apiUrl = 'https://api.web4.oasisomniverse.one', onSuccess = null, onClose = null } = {}) {
7
+ this._landId = landId; this._landName = landName; this._price = price; this._currency = currency;
8
+ this._apiUrl = apiUrl; this._onSuccess = onSuccess; this._onClose = onClose; this._overlay = null;
9
+ this._injectStyles(); if (container) this._render(container);
10
+ }
11
+ _injectStyles() {
12
+ if (document.getElementById('oasis-popup-styles')) return;
13
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
14
+ 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-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-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;width:100%;box-sizing:border-box}.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;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-info-block{background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:16px;margin-bottom:16px}.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-price-big{font-family:'Orbitron',sans-serif;font-size:28px;font-weight:700;color:#00c8ff}.oasis-land-preview{background:linear-gradient(135deg,rgba(0,200,80,.1),rgba(0,100,200,.1));border:1px solid rgba(0,200,80,.2);border-radius:12px;padding:24px;text-align:center;margin-bottom:20px}.oasis-land-icon{font-size:48px;margin-bottom:8px}.oasis-land-name{font-size:15px;font-weight:700;color:#fff}.oasis-divider{border:none;border-top:1px solid rgba(0,200,255,.1);margin:16px 0}`;
15
+ document.head.appendChild(s);
16
+ }
17
+ open() {
18
+ if (this._overlay) return;
19
+ this._overlay = document.createElement('div');
20
+ this._overlay.className = 'oasis-popup-overlay';
21
+ this._overlay.innerHTML = `<div class="oasis-popup-modal"><div class="oasis-popup-header"><span class="oasis-popup-title">Purchase Virtual Land</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body">${this._buildBody()}</div></div>`;
22
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
23
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
24
+ document.body.appendChild(this._overlay);
25
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
26
+ this._overlay.querySelector('#oasis-pvl-confirm').addEventListener('click', () => this._doPurchase());
27
+ }
28
+ _buildBody() {
29
+ return `<div class="oasis-land-preview"><div class="oasis-land-icon">🌍</div><div class="oasis-land-name">${this._landName}</div><div style="color:#7a9bbf;font-size:12px;margin-top:4px">Our World Virtual Land Parcel</div></div>
30
+ <div id="oasis-pvl-msg"></div>
31
+ <div class="oasis-info-block"><div class="oasis-info-label">Price</div><div class="oasis-price-big">${this._price} ${this._currency}</div></div>
32
+ <div class="oasis-field"><label class="oasis-label">Pay with</label><select class="oasis-select" id="oasis-pvl-wallet"><option value="metamask">MetaMask (ETH)</option><option value="solana">Solana Wallet</option><option value="seeds">SEEDS Token</option></select></div>
33
+ <hr class="oasis-divider" />
34
+ <p style="color:#7a9bbf;font-size:12px;margin:0 0 16px">Virtual land NFTs grant you ownership of a parcel in Our World. Transaction fees apply.</p>
35
+ <button class="oasis-btn" id="oasis-pvl-confirm">Purchase Land NFT</button>`;
36
+ }
37
+ async _doPurchase() {
38
+ const msgEl = this._overlay.querySelector('#oasis-pvl-msg');
39
+ const btn = this._overlay.querySelector('#oasis-pvl-confirm');
40
+ btn.textContent = 'Processing…'; btn.disabled = true;
41
+ try {
42
+ const res = await fetch(`${this._apiUrl}/api/nft/land/purchase`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ landId: this._landId, wallet: this._overlay.querySelector('#oasis-pvl-wallet').value }) });
43
+ const data = await res.json();
44
+ if (!res.ok || data.isError) throw new Error(data.message || 'Purchase failed');
45
+ msgEl.innerHTML = '<div class="oasis-msg oasis-msg-success">Land NFT purchased! Welcome to Our World.</div>';
46
+ if (this._onSuccess) this._onSuccess(data);
47
+ btn.textContent = 'Purchased!';
48
+ } catch (e) { msgEl.innerHTML = `<div class="oasis-msg oasis-msg-error">${e.message}</div>`; btn.textContent = 'Purchase Land NFT'; btn.disabled = false; }
49
+ }
50
+ 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(); }
51
+ _render(c) { c.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
52
+ destroy() { this.close(); }
53
+ }
54
+ window.OasisPurchaseOasisVirtualLandNft = OasisPurchaseOasisVirtualLandNft;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * OasisResetPassword — set new password with token popup widget
3
+ * Usage: const w = new OasisResetPassword({ token: 'abc123', onSuccess: () => {} }); w.open();
4
+ */
5
+ class OasisResetPassword {
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">New Password</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
+ this._overlay.querySelector('#oasis-rp-submit').addEventListener('click', () => this._doReset());
32
+ }
33
+
34
+ _buildBody() {
35
+ return `
36
+ <div id="oasis-rp-msg"></div>
37
+ <div class="oasis-field"><label class="oasis-label">New Password</label><input class="oasis-input" id="oasis-rp-pass" type="password" placeholder="••••••••" /></div>
38
+ <div class="oasis-field"><label class="oasis-label">Confirm New Password</label><input class="oasis-input" id="oasis-rp-pass2" type="password" placeholder="••••••••" /></div>
39
+ <button class="oasis-btn" id="oasis-rp-submit">Set New Password</button>
40
+ `;
41
+ }
42
+
43
+ async _doReset() {
44
+ const pass = this._overlay.querySelector('#oasis-rp-pass').value;
45
+ const pass2 = this._overlay.querySelector('#oasis-rp-pass2').value;
46
+ const msgEl = this._overlay.querySelector('#oasis-rp-msg');
47
+ const btn = this._overlay.querySelector('#oasis-rp-submit');
48
+ if (!pass) { msgEl.innerHTML = '<div class="oasis-msg oasis-msg-error">Please enter a new password.</div>'; return; }
49
+ if (pass !== pass2) { msgEl.innerHTML = '<div class="oasis-msg oasis-msg-error">Passwords do not match.</div>'; return; }
50
+ btn.textContent = 'Saving…'; btn.disabled = true;
51
+ try {
52
+ const res = await fetch(`${this._apiUrl}/api/avatar/reset-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: this._token, password: pass }) });
53
+ const data = await res.json();
54
+ if (!res.ok || data.isError) throw new Error(data.message || 'Reset failed');
55
+ msgEl.innerHTML = '<div class="oasis-msg oasis-msg-success">Password updated! You can now log in.</div>';
56
+ if (this._onSuccess) this._onSuccess();
57
+ } catch (e) {
58
+ msgEl.innerHTML = `<div class="oasis-msg oasis-msg-error">${e.message}</div>`;
59
+ btn.textContent = 'Set New Password'; btn.disabled = false;
60
+ }
61
+ }
62
+
63
+ close() {
64
+ if (!this._overlay) return;
65
+ const el = this._overlay; this._overlay = null;
66
+ el.classList.remove('is-open');
67
+ el.addEventListener('transitionend', () => el.remove(), { once: true });
68
+ if (this._onClose) this._onClose();
69
+ }
70
+
71
+ _render(container) { container.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
72
+ destroy() { this.close(); }
73
+ }
74
+
75
+ window.OasisResetPassword = OasisResetPassword;