Avatar
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>$DAB Relic Shop Integrator</title>
<style>
:root{--bg:#071018;--card:#0f1b24;--accent:#ffcc00;--muted:#98a6ab}
body{margin:0;font-family:Inter,system-ui,Arial;background:linear-gradient(180deg,#041018,#071018);color:#e6eef3}
.wrap{max-width:1200px;margin:24px auto;padding:18px}
header h1{margin:0;color:var(--accent)}
.layout{display:grid;grid-template-columns:1fr 360px;gap:18px}
.panel{background:linear-gradient(180deg,var(--card),#071219);border-radius:10px;padding:14px;border:1px solid rgba(255,255,255,0.03)}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px}
.relic{background:#08121a;padding:10px;border-radius:8px;border:1px solid rgba(255,204,0,0.04)}
.relic img{width:100%;height:160px;object-fit:cover;border-radius:6px;background:#000}
.meta{margin-top:8px;font-size:13px;color:var(--muted)}
.btn{padding:8px 10px;border-radius:8px;border:none;cursor:pointer;font-weight:700}
.btn-accent{background:var(--accent);color:#071018}
.btn-ghost{background:transparent;color:var(--accent);border:1px solid rgba(255,204,0,0.08)}
.small{font-size:13px;color:var(--muted)}
#chatWidget{position:fixed;right:18px;bottom:18px;width:360px;max-height:600px;background:#071219;border-radius:10px;box-shadow:0 10px 40px rgba(0,0,0,0.6);overflow:hidden;display:flex;flex-direction:column}
#chatHeader{background:#08141b;padding:10px;color:var(--accent);font-weight:700}
#chatBody{padding:10px;flex:1;overflow:auto;font-size:14px;color:#e6eef3}
#chatInput{display:flex;padding:10px;background:#051019}
#chatInput input{flex:1;padding:8px;border-radius:6px;border:1px solid rgba(255,255,255,0.03);background:#051219;color:#e6eef3}
#chatInput button{margin-left:8px}
.log{max-height:200px;overflow:auto;padding:8px;background:#051219;border-radius:6px;margin-top:8px;font-size:13px;color:var(--muted)}
.aside-block{margin-bottom:12px}
@media(max-width:980px){.layout{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="wrap">
<header><h1>$DAB Relic Shop Integrator</h1></header>
<div class="layout">
<main class="panel">
<div style="display:flex;justify-content:space-between;align-items:center">
<div><strong class="small">Relic Manifest</strong><div id="manifestUrl" class="small">relics.json</div></div>
<div><button id="refreshBtn" class="btn btn-ghost">Refresh</button></div>
</div>
<section style="margin-top:12px">
<div class="grid" id="relicGrid"></div>
</section>
<div class="aside-block">
<div class="small"><strong>Actions</strong></div>
<div style="display:flex;gap:8px;margin-top:8px">
<button id="bulkCreateMerch" class="btn btn-ghost">Bulk Create Merch Links</button>
<button id="syncStore" class="btn btn-ghost">Sync To External Store</button>
<button id="recalcValue" class="btn btn-accent">Recalculate Bank Value</button>
</div>
</div>
<div class="aside-block">
<div class="small"><strong>Bank Value</strong></div>
<div id="bankValue" style="font-weight:700;color:var(--accent);margin-top:6px">—</div>
</div>
<div class="aside-block">
<div class="small"><strong>Activity Log</strong></div>
<div id="activityLog" class="log"></div>
</div>
</main>
<aside class="panel">
<div class="aside-block">
<div class="small"><strong>Integration Endpoints</strong></div>
<div class="small" id="endpointsList">AI Chat: /api/ai-chat<br>Merch Create: /api/create-merch<br>Store Sync: /api/sync-store<br>Mint Hook: /api/mint</div>
</div>
<div class="aside-block">
<div class="small"><strong>External Store</strong></div>
<input id="storeBase" placeholder="https://yourstore.com" style="width:100%;margin-top:8px;padding:8px;border-radius:6px;border:1px solid rgba(255,255,255,0.03);background:#071219;color:#e6eef3">
</div>
<div class="aside-block">
<div class="small"><strong>ERC20/721 Mint Settings</strong></div>
<input id="contractAddress" placeholder="Token contract 0x..." style="width:100%;margin-top:8px;padding:8px;border-radius:6px;border:1px solid rgba(255,255,255,0.03);background:#071219;color:#e6eef3">
<div style="margin-top:8px"><button id="connectWalletBtn" class="btn btn-ghost">Connect Wallet</button></div>
</div>
</aside>
</div>
</div>
<!-- Chat Widget -->
<div id="chatWidget" aria-hidden="false">
<div id="chatHeader">Relic AI Assistant</div>
<div id="chatBody"></div>
<div id="chatInput">
<input id="chatText" placeholder="Ask the AI to manage relics or create merch links">
<button id="sendChat" class="btn btn-accent">Send</button>
</div>
</div>
<!-- Ethers.js for optional minting -->
<script src="https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.min.js"></script>
<script>
// CONFIGURE these endpoints for your server-side implementations
const MANIFEST_URL = 'relics.json';
const AI_CHAT_ENDPOINT = '/api/ai-chat';
const CREATE_MERCH_ENDPOINT = '/api/create-merch';
const SYNC_STORE_ENDPOINT = '/api/sync-store';
const MINT_ENDPOINT = '/api/mint';
// UI elements
const relicGrid = document.getElementById('relicGrid');
const activityLog = document.getElementById('activityLog');
const bankValueEl = document.getElementById('bankValue');
const refreshBtn = document.getElementById('refreshBtn');
const bulkCreateMerch = document.getElementById('bulkCreateMerch');
const syncStoreBtn = document.getElementById('syncStore');
const recalcValueBtn = document.getElementById('recalcValue');
const chatBody = document.getElementById('chatBody');
const chatText = document.getElementById('chatText');
const sendChat = document.getElementById('sendChat');
const storeBase = document.getElementById('storeBase');
const connectWalletBtn = document.getElementById('connectWalletBtn');
const contractAddressInput = document.getElementById('contractAddress');
let relics = [];
let provider = null;
let signer = null;
let userAddress = null;
function log(msg){
const d = document.createElement('div');
d.textContent = new Date().toLocaleTimeString() + ' — ' + msg;
activityLog.prepend(d);
}
async function fetchManifest(){
log('Fetching manifest ' + MANIFEST_URL);
try {
const res = await fetch(MANIFEST_URL, {cache:'no-cache'});
if(!res.ok) throw new Error('HTTP ' + res.status);
const json = await res.json();
relics = Array.isArray(json.relics) ? json.relics : json;
renderRelics(relics);
log('Loaded ' + relics.length + ' relics');
recalcBankValue();
} catch(err){
log('Manifest error: ' + err.message);
relicGrid.innerHTML = '<div class="small">Failed to load manifest.</div>';
}
}
function renderRelics(items){
relicGrid.innerHTML = '';
items.forEach((r, idx) => {
const el = document.createElement('div');
el.className = 'relic';
el.innerHTML = `
<img src="${r.image || ''}" alt="${r.name || 'Relic'}" onerror="this.style.filter='grayscale(80%)';this.alt='Image failed'">
<div class="meta"><strong>${r.name || 'Unnamed'}</strong><div class="small">${r.description || ''}</div></div>
<div style="margin-top:8px;display:flex;gap:8px">
<button class="btn btn-accent btn-open" data-idx="${idx}">Open Store</button>
<button class="btn btn-ghost btn-merch" data-idx="${idx}">Create Merch Link</button>
<button class="btn btn-ghost btn-ai" data-idx="${idx}">AI Assist</button>
<button class="btn btn-ghost btn-mint" data-idx="${idx}">Mint Token</button>
</div>
`;
relicGrid.appendChild(el);
});
// Attach actions
document.querySelectorAll('.btn-open').forEach(b=>{
b.addEventListener('click', e=>{
const i = parseInt(e.currentTarget.dataset.idx,10);
const r = relics[i];
const base = storeBase.value.trim() || '';
if(!base) {
log('Set store base URL to open relic in external store');
return;
}
const url = new URL(base);
url.pathname = '/product/' + encodeURIComponent(r.slug || r.id || r.name);
window.open(url.toString(), '_blank');
log('Opening store product for ' + (r.name||r.id));
});
});
document.querySelectorAll('.btn-merch').forEach(b=>{
b.addEventListener('click', async e=>{
const i = parseInt(e.currentTarget.dataset.idx,10);
const r = relics[i];
log('Creating merch for ' + (r.name||r.id));
try {
const resp = await fetch(CREATE_MERCH_ENDPOINT, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ relic: r })
});
const data = await resp.json();
if(resp.ok) {
log('Merch created: ' + (data.merchUrl || data.id || 'created'));
if(data.merchUrl) window.open(data.merchUrl,'_blank');
} else {
log('Merch create failed: ' + (data.error || resp.status));
}
} catch(err){
log('Merch create error: ' + err.message);
}
});
});
document.querySelectorAll('.btn-ai').forEach(b=>{
b.addEventListener('click', async e=>{
const i = parseInt(e.currentTarget.dataset.idx,10);
const r = relics[i];
const prompt = `Inspect relic and propose actions. Relic JSON: ${JSON.stringify(r)}`;
appendChat('user', prompt);
sendAIMessage(prompt, { context: { relic: r }});
});
});
document.querySelectorAll('.btn-mint').forEach(b=>{
b.addEventListener('click', async e=>{
const i = parseInt(e.currentTarget.dataset.idx,10);
const r = relics[i];
if(!signer) {
log('Wallet not connected for minting');
return;
}
log('Requesting mint for ' + (r.name||r.id));
try {
const resp = await fetch(MINT_ENDPOINT, {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({ relic: r, requester: userAddress })
});
const data = await resp.json();
if(resp.ok){
log('Mint initiated server side: ' + (data.tx || data.message || 'ok'));
} else {
log('Mint failed: ' + (data.error || resp.status));
}
} catch(err){
log('Mint error: ' + err.message);
}
});
});
}
function recalcBankValue(){
const total = relics.reduce((s,r)=> s + (parseFloat(r.value) || 0), 0);
bankValueEl.textContent = total;
log('Bank value recalculated ' + total);
return total;
}
// Bulk actions
bulkCreateMerch.addEventListener('click', async ()=>{
log('Bulk create merch started for ' + relics.length + ' relics');
for(const r of relics){
try {
const res = await fetch(CREATE_MERCH_ENDPOINT, {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({relic:r})});
if(res.ok){
const d = await res.json();
log('Merch created ' + (d.merchUrl || d.id || 'ok') + ' for ' + (r.name||r.id));
} else {
log('Merch create error for ' + (r.name||r.id));
}
} catch(err){
log('Bulk create error ' + err.message);
}
}
log('Bulk create merch finished');
});
syncStoreBtn.addEventListener('click', async ()=>{
log('Triggering store sync');
try {
const resp = await fetch(SYNC_STORE_ENDPOINT, {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ relics })});
const d = await resp.json();
if(resp.ok) log('Store sync queued: ' + (d.message || 'ok')); else log('Store sync failed');
} catch(err){ log('Store sync error ' + err.message); }
});
recalcValueBtn.addEventListener('click', recalcBankValue);
refreshBtn.addEventListener('click', fetchManifest);
// AI chat functions
function appendChat(role, text){
const el = document.createElement('div');
el.style.marginBottom='8px';
el.innerHTML = `<strong class="small">${role === 'user' ? 'You' : 'AI'}</strong><div style="margin-top:4px">${text}</div>`;
chatBody.appendChild(el);
chatBody.scrollTop = chatBody.scrollHeight;
}
async function sendAIMessage(message, options = {}){
appendChat('user', message);
appendChat('ai', 'Thinking...');
try {
const body = { message, context: options.context || { relicsCount: relics.length } };
const res = await fetch(AI_CHAT_ENDPOINT, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) });
const data = await res.json();
// Replace last "Thinking..." with response
chatBody.removeChild(chatBody.lastChild);
appendChat('ai', data.reply || JSON.stringify(data));
log('AI reply received');
} catch(err){
chatBody.removeChild(chatBody.lastChild);
appendChat('ai', 'AI error: ' + err.message);
log('AI call failed ' + err.message);
}
}
sendChat.addEventListener('click', ()=>{
const text = chatText.value.trim();
if(!text) return;
sendAIMessage(text, { context:{ relics } });
chatText.value = '';
});
// Wallet connect
connectWalletBtn.addEventListener('click', async ()=>{
if(window.ethereum == null){ log('No web3 wallet found'); return; }
provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send('eth_requestAccounts', []);
signer = provider.getSigner();
userAddress = await signer.getAddress();
connectWalletBtn.textContent = userAddress.slice(0,6)+'…'+userAddress.slice(-4);
log('Wallet connected ' + userAddress);
});
// Initial load
(async ()=> { await fetchManifest(); })();
</script>
</body>
</html>