İ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

1
items/data/items.json Normal file

File diff suppressed because one or more lines are too long

158
items/index.html Normal file
View file

@ -0,0 +1,158 @@
<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
<title>Stepstead — Eşyalar</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
:root{
--bg:#f5f3ee; --card:#fff; --ink:#2d3a2e; --muted:#77806f; --accent:#3a5a40;
--line:#e4e0d6;
--common:#6b7280; --uncommon:#2e8b45; --rare:#2563c9; --epic:#8b3fc9; --legendary:#c9762e;
}
*{box-sizing:border-box}
body{margin:0;font-family:system-ui,sans-serif;background:var(--bg);color:var(--ink)}
header{background:var(--accent);color:#fff;padding:20px 16px;position:sticky;top:0;z-index:5;box-shadow:0 2px 12px rgba(0,0,0,.15)}
header .inner{max-width:1000px;margin:0 auto}
header h1{margin:0 0 2px;font-size:24px}
header h1 a{color:#fff;text-decoration:none}
header p{margin:0;font-size:13px;opacity:.85}
.controls{max-width:1000px;margin:16px auto 0;padding:0 16px;display:flex;flex-wrap:wrap;gap:8px;align-items:center}
#q{flex:1 1 220px;padding:10px 14px;border:1px solid var(--line);border-radius:8px;font-size:15px;background:var(--card)}
.row{max-width:1000px;margin:8px auto 0;padding:0 16px;display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.row .lbl{font-size:12px;color:var(--muted);margin-right:2px}
.chip{padding:7px 14px;border-radius:999px;border:1px solid var(--line);background:var(--card);cursor:pointer;font-size:13px;white-space:nowrap}
.chip.on{background:var(--accent);color:#fff;border-color:var(--accent)}
.chip.cls.on{background:#2f4a6b;border-color:#2f4a6b}
main{max-width:1000px;margin:16px auto 60px;padding:0 16px}
.stats{color:var(--muted);font-size:13px;margin:8px 2px 16px}
h2.group{font-size:19px;margin:28px 0 4px;color:var(--accent);border-bottom:2px solid var(--line);padding-bottom:6px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px}
.card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:12px 14px}
.card .name{font-weight:600;font-size:15px}
.card .meta{font-size:12px;color:var(--muted);margin:4px 0 6px;display:flex;gap:10px;flex-wrap:wrap}
.card .line{font-size:12.5px;line-height:1.5;margin-top:3px}
.card .line b{color:var(--muted);font-weight:500}
.tag{display:inline-block;border-radius:6px;padding:1px 7px;font-size:11.5px;margin:1px 3px 1px 0}
.tag.slot{background:#e8f0e9;color:var(--accent)}
.tag.cls{background:#e6ecf5;color:#2f4a6b}
.tag.all{background:#eef2ec;color:var(--muted)}
.tag.src{background:#f3ecdd;color:#8a6a2e}
.tag.eff{background:#e9f3f0;color:#2e7d6b}
.r-common{color:var(--common)} .r-uncommon{color:var(--uncommon)} .r-rare{color:var(--rare)}
.r-epic{color:var(--epic)} .r-legendary{color:var(--legendary)}
.empty{color:var(--muted);text-align:center;margin:48px 0}
</style>
<script src="/nav.js?v=8" data-theme="light" defer></script>
</head>
<body>
<header><div class="inner">
<h1><a href="/">Stepstead</a> · Eşyalar</h1>
<p>Tüm silah, giysi, alet ve maddeler — hangi sınıf kullanır, nereden gelir</p>
</div></header>
<div class="controls">
<input id="q" type="search" placeholder="Eşya ara… (örn. Kılıç, Asa, Bakır, Çanta)">
</div>
<div class="row" id="typechips"></div>
<div class="row" id="clschips"><span class="lbl">Sınıf:</span></div>
<main>
<div class="stats" id="stats"></div>
<div id="list"></div>
</main>
<script>
let DATA=null, activeType="Tümü", activeClass=null;
const norm=s=>s.toLocaleLowerCase("tr");
const RORDER={common:0,uncommon:1,rare:2,epic:3,legendary:4};
fetch("data/items.json").then(r=>r.json()).then(d=>{DATA=d;buildChips();render();});
function chip(text,on,cls,fn){
const c=document.createElement("button");
c.className="chip"+(cls?" cls":"")+(on?" on":"");
c.textContent=text; c.onclick=fn; return c;
}
function buildChips(){
const tb=document.getElementById("typechips");
const types=["Tümü",...Object.keys(DATA.types).sort((a,b)=>DATA.types[b]-DATA.types[a])];
for(const t of types){
tb.appendChild(chip(t==="Tümü"?`Tümü (${DATA.total})`:`${t} (${DATA.types[t]})`,t===activeType,false,()=>{
activeType=t;document.querySelectorAll("#typechips .chip").forEach(x=>x.classList.remove("on"));
event.target.classList.add("on");render();
}));
}
const cb=document.getElementById("clschips");
cb.appendChild(chip("Hepsi",true,true,()=>{
activeClass=null;document.querySelectorAll("#clschips .chip").forEach(x=>x.classList.remove("on"));
event.target.classList.add("on");render();
}));
for(const c of DATA.classes){
cb.appendChild(chip(c,false,true,()=>{
activeClass=c;document.querySelectorAll("#clschips .chip").forEach(x=>x.classList.remove("on"));
event.target.classList.add("on");render();
}));
}
document.getElementById("q").addEventListener("input",render);
}
// sınıf filtresi: eşyayı verilen sınıf kullanabiliyor mu?
function usableBy(it,cls){
if(!cls)return true;
if(it.classes.length===0)return true; // herkese açık
return it.classes.includes(cls);
}
function matches(it,q){
if(!q)return true;
return norm(it.name).includes(q)||norm(it.type).includes(q)||
(it.slot&&norm(it.slot).includes(q))||it.classes.some(c=>norm(c).includes(q))||
it.gather.some(g=>norm(g.loc).includes(q)||norm(g.skill).includes(q));
}
function card(it){
const slot=it.slot?`<span class="tag slot">${it.slot}${it.two_handed?" (çift el)":""}</span>`:"";
let cls="";
if(it.slot_code&&it.slot_code!=="tool"){
cls=it.classes.length===0
? `<span class="tag all">Tüm sınıflar</span>`
: it.classes.map(c=>`<span class="tag cls">${c}</span>`).join("");
}
const src=[];
it.gather.forEach(g=>src.push(`<span class="tag src">📍 ${g.loc} · ${g.skill}</span>`));
if(it.craftable)src.push(`<span class="tag src">🔨 Üretilebilir</span>`);
const eff=it.effect&&it.effect.type==="steps"?`<span class="tag eff">+${it.effect.amount} adım</span>`:"";
const clsLine=(it.slot_code&&it.slot_code!=="tool")?`<div class="line"><b>Kullanabilen:</b> ${cls}</div>`:"";
const srcLine=src.length?`<div class="line"><b>Kaynak:</b> ${src.join(" ")}</div>`:"";
const descLine=it.desc?`<div class="line" style="color:var(--muted)">${it.desc}</div>`:"";
return `<div class="card">
<div class="name"><span class="r-${it.rarity}">${it.name}</span> ${slot} ${eff}</div>
<div class="meta"><span>${it.type}</span><span>🪙 ${it.value}</span></div>
${clsLine}${srcLine}${descLine}
</div>`;
}
function render(){
const q=norm(document.getElementById("q").value.trim());
const list=document.getElementById("list");
let rows=DATA.items.filter(it=>
(activeType==="Tümü"||it.type===activeType)&&usableBy(it,activeClass)&&matches(it,q));
document.getElementById("stats").textContent=
`${rows.length} eşya`+(activeClass?` · ${activeClass} kullanabilir`:"");
if(!rows.length){list.innerHTML='<div class="empty">Sonuç yok.</div>';return;}
const byType={};
for(const it of rows)(byType[it.type]??=[]).push(it);
let html="";
for(const t of Object.keys(byType).sort((a,b)=>byType[b].length-byType[a].length)){
html+=`<h2 class="group">${t} (${byType[t].length})</h2>`;
const sorted=byType[t].sort((a,b)=>
(RORDER[a.rarity]-RORDER[b.rarity])||a.name.localeCompare(b.name,"tr"));
html+=`<div class="grid">${sorted.map(card).join("")}</div>`;
}
list.innerHTML=html;
}
</script>
</body>
</html>