@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.
@@ -1,74 +1,74 @@
1
- import { useState } from 'react';
2
-
3
- const s = {
4
- shell: { display: 'grid', gridTemplateColumns: '260px 1fr', height: 480, border: '1px solid rgba(0,200,255,.15)', borderRadius: 12, overflow: 'hidden' },
5
- sidebar: { borderRight: '1px solid rgba(0,200,255,.1)', display: 'flex', flexDirection: 'column' },
6
- searchWrap: { padding: 12, borderBottom: '1px solid rgba(0,200,255,.1)' },
7
- search: { width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '8px 12px', color: '#fff', fontSize: 13, outline: 'none', boxSizing: 'border-box' },
8
- list: { flex: 1, overflowY: 'auto' },
9
- item: { padding: '12px 14px', borderBottom: '1px solid rgba(0,200,255,.07)', cursor: 'pointer' },
10
- from: { fontSize: 13, color: '#e0f0ff', marginBottom: 3 },
11
- preview: { fontSize: 12, color: '#7a9bbf', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
12
- time: { fontSize: 11, color: '#4a6a88', marginTop: 4 },
13
- main: { display: 'flex', flexDirection: 'column', background: 'rgba(0,0,0,.2)' },
14
- placeholder: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1, color: '#4a6a88', fontSize: 14 },
15
- detailHeader: { display: 'flex', justifyContent: 'space-between', padding: '16px 20px', borderBottom: '1px solid rgba(0,200,255,.1)' },
16
- detailFrom: { fontFamily: "'Orbitron',sans-serif", fontSize: 14, color: '#fff' },
17
- detailTime: { fontSize: 12, color: '#4a6a88' },
18
- body: { flex: 1, padding: 20, fontSize: 14, color: '#a8bfd8', lineHeight: 1.7, overflowY: 'auto' },
19
- replyWrap: { padding: 16, borderTop: '1px solid rgba(0,200,255,.1)', display: 'flex', flexDirection: 'column', gap: 10 },
20
- replyInput: { width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '10px 14px', color: '#fff', fontSize: 13, outline: 'none', resize: 'none', boxSizing: 'border-box', fontFamily: 'inherit' },
21
- btn: { alignSelf: 'flex-end', background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 8, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, padding: '9px 24px', cursor: 'pointer' },
22
- empty: { padding: 24, textAlign: 'center', color: '#4a6a88', fontSize: 13 },
23
- };
24
-
25
- const INITIAL = [
26
- { 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.' },
27
- { 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.' },
28
- ];
29
-
30
- export function Messaging() {
31
- const [messages, setMessages] = useState(INITIAL);
32
- const [selected, setSelected] = useState(null);
33
- const [search, setSearch] = useState('');
34
- const [reply, setReply] = useState('');
35
-
36
- const filtered = messages.filter(m => m.from.toLowerCase().includes(search.toLowerCase()) || m.preview.toLowerCase().includes(search.toLowerCase()));
37
-
38
- function open(m) {
39
- setMessages(ms => ms.map(x => x.id === m.id ? { ...x, read: true } : x));
40
- setSelected(m); setReply('');
41
- }
42
-
43
- return (
44
- <div style={s.shell}>
45
- <div style={s.sidebar}>
46
- <div style={s.searchWrap}><input style={s.search} value={search} onChange={e => setSearch(e.target.value)} placeholder="Search messages…" /></div>
47
- <div style={s.list}>
48
- {filtered.map(m => (
49
- <div key={m.id} style={{ ...s.item, background: selected?.id === m.id ? 'rgba(0,200,255,.1)' : undefined, borderLeft: selected?.id === m.id ? '3px solid #00c8ff' : undefined }} onClick={() => open(m)}>
50
- <div style={{ ...s.from, fontWeight: m.read ? undefined : 700 }}>{m.from}</div>
51
- <div style={s.preview}>{m.preview}</div>
52
- <div style={s.time}>{m.time}</div>
53
- </div>
54
- ))}
55
- {filtered.length === 0 && <div style={s.empty}>No messages found.</div>}
56
- </div>
57
- </div>
58
- <div style={s.main}>
59
- {selected ? (
60
- <>
61
- <div style={s.detailHeader}><div style={s.detailFrom}>{selected.from}</div><div style={s.detailTime}>{selected.time}</div></div>
62
- <div style={s.body}>{selected.body}</div>
63
- <div style={s.replyWrap}>
64
- <textarea style={s.replyInput} value={reply} onChange={e => setReply(e.target.value)} placeholder="Write a reply…" rows={3} />
65
- <button style={{ ...s.btn, opacity: reply.trim() ? 1 : .4 }} disabled={!reply.trim()} onClick={() => setReply('')}>Send</button>
66
- </div>
67
- </>
68
- ) : (
69
- <div style={s.placeholder}>📬 Select a message to read it</div>
70
- )}
71
- </div>
72
- </div>
73
- );
74
- }
1
+ import { useState } from 'react';
2
+
3
+ const s = {
4
+ shell: { display: 'grid', gridTemplateColumns: '260px 1fr', height: 480, border: '1px solid rgba(0,200,255,.15)', borderRadius: 12, overflow: 'hidden' },
5
+ sidebar: { borderRight: '1px solid rgba(0,200,255,.1)', display: 'flex', flexDirection: 'column' },
6
+ searchWrap: { padding: 12, borderBottom: '1px solid rgba(0,200,255,.1)' },
7
+ search: { width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '8px 12px', color: '#fff', fontSize: 13, outline: 'none', boxSizing: 'border-box' },
8
+ list: { flex: 1, overflowY: 'auto' },
9
+ item: { padding: '12px 14px', borderBottom: '1px solid rgba(0,200,255,.07)', cursor: 'pointer' },
10
+ from: { fontSize: 13, color: '#e0f0ff', marginBottom: 3 },
11
+ preview: { fontSize: 12, color: '#7a9bbf', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
12
+ time: { fontSize: 11, color: '#4a6a88', marginTop: 4 },
13
+ main: { display: 'flex', flexDirection: 'column', background: 'rgba(0,0,0,.2)' },
14
+ placeholder: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1, color: '#4a6a88', fontSize: 14 },
15
+ detailHeader: { display: 'flex', justifyContent: 'space-between', padding: '16px 20px', borderBottom: '1px solid rgba(0,200,255,.1)' },
16
+ detailFrom: { fontFamily: "'Orbitron',sans-serif", fontSize: 14, color: '#fff' },
17
+ detailTime: { fontSize: 12, color: '#4a6a88' },
18
+ body: { flex: 1, padding: 20, fontSize: 14, color: '#a8bfd8', lineHeight: 1.7, overflowY: 'auto' },
19
+ replyWrap: { padding: 16, borderTop: '1px solid rgba(0,200,255,.1)', display: 'flex', flexDirection: 'column', gap: 10 },
20
+ replyInput: { width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '10px 14px', color: '#fff', fontSize: 13, outline: 'none', resize: 'none', boxSizing: 'border-box', fontFamily: 'inherit' },
21
+ btn: { alignSelf: 'flex-end', background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 8, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, padding: '9px 24px', cursor: 'pointer' },
22
+ empty: { padding: 24, textAlign: 'center', color: '#4a6a88', fontSize: 13 },
23
+ };
24
+
25
+ const INITIAL = [
26
+ { 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.' },
27
+ { 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.' },
28
+ ];
29
+
30
+ export function Messaging() {
31
+ const [messages, setMessages] = useState(INITIAL);
32
+ const [selected, setSelected] = useState(null);
33
+ const [search, setSearch] = useState('');
34
+ const [reply, setReply] = useState('');
35
+
36
+ const filtered = messages.filter(m => m.from.toLowerCase().includes(search.toLowerCase()) || m.preview.toLowerCase().includes(search.toLowerCase()));
37
+
38
+ function open(m) {
39
+ setMessages(ms => ms.map(x => x.id === m.id ? { ...x, read: true } : x));
40
+ setSelected(m); setReply('');
41
+ }
42
+
43
+ return (
44
+ <div style={s.shell}>
45
+ <div style={s.sidebar}>
46
+ <div style={s.searchWrap}><input style={s.search} value={search} onChange={e => setSearch(e.target.value)} placeholder="Search messages…" /></div>
47
+ <div style={s.list}>
48
+ {filtered.map(m => (
49
+ <div key={m.id} style={{ ...s.item, background: selected?.id === m.id ? 'rgba(0,200,255,.1)' : undefined, borderLeft: selected?.id === m.id ? '3px solid #00c8ff' : undefined }} onClick={() => open(m)}>
50
+ <div style={{ ...s.from, fontWeight: m.read ? undefined : 700 }}>{m.from}</div>
51
+ <div style={s.preview}>{m.preview}</div>
52
+ <div style={s.time}>{m.time}</div>
53
+ </div>
54
+ ))}
55
+ {filtered.length === 0 && <div style={s.empty}>No messages found.</div>}
56
+ </div>
57
+ </div>
58
+ <div style={s.main}>
59
+ {selected ? (
60
+ <>
61
+ <div style={s.detailHeader}><div style={s.detailFrom}>{selected.from}</div><div style={s.detailTime}>{selected.time}</div></div>
62
+ <div style={s.body}>{selected.body}</div>
63
+ <div style={s.replyWrap}>
64
+ <textarea style={s.replyInput} value={reply} onChange={e => setReply(e.target.value)} placeholder="Write a reply…" rows={3} />
65
+ <button style={{ ...s.btn, opacity: reply.trim() ? 1 : .4 }} disabled={!reply.trim()} onClick={() => setReply('')}>Send</button>
66
+ </div>
67
+ </>
68
+ ) : (
69
+ <div style={s.placeholder}>📬 Select a message to read it</div>
70
+ )}
71
+ </div>
72
+ </div>
73
+ );
74
+ }
@@ -1,40 +1,40 @@
1
- import { useState } from 'react';
2
-
3
- const STATUS_COLORS = { available: '#7a9bbf', active: '#00c8ff', complete: '#48dc82' };
4
- const INITIAL = [
5
- { id: '1', title: 'First Contact', description: 'Log into OASIS for the first time and explore the hub.', reward: 100, status: 'complete' },
6
- { id: '2', title: 'Karma Cultivator', description: 'Earn 500 karma through positive actions in the OASIS.', reward: 250, status: 'active' },
7
- { id: '3', title: 'NFT Hunter', description: 'Acquire your first rare NFT from the marketplace.', reward: 400, status: 'available' },
8
- { id: '4', title: 'Seed Planter', description: 'Plant 3 seeds and nurture them to full growth.', reward: 150, status: 'available' },
9
- ];
10
- function statusLabel(s) { return s === 'available' ? 'Available' : s === 'active' ? 'In Progress' : 'Complete'; }
11
-
12
- export function Mission() {
13
- const [missions, setMissions] = useState(INITIAL);
14
- function accept(id) { setMissions(l => l.map(x => x.id === id ? { ...x, status: 'active' } : x)); }
15
- function complete(id) { setMissions(l => l.map(x => x.id === id ? { ...x, status: 'complete' } : x)); }
16
- return (
17
- <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
18
- <div style={{ textAlign: 'center' }}>
19
- <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🎯 Missions</h2>
20
- <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Complete missions to earn karma and unlock rewards.</p>
21
- </div>
22
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
23
- {missions.map(m => (
24
- <div key={m.id} style={{ display: 'flex', alignItems: 'center', gap: 16, background: m.status === 'active' ? 'rgba(0,200,255,.05)' : m.status === 'complete' ? 'rgba(72,220,130,.04)' : 'rgba(255,255,255,.04)', border: `1px solid ${m.status === 'active' ? 'rgba(0,200,255,.35)' : m.status === 'complete' ? 'rgba(72,220,130,.25)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: 16, opacity: m.status === 'complete' ? .7 : 1 }}>
25
- <div style={{ flex: 1 }}>
26
- <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff', marginBottom: 4 }}>{m.title}</div>
27
- <div style={{ fontSize: 12, color: '#7a9bbf', lineHeight: 1.5 }}>{m.description}</div>
28
- </div>
29
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 8, flexShrink: 0 }}>
30
- <div style={{ fontSize: 12, color: '#48dc82', fontWeight: 600 }}>+{m.reward} karma</div>
31
- <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', borderRadius: 999, padding: '3px 10px', border: `1px solid ${STATUS_COLORS[m.status]}44`, color: STATUS_COLORS[m.status] }}>{statusLabel(m.status)}</div>
32
- {m.status === 'available' && <button onClick={() => accept(m.id)} style={{ background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '7px 16px', cursor: 'pointer' }}>Accept</button>}
33
- {m.status === 'active' && <button onClick={() => complete(m.id)} style={{ background: 'linear-gradient(135deg,#48dc82,#00aa55)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '7px 16px', cursor: 'pointer' }}>Complete</button>}
34
- </div>
35
- </div>
36
- ))}
37
- </div>
38
- </div>
39
- );
40
- }
1
+ import { useState } from 'react';
2
+
3
+ const STATUS_COLORS = { available: '#7a9bbf', active: '#00c8ff', complete: '#48dc82' };
4
+ const INITIAL = [
5
+ { id: '1', title: 'First Contact', description: 'Log into OASIS for the first time and explore the hub.', reward: 100, status: 'complete' },
6
+ { id: '2', title: 'Karma Cultivator', description: 'Earn 500 karma through positive actions in the OASIS.', reward: 250, status: 'active' },
7
+ { id: '3', title: 'NFT Hunter', description: 'Acquire your first rare NFT from the marketplace.', reward: 400, status: 'available' },
8
+ { id: '4', title: 'Seed Planter', description: 'Plant 3 seeds and nurture them to full growth.', reward: 150, status: 'available' },
9
+ ];
10
+ function statusLabel(s) { return s === 'available' ? 'Available' : s === 'active' ? 'In Progress' : 'Complete'; }
11
+
12
+ export function Mission() {
13
+ const [missions, setMissions] = useState(INITIAL);
14
+ function accept(id) { setMissions(l => l.map(x => x.id === id ? { ...x, status: 'active' } : x)); }
15
+ function complete(id) { setMissions(l => l.map(x => x.id === id ? { ...x, status: 'complete' } : x)); }
16
+ return (
17
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
18
+ <div style={{ textAlign: 'center' }}>
19
+ <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🎯 Missions</h2>
20
+ <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Complete missions to earn karma and unlock rewards.</p>
21
+ </div>
22
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
23
+ {missions.map(m => (
24
+ <div key={m.id} style={{ display: 'flex', alignItems: 'center', gap: 16, background: m.status === 'active' ? 'rgba(0,200,255,.05)' : m.status === 'complete' ? 'rgba(72,220,130,.04)' : 'rgba(255,255,255,.04)', border: `1px solid ${m.status === 'active' ? 'rgba(0,200,255,.35)' : m.status === 'complete' ? 'rgba(72,220,130,.25)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: 16, opacity: m.status === 'complete' ? .7 : 1 }}>
25
+ <div style={{ flex: 1 }}>
26
+ <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff', marginBottom: 4 }}>{m.title}</div>
27
+ <div style={{ fontSize: 12, color: '#7a9bbf', lineHeight: 1.5 }}>{m.description}</div>
28
+ </div>
29
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 8, flexShrink: 0 }}>
30
+ <div style={{ fontSize: 12, color: '#48dc82', fontWeight: 600 }}>+{m.reward} karma</div>
31
+ <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', borderRadius: 999, padding: '3px 10px', border: `1px solid ${STATUS_COLORS[m.status]}44`, color: STATUS_COLORS[m.status] }}>{statusLabel(m.status)}</div>
32
+ {m.status === 'available' && <button onClick={() => accept(m.id)} style={{ background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '7px 16px', cursor: 'pointer' }}>Accept</button>}
33
+ {m.status === 'active' && <button onClick={() => complete(m.id)} style={{ background: 'linear-gradient(135deg,#48dc82,#00aa55)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '7px 16px', cursor: 'pointer' }}>Complete</button>}
34
+ </div>
35
+ </div>
36
+ ))}
37
+ </div>
38
+ </div>
39
+ );
40
+ }
@@ -1,42 +1,42 @@
1
- import { useState } from 'react';
2
-
3
- const RARITY_COLORS = { Common: '#7a9bbf', Rare: '#5ba8ff', Epic: '#b87fff', Legendary: '#ffb43c' };
4
- const TABS = ['All', 'Avatar', 'Land', 'Item', 'Art'];
5
- const NFTS = [
6
- { id: '1', name: 'Cosmic Warrior', rarity: 'Legendary', type: 'Avatar', karma: 500, image: '⚔️' },
7
- { id: '2', name: 'Forest Realm', rarity: 'Epic', type: 'Land', karma: 250, image: '🌲' },
8
- { id: '3', name: 'Quantum Blade', rarity: 'Rare', type: 'Item', karma: 100, image: '🗡️' },
9
- { id: '4', name: 'Nebula Portrait', rarity: 'Common', type: 'Art', karma: 30, image: '🎨' },
10
- { id: '5', name: 'Light Keeper', rarity: 'Epic', type: 'Avatar', karma: 200, image: '🌟' },
11
- { id: '6', name: 'Sky Citadel', rarity: 'Rare', type: 'Land', karma: 120, image: '🏰' },
12
- ];
13
-
14
- export function NFT() {
15
- const [tab, setTab] = useState('All');
16
- const filtered = tab === 'All' ? NFTS : NFTS.filter(n => n.type === tab);
17
- return (
18
- <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
19
- <div style={{ textAlign: 'center' }}>
20
- <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🖼️ NFTs</h2>
21
- <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Your OASIS non-fungible token collection.</p>
22
- </div>
23
- <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
24
- {TABS.map(t => (
25
- <button key={t} onClick={() => setTab(t)} style={{ background: tab === t ? 'rgba(0,200,255,.15)' : 'rgba(255,255,255,.05)', border: `1px solid ${tab === t ? '#00c8ff' : 'rgba(0,200,255,.2)'}`, borderRadius: 8, color: tab === t ? '#00c8ff' : '#7a9bbf', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, letterSpacing: '.06em', padding: '7px 16px', cursor: 'pointer' }}>{t}</button>
26
- ))}
27
- </div>
28
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(150px,1fr))', gap: 14 }}>
29
- {filtered.map(n => (
30
- <div key={n.id} style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.12)', borderRadius: 12, padding: 14, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
31
- <div style={{ fontSize: 44 }}>{n.image}</div>
32
- <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', borderRadius: 999, padding: '3px 10px', border: `1px solid ${RARITY_COLORS[n.rarity]}44`, color: RARITY_COLORS[n.rarity] }}>{n.rarity}</div>
33
- <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#fff', textAlign: 'center' }}>{n.name}</div>
34
- <div style={{ fontSize: 11, color: '#7a9bbf' }}>{n.type}</div>
35
- <div style={{ fontSize: 12, color: '#48dc82', fontWeight: 600 }}>+{n.karma} karma</div>
36
- </div>
37
- ))}
38
- {filtered.length === 0 && <div style={{ gridColumn: '1/-1', textAlign: 'center', color: '#4a6a88', padding: 32, fontSize: 14 }}>No NFTs in this category.</div>}
39
- </div>
40
- </div>
41
- );
42
- }
1
+ import { useState } from 'react';
2
+
3
+ const RARITY_COLORS = { Common: '#7a9bbf', Rare: '#5ba8ff', Epic: '#b87fff', Legendary: '#ffb43c' };
4
+ const TABS = ['All', 'Avatar', 'Land', 'Item', 'Art'];
5
+ const NFTS = [
6
+ { id: '1', name: 'Cosmic Warrior', rarity: 'Legendary', type: 'Avatar', karma: 500, image: '⚔️' },
7
+ { id: '2', name: 'Forest Realm', rarity: 'Epic', type: 'Land', karma: 250, image: '🌲' },
8
+ { id: '3', name: 'Quantum Blade', rarity: 'Rare', type: 'Item', karma: 100, image: '🗡️' },
9
+ { id: '4', name: 'Nebula Portrait', rarity: 'Common', type: 'Art', karma: 30, image: '🎨' },
10
+ { id: '5', name: 'Light Keeper', rarity: 'Epic', type: 'Avatar', karma: 200, image: '🌟' },
11
+ { id: '6', name: 'Sky Citadel', rarity: 'Rare', type: 'Land', karma: 120, image: '🏰' },
12
+ ];
13
+
14
+ export function NFT() {
15
+ const [tab, setTab] = useState('All');
16
+ const filtered = tab === 'All' ? NFTS : NFTS.filter(n => n.type === tab);
17
+ return (
18
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
19
+ <div style={{ textAlign: 'center' }}>
20
+ <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🖼️ NFTs</h2>
21
+ <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Your OASIS non-fungible token collection.</p>
22
+ </div>
23
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
24
+ {TABS.map(t => (
25
+ <button key={t} onClick={() => setTab(t)} style={{ background: tab === t ? 'rgba(0,200,255,.15)' : 'rgba(255,255,255,.05)', border: `1px solid ${tab === t ? '#00c8ff' : 'rgba(0,200,255,.2)'}`, borderRadius: 8, color: tab === t ? '#00c8ff' : '#7a9bbf', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, letterSpacing: '.06em', padding: '7px 16px', cursor: 'pointer' }}>{t}</button>
26
+ ))}
27
+ </div>
28
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(150px,1fr))', gap: 14 }}>
29
+ {filtered.map(n => (
30
+ <div key={n.id} style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.12)', borderRadius: 12, padding: 14, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
31
+ <div style={{ fontSize: 44 }}>{n.image}</div>
32
+ <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', borderRadius: 999, padding: '3px 10px', border: `1px solid ${RARITY_COLORS[n.rarity]}44`, color: RARITY_COLORS[n.rarity] }}>{n.rarity}</div>
33
+ <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#fff', textAlign: 'center' }}>{n.name}</div>
34
+ <div style={{ fontSize: 11, color: '#7a9bbf' }}>{n.type}</div>
35
+ <div style={{ fontSize: 12, color: '#48dc82', fontWeight: 600 }}>+{n.karma} karma</div>
36
+ </div>
37
+ ))}
38
+ {filtered.length === 0 && <div style={{ gridColumn: '1/-1', textAlign: 'center', color: '#4a6a88', padding: 32, fontSize: 14 }}>No NFTs in this category.</div>}
39
+ </div>
40
+ </div>
41
+ );
42
+ }
@@ -1,52 +1,52 @@
1
- import { useState, useEffect } from 'react';
2
-
3
- const styles = {
4
- nav: (scrolled) => ({ position: 'sticky', top: 0, zIndex: 100, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: scrolled ? '12px 32px' : '18px 32px', background: 'rgba(3,7,20,.92)', backdropFilter: 'blur(12px)', borderBottom: '1px solid rgba(0,200,255,.1)', transition: 'padding .3s', fontFamily: "'Rajdhani',sans-serif", boxSizing: 'border-box' }),
5
- brand: { fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.16em', color: '#fff', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 10, whiteSpace: 'nowrap' },
6
- brandIcon: { color: '#00c8ff', fontSize: 16 },
7
- links: { display: 'flex', gap: 16, listStyle: 'none', margin: 0, padding: 0 },
8
- link: { fontFamily: "'Share Tech Mono',monospace", fontSize: 11, letterSpacing: '.08em', color: '#a8bfd8', textDecoration: 'none', textTransform: 'uppercase' },
9
- loginBtn: { background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 10, fontWeight: 700, letterSpacing: '.08em', padding: '8px 16px', cursor: 'pointer', whiteSpace: 'nowrap' },
10
- };
11
-
12
- /**
13
- * NavBar — sticky responsive navigation bar.
14
- * Props:
15
- * brand string — brand name text
16
- * brandIcon string — icon/emoji before brand name
17
- * links Array<{label, href}> — nav links
18
- * loginLabel string — login button label (pass null to hide)
19
- * onLogin fn — login button click handler
20
- */
21
- export function NavBar({
22
- brand = 'OASIS',
23
- brandIcon = '✦',
24
- links = [],
25
- loginLabel = 'Login',
26
- onLogin,
27
- }) {
28
- const [scrolled, setScrolled] = useState(false);
29
-
30
- useEffect(() => {
31
- const handler = () => setScrolled(window.scrollY > 40);
32
- window.addEventListener('scroll', handler, { passive: true });
33
- return () => window.removeEventListener('scroll', handler);
34
- }, []);
35
-
36
- return (
37
- <nav style={styles.nav(scrolled)}>
38
- <a style={styles.brand} href="#">
39
- <span style={styles.brandIcon}>{brandIcon}</span>
40
- <span>{brand}</span>
41
- </a>
42
- <ul style={styles.links}>
43
- {links.map((l, i) => (
44
- <li key={i}><a style={styles.link} href={l.href || '#'}>{l.label}</a></li>
45
- ))}
46
- </ul>
47
- {onLogin && (
48
- <button style={styles.loginBtn} onClick={onLogin}>{loginLabel}</button>
49
- )}
50
- </nav>
51
- );
52
- }
1
+ import { useState, useEffect } from 'react';
2
+
3
+ const styles = {
4
+ nav: (scrolled) => ({ position: 'sticky', top: 0, zIndex: 100, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: scrolled ? '12px 32px' : '18px 32px', background: 'rgba(3,7,20,.92)', backdropFilter: 'blur(12px)', borderBottom: '1px solid rgba(0,200,255,.1)', transition: 'padding .3s', fontFamily: "'Rajdhani',sans-serif", boxSizing: 'border-box' }),
5
+ brand: { fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.16em', color: '#fff', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 10, whiteSpace: 'nowrap' },
6
+ brandIcon: { color: '#00c8ff', fontSize: 16 },
7
+ links: { display: 'flex', gap: 16, listStyle: 'none', margin: 0, padding: 0 },
8
+ link: { fontFamily: "'Share Tech Mono',monospace", fontSize: 11, letterSpacing: '.08em', color: '#a8bfd8', textDecoration: 'none', textTransform: 'uppercase' },
9
+ loginBtn: { background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 10, fontWeight: 700, letterSpacing: '.08em', padding: '8px 16px', cursor: 'pointer', whiteSpace: 'nowrap' },
10
+ };
11
+
12
+ /**
13
+ * NavBar — sticky responsive navigation bar.
14
+ * Props:
15
+ * brand string — brand name text
16
+ * brandIcon string — icon/emoji before brand name
17
+ * links Array<{label, href}> — nav links
18
+ * loginLabel string — login button label (pass null to hide)
19
+ * onLogin fn — login button click handler
20
+ */
21
+ export function NavBar({
22
+ brand = 'OASIS',
23
+ brandIcon = '✦',
24
+ links = [],
25
+ loginLabel = 'Login',
26
+ onLogin,
27
+ }) {
28
+ const [scrolled, setScrolled] = useState(false);
29
+
30
+ useEffect(() => {
31
+ const handler = () => setScrolled(window.scrollY > 40);
32
+ window.addEventListener('scroll', handler, { passive: true });
33
+ return () => window.removeEventListener('scroll', handler);
34
+ }, []);
35
+
36
+ return (
37
+ <nav style={styles.nav(scrolled)}>
38
+ <a style={styles.brand} href="#">
39
+ <span style={styles.brandIcon}>{brandIcon}</span>
40
+ <span>{brand}</span>
41
+ </a>
42
+ <ul style={styles.links}>
43
+ {links.map((l, i) => (
44
+ <li key={i}><a style={styles.link} href={l.href || '#'}>{l.label}</a></li>
45
+ ))}
46
+ </ul>
47
+ {onLogin && (
48
+ <button style={styles.loginBtn} onClick={onLogin}>{loginLabel}</button>
49
+ )}
50
+ </nav>
51
+ );
52
+ }
@@ -1,41 +1,41 @@
1
- import { useState } from 'react';
2
-
3
- const INITIAL = [
4
- { id: '1', name: 'OLAND', description: 'Virtual land management and terraforming in the OASIS.', category: 'Metaverse', icon: '🌍', karma: 200, launched: false },
5
- { id: '2', name: 'OMARKET', description: 'Decentralised marketplace for NFTs and digital goods.', category: 'Commerce', icon: '🛒', karma: 150, launched: false },
6
- { id: '3', name: 'OCHAT', description: 'Encrypted peer-to-peer messaging on the ONET.', category: 'Social', icon: '💬', karma: 80, launched: false },
7
- { id: '4', name: 'OQUESTS', description: 'Daily quests and challenges for karma rewards.', category: 'Gaming', icon: '⚔️', karma: 300, launched: false },
8
- ];
9
-
10
- export function OApp() {
11
- const [oapps, setOapps] = useState(INITIAL);
12
- const [search, setSearch] = useState('');
13
- const filtered = oapps.filter(a => a.name.toLowerCase().includes(search.toLowerCase()) || a.description.toLowerCase().includes(search.toLowerCase()));
14
- function toggle(id) { setOapps(l => l.map(x => x.id === id ? { ...x, launched: !x.launched } : x)); }
15
- return (
16
- <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
17
- <div style={{ textAlign: 'center' }}>
18
- <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>📱 OApps</h2>
19
- <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Decentralised apps built on the OASIS network.</p>
20
- </div>
21
- <input style={{ width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '10px 14px', color: '#fff', fontSize: 14, outline: 'none', boxSizing: 'border-box' }} value={search} onChange={e => setSearch(e.target.value)} placeholder="Search OApps…" />
22
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
23
- {filtered.map(a => (
24
- <div key={a.id} style={{ display: 'flex', alignItems: 'flex-start', gap: 14, background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.12)', borderRadius: 12, padding: 16 }}>
25
- <div style={{ fontSize: 32, flexShrink: 0 }}>{a.icon}</div>
26
- <div style={{ flex: 1 }}>
27
- <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff', marginBottom: 3 }}>{a.name}</div>
28
- <div style={{ fontSize: 11, color: '#5ba8ff', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 5 }}>{a.category}</div>
29
- <div style={{ fontSize: 12, color: '#7a9bbf', lineHeight: 1.5 }}>{a.description}</div>
30
- </div>
31
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 8, flexShrink: 0 }}>
32
- <div style={{ fontSize: 11, color: '#48dc82', fontWeight: 600 }}>+{a.karma} karma</div>
33
- <button onClick={() => toggle(a.id)} style={{ background: a.launched ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: a.launched ? '1px solid rgba(255,80,80,.4)' : 'none', borderRadius: 7, color: a.launched ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, letterSpacing: '.08em', padding: '7px 16px', cursor: 'pointer' }}>{a.launched ? 'Quit' : 'Launch'}</button>
34
- </div>
35
- </div>
36
- ))}
37
- {filtered.length === 0 && <div style={{ textAlign: 'center', color: '#4a6a88', fontSize: 14, padding: 32 }}>No OApps found.</div>}
38
- </div>
39
- </div>
40
- );
41
- }
1
+ import { useState } from 'react';
2
+
3
+ const INITIAL = [
4
+ { id: '1', name: 'OLAND', description: 'Virtual land management and terraforming in the OASIS.', category: 'Metaverse', icon: '🌍', karma: 200, launched: false },
5
+ { id: '2', name: 'OMARKET', description: 'Decentralised marketplace for NFTs and digital goods.', category: 'Commerce', icon: '🛒', karma: 150, launched: false },
6
+ { id: '3', name: 'OCHAT', description: 'Encrypted peer-to-peer messaging on the ONET.', category: 'Social', icon: '💬', karma: 80, launched: false },
7
+ { id: '4', name: 'OQUESTS', description: 'Daily quests and challenges for karma rewards.', category: 'Gaming', icon: '⚔️', karma: 300, launched: false },
8
+ ];
9
+
10
+ export function OApp() {
11
+ const [oapps, setOapps] = useState(INITIAL);
12
+ const [search, setSearch] = useState('');
13
+ const filtered = oapps.filter(a => a.name.toLowerCase().includes(search.toLowerCase()) || a.description.toLowerCase().includes(search.toLowerCase()));
14
+ function toggle(id) { setOapps(l => l.map(x => x.id === id ? { ...x, launched: !x.launched } : x)); }
15
+ return (
16
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
17
+ <div style={{ textAlign: 'center' }}>
18
+ <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>📱 OApps</h2>
19
+ <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Decentralised apps built on the OASIS network.</p>
20
+ </div>
21
+ <input style={{ width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '10px 14px', color: '#fff', fontSize: 14, outline: 'none', boxSizing: 'border-box' }} value={search} onChange={e => setSearch(e.target.value)} placeholder="Search OApps…" />
22
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
23
+ {filtered.map(a => (
24
+ <div key={a.id} style={{ display: 'flex', alignItems: 'flex-start', gap: 14, background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.12)', borderRadius: 12, padding: 16 }}>
25
+ <div style={{ fontSize: 32, flexShrink: 0 }}>{a.icon}</div>
26
+ <div style={{ flex: 1 }}>
27
+ <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff', marginBottom: 3 }}>{a.name}</div>
28
+ <div style={{ fontSize: 11, color: '#5ba8ff', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 5 }}>{a.category}</div>
29
+ <div style={{ fontSize: 12, color: '#7a9bbf', lineHeight: 1.5 }}>{a.description}</div>
30
+ </div>
31
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 8, flexShrink: 0 }}>
32
+ <div style={{ fontSize: 11, color: '#48dc82', fontWeight: 600 }}>+{a.karma} karma</div>
33
+ <button onClick={() => toggle(a.id)} style={{ background: a.launched ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: a.launched ? '1px solid rgba(255,80,80,.4)' : 'none', borderRadius: 7, color: a.launched ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, letterSpacing: '.08em', padding: '7px 16px', cursor: 'pointer' }}>{a.launched ? 'Quit' : 'Launch'}</button>
34
+ </div>
35
+ </div>
36
+ ))}
37
+ {filtered.length === 0 && <div style={{ textAlign: 'center', color: '#4a6a88', fontSize: 14, padding: 32 }}>No OApps found.</div>}
38
+ </div>
39
+ </div>
40
+ );
41
+ }