@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,48 @@
1
+ import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-view-leagues',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="lg-shell">
10
+ <div class="lg-header"><span>🏅</span><h2 class="lg-title">Leagues</h2></div>
11
+ <div class="lg-table-wrap">
12
+ <table class="lg-table">
13
+ <thead><tr><th>Rank</th><th>League</th><th>Members</th><th>Season Points</th><th>Status</th></tr></thead>
14
+ <tbody>
15
+ @for (l of leagues(); track l.rank) {
16
+ <tr><td>{{ l.rank }}</td><td>{{ l.name }}</td><td>{{ l.members }}</td><td>{{ l.points }}</td><td><span class="lg-status lg-status--{{ l.status.toLowerCase() }}">{{ l.status }}</span></td></tr>
17
+ }
18
+ </tbody>
19
+ </table>
20
+ </div>
21
+ <div class="lg-footer"><button class="lg-btn" (click)="close.emit()">Close</button></div>
22
+ </div>
23
+ `,
24
+ styles: [`
25
+ .lg-shell{display:flex;flex-direction:column;gap:16px;padding:20px}
26
+ .lg-header{display:flex;align-items:center;gap:10px}
27
+ .lg-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
28
+ .lg-table-wrap{overflow-x:auto;border:1px solid rgba(0,200,255,.15);border-radius:10px}
29
+ .lg-table{width:100%;border-collapse:collapse;font-size:13px}
30
+ .lg-table th{background:rgba(0,200,255,.08);color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:10px;font-weight:700;letter-spacing:.06em;padding:10px 12px;text-align:left;border-bottom:1px solid rgba(0,200,255,.15)}
31
+ .lg-table td{color:#c8dff0;padding:9px 12px;border-bottom:1px solid rgba(255,255,255,.04)}
32
+ .lg-status{border-radius:999px;padding:2px 9px;font-size:10px;font-weight:700}
33
+ .lg-status--active{background:rgba(0,200,100,.15);color:#00c864;border:1px solid rgba(0,200,100,.3)}
34
+ .lg-status--closed{background:rgba(122,155,191,.1);color:#7a9bbf;border:1px solid rgba(122,155,191,.25)}
35
+ .lg-footer{display:flex;justify-content:flex-end}
36
+ .lg-btn{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:9px 22px;cursor:pointer}
37
+ `]
38
+ })
39
+ export class ViewLeaguesComponent {
40
+ @Input() show = false;
41
+ @Output() close = new EventEmitter<void>();
42
+ leagues = signal([
43
+ { rank:1, name:'Platinum League', members:128, points:9800, status:'Active' },
44
+ { rank:2, name:'Gold League', members:256, points:6500, status:'Active' },
45
+ { rank:3, name:'Silver League', members:512, points:3200, status:'Active' },
46
+ { rank:4, name:'Bronze League', members:1024, points:1100, status:'Closed' },
47
+ ]);
48
+ }
@@ -0,0 +1,49 @@
1
+ import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-view-tournaments',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="tn-shell">
10
+ <div class="tn-header"><span>⚔️</span><h2 class="tn-title">Tournaments</h2></div>
11
+ <div class="tn-list">
12
+ @for (t of tournaments(); track t.id) {
13
+ <div class="tn-card">
14
+ <div class="tn-status tn-status--{{ t.status.toLowerCase() }}">{{ t.status }}</div>
15
+ <div class="tn-name">{{ t.name }}</div>
16
+ <div class="tn-meta">{{ t.participants }} participants · Prize: {{ t.prize }}</div>
17
+ <div class="tn-date">{{ t.date }}</div>
18
+ </div>
19
+ }
20
+ </div>
21
+ <div class="tn-footer"><button class="tn-btn" (click)="close.emit()">Close</button></div>
22
+ </div>
23
+ `,
24
+ styles: [`
25
+ .tn-shell{display:flex;flex-direction:column;gap:16px;padding:20px}
26
+ .tn-header{display:flex;align-items:center;gap:10px}
27
+ .tn-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
28
+ .tn-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}
29
+ .tn-card{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.15);border-radius:10px;padding:16px;display:flex;flex-direction:column;gap:8px}
30
+ .tn-status{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;border-radius:999px;padding:2px 10px;align-self:flex-start;border:1px solid currentColor}
31
+ .tn-status--open{color:#00c864;border-color:rgba(0,200,100,.3)}
32
+ .tn-status--upcoming{color:#5ba8ff;border-color:rgba(91,168,255,.3)}
33
+ .tn-status--closed{color:#7a9bbf;border-color:rgba(122,155,191,.25)}
34
+ .tn-name{font-family:'Orbitron',sans-serif;font-size:13px;color:#fff}
35
+ .tn-meta{font-size:12px;color:#7a9bbf}
36
+ .tn-date{font-size:11px;color:#5ba8ff}
37
+ .tn-footer{display:flex;justify-content:flex-end}
38
+ .tn-btn{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:9px 22px;cursor:pointer}
39
+ `]
40
+ })
41
+ export class ViewTournamentsComponent {
42
+ @Input() show = false;
43
+ @Output() close = new EventEmitter<void>();
44
+ tournaments = signal([
45
+ { id:1, name:'Galactic Cup 2026', participants:64, prize:'500 SEEDS', status:'Open', date:'2026-07-15' },
46
+ { id:2, name:'Neon Arena Finals', participants:32, prize:'1 ETH', status:'Upcoming', date:'2026-08-01' },
47
+ { id:3, name:'Star Wars Classic', participants:128, prize:'200 SEEDS', status:'Closed', date:'2026-06-01' },
48
+ ]);
49
+ }
@@ -0,0 +1,81 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-hyperdrive',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="hd-shell">
10
+ <div class="hd-header"><h2 class="hd-title">🚀 HyperDrive</h2><p class="hd-sub">Quantum-speed data layer for the OASIS Omniverse.</p></div>
11
+ <div class="hd-status-card" [class.hd-status-card--active]="active()">
12
+ <div class="hd-status-icon">{{ active() ? '⚡' : '💤' }}</div>
13
+ <div class="hd-status-info">
14
+ <div class="hd-status-label">HyperDrive Status</div>
15
+ <div class="hd-status-val" [class.hd-status-val--on]="active()">{{ active() ? 'ACTIVE' : 'OFFLINE' }}</div>
16
+ </div>
17
+ <button class="hd-toggle-btn" [class.hd-toggle-btn--on]="active()" (click)="active.set(!active())">{{ active() ? 'Disengage' : 'Engage' }}</button>
18
+ </div>
19
+ @if (active()) {
20
+ <div class="hd-metrics">
21
+ @for (m of metrics; track m.label) {
22
+ <div class="hd-metric-card">
23
+ <div class="hd-metric-val">{{ m.value }}</div>
24
+ <div class="hd-metric-label">{{ m.label }}</div>
25
+ </div>
26
+ }
27
+ </div>
28
+ <div class="hd-nodes">
29
+ <div class="hd-nodes-header">Connected Nodes</div>
30
+ @for (n of nodes; track n.id) {
31
+ <div class="hd-node">
32
+ <div class="hd-node-dot hd-node-dot--{{ n.status }}"></div>
33
+ <div class="hd-node-name">{{ n.name }}</div>
34
+ <div class="hd-node-latency">{{ n.latency }}ms</div>
35
+ </div>
36
+ }
37
+ </div>
38
+ }
39
+ </div>
40
+ `,
41
+ styles: [`
42
+ .hd-shell{display:flex;flex-direction:column;gap:18px}
43
+ .hd-header{text-align:center}
44
+ .hd-title{font-family:'Orbitron',sans-serif;font-size:20px;color:#fff;margin:0 0 6px}
45
+ .hd-sub{font-size:13px;color:#7a9bbf;margin:0}
46
+ .hd-status-card{display:flex;align-items:center;gap:14px;background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.12);border-radius:12px;padding:16px;transition:border-color .2s}
47
+ .hd-status-card--active{border-color:rgba(0,200,255,.4);background:rgba(0,200,255,.05)}
48
+ .hd-status-icon{font-size:28px}
49
+ .hd-status-info{flex:1}
50
+ .hd-status-label{font-size:12px;color:#7a9bbf;text-transform:uppercase;letter-spacing:.06em}
51
+ .hd-status-val{font-family:'Orbitron',sans-serif;font-size:16px;color:#7a9bbf;margin-top:2px}
52
+ .hd-status-val--on{color:#00c8ff}
53
+ .hd-toggle-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 20px;cursor:pointer;transition:opacity .2s}
54
+ .hd-toggle-btn--on{background:rgba(255,80,80,.2);border:1px solid rgba(255,80,80,.35);color:#ff6b6b}
55
+ .hd-metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
56
+ .hd-metric-card{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:14px;text-align:center}
57
+ .hd-metric-val{font-family:'Share Tech Mono',monospace;font-size:20px;color:#00c8ff;font-weight:600}
58
+ .hd-metric-label{font-size:11px;color:#7a9bbf;margin-top:4px;text-transform:uppercase;letter-spacing:.06em}
59
+ .hd-nodes{display:flex;flex-direction:column;gap:8px}
60
+ .hd-nodes-header{font-family:'Orbitron',sans-serif;font-size:12px;color:#7a9bbf;letter-spacing:.06em;text-transform:uppercase}
61
+ .hd-node{display:flex;align-items:center;gap:10px;padding:10px 14px;background:rgba(255,255,255,.03);border:1px solid rgba(0,200,255,.07);border-radius:8px}
62
+ .hd-node-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
63
+ .hd-node-dot--active{background:#48dc82;box-shadow:0 0 6px #48dc82}
64
+ .hd-node-dot--idle{background:#7a9bbf}
65
+ .hd-node-name{flex:1;font-size:13px;color:#e0f0ff}
66
+ .hd-node-latency{font-family:'Share Tech Mono',monospace;font-size:12px;color:#7a9bbf}
67
+ `]
68
+ })
69
+ export class HyperDriveComponent {
70
+ active = signal(false);
71
+ metrics = [
72
+ { label: 'Throughput', value: '4.2 TB/s' },
73
+ { label: 'Latency', value: '0.3ms' },
74
+ { label: 'Uptime', value: '99.98%' },
75
+ ];
76
+ nodes = [
77
+ { id: '1', name: 'ONODE Alpha — US East', status: 'active', latency: 12 },
78
+ { id: '2', name: 'ONODE Beta — EU West', status: 'active', latency: 28 },
79
+ { id: '3', name: 'ONODE Gamma — Asia Pacific', status: 'idle', latency: 55 },
80
+ ];
81
+ }
@@ -0,0 +1,36 @@
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-karma',
7
+ standalone: true,
8
+ imports: [CommonModule, FormsModule],
9
+ template: `
10
+ <div class="sf-shell">
11
+ <div class="sf-header"><span>✨</span><h2 class="sf-title">Search Karma</h2></div>
12
+ <div class="sf-row"><input class="sf-input" [(ngModel)]="query" placeholder="Search by avatar name..." (keyup.enter)="search()" /><button class="sf-btn" (click)="search()">Search</button></div>
13
+ @for (r of results(); track r) { <div class="sf-result">{{ r }}</div> }
14
+ <div class="sf-footer"><button class="sf-cancel" (click)="close.emit()">Close</button></div>
15
+ </div>
16
+ `,
17
+ styles: [`
18
+ .sf-shell{display:flex;flex-direction:column;gap:14px;padding:20px}
19
+ .sf-header{display:flex;align-items:center;gap:10px}
20
+ .sf-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
21
+ .sf-row{display:flex;gap:10px}
22
+ .sf-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}
23
+ .sf-input:focus{border-color:rgba(0,200,255,.6)}
24
+ .sf-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}
25
+ .sf-result{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.12);border-radius:8px;color:#c8dff0;font-size:13px;padding:10px 14px}
26
+ .sf-footer{display:flex;justify-content:flex-end}
27
+ .sf-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}
28
+ `]
29
+ })
30
+ export class SearchKarmaComponent {
31
+ @Input() show = false;
32
+ @Output() close = new EventEmitter<void>();
33
+ query = '';
34
+ results = signal<string[]>([]);
35
+ search() { if (this.query.trim()) this.results.set(['No results found for "' + this.query + '"']); }
36
+ }
@@ -0,0 +1,51 @@
1
+ import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-view-avatar-karma',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="vak-shell">
10
+ <div class="vak-header"><span>⭐</span><h2 class="vak-title">Avatar Karma</h2></div>
11
+ <div class="vak-score">
12
+ <div class="vak-score-val">{{ totalKarma() }}</div>
13
+ <div class="vak-score-lbl">Total Karma Points</div>
14
+ </div>
15
+ <div class="vak-breakdown">
16
+ @for (k of karmaBreakdown(); track k.category) {
17
+ <div class="vak-row"><span class="vak-cat">{{ k.category }}</span><div class="vak-bar-wrap"><div class="vak-bar" [style.width.%]="k.pct"></div></div><span class="vak-pts">{{ k.points }}</span></div>
18
+ }
19
+ </div>
20
+ <div class="vak-footer"><button class="vak-btn" (click)="close.emit()">Close</button></div>
21
+ </div>
22
+ `,
23
+ styles: [`
24
+ .vak-shell{display:flex;flex-direction:column;gap:18px;padding:20px}
25
+ .vak-header{display:flex;align-items:center;gap:10px}
26
+ .vak-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
27
+ .vak-score{text-align:center;padding:20px;background:rgba(255,180,60,.06);border:1px solid rgba(255,180,60,.2);border-radius:12px}
28
+ .vak-score-val{font-family:'Orbitron',sans-serif;font-size:40px;font-weight:700;color:#ffb43c}
29
+ .vak-score-lbl{font-size:12px;color:#7a9bbf;margin-top:4px;text-transform:uppercase;letter-spacing:.08em}
30
+ .vak-breakdown{display:flex;flex-direction:column;gap:10px}
31
+ .vak-row{display:flex;align-items:center;gap:12px}
32
+ .vak-cat{font-size:12px;color:#c8dff0;width:140px;flex-shrink:0}
33
+ .vak-bar-wrap{flex:1;height:6px;background:rgba(255,255,255,.08);border-radius:999px;overflow:hidden}
34
+ .vak-bar{height:100%;background:linear-gradient(90deg,#ffb43c,#ff8c00);border-radius:999px;transition:width .4s}
35
+ .vak-pts{font-size:12px;color:#ffb43c;font-weight:700;width:40px;text-align:right}
36
+ .vak-footer{display:flex;justify-content:flex-end}
37
+ .vak-btn{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:9px 22px;cursor:pointer}
38
+ `]
39
+ })
40
+ export class ViewAvatarKarmaComponent {
41
+ @Input() show = false;
42
+ @Output() close = new EventEmitter<void>();
43
+ totalKarma = signal(840);
44
+ karmaBreakdown = signal([
45
+ { category: 'Quest Completion', points: 300, pct: 80 },
46
+ { category: 'Helping Others', points: 200, pct: 55 },
47
+ { category: 'NFT Minting', points: 150, pct: 40 },
48
+ { category: 'Community Votes', points: 120, pct: 32 },
49
+ { category: 'Seeds Donated', points: 70, pct: 19 },
50
+ ]);
51
+ }
@@ -0,0 +1,46 @@
1
+ import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'oasis-view-karma',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <div class="vk-shell">
10
+ <div class="vk-header"><span>✨</span><h2 class="vk-title">View Karma</h2></div>
11
+ <div class="vk-table-wrap">
12
+ <table class="vk-table">
13
+ <thead><tr><th>Avatar</th><th>Karma</th><th>Level</th><th>Rank</th></tr></thead>
14
+ <tbody>
15
+ @for (k of karmaList(); track k.avatar) {
16
+ <tr><td>{{ k.avatar }}</td><td>{{ k.karma }}</td><td>{{ k.level }}</td><td>#{{ k.rank }}</td></tr>
17
+ }
18
+ </tbody>
19
+ </table>
20
+ </div>
21
+ <div class="vk-footer"><button class="vk-btn" (click)="close.emit()">Close</button></div>
22
+ </div>
23
+ `,
24
+ styles: [`
25
+ .vk-shell{display:flex;flex-direction:column;gap:16px;padding:20px}
26
+ .vk-header{display:flex;align-items:center;gap:10px}
27
+ .vk-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
28
+ .vk-table-wrap{overflow-x:auto;border:1px solid rgba(0,200,255,.15);border-radius:10px}
29
+ .vk-table{width:100%;border-collapse:collapse;font-size:13px}
30
+ .vk-table th{background:rgba(0,200,255,.08);color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:10px;font-weight:700;letter-spacing:.06em;padding:10px 12px;text-align:left;border-bottom:1px solid rgba(0,200,255,.15)}
31
+ .vk-table td{color:#c8dff0;padding:9px 12px;border-bottom:1px solid rgba(255,255,255,.04)}
32
+ .vk-table tr:last-child td{border-bottom:none}
33
+ .vk-footer{display:flex;justify-content:flex-end}
34
+ .vk-btn{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:9px 22px;cursor:pointer}
35
+ `]
36
+ })
37
+ export class ViewKarmaComponent {
38
+ @Input() show = false;
39
+ @Output() close = new EventEmitter<void>();
40
+ karmaList = signal([
41
+ { avatar: 'CosmicSage', karma: 1500, level: 20, rank: 1 },
42
+ { avatar: 'StarWalker', karma: 840, level: 12, rank: 2 },
43
+ { avatar: 'NebulaDancer', karma: 420, level: 7, rank: 3 },
44
+ { avatar: 'VoidWatcher', karma: 210, level: 4, rank: 4 },
45
+ ]);
46
+ }
@@ -0,0 +1,48 @@
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-vote-karma',
7
+ standalone: true,
8
+ imports: [CommonModule, FormsModule],
9
+ template: `
10
+ <div class="vok-shell">
11
+ <div class="vok-header"><span>🗳️</span><h2 class="vok-title">Vote Karma</h2></div>
12
+ <div class="vok-field"><label class="vok-lbl">Avatar Name</label><input class="vok-input" [(ngModel)]="avatarName" placeholder="Enter avatar name..." /></div>
13
+ <div class="vok-vote-row">
14
+ <button class="vok-upvote" (click)="vote('up')">👍 Upvote Karma</button>
15
+ <button class="vok-downvote" (click)="vote('down')">👎 Downvote Karma</button>
16
+ </div>
17
+ @if (message()) { <div class="vok-message">{{ message() }}</div> }
18
+ <div class="vok-footer"><button class="vok-cancel" (click)="close.emit()">Close</button></div>
19
+ </div>
20
+ `,
21
+ styles: [`
22
+ .vok-shell{display:flex;flex-direction:column;gap:18px;padding:20px}
23
+ .vok-header{display:flex;align-items:center;gap:10px}
24
+ .vok-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
25
+ .vok-field{display:flex;flex-direction:column;gap:6px}
26
+ .vok-lbl{font-size:11px;font-weight:700;letter-spacing:.07em;color:#7a9bbf;text-transform:uppercase}
27
+ .vok-input{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}
28
+ .vok-input:focus{border-color:rgba(0,200,255,.6)}
29
+ .vok-vote-row{display:flex;gap:12px}
30
+ .vok-upvote,.vok-downvote{flex:1;border:none;border-radius:8px;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;padding:12px;cursor:pointer;transition:opacity .2s}
31
+ .vok-upvote{background:linear-gradient(135deg,#00c864,#007840);color:#fff}
32
+ .vok-downvote{background:linear-gradient(135deg,#ff4444,#cc0000);color:#fff}
33
+ .vok-upvote:hover,.vok-downvote:hover{opacity:.85}
34
+ .vok-message{background:rgba(0,200,255,.08);border:1px solid rgba(0,200,255,.2);border-radius:8px;color:#c8dff0;font-size:13px;padding:12px 16px;text-align:center}
35
+ .vok-footer{display:flex;justify-content:flex-end}
36
+ .vok-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}
37
+ `]
38
+ })
39
+ export class VoteKarmaComponent {
40
+ @Input() show = false;
41
+ @Output() close = new EventEmitter<void>();
42
+ avatarName = '';
43
+ message = signal('');
44
+ vote(direction: 'up' | 'down') {
45
+ if (!this.avatarName.trim()) { this.message.set('Please enter an avatar name.'); return; }
46
+ this.message.set(direction === 'up' ? 'Upvoted karma for ' + this.avatarName + '!' : 'Downvoted karma for ' + this.avatarName + '.');
47
+ }
48
+ }
@@ -0,0 +1,87 @@
1
+ import { Component, signal, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { OasisService } from '../../services/oasis.service';
4
+
5
+ type Tab = 'records' | 'stats' | 'weightings';
6
+
7
+ @Component({
8
+ selector: 'oasis-karma-panel',
9
+ standalone: true,
10
+ imports: [CommonModule],
11
+ template: `
12
+ <div class="kp-shell">
13
+ <div class="kp-banner">
14
+ <div class="kp-stat">
15
+ <div class="kp-stat-label">KARMA</div>
16
+ <div class="kp-stat-value">{{ oasis.session()?.karma ?? '—' }}</div>
17
+ </div>
18
+ <div class="kp-stat">
19
+ <div class="kp-stat-label">LEVEL</div>
20
+ <div class="kp-stat-value kp-stat-value--level">{{ level() }}</div>
21
+ </div>
22
+ <div class="kp-stat">
23
+ <div class="kp-stat-label">AVATAR TYPE</div>
24
+ <div class="kp-stat-value kp-stat-value--type">User</div>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="kp-tabs">
29
+ <button class="kp-tab" [class.kp-tab--active]="activeTab() === 'records'" (click)="activeTab.set('records')">Akashic Records</button>
30
+ <button class="kp-tab" [class.kp-tab--active]="activeTab() === 'stats'" (click)="activeTab.set('stats')">Stats</button>
31
+ <button class="kp-tab" [class.kp-tab--active]="activeTab() === 'weightings'" (click)="activeTab.set('weightings')">Weightings</button>
32
+ </div>
33
+
34
+ @if (activeTab() === 'records') {
35
+ <div class="kp-panel kp-empty">
36
+ <div class="kp-empty-icon">📜</div>
37
+ <p>No karma records found.<br>Complete actions in the OASIS ecosystem to build your Akashic Record.</p>
38
+ </div>
39
+ }
40
+ @if (activeTab() === 'stats') {
41
+ <div class="kp-panel kp-empty">
42
+ <div class="kp-empty-icon">📊</div>
43
+ <p>No stats available yet.</p>
44
+ </div>
45
+ }
46
+ @if (activeTab() === 'weightings') {
47
+ <div class="kp-panel">
48
+ <div class="kp-vote-banner">
49
+ <div>
50
+ <strong>Karma Voting</strong>
51
+ <p>Community voting to adjust karma weightings is coming soon.</p>
52
+ </div>
53
+ <button class="kp-btn-ghost">Vote on Weightings <span class="kp-soon">Coming Soon</span></button>
54
+ </div>
55
+ </div>
56
+ }
57
+ </div>
58
+ `,
59
+ styles: [`
60
+ .kp-shell { color:#fff; display:flex; flex-direction:column; gap:20px; }
61
+ .kp-banner { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
62
+ .kp-stat { background:rgba(0,200,255,.06); border:1px solid rgba(0,200,255,.12); border-radius:10px; padding:14px; text-align:center; }
63
+ .kp-stat-label { font-size:10px; font-weight:700; letter-spacing:.1em; color:#7a9bbf; text-transform:uppercase; margin-bottom:6px; }
64
+ .kp-stat-value { font-family:'Orbitron',sans-serif; font-size:22px; color:#00c8ff; }
65
+ .kp-stat-value--level { color:#a78bfa; }
66
+ .kp-stat-value--type { color:#48dc82; font-size:16px; }
67
+ .kp-tabs { display:flex; gap:4px; border-bottom:1px solid rgba(0,200,255,.12); }
68
+ .kp-tab { background:none; border:none; color:#7a9bbf; font-size:13px; padding:10px 16px; cursor:pointer; border-bottom:2px solid transparent; transition:all .2s; }
69
+ .kp-tab--active { color:#00c8ff; border-bottom-color:#00c8ff; }
70
+ .kp-panel { padding:16px 0; }
71
+ .kp-empty { display:flex; flex-direction:column; align-items:center; gap:10px; padding:40px 16px; text-align:center; color:#7a9bbf; font-size:13px; line-height:1.6; }
72
+ .kp-empty-icon { font-size:32px; }
73
+ .kp-vote-banner { display:flex; align-items:center; justify-content:space-between; gap:16px; background:rgba(255,255,255,.03); border:1px solid rgba(0,200,255,.1); border-radius:10px; padding:16px; font-size:13px; color:#a8bfd8; line-height:1.5; }
74
+ .kp-vote-banner strong { display:block; color:#fff; margin-bottom:4px; }
75
+ .kp-btn-ghost { flex-shrink:0; background:transparent; border:1px solid rgba(0,200,255,.3); color:#00c8ff; border-radius:6px; padding:8px 14px; font-size:12px; cursor:pointer; white-space:nowrap; }
76
+ .kp-soon { display:inline-block; background:rgba(255,180,60,.15); color:#ffb43c; border-radius:999px; font-size:9px; padding:2px 6px; margin-left:6px; letter-spacing:.06em; vertical-align:middle; }
77
+ `]
78
+ })
79
+ export class KarmaPanelComponent {
80
+ oasis = inject(OasisService);
81
+ activeTab = signal<Tab>('records');
82
+
83
+ level() {
84
+ const k = this.oasis.session()?.karma ?? 0;
85
+ return Math.floor(Math.sqrt(k / 100)) + 1;
86
+ }
87
+ }
@@ -0,0 +1,33 @@
1
+ import { Component, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { OasisService } from '../../services/oasis.service';
4
+
5
+ @Component({
6
+ selector: 'oasis-karma-toast',
7
+ standalone: true,
8
+ imports: [CommonModule],
9
+ template: `
10
+ @if (toast()) {
11
+ <div class="karma-toast">
12
+ <span class="kt-amount">+{{ toast()!.amount }} ✦</span>
13
+ <span class="kt-msg">{{ toast()!.message }}</span>
14
+ </div>
15
+ }
16
+ `,
17
+ styles: [`
18
+ .karma-toast {
19
+ position: fixed; bottom: 32px; right: 32px; z-index: 9999;
20
+ background: linear-gradient(135deg, rgba(232,121,249,.15), rgba(168,85,247,.1));
21
+ border: 1px solid rgba(232,121,249,.4); border-radius: 14px;
22
+ padding: 16px 24px; display: flex; flex-direction: column; gap: 4px;
23
+ backdrop-filter: blur(12px);
24
+ animation: slideIn .35s ease;
25
+ }
26
+ .kt-amount { font-family: 'Orbitron', sans-serif; font-size: 22px; font-weight: 900; color: #e879f9; }
27
+ .kt-msg { font-family: 'Share Tech Mono', monospace; font-size: 11px; letter-spacing: .1em; color: #a8bfd8; }
28
+ @keyframes slideIn { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
29
+ `]
30
+ })
31
+ export class KarmaToastComponent {
32
+ toast = inject(OasisService).karmaToast;
33
+ }
@@ -0,0 +1,95 @@
1
+ import { Component, signal, Output, EventEmitter, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { OasisService } from '../../services/oasis.service';
5
+
6
+ @Component({
7
+ selector: 'oasis-login',
8
+ standalone: true,
9
+ imports: [CommonModule, FormsModule],
10
+ template: `
11
+ <div class="oasis-auth-form">
12
+ <div class="oasis-auth-header">
13
+ <h2 class="oasis-auth-title">Beam In</h2>
14
+ <p class="oasis-auth-sub">Don't have an account? <a class="oasis-link" (click)="switchTo.emit('signup')" href="javascript:void(0)">Sign Up</a></p>
15
+ </div>
16
+
17
+ @if (error()) {
18
+ <div class="oasis-auth-error">{{ error() }}</div>
19
+ }
20
+
21
+ <div class="oasis-field">
22
+ <label class="oasis-label" for="oasis-login-username">Username</label>
23
+ <input class="oasis-input" id="oasis-login-username" type="text" [(ngModel)]="username" placeholder="yourusername" autocomplete="username" required />
24
+ </div>
25
+
26
+ <div class="oasis-field">
27
+ <label class="oasis-label" for="oasis-login-password">Password</label>
28
+ <div class="oasis-input-wrap">
29
+ <input class="oasis-input" id="oasis-login-password" [type]="showPwd ? 'text' : 'password'" [(ngModel)]="password" placeholder="••••••••" autocomplete="current-password" required />
30
+ <button class="oasis-pwd-toggle" type="button" (click)="showPwd = !showPwd" [attr.aria-label]="showPwd ? 'Hide password' : 'Show password'">
31
+ {{ showPwd ? '🙈' : '👁️' }}
32
+ </button>
33
+ </div>
34
+ <a class="oasis-link oasis-link--small" (click)="switchTo.emit('forgot')" href="javascript:void(0)">Forgot Password?</a>
35
+ </div>
36
+
37
+ <div class="oasis-field oasis-field--row">
38
+ <input class="oasis-checkbox" id="oasis-remember" type="checkbox" [(ngModel)]="rememberMe" />
39
+ <label class="oasis-label oasis-label--inline" for="oasis-remember">Remember Me</label>
40
+ </div>
41
+
42
+ <button class="oasis-btn" type="button" [disabled]="loading()" (click)="submit()">
43
+ {{ loading() ? 'Connecting…' : 'Beam In' }}
44
+ </button>
45
+ </div>
46
+ `,
47
+ styles: [`
48
+ .oasis-auth-form { display:flex; flex-direction:column; gap:20px; }
49
+ .oasis-auth-header { text-align:center; }
50
+ .oasis-auth-title { font-family:'Orbitron',sans-serif; font-size:22px; color:#fff; margin:0 0 6px; }
51
+ .oasis-auth-sub { font-size:13px; color:#7a9bbf; margin:0; }
52
+ .oasis-auth-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; }
53
+ .oasis-field { display:flex; flex-direction:column; gap:6px; }
54
+ .oasis-field--row { flex-direction:row; align-items:center; gap:10px; }
55
+ .oasis-label { font-size:12px; font-weight:600; letter-spacing:.06em; color:#7a9bbf; text-transform:uppercase; }
56
+ .oasis-label--inline { text-transform:none; font-size:13px; color:#a8bfd8; }
57
+ .oasis-input-wrap { position:relative; }
58
+ .oasis-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:14px; outline:none; box-sizing:border-box; transition:border-color .2s; }
59
+ .oasis-input:focus { border-color:rgba(0,200,255,.5); }
60
+ .oasis-input-wrap .oasis-input { padding-right:44px; }
61
+ .oasis-pwd-toggle { position:absolute; right:10px; top:50%; transform:translateY(-50%); background:none; border:none; cursor:pointer; font-size:16px; padding:0; line-height:1; }
62
+ .oasis-checkbox { width:16px; height:16px; accent-color:#00c8ff; cursor:pointer; flex-shrink:0; }
63
+ .oasis-link { color:#00c8ff; text-decoration:none; cursor:pointer; }
64
+ .oasis-link--small { font-size:12px; margin-top:2px; }
65
+ .oasis-btn { background:linear-gradient(135deg,#00c8ff,#0080ff); border:none; border-radius:8px; color:#fff; font-family:'Orbitron',sans-serif; font-size:13px; font-weight:700; letter-spacing:.08em; padding:12px; cursor:pointer; transition:opacity .2s; }
66
+ .oasis-btn:disabled { opacity:.5; cursor:not-allowed; }
67
+ `]
68
+ })
69
+ export class LoginComponent {
70
+ @Output() switchTo = new EventEmitter<'signup' | 'forgot'>();
71
+ @Output() loggedIn = new EventEmitter<void>();
72
+
73
+ private oasis = inject(OasisService);
74
+
75
+ username = '';
76
+ password = '';
77
+ rememberMe = false;
78
+ showPwd = false;
79
+ loading = signal(false);
80
+ error = signal('');
81
+
82
+ async submit() {
83
+ if (!this.username || !this.password) { this.error.set('Please fill in all fields.'); return; }
84
+ this.loading.set(true);
85
+ this.error.set('');
86
+ try {
87
+ await this.oasis.login(this.username, this.password);
88
+ this.loggedIn.emit();
89
+ } catch (e: any) {
90
+ this.error.set(e?.message ?? 'Login failed. Please check your credentials.');
91
+ } finally {
92
+ this.loading.set(false);
93
+ }
94
+ }
95
+ }
@@ -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-2d-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 2D 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">Layer</label><input class="mf-inp" placeholder="Layer..." /></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 Add2dObjectMapComponent {
39
+ @Input() show = false;
40
+ @Output() close = new EventEmitter<void>();
41
+ save() { this.close.emit(); }
42
+ }