İlk commit: Stepstead web (statik panel + db)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jts 2026-07-11 11:30:59 +03:00
commit dea01dd6a8
18 changed files with 3764 additions and 0 deletions

469
assets/index.html Normal file
View file

@ -0,0 +1,469 @@
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex,nofollow">
<title>Stepstead — Asset Takip</title>
<style>
:root{
--bg:#0f1419; --panel:#1a2230; --panel2:#222d3d; --line:#2c3a4f;
--txt:#e6edf3; --muted:#8b99a8; --gold:#e8b923;
--eksik:#e5534b; --yapiliyor:#d9a521; --tamam:#2ea043;
--d2:#4a9eff; --d3:#c77dff;
}
*{box-sizing:border-box}
body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
background:var(--bg);color:var(--txt);line-height:1.5}
header{padding:20px 16px;border-bottom:1px solid var(--line);
position:sticky;top:0;background:var(--bg);z-index:10}
h1{margin:0;font-size:20px;color:var(--gold)}
.sub{color:var(--muted);font-size:13px;margin-top:4px}
.wrap{max-width:1040px;margin:0 auto;padding:16px}
.bar{height:10px;background:var(--panel);border-radius:6px;overflow:hidden;margin:10px 0 4px}
.bar > i{display:block;height:100%;background:var(--tamam);width:0;transition:width .3s}
.barlbl{font-size:12px;color:var(--muted)}
.cat{background:var(--panel);border:1px solid var(--line);border-radius:10px;
margin:12px 0;overflow:hidden}
.cat > summary{cursor:pointer;list-style:none;padding:14px 16px;
display:flex;align-items:center;gap:10px;font-weight:600;user-select:none}
.cat > summary::-webkit-details-marker{display:none}
.cat > summary .chev{transition:transform .2s;color:var(--muted)}
.cat[open] > summary .chev{transform:rotate(90deg)}
.cat .count{margin-left:auto;font-size:12px;color:var(--muted);font-weight:400;display:flex;gap:5px;flex-wrap:wrap}
table{width:100%;border-collapse:collapse}
th,td{text-align:left;padding:10px 16px;border-top:1px solid var(--line);font-size:14px;vertical-align:top}
th{color:var(--muted);font-weight:500;font-size:12px;text-transform:uppercase;letter-spacing:.04em}
td.name{font-weight:600}
td.desc{color:var(--muted)}
select.st,select.dm{background:var(--panel2);color:var(--txt);border:1px solid var(--line);
border-radius:6px;padding:5px 8px;font-size:13px;cursor:pointer}
select.st.eksik{color:var(--eksik)} select.st.yapiliyor{color:var(--yapiliyor)} select.st.tamam{color:var(--tamam)}
select.dm.d2{color:var(--d2)} select.dm.d3{color:var(--d3)} select.dm.dx{color:var(--muted)}
tr.done td.name{text-decoration:line-through;opacity:.6}
.del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:16px;opacity:.5}
.del:hover{color:var(--eksik);opacity:1}
.addrow td{padding:8px 16px 14px}
.addrow input{background:var(--panel2);color:var(--txt);border:1px solid var(--line);
border-radius:6px;padding:7px 9px;font-size:13px;margin-right:6px}
.addrow input.n{width:180px} .addrow input.d{width:240px}
.addrow select{background:var(--panel2);color:var(--txt);border:1px solid var(--line);border-radius:6px;padding:7px;font-size:13px;margin-right:6px}
.btn{background:var(--gold);color:#1a1300;border:none;border-radius:6px;
padding:7px 14px;font-size:13px;font-weight:600;cursor:pointer}
.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--line)}
.btn.ghost.on{border-color:var(--gold);color:var(--gold)}
.toolbar{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;align-items:center}
.filt{display:flex;gap:6px;margin-right:auto}
.pill{font-size:11px;padding:1px 7px;border-radius:10px}
.pill.eksik{background:rgba(229,83,75,.15);color:var(--eksik)}
.pill.yapiliyor{background:rgba(217,165,33,.15);color:var(--yapiliyor)}
.pill.tamam{background:rgba(46,160,67,.15);color:var(--tamam)}
.pill.d2{background:rgba(74,158,255,.15);color:var(--d2)}
.pill.d3{background:rgba(199,125,255,.15);color:var(--d3)}
.hide{display:none !important}
.logincard{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:8px}
.logincard input{background:#222d3d;color:var(--txt,#e6edf3);border:1px solid #2c3a4f;border-radius:6px;padding:6px 9px;font-size:13px}
.logincard .msg{font-size:12px;color:#e5534b}
button.up{background:none;border:1px solid #2c3a4f;border-radius:6px;cursor:pointer;font-size:13px;padding:2px 7px}
button.up:hover{border-color:#e8b923}
#toast{position:fixed;bottom:16px;left:50%;transform:translateX(-50%);background:#1a2230;border:1px solid #e8b923;color:#e6edf3;padding:9px 16px;border-radius:8px;font-size:13px;opacity:0;pointer-events:none;transition:.3s;max-width:90vw;z-index:9}
#toast.on{opacity:1}
</style>
<script src="/nav.js?v=8" data-theme="dark" defer></script>
</head>
<body>
<header>
<div class="wrap" style="padding-bottom:0">
<h1>🎨 Stepstead — Asset Takip</h1>
<div class="sub">Eksik görselleri gör, 2D/3D ayır, tamamlandıkça işaretle. Veriler bu tarayıcıda saklanır.</div>
<div class="bar"><i id="pbar"></i></div>
<div class="barlbl" id="plbl"></div>
<div class="toolbar">
<div class="filt">
<button class="btn ghost on" data-filt="all">Tümü</button>
<button class="btn ghost" data-filt="2d">🟦 2D</button>
<button class="btn ghost" data-filt="3d">🟪 3D</button>
</div>
<a class="btn ghost" href="/assets/upload.html" style="text-decoration:none">📤 Dosya Yükle</a>
<button class="btn ghost" id="expBtn">⬇ Dışa aktar</button>
<button class="btn ghost" id="impBtn">⬆ İçe aktar</button>
<input type="file" id="impFile" accept="application/json" hidden>
<button class="btn ghost" id="authBtn">🔑 Giriş</button>
</div>
<div class="logincard hide" id="loginCard">
<input type="email" id="lgEmail" placeholder="E-posta" autocomplete="username">
<input type="password" id="lgPass" placeholder="Şifre" autocomplete="current-password">
<button class="btn ghost" id="lgBtn">Giriş yap</button>
<span class="msg" id="lgMsg"></span>
</div>
</div>
</header>
<div id="toast"></div>
<div class="wrap" id="root"></div>
<script>
const STATES=["eksik","yapiliyor","tamam"];
const STLBL={eksik:"Eksik",yapiliyor:"Yapılıyor",tamam:"Tamam"};
const DIMS=["2d","3d","yok"];
const DMLBL={"2d":"2D","3d":"3D","yok":"—"};
const DMCLS={"2d":"d2","3d":"d3","yok":"dx"};
const KEY="stepstead_assets_v3";
// [ad, açıklama, boyut] — 2026-07-04: lokasyon-graf pivotuna göre yenilendi (tile/3D söküldü)
const SEED=[
{cat:"🗺 Dünya Haritası (EN KRİTİK)", items:[
["Ana harita illüstrasyonu","Statik dünya haritası, portre 720×1280; 15 lokasyon pin koordinatları 0-1000 grid'e oturacak (world.gd TextureRect)","2d"],
["Pin: köy","Başlangıç köyü işareti","2d"],
["Pin: pazar","Ticaret/market işareti","2d"],
["Pin: liman","Liman işareti","2d"],
["Pin: orman (5 tier)","Fısıltı→Karanlık Orman; tier tonlaması","2d"],
["Pin: maden (3 tier)","Taş Ocağı / Derin Maden / Ejder Dağı","2d"],
["Pin: su (göl/kıyı)","Aynalı Göl, Fırtına Kıyısı","2d"],
["Pin: çayır & av","Çayırlar, Av Toprakları","2d"],
["Yol dokusu","Lokasyonlar arası patika çizgi stili (şimdilik Line2D)","2d"],
["Oyuncu konumu işareti","'Buradasın' halkası/bayrak + diğer oyuncu 👤N rozeti","2d"],
]},
{cat:"📍 Lokasyon Kartları (15)", items:[
["Köy","Panel üst görseli / vignette","2d"],
["Pazar","Tezgâhlar, vendor","2d"],
["Liman","Balıı iskeleleri","2d"],
["Fısıltı Ormanı","Tier 1 orman","2d"],
["Gümüş Çamlık","Tier 2 orman","2d"],
["Akça Koru","Tier 3 orman","2d"],
["Kadim Orman","Tier 4 orman","2d"],
["Karanlık Orman","Tier 5 orman","2d"],
["Taş Ocağı","Başlangıç madeni","2d"],
["Derin Maden","Orta seviye maden","2d"],
["Ejder Dağı","Üst seviye maden","2d"],
["Aynalı Göl","Tatlı su balıılığı","2d"],
["Fırtına Kıyısı","Deniz balıılığı","2d"],
["Çayırlar","Ot toplama","2d"],
["Av Toprakları","Avcılık","2d"],
]},
{cat:"🎴 Olay Kartı İllüstrasyonları (20)", items:[
["Gezgin Tacir","Genel yol olayı","2d"],
["Haydut Pususu","Genel yol olayı","2d"],
["Yaralı Gezgin","Genel yol olayı","2d"],
["Kayıp Sandık","Genel yol olayı","2d"],
["Dev Ayı","Genel yol olayı","2d"],
["Mantar Halkası","Genel yol olayı","2d"],
["Eski Harabe","Genel yol olayı","2d"],
["Nehir Geçidi","Genel yol olayı","2d"],
["Gizemli Yaşlı","Genel yol olayı","2d"],
["Kırık Araba","Genel yol olayı","2d"],
["Sağanak","Genel yol olayı","2d"],
["Şanslı Buluş","Genel yol olayı","2d"],
["Kurt Sürüsü","Av Toprakları / Karanlık Orman","2d"],
["Vahşi Domuz","Ormanlar / av","2d"],
["Taze Geyik İzi","Çayır / av","2d"],
["Mağara Örümceği","Madenler","2d"],
["Korsan Artığı","Liman / kıyı (gold ödüllü)","2d"],
["Yaşlı Avcı","Av Toprakları / Çayırlar","2d"],
["Terk Edilmiş Ağ","Göl / kıyı / liman","2d"],
["Ayı İni","Kadim / Karanlık Orman","2d"],
["Olay kartı çerçevesi","Altın kenarlı kart şablonu + karartma","2d"],
["d20 zar görseli","Skill check sonucu (başarılı/başarısız durumları)","2d"],
]},
{cat:"🎒 Item İkonları (~318, aile bazlı)", items:[
["Odunlar (13)","Meşe→Abanoz tier serisi","2d"],
["Cevher + külçe (6+6)","Bakır→Altın","2d"],
["Otlar (12)","Nane, adaçayı, lavanta...","2d"],
["Balıklar (15)","Sazan→Mersin","2d"],
["Et + deri (5+5)","Tavşan/geyik/domuz/kurt/ayı","2d"],
["Bootstrap malzemeleri","Küçük taş, sopa, sarmaşık","2d"],
["Tool'lar","Taş/bakır/demir balta+kazma, 3 olta","2d"],
["Consumable'lar","Çaylar, iksirler, yemekler (adım veren)","2d"],
["Marangozluk ürünleri","Mobilya/yapı ~80 ikon","2d"],
["Okçuluk ürünleri","Yay/ok ~26 ikon","2d"],
["Kuyumculuk ürünleri","Takılar ~6 ikon","2d"],
["Dericilik ürünleri","Giyim ~20 ikon","2d"],
["Nadirlik çerçeveleri","Common→Legendary renk/çerçeve","2d"],
]},
{cat:"⛺ Sistem Görselleri", items:[
["Kamp görseli","Lokasyonda kamp kurulu göstergesi (⛺ yerine)","2d"],
["Adım ikonu","👣 yerine özel ikon","2d"],
["Gold ikonu","🪙 yerine özel ikon","2d"],
["Günlük ödül / streak","🎁 + 🔥 streak görselleri","2d"],
["Liderlik rozetleri","🥇🥈🥉 + sıra çerçeveleri","2d"],
["Skill ikonları (10)","Madencilik, balıılık, toplama, ağaç kesme, avcılık, savaş, demircilik, simya, aşçılık, inşaat","2d"],
]},
{cat:"🧑 Karakter / NPC Portreleri", items:[
["Oyuncu avatarı","2D portre (profil/HUD)","2d"],
["Vendor NPC","Pazar tüccarı portresi","2d"],
["Olay NPC'leri","Tacir, yaşlı avcı, gizemli yaşlı, haydutlar (kartlarda kullanılır)","2d"],
]},
{cat:"🖼 UI / Arayüz", items:[
["Buton seti","Normal / basılı / pasif durumlar","2d"],
["Panel & çerçeve","Envanter, craft, market, lokasyon, chat panelleri","2d"],
["Menü ikonları","💬🏪🎁📊🏆🎒🔨 buton ikonları","2d"],
["Popup / modal","Onay, ödül, uyarı kutusu çerçevesi","2d"],
["Progress bar","Adım / XP / günlük hedef çubukları","2d"],
["Sekme & navigasyon","Panel içi sekmeler (Genel/Burada, İlanlar/Sat/Vendor)","2d"],
]},
{cat:"🏷 Marka", items:[
["Logo","Oyun logosu (yatay + kare)","2d"],
["Splash ekranı","Açılış görseli","2d"],
["App ikonu","Android/iOS launcher ikonu","2d"],
["Store görselleri","Mağaza banner / ekran görüntüleri","2d"],
]},
{cat:"✨ Efekt / Geri bildirim", items:[
["Toplama efekti","Kaynak toplandı parçacık/animasyon","2d"],
["Craft tamam efekti","Üretim başarılı ışıma","2d"],
["Level-up efekti","Seviye atlama kutlaması","2d"],
["Günlük ödül konfetisi","Claim anı","2d"],
]},
{cat:"🔊 Ses", items:[
["Tık sesi","UI buton sesi","yok"],
["Toplama sesi","Kaynak toplama","yok"],
["Craft sesi","Üretim sesi","yok"],
["Zar sesi","Olay kartı d20 atışı","yok"],
["Olay kartı sesi","Kart açılma","yok"],
["Seyahat sesi","Yola çıkma / varış","yok"],
["Level-up sesi","Seviye atlama","yok"],
["Ödül sesi","Günlük ödül / satış","yok"],
["Ambient","Arka plan müzik / ortam (lokasyon türüne göre?)","yok"],
]},
];
function freshSeed(){
return SEED.map(c=>({cat:c.cat,items:c.items.map(i=>({name:i[0],desc:i[1],dim:i[2],state:"eksik"}))}));
}
function load(){
let data;
try{ data=JSON.parse(localStorage.getItem(KEY)); }catch(e){}
if(!data || !Array.isArray(data)){
// v3: lokasyon-graf pivotu sonrası taze liste; v2'deki durumlar isim eşleşmesiyle korunur
data=freshSeed();
let old;
try{ old=JSON.parse(localStorage.getItem("stepstead_assets_v2")); }catch(e){}
if(Array.isArray(old)){
const seedNames=new Set();
data.forEach(c=>c.items.forEach(i=>seedNames.add(i.name)));
const states={};
const extras=[];
old.forEach(c=>c.items.forEach(i=>{
states[i.name]=i.state;
if(!seedNames.has(i.name) && i.state!=="tamam")
extras.push({name:i.name,desc:i.desc||"",dim:i.dim||"2d",state:i.state||"eksik"});
}));
data.forEach(c=>c.items.forEach(i=>{ if(states[i.name]) i.state=states[i.name]; }));
if(extras.length) data.push({cat:"📦 Eski listeden taşınanlar",items:extras});
}
save(data);
}
data.forEach(c=>c.items.forEach(i=>{ if(!i.dim) i.dim="2d"; }));
// Sunucu gerçeği: oyuna bağlanmış asset'ler her tarayıcıda "tamam" görünür (2026-07-06)
const SERVER_DONE=new Set([
"Ana harita illüstrasyonu","Pin: köy","Pin: pazar","Pin: liman",
"Pin: orman (5 tier)","Pin: maden (3 tier)","Pin: su (göl/kıyı)","Pin: çayır & av",
"Oyuncu konumu işareti",
"Köy","Pazar","Liman","Fısıltı Ormanı","Gümüş Çamlık","Akça Koru",
"Kadim Orman","Karanlık Orman","Taş Ocağı","Derin Maden","Ejder Dağı",
"Aynalı Göl","Fırtına Kıyısı","Çayırlar","Av Toprakları",
"Haydut Pususu","Yaralı Gezgin","Kayıp Sandık",
]);
// Kısmen bağlananlar: her tarayıcıda "yapılıyor" görünür (tamam'ı ezmez)
const SERVER_WIP=new Set([]);
data.forEach(c=>c.items.forEach(i=>{
if(SERVER_DONE.has(i.name)) i.state="tamam";
else if(SERVER_WIP.has(i.name) && i.state!=="tamam") i.state="yapiliyor";
}));
save(data);
return data;
}
function save(d){ localStorage.setItem(KEY,JSON.stringify(d)); }
let DATA=load();
let FILT="all";
// ---- Giriş + satırdan upload (up_<asset adı>.<ext> olarak kaydedilir) ----
const API="https://api.stepstead.com";
let TOKEN=localStorage.getItem("ss_upload_token")||"";
let ADMIN=false;
let PENDING=null; // {c,i} — hangi asset için dosya seçiliyor
function toast(msg,ms=3500){
const t=document.getElementById("toast");
t.textContent=msg; t.classList.add("on");
clearTimeout(t._tm); t._tm=setTimeout(()=>t.classList.remove("on"),ms);
}
function updateAuthUI(){
document.getElementById("authBtn").textContent=ADMIN?"🚪 Çıkış":"🔑 Giriş";
if(window.ssNavRefresh)window.ssNavRefresh();
}
async function checkAdmin(){
if(!TOKEN){ADMIN=false;updateAuthUI();return;}
try{
const r=await fetch(API+"/admin/uploads",{headers:{Authorization:"Bearer "+TOKEN}});
ADMIN=r.ok;
if(!r.ok&&(r.status===401||r.status===403)){TOKEN="";localStorage.removeItem("ss_upload_token");}
}catch(e){ADMIN=false;}
updateAuthUI(); render();
}
async function doLogin(){
const m=document.getElementById("lgMsg"); m.textContent="…";
try{
const r=await fetch(API+"/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({email:document.getElementById("lgEmail").value.trim(),
password:document.getElementById("lgPass").value})});
const b=await r.json().catch(()=>({}));
if(r.ok&&b.access_token){
TOKEN=b.access_token; localStorage.setItem("ss_upload_token",TOKEN);
m.textContent=""; document.getElementById("loginCard").classList.add("hide");
await checkAdmin();
if(!ADMIN) toast("⚠ Bu hesabın yükleme yetkisi yok");
}else m.textContent=b.detail||"Giriş başarısız";
}catch(e){ m.textContent="Bağlantı hatası"; }
}
async function uploadFor(c,i,file){
if(!file) return;
const it=DATA[c].items[i];
toast("⏳ Yükleniyor: "+it.name);
const fd=new FormData();
fd.append("file",file);
fd.append("target_name",it.name);
try{
const r=await fetch(API+"/admin/upload",{method:"POST",
headers:{Authorization:"Bearer "+TOKEN},body:fd});
const b=await r.json().catch(()=>({}));
if(r.ok){
if(it.state!=="tamam") it.state="yapiliyor"; // kontrol bekliyor
save(DATA); render();
toast("✅ Yüklendi: "+b.filename+" — kontrol edilecek");
}else toast("⚠ "+(b.detail||("Hata "+r.status)));
}catch(e){ toast("⚠ Bağlantı hatası"); }
}
document.getElementById("authBtn").onclick=()=>{
if(ADMIN){
TOKEN=""; localStorage.removeItem("ss_upload_token");
ADMIN=false; updateAuthUI(); render(); toast("Çıkış yapıldı");
}else document.getElementById("loginCard").classList.toggle("hide");
};
document.getElementById("lgBtn").onclick=doLogin;
document.getElementById("lgPass").addEventListener("keydown",e=>{if(e.key==="Enter")doLogin();});
const upInput=document.createElement("input");
upInput.type="file"; upInput.accept=".png,.jpg,.jpeg,.webp,.svg,.json,.zip"; upInput.hidden=true;
document.body.appendChild(upInput);
upInput.onchange=e=>{
if(PENDING) uploadFor(PENDING.c,PENDING.i,e.target.files[0]);
PENDING=null; upInput.value="";
};
function visible(it){ return FILT==="all" || it.dim===FILT; }
function render(){
const root=document.getElementById("root");
root.innerHTML="";
let total=0,done=0;
DATA.forEach((cat,ci)=>{
const shown=cat.items.filter(visible);
const cTotal=shown.length;
const cDone=shown.filter(i=>i.state==="tamam").length;
const cWip=shown.filter(i=>i.state==="yapiliyor").length;
const cEksik=cTotal-cDone-cWip;
const c2=cat.items.filter(i=>i.dim==="2d").length;
const c3=cat.items.filter(i=>i.dim==="3d").length;
total+=cTotal; done+=cDone;
const det=document.createElement("details");
det.className="cat";
if(FILT!=="all" && cTotal===0){ det.classList.add("hide"); }
const sum=document.createElement("summary");
sum.innerHTML=`<span class="chev"></span>${cat.cat}
<span class="count">
${c2?`<span class="pill d2">${c2} 2D</span>`:""}
${c3?`<span class="pill d3">${c3} 3D</span>`:""}
<span class="pill tamam">${cDone} ✓</span>
<span class="pill eksik">${cEksik} eksik</span>
</span>`;
det.appendChild(sum);
const tbl=document.createElement("table");
tbl.innerHTML=`<thead><tr><th>Asset</th><th>ıklama</th><th>Boyut</th><th>Durum</th><th></th></tr></thead>`;
const tb=document.createElement("tbody");
cat.items.forEach((it,ii)=>{
if(!visible(it)) return;
const tr=document.createElement("tr");
if(it.state==="tamam") tr.className="done";
tr.innerHTML=`<td class="name">${esc(it.name)}</td>
<td class="desc">${esc(it.desc||"")}</td>
<td><select class="dm ${DMCLS[it.dim]}" data-c="${ci}" data-i="${ii}">
${DIMS.map(d=>`<option value="${d}" ${d===it.dim?"selected":""}>${DMLBL[d]}</option>`).join("")}
</select></td>
<td><select class="st ${it.state}" data-c="${ci}" data-i="${ii}">
${STATES.map(s=>`<option value="${s}" ${s===it.state?"selected":""}>${STLBL[s]}</option>`).join("")}
</select></td>
<td style="white-space:nowrap">${ADMIN?`<button class="up" data-c="${ci}" data-i="${ii}" title="Bu asset için görsel yükle">📤</button> `:""}<button class="del" data-c="${ci}" data-i="${ii}" title="Sil"></button></td>`;
tb.appendChild(tr);
});
const addtr=document.createElement("tr");
addtr.className="addrow";
addtr.innerHTML=`<td colspan="5">
<input class="n" placeholder="Yeni asset adı" data-add-n="${ci}">
<input class="d" placeholder="Açıklama (ops.)" data-add-d="${ci}">
<select data-add-dim="${ci}">${DIMS.map(d=>`<option value="${d}">${DMLBL[d]}</option>`).join("")}</select>
<button class="btn" data-add="${ci}">+ Ekle</button></td>`;
tb.appendChild(addtr);
tbl.appendChild(tb);
det.appendChild(tbl);
root.appendChild(det);
});
const pct=total?Math.round(done/total*100):0;
document.getElementById("pbar").style.width=pct+"%";
const fl=FILT==="all"?"":` (${DMLBL[FILT]})`;
document.getElementById("plbl").textContent=`${done}/${total} tamamlandı${fl} — %${pct}`;
}
function esc(s){return (s+"").replace(/[&<>"]/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c]));}
document.addEventListener("change",e=>{
const c=+e.target.dataset.c, i=+e.target.dataset.i;
if(e.target.classList.contains("st")){
DATA[c].items[i].state=e.target.value; save(DATA); render();
}
if(e.target.classList.contains("dm")){
DATA[c].items[i].dim=e.target.value; save(DATA); render();
}
});
document.addEventListener("click",e=>{
if(e.target.dataset.filt){
FILT=e.target.dataset.filt;
document.querySelectorAll("[data-filt]").forEach(b=>b.classList.toggle("on",b.dataset.filt===FILT));
render(); return;
}
if(e.target.classList.contains("up")){
PENDING={c:+e.target.dataset.c,i:+e.target.dataset.i};
upInput.click(); return;
}
if(e.target.classList.contains("del")){
const c=+e.target.dataset.c, i=+e.target.dataset.i;
if(confirm("Bu asset satırı silinsin mi?")){ DATA[c].items.splice(i,1); save(DATA); render(); }
}
if(e.target.dataset.add!==undefined){
const c=+e.target.dataset.add;
const ni=document.querySelector(`[data-add-n="${c}"]`);
const di=document.querySelector(`[data-add-d="${c}"]`);
const dm=document.querySelector(`[data-add-dim="${c}"]`);
const name=ni.value.trim(); if(!name) return;
DATA[c].items.push({name,desc:di.value.trim(),dim:dm.value,state:"eksik"});
save(DATA); render();
document.querySelectorAll(".cat")[c].open=true;
}
});
document.getElementById("expBtn").onclick=()=>{
const blob=new Blob([JSON.stringify(DATA,null,2)],{type:"application/json"});
const a=document.createElement("a");
a.href=URL.createObjectURL(blob);
a.download="stepstead-assets.json"; a.click();
};
document.getElementById("impBtn").onclick=()=>document.getElementById("impFile").click();
document.getElementById("impFile").onchange=ev=>{
const f=ev.target.files[0]; if(!f) return;
const r=new FileReader();
r.onload=()=>{ try{ const d=JSON.parse(r.result);
if(Array.isArray(d)){ DATA=d; load.call&&0; DATA.forEach(c=>c.items.forEach(i=>{if(!i.dim)i.dim="2d";})); save(DATA); render(); } else alert("Geçersiz dosya"); }
catch(e){ alert("Okunamadı"); } };
r.readAsText(f);
};
render();
checkAdmin();
</script>
</body>
</html>