@oasisomniverse/js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/package.json +36 -0
  2. package/src/add-data.js +54 -0
  3. package/src/avatar-connect.js +126 -0
  4. package/src/avatar-wallet.js +60 -0
  5. package/src/coming-soon.js +35 -0
  6. package/src/confirmation.js +53 -0
  7. package/src/contact.js +84 -0
  8. package/src/cross-chain-management.js +36 -0
  9. package/src/edit-avatar.js +79 -0
  10. package/src/eggs.js +88 -0
  11. package/src/forgot-password.js +73 -0
  12. package/src/index.esm.js +15 -0
  13. package/src/index.js +15 -0
  14. package/src/karma-toast.js +50 -0
  15. package/src/load-data.js +56 -0
  16. package/src/login.js +154 -0
  17. package/src/manage-data.js +42 -0
  18. package/src/manage-eggs.js +41 -0
  19. package/src/manage-mission.js +42 -0
  20. package/src/manage-oasis-nft.js +41 -0
  21. package/src/message.js +57 -0
  22. package/src/navbar.js +80 -0
  23. package/src/oasis-modal.js +79 -0
  24. package/src/off-chain-management.js +36 -0
  25. package/src/provider-dropdown.js +87 -0
  26. package/src/purchase-oasis-nft.js +54 -0
  27. package/src/purchase-oasis-virtual-land-nft.js +54 -0
  28. package/src/reset-password.js +75 -0
  29. package/src/search-avatar.js +78 -0
  30. package/src/search-data.js +59 -0
  31. package/src/search-eggs.js +49 -0
  32. package/src/search-karma.js +48 -0
  33. package/src/search-mission.js +50 -0
  34. package/src/search-oasis-nft.js +49 -0
  35. package/src/search-profiles.js +50 -0
  36. package/src/seeds.js +83 -0
  37. package/src/signup.js +154 -0
  38. package/src/star-field.js +68 -0
  39. package/src/verify-email.js +77 -0
  40. package/src/view-achievements.js +42 -0
  41. package/src/view-avatar-karma.js +50 -0
  42. package/src/view-avatar.js +69 -0
  43. package/src/view-eggs.js +44 -0
  44. package/src/view-karma.js +41 -0
  45. package/src/view-leagues.js +41 -0
  46. package/src/view-mission.js +44 -0
  47. package/src/view-oasis-nft.js +46 -0
  48. package/src/view-tournaments.js +41 -0
  49. package/src/vote-karma.js +61 -0
package/src/eggs.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * OasisEggs — standalone Egg incubation widget
3
+ * Usage: new OasisEggs({ container: document.getElementById('eggs') })
4
+ */
5
+ (function () {
6
+ const EGGS = [
7
+ { id: '1', name: 'Cosmic Egg', rarity: 'legendary', hatchProgress: 75, hatched: false, reward: '+500 Karma Companion' },
8
+ { id: '2', name: 'Forest Egg', rarity: 'common', hatchProgress: 100, hatched: true, reward: 'Woodland Sprite' },
9
+ { id: '3', name: 'Astral Egg', rarity: 'epic', hatchProgress: 30, hatched: false, reward: '+200 Karma Companion' },
10
+ { id: '4', name: 'Ocean Egg', rarity: 'rare', hatchProgress: 55, hatched: false, reward: 'Sea Guardian' },
11
+ ];
12
+ const RARITY_COLORS = { common: '#7a9bbf', rare: '#5ba8ff', epic: '#b87fff', legendary: '#ffb43c' };
13
+
14
+ const CSS = `
15
+ .oasis-eggs-shell{font-family:'Rajdhani',sans-serif;display:flex;flex-direction:column;gap:20px}
16
+ .oasis-eggs-header{text-align:center}
17
+ .oasis-eggs-title{font-family:'Orbitron',sans-serif;font-size:20px;color:#fff;margin:0 0 6px}
18
+ .oasis-eggs-sub{font-size:13px;color:#7a9bbf;margin:0}
19
+ .oasis-eggs-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:16px}
20
+ .oasis-egg-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}
21
+ .oasis-egg-card:hover{border-color:rgba(0,200,255,.35)}
22
+ .oasis-egg-card.hatched{border-color:rgba(255,180,60,.3);background:rgba(255,180,60,.04)}
23
+ .oasis-egg-rarity{font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;border-radius:999px;padding:3px 10px;border:1px solid currentColor}
24
+ .oasis-egg-icon{font-size:40px}
25
+ .oasis-egg-name{font-family:'Orbitron',sans-serif;font-size:12px;color:#fff;text-align:center}
26
+ .oasis-egg-progress-wrap{width:100%;display:flex;flex-direction:column;gap:4px;align-items:center}
27
+ .oasis-egg-progress-bar{width:100%;height:6px;background:rgba(255,255,255,.1);border-radius:999px;overflow:hidden}
28
+ .oasis-egg-progress-fill{height:100%;background:linear-gradient(90deg,#00c8ff,#0080ff);border-radius:999px;transition:width .4s}
29
+ .oasis-egg-pct{font-size:11px;color:#7a9bbf}
30
+ .oasis-egg-btn{width:100%;background:linear-gradient(135deg,#ffb43c,#ff8c00);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}
31
+ .oasis-egg-btn:hover{opacity:.85}
32
+ .oasis-egg-reward{font-size:12px;color:#ffb43c;font-weight:600;text-align:center}
33
+ `;
34
+
35
+ class OasisEggs {
36
+ constructor({ container } = {}) {
37
+ this._container = typeof container === 'string' ? document.querySelector(container) : container;
38
+ if (!this._container) throw new Error('OasisEggs: container not found');
39
+ if (!document.getElementById('oasis-eggs-styles')) {
40
+ const s = document.createElement('style'); s.id = 'oasis-eggs-styles'; s.textContent = CSS;
41
+ document.head.appendChild(s);
42
+ }
43
+ this._eggs = EGGS.map(e => ({ ...e }));
44
+ this._render();
45
+ }
46
+
47
+ _render() {
48
+ this._container.innerHTML = `
49
+ <div class="oasis-eggs-shell">
50
+ <div class="oasis-eggs-header">
51
+ <h2 class="oasis-eggs-title">🥚 Eggs</h2>
52
+ <p class="oasis-eggs-sub">Hatch eggs to discover rare OASIS companions and rewards.</p>
53
+ </div>
54
+ <div class="oasis-eggs-grid">
55
+ ${this._eggs.map(e => `
56
+ <div class="oasis-egg-card${e.hatched ? ' hatched' : ''}" data-id="${e.id}">
57
+ <div class="oasis-egg-rarity" style="color:${RARITY_COLORS[e.rarity]};border-color:${RARITY_COLORS[e.rarity]}44">${e.rarity}</div>
58
+ <div class="oasis-egg-icon">${e.hatched ? '🐣' : '🥚'}</div>
59
+ <div class="oasis-egg-name">${e.name}</div>
60
+ ${e.hatched
61
+ ? `<div class="oasis-egg-reward">✨ ${e.reward}</div>`
62
+ : `<div class="oasis-egg-progress-wrap">
63
+ <div class="oasis-egg-progress-bar"><div class="oasis-egg-progress-fill" style="width:${e.hatchProgress}%"></div></div>
64
+ <div class="oasis-egg-pct">${e.hatchProgress}%</div>
65
+ </div>
66
+ <button class="oasis-egg-btn" data-incubate="${e.id}">Incubate</button>`}
67
+ </div>`).join('')}
68
+ </div>
69
+ </div>`;
70
+ this._container.querySelectorAll('[data-incubate]').forEach(btn => {
71
+ btn.addEventListener('click', () => this._incubate(btn.dataset.incubate));
72
+ });
73
+ }
74
+
75
+ _incubate(id) {
76
+ const e = this._eggs.find(x => x.id === id);
77
+ if (e && !e.hatched) {
78
+ e.hatchProgress = Math.min(100, e.hatchProgress + 25);
79
+ if (e.hatchProgress >= 100) e.hatched = true;
80
+ this._render();
81
+ }
82
+ }
83
+
84
+ destroy() { this._container.innerHTML = ''; }
85
+ }
86
+
87
+ window.OasisEggs = OasisEggs;
88
+ })();
@@ -0,0 +1,73 @@
1
+ /**
2
+ * OasisForgotPassword — request password reset popup widget
3
+ * Usage: const w = new OasisForgotPassword({ onClose: () => {} }); w.open();
4
+ */
5
+ class OasisForgotPassword {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onClose = onClose; this._overlay = null;
8
+ this._injectStyles();
9
+ if (container) this._render(container);
10
+ }
11
+
12
+ _injectStyles() {
13
+ if (document.getElementById('oasis-popup-styles')) return;
14
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
15
+ s.textContent = `.oasis-popup-overlay{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s}.oasis-popup-overlay.is-open{opacity:1}.oasis-popup-modal{background:#0a1628;border:1px solid rgba(0,200,255,.2);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.6);width:90%;max-width: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;transition:color .2s}.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;width:100%}.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;text-underline-offset:2px}.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">Reset 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-fp-submit').addEventListener('click', () => this._doReset());
32
+ }
33
+
34
+ _buildBody() {
35
+ return `
36
+ <p style="color:#7a9bbf;font-size:13px;margin:0 0 20px">Enter your email address and we'll send you a link to reset your password.</p>
37
+ <div id="oasis-fp-msg"></div>
38
+ <div class="oasis-field"><label class="oasis-label">Email Address</label><input class="oasis-input" id="oasis-fp-email" type="email" placeholder="you@example.com" /></div>
39
+ <button class="oasis-btn" id="oasis-fp-submit">Send Reset Link</button>
40
+ `;
41
+ }
42
+
43
+ async _doReset() {
44
+ const email = this._overlay.querySelector('#oasis-fp-email').value.trim();
45
+ const msgEl = this._overlay.querySelector('#oasis-fp-msg');
46
+ const btn = this._overlay.querySelector('#oasis-fp-submit');
47
+ if (!email) { msgEl.innerHTML = '<div class="oasis-msg oasis-msg-error">Please enter your email address.</div>'; return; }
48
+ btn.textContent = 'Sending…'; btn.disabled = true;
49
+ try {
50
+ const res = await fetch(`${this._apiUrl}/api/avatar/forgot-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) });
51
+ const data = await res.json();
52
+ if (!res.ok || data.isError) throw new Error(data.message || 'Request failed');
53
+ msgEl.innerHTML = '<div class="oasis-msg oasis-msg-success">Reset link sent! Check your inbox.</div>';
54
+ btn.textContent = 'Sent'; btn.disabled = true;
55
+ } catch (e) {
56
+ msgEl.innerHTML = `<div class="oasis-msg oasis-msg-error">${e.message}</div>`;
57
+ btn.textContent = 'Send Reset Link'; btn.disabled = false;
58
+ }
59
+ }
60
+
61
+ close() {
62
+ if (!this._overlay) return;
63
+ const el = this._overlay; this._overlay = null;
64
+ el.classList.remove('is-open');
65
+ el.addEventListener('transitionend', () => el.remove(), { once: true });
66
+ if (this._onClose) this._onClose();
67
+ }
68
+
69
+ _render(container) { container.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
70
+ destroy() { this.close(); }
71
+ }
72
+
73
+ window.OasisForgotPassword = OasisForgotPassword;
@@ -0,0 +1,15 @@
1
+ // @oasisomniverse/js — ESM re-exports
2
+ // Each widget self-registers on window.* when imported.
3
+ // You can also import the class directly: import { OasisNavBar } from '@oasisomniverse/js'
4
+
5
+ export { OasisNavBar } from './widgets/navbar.js';
6
+ export { OasisModal } from './widgets/oasis-modal.js';
7
+ export { OasisContact } from './widgets/contact.js';
8
+ export { OasisConfirmation } from './widgets/confirmation.js';
9
+ export { OasisComingSoon } from './widgets/coming-soon.js';
10
+ export { OasisKarmaToast } from './widgets/karma-toast.js';
11
+ export { OasisAvatarConnect } from './widgets/avatar-connect.js';
12
+ export { OasisSeeds } from './widgets/seeds.js';
13
+ export { OasisEggs } from './widgets/eggs.js';
14
+ export { OasisStarField } from './widgets/star-field.js';
15
+ export { OasisProviderDropdown } from './widgets/provider-dropdown.js';
package/src/index.js ADDED
@@ -0,0 +1,15 @@
1
+ // @oasisomniverse/js — OASIS Omniverse vanilla JS widgets
2
+ // Load this file to register all widgets on window.*
3
+ // Or import individual widgets from ./widgets/<name>.js
4
+
5
+ import './widgets/avatar-connect.js';
6
+ import './widgets/coming-soon.js';
7
+ import './widgets/confirmation.js';
8
+ import './widgets/contact.js';
9
+ import './widgets/eggs.js';
10
+ import './widgets/karma-toast.js';
11
+ import './widgets/navbar.js';
12
+ import './widgets/oasis-modal.js';
13
+ import './widgets/provider-dropdown.js';
14
+ import './widgets/seeds.js';
15
+ import './widgets/star-field.js';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * OasisKarmaToast — floating karma notification
3
+ * Usage:
4
+ * OasisKarmaToast.show({ message: 'Healed a patient', amount: 150 })
5
+ */
6
+ const OasisKarmaToast = (() => {
7
+ let _el = null;
8
+ let _timer = null;
9
+
10
+ function _inject() {
11
+ if (document.getElementById('oasis-karma-toast')) return;
12
+ const el = document.createElement('div');
13
+ el.id = 'oasis-karma-toast';
14
+ el.innerHTML = `
15
+ <div class="okt-inner">
16
+ <span class="okt-icon">⚡</span>
17
+ <div>
18
+ <div class="okt-amount"></div>
19
+ <div class="okt-message"></div>
20
+ </div>
21
+ </div>
22
+ `;
23
+ document.body.appendChild(el);
24
+ _el = el;
25
+
26
+ const s = document.createElement('style');
27
+ s.textContent = `
28
+ #oasis-karma-toast{position:fixed;bottom:28px;right:28px;z-index:10000;pointer-events:none;transform:translateY(20px);opacity:0;transition:opacity .35s,transform .35s}
29
+ #oasis-karma-toast.is-visible{opacity:1;transform:none}
30
+ .okt-inner{display:flex;align-items:center;gap:12px;background:#0d1829;border:1px solid rgba(0,200,255,.25);border-radius:12px;padding:14px 18px;box-shadow:0 8px 32px rgba(0,0,0,.5)}
31
+ .okt-icon{font-size:22px}
32
+ .okt-amount{font-family:'Orbitron',sans-serif;font-size:15px;font-weight:700;color:#00c8ff}
33
+ .okt-message{font-size:12px;color:#7a9bbf;margin-top:2px}
34
+ `;
35
+ document.head.appendChild(s);
36
+ }
37
+
38
+ function show({ message = '', amount = 0, duration = 4000 } = {}) {
39
+ _inject();
40
+ _el.querySelector('.okt-amount').textContent = `+${amount} Karma`;
41
+ _el.querySelector('.okt-message').textContent = message;
42
+ _el.classList.add('is-visible');
43
+ clearTimeout(_timer);
44
+ _timer = setTimeout(() => _el.classList.remove('is-visible'), duration);
45
+ }
46
+
47
+ return { show };
48
+ })();
49
+
50
+ window.OasisKarmaToast = OasisKarmaToast;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * OasisLoadData — load data from OASIS providers popup widget
3
+ * Usage: const w = new OasisLoadData({ onLoad: (data) => {} }); w.open();
4
+ */
5
+ class OasisLoadData {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onLoad = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onLoad = onLoad; 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-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-msg-success{background:rgba(0,200,100,.12);border:1px solid rgba(0,200,100,.3);color:#00c864}.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">Load 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
+ this._overlay.querySelector('#oasis-ld-btn').addEventListener('click', () => this._doLoad());
26
+ }
27
+ _buildBody() {
28
+ return `<div id="oasis-ld-msg"></div>
29
+ <div class="oasis-field"><label class="oasis-label">Data Key</label><input class="oasis-input" id="oasis-ld-key" type="text" placeholder="my.data.key" /></div>
30
+ <div class="oasis-field"><label class="oasis-label">Provider</label><select class="oasis-select" id="oasis-ld-prov"><option value="">Auto (fastest)</option><option value="holochain">Holochain</option><option value="ethereum">Ethereum</option><option value="ipfs">IPFS</option><option value="mongodb">MongoDB</option></select></div>
31
+ <div class="oasis-field"><label class="oasis-label">Decrypt</label><select class="oasis-select" id="oasis-ld-dec"><option value="true">Yes (if encrypted)</option><option value="false">No</option></select></div>
32
+ <button class="oasis-btn" id="oasis-ld-btn" style="width:100%">Load Data</button>
33
+ <div id="oasis-ld-output" style="margin-top:16px"></div>`;
34
+ }
35
+ async _doLoad() {
36
+ const key = this._overlay.querySelector('#oasis-ld-key').value.trim();
37
+ const msgEl = this._overlay.querySelector('#oasis-ld-msg');
38
+ const btn = this._overlay.querySelector('#oasis-ld-btn');
39
+ const out = this._overlay.querySelector('#oasis-ld-output');
40
+ if (!key) { msgEl.innerHTML = '<div class="oasis-msg oasis-msg-error">Key is required.</div>'; return; }
41
+ btn.textContent = 'Loading…'; btn.disabled = true;
42
+ try {
43
+ const prov = this._overlay.querySelector('#oasis-ld-prov').value;
44
+ const r = await fetch(`${this._apiUrl}/api/data/${encodeURIComponent(key)}${prov ? '?provider=' + prov : ''}`);
45
+ const data = await r.json();
46
+ if (!r.ok || data.isError) throw new Error(data.message || 'Load failed');
47
+ out.innerHTML = `<div class="oasis-msg oasis-msg-success">Data loaded.</div><pre style="background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.1);border-radius:8px;padding:12px;color:#fff;font-size:12px;overflow-x:auto;white-space:pre-wrap">${JSON.stringify(data.result ?? data, null, 2)}</pre>`;
48
+ if (this._onLoad) this._onLoad(data.result ?? data);
49
+ } catch (e) { msgEl.innerHTML = `<div class="oasis-msg oasis-msg-error">${e.message}</div>`; }
50
+ btn.textContent = 'Load Data'; btn.disabled = false;
51
+ }
52
+ 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(); }
53
+ _render(c) { c.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`; }
54
+ destroy() { this.close(); }
55
+ }
56
+ window.OasisLoadData = OasisLoadData;
package/src/login.js ADDED
@@ -0,0 +1,154 @@
1
+ /**
2
+ * OasisLogin — avatar login popup widget
3
+ * Usage: const w = new OasisLogin({ onSuccess: (session) => {} }); w.open();
4
+ */
5
+ class OasisLogin {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onSuccess = null, onClose = null } = {}) {
7
+ this._apiUrl = apiUrl;
8
+ this._onSuccess = onSuccess;
9
+ this._onClose = onClose;
10
+ this._overlay = null;
11
+ this._injectStyles();
12
+ if (container) this._render(container);
13
+ }
14
+
15
+ _injectStyles() {
16
+ if (document.getElementById('oasis-popup-styles')) return;
17
+ const s = document.createElement('style');
18
+ s.id = 'oasis-popup-styles';
19
+ s.textContent = `
20
+ .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}
21
+ .oasis-popup-overlay.is-open{opacity:1}
22
+ .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}
23
+ .oasis-popup-overlay.is-open .oasis-popup-modal{transform:none}
24
+ .oasis-popup-modal.lg{max-width:900px}
25
+ .oasis-popup-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(0,200,255,.1)}
26
+ .oasis-popup-title{font-family:'Orbitron',sans-serif;font-size:14px;font-weight:700;color:#00c8ff;letter-spacing:.08em}
27
+ .oasis-popup-close{background:none;border:none;color:#7a9bbf;font-size:22px;cursor:pointer;padding:0;line-height:1;transition:color .2s}
28
+ .oasis-popup-close:hover{color:#fff}
29
+ .oasis-popup-body{padding:24px}
30
+ .oasis-field{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}
31
+ .oasis-label{font-size:11px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase}
32
+ .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}
33
+ .oasis-input:focus,.oasis-select:focus,.oasis-textarea:focus{border-color:rgba(0,200,255,.5)}
34
+ .oasis-textarea{resize:vertical;min-height:80px}
35
+ .oasis-select option{background:#0a1628}
36
+ .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%}
37
+ .oasis-btn:hover{opacity:.85}
38
+ .oasis-btn:disabled{opacity:.5;cursor:not-allowed}
39
+ .oasis-btn-ghost{background:rgba(0,200,255,.1);border:1px solid rgba(0,200,255,.3);color:#00c8ff;border-radius:8px;font-size:12px;font-weight:600;padding:9px 16px;cursor:pointer;transition:background .2s}
40
+ .oasis-btn-ghost:hover{background:rgba(0,200,255,.2)}
41
+ .oasis-btn-row{display:flex;gap:10px;justify-content:flex-end;margin-top:20px}
42
+ .oasis-msg{border-radius:8px;padding:10px 14px;font-size:13px;margin-bottom:16px}
43
+ .oasis-msg-error{background:rgba(255,80,80,.12);border:1px solid rgba(255,80,80,.3);color:#ff6b6b}
44
+ .oasis-msg-success{background:rgba(0,200,100,.12);border:1px solid rgba(0,200,100,.3);color:#00c864}
45
+ .oasis-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
46
+ .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}
47
+ .oasis-info-label{font-size:10px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase;margin-bottom:4px}
48
+ .oasis-info-value{color:#fff;font-size:14px;font-weight:500}
49
+ .oasis-cs{text-align:center;padding:32px 16px}
50
+ .oasis-cs-icon{font-size:48px;margin-bottom:12px}
51
+ .oasis-cs h3{font-family:'Orbitron',sans-serif;font-size:15px;color:#00c8ff;margin:0 0 8px}
52
+ .oasis-cs p{color:#7a9bbf;font-size:13px;margin:0}
53
+ .oasis-table{width:100%;border-collapse:collapse;font-size:13px}
54
+ .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)}
55
+ .oasis-table td{padding:10px 12px;color:#fff;border-bottom:1px solid rgba(255,255,255,.05)}
56
+ .oasis-table tr:hover td{background:rgba(0,200,255,.04)}
57
+ .oasis-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:700}
58
+ .oasis-badge-blue{background:rgba(0,200,255,.15);color:#00c8ff}
59
+ .oasis-badge-green{background:rgba(0,200,100,.15);color:#00c864}
60
+ .oasis-badge-red{background:rgba(255,80,80,.15);color:#ff6b6b}
61
+ .oasis-badge-yellow{background:rgba(255,200,0,.15);color:#ffc800}
62
+ .oasis-search-row{display:flex;gap:10px;margin-bottom:16px}
63
+ .oasis-search-row .oasis-input{flex:1}
64
+ .oasis-stat-row{display:flex;gap:12px;margin-bottom:20px}
65
+ .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}
66
+ .oasis-stat-val{font-family:'Orbitron',sans-serif;font-size:20px;font-weight:700;color:#00c8ff}
67
+ .oasis-stat-lbl{font-size:10px;color:#7a9bbf;margin-top:4px;letter-spacing:.04em}
68
+ .oasis-divider{border:none;border-top:1px solid rgba(0,200,255,.1);margin:16px 0}
69
+ .oasis-section-title{font-size:11px;font-weight:700;color:#7a9bbf;letter-spacing:.08em;text-transform:uppercase;margin:0 0 12px}
70
+ .oasis-radio-group{display:flex;gap:16px;margin-bottom:16px}
71
+ .oasis-radio-label{display:flex;align-items:center;gap:8px;font-size:13px;color:#fff;cursor:pointer}
72
+ .oasis-radio-label input[type=radio]{accent-color:#00c8ff}
73
+ .oasis-link{background:none;border:none;color:#00c8ff;font-size:13px;cursor:pointer;padding:0;text-decoration:underline;text-underline-offset:2px}
74
+ .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}
75
+ .oasis-chat-bubble{background:rgba(0,200,255,.1);border-radius:10px;padding:8px 12px;font-size:13px;color:#fff;max-width:75%}
76
+ .oasis-chat-bubble.mine{background:rgba(0,128,255,.2);align-self:flex-end}
77
+ .oasis-chat-input-row{display:flex;gap:8px}
78
+ .oasis-chat-input-row .oasis-input{flex:1}
79
+ `;
80
+ document.head.appendChild(s);
81
+ }
82
+
83
+ open() {
84
+ if (this._overlay) return;
85
+ this._overlay = document.createElement('div');
86
+ this._overlay.className = 'oasis-popup-overlay';
87
+ this._overlay.innerHTML = `<div class="oasis-popup-modal">
88
+ <div class="oasis-popup-header"><span class="oasis-popup-title">Beam In</span><button class="oasis-popup-close" aria-label="Close">×</button></div>
89
+ <div class="oasis-popup-body">${this._buildBody()}</div>
90
+ </div>`;
91
+ this._overlay.querySelector('.oasis-popup-close').addEventListener('click', () => this.close());
92
+ this._overlay.addEventListener('click', e => { if (e.target === this._overlay) this.close(); });
93
+ document.body.appendChild(this._overlay);
94
+ requestAnimationFrame(() => this._overlay.classList.add('is-open'));
95
+ this._attachEvents();
96
+ }
97
+
98
+ _buildBody() {
99
+ return `
100
+ <div id="oasis-login-msg"></div>
101
+ <div class="oasis-field"><label class="oasis-label">Username</label><input class="oasis-input" id="oasis-login-user" type="text" placeholder="yourusername" autocomplete="username" /></div>
102
+ <div class="oasis-field"><label class="oasis-label">Password</label><input class="oasis-input" id="oasis-login-pass" type="password" placeholder="••••••••" autocomplete="current-password" /></div>
103
+ <button class="oasis-btn" id="oasis-login-submit" style="margin-top:4px">Beam In</button>
104
+ <div style="text-align:center;margin-top:14px">
105
+ <button class="oasis-link" id="oasis-login-forgot">Forgot password?</button>
106
+ </div>
107
+ `;
108
+ }
109
+
110
+ _attachEvents() {
111
+ const submit = this._overlay.querySelector('#oasis-login-submit');
112
+ const passEl = this._overlay.querySelector('#oasis-login-pass');
113
+ if (submit) submit.addEventListener('click', () => this._doLogin());
114
+ if (passEl) passEl.addEventListener('keydown', e => { if (e.key === 'Enter') this._doLogin(); });
115
+ }
116
+
117
+ async _doLogin() {
118
+ const userEl = this._overlay.querySelector('#oasis-login-user');
119
+ const passEl = this._overlay.querySelector('#oasis-login-pass');
120
+ const msgEl = this._overlay.querySelector('#oasis-login-msg');
121
+ const btn = this._overlay.querySelector('#oasis-login-submit');
122
+ const username = userEl.value.trim();
123
+ const password = passEl.value;
124
+ if (!username || !password) { msgEl.innerHTML = '<div class="oasis-msg oasis-msg-error">Please fill in all fields.</div>'; return; }
125
+ btn.textContent = 'Connecting…'; btn.disabled = true; msgEl.innerHTML = '';
126
+ try {
127
+ const res = await fetch(`${this._apiUrl}/api/avatar/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) });
128
+ const data = await res.json();
129
+ if (!res.ok || data.isError) throw new Error(data.message || 'Login failed');
130
+ msgEl.innerHTML = '<div class="oasis-msg oasis-msg-success">Logged in successfully!</div>';
131
+ if (this._onSuccess) this._onSuccess(data);
132
+ setTimeout(() => this.close(), 1200);
133
+ } catch (e) {
134
+ msgEl.innerHTML = `<div class="oasis-msg oasis-msg-error">${e.message}</div>`;
135
+ btn.textContent = 'Beam In'; btn.disabled = false;
136
+ }
137
+ }
138
+
139
+ close() {
140
+ if (!this._overlay) return;
141
+ const el = this._overlay; this._overlay = null;
142
+ el.classList.remove('is-open');
143
+ el.addEventListener('transitionend', () => el.remove(), { once: true });
144
+ if (this._onClose) this._onClose();
145
+ }
146
+
147
+ _render(container) {
148
+ container.innerHTML = `<div class="oasis-popup-body">${this._buildBody()}</div>`;
149
+ }
150
+
151
+ destroy() { this.close(); }
152
+ }
153
+
154
+ window.OasisLogin = OasisLogin;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * OasisManageData — manage avatar data entries popup widget
3
+ * Usage: const w = new OasisManageData({}); w.open();
4
+ */
5
+ class OasisManageData {
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: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{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-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-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-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;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-red{background:rgba(255,80,80,.15);color:#ff6b6b}.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-section-title{font-size:11px;font-weight:700;color:#7a9bbf;letter-spacing:.08em;text-transform:uppercase;margin:0 0 12px}.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 Data</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body" id="oasis-md-body"><div class="oasis-cs"><div class="oasis-cs-icon">⏳</div><p>Loading data…</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-md-body');
29
+ try {
30
+ const r = await fetch(`${this._apiUrl}/api/data`);
31
+ const data = await r.json();
32
+ const items = Array.isArray(data) ? data : (data.result || []);
33
+ if (!items.length) { body.innerHTML = '<div class="oasis-cs"><div class="oasis-cs-icon">📦</div><h3>No Data</h3><p>No data entries found.</p></div>'; return; }
34
+ body.innerHTML = `<table class="oasis-table"><thead><tr><th>Key</th><th>Provider</th><th>Encrypted</th><th>Actions</th></tr></thead><tbody>${items.map((i,idx) => `<tr><td>${i.key||'—'}</td><td>${i.provider||'—'}</td><td>${i.isEncrypted?'✓':'—'}</td><td><span class="oasis-badge oasis-badge-blue" data-del="${idx}">Delete</span></td></tr>`).join('')}</tbody></table>`;
35
+ body.querySelectorAll('[data-del]').forEach(b => b.addEventListener('click', () => { if (confirm('Delete this entry?')) fetch(`${this._apiUrl}/api/data/${items[+b.dataset.del].id}`, { method: 'DELETE' }).then(() => this._load()); }));
36
+ } catch { body.innerHTML = '<div class="oasis-msg oasis-msg-error">Failed to load data.</div>'; }
37
+ }
38
+ close() { if (!this._overlay) return; const el = this._overlay; this._overlay = null; el.classList.remove('is-open'); el.addEventListener('transitionend', () => el.remove(), { once: true }); if (this._onClose) this._onClose(); }
39
+ _render(c) { c.innerHTML = '<div class="oasis-popup-body"><p style="color:#7a9bbf">Loading…</p></div>'; }
40
+ destroy() { this.close(); }
41
+ }
42
+ window.OasisManageData = OasisManageData;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * OasisManageEggs — manage OASIS Eggs popup widget
3
+ * Usage: const w = new OasisManageEggs({}); w.open();
4
+ */
5
+ class OasisManageEggs {
6
+ constructor({ container, apiUrl = 'https://api.web4.oasisomniverse.one', onClose = null } = {}) {
7
+ this._apiUrl = apiUrl; this._onClose = onClose; this._overlay = null;
8
+ this._injectStyles(); if (container) this._render(container);
9
+ }
10
+ _injectStyles() {
11
+ if (document.getElementById('oasis-popup-styles')) return;
12
+ const s = document.createElement('style'); s.id = 'oasis-popup-styles';
13
+ s.textContent = `.oasis-popup-overlay{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s}.oasis-popup-overlay.is-open{opacity:1}.oasis-popup-modal{background:#0a1628;border:1px solid rgba(0,200,255,.2);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.6);width:90%;max-width:600px;max-height:90vh;overflow-y:auto;transform:translateY(16px);transition:transform .25s}.oasis-popup-overlay.is-open .oasis-popup-modal{transform:none}.oasis-popup-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(0,200,255,.1)}.oasis-popup-title{font-family:'Orbitron',sans-serif;font-size:14px;font-weight:700;color:#00c8ff;letter-spacing:.08em}.oasis-popup-close{background:none;border:none;color:#7a9bbf;font-size:22px;cursor:pointer;padding:0;line-height:1}.oasis-popup-close:hover{color:#fff}.oasis-popup-body{padding:24px}.oasis-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-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-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-stat-row{display:flex;gap:12px;margin-bottom:20px}.oasis-stat{flex:1;background:rgba(0,200,255,.05);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:14px;text-align:center}.oasis-stat-val{font-family:'Orbitron',sans-serif;font-size:20px;font-weight:700;color:#00c8ff}.oasis-stat-lbl{font-size:10px;color:#7a9bbf;margin-top:4px}.oasis-cs{text-align:center;padding:32px 16px}.oasis-cs-icon{font-size:48px;margin-bottom:12px}.oasis-cs h3{font-family:'Orbitron',sans-serif;font-size:15px;color:#00c8ff;margin:0 0 8px}.oasis-cs p{color:#7a9bbf;font-size:13px;margin:0}.oasis-msg{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}`;
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 Eggs</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body" id="oasis-me-body"><div class="oasis-cs"><div class="oasis-cs-icon">⏳</div><p>Loading eggs…</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-me-body');
29
+ try {
30
+ const r = await fetch(`${this._apiUrl}/api/avatar/eggs`);
31
+ const data = await r.json();
32
+ const eggs = Array.isArray(data) ? data : (data.result || []);
33
+ if (!eggs.length) { body.innerHTML = '<div class="oasis-cs"><div class="oasis-cs-icon">🥚</div><h3>No Eggs</h3><p>No eggs found in your inventory.</p></div>'; return; }
34
+ body.innerHTML = `<div class="oasis-stat-row"><div class="oasis-stat"><div class="oasis-stat-val">${eggs.length}</div><div class="oasis-stat-lbl">Total Eggs</div></div><div class="oasis-stat"><div class="oasis-stat-val">${eggs.filter(e=>e.isHatched).length}</div><div class="oasis-stat-lbl">Hatched</div></div></div><table class="oasis-table"><thead><tr><th>Name</th><th>Type</th><th>Status</th><th>Actions</th></tr></thead><tbody>${eggs.map(e=>`<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>${e.isHatched?'—':`<button class="oasis-badge oasis-badge-blue" onclick="alert('Hatch feature coming soon!')">Hatch</button>`}</td></tr>`).join('')}</tbody></table>`;
35
+ } catch { body.innerHTML = '<div class="oasis-msg oasis-msg-error">Failed to load eggs.</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.OasisManageEggs = OasisManageEggs;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * OasisManageMission — manage avatar missions popup widget
3
+ * Usage: const w = new OasisManageMission({}); w.open();
4
+ */
5
+ class OasisManageMission {
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}.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 Missions</span><button class="oasis-popup-close">×</button></div><div class="oasis-popup-body" id="oasis-mm-body"><div class="oasis-cs"><div class="oasis-cs-icon">⏳</div><p>Loading missions…</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-mm-body');
29
+ try {
30
+ const r = await fetch(`${this._apiUrl}/api/mission`);
31
+ const data = await r.json();
32
+ const ms = Array.isArray(data) ? data : (data.result || []);
33
+ if (!ms.length) { body.innerHTML = '<div class="oasis-cs"><div class="oasis-cs-icon">🎯</div><h3>No Missions</h3><p>No missions assigned.</p></div>'; return; }
34
+ const active = ms.filter(m => m.status === 'active').length;
35
+ body.innerHTML = `<div class="oasis-stat-row"><div class="oasis-stat"><div class="oasis-stat-val">${ms.length}</div><div class="oasis-stat-lbl">Total</div></div><div class="oasis-stat"><div class="oasis-stat-val">${active}</div><div class="oasis-stat-lbl">Active</div></div><div class="oasis-stat"><div class="oasis-stat-val">${ms.filter(m=>m.status==='completed').length}</div><div class="oasis-stat-lbl">Done</div></div></div><table class="oasis-table"><thead><tr><th>Mission</th><th>Type</th><th>Karma</th><th>Status</th></tr></thead><tbody>${ms.map(m=>`<tr><td>${m.name||'—'}</td><td><span class="oasis-badge oasis-badge-blue">${m.missionType||'—'}</span></td><td>+${m.karmaReward??0} ⚡</td><td><span class="oasis-badge ${m.status==='active'?'oasis-badge-green':m.status==='completed'?'oasis-badge-blue':'oasis-badge-yellow'}">${m.status||'—'}</span></td></tr>`).join('')}</tbody></table>`;
36
+ } catch { body.innerHTML = '<div class="oasis-msg-error">Failed to load missions.</div>'; }
37
+ }
38
+ close() { if (!this._overlay) return; const el = this._overlay; this._overlay = null; el.classList.remove('is-open'); el.addEventListener('transitionend', () => el.remove(), { once: true }); if (this._onClose) this._onClose(); }
39
+ _render(c) { c.innerHTML = '<div class="oasis-popup-body"><p style="color:#7a9bbf">Loading…</p></div>'; }
40
+ destroy() { this.close(); }
41
+ }
42
+ window.OasisManageMission = OasisManageMission;