@oasisomniverse/angular 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 (118) hide show
  1. package/package.json +37 -0
  2. package/src/components/avatar/avatar-wallet.component.ts +49 -0
  3. package/src/components/avatar/edit-avatar.component.ts +28 -0
  4. package/src/components/avatar/search-avatar.component.ts +45 -0
  5. package/src/components/avatar/view-avatar.component.ts +49 -0
  6. package/src/components/avatar-connect/avatar-connect.component.ts +78 -0
  7. package/src/components/avatar-profile/avatar-profile.component.ts +146 -0
  8. package/src/components/coming-soon/coming-soon.component.ts +27 -0
  9. package/src/components/confirmation/confirmation.component.ts +38 -0
  10. package/src/components/contact/contact.component.ts +64 -0
  11. package/src/components/data-screen/add-data.component.ts +50 -0
  12. package/src/components/data-screen/cross-chain-management.component.ts +28 -0
  13. package/src/components/data-screen/load-data.component.ts +28 -0
  14. package/src/components/data-screen/manage-data.component.ts +50 -0
  15. package/src/components/data-screen/off-chain-management.component.ts +28 -0
  16. package/src/components/data-screen/search-data.component.ts +40 -0
  17. package/src/components/eggs/eggs.component.ts +76 -0
  18. package/src/components/eggs/manage-eggs.component.ts +50 -0
  19. package/src/components/eggs/search-eggs.component.ts +40 -0
  20. package/src/components/eggs/view-eggs.component.ts +49 -0
  21. package/src/components/forgot-password/forgot-password.component.ts +80 -0
  22. package/src/components/game/game.component.ts +58 -0
  23. package/src/components/game/search-profiles.component.ts +36 -0
  24. package/src/components/game/view-achievements.component.ts +48 -0
  25. package/src/components/game/view-leagues.component.ts +48 -0
  26. package/src/components/game/view-tournaments.component.ts +49 -0
  27. package/src/components/hyperdrive/hyperdrive.component.ts +81 -0
  28. package/src/components/karma/search-karma.component.ts +36 -0
  29. package/src/components/karma/view-avatar-karma.component.ts +51 -0
  30. package/src/components/karma/view-karma.component.ts +46 -0
  31. package/src/components/karma/vote-karma.component.ts +48 -0
  32. package/src/components/karma-panel/karma-panel.component.ts +87 -0
  33. package/src/components/karma-toast/karma-toast.component.ts +33 -0
  34. package/src/components/login/login.component.ts +95 -0
  35. package/src/components/map/add-2d-object-map.component.ts +42 -0
  36. package/src/components/map/add-3d-object-map.component.ts +43 -0
  37. package/src/components/map/add-quest-to-map.component.ts +42 -0
  38. package/src/components/map/download-our-world.component.ts +28 -0
  39. package/src/components/map/manage-map.component.ts +28 -0
  40. package/src/components/map/map.component.ts +70 -0
  41. package/src/components/map/plot-route-on-map.component.ts +42 -0
  42. package/src/components/map/search-map.component.ts +44 -0
  43. package/src/components/map/view-global-3d-map.component.ts +28 -0
  44. package/src/components/map/view-halons-on-map.component.ts +28 -0
  45. package/src/components/map/view-oapp-on-map.component.ts +28 -0
  46. package/src/components/map/view-quest-on-map.component.ts +28 -0
  47. package/src/components/menu-message/menu-message.component.ts +69 -0
  48. package/src/components/messages/message.component.ts +59 -0
  49. package/src/components/messaging/messaging.component.ts +89 -0
  50. package/src/components/mission/manage-mission.component.ts +43 -0
  51. package/src/components/mission/mission.component.ts +63 -0
  52. package/src/components/mission/search-mission.component.ts +36 -0
  53. package/src/components/mission/view-mission.component.ts +49 -0
  54. package/src/components/modal/modal.component.ts +28 -0
  55. package/src/components/navbar/navbar.component.ts +64 -0
  56. package/src/components/nft/contact-popup.component.ts +41 -0
  57. package/src/components/nft/manage-oasis-nft.component.ts +43 -0
  58. package/src/components/nft/nft.component.ts +70 -0
  59. package/src/components/nft/purchase-oasis-nft.component.ts +53 -0
  60. package/src/components/nft/purchase-oasis-virtual-land-nft.component.ts +28 -0
  61. package/src/components/nft/search-oasis-nft.component.ts +36 -0
  62. package/src/components/nft/solana.component.ts +28 -0
  63. package/src/components/nft/view-oasis-nft.component.ts +52 -0
  64. package/src/components/oapp/create-oapp.component.ts +43 -0
  65. package/src/components/oapp/deploy-oapp.component.ts +42 -0
  66. package/src/components/oapp/download-our-world.component.ts +28 -0
  67. package/src/components/oapp/edit-oapp.component.ts +41 -0
  68. package/src/components/oapp/install-oapp.component.ts +36 -0
  69. package/src/components/oapp/launch-oapp.component.ts +58 -0
  70. package/src/components/oapp/manage-oapp.component.ts +42 -0
  71. package/src/components/oapp/oapp.component.ts +70 -0
  72. package/src/components/oapp/search-oapp.component.ts +36 -0
  73. package/src/components/oasis-card/oasis-card.component.ts +30 -0
  74. package/src/components/onet/onet.component.ts +77 -0
  75. package/src/components/onode/onode.component.ts +73 -0
  76. package/src/components/provider/activity-pub.component.ts +44 -0
  77. package/src/components/provider/compare-provider-speeds.component.ts +48 -0
  78. package/src/components/provider/eosio.component.ts +44 -0
  79. package/src/components/provider/ethereum.component.ts +44 -0
  80. package/src/components/provider/holochain.component.ts +44 -0
  81. package/src/components/provider/ipfs.component.ts +44 -0
  82. package/src/components/provider/manage-auto-fail-over.component.ts +42 -0
  83. package/src/components/provider/manage-auto-replicaton.component.ts +42 -0
  84. package/src/components/provider/manage-load-balancing.component.ts +42 -0
  85. package/src/components/provider/manage-providers.component.ts +28 -0
  86. package/src/components/provider/mongo-db.component.ts +44 -0
  87. package/src/components/provider/neo4j.component.ts +44 -0
  88. package/src/components/provider/search-providers.component.ts +36 -0
  89. package/src/components/provider/seeds-provider.component.ts +42 -0
  90. package/src/components/provider/solid.component.ts +44 -0
  91. package/src/components/provider/sql-lite.component.ts +44 -0
  92. package/src/components/provider/three-fold.component.ts +44 -0
  93. package/src/components/provider/view-provider-stats.component.ts +61 -0
  94. package/src/components/provider/view-providers.component.ts +54 -0
  95. package/src/components/provider-dropdown/provider-dropdown.component.ts +64 -0
  96. package/src/components/providers/providers.component.ts +60 -0
  97. package/src/components/quest/manage-quest.component.ts +43 -0
  98. package/src/components/quest/quest.component.ts +65 -0
  99. package/src/components/quest/search-quest.component.ts +36 -0
  100. package/src/components/quest/view-quest.component.ts +49 -0
  101. package/src/components/reset-password/reset-password.component.ts +68 -0
  102. package/src/components/search-avatars/search-avatars.component.ts +80 -0
  103. package/src/components/seeds/accept-invite.component.ts +44 -0
  104. package/src/components/seeds/donate-seeds.component.ts +42 -0
  105. package/src/components/seeds/manage-seeds.component.ts +42 -0
  106. package/src/components/seeds/pay-with-seeds.component.ts +41 -0
  107. package/src/components/seeds/reward-seeds.component.ts +41 -0
  108. package/src/components/seeds/search-seeds.component.ts +36 -0
  109. package/src/components/seeds/seeds.component.ts +75 -0
  110. package/src/components/seeds/send-invite.component.ts +41 -0
  111. package/src/components/seeds/view-organizations.component.ts +55 -0
  112. package/src/components/seeds/view-seeds.component.ts +78 -0
  113. package/src/components/settings/settings.component.ts +96 -0
  114. package/src/components/signup/signup.component.ts +118 -0
  115. package/src/components/star-field/star-field.component.ts +44 -0
  116. package/src/components/verify-email/verify-email.component.ts +48 -0
  117. package/src/components/wallet/wallet.component.ts +83 -0
  118. package/src/index.ts +145 -0
@@ -0,0 +1,43 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-add-3d-object-map',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="mf-shell">
10
+ <div class="mf-header"><span>🌐</span><h2 class="mf-title">Add 3D Object to Map</h2></div>
11
+ <div class="mf-body">
12
+ <div class="mf-field"><label class="mf-lbl">Object Name</label><input class="mf-inp" placeholder="Object Name..." /></div>
13
+ <div class="mf-field"><label class="mf-lbl">X Position</label><input class="mf-inp" placeholder="X Position..." /></div>
14
+ <div class="mf-field"><label class="mf-lbl">Y Position</label><input class="mf-inp" placeholder="Y Position..." /></div>
15
+ <div class="mf-field"><label class="mf-lbl">Z Position</label><input class="mf-inp" placeholder="Z Position..." /></div>
16
+ <div class="mf-field"><label class="mf-lbl">Scale</label><input class="mf-inp" placeholder="Scale..." /></div>
17
+ <div class="mf-field"><label class="mf-lbl">Model URL</label><input class="mf-inp" placeholder="Model URL..." /></div>
18
+ </div>
19
+ <div class="mf-footer">
20
+ <button class="mf-save" (click)="save()">Save</button>
21
+ <button class="mf-cancel" (click)="close.emit()">Cancel</button>
22
+ </div>
23
+ </div>
24
+ `,
25
+ styles: [`
26
+ .mf-shell{display:flex;flex-direction:column;gap:18px;padding:20px}
27
+ .mf-header{display:flex;align-items:center;gap:10px}
28
+ .mf-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
29
+ .mf-body{display:flex;flex-direction:column;gap:12px}
30
+ .mf-field{display:flex;flex-direction:column;gap:5px}
31
+ .mf-lbl{font-size:11px;font-weight:700;letter-spacing:.07em;color:#7a9bbf;text-transform:uppercase}
32
+ .mf-inp{background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.25);border-radius:8px;color:#fff;font-size:13px;padding:10px 14px;outline:none}
33
+ .mf-inp:focus{border-color:rgba(0,200,255,.6)}
34
+ .mf-footer{display:flex;gap:10px;justify-content:flex-end}
35
+ .mf-save{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;padding:10px 22px;cursor:pointer}
36
+ .mf-cancel{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:10px 18px;cursor:pointer}
37
+ `]
38
+ })
39
+ export class Add3dObjectMapComponent {
40
+ @Input() show = false;
41
+ @Output() close = new EventEmitter<void>();
42
+ save() { this.close.emit(); }
43
+ }
@@ -0,0 +1,42 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-add-quest-to-map',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="mf-shell">
10
+ <div class="mf-header"><span>⚔️</span><h2 class="mf-title">Add Quest to Map</h2></div>
11
+ <div class="mf-body">
12
+ <div class="mf-field"><label class="mf-lbl">Quest Name</label><input class="mf-inp" placeholder="Quest Name..." /></div>
13
+ <div class="mf-field"><label class="mf-lbl">Location X</label><input class="mf-inp" placeholder="Location X..." /></div>
14
+ <div class="mf-field"><label class="mf-lbl">Location Y</label><input class="mf-inp" placeholder="Location Y..." /></div>
15
+ <div class="mf-field"><label class="mf-lbl">Radius</label><input class="mf-inp" placeholder="Radius..." /></div>
16
+ <div class="mf-field"><label class="mf-lbl">Description</label><input class="mf-inp" placeholder="Description..." /></div>
17
+ </div>
18
+ <div class="mf-footer">
19
+ <button class="mf-save" (click)="save()">Save</button>
20
+ <button class="mf-cancel" (click)="close.emit()">Cancel</button>
21
+ </div>
22
+ </div>
23
+ `,
24
+ styles: [`
25
+ .mf-shell{display:flex;flex-direction:column;gap:18px;padding:20px}
26
+ .mf-header{display:flex;align-items:center;gap:10px}
27
+ .mf-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
28
+ .mf-body{display:flex;flex-direction:column;gap:12px}
29
+ .mf-field{display:flex;flex-direction:column;gap:5px}
30
+ .mf-lbl{font-size:11px;font-weight:700;letter-spacing:.07em;color:#7a9bbf;text-transform:uppercase}
31
+ .mf-inp{background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.25);border-radius:8px;color:#fff;font-size:13px;padding:10px 14px;outline:none}
32
+ .mf-inp:focus{border-color:rgba(0,200,255,.6)}
33
+ .mf-footer{display:flex;gap:10px;justify-content:flex-end}
34
+ .mf-save{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;padding:10px 22px;cursor:pointer}
35
+ .mf-cancel{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:10px 18px;cursor:pointer}
36
+ `]
37
+ })
38
+ export class AddQuestToMapComponent {
39
+ @Input() show = false;
40
+ @Output() close = new EventEmitter<void>();
41
+ save() { this.close.emit(); }
42
+ }
@@ -0,0 +1,28 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-download-our-world',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="popup-shell">
10
+ <div class="popup-icon">⬇️</div>
11
+ <h2 class="popup-title">Download Our World</h2>
12
+ <p class="popup-body">UI Coming Soon — access this feature via the OASIS API from the Developer menu.</p>
13
+ <button class="popup-btn" (click)="close.emit()">OK</button>
14
+ </div>
15
+ `,
16
+ styles: [`
17
+ .popup-shell{display:flex;flex-direction:column;align-items:center;gap:16px;padding:24px;text-align:center}
18
+ .popup-icon{font-size:48px}
19
+ .popup-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
20
+ .popup-body{font-size:13px;color:#a8bfd8;line-height:1.6;margin:0;max-width:380px}
21
+ .popup-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:10px 28px;cursor:pointer;transition:opacity .2s}
22
+ .popup-btn:hover{opacity:.85}
23
+ `]
24
+ })
25
+ export class DownloadOurWorldComponent {
26
+ @Input() show = false;
27
+ @Output() close = new EventEmitter<void>();
28
+ }
@@ -0,0 +1,28 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-manage-map',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="popup-shell">
10
+ <div class="popup-icon">🗺️</div>
11
+ <h2 class="popup-title">Manage Map</h2>
12
+ <p class="popup-body">UI Coming Soon — access this feature via the OASIS API from the Developer menu.</p>
13
+ <button class="popup-btn" (click)="close.emit()">OK</button>
14
+ </div>
15
+ `,
16
+ styles: [`
17
+ .popup-shell{display:flex;flex-direction:column;align-items:center;gap:16px;padding:24px;text-align:center}
18
+ .popup-icon{font-size:48px}
19
+ .popup-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
20
+ .popup-body{font-size:13px;color:#a8bfd8;line-height:1.6;margin:0;max-width:380px}
21
+ .popup-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:10px 28px;cursor:pointer;transition:opacity .2s}
22
+ .popup-btn:hover{opacity:.85}
23
+ `]
24
+ })
25
+ export class ManageMapComponent {
26
+ @Input() show = false;
27
+ @Output() close = new EventEmitter<void>();
28
+ }
@@ -0,0 +1,70 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ interface Location { id: string; name: string; type: string; x: number; y: number; description: string }
5
+
6
+ @Component({
7
+ selector: 'oasis-map',
8
+ standalone: true,
9
+ imports: [CommonModule],
10
+ template: `
11
+ <div class="map-shell">
12
+ <div class="map-header"><h2 class="map-title">🗺️ OASIS Map</h2><p class="map-sub">Explore the OASIS Omniverse.</p></div>
13
+ <div class="map-viewport">
14
+ <div class="map-grid-bg"></div>
15
+ @for (loc of locations(); track loc.id) {
16
+ <div class="map-pin" [style.left.%]="loc.x" [style.top.%]="loc.y" [class.map-pin--selected]="selected()?.id === loc.id" (click)="selected.set(loc)" [title]="loc.name">
17
+ <div class="map-pin-dot">{{ locIcon(loc.type) }}</div>
18
+ </div>
19
+ }
20
+ </div>
21
+ @if (selected()) {
22
+ <div class="map-detail">
23
+ <div class="map-detail-name">{{ selected()!.name }}</div>
24
+ <div class="map-detail-type">{{ selected()!.type }}</div>
25
+ <div class="map-detail-desc">{{ selected()!.description }}</div>
26
+ <button class="map-btn" (click)="selected.set(null)">Close</button>
27
+ </div>
28
+ }
29
+ <div class="map-legend">
30
+ @for (t of types; track t.label) {
31
+ <div class="map-legend-item"><span>{{ t.icon }}</span><span>{{ t.label }}</span></div>
32
+ }
33
+ </div>
34
+ </div>
35
+ `,
36
+ styles: [`
37
+ .map-shell{display:flex;flex-direction:column;gap:16px}
38
+ .map-header{text-align:center}
39
+ .map-title{font-family:'Orbitron',sans-serif;font-size:20px;color:#fff;margin:0 0 6px}
40
+ .map-sub{font-size:13px;color:#7a9bbf;margin:0}
41
+ .map-viewport{position:relative;height:300px;background:#030d1a;border:1px solid rgba(0,200,255,.2);border-radius:12px;overflow:hidden}
42
+ .map-grid-bg{position:absolute;inset:0;background-image:linear-gradient(rgba(0,200,255,.05) 1px,transparent 1px),linear-gradient(90deg,rgba(0,200,255,.05) 1px,transparent 1px);background-size:40px 40px}
43
+ .map-pin{position:absolute;transform:translate(-50%,-50%);cursor:pointer;transition:transform .2s}
44
+ .map-pin:hover{transform:translate(-50%,-50%) scale(1.2)}
45
+ .map-pin--selected .map-pin-dot{outline:2px solid #00c8ff;outline-offset:3px}
46
+ .map-pin-dot{font-size:22px}
47
+ .map-detail{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.2);border-radius:10px;padding:14px 16px;display:flex;align-items:center;gap:12px}
48
+ .map-detail-name{font-family:'Orbitron',sans-serif;font-size:13px;color:#fff;flex:0 0 auto}
49
+ .map-detail-type{font-size:11px;color:#5ba8ff;text-transform:uppercase;letter-spacing:.06em;flex:0 0 auto}
50
+ .map-detail-desc{font-size:12px;color:#7a9bbf;flex:1}
51
+ .map-btn{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:7px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:6px 14px;cursor:pointer}
52
+ .map-legend{display:flex;gap:16px;flex-wrap:wrap}
53
+ .map-legend-item{display:flex;align-items:center;gap:6px;font-size:12px;color:#7a9bbf}
54
+ `]
55
+ })
56
+ export class MapComponent {
57
+ types = [{ icon: '🏙️', label: 'City' }, { icon: '🌲', label: 'Nature' }, { icon: '⚔️', label: 'Quest Zone' }, { icon: '🏛️', label: 'Hub' }];
58
+ selected = signal<Location | null>(null);
59
+ locations = signal<Location[]>([
60
+ { id: '1', name: 'OASIS Hub Alpha', type: 'Hub', x: 20, y: 30, description: 'Central hub for OASIS operations and avatar registration.' },
61
+ { id: '2', name: 'Neon City', type: 'City', x: 55, y: 20, description: 'A sprawling cyberpunk metropolis in the OASIS.' },
62
+ { id: '3', name: 'Enchanted Forest', type: 'Nature', x: 75, y: 60, description: 'Ancient woodland teeming with rare seeds and creatures.' },
63
+ { id: '4', name: 'Battle Grounds', type: 'Quest Zone', x: 35, y: 70, description: 'Arena for quests, challenges, and PvP competitions.' },
64
+ { id: '5', name: 'Crystal Spire', type: 'Hub', x: 85, y: 35, description: 'Trading post and NFT marketplace in the sky.' },
65
+ ]);
66
+ locIcon(type: string) {
67
+ const m: Record<string, string> = { Hub: '🏛️', City: '🏙️', Nature: '🌲', 'Quest Zone': '⚔️' };
68
+ return m[type] ?? '📍';
69
+ }
70
+ }
@@ -0,0 +1,42 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-plot-route-on-map',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="mf-shell">
10
+ <div class="mf-header"><span>📍</span><h2 class="mf-title">Plot Route on Map</h2></div>
11
+ <div class="mf-body">
12
+ <div class="mf-field"><label class="mf-lbl">Route Name</label><input class="mf-inp" placeholder="Route Name..." /></div>
13
+ <div class="mf-field"><label class="mf-lbl">Start Location</label><input class="mf-inp" placeholder="Start Location..." /></div>
14
+ <div class="mf-field"><label class="mf-lbl">End Location</label><input class="mf-inp" placeholder="End Location..." /></div>
15
+ <div class="mf-field"><label class="mf-lbl">Waypoints</label><input class="mf-inp" placeholder="Waypoints..." /></div>
16
+ <div class="mf-field"><label class="mf-lbl">Description</label><input class="mf-inp" placeholder="Description..." /></div>
17
+ </div>
18
+ <div class="mf-footer">
19
+ <button class="mf-save" (click)="save()">Save</button>
20
+ <button class="mf-cancel" (click)="close.emit()">Cancel</button>
21
+ </div>
22
+ </div>
23
+ `,
24
+ styles: [`
25
+ .mf-shell{display:flex;flex-direction:column;gap:18px;padding:20px}
26
+ .mf-header{display:flex;align-items:center;gap:10px}
27
+ .mf-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
28
+ .mf-body{display:flex;flex-direction:column;gap:12px}
29
+ .mf-field{display:flex;flex-direction:column;gap:5px}
30
+ .mf-lbl{font-size:11px;font-weight:700;letter-spacing:.07em;color:#7a9bbf;text-transform:uppercase}
31
+ .mf-inp{background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.25);border-radius:8px;color:#fff;font-size:13px;padding:10px 14px;outline:none}
32
+ .mf-inp:focus{border-color:rgba(0,200,255,.6)}
33
+ .mf-footer{display:flex;gap:10px;justify-content:flex-end}
34
+ .mf-save{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;padding:10px 22px;cursor:pointer}
35
+ .mf-cancel{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:10px 18px;cursor:pointer}
36
+ `]
37
+ })
38
+ export class PlotRouteOnMapComponent {
39
+ @Input() show = false;
40
+ @Output() close = new EventEmitter<void>();
41
+ save() { this.close.emit(); }
42
+ }
@@ -0,0 +1,44 @@
1
+ import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+
5
+ @Component({
6
+ selector: 'oasis-search-map',
7
+ standalone: true,
8
+ imports: [CommonModule, FormsModule],
9
+ template: `
10
+ <div class="sm-shell">
11
+ <div class="sm-header"><span>🔍</span><h2 class="sm-title">Search Map</h2></div>
12
+ <div class="sm-row"><input class="sm-input" [(ngModel)]="query" placeholder="Search locations, objects, quests..." (keyup.enter)="search()" /><button class="sm-btn" (click)="search()">Search</button></div>
13
+ @for (r of results(); track r.name) {
14
+ <div class="sm-card"><div class="sm-card-icon">{{ r.icon }}</div><div class="sm-card-info"><div class="sm-card-name">{{ r.name }}</div><div class="sm-card-type">{{ r.type }}</div></div></div>
15
+ }
16
+ <div class="sm-footer"><button class="sm-cancel" (click)="close.emit()">Close</button></div>
17
+ </div>
18
+ `,
19
+ styles: [`
20
+ .sm-shell{display:flex;flex-direction:column;gap:14px;padding:20px}
21
+ .sm-header{display:flex;align-items:center;gap:10px}
22
+ .sm-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
23
+ .sm-row{display:flex;gap:10px}
24
+ .sm-input{flex:1;background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.25);border-radius:8px;color:#fff;font-size:13px;padding:10px 14px;outline:none}
25
+ .sm-input:focus{border-color:rgba(0,200,255,.6)}
26
+ .sm-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;padding:10px 20px;cursor:pointer;white-space:nowrap}
27
+ .sm-card{display:flex;align-items:center;gap:12px;background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.12);border-radius:8px;padding:10px 14px}
28
+ .sm-card-icon{font-size:24px}
29
+ .sm-card-name{font-family:'Orbitron',sans-serif;font-size:13px;color:#fff}
30
+ .sm-card-type{font-size:11px;color:#7a9bbf;text-transform:uppercase;letter-spacing:.06em;margin-top:2px}
31
+ .sm-footer{display:flex;justify-content:flex-end}
32
+ .sm-cancel{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:8px 20px;cursor:pointer}
33
+ `]
34
+ })
35
+ export class SearchMapComponent {
36
+ @Input() show = false;
37
+ @Output() close = new EventEmitter<void>();
38
+ query = '';
39
+ results = signal<{name:string;type:string;icon:string}[]>([]);
40
+ search() {
41
+ if (!this.query.trim()) return;
42
+ this.results.set([{ name: this.query + ' Hub', type: 'Location', icon: '🏛️' }]);
43
+ }
44
+ }
@@ -0,0 +1,28 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-view-global-3d-map',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="popup-shell">
10
+ <div class="popup-icon">🌍</div>
11
+ <h2 class="popup-title">View Global 3D Map</h2>
12
+ <p class="popup-body">UI Coming Soon — access this feature via the OASIS API from the Developer menu.</p>
13
+ <button class="popup-btn" (click)="close.emit()">OK</button>
14
+ </div>
15
+ `,
16
+ styles: [`
17
+ .popup-shell{display:flex;flex-direction:column;align-items:center;gap:16px;padding:24px;text-align:center}
18
+ .popup-icon{font-size:48px}
19
+ .popup-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
20
+ .popup-body{font-size:13px;color:#a8bfd8;line-height:1.6;margin:0;max-width:380px}
21
+ .popup-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:10px 28px;cursor:pointer;transition:opacity .2s}
22
+ .popup-btn:hover{opacity:.85}
23
+ `]
24
+ })
25
+ export class ViewGlobal3dMapComponent {
26
+ @Input() show = false;
27
+ @Output() close = new EventEmitter<void>();
28
+ }
@@ -0,0 +1,28 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-view-halons-on-map',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="popup-shell">
10
+ <div class="popup-icon">🔮</div>
11
+ <h2 class="popup-title">View Halons on Map</h2>
12
+ <p class="popup-body">UI Coming Soon — access this feature via the OASIS API from the Developer menu.</p>
13
+ <button class="popup-btn" (click)="close.emit()">OK</button>
14
+ </div>
15
+ `,
16
+ styles: [`
17
+ .popup-shell{display:flex;flex-direction:column;align-items:center;gap:16px;padding:24px;text-align:center}
18
+ .popup-icon{font-size:48px}
19
+ .popup-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
20
+ .popup-body{font-size:13px;color:#a8bfd8;line-height:1.6;margin:0;max-width:380px}
21
+ .popup-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:10px 28px;cursor:pointer;transition:opacity .2s}
22
+ .popup-btn:hover{opacity:.85}
23
+ `]
24
+ })
25
+ export class ViewHalonsOnMapComponent {
26
+ @Input() show = false;
27
+ @Output() close = new EventEmitter<void>();
28
+ }
@@ -0,0 +1,28 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-view-oapp-on-map',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="popup-shell">
10
+ <div class="popup-icon">📱</div>
11
+ <h2 class="popup-title">View OAPPs on Map</h2>
12
+ <p class="popup-body">UI Coming Soon — access this feature via the OASIS API from the Developer menu.</p>
13
+ <button class="popup-btn" (click)="close.emit()">OK</button>
14
+ </div>
15
+ `,
16
+ styles: [`
17
+ .popup-shell{display:flex;flex-direction:column;align-items:center;gap:16px;padding:24px;text-align:center}
18
+ .popup-icon{font-size:48px}
19
+ .popup-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
20
+ .popup-body{font-size:13px;color:#a8bfd8;line-height:1.6;margin:0;max-width:380px}
21
+ .popup-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:10px 28px;cursor:pointer;transition:opacity .2s}
22
+ .popup-btn:hover{opacity:.85}
23
+ `]
24
+ })
25
+ export class ViewOappOnMapComponent {
26
+ @Input() show = false;
27
+ @Output() close = new EventEmitter<void>();
28
+ }
@@ -0,0 +1,28 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-view-quest-on-map',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="popup-shell">
10
+ <div class="popup-icon">⚔️</div>
11
+ <h2 class="popup-title">View Quest on Map</h2>
12
+ <p class="popup-body">UI Coming Soon — access this feature via the OASIS API from the Developer menu.</p>
13
+ <button class="popup-btn" (click)="close.emit()">OK</button>
14
+ </div>
15
+ `,
16
+ styles: [`
17
+ .popup-shell{display:flex;flex-direction:column;align-items:center;gap:16px;padding:24px;text-align:center}
18
+ .popup-icon{font-size:48px}
19
+ .popup-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
20
+ .popup-body{font-size:13px;color:#a8bfd8;line-height:1.6;margin:0;max-width:380px}
21
+ .popup-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:10px 28px;cursor:pointer;transition:opacity .2s}
22
+ .popup-btn:hover{opacity:.85}
23
+ `]
24
+ })
25
+ export class ViewQuestOnMapComponent {
26
+ @Input() show = false;
27
+ @Output() close = new EventEmitter<void>();
28
+ }
@@ -0,0 +1,69 @@
1
+ import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ interface Action { label: string; onClick: () => void; }
5
+ type MessageType = 'info' | 'success' | 'warning' | 'error';
6
+
7
+ const TYPE_COLORS: Record<MessageType, { border: string; bg: string }> = {
8
+ info: { border: 'rgba(0,200,255,.25)', bg: 'rgba(0,200,255,.05)' },
9
+ success: { border: 'rgba(72,220,130,.25)', bg: 'rgba(72,220,130,.05)' },
10
+ warning: { border: 'rgba(255,180,60,.25)', bg: 'rgba(255,180,60,.05)' },
11
+ error: { border: 'rgba(255,80,80,.25)', bg: 'rgba(255,80,80,.05)' },
12
+ };
13
+
14
+ @Component({
15
+ selector: 'app-menu-message',
16
+ standalone: true,
17
+ imports: [CommonModule],
18
+ template: `
19
+ @if (visible()) {
20
+ <div class="mm-shell" [style.border]="'1px solid ' + colors().border" [style.background]="colors().bg">
21
+ <div class="mm-header">
22
+ <span class="mm-icon">{{ icon }}</span>
23
+ <span class="mm-title">{{ title }}</span>
24
+ @if (badge) { <span class="mm-badge">{{ badge }}</span> }
25
+ <button class="mm-close" (click)="dismiss()" aria-label="Dismiss">✕</button>
26
+ </div>
27
+ <div class="mm-body">
28
+ @if (message) { <p class="mm-msg">{{ message }}</p> }
29
+ @if (actions.length > 0) {
30
+ <div class="mm-actions">
31
+ @for (a of actions; track $index; let i = $index) {
32
+ <button [class]="i === 0 ? 'mm-btn' : 'mm-btn-ghost'" (click)="a.onClick()">{{ a.label }}</button>
33
+ }
34
+ </div>
35
+ }
36
+ </div>
37
+ </div>
38
+ }
39
+ `,
40
+ styles: [`
41
+ .mm-shell { font-family: 'Rajdhani', sans-serif; border-radius: 12px; overflow: hidden; }
42
+ .mm-header { padding: 14px 18px; background: rgba(0,200,255,.07); border-bottom: 1px solid rgba(0,200,255,.12); display: flex; align-items: center; gap: 12px; }
43
+ .mm-icon { font-size: 20px; }
44
+ .mm-title { font-family: 'Orbitron', sans-serif; font-size: 15px; font-weight: 700; color: #fff; flex: 1; }
45
+ .mm-badge { display: inline-block; background: rgba(0,200,255,.12); border: 1px solid rgba(0,200,255,.3); color: #00c8ff; border-radius: 999px; font-size: 11px; font-weight: 700; letter-spacing: .08em; padding: 3px 12px; text-transform: uppercase; }
46
+ .mm-close { background: none; border: none; color: #7a9bbf; font-size: 18px; cursor: pointer; line-height: 1; }
47
+ .mm-body { padding: 18px; display: flex; flex-direction: column; gap: 12px; }
48
+ .mm-msg { font-size: 14px; color: #a8bfd8; line-height: 1.65; text-align: center; margin: 0; }
49
+ .mm-actions { display: flex; gap: 10px; justify-content: center; margin-top: 4px; }
50
+ .mm-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: 9px 22px; cursor: pointer; }
51
+ .mm-btn-ghost { background: transparent; border: 1px solid rgba(0,200,255,.3); border-radius: 8px; color: #00c8ff; font-family: 'Orbitron', sans-serif; font-size: 12px; font-weight: 700; letter-spacing: .08em; padding: 9px 22px; cursor: pointer; }
52
+ `],
53
+ })
54
+ export class MenuMessageComponent {
55
+ @Input() title = 'Notice';
56
+ @Input() message = '';
57
+ @Input() icon = 'ℹ️';
58
+ @Input() type: MessageType = 'info';
59
+ @Input() badge = '';
60
+ @Input() actions: Action[] = [];
61
+ @Output() dismissed = new EventEmitter<void>();
62
+
63
+ visible = signal(true);
64
+ colors = signal(TYPE_COLORS.info);
65
+
66
+ ngOnChanges() { this.colors.set(TYPE_COLORS[this.type] ?? TYPE_COLORS.info); }
67
+
68
+ dismiss() { this.visible.set(false); this.dismissed.emit(); }
69
+ }
@@ -0,0 +1,59 @@
1
+ import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+
5
+ @Component({
6
+ selector: 'oasis-message',
7
+ standalone: true,
8
+ imports: [CommonModule, FormsModule],
9
+ template: `
10
+ <div class="msg-shell">
11
+ <div class="msg-header"><span>✉️</span><h2 class="msg-title">Message</h2></div>
12
+ <div class="msg-thread">
13
+ @for (m of messages(); track m.id) {
14
+ <div class="msg-bubble" [class.msg-bubble--mine]="m.mine">
15
+ <div class="msg-sender">{{ m.sender }}</div>
16
+ <div class="msg-text">{{ m.text }}</div>
17
+ <div class="msg-time">{{ m.time }}</div>
18
+ </div>
19
+ }
20
+ </div>
21
+ <div class="msg-compose">
22
+ <input class="msg-input" [(ngModel)]="draft" placeholder="Type a message..." (keyup.enter)="send()" />
23
+ <button class="msg-send" (click)="send()">Send</button>
24
+ </div>
25
+ <div class="msg-footer"><button class="msg-cancel" (click)="close.emit()">Close</button></div>
26
+ </div>
27
+ `,
28
+ styles: [`
29
+ .msg-shell{display:flex;flex-direction:column;gap:14px;padding:20px}
30
+ .msg-header{display:flex;align-items:center;gap:10px}
31
+ .msg-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
32
+ .msg-thread{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto;padding:4px 0}
33
+ .msg-bubble{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:10px 14px;max-width:80%}
34
+ .msg-bubble--mine{align-self:flex-end;background:rgba(0,200,255,.1);border-color:rgba(0,200,255,.25)}
35
+ .msg-sender{font-size:11px;font-weight:700;color:#5ba8ff;margin-bottom:4px}
36
+ .msg-text{font-size:13px;color:#c8dff0}
37
+ .msg-time{font-size:10px;color:#7a9bbf;margin-top:4px;text-align:right}
38
+ .msg-compose{display:flex;gap:10px}
39
+ .msg-input{flex:1;background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.25);border-radius:8px;color:#fff;font-size:13px;padding:10px 14px;outline:none}
40
+ .msg-input:focus{border-color:rgba(0,200,255,.6)}
41
+ .msg-send{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;padding:10px 18px;cursor:pointer;white-space:nowrap}
42
+ .msg-footer{display:flex;justify-content:flex-end}
43
+ .msg-cancel{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:8px 20px;cursor:pointer}
44
+ `]
45
+ })
46
+ export class MessageComponent {
47
+ @Input() show = false;
48
+ @Output() close = new EventEmitter<void>();
49
+ draft = '';
50
+ messages = signal([
51
+ { id:1, sender:'CosmicSage', text:'Welcome to the OASIS!', time:'10:30', mine:false },
52
+ { id:2, sender:'You', text:'Thanks! Great to be here.', time:'10:31', mine:true },
53
+ ]);
54
+ send() {
55
+ if (!this.draft.trim()) return;
56
+ this.messages.update(m => [...m, { id: m.length+1, sender:'You', text:this.draft, time:new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}), mine:true }]);
57
+ this.draft = '';
58
+ }
59
+ }
@@ -0,0 +1,89 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+
5
+ interface Message { id: string; from: string; preview: string; time: string; read: boolean; body: string }
6
+
7
+ @Component({
8
+ selector: 'oasis-messaging',
9
+ standalone: true,
10
+ imports: [CommonModule, FormsModule],
11
+ template: `
12
+ <div class="msg-shell">
13
+ <div class="msg-sidebar">
14
+ <div class="msg-search-wrap"><input class="msg-search" type="text" [(ngModel)]="searchQuery" placeholder="Search messages…" /></div>
15
+ <div class="msg-list">
16
+ @for (m of filtered(); track m.id) {
17
+ <div class="msg-item" [class.msg-item--active]="selected()?.id === m.id" [class.msg-item--unread]="!m.read" (click)="open(m)">
18
+ <div class="msg-from">{{ m.from }}</div>
19
+ <div class="msg-preview">{{ m.preview }}</div>
20
+ <div class="msg-time">{{ m.time }}</div>
21
+ </div>
22
+ }
23
+ @if (filtered().length === 0) { <div class="msg-empty">No messages found.</div> }
24
+ </div>
25
+ </div>
26
+ <div class="msg-main">
27
+ @if (selected()) {
28
+ <div class="msg-detail-header"><div class="msg-detail-from">{{ selected()!.from }}</div><div class="msg-detail-time">{{ selected()!.time }}</div></div>
29
+ <div class="msg-body">{{ selected()!.body }}</div>
30
+ <div class="msg-reply-wrap">
31
+ <textarea class="msg-reply-input" [(ngModel)]="reply" placeholder="Write a reply…" rows="3"></textarea>
32
+ <button class="oasis-btn" [disabled]="!reply.trim()" (click)="sendReply()">Send</button>
33
+ </div>
34
+ } @else {
35
+ <div class="msg-placeholder">📬 Select a message to read it</div>
36
+ }
37
+ </div>
38
+ </div>
39
+ `,
40
+ styles: [`
41
+ .msg-shell{display:grid;grid-template-columns:280px 1fr;height:480px;border:1px solid rgba(0,200,255,.15);border-radius:12px;overflow:hidden}
42
+ .msg-sidebar{border-right:1px solid rgba(0,200,255,.1);display:flex;flex-direction:column}
43
+ .msg-search-wrap{padding:12px;border-bottom:1px solid rgba(0,200,255,.1)}
44
+ .msg-search{width:100%;background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.2);border-radius:8px;padding:8px 12px;color:#fff;font-size:13px;outline:none;box-sizing:border-box}
45
+ .msg-list{flex:1;overflow-y:auto}
46
+ .msg-item{padding:12px 14px;border-bottom:1px solid rgba(0,200,255,.07);cursor:pointer;transition:background .15s}
47
+ .msg-item:hover{background:rgba(0,200,255,.06)}
48
+ .msg-item--active{background:rgba(0,200,255,.1);border-left:3px solid #00c8ff}
49
+ .msg-item--unread .msg-from{font-weight:700}
50
+ .msg-from{font-size:13px;color:#e0f0ff;margin-bottom:3px}
51
+ .msg-preview{font-size:12px;color:#7a9bbf;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
52
+ .msg-time{font-size:11px;color:#4a6a88;margin-top:4px}
53
+ .msg-empty{padding:24px;text-align:center;color:#4a6a88;font-size:13px}
54
+ .msg-main{display:flex;flex-direction:column;background:rgba(0,0,0,.2)}
55
+ .msg-placeholder{display:flex;align-items:center;justify-content:center;flex:1;color:#4a6a88;font-size:14px}
56
+ .msg-detail-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid rgba(0,200,255,.1)}
57
+ .msg-detail-from{font-family:'Orbitron',sans-serif;font-size:14px;color:#fff}
58
+ .msg-detail-time{font-size:12px;color:#4a6a88}
59
+ .msg-body{flex:1;padding:20px;font-size:14px;color:#a8bfd8;line-height:1.7;overflow-y:auto}
60
+ .msg-reply-wrap{padding:16px;border-top:1px solid rgba(0,200,255,.1);display:flex;flex-direction:column;gap:10px}
61
+ .msg-reply-input{width:100%;background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.2);border-radius:8px;padding:10px 14px;color:#fff;font-size:13px;outline:none;resize:none;box-sizing:border-box;font-family:inherit}
62
+ .oasis-btn{align-self:flex-end;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:9px 24px;cursor:pointer;transition:opacity .2s}
63
+ .oasis-btn:disabled{opacity:.4;cursor:not-allowed}
64
+ `]
65
+ })
66
+ export class MessagingComponent {
67
+ searchQuery = ''; reply = '';
68
+ selected = signal<Message | null>(null);
69
+ messages = signal<Message[]>([
70
+ { id: '1', from: 'OASIS Team', preview: 'Welcome to the OASIS network!', time: 'Just now', read: false, body: 'Welcome to the OASIS Omniverse! Your avatar is now connected to the network. Explore NFTs, karma, quests and more.' },
71
+ { id: '2', from: 'System', preview: 'Your karma level has increased.', time: '2h ago', read: true, body: 'Congratulations! Your karma level has increased. Keep contributing to the community to unlock new features and NFTs.' },
72
+ ]);
73
+
74
+ filtered() {
75
+ const q = this.searchQuery.toLowerCase();
76
+ return this.messages().filter(m => m.from.toLowerCase().includes(q) || m.preview.toLowerCase().includes(q));
77
+ }
78
+
79
+ open(m: Message) {
80
+ m.read = true;
81
+ this.selected.set({ ...m });
82
+ this.reply = '';
83
+ }
84
+
85
+ sendReply() {
86
+ if (!this.reply.trim()) return;
87
+ this.reply = '';
88
+ }
89
+ }