📜 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');
})();