Logo
This commit is contained in:
+2
-2
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#1e1e2e" />
|
||||
<meta name="description" content="Mine knowledge. Collect the extraordinary. A browser-mined Wikipedia trading card experiment." />
|
||||
<meta name="description" content="Mine knowledge. Collect the extraordinary. An experimental RandomX testnet for illustrated Wikipedia article NFTs." />
|
||||
<title>Wikideck — Mine knowledge.</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/Wikideck.png" />
|
||||
<script src="/theme-init.js"></script>
|
||||
</head>
|
||||
<body><div id="root"></div><script type="module" src="/src/main.tsx"></script></body>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 10 KiB |
+3
-3
@@ -151,7 +151,7 @@ export default function App() {
|
||||
|
||||
return <div className="app-shell">
|
||||
<aside className="sidebar">
|
||||
<a href="#" className="brand" onClick={e => { e.preventDefault(); setView('Discover'); }}><span className="brand-mark">w<span>✦</span></span>wikideck<span className="brand-dot">.</span></a>
|
||||
<a href="#" className="brand" onClick={e => { e.preventDefault(); setView('Discover'); }}><img className="brand-logo" src="/Wikideck.png" alt="" />wikideck</a>
|
||||
<div className="sidebar-label">YOUR NEXT DISCOVERY</div>
|
||||
<nav aria-label="Main navigation">{(['Discover', 'My collection', 'Block explorer'] as View[]).map((item, i) => {
|
||||
const Icon = [LayoutDashboard, Layers3, Globe2][i];
|
||||
@@ -185,11 +185,11 @@ export default function App() {
|
||||
{view !== 'Block explorer' ? <section className="discover-section">
|
||||
<div className="section-heading"><div><div className="section-eyebrow">{view === 'My collection' ? 'YOUR PERSONAL ENCYCLOPEDIA' : 'AN ENCYCLOPEDIA, REIMAGINED'}</div><h2>{view === 'My collection' ? 'Your cabinet of curiosities' : 'A world of possible discoveries'}<span className="count-badge">{visibleCards.length}</span></h2><p>{view === 'My collection' ? 'Confirmed native NFT ownership. Pending transfers stay here until mined.' : snapshot?.blocks.length ? 'Recent native NFTs on this node’s canonical testnet chain.' : 'Sample previews only; mined NFTs use random Wikipedia pages with pictures. The same page may be minted more than once.'}</p></div>{view === 'Discover' && <button className="text-button" onClick={() => setView('My collection')}>My collection <ArrowRight size={16} /></button>}</div>
|
||||
<div className="filter-bar"><div className="category-tabs">{['All cards', 'Nature', 'Space', 'Science', 'People', 'History', 'Culture'].map(c => <button key={c} className={category === c ? 'selected' : ''} onClick={() => setCategory(c)}>{c}</button>)}</div><label className="search-input"><Search size={15} /><input aria-label="Search cards" placeholder="Find a discovery…" value={query} onChange={e => setQuery(e.target.value)} /></label></div>
|
||||
{filtered.length ? <div className="card-grid">{filtered.map(c => <div className="card-wrap" key={c.block?.tokenId ?? c.hash}><Card card={c} onClick={() => { setSelected(c); setTransferError(''); }} /><div className="card-list-caption"><span>{c.block ? `Block #${c.block.height}` : 'Example discovery'}</span><span className="caption-dot" />{c.block && snapshot ? short(testnetOwnerOf(snapshot, c.block.tokenId) ?? '') : 'Not owned'}</div>{c.block && snapshot?.pendingTransfers.some(t => t.cardHash === c.block!.tokenId) && <span className="pending-label">Transfer pending mining</span>}</div>)}</div> : <div className="empty-state"><Layers3 size={38} /><h3>{query || category !== 'All cards' ? 'No discoveries here. Yet.' : 'Your first discovery is waiting.'}</h3><p>{query || category !== 'All cards' ? 'Try another search or explore a different category.' : 'Use the native miner or receive a mined transfer on this chain. Restore a backup to access an existing wallet.'}</p><button className="button dark" onClick={goMine}><Pickaxe size={16} /> Explore native mining</button></div>}
|
||||
{filtered.length ? <div className="card-grid">{filtered.map(c => <div className="card-wrap" key={c.block?.tokenId ?? c.hash}><Card card={c} onClick={() => { setSelected(c); setTransferError(''); }} /><div className="card-list-caption"><span>{c.block ? `Block #${c.block.height}` : 'Example discovery'}</span><span className="caption-dot" />{c.block && snapshot ? short(testnetOwnerOf(snapshot, c.block.tokenId) ?? '') : 'Not owned'}</div>{c.block && snapshot?.pendingTransfers.some(t => t.cardHash === c.block!.tokenId) && <span className="pending-label">Transfer pending mining</span>}</div>)}</div> : <div className="empty-state"><img className="empty-state-logo" src="/Wikideck.png" alt="Wikideck" /><h3>{query || category !== 'All cards' ? 'No discoveries here. Yet.' : 'Your first discovery is waiting.'}</h3><p>{query || category !== 'All cards' ? 'Try another search or explore a different category.' : 'Use the native miner or receive a mined transfer on this chain. Restore a backup to access an existing wallet.'}</p><button className="button dark" onClick={goMine}><Pickaxe size={16} /> Explore native mining</button></div>}
|
||||
</section> : <section className="explorer-section"><div className="section-eyebrow">FOLLOW THE CANONICAL CHAIN</div><div className="section-heading"><div><h1>Block explorer<span className="count-badge">{snapshot?.blocks.length ?? 0}</span></h1><p>Native NFT mints, RandomX proofs, and mined ownership transfers.</p></div><a className="button outline" href="/api/chain" target="_blank" rel="noreferrer">View full chain JSON <ExternalLink size={15} /></a></div>
|
||||
<div className="explorer-note"><Shield size={18} /><p>Sovereign testnet with independently validating peers and cumulative-work chain selection. This network is unbootstrapped, not a public mainnet. Zero peers means an isolated node, not a connected network. Canonical ownership can change during a reorganization.</p></div>
|
||||
<dl className="chain-summary"><div><dt>Cumulative work</dt><dd>{snapshot?.cumulativeWork ?? '—'}</dd></div><div><dt>Peer count</dt><dd>{rpcAvailable ? snapshot?.peers ?? '—' : 'Unknown'} · {peerLabel}</dd></div><div><dt>Chain ID · pinned genesis</dt><dd>{snapshot?.chainId ?? '—'}</dd></div><div><dt>Network</dt><dd>{NETWORK}</dd></div><div><dt>Tip block ID</dt><dd>{snapshot?.tip ?? '—'}</dd></div><div><dt>Mint maturity</dt><dd>{snapshot?.maturity ?? CONSENSUS.coinbaseMaturity} subsequent blocks at inclusion height</dd></div></dl>
|
||||
{snapshot?.blocks.length ? <div className="table-scroll"><table><thead><tr><th>Block / confirmations</th><th>Discovery</th><th>Block ID / NFT token ID</th><th>Confirmed owner</th><th>RandomX proof / rarity</th></tr></thead><tbody>{[...snapshot.blocks].reverse().map(b => <tr key={b.hash}><td><button className="explorer-card-link" onClick={() => setSelected(asCard(b))}>#{b.height.toString().padStart(4, '0')}</button><small>{snapshot.blocks.length - b.height + 1} confirmations</small></td><td><strong>{b.article.title}</strong><small>Wikipedia #{b.article.pageId}</small></td><td className="mono"><span title={b.hash}>Block {short(b.hash)}</span><a className="nft-link" href={`/api/nfts/${encodeURIComponent(b.tokenId)}`} target="_blank" rel="noreferrer" title={b.tokenId}>NFT {short(b.tokenId)} <ExternalLink size={11} /></a></td><td className="mono" title={testnetOwnerOf(snapshot, b.tokenId)}>{short(testnetOwnerOf(snapshot, b.tokenId) ?? '')}</td><td><span className="proof-caption mono" title={b.powHash}>{short(b.powHash)}</span><span className={`rarity-tag ${rarity(b.powHash).toLowerCase()}`}>{rarity(b.powHash)}</span></td></tr>)}</tbody></table></div> : <div className="empty-state"><Globe2 size={40} /><h3>A new chain. A blank page.</h3><p>No canonical NFT blocks loaded. Native mining is an explicit, separate step.</p><button className="button dark" onClick={goMine}>Explore native mining <ArrowRight size={16} /></button></div>}
|
||||
{snapshot?.blocks.length ? <div className="table-scroll"><table><thead><tr><th>Block / confirmations</th><th>Discovery</th><th>Block ID / NFT token ID</th><th>Confirmed owner</th><th>RandomX proof / rarity</th></tr></thead><tbody>{[...snapshot.blocks].reverse().map(b => <tr key={b.hash}><td><button className="explorer-card-link" onClick={() => setSelected(asCard(b))}>#{b.height.toString().padStart(4, '0')}</button><small>{snapshot.blocks.length - b.height + 1} confirmations</small></td><td><strong>{b.article.title}</strong><small>Wikipedia #{b.article.pageId}</small></td><td className="mono"><span title={b.hash}>Block {short(b.hash)}</span><a className="nft-link" href={`/api/nfts/${encodeURIComponent(b.tokenId)}`} target="_blank" rel="noreferrer" title={b.tokenId}>NFT {short(b.tokenId)} <ExternalLink size={11} /></a></td><td className="mono" title={testnetOwnerOf(snapshot, b.tokenId)}>{short(testnetOwnerOf(snapshot, b.tokenId) ?? '')}</td><td><span className="proof-caption mono" title={b.powHash}>{short(b.powHash)}</span><span className={`rarity-tag ${rarity(b.powHash).toLowerCase()}`}>{rarity(b.powHash)}</span></td></tr>)}</tbody></table></div> : <div className="empty-state"><img className="empty-state-logo" src="/Wikideck.png" alt="Wikideck" /><h3>A new chain. A blank page.</h3><p>No canonical NFT blocks loaded. Native mining is an explicit, separate step.</p><button className="button dark" onClick={goMine}>Explore native mining <ArrowRight size={16} /></button></div>}
|
||||
<h2 className="transfer-title">Mined ownership transfers</h2>{snapshot?.transfers.length ? <div className="journal">{snapshot.transfers.map((t, i) => <div key={`${t.cardHash}-${t.sequence}`}><ArrowDownLeft size={18} /><a href={`/api/nfts/${encodeURIComponent(t.cardHash)}`} target="_blank" rel="noreferrer" title={t.cardHash}>{short(t.cardHash)}</a><span>{short(t.from)} → {short(t.to)}</span><span>Sequence {t.sequence} · mined #{snapshot.blocks.find(b => b.transactions.some(tx => tx.signature === t.signature))?.height ?? '—'}</span><Check size={14} aria-label={`Mined transfer ${i + 1}`} /></div>)}</div> : <p className="muted">No mined transfers. Ownership changes only when a transfer is included in a canonical block.</p>}
|
||||
<h2 className="transfer-title">Pending transfers <span className="count-badge">{snapshot?.pendingTransfers.length ?? 0}</span></h2><p className="muted">Mempool only · not confirmed ownership. Pending transactions may be dropped or invalidated by a reorganization.</p><div className="journal">{snapshot?.pendingTransfers.map(t => <div key={`${t.cardHash}-${t.sequence}`}><LoaderCircle size={16} /><span title={t.cardHash}>{short(t.cardHash)}</span><span>{short(t.from)} → {short(t.to)}</span><span className="pending-label">Awaiting mining</span></div>)}</div>
|
||||
</section>}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { ArrowUpRight, BookOpen, Globe2, Shield, Sparkles, Swords } from 'lucide-react';
|
||||
import { ArrowUpRight, BookOpen, Shield, Sparkles, Swords } from 'lucide-react';
|
||||
import { rarity, stats, type Article } from '../shared/protocol.ts';
|
||||
import type { TestnetBlock } from '../shared/testnet.ts';
|
||||
import { STARTER_ARTICLES } from '../shared/catalog.ts';
|
||||
@@ -25,7 +25,7 @@ export function Card({ card, onClick }: { card: CardData; onClick?: () => void }
|
||||
return <><button className={`wiki-card ${tier.toLowerCase()}`} onClick={onClick} tabIndex={onClick ? 0 : -1} aria-label={`View ${article.title} ${tier} card`}>
|
||||
<div className="card-top"><span><Sparkles size={11} /> {tier}</span><span>W / {String(article.pageId).slice(-4)}</span></div>
|
||||
<div className={`card-art art-${article.category.toLowerCase()}`}>
|
||||
{article.image ? <img src={article.image} alt={article.title} loading="lazy" referrerPolicy="no-referrer" onError={e => { e.currentTarget.style.display = 'none'; }} /> : <Globe2 size={64} />}
|
||||
{article.image ? <img src={article.image} alt={article.title} loading="lazy" referrerPolicy="no-referrer" onError={e => { e.currentTarget.style.display = 'none'; }} /> : <img className="card-placeholder-logo" src="/Wikideck.png" alt="Wikideck" />}
|
||||
<span className="card-category">{article.category}</span>
|
||||
</div>
|
||||
<div className="card-content"><div className="card-title"><h3>{article.title}</h3><ArrowUpRight size={16} /></div><p>{article.extract}</p>
|
||||
|
||||
Reference in New Issue
Block a user