📜 Guide

Popmundo quest rehberi — guide modu, adım takibi, log ve zamanlayıcı.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         📜 Guide
// @name:en      📜 Guide
// @name:pt-BR   📜 Guide
// @namespace    popmundo.guide
// @version      2.1
// @description  Popmundo quest rehberi — guide modu, adım takibi, log ve zamanlayıcı.
// @description:en  Popmundo quest guide — guide mode, step navigation, log and timer.
// @description:pt-BR  Guia de quests do Popmundo — modo guia, navegação por etapas, log e timer.
// @author       luke-james-gibson
// @license      MIT
// @id           568413
// @match        https://*.popmundo.com/*
// @homepageURL  https://rentry.org/questrunner
// @supportURL   https://rentry.org/kobequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        unsafeWindow
// ==/UserScript==

(function () {
    'use strict';

    // ─── POPCONTROL DISABLE CHECK ───────
    try { const _ppc = JSON.parse(localStorage.getItem('ppc_enabled')||'{}'); if (_ppc['guide'] === false) return; } catch {}

    window.QR = {};
    const QR = window.QR;

    // EVENTS
    const _ev = {};
    QR.on   = (e,fn)   => { (_ev[e]=_ev[e]||[]).push(fn); };
    QR.off  = (e,fn)   => { if(_ev[e]) _ev[e]=_ev[e].filter(f=>f!==fn); };
    QR.emit = (e,...a) => { (_ev[e]||[]).forEach(fn=>{ try{fn(...a);}catch(err){} }); };

    // STORAGE
    const S = {
        get:  (k,d) => { try { const v=GM_getValue(k,null); return v!==null?JSON.parse(v):d; } catch(e){return d;} },
        set:  (k,v) => { try { GM_setValue(k,JSON.stringify(v)); } catch(e){} },
        del:  k     => { try { GM_deleteValue(k); } catch(e){} },
        keys: p     => { try { return (GM_listValues?GM_listValues():[]).filter(k=>k.startsWith(p)); } catch(e){return[];} },
    };
    QR.S = S;

    // LANG
    const _getLang = () => {
        try { const m=document.cookie.match(/ppm_lang=([^;]+)/); return m?m[1]:'TR'; } catch(e){return 'TR';}
    };
    const _setLang = code => { document.cookie=`ppm_lang=${code};domain=.popmundo.com;path=/;max-age=31536000`; };
    const LANG = _getLang();
    const _i18n = {
        TR: {
            tabChars:'Karakterler', tabQuests:'Questler', tabLog:'Log', tabSettings:'Ayarlar',
            readMe:'📖 Beni Oku', kobe:'🗺️ Kobe', otherQuests:'🗺️ Diğer Questler',
            questDb:'📋 Quest Veritabanı',
            close:'Kapat',
            langLabel:'Dil',
        },
        EN: {
            tabChars:'Characters', tabQuests:'Quests', tabLog:'Log', tabSettings:'Settings',
            readMe:'📖 Read Me', kobe:'🗺️ Kobe', otherQuests:'🗺️ Other Quests',
            questDb:'📋 Quest Database',
            close:'Close',
            langLabel:'Language',
        },
        PT: {
            tabChars:'Personagens', tabQuests:'Quests', tabLog:'Log', tabSettings:'Configurações',
            readMe:'📖 Leia-me', kobe:'🗺️ Kobe', otherQuests:'🗺️ Outros Quests',
            questDb:'📋 Banco de Quests',
            close:'Fechar',
            langLabel:'Idioma',
        },
    };
    const _clMA = (() => { try { const v = localStorage.getItem('ppc_lc_guide'); return v ? JSON.parse(v) : null; } catch { return null; } })();
    const t = k => (_clMA && _clMA[k]) ? _clMA[k] : ((_i18n[LANG]||_i18n.TR)[k] || (_i18n.TR)[k] || k);

    // LOGGING
    const LOG_KEY  = c => 'qr_log_'  + c;
    const norm = s => s.trim().toLowerCase().replace(/\s+/g,' ');

    function addLog(char, type, msg, loc) {
        const log = S.get(LOG_KEY(char), []);
        log.push({ ts:Date.now(), type, msg, loc:loc||QR.dom?.locName?.()||'' });
        if (log.length>500) log.splice(0, log.length-500);
        S.set(LOG_KEY(char), log);
        QR.emit('log', char, { type, msg });
    }
    const addDLog = () => {};

    QR.log      = addLog;
    QR.dlog     = addDLog;
    QR.getLog   = c => S.get(LOG_KEY(c), []);
    QR.clearLog = c => S.del(LOG_KEY(c));

    // PAGE / CHAR
    QR.page = {
        isCompass: () => /\/Locale\/Compass/i.test(location.href),
        isLocale:  () => /\/Locale/i.test(location.href),
        isMobile:  () => window.innerWidth<=768||/Android|iPhone|iPad/i.test(navigator.userAgent),
    };
    QR.char = {
        current: () => { const d=document.querySelector('[id$="ucCharacterBar_ddlCurrentCharacter"]'); return d?d.options[d.selectedIndex].text.trim():null; },
        all:     () => { const d=document.querySelector('[id$="ucCharacterBar_ddlCurrentCharacter"]'); return d?[...d.options].map(o=>o.text.trim()).filter(Boolean):[]; },
    };

    // CHARACTER STATE
    const ck = (c,k) => 'qr_c_'+c+'_'+k;
    const C = {
        getStep:         c     => S.get(ck(c,'step'),0),
        setStep:         (c,n) => S.set(ck(c,'step'),n),
        activeQuestId:   c     => S.get(ck(c,'questId'),null)||S.get(ck(c,'assigned'),null),
        getQuestId:      c     => S.get(ck(c,'questId'),null),
        setQuestId:      (c,q) => S.set(ck(c,'questId'),q),
        getAssigned:     c     => S.get(ck(c,'assigned'),null),
        setAssigned:     (c,q) => S.set(ck(c,'assigned'),q),
        getStopMode:     c     => S.get(ck(c,'stopmode'),'ask'),
        setStopMode:     (c,v) => S.set(ck(c,'stopmode'),v),
        getPassageTs:    c     => S.get(ck(c,'passageTs'),null),
        setPassageTs:    (c,v) => S.set(ck(c,'passageTs'),v),
        getPassageMin:   c     => S.get(ck(c,'passageMin'),null),
        setPassageMin:   (c,v) => S.set(ck(c,'passageMin'),v),
        getQuestStartTs: c     => S.get(ck(c,'questStartTs'),null),
        setQuestStartTs: (c,v) => S.set(ck(c,'questStartTs'),v),
        getCheckpoints:  c     => S.get(ck(c,'checkpoints'),[]),
        addCheckpoint:   (c,d) => { const cps=S.get(ck(c,'checkpoints'),[]); cps.push({...d,ts:Date.now()}); S.set(ck(c,'checkpoints'),cps); },
        resumeFromCheckpoint: (c,labelOrIdx) => {
            const quest=QR.quests?.get(C.activeQuestId(c)); if(!quest) return false;
            let target=-1, cpCount=0;
            quest.sequence.forEach((s,i) => {
                if(s.type!=='checkpoint') return;
                if(typeof labelOrIdx==='number'?cpCount===labelOrIdx:s.label===labelOrIdx) target=i+1;
                cpCount++;
            });
            if(target<0) return false;
            C.setStep(c,target); QR.emit('resume',c,target,labelOrIdx); return true;
        },
        reset: c => ['step','questId','passageTs','passageMin','checkpoints','questStartTs'].forEach(k=>S.del(ck(c,k))),
        getNote: c     => S.get(ck(c,'note'),''),
        setNote: (c,v) => S.set(ck(c,'note'),v),
        all:   () => { const s=new Set(); S.keys('qr_c_').forEach(k=>{ const m=k.match(/^qr_c_(.+)_\w+$/); if(m) s.add(m[1]); }); return [...s]; },
    };
    QR.C = C;

    // DOM — SVG + onCompassLoad fallback
    const VALID_DIRS = new Set(['North','NorthEast','NorthWest','South','SouthEast','SouthWest','East','West','Up','Down']);

    function parseCompassScript() {
        const result = { active:{}, inactive:[] };
        for (const s of document.querySelectorAll('script')) {
            if (!s.textContent.includes('onCompassLoad')) continue;
            const activeRe   = /activateCompassObject\("([^"]+)",\s*"([^"]+)"/g;
            const inactiveRe = /deactivateCompassObject\("([^"]+)"/g;
            let m;
            while ((m=activeRe.exec(s.textContent))!==null)   result.active[m[1]]=m[2];
            while ((m=inactiveRe.exec(s.textContent))!==null) result.inactive.push(m[1]);
        }
        return result;
    }

    let _compassCache = null;
    function getCompassData() { if (!_compassCache) _compassCache=parseCompassScript(); return _compassCache; }
    QR.on('map_updated', () => { _compassCache=null; });

    QR.dom = {
        moveEl: dir => {
            const svgEl=document.querySelector('#compass g#'+dir+'.fe-move');
            if (svgEl) return svgEl;
            const data=getCompassData();
            if (data.active[dir]) return { getAttribute:(attr)=>attr==='data-lc'?data.active[dir]:null, _synthetic:true };
            return null;
        },
        activeDirections: () => {
            const svgDirs=[...document.querySelectorAll('#compass g.fe-move')].map(el=>el.id).filter(id=>VALID_DIRS.has(id));
            if (svgDirs.length) return svgDirs;
            return Object.keys(getCompassData().active).filter(d=>VALID_DIRS.has(d));
        },
        getItems: () => {
            const items=[];
            document.querySelectorAll('[id$="lnkItem"]').forEach(link => {
                const row=link.closest('tr'); if(!row) return;
                const useBtn=row.querySelector('[id$="btnUse"]'); if(!useBtn) return;
                const name=link.textContent.trim();
                const subEl=row.querySelector('.cText_Light em');
                const subtitle=subEl?subEl.textContent.trim():'';
                const fullLabel=subtitle?name+' — '+subtitle:name;
                items.push({ name, subtitle, fullLabel, useBtn });
            });
            return items;
        },
        isTraveling: () => /seyahat|varis|traveling|arriving/i.test(document.body?.innerText||''),
        locName:     () => { const e=document.querySelector('h1'); return e?e.textContent.trim():null; },
    };

    // DIRECTION CLICK
    function clickDirection(char, dir) {
        const el=QR.dom.moveEl(dir), lc=el?.getAttribute('data-lc'), loc=QR.dom.locName();
        if (el && !el._synthetic) {
            try {
                el.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true,view:unsafeWindow}));
                addLog(char,'move',(QR.dirArrow?.(dir)||dir)+' '+dir,loc);
                if(mapMode(char)) mapAppend(char,{type:'move',dirs:[dir]});
                return true;
            } catch(e) {}
        }
        if (lc) {
            addLog(char,'move',(QR.dirArrow?.(dir)||dir)+' '+dir+' (nav)',loc);
            if(mapMode(char)) mapAppend(char,{type:'move',dirs:[dir]});
            location.href='/World/Popmundo.aspx/Locale/'+lc;
            return true;
        }
        return false;
    }

    // ITEM MATCHING
    function matchItem(step, items) {
        const loc=QR.dom.locName(), normLoc=norm(loc||'');
        const isWild=!step.item||step.item==='?';
        if (items.length===1 && isWild) return {result:'match',item:items[0]};
        if (!isWild) {
            const needle=norm(step.item);
            const matched=items.filter(i=>{
                const ni=norm(i.name), ns=norm(i.subtitle||'');
                return ni.includes(needle)||needle.includes(ni)||ns.includes(needle)||needle.includes(ns);
            });
            if (matched.length===1) return {result:'match',item:matched[0]};
            if (matched.length>1)   return {result:'ambiguous',items:matched};
        }
        if (step.location) {
            const locOk=normLoc.includes(norm(step.location))||norm(step.location).includes(normLoc);
            if (!locOk) return {result:'wrong_location'};
        }
        if (items.length===1) return {result:'match',item:items[0]};
        if (items.length>1)   return {result:'ambiguous',items};
        return {result:'no_items'};
    }

    // ALERT
    const Alert = {
        _ac:null, _muteUntil:0, _intervals:{},
        _ctx: () => { if(!Alert._ac) Alert._ac=new(window.AudioContext||window.webkitAudioContext)(); return Alert._ac; },
        sound: () => {
            if (Date.now()<Alert._muteUntil) return;
            try {
                const ac=Alert._ctx();
                [440,550,660,880,1100].forEach((f,i) => {
                    const osc=ac.createOscillator(), gain=ac.createGain();
                    osc.connect(gain); gain.connect(ac.destination);
                    osc.frequency.value=f; osc.type='sine';
                    const t=ac.currentTime+i*0.18;
                    gain.gain.setValueAtTime(0,t);
                    gain.gain.linearRampToValueAtTime(0.4,t+0.05);
                    gain.gain.linearRampToValueAtTime(0,t+0.16);
                    osc.start(t); osc.stop(t+0.18);
                });
            } catch(e){}
        },
        notify: (title,body) => {
            if (Notification.permission==='granted') new Notification(title,{body,icon:'/Static/Icons/popmundo.ico'});
            else if (Notification.permission!=='denied') Notification.requestPermission().then(p=>{ if(p==='granted') new Notification(title,{body}); });
        },
        fire: (char,msg) => {
            Alert._stopRepeat(char); Alert._ring(char,msg);
            let count=0;
            Alert._intervals[char]=setInterval(()=>{ count++; if(count>=5||Date.now()<Alert._muteUntil){Alert._stopRepeat(char);return;} Alert._ring(char,msg); },60000);
        },
        _ring: (char,msg) => {
            Alert.sound(); Alert.notify('Guide',msg);
            S.set('qr_alert_pulse',{ts:Date.now(),msg});
            QR.emit('alert_pulse',msg); addLog(char,'alert',msg);
        },
        _stopRepeat: (char) => { if(Alert._intervals[char]){clearInterval(Alert._intervals[char]);delete Alert._intervals[char];} },
        mute:    (char) => { Alert._muteUntil=Date.now()+60*60*1000; Alert._stopRepeat(char); QR.emit('alert_muted',char); },
        isMuted: ()     => Date.now()<Alert._muteUntil,
    };
    QR.alert = Alert;
    window.addEventListener('storage', e => {
        if(e.key==='qr_alert_pulse') { Alert.sound(); QR.emit('alert_pulse',JSON.parse(e.newValue||'{}').msg); }
    });

    // ── MAPPER ──────────────────────────────────────────────────────────────

    // DIR MAP
    const DIR = {
        N:'North', NE:'NorthEast', NW:'NorthWest',
        S:'South', SE:'SouthEast', SW:'SouthWest',
        E:'East',  W:'West', UP:'Up', DOWN:'Down',
        NORTH:'North', NORTHEAST:'NorthEast', NORTHWEST:'NorthWest',
        SOUTH:'South', SOUTHEAST:'SouthEast', SOUTHWEST:'SouthWest',
        EAST:'East',   WEST:'West', UPWARD:'Up', DOWNWARD:'Down',
        K:'North', KD:'NorthEast', KB:'NorthWest',
        G:'South', GD:'SouthEast', GB:'SouthWest',
        D:'East',  B:'West', Y:'Up', A:'Down',
        KUZEY:'North', KUZEYDOGU:'NorthEast', KUZEYBATI:'NorthWest',
        GUNEY:'South', GUNEYDOGU:'SouthEast', GUNEYBATI:'SouthWest',
        DOGU:'East',   BATI:'West', YUKARI:'Up', ASAGI:'Down',
        L:'East', O:'West', NO:'NorthWest', SO:'SouthWest', NL:'NorthEast', SL:'SouthEast',
        NORTE:'North', SUL:'South', LESTE:'East', OESTE:'West',
        NORDESTE:'NorthEast', NOROESTE:'NorthWest', SUDESTE:'SouthEast', SUDOESTE:'SouthWest',
        CIMA:'Up', ACIMA:'Up', BAIXO:'Down', ABAIXO:'Down',
    };
    const DIR_ARROWS = {
        North:'↑', NorthEast:'↗', East:'→', SouthEast:'↘',
        South:'↓', SouthWest:'↙', West:'←', NorthWest:'↖',
        Up:'⬆', Down:'⬇',
    };

    function cleanToken(t) {
        return t.toUpperCase()
            .replace(/Ğ/g,'G').replace(/Ş/g,'S').replace(/İ/g,'I').replace(/ı/g,'I')
            .replace(/Ü/g,'U').replace(/Ö/g,'O').replace(/Ç/g,'C')
            .replace(/[^A-Z]/g,'');
    }
    QR.dirArrow   = d => DIR_ARROWS[d]||d;
    QR.resolveDir = t => DIR[cleanToken(t)]||null;

    // PARSER
    function parseUseItem(raw) {
        const body=raw.slice(9).trim(); if(!body) return {item:null,location:null};
        const at=body.indexOf('@');
        if (at>=0) { const rawItem=body.slice(0,at).trim(); return {item:(!rawItem||rawItem==='?')?null:norm(rawItem),location:norm(body.slice(at+1))}; }
        const rawItem=body.trim(); return {item:(!rawItem||rawItem==='?')?null:norm(rawItem),location:null};
    }

    function parseDirToken(token, errors) {
        if (!token) return null;
        const bm=token.match(/^[\[\(](.+)[\]\)]$/);
        if (bm) { const dirs=bm[1].split('|').map(QR.resolveDir).filter(Boolean); if(dirs.length) return {type:'move',dirs}; errors&&errors.push('Bad branch: '+token); return null; }
        const dir=QR.resolveDir(token); if(dir) return {type:'move',dirs:[dir]};
        if (token.length>1&&!/^\d+$/.test(token)) errors&&errors.push('Unknown dir: "'+token+'"');
        return null;
    }

    function extractMin(line) { const m=line.match(/(\d+)\s*(min|dk)/i); return m?parseInt(m[1]):null; }

    function trySimpleLine(line, steps) {
        if (/^use\s+\S/i.test(line)&&!/^use_item/i.test(line)) {
            const loc=line.slice(4).trim(); if(loc){steps.push({type:'use_item',item:null,location:norm(loc)});return true;}
        }
        const minM=line.match(/^(\d+)\s*(?:minutes?|min|dk)$/i);
        if (minM) { steps.push({type:'passage',minutes:parseInt(minM[1])});return true; }
        if (/^arrival\s+/i.test(line)) {
            const label=line.slice(8).trim().replace(/\s+/g,'_').toUpperCase();
            steps.push({type:'checkpoint',label:'ARRIVAL_'+label}); return true;
        }
        return false;
    }

    function parseText(text) {
        const steps=[], errors=[];
        for (const raw of text.split('\n')) {
            const line=raw.trim();
            if (!line||/^(#|\/)/.test(line)) continue;
            if (/^move\s/i.test(line)) {
                line.slice(5).trim().split(/[\s,\-]+/).forEach(t=>{const s=parseDirToken(t,errors);if(s)steps.push(s);});
            } else if (/^use_item/i.test(line)) {
                steps.push({type:'use_item',...parseUseItem(line)});
            } else if (/^try_item/i.test(line)) {
                const body=line.slice(9).trim(); const base={type:'try_item'};
                if (!body) { steps.push({...base,item:null,location:null}); continue; }
                const at=body.indexOf('@');
                if (at>=0) { const rawItem=body.slice(0,at).trim(); steps.push({...base,item:(!rawItem||rawItem==='?')?null:norm(rawItem),location:norm(body.slice(at+1))}); }
                else { const rawItem=body.trim(); steps.push({...base,item:(!rawItem||rawItem==='?')?null:norm(rawItem),location:null}); }
            } else if (/^passage/i.test(line)) {
                steps.push({type:'passage',minutes:extractMin(line)});
            } else if (/^wait/i.test(line)) {
                steps.push({type:'wait',minutes:extractMin(line)});
            } else if (/^checkpoint/i.test(line)) {
                steps.push({type:'checkpoint',label:line.slice(10).trim()||null});
            } else if (/^require\s/i.test(line)) {
                const body=line.slice(8).trim(), pipe=body.indexOf('|');
                const item=pipe>=0?body.slice(0,pipe).trim():body;
                const description=pipe>=0?body.slice(pipe+1).trim():'';
                steps.push({type:'require',item,description});
            } else if (/^info\s/i.test(line)) {
                const body=line.slice(5).trim(), pipe=body.indexOf('|');
                const text=pipe>=0?body.slice(0,pipe).trim():body;
                const link=pipe>=0?body.slice(pipe+1).trim():'';
                steps.push({type:'info',text,link});
            } else if (/^goto\s/i.test(line)) {
                const body=line.slice(5).trim(), pipe=body.lastIndexOf('|');
                const label=pipe>=0?body.slice(0,pipe).trim():body;
                const locId=pipe>=0?body.slice(pipe+1).trim():'';
                steps.push({type:'goto',label,locId});
            } else if (/^branch\s/i.test(line)) {
                // branch <Dir> -> <CHECKPOINT>
                const m=line.match(/^branch\s+(\S+)\s*->\s*(.+)$/i);
                if (m) {
                    const dir=QR.resolveDir(m[1].trim()), cp=m[2].trim();
                    if (dir) steps.push({type:'branch',dir,checkpoint:cp});
                    else errors.push('branch: bad dir "'+m[1]+'"');
                } else errors.push('branch: bad syntax "'+line.slice(0,40)+'"');
            } else if (trySimpleLine(line,steps)) {
                // handled
            } else {
                const tokens=line.split(/[\s,\-]+/).filter(Boolean); let any=false;
                tokens.forEach(t=>{const s=parseDirToken(t,errors);if(s){steps.push(s);any=true;}});
                if (!any&&tokens.length) errors.push('Unknown: "'+line.slice(0,40)+'"');
            }
        }
        return {steps,errors};
    }
    QR.parseText = parseText;

    // QUEST STORE
    const QUEST_KEY='qr_quests';
    const QStore = {
        load:   ()        => S.get(QUEST_KEY,{}),
        save:   db        => S.set(QUEST_KEY,db),
        get:    id        => S.get(QUEST_KEY,{})[id]||null,
        delete: id        => { const db=S.get(QUEST_KEY,{}); delete db[id]; S.set(QUEST_KEY,db); },
        upsert: q         => {
            const db=S.get(QUEST_KEY,{});
            if(db[q.id]) q.versions=[...(db[q.id].versions||[]),{ts:Date.now(),text:db[q.id].text}];
            else q.versions=q.versions||[];
            db[q.id]=q; S.set(QUEST_KEY,db);
        },
        new: (name,text)  => { const {steps,errors}=parseText(text); return {id:'q_'+Date.now(),name,text,sequence:steps,versions:[],builtin:false,errors}; },
        copy:(srcId,name) => { const src=S.get(QUEST_KEY,{})[srcId]; return src?{...src,id:'q_'+Date.now(),name,versions:[],builtin:false}:null; },
    };
    QR.quests = QStore;

    // NORTH POLE SEED
    (function(){
        const db=QStore.load();
        const npSeedVersion=2;
        if(db['north-pole']&&(db['north-pole'].seedVersion||1)>=npSeedVersion) return;
        const NP=[
            '# ICY CAVERNS - Giriş','checkpoint ICY_ENTRANCE',
            'move North SouthEast East NorthEast','passage 16min','',
            '# ICY CAVERNS - Yol','checkpoint ICY_PATH_START',
            'move East SouthEast South SouthEast SouthEast Down South SouthWest South SouthEast South South SouthEast South SouthWest South SouthEast SouthWest SouthEast East SouthEast SouthWest West',
            'use_item çukur @ Icy Caverns','',
            '# ICY CAVERNS - Dönüş','checkpoint ICY_RETURN',
            'move East NorthEast NorthWest West NorthWest NorthEast NorthWest North NorthEast North NorthWest North North NorthWest North NorthEast North Up NorthWest NorthWest North NorthWest West',
            'passage 16min','',
            '# ICEPEAK MOUNTAIN - Giriş','checkpoint ICEPEAK_ENTRANCE',
            'move SouthWest West NorthWest West SouthWest West','passage 16min','',
            '# ICEPEAK MOUNTAIN - Yol','checkpoint ICEPEAK_PATH_START',
            'move NorthWest Up NorthWest Up NorthEast Up NorthEast Down SouthEast East Down NorthEast North Up NorthEast Up West Down NorthWest Up North Up Up',
            'use_item ? @ Icepeak Mountain','',
            '# ICEPEAK MOUNTAIN - Dönüş','checkpoint ICEPEAK_RETURN',
            'move Down Down South Down SouthEast Up East Down SouthWest Down South SouthWest Up West NorthWest Up SouthWest Down SouthWest Down SouthEast Down SouthEast',
            'passage 16min','',
            '# BLINDING BLIZZARD - Giriş','checkpoint BLIZZARD_ENTRANCE',
            'move East NorthEast East North NorthEast East NorthEast North','passage 16min','',
            '# BLINDING BLIZZARD - Yol','checkpoint BLIZZARD_PATH_START',
            'move North North East North East North East North East North East North East North East',
            'use_item nehir @ Blinding Blizzard','move North West','passage 16min','',
            '# FIREPLACE','checkpoint FIREPLACE',
            'move South SouthWest West North','use_item ? @ Fireplace','',
            '# TREE','checkpoint TREE',
            'move South SouthWest NorthWest North','use_item ? @ Tree',
        ].join('\n');
        const {steps}=parseText(NP);
        db['north-pole']={id:'north-pole',name:'❄️ North Pole',text:NP,sequence:steps,versions:[],builtin:true,seedVersion:2};
        QStore.save(db);
    })();

    // BUILTIN QUESTS SEED
    (function(){
        const db=QStore.load();
        const seeds=[
        {
            id:'antenora',
            name:'☠️ Antenora',
            text:[
'# Başlamadan önce envanterinde Gümüş Sikke olduğundan emin ol',
'checkpoint BASLANGIÇ',
'use_item Gümüş Sikke',
'passage 5min',
'# The Eternal Darkness',
'checkpoint ETERNAL_DARKNESS',
'move K K D G G D',
'# The Dark Pit',
'use_item Çukur (Siyah)',
'passage 5min',
'# Bottom of the Pit',
'checkpoint BOTTOM_OF_PIT',
'move K D K D G G D G G',
'# The Ferry — Feribot süren dolunca kullan',
'checkpoint THE_FERRY',
'use_item Feribot',
'passage 5min',
'# The Gates to Hades — Eastern Path to Antenora (8 adım)',
'checkpoint GATES_TO_HADES',
'move D D D D',
'# The Eastern Crossroads',
'move K D G G',
'# Antenora',
            ].join('\n'),
        },
        {
            id:'derinligin-sirlari',
            name:'🌊 Derinliğin Sırları',
            seedVersion:2,
            text:[
'require Dalış Ekipmanı | Spor Malzemeleri kategorisinden satın alın (%50 yer kaplar)',
'info Dalış serisi sırasıyla tamamlanmalı: 1. Derinliğin Sırları → 2. Unholy Diver → 3. Nautilus/Kraken Takımı | /Forum/Popmundo.aspx/Thread/2209668.1',
'# Başlangıç: Tromso Halk Plajı',
'checkpoint BASLANGIÇ',
'use_item Denizaltı',
'passage 12min',
'# The Helm of the Nautilus',
'checkpoint HELM',
'use_item Gemi Dümeni North Atlantic',
'passage 3min',
'# The Nautilus - North Atlantic',
'checkpoint NORTH_ATLANTIC_NAUTILUS',
'use_item Basınç Odası',
'passage 4min',
'# The North Atlantic',
'checkpoint NORTH_ATLANTIC',
'move K D D',
'# Mysterious Cave Opening',
'use_item Mağara Ağzı',
'passage 2min',
'# Underwater Cavern',
'checkpoint UNDERWATER_CAVERN',
'move G B B K B KB K KB K KB B B KB K K KD K K KB K KB K K K KD KD K KD D D GD GD D GD GD D GD GD G G',
'use_item Mağara Ağzı',
'passage 5min',
'# Dakkar\'s Camp',
'checkpoint DAKKARS_CAMP',
'move K GD D',
'# Dakkar\'s Emergency Supplies — Kutu 4 saatte bir kullanılabilir',
'# NOT: Yanında asistan/çocuk olması önerilir',
'use_item kutu',
'checkpoint OKSIJEN_ALINDI',
'move G G',
'use_item Oksijen Kaynağı',
'move GD',
'use_item Mağara Ağzı',
'passage 4min',
'# Dark Waters',
'checkpoint DARK_WATERS',
'move D K KB B K KB B K KD D D KD D',
'use_item Mağara Ağzı',
'passage 4min',
'# Dakkar\'s Abyss — UYARI: Oksijen hızla azalıyor! En az 2 Acil Durum Oksijen Tüpü hazır olsun',
'checkpoint DAKKARS_ABYSS',
'move A A A A A A A A A A A A A A A A A A A A',
'# Cold Abyss',
'checkpoint COLD_ABYSS',
'move A A A A A A A A A A A A A A A',
'# Dakkar\'s Ledge',
'checkpoint DAKKARS_LEDGE',
'move A',
'# Dakkar\'s Infinite Abyss',
'checkpoint DAKKARS_INFINITE_ABYSS',
'move A A A A A A A A A A A A A A A A A A A A A A A A A A A A A',
'# Dakkar\'s Point of no Return',
'checkpoint POINT_OF_NO_RETURN',
'move A',
'# Dakkar\'s Fall',
'checkpoint DAKKARS_FALL',
'move A A A A A A A A A A A A A A A A A A A A A A A A A A',
'# Dakkar\'s Landing',
'checkpoint DAKKARS_LANDING',
'move D G G B B GB B GB G GD G D KD KD D D KD D GD D D',
'# The Sunken Monolith',
'use_item Monolith',
            ].join('\n'),
        },
        {
            id:'unholy-diver',
            name:'🤿 Unholy Diver',
            seedVersion:2,
            text:[
'require Batık Monolitin Sırları | Derinliğin Sırları questinden elde edilir',
'info Dalış serisi: 2. Unholy Diver | /Forum/Popmundo.aspx/Thread/2212309.1',
'# Başlangıç: Tromso Halk Plajı',
'checkpoint BASLANGIÇ',
'use_item Denizaltı',
'passage 16min',
'# The Helm of the Nautilus',
'checkpoint HELM',
'use_item Gemi Dümeni Desert Island',
'passage 16min',
'# The Nautilus - Desert Island',
'checkpoint NAUTILUS_DESERT_ISLAND',
'use_item Desert Island',
'passage 9min',
'# Castaway Beach — Potkal alma',
'checkpoint CASTAWAY_BEACH',
'move GB G B K K KB K KD',
'# Abandoned Shelter',
'use_item Çalışma Masası',
'checkpoint POTKAL_ALINDI',
'# Zıpkın alma',
'move GB KB K KB K KD KD K K KD KD G',
'# Abandoned Raft',
'use_item Sal',
'checkpoint ZIPKIN_ALINDI',
'# Alyans alma',
'move K GB GB G G GB D KD KD D KD GD',
'# Dakkar\'s End',
'use_item mezar taşı',
'checkpoint ALYANS_ALINDI',
'# Black Lagoon\'a git',
'move KB KB D KD KD GD D',
'# To The Black Lagoon',
'use_item Patika',
'passage 5min',
'# Black Lagoon — Fener alma',
'checkpoint BLACK_LAGOON',
'move K KB KB K K K B G',
'# Dead Scuba Diver',
'use_item ceset',
'checkpoint FENER_ALINDI',
'# Sonraki patikaya git',
'move K D G G G GD GD G',
'use_item Patika',
'passage 5min',
'# Castaway Beach\'e dön',
'checkpoint PATIKA_2_BITTI',
'move B KB GB GB B GB GB GB GD G G GB GB G G D K KD',
'# Castaway Beach',
'use_item Denizaltı',
'passage 16min',
'# The Nautilus - Desert Island',
'checkpoint NAUTILUS_RETURN',
'use_item Basınç Odası',
'passage 5min',
'# The Shipwreck\'e git',
'checkpoint BASIN_SONRASI',
'move K D K D K D K D K D K D K D K D K',
'# The Shipwreck',
'use_item Merdiven Lower deck',
'passage 5min',
'move D D',
'use_item Kapı',
'passage 5min',
'# Captain\'s Quarters',
'use_item kutu',
'move K',
'use_item yatak',
'# Tebrikler! Üç Dişli Mızrak ve başarı!',
            ].join('\n'),
        },
        {
            id:'nautilus-takimi',
            name:'⚙️ Nautilus Takımı',
            seedVersion:2,
            text:[
'info Atlantis\'in Tacı başarısı tekrar alınabilir | /Forum/Popmundo.aspx/Thread/2339858.1',
'# Başlangıç: Telegrafbukta Beach',
'checkpoint BASLANGIÇ',
'use_item Denizaltı',
'passage 16min',
'# The Helm of the Nautilus',
'checkpoint HELM_OF_NAUTILUS',
'use_item Gemi Dümeni Kraken',
'passage 5min',
'# Helm of Nautilus — giriş: Torpido Yuvası (1. numara)',
'checkpoint HELM_GIRIS',
'move K B',
'# Torpedo Bay',
'use_item Torpido Yuvası',
'# Gemi Dümeni (FIRE TORPEDO) için 2. numaraya git',
'move D G',
'use_item Gemi Dümeni',
'checkpoint TORPEDO_ATESLENDI',
'# Nautilus Cargo Hold — İngiliz Anahtarı (3. numara)',
'move G G D',
'use_item kutu',
'checkpoint ANAHTAR_ALINDI',
'# Gear Mechanism — Batı sonra sürekli Güney (yaklaşık 15-20 adım)',
'# Gear Mechanism\'e varınca İleri → basabilirsin',
'checkpoint GEAR_ARAMA',
'move B G G G G G G G G G G G G G G G G G G G G',
'# Gear Mechanism',
'use_item İngiliz Anahtarı',
'# NOT: Başarı gelmezse tekrar kullan (20dk süresi var)',
'checkpoint ANAHTAR_KULLANILDI',
'# Nautilus Tower (5. numara)',
'move K K K K B',
'checkpoint NAUTILUS_TOWER',
'# MANUEL: Haritada 5. numara için belirtilen adımı uygula, sonra İleri → bas',
'# Neverending Caverns beneath the Sea',
'checkpoint NEVERENDING_CAVERNS',
'move K KD KB K B K K KB KD K K D GD D D KD KD K KB B KB K KD KB KD KD KD D K B K D KD K D',
'use_item Oksijen Kaynağı',
'move B K KB KB KD GD D KD K KB KB K B K KB KB K KD KB K KD KD KD KB K D',
'use_item Çukur',
'# Tebrikler! Atlantis\'in Tacı başarısı!',
            ].join('\n'),
        },
        {
            id:'kraken-takimi',
            name:'🦑 Kraken Takımı',
            seedVersion:2,
            text:[
'info Atlantis\'in Tacı başarısı tekrar alınabilir | /Forum/Popmundo.aspx/Thread/2339858.1',
'# Başlangıç: Telegrafbukta Beach',
'checkpoint BASLANGIÇ',
'use_item Denizaltı',
'passage 16min',
'# The Helm of the Nautilus',
'checkpoint HELM_OF_NAUTILUS',
'use_item Gemi Dümeni Kraken',
'passage 5min',
'# Helm of Nautilus — giriş: Nautilus Pipe Organ',
'checkpoint HELM_GIRIS',
'move B',
'# Nautilus Pipe Organ',
'use_item Org',
'checkpoint ORG_KULLANILDI',
'# Crew Quarters — Takunyalar',
'move D K D',
'use_item kutu',
'checkpoint TAKUNYALAR_ALINDI',
'# Gear Mechanism — Batı sonra sürekli Güney (yaklaşık 15-20 adım)',
'# Gear Mechanism\'e varınca İleri → basabilirsin',
'checkpoint GEAR_ARAMA',
'move B G G G G G G G G G G G G G G G G G G G G',
'# Gear Mechanism',
'use_item Takunyalar',
'# NOT: Başarı gelmezse tekrar kullan (10dk süresi var)',
'checkpoint TAKUNYALAR_KULLANILDI',
'# Nautilus Tower (5. numara)',
'move K K K K B',
'checkpoint NAUTILUS_TOWER',
'# MANUEL: Haritada 5. numara için belirtilen adımı uygula, sonra İleri → bas',
'# Neverending Caverns beneath the Sea',
'checkpoint NEVERENDING_CAVERNS',
'move K KD KB K B K K KB KD K K D GD D D KD KD K KB B KB K KD KB KD KD KD D K B K D KD K D',
'use_item Oksijen Kaynağı',
'move B K KB KB KD GD D KD K KB KB K B K KB KB K KD KB K KD KD KD KB K D',
'use_item Çukur',
'# Tebrikler! Atlantis\'in Tacı başarısı!',
            ].join('\n'),
        },
        {
            id:'mozaik-mucizeler',
            name:'🏛️ Mozaik Mucizeler',
            text:[
'# Başlangıç: Telegrafbukta Beach',
'checkpoint BASLANGIÇ',
'use_item Denizaltı (The Nautilus)',
'passage 12min',
'# The Helm of the Nautilus',
'checkpoint HELM',
'use_item Gemi Dümeni (Secrets of the Atlantic)',
'passage 12min',
'# The Nautilus - Atlantic Ocean',
'checkpoint NAUTILUS_ATLANTIC',
'use_item Sandal (Atlantic Ocean)',
'passage 12min',
'# Atlantic Ocean — SARKAÇ NAVIGASYONU BAŞLIYOR',
'checkpoint ATLANTIC_OCEAN',
'# MANUEL: Sarkaç\'ı kullanarak Ancient Buoy\'a ulaş',
'# Sarkaç\'ın gösterdiği yönde 50-60 adım git, tekrar kullan',
'# "Varış noktasına ulaştım" mesajı çıkınca Ancient Buoy\'dasin',
'# Ancient Buoy\'a varınca Merdiven\'i kullan — script otomatik devam eder',
'use_item Merdiven',
'passage 10min',
'# Sunken Ruins',
'checkpoint SUNKEN_RUINS',
'# Hece 1: Ἀτ',
'move D D K',
'use_item Mozaik Zemin (Ἀτ)',
'checkpoint AT_TAMAM',
'# Hece 2: λαν',
'move G B B B K K KB K KB',
'use_item Mozaik Zemin (λαν)',
'checkpoint LAN_TAMAM',
'# Hece 3: τίς',
'move GD G GD G G G GB GB G GD GB',
'use_item Mozaik Zemin (τίς)',
'checkpoint TIS_TAMAM',
'# Hece 4: νῆσ',
'move KD D GD D K K KD D KD K K K K KD D',
'use_item Mozaik Zemin (νῆσ)',
'checkpoint NIS_TAMAM',
'# Hece 5: ος',
'move B GB KB B B G GB G B B GB G G',
'use_item Mozaik Zemin (ος)',
'checkpoint OS_TAMAM',
'# Geçit — Atlantis Nesos',
'move K K KD D D K KB K KD K KD',
'use_item Geçit (Atlantis Nesos)',
'# Tebrikler! Mozaik Mucizeler başarısı!',
            ].join('\n'),
        },
        {
            id:'nec-plus-ultra',
            name:'🔱 Nec Plus Ultra',
            text:[
'# Ön koşul: Mozaik Mucizeler tamamlanmış olmalı',
'# Mozaik\'in son adımından (Geçit Atlantis Nesos) devam',
'checkpoint BASLANGIÇ',
'use_item Patika (Nec Plus Ultra)',
'move D',
'use_item Denizaltı (The Ancient Mariner)',
'passage 30min',
'# The Traveller\'s Cave Entrance',
'checkpoint CAVE_ENTRANCE',
'move GD D KD K',
'use_item Patika',
'passage 60min',
'checkpoint PATIKA_1_BITTI',
'move B KB K D KD',
'use_item Patika',
'passage 60min',
'checkpoint PATIKA_2_BITTI',
'move GB GB',
'use_item Patika',
'passage 180min',
'checkpoint PATIKA_3_BITTI',
'# The Waiting Traveller — Frank Blomdahl bekleniyor',
'move B G',
'checkpoint FRANK_BEKLENIYOR',
'# MANUEL: Frank 6 saatte bir gelir (TR: 08:19 / 14:19 / 20:19 / 06:19)',
'# Frank gelince Garip Kontrol Manivelası verir — script otomatik devam eder',
'use_item Garip Kontrol Manivelası',
'checkpoint MAKINE_YANINDA',
'# MANUEL: Makineden şu sırayla bas: 7 → 9 → - → 2 → 9 → START',
'# Karanlık Patika (Plato\'s Cave) belirince İleri → bas',
'checkpoint MAKINE_KOD1',
'use_item Karanlık Patika (Plato\'s Cave)',
'passage 10min',
'# Plato\'s Cave',
'checkpoint PLATOS_CAVE',
'use_item Pranga',
'checkpoint MÜZIK_ANISI_BEKLENIYOR',
'# MANUEL: 6 saatte bir Müzik Anısı gelir, otomatik çıkarsın — sonra İleri → bas',
'use_item Buton (RETURN)',
'passage 60min',
'checkpoint MAKINE_GERI',
'# MANUEL: Makineden şu sırayla bas: - → 1 → 1 → 0 → - → 3 → 1 → START',
'# Island of Atlantis\'e ışınlanınca İleri → bas',
'checkpoint MAKINE_KOD2',
'# Island of Atlantis',
'checkpoint ISLAND_OF_ATLANTIS',
'move K KB K KD KB KB K KD',
'use_item Patika',
'passage 60min',
'checkpoint ATLANTIS_PATIKA_1',
'move KD GD GD GB',
'use_item Patika',
'passage 180min',
'checkpoint ATLANTIS_PATIKA_2',
'move K KD D KD K KB B KB KD KB',
'use_item Patika (Atlantis Mountain Path)',
'passage 240min',
'# Throne of Poseidon',
'checkpoint ATLANTIS_MOUNTAIN',
'move K K',
'use_item Taht',
'passage 5min',
'checkpoint THRONE_OF_POSEIDON',
'use_item Org (Poseidon\'s Pipes)',
'# Org Anahtarı 05-SOL envanterine eklendi',
'checkpoint ORG_ANAHTARI_ALINDI',
'# Geri dönüş: Taht → G-G → Patika (4sa) → GD-GB-GD-D-GD-G-GB-B-GB-G → Patika (3sa)',
'# → KD-KB-KB-GB → Patika (1sa) → GB-G-GD-GD-GB-G-GD-G → Buton RETURN (1sa)',
'# → K-KB → Denizaltı The Ancient Mariner (30dk) → Underwater Cliff',
'use_item Taht',
'passage 5min',
'checkpoint DONUŞ_BAŞLADI',
'move G G',
'use_item Patika',
'passage 240min',
'checkpoint DONUŞ_PATIKA_1',
'move GD GB GD D GD G GB B GB G',
'use_item Patika',
'passage 180min',
'checkpoint DONUŞ_PATIKA_2',
'move KD KB KB GB',
'use_item Patika',
'passage 60min',
'checkpoint DONUŞ_PATIKA_3',
'move GB G GD GD GB G GD G',
'use_item Buton (RETURN)',
'passage 60min',
'checkpoint MAKINE_DONUŞ',
'move K KB',
'use_item Denizaltı (The Ancient Mariner)',
'passage 30min',
'# Underwater Cliff — UYARI: Oksijen hemen azalmaya başlar!',
'checkpoint UNDERWATER_CLIFF',
'move B',
'# Sunken Temple of Gods — HİÇBİR ŞEYE TIKLAMA',
'checkpoint SUNKEN_TEMPLE',
'move K K K',
'# Poseidon\'s Broken Pipes — Org tuşları sırasıyla',
'use_item Org (09-RE)',
'use_item Org (10-MI)',
'use_item Org (08-DO)',
'use_item Org (01-DO)',
'use_item Org Anahtarı (05-SOL)',
'# Tebrikler! Nec Plus Ultra başarısı, 4dp ve Mars Haritası!',
            ].join('\n'),
        },
        ];
        let changed=false;
        seeds.forEach(({id,name,text,seedVersion})=>{
            const ex=db[id];
            if(ex&&ex.builtin&&(ex.seedVersion||1)>=(seedVersion||1)) return;
            const {steps}=parseText(text);
            db[id]={id,name,text,sequence:steps,versions:[],builtin:true,seedVersion:seedVersion||1};
            changed=true;
        });
        if(changed) QStore.save(db);
    })();

    // ── ENGINE ───────────────────────────────────────────────────────────────

    function runStep(char) {
        const qid=C.activeQuestId(char); if(!qid) return;
        const quest=QStore.get(qid); if(!quest) return;
        const idx=C.getStep(char);
        if (idx>=quest.sequence.length) {
            addLog(char,'complete','Quest tamamlandı: '+quest.name);
            QR.emit('complete',char); return;
        }
        const step=quest.sequence[idx];
        QR.emit('step',char,step,idx);
        if      (step.type==='move')       _execMove(char,step,idx);
        else if (step.type==='use_item')   _execUseItem(char,step,idx);
        else if (step.type==='try_item')   _execTryItem(char,step,idx);
        else if (step.type==='passage')    _execPassage(char,step,idx);
        else if (step.type==='wait')       _execWait(char,step,idx);
        else if (step.type==='checkpoint') _execCheckpoint(char,step,idx);
        else if (step.type==='branch')     _execBranch(char,step,idx);
        else if (step.type==='goto')       _execGoto(char,step,idx);
        else if (step.type==='require')    _execRequire(char,step,idx);
        else if (step.type==='info')       _execInfo(char,step,idx);
    }

    function _execMove(char,step,idx) {
        const activeDirs=QR.dom.activeDirections();
        let moved=false;
        for (const dir of step.dirs) {
            if (!activeDirs.includes(dir)) continue;
            if (clickDirection(char,dir)) { C.setStep(char,idx+1); moved=true; break; }
        }
        if (!moved) {
            const msg='Yön yok: '+step.dirs.map(d=>(QR.dirArrow?.(d)||d)+' '+d).join('|')+' | Aktif: '+(activeDirs.length?activeDirs.join(', '):'hiçbiri');
            addLog(char,'error',msg); QR.emit('error',char,step,idx,msg);
        }
    }

    function _execUseItem(char,step,idx) {
        const items=QR.dom.getItems();
        if (!C.getQuestStartTs(char)) { C.setQuestStartTs(char,Date.now()); addLog(char,'timer_start','Sayaç başladı'); }
        const r=matchItem(step,items);
        if (r.result==='match') {
            C.setStep(char,idx+1); r.item.useBtn.click();
            addLog(char,'use_item','✓ '+r.item.name); QR.emit('used_item',char,r.item.name,idx);
        } else if (r.result==='wrong_location') {
            addLog(char,'error','Yanlış lokasyon: '+(step.location||'?')+' ≠ '+(QR.dom.locName()||'?'));
            QR.emit('use_item_ambiguous',char,step,idx,items.map(i=>i.fullLabel||i.name));
        } else {
            const label=r.result==='no_items'?'Sayfada item yok':'Item eşleşmedi: '+(step.item||'?');
            addLog(char,'error',label+' @ '+(QR.dom.locName()||'?'));
            QR.emit('use_item_ambiguous',char,step,idx,(r.items||items).map(i=>i.fullLabel||i.name));
        }
    }

    function _execTryItem(char,step,idx) {
        const items=QR.dom.getItems();
        if (!C.getQuestStartTs(char)) C.setQuestStartTs(char,Date.now());
        const r=matchItem(step,items);
        if (r.result==='match') {
            C.setStep(char,idx+1); r.item.useBtn.click();
            addLog(char,'use_item','✓ (try) '+r.item.name); QR.emit('used_item',char,r.item.name,idx);
        } else if (r.result==='ambiguous'||r.result==='no_match') {
            QR.emit('use_item_ambiguous',char,step,idx,(r.items||[]).map(i=>i.name));
        } else {
            addLog(char,'use_item','⊘ try_item — item yok, geçildi');
            C.setStep(char,idx+1);
        }
    }

    function _execBranch(char,step,idx) {
        const activeDirs=QR.dom.activeDirections();
        if (activeDirs.includes(step.dir)) {
            const ok=C.resumeFromCheckpoint(char,step.checkpoint);
            if (ok) {
                addLog(char,'branch','Dal: '+step.dir+' → '+step.checkpoint);
                QR.emit('checkpoint',char,step,idx,QR.dom.locName());
            } else {
                addLog(char,'error','Branch checkpoint bulunamadı: '+step.checkpoint);
                QR.emit('error',char,step,idx,'branch_checkpoint_missing');
            }
        } else {
            C.setStep(char,idx+1);
        }
    }

    function _execGoto(char,step,idx) {
        if (!step.locId) { C.setStep(char,idx+1); buildCompass(); return; }
        addLog(char,'move','🗺️ → '+(step.label||step.locId));
        C.setStep(char,idx+1);
        location.href='/World/Popmundo.aspx/Locale/MoveToLocale/'+step.locId;
    }

    QR.resolveItemChoice = (char,stepIdx,chosenName) => {
        const items=QR.dom.getItems();
        const found=items.find(i=>norm(i.fullLabel||i.name)===norm(chosenName)||norm(i.name)===norm(chosenName));
        if (!found) { QR.emit('error',char,null,stepIdx,'Seçilen item yok: '+chosenName); return; }
        C.setStep(char,stepIdx+1); found.useBtn.click();
        addLog(char,'use_item','✓ (seçim) '+found.name); QR.emit('used_item',char,found.name,stepIdx);
    };

    function _execPassage(char,step,idx) {
        if (!C.getQuestStartTs(char)) C.setQuestStartTs(char,Date.now());
        C.setStep(char,idx+1); C.setPassageTs(char,Date.now()); C.setPassageMin(char,step.minutes);
        if (!QR.dom.isTraveling()) {
            const items=QR.dom.getItems();
            if (items.length) items[0].useBtn.click();
        }
        addLog(char,'passage','Passage: '+(step.minutes||'?')+'dk');
        QR.emit('passage_start',char,step);
    }

    function _execWait(char,step,idx) {
        C.setStep(char,idx+1); C.setPassageTs(char,Date.now()); C.setPassageMin(char,step.minutes);
        addLog(char,'wait','Bekleme: '+(step.minutes||'?')+'dk'); QR.emit('passage_start',char,step);
    }

    function _execCheckpoint(char,step,idx) {
        const loc=QR.dom.locName();
        C.addCheckpoint(char,{label:step.label,stepIdx:idx,locName:loc}); C.setStep(char,idx+1);
        addLog(char,'checkpoint','📍 '+(step.label||'#'+idx)+' @ '+(loc||'?'));
        QR.emit('checkpoint',char,step,idx,loc);
    }

    function _execRequire(char,step,idx) {
        const key='qr_req_'+char+'_'+(C.activeQuestId(char)||'')+'_'+idx;
        if (sessionStorage.getItem(key)) { C.setStep(char,idx+1); return; }
        showRequirePopup(step,()=>{ sessionStorage.setItem(key,'1'); C.setStep(char,idx+1); buildCompass(); });
    }

    function _execInfo(char,step,idx) {
        const qid=C.activeQuestId(char)||'';
        const key='qr_info_'+qid+'_'+idx;
        if (S.get(key,false)) { C.setStep(char,idx+1); buildCompass(); return; }
        S.set(key,true);
        const quest=QStore.get(qid);
        const infoSteps=quest?quest.sequence.filter(s=>s.type==='info'):[step];
        showInfoModal(infoSteps.length?infoSteps:[step]);
        C.setStep(char,idx+1); buildCompass();
    }

    // PASSAGE WATCHER
    function watchPassage(char) {
        if (QR.dom.isTraveling()) return;
        const ts=C.getPassageTs(char); if(!ts) return;
        const min=C.getPassageMin(char)||0;
        const elapsed=Date.now()-ts;
        const early=elapsed<min*60000;
        C.setPassageTs(char,null); C.setPassageMin(char,null);
        addLog(char,'passage_end','Seyahat tamamlandı'+(early?' (erken varış)':''));
        if(!early){
            const quest=QStore.get(C.activeQuestId(char));
            const msg=char+(quest?' — '+quest.name+' questine':'')+' devam edebilirsin';
            Alert.fire(char,msg);
        }
        QR.emit('passage_end',char,{early});
    }

    QR.engine = { runStep, watchPassage, clickDirection };

    // MANUAL MOVE HOOK
    // Sayfa yenilenmeden önce sessionStorage'a yönü yaz, yeni sayfada oku
    (function(){
        const key='qr_pending_manual';
        const pending=sessionStorage.getItem(key);
        if(pending){
            sessionStorage.removeItem(key);
            try{
                const {char,dir}=JSON.parse(pending);
                if(char&&dir) {
                    addLog(char,'move',(QR.dirArrow?.(dir)||dir)+' '+dir+' (manuel)');
                    QR.emit('manual_move',char,dir);
                    // harita modu aktifse kayıt al
                    if(S.get('qr_mapmode_'+char,false)) mapAppend(char,{type:'move',dirs:[dir]});
                }
            }catch(e){}
        }
    })();

    function hookManualMoves() {
        if (!QR.page.isCompass()) return;
        document.querySelectorAll('#compass g.fe-move').forEach(el => {
            if (el._qrHooked) return;
            el._qrHooked = true;
            el.addEventListener('click', () => {
                const char=QR.char.current(); if(!char) return;
                const dir=el.id; if(!VALID_DIRS.has(dir)) return;
                sessionStorage.setItem('qr_pending_manual',JSON.stringify({char,dir}));
            }, true);
        });
    }

    // MAIN LOOP — 3–6s random delay
    function scheduleLoop() {
        setTimeout(() => {
            try {
                if (QR.page.isLocale()) {
                    const char=QR.char.current();
                    if (char && C.getPassageTs(char)) watchPassage(char);
                }
            } catch(e){}
            scheduleLoop();
        }, 3000+Math.random()*3000);
    }
    scheduleLoop();

    // ── UI ───────────────────────────────────────────────────────────────────

    const BG  = 'background:linear-gradient(160deg,#1a2f5e,#243d7a)';
    const BDR = 'border:1.5px solid rgba(255,255,255,.22);border-radius:8px';
    const FNT = 'font-family:Arial,sans-serif;font-size:12px;color:white';
    const inp = (x='') => 'width:100%;box-sizing:border-box;background:rgba(255,255,255,.12);color:white;border:1px solid rgba(255,255,255,.25);border-radius:6px;padding:6px;font-size:11px;'+x;
    const sec = (x='') => 'background:rgba(255,255,255,.07);border-radius:6px;padding:8px;margin-bottom:6px;'+x;
    const row = (x='') => 'display:flex;gap:6px;margin-bottom:6px;align-items:center;'+x;
    const btn = (bg,x='') => 'background:'+bg+';color:white;border:none;border-radius:6px;padding:6px 10px;font-size:11px;cursor:pointer;'+x;

    function mkEl(tag, style, html) {
        const e=document.createElement(tag);
        if(style) e.style.cssText=style;
        if(html!==undefined) e.innerHTML=html;
        return e;
    }

    // TOAST
    function notify(msg, color) {
        const n=mkEl('div',
            'position:fixed;top:18px;left:50%;transform:translateX(-50%);'+
            'background:'+(color||'#1a2f5e')+';color:white;padding:9px 16px;'+
            'border-radius:8px;font-weight:bold;font-size:13px;z-index:100002;'+
            'box-shadow:0 4px 12px rgba(0,0,0,.5);pointer-events:none;',msg);
        document.body.appendChild(n); setTimeout(()=>n.remove(),4000);
    }

    function stepLabel(s) {
        if(!s) return '—';
        if(s.type==='move')       return (QR.dirArrow?.(s.dirs[0])||'')+' '+s.dirs.join(' | ');
        if(s.type==='use_item')   return '🎒 '+(s.item||'?')+(s.location?' @ '+s.location:'');
        if(s.type==='try_item')   return '🎒? '+(s.item||'?')+(s.location?' @ '+s.location:'');
        if(s.type==='passage')    return '⛵ '+(s.minutes||'?')+'dk';
        if(s.type==='wait')       return '⏳ '+(s.minutes||'?')+'dk';
        if(s.type==='checkpoint') return '📍 '+(s.label||'');
        if(s.type==='goto')       return '🗺️ → '+(s.label||s.locId);
        if(s.type==='branch')     return '🔀 '+s.dir+' → '+s.checkpoint;
        if(s.type==='require')    return '⚠️ '+s.item+(s.description?' — '+s.description:'');
        if(s.type==='info')       return 'ℹ️ '+s.text+(s.link?' ['+s.link+']':'');
        return s.type;
    }

    function elapsedStr(char) {
        const ts=C.getQuestStartTs(char); if(!ts) return null;
        const d=Date.now()-ts, h=Math.floor(d/3600000), m=Math.floor((d%3600000)/60000);
        return h?h+'s '+m+'dk':m+'dk';
    }

    // PASSAGE TIMER
    let _timerInterval=null;
    function startTimer(char) {
        if(_timerInterval) clearInterval(_timerInterval);
        _timerInterval=setInterval(()=>{
            const el=document.getElementById('qrTimer'); if(!el){clearInterval(_timerInterval);_timerInterval=null;return;}
            const ts=C.getPassageTs(char), min=C.getPassageMin(char);
            if(!ts||!min){el.innerHTML='';clearInterval(_timerInterval);_timerInterval=null;return;}
            const left=ts+min*60000-Date.now();
            if(left<=0){el.innerHTML='<span style="color:#8BC34A;font-weight:bold;">✓ Hazır!</span>';clearInterval(_timerInterval);_timerInterval=null;return;}
            const m2=Math.floor(left/60000), s=Math.floor((left%60000)/1000);
            const eta=new Date(ts+min*60000);
            el.innerHTML='<span style="color:#FFD600;">⏳ '+m2+'m '+s.toString().padStart(2,'0')+'s → '+
                eta.getHours().toString().padStart(2,'0')+':'+eta.getMinutes().toString().padStart(2,'0')+'</span>';
        },1000);
    }

    // PULSE STYLE
    if (!document.getElementById('qrPulseStyle')) {
        const st=document.createElement('style'); st.id='qrPulseStyle';
        st.textContent='@keyframes qrPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.2)}}';
        document.head.appendChild(st);
    }
    QR.on('alert_pulse',()=>{
        const fb=document.getElementById('qrFloat'); if(!fb) return;
        fb.style.boxShadow='0 0 0 4px #f44336'; fb.style.animation='qrPulse 0.6s ease-in-out 4';
        setTimeout(()=>{if(fb){fb.style.boxShadow='';fb.style.animation='';}},3000);
    });

    // FLOATING BUTTON
    function createFloat() {
        if(document.getElementById('qrFloat')) return;
        const b=mkEl('button',
            'position:fixed;bottom:18px;right:14px;'+
            'background:linear-gradient(135deg,#1a2f5e,#2a5298);color:white;'+
            'border:2px solid rgba(255,255,255,.35);border-radius:50%;'+
            'width:46px;height:46px;font-size:20px;cursor:pointer;'+
            'z-index:9999;box-shadow:0 2px 10px rgba(0,0,0,.5);');
        b.id='qrFloat'; b.title='Guide'; b.textContent='🗺️';
        b.onclick=()=>{
            _dismissed=false;
            const p=document.getElementById('qrCompass');
            if(p) p.remove();
            else if(QR.page.isLocale()) buildCompass();
            else openAdmin('quests');
        };
        document.body.appendChild(b);
    }

    // DRAGGABLE
    function makeDraggable(panel, handle) {
        let ox,oy,drag=false;
        handle.addEventListener('mousedown',e=>{drag=true;ox=e.clientX-panel.offsetLeft;oy=e.clientY-panel.offsetTop;e.preventDefault();});
        document.addEventListener('mousemove',e=>{
            if(!drag) return;
            panel.style.left=Math.max(0,Math.min(window.innerWidth-panel.offsetWidth,e.clientX-ox))+'px';
            panel.style.top=Math.max(0,Math.min(window.innerHeight-panel.offsetHeight,e.clientY-oy))+'px';
        });
        document.addEventListener('mouseup',()=>{
            if(!drag) return; drag=false;
            S.set('qr_panel_pos',{x:parseInt(panel.style.left),y:parseInt(panel.style.top)});
        });
    }

    // DISMISSED FLAG — × ile kapanınca setInterval yeniden açmaz
    let _dismissed = false;

    // HARITA MODU
    const mapKey  = c => 'qr_maplog_'+c;
    const mapMode = c => S.get('qr_mapmode_'+c,false);

    function mapStart(char) {
        S.set('qr_mapmode_'+char,true);
        S.set(mapKey(char),[]);
        addLog(char,'map','🗺 Harita kaydı başladı');
        buildCompass();
    }
    function mapStop(char) {
        S.set('qr_mapmode_'+char,false);
        buildCompass();
    }
    function mapAppend(char, step) {
        const log=S.get(mapKey(char),[]);
        // move adımlarını birleştir
        if(step.type==='move' && log.length && log[log.length-1].type==='move') {
            log[log.length-1].dirs=[...log[log.length-1].dirs,...step.dirs];
        } else {
            log.push(step);
        }
        S.set(mapKey(char),log);
    }
    function mapToText(log) {
        return log.map(s=>{
            if(s.type==='move')     return 'move '+s.dirs.join(' ');
            if(s.type==='use_item') return 'use_item '+(s.item||'?')+(s.location?' @ '+s.location:'');
            if(s.type==='passage')  return 'passage '+(s.minutes||'?')+'min';
            if(s.type==='checkpoint') return 'checkpoint '+(s.label||'MAP_CP');
            return '';
        }).filter(Boolean).join('\n');
    }
    function mapSaveAsQuest(char) {
        const log=S.get(mapKey(char),[]); if(!log.length){notify('Harita boş','#f44336');return;}
        const name=prompt('Quest adı:'); if(!name) return;
        const text=mapToText(log);
        const {steps}=parseText(text);
        if(!steps.length){notify('Parse başarısız','#f44336');return;}
        QStore.upsert({...QStore.new(name,text),sequence:steps});
        S.set('qr_mapmode_'+char,false);
        S.del(mapKey(char));
        notify('"'+name+'" kaydedildi — '+steps.length+' adım','#4CAF50');
        buildCompass();
    }

    // Harita modunda her move/use_item'ı kaydet
    QR.on('manual_move',(char,dir)=>{ if(mapMode(char)) mapAppend(char,{type:'move',dirs:[dir]}); });
    QR.on('used_item',(char,name,idx)=>{
        if(!mapMode(char)) return;
        const loc=QR.dom.locName();
        mapAppend(char,{type:'use_item',item:name,location:loc});
    });
    QR.on('passage_start',(char,step)=>{ if(mapMode(char)) mapAppend(char,{type:'passage',minutes:step.minutes}); });

    // COMPACT MODE
    const getCompact = () => S.get('qr_compact', false);
    const setCompact = v  => S.set('qr_compact', v);

    // QUEST EDIT OVERLAY
    function openQuestEdit(qid) {
        document.getElementById('qrQEOv')?.remove();
        const quest=QStore.get(qid); if(!quest) return;
        let parsed=null;
        const ov=mkEl('div','position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:100003;display:flex;align-items:center;justify-content:center;');
        ov.id='qrQEOv';
        const box=mkEl('div',BG+';'+BDR+';'+FNT+';width:92%;max-width:520px;max-height:90vh;overflow-y:auto;padding:14px;');
        box.innerHTML=
            '<div style="font-weight:bold;font-size:13px;margin-bottom:8px;">✏️ Düzenle: '+quest.name+'</div>'+
            '<input id="qrQEName" style="'+inp('margin-bottom:6px;')+'" value="'+quest.name.replace(/"/g,'&quot;')+'">'+
            '<textarea id="qrQEText" rows="12" style="'+inp('resize:vertical;font-family:monospace;font-size:11px;margin-bottom:6px;')+'">'+(quest.text||'')+'</textarea>'+
            '<div id="qrQEOut" style="display:none;background:rgba(0,0,0,.25);border-radius:6px;padding:6px;font-size:10px;max-height:100px;overflow-y:auto;font-family:monospace;margin-bottom:6px;"></div>'+
            '<div style="display:flex;gap:6px;">'+
            '<button id="qrQEParse"  style="'+btn('#607D8B','flex:1;padding:7px;font-size:11px;')+'">Parse</button>'+
            '<button id="qrQESave"   style="'+btn('#4CAF50','flex:1;padding:7px;font-size:11px;')+'">Kaydet</button>'+
            '<button id="qrQECancel" style="'+btn('#888','padding:7px 14px;font-size:11px;')+'">İptal</button></div>';
        ov.appendChild(box); document.body.appendChild(ov);
        ov.onclick=e=>{if(e.target===ov)ov.remove();};
        box.querySelector('#qrQECancel').onclick=()=>ov.remove();
        box.querySelector('#qrQEParse').onclick=()=>{
            const {steps,errors}=QR.parseText(box.querySelector('#qrQEText').value); parsed=steps;
            const out=box.querySelector('#qrQEOut'); out.style.display='block';
            const LC2={use_item:'#FFD600',try_item:'#FFD600',passage:'#90CAF9',wait:'#90CAF9',checkpoint:'#CE93D8',branch:'#A5D6A7'};
            out.innerHTML=steps.map((s,i)=>'<div style="color:'+(LC2[s.type]||'white')+';padding:1px 0;">'+(i+1)+'. '+stepLabel(s)+'</div>').join('')+
                (errors.length?errors.map(e=>'<div style="color:#f44336;">⚠ '+e+'</div>').join(''):'')+
                '<div style="border-top:1px solid rgba(255,255,255,.15);margin-top:4px;padding-top:4px;opacity:.7;">'+steps.length+' adım</div>';
        };
        box.querySelector('#qrQESave').onclick=()=>{
            const name=box.querySelector('#qrQEName').value.trim(), text=box.querySelector('#qrQEText').value.trim();
            if(!name||!text){notify('Ad ve adımlar gerekli','#f44336');return;}
            if(!parsed){const r=QR.parseText(text);parsed=r.steps;}
            if(!parsed.length){notify('Geçerli adım yok','#f44336');return;}
            QStore.upsert({...QStore.get(qid),name,text,sequence:parsed});
            notify('"'+name+'" güncellendi','#4CAF50'); parsed=null; ov.remove(); buildCompass();
        };
    }

    // COMPASS / LOCALE PANEL
    function buildCompass() {
        if(!QR.page.isLocale()) return;
        document.getElementById('qrCompass')?.remove();
        const char=QR.char.current(); if(!char) return;

        const quest=QStore.get(C.activeQuestId(char));
        const stepIdx=C.getStep(char), total=quest?.sequence.length||0;
        const stopmode=C.getStopMode(char);
        const inPass=!!C.getPassageTs(char), complete=quest&&stepIdx>=total;
        const pct=total?Math.min(100,Math.round(stepIdx/total*100)):0;
        const pos=S.get('qr_panel_pos',{x:10,y:10});
        const elapsed=elapsedStr(char);
        const activeDirs=QR.dom.activeDirections(), muted=Alert.isMuted();
        const isMapping=mapMode(char);
        const mapLog=isMapping?S.get(mapKey(char),[]):[];
        const onCompass=QR.page.isCompass();
        const isCompact=getCompact();

        const infoSteps=quest?quest.sequence.filter(s=>s.type==='info'):[];

        const nextSteps=quest&&!complete?quest.sequence.slice(stepIdx,stepIdx+6):[];
        const curStep=nextSteps[0];

        // Big action button content
        let actionInner, actionClickable=!!curStep;
        if (curStep?.type==='move') {
            if (onCompass) {
                const wantedDirs=curStep.dirs;
                const fnDirs=wantedDirs.filter(d=>!activeDirs.includes(d));
                const fpDirs=activeDirs.filter(d=>!wantedDirs.includes(d));
                const dirsHtml=wantedDirs.map(d=>{
                    const ok=activeDirs.includes(d);
                    return '<span style="display:inline-flex;align-items:center;gap:4px;background:'+(ok?'rgba(46,125,50,.85)':'rgba(183,28,28,.85)')+
                        ';border-radius:6px;padding:5px 10px;margin:2px;white-space:nowrap;">'+
                        '<span style="font-size:22px;line-height:1;">'+(QR.dirArrow?.(d)||d)+'</span>'+
                        '<span style="font-size:12px;">'+d+'</span>'+
                        '<span style="font-size:11px;opacity:.8;">'+(ok?'✓':'✗')+'</span></span>';
                }).join('');
                const fpHtml=fpDirs.length
                    ?'<div style="margin-top:5px;padding-top:5px;border-top:1px solid rgba(255,255,255,.12);">'+
                      '<div style="font-size:9px;opacity:.4;margin-bottom:2px;">DİĞER YÖNLER</div>'+
                      fpDirs.map(d=>'<span style="display:inline-flex;align-items:center;gap:3px;background:rgba(230,81,0,.8);border-radius:5px;padding:3px 8px;font-size:11px;margin:1px;">'+
                          (QR.dirArrow?.(d)||d)+' '+d+'</span>').join('')+'</div>'
                    :'';
                actionInner='<div style="display:flex;flex-wrap:wrap;gap:1px;">'+dirsHtml+'</div>'+fpHtml;
            } else {
                actionInner='<div style="text-align:center;padding:6px 0;">'+
                    '<div style="font-size:11px;opacity:.6;margin-bottom:6px;">🧭 Yön adımı — pusulaya git</div>'+
                    '<div>'+curStep.dirs.map(d=>'<span style="margin:2px;padding:4px 10px;background:rgba(255,255,255,.15);border-radius:5px;font-size:13px;display:inline-block;">'+(QR.dirArrow?.(d)||d)+' '+d+'</span>').join('')+'</div>'+
                    '</div>';
                actionClickable=false;
            }
        } else if (curStep?.type==='goto') {
            actionInner='<div style="text-align:center;padding:4px 0;">'+
                '<div style="font-size:11px;opacity:.6;margin-bottom:4px;">📍 Konum</div>'+
                '<div style="font-size:14px;font-weight:bold;">🗺️ → '+(curStep.label||curStep.locId)+'</div></div>';
        } else if (curStep?.type==='require') {
            actionInner='<div style="text-align:center;padding:4px 0;">'+
                '<div style="color:#FF9800;font-size:12px;font-weight:bold;margin-bottom:5px;">⚠️ Gerekli Eşya</div>'+
                '<div style="font-size:14px;font-weight:bold;padding:6px 10px;background:rgba(255,152,0,.18);border-radius:6px;margin-bottom:'+(curStep.description?'4px':'0')+';">'+curStep.item+'</div>'+
                (curStep.description?'<div style="font-size:10px;opacity:.7;">'+curStep.description+'</div>':'')+
                '</div>';
        } else if (curStep?.type==='info') {
            actionInner='<div style="text-align:center;padding:4px 0;">'+
                '<div style="color:#42A5F5;font-size:12px;font-weight:bold;margin-bottom:5px;">ℹ️ Bilgi</div>'+
                '<div style="font-size:11px;opacity:.85;">'+curStep.text+'</div>'+
                '</div>';
        } else if (curStep) {
            actionInner='<div style="font-size:14px;font-weight:bold;text-align:center;padding:4px 0;">'+stepLabel(curStep)+'</div>';
        } else {
            actionInner='<div style="text-align:center;opacity:.35;font-size:11px;padding:6px 0;">Quest yok veya tamamlandı</div>';
            actionClickable=false;
        }

        const upcomingHtml=nextSteps.slice(1,6).map((s,i)=>
            '<div style="font-size:10px;opacity:.6;padding:2px 0;">'+(i+2)+'. '+stepLabel(s)+'</div>'
        ).join('');

        const recentLog=QR.getLog(char).slice(-20).reverse();
        const LC={error:'#f44336',use_item:'#FFD600',checkpoint:'#CE93D8',passage:'#90CAF9',wait:'#90CAF9',move:'rgba(255,255,255,.55)',manual_move:'#80CBC4'};
        const recentHtml=recentLog.length
            ?recentLog.map(e=>'<div style="font-size:10px;padding:1px 0;color:'+(LC[e.type]||'rgba(255,255,255,.7)')+';">'+e.msg+'</div>').join('')
            :'<div style="font-size:10px;opacity:.3;">—</div>';

        const allQ=QStore.load(), qid=C.activeQuestId(char);
        const questOpts='<option value="">— quest seç —</option>'+
            '<option value="__map__"'+(isMapping?' selected':'')+'>🗺 Yeni Harita</option>'+
            Object.values(allQ).map(q=>'<option value="'+q.id+'"'+(qid===q.id?' selected':'')+'>'+q.name+'</option>').join('');
        let cpOpts='<option value="">— checkpoint —</option>';
        if(quest){let ci=0;quest.sequence.forEach((s,i)=>{if(s.type==='checkpoint'){cpOpts+='<option value="'+i+'">'+(s.label||'#'+ci)+' ('+i+')</option>';ci++;}});}

        const timerHtml=inPass?'<div style="'+sec('text-align:center;margin-bottom:6px;')+'"><span id="qrTimer">⏳ Yükleniyor...</span></div>':'';

        const actionStyle=actionClickable
            ?btn('#1a3a6e','width:100%;text-align:left;padding:8px 10px;margin-bottom:6px;border:1.5px solid rgba(255,255,255,.2);')
            :'width:100%;padding:8px 10px;margin-bottom:6px;background:rgba(255,255,255,.05);border-radius:6px;border:1px solid rgba(255,255,255,.1);';

        const panel=mkEl('div');
        panel.id='qrCompass';
        panel.style.cssText=BG+';'+BDR+';'+FNT+';position:fixed;left:'+pos.x+'px;top:'+pos.y+'px;width:'+(isCompact?'200':'300')+'px;z-index:9998;box-shadow:0 4px 20px rgba(0,0,0,.6);';
        panel.innerHTML=
            '<div id="qrDrag" style="padding:8px 12px;cursor:move;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid rgba(255,255,255,.12);">'+
              '<span style="font-size:13px;font-weight:bold;">📜 Guide '+GM_info.script.version+'</span>'+
              '<div style="display:flex;gap:4px;align-items:center;">'+
                (elapsed?'<span style="font-size:10px;color:#FFD600;">⏱'+elapsed+'</span>':'')+
                (!isCompact?'<span style="font-size:10px;opacity:.5;">'+char+'</span>':'')+
                (!isCompact&&infoSteps.length?'<button id="qrInfoBtn" style="'+btn('#1565C0','width:22px;height:22px;padding:0;font-size:13px;border-radius:50%;')+'" title="Bilgi">ℹ️</button>':'')+
                (!isCompact&&quest?'<button id="qrQEdit" style="'+btn('#37474F','width:22px;height:22px;padding:0;font-size:12px;border-radius:50%;')+'" title="Quest Düzenle">✏️</button>':'')+
                '<button id="qrCmpToggle" style="'+btn('rgba(255,255,255,.15)','width:22px;height:22px;padding:0;font-size:12px;')+'" title="'+(isCompact?'Normal Mod':'Kompakt Mod')+'">'+(isCompact?'⛶':'⊡')+'</button>'+
                '<button id="qrClose" style="'+btn('rgba(255,255,255,.15)','width:20px;height:20px;padding:0;font-size:12px;')+'">×</button>'+
              '</div>'+
            '</div>'+
            (isCompact
              ? '<div style="padding:7px 8px;">'+
                  '<div style="'+row('margin-bottom:5px;')+'">'+
                    '<select id="qrQSel" style="'+inp('flex:1;font-size:10px;padding:4px;')+'">'+questOpts+'</select>'+
                    '<span style="font-size:10px;color:#8BC34A;white-space:nowrap;margin-left:4px;">'+stepIdx+'/'+total+'</span>'+
                  '</div>'+
                  timerHtml+
                  (actionClickable
                    ?'<button id="qrNext" style="'+btn('#1a3a6e','width:100%;text-align:center;padding:7px 6px;margin-bottom:5px;border:1.5px solid rgba(255,255,255,.2);font-size:13px;')+'">'+actionInner+'</button>'
                    :'<div style="width:100%;padding:7px 6px;margin-bottom:5px;background:rgba(255,255,255,.05);border-radius:6px;border:1px solid rgba(255,255,255,.1);font-size:11px;text-align:center;">'+actionInner+'</div>')+
                  '<div style="'+row('margin-bottom:0;')+'">'+
                    '<button id="qrPrev" style="'+btn('#37474F','flex:1;font-size:11px;padding:5px;')+'">← Geri</button>'+
                    '<button id="qrFwd"  style="'+btn('#37474F','flex:1;font-size:11px;padding:5px;')+'">İleri →</button>'+
                  '</div>'+
                '</div>'
              : '<div style="padding:10px 12px;">'+
                  '<select id="qrQSel" style="'+inp('margin-bottom:6px;')+'">'+questOpts+'</select>'+
                  '<div style="position:relative;background:rgba(255,255,255,.15);border-radius:6px;height:14px;margin-bottom:8px;">'+
                    '<div style="background:linear-gradient(90deg,#4CAF50,#8BC34A);height:100%;width:'+pct+'%;border-radius:6px;transition:width .3s;"></div>'+
                    '<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:bold;text-shadow:0 0 4px rgba(0,0,0,.9);">'+stepIdx+'/'+total+'</div>'+
                  '</div>'+
                  timerHtml+
                  (isMapping?
                    '<div style="'+sec('margin-bottom:6px;border:1.5px solid #80CBC4;')+'">'+
                      '<div style="font-size:9px;color:#80CBC4;margin-bottom:3px;">🗺 HARITA KAYDI — '+mapLog.length+' adım</div>'+
                      '<div style="display:flex;gap:4px;">'+
                      '<button id="qrMapSave" style="'+btn('#00897B','flex:1;font-size:11px;padding:5px;')+'">💾 Quest Olarak Kaydet</button>'+
                      '<button id="qrMapStop" style="'+btn('#607D8B','font-size:11px;padding:5px;')+'">✖ İptal</button>'+
                      '</div></div>'
                    :'')+
                  (actionClickable
                    ?'<button id="qrNext" style="'+actionStyle+'">'+actionInner+'</button>'
                    :'<div style="'+actionStyle+'">'+actionInner+'</div>')+
                  (upcomingHtml?'<div style="'+sec('padding:5px 8px;margin-bottom:6px;')+'">'+upcomingHtml+'</div>':'')+
                  '<div style="'+row('margin-bottom:6px;')+'">'+
                    '<button id="qrPrev" style="'+btn('#37474F','flex:1;font-size:11px;padding:6px;')+'">← Geri</button>'+
                    '<button id="qrFwd"  style="'+btn('#37474F','flex:1;font-size:11px;padding:6px;')+'">İleri →</button>'+
                    '<button id="qrStop" style="'+btn(stopmode==='ask'?'#FF9800':'#607D8B','flex:1;')+'">'+(stopmode==='ask'?'Dur':'Geç')+'</button>'+
                  '</div>'+
                  '<div style="'+row('margin-bottom:6px;')+'">'+
                    '<select id="qrCpSel" style="'+inp('flex:1;')+'">'+cpOpts+'</select>'+
                    '<button id="qrCpGo" style="'+btn('#9C27B0','white-space:nowrap;')+'">↩ Git</button>'+
                  '</div>'+
                  '<div style="'+sec('margin-bottom:6px;')+'">'+
                    '<div style="font-size:9px;opacity:.4;margin-bottom:3px;">SON 20 OLAY</div>'+
                    '<div id="qrRecentLog" style="max-height:120px;overflow-y:auto;">'+recentHtml+'</div>'+
                  '</div>'+
                  '<div style="'+row('margin-bottom:0;')+'">'+
                    (muted?'<button id="qrUnmute" style="'+btn('#607D8B','flex:1;font-size:10px;padding:5px;')+'">🔇 Sessiz</button>':'')+
                    '<button id="qrAdm" style="'+btn('#1565C0','flex:1;font-size:10px;padding:5px;')+'">Admin</button>'+
                    '<button id="qrRes" style="'+btn('#e65100','flex:1;font-size:10px;padding:5px;')+'">Reset</button>'+
                  '</div>'+
                '</div>'
            );

        document.body.appendChild(panel);
        panel.querySelector('#qrCmpToggle').onclick=()=>{setCompact(!isCompact);buildCompass();};
        panel.querySelector('#qrQEdit')?.addEventListener('click',()=>openQuestEdit(C.activeQuestId(char)));
        // item pick aktifse panel içine listele
        if(_itemPick&&_itemPick.char===char) {
            const body=panel.querySelector('div[style*="padding:10px"]');
            if(body) renderItemPick(body,_itemPick.char,_itemPick.step,_itemPick.idx,_itemPick.names);
        }
        makeDraggable(panel,panel.querySelector('#qrDrag'));
        panel.querySelector('#qrClose').onclick  = ()=>{ _dismissed=true; panel.remove(); };
        panel.querySelector('#qrInfoBtn')?.addEventListener('click',()=>showInfoModal(infoSteps));
        panel.querySelector('#qrQSel').onchange  = e=>{
            if(e.target.value==='__map__'){mapStart(char);return;}
            if(e.target.value){C.setQuestId(char,e.target.value);C.setStep(char,0);}
            buildCompass();
        };
        const mapSaveBtn=panel.querySelector('#qrMapSave');
        const mapStopBtn=panel.querySelector('#qrMapStop');
        if(mapSaveBtn) mapSaveBtn.onclick=()=>mapSaveAsQuest(char);
        if(mapStopBtn) mapStopBtn.onclick=()=>mapStop(char);
        panel.querySelector('#qrStop')?.addEventListener('click',()=>{ C.setStopMode(char,stopmode==='ask'?'continue':'ask'); buildCompass(); });
        panel.querySelector('#qrPrev').onclick   = ()=>{ C.setStep(char,Math.max(0,stepIdx-1)); QR.log(char,'manual','← Geri ('+(Math.max(0,stepIdx-1))+')'); buildCompass(); };
        panel.querySelector('#qrFwd').onclick    = ()=>{ C.setStep(char,stepIdx+1); QR.log(char,'manual','→ İleri ('+(stepIdx+1)+')'); buildCompass(); };
        panel.querySelector('#qrAdm')?.addEventListener('click',()=>openAdmin('chars'));
        panel.querySelector('#qrRes')?.addEventListener('click',()=>{ if(confirm('Reset: '+char+'?')){C.reset(char);buildCompass();} });
        panel.querySelector('#qrCpGo')?.addEventListener('click',()=>{
            const sel=panel.querySelector('#qrCpSel'); if(!sel||!sel.value) return;
            C.setStep(char,parseInt(sel.value)+1);
            QR.log(char,'resume','↩ '+sel.options[sel.selectedIndex].text);
            notify('↩ '+sel.options[sel.selectedIndex].text,'#9C27B0'); buildCompass();
        });
        const nb=panel.querySelector('#qrNext'); if(nb) nb.onclick=()=>{ QR.engine.runStep(char); buildCompass(); };
        const um=panel.querySelector('#qrUnmute'); if(um) um.onclick=()=>{ Alert._muteUntil=0; buildCompass(); };
        if(onCompass) hookManualMoves();
        if(inPass) startTimer(char);
    }

    // ENGINE EVENTS
    QR.on('complete',      ()   => { notify('✅ Quest tamamlandı!','#4CAF50'); buildCompass(); });
    QR.on('error',         ()   => buildCompass());
    QR.on('checkpoint',    ()   => buildCompass());
    QR.on('passage_start', ()   => buildCompass());
    QR.on('passage_end',   (char,opts) => { if(!opts?.early) showPassageDialog(char); else buildCompass(); });
    QR.on('used_item',     ()   => buildCompass());
    QR.on('step',          ()   => buildCompass());
    QR.on('log',           ()   => {
        const el=document.getElementById('qrCompass'); if(!el) return;
        const char=QR.char.current(); if(!char) return;
        const log=QR.getLog(char).slice(-20).reverse();
        const LC={error:'#f44336',use_item:'#FFD600',checkpoint:'#CE93D8',passage:'#90CAF9',wait:'#90CAF9'};
        const recEl=el.querySelector('#qrRecentLog');
        if(recEl) recEl.innerHTML=log.map(e=>'<div style="font-size:10px;padding:1px 0;color:'+(LC[e.type]||'rgba(255,255,255,.7)')+';">'+e.msg+'</div>').join('');
    });

    // PASSAGE DIALOG
    function showPassageDialog(char) {
        document.getElementById('qrPassDlg')?.remove();
        const ov=mkEl('div','position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:100001;display:flex;align-items:center;justify-content:center;');
        ov.id='qrPassDlg';
        const box=mkEl('div',BG+';'+BDR+';'+FNT+';padding:20px;width:280px;text-align:center;',
            '<div style="font-size:15px;font-weight:bold;margin-bottom:8px;">⛵ Seyahat Tamamlandı</div>'+
            '<div style="margin-bottom:16px;opacity:.8;">'+char+' — sonraki adıma geçebilirsin.</div>'+
            '<div style="display:flex;gap:8px;justify-content:center;">'+
            '<button id="qrPOk" style="'+btn('#4CAF50','padding:8px 24px;font-size:13px;')+'">Tamam</button>'+
            '<button id="qrPM"  style="'+btn('#607D8B','padding:8px 14px;font-size:13px;')+'">🔇</button></div>');
        ov.appendChild(box); document.body.appendChild(ov);
        ov.onclick=e=>{if(e.target===ov){ov.remove();buildCompass();}};
        box.querySelector('#qrPOk').onclick=()=>{ov.remove();buildCompass();};
        box.querySelector('#qrPM').onclick =()=>{Alert.mute(char);notify('Alarm susturuldu','#607D8B');ov.remove();buildCompass();};
    }

    // ITEM PICK — panel içi tıklanabilir liste
    let _itemPick=null; // {char, step, idx, names}
    function showItemPick(char,step,idx,names) {
        _itemPick={char,step,idx,names};
        notify('Item seç — panelden tıkla','#FF9800');
        buildCompass();
    }
    function renderItemPick(panel,char,step,idx,names) {
        const wrap=mkEl('div',sec('margin-bottom:6px;border:1.5px solid #FF9800;'));
        wrap.innerHTML='<div style="font-size:9px;opacity:.6;margin-bottom:4px;">KULLANILACAK İTEM — Seç</div>'+
            names.map((n,i)=>'<button data-i="'+i+'" class="qrIP" style="'+btn('#1565C0','display:block;width:100%;text-align:left;margin-bottom:3px;padding:5px 8px;font-size:11px;')+'">'+n+'</button>').join('')+
            '<button id="qrIPSkip" style="'+btn('#607D8B','width:100%;font-size:10px;padding:4px;margin-top:2px;')+'">Atla</button>';
        panel.appendChild(wrap);
        wrap.querySelectorAll('.qrIP').forEach(b=>b.onclick=()=>{
            const name=names[parseInt(b.dataset.i)];
            const items=QR.dom.getItems(), found=items.find(i=>norm(i.name)===norm(name));
            _itemPick=null;
            if(!found){notify('Item artık sayfada yok','#f44336');buildCompass();return;}
            C.setStep(char,idx+1); found.useBtn.click();
            const msg='✓ (seçim) '+found.name+' @ '+(QR.dom.locName()||'?');
            addLog(char,'use_item',msg);
            addLog(char,'use_item_pick','🎯 '+found.name+' — adım '+(idx+1));
            QR.emit('used_item',char,found.name,idx);
            buildCompass();
        });
        wrap.querySelector('#qrIPSkip').onclick=()=>{
            _itemPick=null; C.setStep(char,idx+1);
            addLog(char,'skip','Atlandı — adım '+(idx+1)); buildCompass();
        };
    }
    QR.on('use_item_ambiguous',(char,step,idx,names)=>showItemPick(char,step,idx,names));

    // REQUIRE POPUP
    function showRequirePopup(step, onConfirm) {
        document.getElementById('qrReqDlg')?.remove();
        const ov=mkEl('div','position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:100003;display:flex;align-items:center;justify-content:center;');
        ov.id='qrReqDlg';
        const box=mkEl('div',BG+';border:2px solid #FF9800;border-radius:10px;'+FNT+';padding:22px 20px;width:300px;text-align:center;');
        box.innerHTML=
            '<div style="font-size:28px;margin-bottom:6px;">⚠️</div>'+
            '<div style="font-size:13px;font-weight:bold;color:#FF9800;margin-bottom:10px;">Başlamadan önce hazırla!</div>'+
            '<div style="font-size:16px;font-weight:bold;padding:10px;background:rgba(255,152,0,.18);border-radius:8px;margin-bottom:'+(step.description?'8px':'12px')+';">'+step.item+'</div>'+
            (step.description?'<div style="font-size:11px;opacity:.75;margin-bottom:12px;">'+step.description+'</div>':'')+
            '<div style="display:flex;gap:8px;justify-content:center;">'+
            '<button id="qrReqOk" style="'+btn('#4CAF50','padding:9px 22px;font-size:13px;font-weight:bold;')+'">✓ Elimde var</button>'+
            '<button id="qrReqNo" style="'+btn('#607D8B','padding:9px 14px;font-size:12px;')+'">Geri</button></div>';
        ov.appendChild(box); document.body.appendChild(ov);
        box.querySelector('#qrReqOk').onclick=()=>{ ov.remove(); onConfirm(); };
        box.querySelector('#qrReqNo').onclick=()=>ov.remove();
    }

    // INFO MODAL
    function showInfoModal(infoSteps) {
        if(!infoSteps||!infoSteps.length) return;
        document.getElementById('qrInfoDlg')?.remove();
        const ov=mkEl('div','position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:100003;display:flex;align-items:center;justify-content:center;');
        ov.id='qrInfoDlg';
        const box=mkEl('div',BG+';border:2px solid #1565C0;border-radius:10px;'+FNT+';padding:20px;width:320px;max-height:80vh;overflow-y:auto;');
        const content=infoSteps.map(s=>{
            const threadId=s.link?(s.link.match(/Thread\/(\S+)$/)?.[1]||s.link):'';
            const linkHtml=threadId?'<div style="margin-top:6px;"><a data-href="'+s.link+'" href="#" style="color:#42A5F5;font-size:11px;text-decoration:none;">📋 Forum: '+threadId+'</a></div>':'';
            return '<div style="background:rgba(255,255,255,.07);border-radius:6px;padding:10px;margin-bottom:8px;text-align:left;">'+
                '<div style="font-size:12px;line-height:1.5;">'+s.text+'</div>'+linkHtml+'</div>';
        }).join('');
        box.innerHTML=
            '<div style="font-size:14px;font-weight:bold;color:#42A5F5;margin-bottom:12px;text-align:center;">ℹ️ Bilgi</div>'+
            content+
            '<button id="qrInfoClose" style="'+btn('#1565C0','width:100%;padding:8px;margin-top:4px;font-size:12px;')+'">Tamam</button>';
        ov.appendChild(box); document.body.appendChild(ov);
        ov.onclick=e=>{if(e.target===ov)ov.remove();};
        box.querySelector('#qrInfoClose').onclick=()=>ov.remove();
        box.querySelectorAll('[data-href]').forEach(a=>{
            a.onclick=e=>{e.preventDefault();window.location.href=location.origin+a.dataset.href;};
        });
    }

    // ADMIN PANEL
    let _tab='chars';
    function openAdmin(tab) {
        const ex=document.getElementById('qrAdmin'); if(ex){ex.remove();return;}
        if(tab) _tab=tab;
        const ov=mkEl('div','position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:99999;display:flex;align-items:center;justify-content:center;');
        ov.id='qrAdmin';
        const box=mkEl('div',BG+';'+BDR+';'+FNT+';width:92%;max-width:640px;max-height:88vh;overflow-y:auto;padding:14px;');
        ov.appendChild(box); document.body.appendChild(ov);
        ov.onclick=e=>{if(e.target===ov)ov.remove();};
        const TABS={chars:t('tabChars'),quests:t('tabQuests'),log:t('tabLog'),settings:t('tabSettings')};
        function render(){
            box.innerHTML=
                '<div style="font-size:14px;font-weight:bold;text-align:center;margin-bottom:8px;">📜 Guide</div>'+
                '<div style="display:flex;gap:2px;margin-bottom:10px;">'+
                Object.keys(TABS).map(k=>'<button data-t="'+k+'" style="flex:1;padding:7px;font-size:11px;cursor:pointer;border:none;border-radius:6px 6px 0 0;'+
                    'background:rgba(255,255,255,'+(_tab===k?'.18':'.06')+');color:white;font-weight:'+(_tab===k?'bold':'normal')+';">'+TABS[k]+'</button>').join('')+
                '</div><div id="tabBody"></div>'+
                '<button id="qrAClose" style="background:rgba(255,255,255,.15);color:white;border:none;border-radius:6px;padding:6px;width:100%;font-size:11px;cursor:pointer;margin-top:8px;">'+t('close')+'</button>';
            box.querySelectorAll('[data-t]').forEach(b=>b.onclick=()=>{_tab=b.dataset.t;render();});
            box.querySelector('#qrAClose').onclick=()=>ov.remove();
            const body=box.querySelector('#tabBody');
            if(_tab==='chars')    renderChars(body);
            if(_tab==='quests')   renderQuests(body);
            if(_tab==='log')      renderLog(body);
            if(_tab==='settings') renderSettings(body);
        }
        render();
    }

    function renderSettings(body) {
        const lw=mkEl('div',sec('display:flex;gap:4px;flex-wrap:wrap;margin-bottom:8px;'));
        lw.innerHTML=
            '<button id="qrLR" style="'+btn('#1565C0','flex:1;font-size:10px;padding:5px;')+'">'+t('readMe')+'</button>'+
            '<button id="qrLK" style="'+btn('#607D8B','flex:1;font-size:10px;padding:5px;')+'">'+t('kobe')+'</button>'+
            '<button id="qrLP" style="'+btn('#607D8B','flex:1;font-size:10px;padding:5px;')+'">'+t('otherQuests')+'</button>'+
            '<button id="qrDB" style="'+btn('#2E7D32','flex:1;font-size:10px;padding:5px;')+'">'+t('questDb')+'</button>';
        body.appendChild(lw);
        lw.querySelector('#qrLR').onclick=()=>window.open('https://rentry.org/questrunner','_blank');
        lw.querySelector('#qrLK').onclick=()=>window.open('https://rentry.org/kobequest','_blank');
        lw.querySelector('#qrLP').onclick=()=>window.open('https://rentry.org/popquest','_blank');
        lw.querySelector('#qrDB').onclick=()=>window.open('https://rentry.org/questrunnerdb','_blank');


        // LANGUAGE SELECTOR
        const langSec=mkEl('div',sec('font-size:10px;font-weight:bold;color:#888;text-transform:uppercase;letter-spacing:.5px;margin:10px 0 4px;'));
        langSec.textContent=t('langLabel');
        body.appendChild(langSec);
        const langRow=mkEl('div',sec('display:flex;gap:4px;margin-bottom:6px;'));
        [['TR','🇹🇷 Türkçe'],['EN','🇬🇧 English'],['PT','🇧🇷 Português']].forEach(([code,label])=>{
            const b=mkEl('button',btn(LANG===code?'#6f42c1':'#607D8B','flex:1;font-size:10px;padding:5px;border-radius:4px;cursor:pointer;border:none;font-weight:'+(LANG===code?'bold':'normal')+';'));
            b.textContent=label;
            b.onclick=()=>{_setLang(code);location.reload();};
            langRow.appendChild(b);
        });
        body.appendChild(langRow);
    }

    // CHARS TAB
    function renderChars(body){
        const chars=QR.char.all(), allQ=QStore.load();
        const opts='<option value="">— none —</option>'+Object.values(allQ).map(q=>'<option value="'+q.id+'">'+q.name+'</option>').join('');
        const rows=chars.map(c=>{
            const asgn=C.getAssigned(c)||C.getQuestId(c)||'', step=C.getStep(c), total=allQ[asgn]?.sequence.length||0;
            const cps=C.getCheckpoints(c), lastCp=cps.length?(cps[cps.length-1].label||'#'+(cps.length-1)):'—';
            const note=C.getNote(c)||'';
            // passage kalan süre
            const pts=C.getPassageTs(c), pmin=C.getPassageMin(c);
            let passLeft='';
            if(pts&&pmin){
                const elapsed=Math.floor((Date.now()-pts)/60000);
                const left=Math.max(0,Math.round(pmin-elapsed));
                passLeft='<br><span style="color:#90CAF9;font-size:9px;">⏳ ~'+left+'dk kaldı</span>';
            }
            return '<tr style="border-bottom:1px solid rgba(255,255,255,.08);">'+
                '<td style="padding:5px 4px;font-size:11px;white-space:nowrap;">'+c+'</td>'+
                '<td style="padding:5px 4px;"><select data-c="'+c+'" class="qrCQ" style="'+inp()+'">'+opts.replace('value="'+asgn+'"','value="'+asgn+'" selected')+'</select>'+
                '<input data-c="'+c+'" class="qrCN" placeholder="Not..." style="'+inp('margin-top:3px;font-size:10px;')+'" value="'+note.replace(/"/g,'&quot;')+'"></td>'+
                '<td style="padding:5px 4px;font-size:10px;text-align:center;">'+step+'/'+total+'<br>'+
                '<span style="color:#CE93D8;font-size:9px;">📍'+lastCp+'</span><br>'+
                '<span style="color:#FFD600;font-size:9px;">⏱'+(elapsedStr(c)||'—')+'</span>'+passLeft+'</td>'+
                '<td style="padding:5px 4px;"><button data-c="'+c+'" class="qrCR" style="'+btn('#FF9800','font-size:10px;padding:3px 7px;')+'">Reset</button></td></tr>';
        }).join('');
        body.innerHTML='<table style="width:100%;border-collapse:collapse;"><thead><tr style="font-size:9px;opacity:.5;border-bottom:1px solid rgba(255,255,255,.15);">'+
            '<th style="padding:4px;text-align:left;">Karakter</th><th style="padding:4px;text-align:left;">Quest / Not</th>'+
            '<th style="padding:4px;text-align:center;">Durum</th><th></th></tr></thead>'+
            '<tbody>'+(rows||'<tr><td colspan="4" style="padding:10px;opacity:.5;text-align:center;">Karakter yok</td></tr>')+'</tbody></table>'+
            '<div style="'+sec('margin-top:8px;')+'"><div style="font-size:10px;opacity:.6;margin-bottom:4px;">Toplu ata</div>'+
            '<div style="display:flex;gap:6px;"><select id="qrBulkQ" style="'+inp()+'flex:1;">'+opts+'</select>'+
            '<button id="qrBulk" style="'+btn('#4CAF50','padding:6px 10px;font-size:11px;')+'">Ata</button></div></div>';
        body.querySelectorAll('.qrCQ').forEach(s=>s.onchange=e=>{C.setAssigned(e.target.dataset.c,e.target.value);C.setStep(e.target.dataset.c,0);});
        body.querySelectorAll('.qrCN').forEach(i=>i.onblur=e=>{C.setNote(e.target.dataset.c,e.target.value.trim());});
        body.querySelectorAll('.qrCR').forEach(b=>b.onclick=e=>{const c=e.target.dataset.c;if(confirm('Reset: '+c+'?')){C.reset(c);renderChars(body);}});
        body.querySelector('#qrBulk').onclick=()=>{
            const qid=body.querySelector('#qrBulkQ').value; if(!qid) return;
            QR.char.all().forEach(c=>{C.setAssigned(c,qid);C.setStep(c,0);});
            notify('"'+(QStore.get(qid)?.name||qid)+'" → '+QR.char.all().length+' karakter'); renderChars(body);
        };
    }

    // QUESTS TAB
    let _editId=null;
    function renderQuests(body){
        const allQ=QStore.load(), editing=_editId?QStore.get(_editId):null;
        const list=Object.values(allQ).map(q=>
            '<div style="'+sec('display:flex;justify-content:space-between;align-items:center;')+'">'+
            '<div><div style="font-weight:bold;font-size:12px;">'+q.name+'</div>'+
            '<div style="font-size:10px;opacity:.6;">'+q.sequence.length+' adım'+(q.builtin?' · builtin':'')+'</div></div>'+
            '<div style="display:flex;gap:4px;">'+
            '<button data-q="'+q.id+'" class="qrEd" style="'+btn('#2196F3','font-size:10px;padding:4px 8px;')+'">Düzenle</button>'+
            '<button data-q="'+q.id+'" class="qrCp" style="'+btn('#9C27B0','font-size:10px;padding:4px 8px;')+'">Kopyala</button>'+
            '<button data-q="'+q.id+'" class="qrDl" style="'+btn('#f44336','font-size:10px;padding:4px 8px;')+'">Sil</button>'+
            '</div></div>'
        ).join('')||'<div style="opacity:.5;font-size:11px;margin-bottom:8px;">Quest yok.</div>';
        let parsed=null;
        body.innerHTML=list+
            '<div style="border-top:1px solid rgba(255,255,255,.15);padding-top:10px;margin-top:4px;">'+
            '<div style="font-size:11px;font-weight:bold;margin-bottom:6px;">'+(_editId?'Düzenle: '+editing?.name:'Yeni Quest')+'</div>'+
            '<input id="qrQN" placeholder="Quest adı" style="'+inp('margin-bottom:6px;')+'"'+(editing?' value="'+editing.name+'"':'')+'>'+
            '<textarea id="qrQT" rows="8" placeholder="North South East&#10;Use Icy Caverns&#10;16 minutes&#10;Arrival Label&#10;&#10;# veya:&#10;move North&#10;use_item ? @ Icy Caverns&#10;passage 16min&#10;checkpoint LABEL&#10;branch West -> CHECKPOINT&#10;try_item ? @ Location" style="'+inp('resize:vertical;font-family:monospace;font-size:11px;margin-bottom:6px;')+'">'+((editing?.text)||'')+'</textarea>'+
            '<div id="qrPOut" style="display:none;background:rgba(0,0,0,.25);border-radius:6px;padding:6px;font-size:10px;max-height:120px;overflow-y:auto;font-family:monospace;margin-bottom:6px;"></div>'+
            '<div style="display:flex;gap:6px;">'+
            '<button id="qrParse"  style="'+btn('#607D8B','flex:1;font-size:11px;padding:6px;')+'">Parse</button>'+
            '<button id="qrSaveQ"  style="'+btn('#4CAF50','flex:1;font-size:11px;padding:6px;')+'">Kaydet</button>'+
            '<button id="qrImpBtn" style="'+btn('#2196F3','flex:1;font-size:11px;padding:6px;')+'">Import</button>'+
            '<button id="qrExpBtn" style="'+btn('#607D8B','flex:1;font-size:11px;padding:6px;')+'">Export</button>'+
            '<button id="qrCancel" style="'+btn('#888','font-size:11px;padding:6px;')+'">İptal</button></div></div>';
        body.querySelectorAll('.qrEd').forEach(b=>b.onclick=e=>{_editId=e.target.dataset.q;renderQuests(body);});
        body.querySelectorAll('.qrCp').forEach(b=>b.onclick=e=>{const n=prompt('Kopya adı:');if(!n)return;const c=QStore.copy(e.target.dataset.q,n);if(c){QStore.upsert(c);renderQuests(body);notify('Kopyalandı: "'+n+'"');}});
        body.querySelectorAll('.qrDl').forEach(b=>b.onclick=e=>{const q=allQ[e.target.dataset.q];if(confirm('Sil: "'+q?.name+'"?')){QStore.delete(e.target.dataset.q);_editId=null;renderQuests(body);}});
        body.querySelector('#qrParse').onclick=()=>{
            const {steps,errors}=QR.parseText(body.querySelector('#qrQT').value); parsed=steps;
            const out=body.querySelector('#qrPOut'); out.style.display='block';
            const LC2={use_item:'#FFD600',try_item:'#FFD600',passage:'#90CAF9',wait:'#90CAF9',checkpoint:'#CE93D8',branch:'#A5D6A7'};
            out.innerHTML=steps.map((s,i)=>'<div style="color:'+(LC2[s.type]||'white')+';padding:1px 0;">'+(i+1)+'. '+stepLabel(s)+'</div>').join('')+
                (errors.length?errors.map(e=>'<div style="color:#f44336;">⚠ '+e+'</div>').join(''):'')+
                '<div style="border-top:1px solid rgba(255,255,255,.15);margin-top:4px;padding-top:4px;opacity:.7;">'+steps.length+' adım</div>';
        };
        body.querySelector('#qrSaveQ').onclick=()=>{
            const name=body.querySelector('#qrQN').value.trim(), text=body.querySelector('#qrQT').value.trim();
            if(!name||!text){notify('Ad ve adımlar gerekli','#f44336');return;}
            if(!parsed){const r=QR.parseText(text);parsed=r.steps;}
            if(!parsed.length){notify('Geçerli adım yok','#f44336');return;}
            if(_editId){const ex=QStore.get(_editId);QStore.upsert({...ex,name,text,sequence:parsed});notify('"'+name+'" güncellendi');}
            else{QStore.upsert({...QStore.new(name,text),sequence:parsed});notify('"'+name+'" kaydedildi — '+parsed.length+' adım');}
            parsed=null;_editId=null;renderQuests(body);
        };
        body.querySelector('#qrCancel').onclick=()=>{_editId=null;renderQuests(body);};
        body.querySelector('#qrImpBtn').onclick=openImport;
        body.querySelector('#qrExpBtn').onclick=exportAll;
    }

    // LOG TAB
    function renderLog(body){
        const chars=QR.char.all(); let selChar=QR.char.current()||chars[0]||'';
        function draw(){
            const log=QR.getLog(selChar).slice(-150).reverse();
            const LC={error:'#f44336',checkpoint:'#CE93D8',manual:'#90CAF9',manual_move:'#80CBC4',resume:'#A5D6A7',variant:'#FFCC02',complete:'#8BC34A',alert:'#FF9800',move:'rgba(255,255,255,.5)'};
            const t=e=>{const d=new Date(e.ts);return d.getHours().toString().padStart(2,'0')+':'+d.getMinutes().toString().padStart(2,'0')+':'+d.getSeconds().toString().padStart(2,'0');};
            const cOpts=chars.map(c=>'<option value="'+c+'"'+(c===selChar?' selected':'')+'>'+c+'</option>').join('');
            body.innerHTML=
                '<div style="'+row('margin-bottom:8px;')+'">'+
                '<select id="qrLC" style="'+inp('flex:1;')+'"><option value="">— karakter —</option>'+cOpts+'</select>'+
                '<button id="qrLCl" style="'+btn('#f44336','font-size:10px;padding:5px 8px;')+'">Sil</button>'+
                '<button id="qrLCopy" style="'+btn('#1565C0','font-size:10px;padding:5px 8px;')+'">📋</button></div>'+
                '<div style="max-height:320px;overflow-y:auto;"><table style="width:100%;border-collapse:collapse;">'+
                '<thead><tr style="font-size:9px;opacity:.4;border-bottom:1px solid rgba(255,255,255,.1);">'+
                '<th style="padding:2px 4px;text-align:left;">Saat</th><th style="padding:2px 4px;text-align:left;">Mesaj</th><th style="padding:2px 4px;text-align:left;">Yer</th></tr></thead>'+
                '<tbody>'+(log.map(e=>'<tr style="border-bottom:1px solid rgba(255,255,255,.05);color:'+(LC[e.type]||'white')+';">'+
                '<td style="padding:2px 6px;font-size:9px;opacity:.6;white-space:nowrap;">'+t(e)+'</td>'+
                '<td style="padding:2px 6px;font-size:10px;">'+e.msg+'</td>'+
                '<td style="padding:2px 6px;font-size:9px;opacity:.4;">'+(e.loc||'')+'</td></tr>').join('')||
                '<tr><td colspan="3" style="padding:8px;opacity:.4;text-align:center;">Boş</td></tr>')+'</tbody></table></div>';
            body.querySelector('#qrLC').onchange=e=>{selChar=e.target.value;draw();};
            body.querySelector('#qrLCl').onclick=()=>{if(confirm('Log sil?')){QR.clearLog(selChar);draw();}};
            body.querySelector('#qrLCopy').onclick=()=>{
                const rows=QR.getLog(selChar).slice(-150).reverse().map(e=>t(e)+'\t'+e.msg+'\t'+(e.loc||''));
                navigator.clipboard.writeText('Saat\tMesaj\tYer\n'+rows.join('\n')).then(()=>notify('Kopyalandı','#4CAF50')).catch(()=>notify('Hata','#f44336'));
            };
        }
        draw();
    }

    // IMPORT / EXPORT
    function exportAll(){
        const db=QStore.load();
        const plain=Object.values(db).map(q=>'# '+q.name+'\n'+(q.text||q.sequence.map(stepLabel).join('\n'))).join('\n\n---\n\n');
        document.getElementById('qrTM')?.remove();
        const ov=mkEl('div','position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:100000;display:flex;align-items:center;justify-content:center;');
        ov.id='qrTM';
        const box=mkEl('div',BG+';'+BDR+';'+FNT+';width:92%;max-width:580px;max-height:80vh;overflow-y:auto;padding:14px;');
        box.innerHTML='<div style="font-weight:bold;margin-bottom:8px;">Export</div>'+
            '<div style="display:flex;gap:8px;margin-bottom:8px;">'+
            '<div style="flex:1;"><div style="font-size:10px;opacity:.6;margin-bottom:3px;">Plain text</div><textarea rows="10" readonly style="'+inp('resize:vertical;font-family:monospace;font-size:10px;')+'">'+plain+'</textarea></div>'+
            '<div style="flex:1;"><div style="font-size:10px;opacity:.6;margin-bottom:3px;">JSON DB</div><textarea rows="10" readonly style="'+inp('resize:vertical;font-family:monospace;font-size:10px;')+'">'+JSON.stringify(db,null,2)+'</textarea></div></div>'+
            '<button id="qrEC" style="'+btn('#607D8B','width:100%;padding:7px;')+'">Kapat</button>';
        ov.appendChild(box); document.body.appendChild(ov);
        ov.onclick=e=>{if(e.target===ov)ov.remove();};
        box.querySelector('#qrEC').onclick=()=>ov.remove();
    }

    function openImport(){
        document.getElementById('qrIM')?.remove();
        const ov=mkEl('div','position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:100000;display:flex;align-items:center;justify-content:center;');
        ov.id='qrIM';
        const box=mkEl('div',BG+';'+BDR+';'+FNT+';width:90%;max-width:480px;padding:14px;');
        box.innerHTML='<div style="font-weight:bold;margin-bottom:8px;">Import</div>'+
            '<div style="display:flex;gap:6px;margin-bottom:8px;">'+
            ['Plain text','JSON DB'].map(l=>'<label style="flex:1;text-align:center;padding:6px;background:rgba(255,255,255,.1);border-radius:6px;cursor:pointer;font-size:11px;">'+
            '<input type="radio" name="qrIFmt" value="'+(l==='JSON DB'?'json':'plain')+'"'+(l==='Plain text'?' checked':'')+'>'+' '+l+'</label>').join('')+
            '</div><input id="qrIN" placeholder="Quest adı (plain text için)" style="'+inp('margin-bottom:6px;')+'">'+
            '<textarea id="qrIT" rows="8" style="'+inp('resize:vertical;font-family:monospace;font-size:11px;margin-bottom:8px;')+'"></textarea>'+
            '<div style="display:flex;gap:6px;">'+
            '<button id="qrDoI" style="'+btn('#4CAF50','flex:1;padding:7px;')+'">Import</button>'+
            '<button id="qrIC"  style="'+btn('#607D8B','flex:1;padding:7px;')+'">İptal</button></div>';
        ov.appendChild(box); document.body.appendChild(ov);
        ov.onclick=e=>{if(e.target===ov)ov.remove();};
        box.querySelector('#qrIC').onclick=()=>ov.remove();
        box.querySelector('#qrDoI').onclick=()=>{
            const text=box.querySelector('#qrIT').value.trim(); if(!text){notify('Bir şey yapıştır','#f44336');return;}
            const fmt=box.querySelector('[name="qrIFmt"]:checked').value;
            if(fmt==='json'){
                try{const db=JSON.parse(text);const ex=QStore.load();Object.assign(ex,db);QStore.save(ex);notify(Object.keys(db).length+' quest import edildi');ov.remove();}
                catch(e){notify('Geçersiz JSON','#f44336');}
            } else {
                const name=box.querySelector('#qrIN').value.trim()||'Quest '+Date.now();
                const q=QStore.new(name,text);QStore.upsert(q);
                notify('"'+name+'" import edildi — '+q.sequence.length+' adım');ov.remove();
            }
        };
    }

    // ── INIT ────────────────────────────────

    createFloat();
    if (QR.page.isCompass()) { buildCompass(); hookManualMoves(); }

    // PopControl hazır olana kadar bekler
    // EN strings for PopControl export
    window.__ppcStrGuide = {"tabChars": "Characters", "tabQuests": "Quests", "tabLog": "Log", "tabSettings": "Settings", "readMe": "📖 Read Me", "kobe": "🗺️ Kobe", "otherQuests": "🗺️ Other Quests", "questDb": "📋 Quest Database", "close": "Close", "langLabel": "Language"};

    function _waitPC(cb,n){n=n||0;if(unsafeWindow.PopControl){cb();return;}if(n<20)setTimeout(function(){_waitPC(cb,n+1);},300);}

    // Auto-connect to PopControl if available
    _waitPC(function() {
        unsafeWindow.PopControl.register({
            id:'guide', icon:'📜', label:'Guide',
            strings: window.__ppcStrGuide || {},
            buttons:[{icon:'📜', label:'Guide', onClick:function(){ _dismissed=false; var p=document.getElementById('qrCompass'); if(p)p.remove(); else if(QR.page.isLocale())buildCompass(); else openAdmin('quests'); }}],
            onUndo:function(){ S.set('qr_pc_connected',false); if(!document.getElementById('qrFloat')) createFloat(); }
        });
        document.getElementById('qrFloat')?.remove();
        S.set('qr_pc_connected',true);
    });

    setInterval(()=>{
        if(!S.get('qr_pc_connected',false) && !document.getElementById('qrFloat')) createFloat();
        if(QR.page.isCompass()) {
            hookManualMoves();
            if(!_dismissed && !document.getElementById('qrCompass')) buildCompass();
        }
    },5000);

    QR.ui = { buildCompass, openAdmin, notify, stepLabel };
    QR.ready = true;
    QR.emit('ready');
    console.log('[QR] v'+GM_info.script.version+' ready');
})();