stepstead-web/assets/lore.html
jts dea01dd6a8 İlk commit: Stepstead web (statik panel + db)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:30:59 +03:00

451 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 — Lore Notları</title>
<style>
:root{--bg:#0f1419;--panel:#1a2230;--line:#2c3a4f;--txt:#e6edf3;--muted:#8b99a8;--gold:#e8b923;--ok:#2ea043;--err:#e5534b}
*{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.6}
.wrap{max-width:1240px;margin:0 auto;padding:20px 16px}
.cols{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start}
@media(max-width:900px){.cols{grid-template-columns:1fr}}
.summary{white-space:pre-wrap;word-wrap:break-word;background:#141c28;border:1px solid var(--line);border-radius:8px;padding:14px;min-height:200px;font-size:14px}
.summary h3{color:var(--gold);font-size:15px;margin:16px 0 6px;border-bottom:1px solid var(--line);padding-bottom:4px}
.summary h3:first-child{margin-top:0}
.summary p{margin:0 0 8px}
.colhead{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.colhead h2{font-size:15px;color:var(--gold);margin:0}
.colhead .count{margin-left:auto}
h1{font-size:20px;color:var(--gold);margin:0 0 2px}
.sub{color:var(--muted);font-size:13px;margin-bottom:16px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:16px;margin-bottom:14px}
input,button,textarea{font-size:14px;border-radius:6px;font-family:inherit}
input,textarea,select{background:#222d3d;color:var(--txt);border:1px solid var(--line);padding:9px 10px}
select{border-radius:6px;font-size:13px;font-family:inherit;max-width:220px}
.note .subj{background:#243247;color:#7fb3e8;border-radius:4px;padding:1px 7px;font-weight:600;cursor:pointer}
.note .subj:hover{color:var(--gold)}
input[type=email],input[type=password]{width:100%;margin-bottom:10px}
textarea{width:100%;min-height:90px;resize:vertical;line-height:1.5}
.btn{background:var(--gold);color:#1a1300;border:none;padding:9px 18px;font-weight:600;cursor:pointer}
.btn.sec{background:#2a3648;color:var(--txt)}
.btn.sm{padding:5px 10px;font-size:12px}
.btn.link{background:none;border:none;color:var(--muted);cursor:pointer;padding:4px 6px;font-size:12px}
.btn.link:hover{color:var(--gold)}
.btn.link.del:hover{color:var(--err)}
.msg{font-size:13px;margin-top:8px}
.msg.ok{color:var(--ok)} .msg.err{color:var(--err)}
.hide{display:none}
.toolbar{display:flex;gap:8px;align-items:center;margin-bottom:10px}
.toolbar input{flex:0 0 180px}
.note{border-top:1px solid var(--line);padding:12px 2px}
.note:first-child{border-top:none}
.note .meta{display:flex;gap:10px;align-items:center;font-size:11px;color:var(--muted);margin-bottom:6px}
.note .tag{background:#243247;color:var(--gold);border-radius:4px;padding:1px 7px;font-weight:600}
.author{color:#7fd0a0;font-weight:600}
.note .body{white-space:pre-wrap;word-wrap:break-word}
.bilingual{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media(max-width:640px){.bilingual{grid-template-columns:1fr}}
.bilingual .lang{border:1px solid var(--line);border-radius:6px;padding:8px 10px;background:#141c28}
.bilingual .lang .lbl{font-size:10px;color:var(--muted);font-weight:600;letter-spacing:.5px;margin-bottom:4px}
.bilingual .lang .txt{white-space:pre-wrap;word-wrap:break-word;font-size:13px}
.bilingual .lang.en{opacity:.9}
.note .acts{margin-left:auto;display:flex;gap:2px}
.count{color:var(--muted);font-size:12px}
a{color:var(--gold)}
.story{border-top:1px solid var(--line);padding:14px 2px}
.story:first-of-type{border-top:none}
.story h3{color:var(--gold);font-size:16px;margin:0 0 4px}
.story .meta{font-size:11px;color:var(--muted);margin-bottom:8px;display:flex;gap:10px;align-items:center}
.story .meta .acts{margin-left:auto;display:flex;gap:2px}
.story .body{white-space:pre-wrap;word-wrap:break-word;line-height:1.7}
.story textarea{min-height:260px}
#stBody{min-height:260px}
#stTitle{width:100%;margin-bottom:8px}
</style>
<script src="/nav.js?v=8" data-theme="dark" defer></script>
</head>
<body>
<div class="wrap">
<h1>📜 Lore Notları</h1>
<div class="sub">Özel — sadece admin. Küçük notlar al, sonra hikaye olarak derlenir.</div>
<div class="card" id="loginCard">
<input type="email" id="email" placeholder="E-posta" autocomplete="username">
<input type="password" id="pass" placeholder="Şifre" autocomplete="current-password">
<button class="btn" id="loginBtn">Giriş</button>
<div class="msg" id="loginMsg"></div>
</div>
<div id="app" class="hide">
<div style="display:flex;justify-content:flex-end;margin-bottom:10px">
<button class="btn sec sm" id="langBtn">🌐 English</button>
</div>
<div class="card">
<div class="toolbar">
<select id="newSubj" title="Bir kişi veya yerle ilişkilendir"></select>
<input type="text" id="newTag" placeholder="etiket (ops.) örn. Eldoria">
<span class="count" id="cnt"></span>
</div>
<textarea id="newBody" placeholder="Not yaz… (kısa fikir, olay, karakter, cümle — ne olursa)"></textarea>
<div style="margin-top:10px;display:flex;gap:8px;align-items:center">
<button class="btn" id="addBtn">+ Not Ekle</button>
<span style="color:var(--muted);font-size:12px">Ctrl/Cmd + Enter · yazınca otomatik çevrilir</span>
<div class="msg" id="addMsg" style="margin:0"></div>
</div>
</div>
<div class="card" id="listCard">
<div class="colhead"><h2>📝 Notlar</h2>
<select id="filterSubj" title="Konuya göre filtrele" style="margin-left:auto"></select>
</div>
<div id="notes"></div>
<div class="count" id="empty">Yükleniyor…</div>
</div>
<div class="card">
<div class="colhead"><h2>📖 Temel Hikaye</h2><span class="count" id="stCnt"></span></div>
<div id="stories"></div>
<div class="count" id="stEmpty">Yükleniyor…</div>
</div>
<div class="card">
<h2 style="font-size:15px;color:var(--gold);margin:0 0 10px">+ Yeni Bölüm</h2>
<input type="text" id="stTitle" placeholder="Bölüm başlığı (ops.) örn. Eldoria'nın Doğuşu">
<textarea id="stBody" placeholder="Uzun hikaye metnini buraya yaz…"></textarea>
<div style="margin-top:10px;display:flex;gap:8px;align-items:center">
<button class="btn" id="stAddBtn">+ Bölüm Ekle</button>
<span style="color:var(--muted);font-size:12px">yazınca otomatik çevrilir</span>
<div class="msg" id="stMsg" style="margin:0"></div>
</div>
</div>
</div>
</div>
<script>
const API="https://api.stepstead.com";
let TOKEN=localStorage.getItem("ss_upload_token")||"";
let notes=[];
let editId=null;
let ME={id:null,admin:false};
function canEdit(item){return ME.admin || (item.author_id!=null && item.author_id===ME.id);}
const $=id=>document.getElementById(id);
/* ---- Konu (kişi/yer) bağlama ---- */
let SUBJECTS={npc:[],place:[]};
let filterKey=""; // "", "npc:12", "place:3"
const KIND_TR={npc:"Kişiler",place:"Yerler"};
function subjKey(kind,id){return (kind&&id)?kind+":"+id:"";}
function subjLabel(kind,id){
const arr=SUBJECTS[kind]||[];const m=arr.find(x=>x.id===id);return m?m.label:null;
}
async function loadSubjects(){
const r=await api("/lore/subjects");
if(r.ok)SUBJECTS={npc:r.body.npc||[],place:r.body.place||[]};
buildSubjSelect($("newSubj"),"","",true,"— konu yok —");
buildFilterSelect();
}
// select'i optgroup'larla doldur. cur = "kind:id" seçili.
function buildSubjSelect(sel,curKind,curId,withNone,noneLabel){
if(!sel)return;
const cur=subjKey(curKind,curId);
sel.innerHTML="";
if(withNone){const o=document.createElement("option");o.value="";o.textContent=noneLabel||"— konu yok —";sel.appendChild(o);}
for(const kind of ["npc","place"]){
if(!SUBJECTS[kind].length)continue;
const og=document.createElement("optgroup");og.label=KIND_TR[kind];
for(const s of SUBJECTS[kind]){
const o=document.createElement("option");o.value=kind+":"+s.id;
o.textContent=s.label+(s.sub?` · ${s.sub}`:"");
if(o.value===cur)o.selected=true;
og.appendChild(o);
}
sel.appendChild(og);
}
}
function buildFilterSelect(){
const sel=$("filterSubj");if(!sel)return;
const prev=filterKey;sel.innerHTML="";
const o0=document.createElement("option");o0.value="";o0.textContent="Tüm konular";sel.appendChild(o0);
for(const kind of ["npc","place"]){
if(!SUBJECTS[kind].length)continue;
const og=document.createElement("optgroup");og.label=KIND_TR[kind];
for(const s of SUBJECTS[kind]){
const o=document.createElement("option");o.value=kind+":"+s.id;o.textContent=s.label;
if(o.value===prev)o.selected=true;og.appendChild(o);
}
sel.appendChild(og);
}
sel.onchange=()=>{filterKey=sel.value;render();};
}
function readSubj(sel){
const v=sel&&sel.value?sel.value:"";
if(!v)return {subject_kind:null,subject_id:null,subject_label:null};
const [kind,idStr]=v.split(":");const id=parseInt(idStr,10);
return {subject_kind:kind,subject_id:id,subject_label:subjLabel(kind,id)};
}
let LANG=localStorage.getItem("ss_lore_lang")||"tr";
function pickLang(item){return LANG==="en"?(item.body_en||item.body):item.body;}
function setLang(l){LANG=l;localStorage.setItem("ss_lore_lang",l);
$("langBtn").textContent=LANG==="tr"?"🌐 English":"🌐 Türkçe";
render();renderStories();}
async function api(path,opts={}){
opts.headers=Object.assign({"Content-Type":"application/json"},opts.headers,{Authorization:"Bearer "+TOKEN});
const r=await fetch(API+path,opts);
let body={};try{body=await r.json();}catch(e){}
return {ok:r.ok,status:r.status,body};
}
function esc(s){return (s||"").replace(/[&<>]/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;"}[c]));}
function fmtDate(iso){const d=new Date(iso);return d.toLocaleString("tr-TR",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"});}
async function login(){
$("loginMsg").textContent="…";
const r=await fetch(API+"/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({email:$("email").value.trim(),password:$("pass").value})});
const b=await r.json().catch(()=>({}));
if(r.ok&&b.access_token){TOKEN=b.access_token;localStorage.setItem("ss_upload_token",TOKEN);showApp();}
else{$("loginMsg").className="msg err";$("loginMsg").textContent=b.detail||"Giriş başarısız";}
}
async function showApp(){
const r=await api("/lore");
if(r.status===401||r.status===403){$("loginMsg").className="msg err";$("loginMsg").textContent="Yetki yok / oturum bitti";return;}
$("loginCard").classList.add("hide");$("app").classList.remove("hide");
$("langBtn").textContent=LANG==="tr"?"🌐 English":"🌐 Türkçe";
const me=await api("/auth/me");
if(me.ok){ME.id=me.body.id;ME.admin=me.body.is_admin||(me.body.permissions||[]).includes("*");}
await loadSubjects();
notes=r.ok?r.body:[];render();
await loadStories();
}
/* ---- Temel Hikaye ---- */
let stories=[],stEditId=null;
async function loadStories(){const r=await api("/lore/story");stories=r.ok?r.body:[];renderStories();}
function renderStories(){
const box=$("stories");box.innerHTML="";
$("stCnt").textContent=stories.length+" bölüm";
$("stEmpty").style.display=stories.length?"none":"block";
$("stEmpty").textContent="Henüz bölüm yok — aşağıdan ilk bölümü ekle.";
for(const s of stories){
const div=document.createElement("div");div.className="story";
if(stEditId===s.id){
div.innerHTML=`<input type="text" class="etitle" value="${esc(s.title||"")}" placeholder="Bölüm başlığı" style="width:100%;margin-bottom:8px">
<div class="lbl" style="font-size:10px;color:var(--muted);margin-bottom:3px">TÜRKÇE</div>
<textarea class="ebody">${esc(s.body)}</textarea>
<div class="lbl" style="font-size:10px;color:var(--muted);margin:8px 0 3px">ENGLISH</div>
<textarea class="ebodyen">${esc(s.body_en||"")}</textarea>
<div style="margin-top:8px;display:flex;gap:6px;align-items:center">
<button class="btn sm save">Kaydet</button>
<button class="btn sec sm retr">🌐 Yeniden Çevir</button>
<button class="btn sec sm cancel">Vazgeç</button>
<span class="msg emsg" style="margin:0"></span>
</div>`;
div.querySelector(".save").onclick=async()=>{
const body=div.querySelector(".ebody").value.trim();
if(!body)return;
const body_en=div.querySelector(".ebodyen").value.trim()||null;
const r=await api("/lore/story/"+s.id,{method:"POST",
body:JSON.stringify({title:div.querySelector(".etitle").value.trim()||null,body,body_en})});
if(!r.ok){alert("Kaydedilemedi");return;}
stEditId=null;await loadStories();
};
div.querySelector(".retr").onclick=async()=>{
const em=div.querySelector(".emsg");em.className="msg";em.textContent="Çeviriliyor…";
try{const p=await translatePair(div.querySelector(".ebody").value.trim());
div.querySelector(".ebody").value=p.tr;div.querySelector(".ebodyen").value=p.en;
em.className="msg ok";em.textContent="Çevrildi";}
catch(err){em.className="msg err";em.textContent="Olmadı: "+err.message;}
};
div.querySelector(".cancel").onclick=()=>{stEditId=null;renderStories();};
}else{
div.innerHTML=`${s.title?`<h3>${esc(s.title)}</h3>`:""}
<div class="meta"><span>${fmtDate(s.updated_at)}</span>
${s.author?`<span class="author">✍ ${esc(s.author)}</span>`:""}
${canEdit(s)?`<span class="acts">
<button class="btn link edit">düzenle</button>
<button class="btn link del delx">sil</button>
</span>`:""}</div>
<div class="body">${esc(pickLang(s))}</div>`;
if(canEdit(s)){
div.querySelector(".edit").onclick=()=>{stEditId=s.id;renderStories();};
div.querySelector(".delx").onclick=async()=>{
if(!confirm("Bölüm silinsin mi?"))return;
const r=await api("/lore/story/"+s.id+"/delete",{method:"POST"});
if(!r.ok){alert("Silinemedi");return;}
await loadStories();
};
}
}
box.appendChild(div);
}
}
async function loadNotes(){const r=await api("/lore");notes=r.ok?r.body:[];render();}
function render(){
const box=$("notes");box.innerHTML="";
const shown=filterKey?notes.filter(n=>subjKey(n.subject_kind,n.subject_id)===filterKey):notes;
$("cnt").textContent=filterKey?`${shown.length}/${notes.length} not`:notes.length+" not";
$("empty").style.display=shown.length?"none":"block";
$("empty").textContent=filterKey?"Bu konuda not yok.":"Henüz not yok.";
for(const n of shown){
const div=document.createElement("div");div.className="note";
if(editId===n.id){
div.innerHTML=`<div style="display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap">
<select class="esubj"></select>
<input type="text" class="etag" value="${esc(n.tag||"")}" placeholder="etiket" style="width:160px">
</div>
<div class="bilingual">
<div><div class="lbl" style="font-size:10px;color:var(--muted);margin-bottom:3px">TÜRKÇE</div>
<textarea class="ebody">${esc(n.body)}</textarea></div>
<div><div class="lbl" style="font-size:10px;color:var(--muted);margin-bottom:3px">ENGLISH</div>
<textarea class="ebodyen">${esc(n.body_en||"")}</textarea></div>
</div>
<div style="margin-top:8px;display:flex;gap:6px;align-items:center">
<button class="btn sm save">Kaydet</button>
<button class="btn sec sm retr">🌐 Yeniden Çevir</button>
<button class="btn sec sm cancel">Vazgeç</button>
<span class="msg emsg" style="margin:0"></span>
</div>`;
buildSubjSelect(div.querySelector(".esubj"),n.subject_kind,n.subject_id,true,"— konu yok —");
div.querySelector(".save").onclick=()=>saveEdit(n.id,div);
div.querySelector(".cancel").onclick=()=>{editId=null;render();};
div.querySelector(".retr").onclick=async(e)=>{
const em=div.querySelector(".emsg");em.className="msg";em.textContent="Çeviriliyor…";
try{const p=await translatePair(div.querySelector(".ebody").value.trim());
div.querySelector(".ebody").value=p.tr;div.querySelector(".ebodyen").value=p.en;
em.className="msg ok";em.textContent="Çevrildi";}
catch(err){em.className="msg err";em.textContent="Olmadı: "+err.message;}
};
}else{
const subjChip=n.subject_kind&&n.subject_id
? `<span class="subj" data-k="${subjKey(n.subject_kind,n.subject_id)}">${n.subject_kind==="npc"?"👤":"📍"} ${esc(n.subject_label||subjLabel(n.subject_kind,n.subject_id)||"?")}</span>`:"";
div.innerHTML=`<div class="meta">
${subjChip}
${n.tag?`<span class="tag">${esc(n.tag)}</span>`:""}
<span>${fmtDate(n.created_at)}</span>
${n.author?`<span class="author">✍ ${esc(n.author)}</span>`:""}
${canEdit(n)?`<span class="acts">
<button class="btn link edit">düzenle</button>
<button class="btn link del delx">sil</button>
</span>`:""}</div>
<div class="body">${esc(pickLang(n))}</div>`;
const sc=div.querySelector(".subj");
if(sc)sc.onclick=()=>{filterKey=sc.dataset.k;$("filterSubj").value=filterKey;render();};
if(canEdit(n)){
div.querySelector(".edit").onclick=()=>{editId=n.id;render();};
div.querySelector(".delx").onclick=()=>delNote(n.id);
}
}
box.appendChild(div);
}
}
async function add(){
const input=$("newBody").value.trim();
if(!input){return;}
$("addMsg").className="msg";$("addMsg").textContent="Çeviriliyor…";
let tr,en;
try{const p=await translatePair(input);tr=p.tr;en=p.en;}
catch(e){$("addMsg").className="msg err";$("addMsg").textContent="Çeviri olmadı: "+e.message;return;}
$("addMsg").textContent="Kaydediliyor…";
const subj=readSubj($("newSubj"));
const r=await api("/lore",{method:"POST",body:JSON.stringify(Object.assign(
{body:tr,body_en:en,tag:$("newTag").value.trim()||null},subj))});
if(!r.ok){$("addMsg").className="msg err";$("addMsg").textContent=r.body.detail||"Hata";return;}
$("newBody").value="";$("newTag").value="";$("newSubj").value="";
$("addMsg").className="msg ok";$("addMsg").textContent="Eklendi (TR + EN)";
setTimeout(()=>$("addMsg").textContent="",1500);
await loadNotes();
}
async function saveEdit(id,div){
const body=div.querySelector(".ebody").value.trim();
const body_en=div.querySelector(".ebodyen").value.trim()||null;
if(!body)return;
const subj=readSubj(div.querySelector(".esubj"));
const r=await api("/lore/"+id,{method:"POST",body:JSON.stringify(Object.assign(
{body,body_en,tag:div.querySelector(".etag").value.trim()||null},subj))});
if(!r.ok){alert("Kaydedilemedi");return;}
editId=null;await loadNotes();
}
async function delNote(id){
if(!confirm("Not silinsin mi?"))return;
const r=await api("/lore/"+id+"/delete",{method:"POST"});
if(!r.ok){alert("Silinemedi");return;}
await loadNotes();
}
/* ---- Çeviri (TR↔EN, otomatik yön) ---- */
function detectLang(t){
if(/[çğıöşüÇĞİÖŞÜ]/.test(t))return "tr";
const w=(t.toLowerCase().match(/\b(ve|bir|bu|için|ile|çok|ama|değil|ben|sen|olan|gibi|daha|kadar|olarak|sonra)\b/g)||[]).length;
return w>=2?"tr":"en";
}
function chunk(t,max=450){
const parts=t.split(/(?<=[.!?\n])\s+/);const out=[];let cur="";
for(const p of parts){
if((cur+" "+p).length>max){if(cur)out.push(cur);cur=p.length>max?p:p;
while(cur.length>max){out.push(cur.slice(0,max));cur=cur.slice(max);}}
else cur=cur?cur+" "+p:p;
}
if(cur)out.push(cur);return out;
}
async function mmTranslate(text,src,tgt){
const r=await fetch(`https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=${src}|${tgt}&de=joethesleeper84@gmail.com`);
const b=await r.json();
if(!b.responseData||!b.responseData.translatedText)throw new Error(b.responseDetails||"çeviri hatası");
return b.responseData.translatedText;
}
async function translateAll(text,src,tgt){
const outs=[];for(const c of chunk(text))outs.push(await mmTranslate(c,src,tgt));
return outs.join(" ");
}
// Girdiyi TR+EN çifte çevir; sol her zaman TR, sağ her zaman EN.
async function translatePair(input){
if(!input)throw new Error("boş metin");
const src=detectLang(input);
if(src==="tr")return {tr:input,en:await translateAll(input,"tr","en")};
return {en:input,tr:await translateAll(input,"en","tr")};
}
async function translateBox(taId,msgId){
const ta=$(taId),msg=$(msgId);
const text=ta.value.trim();
if(!text){msg.className="msg err";msg.textContent="Metin boş";return;}
const src=detectLang(text),tgt=src==="tr"?"en":"tr";
msg.className="msg";msg.textContent=`Çeviriliyor… (${src.toUpperCase()}${tgt.toUpperCase()})`;
try{
const outs=[];
for(const c of chunk(text))outs.push(await mmTranslate(c,src,tgt));
ta.value=text+"\n\n--- "+tgt.toUpperCase()+" ---\n"+outs.join(" ");
msg.className="msg ok";msg.textContent=`Çevrildi (${src.toUpperCase()}${tgt.toUpperCase()})`;
setTimeout(()=>msg.textContent="",2000);
}catch(e){msg.className="msg err";msg.textContent="Çeviri olmadı: "+e.message;}
}
$("langBtn").onclick=()=>setLang(LANG==="tr"?"en":"tr");
$("loginBtn").onclick=login;
$("pass").addEventListener("keydown",e=>{if(e.key==="Enter")login();});
$("addBtn").onclick=add;
$("newBody").addEventListener("keydown",e=>{if((e.ctrlKey||e.metaKey)&&e.key==="Enter")add();});
$("stAddBtn").onclick=async()=>{
const input=$("stBody").value.trim();
if(!input){$("stMsg").className="msg err";$("stMsg").textContent="Metin boş";return;}
$("stMsg").className="msg";$("stMsg").textContent="Çeviriliyor…";
let tr,en;
try{const p=await translatePair(input);tr=p.tr;en=p.en;}
catch(e){$("stMsg").className="msg err";$("stMsg").textContent="Çeviri olmadı: "+e.message;return;}
$("stMsg").textContent="Kaydediliyor…";
const r=await api("/lore/story",{method:"POST",
body:JSON.stringify({title:$("stTitle").value.trim()||null,body:tr,body_en:en})});
if(!r.ok){$("stMsg").className="msg err";$("stMsg").textContent=r.body.detail||"Hata";return;}
$("stTitle").value="";$("stBody").value="";
$("stMsg").className="msg ok";$("stMsg").textContent="Bölüm eklendi (TR + EN)";
setTimeout(()=>$("stMsg").textContent="",1500);
await loadStories();
};
if(TOKEN)showApp();
</script>
</body>
</html>