@oasisomniverse/react 1.0.0 â 1.1.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.
- package/README.md +68 -53
- package/package.json +72 -72
- package/src/components/common/AvatarConnect.jsx +89 -89
- package/src/components/common/Contact.jsx +60 -60
- package/src/components/common/Eggs.jsx +50 -50
- package/src/components/common/Game.jsx +36 -36
- package/src/components/common/HyperDrive.jsx +50 -50
- package/src/components/common/KarmaToast.jsx +38 -38
- package/src/components/common/Map.jsx +42 -42
- package/src/components/common/MenuMessage.jsx +70 -70
- package/src/components/common/Messaging.jsx +74 -74
- package/src/components/common/Mission.jsx +40 -40
- package/src/components/common/NFT.jsx +42 -42
- package/src/components/common/NavBar.jsx +52 -52
- package/src/components/common/OApp.jsx +41 -41
- package/src/components/common/ONET.jsx +50 -50
- package/src/components/common/ONODE.jsx +49 -49
- package/src/components/common/OasisModal.css +9 -9
- package/src/components/common/OasisModal.jsx +29 -29
- package/src/components/common/ProviderDropdown.jsx +61 -61
- package/src/components/common/Providers.jsx +38 -38
- package/src/components/common/Quest.jsx +40 -40
- package/src/components/common/SearchAvatars.jsx +49 -49
- package/src/components/common/Seeds.jsx +39 -39
- package/src/components/common/Settings.jsx +70 -70
- package/src/components/common/StarField.jsx +56 -56
- package/src/components/common/Wallet.jsx +45 -45
- package/src/components/index.js +26 -26
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const RARITY_COLORS = { Common: '#7a9bbf', Rare: '#5ba8ff', Epic: '#b87fff', Legendary: '#ffb43c' };
|
|
4
|
-
const INITIAL = [
|
|
5
|
-
{ id: '1', name: 'Cosmic Egg', rarity: 'Legendary', hatchProgress: 75, hatched: false, reward: '+500 Karma Companion' },
|
|
6
|
-
{ id: '2', name: 'Forest Egg', rarity: 'Common', hatchProgress: 100, hatched: true, reward: 'Woodland Sprite' },
|
|
7
|
-
{ id: '3', name: 'Astral Egg', rarity: 'Epic', hatchProgress: 30, hatched: false, reward: '+200 Karma Companion' },
|
|
8
|
-
{ id: '4', name: 'Ocean Egg', rarity: 'Rare', hatchProgress: 55, hatched: false, reward: 'Sea Guardian' },
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
export function Eggs() {
|
|
12
|
-
const [eggs, setEggs] = useState(INITIAL);
|
|
13
|
-
function incubate(id) {
|
|
14
|
-
setEggs(es => es.map(e => {
|
|
15
|
-
if (e.id !== id) return e;
|
|
16
|
-
const next = Math.min(100, e.hatchProgress + 25);
|
|
17
|
-
return { ...e, hatchProgress: next, hatched: next >= 100 };
|
|
18
|
-
}));
|
|
19
|
-
}
|
|
20
|
-
return (
|
|
21
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
|
22
|
-
<div style={{ textAlign: 'center' }}>
|
|
23
|
-
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>đĨ Eggs</h2>
|
|
24
|
-
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Hatch eggs to discover rare OASIS companions and rewards.</p>
|
|
25
|
-
</div>
|
|
26
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(160px,1fr))', gap: 16 }}>
|
|
27
|
-
{eggs.map(e => (
|
|
28
|
-
<div key={e.id} style={{ background: e.hatched ? 'rgba(255,180,60,.04)' : 'rgba(255,255,255,.04)', border: `1px solid ${e.hatched ? 'rgba(255,180,60,.3)' : 'rgba(0,200,255,.15)'}`, borderRadius: 12, padding: '18px 14px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
|
|
29
|
-
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', borderRadius: 999, padding: '3px 10px', border: `1px solid ${RARITY_COLORS[e.rarity]}44`, color: RARITY_COLORS[e.rarity] }}>{e.rarity}</div>
|
|
30
|
-
<div style={{ fontSize: 40 }}>{e.hatched ? 'đŖ' : 'đĨ'}</div>
|
|
31
|
-
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#fff', textAlign: 'center' }}>{e.name}</div>
|
|
32
|
-
{!e.hatched ? (
|
|
33
|
-
<>
|
|
34
|
-
<div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center' }}>
|
|
35
|
-
<div style={{ width: '100%', height: 6, background: 'rgba(255,255,255,.1)', borderRadius: 999, overflow: 'hidden' }}>
|
|
36
|
-
<div style={{ height: '100%', width: `${e.hatchProgress}%`, background: 'linear-gradient(90deg,#00c8ff,#0080ff)', borderRadius: 999, transition: 'width .4s' }} />
|
|
37
|
-
</div>
|
|
38
|
-
<div style={{ fontSize: 11, color: '#7a9bbf' }}>{e.hatchProgress}%</div>
|
|
39
|
-
</div>
|
|
40
|
-
<button style={{ width: '100%', background: 'linear-gradient(135deg,#ffb43c,#ff8c00)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, letterSpacing: '.08em', padding: 8, cursor: 'pointer' }} onClick={() => incubate(e.id)}>Incubate</button>
|
|
41
|
-
</>
|
|
42
|
-
) : (
|
|
43
|
-
<div style={{ fontSize: 12, color: '#ffb43c', fontWeight: 600, textAlign: 'center' }}>⨠{e.reward}</div>
|
|
44
|
-
)}
|
|
45
|
-
</div>
|
|
46
|
-
))}
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const RARITY_COLORS = { Common: '#7a9bbf', Rare: '#5ba8ff', Epic: '#b87fff', Legendary: '#ffb43c' };
|
|
4
|
+
const INITIAL = [
|
|
5
|
+
{ id: '1', name: 'Cosmic Egg', rarity: 'Legendary', hatchProgress: 75, hatched: false, reward: '+500 Karma Companion' },
|
|
6
|
+
{ id: '2', name: 'Forest Egg', rarity: 'Common', hatchProgress: 100, hatched: true, reward: 'Woodland Sprite' },
|
|
7
|
+
{ id: '3', name: 'Astral Egg', rarity: 'Epic', hatchProgress: 30, hatched: false, reward: '+200 Karma Companion' },
|
|
8
|
+
{ id: '4', name: 'Ocean Egg', rarity: 'Rare', hatchProgress: 55, hatched: false, reward: 'Sea Guardian' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export function Eggs() {
|
|
12
|
+
const [eggs, setEggs] = useState(INITIAL);
|
|
13
|
+
function incubate(id) {
|
|
14
|
+
setEggs(es => es.map(e => {
|
|
15
|
+
if (e.id !== id) return e;
|
|
16
|
+
const next = Math.min(100, e.hatchProgress + 25);
|
|
17
|
+
return { ...e, hatchProgress: next, hatched: next >= 100 };
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
return (
|
|
21
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
|
22
|
+
<div style={{ textAlign: 'center' }}>
|
|
23
|
+
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>đĨ Eggs</h2>
|
|
24
|
+
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Hatch eggs to discover rare OASIS companions and rewards.</p>
|
|
25
|
+
</div>
|
|
26
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(160px,1fr))', gap: 16 }}>
|
|
27
|
+
{eggs.map(e => (
|
|
28
|
+
<div key={e.id} style={{ background: e.hatched ? 'rgba(255,180,60,.04)' : 'rgba(255,255,255,.04)', border: `1px solid ${e.hatched ? 'rgba(255,180,60,.3)' : 'rgba(0,200,255,.15)'}`, borderRadius: 12, padding: '18px 14px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
|
|
29
|
+
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', borderRadius: 999, padding: '3px 10px', border: `1px solid ${RARITY_COLORS[e.rarity]}44`, color: RARITY_COLORS[e.rarity] }}>{e.rarity}</div>
|
|
30
|
+
<div style={{ fontSize: 40 }}>{e.hatched ? 'đŖ' : 'đĨ'}</div>
|
|
31
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#fff', textAlign: 'center' }}>{e.name}</div>
|
|
32
|
+
{!e.hatched ? (
|
|
33
|
+
<>
|
|
34
|
+
<div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center' }}>
|
|
35
|
+
<div style={{ width: '100%', height: 6, background: 'rgba(255,255,255,.1)', borderRadius: 999, overflow: 'hidden' }}>
|
|
36
|
+
<div style={{ height: '100%', width: `${e.hatchProgress}%`, background: 'linear-gradient(90deg,#00c8ff,#0080ff)', borderRadius: 999, transition: 'width .4s' }} />
|
|
37
|
+
</div>
|
|
38
|
+
<div style={{ fontSize: 11, color: '#7a9bbf' }}>{e.hatchProgress}%</div>
|
|
39
|
+
</div>
|
|
40
|
+
<button style={{ width: '100%', background: 'linear-gradient(135deg,#ffb43c,#ff8c00)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, letterSpacing: '.08em', padding: 8, cursor: 'pointer' }} onClick={() => incubate(e.id)}>Incubate</button>
|
|
41
|
+
</>
|
|
42
|
+
) : (
|
|
43
|
+
<div style={{ fontSize: 12, color: '#ffb43c', fontWeight: 600, textAlign: 'center' }}>⨠{e.reward}</div>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const INITIAL = [
|
|
4
|
-
{ id: '1', name: 'Karma Wars', genre: 'Strategy', description: 'Battle other avatars for karma dominance in real-time.', players: 12450, karma: 50, icon: 'âī¸', playing: false },
|
|
5
|
-
{ id: '2', name: 'Seed Garden', genre: 'Simulation', description: 'Grow and cultivate a virtual garden to earn seeds and karma.', players: 8900, karma: 30, icon: 'đą', playing: false },
|
|
6
|
-
{ id: '3', name: 'OASIS Racer', genre: 'Racing', description: 'Race across virtual worlds and earn speed karma.', players: 5600, karma: 40, icon: 'đī¸', playing: false },
|
|
7
|
-
{ id: '4', name: 'NFT Quest', genre: 'RPG', description: 'An epic RPG adventure to discover legendary NFTs.', players: 22000, karma: 80, icon: 'đĄī¸', playing: false },
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
export function Game() {
|
|
11
|
-
const [games, setGames] = useState(INITIAL);
|
|
12
|
-
function toggle(id) { setGames(l => l.map(x => x.id === id ? { ...x, playing: !x.playing } : x)); }
|
|
13
|
-
return (
|
|
14
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
|
15
|
-
<div style={{ textAlign: 'center' }}>
|
|
16
|
-
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>đŽ Games</h2>
|
|
17
|
-
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Play games in the OASIS to earn karma and rewards.</p>
|
|
18
|
-
</div>
|
|
19
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(200px,1fr))', gap: 16 }}>
|
|
20
|
-
{games.map(g => (
|
|
21
|
-
<div key={g.id} style={{ background: g.playing ? 'rgba(0,200,255,.05)' : 'rgba(255,255,255,.04)', border: `1px solid ${g.playing ? 'rgba(0,200,255,.5)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: '18px 14px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
22
|
-
<div style={{ fontSize: 40 }}>{g.icon}</div>
|
|
23
|
-
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', color: '#5ba8ff' }}>{g.genre}</div>
|
|
24
|
-
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff' }}>{g.name}</div>
|
|
25
|
-
<div style={{ fontSize: 12, color: '#7a9bbf', lineHeight: 1.5, flex: 1 }}>{g.description}</div>
|
|
26
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
27
|
-
<div style={{ fontSize: 11, color: '#7a9bbf' }}>đĨ {g.players.toLocaleString()}</div>
|
|
28
|
-
<div style={{ fontSize: 11, color: '#48dc82', fontWeight: 600 }}>+{g.karma} karma/hr</div>
|
|
29
|
-
</div>
|
|
30
|
-
<button onClick={() => toggle(g.id)} style={{ background: g.playing ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: g.playing ? '1px solid rgba(255,80,80,.35)' : 'none', borderRadius: 8, color: g.playing ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: 9, cursor: 'pointer', marginTop: 4 }}>{g.playing ? 'âš Stop' : 'âļ Play'}</button>
|
|
31
|
-
</div>
|
|
32
|
-
))}
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const INITIAL = [
|
|
4
|
+
{ id: '1', name: 'Karma Wars', genre: 'Strategy', description: 'Battle other avatars for karma dominance in real-time.', players: 12450, karma: 50, icon: 'âī¸', playing: false },
|
|
5
|
+
{ id: '2', name: 'Seed Garden', genre: 'Simulation', description: 'Grow and cultivate a virtual garden to earn seeds and karma.', players: 8900, karma: 30, icon: 'đą', playing: false },
|
|
6
|
+
{ id: '3', name: 'OASIS Racer', genre: 'Racing', description: 'Race across virtual worlds and earn speed karma.', players: 5600, karma: 40, icon: 'đī¸', playing: false },
|
|
7
|
+
{ id: '4', name: 'NFT Quest', genre: 'RPG', description: 'An epic RPG adventure to discover legendary NFTs.', players: 22000, karma: 80, icon: 'đĄī¸', playing: false },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export function Game() {
|
|
11
|
+
const [games, setGames] = useState(INITIAL);
|
|
12
|
+
function toggle(id) { setGames(l => l.map(x => x.id === id ? { ...x, playing: !x.playing } : x)); }
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
|
15
|
+
<div style={{ textAlign: 'center' }}>
|
|
16
|
+
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>đŽ Games</h2>
|
|
17
|
+
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Play games in the OASIS to earn karma and rewards.</p>
|
|
18
|
+
</div>
|
|
19
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(200px,1fr))', gap: 16 }}>
|
|
20
|
+
{games.map(g => (
|
|
21
|
+
<div key={g.id} style={{ background: g.playing ? 'rgba(0,200,255,.05)' : 'rgba(255,255,255,.04)', border: `1px solid ${g.playing ? 'rgba(0,200,255,.5)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: '18px 14px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
22
|
+
<div style={{ fontSize: 40 }}>{g.icon}</div>
|
|
23
|
+
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', color: '#5ba8ff' }}>{g.genre}</div>
|
|
24
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff' }}>{g.name}</div>
|
|
25
|
+
<div style={{ fontSize: 12, color: '#7a9bbf', lineHeight: 1.5, flex: 1 }}>{g.description}</div>
|
|
26
|
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
27
|
+
<div style={{ fontSize: 11, color: '#7a9bbf' }}>đĨ {g.players.toLocaleString()}</div>
|
|
28
|
+
<div style={{ fontSize: 11, color: '#48dc82', fontWeight: 600 }}>+{g.karma} karma/hr</div>
|
|
29
|
+
</div>
|
|
30
|
+
<button onClick={() => toggle(g.id)} style={{ background: g.playing ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: g.playing ? '1px solid rgba(255,80,80,.35)' : 'none', borderRadius: 8, color: g.playing ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: 9, cursor: 'pointer', marginTop: 4 }}>{g.playing ? 'âš Stop' : 'âļ Play'}</button>
|
|
31
|
+
</div>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const METRICS = [{ label: 'Throughput', value: '4.2 TB/s' }, { label: 'Latency', value: '0.3ms' }, { label: 'Uptime', value: '99.98%' }];
|
|
4
|
-
const NODES = [
|
|
5
|
-
{ id: '1', name: 'ONODE Alpha â US East', status: 'active', latency: 12 },
|
|
6
|
-
{ id: '2', name: 'ONODE Beta â EU West', status: 'active', latency: 28 },
|
|
7
|
-
{ id: '3', name: 'ONODE Gamma â Asia Pacific', status: 'idle', latency: 55 },
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
export function HyperDrive() {
|
|
11
|
-
const [active, setActive] = useState(false);
|
|
12
|
-
return (
|
|
13
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
|
14
|
-
<div style={{ textAlign: 'center' }}>
|
|
15
|
-
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>đ HyperDrive</h2>
|
|
16
|
-
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Quantum-speed data layer for the OASIS Omniverse.</p>
|
|
17
|
-
</div>
|
|
18
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 14, background: active ? 'rgba(0,200,255,.05)' : 'rgba(255,255,255,.04)', border: `1px solid ${active ? 'rgba(0,200,255,.4)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: 16 }}>
|
|
19
|
-
<div style={{ fontSize: 28 }}>{active ? 'âĄ' : 'đ¤'}</div>
|
|
20
|
-
<div style={{ flex: 1 }}>
|
|
21
|
-
<div style={{ fontSize: 12, color: '#7a9bbf', textTransform: 'uppercase', letterSpacing: '.06em' }}>HyperDrive Status</div>
|
|
22
|
-
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 16, color: active ? '#00c8ff' : '#7a9bbf', marginTop: 2 }}>{active ? 'ACTIVE' : 'OFFLINE'}</div>
|
|
23
|
-
</div>
|
|
24
|
-
<button onClick={() => setActive(a => !a)} style={{ background: active ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: active ? '1px solid rgba(255,80,80,.35)' : 'none', borderRadius: 8, color: active ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: '10px 20px', cursor: 'pointer' }}>{active ? 'Disengage' : 'Engage'}</button>
|
|
25
|
-
</div>
|
|
26
|
-
{active && (
|
|
27
|
-
<>
|
|
28
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 12 }}>
|
|
29
|
-
{METRICS.map(m => (
|
|
30
|
-
<div key={m.label} style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.1)', borderRadius: 10, padding: 14, textAlign: 'center' }}>
|
|
31
|
-
<div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 20, color: '#00c8ff', fontWeight: 600 }}>{m.value}</div>
|
|
32
|
-
<div style={{ fontSize: 11, color: '#7a9bbf', marginTop: 4, textTransform: 'uppercase', letterSpacing: '.06em' }}>{m.label}</div>
|
|
33
|
-
</div>
|
|
34
|
-
))}
|
|
35
|
-
</div>
|
|
36
|
-
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#7a9bbf', letterSpacing: '.06em', textTransform: 'uppercase' }}>Connected Nodes</div>
|
|
37
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
38
|
-
{NODES.map(n => (
|
|
39
|
-
<div key={n.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'rgba(255,255,255,.03)', border: '1px solid rgba(0,200,255,.07)', borderRadius: 8 }}>
|
|
40
|
-
<div style={{ width: 8, height: 8, borderRadius: '50%', background: n.status === 'active' ? '#48dc82' : '#7a9bbf', boxShadow: n.status === 'active' ? '0 0 6px #48dc82' : undefined, flexShrink: 0 }} />
|
|
41
|
-
<div style={{ flex: 1, fontSize: 13, color: '#e0f0ff' }}>{n.name}</div>
|
|
42
|
-
<div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 12, color: '#7a9bbf' }}>{n.latency}ms</div>
|
|
43
|
-
</div>
|
|
44
|
-
))}
|
|
45
|
-
</div>
|
|
46
|
-
</>
|
|
47
|
-
)}
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const METRICS = [{ label: 'Throughput', value: '4.2 TB/s' }, { label: 'Latency', value: '0.3ms' }, { label: 'Uptime', value: '99.98%' }];
|
|
4
|
+
const NODES = [
|
|
5
|
+
{ id: '1', name: 'ONODE Alpha â US East', status: 'active', latency: 12 },
|
|
6
|
+
{ id: '2', name: 'ONODE Beta â EU West', status: 'active', latency: 28 },
|
|
7
|
+
{ id: '3', name: 'ONODE Gamma â Asia Pacific', status: 'idle', latency: 55 },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export function HyperDrive() {
|
|
11
|
+
const [active, setActive] = useState(false);
|
|
12
|
+
return (
|
|
13
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
|
14
|
+
<div style={{ textAlign: 'center' }}>
|
|
15
|
+
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>đ HyperDrive</h2>
|
|
16
|
+
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Quantum-speed data layer for the OASIS Omniverse.</p>
|
|
17
|
+
</div>
|
|
18
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 14, background: active ? 'rgba(0,200,255,.05)' : 'rgba(255,255,255,.04)', border: `1px solid ${active ? 'rgba(0,200,255,.4)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: 16 }}>
|
|
19
|
+
<div style={{ fontSize: 28 }}>{active ? 'âĄ' : 'đ¤'}</div>
|
|
20
|
+
<div style={{ flex: 1 }}>
|
|
21
|
+
<div style={{ fontSize: 12, color: '#7a9bbf', textTransform: 'uppercase', letterSpacing: '.06em' }}>HyperDrive Status</div>
|
|
22
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 16, color: active ? '#00c8ff' : '#7a9bbf', marginTop: 2 }}>{active ? 'ACTIVE' : 'OFFLINE'}</div>
|
|
23
|
+
</div>
|
|
24
|
+
<button onClick={() => setActive(a => !a)} style={{ background: active ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: active ? '1px solid rgba(255,80,80,.35)' : 'none', borderRadius: 8, color: active ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: '10px 20px', cursor: 'pointer' }}>{active ? 'Disengage' : 'Engage'}</button>
|
|
25
|
+
</div>
|
|
26
|
+
{active && (
|
|
27
|
+
<>
|
|
28
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 12 }}>
|
|
29
|
+
{METRICS.map(m => (
|
|
30
|
+
<div key={m.label} style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.1)', borderRadius: 10, padding: 14, textAlign: 'center' }}>
|
|
31
|
+
<div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 20, color: '#00c8ff', fontWeight: 600 }}>{m.value}</div>
|
|
32
|
+
<div style={{ fontSize: 11, color: '#7a9bbf', marginTop: 4, textTransform: 'uppercase', letterSpacing: '.06em' }}>{m.label}</div>
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#7a9bbf', letterSpacing: '.06em', textTransform: 'uppercase' }}>Connected Nodes</div>
|
|
37
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
38
|
+
{NODES.map(n => (
|
|
39
|
+
<div key={n.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'rgba(255,255,255,.03)', border: '1px solid rgba(0,200,255,.07)', borderRadius: 8 }}>
|
|
40
|
+
<div style={{ width: 8, height: 8, borderRadius: '50%', background: n.status === 'active' ? '#48dc82' : '#7a9bbf', boxShadow: n.status === 'active' ? '0 0 6px #48dc82' : undefined, flexShrink: 0 }} />
|
|
41
|
+
<div style={{ flex: 1, fontSize: 13, color: '#e0f0ff' }}>{n.name}</div>
|
|
42
|
+
<div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 12, color: '#7a9bbf' }}>{n.latency}ms</div>
|
|
43
|
+
</div>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
</>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import React, { useState, useCallback } from 'react';
|
|
2
|
-
|
|
3
|
-
let _show = null;
|
|
4
|
-
|
|
5
|
-
export function showKarmaToast({ message, amount, duration = 4000 }) {
|
|
6
|
-
_show?.({ message, amount, duration });
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export default function KarmaToast() {
|
|
10
|
-
const [toast, setToast] = useState(null);
|
|
11
|
-
const timerRef = React.useRef(null);
|
|
12
|
-
|
|
13
|
-
_show = useCallback(({ message, amount, duration }) => {
|
|
14
|
-
clearTimeout(timerRef.current);
|
|
15
|
-
setToast({ message, amount });
|
|
16
|
-
timerRef.current = setTimeout(() => setToast(null), duration);
|
|
17
|
-
}, []);
|
|
18
|
-
|
|
19
|
-
if (!toast) return null;
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<div style={{
|
|
23
|
-
position: 'fixed', bottom: 28, right: 28, zIndex: 10000,
|
|
24
|
-
display: 'flex', alignItems: 'center', gap: 12,
|
|
25
|
-
background: '#0d1829', border: '1px solid rgba(0,200,255,.25)',
|
|
26
|
-
borderRadius: 12, padding: '14px 18px', boxShadow: '0 8px 32px rgba(0,0,0,.5)',
|
|
27
|
-
animation: 'oasisSlideUp .35s ease',
|
|
28
|
-
}}>
|
|
29
|
-
<span style={{ fontSize: 22 }}>âĄ</span>
|
|
30
|
-
<div>
|
|
31
|
-
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 15, fontWeight: 700, color: '#00c8ff' }}>
|
|
32
|
-
+{toast.amount} Karma
|
|
33
|
-
</div>
|
|
34
|
-
<div style={{ fontSize: 12, color: '#7a9bbf', marginTop: 2 }}>{toast.message}</div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
let _show = null;
|
|
4
|
+
|
|
5
|
+
export function showKarmaToast({ message, amount, duration = 4000 }) {
|
|
6
|
+
_show?.({ message, amount, duration });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function KarmaToast() {
|
|
10
|
+
const [toast, setToast] = useState(null);
|
|
11
|
+
const timerRef = React.useRef(null);
|
|
12
|
+
|
|
13
|
+
_show = useCallback(({ message, amount, duration }) => {
|
|
14
|
+
clearTimeout(timerRef.current);
|
|
15
|
+
setToast({ message, amount });
|
|
16
|
+
timerRef.current = setTimeout(() => setToast(null), duration);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
if (!toast) return null;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div style={{
|
|
23
|
+
position: 'fixed', bottom: 28, right: 28, zIndex: 10000,
|
|
24
|
+
display: 'flex', alignItems: 'center', gap: 12,
|
|
25
|
+
background: '#0d1829', border: '1px solid rgba(0,200,255,.25)',
|
|
26
|
+
borderRadius: 12, padding: '14px 18px', boxShadow: '0 8px 32px rgba(0,0,0,.5)',
|
|
27
|
+
animation: 'oasisSlideUp .35s ease',
|
|
28
|
+
}}>
|
|
29
|
+
<span style={{ fontSize: 22 }}>âĄ</span>
|
|
30
|
+
<div>
|
|
31
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 15, fontWeight: 700, color: '#00c8ff' }}>
|
|
32
|
+
+{toast.amount} Karma
|
|
33
|
+
</div>
|
|
34
|
+
<div style={{ fontSize: 12, color: '#7a9bbf', marginTop: 2 }}>{toast.message}</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const LOC_ICONS = { Hub: 'đī¸', City: 'đī¸', Nature: 'đ˛', 'Quest Zone': 'âī¸' };
|
|
4
|
-
const LOCATIONS = [
|
|
5
|
-
{ id: '1', name: 'OASIS Hub Alpha', type: 'Hub', x: 20, y: 30, description: 'Central hub for OASIS operations and avatar registration.' },
|
|
6
|
-
{ id: '2', name: 'Neon City', type: 'City', x: 55, y: 20, description: 'A sprawling cyberpunk metropolis in the OASIS.' },
|
|
7
|
-
{ id: '3', name: 'Enchanted Forest', type: 'Nature', x: 75, y: 60, description: 'Ancient woodland teeming with rare seeds and creatures.' },
|
|
8
|
-
{ id: '4', name: 'Battle Grounds', type: 'Quest Zone', x: 35, y: 70, description: 'Arena for quests, challenges, and PvP competitions.' },
|
|
9
|
-
{ id: '5', name: 'Crystal Spire', type: 'Hub', x: 85, y: 35, description: 'Trading post and NFT marketplace in the sky.' },
|
|
10
|
-
];
|
|
11
|
-
const LEGEND = [{ icon: 'đī¸', label: 'City' }, { icon: 'đ˛', label: 'Nature' }, { icon: 'âī¸', label: 'Quest Zone' }, { icon: 'đī¸', label: 'Hub' }];
|
|
12
|
-
|
|
13
|
-
export function Map() {
|
|
14
|
-
const [selected, setSelected] = useState(null);
|
|
15
|
-
return (
|
|
16
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
17
|
-
<div style={{ textAlign: 'center' }}>
|
|
18
|
-
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>đēī¸ OASIS Map</h2>
|
|
19
|
-
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Explore the OASIS Omniverse.</p>
|
|
20
|
-
</div>
|
|
21
|
-
<div style={{ position: 'relative', height: 300, background: '#030d1a', border: '1px solid rgba(0,200,255,.2)', borderRadius: 12, overflow: 'hidden' }}>
|
|
22
|
-
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(0,200,255,.05) 1px,transparent 1px),linear-gradient(90deg,rgba(0,200,255,.05) 1px,transparent 1px)', backgroundSize: '40px 40px' }} />
|
|
23
|
-
{LOCATIONS.map(loc => (
|
|
24
|
-
<div key={loc.id} onClick={() => setSelected(loc)} title={loc.name} style={{ position: 'absolute', left: `${loc.x}%`, top: `${loc.y}%`, transform: 'translate(-50%,-50%)', cursor: 'pointer', fontSize: 22, outline: selected?.id === loc.id ? '2px solid #00c8ff' : undefined, outlineOffset: selected?.id === loc.id ? 3 : undefined, borderRadius: '50%' }}>
|
|
25
|
-
{LOC_ICONS[loc.type] ?? 'đ'}
|
|
26
|
-
</div>
|
|
27
|
-
))}
|
|
28
|
-
</div>
|
|
29
|
-
{selected && (
|
|
30
|
-
<div style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 10, padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
31
|
-
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff' }}>{selected.name}</div>
|
|
32
|
-
<div style={{ fontSize: 11, color: '#5ba8ff', textTransform: 'uppercase', letterSpacing: '.06em' }}>{selected.type}</div>
|
|
33
|
-
<div style={{ fontSize: 12, color: '#7a9bbf', flex: 1 }}>{selected.description}</div>
|
|
34
|
-
<button onClick={() => setSelected(null)} style={{ background: 'transparent', border: '1px solid rgba(0,200,255,.3)', borderRadius: 7, color: '#00c8ff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '6px 14px', cursor: 'pointer' }}>Close</button>
|
|
35
|
-
</div>
|
|
36
|
-
)}
|
|
37
|
-
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
|
38
|
-
{LEGEND.map(t => <div key={t.label} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: '#7a9bbf' }}><span>{t.icon}</span><span>{t.label}</span></div>)}
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const LOC_ICONS = { Hub: 'đī¸', City: 'đī¸', Nature: 'đ˛', 'Quest Zone': 'âī¸' };
|
|
4
|
+
const LOCATIONS = [
|
|
5
|
+
{ id: '1', name: 'OASIS Hub Alpha', type: 'Hub', x: 20, y: 30, description: 'Central hub for OASIS operations and avatar registration.' },
|
|
6
|
+
{ id: '2', name: 'Neon City', type: 'City', x: 55, y: 20, description: 'A sprawling cyberpunk metropolis in the OASIS.' },
|
|
7
|
+
{ id: '3', name: 'Enchanted Forest', type: 'Nature', x: 75, y: 60, description: 'Ancient woodland teeming with rare seeds and creatures.' },
|
|
8
|
+
{ id: '4', name: 'Battle Grounds', type: 'Quest Zone', x: 35, y: 70, description: 'Arena for quests, challenges, and PvP competitions.' },
|
|
9
|
+
{ id: '5', name: 'Crystal Spire', type: 'Hub', x: 85, y: 35, description: 'Trading post and NFT marketplace in the sky.' },
|
|
10
|
+
];
|
|
11
|
+
const LEGEND = [{ icon: 'đī¸', label: 'City' }, { icon: 'đ˛', label: 'Nature' }, { icon: 'âī¸', label: 'Quest Zone' }, { icon: 'đī¸', label: 'Hub' }];
|
|
12
|
+
|
|
13
|
+
export function Map() {
|
|
14
|
+
const [selected, setSelected] = useState(null);
|
|
15
|
+
return (
|
|
16
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
17
|
+
<div style={{ textAlign: 'center' }}>
|
|
18
|
+
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>đēī¸ OASIS Map</h2>
|
|
19
|
+
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Explore the OASIS Omniverse.</p>
|
|
20
|
+
</div>
|
|
21
|
+
<div style={{ position: 'relative', height: 300, background: '#030d1a', border: '1px solid rgba(0,200,255,.2)', borderRadius: 12, overflow: 'hidden' }}>
|
|
22
|
+
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(0,200,255,.05) 1px,transparent 1px),linear-gradient(90deg,rgba(0,200,255,.05) 1px,transparent 1px)', backgroundSize: '40px 40px' }} />
|
|
23
|
+
{LOCATIONS.map(loc => (
|
|
24
|
+
<div key={loc.id} onClick={() => setSelected(loc)} title={loc.name} style={{ position: 'absolute', left: `${loc.x}%`, top: `${loc.y}%`, transform: 'translate(-50%,-50%)', cursor: 'pointer', fontSize: 22, outline: selected?.id === loc.id ? '2px solid #00c8ff' : undefined, outlineOffset: selected?.id === loc.id ? 3 : undefined, borderRadius: '50%' }}>
|
|
25
|
+
{LOC_ICONS[loc.type] ?? 'đ'}
|
|
26
|
+
</div>
|
|
27
|
+
))}
|
|
28
|
+
</div>
|
|
29
|
+
{selected && (
|
|
30
|
+
<div style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 10, padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
31
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff' }}>{selected.name}</div>
|
|
32
|
+
<div style={{ fontSize: 11, color: '#5ba8ff', textTransform: 'uppercase', letterSpacing: '.06em' }}>{selected.type}</div>
|
|
33
|
+
<div style={{ fontSize: 12, color: '#7a9bbf', flex: 1 }}>{selected.description}</div>
|
|
34
|
+
<button onClick={() => setSelected(null)} style={{ background: 'transparent', border: '1px solid rgba(0,200,255,.3)', borderRadius: 7, color: '#00c8ff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '6px 14px', cursor: 'pointer' }}>Close</button>
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
|
38
|
+
{LEGEND.map(t => <div key={t.label} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: '#7a9bbf' }}><span>{t.icon}</span><span>{t.label}</span></div>)}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const styles = {
|
|
4
|
-
shell: { fontFamily: "'Rajdhani', sans-serif", display: 'flex', flexDirection: 'column', gap: 18 },
|
|
5
|
-
header: { padding: '14px 18px', background: 'rgba(0,200,255,.07)', borderBottom: '1px solid rgba(0,200,255,.12)', display: 'flex', alignItems: 'center', gap: 12 },
|
|
6
|
-
title: { fontFamily: "'Orbitron', sans-serif", fontSize: 15, fontWeight: 700, color: '#fff', margin: 0, flex: 1 },
|
|
7
|
-
closebtn: { background: 'none', border: 'none', color: '#7a9bbf', fontSize: 18, cursor: 'pointer', lineHeight: 1 },
|
|
8
|
-
body: { padding: '18px', display: 'flex', flexDirection: 'column', gap: 12 },
|
|
9
|
-
icon: { fontSize: 44, textAlign: 'center' },
|
|
10
|
-
msg: { fontSize: 14, color: '#a8bfd8', lineHeight: 1.65, textAlign: 'center' },
|
|
11
|
-
actions: { display: 'flex', gap: 10, justifyContent: 'center', marginTop: 4 },
|
|
12
|
-
btn: { background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 8, color: '#fff', fontFamily: "'Orbitron', sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: '9px 22px', cursor: 'pointer' },
|
|
13
|
-
btnGhost: { background: 'transparent', border: '1px solid rgba(0,200,255,.3)', borderRadius: 8, color: '#00c8ff', fontFamily: "'Orbitron', sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: '9px 22px', cursor: 'pointer' },
|
|
14
|
-
badge: { display: 'inline-block', background: 'rgba(0,200,255,.12)', border: '1px solid rgba(0,200,255,.3)', color: '#00c8ff', borderRadius: 999, fontSize: 11, fontWeight: 700, letterSpacing: '.08em', padding: '3px 12px', textTransform: 'uppercase', textAlign: 'center' },
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* MenuMessage â a dismissable banner/message panel for menu-level notices.
|
|
19
|
-
* Props:
|
|
20
|
-
* title string â header title
|
|
21
|
-
* message string â body copy
|
|
22
|
-
* icon string â emoji icon
|
|
23
|
-
* type 'info' | 'success' | 'warning' | 'error'
|
|
24
|
-
* badge string â optional badge text
|
|
25
|
-
* actions Array<{ label, onClick }> â action buttons
|
|
26
|
-
* onDismiss fn â called when closed
|
|
27
|
-
*/
|
|
28
|
-
export function MenuMessage({
|
|
29
|
-
title = 'Notice',
|
|
30
|
-
message = '',
|
|
31
|
-
icon = 'âšī¸',
|
|
32
|
-
type = 'info',
|
|
33
|
-
badge,
|
|
34
|
-
actions = [],
|
|
35
|
-
onDismiss,
|
|
36
|
-
}) {
|
|
37
|
-
const [visible, setVisible] = useState(true);
|
|
38
|
-
if (!visible) return null;
|
|
39
|
-
|
|
40
|
-
const typeColors = {
|
|
41
|
-
info: { border: 'rgba(0,200,255,.25)', bg: 'rgba(0,200,255,.05)' },
|
|
42
|
-
success: { border: 'rgba(72,220,130,.25)', bg: 'rgba(72,220,130,.05)' },
|
|
43
|
-
warning: { border: 'rgba(255,180,60,.25)', bg: 'rgba(255,180,60,.05)' },
|
|
44
|
-
error: { border: 'rgba(255,80,80,.25)', bg: 'rgba(255,80,80,.05)' },
|
|
45
|
-
};
|
|
46
|
-
const colors = typeColors[type] || typeColors.info;
|
|
47
|
-
|
|
48
|
-
function dismiss() { setVisible(false); onDismiss?.(); }
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div style={{ ...styles.shell, border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.bg, overflow: 'hidden' }}>
|
|
52
|
-
<div style={styles.header}>
|
|
53
|
-
<span style={{ fontSize: 20 }}>{icon}</span>
|
|
54
|
-
<span style={styles.title}>{title}</span>
|
|
55
|
-
{badge && <span style={styles.badge}>{badge}</span>}
|
|
56
|
-
<button style={styles.closebtn} onClick={dismiss} aria-label="Dismiss">â</button>
|
|
57
|
-
</div>
|
|
58
|
-
<div style={styles.body}>
|
|
59
|
-
{message && <p style={styles.msg}>{message}</p>}
|
|
60
|
-
{actions.length > 0 && (
|
|
61
|
-
<div style={styles.actions}>
|
|
62
|
-
{actions.map((a, i) => (
|
|
63
|
-
<button key={i} style={i === 0 ? styles.btn : styles.btnGhost} onClick={a.onClick}>{a.label}</button>
|
|
64
|
-
))}
|
|
65
|
-
</div>
|
|
66
|
-
)}
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const styles = {
|
|
4
|
+
shell: { fontFamily: "'Rajdhani', sans-serif", display: 'flex', flexDirection: 'column', gap: 18 },
|
|
5
|
+
header: { padding: '14px 18px', background: 'rgba(0,200,255,.07)', borderBottom: '1px solid rgba(0,200,255,.12)', display: 'flex', alignItems: 'center', gap: 12 },
|
|
6
|
+
title: { fontFamily: "'Orbitron', sans-serif", fontSize: 15, fontWeight: 700, color: '#fff', margin: 0, flex: 1 },
|
|
7
|
+
closebtn: { background: 'none', border: 'none', color: '#7a9bbf', fontSize: 18, cursor: 'pointer', lineHeight: 1 },
|
|
8
|
+
body: { padding: '18px', display: 'flex', flexDirection: 'column', gap: 12 },
|
|
9
|
+
icon: { fontSize: 44, textAlign: 'center' },
|
|
10
|
+
msg: { fontSize: 14, color: '#a8bfd8', lineHeight: 1.65, textAlign: 'center' },
|
|
11
|
+
actions: { display: 'flex', gap: 10, justifyContent: 'center', marginTop: 4 },
|
|
12
|
+
btn: { background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 8, color: '#fff', fontFamily: "'Orbitron', sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: '9px 22px', cursor: 'pointer' },
|
|
13
|
+
btnGhost: { background: 'transparent', border: '1px solid rgba(0,200,255,.3)', borderRadius: 8, color: '#00c8ff', fontFamily: "'Orbitron', sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: '9px 22px', cursor: 'pointer' },
|
|
14
|
+
badge: { display: 'inline-block', background: 'rgba(0,200,255,.12)', border: '1px solid rgba(0,200,255,.3)', color: '#00c8ff', borderRadius: 999, fontSize: 11, fontWeight: 700, letterSpacing: '.08em', padding: '3px 12px', textTransform: 'uppercase', textAlign: 'center' },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* MenuMessage â a dismissable banner/message panel for menu-level notices.
|
|
19
|
+
* Props:
|
|
20
|
+
* title string â header title
|
|
21
|
+
* message string â body copy
|
|
22
|
+
* icon string â emoji icon
|
|
23
|
+
* type 'info' | 'success' | 'warning' | 'error'
|
|
24
|
+
* badge string â optional badge text
|
|
25
|
+
* actions Array<{ label, onClick }> â action buttons
|
|
26
|
+
* onDismiss fn â called when closed
|
|
27
|
+
*/
|
|
28
|
+
export function MenuMessage({
|
|
29
|
+
title = 'Notice',
|
|
30
|
+
message = '',
|
|
31
|
+
icon = 'âšī¸',
|
|
32
|
+
type = 'info',
|
|
33
|
+
badge,
|
|
34
|
+
actions = [],
|
|
35
|
+
onDismiss,
|
|
36
|
+
}) {
|
|
37
|
+
const [visible, setVisible] = useState(true);
|
|
38
|
+
if (!visible) return null;
|
|
39
|
+
|
|
40
|
+
const typeColors = {
|
|
41
|
+
info: { border: 'rgba(0,200,255,.25)', bg: 'rgba(0,200,255,.05)' },
|
|
42
|
+
success: { border: 'rgba(72,220,130,.25)', bg: 'rgba(72,220,130,.05)' },
|
|
43
|
+
warning: { border: 'rgba(255,180,60,.25)', bg: 'rgba(255,180,60,.05)' },
|
|
44
|
+
error: { border: 'rgba(255,80,80,.25)', bg: 'rgba(255,80,80,.05)' },
|
|
45
|
+
};
|
|
46
|
+
const colors = typeColors[type] || typeColors.info;
|
|
47
|
+
|
|
48
|
+
function dismiss() { setVisible(false); onDismiss?.(); }
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div style={{ ...styles.shell, border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.bg, overflow: 'hidden' }}>
|
|
52
|
+
<div style={styles.header}>
|
|
53
|
+
<span style={{ fontSize: 20 }}>{icon}</span>
|
|
54
|
+
<span style={styles.title}>{title}</span>
|
|
55
|
+
{badge && <span style={styles.badge}>{badge}</span>}
|
|
56
|
+
<button style={styles.closebtn} onClick={dismiss} aria-label="Dismiss">â</button>
|
|
57
|
+
</div>
|
|
58
|
+
<div style={styles.body}>
|
|
59
|
+
{message && <p style={styles.msg}>{message}</p>}
|
|
60
|
+
{actions.length > 0 && (
|
|
61
|
+
<div style={styles.actions}>
|
|
62
|
+
{actions.map((a, i) => (
|
|
63
|
+
<button key={i} style={i === 0 ? styles.btn : styles.btnGhost} onClick={a.onClick}>{a.label}</button>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|